From df2f260509a0a5a77b9818c39ca35f656dbd38b2 Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Wed, 21 Feb 2018 10:44:20 +0100 Subject: [PATCH 001/442] Repo init. --- CMakeLists.txt | 4 +- README | 55 +- bindings/CMakeLists.txt | 4 - bindings/python/.gitattributes | 1 - bindings/python/.gitignore | 7 - bindings/python/CMakeLists.txt | 30 - bindings/python/MANIFEST.in | 6 - bindings/python/README.rst | 16 - bindings/python/docs/Makefile | 153 - bindings/python/docs/ReleaseNotes.txt | 48 - .../python/docs/source/.static/css/custom.css | 784 --- .../docs/source/.static/img/favicon.ico | Bin 4286 -> 0 bytes .../docs/source/.static/img/xrootd-200x68.png | Bin 14979 -> 0 bytes .../python/docs/source/.templates/layout.html | 4 - bindings/python/docs/source/conf.py | 243 - bindings/python/docs/source/examples.rst | 11 - .../docs/source/examples/copyprocess.rst | 14 - bindings/python/docs/source/examples/file.rst | 105 - .../docs/source/examples/filesystem.rst | 118 - .../python/docs/source/gettingstarted.rst | 88 - bindings/python/docs/source/index.rst | 38 - bindings/python/docs/source/install.rst | 6 - .../source/modules/client/copyprocess.rst | 17 - .../docs/source/modules/client/file.rst | 40 - .../docs/source/modules/client/filesystem.rst | 38 - .../docs/source/modules/client/flags.rst | 114 - .../docs/source/modules/client/responses.rst | 21 - .../python/docs/source/modules/client/url.rst | 16 - .../docs/source/modules/client/utils.rst | 11 - bindings/python/examples/copy.py | 12 - bindings/python/examples/copyprocess.py | 59 - bindings/python/examples/dirlist.py | 20 - bindings/python/examples/fcntl.py | 8 - bindings/python/examples/fcntl_async.py | 12 - bindings/python/examples/fileproperties.py | 40 - .../python/examples/filesystemproperties.py | 16 - bindings/python/examples/iterate.py | 19 - bindings/python/examples/locate.py | 15 - bindings/python/examples/mkdir.py | 9 - bindings/python/examples/mv.py | 8 - bindings/python/examples/query.py | 19 - bindings/python/examples/read.py | 27 - bindings/python/examples/readchunks.py | 17 - bindings/python/examples/readlines.py | 19 - bindings/python/examples/rm.py | 8 - bindings/python/examples/rmdir.py | 8 - bindings/python/examples/vector_read.py | 28 - bindings/python/examples/visa.py | 8 - bindings/python/examples/visa_async.py | 12 - bindings/python/examples/write.py | 18 - bindings/python/libs/__init__.py | 0 bindings/python/libs/client/__init__.py | 6 - bindings/python/libs/client/copyprocess.py | 140 - bindings/python/libs/client/file.py | 293 - bindings/python/libs/client/filesystem.py | 366 -- bindings/python/libs/client/flags.py | 121 - bindings/python/libs/client/responses.py | 239 - bindings/python/libs/client/url.py | 87 - bindings/python/libs/client/utils.py | 109 - bindings/python/setup.py.in | 64 - bindings/python/setup_pypi.py | 68 - bindings/python/src/AsyncResponseHandler.hh | 288 - bindings/python/src/ChunkIterator.hh | 152 - bindings/python/src/Conversions.hh | 396 -- bindings/python/src/PyXRootD.hh | 53 - bindings/python/src/PyXRootDCopyProcess.cc | 154 - bindings/python/src/PyXRootDCopyProcess.hh | 139 - .../python/src/PyXRootDCopyProgressHandler.cc | 113 - .../python/src/PyXRootDCopyProgressHandler.hh | 76 - bindings/python/src/PyXRootDFile.cc | 764 --- bindings/python/src/PyXRootDFile.hh | 250 - bindings/python/src/PyXRootDFileSystem.cc | 699 --- bindings/python/src/PyXRootDFileSystem.hh | 193 - bindings/python/src/PyXRootDModule.cc | 141 - bindings/python/src/PyXRootDURL.cc | 208 - bindings/python/src/PyXRootDURL.hh | 176 - bindings/python/src/Utils.cc | 201 - bindings/python/src/Utils.hh | 100 - bindings/python/src/__init__.py | 0 bindings/python/tests/env.py | 6 - bindings/python/tests/test_copy.py | 80 - bindings/python/tests/test_file.py | 452 -- bindings/python/tests/test_filesystem.py | 201 - bindings/python/tests/test_threads.py | 36 - bindings/python/tests/test_url.py | 53 - cmake/FindKerberos5.cmake | 40 - cmake/FindOpenSSL.cmake | 83 - cmake/FindReadline.cmake | 64 - cmake/FindSystemd.cmake | 30 - cmake/FindXRootD.cmake | 31 + cmake/Findfuse.cmake | 23 - cmake/XRootDCommon.cmake | 7 - cmake/XRootDDefaults.cmake | 8 - cmake/XRootDFindLibs.cmake | 84 +- cmake/XRootDOSDefs.cmake | 96 +- cmake/XRootDSummary.cmake | 20 +- cmake/XRootDSystemCheck.cmake | 130 - cmake/XRootDUtils.cmake | 34 - docs/README_IPV4_To_IPV6 | 80 - docs/man/XrdCnsd.8 | 34 - docs/man/cmsd.8 | 32 - docs/man/cns_ssi.8 | 30 - docs/man/frm_admin.8 | 33 - docs/man/frm_purged.8 | 34 - docs/man/frm_xfragent.8 | 31 - docs/man/frm_xfrd.8 | 33 - docs/man/mpxstats.8 | 34 - docs/man/xprep.1 | 96 - docs/man/xrd.1 | 52 - docs/man/xrdadler32.1 | 28 - docs/man/xrdcp-old.1 | 253 - docs/man/xrdcp.1 | 480 -- docs/man/xrdfs.1 | 227 - docs/man/xrdgsiproxy.1 | 66 - docs/man/xrdgsitest.1 | 173 - docs/man/xrdpfc_print.8 | 54 - docs/man/xrdpwdadmin.8 | 30 - docs/man/xrdsssadmin.8 | 30 - docs/man/xrdstagetool.1 | 31 - docs/man/xrootd.8 | 33 - docs/man/xrootdfs.1 | 123 - dopy.sh | 10 - packaging/common/client-plugin.conf.example | 16 - packaging/common/client.conf | 142 - packaging/common/cmsd@.service | 20 - packaging/common/frm_purged@.service | 19 - packaging/common/frm_xfrd@.service | 19 - packaging/common/xrdhttp@.socket | 9 - packaging/common/xrootd-clustered.cfg | 53 - .../common/xrootd-filecache-clustered.cfg | 90 - .../common/xrootd-filecache-standalone.cfg | 37 - packaging/common/xrootd-http.cfg | 36 - packaging/common/xrootd-standalone.cfg | 25 - packaging/common/xrootd.logrotate | 28 - packaging/common/xrootd.te | 22 - packaging/common/xrootd@.service | 20 - packaging/common/xrootd@.socket | 9 - packaging/makesrpm.sh | 35 +- packaging/rhel/cmsd.init | 43 - packaging/rhel/frm_purged.init | 43 - packaging/rhel/frm_xfrd.init | 43 - packaging/rhel/xrootd-ceph.spec.in | 147 + packaging/rhel/xrootd.functions | 305 - packaging/rhel/xrootd.functions-slc4 | 301 - packaging/rhel/xrootd.init | 47 - packaging/rhel/xrootd.spec.in | 944 --- packaging/rhel/xrootd.sysconfig | 66 - packaging/rhel/xrootd.tmpfiles | 1 - packaging/tgz/README | 37 - packaging/tgz/StartCMS | 199 - packaging/tgz/StartFRM | 175 - packaging/tgz/StartOLB | 228 - packaging/tgz/StartOLB.cf.example | 76 - packaging/tgz/StartXRD | 204 - packaging/tgz/StartXRD.cf.example | 134 - packaging/tgz/StopCMS | 131 - packaging/tgz/StopFRM | 156 - packaging/tgz/StopOLB | 123 - packaging/tgz/StopXRD | 118 - packaging/tgz/xrootd.cf.example | 100 - packaging/tgz/xrootd.cf.example2 | 134 - src/CMakeLists.txt | 89 +- src/XProtocol/README | 19 - src/XProtocol/XProtocol.cc | 124 - src/XProtocol/XProtocol.hh | 939 --- src/XProtocol/XPtypes.hh | 74 - src/XProtocol/YProtocol.hh | 622 -- src/Xrd/XrdBuffXL.cc | 252 - src/Xrd/XrdBuffXL.hh | 80 - src/Xrd/XrdBuffer.cc | 347 -- src/Xrd/XrdBuffer.hh | 124 - src/Xrd/XrdConfig.cc | 1920 ------ src/Xrd/XrdConfig.hh | 125 - src/Xrd/XrdInet.cc | 253 - src/Xrd/XrdInet.hh | 79 - src/Xrd/XrdInfo.cc | 41 - src/Xrd/XrdInfo.hh | 39 - src/Xrd/XrdJob.hh | 58 - src/Xrd/XrdLink.cc | 1410 ----- src/Xrd/XrdLink.hh | 337 - src/Xrd/XrdLinkMatch.cc | 124 - src/Xrd/XrdLinkMatch.hh | 69 - src/Xrd/XrdMain.cc | 216 - src/Xrd/XrdObject.hh | 142 - src/Xrd/XrdObject.icc | 104 - src/Xrd/XrdPoll.cc | 367 -- src/Xrd/XrdPoll.hh | 152 - src/Xrd/XrdPollDev.hh | 64 - src/Xrd/XrdPollDev.icc | 314 - src/Xrd/XrdPollE.hh | 74 - src/Xrd/XrdPollE.icc | 280 - src/Xrd/XrdPollPoll.hh | 72 - src/Xrd/XrdPollPoll.icc | 509 -- src/Xrd/XrdProtLoad.cc | 306 - src/Xrd/XrdProtLoad.hh | 85 - src/Xrd/XrdProtocol.cc | 73 - src/Xrd/XrdProtocol.hh | 213 - src/Xrd/XrdScheduler.cc | 669 -- src/Xrd/XrdScheduler.hh | 122 - src/Xrd/XrdSendQ.cc | 403 -- src/Xrd/XrdSendQ.hh | 102 - src/Xrd/XrdStats.cc | 329 - src/Xrd/XrdStats.hh | 93 - src/Xrd/XrdTrace.hh | 71 - src/XrdAcc/XrdAccAccess.cc | 430 -- src/XrdAcc/XrdAccAccess.hh | 169 - src/XrdAcc/XrdAccAudit.cc | 99 - src/XrdAcc/XrdAccAudit.hh | 107 - src/XrdAcc/XrdAccAuthDB.hh | 112 - src/XrdAcc/XrdAccAuthFile.cc | 396 -- src/XrdAcc/XrdAccAuthFile.hh | 80 - src/XrdAcc/XrdAccAuthorize.hh | 183 - src/XrdAcc/XrdAccCapability.cc | 170 - src/XrdAcc/XrdAccCapability.hh | 123 - src/XrdAcc/XrdAccConfig.cc | 841 --- src/XrdAcc/XrdAccConfig.hh | 117 - src/XrdAcc/XrdAccGroups.cc | 412 -- src/XrdAcc/XrdAccGroups.hh | 174 - src/XrdAcc/XrdAccPrivs.hh | 86 - src/XrdApps.cmake | 180 - src/XrdApps/XrdAccTest.cc | 363 -- src/XrdApps/XrdAppsCconfig.cc | 177 - .../XrdClProxyPlugin/ProxyPrefixFile.cc | 228 - .../XrdClProxyPlugin/ProxyPrefixFile.hh | 224 - .../XrdClProxyPlugin/ProxyPrefixPlugin.cc | 106 - .../XrdClProxyPlugin/ProxyPrefixPlugin.hh | 59 - src/XrdApps/XrdClProxyPlugin/README.md | 31 - src/XrdApps/XrdCpConfig.cc | 923 --- src/XrdApps/XrdCpConfig.hh | 231 - src/XrdApps/XrdCpFile.cc | 175 - src/XrdApps/XrdCpFile.hh | 72 - src/XrdApps/XrdCpy.cc | 1694 ------ src/XrdApps/XrdMapCluster.cc | 588 -- src/XrdApps/XrdMpxStats.cc | 301 - src/XrdApps/XrdMpxXml.cc | 372 -- src/XrdApps/XrdMpxXml.hh | 69 - src/XrdApps/XrdQStats.cc | 207 - src/XrdApps/XrdWait41.cc | 301 - src/XrdApps/Xrdadler32.cc | 248 - src/XrdBwm/XrdBwm.cc | 1039 ---- src/XrdBwm/XrdBwm.hh | 304 - src/XrdBwm/XrdBwmConfig.cc | 428 -- src/XrdBwm/XrdBwmHandle.cc | 395 -- src/XrdBwm/XrdBwmHandle.hh | 109 - src/XrdBwm/XrdBwmLogger.cc | 315 - src/XrdBwm/XrdBwmLogger.hh | 92 - src/XrdBwm/XrdBwmPolicy.hh | 160 - src/XrdBwm/XrdBwmPolicy1.cc | 159 - src/XrdBwm/XrdBwmPolicy1.hh | 108 - src/XrdBwm/XrdBwmTrace.hh | 80 - src/XrdCeph.cmake | 10 +- src/XrdCeph/XrdCephOss.cc | 2 +- src/XrdCks/XrdCks.hh | 278 - src/XrdCks/XrdCksAssist.cc | 201 - src/XrdCks/XrdCksAssist.hh | 108 - src/XrdCks/XrdCksCalc.hh | 171 - src/XrdCks/XrdCksCalcadler32.hh | 126 - src/XrdCks/XrdCksCalccrc32.cc | 178 - src/XrdCks/XrdCksCalccrc32.hh | 78 - src/XrdCks/XrdCksCalcmd5.cc | 304 - src/XrdCks/XrdCksCalcmd5.hh | 84 - src/XrdCks/XrdCksCalczcrc32.cc | 117 - src/XrdCks/XrdCksConfig.cc | 216 - src/XrdCks/XrdCksConfig.hh | 76 - src/XrdCks/XrdCksData.hh | 125 - src/XrdCks/XrdCksLoad.hh | 112 - src/XrdCks/XrdCksLoader.cc | 212 - src/XrdCks/XrdCksLoader.hh | 116 - src/XrdCks/XrdCksManOss.cc | 268 - src/XrdCks/XrdCksManOss.hh | 71 - src/XrdCks/XrdCksManager.cc | 640 -- src/XrdCks/XrdCksManager.hh | 116 - src/XrdCks/XrdCksXAttr.hh | 90 - src/XrdCl/CMakeLists.txt | 182 - src/XrdCl/XrdClAnyObject.hh | 136 - src/XrdCl/XrdClAsyncSocketHandler.cc | 1020 ---- src/XrdCl/XrdClAsyncSocketHandler.hh | 263 - src/XrdCl/XrdClBuffer.hh | 237 - src/XrdCl/XrdClChannel.cc | 365 -- src/XrdCl/XrdClChannel.hh | 171 - src/XrdCl/XrdClChannelHandlerList.cc | 70 - src/XrdCl/XrdClChannelHandlerList.hh | 59 - src/XrdCl/XrdClCheckSumManager.cc | 160 - src/XrdCl/XrdClCheckSumManager.hh | 81 - src/XrdCl/XrdClClassicCopyJob.cc | 1611 ----- src/XrdCl/XrdClClassicCopyJob.hh | 47 - src/XrdCl/XrdClConstants.hh | 85 - src/XrdCl/XrdClCopy.cc | 899 --- src/XrdCl/XrdClCopyJob.hh | 108 - src/XrdCl/XrdClCopyProcess.cc | 426 -- src/XrdCl/XrdClCopyProcess.hh | 183 - src/XrdCl/XrdClDefaultEnv.cc | 852 --- src/XrdCl/XrdClDefaultEnv.hh | 183 - src/XrdCl/XrdClEnv.cc | 196 - src/XrdCl/XrdClEnv.hh | 128 - src/XrdCl/XrdClFS.cc | 1879 ------ src/XrdCl/XrdClFSExecutor.cc | 102 - src/XrdCl/XrdClFSExecutor.hh | 97 - src/XrdCl/XrdClFile.cc | 484 -- src/XrdCl/XrdClFile.hh | 480 -- src/XrdCl/XrdClFileStateHandler.cc | 2052 ------- src/XrdCl/XrdClFileStateHandler.hh | 501 -- src/XrdCl/XrdClFileSystem.cc | 1628 ----- src/XrdCl/XrdClFileSystem.hh | 752 --- src/XrdCl/XrdClFileSystemUtils.cc | 115 - src/XrdCl/XrdClFileSystemUtils.hh | 91 - src/XrdCl/XrdClFileTimer.cc | 41 - src/XrdCl/XrdClFileTimer.hh | 95 - src/XrdCl/XrdClForkHandler.cc | 136 - src/XrdCl/XrdClForkHandler.hh | 115 - src/XrdCl/XrdClInQueue.cc | 230 - src/XrdCl/XrdClInQueue.hh | 110 - src/XrdCl/XrdClJobManager.cc | 152 - src/XrdCl/XrdClJobManager.hh | 130 - src/XrdCl/XrdClLocalFileHandler.cc | 701 --- src/XrdCl/XrdClLocalFileHandler.hh | 259 - src/XrdCl/XrdClLocalFileTask.cc | 43 - src/XrdCl/XrdClLocalFileTask.hh | 42 - src/XrdCl/XrdClLog.cc | 311 - src/XrdCl/XrdClLog.hh | 265 - src/XrdCl/XrdClMessage.hh | 102 - src/XrdCl/XrdClMessageUtils.cc | 323 - src/XrdCl/XrdClMessageUtils.hh | 255 - src/XrdCl/XrdClMetalinkRedirector.cc | 482 -- src/XrdCl/XrdClMetalinkRedirector.hh | 173 - src/XrdCl/XrdClMonitor.hh | 237 - src/XrdCl/XrdClOptimizers.hh | 30 - src/XrdCl/XrdClOutQueue.cc | 144 - src/XrdCl/XrdClOutQueue.hh | 153 - src/XrdCl/XrdClPlugInInterface.hh | 416 -- src/XrdCl/XrdClPlugInManager.cc | 481 -- src/XrdCl/XrdClPlugInManager.hh | 174 - src/XrdCl/XrdClPoller.hh | 163 - src/XrdCl/XrdClPollerBuiltIn.cc | 587 -- src/XrdCl/XrdClPollerBuiltIn.hh | 164 - src/XrdCl/XrdClPollerFactory.cc | 97 - src/XrdCl/XrdClPollerFactory.hh | 46 - src/XrdCl/XrdClPostMaster.cc | 312 - src/XrdCl/XrdClPostMaster.hh | 215 - src/XrdCl/XrdClPostMasterInterfaces.hh | 451 -- src/XrdCl/XrdClPropertyList.hh | 317 - src/XrdCl/XrdClRedirectorRegistry.cc | 155 - src/XrdCl/XrdClRedirectorRegistry.hh | 180 - src/XrdCl/XrdClRequestSync.hh | 114 - src/XrdCl/XrdClResponseJob.hh | 70 - src/XrdCl/XrdClSIDManager.cc | 122 - src/XrdCl/XrdClSIDManager.hh | 97 - src/XrdCl/XrdClSocket.cc | 604 -- src/XrdCl/XrdClSocket.hh | 257 - src/XrdCl/XrdClStatus.cc | 132 - src/XrdCl/XrdClStatus.hh | 140 - src/XrdCl/XrdClStream.cc | 996 --- src/XrdCl/XrdClStream.hh | 351 -- src/XrdCl/XrdClSyncQueue.hh | 107 - src/XrdCl/XrdClTPFallBackCopyJob.cc | 88 - src/XrdCl/XrdClTPFallBackCopyJob.hh | 61 - src/XrdCl/XrdClTaskManager.cc | 247 - src/XrdCl/XrdClTaskManager.hh | 161 - src/XrdCl/XrdClThirdPartyCopyJob.cc | 618 -- src/XrdCl/XrdClThirdPartyCopyJob.hh | 61 - src/XrdCl/XrdClTransportManager.cc | 73 - src/XrdCl/XrdClTransportManager.hh | 66 - src/XrdCl/XrdClURL.cc | 485 -- src/XrdCl/XrdClURL.hh | 275 - src/XrdCl/XrdClUglyHacks.hh | 43 - src/XrdCl/XrdClUtils.cc | 492 -- src/XrdCl/XrdClUtils.hh | 316 - src/XrdCl/XrdClXCpCtx.cc | 199 - src/XrdCl/XrdClXCpCtx.hh | 305 - src/XrdCl/XrdClXCpSrc.cc | 543 -- src/XrdCl/XrdClXCpSrc.hh | 367 -- src/XrdCl/XrdClXRootDMsgHandler.cc | 2083 ------- src/XrdCl/XrdClXRootDMsgHandler.hh | 431 -- src/XrdCl/XrdClXRootDResponses.cc | 299 - src/XrdCl/XrdClXRootDResponses.hh | 871 --- src/XrdCl/XrdClXRootDTransport.cc | 2564 -------- src/XrdCl/XrdClXRootDTransport.hh | 354 -- src/XrdCl/XrdClZipArchiveReader.cc | 716 --- src/XrdCl/XrdClZipArchiveReader.hh | 151 - src/XrdClient.cmake | 117 - src/XrdClient/README_Xrdcp | 21 - src/XrdClient/README_params | 98 - src/XrdClient/TestXrdClient.cc | 117 - src/XrdClient/TestXrdClient_read.cc | 778 --- src/XrdClient/XrdClient.cc | 2050 ------- src/XrdClient/XrdClient.hh | 315 - src/XrdClient/XrdClientAbs.cc | 268 - src/XrdClient/XrdClientAbs.hh | 120 - src/XrdClient/XrdClientAbsMonIntf.hh | 74 - src/XrdClient/XrdClientAdmin.cc | 1613 ----- src/XrdClient/XrdClientAdmin.hh | 195 - src/XrdClient/XrdClientCallback.hh | 49 - src/XrdClient/XrdClientConn.cc | 2764 --------- src/XrdClient/XrdClientConn.hh | 454 -- src/XrdClient/XrdClientConnMgr.cc | 734 --- src/XrdClient/XrdClientConnMgr.hh | 130 - src/XrdClient/XrdClientConst.hh | 192 - src/XrdClient/XrdClientDebug.cc | 67 - src/XrdClient/XrdClientDebug.hh | 126 - src/XrdClient/XrdClientEnv.cc | 261 - src/XrdClient/XrdClientEnv.hh | 127 - src/XrdClient/XrdClientInputBuffer.cc | 230 - src/XrdClient/XrdClientInputBuffer.hh | 89 - src/XrdClient/XrdClientLogConnection.cc | 112 - src/XrdClient/XrdClientLogConnection.hh | 78 - src/XrdClient/XrdClientMStream.cc | 359 -- src/XrdClient/XrdClientMStream.hh | 75 - src/XrdClient/XrdClientMessage.cc | 254 - src/XrdClient/XrdClientMessage.hh | 103 - src/XrdClient/XrdClientPSock.cc | 457 -- src/XrdClient/XrdClientPSock.hh | 157 - src/XrdClient/XrdClientPhyConnection.cc | 908 --- src/XrdClient/XrdClientPhyConnection.hh | 226 - src/XrdClient/XrdClientPrep.cc | 207 - src/XrdClient/XrdClientProtocol.cc | 667 -- src/XrdClient/XrdClientProtocol.hh | 60 - src/XrdClient/XrdClientReadAhead.cc | 293 - src/XrdClient/XrdClientReadAhead.hh | 64 - src/XrdClient/XrdClientReadCache.cc | 912 --- src/XrdClient/XrdClientReadCache.hh | 285 - src/XrdClient/XrdClientReadV.cc | 273 - src/XrdClient/XrdClientReadV.hh | 72 - src/XrdClient/XrdClientSid.cc | 282 - src/XrdClient/XrdClientSid.hh | 130 - src/XrdClient/XrdClientSock.cc | 515 -- src/XrdClient/XrdClientSock.hh | 151 - src/XrdClient/XrdClientThread.cc | 76 - src/XrdClient/XrdClientThread.hh | 105 - src/XrdClient/XrdClientUnsolMsg.hh | 82 - src/XrdClient/XrdClientUrlInfo.cc | 272 - src/XrdClient/XrdClientUrlInfo.hh | 77 - src/XrdClient/XrdClientUrlSet.cc | 417 -- src/XrdClient/XrdClientUrlSet.hh | 99 - src/XrdClient/XrdClientVector.hh | 388 -- src/XrdClient/XrdCommandLine.cc | 1957 ------ src/XrdClient/XrdCpMthrQueue.cc | 114 - src/XrdClient/XrdCpMthrQueue.hh | 76 - src/XrdClient/XrdCpWorkLst.cc | 464 -- src/XrdClient/XrdCpWorkLst.hh | 97 - src/XrdClient/XrdStageTool.cc | 357 -- src/XrdClient/XrdcpXtremeRead.cc | 209 - src/XrdClient/XrdcpXtremeRead.hh | 102 - src/XrdClient/tinytestXTNetAdmin.pl | 45 - src/XrdClient/xrdadmin | 547 -- src/XrdCms/XrdCmsAdmin.cc | 810 --- src/XrdCms/XrdCmsAdmin.hh | 96 - src/XrdCms/XrdCmsBaseFS.cc | 420 -- src/XrdCms/XrdCmsBaseFS.hh | 207 - src/XrdCms/XrdCmsBlackList.cc | 611 -- src/XrdCms/XrdCmsBlackList.hh | 102 - src/XrdCms/XrdCmsCache.cc | 570 -- src/XrdCms/XrdCmsCache.hh | 125 - src/XrdCms/XrdCmsClient.cc | 54 - src/XrdCms/XrdCmsClient.hh | 455 -- src/XrdCms/XrdCmsClientConfig.cc | 634 -- src/XrdCms/XrdCmsClientConfig.hh | 101 - src/XrdCms/XrdCmsClientMan.cc | 491 -- src/XrdCms/XrdCmsClientMan.hh | 134 - src/XrdCms/XrdCmsClientMsg.cc | 187 - src/XrdCms/XrdCmsClientMsg.hh | 88 - src/XrdCms/XrdCmsClustID.cc | 255 - src/XrdCms/XrdCmsClustID.hh | 79 - src/XrdCms/XrdCmsCluster.cc | 1984 ------ src/XrdCms/XrdCmsCluster.hh | 270 - src/XrdCms/XrdCmsConfig.cc | 3111 ---------- src/XrdCms/XrdCmsConfig.hh | 268 - src/XrdCms/XrdCmsFinder.cc | 1364 ----- src/XrdCms/XrdCmsFinder.hh | 176 - src/XrdCms/XrdCmsJob.cc | 128 - src/XrdCms/XrdCmsJob.hh | 64 - src/XrdCms/XrdCmsKey.cc | 210 - src/XrdCms/XrdCmsKey.hh | 163 - src/XrdCms/XrdCmsLogin.cc | 289 - src/XrdCms/XrdCmsLogin.hh | 64 - src/XrdCms/XrdCmsManList.cc | 239 - src/XrdCms/XrdCmsManList.hh | 80 - src/XrdCms/XrdCmsManTree.cc | 226 - src/XrdCms/XrdCmsManTree.hh | 93 - src/XrdCms/XrdCmsManager.cc | 618 -- src/XrdCms/XrdCmsManager.hh | 112 - src/XrdCms/XrdCmsMeter.cc | 521 -- src/XrdCms/XrdCmsMeter.hh | 121 - src/XrdCms/XrdCmsNash.cc | 179 - src/XrdCms/XrdCmsNash.hh | 63 - src/XrdCms/XrdCmsNode.cc | 1860 ------ src/XrdCms/XrdCmsNode.hh | 281 - src/XrdCms/XrdCmsPList.cc | 216 - src/XrdCms/XrdCmsPList.hh | 141 - src/XrdCms/XrdCmsParser.cc | 420 -- src/XrdCms/XrdCmsParser.hh | 106 - src/XrdCms/XrdCmsPrepArgs.cc | 150 - src/XrdCms/XrdCmsPrepArgs.hh | 82 - src/XrdCms/XrdCmsPrepare.cc | 517 -- src/XrdCms/XrdCmsPrepare.hh | 110 - src/XrdCms/XrdCmsProtocol.cc | 1186 ---- src/XrdCms/XrdCmsProtocol.hh | 112 - src/XrdCms/XrdCmsRRData.cc | 85 - src/XrdCms/XrdCmsRRData.hh | 96 - src/XrdCms/XrdCmsRRQ.cc | 510 -- src/XrdCms/XrdCmsRRQ.hh | 190 - src/XrdCms/XrdCmsRTable.cc | 129 - src/XrdCms/XrdCmsRTable.hh | 70 - src/XrdCms/XrdCmsReq.cc | 424 -- src/XrdCms/XrdCmsReq.hh | 113 - src/XrdCms/XrdCmsResp.cc | 304 - src/XrdCms/XrdCmsResp.hh | 142 - src/XrdCms/XrdCmsRole.hh | 105 - src/XrdCms/XrdCmsRouting.cc | 227 - src/XrdCms/XrdCmsRouting.hh | 115 - src/XrdCms/XrdCmsSecurity.cc | 437 -- src/XrdCms/XrdCmsSecurity.hh | 70 - src/XrdCms/XrdCmsSelect.hh | 154 - src/XrdCms/XrdCmsState.cc | 316 - src/XrdCms/XrdCmsState.hh | 96 - src/XrdCms/XrdCmsSupervisor.cc | 118 - src/XrdCms/XrdCmsSupervisor.hh | 53 - src/XrdCms/XrdCmsTalk.cc | 131 - src/XrdCms/XrdCmsTalk.hh | 57 - src/XrdCms/XrdCmsTrace.hh | 77 - src/XrdCms/XrdCmsTypes.hh | 49 - src/XrdCms/XrdCmsUtils.cc | 280 - src/XrdCms/XrdCmsUtils.hh | 106 - src/XrdCms/XrdCmsVnId.hh | 94 - src/XrdCns.cmake | 67 - src/XrdCns/README | 31 - src/XrdCns/XrdCnsConfig.cc | 568 -- src/XrdCns/XrdCnsConfig.hh | 86 - src/XrdCns/XrdCnsDaemon.cc | 148 - src/XrdCns/XrdCnsDaemon.hh | 50 - src/XrdCns/XrdCnsInventory.cc | 183 - src/XrdCns/XrdCnsInventory.hh | 66 - src/XrdCns/XrdCnsLog.cc | 160 - src/XrdCns/XrdCnsLog.hh | 57 - src/XrdCns/XrdCnsLogClient.cc | 645 -- src/XrdCns/XrdCnsLogClient.hh | 100 - src/XrdCns/XrdCnsLogFile.cc | 301 - src/XrdCns/XrdCnsLogFile.hh | 94 - src/XrdCns/XrdCnsLogRec.cc | 186 - src/XrdCns/XrdCnsLogRec.hh | 224 - src/XrdCns/XrdCnsLogServer.cc | 242 - src/XrdCns/XrdCnsLogServer.hh | 60 - src/XrdCns/XrdCnsMain.cc | 191 - src/XrdCns/XrdCnsSsi.cc | 810 --- src/XrdCns/XrdCnsSsi.hh | 67 - src/XrdCns/XrdCnsSsiCfg.cc | 186 - src/XrdCns/XrdCnsSsiCfg.hh | 70 - src/XrdCns/XrdCnsSsiMain.cc | 160 - src/XrdCns/XrdCnsSsiSay.hh | 57 - src/XrdCns/XrdCnsXref.cc | 158 - src/XrdCns/XrdCnsXref.hh | 68 - src/XrdCns/cns.sh | 101 - src/XrdCns/example.cns.cfg | 72 - src/XrdCns/example.xrdcluster.cfg | 92 - src/XrdCns/xrdcluster.sh | 100 - src/XrdCrypto.cmake | 128 - src/XrdCrypto/XrdCryptoAux.cc | 91 - src/XrdCrypto/XrdCryptoAux.hh | 88 - src/XrdCrypto/XrdCryptoBasic.cc | 246 - src/XrdCrypto/XrdCryptoBasic.hh | 76 - src/XrdCrypto/XrdCryptoCipher.cc | 181 - src/XrdCrypto/XrdCryptoCipher.hh | 80 - src/XrdCrypto/XrdCryptoFactory.cc | 494 -- src/XrdCrypto/XrdCryptoFactory.hh | 190 - src/XrdCrypto/XrdCryptoLite.cc | 61 - src/XrdCrypto/XrdCryptoLite.hh | 99 - src/XrdCrypto/XrdCryptoLite_bf32.cc | 176 - src/XrdCrypto/XrdCryptoMsgDigest.cc | 86 - src/XrdCrypto/XrdCryptoMsgDigest.hh | 65 - src/XrdCrypto/XrdCryptoRSA.cc | 258 - src/XrdCrypto/XrdCryptoRSA.hh | 98 - src/XrdCrypto/XrdCryptoTrace.hh | 60 - src/XrdCrypto/XrdCryptoX509.cc | 254 - src/XrdCrypto/XrdCryptoX509.hh | 117 - src/XrdCrypto/XrdCryptoX509Chain.cc | 932 --- src/XrdCrypto/XrdCryptoX509Chain.hh | 175 - src/XrdCrypto/XrdCryptoX509Crl.cc | 134 - src/XrdCrypto/XrdCryptoX509Crl.hh | 84 - src/XrdCrypto/XrdCryptoX509Req.cc | 122 - src/XrdCrypto/XrdCryptoX509Req.hh | 90 - src/XrdCrypto/XrdCryptogsiX509Chain.cc | 287 - src/XrdCrypto/XrdCryptogsiX509Chain.hh | 71 - src/XrdCrypto/XrdCryptosslAux.cc | 686 --- src/XrdCrypto/XrdCryptosslAux.hh | 117 - src/XrdCrypto/XrdCryptosslCipher.cc | 1092 ---- src/XrdCrypto/XrdCryptosslCipher.hh | 102 - src/XrdCrypto/XrdCryptosslFactory.cc | 534 -- src/XrdCrypto/XrdCryptosslFactory.hh | 114 - src/XrdCrypto/XrdCryptosslMsgDigest.cc | 162 - src/XrdCrypto/XrdCryptosslMsgDigest.hh | 69 - src/XrdCrypto/XrdCryptosslRSA.cc | 673 -- src/XrdCrypto/XrdCryptosslRSA.hh | 84 - src/XrdCrypto/XrdCryptosslTrace.hh | 59 - src/XrdCrypto/XrdCryptosslX509.cc | 1093 ---- src/XrdCrypto/XrdCryptosslX509.hh | 129 - src/XrdCrypto/XrdCryptosslX509Crl.cc | 656 -- src/XrdCrypto/XrdCryptosslX509Crl.hh | 101 - src/XrdCrypto/XrdCryptosslX509Req.cc | 377 -- src/XrdCrypto/XrdCryptosslX509Req.hh | 85 - src/XrdCrypto/XrdCryptosslX509Store.cc | 90 - src/XrdCrypto/XrdCryptosslX509Store.hh | 67 - src/XrdCrypto/XrdCryptosslgsiAux.cc | 1401 ----- src/XrdCrypto/XrdCryptosslgsiAux.hh | 102 - src/XrdCrypto/XrdCryptotest.cc | 462 -- src/XrdDaemons.cmake | 76 - src/XrdDig/XrdDigAuth.cc | 431 -- src/XrdDig/XrdDigAuth.hh | 99 - src/XrdDig/XrdDigConfig.cc | 647 -- src/XrdDig/XrdDigConfig.hh | 86 - src/XrdDig/XrdDigFS.cc | 817 --- src/XrdDig/XrdDigFS.hh | 267 - src/XrdFfs.cmake | 70 - src/XrdFfs/README | 160 - src/XrdFfs/XrdFfsDent.cc | 344 -- src/XrdFfs/XrdFfsDent.hh | 58 - src/XrdFfs/XrdFfsFsinfo.cc | 140 - src/XrdFfs/XrdFfsFsinfo.hh | 40 - src/XrdFfs/XrdFfsMisc.cc | 444 -- src/XrdFfs/XrdFfsMisc.hh | 51 - src/XrdFfs/XrdFfsPosix.cc | 826 --- src/XrdFfs/XrdFfsPosix.hh | 85 - src/XrdFfs/XrdFfsQueue.cc | 331 - src/XrdFfs/XrdFfsQueue.hh | 61 - src/XrdFfs/XrdFfsWcache.cc | 216 - src/XrdFfs/XrdFfsWcache.hh | 42 - src/XrdFfs/XrdFfsXrootdfs.cc | 1451 ----- src/XrdFfs/xrootdfs.template | 71 - src/XrdFileCache.cmake | 98 - src/XrdFileCache/README | 164 - src/XrdFileCache/XrdFileCache.cc | 578 -- src/XrdFileCache/XrdFileCache.hh | 272 - src/XrdFileCache/XrdFileCacheAllowDecision.cc | 45 - .../XrdFileCacheBlacklistDecision.cc | 126 - src/XrdFileCache/XrdFileCacheConfiguration.cc | 460 -- src/XrdFileCache/XrdFileCacheDecision.hh | 67 - src/XrdFileCache/XrdFileCacheFile.cc | 976 --- src/XrdFileCache/XrdFileCacheFile.hh | 293 - src/XrdFileCache/XrdFileCacheIO.cc | 31 - src/XrdFileCache/XrdFileCacheIO.hh | 64 - src/XrdFileCache/XrdFileCacheIOEntireFile.cc | 215 - src/XrdFileCache/XrdFileCacheIOEntireFile.hh | 104 - src/XrdFileCache/XrdFileCacheIOFileBlock.cc | 365 -- src/XrdFileCache/XrdFileCacheIOFileBlock.hh | 90 - src/XrdFileCache/XrdFileCacheInfo.cc | 384 -- src/XrdFileCache/XrdFileCacheInfo.hh | 368 -- src/XrdFileCache/XrdFileCachePrint.cc | 277 - src/XrdFileCache/XrdFileCachePrint.hh | 54 - src/XrdFileCache/XrdFileCachePurge.cc | 219 - src/XrdFileCache/XrdFileCacheStats.hh | 62 - src/XrdFileCache/XrdFileCacheTrace.hh | 59 - src/XrdFileCache/XrdFileCacheVRead.cc | 383 -- src/XrdFrc/XrdFrcCID.cc | 337 - src/XrdFrc/XrdFrcCID.hh | 99 - src/XrdFrc/XrdFrcProxy.cc | 323 - src/XrdFrc/XrdFrcProxy.hh | 91 - src/XrdFrc/XrdFrcReqAgent.cc | 239 - src/XrdFrc/XrdFrcReqAgent.hh | 66 - src/XrdFrc/XrdFrcReqFile.cc | 623 -- src/XrdFrc/XrdFrcReqFile.hh | 105 - src/XrdFrc/XrdFrcRequest.hh | 95 - src/XrdFrc/XrdFrcTrace.cc | 39 - src/XrdFrc/XrdFrcTrace.hh | 75 - src/XrdFrc/XrdFrcUtils.cc | 309 - src/XrdFrc/XrdFrcUtils.hh | 69 - src/XrdFrc/XrdFrcXAttr.hh | 196 - src/XrdFrc/XrdFrcXLock.hh | 57 - src/XrdFrm.cmake | 112 - src/XrdFrm/XrdFrmAdmin.cc | 1068 ---- src/XrdFrm/XrdFrmAdmin.hh | 249 - src/XrdFrm/XrdFrmAdminAudit.cc | 912 --- src/XrdFrm/XrdFrmAdminConvert.cc | 479 -- src/XrdFrm/XrdFrmAdminFiles.cc | 409 -- src/XrdFrm/XrdFrmAdminFind.cc | 339 -- src/XrdFrm/XrdFrmAdminMain.cc | 184 - src/XrdFrm/XrdFrmAdminQuery.cc | 283 - src/XrdFrm/XrdFrmAdminReloc.cc | 271 - src/XrdFrm/XrdFrmAdminUnlink.cc | 258 - src/XrdFrm/XrdFrmCns.cc | 280 - src/XrdFrm/XrdFrmCns.hh | 80 - src/XrdFrm/XrdFrmConfig.cc | 2127 ------- src/XrdFrm/XrdFrmConfig.hh | 235 - src/XrdFrm/XrdFrmFiles.cc | 480 -- src/XrdFrm/XrdFrmFiles.hh | 144 - src/XrdFrm/XrdFrmMigrate.cc | 305 - src/XrdFrm/XrdFrmMigrate.hh | 68 - src/XrdFrm/XrdFrmMonitor.cc | 298 - src/XrdFrm/XrdFrmMonitor.hh | 87 - src/XrdFrm/XrdFrmPurgMain.cc | 250 - src/XrdFrm/XrdFrmPurge.cc | 926 --- src/XrdFrm/XrdFrmPurge.hh | 123 - src/XrdFrm/XrdFrmReqBoss.cc | 204 - src/XrdFrm/XrdFrmReqBoss.hh | 66 - src/XrdFrm/XrdFrmTSort.cc | 170 - src/XrdFrm/XrdFrmTSort.hh | 71 - src/XrdFrm/XrdFrmTransfer.cc | 836 --- src/XrdFrm/XrdFrmTransfer.hh | 75 - src/XrdFrm/XrdFrmXfrAgent.cc | 268 - src/XrdFrm/XrdFrmXfrAgent.hh | 61 - src/XrdFrm/XrdFrmXfrDaemon.cc | 215 - src/XrdFrm/XrdFrmXfrDaemon.hh | 56 - src/XrdFrm/XrdFrmXfrJob.hh | 55 - src/XrdFrm/XrdFrmXfrMain.cc | 158 - src/XrdFrm/XrdFrmXfrQueue.cc | 516 -- src/XrdFrm/XrdFrmXfrQueue.hh | 88 - src/XrdHeaders.cmake | 157 - src/XrdHttp.cmake | 52 - src/XrdHttp/XrdHttpExtHandler.cc | 88 - src/XrdHttp/XrdHttpExtHandler.hh | 157 - src/XrdHttp/XrdHttpProtocol.cc | 2536 -------- src/XrdHttp/XrdHttpProtocol.hh | 378 -- src/XrdHttp/XrdHttpReq.cc | 2309 ------- src/XrdHttp/XrdHttpReq.hh | 391 -- src/XrdHttp/XrdHttpSecXtractor.hh | 113 - src/XrdHttp/XrdHttpStatic.hh | 57 - src/XrdHttp/XrdHttpTrace.cc | 40 - src/XrdHttp/XrdHttpTrace.hh | 97 - src/XrdHttp/XrdHttpUtils.cc | 365 -- src/XrdHttp/XrdHttpUtils.hh | 82 - src/XrdHttp/static/favicon.ico | Bin 1400 -> 0 bytes src/XrdHttp/static/xrdhttp.css | 114 - src/XrdHttp/static/xrdhttp_css.h | 110 - src/XrdHttp/static/xrdhttp_favicon_ico.h | 120 - src/XrdHttp/xrootd-http-rdr.cf | 73 - src/XrdHttp/xrootd-http.cf | 35 - src/XrdNet/XrdNet.cc | 532 -- src/XrdNet/XrdNet.hh | 300 - src/XrdNet/XrdNetAddr.cc | 530 -- src/XrdNet/XrdNetAddr.hh | 247 - src/XrdNet/XrdNetAddrInfo.cc | 453 -- src/XrdNet/XrdNetAddrInfo.hh | 345 -- src/XrdNet/XrdNetBuffer.cc | 146 - src/XrdNet/XrdNetBuffer.hh | 91 - src/XrdNet/XrdNetCache.cc | 235 - src/XrdNet/XrdNetCache.hh | 133 - src/XrdNet/XrdNetCmsNotify.cc | 142 - src/XrdNet/XrdNetCmsNotify.hh | 59 - src/XrdNet/XrdNetConnect.cc | 81 - src/XrdNet/XrdNetConnect.hh | 59 - src/XrdNet/XrdNetIF.cc | 890 --- src/XrdNet/XrdNetIF.hh | 450 -- src/XrdNet/XrdNetMsg.cc | 155 - src/XrdNet/XrdNetMsg.hh | 121 - src/XrdNet/XrdNetOpts.hh | 119 - src/XrdNet/XrdNetPeer.hh | 52 - src/XrdNet/XrdNetSecurity.cc | 267 - src/XrdNet/XrdNetSecurity.hh | 80 - src/XrdNet/XrdNetSockAddr.hh | 47 - src/XrdNet/XrdNetSocket.cc | 487 -- src/XrdNet/XrdNetSocket.hh | 154 - src/XrdNet/XrdNetUtils.cc | 654 -- src/XrdNet/XrdNetUtils.hh | 363 -- src/XrdOfs/XrdOfs.cc | 2394 -------- src/XrdOfs/XrdOfs.hh | 423 -- src/XrdOfs/XrdOfsConfig.cc | 1435 ----- src/XrdOfs/XrdOfsConfigPI.cc | 532 -- src/XrdOfs/XrdOfsConfigPI.hh | 241 - src/XrdOfs/XrdOfsEvr.cc | 365 -- src/XrdOfs/XrdOfsEvr.hh | 121 - src/XrdOfs/XrdOfsEvs.cc | 537 -- src/XrdOfs/XrdOfsEvs.hh | 184 - src/XrdOfs/XrdOfsFS.cc | 76 - src/XrdOfs/XrdOfsHandle.cc | 775 --- src/XrdOfs/XrdOfsHandle.hh | 207 - src/XrdOfs/XrdOfsPoscq.cc | 355 -- src/XrdOfs/XrdOfsPoscq.hh | 100 - src/XrdOfs/XrdOfsSecurity.hh | 48 - src/XrdOfs/XrdOfsStats.cc | 73 - src/XrdOfs/XrdOfsStats.hh | 77 - src/XrdOfs/XrdOfsTPC.cc | 557 -- src/XrdOfs/XrdOfsTPC.hh | 139 - src/XrdOfs/XrdOfsTPCAuth.cc | 299 - src/XrdOfs/XrdOfsTPCAuth.hh | 68 - src/XrdOfs/XrdOfsTPCInfo.cc | 175 - src/XrdOfs/XrdOfsTPCInfo.hh | 85 - src/XrdOfs/XrdOfsTPCJob.cc | 192 - src/XrdOfs/XrdOfsTPCJob.hh | 65 - src/XrdOfs/XrdOfsTPCProg.cc | 231 - src/XrdOfs/XrdOfsTPCProg.hh | 70 - src/XrdOfs/XrdOfsTrace.hh | 100 - src/XrdOss/XrdOss.hh | 273 - src/XrdOss/XrdOssAio.cc | 497 -- src/XrdOss/XrdOssApi.cc | 1210 ---- src/XrdOss/XrdOssApi.hh | 385 -- src/XrdOss/XrdOssCache.cc | 704 --- src/XrdOss/XrdOssCache.hh | 253 - src/XrdOss/XrdOssConfig.cc | 1899 ------ src/XrdOss/XrdOssConfig.hh | 54 - src/XrdOss/XrdOssCopy.cc | 181 - src/XrdOss/XrdOssCopy.hh | 45 - src/XrdOss/XrdOssCreate.cc | 368 -- src/XrdOss/XrdOssDefaultSS.hh | 57 - src/XrdOss/XrdOssError.hh | 90 - src/XrdOss/XrdOssMSS.cc | 440 -- src/XrdOss/XrdOssMio.cc | 339 -- src/XrdOss/XrdOssMio.hh | 83 - src/XrdOss/XrdOssMioFile.hh | 60 - src/XrdOss/XrdOssOpaque.hh | 48 - src/XrdOss/XrdOssPath.cc | 391 -- src/XrdOss/XrdOssPath.hh | 96 - src/XrdOss/XrdOssReloc.cc | 210 - src/XrdOss/XrdOssRename.cc | 307 - src/XrdOss/XrdOssSIgpfsT.cc | 198 - src/XrdOss/XrdOssSpace.cc | 516 -- src/XrdOss/XrdOssSpace.hh | 98 - src/XrdOss/XrdOssStage.cc | 508 -- src/XrdOss/XrdOssStage.hh | 83 - src/XrdOss/XrdOssStat.cc | 536 -- src/XrdOss/XrdOssStatInfo.hh | 174 - src/XrdOss/XrdOssTrace.hh | 70 - src/XrdOss/XrdOssUnlink.cc | 218 - src/XrdOuc/README.bonjour | 26 - src/XrdOuc/XrdOucArgs.cc | 250 - src/XrdOuc/XrdOucArgs.hh | 119 - src/XrdOuc/XrdOucBackTrace.cc | 500 -- src/XrdOuc/XrdOucBackTrace.hh | 178 - src/XrdOuc/XrdOucBuffer.cc | 267 - src/XrdOuc/XrdOucBuffer.hh | 278 - src/XrdOuc/XrdOucCRC.cc | 178 - src/XrdOuc/XrdOucCRC.hh | 46 - src/XrdOuc/XrdOucCache.hh | 480 -- src/XrdOuc/XrdOucCache2.hh | 321 - src/XrdOuc/XrdOucCacheData.cc | 603 -- src/XrdOuc/XrdOucCacheData.hh | 155 - src/XrdOuc/XrdOucCacheDram.cc | 53 - src/XrdOuc/XrdOucCacheDram.hh | 132 - src/XrdOuc/XrdOucCacheReal.cc | 601 -- src/XrdOuc/XrdOucCacheReal.hh | 139 - src/XrdOuc/XrdOucCacheSlot.hh | 154 - src/XrdOuc/XrdOucCallBack.cc | 107 - src/XrdOuc/XrdOucCallBack.hh | 113 - src/XrdOuc/XrdOucChain.hh | 94 - src/XrdOuc/XrdOucCompiler.hh | 34 - src/XrdOuc/XrdOucDLlist.hh | 113 - src/XrdOuc/XrdOucERoute.cc | 94 - src/XrdOuc/XrdOucERoute.hh | 78 - src/XrdOuc/XrdOucEnum.hh | 32 - src/XrdOuc/XrdOucEnv.cc | 244 - src/XrdOuc/XrdOucEnv.hh | 119 - src/XrdOuc/XrdOucErrInfo.cc | 64 - src/XrdOuc/XrdOucErrInfo.hh | 524 -- src/XrdOuc/XrdOucExport.cc | 212 - src/XrdOuc/XrdOucExport.hh | 117 - src/XrdOuc/XrdOucFileInfo.cc | 253 - src/XrdOuc/XrdOucFileInfo.hh | 208 - src/XrdOuc/XrdOucGMap.cc | 351 -- src/XrdOuc/XrdOucGMap.hh | 173 - src/XrdOuc/XrdOucHash.hh | 213 - src/XrdOuc/XrdOucHash.icc | 295 - src/XrdOuc/XrdOucHashVal.cc | 69 - src/XrdOuc/XrdOucIOVec.hh | 59 - src/XrdOuc/XrdOucLock.hh | 47 - src/XrdOuc/XrdOucLogging.cc | 292 - src/XrdOuc/XrdOucLogging.hh | 60 - src/XrdOuc/XrdOucMsubs.cc | 259 - src/XrdOuc/XrdOucMsubs.hh | 120 - src/XrdOuc/XrdOucN2NLoader.cc | 81 - src/XrdOuc/XrdOucN2NLoader.hh | 59 - src/XrdOuc/XrdOucN2No2p.cc | 264 - src/XrdOuc/XrdOucNList.cc | 127 - src/XrdOuc/XrdOucNList.hh | 131 - src/XrdOuc/XrdOucNSWalk.cc | 410 -- src/XrdOuc/XrdOucNSWalk.hh | 160 - src/XrdOuc/XrdOucName2Name.cc | 195 - src/XrdOuc/XrdOucName2Name.hh | 234 - src/XrdOuc/XrdOucPList.hh | 131 - src/XrdOuc/XrdOucPinLoader.cc | 242 - src/XrdOuc/XrdOucPinLoader.hh | 189 - src/XrdOuc/XrdOucPinPath.cc | 37 - src/XrdOuc/XrdOucPinPath.hh | 59 - src/XrdOuc/XrdOucPreload.cc | 63 - src/XrdOuc/XrdOucPreload.hh | 60 - src/XrdOuc/XrdOucProg.cc | 329 - src/XrdOuc/XrdOucProg.hh | 120 - src/XrdOuc/XrdOucPsx.cc | 831 --- src/XrdOuc/XrdOucPsx.hh | 116 - src/XrdOuc/XrdOucPup.cc | 379 -- src/XrdOuc/XrdOucPup.hh | 167 - src/XrdOuc/XrdOucRash.hh | 185 - src/XrdOuc/XrdOucRash.icc | 252 - src/XrdOuc/XrdOucReqID.cc | 168 - src/XrdOuc/XrdOucReqID.hh | 67 - src/XrdOuc/XrdOucSFVec.hh | 51 - src/XrdOuc/XrdOucSid.cc | 152 - src/XrdOuc/XrdOucSid.hh | 122 - src/XrdOuc/XrdOucSiteName.cc | 59 - src/XrdOuc/XrdOucSiteName.hh | 42 - src/XrdOuc/XrdOucStats.hh | 54 - src/XrdOuc/XrdOucStream.cc | 1149 ---- src/XrdOuc/XrdOucStream.hh | 250 - src/XrdOuc/XrdOucString.cc | 1314 ---- src/XrdOuc/XrdOucString.hh | 388 -- src/XrdOuc/XrdOucSxeq.cc | 219 - src/XrdOuc/XrdOucSxeq.hh | 67 - src/XrdOuc/XrdOucTList.hh | 121 - src/XrdOuc/XrdOucTPC.cc | 183 - src/XrdOuc/XrdOucTPC.hh | 82 - src/XrdOuc/XrdOucTable.hh | 162 - src/XrdOuc/XrdOucTokenizer.cc | 134 - src/XrdOuc/XrdOucTokenizer.hh | 75 - src/XrdOuc/XrdOucTrace.cc | 50 - src/XrdOuc/XrdOucTrace.hh | 56 - src/XrdOuc/XrdOucUtils.cc | 844 --- src/XrdOuc/XrdOucUtils.hh | 104 - src/XrdOuc/XrdOucVerName.cc | 78 - src/XrdOuc/XrdOucVerName.hh | 59 - src/XrdOuc/XrdOucXAttr.hh | 148 - src/XrdOuc/XrdOuca2x.cc | 346 -- src/XrdOuc/XrdOuca2x.hh | 60 - src/XrdPlugins.cmake | 154 - src/XrdPosix.cmake | 79 - src/XrdPosix/README | 159 - src/XrdPosix/XrdPosix.cc | 1057 ---- src/XrdPosix/XrdPosix.hh | 118 - src/XrdPosix/XrdPosixAdmin.cc | 155 - src/XrdPosix/XrdPosixAdmin.hh | 66 - src/XrdPosix/XrdPosixCacheBC.hh | 136 - src/XrdPosix/XrdPosixCallBack.cc | 53 - src/XrdPosix/XrdPosixCallBack.hh | 91 - src/XrdPosix/XrdPosixConfig.cc | 344 -- src/XrdPosix/XrdPosixConfig.hh | 66 - src/XrdPosix/XrdPosixDir.cc | 119 - src/XrdPosix/XrdPosixDir.hh | 94 - src/XrdPosix/XrdPosixExtern.hh | 205 - src/XrdPosix/XrdPosixFile.cc | 547 -- src/XrdPosix/XrdPosixFile.hh | 195 - src/XrdPosix/XrdPosixFileRH.cc | 163 - src/XrdPosix/XrdPosixFileRH.hh | 82 - src/XrdPosix/XrdPosixLinkage.cc | 307 - src/XrdPosix/XrdPosixLinkage.hh | 426 -- src/XrdPosix/XrdPosixMap.cc | 158 - src/XrdPosix/XrdPosixMap.hh | 59 - src/XrdPosix/XrdPosixObjGuard.hh | 53 - src/XrdPosix/XrdPosixObject.cc | 327 - src/XrdPosix/XrdPosixObject.hh | 110 - src/XrdPosix/XrdPosixOsDep.hh | 75 - src/XrdPosix/XrdPosixPreload.cc | 770 --- src/XrdPosix/XrdPosixPreload32.cc | 559 -- src/XrdPosix/XrdPosixPrepIO.cc | 85 - src/XrdPosix/XrdPosixPrepIO.hh | 105 - src/XrdPosix/XrdPosixTrace.hh | 60 - src/XrdPosix/XrdPosixXrootd.cc | 1567 ----- src/XrdPosix/XrdPosixXrootd.hh | 397 -- src/XrdPosix/XrdPosixXrootdPath.cc | 278 - src/XrdPosix/XrdPosixXrootdPath.hh | 77 - src/XrdPss/XrdPss.cc | 1196 ---- src/XrdPss/XrdPss.hh | 212 - src/XrdPss/XrdPssAio.cc | 112 - src/XrdPss/XrdPssAioCB.cc | 107 - src/XrdPss/XrdPssAioCB.hh | 64 - src/XrdPss/XrdPssCks.cc | 194 - src/XrdPss/XrdPssCks.hh | 85 - src/XrdPss/XrdPssConfig.cc | 709 --- src/XrdRootd/XrdRootdProtocol.cc | 237 - src/XrdRootd/XrdRootdProtocol.hh | 73 - src/XrdSec.cmake | 165 - src/XrdSec/XrdSecClient.cc | 120 - src/XrdSec/XrdSecEntity.hh | 99 - src/XrdSec/XrdSecInterface.hh | 638 -- src/XrdSec/XrdSecLoadSecurity.cc | 296 - src/XrdSec/XrdSecLoadSecurity.hh | 130 - src/XrdSec/XrdSecPManager.cc | 369 -- src/XrdSec/XrdSecPManager.hh | 103 - src/XrdSec/XrdSecProtect.cc | 455 -- src/XrdSec/XrdSecProtect.hh | 168 - src/XrdSec/XrdSecProtector.cc | 312 - src/XrdSec/XrdSecProtector.hh | 162 - src/XrdSec/XrdSecProtocolhost.cc | 88 - src/XrdSec/XrdSecProtocolhost.hh | 67 - src/XrdSec/XrdSecServer.cc | 1066 ---- src/XrdSec/XrdSecServer.hh | 87 - src/XrdSec/XrdSecTLayer.cc | 361 -- src/XrdSec/XrdSecTLayer.hh | 159 - src/XrdSec/XrdSecTrace.hh | 65 - src/XrdSec/XrdSectestClient.cc | 202 - src/XrdSec/XrdSectestServer.cc | 333 - src/XrdSecgsi.cmake | 111 - src/XrdSecgsi/XrdSecProtocolgsi.cc | 5416 ----------------- src/XrdSecgsi/XrdSecProtocolgsi.hh | 525 -- src/XrdSecgsi/XrdSecgsiAuthzFunDN.cc | 191 - src/XrdSecgsi/XrdSecgsiAuthzFunVO.cc | 294 - src/XrdSecgsi/XrdSecgsiGMAPFunDN.cc | 243 - src/XrdSecgsi/XrdSecgsiGMAPFunDN.cf | 26 - src/XrdSecgsi/XrdSecgsiProxy.cc | 759 --- src/XrdSecgsi/XrdSecgsiTrace.hh | 65 - src/XrdSecgsi/XrdSecgsiVOMSFunLite.cc | 219 - src/XrdSecgsi/XrdSecgsitest.cc | 522 -- src/XrdSeckrb5.cmake | 30 - src/XrdSeckrb5/XrdSecProtocolkrb5.cc | 1049 ---- src/XrdSecpwd/XrdSecProtocolpwd.cc | 3873 ------------ src/XrdSecpwd/XrdSecProtocolpwd.hh | 422 -- src/XrdSecpwd/XrdSecpwdPlatform.hh | 50 - src/XrdSecpwd/XrdSecpwdSrvAdmin.cc | 2465 -------- src/XrdSecpwd/XrdSecpwdTrace.hh | 65 - src/XrdSecsss/XrdSecProtocolsss.cc | 934 --- src/XrdSecsss/XrdSecProtocolsss.hh | 125 - src/XrdSecsss/XrdSecsssAdmin.cc | 530 -- src/XrdSecsss/XrdSecsssID.cc | 250 - src/XrdSecsss/XrdSecsssID.hh | 116 - src/XrdSecsss/XrdSecsssKT.cc | 688 --- src/XrdSecsss/XrdSecsssKT.hh | 138 - src/XrdSecsss/XrdSecsssRR.hh | 78 - src/XrdSecunix/XrdSecProtocolunix.cc | 220 - src/XrdServer.cmake | 202 - src/XrdSfs/XrdSfsAio.hh | 92 - src/XrdSfs/XrdSfsDio.hh | 106 - src/XrdSfs/XrdSfsFlags.hh | 66 - src/XrdSfs/XrdSfsInterface.hh | 1077 ---- src/XrdSfs/XrdSfsNative.cc | 1051 ---- src/XrdSfs/XrdSfsNative.hh | 252 - src/XrdSfs/XrdSfsXio.hh | 124 - src/XrdSsi.cmake | 140 - src/XrdSsi/XrdSsiAlert.cc | 159 - src/XrdSsi/XrdSsiAlert.hh | 72 - src/XrdSsi/XrdSsiAtomics.hh | 175 - src/XrdSsi/XrdSsiBVec.hh | 65 - src/XrdSsi/XrdSsiClient.cc | 308 - src/XrdSsi/XrdSsiCluster.hh | 145 - src/XrdSsi/XrdSsiCms.cc | 77 - src/XrdSsi/XrdSsiCms.hh | 85 - src/XrdSsi/XrdSsiDir.cc | 205 - src/XrdSsi/XrdSsiDir.hh | 64 - src/XrdSsi/XrdSsiEntity.hh | 68 - src/XrdSsi/XrdSsiErrInfo.hh | 145 - src/XrdSsi/XrdSsiEvent.cc | 158 - src/XrdSsi/XrdSsiEvent.hh | 88 - src/XrdSsi/XrdSsiFile.cc | 638 -- src/XrdSsi/XrdSsiFile.hh | 114 - src/XrdSsi/XrdSsiFileReq.cc | 1003 --- src/XrdSsi/XrdSsiFileReq.hh | 171 - src/XrdSsi/XrdSsiFileResource.cc | 76 - src/XrdSsi/XrdSsiFileResource.hh | 55 - src/XrdSsi/XrdSsiFileSess.cc | 781 --- src/XrdSsi/XrdSsiFileSess.hh | 137 - src/XrdSsi/XrdSsiGCS.cc | 63 - src/XrdSsi/XrdSsiLogger.cc | 220 - src/XrdSsi/XrdSsiLogger.hh | 161 - src/XrdSsi/XrdSsiLogging.cc | 159 - src/XrdSsi/XrdSsiPacer.cc | 174 - src/XrdSsi/XrdSsiPacer.hh | 88 - src/XrdSsi/XrdSsiProvider.hh | 255 - src/XrdSsi/XrdSsiRRAgent.hh | 68 - src/XrdSsi/XrdSsiRRInfo.hh | 102 - src/XrdSsi/XrdSsiRRTable.hh | 98 - src/XrdSsi/XrdSsiRequest.cc | 203 - src/XrdSsi/XrdSsiRequest.hh | 364 -- src/XrdSsi/XrdSsiResource.hh | 108 - src/XrdSsi/XrdSsiRespInfo.hh | 131 - src/XrdSsi/XrdSsiResponder.cc | 336 - src/XrdSsi/XrdSsiResponder.hh | 267 - src/XrdSsi/XrdSsiScale.hh | 93 - src/XrdSsi/XrdSsiServReal.cc | 307 - src/XrdSsi/XrdSsiServReal.hh | 73 - src/XrdSsi/XrdSsiService.cc | 58 - src/XrdSsi/XrdSsiService.hh | 185 - src/XrdSsi/XrdSsiSessReal.cc | 454 -- src/XrdSsi/XrdSsiSessReal.hh | 106 - src/XrdSsi/XrdSsiSfs.cc | 520 -- src/XrdSsi/XrdSsiSfs.hh | 149 - src/XrdSsi/XrdSsiSfsConfig.cc | 738 --- src/XrdSsi/XrdSsiSfsConfig.hh | 81 - src/XrdSsi/XrdSsiShMam.cc | 1405 ----- src/XrdSsi/XrdSsiShMam.hh | 152 - src/XrdSsi/XrdSsiShMap.hh | 429 -- src/XrdSsi/XrdSsiShMap.icc | 368 -- src/XrdSsi/XrdSsiShMat.cc | 57 - src/XrdSsi/XrdSsiShMat.hh | 366 -- src/XrdSsi/XrdSsiStat.cc | 146 - src/XrdSsi/XrdSsiStream.hh | 162 - src/XrdSsi/XrdSsiTaskReal.cc | 767 --- src/XrdSsi/XrdSsiTaskReal.hh | 124 - src/XrdSsi/XrdSsiTrace.hh | 59 - src/XrdSsi/XrdSsiUtils.cc | 254 - src/XrdSsi/XrdSsiUtils.hh | 64 - src/XrdSut/XrdSutAux.cc | 646 -- src/XrdSut/XrdSutAux.hh | 250 - src/XrdSut/XrdSutBuckList.cc | 177 - src/XrdSut/XrdSutBuckList.hh | 91 - src/XrdSut/XrdSutBucket.cc | 243 - src/XrdSut/XrdSutBucket.hh | 73 - src/XrdSut/XrdSutBuffer.cc | 506 -- src/XrdSut/XrdSutBuffer.hh | 95 - src/XrdSut/XrdSutCache.hh | 154 - src/XrdSut/XrdSutCacheEntry.cc | 184 - src/XrdSut/XrdSutCacheEntry.hh | 129 - src/XrdSut/XrdSutPFCache.cc | 808 --- src/XrdSut/XrdSutPFCache.hh | 122 - src/XrdSut/XrdSutPFEntry.cc | 184 - src/XrdSut/XrdSutPFEntry.hh | 102 - src/XrdSut/XrdSutPFile.cc | 2308 ------- src/XrdSut/XrdSutPFile.hh | 198 - src/XrdSut/XrdSutRndm.cc | 259 - src/XrdSut/XrdSutRndm.hh | 67 - src/XrdSut/XrdSutTrace.hh | 63 - src/XrdSys/XrdSysAtomics.hh | 100 - src/XrdSys/XrdSysDNS.cc | 769 --- src/XrdSys/XrdSysDNS.hh | 245 - src/XrdSys/XrdSysDir.cc | 129 - src/XrdSys/XrdSysDir.hh | 62 - src/XrdSys/XrdSysError.cc | 182 - src/XrdSys/XrdSysError.hh | 175 - src/XrdSys/XrdSysFAttr.cc | 173 - src/XrdSys/XrdSysFAttr.hh | 92 - src/XrdSys/XrdSysFAttrBsd.icc | 177 - src/XrdSys/XrdSysFAttrLnx.icc | 173 - src/XrdSys/XrdSysFAttrMac.icc | 151 - src/XrdSys/XrdSysFAttrSun.icc | 199 - src/XrdSys/XrdSysFD.hh | 141 - src/XrdSys/XrdSysHeaders.hh | 51 - src/XrdSys/XrdSysIOEvents.cc | 1203 ---- src/XrdSys/XrdSysIOEvents.hh | 531 -- src/XrdSys/XrdSysIOEventsPollE.icc | 428 -- src/XrdSys/XrdSysIOEventsPollKQ.icc | 469 -- src/XrdSys/XrdSysIOEventsPollPoll.icc | 528 -- src/XrdSys/XrdSysIOEventsPollPort.icc | 394 -- src/XrdSys/XrdSysLinuxSemaphore.hh | 318 - src/XrdSys/XrdSysLogPI.hh | 94 - src/XrdSys/XrdSysLogger.cc | 860 --- src/XrdSys/XrdSysLogger.hh | 285 - src/XrdSys/XrdSysLogging.cc | 352 -- src/XrdSys/XrdSysLogging.hh | 122 - src/XrdSys/XrdSysPlatform.cc | 71 - src/XrdSys/XrdSysPlatform.hh | 272 - src/XrdSys/XrdSysPlugin.cc | 478 -- src/XrdSys/XrdSysPlugin.hh | 230 - src/XrdSys/XrdSysPriv.cc | 424 -- src/XrdSys/XrdSysPriv.hh | 99 - src/XrdSys/XrdSysPthread.cc | 324 - src/XrdSys/XrdSysPthread.hh | 451 -- src/XrdSys/XrdSysPwd.hh | 67 - src/XrdSys/XrdSysSemWait.hh | 124 - src/XrdSys/XrdSysTimer.cc | 272 - src/XrdSys/XrdSysTimer.hh | 88 - src/XrdSys/XrdSysTrace.cc | 394 -- src/XrdSys/XrdSysTrace.hh | 114 - src/XrdSys/XrdSysUtils.cc | 224 - src/XrdSys/XrdSysUtils.hh | 99 - src/XrdSys/XrdSysXAttr.cc | 116 - src/XrdSys/XrdSysXAttr.hh | 248 - src/XrdSys/XrdSysXSLock.cc | 134 - src/XrdSys/XrdSysXSLock.hh | 68 - src/XrdThrottle/README | 77 - src/XrdThrottle/XrdThrottle.hh | 254 - src/XrdThrottle/XrdThrottleFile.cc | 171 - src/XrdThrottle/XrdThrottleFileSystem.cc | 178 - .../XrdThrottleFileSystemConfig.cc | 351 -- src/XrdThrottle/XrdThrottleManager.cc | 375 -- src/XrdThrottle/XrdThrottleManager.hh | 199 - src/XrdThrottle/XrdThrottleTrace.hh | 42 - src/XrdUtils.cmake | 225 - src/XrdVersionPlugin.hh | 180 - src/XrdXml.cmake | 68 - src/XrdXml/XrdXmlMetaLink.cc | 590 -- src/XrdXml/XrdXmlMetaLink.hh | 184 - src/XrdXml/XrdXmlRdrTiny.cc | 300 - src/XrdXml/XrdXmlRdrTiny.hh | 75 - src/XrdXml/XrdXmlRdrXml2.cc | 297 - src/XrdXml/XrdXmlRdrXml2.hh | 75 - src/XrdXml/XrdXmlReader.cc | 106 - src/XrdXml/XrdXmlReader.hh | 170 - src/XrdXml/tinystr.cpp | 111 - src/XrdXml/tinystr.h | 305 - src/XrdXml/tinyxml.cpp | 1886 ------ src/XrdXml/tinyxml.h | 1805 ------ src/XrdXml/tinyxmlerror.cpp | 52 - src/XrdXml/tinyxmlparser.cpp | 1643 ----- src/XrdXrootd/XrdXrootdAdmin.cc | 774 --- src/XrdXrootd/XrdXrootdAdmin.hh | 102 - src/XrdXrootd/XrdXrootdAio.cc | 665 -- src/XrdXrootd/XrdXrootdAio.hh | 172 - src/XrdXrootd/XrdXrootdBridge.cc | 49 - src/XrdXrootd/XrdXrootdBridge.hh | 499 -- src/XrdXrootd/XrdXrootdCallBack.cc | 392 -- src/XrdXrootd/XrdXrootdCallBack.hh | 84 - src/XrdXrootd/XrdXrootdConfig.cc | 1700 ------ src/XrdXrootd/XrdXrootdFile.cc | 299 - src/XrdXrootd/XrdXrootdFile.hh | 126 - src/XrdXrootd/XrdXrootdFileLock.hh | 47 - src/XrdXrootd/XrdXrootdFileLock1.cc | 149 - src/XrdXrootd/XrdXrootdFileLock1.hh | 55 - src/XrdXrootd/XrdXrootdFileStats.hh | 129 - src/XrdXrootd/XrdXrootdJob.cc | 676 -- src/XrdXrootd/XrdXrootdJob.hh | 95 - src/XrdXrootd/XrdXrootdLoadLib.cc | 84 - src/XrdXrootd/XrdXrootdMonData.hh | 284 - src/XrdXrootd/XrdXrootdMonFMap.cc | 130 - src/XrdXrootd/XrdXrootdMonFMap.hh | 65 - src/XrdXrootd/XrdXrootdMonFile.cc | 531 -- src/XrdXrootd/XrdXrootdMonFile.hh | 101 - src/XrdXrootd/XrdXrootdMonitor.cc | 1089 ---- src/XrdXrootd/XrdXrootdMonitor.hh | 271 - src/XrdXrootd/XrdXrootdPio.cc | 85 - src/XrdXrootd/XrdXrootdPio.hh | 78 - src/XrdXrootd/XrdXrootdPlugin.cc | 93 - src/XrdXrootd/XrdXrootdPrepare.cc | 358 -- src/XrdXrootd/XrdXrootdPrepare.hh | 121 - src/XrdXrootd/XrdXrootdProtocol.cc | 875 --- src/XrdXrootd/XrdXrootdProtocol.hh | 422 -- src/XrdXrootd/XrdXrootdReqID.hh | 76 - src/XrdXrootd/XrdXrootdResponse.cc | 415 -- src/XrdXrootd/XrdXrootdResponse.hh | 107 - src/XrdXrootd/XrdXrootdStat.icc | 98 - src/XrdXrootd/XrdXrootdStats.cc | 166 - src/XrdXrootd/XrdXrootdStats.hh | 84 - src/XrdXrootd/XrdXrootdTrace.hh | 80 - src/XrdXrootd/XrdXrootdTransPend.cc | 116 - src/XrdXrootd/XrdXrootdTransPend.hh | 70 - src/XrdXrootd/XrdXrootdTransSend.cc | 88 - src/XrdXrootd/XrdXrootdTransSend.hh | 72 - src/XrdXrootd/XrdXrootdTransit.cc | 806 --- src/XrdXrootd/XrdXrootdTransit.hh | 214 - src/XrdXrootd/XrdXrootdXPath.hh | 102 - src/XrdXrootd/XrdXrootdXeq.cc | 3553 ----------- src/XrdXrootd/XrdXrootdXeqAio.cc | 248 - tests/CMakeLists.txt | 9 +- tests/XrdCephTests/CMakeLists.txt | 6 +- tests/XrdClTests/CMakeLists.txt | 44 - tests/XrdClTests/FileCopyTest.cc | 498 -- tests/XrdClTests/FileSystemTest.cc | 536 -- tests/XrdClTests/FileTest.cc | 720 --- tests/XrdClTests/IdentityPlugIn.cc | 488 -- tests/XrdClTests/IdentityPlugIn.hh | 55 - tests/XrdClTests/LocalFileHandlerTest.cc | 465 -- tests/XrdClTests/MonitorTestLib.cc | 212 - tests/XrdClTests/PollerTest.cc | 280 - tests/XrdClTests/PostMasterTest.cc | 473 -- tests/XrdClTests/SocketTest.cc | 236 - tests/XrdClTests/ThreadingTest.cc | 348 -- tests/XrdClTests/UtilsTest.cc | 461 -- tests/XrdClTests/XRootDProtocolHelper.cc | 118 - tests/XrdClTests/XRootDProtocolHelper.hh | 45 - tests/XrdClTests/cppunit.supp | 17 - tests/XrdClTests/printenv.sh | 24 - tests/XrdSsiTests/CMakeLists.txt | 20 - tests/XrdSsiTests/XrdShMap.cc | 1050 ---- tests/common/CMakeLists.txt | 27 - tests/common/CppUnitXrdHelpers.hh | 57 - tests/common/PathProcessor.hh | 83 - tests/common/Server.cc | 372 -- tests/common/Server.hh | 201 - tests/common/TestEnv.cc | 117 - tests/common/TestEnv.hh | 62 - tests/common/TextRunner.cc | 154 - tests/common/Utils.cc | 69 - tests/common/Utils.hh | 98 - ups/eupspkg.cfg.sh | 37 - ups/xrootd.table | 5 - utils/XrdCmsNotify.pm | 198 - utils/XrdOlbMonPerf | 283 - utils/hpsscp | 820 --- utils/netchk | 124 - utils/xrootd-config | 79 - 1250 files changed, 223 insertions(+), 343136 deletions(-) delete mode 100644 bindings/CMakeLists.txt delete mode 100644 bindings/python/.gitattributes delete mode 100755 bindings/python/.gitignore delete mode 100644 bindings/python/CMakeLists.txt delete mode 100644 bindings/python/MANIFEST.in delete mode 100644 bindings/python/README.rst delete mode 100644 bindings/python/docs/Makefile delete mode 100644 bindings/python/docs/ReleaseNotes.txt delete mode 100755 bindings/python/docs/source/.static/css/custom.css delete mode 100644 bindings/python/docs/source/.static/img/favicon.ico delete mode 100644 bindings/python/docs/source/.static/img/xrootd-200x68.png delete mode 100755 bindings/python/docs/source/.templates/layout.html delete mode 100644 bindings/python/docs/source/conf.py delete mode 100644 bindings/python/docs/source/examples.rst delete mode 100644 bindings/python/docs/source/examples/copyprocess.rst delete mode 100644 bindings/python/docs/source/examples/file.rst delete mode 100644 bindings/python/docs/source/examples/filesystem.rst delete mode 100644 bindings/python/docs/source/gettingstarted.rst delete mode 100644 bindings/python/docs/source/index.rst delete mode 100644 bindings/python/docs/source/install.rst delete mode 100644 bindings/python/docs/source/modules/client/copyprocess.rst delete mode 100644 bindings/python/docs/source/modules/client/file.rst delete mode 100644 bindings/python/docs/source/modules/client/filesystem.rst delete mode 100644 bindings/python/docs/source/modules/client/flags.rst delete mode 100644 bindings/python/docs/source/modules/client/responses.rst delete mode 100644 bindings/python/docs/source/modules/client/url.rst delete mode 100644 bindings/python/docs/source/modules/client/utils.rst delete mode 100644 bindings/python/examples/copy.py delete mode 100644 bindings/python/examples/copyprocess.py delete mode 100644 bindings/python/examples/dirlist.py delete mode 100644 bindings/python/examples/fcntl.py delete mode 100644 bindings/python/examples/fcntl_async.py delete mode 100644 bindings/python/examples/fileproperties.py delete mode 100644 bindings/python/examples/filesystemproperties.py delete mode 100644 bindings/python/examples/iterate.py delete mode 100644 bindings/python/examples/locate.py delete mode 100644 bindings/python/examples/mkdir.py delete mode 100644 bindings/python/examples/mv.py delete mode 100644 bindings/python/examples/query.py delete mode 100644 bindings/python/examples/read.py delete mode 100644 bindings/python/examples/readchunks.py delete mode 100644 bindings/python/examples/readlines.py delete mode 100644 bindings/python/examples/rm.py delete mode 100644 bindings/python/examples/rmdir.py delete mode 100644 bindings/python/examples/vector_read.py delete mode 100644 bindings/python/examples/visa.py delete mode 100644 bindings/python/examples/visa_async.py delete mode 100644 bindings/python/examples/write.py delete mode 100644 bindings/python/libs/__init__.py delete mode 100644 bindings/python/libs/client/__init__.py delete mode 100644 bindings/python/libs/client/copyprocess.py delete mode 100644 bindings/python/libs/client/file.py delete mode 100644 bindings/python/libs/client/filesystem.py delete mode 100644 bindings/python/libs/client/flags.py delete mode 100644 bindings/python/libs/client/responses.py delete mode 100644 bindings/python/libs/client/url.py delete mode 100644 bindings/python/libs/client/utils.py delete mode 100644 bindings/python/setup.py.in delete mode 100644 bindings/python/setup_pypi.py delete mode 100644 bindings/python/src/AsyncResponseHandler.hh delete mode 100644 bindings/python/src/ChunkIterator.hh delete mode 100644 bindings/python/src/Conversions.hh delete mode 100644 bindings/python/src/PyXRootD.hh delete mode 100644 bindings/python/src/PyXRootDCopyProcess.cc delete mode 100644 bindings/python/src/PyXRootDCopyProcess.hh delete mode 100644 bindings/python/src/PyXRootDCopyProgressHandler.cc delete mode 100644 bindings/python/src/PyXRootDCopyProgressHandler.hh delete mode 100644 bindings/python/src/PyXRootDFile.cc delete mode 100644 bindings/python/src/PyXRootDFile.hh delete mode 100644 bindings/python/src/PyXRootDFileSystem.cc delete mode 100644 bindings/python/src/PyXRootDFileSystem.hh delete mode 100644 bindings/python/src/PyXRootDModule.cc delete mode 100644 bindings/python/src/PyXRootDURL.cc delete mode 100644 bindings/python/src/PyXRootDURL.hh delete mode 100644 bindings/python/src/Utils.cc delete mode 100644 bindings/python/src/Utils.hh delete mode 100644 bindings/python/src/__init__.py delete mode 100644 bindings/python/tests/env.py delete mode 100644 bindings/python/tests/test_copy.py delete mode 100644 bindings/python/tests/test_file.py delete mode 100644 bindings/python/tests/test_filesystem.py delete mode 100644 bindings/python/tests/test_threads.py delete mode 100644 bindings/python/tests/test_url.py delete mode 100644 cmake/FindKerberos5.cmake delete mode 100644 cmake/FindOpenSSL.cmake delete mode 100644 cmake/FindReadline.cmake delete mode 100644 cmake/FindSystemd.cmake create mode 100644 cmake/FindXRootD.cmake delete mode 100644 cmake/Findfuse.cmake delete mode 100644 cmake/XRootDCommon.cmake delete mode 100644 cmake/XRootDSystemCheck.cmake delete mode 100644 docs/README_IPV4_To_IPV6 delete mode 100644 docs/man/XrdCnsd.8 delete mode 100644 docs/man/cmsd.8 delete mode 100644 docs/man/cns_ssi.8 delete mode 100644 docs/man/frm_admin.8 delete mode 100644 docs/man/frm_purged.8 delete mode 100644 docs/man/frm_xfragent.8 delete mode 100644 docs/man/frm_xfrd.8 delete mode 100644 docs/man/mpxstats.8 delete mode 100644 docs/man/xprep.1 delete mode 100644 docs/man/xrd.1 delete mode 100644 docs/man/xrdadler32.1 delete mode 100644 docs/man/xrdcp-old.1 delete mode 100644 docs/man/xrdcp.1 delete mode 100644 docs/man/xrdfs.1 delete mode 100644 docs/man/xrdgsiproxy.1 delete mode 100644 docs/man/xrdgsitest.1 delete mode 100644 docs/man/xrdpfc_print.8 delete mode 100644 docs/man/xrdpwdadmin.8 delete mode 100644 docs/man/xrdsssadmin.8 delete mode 100644 docs/man/xrdstagetool.1 delete mode 100644 docs/man/xrootd.8 delete mode 100644 docs/man/xrootdfs.1 delete mode 100755 dopy.sh delete mode 100644 packaging/common/client-plugin.conf.example delete mode 100644 packaging/common/client.conf delete mode 100644 packaging/common/cmsd@.service delete mode 100644 packaging/common/frm_purged@.service delete mode 100644 packaging/common/frm_xfrd@.service delete mode 100644 packaging/common/xrdhttp@.socket delete mode 100644 packaging/common/xrootd-clustered.cfg delete mode 100644 packaging/common/xrootd-filecache-clustered.cfg delete mode 100644 packaging/common/xrootd-filecache-standalone.cfg delete mode 100644 packaging/common/xrootd-http.cfg delete mode 100644 packaging/common/xrootd-standalone.cfg delete mode 100644 packaging/common/xrootd.logrotate delete mode 100644 packaging/common/xrootd.te delete mode 100644 packaging/common/xrootd@.service delete mode 100644 packaging/common/xrootd@.socket delete mode 100644 packaging/rhel/cmsd.init delete mode 100644 packaging/rhel/frm_purged.init delete mode 100644 packaging/rhel/frm_xfrd.init create mode 100644 packaging/rhel/xrootd-ceph.spec.in delete mode 100644 packaging/rhel/xrootd.functions delete mode 100644 packaging/rhel/xrootd.functions-slc4 delete mode 100644 packaging/rhel/xrootd.init delete mode 100644 packaging/rhel/xrootd.spec.in delete mode 100644 packaging/rhel/xrootd.sysconfig delete mode 100644 packaging/rhel/xrootd.tmpfiles delete mode 100644 packaging/tgz/README delete mode 100755 packaging/tgz/StartCMS delete mode 100755 packaging/tgz/StartFRM delete mode 100755 packaging/tgz/StartOLB delete mode 100644 packaging/tgz/StartOLB.cf.example delete mode 100755 packaging/tgz/StartXRD delete mode 100644 packaging/tgz/StartXRD.cf.example delete mode 100755 packaging/tgz/StopCMS delete mode 100755 packaging/tgz/StopFRM delete mode 100755 packaging/tgz/StopOLB delete mode 100755 packaging/tgz/StopXRD delete mode 100644 packaging/tgz/xrootd.cf.example delete mode 100644 packaging/tgz/xrootd.cf.example2 delete mode 100644 src/XProtocol/README delete mode 100644 src/XProtocol/XProtocol.cc delete mode 100644 src/XProtocol/XProtocol.hh delete mode 100644 src/XProtocol/XPtypes.hh delete mode 100644 src/XProtocol/YProtocol.hh delete mode 100644 src/Xrd/XrdBuffXL.cc delete mode 100644 src/Xrd/XrdBuffXL.hh delete mode 100644 src/Xrd/XrdBuffer.cc delete mode 100644 src/Xrd/XrdBuffer.hh delete mode 100644 src/Xrd/XrdConfig.cc delete mode 100644 src/Xrd/XrdConfig.hh delete mode 100644 src/Xrd/XrdInet.cc delete mode 100644 src/Xrd/XrdInet.hh delete mode 100644 src/Xrd/XrdInfo.cc delete mode 100644 src/Xrd/XrdInfo.hh delete mode 100644 src/Xrd/XrdJob.hh delete mode 100644 src/Xrd/XrdLink.cc delete mode 100644 src/Xrd/XrdLink.hh delete mode 100644 src/Xrd/XrdLinkMatch.cc delete mode 100644 src/Xrd/XrdLinkMatch.hh delete mode 100644 src/Xrd/XrdMain.cc delete mode 100644 src/Xrd/XrdObject.hh delete mode 100644 src/Xrd/XrdObject.icc delete mode 100644 src/Xrd/XrdPoll.cc delete mode 100644 src/Xrd/XrdPoll.hh delete mode 100644 src/Xrd/XrdPollDev.hh delete mode 100644 src/Xrd/XrdPollDev.icc delete mode 100644 src/Xrd/XrdPollE.hh delete mode 100644 src/Xrd/XrdPollE.icc delete mode 100644 src/Xrd/XrdPollPoll.hh delete mode 100644 src/Xrd/XrdPollPoll.icc delete mode 100644 src/Xrd/XrdProtLoad.cc delete mode 100644 src/Xrd/XrdProtLoad.hh delete mode 100644 src/Xrd/XrdProtocol.cc delete mode 100644 src/Xrd/XrdProtocol.hh delete mode 100644 src/Xrd/XrdScheduler.cc delete mode 100644 src/Xrd/XrdScheduler.hh delete mode 100644 src/Xrd/XrdSendQ.cc delete mode 100644 src/Xrd/XrdSendQ.hh delete mode 100644 src/Xrd/XrdStats.cc delete mode 100644 src/Xrd/XrdStats.hh delete mode 100644 src/Xrd/XrdTrace.hh delete mode 100644 src/XrdAcc/XrdAccAccess.cc delete mode 100644 src/XrdAcc/XrdAccAccess.hh delete mode 100644 src/XrdAcc/XrdAccAudit.cc delete mode 100644 src/XrdAcc/XrdAccAudit.hh delete mode 100644 src/XrdAcc/XrdAccAuthDB.hh delete mode 100644 src/XrdAcc/XrdAccAuthFile.cc delete mode 100644 src/XrdAcc/XrdAccAuthFile.hh delete mode 100644 src/XrdAcc/XrdAccAuthorize.hh delete mode 100644 src/XrdAcc/XrdAccCapability.cc delete mode 100644 src/XrdAcc/XrdAccCapability.hh delete mode 100644 src/XrdAcc/XrdAccConfig.cc delete mode 100644 src/XrdAcc/XrdAccConfig.hh delete mode 100644 src/XrdAcc/XrdAccGroups.cc delete mode 100644 src/XrdAcc/XrdAccGroups.hh delete mode 100644 src/XrdAcc/XrdAccPrivs.hh delete mode 100644 src/XrdApps.cmake delete mode 100644 src/XrdApps/XrdAccTest.cc delete mode 100644 src/XrdApps/XrdAppsCconfig.cc delete mode 100644 src/XrdApps/XrdClProxyPlugin/ProxyPrefixFile.cc delete mode 100644 src/XrdApps/XrdClProxyPlugin/ProxyPrefixFile.hh delete mode 100644 src/XrdApps/XrdClProxyPlugin/ProxyPrefixPlugin.cc delete mode 100644 src/XrdApps/XrdClProxyPlugin/ProxyPrefixPlugin.hh delete mode 100644 src/XrdApps/XrdClProxyPlugin/README.md delete mode 100644 src/XrdApps/XrdCpConfig.cc delete mode 100644 src/XrdApps/XrdCpConfig.hh delete mode 100644 src/XrdApps/XrdCpFile.cc delete mode 100644 src/XrdApps/XrdCpFile.hh delete mode 100644 src/XrdApps/XrdCpy.cc delete mode 100644 src/XrdApps/XrdMapCluster.cc delete mode 100644 src/XrdApps/XrdMpxStats.cc delete mode 100644 src/XrdApps/XrdMpxXml.cc delete mode 100644 src/XrdApps/XrdMpxXml.hh delete mode 100644 src/XrdApps/XrdQStats.cc delete mode 100644 src/XrdApps/XrdWait41.cc delete mode 100644 src/XrdApps/Xrdadler32.cc delete mode 100644 src/XrdBwm/XrdBwm.cc delete mode 100644 src/XrdBwm/XrdBwm.hh delete mode 100644 src/XrdBwm/XrdBwmConfig.cc delete mode 100644 src/XrdBwm/XrdBwmHandle.cc delete mode 100644 src/XrdBwm/XrdBwmHandle.hh delete mode 100644 src/XrdBwm/XrdBwmLogger.cc delete mode 100644 src/XrdBwm/XrdBwmLogger.hh delete mode 100644 src/XrdBwm/XrdBwmPolicy.hh delete mode 100644 src/XrdBwm/XrdBwmPolicy1.cc delete mode 100644 src/XrdBwm/XrdBwmPolicy1.hh delete mode 100644 src/XrdBwm/XrdBwmTrace.hh delete mode 100644 src/XrdCks/XrdCks.hh delete mode 100644 src/XrdCks/XrdCksAssist.cc delete mode 100644 src/XrdCks/XrdCksAssist.hh delete mode 100644 src/XrdCks/XrdCksCalc.hh delete mode 100644 src/XrdCks/XrdCksCalcadler32.hh delete mode 100644 src/XrdCks/XrdCksCalccrc32.cc delete mode 100644 src/XrdCks/XrdCksCalccrc32.hh delete mode 100644 src/XrdCks/XrdCksCalcmd5.cc delete mode 100644 src/XrdCks/XrdCksCalcmd5.hh delete mode 100644 src/XrdCks/XrdCksCalczcrc32.cc delete mode 100644 src/XrdCks/XrdCksConfig.cc delete mode 100644 src/XrdCks/XrdCksConfig.hh delete mode 100644 src/XrdCks/XrdCksData.hh delete mode 100644 src/XrdCks/XrdCksLoad.hh delete mode 100644 src/XrdCks/XrdCksLoader.cc delete mode 100644 src/XrdCks/XrdCksLoader.hh delete mode 100644 src/XrdCks/XrdCksManOss.cc delete mode 100644 src/XrdCks/XrdCksManOss.hh delete mode 100644 src/XrdCks/XrdCksManager.cc delete mode 100644 src/XrdCks/XrdCksManager.hh delete mode 100644 src/XrdCks/XrdCksXAttr.hh delete mode 100644 src/XrdCl/CMakeLists.txt delete mode 100644 src/XrdCl/XrdClAnyObject.hh delete mode 100644 src/XrdCl/XrdClAsyncSocketHandler.cc delete mode 100644 src/XrdCl/XrdClAsyncSocketHandler.hh delete mode 100644 src/XrdCl/XrdClBuffer.hh delete mode 100644 src/XrdCl/XrdClChannel.cc delete mode 100644 src/XrdCl/XrdClChannel.hh delete mode 100644 src/XrdCl/XrdClChannelHandlerList.cc delete mode 100644 src/XrdCl/XrdClChannelHandlerList.hh delete mode 100644 src/XrdCl/XrdClCheckSumManager.cc delete mode 100644 src/XrdCl/XrdClCheckSumManager.hh delete mode 100644 src/XrdCl/XrdClClassicCopyJob.cc delete mode 100644 src/XrdCl/XrdClClassicCopyJob.hh delete mode 100644 src/XrdCl/XrdClConstants.hh delete mode 100644 src/XrdCl/XrdClCopy.cc delete mode 100644 src/XrdCl/XrdClCopyJob.hh delete mode 100644 src/XrdCl/XrdClCopyProcess.cc delete mode 100644 src/XrdCl/XrdClCopyProcess.hh delete mode 100644 src/XrdCl/XrdClDefaultEnv.cc delete mode 100644 src/XrdCl/XrdClDefaultEnv.hh delete mode 100644 src/XrdCl/XrdClEnv.cc delete mode 100644 src/XrdCl/XrdClEnv.hh delete mode 100644 src/XrdCl/XrdClFS.cc delete mode 100644 src/XrdCl/XrdClFSExecutor.cc delete mode 100644 src/XrdCl/XrdClFSExecutor.hh delete mode 100644 src/XrdCl/XrdClFile.cc delete mode 100644 src/XrdCl/XrdClFile.hh delete mode 100644 src/XrdCl/XrdClFileStateHandler.cc delete mode 100644 src/XrdCl/XrdClFileStateHandler.hh delete mode 100644 src/XrdCl/XrdClFileSystem.cc delete mode 100644 src/XrdCl/XrdClFileSystem.hh delete mode 100644 src/XrdCl/XrdClFileSystemUtils.cc delete mode 100644 src/XrdCl/XrdClFileSystemUtils.hh delete mode 100644 src/XrdCl/XrdClFileTimer.cc delete mode 100644 src/XrdCl/XrdClFileTimer.hh delete mode 100644 src/XrdCl/XrdClForkHandler.cc delete mode 100644 src/XrdCl/XrdClForkHandler.hh delete mode 100644 src/XrdCl/XrdClInQueue.cc delete mode 100644 src/XrdCl/XrdClInQueue.hh delete mode 100644 src/XrdCl/XrdClJobManager.cc delete mode 100644 src/XrdCl/XrdClJobManager.hh delete mode 100644 src/XrdCl/XrdClLocalFileHandler.cc delete mode 100644 src/XrdCl/XrdClLocalFileHandler.hh delete mode 100644 src/XrdCl/XrdClLocalFileTask.cc delete mode 100644 src/XrdCl/XrdClLocalFileTask.hh delete mode 100644 src/XrdCl/XrdClLog.cc delete mode 100644 src/XrdCl/XrdClLog.hh delete mode 100644 src/XrdCl/XrdClMessage.hh delete mode 100644 src/XrdCl/XrdClMessageUtils.cc delete mode 100644 src/XrdCl/XrdClMessageUtils.hh delete mode 100644 src/XrdCl/XrdClMetalinkRedirector.cc delete mode 100644 src/XrdCl/XrdClMetalinkRedirector.hh delete mode 100644 src/XrdCl/XrdClMonitor.hh delete mode 100644 src/XrdCl/XrdClOptimizers.hh delete mode 100644 src/XrdCl/XrdClOutQueue.cc delete mode 100644 src/XrdCl/XrdClOutQueue.hh delete mode 100644 src/XrdCl/XrdClPlugInInterface.hh delete mode 100644 src/XrdCl/XrdClPlugInManager.cc delete mode 100644 src/XrdCl/XrdClPlugInManager.hh delete mode 100644 src/XrdCl/XrdClPoller.hh delete mode 100644 src/XrdCl/XrdClPollerBuiltIn.cc delete mode 100644 src/XrdCl/XrdClPollerBuiltIn.hh delete mode 100644 src/XrdCl/XrdClPollerFactory.cc delete mode 100644 src/XrdCl/XrdClPollerFactory.hh delete mode 100644 src/XrdCl/XrdClPostMaster.cc delete mode 100644 src/XrdCl/XrdClPostMaster.hh delete mode 100644 src/XrdCl/XrdClPostMasterInterfaces.hh delete mode 100644 src/XrdCl/XrdClPropertyList.hh delete mode 100644 src/XrdCl/XrdClRedirectorRegistry.cc delete mode 100644 src/XrdCl/XrdClRedirectorRegistry.hh delete mode 100644 src/XrdCl/XrdClRequestSync.hh delete mode 100644 src/XrdCl/XrdClResponseJob.hh delete mode 100644 src/XrdCl/XrdClSIDManager.cc delete mode 100644 src/XrdCl/XrdClSIDManager.hh delete mode 100644 src/XrdCl/XrdClSocket.cc delete mode 100644 src/XrdCl/XrdClSocket.hh delete mode 100644 src/XrdCl/XrdClStatus.cc delete mode 100644 src/XrdCl/XrdClStatus.hh delete mode 100644 src/XrdCl/XrdClStream.cc delete mode 100644 src/XrdCl/XrdClStream.hh delete mode 100644 src/XrdCl/XrdClSyncQueue.hh delete mode 100644 src/XrdCl/XrdClTPFallBackCopyJob.cc delete mode 100644 src/XrdCl/XrdClTPFallBackCopyJob.hh delete mode 100644 src/XrdCl/XrdClTaskManager.cc delete mode 100644 src/XrdCl/XrdClTaskManager.hh delete mode 100644 src/XrdCl/XrdClThirdPartyCopyJob.cc delete mode 100644 src/XrdCl/XrdClThirdPartyCopyJob.hh delete mode 100644 src/XrdCl/XrdClTransportManager.cc delete mode 100644 src/XrdCl/XrdClTransportManager.hh delete mode 100644 src/XrdCl/XrdClURL.cc delete mode 100644 src/XrdCl/XrdClURL.hh delete mode 100644 src/XrdCl/XrdClUglyHacks.hh delete mode 100644 src/XrdCl/XrdClUtils.cc delete mode 100644 src/XrdCl/XrdClUtils.hh delete mode 100644 src/XrdCl/XrdClXCpCtx.cc delete mode 100644 src/XrdCl/XrdClXCpCtx.hh delete mode 100644 src/XrdCl/XrdClXCpSrc.cc delete mode 100644 src/XrdCl/XrdClXCpSrc.hh delete mode 100644 src/XrdCl/XrdClXRootDMsgHandler.cc delete mode 100644 src/XrdCl/XrdClXRootDMsgHandler.hh delete mode 100644 src/XrdCl/XrdClXRootDResponses.cc delete mode 100644 src/XrdCl/XrdClXRootDResponses.hh delete mode 100644 src/XrdCl/XrdClXRootDTransport.cc delete mode 100644 src/XrdCl/XrdClXRootDTransport.hh delete mode 100644 src/XrdCl/XrdClZipArchiveReader.cc delete mode 100644 src/XrdCl/XrdClZipArchiveReader.hh delete mode 100644 src/XrdClient.cmake delete mode 100644 src/XrdClient/README_Xrdcp delete mode 100644 src/XrdClient/README_params delete mode 100644 src/XrdClient/TestXrdClient.cc delete mode 100644 src/XrdClient/TestXrdClient_read.cc delete mode 100644 src/XrdClient/XrdClient.cc delete mode 100644 src/XrdClient/XrdClient.hh delete mode 100644 src/XrdClient/XrdClientAbs.cc delete mode 100644 src/XrdClient/XrdClientAbs.hh delete mode 100644 src/XrdClient/XrdClientAbsMonIntf.hh delete mode 100644 src/XrdClient/XrdClientAdmin.cc delete mode 100644 src/XrdClient/XrdClientAdmin.hh delete mode 100644 src/XrdClient/XrdClientCallback.hh delete mode 100644 src/XrdClient/XrdClientConn.cc delete mode 100644 src/XrdClient/XrdClientConn.hh delete mode 100644 src/XrdClient/XrdClientConnMgr.cc delete mode 100644 src/XrdClient/XrdClientConnMgr.hh delete mode 100644 src/XrdClient/XrdClientConst.hh delete mode 100644 src/XrdClient/XrdClientDebug.cc delete mode 100644 src/XrdClient/XrdClientDebug.hh delete mode 100644 src/XrdClient/XrdClientEnv.cc delete mode 100644 src/XrdClient/XrdClientEnv.hh delete mode 100644 src/XrdClient/XrdClientInputBuffer.cc delete mode 100644 src/XrdClient/XrdClientInputBuffer.hh delete mode 100644 src/XrdClient/XrdClientLogConnection.cc delete mode 100644 src/XrdClient/XrdClientLogConnection.hh delete mode 100644 src/XrdClient/XrdClientMStream.cc delete mode 100644 src/XrdClient/XrdClientMStream.hh delete mode 100644 src/XrdClient/XrdClientMessage.cc delete mode 100644 src/XrdClient/XrdClientMessage.hh delete mode 100644 src/XrdClient/XrdClientPSock.cc delete mode 100644 src/XrdClient/XrdClientPSock.hh delete mode 100644 src/XrdClient/XrdClientPhyConnection.cc delete mode 100644 src/XrdClient/XrdClientPhyConnection.hh delete mode 100644 src/XrdClient/XrdClientPrep.cc delete mode 100644 src/XrdClient/XrdClientProtocol.cc delete mode 100644 src/XrdClient/XrdClientProtocol.hh delete mode 100644 src/XrdClient/XrdClientReadAhead.cc delete mode 100644 src/XrdClient/XrdClientReadAhead.hh delete mode 100644 src/XrdClient/XrdClientReadCache.cc delete mode 100644 src/XrdClient/XrdClientReadCache.hh delete mode 100644 src/XrdClient/XrdClientReadV.cc delete mode 100644 src/XrdClient/XrdClientReadV.hh delete mode 100644 src/XrdClient/XrdClientSid.cc delete mode 100644 src/XrdClient/XrdClientSid.hh delete mode 100644 src/XrdClient/XrdClientSock.cc delete mode 100644 src/XrdClient/XrdClientSock.hh delete mode 100644 src/XrdClient/XrdClientThread.cc delete mode 100644 src/XrdClient/XrdClientThread.hh delete mode 100644 src/XrdClient/XrdClientUnsolMsg.hh delete mode 100644 src/XrdClient/XrdClientUrlInfo.cc delete mode 100644 src/XrdClient/XrdClientUrlInfo.hh delete mode 100644 src/XrdClient/XrdClientUrlSet.cc delete mode 100644 src/XrdClient/XrdClientUrlSet.hh delete mode 100644 src/XrdClient/XrdClientVector.hh delete mode 100644 src/XrdClient/XrdCommandLine.cc delete mode 100644 src/XrdClient/XrdCpMthrQueue.cc delete mode 100644 src/XrdClient/XrdCpMthrQueue.hh delete mode 100644 src/XrdClient/XrdCpWorkLst.cc delete mode 100644 src/XrdClient/XrdCpWorkLst.hh delete mode 100644 src/XrdClient/XrdStageTool.cc delete mode 100644 src/XrdClient/XrdcpXtremeRead.cc delete mode 100644 src/XrdClient/XrdcpXtremeRead.hh delete mode 100755 src/XrdClient/tinytestXTNetAdmin.pl delete mode 100755 src/XrdClient/xrdadmin delete mode 100644 src/XrdCms/XrdCmsAdmin.cc delete mode 100644 src/XrdCms/XrdCmsAdmin.hh delete mode 100644 src/XrdCms/XrdCmsBaseFS.cc delete mode 100644 src/XrdCms/XrdCmsBaseFS.hh delete mode 100644 src/XrdCms/XrdCmsBlackList.cc delete mode 100644 src/XrdCms/XrdCmsBlackList.hh delete mode 100644 src/XrdCms/XrdCmsCache.cc delete mode 100644 src/XrdCms/XrdCmsCache.hh delete mode 100644 src/XrdCms/XrdCmsClient.cc delete mode 100644 src/XrdCms/XrdCmsClient.hh delete mode 100644 src/XrdCms/XrdCmsClientConfig.cc delete mode 100644 src/XrdCms/XrdCmsClientConfig.hh delete mode 100644 src/XrdCms/XrdCmsClientMan.cc delete mode 100644 src/XrdCms/XrdCmsClientMan.hh delete mode 100644 src/XrdCms/XrdCmsClientMsg.cc delete mode 100644 src/XrdCms/XrdCmsClientMsg.hh delete mode 100644 src/XrdCms/XrdCmsClustID.cc delete mode 100644 src/XrdCms/XrdCmsClustID.hh delete mode 100644 src/XrdCms/XrdCmsCluster.cc delete mode 100644 src/XrdCms/XrdCmsCluster.hh delete mode 100644 src/XrdCms/XrdCmsConfig.cc delete mode 100644 src/XrdCms/XrdCmsConfig.hh delete mode 100644 src/XrdCms/XrdCmsFinder.cc delete mode 100644 src/XrdCms/XrdCmsFinder.hh delete mode 100644 src/XrdCms/XrdCmsJob.cc delete mode 100644 src/XrdCms/XrdCmsJob.hh delete mode 100644 src/XrdCms/XrdCmsKey.cc delete mode 100644 src/XrdCms/XrdCmsKey.hh delete mode 100644 src/XrdCms/XrdCmsLogin.cc delete mode 100644 src/XrdCms/XrdCmsLogin.hh delete mode 100644 src/XrdCms/XrdCmsManList.cc delete mode 100644 src/XrdCms/XrdCmsManList.hh delete mode 100644 src/XrdCms/XrdCmsManTree.cc delete mode 100644 src/XrdCms/XrdCmsManTree.hh delete mode 100644 src/XrdCms/XrdCmsManager.cc delete mode 100644 src/XrdCms/XrdCmsManager.hh delete mode 100644 src/XrdCms/XrdCmsMeter.cc delete mode 100644 src/XrdCms/XrdCmsMeter.hh delete mode 100644 src/XrdCms/XrdCmsNash.cc delete mode 100644 src/XrdCms/XrdCmsNash.hh delete mode 100644 src/XrdCms/XrdCmsNode.cc delete mode 100644 src/XrdCms/XrdCmsNode.hh delete mode 100644 src/XrdCms/XrdCmsPList.cc delete mode 100644 src/XrdCms/XrdCmsPList.hh delete mode 100644 src/XrdCms/XrdCmsParser.cc delete mode 100644 src/XrdCms/XrdCmsParser.hh delete mode 100644 src/XrdCms/XrdCmsPrepArgs.cc delete mode 100644 src/XrdCms/XrdCmsPrepArgs.hh delete mode 100644 src/XrdCms/XrdCmsPrepare.cc delete mode 100644 src/XrdCms/XrdCmsPrepare.hh delete mode 100644 src/XrdCms/XrdCmsProtocol.cc delete mode 100644 src/XrdCms/XrdCmsProtocol.hh delete mode 100644 src/XrdCms/XrdCmsRRData.cc delete mode 100644 src/XrdCms/XrdCmsRRData.hh delete mode 100644 src/XrdCms/XrdCmsRRQ.cc delete mode 100644 src/XrdCms/XrdCmsRRQ.hh delete mode 100644 src/XrdCms/XrdCmsRTable.cc delete mode 100644 src/XrdCms/XrdCmsRTable.hh delete mode 100644 src/XrdCms/XrdCmsReq.cc delete mode 100644 src/XrdCms/XrdCmsReq.hh delete mode 100644 src/XrdCms/XrdCmsResp.cc delete mode 100644 src/XrdCms/XrdCmsResp.hh delete mode 100644 src/XrdCms/XrdCmsRole.hh delete mode 100644 src/XrdCms/XrdCmsRouting.cc delete mode 100644 src/XrdCms/XrdCmsRouting.hh delete mode 100644 src/XrdCms/XrdCmsSecurity.cc delete mode 100644 src/XrdCms/XrdCmsSecurity.hh delete mode 100644 src/XrdCms/XrdCmsSelect.hh delete mode 100644 src/XrdCms/XrdCmsState.cc delete mode 100644 src/XrdCms/XrdCmsState.hh delete mode 100644 src/XrdCms/XrdCmsSupervisor.cc delete mode 100644 src/XrdCms/XrdCmsSupervisor.hh delete mode 100644 src/XrdCms/XrdCmsTalk.cc delete mode 100644 src/XrdCms/XrdCmsTalk.hh delete mode 100644 src/XrdCms/XrdCmsTrace.hh delete mode 100644 src/XrdCms/XrdCmsTypes.hh delete mode 100644 src/XrdCms/XrdCmsUtils.cc delete mode 100644 src/XrdCms/XrdCmsUtils.hh delete mode 100644 src/XrdCms/XrdCmsVnId.hh delete mode 100644 src/XrdCns.cmake delete mode 100644 src/XrdCns/README delete mode 100644 src/XrdCns/XrdCnsConfig.cc delete mode 100644 src/XrdCns/XrdCnsConfig.hh delete mode 100644 src/XrdCns/XrdCnsDaemon.cc delete mode 100644 src/XrdCns/XrdCnsDaemon.hh delete mode 100644 src/XrdCns/XrdCnsInventory.cc delete mode 100644 src/XrdCns/XrdCnsInventory.hh delete mode 100644 src/XrdCns/XrdCnsLog.cc delete mode 100644 src/XrdCns/XrdCnsLog.hh delete mode 100644 src/XrdCns/XrdCnsLogClient.cc delete mode 100644 src/XrdCns/XrdCnsLogClient.hh delete mode 100644 src/XrdCns/XrdCnsLogFile.cc delete mode 100644 src/XrdCns/XrdCnsLogFile.hh delete mode 100644 src/XrdCns/XrdCnsLogRec.cc delete mode 100644 src/XrdCns/XrdCnsLogRec.hh delete mode 100644 src/XrdCns/XrdCnsLogServer.cc delete mode 100644 src/XrdCns/XrdCnsLogServer.hh delete mode 100644 src/XrdCns/XrdCnsMain.cc delete mode 100644 src/XrdCns/XrdCnsSsi.cc delete mode 100644 src/XrdCns/XrdCnsSsi.hh delete mode 100644 src/XrdCns/XrdCnsSsiCfg.cc delete mode 100644 src/XrdCns/XrdCnsSsiCfg.hh delete mode 100644 src/XrdCns/XrdCnsSsiMain.cc delete mode 100644 src/XrdCns/XrdCnsSsiSay.hh delete mode 100644 src/XrdCns/XrdCnsXref.cc delete mode 100644 src/XrdCns/XrdCnsXref.hh delete mode 100755 src/XrdCns/cns.sh delete mode 100644 src/XrdCns/example.cns.cfg delete mode 100644 src/XrdCns/example.xrdcluster.cfg delete mode 100755 src/XrdCns/xrdcluster.sh delete mode 100644 src/XrdCrypto.cmake delete mode 100644 src/XrdCrypto/XrdCryptoAux.cc delete mode 100644 src/XrdCrypto/XrdCryptoAux.hh delete mode 100644 src/XrdCrypto/XrdCryptoBasic.cc delete mode 100644 src/XrdCrypto/XrdCryptoBasic.hh delete mode 100644 src/XrdCrypto/XrdCryptoCipher.cc delete mode 100644 src/XrdCrypto/XrdCryptoCipher.hh delete mode 100644 src/XrdCrypto/XrdCryptoFactory.cc delete mode 100644 src/XrdCrypto/XrdCryptoFactory.hh delete mode 100644 src/XrdCrypto/XrdCryptoLite.cc delete mode 100644 src/XrdCrypto/XrdCryptoLite.hh delete mode 100644 src/XrdCrypto/XrdCryptoLite_bf32.cc delete mode 100644 src/XrdCrypto/XrdCryptoMsgDigest.cc delete mode 100644 src/XrdCrypto/XrdCryptoMsgDigest.hh delete mode 100644 src/XrdCrypto/XrdCryptoRSA.cc delete mode 100644 src/XrdCrypto/XrdCryptoRSA.hh delete mode 100644 src/XrdCrypto/XrdCryptoTrace.hh delete mode 100644 src/XrdCrypto/XrdCryptoX509.cc delete mode 100644 src/XrdCrypto/XrdCryptoX509.hh delete mode 100644 src/XrdCrypto/XrdCryptoX509Chain.cc delete mode 100644 src/XrdCrypto/XrdCryptoX509Chain.hh delete mode 100644 src/XrdCrypto/XrdCryptoX509Crl.cc delete mode 100644 src/XrdCrypto/XrdCryptoX509Crl.hh delete mode 100644 src/XrdCrypto/XrdCryptoX509Req.cc delete mode 100644 src/XrdCrypto/XrdCryptoX509Req.hh delete mode 100644 src/XrdCrypto/XrdCryptogsiX509Chain.cc delete mode 100644 src/XrdCrypto/XrdCryptogsiX509Chain.hh delete mode 100644 src/XrdCrypto/XrdCryptosslAux.cc delete mode 100644 src/XrdCrypto/XrdCryptosslAux.hh delete mode 100644 src/XrdCrypto/XrdCryptosslCipher.cc delete mode 100644 src/XrdCrypto/XrdCryptosslCipher.hh delete mode 100644 src/XrdCrypto/XrdCryptosslFactory.cc delete mode 100644 src/XrdCrypto/XrdCryptosslFactory.hh delete mode 100644 src/XrdCrypto/XrdCryptosslMsgDigest.cc delete mode 100644 src/XrdCrypto/XrdCryptosslMsgDigest.hh delete mode 100644 src/XrdCrypto/XrdCryptosslRSA.cc delete mode 100644 src/XrdCrypto/XrdCryptosslRSA.hh delete mode 100644 src/XrdCrypto/XrdCryptosslTrace.hh delete mode 100644 src/XrdCrypto/XrdCryptosslX509.cc delete mode 100644 src/XrdCrypto/XrdCryptosslX509.hh delete mode 100644 src/XrdCrypto/XrdCryptosslX509Crl.cc delete mode 100644 src/XrdCrypto/XrdCryptosslX509Crl.hh delete mode 100644 src/XrdCrypto/XrdCryptosslX509Req.cc delete mode 100644 src/XrdCrypto/XrdCryptosslX509Req.hh delete mode 100644 src/XrdCrypto/XrdCryptosslX509Store.cc delete mode 100644 src/XrdCrypto/XrdCryptosslX509Store.hh delete mode 100644 src/XrdCrypto/XrdCryptosslgsiAux.cc delete mode 100644 src/XrdCrypto/XrdCryptosslgsiAux.hh delete mode 100644 src/XrdCrypto/XrdCryptotest.cc delete mode 100644 src/XrdDaemons.cmake delete mode 100644 src/XrdDig/XrdDigAuth.cc delete mode 100644 src/XrdDig/XrdDigAuth.hh delete mode 100644 src/XrdDig/XrdDigConfig.cc delete mode 100644 src/XrdDig/XrdDigConfig.hh delete mode 100644 src/XrdDig/XrdDigFS.cc delete mode 100644 src/XrdDig/XrdDigFS.hh delete mode 100644 src/XrdFfs.cmake delete mode 100644 src/XrdFfs/README delete mode 100644 src/XrdFfs/XrdFfsDent.cc delete mode 100644 src/XrdFfs/XrdFfsDent.hh delete mode 100644 src/XrdFfs/XrdFfsFsinfo.cc delete mode 100644 src/XrdFfs/XrdFfsFsinfo.hh delete mode 100644 src/XrdFfs/XrdFfsMisc.cc delete mode 100644 src/XrdFfs/XrdFfsMisc.hh delete mode 100644 src/XrdFfs/XrdFfsPosix.cc delete mode 100644 src/XrdFfs/XrdFfsPosix.hh delete mode 100644 src/XrdFfs/XrdFfsQueue.cc delete mode 100644 src/XrdFfs/XrdFfsQueue.hh delete mode 100644 src/XrdFfs/XrdFfsWcache.cc delete mode 100644 src/XrdFfs/XrdFfsWcache.hh delete mode 100644 src/XrdFfs/XrdFfsXrootdfs.cc delete mode 100755 src/XrdFfs/xrootdfs.template delete mode 100644 src/XrdFileCache.cmake delete mode 100644 src/XrdFileCache/README delete mode 100644 src/XrdFileCache/XrdFileCache.cc delete mode 100644 src/XrdFileCache/XrdFileCache.hh delete mode 100644 src/XrdFileCache/XrdFileCacheAllowDecision.cc delete mode 100644 src/XrdFileCache/XrdFileCacheBlacklistDecision.cc delete mode 100644 src/XrdFileCache/XrdFileCacheConfiguration.cc delete mode 100644 src/XrdFileCache/XrdFileCacheDecision.hh delete mode 100644 src/XrdFileCache/XrdFileCacheFile.cc delete mode 100644 src/XrdFileCache/XrdFileCacheFile.hh delete mode 100644 src/XrdFileCache/XrdFileCacheIO.cc delete mode 100644 src/XrdFileCache/XrdFileCacheIO.hh delete mode 100644 src/XrdFileCache/XrdFileCacheIOEntireFile.cc delete mode 100644 src/XrdFileCache/XrdFileCacheIOEntireFile.hh delete mode 100644 src/XrdFileCache/XrdFileCacheIOFileBlock.cc delete mode 100644 src/XrdFileCache/XrdFileCacheIOFileBlock.hh delete mode 100644 src/XrdFileCache/XrdFileCacheInfo.cc delete mode 100644 src/XrdFileCache/XrdFileCacheInfo.hh delete mode 100644 src/XrdFileCache/XrdFileCachePrint.cc delete mode 100644 src/XrdFileCache/XrdFileCachePrint.hh delete mode 100644 src/XrdFileCache/XrdFileCachePurge.cc delete mode 100644 src/XrdFileCache/XrdFileCacheStats.hh delete mode 100644 src/XrdFileCache/XrdFileCacheTrace.hh delete mode 100644 src/XrdFileCache/XrdFileCacheVRead.cc delete mode 100644 src/XrdFrc/XrdFrcCID.cc delete mode 100644 src/XrdFrc/XrdFrcCID.hh delete mode 100644 src/XrdFrc/XrdFrcProxy.cc delete mode 100644 src/XrdFrc/XrdFrcProxy.hh delete mode 100644 src/XrdFrc/XrdFrcReqAgent.cc delete mode 100644 src/XrdFrc/XrdFrcReqAgent.hh delete mode 100644 src/XrdFrc/XrdFrcReqFile.cc delete mode 100644 src/XrdFrc/XrdFrcReqFile.hh delete mode 100644 src/XrdFrc/XrdFrcRequest.hh delete mode 100644 src/XrdFrc/XrdFrcTrace.cc delete mode 100644 src/XrdFrc/XrdFrcTrace.hh delete mode 100644 src/XrdFrc/XrdFrcUtils.cc delete mode 100644 src/XrdFrc/XrdFrcUtils.hh delete mode 100644 src/XrdFrc/XrdFrcXAttr.hh delete mode 100644 src/XrdFrc/XrdFrcXLock.hh delete mode 100644 src/XrdFrm.cmake delete mode 100644 src/XrdFrm/XrdFrmAdmin.cc delete mode 100644 src/XrdFrm/XrdFrmAdmin.hh delete mode 100644 src/XrdFrm/XrdFrmAdminAudit.cc delete mode 100644 src/XrdFrm/XrdFrmAdminConvert.cc delete mode 100644 src/XrdFrm/XrdFrmAdminFiles.cc delete mode 100644 src/XrdFrm/XrdFrmAdminFind.cc delete mode 100644 src/XrdFrm/XrdFrmAdminMain.cc delete mode 100644 src/XrdFrm/XrdFrmAdminQuery.cc delete mode 100644 src/XrdFrm/XrdFrmAdminReloc.cc delete mode 100644 src/XrdFrm/XrdFrmAdminUnlink.cc delete mode 100644 src/XrdFrm/XrdFrmCns.cc delete mode 100644 src/XrdFrm/XrdFrmCns.hh delete mode 100644 src/XrdFrm/XrdFrmConfig.cc delete mode 100644 src/XrdFrm/XrdFrmConfig.hh delete mode 100644 src/XrdFrm/XrdFrmFiles.cc delete mode 100644 src/XrdFrm/XrdFrmFiles.hh delete mode 100644 src/XrdFrm/XrdFrmMigrate.cc delete mode 100644 src/XrdFrm/XrdFrmMigrate.hh delete mode 100644 src/XrdFrm/XrdFrmMonitor.cc delete mode 100644 src/XrdFrm/XrdFrmMonitor.hh delete mode 100644 src/XrdFrm/XrdFrmPurgMain.cc delete mode 100644 src/XrdFrm/XrdFrmPurge.cc delete mode 100644 src/XrdFrm/XrdFrmPurge.hh delete mode 100644 src/XrdFrm/XrdFrmReqBoss.cc delete mode 100644 src/XrdFrm/XrdFrmReqBoss.hh delete mode 100644 src/XrdFrm/XrdFrmTSort.cc delete mode 100644 src/XrdFrm/XrdFrmTSort.hh delete mode 100644 src/XrdFrm/XrdFrmTransfer.cc delete mode 100644 src/XrdFrm/XrdFrmTransfer.hh delete mode 100644 src/XrdFrm/XrdFrmXfrAgent.cc delete mode 100644 src/XrdFrm/XrdFrmXfrAgent.hh delete mode 100644 src/XrdFrm/XrdFrmXfrDaemon.cc delete mode 100644 src/XrdFrm/XrdFrmXfrDaemon.hh delete mode 100644 src/XrdFrm/XrdFrmXfrJob.hh delete mode 100644 src/XrdFrm/XrdFrmXfrMain.cc delete mode 100644 src/XrdFrm/XrdFrmXfrQueue.cc delete mode 100644 src/XrdFrm/XrdFrmXfrQueue.hh delete mode 100644 src/XrdHeaders.cmake delete mode 100644 src/XrdHttp.cmake delete mode 100644 src/XrdHttp/XrdHttpExtHandler.cc delete mode 100644 src/XrdHttp/XrdHttpExtHandler.hh delete mode 100644 src/XrdHttp/XrdHttpProtocol.cc delete mode 100644 src/XrdHttp/XrdHttpProtocol.hh delete mode 100644 src/XrdHttp/XrdHttpReq.cc delete mode 100644 src/XrdHttp/XrdHttpReq.hh delete mode 100644 src/XrdHttp/XrdHttpSecXtractor.hh delete mode 100644 src/XrdHttp/XrdHttpStatic.hh delete mode 100644 src/XrdHttp/XrdHttpTrace.cc delete mode 100644 src/XrdHttp/XrdHttpTrace.hh delete mode 100644 src/XrdHttp/XrdHttpUtils.cc delete mode 100644 src/XrdHttp/XrdHttpUtils.hh delete mode 100644 src/XrdHttp/static/favicon.ico delete mode 100644 src/XrdHttp/static/xrdhttp.css delete mode 100644 src/XrdHttp/static/xrdhttp_css.h delete mode 100644 src/XrdHttp/static/xrdhttp_favicon_ico.h delete mode 100644 src/XrdHttp/xrootd-http-rdr.cf delete mode 100644 src/XrdHttp/xrootd-http.cf delete mode 100644 src/XrdNet/XrdNet.cc delete mode 100644 src/XrdNet/XrdNet.hh delete mode 100644 src/XrdNet/XrdNetAddr.cc delete mode 100644 src/XrdNet/XrdNetAddr.hh delete mode 100644 src/XrdNet/XrdNetAddrInfo.cc delete mode 100644 src/XrdNet/XrdNetAddrInfo.hh delete mode 100644 src/XrdNet/XrdNetBuffer.cc delete mode 100644 src/XrdNet/XrdNetBuffer.hh delete mode 100644 src/XrdNet/XrdNetCache.cc delete mode 100644 src/XrdNet/XrdNetCache.hh delete mode 100644 src/XrdNet/XrdNetCmsNotify.cc delete mode 100644 src/XrdNet/XrdNetCmsNotify.hh delete mode 100644 src/XrdNet/XrdNetConnect.cc delete mode 100644 src/XrdNet/XrdNetConnect.hh delete mode 100644 src/XrdNet/XrdNetIF.cc delete mode 100644 src/XrdNet/XrdNetIF.hh delete mode 100644 src/XrdNet/XrdNetMsg.cc delete mode 100644 src/XrdNet/XrdNetMsg.hh delete mode 100644 src/XrdNet/XrdNetOpts.hh delete mode 100644 src/XrdNet/XrdNetPeer.hh delete mode 100644 src/XrdNet/XrdNetSecurity.cc delete mode 100644 src/XrdNet/XrdNetSecurity.hh delete mode 100644 src/XrdNet/XrdNetSockAddr.hh delete mode 100644 src/XrdNet/XrdNetSocket.cc delete mode 100644 src/XrdNet/XrdNetSocket.hh delete mode 100644 src/XrdNet/XrdNetUtils.cc delete mode 100644 src/XrdNet/XrdNetUtils.hh delete mode 100644 src/XrdOfs/XrdOfs.cc delete mode 100644 src/XrdOfs/XrdOfs.hh delete mode 100644 src/XrdOfs/XrdOfsConfig.cc delete mode 100644 src/XrdOfs/XrdOfsConfigPI.cc delete mode 100644 src/XrdOfs/XrdOfsConfigPI.hh delete mode 100644 src/XrdOfs/XrdOfsEvr.cc delete mode 100644 src/XrdOfs/XrdOfsEvr.hh delete mode 100644 src/XrdOfs/XrdOfsEvs.cc delete mode 100644 src/XrdOfs/XrdOfsEvs.hh delete mode 100644 src/XrdOfs/XrdOfsFS.cc delete mode 100644 src/XrdOfs/XrdOfsHandle.cc delete mode 100644 src/XrdOfs/XrdOfsHandle.hh delete mode 100644 src/XrdOfs/XrdOfsPoscq.cc delete mode 100644 src/XrdOfs/XrdOfsPoscq.hh delete mode 100644 src/XrdOfs/XrdOfsSecurity.hh delete mode 100644 src/XrdOfs/XrdOfsStats.cc delete mode 100644 src/XrdOfs/XrdOfsStats.hh delete mode 100644 src/XrdOfs/XrdOfsTPC.cc delete mode 100644 src/XrdOfs/XrdOfsTPC.hh delete mode 100644 src/XrdOfs/XrdOfsTPCAuth.cc delete mode 100644 src/XrdOfs/XrdOfsTPCAuth.hh delete mode 100644 src/XrdOfs/XrdOfsTPCInfo.cc delete mode 100644 src/XrdOfs/XrdOfsTPCInfo.hh delete mode 100644 src/XrdOfs/XrdOfsTPCJob.cc delete mode 100644 src/XrdOfs/XrdOfsTPCJob.hh delete mode 100644 src/XrdOfs/XrdOfsTPCProg.cc delete mode 100644 src/XrdOfs/XrdOfsTPCProg.hh delete mode 100644 src/XrdOfs/XrdOfsTrace.hh delete mode 100644 src/XrdOss/XrdOss.hh delete mode 100644 src/XrdOss/XrdOssAio.cc delete mode 100644 src/XrdOss/XrdOssApi.cc delete mode 100644 src/XrdOss/XrdOssApi.hh delete mode 100644 src/XrdOss/XrdOssCache.cc delete mode 100644 src/XrdOss/XrdOssCache.hh delete mode 100644 src/XrdOss/XrdOssConfig.cc delete mode 100644 src/XrdOss/XrdOssConfig.hh delete mode 100644 src/XrdOss/XrdOssCopy.cc delete mode 100644 src/XrdOss/XrdOssCopy.hh delete mode 100644 src/XrdOss/XrdOssCreate.cc delete mode 100644 src/XrdOss/XrdOssDefaultSS.hh delete mode 100644 src/XrdOss/XrdOssError.hh delete mode 100644 src/XrdOss/XrdOssMSS.cc delete mode 100644 src/XrdOss/XrdOssMio.cc delete mode 100644 src/XrdOss/XrdOssMio.hh delete mode 100644 src/XrdOss/XrdOssMioFile.hh delete mode 100644 src/XrdOss/XrdOssOpaque.hh delete mode 100644 src/XrdOss/XrdOssPath.cc delete mode 100644 src/XrdOss/XrdOssPath.hh delete mode 100644 src/XrdOss/XrdOssReloc.cc delete mode 100644 src/XrdOss/XrdOssRename.cc delete mode 100644 src/XrdOss/XrdOssSIgpfsT.cc delete mode 100644 src/XrdOss/XrdOssSpace.cc delete mode 100644 src/XrdOss/XrdOssSpace.hh delete mode 100644 src/XrdOss/XrdOssStage.cc delete mode 100644 src/XrdOss/XrdOssStage.hh delete mode 100644 src/XrdOss/XrdOssStat.cc delete mode 100644 src/XrdOss/XrdOssStatInfo.hh delete mode 100644 src/XrdOss/XrdOssTrace.hh delete mode 100644 src/XrdOss/XrdOssUnlink.cc delete mode 100644 src/XrdOuc/README.bonjour delete mode 100644 src/XrdOuc/XrdOucArgs.cc delete mode 100644 src/XrdOuc/XrdOucArgs.hh delete mode 100644 src/XrdOuc/XrdOucBackTrace.cc delete mode 100644 src/XrdOuc/XrdOucBackTrace.hh delete mode 100644 src/XrdOuc/XrdOucBuffer.cc delete mode 100644 src/XrdOuc/XrdOucBuffer.hh delete mode 100644 src/XrdOuc/XrdOucCRC.cc delete mode 100644 src/XrdOuc/XrdOucCRC.hh delete mode 100644 src/XrdOuc/XrdOucCache.hh delete mode 100644 src/XrdOuc/XrdOucCache2.hh delete mode 100644 src/XrdOuc/XrdOucCacheData.cc delete mode 100644 src/XrdOuc/XrdOucCacheData.hh delete mode 100644 src/XrdOuc/XrdOucCacheDram.cc delete mode 100644 src/XrdOuc/XrdOucCacheDram.hh delete mode 100644 src/XrdOuc/XrdOucCacheReal.cc delete mode 100644 src/XrdOuc/XrdOucCacheReal.hh delete mode 100644 src/XrdOuc/XrdOucCacheSlot.hh delete mode 100644 src/XrdOuc/XrdOucCallBack.cc delete mode 100644 src/XrdOuc/XrdOucCallBack.hh delete mode 100644 src/XrdOuc/XrdOucChain.hh delete mode 100644 src/XrdOuc/XrdOucCompiler.hh delete mode 100644 src/XrdOuc/XrdOucDLlist.hh delete mode 100644 src/XrdOuc/XrdOucERoute.cc delete mode 100644 src/XrdOuc/XrdOucERoute.hh delete mode 100644 src/XrdOuc/XrdOucEnum.hh delete mode 100644 src/XrdOuc/XrdOucEnv.cc delete mode 100644 src/XrdOuc/XrdOucEnv.hh delete mode 100644 src/XrdOuc/XrdOucErrInfo.cc delete mode 100644 src/XrdOuc/XrdOucErrInfo.hh delete mode 100644 src/XrdOuc/XrdOucExport.cc delete mode 100644 src/XrdOuc/XrdOucExport.hh delete mode 100644 src/XrdOuc/XrdOucFileInfo.cc delete mode 100644 src/XrdOuc/XrdOucFileInfo.hh delete mode 100644 src/XrdOuc/XrdOucGMap.cc delete mode 100644 src/XrdOuc/XrdOucGMap.hh delete mode 100644 src/XrdOuc/XrdOucHash.hh delete mode 100644 src/XrdOuc/XrdOucHash.icc delete mode 100644 src/XrdOuc/XrdOucHashVal.cc delete mode 100644 src/XrdOuc/XrdOucIOVec.hh delete mode 100644 src/XrdOuc/XrdOucLock.hh delete mode 100644 src/XrdOuc/XrdOucLogging.cc delete mode 100644 src/XrdOuc/XrdOucLogging.hh delete mode 100644 src/XrdOuc/XrdOucMsubs.cc delete mode 100644 src/XrdOuc/XrdOucMsubs.hh delete mode 100644 src/XrdOuc/XrdOucN2NLoader.cc delete mode 100644 src/XrdOuc/XrdOucN2NLoader.hh delete mode 100644 src/XrdOuc/XrdOucN2No2p.cc delete mode 100644 src/XrdOuc/XrdOucNList.cc delete mode 100644 src/XrdOuc/XrdOucNList.hh delete mode 100644 src/XrdOuc/XrdOucNSWalk.cc delete mode 100644 src/XrdOuc/XrdOucNSWalk.hh delete mode 100644 src/XrdOuc/XrdOucName2Name.cc delete mode 100644 src/XrdOuc/XrdOucName2Name.hh delete mode 100644 src/XrdOuc/XrdOucPList.hh delete mode 100644 src/XrdOuc/XrdOucPinLoader.cc delete mode 100644 src/XrdOuc/XrdOucPinLoader.hh delete mode 100644 src/XrdOuc/XrdOucPinPath.cc delete mode 100644 src/XrdOuc/XrdOucPinPath.hh delete mode 100644 src/XrdOuc/XrdOucPreload.cc delete mode 100644 src/XrdOuc/XrdOucPreload.hh delete mode 100644 src/XrdOuc/XrdOucProg.cc delete mode 100644 src/XrdOuc/XrdOucProg.hh delete mode 100644 src/XrdOuc/XrdOucPsx.cc delete mode 100644 src/XrdOuc/XrdOucPsx.hh delete mode 100644 src/XrdOuc/XrdOucPup.cc delete mode 100644 src/XrdOuc/XrdOucPup.hh delete mode 100644 src/XrdOuc/XrdOucRash.hh delete mode 100644 src/XrdOuc/XrdOucRash.icc delete mode 100644 src/XrdOuc/XrdOucReqID.cc delete mode 100644 src/XrdOuc/XrdOucReqID.hh delete mode 100644 src/XrdOuc/XrdOucSFVec.hh delete mode 100644 src/XrdOuc/XrdOucSid.cc delete mode 100644 src/XrdOuc/XrdOucSid.hh delete mode 100644 src/XrdOuc/XrdOucSiteName.cc delete mode 100644 src/XrdOuc/XrdOucSiteName.hh delete mode 100644 src/XrdOuc/XrdOucStats.hh delete mode 100644 src/XrdOuc/XrdOucStream.cc delete mode 100644 src/XrdOuc/XrdOucStream.hh delete mode 100644 src/XrdOuc/XrdOucString.cc delete mode 100644 src/XrdOuc/XrdOucString.hh delete mode 100644 src/XrdOuc/XrdOucSxeq.cc delete mode 100644 src/XrdOuc/XrdOucSxeq.hh delete mode 100644 src/XrdOuc/XrdOucTList.hh delete mode 100644 src/XrdOuc/XrdOucTPC.cc delete mode 100644 src/XrdOuc/XrdOucTPC.hh delete mode 100644 src/XrdOuc/XrdOucTable.hh delete mode 100644 src/XrdOuc/XrdOucTokenizer.cc delete mode 100644 src/XrdOuc/XrdOucTokenizer.hh delete mode 100644 src/XrdOuc/XrdOucTrace.cc delete mode 100644 src/XrdOuc/XrdOucTrace.hh delete mode 100644 src/XrdOuc/XrdOucUtils.cc delete mode 100644 src/XrdOuc/XrdOucUtils.hh delete mode 100644 src/XrdOuc/XrdOucVerName.cc delete mode 100644 src/XrdOuc/XrdOucVerName.hh delete mode 100644 src/XrdOuc/XrdOucXAttr.hh delete mode 100644 src/XrdOuc/XrdOuca2x.cc delete mode 100644 src/XrdOuc/XrdOuca2x.hh delete mode 100644 src/XrdPlugins.cmake delete mode 100644 src/XrdPosix.cmake delete mode 100644 src/XrdPosix/README delete mode 100644 src/XrdPosix/XrdPosix.cc delete mode 100644 src/XrdPosix/XrdPosix.hh delete mode 100644 src/XrdPosix/XrdPosixAdmin.cc delete mode 100644 src/XrdPosix/XrdPosixAdmin.hh delete mode 100644 src/XrdPosix/XrdPosixCacheBC.hh delete mode 100644 src/XrdPosix/XrdPosixCallBack.cc delete mode 100644 src/XrdPosix/XrdPosixCallBack.hh delete mode 100644 src/XrdPosix/XrdPosixConfig.cc delete mode 100644 src/XrdPosix/XrdPosixConfig.hh delete mode 100644 src/XrdPosix/XrdPosixDir.cc delete mode 100644 src/XrdPosix/XrdPosixDir.hh delete mode 100644 src/XrdPosix/XrdPosixExtern.hh delete mode 100644 src/XrdPosix/XrdPosixFile.cc delete mode 100644 src/XrdPosix/XrdPosixFile.hh delete mode 100644 src/XrdPosix/XrdPosixFileRH.cc delete mode 100644 src/XrdPosix/XrdPosixFileRH.hh delete mode 100644 src/XrdPosix/XrdPosixLinkage.cc delete mode 100644 src/XrdPosix/XrdPosixLinkage.hh delete mode 100644 src/XrdPosix/XrdPosixMap.cc delete mode 100644 src/XrdPosix/XrdPosixMap.hh delete mode 100644 src/XrdPosix/XrdPosixObjGuard.hh delete mode 100644 src/XrdPosix/XrdPosixObject.cc delete mode 100644 src/XrdPosix/XrdPosixObject.hh delete mode 100644 src/XrdPosix/XrdPosixOsDep.hh delete mode 100644 src/XrdPosix/XrdPosixPreload.cc delete mode 100644 src/XrdPosix/XrdPosixPreload32.cc delete mode 100644 src/XrdPosix/XrdPosixPrepIO.cc delete mode 100644 src/XrdPosix/XrdPosixPrepIO.hh delete mode 100644 src/XrdPosix/XrdPosixTrace.hh delete mode 100644 src/XrdPosix/XrdPosixXrootd.cc delete mode 100644 src/XrdPosix/XrdPosixXrootd.hh delete mode 100644 src/XrdPosix/XrdPosixXrootdPath.cc delete mode 100644 src/XrdPosix/XrdPosixXrootdPath.hh delete mode 100644 src/XrdPss/XrdPss.cc delete mode 100644 src/XrdPss/XrdPss.hh delete mode 100644 src/XrdPss/XrdPssAio.cc delete mode 100644 src/XrdPss/XrdPssAioCB.cc delete mode 100644 src/XrdPss/XrdPssAioCB.hh delete mode 100644 src/XrdPss/XrdPssCks.cc delete mode 100644 src/XrdPss/XrdPssCks.hh delete mode 100644 src/XrdPss/XrdPssConfig.cc delete mode 100644 src/XrdRootd/XrdRootdProtocol.cc delete mode 100644 src/XrdRootd/XrdRootdProtocol.hh delete mode 100644 src/XrdSec.cmake delete mode 100644 src/XrdSec/XrdSecClient.cc delete mode 100644 src/XrdSec/XrdSecEntity.hh delete mode 100644 src/XrdSec/XrdSecInterface.hh delete mode 100644 src/XrdSec/XrdSecLoadSecurity.cc delete mode 100644 src/XrdSec/XrdSecLoadSecurity.hh delete mode 100644 src/XrdSec/XrdSecPManager.cc delete mode 100644 src/XrdSec/XrdSecPManager.hh delete mode 100644 src/XrdSec/XrdSecProtect.cc delete mode 100644 src/XrdSec/XrdSecProtect.hh delete mode 100644 src/XrdSec/XrdSecProtector.cc delete mode 100644 src/XrdSec/XrdSecProtector.hh delete mode 100644 src/XrdSec/XrdSecProtocolhost.cc delete mode 100644 src/XrdSec/XrdSecProtocolhost.hh delete mode 100644 src/XrdSec/XrdSecServer.cc delete mode 100644 src/XrdSec/XrdSecServer.hh delete mode 100644 src/XrdSec/XrdSecTLayer.cc delete mode 100644 src/XrdSec/XrdSecTLayer.hh delete mode 100644 src/XrdSec/XrdSecTrace.hh delete mode 100644 src/XrdSec/XrdSectestClient.cc delete mode 100644 src/XrdSec/XrdSectestServer.cc delete mode 100644 src/XrdSecgsi.cmake delete mode 100644 src/XrdSecgsi/XrdSecProtocolgsi.cc delete mode 100644 src/XrdSecgsi/XrdSecProtocolgsi.hh delete mode 100644 src/XrdSecgsi/XrdSecgsiAuthzFunDN.cc delete mode 100644 src/XrdSecgsi/XrdSecgsiAuthzFunVO.cc delete mode 100644 src/XrdSecgsi/XrdSecgsiGMAPFunDN.cc delete mode 100644 src/XrdSecgsi/XrdSecgsiGMAPFunDN.cf delete mode 100644 src/XrdSecgsi/XrdSecgsiProxy.cc delete mode 100644 src/XrdSecgsi/XrdSecgsiTrace.hh delete mode 100644 src/XrdSecgsi/XrdSecgsiVOMSFunLite.cc delete mode 100644 src/XrdSecgsi/XrdSecgsitest.cc delete mode 100644 src/XrdSeckrb5.cmake delete mode 100644 src/XrdSeckrb5/XrdSecProtocolkrb5.cc delete mode 100644 src/XrdSecpwd/XrdSecProtocolpwd.cc delete mode 100644 src/XrdSecpwd/XrdSecProtocolpwd.hh delete mode 100644 src/XrdSecpwd/XrdSecpwdPlatform.hh delete mode 100644 src/XrdSecpwd/XrdSecpwdSrvAdmin.cc delete mode 100644 src/XrdSecpwd/XrdSecpwdTrace.hh delete mode 100644 src/XrdSecsss/XrdSecProtocolsss.cc delete mode 100644 src/XrdSecsss/XrdSecProtocolsss.hh delete mode 100644 src/XrdSecsss/XrdSecsssAdmin.cc delete mode 100644 src/XrdSecsss/XrdSecsssID.cc delete mode 100644 src/XrdSecsss/XrdSecsssID.hh delete mode 100644 src/XrdSecsss/XrdSecsssKT.cc delete mode 100644 src/XrdSecsss/XrdSecsssKT.hh delete mode 100644 src/XrdSecsss/XrdSecsssRR.hh delete mode 100644 src/XrdSecunix/XrdSecProtocolunix.cc delete mode 100644 src/XrdServer.cmake delete mode 100644 src/XrdSfs/XrdSfsAio.hh delete mode 100644 src/XrdSfs/XrdSfsDio.hh delete mode 100644 src/XrdSfs/XrdSfsFlags.hh delete mode 100644 src/XrdSfs/XrdSfsInterface.hh delete mode 100644 src/XrdSfs/XrdSfsNative.cc delete mode 100644 src/XrdSfs/XrdSfsNative.hh delete mode 100644 src/XrdSfs/XrdSfsXio.hh delete mode 100644 src/XrdSsi.cmake delete mode 100644 src/XrdSsi/XrdSsiAlert.cc delete mode 100644 src/XrdSsi/XrdSsiAlert.hh delete mode 100644 src/XrdSsi/XrdSsiAtomics.hh delete mode 100644 src/XrdSsi/XrdSsiBVec.hh delete mode 100644 src/XrdSsi/XrdSsiClient.cc delete mode 100644 src/XrdSsi/XrdSsiCluster.hh delete mode 100644 src/XrdSsi/XrdSsiCms.cc delete mode 100644 src/XrdSsi/XrdSsiCms.hh delete mode 100644 src/XrdSsi/XrdSsiDir.cc delete mode 100644 src/XrdSsi/XrdSsiDir.hh delete mode 100644 src/XrdSsi/XrdSsiEntity.hh delete mode 100644 src/XrdSsi/XrdSsiErrInfo.hh delete mode 100644 src/XrdSsi/XrdSsiEvent.cc delete mode 100644 src/XrdSsi/XrdSsiEvent.hh delete mode 100644 src/XrdSsi/XrdSsiFile.cc delete mode 100644 src/XrdSsi/XrdSsiFile.hh delete mode 100644 src/XrdSsi/XrdSsiFileReq.cc delete mode 100644 src/XrdSsi/XrdSsiFileReq.hh delete mode 100644 src/XrdSsi/XrdSsiFileResource.cc delete mode 100644 src/XrdSsi/XrdSsiFileResource.hh delete mode 100644 src/XrdSsi/XrdSsiFileSess.cc delete mode 100644 src/XrdSsi/XrdSsiFileSess.hh delete mode 100644 src/XrdSsi/XrdSsiGCS.cc delete mode 100644 src/XrdSsi/XrdSsiLogger.cc delete mode 100644 src/XrdSsi/XrdSsiLogger.hh delete mode 100644 src/XrdSsi/XrdSsiLogging.cc delete mode 100644 src/XrdSsi/XrdSsiPacer.cc delete mode 100644 src/XrdSsi/XrdSsiPacer.hh delete mode 100644 src/XrdSsi/XrdSsiProvider.hh delete mode 100644 src/XrdSsi/XrdSsiRRAgent.hh delete mode 100644 src/XrdSsi/XrdSsiRRInfo.hh delete mode 100644 src/XrdSsi/XrdSsiRRTable.hh delete mode 100644 src/XrdSsi/XrdSsiRequest.cc delete mode 100644 src/XrdSsi/XrdSsiRequest.hh delete mode 100644 src/XrdSsi/XrdSsiResource.hh delete mode 100644 src/XrdSsi/XrdSsiRespInfo.hh delete mode 100644 src/XrdSsi/XrdSsiResponder.cc delete mode 100644 src/XrdSsi/XrdSsiResponder.hh delete mode 100644 src/XrdSsi/XrdSsiScale.hh delete mode 100644 src/XrdSsi/XrdSsiServReal.cc delete mode 100644 src/XrdSsi/XrdSsiServReal.hh delete mode 100644 src/XrdSsi/XrdSsiService.cc delete mode 100644 src/XrdSsi/XrdSsiService.hh delete mode 100644 src/XrdSsi/XrdSsiSessReal.cc delete mode 100644 src/XrdSsi/XrdSsiSessReal.hh delete mode 100644 src/XrdSsi/XrdSsiSfs.cc delete mode 100644 src/XrdSsi/XrdSsiSfs.hh delete mode 100644 src/XrdSsi/XrdSsiSfsConfig.cc delete mode 100644 src/XrdSsi/XrdSsiSfsConfig.hh delete mode 100644 src/XrdSsi/XrdSsiShMam.cc delete mode 100644 src/XrdSsi/XrdSsiShMam.hh delete mode 100644 src/XrdSsi/XrdSsiShMap.hh delete mode 100644 src/XrdSsi/XrdSsiShMap.icc delete mode 100644 src/XrdSsi/XrdSsiShMat.cc delete mode 100644 src/XrdSsi/XrdSsiShMat.hh delete mode 100644 src/XrdSsi/XrdSsiStat.cc delete mode 100644 src/XrdSsi/XrdSsiStream.hh delete mode 100644 src/XrdSsi/XrdSsiTaskReal.cc delete mode 100644 src/XrdSsi/XrdSsiTaskReal.hh delete mode 100644 src/XrdSsi/XrdSsiTrace.hh delete mode 100644 src/XrdSsi/XrdSsiUtils.cc delete mode 100644 src/XrdSsi/XrdSsiUtils.hh delete mode 100644 src/XrdSut/XrdSutAux.cc delete mode 100644 src/XrdSut/XrdSutAux.hh delete mode 100644 src/XrdSut/XrdSutBuckList.cc delete mode 100644 src/XrdSut/XrdSutBuckList.hh delete mode 100644 src/XrdSut/XrdSutBucket.cc delete mode 100644 src/XrdSut/XrdSutBucket.hh delete mode 100644 src/XrdSut/XrdSutBuffer.cc delete mode 100644 src/XrdSut/XrdSutBuffer.hh delete mode 100644 src/XrdSut/XrdSutCache.hh delete mode 100644 src/XrdSut/XrdSutCacheEntry.cc delete mode 100644 src/XrdSut/XrdSutCacheEntry.hh delete mode 100644 src/XrdSut/XrdSutPFCache.cc delete mode 100644 src/XrdSut/XrdSutPFCache.hh delete mode 100644 src/XrdSut/XrdSutPFEntry.cc delete mode 100644 src/XrdSut/XrdSutPFEntry.hh delete mode 100644 src/XrdSut/XrdSutPFile.cc delete mode 100644 src/XrdSut/XrdSutPFile.hh delete mode 100644 src/XrdSut/XrdSutRndm.cc delete mode 100644 src/XrdSut/XrdSutRndm.hh delete mode 100644 src/XrdSut/XrdSutTrace.hh delete mode 100644 src/XrdSys/XrdSysAtomics.hh delete mode 100644 src/XrdSys/XrdSysDNS.cc delete mode 100644 src/XrdSys/XrdSysDNS.hh delete mode 100644 src/XrdSys/XrdSysDir.cc delete mode 100644 src/XrdSys/XrdSysDir.hh delete mode 100644 src/XrdSys/XrdSysError.cc delete mode 100644 src/XrdSys/XrdSysError.hh delete mode 100644 src/XrdSys/XrdSysFAttr.cc delete mode 100644 src/XrdSys/XrdSysFAttr.hh delete mode 100644 src/XrdSys/XrdSysFAttrBsd.icc delete mode 100644 src/XrdSys/XrdSysFAttrLnx.icc delete mode 100644 src/XrdSys/XrdSysFAttrMac.icc delete mode 100644 src/XrdSys/XrdSysFAttrSun.icc delete mode 100644 src/XrdSys/XrdSysFD.hh delete mode 100644 src/XrdSys/XrdSysHeaders.hh delete mode 100644 src/XrdSys/XrdSysIOEvents.cc delete mode 100644 src/XrdSys/XrdSysIOEvents.hh delete mode 100644 src/XrdSys/XrdSysIOEventsPollE.icc delete mode 100644 src/XrdSys/XrdSysIOEventsPollKQ.icc delete mode 100644 src/XrdSys/XrdSysIOEventsPollPoll.icc delete mode 100644 src/XrdSys/XrdSysIOEventsPollPort.icc delete mode 100644 src/XrdSys/XrdSysLinuxSemaphore.hh delete mode 100644 src/XrdSys/XrdSysLogPI.hh delete mode 100644 src/XrdSys/XrdSysLogger.cc delete mode 100644 src/XrdSys/XrdSysLogger.hh delete mode 100644 src/XrdSys/XrdSysLogging.cc delete mode 100644 src/XrdSys/XrdSysLogging.hh delete mode 100644 src/XrdSys/XrdSysPlatform.cc delete mode 100644 src/XrdSys/XrdSysPlatform.hh delete mode 100644 src/XrdSys/XrdSysPlugin.cc delete mode 100644 src/XrdSys/XrdSysPlugin.hh delete mode 100644 src/XrdSys/XrdSysPriv.cc delete mode 100644 src/XrdSys/XrdSysPriv.hh delete mode 100644 src/XrdSys/XrdSysPthread.cc delete mode 100644 src/XrdSys/XrdSysPthread.hh delete mode 100644 src/XrdSys/XrdSysPwd.hh delete mode 100644 src/XrdSys/XrdSysSemWait.hh delete mode 100644 src/XrdSys/XrdSysTimer.cc delete mode 100644 src/XrdSys/XrdSysTimer.hh delete mode 100644 src/XrdSys/XrdSysTrace.cc delete mode 100644 src/XrdSys/XrdSysTrace.hh delete mode 100644 src/XrdSys/XrdSysUtils.cc delete mode 100644 src/XrdSys/XrdSysUtils.hh delete mode 100644 src/XrdSys/XrdSysXAttr.cc delete mode 100644 src/XrdSys/XrdSysXAttr.hh delete mode 100644 src/XrdSys/XrdSysXSLock.cc delete mode 100644 src/XrdSys/XrdSysXSLock.hh delete mode 100644 src/XrdThrottle/README delete mode 100644 src/XrdThrottle/XrdThrottle.hh delete mode 100644 src/XrdThrottle/XrdThrottleFile.cc delete mode 100644 src/XrdThrottle/XrdThrottleFileSystem.cc delete mode 100644 src/XrdThrottle/XrdThrottleFileSystemConfig.cc delete mode 100644 src/XrdThrottle/XrdThrottleManager.cc delete mode 100644 src/XrdThrottle/XrdThrottleManager.hh delete mode 100644 src/XrdThrottle/XrdThrottleTrace.hh delete mode 100644 src/XrdUtils.cmake delete mode 100644 src/XrdVersionPlugin.hh delete mode 100644 src/XrdXml.cmake delete mode 100644 src/XrdXml/XrdXmlMetaLink.cc delete mode 100644 src/XrdXml/XrdXmlMetaLink.hh delete mode 100644 src/XrdXml/XrdXmlRdrTiny.cc delete mode 100644 src/XrdXml/XrdXmlRdrTiny.hh delete mode 100644 src/XrdXml/XrdXmlRdrXml2.cc delete mode 100644 src/XrdXml/XrdXmlRdrXml2.hh delete mode 100644 src/XrdXml/XrdXmlReader.cc delete mode 100644 src/XrdXml/XrdXmlReader.hh delete mode 100644 src/XrdXml/tinystr.cpp delete mode 100644 src/XrdXml/tinystr.h delete mode 100644 src/XrdXml/tinyxml.cpp delete mode 100644 src/XrdXml/tinyxml.h delete mode 100644 src/XrdXml/tinyxmlerror.cpp delete mode 100644 src/XrdXml/tinyxmlparser.cpp delete mode 100644 src/XrdXrootd/XrdXrootdAdmin.cc delete mode 100644 src/XrdXrootd/XrdXrootdAdmin.hh delete mode 100644 src/XrdXrootd/XrdXrootdAio.cc delete mode 100644 src/XrdXrootd/XrdXrootdAio.hh delete mode 100644 src/XrdXrootd/XrdXrootdBridge.cc delete mode 100644 src/XrdXrootd/XrdXrootdBridge.hh delete mode 100644 src/XrdXrootd/XrdXrootdCallBack.cc delete mode 100644 src/XrdXrootd/XrdXrootdCallBack.hh delete mode 100644 src/XrdXrootd/XrdXrootdConfig.cc delete mode 100644 src/XrdXrootd/XrdXrootdFile.cc delete mode 100644 src/XrdXrootd/XrdXrootdFile.hh delete mode 100644 src/XrdXrootd/XrdXrootdFileLock.hh delete mode 100644 src/XrdXrootd/XrdXrootdFileLock1.cc delete mode 100644 src/XrdXrootd/XrdXrootdFileLock1.hh delete mode 100644 src/XrdXrootd/XrdXrootdFileStats.hh delete mode 100644 src/XrdXrootd/XrdXrootdJob.cc delete mode 100644 src/XrdXrootd/XrdXrootdJob.hh delete mode 100644 src/XrdXrootd/XrdXrootdLoadLib.cc delete mode 100644 src/XrdXrootd/XrdXrootdMonData.hh delete mode 100644 src/XrdXrootd/XrdXrootdMonFMap.cc delete mode 100644 src/XrdXrootd/XrdXrootdMonFMap.hh delete mode 100644 src/XrdXrootd/XrdXrootdMonFile.cc delete mode 100644 src/XrdXrootd/XrdXrootdMonFile.hh delete mode 100644 src/XrdXrootd/XrdXrootdMonitor.cc delete mode 100644 src/XrdXrootd/XrdXrootdMonitor.hh delete mode 100644 src/XrdXrootd/XrdXrootdPio.cc delete mode 100644 src/XrdXrootd/XrdXrootdPio.hh delete mode 100644 src/XrdXrootd/XrdXrootdPlugin.cc delete mode 100644 src/XrdXrootd/XrdXrootdPrepare.cc delete mode 100644 src/XrdXrootd/XrdXrootdPrepare.hh delete mode 100644 src/XrdXrootd/XrdXrootdProtocol.cc delete mode 100644 src/XrdXrootd/XrdXrootdProtocol.hh delete mode 100644 src/XrdXrootd/XrdXrootdReqID.hh delete mode 100644 src/XrdXrootd/XrdXrootdResponse.cc delete mode 100644 src/XrdXrootd/XrdXrootdResponse.hh delete mode 100644 src/XrdXrootd/XrdXrootdStat.icc delete mode 100644 src/XrdXrootd/XrdXrootdStats.cc delete mode 100644 src/XrdXrootd/XrdXrootdStats.hh delete mode 100644 src/XrdXrootd/XrdXrootdTrace.hh delete mode 100644 src/XrdXrootd/XrdXrootdTransPend.cc delete mode 100644 src/XrdXrootd/XrdXrootdTransPend.hh delete mode 100644 src/XrdXrootd/XrdXrootdTransSend.cc delete mode 100644 src/XrdXrootd/XrdXrootdTransSend.hh delete mode 100644 src/XrdXrootd/XrdXrootdTransit.cc delete mode 100644 src/XrdXrootd/XrdXrootdTransit.hh delete mode 100644 src/XrdXrootd/XrdXrootdXPath.hh delete mode 100644 src/XrdXrootd/XrdXrootdXeq.cc delete mode 100644 src/XrdXrootd/XrdXrootdXeqAio.cc delete mode 100644 tests/XrdClTests/CMakeLists.txt delete mode 100644 tests/XrdClTests/FileCopyTest.cc delete mode 100644 tests/XrdClTests/FileSystemTest.cc delete mode 100644 tests/XrdClTests/FileTest.cc delete mode 100644 tests/XrdClTests/IdentityPlugIn.cc delete mode 100644 tests/XrdClTests/IdentityPlugIn.hh delete mode 100644 tests/XrdClTests/LocalFileHandlerTest.cc delete mode 100644 tests/XrdClTests/MonitorTestLib.cc delete mode 100644 tests/XrdClTests/PollerTest.cc delete mode 100644 tests/XrdClTests/PostMasterTest.cc delete mode 100644 tests/XrdClTests/SocketTest.cc delete mode 100644 tests/XrdClTests/ThreadingTest.cc delete mode 100644 tests/XrdClTests/UtilsTest.cc delete mode 100644 tests/XrdClTests/XRootDProtocolHelper.cc delete mode 100644 tests/XrdClTests/XRootDProtocolHelper.hh delete mode 100644 tests/XrdClTests/cppunit.supp delete mode 100755 tests/XrdClTests/printenv.sh delete mode 100644 tests/XrdSsiTests/CMakeLists.txt delete mode 100644 tests/XrdSsiTests/XrdShMap.cc delete mode 100644 tests/common/CMakeLists.txt delete mode 100644 tests/common/CppUnitXrdHelpers.hh delete mode 100644 tests/common/PathProcessor.hh delete mode 100644 tests/common/Server.cc delete mode 100644 tests/common/Server.hh delete mode 100644 tests/common/TestEnv.cc delete mode 100644 tests/common/TestEnv.hh delete mode 100644 tests/common/TextRunner.cc delete mode 100644 tests/common/Utils.cc delete mode 100644 tests/common/Utils.hh delete mode 100644 ups/eupspkg.cfg.sh delete mode 100644 ups/xrootd.table delete mode 100755 utils/XrdCmsNotify.pm delete mode 100755 utils/XrdOlbMonPerf delete mode 100755 utils/hpsscp delete mode 100755 utils/netchk delete mode 100755 utils/xrootd-config diff --git a/CMakeLists.txt b/CMakeLists.txt index 32e6d5a72cd..965fdc8bf8b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ IF(CMAKE_VERSION VERSION_GREATER "2.8.12") CMAKE_POLICY(SET CMP0022 OLD) ENDIF() -project( XRootD ) +project( xrootd-ceph ) set( CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/src @@ -22,7 +22,6 @@ CheckBuildDirectory() include( XRootDOSDefs ) include( XRootDDefaults ) -include( XRootDSystemCheck ) include( XRootDFindLibs ) add_definitions( -DXRDPLUGIN_SOVERSION="${PLUGIN_VERSION}" ) @@ -54,7 +53,6 @@ endmacro() # Build in subdirectories #------------------------------------------------------------------------------- add_subdirectory( src ) -add_subdirectory( bindings ) if( BUILD_TESTS ) ENABLE_TESTING() diff --git a/README b/README index f194c372707..3654eabb484 100644 --- a/README +++ b/README @@ -9,51 +9,28 @@ -------------------------------------------------------------------------------- +0. xrootd-ceph is a OSS layer XRootD plug-in for interfacing with Ceph storage + platform. The plug-in has to be build against respective Ceph version, the + repository can be found at: + + https://download.ceph.com/rpm-{ceph-release}/{distro}/$basearch + 1. S U P P O R T E D O P E R A T I N G S Y S T E M S XRootD is supported on the following platforms: - * RedHat Enterprise Linux 5 and 6 and derivatives (Scientific Linux) + * RedHat Enterprise Linux 7 and derivatives (Scientific Linux) compiled with gcc - * Solaris 10 compiled with SunCC - * MacOSX 10.6 and 10.7 compiled with gcc or clang 2. B U I L D I N S T R U C T I O N S 2.1 Build system - XRootD uses CMake to handle the build process. It should build fine with -cmake 2.6, however, on some platforms, this version of cmake has problems -handling the perl libraries, therefore version 2.8 or newer is recommended. - -2.2 Build parameters - - The build process supports the following parameters: - - * CMAKE_INSTALL_PREFIX - indicates where the XRootD files should be installed, - (default: /usr) - * CMAKE_BUILD_TYPE - type of the build: Release/Debug/RelWithDebInfo - * FORCE_32BITS - Force building 32 bit binaries when on Solaris AMD64 - (default: FALSE) - * ENABLE_PERL - enable the perl bindings if possible (default: TRUE) - * ENABLE_FUSE - enable the fuse filesystem driver if possible - (default: TRUE) - * ENABLE_CRYPTO - enable the OpenSSL cryprography support (including - the X509 authentication) if possible (default: TRUE) - * ENABLE_KRB5 - enable the Kerberos 5 authentication if possible - (default: TRUE) - * ENABLE_READLINE - enable the lib readline support in the commandline - utilities (default: TRUE) - * OPENSSL_ROOT_DIR - path to the root of the openssl installation if it - cannot be detected in a standard location - * KERBEROS5_ROOT_DIR - path to the root of the kerberos installation if it - cannot be detected in a standard location - * READLINE_ROOT_DIR - path to the root of the readline installation if it - cannot be detected in a standard location - * CMAKE_C_COMPILER - path to the c compiler that should be used - * CMAKE_CXX_COMPILER - path to the c++ compiler that should be used - -2.3 Build steps + xrootd-ceph uses CMake to handle the build process. It should build fine + with cmake 2.6, however, on some platforms, this version of cmake has problems + handling the perl libraries, therefore version 2.8 or newer is recommended. + +2.2 Build steps * Create an empty build directory: @@ -72,11 +49,3 @@ handling the perl libraries, therefore version 2.8 or newer is recommended. * Install the source: make install - -3. P L A T F O R M N O T E S - -3.1 Solaris - - * On Solaris x86 the Sun Studio <= 12.1 compiler optimization algorithms - are broken, only Debug build is supported. For the optimized mode upgrade - the compiler to 12.2 or later. diff --git a/bindings/CMakeLists.txt b/bindings/CMakeLists.txt deleted file mode 100644 index 4834bb3579a..00000000000 --- a/bindings/CMakeLists.txt +++ /dev/null @@ -1,4 +0,0 @@ - -if( BUILD_PYTHON ) - add_subdirectory( python ) -endif() diff --git a/bindings/python/.gitattributes b/bindings/python/.gitattributes deleted file mode 100644 index 0893fe6afee..00000000000 --- a/bindings/python/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -VERSION_INFO export-subst diff --git a/bindings/python/.gitignore b/bindings/python/.gitignore deleted file mode 100755 index 22eb52622ed..00000000000 --- a/bindings/python/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -build -.project -.pydevproject -.cproject -*.py[co] -MANIFEST -VERSION_INFO diff --git a/bindings/python/CMakeLists.txt b/bindings/python/CMakeLists.txt deleted file mode 100644 index 7ec8981d0d0..00000000000 --- a/bindings/python/CMakeLists.txt +++ /dev/null @@ -1,30 +0,0 @@ - -set(SETUP_PY_IN "${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in") -set(SETUP_PY "${CMAKE_CURRENT_BINARY_DIR}/setup.py") -set(DEPS "${CMAKE_CURRENT_SOURCE_DIR}/libs/__init__.py") -set(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/python_bindings") -set(XRD_SRCINCDIR "${CMAKE_SOURCE_DIR}/src") -set(XRD_BININCDIR "${CMAKE_BINARY_DIR}/src") -set(XRDCL_LIBDIR "${CMAKE_BINARY_DIR}/src/XrdCl") -set(XRD_LIBDIR "${CMAKE_BINARY_DIR}/src") - -if( "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" ) - if( CMAKE_CXX_COMPILER_VERSION VERSION_LESS 3.7 ) - message( "clang 3.5" ) - set( CLANG_PROHIBITED ", '-Wp,-D_FORTIFY_SOURCE=2', '-fstack-protector-strong'" ) - endif() -endif() - -configure_file(${SETUP_PY_IN} ${SETUP_PY}) - -add_custom_command(OUTPUT ${OUTPUT} - COMMAND ${PYTHON_EXECUTABLE} ${SETUP_PY} build - DEPENDS ${DEPS}) - -add_custom_target(python_target ALL DEPENDS ${OUTPUT} XrdCl) - -install( - CODE - "EXECUTE_PROCESS( - COMMAND ${PYTHON_EXECUTABLE} ${SETUP_PY} install --prefix \$ENV{DESTDIR}/${CMAKE_INSTALL_PREFIX} --record PYTHON_INSTALLED)") - diff --git a/bindings/python/MANIFEST.in b/bindings/python/MANIFEST.in deleted file mode 100644 index 2110b2157d3..00000000000 --- a/bindings/python/MANIFEST.in +++ /dev/null @@ -1,6 +0,0 @@ -include README.rst -include VERSION_INFO -recursive-include tests * -recursive-include examples *.py -recursive-include docs * -recursive-include src * diff --git a/bindings/python/README.rst b/bindings/python/README.rst deleted file mode 100644 index cf5c2bd0129..00000000000 --- a/bindings/python/README.rst +++ /dev/null @@ -1,16 +0,0 @@ -Prerequisites (incomplete): xrootd - -# Installing on OSX -Setup should succeed if: - - xrootd is installed on your system - - xrootd was installed via homebrew - - you're installing the bindings package with the same version number as the - xrootd installation (`xrootd -v`). - -If you have xrootd installed and the installation still fails, do -`XRD_LIBDIR=XYZ; XRD_INCDIR=ZYX; pip install xrootd` -where XYZ and ZYX are the paths to the XRootD library and include directories on your system. - -## How to find the lib and inc directories -To find the library directory, search your system for "libXrd*" files. -The include directory should contain a file named "XrdVersion.hh", so search for that. diff --git a/bindings/python/docs/Makefile b/bindings/python/docs/Makefile deleted file mode 100644 index 26c73b40844..00000000000 --- a/bindings/python/docs/Makefile +++ /dev/null @@ -1,153 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = build - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - -rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PyXRootD.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PyXRootD.qhc" - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/PyXRootD" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PyXRootD" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." diff --git a/bindings/python/docs/ReleaseNotes.txt b/bindings/python/docs/ReleaseNotes.txt deleted file mode 100644 index 083d69a364b..00000000000 --- a/bindings/python/docs/ReleaseNotes.txt +++ /dev/null @@ -1,48 +0,0 @@ -======== -PyXRootD -======== - -Release Notes -============= - -------------- -Version 0.3.0 -------------- - -+ **New features** - * Improve integration with xrootd 4.x CopyProcess API by passing to python - all of the result dictionaties. This involves one API change, where the - CopyProcess.run method now returns a tuple (Status, [Results]) instead - of just Status. - -+ Major bug fixes - * Fix memory leaks by doing proper reference counting of objects created - within C++ code. - * Consistently create the URL objects from urls represented as strings. - -------------- -Version 0.2.0 -------------- - -+ **New features** - * Move copy process to xrootd4 API - * Move file and filesystem to xrootd4 API - * Implement file.fcntl and file.visa - -+ Major bug fixes - * Release the GIL while running copy jobs to allow other Python threads to run - -+ **Miscellaneous** - * Cleanup of compilation scripts - -------------- -Version 0.1.3 -------------- - -+ **Minor bug fixes** - * Make the MkDirFlags.MAKEPATH flag work (issue #6) - * Fix a segfault when listing invalid directory (issues #3 and #4) - * Fix a SystemError exception occuring when trying to copy a file (issue #2) - -+ **Miscellaneous** - * Fix file permissions diff --git a/bindings/python/docs/source/.static/css/custom.css b/bindings/python/docs/source/.static/css/custom.css deleted file mode 100755 index 1932628f8ad..00000000000 --- a/bindings/python/docs/source/.static/css/custom.css +++ /dev/null @@ -1,784 +0,0 @@ -/* - * rtd.css - * ~~~~~~~~~~~~~~~ - * - * Sphinx stylesheet -- sphinxdoc theme. Originally created by - * Armin Ronacher for Werkzeug. - * - * Customized for ReadTheDocs by Eric Pierce & Eric Holscher - * - * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ - -/* RTD colors - * light blue: #e8ecef - * medium blue: #8ca1af - * dark blue: #465158 - * dark grey: #444444 - * - * white hover: #d1d9df; - * medium blue hover: #697983; - * green highlight: #8ecc4c - * light blue (project bar): #e8ecef - */ - -@import url(http://fonts.googleapis.com/css?family=Alegreya:700); - -/* PAGE LAYOUT -------------------------------------------------------------- */ - -body { - font: 100%/1.5 "ff-meta-web-pro-1","ff-meta-web-pro-2",Arial,"Helvetica Neue",sans-serif; - text-align: center; - color: black; - background-color: #465158; - padding: 0; - margin: 0; -} - -div.document { - text-align: left; - background-color: #e8ecef; -} - -div.bodywrapper { - background-color: #ffffff; - border-left: 1px solid #ccc; - border-bottom: 1px solid #ccc; - margin: 0 0 0 16em; -} - -div.body { - margin: 0; - padding: 0.5em 1.3em; - min-width: 20em; -} - -div.related { - font-size: 1em; - background-color: #465158; -} - -div.documentwrapper { - float: left; - width: 100%; - background-color: #e8ecef; -} - -/* HEADINGS --------------------------------------------------------------- */ - -h1 { - margin: 0; - padding: 0.7em 0 0.3em 0; - font-size: 1.5em; - line-height: 1.15; - color: #111; - clear: both; -} - -h1.main { - font-size: 3.0em; - font-family: 'Alegreya', serif; -} - -h2 { - margin: 2em 0 0.2em 0; - font-size: 1.35em; - padding: 0; - color: #465158; -} - -h3 { - margin: 1em 0 -0.3em 0; - font-size: 1.2em; - color: #6c818f; -} - -div.body h1 a, div.body h2 a, div.body h3 a, div.body h4 a, div.body h5 a, div.body h6 a { - color: black; -} - -h1 a.anchor, h2 a.anchor, h3 a.anchor, h4 a.anchor, h5 a.anchor, h6 a.anchor { - display: none; - margin: 0 0 0 0.3em; - padding: 0 0.2em 0 0.2em; - color: #aaa !important; -} - -h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor, -h5:hover a.anchor, h6:hover a.anchor { - display: inline; -} - -h1 a.anchor:hover, h2 a.anchor:hover, h3 a.anchor:hover, h4 a.anchor:hover, -h5 a.anchor:hover, h6 a.anchor:hover { - color: #777; - background-color: #eee; -} - -/* LINKS ------------------------------------------------------------------ */ - -/* Normal links get a pseudo-underline */ -a { - color: #444; - text-decoration: none; - border-bottom: 1px solid #ccc; -} - -/* Links in sidebar, TOC, index trees and tables have no underline */ -.sphinxsidebar a, -.toctree-wrapper a, -.indextable a, -#indices-and-tables a { - color: #444; - text-decoration: none; - border-bottom: none; -} - -/* Most links get an underline-effect when hovered */ -a:hover, -div.toctree-wrapper a:hover, -.indextable a:hover, -#indices-and-tables a:hover { - color: #111; - text-decoration: none; - border-bottom: 1px solid #111; -} - -/* Footer links */ -div.footer a { - color: #86989B; - text-decoration: none; - border: none; -} -div.footer a:hover { - color: #a6b8bb; - text-decoration: underline; - border: none; -} - -/* Permalink anchor (subtle grey with a red hover) */ -div.body a.headerlink { - color: #ccc; - font-size: 1em; - margin-left: 6px; - padding: 0 4px 0 4px; - text-decoration: none; - border: none; -} -div.body a.headerlink:hover { - color: #c60f0f; - border: none; -} - -/* NAVIGATION BAR --------------------------------------------------------- */ - -div.related ul { - height: 2.5em; -} - -div.related ul li { - margin: 0; - padding: 0.65em 0; - float: left; - display: block; - color: white; /* For the >> separators */ - font-size: 0.8em; -} - -div.related ul li.right { - float: right; - margin-right: 5px; - color: transparent; /* Hide the | separators */ -} - -/* "Breadcrumb" links in nav bar */ -div.related ul li a { - order: none; - background-color: inherit; - font-weight: bold; - margin: 6px 0 6px 4px; - line-height: 1.75em; - color: #ffffff; - padding: 0.4em 0.8em; - border: none; - border-radius: 3px; -} -/* previous / next / modules / index links look more like buttons */ -div.related ul li.right a { - margin: 0.375em 0; - background-color: #697983; - text-shadow: 0 1px rgba(0, 0, 0, 0.5); - border-radius: 3px; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; -} -/* All navbar links light up as buttons when hovered */ -div.related ul li a:hover { - background-color: #8ca1af; - color: #ffffff; - text-decoration: none; - border-radius: 3px; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; -} -/* Take extra precautions for tt within links */ -a tt, -div.related ul li a tt { - background: inherit !important; - color: inherit !important; -} - -/* SIDEBAR ---------------------------------------------------------------- */ - -div.sphinxsidebarwrapper { - padding: 0; -} - -div.sphinxsidebar { - margin: 0; - margin-left: -100%; - float: left; - top: 3em; - left: 0; - padding: 0 1em; - width: 14em; - font-size: 1em; - text-align: left; - background-color: #e8ecef; -} - -div.sphinxsidebar img { - max-width: 12em; -} - -div.sphinxsidebar h3, -div.sphinxsidebar h4, -div.sphinxsidebar p.logo { - margin: 1.2em 0 0.3em 0; - font-size: 1em; - padding: 0; - color: #222222; - font-family: "ff-meta-web-pro-1", "ff-meta-web-pro-2", "Arial", "Helvetica Neue", sans-serif; -} - -div.sphinxsidebar h3 a { - color: #444444; -} - -div.sphinxsidebar ul, -div.sphinxsidebar p { - margin-top: 0; - padding-left: 0; - line-height: 130%; - background-color: #e8ecef; -} - -/* No bullets for nested lists, but a little extra indentation */ -div.sphinxsidebar ul ul { - list-style-type: none; - margin-left: 1.5em; - padding: 0; -} - -/* A little top/bottom padding to prevent adjacent links' borders - * from overlapping each other */ -div.sphinxsidebar ul li { - padding: 1px 0; -} - -/* A little left-padding to make these align with the ULs */ -div.sphinxsidebar p.topless { - padding-left: 0 0 0 1em; -} - -/* Make these into hidden one-liners */ -div.sphinxsidebar ul li, -div.sphinxsidebar p.topless { - white-space: nowrap; - overflow: hidden; -} -/* ...which become visible when hovered */ -div.sphinxsidebar ul li:hover, -div.sphinxsidebar p.topless:hover { - overflow: visible; -} - -/* Search text box and "Go" button */ -#searchbox { - margin-top: 2em; - margin-bottom: 1em; - background: #ddd; - padding: 0.5em; - border-radius: 6px; - -moz-border-radius: 6px; - -webkit-border-radius: 6px; -} -#searchbox h3 { - margin-top: 0; -} - -/* Make search box and button abut and have a border */ -input, -div.sphinxsidebar input { - border: 1px solid #999; - float: left; -} - -/* Search textbox */ -input[type="text"] { - margin: 0; - padding: 0 3px; - height: 20px; - width: 144px; - border-top-left-radius: 3px; - border-bottom-left-radius: 3px; - -moz-border-radius-topleft: 3px; - -moz-border-radius-bottomleft: 3px; - -webkit-border-top-left-radius: 3px; - -webkit-border-bottom-left-radius: 3px; -} -/* Search button */ -input[type="submit"] { - margin: 0 0 0 -1px; /* -1px prevents a double-border with textbox */ - height: 22px; - color: #444; - background-color: #e8ecef; - padding: 1px 4px; - font-weight: bold; - border-top-right-radius: 3px; - border-bottom-right-radius: 3px; - -moz-border-radius-topright: 3px; - -moz-border-radius-bottomright: 3px; - -webkit-border-top-right-radius: 3px; - -webkit-border-bottom-right-radius: 3px; -} -input[type="submit"]:hover { - color: #ffffff; - background-color: #8ecc4c; -} - -div.sphinxsidebar p.searchtip { - clear: both; - padding: 0.5em 0 0 0; - background: #ddd; - color: #666; - font-size: 0.9em; -} - -/* Sidebar links are unusual */ -div.sphinxsidebar li a, -div.sphinxsidebar p a { - background: #e8ecef; /* In case links overlap main content */ - border-radius: 3px; - -moz-border-radius: 3px; - -webkit-border-radius: 3px; - border: 1px solid transparent; /* To prevent things jumping around on hover */ - padding: 0 5px 0 5px; -} -div.sphinxsidebar li a:hover, -div.sphinxsidebar p a:hover { - color: #111; - text-decoration: none; - border: 1px solid #888; -} -div.sphinxsidebar p.logo a { - border: 0; -} - -/* Tweak any link appearing in a heading */ -div.sphinxsidebar h3 a { -} - -/* OTHER STUFF ------------------------------------------------------------ */ - -cite, code, tt { - font-family: 'Consolas', 'Deja Vu Sans Mono', - 'Bitstream Vera Sans Mono', monospace; - font-size: 0.95em; - letter-spacing: 0.01em; -} - -tt { - background-color: #f2f2f2; - color: #444; -} - -tt.descname, tt.descclassname, tt.xref { - border: 0; -} - -hr { - border: 1px solid #abc; - margin: 2em; -} - -pre, #_fontwidthtest { - font-family: 'Consolas', 'Deja Vu Sans Mono', - 'Bitstream Vera Sans Mono', monospace; - margin: 1em 2em; - font-size: 0.95em; - letter-spacing: 0.015em; - line-height: 120%; - padding: 0.5em; - border: 1px solid #ccc; - background-color: #eee; - border-radius: 6px; - -moz-border-radius: 6px; - -webkit-border-radius: 6px; -} - -pre a { - color: inherit; - text-decoration: underline; -} - -td.linenos pre { - margin: 1em 0em; -} - -td.code pre { - margin: 1em 0em; -} - -div.quotebar { - background-color: #f8f8f8; - max-width: 250px; - float: right; - padding: 2px 7px; - border: 1px solid #ccc; -} - -div.topic { - background-color: #f8f8f8; -} - -table { - border-collapse: collapse; - margin: 0 -0.5em 0 -0.5em; -} - -table td, table th { - padding: 0.2em 0.5em 0.2em 0.5em; -} - -/* ADMONITIONS AND WARNINGS ------------------------------------------------- */ - -/* Shared by admonitions, warnings and sidebars */ -div.admonition, -div.warning, -div.sidebar { - font-size: 0.9em; - margin: 2em; - padding: 0; - /* - border-radius: 6px; - -moz-border-radius: 6px; - -webkit-border-radius: 6px; - */ -} -div.admonition p, -div.warning p, -div.sidebar p { - margin: 0.5em 1em 0.5em 1em; - padding: 0; -} -div.admonition pre, -div.warning pre, -div.sidebar pre { - margin: 0.4em 1em 0.4em 1em; -} -div.admonition p.admonition-title, -div.warning p.admonition-title, -div.sidebar p.sidebar-title { - margin: 0; - padding: 0.1em 0 0.1em 0.5em; - color: white; - font-weight: bold; - font-size: 1.1em; - text-shadow: 0 1px rgba(0, 0, 0, 0.5); -} -div.admonition ul, div.admonition ol, -div.warning ul, div.warning ol, -div.sidebar ul, div.sidebar ol { - margin: 0.1em 0.5em 0.5em 3em; - padding: 0; -} - -/* Admonitions and sidebars only */ -div.admonition, div.sidebar { - border: 1px solid #609060; - background-color: #e9ffe9; -} -div.admonition p.admonition-title, -div.sidebar p.sidebar-title { - background-color: #70A070; - border-bottom: 1px solid #609060; -} - -/* Warnings only */ -div.warning { - border: 1px solid #900000; - background-color: #ffe9e9; -} -div.warning p.admonition-title { - background-color: #b04040; - border-bottom: 1px solid #900000; -} - -/* Sidebars only */ -div.sidebar { - max-width: 30%; -} - -div.versioninfo { - margin: 1em 0 0 0; - border: 1px solid #ccc; - background-color: #DDEAF0; - padding: 8px; - line-height: 1.3em; - font-size: 0.9em; -} - -.viewcode-back { - font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', - 'Verdana', sans-serif; -} - -div.viewcode-block:target { - background-color: #f4debf; - border-top: 1px solid #ac9; - border-bottom: 1px solid #ac9; -} - -dl { - margin: 1em 0 2.5em 0; -} - -/* Highlight target when you click an internal link */ -dt:target { - background: #ffe080; -} -/* Don't highlight whole divs */ -div.highlight { - background: transparent; -} -/* But do highlight spans (so search results can be highlighted) */ -span.highlight { - background: #ffe080; -} - -div.footer { - background-color: #465158; - color: #eeeeee; - padding: 0 2em 2em 2em; - clear: both; - font-size: 0.8em; - text-align: center; -} - -p { - margin: 0.8em 0 0.5em 0; -} - -.section p img.math { - margin: 0; -} - -.section p img { - margin: 1em 2em; -} - -/* MOBILE LAYOUT -------------------------------------------------------------- */ - -@media screen and (max-width: 600px) { - - h1, h2, h3, h4, h5 { - position: relative; - } - - ul { - padding-left: 1.25em; - } - - div.bodywrapper a.headerlink, #indices-and-tables h1 a { - color: #e6e6e6; - font-size: 80%; - float: right; - line-height: 1.8; - position: absolute; - right: -0.7em; - visibility: inherit; - } - - div.bodywrapper h1 a.headerlink, #indices-and-tables h1 a { - line-height: 1.5; - } - - pre { - font-size: 0.7em; - overflow: auto; - word-wrap: break-word; - white-space: pre-wrap; - } - - div.related ul { - height: 2.5em; - padding: 0; - text-align: left; - } - - div.related ul li { - clear: both; - color: #465158; - padding: 0.2em 0; - } - - div.related ul li:last-child { - border-bottom: 1px dotted #8ca1af; - padding-bottom: 0.4em; - margin-bottom: 1em; - width: 100%; - } - - div.related ul li a { - color: #465158; - padding-right: 0; - } - - div.related ul li a:hover { - background: inherit; - color: inherit; - } - - div.related ul li.right { - clear: none; - padding: 0.65em 0; - margin-bottom: 0.5em; - } - - div.related ul li.right a { - color: #fff; - padding-right: 0.8em; - } - - div.related ul li.right a:hover { - background-color: #8ca1af; - } - - div.body { - clear: both; - min-width: 0; - word-wrap: break-word; - } - - div.bodywrapper { - margin: 0 0 0 0; - } - - div.sphinxsidebar { - float: none; - margin: 0; - width: auto; - } - - div.sphinxsidebar input[type="text"] { - height: 2em; - line-height: 2em; - width: 70%; - } - - div.sphinxsidebar input[type="submit"] { - height: 2em; - margin-left: 0.5em; - width: 20%; - } - - div.sphinxsidebar p.searchtip { - background: inherit; - margin-bottom: 1em; - } - - div.sphinxsidebar ul li, div.sphinxsidebar p.topless { - white-space: normal; - } - - .bodywrapper img { - display: block; - margin-left: auto; - margin-right: auto; - max-width: 100%; - } - - div.documentwrapper { - float: none; - } - - div.admonition, div.warning, pre, blockquote { - margin-left: 0em; - margin-right: 0em; - } - - .body p img { - margin: 0; - } - - #searchbox { - background: transparent; - } - - .related:not(:first-child) li { - display: none; - } - - .related:not(:first-child) li.right { - display: block; - } - - div.footer { - padding: 1em; - } - - .rtd_doc_footer .rtd-badge { - float: none; - margin: 1em auto; - position: static; - } - - .rtd_doc_footer .rtd-badge.revsys-inline { - margin-right: auto; - margin-bottom: 2em; - } - - table.indextable { - display: block; - width: auto; - } - - .indextable tr { - display: block; - } - - .indextable td { - display: block; - padding: 0; - width: auto !important; - } - - .indextable td dt { - margin: 1em 0; - } - - ul.search { - margin-left: 0.25em; - } - - ul.search li div.context { - font-size: 90%; - line-height: 1.1; - margin-bottom: 1; - margin-left: 0; - } - -} \ No newline at end of file diff --git a/bindings/python/docs/source/.static/img/favicon.ico b/bindings/python/docs/source/.static/img/favicon.ico deleted file mode 100644 index 21490b9e6b0276985105630aa68436b9044e2f8c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4286 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 N002ovPDHLkV1m7sM5zD( diff --git a/bindings/python/docs/source/.templates/layout.html b/bindings/python/docs/source/.templates/layout.html deleted file mode 100755 index 3f5220fafd4..00000000000 --- a/bindings/python/docs/source/.templates/layout.html +++ /dev/null @@ -1,4 +0,0 @@ -{% extends "!layout.html" %} -{% set css_files = css_files + ['_static/css/custom.css']%} -{% set logo = 'img/xrootd-200x68.png'%} -{% set favicon = 'img/favicon.ico?v=2'%} \ No newline at end of file diff --git a/bindings/python/docs/source/conf.py b/bindings/python/docs/source/conf.py deleted file mode 100644 index e907a79eda7..00000000000 --- a/bindings/python/docs/source/conf.py +++ /dev/null @@ -1,243 +0,0 @@ -# -*- coding: utf-8 -*- -# -# PyXRootD documentation build configuration file, created by -# sphinx-quickstart on Tue Mar 19 22:57:10 2013. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys, os - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) - -# -- General configuration ----------------------------------------------------- - -# If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', - 'sphinx.ext.intersphinx', 'sphinx.ext.todo', - 'sphinx.ext.coverage', 'sphinx.ext.viewcode'] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['.templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'pyxrootd' -copyright = u'2013, CERN' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = 'current' -# The full version, including alpha/beta/rc tags. -release = 'current' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = [] - -# The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - -# -- Options for HTML output --------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -html_theme = 'basic' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -html_logo = {} - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['.static'] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Output file base name for HTML help builder. -htmlhelp_basename = 'pyxrootddoc' - -# -- Options for LaTeX output -------------------------------------------------- - -latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). -latex_documents = [ - ('index', 'pyxrootd.tex', u'pyxrootd Documentation', - u'CERN', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - -# -- Options for manual page output -------------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'pyxrootd', u'pyxrootd Documentation', - [u'CERN'], 1) -] - -# If true, show URL addresses after external links. -#man_show_urls = False - -# -- Options for Texinfo output ------------------------------------------------ - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ('index', 'pyxrootd', u'pyxrootd Documentation', - u'CERN', 'pyxrootd', 'One line description of project.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -#texinfo_appendices = [] - -# If false, no module index is generated. -#texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' - -# Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'http://docs.python.org/': None} diff --git a/bindings/python/docs/source/examples.rst b/bindings/python/docs/source/examples.rst deleted file mode 100644 index 66936148e64..00000000000 --- a/bindings/python/docs/source/examples.rst +++ /dev/null @@ -1,11 +0,0 @@ -============ -**Examples** -============ - -.. toctree:: - :numbered: - :maxdepth: 2 - - examples/filesystem - examples/file - examples/copyprocess \ No newline at end of file diff --git a/bindings/python/docs/source/examples/copyprocess.rst b/bindings/python/docs/source/examples/copyprocess.rst deleted file mode 100644 index 3683d44b4c0..00000000000 --- a/bindings/python/docs/source/examples/copyprocess.rst +++ /dev/null @@ -1,14 +0,0 @@ -======================== -``CopyProcess`` examples -======================== - -.. include:: ../../../examples/copyprocess.py - :start-line: 1 - :end-line: 3 - -.. literalinclude:: ../../../examples/copyprocess.py - :lines: 29- - -.. include:: ../../../examples/copyprocess.py - :start-line: 4 - :end-line: 27 diff --git a/bindings/python/docs/source/examples/file.rst b/bindings/python/docs/source/examples/file.rst deleted file mode 100644 index bbcdb5dadfd..00000000000 --- a/bindings/python/docs/source/examples/file.rst +++ /dev/null @@ -1,105 +0,0 @@ -================= -``File`` examples -================= - -This page includes some simple examples of how to use the ``pyxrootd`` `File` -object to manipulate files on an ``xrootd`` server. - -We'll use the following `File` object as a basis for the rest of the examples:: - - from XRootD import client - from XRootD.client.flags import OpenFlags - - with client.File() as f: - f.open('root://someserver//tmp/eggs', OpenFlags.UPDATE) - f.write('green\neggs\nand\nham\n') - -.. ----------------------------------------------------------------------------- -.. read -.. ----------------------------------------------------------------------------- - -.. include:: ../../../examples/read.py - :start-line: 1 - :end-line: 3 - -.. literalinclude:: ../../../examples/read.py - :lines: 17- - -.. include:: ../../../examples/read.py - :start-line: 4 - :end-line: 8 - -.. ----------------------------------------------------------------------------- -.. write -.. ----------------------------------------------------------------------------- - -.. include:: ../../../examples/write.py - :start-line: 1 - :end-line: 3 - -.. literalinclude:: ../../../examples/write.py - :lines: 16- - -.. include:: ../../../examples/write.py - :start-line: 4 - :end-line: 8 - -.. ----------------------------------------------------------------------------- -.. iterate -.. ----------------------------------------------------------------------------- - -.. include:: ../../../examples/iterate.py - :start-line: 1 - :end-line: 3 - -.. literalinclude:: ../../../examples/iterate.py - :lines: 18- - -.. include:: ../../../examples/iterate.py - :start-line: 4 - :end-line: 10 - -.. ----------------------------------------------------------------------------- -.. readlines -.. ----------------------------------------------------------------------------- - -.. include:: ../../../examples/readlines.py - :start-line: 1 - :end-line: 3 - -.. literalinclude:: ../../../examples/readlines.py - :lines: 18- - -.. include:: ../../../examples/readlines.py - :start-line: 4 - :end-line: 10 - -.. ----------------------------------------------------------------------------- -.. readchunks -.. ----------------------------------------------------------------------------- - -.. include:: ../../../examples/readchunks.py - :start-line: 1 - :end-line: 3 - -.. literalinclude:: ../../../examples/readchunks.py - :lines: 16- - -.. include:: ../../../examples/readchunks.py - :start-line: 4 - :end-line: 8 - -.. ----------------------------------------------------------------------------- -.. vector_read -.. ----------------------------------------------------------------------------- - -.. include:: ../../../examples/vector_read.py - :start-line: 1 - :end-line: 3 - -.. literalinclude:: ../../../examples/vector_read.py - :lines: 17- - -.. include:: ../../../examples/vector_read.py - :start-line: 4 - :end-line: 9 diff --git a/bindings/python/docs/source/examples/filesystem.rst b/bindings/python/docs/source/examples/filesystem.rst deleted file mode 100644 index 5b2fb6568bb..00000000000 --- a/bindings/python/docs/source/examples/filesystem.rst +++ /dev/null @@ -1,118 +0,0 @@ -======================= -``FileSystem`` examples -======================= - -This page includes some simple examples of basic usage of the ``pyxrootd`` -`FileSystem` object to interact with an ``xrootd`` server. - -We'll use the following imports and `FileSystem` object as the basis for the -rest of the examples:: - - from XRootD import client - from XRootD.client.flags import DirListFlags, OpenFlags, MkDirFlags, QueryCode - - myclient = client.FileSystem('root://someserver:1094') - -.. ----------------------------------------------------------------------------- -.. copy -.. ----------------------------------------------------------------------------- - -.. include:: ../../../examples/copy.py - :start-line: 1 - :end-line: 3 - -.. literalinclude:: ../../../examples/copy.py - :lines: 11- - -.. include:: ../../../examples/copy.py - :start-line: 4 - :end-line: 6 - -.. ----------------------------------------------------------------------------- -.. dirlist -.. ----------------------------------------------------------------------------- - -.. include:: ../../../examples/dirlist.py - :start-line: 1 - :end-line: 3 - -.. literalinclude:: ../../../examples/dirlist.py - :lines: 16- - -.. include:: ../../../examples/dirlist.py - :start-line: 4 - :end-line: 9 - -.. ----------------------------------------------------------------------------- -.. mkdir -.. ----------------------------------------------------------------------------- - -.. include:: ../../../examples/mkdir.py - :start-line: 1 - :end-line: 3 - -.. literalinclude:: ../../../examples/mkdir.py - :lines: 9- - -.. ----------------------------------------------------------------------------- -.. rmdir -.. ----------------------------------------------------------------------------- - -.. include:: ../../../examples/rmdir.py - :start-line: 1 - :end-line: 3 - -.. literalinclude:: ../../../examples/rmdir.py - :lines: 8- - -.. ----------------------------------------------------------------------------- -.. mv -.. ----------------------------------------------------------------------------- - -.. include:: ../../../examples/mv.py - :start-line: 1 - :end-line: 3 - -.. literalinclude:: ../../../examples/mv.py - :lines: 8- - -.. ----------------------------------------------------------------------------- -.. rm -.. ----------------------------------------------------------------------------- - -.. include:: ../../../examples/rm.py - :start-line: 1 - :end-line: 3 - -.. literalinclude:: ../../../examples/rm.py - :lines: 8- - -.. ----------------------------------------------------------------------------- -.. locate -.. ----------------------------------------------------------------------------- - -.. include:: ../../../examples/locate.py - :start-line: 1 - :end-line: 3 - -.. literalinclude:: ../../../examples/locate.py - :lines: 13- - -.. include:: ../../../examples/locate.py - :start-line: 4 - :end-line: 7 - -.. ----------------------------------------------------------------------------- -.. query -.. ----------------------------------------------------------------------------- -.. include:: ../../../examples/query.py - :start-line: 1 - :end-line: 3 - -.. literalinclude:: ../../../examples/query.py - :lines: 17- - -.. include:: ../../../examples/query.py - :start-line: 4 - :end-line: 11 - diff --git a/bindings/python/docs/source/gettingstarted.rst b/bindings/python/docs/source/gettingstarted.rst deleted file mode 100644 index fa777899ea4..00000000000 --- a/bindings/python/docs/source/gettingstarted.rst +++ /dev/null @@ -1,88 +0,0 @@ -=================== -**Getting Started** -=================== - -``File`` and ``FileSystem`` usage -================================= - -Synchronous and Asynchronous requests -------------------------------------- - -The new XRootD client is capable of making both synchronous and asynchronous -requests. Therefore, ``pyxrootd`` must also be capable of this, although most -people will probably only need synchronous functionality most of the time. - -Each method in the `File` and `FileSystem` classes can take an optional -``callback`` argument. If you don't pass in a callback, you're asking for a -synchronous request. If you do, the request becomes asynchronous (assuming the -callback is valid, of course), and your callback will be invoked when the -response is received. - -``pyxrootd`` comes with a callback helper class: -:mod:`XRootD.client.utils.AsyncResponseHandler`. If you use an instance of this -class as your callback, you can call the :func:`wait` function whenever you -like after the request is made, and it will block until the response is -received. - -Return types ------------- - -.. note:: The return signature of the `File` and `FileSystem` functions changes - depending on whether you make a synchronous or asynchronous request, - so be careful. - -Synchronous requests -******************** - -You always get a **2-tuple** in return when you make a synchronous request. The -first item in the tuple is always an :mod:`XRootD.client.responses.XRootDStatus` -instance. The ``XRootDStatus`` object tells you whether the request was -successful or not, along with some other information. - -The second item in the tuple depends on which request you made. If it's a simple -request without any response information, such as -:func:`XRootD.client.FileSystem.ping`, the second item is ``None``. Otherwise, -you get one of the objects in :mod:`XRootD.client.responses`. For example, if -you call :func:`XRootD.client.FileSystem.dirlist`, you get an instance of -:mod:`XRootD.client.responses.DirectoryList`. - -Asynchronous requests -********************* - -You get a single object, an :mod:`XRootD.client.responses.XRootDStatus` -instance, when you fire off an asynchronous request. This can inform you about -any immediate problems in making the request, e.g. the network is not reachable -(or something). - -However, when that callback you gave us (remember him?) gets triggered - you get -not 2, but a **3-tuple**. The first, again, is an ``XRootDStatus``. The second -follows the synchronous pattern, i.e. you get your response object, or ``None``. -The third item is an :mod:`XRootD.client.responses.HostList` instance. This -contains a list of all the hosts that were implicated while carrying out that -request you made. - -Timeouts --------- - -All of the functions in this class accept an optional ``timeout`` keyword -argument. The default timeout is `0`, which means that the environment default -will be used. You can change the timeout value on a per-request basis with the -optional parameter, or you can set it system-wide with the -``XRD_REQUESTTIMEOUT`` environment variable. Also, the timeout resolution -(time interval between timeout detection) can be set with the -``XRD_TIMEOUTRESOLUTION`` environment variable. - -Copying files -============= - -If you want to copy files simply and quickly with default options, you can just -use :func:`XRootD.client.FileSystem.copy`. - -But if you want more configurable copy jobs, or you want to copy a large number -of files at once, you can use :mod:`XRootD.client.CopyProcess`. - -You can even pass in a copy progress handler to :func:`CopyProcess.run()` and -use it to build some kind of progress display (much like the ``xrdcopy`` -command does). - - diff --git a/bindings/python/docs/source/index.rst b/bindings/python/docs/source/index.rst deleted file mode 100644 index 5f64698d1a6..00000000000 --- a/bindings/python/docs/source/index.rst +++ /dev/null @@ -1,38 +0,0 @@ -======================================== -``pyxrootd``: Python bindings for XRootD -======================================== - -``pyxrootd`` is a set of simple but pythonic bindings for -`XRootD `_. It is designed to make it easy to -interface with the XRootD client, by writing Python instead of having to write -C++. - -For bug reporting and issue tracking, please see `the pyxrootd github issue -tracker `_ - -User Guide -========== - -.. toctree:: - :numbered: - :maxdepth: 2 - - install - gettingstarted - examples - -API Reference -============= - -.. toctree:: - :numbered: - :maxdepth: 1 - - modules/client/filesystem - modules/client/file - modules/client/copyprocess - modules/client/responses - modules/client/flags - modules/client/url - modules/client/utils - diff --git a/bindings/python/docs/source/install.rst b/bindings/python/docs/source/install.rst deleted file mode 100644 index 6cd6b7a4b65..00000000000 --- a/bindings/python/docs/source/install.rst +++ /dev/null @@ -1,6 +0,0 @@ -=========================== -**Installing** ``pyxrootd`` -=========================== - -.. include:: ../../README.rst - :start-line: 8 \ No newline at end of file diff --git a/bindings/python/docs/source/modules/client/copyprocess.rst b/bindings/python/docs/source/modules/client/copyprocess.rst deleted file mode 100644 index 46f7c0cbe7e..00000000000 --- a/bindings/python/docs/source/modules/client/copyprocess.rst +++ /dev/null @@ -1,17 +0,0 @@ -=============================================== -:mod:`XRootD.client.CopyProcess`: Copying files -=============================================== - -Class Reference ---------------- - -.. module:: XRootD.client - -.. autoclass:: XRootD.client.CopyProcess - -Methods -******* - -.. automethod:: XRootD.client.CopyProcess.add_job -.. automethod:: XRootD.client.CopyProcess.prepare -.. automethod:: XRootD.client.CopyProcess.run \ No newline at end of file diff --git a/bindings/python/docs/source/modules/client/file.rst b/bindings/python/docs/source/modules/client/file.rst deleted file mode 100644 index 168e9599335..00000000000 --- a/bindings/python/docs/source/modules/client/file.rst +++ /dev/null @@ -1,40 +0,0 @@ -================================================ -:mod:`XRootD.client.File`: File-based operations -================================================ - -.. module:: XRootD.client - -.. autoclass:: XRootD.client.File - -Similarities with Python built-in `file` object ------------------------------------------------ - -To provide an interface like the python built-in file object, the -__iter__(), next(), readline() and readlines() methods have been implemented. -These look for newlines in files, which may not always be appropriate, -especially for binary data. - -Additionally, these methods can't be called asynchronously, and they don't -return an ``XRootDStatus`` object like the others. You only get the data that -was read. - -Class Reference ---------------- - -Methods -******* - -.. automethod:: XRootD.client.File.open -.. automethod:: XRootD.client.File.close -.. automethod:: XRootD.client.File.stat -.. automethod:: XRootD.client.File.read -.. automethod:: XRootD.client.File.readline -.. automethod:: XRootD.client.File.readlines -.. automethod:: XRootD.client.File.readchunks -.. automethod:: XRootD.client.File.write -.. automethod:: XRootD.client.File.sync -.. automethod:: XRootD.client.File.truncate -.. automethod:: XRootD.client.File.vector_read -.. automethod:: XRootD.client.File.is_open -.. automethod:: XRootD.client.File.set_property -.. automethod:: XRootD.client.File.get_property diff --git a/bindings/python/docs/source/modules/client/filesystem.rst b/bindings/python/docs/source/modules/client/filesystem.rst deleted file mode 100644 index 6592259fd1a..00000000000 --- a/bindings/python/docs/source/modules/client/filesystem.rst +++ /dev/null @@ -1,38 +0,0 @@ -============================================================ -:mod:`XRootD.client.FileSystem`: Filesystem-based operations -============================================================ - -Class Reference ---------------- - -.. module:: XRootD.client - -.. autoclass:: XRootD.client.FileSystem - -Attributes -********** - -.. autoattribute:: XRootD.client.FileSystem.url - -Methods -******* - -.. automethod:: XRootD.client.FileSystem.copy -.. automethod:: XRootD.client.FileSystem.locate -.. automethod:: XRootD.client.FileSystem.deeplocate -.. automethod:: XRootD.client.FileSystem.mv -.. automethod:: XRootD.client.FileSystem.query -.. automethod:: XRootD.client.FileSystem.truncate -.. automethod:: XRootD.client.FileSystem.rm -.. automethod:: XRootD.client.FileSystem.mkdir -.. automethod:: XRootD.client.FileSystem.rmdir -.. automethod:: XRootD.client.FileSystem.chmod -.. automethod:: XRootD.client.FileSystem.ping -.. automethod:: XRootD.client.FileSystem.stat -.. automethod:: XRootD.client.FileSystem.statvfs -.. automethod:: XRootD.client.FileSystem.protocol -.. automethod:: XRootD.client.FileSystem.dirlist -.. automethod:: XRootD.client.FileSystem.sendinfo -.. automethod:: XRootD.client.FileSystem.prepare -.. automethod:: XRootD.client.FileSystem.set_property -.. automethod:: XRootD.client.FileSystem.get_property diff --git a/bindings/python/docs/source/modules/client/flags.rst b/bindings/python/docs/source/modules/client/flags.rst deleted file mode 100644 index c2ef274a1ee..00000000000 --- a/bindings/python/docs/source/modules/client/flags.rst +++ /dev/null @@ -1,114 +0,0 @@ -=============================================== -:mod:`XRootD.client.flags`: Flags and constants -=============================================== - -.. module:: XRootD.client.flags - -.. attribute:: OpenFlags - - | :mod:`OpenFlags.NONE`: Nothing - | :mod:`OpenFlags.DELETE`: Open a new file, deleting any existing file - | :mod:`OpenFlags.FORCE`: Ignore file usage rules - | :mod:`OpenFlags.NEW`: Open the file only if it does not already exist - | :mod:`OpenFlags.READ`: Open only for reading - | :mod:`OpenFlags.UPDATE`: Open for reading and writing - | :mod:`OpenFlags.REFRESH`: Refresh the cached information on file location. - Voids `NoWait`. - | :mod:`OpenFlags.MAKEPATH`: Create directory path if it doesn't already exist - | :mod:`OpenFlags.APPEND`: Open only for appending - | :mod:`OpenFlags.REPLICA`: The file is being opened for replica creation - | :mod:`OpenFlags.POSC`: Enable `Persist On Successful Close` processing - | :mod:`OpenFlags.NOWAIT`: Open the file only if it does not cause a wait. - For :func:`XRootD.client.FileSystem.locate` : - provide a location as soon as one becomes known. - This means that not all locations are necessarily - returned. If the file does not exist a wait is - still imposed. - | :mod:`OpenFlags.SEQIO`: File will be read or written sequentially - -.. attribute:: MkDirFlags - - | :mod:`MkDirFlags.NONE`: Nothing special - | :mod:`MkDirFlags.MAKEPATH`: Create the entire directory tree if it doesn't - exist - -.. attribute:: DirListFlags - - | :mod:`DirListFlags.NONE`: Nothing special - | :mod:`DirListFlags.STAT`: Stat each entry - | :mod:`DirListFlags.LOCATE`: Locate all servers hosting the directory and - send the dirlist request to all of them - -.. attribute:: PrepareFlags - - | :mod:`PrepareFlags.STAGE`: Stage the file to disk if it is not online - | :mod:`PrepareFlags.WRITEMODE`: The file will be accessed for modification - | :mod:`PrepareFlags.COLOCATE`: Co-locate staged files, if possible - | :mod:`PrepareFlags.FRESH`: Refresh file access time even if the location - is known - -.. attribute:: AccessMode - - | :mod:`AccessMode.NONE`: Default, no flags - | :mod:`AccessMode.UR`: Owner readable - | :mod:`AccessMode.UW`: Owner writable - | :mod:`AccessMode.UX`: Owner executable/browsable - | :mod:`AccessMode.GR`: Group readable - | :mod:`AccessMode.GW`: Group writable - | :mod:`AccessMode.GX`: Group executable/browsable - | :mod:`AccessMode.OR`: World readable - | :mod:`AccessMode.OW`: World writable - | :mod:`AccessMode.OX`: World executable/browsable - -.. attribute:: QueryCode - - | :mod:`QueryCode.STATS`: Query server stats - | :mod:`QueryCode.PREPARE`: Query prepare status - | :mod:`QueryCode.CHECKSUM`: Query file checksum - | :mod:`QueryCode.XATTR`: Query file extended attributes - | :mod:`QueryCode.SPACE`: Query logical space stats - | :mod:`QueryCode.CHECKSUMCANCEL`: Query file checksum cancellation - | :mod:`QueryCode.CONFIG`: Query server configuration - | :mod:`QueryCode.VISA`: Query file visa attributes - | :mod:`QueryCode.OPAQUE`: Implementation dependent - | :mod:`QueryCode.OPAQUEFILE`: Implementation dependent - -.. attribute:: HostTypes - - | :mod:`HostTypes.IS_MANAGER`: Manager - | :mod:`HostTypes.IS_SERVER`: Data server - | :mod:`HostTypes.ATTR_META`: Meta manager attribute - | :mod:`HostTypes.ATTR_PROXY`: Proxy server attribute - | :mod:`HostTypes.ATTR_SUPER`: Supervisor attribute - -.. attribute:: StatInfoFlags - - | :mod:`StatInfoFlags.X_BIT_SET`: Executable/searchable bit set - | :mod:`StatInfoFlags.IS_DIR`: This is a directory - | :mod:`StatInfoFlags.OTHER`: Neither a file nor a directory - | :mod:`StatInfoFlags.OFFLINE`: File is not online (ie. on disk) - | :mod:`StatInfoFlags.POSC_PENDING`: File opened with POSC flag, not yet - successfully closed - | :mod:`StatInfoFlags.IS_READABLE`: Read access is allowed - | :mod:`StatInfoFlags.IS_WRITABLE`: Write access is allowed - -.. attribute:: LocationType - - Describes the node type and file status for a given location. Used with the - ``type`` attribute of :mod:`XRootD.client.responses.LocationInfo`. - - | :mod:`LocationType.MANAGER_ONLINE`: manager node where the file is online - | :mod:`LocationType.MANAGER_PENDING`: manager node where the file is pending - to be online - | :mod:`LocationType.SERVER_ONLINE`: server node where the file is online - | :mod:`LocationType.SERVER_PENDING`: server node where the file is pending - to be online - -.. attribute:: AccessType - - Describes the allowed access type for the file at given location Used with the - ``accesstype`` attribute of :mod:`XRootD.client.responses.LocationInfo`. - - | :mod:`AccessType.READ`: Read access is allowed - | :mod:`AccessType.READ_WRITE`: Write access is allowed - diff --git a/bindings/python/docs/source/modules/client/responses.rst b/bindings/python/docs/source/modules/client/responses.rst deleted file mode 100644 index 309d698a557..00000000000 --- a/bindings/python/docs/source/modules/client/responses.rst +++ /dev/null @@ -1,21 +0,0 @@ -======================================================= -:mod:`XRootD.client.responses`: Server response objects -======================================================= - -This page documents the various response objects that are returned by making -requests to an `XRootD` server. - -.. module:: XRootD.client.responses - -.. autoclass:: XRootD.client.responses.XRootDStatus() -.. autoclass:: XRootD.client.responses.DirectoryList() -.. autoclass:: XRootD.client.responses.ListEntry() -.. autoclass:: XRootD.client.responses.StatInfo() -.. autoclass:: XRootD.client.responses.StatInfoVFS() -.. autoclass:: XRootD.client.responses.VectorReadInfo() -.. autoclass:: XRootD.client.responses.ChunkInfo() -.. autoclass:: XRootD.client.responses.LocationInfo() -.. autoclass:: XRootD.client.responses.Location() -.. autoclass:: XRootD.client.responses.HostList() -.. autoclass:: XRootD.client.responses.HostInfo() -.. autoclass:: XRootD.client.responses.ProtocolInfo() \ No newline at end of file diff --git a/bindings/python/docs/source/modules/client/url.rst b/bindings/python/docs/source/modules/client/url.rst deleted file mode 100644 index 8b5168743db..00000000000 --- a/bindings/python/docs/source/modules/client/url.rst +++ /dev/null @@ -1,16 +0,0 @@ -=========================================== -:mod:`XRootD.client.URL`: XRootD URL object -=========================================== - -Class Reference ---------------- - -.. module:: XRootD.client - -.. autoclass:: XRootD.client.URL() - -Methods -******* - -.. automethod:: XRootD.client.URL.is_valid -.. automethod:: XRootD.client.URL.clear \ No newline at end of file diff --git a/bindings/python/docs/source/modules/client/utils.rst b/bindings/python/docs/source/modules/client/utils.rst deleted file mode 100644 index 2ef43d36b47..00000000000 --- a/bindings/python/docs/source/modules/client/utils.rst +++ /dev/null @@ -1,11 +0,0 @@ -=========================================== -:mod:`XRootD.client.utils`: Utility classes -=========================================== - -.. module:: XRootD.client.utils - -.. autoclass:: XRootD.client.utils.AsyncResponseHandler - :members: - -.. autoclass:: XRootD.client.utils.CopyProgressHandler - :members: \ No newline at end of file diff --git a/bindings/python/examples/copy.py b/bindings/python/examples/copy.py deleted file mode 100644 index 288261edc31..00000000000 --- a/bindings/python/examples/copy.py +++ /dev/null @@ -1,12 +0,0 @@ -""" -Copy a file ------------ - -See :mod:`XRootD.client.CopyProcess` if you need multiple/more configurable -copy jobs. -""" -from XRootD import client - -myclient = client.FileSystem('root://localhost') -status = myclient.copy('/tmp/spam', '/tmp/eggs', force=True) -assert status[0].ok diff --git a/bindings/python/examples/copyprocess.py b/bindings/python/examples/copyprocess.py deleted file mode 100644 index 132524064b9..00000000000 --- a/bindings/python/examples/copyprocess.py +++ /dev/null @@ -1,59 +0,0 @@ -""" -Add a number of copy jobs and run them in parallel with a progress handler --------------------------------------------------------------------------- - -Produces output similar to the following:: - - id: 1, total: 4 - source: /tmp/spam - target: /tmp/spam1 - processed: 20, total: 20 - end status: [SUCCESS] - id: 2, total: 4 - source: /tmp/spam - target: root://localhost//tmp/spam2 - processed: 20, total: 20 - end status: [SUCCESS] - id: 3, total: 4 - source: root://localhost//tmp/spam - target: /tmp/spam3 - processed: 20, total: 20 - end status: [SUCCESS] - id: 4, total: 4 - source: root://localhost//tmp/spam - target: root://localhost//tmp/spam4 - processed: 20, total: 20 - end status: [SUCCESS] - -""" -from XRootD import client - -class MyCopyProgressHandler(client.utils.CopyProgressHandler): - def begin(self, jobId, total, source, target): - print 'id: %d, total: %d' % (jobId, total) - print 'source: %s' % source - print 'target: %s' % target - - def end(self, jobId, result): - print 'end status:', jobId, result - - def update(self, jobId, processed, total): - print 'jobId: %d, processed: %d, total: %d' % (jobId, processed, total) - - def should_cancel( jobId ): - return False - -process = client.CopyProcess() - -# From local to local -process.add_job( '/tmp/spam', '/tmp/spam1' ) -# From local to remote -process.add_job( '/tmp/spam', 'root://localhost//tmp/spam2' ) -# From remote to local -process.add_job( 'root://localhost//tmp/spam', '/tmp/spam3' ) -# From remote to remote -process.add_job( 'root://localhost//tmp/spam', 'root://localhost//tmp/spam4' ) - -handler = MyCopyProgressHandler() -process.prepare() -process.run(handler) diff --git a/bindings/python/examples/dirlist.py b/bindings/python/examples/dirlist.py deleted file mode 100644 index 686e2749979..00000000000 --- a/bindings/python/examples/dirlist.py +++ /dev/null @@ -1,20 +0,0 @@ -""" -Ask a for a directory listing ------------------------------ - -Produces output similar to the following:: - - 2013-04-12 09:46:51 20 spam - 2013-04-05 08:23:00 4096 .xrootd - 2013-04-12 09:33:25 20 eggs -""" - -from XRootD import client -from XRootD.client.flags import DirListFlags - -myclient = client.FileSystem('root://localhost') -status, listing = myclient.dirlist('/tmp', DirListFlags.STAT) - -print listing.parent -for entry in listing: - print "{0} {1:>10} {2}".format(entry.statinfo.modtimestr, entry.statinfo.size, entry.name) \ No newline at end of file diff --git a/bindings/python/examples/fcntl.py b/bindings/python/examples/fcntl.py deleted file mode 100644 index 40ae965dfba..00000000000 --- a/bindings/python/examples/fcntl.py +++ /dev/null @@ -1,8 +0,0 @@ - -from XRootD import client -from XRootD.client.flags import OpenFlags - -with client.File() as f: - status, response = f.open('root://localhost//tmp/eggs', OpenFlags.DELETE) - status, response = f.fcntl( 'asdf' ) - print status, response diff --git a/bindings/python/examples/fcntl_async.py b/bindings/python/examples/fcntl_async.py deleted file mode 100644 index 2796027ce96..00000000000 --- a/bindings/python/examples/fcntl_async.py +++ /dev/null @@ -1,12 +0,0 @@ - -from XRootD import client -from XRootD.client.flags import OpenFlags -from time import sleep - -def callback( status, response, hostlist ): - print "Called:", status, response, hostlist - -with client.File() as f: - status, response = f.open('root://localhost//tmp/eggs', OpenFlags.DELETE) - status = f.fcntl( 'asdf', callback = callback ) - sleep(20) diff --git a/bindings/python/examples/fileproperties.py b/bindings/python/examples/fileproperties.py deleted file mode 100644 index d71df85c288..00000000000 --- a/bindings/python/examples/fileproperties.py +++ /dev/null @@ -1,40 +0,0 @@ -""" -Write a chunk of data to a file and test it's property system -------------------------------------------------------------- - -Produces the following output:: - -spam\n -true\n -true\n -true\n -localhost:1094\n -root://localhost:1094//tmp/eggs\n -True\n -True\n -True\n -false\n -false\n -false\n - -""" -from XRootD import client -from XRootD.client.flags import OpenFlags - -with client.File() as f: - f.open('root://localhost//tmp/eggs', OpenFlags.DELETE) - - data = 'spam\n' - f.write(data) - print f.read()[1] - print f.get_property( "ReadRecovery" ) - print f.get_property( "WriteRecovery" ) - print f.get_property( "FollowRedirects" ) - print f.get_property( "DataServer" ) - print f.get_property( "LastURL" ) - print f.set_property( "ReadRecovery", "false" ) - print f.set_property( "WriteRecovery", "false" ) - print f.set_property( "FollowRedirects", "false" ) - print f.get_property( "ReadRecovery" ) - print f.get_property( "WriteRecovery" ) - print f.get_property( "FollowRedirects" ) diff --git a/bindings/python/examples/filesystemproperties.py b/bindings/python/examples/filesystemproperties.py deleted file mode 100644 index dec48d10776..00000000000 --- a/bindings/python/examples/filesystemproperties.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -Make a directory, remove it and test the filesystem properties --------------------------------------------------------------- -""" -from XRootD import client -from XRootD.client.flags import MkDirFlags - -myclient = client.FileSystem("root://localhost") -myclient.mkdir("/tmp/some/dir", MkDirFlags.MAKEPATH) -myclient.rmdir("/tmp/some/dir") -myclient.rmdir("/tmp/some") - -print myclient.get_property( "FollowRedirects" ) -print myclient.set_property( "FollowRedirects", "false" ) -print myclient.get_property( "FollowRedirects" ) - diff --git a/bindings/python/examples/iterate.py b/bindings/python/examples/iterate.py deleted file mode 100644 index 672f517497e..00000000000 --- a/bindings/python/examples/iterate.py +++ /dev/null @@ -1,19 +0,0 @@ -""" -Iterate over a file, delimited by newline characters ----------------------------------------------------- - -Produces the following output:: - - 'green\n' - 'eggs\n' - 'and\n' - 'spam\n' - -""" -from XRootD import client - -with client.File() as f: - f.open('root://localhost//tmp/eggs') - - for line in f: - print '%r' % line \ No newline at end of file diff --git a/bindings/python/examples/locate.py b/bindings/python/examples/locate.py deleted file mode 100644 index 5ae85ea5784..00000000000 --- a/bindings/python/examples/locate.py +++ /dev/null @@ -1,15 +0,0 @@ -""" -Locate a file -------------- - -Produces output similar to the following:: - - ]> -""" -from XRootD import client -from XRootD.client.flags import OpenFlags - -myclient = client.FileSystem("root://localhost") -status, locations = myclient.locate("/tmp", OpenFlags.REFRESH) - -print locations diff --git a/bindings/python/examples/mkdir.py b/bindings/python/examples/mkdir.py deleted file mode 100644 index 516c100afa5..00000000000 --- a/bindings/python/examples/mkdir.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -Make a directory ----------------- -""" -from XRootD import client -from XRootD.client.flags import MkDirFlags - -myclient = client.FileSystem("root://localhost") -myclient.mkdir("/tmp/some/dir", MkDirFlags.MAKEPATH) diff --git a/bindings/python/examples/mv.py b/bindings/python/examples/mv.py deleted file mode 100644 index dd6ae2a8301..00000000000 --- a/bindings/python/examples/mv.py +++ /dev/null @@ -1,8 +0,0 @@ -""" -Move/rename a file ------------------- -""" -from XRootD import client - -myclient = client.FileSystem("root://localhost") -print myclient.mv("/tmp/spam", "/tmp/eggs") diff --git a/bindings/python/examples/query.py b/bindings/python/examples/query.py deleted file mode 100644 index 4c3125f806f..00000000000 --- a/bindings/python/examples/query.py +++ /dev/null @@ -1,19 +0,0 @@ -""" -Ask the server for some information ------------------------------------ - -Produces output similar to the following:: - - oss.cgroup=public&oss.space=52844687360&oss.free=27084992512&oss.maxf=27084992512&oss.used=25759694848&oss.quota=-1 - -For more information about XRootD query codes and arguments, see -`the relevant section in the protocol reference -`_. -""" -from XRootD import client -from XRootD.client.flags import QueryCode - -myclient = client.FileSystem("root://localhost") -status, response = myclient.query(QueryCode.SPACE, '/tmp') - -print response \ No newline at end of file diff --git a/bindings/python/examples/read.py b/bindings/python/examples/read.py deleted file mode 100644 index 2114d34315a..00000000000 --- a/bindings/python/examples/read.py +++ /dev/null @@ -1,27 +0,0 @@ -""" -Read a certain amount of data from a certain offset in a file -------------------------------------------------------------- - -Produces the following output:: - - 'green\neggs\nand\nham\n' - 'eggs' - -""" -from XRootD import client -from XRootD.client.flags import OpenFlags - -with client.File() as f: - status, response = f.open('root://localhost//tmp/eggs', OpenFlags.DELETE) - f.write('green\neggs\nand\nham\n') - - status, data = f.read() # Reads the whole file - print '%r' % data - print f.get_property('DataServer') - print f.get_property('LastURL') - print f.get_property('ReadRecovery') - f.set_property('ReadRecovery', 'false') - print f.get_property('ReadRecovery') - - status, data = f.read(offset=6, size=4) # Reads "eggs" - print '%r' % data \ No newline at end of file diff --git a/bindings/python/examples/readchunks.py b/bindings/python/examples/readchunks.py deleted file mode 100644 index d9a57052c41..00000000000 --- a/bindings/python/examples/readchunks.py +++ /dev/null @@ -1,17 +0,0 @@ -""" -Iterate over a file in chunks of the specified size ---------------------------------------------------- - -Produces the following output:: - - 'green\neggs' - '\nand\nspam\n' - -""" -from XRootD import client - -with client.File() as f: - f.open('root://localhost//tmp/eggs') - - for chunk in f.readchunks(offset=0, chunksize=10): - print '%r' % chunk diff --git a/bindings/python/examples/readlines.py b/bindings/python/examples/readlines.py deleted file mode 100644 index 488451c318d..00000000000 --- a/bindings/python/examples/readlines.py +++ /dev/null @@ -1,19 +0,0 @@ -""" -Read all lines from a file into a list --------------------------------------- - -Produces the following output (Note how the first line is not returned with the -call to :func:`readlines()` because we ate it with the first call to -:func:`readline()`):: - - 'green\n' - ['eggs\n', 'and\n', 'spam\n'] - -""" -from XRootD import client - -with client.File() as f: - f.open('root://localhost//tmp/eggs') - - print '%r' % f.readline() - print f.readlines() diff --git a/bindings/python/examples/rm.py b/bindings/python/examples/rm.py deleted file mode 100644 index 36954b332ff..00000000000 --- a/bindings/python/examples/rm.py +++ /dev/null @@ -1,8 +0,0 @@ -""" -Delete a file -------------- -""" -from XRootD import client - -myclient = client.FileSystem("root://localhost") -print myclient.rm("/tmp/eggs") diff --git a/bindings/python/examples/rmdir.py b/bindings/python/examples/rmdir.py deleted file mode 100644 index f41e380961b..00000000000 --- a/bindings/python/examples/rmdir.py +++ /dev/null @@ -1,8 +0,0 @@ -""" -Delete a directory ------------------- -""" -from XRootD import client - -myclient = client.FileSystem("root://localhost") -print myclient.rmdir("/tmp/some/dir") diff --git a/bindings/python/examples/vector_read.py b/bindings/python/examples/vector_read.py deleted file mode 100644 index 6268b1ac9b4..00000000000 --- a/bindings/python/examples/vector_read.py +++ /dev/null @@ -1,28 +0,0 @@ -""" -Read scattered data chunks in one operation -------------------------------------------- - -Produces the following output:: - - - - - -""" -from XRootD import client -from XRootD.client.flags import OpenFlags - -with client.File() as f: - print f.open('root://localhost//tmp/eggs', OpenFlags.UPDATE) - - f.write(r'The XROOTD project aims at giving high performance, scalable ' - +' fault tolerant access to data repositories of many kinds') - - size = f.stat()[1].size - v = [(0, 40), (40, 40), (80, size - 80)] - - status, response = f.vector_read(chunks=v) - print status - - for chunk in response.chunks: - print chunk diff --git a/bindings/python/examples/visa.py b/bindings/python/examples/visa.py deleted file mode 100644 index 90203db6314..00000000000 --- a/bindings/python/examples/visa.py +++ /dev/null @@ -1,8 +0,0 @@ - -from XRootD import client -from XRootD.client.flags import OpenFlags - -with client.File() as f: - status, response = f.open('root://localhost//tmp/eggs', OpenFlags.DELETE) - status, response = f.visa() - print status, response diff --git a/bindings/python/examples/visa_async.py b/bindings/python/examples/visa_async.py deleted file mode 100644 index 3bb94c0164c..00000000000 --- a/bindings/python/examples/visa_async.py +++ /dev/null @@ -1,12 +0,0 @@ - -from XRootD import client -from XRootD.client.flags import OpenFlags -from time import sleep - -def callback( status, response, hostlist ): - print "Called:", status, response, hostlist - -with client.File() as f: - status, response = f.open('root://localhost//tmp/eggs', OpenFlags.DELETE) - status = f.visa( callback = callback ) - sleep(20) diff --git a/bindings/python/examples/write.py b/bindings/python/examples/write.py deleted file mode 100644 index bc6cbd67cb1..00000000000 --- a/bindings/python/examples/write.py +++ /dev/null @@ -1,18 +0,0 @@ -""" -Write a chunk of data to a file -------------------------------- - -Produces the following output:: - - 'green\neggs\nand\nspam\n' - -""" -from XRootD import client -from XRootD.client.flags import OpenFlags - -with client.File() as f: - print f.open('root://localhost//tmp/eggs', OpenFlags.UPDATE) - - data = 'spam\n' - f.write(data, offset=15) - print f.read() diff --git a/bindings/python/libs/__init__.py b/bindings/python/libs/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/bindings/python/libs/client/__init__.py b/bindings/python/libs/client/__init__.py deleted file mode 100644 index 10c6646fbfe..00000000000 --- a/bindings/python/libs/client/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from __future__ import absolute_import, division, print_function - -from .filesystem import FileSystem as FileSystem -from .file import File as File -from .url import URL as URL -from .copyprocess import CopyProcess as CopyProcess diff --git a/bindings/python/libs/client/copyprocess.py b/bindings/python/libs/client/copyprocess.py deleted file mode 100644 index a8c88fbf4ec..00000000000 --- a/bindings/python/libs/client/copyprocess.py +++ /dev/null @@ -1,140 +0,0 @@ -#------------------------------------------------------------------------------- -# Copyright (c) 2012-2014 by European Organization for Nuclear Research (CERN) -# Author: Justin Salmon -#------------------------------------------------------------------------------- -# This file is part of the XRootD software suite. -# -# XRootD is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# XRootD is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with XRootD. If not, see . -# -# In applying this licence, CERN does not waive the privileges and immunities -# granted to it by virtue of its status as an Intergovernmental Organization -# or submit itself to any jurisdiction. -#------------------------------------------------------------------------------- -from __future__ import absolute_import, division, print_function - -from pyxrootd import client -from XRootD.client.url import URL -from XRootD.client.responses import XRootDStatus - -class ProgressHandlerWrapper(object): - """Internal progress handler wrapper to convert parameters to friendly - types""" - def __init__(self, handler): - self.handler = handler - - def begin(self, jobId, total, source, target): - if self.handler: - self.handler.begin(jobId, total, URL(source), URL(target)) - - def end(self, jobId, results): - if 'status' in results: - results['status'] = XRootDStatus(results['status']) - if self.handler: - self.handler.end(jobId, results) - - def update(self, jobId, processed, total): - if self.handler: - self.handler.update(jobId, processed, total) - - def should_cancel(self, jobId): - if self.handler: - return self.handler.should_cancel(jobId) - else: - return False - -class CopyProcess(object): - """Add multiple individually-configurable copy jobs to a "copy process" and - run them in parallel (yes, in parallel, because ``xrootd`` isn't limited - by the `GIL`.""" - - def __init__(self): - self.__process = client.CopyProcess() - - def add_job(self, - source, - target, - sourcelimit = 1, - force = False, - posc = False, - coerce = False, - mkdir = False, - thirdparty = 'none', - checksummode = 'none', - checksumtype = '', - checksumpreset = '', - dynamicsource = False, - chunksize = 4194304, - parallelchunks = 8, - inittimeout = 600, - tpctimeout = 1800): - """Add a job to the copy process. - - :param source: original source URL - :type source: string - :param target: target directory or file - :type target: string - :param sourcelimit: max number of download sources - :type sourcelimit: integer - :param force: overwrite target if it exists - :type force: boolean - :param posc: persist on successful close - :type posc: boolean - :param coerce: ignore file usage rules, i.e. apply `FORCE` flag to - ``open()`` - :type coerce: boolean - :param mkdir: create the parent directories when creating a file - :type mkdir: boolean - :param thirdparty: third party copy mode - :type thirdparty: string - :param checksummode: checksum mode to be used - :type checksummode: string - :param checksumtype: type of the checksum to be computed - :type checksumtype: string - :param checksumpreset: pre-set checksum instead of computing it - :type checksumpreset: string - :param dynamicsource: read as much data from source as is available without - checking the size - :type dynamicsource: boolean - :param chunksize: chunk size for remote transfers - :type chunksize: integer - :param parallelchunks: number of chunks that should be requested in parallel - :type parallelchunks: integer - :param inittimeout: copy initialization timeout - :type inittimeout: integer - :param tpctimeout: timeout for a third-party copy to finish - :type tpctimeout: integer - """ - self.__process.add_job(source, target, sourcelimit, force, posc, coerce, mkdir, - thirdparty, checksummode, checksumtype, checksumpreset, - dynamicsource, chunksize, parallelchunks, inittimeout, - tpctimeout) - - def prepare(self): - """Prepare the copy jobs. **Must be called before** ``run()``.""" - status = self.__process.prepare() - return XRootDStatus(status) - - def run(self, handler=None): - """Run the copy jobs with an optional progress handler. - - :param handler: a copy progress handler. You can subclass - :mod:`XRootD.client.utils.CopyProgressHandler` and implement - the three methods (``begin()``, ``progress()`` and ``end()`` - ) to get regular progress updates for your copy jobs. - """ - status, results = self.__process.run(ProgressHandlerWrapper(handler)) - for x in results: - if 'status' in x: - x['status'] = XRootDStatus(x['status']) - return XRootDStatus(status), results diff --git a/bindings/python/libs/client/file.py b/bindings/python/libs/client/file.py deleted file mode 100644 index cf7ed3cdf45..00000000000 --- a/bindings/python/libs/client/file.py +++ /dev/null @@ -1,293 +0,0 @@ -#------------------------------------------------------------------------------- -# Copyright (c) 2012-2014 by European Organization for Nuclear Research (CERN) -# Author: Justin Salmon -#------------------------------------------------------------------------------- -# This file is part of the XRootD software suite. -# -# XRootD is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# XRootD is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with XRootD. If not, see . -# -# In applying this licence, CERN does not waive the privileges and immunities -# granted to it by virtue of its status as an Intergovernmental Organization -# or submit itself to any jurisdiction. -#------------------------------------------------------------------------------- -from __future__ import absolute_import, division, print_function - -from pyxrootd import client -from XRootD.client.responses import XRootDStatus, StatInfo, VectorReadInfo -from XRootD.client.utils import CallbackWrapper - -class File(object): - """Interact with an ``xrootd`` server to perform file-based operations such - as reading, writing, vector reading, etc.""" - - def __init__(self): - self.__file = client.File() - - def __enter__(self): - return self - - def __exit__(self, type, value, traceback): - self.__file.__exit__() - - def __iter__(self): - return self - - def __next__(self): - return self.__file.next() - - # Python 2 compatibility - next = __next__ - - def open(self, url, flags=0, mode=0, timeout=0, callback=None): - """Open the file pointed to by the given URL. - - :param url: url of the file to be opened - :type url: string - :param flags: An `ORed` combination of :mod:`XRootD.client.flags.OpenFlags` - where the default is `OpenFlags.NONE` - :param mode: access mode for new files, an `ORed` combination of - :mod:`XRootD.client.flags.AccessMode` where the default is - `AccessMode.NONE` - :returns: tuple containing :mod:`XRootD.client.responses.XRootDStatus` - object and None - """ - if callback: - callback = CallbackWrapper(callback, None) - return XRootDStatus(self.__file.open(url, flags, mode, timeout, callback)) - - status, response = self.__file.open(url, flags, mode, timeout) - return XRootDStatus(status), None - - def close(self, timeout=0, callback=None): - """Close the file. - - :returns: tuple containing :mod:`XRootD.client.responses.XRootDStatus` - object and None - - As of Python 2.5, you can avoid having to call this method explicitly if you - use the :keyword:`with` statement. For example, the following code will - automatically close *f* when the :keyword:`with` block is exited:: - - from __future__ import with_statement # This isn't required in Python 2.6 - - with client.File() as f: - f.open("root://someserver//somefile") - for line in f: - print line, - """ - if callback: - callback = CallbackWrapper(callback, None) - return XRootDStatus(self.__file.close(timeout, callback)) - - status, response = self.__file.close(timeout) - return XRootDStatus(status), None - - def stat(self, force=False, timeout=0, callback=None): - """Obtain status information for this file. - - :param force: do not use the cached information, force re-stating - :type force: boolean - :returns: tuple containing :mod:`XRootD.client.responses.XRootDStatus` - object and :mod:`XRootD.client.responses.StatInfo` object - """ - if callback: - callback = CallbackWrapper(callback, StatInfo) - return XRootDStatus(self.__file.stat(force, timeout, callback)) - - status, response = self.__file.stat(force, timeout) - if response: response = StatInfo(response) - return XRootDStatus(status), response - - def read(self, offset=0, size=0, timeout=0, callback=None): - """Read a data chunk from a given offset. - - :param offset: offset from the beginning of the file - :type offset: integer - :param size: number of bytes to be read - :type size: integer - :returns: tuple containing :mod:`XRootD.client.responses.XRootDStatus` - object and the data that was read - """ - if callback: - callback = CallbackWrapper(callback, None) - return XRootDStatus(self.__file.read(offset, size, timeout, callback)) - - status, response = self.__file.read(offset, size, timeout) - return XRootDStatus(status), response - - def readline(self, offset=0, size=0, chunksize=0): - """Read a data chunk from a given offset, until the first newline or EOF - encountered. - - :param offset: offset from the beginning of the file - :type offset: integer - :param size: maximum number of bytes to be read - :type size: integer - :param chunksize: size of chunk used for reading, in bytes - :type chunksize: integer - :returns: data that was read, including the trailing newline - :rtype: string - """ - return self.__file.readline(offset, size, chunksize) - - def readlines(self, offset=0, size=0, chunksize=0): - """Read lines from a given offset until EOF encountered. Return list of - lines read. - - :param offset: offset from the beginning of the file - :type offset: integer - :param size: maximum number of bytes to be read - :type size: integer - :param chunksize: size of chunk used for reading, in bytes - :type chunksize: integer - :returns: data that was read, including trailing newlines - :rtype: list of strings - - .. warning:: This method will read the whole file into memory if you don't - specify an offset. Think twice about using it if your files - are big. - """ - return self.__file.readlines(offset, size, chunksize) - - def readchunks(self, offset=0, chunksize=1024 * 1024 * 2): - """Return an iterator object which will read data chunks from a given - offset of the given chunksize until EOF. - - :param offset: offset from the beginning of the file - :type offset: integer - :param chunksize: size of chunk to read, in bytes - :type chunksize: integer - :returns: iterator object - """ - return self.__file.readchunks(offset, chunksize) - - def write(self, buffer, offset=0, size=0, timeout=0, callback=None): - """Write a data chunk at a given offset. - - :param buffer: data to be written - :param offset: offset from the beginning of the file - :type offset: integer - :param size: number of bytes to be written - :type size: integer - :returns: tuple containing :mod:`XRootD.client.responses.XRootDStatus` - object and None - """ - if callback: - callback = CallbackWrapper(callback, None) - return XRootDStatus(self.__file.write(buffer, offset, size, timeout, callback)) - - status, response = self.__file.write(buffer, offset, size, timeout) - return XRootDStatus(status), None - - def sync(self, timeout=0, callback=None): - """Commit all pending disk writes. - - :returns: tuple containing :mod:`XRootD.client.responses.XRootDStatus` - object and None - """ - if callback: - callback = CallbackWrapper(callback, None) - return XRootDStatus(self.__file.sync(timeout, callback)) - - status, response = self.__file.sync(timeout) - return XRootDStatus(status), None - - def truncate(self, size, timeout=0, callback=None): - """Truncate the file to a particular size. - - :param size: desired size of the file - :type size: integer - :returns: tuple containing :mod:`XRootD.client.responses.XRootDStatus` - object and None - """ - if callback: - callback = CallbackWrapper(callback, None) - return XRootDStatus(self.__file.truncate(size, timeout, callback)) - - status, response = self.__file.truncate(size, timeout) - return XRootDStatus(status), None - - def vector_read(self, chunks, timeout=0, callback=None): - """Read scattered data chunks in one operation. - - :param chunks: list of the chunks to be read. The default maximum - chunk size is 2097136 bytes and the default maximum - number of chunks per request is 1024. The server may - be queried using :func:`XRootD.client.FileSystem.query` - for the actual settings. - :type chunks: list of 2-tuples of the form (offset, size) - :returns: tuple containing :mod:`XRootD.client.responses.XRootDStatus` - object and :mod:`XRootD.client.responses.VectorReadInfo` - object - """ - if callback: - callback = CallbackWrapper(callback, VectorReadInfo) - return XRootDStatus(self.__file.vector_read(chunks, timeout, callback)) - - status, response = self.__file.vector_read(chunks, timeout) - if response: response = VectorReadInfo(response) - return XRootDStatus(status), response - - def fcntl(self, arg, timeout=0, callback=None): - """Perform a custom operation on an open file. - - :param arg: argument - :type arg: string - :returns: tuple containing :mod:`XRootD.client.responses.XRootDStatus` - object and a string - """ - if callback: - callback = CallbackWrapper(callback, None) - return XRootDStatus(self.__file.fcntl( arg, timeout, callback)) - - status, response = self.__file.fcntl( arg, timeout ) - return XRootDStatus(status), response - - def visa(self, timeout=0, callback=None): - """Get access token to a file. - - :returns: tuple containing :mod:`XRootD.client.responses.XRootDStatus` - object and a string - """ - if callback: - callback = CallbackWrapper(callback, None) - return XRootDStatus(self.__file.visa(timeout, callback)) - - status, response = self.__file.visa(timeout) - return XRootDStatus(status), response - - def is_open(self): - """Check if the file is open. - - :rtype: boolean - """ - return self.__file.is_open() - - def set_property(self, name, value): - """Set file property. - - :param name: name of the property to set - :type name: string - :returns: boolean denoting if property setting was successful - :rtype: boolean - """ - return self.__file.set_property(name, value) - - def get_property(self, name): - """Get file property. - - :param name: name of the property - :type name: string - """ - return self.__file.get_property(name) diff --git a/bindings/python/libs/client/filesystem.py b/bindings/python/libs/client/filesystem.py deleted file mode 100644 index 5f9bb0b59c9..00000000000 --- a/bindings/python/libs/client/filesystem.py +++ /dev/null @@ -1,366 +0,0 @@ -#------------------------------------------------------------------------------- -# Copyright (c) 2012-2014 by European Organization for Nuclear Research (CERN) -# Author: Justin Salmon -#------------------------------------------------------------------------------- -# This file is part of the XRootD software suite. -# -# XRootD is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# XRootD is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with XRootD. If not, see . -# -# In applying this licence, CERN does not waive the privileges and immunities -# granted to it by virtue of its status as an Intergovernmental Organization -# or submit itself to any jurisdiction. -#------------------------------------------------------------------------------- -from __future__ import absolute_import, division, print_function - -from pyxrootd import client -from XRootD.client.responses import XRootDStatus, StatInfo, StatInfoVFS -from XRootD.client.responses import LocationInfo, DirectoryList, ProtocolInfo -from XRootD.client.utils import CallbackWrapper -from XRootD.client.flags import AccessMode - -class FileSystem(object): - """Interact with an ``xrootd`` server to perform filesystem-based operations - such as copying files, creating directories, changing file permissions, - listing directories, etc. - - :param url: The URL of the server to connect with - :type url: string - """ - - def __init__(self, url): - self.__fs = client.FileSystem(url) - - @property - def url(self): - """The server URL object, instance of :mod:`XRootD.client.URL`""" - return self.__fs.url - - def copy(self, source, target, force=False): - """Copy a file. - - .. note:: This method is less configurable than using - :mod:`XRootD.client.CopyProcess` - it is designed to be as simple - as possible by using sensible defaults for the underlying copy - job. If you need more configurability, or want to make multiple - copy jobs run at once in parallel, use - :mod:`XRootD.client.CopyProcess`. - - :param source: Source file path - :type source: string - :param target: Destination file path - :type target: string - :param force: overwrite target if it exists - :type force: boolean - :returns: tuple containing :mod:`XRootD.client.responses.XRootDStatus` - object and None - """ - result = self.__fs.copy(source=source, target=target, force=force)[0] - return XRootDStatus(result), None - - def locate(self, path, flags, timeout=0, callback=None): - """Locate a file. - - :param path: path to the file to be located - :type path: string - :param flags: An `ORed` combination of :mod:`XRootD.client.flags.OpenFlags` - :returns: tuple containing :mod:`XRootD.client.responses.XRootDStatus` - object and :mod:`XRootD.client.responses.LocationInfo` object - """ - if callback: - callback = CallbackWrapper(callback, LocationInfo) - return XRootDStatus(self.__fs.locate(path, flags, timeout, callback)) - - status, response = self.__fs.locate(path, flags, timeout) - if response: response = LocationInfo(response) - return XRootDStatus(status), response - - def deeplocate(self, path, flags, timeout=0, callback=None): - """Locate a file, recursively locate all disk servers. - - :param path: path to the file to be located - :type path: string - :param flags: An `ORed` combination of :mod:`XRootD.client.flags.OpenFlags` - :returns: tuple containing :mod:`XRootD.client.responses.XRootDStatus` - object and :mod:`XRootD.client.responses.LocationInfo` object - """ - if callback: - callback = CallbackWrapper(callback, LocationInfo) - return XRootDStatus(self.__fs.deeplocate(path, flags, timeout, callback)) - - status, response = self.__fs.deeplocate(path, flags, timeout) - if response: response = LocationInfo(response) - return XRootDStatus(status), response - - def mv(self, source, dest, timeout=0, callback=None): - """Move a directory or a file. - - :param source: the file or directory to be moved - :type source: string - :param dest: the new name - :type dest: string - :returns: tuple containing :mod:`XRootD.client.responses.XRootDStatus` - object and None - """ - if callback: - callback = CallbackWrapper(callback, None) - return XRootDStatus(self.__fs.mv(source, dest, timeout, callback)) - - status, response = self.__fs.mv(source, dest, timeout) - return XRootDStatus(status), None - - def query(self, querycode, arg, timeout=0, callback=None): - """Obtain server information. - - :param querycode: the query code as specified in - :mod:`XRootD.client.flags.QueryCode` - :param arg: query argument - :type arg: string - :returns: the query response or None if there was an error - :rtype: string - - .. note:: - For more information about XRootD query codes and arguments, see - `the relevant section in the protocol reference - `_. - """ - if callback: - callback = CallbackWrapper(callback, None) - return XRootDStatus(self.__fs.query(querycode, arg, timeout, callback)) - - status, response = self.__fs.query(querycode, arg, timeout) - return XRootDStatus(status), response - - def truncate(self, path, size, timeout=0, callback=None): - """Truncate a file. - - :param path: path to the file to be truncated - :type path: string - :param size: file size - :type size: integer - :returns: tuple containing :mod:`XRootD.client.responses.XRootDStatus` - object and None - """ - if callback: - callback = CallbackWrapper(callback, None) - return XRootDStatus(self.__fs.truncate(path, size, timeout, callback)) - - status, response = self.__fs.truncate(path, size, timeout) - return XRootDStatus(status), None - - def rm(self, path, timeout=0, callback=None): - """Remove a file. - - :param path: path to the file to be removed - :type path: string - :returns: tuple containing :mod:`XRootD.client.responses.XRootDStatus` - object and None - """ - if callback: - callback = CallbackWrapper(callback, None) - return XRootDStatus(self.__fs.rm(path, timeout, callback)) - - status, response = self.__fs.rm(path, timeout) - return XRootDStatus(status), None - - def mkdir(self, path, flags=0, mode=0, timeout=0, callback=None): - """Create a directory. - - :param path: path to the directory to create - :type path: string - :param flags: An `ORed` combination of :mod:`XRootD.client.flags.MkDirFlags` - where the default is `MkDirFlags.NONE` - :param mode: the initial file access mode, an `ORed` combination of - :mod:`XRootD.client.flags.AccessMode` where the default is - `rwxr-x---` - :returns: tuple containing :mod:`XRootD.client.responses.XRootDStatus` - object and None - """ - if mode == 0: - mode = AccessMode.UR | AccessMode.UW | AccessMode.UX | \ - AccessMode.GR | AccessMode.GX - - if callback: - callback = CallbackWrapper(callback, None) - return XRootDStatus(self.__fs.mkdir(path, flags, mode, timeout, callback)) - - status, response = self.__fs.mkdir(path, flags, mode, timeout) - return XRootDStatus(status), None - - def rmdir(self, path, timeout=0, callback=None): - """Remove a directory. - - :param path: path to the directory to remove - :type path: string - :returns: tuple containing :mod:`XRootD.client.responses.XRootDStatus` - object and None - """ - if callback: - callback = CallbackWrapper(callback, None) - return XRootDStatus(self.__fs.rmdir(path, timeout, callback)) - - status, response = self.__fs.rmdir(path, timeout) - return XRootDStatus(status), None - - def chmod(self, path, mode, timeout=0, callback=None): - """Change access mode on a directory or a file. - - :param path: path to the file/directory to change access mode - :type path: string - :param mode: An `OR`ed` combination of :mod:`XRootD.client.flags.AccessMode` - :returns: tuple containing :mod:`XRootD.client.responses.XRootDStatus` - object and None - """ - if callback: - callback = CallbackWrapper(callback, None) - return XRootDStatus(self.__fs.chmod(path, mode, timeout, callback)) - - status, response = self.__fs.chmod(path, mode, timeout) - return XRootDStatus(status), None - - def ping(self, timeout=0, callback=None): - """Check if the server is alive. - - :returns: tuple containing :mod:`XRootD.client.responses.XRootDStatus` - object and None - """ - if callback: - callback = CallbackWrapper(callback, None) - return XRootDStatus(self.__fs.ping(timeout, callback)) - - status, response = self.__fs.ping(timeout) - return XRootDStatus(status), None - - def stat(self, path, timeout=0, callback=None): - """Obtain status information for a path. - - :param path: path to the file/directory to stat - :type path: string - :returns: tuple containing :mod:`XRootD.client.responses.XRootDStatus` - object and :mod:`XRootD.client.responses.StatInfo` object - """ - if callback: - callback = CallbackWrapper(callback, StatInfo) - return XRootDStatus(self.__fs.stat(path, timeout, callback)) - - status, response = self.__fs.stat(path, timeout) - if response: response = StatInfo(response) - return XRootDStatus(status), response - - def statvfs(self, path, timeout=0, callback=None): - """Obtain status information for a Virtual File System. - - :param path: path to the file/directory to stat - :type path: string - :returns: tuple containing :mod:`XRootD.client.responses.XRootDStatus` - object and :mod:`XRootD.client.responses.StatInfoVFS` object - """ - if callback: - callback = CallbackWrapper(callback, StatInfoVFS) - return XRootDStatus(self.__fs.statvfs(path, timeout, callback)) - - status, response = self.__fs.statvfs(path, timeout) - if response: response = StatInfoVFS(response) - return XRootDStatus(status), response - - def protocol(self, timeout=0, callback=None): - """Obtain server protocol information. - - :returns: tuple containing :mod:`XRootD.client.responses.XRootDStatus` - object and :mod:`XRootD.client.responses.ProtocolInfo` object - """ - if callback: - callback = CallbackWrapper(callback, ProtocolInfo) - return XRootDStatus(self.__fs.protocol(timeout, callback)) - - status, response = self.__fs.protocol(timeout) - if response: response = ProtocolInfo(response) - return XRootDStatus(status), response - - def dirlist(self, path, flags=0, timeout=0, callback=None): - """List entries of a directory. - - :param path: path to the directory to list - :type path: string - :param flags: An `ORed` combination of :mod:`XRootD.client.flags.DirListFlags` - where the default is `DirListFlags.NONE` - :returns: tuple containing :mod:`XRootD.client.responses.XRootDStatus` - object and :mod:`XRootD.client.responses.DirectoryList` object - - .. warning:: Currently, passing `DirListFlags.STAT` with an asynchronous - call to :mod:`XRootD.client.FileSystem.dirlist()` does not - work, due to an xrootd client limitation. So you'll get - ``None`` instead of the ``StatInfo`` instance. See - `the GitHub issue `_ - for more details. - """ - if callback: - callback = CallbackWrapper(callback, DirectoryList) - return XRootDStatus(self.__fs.dirlist(path, flags, timeout, callback)) - - status, response = self.__fs.dirlist(path, flags, timeout) - if response: response = DirectoryList(response) - return XRootDStatus(status), response - - def sendinfo(self, info, timeout=0, callback=None): - """Send info to the server (up to 1024 characters). - - :param info: the info string to be sent - :type info: string - :returns: tuple containing :mod:`XRootD.client.responses.XRootDStatus` - object and None - """ - if callback: - callback = CallbackWrapper(callback, None) - return XRootDStatus(self.__fs.sendinfo(info, timeout, callback)) - - status, response = self.__fs.sendinfo(info, timeout) - return XRootDStatus(status), response - - def prepare(self, files, flags, priority=0, timeout=0, callback=None): - """Prepare one or more files for access. - - :param files: list of files to be prepared - :type files: list - :param flags: An `ORed` combination of - :mod:`XRootD.client.flags.PrepareFlags` - :param priority: priority of the request 0 (lowest) - 3 (highest) - :type priority: integer - :returns: tuple containing :mod:`XRootD.client.responses.XRootDStatus` - object and None - """ - if callback: - callback = CallbackWrapper(callback, None) - return XRootDStatus(self.__fs.prepare(files, flags, priority, timeout, - callback)) - - status, response = self.__fs.prepare(files, flags, priority, timeout) - return XRootDStatus(status), response - - def set_property(self, name, value): - """Set file system property. - - :param name: name of the property to set - :type name: string - :returns: boolean denoting if property setting was successful - :rtype: boolean - """ - return self.__fs.set_property(name, value) - - def get_property(self, name): - """Get file system property. - - :param name: name of the property - :type name: string - """ - return self.__fs.get_property(name) diff --git a/bindings/python/libs/client/flags.py b/bindings/python/libs/client/flags.py deleted file mode 100644 index 232edb0a0ca..00000000000 --- a/bindings/python/libs/client/flags.py +++ /dev/null @@ -1,121 +0,0 @@ -#------------------------------------------------------------------------------- -# Copyright (c) 2012-2013 by European Organization for Nuclear Research (CERN) -# Author: Justin Salmon -#------------------------------------------------------------------------------- -# XRootD is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# XRootD is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with XRootD. If not, see . -#------------------------------------------------------------------------------- -from __future__ import absolute_import, division, print_function - -def enum(**enums): - """Build the equivalent of a C++ enum""" - reverse = dict((value, key) for key, value in enums.items()) - enums['reverse_mapping'] = reverse - return type('Enum', (), enums) - -QueryCode = enum( - STATS = 1, - PREPARE = 2, - CHECKSUM = 3, - XATTR = 4, - SPACE = 5, - CHECKSUMCANCEL = 6, - CONFIG = 7, - VISA = 8, - OPAQUE = 16, - OPAQUEFILE = 32 -) - -OpenFlags = enum( - NONE = 0, -# COMPRESS = 1, - DELETE = 2, - FORCE = 4, - NEW = 8, - READ = 16, - UPDATE = 32, -# ASYNC = 64, - REFRESH = 128, - MAKEPATH = 256, - APPEND = 512, -# RETSTAT = 1024, - REPLICA = 2048, - POSC = 4096, - NOWAIT = 8192, - SEQIO = 16384 -) - -AccessMode = enum( - NONE = 0, - UR = 0x100, - UW = 0x080, - UX = 0x040, - GR = 0x020, - GW = 0x010, - GX = 0x008, - OR = 0x004, - OW = 0x002, - OX = 0x001 -) - -MkDirFlags = enum( - NONE = 0, - MAKEPATH = 1 -) - -DirListFlags = enum( - NONE = 0, - STAT = 1, - LOCATE = 2 -) - -PrepareFlags = enum( -# CANCEL = 1, -# NOTIFY = 2, -# NOERRS = 4, - STAGE = 8, - WRITEMODE = 16, - COLOCATE = 32, - FRESH = 64 -) - -HostTypes = enum( - IS_MANAGER = 0x00000002, - IS_SERVER = 0x00000001, - ATTR_META = 0x00000100, - ATTR_PROXY = 0x00000200, - ATTR_SUPER = 0x00000400 -) - -StatInfoFlags = enum( - X_BIT_SET = 1, - IS_DIR = 2, - OTHER = 4, - OFFLINE = 8, - IS_READABLE = 16, - IS_WRITABLE = 32, - POSC_PENDING = 64, - BACKUP_EXISTS = 128 -) - -LocationType = enum( - MANAGER_ONLINE = 0, - MANAGER_PENDING = 1, - SERVER_ONLINE = 2, - SERVER_PENDING = 3 -) - -AccessType = enum( - READ = 0, - READ_WRITE = 1 -) diff --git a/bindings/python/libs/client/responses.py b/bindings/python/libs/client/responses.py deleted file mode 100644 index c47863b8593..00000000000 --- a/bindings/python/libs/client/responses.py +++ /dev/null @@ -1,239 +0,0 @@ -#------------------------------------------------------------------------------- -# Copyright (c) 2012-2013 by European Organization for Nuclear Research (CERN) -# Author: Justin Salmon -#------------------------------------------------------------------------------- -# XRootD is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# XRootD is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with XRootD. If not, see . -#------------------------------------------------------------------------------- -from __future__ import absolute_import, division, print_function - -from XRootD.client.url import URL - -class Struct(object): - """Convert a dict into an object by adding each dict entry to __dict__""" - def __init__(self, entries): - self.__dict__.update(**entries) - def __repr__(self): - return '<%s>' % str(', '.join('%s: %s' % (k, repr(v)) - for (k, v) in self.__dict__.items())) - -class LocationInfo(Struct): - """Path location information (a list of discovered file locations). - - :param locations: (List of :mod:`XRootD.client.responses.Location` objects) - List of discovered locations - - This object is iterable:: - - >>> status, locations = filesystem.locate('/tmp', OpenFlags.REFRESH) - >>> print locations - - >>> for location in locations: - ... print location.address - ... - [::127.0.0.1]:1094 - - """ - def __init__(self, locations): - super(LocationInfo, self).__init__({'locations': - [Location(l) for l in locations]}) - - def __iter__(self): - return iter(self.locations) - -class Location(Struct): - """Information about a single location. - - :var address: The address of this location - :var type: The type of this location, one of - :mod:`XRootD.client.flags.LocationType` - :var accesstype: The allowed access type of this location, one of - :mod:`XRootD.client.flags.AccessType` - :var is_manager: Is the location a manager - :var is_server: Is the location a server - """ - def __init__(self, location): - super(Location, self).__init__(location) - -class XRootDStatus(Struct): - """Status of a request. Returned with all requests. - - :var message: Message describing the status of this request - :var ok: The request was successful - :var error: Error making request - :var fatal: Fatal error making request - :var status: Status of the request - :var code: Error type, or additional hints on what to do - :var shellcode: Status code that may be returned to the shell - :var errno: Errno, if any - """ - def __init__(self, status): - super(XRootDStatus, self).__init__(status) - - def __str__(self): - return self.message - -class ProtocolInfo(Struct): - """Protocol information for a server. - - :var version: The version of the protocol this server is speaking - :var hostinfo: Informational flags for this host. An `ORed` combination of - :mod:`XRootD.client.flags.HostTypes` - """ - def __init__(self, info): - super(ProtocolInfo, self).__init__(info) - -class StatInfo(Struct): - """Status information for files and directories. - - :var id: This file's unique identifier - :var flags: Informational flags. An `ORed` combination of - :mod:`XRootD.client.flags.StatInfoFlags` - :var size: The file size (in bytes) - :var modtime: Modification time (in seconds since epoch) - :var modtimestr: Modification time (as readable string) - """ - def __init__(self, info): - super(StatInfo, self).__init__(info) - -class StatInfoVFS(Struct): - """Status information for Virtual File Systems. - - :var nodes_rw: Number of nodes that can provide read/write space - :var free_rw: Size of the largest contiguous area of free r/w - space (in MB) - :var utilization_rw: Percentage of the partition utilization represented - by ``free_rw`` - :var nodes_staging: Number of nodes that can provide staging space - :var free_staging: Size of the largest contiguous area of free staging - space (in MB) - :var utilization_staging: Percentage of the partition utilization represented - by ``free_staging`` - """ - def __init__(self, info): - super(StatInfoVFS, self).__init__(info) - -class DirectoryList(Struct): - """Directory listing. - - This object is iterable:: - - >>> status, dirlist = filesystem.dirlist('/tmp', DirListFlags.STAT) - >>> print dirlist - - >>> print 'Entries:', dirlist.size - Entries: 2 - >>> for item in dirlist: - ... print item.name, item.statinfo.size - ... - spam 1024 - eggs 2048 - - :var size: The size of this listing (number of entries) - :var parent: The name of the parent directory of this directory - :var dirlist: (List of :mod:`XRootD.client.responses.ListEntry` objects) - - The list of directory entries - """ - def __init__(self, dirlist): - dirlist.update({'dirlist': [ListEntry(e) for e in dirlist['dirlist']]}) - super(DirectoryList, self).__init__(dirlist) - - def __iter__(self): - return iter(self.dirlist) - -class ListEntry(Struct): - """An entry in a directory listing. - - :var name: The name of the file/directory - :var hostaddr: The address of the host on which this file/directory lives - :var statinfo: (Instance of :mod:`XRootD.client.responses.StatInfo`) - - Status information about this file/directory. You must pass - `DirListFlags.STAT` with the call to - :mod:`XRootD.client.FileSystem.dirlist()` to retrieve status - information. - """ - def __init__(self, entry): - if entry['statinfo']: entry.update({'statinfo': StatInfo(entry['statinfo'])}) - super(ListEntry, self).__init__(entry) - -class ChunkInfo(Struct): - """Describes a data chunk for a vector read. - - :var offset: The offset in the file from which this chunk came - :var length: The length of this chunk - :var buffer: The actual chunk data - """ - def __init__(self, info): - super(ChunkInfo, self).__init__(info) - -class VectorReadInfo(Struct): - """Vector read response object. - Returned by :mod:`XRootD.client.File.vector_read()`. - - This object is iterable:: - - >>> f.open('root://localhost/tmp/spam') - >>> status, chunks = file.vector_read([(0, 10), (10, 10)]) - >>> print chunks - - >>> print chunks.size - 20 - >>> for chunk in chunks: - ... print chunk.offset, chunk.length - ... - 0 10 - 10 10 - - :var size: Total size of all chunks - :var chunks: (List of :mod:`XRootD.client.responses.ChunkInfo` objects) - - The list of chunks that were read - """ - def __init__(self, info): - info.update({'chunks': [ChunkInfo(c) for c in info['chunks']]}) - super(VectorReadInfo, self).__init__(info) - - def __iter__(self): - return iter(self.chunks) - -class HostList(Struct): - """A list of hosts that were involved in the request. - - This object is iterable:: - - >>> print hostlist - - >>> for host in hostlist: - ... print host.url - ... - root://localhost - - :var hosts: (List of :mod:`XRootD.client.responses.HostInfo` objects) - - The list of hosts - """ - def __init__(self, hostlist): - super(HostList, self).__init__({'hosts': [HostInfo(h) for h in hostlist]}) - - def __iter__(self): - return iter(self.hosts) - -class HostInfo(Struct): - """Information about a single host. - - :var url: URL of the host, instance of :mod:`XRootD.client.URL` - :var protocol: Version of the protocol the host is speaking - :var flags: Host type, an `ORed` combination of - :mod:`XRootD.client.flags.HostTypes` - :var load_balancer: Was the host used as a load balancer - """ - def __init__(self, info): - super(HostInfo, self).__init__(info) diff --git a/bindings/python/libs/client/url.py b/bindings/python/libs/client/url.py deleted file mode 100644 index 0a3fd249939..00000000000 --- a/bindings/python/libs/client/url.py +++ /dev/null @@ -1,87 +0,0 @@ -#------------------------------------------------------------------------------- -# Copyright (c) 2012-2013 by European Organization for Nuclear Research (CERN) -# Author: Justin Salmon -#------------------------------------------------------------------------------- -# XRootD is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# XRootD is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with XRootD. If not, see . -#------------------------------------------------------------------------------- -from __future__ import absolute_import, division, print_function - -from pyxrootd import client - -class URL(object): - """Server URL object. - - This class has each portion of an `XRootD` URL split up as attributes. For - example, given the URL:: - - >>> url = URL(root://user1:passwd1@host1:1234//path?param1=val1¶m2=val2) - - then ``url.hostid`` would return `user1:passwd1@host1:1234`. - - :var hostid: The host part of the URL, i.e. ``user1:passwd1@host1:1234`` - :var protocol: The protocol part of the URL, i.e. ``root`` - :var username: The username part of the URL, i.e. ``user1`` - :var password: The password part of the URL, i.e. ``passwd1`` - :var hostname: The name of the target host part of the URL, i.e. ``host1`` - :var port: The target port part of the URL, i.e. ``1234`` - :var path: The path part of the URL, i.e. ``path`` - :var path_with_params: The path part of the URL with parameters, i.e. - ``path?param1=val1¶m2=val2`` - """ - - def __init__(self, url): - self.__url = client.URL(url) - - def __str__(self): - return str(self.__url) - - @property - def hostid(self): - return self.__url.hostid - - @property - def protocol(self): - return self.__url.protocol - - @property - def username(self): - return self.__url.username - - @property - def password(self): - return self.__url.password - - @property - def hostname(self): - return self.__url.hostname - - @property - def port(self): - return self.__url.port - - @property - def path(self): - return self.__url.path - - @property - def path_with_params(self): - return self.__url.path_with_params - - def is_valid(self): - """Return the validity of the URL""" - return self.__url.is_valid() - - def clear(self): - """Clear the URL""" - return self.__url.clear() diff --git a/bindings/python/libs/client/utils.py b/bindings/python/libs/client/utils.py deleted file mode 100644 index 31ae0dbbeea..00000000000 --- a/bindings/python/libs/client/utils.py +++ /dev/null @@ -1,109 +0,0 @@ -#------------------------------------------------------------------------------- -# Copyright (c) 2012-2013 by European Organization for Nuclear Research (CERN) -# Author: Justin Salmon -#------------------------------------------------------------------------------- -# XRootD is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# XRootD is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with XRootD. If not, see . -#------------------------------------------------------------------------------- -from __future__ import absolute_import, division, print_function - -from threading import Lock -from XRootD.client.responses import XRootDStatus, HostList - -class CallbackWrapper(object): - def __init__(self, callback, responsetype): - if not hasattr(callback, '__call__'): - raise TypeError('callback must be callable function, class or lambda') - self.callback = callback - self.responsetype = responsetype - - def __call__(self, status, response, *argv): - self.status = XRootDStatus(status) - self.response = response - if self.responsetype: - self.response = self.responsetype(response) - if argv: - self.hostlist = HostList(argv[0]) - else: - self.hostlist = HostList([]) - self.callback(self.status, self.response, self.hostlist) - -class AsyncResponseHandler(object): - """Utility class to handle asynchronous method calls.""" - def __init__(self): - self.mutex = Lock() - self.mutex.acquire() - - def __call__(self, status, response, hostlist): - self.status = status - self.response = response - self.hostlist = hostlist - self.mutex.release() - - def wait(self): - """Block and wait for the async response""" - self.mutex.acquire() - self.mutex.release() - return self.status, self.response, self.hostlist - -class CopyProgressHandler(object): - """Utility class to handle progress updates from copy jobs - - .. note:: This class does nothing by itself. You have to subclass it and do - something useful with the progress updates yourself. - """ - - def begin(self, jobId, total, source, target): - """Notify when a new job is about to start - - :param jobId: the job number of the copy job concerned - :type jobId: integer - :param total: total number of jobs being processed - :type total: integer - :param source: the source url of the current job - :type source: :mod:`XRootD.client.URL` object - :param target: the destination url of the current job - :type target: :mod:`XRootD.client.URL` object - """ - pass - - def end(self, jobId, results): - """Notify when the previous job has finished - - :param jobId: the job number of the copy job concerned - :type jobId: integer - :param status: status of the job - :type status: :mod:`XRootD.client.responses.XRootDStatus` object - """ - pass - - def update(self, jobId, processed, total): - """Notify about the progress of the current job - - :param jobId: the job number of the copy job concerned - :type jobId: integer - :param processed: bytes processed by the current job - :type processed: integer - :param total: total number of bytes to be processed by the current job - :type total: integer - """ - pass - - - def should_cancel( self, jobId ): - """Check whether the current job should be canceled. - - :param jobId: the job number of the copy job concerned - :type jobId: integer - """ - return False diff --git a/bindings/python/setup.py.in b/bindings/python/setup.py.in deleted file mode 100644 index 8c7d72bfa40..00000000000 --- a/bindings/python/setup.py.in +++ /dev/null @@ -1,64 +0,0 @@ -from __future__ import print_function - -from distutils.core import setup, Extension -from distutils import sysconfig -from os import getenv, walk, path -import subprocess - -# Remove the "-Wstrict-prototypes" compiler option, which isn't valid for C++. -cfg_vars = sysconfig.get_config_vars() -opt = cfg_vars["OPT"] -cfg_vars["OPT"] = " ".join( flag for flag in opt.split() if flag not in ['-Wstrict-prototypes' ${CLANG_PROHIBITED} ] ) - -cflags = cfg_vars["CFLAGS"] -cfg_vars["CFLAGS"] = " ".join( flag for flag in cflags.split() if flag not in ['-Wstrict-prototypes' ${CLANG_PROHIBITED} ] ) - -py_cflags = cfg_vars["PY_CFLAGS"] -cfg_vars["PY_CFLAGS"] = " ".join( flag for flag in py_cflags.split() if flag not in ['-Wstrict-prototypes' ${CLANG_PROHIBITED} ] ) - - -sources = list() -depends = list() - -for dirname, dirnames, filenames in walk('${CMAKE_CURRENT_SOURCE_DIR}/src'): - for filename in filenames: - if filename.endswith('.cc'): - sources.append(path.join(dirname, filename)) - elif filename.endswith('.hh'): - depends.append(path.join(dirname, filename)) - -xrdcllibdir = "${XRDCL_LIBDIR}" -xrdlibdir = "${XRD_LIBDIR}" -xrdsrcincdir = "${XRD_SRCINCDIR}" -xrdbinincdir = "${XRD_BININCDIR}" -version = "${XROOTD_VERSION}" - -print('XRootD library dir: ', xrdlibdir) -print('XRootD src include dir:', xrdsrcincdir) -print('XRootD bin include dir:', xrdbinincdir) -print('Version: ', version) - -setup( name = 'pyxrootd', - version = version, - author = 'XRootD Developers', - author_email = 'xrootd-dev@slac.stanford.edu', - url = 'http://xrootd.org', - license = 'LGPLv3+', - description = "XRootD Python bindings", - long_description = "XRootD Python bindings", - packages = ['pyxrootd', 'XRootD', 'XRootD.client'], - package_dir = {'pyxrootd' : '${CMAKE_CURRENT_SOURCE_DIR}/src', - 'XRootD' : '${CMAKE_CURRENT_SOURCE_DIR}/libs', - 'XRootD.client': '${CMAKE_CURRENT_SOURCE_DIR}/libs/client'}, - ext_modules = [ - Extension( - 'pyxrootd.client', - sources = sources, - depends = depends, - libraries = ['XrdCl', 'XrdUtils', 'dl'], - extra_compile_args = ['-g'], - include_dirs = [xrdsrcincdir, xrdbinincdir], - library_dirs = [xrdlibdir, xrdcllibdir] - ) - ] - ) diff --git a/bindings/python/setup_pypi.py b/bindings/python/setup_pypi.py deleted file mode 100644 index 831a3bdeabe..00000000000 --- a/bindings/python/setup_pypi.py +++ /dev/null @@ -1,68 +0,0 @@ -from setuptools import setup -from distutils.core import Extension -from distutils import sysconfig -from os import getenv, walk, path, path, getcwd, chdir -from platform import system -import subprocess - -# Remove the "-Wstrict-prototypes" compiler option, which isn't valid for C++. -cfg_vars = sysconfig.get_config_vars() -opt = cfg_vars["OPT"] -cfg_vars["OPT"] = " ".join( flag for flag in opt.split() if flag != '-Wstrict-prototypes' ) - - -sources = list() -depends = list() - -for dirname, dirnames, filenames in walk('src'): - for filename in filenames: - if filename.endswith('.cc'): - sources.append(path.join(dirname, filename)) - elif filename.endswith('.hh'): - depends.append(path.join(dirname, filename)) - -# Get package version -with open ('VERSION_INFO') as verfile: - version = verfile.read().strip() - -def getincdir_osx(): - # Assume xrootd was installed via homebrew - return '/usr/local/Cellar/xrootd/{0}/include/xrootd'.format(version) - -def getlibdir(): - return (system() == 'Darwin' and '/usr/local/lib') or '/usr/lib' - -def getincdir(): - return (system() == 'Darwin' and getincdir_osx()) or '/usr/include/xrootd' - -xrdlibdir = getenv( 'XRD_LIBDIR' ) or getlibdir() -xrdincdir = getenv( 'XRD_INCDIR' ) or getincdir() - -print 'XRootD library dir: ', xrdlibdir -print 'XRootD include dir: ', xrdincdir -print 'Version: ', version - -setup( name = 'pyxrootd', - version = version, - author = 'XRootD Developers', - author_email = 'xrootd-dev@slac.stanford.edu', - url = 'http://xrootd.org', - license = 'LGPLv3+', - description = "XRootD Python bindings", - long_description = "XRootD Python bindings", - packages = ['pyxrootd', 'XRootD', 'XRootD.client'], - package_dir = {'pyxrootd' : 'src', - 'XRootD' : 'libs', - 'XRootD.client': 'libs/client'}, - ext_modules = [ - Extension( - 'pyxrootd.client', - sources = sources, - depends = depends, - libraries = ['XrdCl', 'XrdUtils', 'dl'], - extra_compile_args = ['-g'], - include_dirs = [xrdincdir], - library_dirs = [xrdlibdir] - ) - ] - ) diff --git a/bindings/python/src/AsyncResponseHandler.hh b/bindings/python/src/AsyncResponseHandler.hh deleted file mode 100644 index 79c53c2b82b..00000000000 --- a/bindings/python/src/AsyncResponseHandler.hh +++ /dev/null @@ -1,288 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2012-2013 by European Organization for Nuclear Research (CERN) -// Author: Justin Salmon -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#ifndef ASYNCRESPONSEHANDLER_HH_ -#define ASYNCRESPONSEHANDLER_HH_ - -#include "PyXRootD.hh" -#include "Conversions.hh" -#include "Utils.hh" -#include "XrdCl/XrdClXRootDResponses.hh" - -namespace PyXRootD -{ - //---------------------------------------------------------------------------- - //! Generic asynchronous response handler - //---------------------------------------------------------------------------- - template - class AsyncResponseHandler: public XrdCl::ResponseHandler - { - public: - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - AsyncResponseHandler( PyObject *callback ) : - callback( callback ), state( PyGILState_UNLOCKED ) {} - - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - virtual ~AsyncResponseHandler() {}; - - //------------------------------------------------------------------------ - //! Handle the asynchronous response call - //------------------------------------------------------------------------ - void HandleResponseWithHosts( XrdCl::XRootDStatus *status, - XrdCl::AnyObject *response, - XrdCl::HostList *hostList ) - { - // If we get called while the program's exit handlers are being called, - // then calls to PyGILState_Ensure() deadlock. Py_IsInitialized() is - // not thread-safe but we appear to be lacking in alternates. - if (!Py_IsInitialized()) {return;} - - //---------------------------------------------------------------------- - // Ensure we hold the Global Interpreter Lock - //---------------------------------------------------------------------- - state = PyGILState_Ensure(); - if ( InitTypes() != 0) { - delete status; - delete response; - delete hostList; - return Exit(); - } - - //---------------------------------------------------------------------- - // Convert the XRootDStatus object - //---------------------------------------------------------------------- - PyObject *pystatus = ConvertType( status ); - if ( !pystatus || PyErr_Occurred() ) { - delete status; - delete response; - delete hostList; - return Exit(); - } - - //---------------------------------------------------------------------- - // Convert the response object, if any - //---------------------------------------------------------------------- - PyObject *pyresponse = NULL; - if ( response != NULL) { - pyresponse = ParseResponse( response ); - if ( pyresponse == NULL || PyErr_Occurred() ) { - Py_XDECREF( pystatus ); - delete status; - delete response; - delete hostList; - return Exit(); - } - } - - //---------------------------------------------------------------------- - // Convert the host list - //---------------------------------------------------------------------- - PyObject *pyhostlist = PyList_New( 0 ); - if ( hostList != NULL ) { - pyhostlist = ConvertType( hostList ); - if ( pyhostlist == NULL || PyErr_Occurred() ) { - Py_XDECREF( pystatus ); - Py_XDECREF( pyresponse ); - delete status; - delete response; - delete hostList; - return Exit(); - } - } - - //---------------------------------------------------------------------- - // Build the callback arguments - //---------------------------------------------------------------------- - if (pyresponse == NULL) pyresponse = Py_BuildValue( "" ); - PyObject *args = Py_BuildValue( "(OOO)", pystatus, pyresponse, pyhostlist ); - if ( !args || PyErr_Occurred() ) { - Py_XDECREF( pystatus ); - Py_XDECREF( pyresponse ); - Py_XDECREF( pyhostlist ); - delete status; - delete response; - delete hostList; - return Exit(); - } - - //---------------------------------------------------------------------- - // Invoke the Python callback - //---------------------------------------------------------------------- - PyObject *callbackResult = PyObject_CallObject( this->callback, args ); - Py_DECREF( args ); - if ( !callbackResult || PyErr_Occurred() ) { - Py_XDECREF( pystatus ); - Py_XDECREF( pyresponse ); - Py_XDECREF( pyhostlist ); - delete status; - delete response; - delete hostList; - return Exit(); - } - - //---------------------------------------------------------------------- - // Clean up - //---------------------------------------------------------------------- - Py_XDECREF( pystatus ); - Py_XDECREF( pyresponse ); - Py_XDECREF( pyhostlist ); - Py_XDECREF( callbackResult ); - Py_XDECREF( this->callback ); - - PyGILState_Release( state ); - - delete status; - delete response; - delete hostList; - // Commit suicide... - delete this; - } - - //------------------------------------------------------------------------ - //! Handle the asynchronous response call - //------------------------------------------------------------------------ - void HandleResponse( XrdCl::XRootDStatus *status, - XrdCl::AnyObject *response ) - { - // If we get called while the program's exit handlers are being called, - // then calls to PyGILState_Ensure() deadlock. Py_IsInitialized() is - // not thread-safe but we appear to be lacking in alternates. - if (!Py_IsInitialized()) {return;} - - //---------------------------------------------------------------------- - // Ensure we hold the Global Interpreter Lock - //---------------------------------------------------------------------- - state = PyGILState_Ensure(); - if ( InitTypes() != 0) { - return Exit(); - } - - //---------------------------------------------------------------------- - // Convert the XRootDStatus object - //---------------------------------------------------------------------- - PyObject *pystatus = ConvertType( status ); - if ( !pystatus || PyErr_Occurred() ) { - return Exit(); - } - - //---------------------------------------------------------------------- - // Convert the response object, if any - //---------------------------------------------------------------------- - PyObject *pyresponse = NULL; - if ( response != NULL) { - pyresponse = ParseResponse( response ); - if ( pyresponse == NULL || PyErr_Occurred() ) { - Py_XDECREF( pystatus ); - delete response; - return Exit(); - } - } - - //---------------------------------------------------------------------- - // Build the callback arguments - //---------------------------------------------------------------------- - if (pyresponse == NULL) pyresponse = Py_BuildValue( "" ); - PyObject *args = Py_BuildValue( "(OO)", pystatus, pyresponse ); - if ( !args || PyErr_Occurred() ) { - Py_XDECREF( pystatus ); - Py_XDECREF( pyresponse ); - delete response; - return Exit(); - } - - //---------------------------------------------------------------------- - // Invoke the Python callback - //---------------------------------------------------------------------- - PyObject *callbackResult = PyObject_CallObject( this->callback, args ); - Py_DECREF( args ); - if ( !callbackResult || PyErr_Occurred() ) { - Py_XDECREF( pystatus ); - Py_XDECREF( pyresponse ); - delete response; - return Exit(); - } - - //---------------------------------------------------------------------- - // Clean up - //---------------------------------------------------------------------- - Py_XDECREF( pystatus ); - Py_XDECREF( pyresponse ); - Py_XDECREF( callbackResult ); - Py_XDECREF( this->callback ); - - PyGILState_Release( state ); - - delete status; - delete response; - // Commit suicide... - delete this; - } - - //------------------------------------------------------------------------ - //! Parse out and convert the AnyObject response to a mapping type - //------------------------------------------------------------------------ - PyObject* ParseResponse( XrdCl::AnyObject *response ) - { - PyObject *pyresponse = 0; - Type *type; - response->Get( type ); - pyresponse = ConvertType( type ); - return ( !pyresponse || PyErr_Occurred() ) ? NULL : pyresponse; - } - - //------------------------------------------------------------------------ - //! Something went wrong, print error and release the GIL before returning - //------------------------------------------------------------------------ - void Exit() - { - PyErr_Print(); - PyGILState_Release( state ); - delete this; - } - - private: - - PyObject *callback; - PyGILState_STATE state; - }; - - //---------------------------------------------------------------------------- - //! Get an async response handler of the correct type - //---------------------------------------------------------------------------- - template - XrdCl::ResponseHandler* GetHandler( PyObject *callback ) - { - if (!IsCallable(callback)) { - return NULL; - } - - return new AsyncResponseHandler( callback ); - } -} - -#endif /* ASYNCRESPONSEHANDLER_HH_ */ diff --git a/bindings/python/src/ChunkIterator.hh b/bindings/python/src/ChunkIterator.hh deleted file mode 100644 index 7fbe931bffd..00000000000 --- a/bindings/python/src/ChunkIterator.hh +++ /dev/null @@ -1,152 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2012-2013 by European Organization for Nuclear Research (CERN) -// Author: Justin Salmon -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#ifndef CHUNKITERATOR_HH_ -#define CHUNKITERATOR_HH_ - -#include "PyXRootD.hh" -#include "PyXRootDFile.hh" - -namespace PyXRootD -{ - //---------------------------------------------------------------------------- - //! Iterator class for looping over a file and yielding chunks from it - //---------------------------------------------------------------------------- - class ChunkIterator - { - public: - PyObject_HEAD - File *file; - uint32_t chunksize; - uint64_t startOffset; - uint64_t currentOffset; - }; - - //---------------------------------------------------------------------------- - //! __init__ - //---------------------------------------------------------------------------- - static int ChunkIterator_init(ChunkIterator *self, PyObject *args) - { - PyObject *py_offset = NULL, *py_chunksize = NULL; - - if ( !PyArg_ParseTuple( args, "OOO", &self->file,&py_offset, - &py_chunksize ) ) return -1; - - unsigned long long tmp_offset = 0; - unsigned int tmp_chunksize = 2 * 1024 * 1024; // 2 MB - - if ( py_offset && PyObjToUllong( py_offset, &tmp_offset, "offset" ) ) - return -1; - - if ( py_chunksize && PyObjToUint( py_chunksize, &tmp_chunksize, "chunksize" ) ) - return -1; - - self->startOffset = (uint64_t)tmp_offset; - self->chunksize = (uint32_t)tmp_chunksize; - self->currentOffset = self->startOffset; - return 0; - } - - //---------------------------------------------------------------------------- - //! __iter__ - //---------------------------------------------------------------------------- - static PyObject* ChunkIterator_iter(ChunkIterator *self) - { - Py_INCREF(self); - return (PyObject*) self; - } - - //---------------------------------------------------------------------------- - //! __iternext__ - //! - //! Dumb implementation, should use prefetching/readv - //---------------------------------------------------------------------------- - static PyObject* ChunkIterator_iternext(ChunkIterator *self) - { - XrdCl::Buffer *chunk = self->file->ReadChunk( self->file, - self->currentOffset, - self->chunksize); - PyObject *pychunk = NULL; - - if ( chunk->GetSize() == 0 ) { - //------------------------------------------------------------------------ - // Raise StopIteration exception when we are done - //------------------------------------------------------------------------ - PyErr_SetNone( PyExc_StopIteration ); - } - - else { - self->currentOffset += self->chunksize; - pychunk = PyBytes_FromStringAndSize( (const char*) chunk->GetBuffer(), - chunk->GetSize() ); - } - - delete chunk; - return pychunk; - } - - //---------------------------------------------------------------------------- - //! ChunkIterator type structure - //---------------------------------------------------------------------------- - static PyTypeObject ChunkIteratorType = { - PyVarObject_HEAD_INIT(NULL, 0) - "client.File.ChunkIterator", /* tp_name */ - sizeof(ChunkIterator), /* tp_basicsize */ - 0, /* tp_itemsize */ - 0, /* tp_dealloc */ - 0, /* tp_print */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_compare */ - 0, /* tp_repr */ - 0, /* tp_as_number */ - 0, /* tp_as_sequence */ - 0, /* tp_as_mapping */ - 0, /* tp_hash */ - 0, /* tp_call */ - 0, /* tp_str */ - 0, /* tp_getattro */ - 0, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_ITER, /* tp_flags */ - "Internal chunk iterator object", /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - (getiterfunc) ChunkIterator_iter, /* tp_iter */ - (iternextfunc) ChunkIterator_iternext, /* tp_iternext */ - 0, /* tp_methods */ - 0, /* tp_members */ - 0, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - (initproc) ChunkIterator_init, /* tp_init */ - }; -} - -#endif /* CHUNKITERATOR_HH_ */ diff --git a/bindings/python/src/Conversions.hh b/bindings/python/src/Conversions.hh deleted file mode 100644 index 30414f7f8e3..00000000000 --- a/bindings/python/src/Conversions.hh +++ /dev/null @@ -1,396 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2012-2015 by European Organization for Nuclear Research (CERN) -// Author: Justin Salmon -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#ifndef CONVERSIONS_HH_ -#define CONVERSIONS_HH_ - -#include "PyXRootDURL.hh" -#include "Utils.hh" -#include - -#include "XrdCl/XrdClXRootDResponses.hh" -#include "XrdCl/XrdClPropertyList.hh" - -namespace PyXRootD -{ - //---------------------------------------------------------------------------- - //! Convert an object of type T into a Python dictionary type. - //---------------------------------------------------------------------------- - template struct PyDict; - - template - inline PyObject* ConvertType( T *response ) - { - if ( response != NULL ) { - return PyDict::Convert( response ); - } else { - Py_RETURN_NONE; - } - } - - template<> - inline PyObject* ConvertType >(const std::deque *list ) - { - if(list == NULL) - Py_RETURN_NONE; - - PyObject *pylist = NULL; - - if(list) - { - pylist = PyList_New(list->size()); - std::deque::const_iterator it = list->begin(); - for(unsigned int i = 0; i < list->size(); ++i) - { - const XrdCl::PropertyList &result = *it++; - PyObject *pyresult = ConvertType(&result); - PyList_SetItem(pylist, i, pyresult); - } - } - - return pylist; - } - - template<> - inline PyObject* ConvertType >(std::deque *list ) - { - return ConvertType((const std::deque*) list); - } - - template<> struct PyDict - { - static PyObject* Convert( XrdCl::AnyObject *object ) - { - Py_RETURN_NONE; - } - }; - - template<> struct PyDict - { - static PyObject* Convert( XrdCl::XRootDStatus *status ) - { - PyObject *error = PyBool_FromLong(status->IsError()); - PyObject *fatal = PyBool_FromLong(status->IsFatal()); - PyObject *ok = PyBool_FromLong(status->IsOK()); - PyObject *obj = - Py_BuildValue( "{sHsHsIsssisOsOsO}", - "status", status->status, - "code", status->code, - "errno", status->errNo, - "message", status->ToStr().c_str(), - "shellcode", status->GetShellCode(), - "error", error, - "fatal", fatal, - "ok", ok); - Py_DECREF(error); - Py_DECREF(fatal); - Py_DECREF(ok); - return obj; - } - }; - - template<> struct PyDict - { - static PyObject* Convert( XrdCl::ProtocolInfo *info ) - { - return Py_BuildValue( "{sIsI}", - "version", info->GetVersion(), - "hostinfo", info->GetHostInfo() ); - } - }; - - template<> struct PyDict - { - static PyObject* Convert( XrdCl::StatInfo *info ) - { - return Py_BuildValue("{sOsOsOsOsO}", - "id", Py_BuildValue("s", info->GetId().c_str()), - "size", Py_BuildValue("k", info->GetSize()), - "flags", Py_BuildValue("I", info->GetFlags()), - "modtime", Py_BuildValue("k", info->GetModTime()), - "modtimestr", Py_BuildValue("s", - info->GetModTimeAsString().c_str())); - } - }; - - template<> struct PyDict - { - static PyObject* Convert( XrdCl::StatInfoVFS *info ) - { - return Py_BuildValue( "{sksksksksbsb}", - "nodes_rw", info->GetNodesRW(), - "nodes_staging", info->GetNodesStaging(), - "free_rw", info->GetFreeRW(), - "free_staging", info->GetFreeStaging(), - "utilization_rw", info->GetUtilizationRW(), - "utilization_staging", info->GetUtilizationStaging() ); - } - }; - - template<> struct PyDict - { - static PyObject* Convert( XrdCl::DirectoryList *list ) - { - PyObject *directoryList = PyList_New( list->GetSize() ); - PyObject *statInfo; - int i = 0; - - for ( XrdCl::DirectoryList::Iterator it = list->Begin(); - it < list->End(); ++it ) { - statInfo = ConvertType( (*it)->GetStatInfo() ); - - PyList_SET_ITEM( directoryList, i, - Py_BuildValue( "{sssssO}", - "hostaddr", (*it)->GetHostAddress().c_str(), - "name", (*it)->GetName().c_str(), - "statinfo", statInfo ) ); - Py_DECREF( statInfo ); - i++; - } - - PyObject *o = Py_BuildValue( "{sisssO}", - "size", list->GetSize(), - "parent", list->GetParentName().c_str(), - "dirlist", directoryList ); - Py_DECREF( directoryList ); - return o; - } - }; - - template<> struct PyDict - { - static PyObject* Convert( XrdCl::HostList *list ) - { - URLType.tp_new = PyType_GenericNew; - if ( PyType_Ready( &URLType ) < 0 ) return NULL; - Py_INCREF( &URLType ); - - PyObject *pyhostlist = NULL; - - if ( list ) { - pyhostlist = PyList_New( list->size() ); - for ( unsigned int i = 0; i < list->size(); ++i ) { - XrdCl::HostInfo *info = &list->at( i ); - - PyObject *url = PyObject_CallObject( (PyObject*) &URLType, - Py_BuildValue( "(s)", info->url.GetURL().c_str() ) ); - - PyObject *pyhostinfo = Py_BuildValue( "{sIsIsOsO}", - "flags", info->flags, - "protocol", info->protocol, - "load_balancer", PyBool_FromLong(info->loadBalancer), - "url", url ); - - Py_DECREF( url ); - PyList_SET_ITEM( pyhostlist, i, pyhostinfo ); - } - } - - return pyhostlist; - } - }; - - template<> struct PyDict - { - static PyObject* Convert( XrdCl::LocationInfo *info ) - { - PyObject *locationList = PyList_New( info->GetSize() ); - int i = 0; - - for ( XrdCl::LocationInfo::Iterator it = info->Begin(); it < info->End(); - ++it ) { - PyList_SET_ITEM( locationList, i, - Py_BuildValue( "{sssIsIsOsO}", - "address", it->GetAddress().c_str(), - "type", it->GetType(), - "accesstype", it->GetAccessType(), - "is_server", PyBool_FromLong( it->IsServer() ), - "is_manager", PyBool_FromLong( it->IsManager() ) ) ); - i++; - } - - PyObject *o = Py_BuildValue( "O", locationList ); - Py_DECREF( locationList ); - return o; - } - }; - - template<> struct PyDict - { - static PyObject* Convert( XrdCl::Buffer *buffer ) - { - return PyBytes_FromStringAndSize( buffer->GetBuffer(), buffer->GetSize() ); - } - }; - - template<> struct PyDict - { - static PyObject* Convert( XrdCl::ChunkInfo *chunk ) - { - PyObject *o = PyBytes_FromStringAndSize( (const char*)chunk->buffer, - chunk->length ); - delete[] (char*) chunk->buffer; - return o; - } - }; - - template<> struct PyDict - { - static PyObject* Convert( XrdCl::VectorReadInfo *info ) - { - if ( !info ) return Py_BuildValue( "" ); - - XrdCl::ChunkList chunks = info->GetChunks(); - PyObject *pychunks = PyList_New( chunks.size() ); - - for ( uint32_t i = 0; i < chunks.size(); ++i ) { - XrdCl::ChunkInfo chunk = chunks.at( i ); - - PyObject *buffer = PyBytes_FromStringAndSize( (const char *) chunk.buffer, - chunk.length ); - PyList_SET_ITEM( pychunks, i, - Py_BuildValue( "{sOsOsO}", - "offset", Py_BuildValue( "k", chunk.offset ), - "length", Py_BuildValue( "I", chunk.length ), - "buffer", buffer ) ); - Py_DECREF( buffer ); - } - - PyObject *o = Py_BuildValue( "{sIsO}", "size", info->GetSize(), - "chunks", pychunks ); - Py_DECREF( pychunks ); - return o; - } - }; - - template<> struct PyDict - { - static PyObject* Convert(const XrdCl::PropertyList *result) - { - PyObject *pyresult = PyDict_New(); - PyObject *kO = 0; - PyObject *vO = 0; - const char *key = "sourceCheckSum"; - - if(result->HasProperty(key)) - { - std::string s; - result->Get(key, s); - kO = Py_BuildValue("s", key); - vO = Py_BuildValue("s", s.c_str()); - PyDict_SetItem(pyresult, kO, vO); - Py_DECREF(kO); - Py_DECREF(vO); - } - - key = "targetCheckSum"; - if(result->HasProperty(key)) - { - std::string s; - result->Get(key, s); - kO = Py_BuildValue("s", key); - vO = Py_BuildValue("s", s.c_str()); - PyDict_SetItem(pyresult, kO, vO); - Py_DECREF(kO); - Py_DECREF(vO); - } - - key = "size"; - if(result->HasProperty(key)) - { - uint64_t s; - result->Get(key, s); - kO = Py_BuildValue("s", key); - vO = Py_BuildValue("K", s); - PyDict_SetItem(pyresult, kO, vO); - Py_DECREF(kO); - Py_DECREF(vO); - - } - - key = "status"; - if(result->HasProperty(key)) - { - XrdCl::XRootDStatus s; - result->Get(key, s); - kO = Py_BuildValue("s", key); - vO = ConvertType(&s); - PyDict_SetItem(pyresult, kO, vO); - Py_DECREF(kO); - Py_DECREF(vO); - - } - - key = "sources"; - if(result->HasProperty(key)) - { - std::vector s; - result->Get(key, s); - kO = Py_BuildValue("s", key); - vO = ConvertType(&s); - PyDict_SetItem(pyresult, kO, vO); - Py_DECREF(kO); - Py_DECREF(vO); - - } - - key = "realTarget"; - if(result->HasProperty(key)) - { - std::string s; - result->Get(key, s); - kO = Py_BuildValue("s", key); - vO = Py_BuildValue("s", s.c_str()); - PyDict_SetItem(pyresult, kO, vO); - Py_DECREF(kO); - Py_DECREF(vO); - } - - return pyresult; - } - }; - - template<> struct PyDict > - { - static PyObject* Convert( std::vector *list ) - { - PyObject *pylist = NULL; - - if(list) - { - pylist = PyList_New(list->size()); - for(unsigned int i = 0; i < list->size(); ++i) - { - std::string &str = list->at(i); - PyList_SetItem(pylist, i, Py_BuildValue("s", str.c_str())); - } - } - return pylist; - } - }; - -} - -#endif /* CONVERSIONS_HH_ */ diff --git a/bindings/python/src/PyXRootD.hh b/bindings/python/src/PyXRootD.hh deleted file mode 100644 index dcdacd1a10c..00000000000 --- a/bindings/python/src/PyXRootD.hh +++ /dev/null @@ -1,53 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2012-2013 by European Organization for Nuclear Research (CERN) -// Author: Justin Salmon -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#ifndef PYXROOTD_HH_ -#define PYXROOTD_HH_ - -#include -#include -#include "structmember.h" - -#if PY_MAJOR_VERSION >= 3 -#define IS_PY3K -#endif - -#define async( func ) \ - Py_BEGIN_ALLOW_THREADS \ - func; \ - Py_END_ALLOW_THREADS \ - -#ifdef IS_PY3K -#define Py_TPFLAGS_HAVE_ITER 0 -#else -#if PY_MINOR_VERSION <= 5 -#define PyUnicode_FromString PyString_FromString -#endif -#define PyBytes_Size PyString_Size -#define PyBytes_Check PyString_Check -#define PyBytes_FromString PyString_FromString -#define PyBytes_FromStringAndSize PyString_FromStringAndSize -#endif - -#endif /* PYXROOTD_HH_ */ diff --git a/bindings/python/src/PyXRootDCopyProcess.cc b/bindings/python/src/PyXRootDCopyProcess.cc deleted file mode 100644 index fa65bd1f91f..00000000000 --- a/bindings/python/src/PyXRootDCopyProcess.cc +++ /dev/null @@ -1,154 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2012-2014 by European Organization for Nuclear Research (CERN) -// Author: Justin Salmon -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#include "PyXRootDCopyProcess.hh" -#include "PyXRootDCopyProgressHandler.hh" -#include "XrdCl/XrdClConstants.hh" -#include "XrdCl/XrdClDefaultEnv.hh" - -#include "Conversions.hh" -#include -#include - -namespace PyXRootD -{ - //---------------------------------------------------------------------------- - // Add a job to the copy process - //---------------------------------------------------------------------------- - PyObject* CopyProcess::AddJob( CopyProcess *self, PyObject *args, PyObject *kwds ) - { - //-------------------------------------------------------------------------- - // Initialize default parameters - //-------------------------------------------------------------------------- - XrdCl::Env *env = XrdCl::DefaultEnv::GetEnv(); - - static const char *kwlist[] - = { "source", "target", "sourcelimit", "force", "posc", "coerce", - "mkdir", "thirdparty", "checksummode", "checksumtype", - "checksumpreset", "dynamicsource", "chunksize", "parallelchunks", "inittimeout", - "tpctimeout", NULL }; - - const char *source; - const char *target; - uint16_t sourceLimit = 1; - bool force = false; - bool posc = false; - bool coerce = false; - bool mkdir = false; - const char *thirdParty = "none"; - const char *checkSumMode = "none"; - const char *checkSumType = ""; - const char *checkSumPreset = ""; - bool dynamicSource = false; - - - int val = XrdCl::DefaultCPChunkSize; - env->GetInt( "CPChunkSize", val ); - uint32_t chunkSize = val; - - val = XrdCl::DefaultCPParallelChunks; - env->GetInt( "CPParallelChunks", val ); - uint16_t parallelChunks = val; - - val = XrdCl::DefaultCPInitTimeout; - env->GetInt( "CPInitTimeout", val ); - uint16_t initTimeout = val; - - val = XrdCl::DefaultCPTPCTimeout; - env->GetInt( "CPTPCTimeout", val ); - uint16_t tpcTimeout = val; - - - if ( !PyArg_ParseTupleAndKeywords( args, kwds, "ss|HbbbbssssbIHHH:add_job", - (char**) kwlist, &source, &target, &sourceLimit, &force, &posc, - &coerce, &mkdir, &thirdParty, &checkSumMode, &checkSumType, - &checkSumPreset, &dynamicSource, &chunkSize, ¶llelChunks, - &initTimeout, &tpcTimeout) ) - return NULL; - - XrdCl::PropertyList properties; - self->results->push_back(XrdCl::PropertyList()); - - properties.Set( "source", source ); - properties.Set( "target", target ); - properties.Set( "force", force ); - properties.Set( "posc", posc ); - properties.Set( "coerce", coerce ); - properties.Set( "makeDir", mkdir ); - properties.Set( "dynamicSource", dynamicSource ); - properties.Set( "thirdParty", thirdParty ); - properties.Set( "checkSumMode", checkSumMode ); - properties.Set( "checkSumType", checkSumType ); - properties.Set( "checkSumPreset", checkSumPreset ); - properties.Set( "chunkSize", chunkSize ); - properties.Set( "parallelChunks", parallelChunks ); - properties.Set( "initTimeout", initTimeout ); - properties.Set( "tpcTimeout", tpcTimeout ); - - XrdCl::XRootDStatus status = self->process->AddJob(properties, - &self->results->back()); - - return ConvertType( &status ); - } - - //---------------------------------------------------------------------------- - // Prepare the copy jobs - //---------------------------------------------------------------------------- - PyObject* CopyProcess::Prepare( CopyProcess *self, PyObject *args, PyObject *kwds ) - { - XrdCl::XRootDStatus status = self->process->Prepare(); - return ConvertType( &status ); - } - - //---------------------------------------------------------------------------- - // Run the copy jobs - //---------------------------------------------------------------------------- - PyObject* CopyProcess::Run( CopyProcess *self, PyObject *args, PyObject *kwds ) - { - (void) CopyProcessType; // Suppress unused variable warning - static const char *kwlist[] = { "handler", NULL }; - PyObject *pyhandler = 0; - XrdCl::CopyProgressHandler *handler = 0; - - if( !PyArg_ParseTupleAndKeywords( args, kwds, "|O", (char**) kwlist, - &pyhandler ) ) return NULL; - - handler = new CopyProgressHandler( pyhandler ); - XrdCl::XRootDStatus status; - - //-------------------------------------------------------------------------- - //! Allow other threads to acquire the GIL while the copy jobs are running - //-------------------------------------------------------------------------- - Py_BEGIN_ALLOW_THREADS - status = self->process->Run( handler ); - Py_END_ALLOW_THREADS - - PyObject *tuple = PyTuple_New(2); - PyTuple_SetItem(tuple, 0, ConvertType(&status)); - PyTuple_SetItem(tuple, 1, ConvertType(self->results)); - - return tuple; - } -} diff --git a/bindings/python/src/PyXRootDCopyProcess.hh b/bindings/python/src/PyXRootDCopyProcess.hh deleted file mode 100644 index 3345e721c4f..00000000000 --- a/bindings/python/src/PyXRootDCopyProcess.hh +++ /dev/null @@ -1,139 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2012-2014 by European Organization for Nuclear Research (CERN) -// Author: Justin Salmon -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#ifndef PYXROOTD_COPY_PROCESS_HH_ -#define PYXROOTD_COPY_PROCESS_HH_ - -#include "PyXRootD.hh" - -#include "XrdCl/XrdClCopyProcess.hh" -#include "XrdCl/XrdClXRootDResponses.hh" -#include - -namespace PyXRootD -{ - //---------------------------------------------------------------------------- - //! XrdCl::CopyProcess binding class - //---------------------------------------------------------------------------- - class CopyProcess - { - public: - static PyObject* AddJob(CopyProcess *self, PyObject *args, PyObject *kwds); - static PyObject* Prepare(CopyProcess *self, PyObject *args, PyObject *kwds); - static PyObject* Run(CopyProcess *self, PyObject *args, PyObject *kwds); - public: - PyObject_HEAD - XrdCl::CopyProcess *process; - std::deque *results; - }; - - PyDoc_STRVAR(copyprocess_type_doc, "CopyProcess object (internal)"); - - //---------------------------------------------------------------------------- - //! __init__() - //---------------------------------------------------------------------------- - static int CopyProcess_init( CopyProcess *self, PyObject *args ) - { - self->process = new XrdCl::CopyProcess(); - self->results = new std::deque(); - return 0; - } - - //---------------------------------------------------------------------------- - //! Deallocation function, called when object is deleted - //---------------------------------------------------------------------------- - static void CopyProcess_dealloc( CopyProcess *self ) - { - delete self->process; - delete self->results; - Py_TYPE(self)->tp_free( (PyObject*) self ); - } - - //---------------------------------------------------------------------------- - //! Visible method definitions - //---------------------------------------------------------------------------- - static PyMethodDef CopyProcessMethods[] = - { - { "add_job", - (PyCFunction) PyXRootD::CopyProcess::AddJob, METH_VARARGS | METH_KEYWORDS, NULL }, - { "prepare", - (PyCFunction) PyXRootD::CopyProcess::Prepare, METH_VARARGS | METH_KEYWORDS, NULL }, - { "run", - (PyCFunction) PyXRootD::CopyProcess::Run, METH_VARARGS | METH_KEYWORDS, NULL }, - - { NULL } /* Sentinel */ - }; - - //---------------------------------------------------------------------------- - //! Visible member definitions - //---------------------------------------------------------------------------- - static PyMemberDef CopyProcessMembers[] = - { - { NULL } /* Sentinel */ - }; - - //---------------------------------------------------------------------------- - //! CopyProcess binding type object - //---------------------------------------------------------------------------- - static PyTypeObject CopyProcessType = { - PyVarObject_HEAD_INIT(NULL, 0) - "pyxrootd.CopyProcess", /* tp_name */ - sizeof(CopyProcess), /* tp_basicsize */ - 0, /* tp_itemsize */ - (destructor) CopyProcess_dealloc, /* tp_dealloc */ - 0, /* tp_print */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_compare */ - 0, /* tp_repr */ - 0, /* tp_as_number */ - 0, /* tp_as_sequence */ - 0, /* tp_as_mapping */ - 0, /* tp_hash */ - 0, /* tp_call */ - 0, /* tp_str */ - 0, /* tp_getattro */ - 0, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ - copyprocess_type_doc, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - CopyProcessMethods, /* tp_methods */ - CopyProcessMembers, /* tp_members */ - 0, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - (initproc) CopyProcess_init, /* tp_init */ - }; -} - -#endif /* PYXROOTD_COPY_PROCESS_HH_ */ diff --git a/bindings/python/src/PyXRootDCopyProgressHandler.cc b/bindings/python/src/PyXRootDCopyProgressHandler.cc deleted file mode 100644 index 34a2021494d..00000000000 --- a/bindings/python/src/PyXRootDCopyProgressHandler.cc +++ /dev/null @@ -1,113 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2012-2014 by European Organization for Nuclear Research (CERN) -// Author: Justin Salmon -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#include "PyXRootDCopyProgressHandler.hh" -#include "Conversions.hh" -#include "XrdCl/XrdClXRootDResponses.hh" - -namespace PyXRootD -{ - //---------------------------------------------------------------------------- - // Notify when a new job is about to start - //---------------------------------------------------------------------------- - void CopyProgressHandler::BeginJob( uint16_t jobNum, - uint16_t jobTotal, - const XrdCl::URL *source, - const XrdCl::URL *target ) - { - PyGILState_STATE state = PyGILState_Ensure(); - - PyObject *ret = 0; - if (handler != NULL) - { - ret = PyObject_CallMethod( handler, (char*)"begin", (char*)"(HHss)", - jobNum, jobTotal, source->GetURL().c_str(), - target->GetURL().c_str() ); - Py_XDECREF(ret); - } - - PyGILState_Release(state); - } - - //---------------------------------------------------------------------------- - // Notify when the previous job has finished - //---------------------------------------------------------------------------- - void CopyProgressHandler::EndJob( uint16_t jobNum, - const XrdCl::PropertyList *result ) - { - PyGILState_STATE state = PyGILState_Ensure(); - PyObject *pyresult = ConvertType(result); - PyObject *ret = 0; - - if (handler) - { - ret = PyObject_CallMethod( handler, (char*)"end", (char*)"HO", - jobNum, pyresult ); - Py_XDECREF(ret); - } - PyGILState_Release(state); - } - - //---------------------------------------------------------------------------- - // Notify about the progress of the current job - //---------------------------------------------------------------------------- - void CopyProgressHandler::JobProgress( uint16_t jobNum, - uint64_t bytesProcessed, - uint64_t bytesTotal ) - { - PyGILState_STATE state = PyGILState_Ensure(); - PyObject *ret = 0; - if(handler) - { - ret = PyObject_CallMethod( handler, (char*)"update", (char*)"HKK", - jobNum, bytesProcessed, bytesTotal ); - Py_XDECREF(ret); - } - - PyGILState_Release(state); - } - - //---------------------------------------------------------------------------- - // Check if the job should be canceled - //---------------------------------------------------------------------------- - bool CopyProgressHandler::ShouldCancel( uint16_t jobNum ) - { - PyGILState_STATE state = PyGILState_Ensure(); - bool ret = false; - if(handler) - { - PyObject *val = PyObject_CallMethod( handler, (char*)"should_cancel", - (char*)"H", jobNum ); - if(val) - { - if(PyBool_Check(val)) - ret = (val == Py_True); - Py_DECREF(val); - } - } - PyGILState_Release(state); - return ret; - } -} diff --git a/bindings/python/src/PyXRootDCopyProgressHandler.hh b/bindings/python/src/PyXRootDCopyProgressHandler.hh deleted file mode 100644 index 04ca6fb0c18..00000000000 --- a/bindings/python/src/PyXRootDCopyProgressHandler.hh +++ /dev/null @@ -1,76 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2012-2014 by European Organization for Nuclear Research (CERN) -// Author: Justin Salmon -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#ifndef PYXROOTD_COPY_PROGRESS_HANDLER_HH_ -#define PYXROOTD_COPY_PROGRESS_HANDLER_HH_ - -#include "PyXRootD.hh" -#include "XrdCl/XrdClCopyProcess.hh" -#include "XrdCl/XrdClPropertyList.hh" -#include "XrdCl/XrdClURL.hh" - -namespace PyXRootD -{ - //---------------------------------------------------------------------------- - //! Interface for copy progress notification - //---------------------------------------------------------------------------- - class CopyProgressHandler : public XrdCl::CopyProgressHandler - { - public: - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - CopyProgressHandler( PyObject *handler ) : handler( handler ) {} - - //------------------------------------------------------------------------ - //! Notify when a new job is about to start - //------------------------------------------------------------------------ - virtual void BeginJob( uint16_t jobNum, - uint16_t jobTotal, - const XrdCl::URL *source, - const XrdCl::URL *target ); - - //------------------------------------------------------------------------ - //! Notify when the previous job has finished - //------------------------------------------------------------------------ - virtual void EndJob( uint16_t jobNum, - const XrdCl::PropertyList *result ); - - //------------------------------------------------------------------------ - //! Notify about the progress of the current job - //------------------------------------------------------------------------ - virtual void JobProgress( uint16_t jobNum, - uint64_t bytesProcessed, - uint64_t bytesTotal ); - - //------------------------------------------------------------------------ - //! Determine whether the job should be canceled - //------------------------------------------------------------------------ - virtual bool ShouldCancel(uint16_t jobNum); - - public: - PyObject *handler; - }; -} -#endif /* PYXROOTD_COPY_PROGRESS_HANDLER_HH_ */ diff --git a/bindings/python/src/PyXRootDFile.cc b/bindings/python/src/PyXRootDFile.cc deleted file mode 100644 index b37decb478d..00000000000 --- a/bindings/python/src/PyXRootDFile.cc +++ /dev/null @@ -1,764 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2012-2014 by European Organization for Nuclear Research (CERN) -// Author: Justin Salmon -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#include "PyXRootD.hh" -#include "PyXRootDFile.hh" -#include "AsyncResponseHandler.hh" -#include "ChunkIterator.hh" -#include "Utils.hh" - -#include "XrdCl/XrdClFile.hh" -#include "XrdCl/XrdClFileSystem.hh" - -namespace PyXRootD -{ - //---------------------------------------------------------------------------- - //! Open the file pointed to by the given URL - //---------------------------------------------------------------------------- - PyObject* File::Open( File *self, PyObject *args, PyObject *kwds ) - { - static const char *kwlist[] = { "url", "flags", "mode", - "timeout", "callback", NULL }; - const char *url; - XrdCl::OpenFlags::Flags flags = XrdCl::OpenFlags::None; - XrdCl::Access::Mode mode = XrdCl::Access::None; - uint16_t timeout = 0; - PyObject *callback = NULL, *pystatus = NULL; - XrdCl::XRootDStatus status; - - if ( !PyArg_ParseTupleAndKeywords( args, kwds, "s|HHHO:open", - (char**) kwlist, &url, &flags, &mode, &timeout, &callback ) ) - return NULL; - - if ( callback && callback != Py_None ) { - XrdCl::ResponseHandler *handler = GetHandler( callback ); - if ( !handler ) return NULL; - async( status = self->file->Open( url, flags, mode, handler, timeout ) ); - } - - else { - async( status = self->file->Open( url, flags, mode, timeout ) ); - } - - pystatus = ConvertType( &status ); - PyObject *o = ( callback && callback != Py_None ) ? - Py_BuildValue( "O", pystatus ) : - Py_BuildValue( "OO", pystatus, Py_BuildValue( "" ) ); - Py_DECREF( pystatus ); - return o; - } - - //---------------------------------------------------------------------------- - //! Close the file - //---------------------------------------------------------------------------- - PyObject* File::Close( File *self, PyObject *args, PyObject *kwds ) - { - static const char *kwlist[] = { "timeout", "callback", NULL }; - uint16_t timeout = 0; - PyObject *callback = NULL, *pystatus = NULL; - XrdCl::XRootDStatus status; - - if ( !PyArg_ParseTupleAndKeywords( args, kwds, "|HO:close", (char**) kwlist, - &timeout, &callback ) ) return NULL; - - if ( callback && callback != Py_None ) { - XrdCl::ResponseHandler *handler = GetHandler( callback ); - if ( !handler ) return NULL; - async( status = self->file->Close( handler, timeout ) ); - } - - else { - async( status = self->file->Close( timeout ) ) - } - - pystatus = ConvertType( &status ); - PyObject *o = ( callback && callback != Py_None ) ? - Py_BuildValue( "O", pystatus ) : - Py_BuildValue( "OO", pystatus, Py_BuildValue( "" ) ); - Py_DECREF( pystatus ); - return o; - } - - //---------------------------------------------------------------------------- - //! Obtain status information for this file - //---------------------------------------------------------------------------- - PyObject* File::Stat( File *self, PyObject *args, PyObject *kwds ) - { - static const char *kwlist[] = { "force", "timeout", "callback", NULL }; - bool force = false; - uint16_t timeout = 0; - PyObject *callback = NULL, *pyresponse = NULL, *pystatus = NULL; - XrdCl::XRootDStatus status; - - if ( !self->file->IsOpen() ) return FileClosedError(); - - if ( !PyArg_ParseTupleAndKeywords( args, kwds, "|iHO:stat", (char**) kwlist, - &force, &timeout, &callback ) ) return NULL; - - if ( callback && callback != Py_None ) { - XrdCl::ResponseHandler *handler = GetHandler( callback ); - async( status = self->file->Stat( force, handler, timeout ) ); - } - - else { - XrdCl::StatInfo *response = 0; - async( status = self->file->Stat( force, response, timeout ) ); - pyresponse = ConvertType( response ); - delete response; - } - - pystatus = ConvertType( &status ); - PyObject *o = ( callback && callback != Py_None ) ? - Py_BuildValue( "O", pystatus ) : - Py_BuildValue( "OO", pystatus, pyresponse ); - Py_DECREF( pystatus ); - Py_XDECREF( pyresponse ); - return o; - } - - //---------------------------------------------------------------------------- - //! Read a data chunk at a given offset - //---------------------------------------------------------------------------- - PyObject* File::Read( File *self, PyObject *args, PyObject *kwds ) - { - static const char *kwlist[] = { "offset", "size", "timeout", "callback", - NULL }; - uint64_t offset = 0; - uint32_t size = 0; - uint16_t timeout = 0; - PyObject *callback = NULL, *pystatus = NULL, *pyresponse = NULL; - PyObject *py_offset = NULL, *py_size = NULL, *py_timeout = NULL; - char *buffer = 0; - XrdCl::XRootDStatus status; - - if ( !self->file->IsOpen() ) return FileClosedError(); - - if ( !PyArg_ParseTupleAndKeywords( args, kwds, "|OOOO:read", - (char**) kwlist, &py_offset, &py_size, &py_timeout, &callback ) ) return NULL; - - unsigned long long tmp_offset = 0; - unsigned int tmp_size = 0; - unsigned short int tmp_timeout = 0; - - if ( py_offset && PyObjToUllong( py_offset, &tmp_offset, "offset" ) ) - return NULL; - - if ( py_size && PyObjToUint(py_size, &tmp_size, "size" ) ) - return NULL; - - if ( py_timeout && PyObjToUshrt(py_timeout, &tmp_timeout, "timeout" ) ) - return NULL; - - offset = (uint64_t)tmp_offset; - size = (uint32_t)tmp_size; - timeout = (uint16_t)tmp_timeout; - - if (!size) { - XrdCl::StatInfo *info = 0; - async( XrdCl::XRootDStatus status = self->file->Stat(true, info, timeout) ); - size = info->GetSize(); - if (info) delete info; - } - - buffer = new char[size]; - - if ( callback && callback != Py_None ) { - XrdCl::ResponseHandler *handler = GetHandler( callback ); - if ( !handler ) { - delete[] buffer; - return NULL; - } - async( status = self->file->Read( offset, size, buffer, handler, timeout ) ); - } - - else { - uint32_t bytesRead; - async( status = self->file->Read( offset, size, buffer, bytesRead, timeout ) ); - pyresponse = PyBytes_FromStringAndSize( buffer, bytesRead ); - delete[] buffer; - } - - pystatus = ConvertType( &status ); - PyObject *o = ( callback && callback != Py_None ) ? - Py_BuildValue( "O", pystatus ) : - Py_BuildValue( "OO", pystatus, pyresponse ); - Py_DECREF( pystatus ); - Py_XDECREF( pyresponse ); - return o; - } - - //---------------------------------------------------------------------------- - // Read a data chunk at a given offset, until the first newline encountered - // or size data read. - //---------------------------------------------------------------------------- - PyObject* File::ReadLine( File *self, PyObject *args, PyObject *kwds ) - { - static const char *kwlist[] = { "offset", "size", "chunksize", NULL }; - uint64_t offset = 0; - uint32_t size = 0; - uint32_t chunksize = 0; - PyObject *pyline = NULL; - PyObject *py_offset = NULL, *py_size = NULL, *py_chunksize = NULL; - - if ( !self->file->IsOpen() ) return FileClosedError(); - - if ( !PyArg_ParseTupleAndKeywords( args, kwds, "|OOO:readline", - (char**) kwlist, &py_offset, &py_size, &py_chunksize ) ) return NULL; - - unsigned long long tmp_offset = 0; - unsigned int tmp_size = 0, tmp_chunksize = 0; - - if ( py_offset && PyObjToUllong(py_offset, &tmp_offset, "offset" ) ) - return NULL; - - if ( py_size && PyObjToUint(py_size, &tmp_size, "size" ) ) - return NULL; - - if ( py_chunksize && PyObjToUint(py_chunksize, &tmp_chunksize, "chunksize" ) ) - return NULL; - - offset = (uint64_t)tmp_offset; - size = (uint32_t)tmp_size; - chunksize = (uint32_t)tmp_chunksize; - uint64_t off_init = offset; - - if (offset == 0) - offset = self->currentOffset; - else - self->currentOffset = offset; - - // Default chunk size is 2MB or equal to size if size less then 2MB - if ( !chunksize ) chunksize = 1024 * 1024 * 2; - if ( !size ) size = UINT_MAX; - if ( size < chunksize ) chunksize = size; - - uint64_t off_end = offset + size; - XrdCl::Buffer* chunk = new XrdCl::Buffer(); - XrdCl::Buffer* line = new XrdCl::Buffer(); - - while ( offset < off_end ) - { - chunk = self->ReadChunk( self, offset, chunksize ); - offset += chunk->GetSize(); - - // Reached end of file - if ( !chunk->GetSize() ) - break; - - // Check if we read a new line - bool found_newline = false; - - for( uint32_t i = 0; i < chunk->GetSize(); ++i ) - { - chunk->SetCursor( i ); - - // Stop if found newline or read required amount of data - if( ( *chunk->GetBufferAtCursor() == '\n') || - ( line->GetSize() + i >= size)) - { - found_newline = true; - line->Append( chunk->GetBuffer(), i + 1 ); - break; - } - } - - if ( !found_newline ) - line->Append( chunk->GetBuffer(), chunk->GetSize() ); - else - break; - } - - if ( line->GetSize() != 0 ) - { - // Update file offset if default readline call - if ( off_init == 0 ) - self->currentOffset += line->GetSize(); - - pyline = PyBytes_FromStringAndSize( line->GetBuffer(), line->GetSize() ); - } - else - pyline = PyBytes_FromString( "" ); - - delete line; - delete chunk; - return pyline; - } - - //---------------------------------------------------------------------------- - //! Read data chunks from a given offset, separated by newlines, until EOF - //! encountered. Return list of lines read. A max read size can be specified, - //! but it should be noted that using this method is probably a bad idea. - //---------------------------------------------------------------------------- - PyObject* File::ReadLines( File *self, PyObject *args, PyObject *kwds ) - { - static const char *kwlist[] = { "offset", "size", "chunksize", NULL }; - uint64_t offset = 0; - uint32_t size = 0; - uint32_t chunksize = 0; - PyObject *py_offset = NULL, *py_size = NULL, *py_chunksize = NULL; - - if ( !self->file->IsOpen() ) return FileClosedError(); - - if ( !PyArg_ParseTupleAndKeywords( args, kwds, "|kII:readlines", - (char**) kwlist, &offset, &size, &chunksize ) ) return NULL; - - unsigned long long tmp_offset = 0; - unsigned int tmp_size = 0, tmp_chunksize = 0; - - if ( py_offset && PyObjToUllong( py_offset, &tmp_offset, "offset" ) ) - return NULL; - - if ( py_size && PyObjToUint( py_size, &tmp_size, "size" ) ) - return NULL; - - if ( py_chunksize && PyObjToUint( py_chunksize, &tmp_chunksize, "chunksize" ) ) - return NULL; - - offset = (uint64_t)tmp_offset; - size = (uint32_t)tmp_size; - chunksize = (uint16_t)tmp_chunksize; - - PyObject *lines = PyList_New( 0 ); - PyObject *line = NULL; - - for (;;) - { - line = self->ReadLine( self, args, kwds ); - - if ( !line || PyBytes_Size( line ) == 0 ) - break; - - PyList_Append( lines, line ); - } - - return lines; - } - - //---------------------------------------------------------------------------- - //! Read a chunk of the given size from the given offset as a string - //---------------------------------------------------------------------------- - XrdCl::Buffer* File::ReadChunk( File *self, uint64_t offset, uint32_t size ) - { - XrdCl::XRootDStatus status; - XrdCl::Buffer *buffer; - XrdCl::Buffer *temp; - uint32_t bytesRead; - - temp = new XrdCl::Buffer( size ); - status = self->file->Read( offset, size, temp->GetBuffer(), bytesRead ); - - buffer = new XrdCl::Buffer( bytesRead ); - buffer->Append( temp->GetBuffer(), bytesRead ); - delete temp; - return buffer; - } - - //---------------------------------------------------------------------------- - //! Read data chunks from a given offset of the given size, until EOF - //! encountered. Return chunk iterator. - //---------------------------------------------------------------------------- - PyObject* File::ReadChunks( File *self, PyObject *args, PyObject *kwds ) - { - static const char *kwlist[] = { "offset", "chunksize", NULL }; - uint64_t offset = 0; - uint32_t chunksize = 0; - ChunkIterator *iterator; - PyObject *py_offset = NULL, *py_chunksize = NULL; - - if ( !self->file->IsOpen() ) return FileClosedError(); - - if ( !PyArg_ParseTupleAndKeywords( args, kwds, "|OO:readchunks", - (char**) kwlist, &py_offset, &py_chunksize ) ) return NULL; - - unsigned long long tmp_offset = 0; - unsigned int tmp_chunksize = 1024 * 1024 *2; // 2 MB - - if ( py_offset && PyObjToUllong( py_offset, &tmp_offset, "offset" ) ) - return NULL; - - if ( py_chunksize && PyObjToUint( py_chunksize, &tmp_chunksize, "chunksize" ) ) - return NULL; - - offset = (uint64_t)tmp_offset; - chunksize = (uint32_t)tmp_chunksize; - ChunkIteratorType.tp_new = PyType_GenericNew; - - if ( PyType_Ready( &ChunkIteratorType ) < 0 ) return NULL; - - args = Py_BuildValue( "OOO", self, Py_BuildValue("k", offset), - Py_BuildValue("I", chunksize) ); - iterator = (ChunkIterator*) - PyObject_CallObject( (PyObject *) &ChunkIteratorType, args ); - Py_DECREF( args ); - if ( !iterator ) return NULL; - - return (PyObject *) iterator; - } - - //---------------------------------------------------------------------------- - //! Write a data chunk at a given offset - //---------------------------------------------------------------------------- - PyObject* File::Write( File *self, PyObject *args, PyObject *kwds ) - { - static const char *kwlist[] = { "buffer", "offset", "size", "timeout", - "callback", NULL }; - const char *buffer; - int buffsize; - uint64_t offset = 0; - uint32_t size = 0; - uint16_t timeout = 0; - PyObject *callback = NULL, *pystatus = NULL; - PyObject *py_offset = NULL, *py_size = NULL, *py_timeout = NULL; - XrdCl::XRootDStatus status; - - if ( !self->file->IsOpen() ) return FileClosedError(); - - if ( !PyArg_ParseTupleAndKeywords( args, kwds, "s#|OOOO:write", - (char**) kwlist, &buffer, &buffsize, &py_offset, &py_size, - &py_timeout, &callback ) ) return NULL; - - unsigned long long tmp_offset = 0; - unsigned int tmp_size = 0; - unsigned short int tmp_timeout = 0; - - if (py_offset && PyObjToUllong(py_offset, &tmp_offset, "offset")) - return NULL; - - if (py_size && PyObjToUint(py_size, &tmp_size, "size")) - return NULL; - - if (py_timeout && PyObjToUshrt(py_timeout, &tmp_timeout, "timeout")) - return NULL; - - offset = (uint64_t)tmp_offset; - size = (uint32_t)tmp_size; - timeout = (uint16_t)tmp_timeout; - - if (!size) { - size = buffsize; - } - - if ( callback && callback != Py_None ) { - XrdCl::ResponseHandler *handler = GetHandler( callback ); - if ( !handler ) return NULL; - async( status = self->file->Write( offset, size, buffer, handler, timeout ) ); - } - - else { - async( status = self->file->Write( offset, size, buffer, timeout ) ); - } - - pystatus = ConvertType( &status ); - PyObject *o = ( callback && callback != Py_None ) ? - Py_BuildValue( "O", pystatus ) : - Py_BuildValue( "OO", pystatus, Py_BuildValue( "" ) ); - Py_DECREF( pystatus ); - return o; - } - - //---------------------------------------------------------------------------- - //! Commit all pending disk writes - //---------------------------------------------------------------------------- - PyObject* File::Sync( File *self, PyObject *args, PyObject *kwds ) - { - static const char *kwlist[] = { "timeout", "callback", NULL }; - uint16_t timeout = 0; - PyObject *callback = NULL, *pystatus = NULL; - XrdCl::XRootDStatus status; - - if ( !self->file->IsOpen() ) return FileClosedError(); - - if ( !PyArg_ParseTupleAndKeywords( args, kwds, "|HO:sync", (char**) kwlist, - &timeout, &callback ) ) return NULL; - - if ( callback && callback != Py_None ) { - XrdCl::ResponseHandler *handler = GetHandler( callback ); - if ( !handler ) return NULL; - async( status = self->file->Sync( handler, timeout ) ); - } - else { - async( status = self->file->Sync( timeout ) ); - } - - pystatus = ConvertType( &status ); - PyObject *o = ( callback && callback != Py_None ) ? - Py_BuildValue( "O", pystatus ) : - Py_BuildValue( "OO", pystatus, Py_BuildValue( "" ) ); - Py_DECREF( pystatus ); - return o; - } - - //---------------------------------------------------------------------------- - //! Truncate the file to a particular size - //---------------------------------------------------------------------------- - PyObject* File::Truncate( File *self, PyObject *args, PyObject *kwds ) - { - static const char *kwlist[] = { "size", "timeout", "callback", NULL }; - uint64_t size; - uint16_t timeout = 0; - PyObject *callback = NULL, *pystatus = NULL; - PyObject *py_size = NULL, *py_timeout = NULL; - XrdCl::XRootDStatus status; - - if ( !self->file->IsOpen() ) return FileClosedError(); - - if ( !PyArg_ParseTupleAndKeywords( args, kwds, "O|OO:truncate", - (char**) kwlist, &py_size, &py_timeout, &callback ) ) return NULL; - - unsigned long long tmp_size = 0; - unsigned short int tmp_timeout = 0; - - if ( py_size && PyObjToUllong( py_size, &tmp_size, "size" ) ) - return NULL; - - if ( py_timeout && PyObjToUshrt( py_timeout, &tmp_timeout, "timeout" ) ) - return NULL; - - size = (uint64_t)tmp_size; - timeout = (uint16_t)tmp_timeout; - - if ( callback && callback != Py_None ) { - XrdCl::ResponseHandler *handler = GetHandler( callback ); - if ( !handler ) return NULL; - async( status = self->file->Truncate( size, handler, timeout ) ); - } - - else { - async( status = self->file->Truncate( size, timeout ) ); - } - - pystatus = ConvertType( &status ); - PyObject *o = ( callback && callback != Py_None ) ? - Py_BuildValue( "O", pystatus ) : - Py_BuildValue( "OO", pystatus, Py_BuildValue( "" ) ); - Py_DECREF( pystatus ); - return o; - } - - //---------------------------------------------------------------------------- - //! Read scattered data chunks in one operation - //---------------------------------------------------------------------------- - PyObject* File::VectorRead( File *self, PyObject *args, PyObject *kwds ) - { - static const char *kwlist[] = { "chunks", "timeout", "callback", NULL }; - uint16_t timeout = 0; - uint64_t offset = 0; - uint32_t length = 0; - PyObject *pychunks = NULL, *callback = NULL; - PyObject *pyresponse = NULL, *pystatus = NULL, *py_timeout = NULL; - XrdCl::XRootDStatus status; - XrdCl::ChunkList chunks; - - if ( !self->file->IsOpen() ) return FileClosedError(); - - if ( !PyArg_ParseTupleAndKeywords( args, kwds, "O|OO:vector_read", - (char**) kwlist, &pychunks, &py_timeout, &callback ) ) return NULL; - - unsigned short int tmp_timeout = 0; - - if ( py_timeout && PyObjToUshrt( py_timeout, &tmp_timeout, "timeout" ) ) - return NULL; - - timeout = (uint16_t)tmp_timeout; - - if ( !PyList_Check( pychunks ) ) { - PyErr_SetString( PyExc_TypeError, "chunks parameter must be a list" ); - return NULL; - } - - for ( int i = 0; i < PyList_Size( pychunks ); ++i ) { - PyObject *chunk = PyList_GetItem( pychunks, i ); - - if ( !PyTuple_Check( chunk ) || ( PyTuple_Size( chunk ) != 2 ) ) { - PyErr_SetString( PyExc_TypeError, "vector_read() expects list of tuples" - " of length 2" ); - return NULL; - } - - // Check that offset and length values are valid - unsigned long long tmp_offset = 0; - unsigned int tmp_length = 0; - - if ( PyObjToUllong( PyTuple_GetItem( chunk, 0 ), &tmp_offset, "offset" ) ) - return NULL; - - if ( PyObjToUint( PyTuple_GetItem( chunk, 1 ), &tmp_length, "length" ) ) - return NULL; - - offset = (uint64_t)tmp_offset; - length = (uint32_t)tmp_length; - char *buffer = new char[length]; - chunks.push_back( XrdCl::ChunkInfo( offset, length, buffer ) ); - } - - if ( callback && callback != Py_None ) { - XrdCl::ResponseHandler *handler - = GetHandler( callback ); - if ( !handler ) return NULL; - async( status = self->file->VectorRead( chunks, 0, handler, timeout ) ); - } - else { - XrdCl::VectorReadInfo *info = 0; - async( status = self->file->VectorRead( chunks, 0, info, timeout ) ); - pyresponse = ConvertType( info ); - delete info; - } - - pystatus = ConvertType( &status ); - PyObject *o = ( callback && callback != Py_None ) ? - Py_BuildValue( "O", pystatus ) : - Py_BuildValue( "OO", pystatus, pyresponse ); - Py_DECREF( pystatus ); - Py_XDECREF( pyresponse ); - return o; - } - - //---------------------------------------------------------------------------- - // Perform a custom operation on an open file - //---------------------------------------------------------------------------- - PyObject* File::Fcntl( File *self, PyObject *args, PyObject *kwds ) - { - static const char *kwlist[] = { "arg", "timeout", "callback", NULL }; - const char *buffer = 0; - int buffSize = 0; - uint16_t timeout = 0; - PyObject *callback = NULL, *pystatus = NULL, *pyresponse = NULL; - XrdCl::XRootDStatus status; - - if ( !self->file->IsOpen() ) return FileClosedError(); - - if ( !PyArg_ParseTupleAndKeywords( args, kwds, "s#|HO:fcntl", - (char**) kwlist, &buffer, &buffSize, &timeout, &callback ) ) - return NULL; - - XrdCl::Buffer arg; arg.Append( buffer, buffSize ); - - if ( callback && callback != Py_None ) - { - XrdCl::ResponseHandler *handler = GetHandler( callback ); - if( !handler ) - return NULL; - async( status = self->file->Fcntl( arg, handler, timeout ) ); - } - - else { - XrdCl::Buffer *response = 0; - async( status = self->file->Fcntl( arg, response, timeout ) ); - pyresponse = ConvertType( response ); - delete response; - } - - pystatus = ConvertType( &status ); - PyObject *o = ( callback && callback != Py_None ) ? - Py_BuildValue( "O", pystatus ) : - Py_BuildValue( "OO", pystatus, pyresponse ); - Py_DECREF( pystatus ); - Py_XDECREF( pyresponse ); - return o; - } - - //---------------------------------------------------------------------------- - // Perform a custom operation on an open file - //---------------------------------------------------------------------------- - PyObject* File::Visa( File *self, PyObject *args, PyObject *kwds ) - { - static const char *kwlist[] = { "timeout", "callback", NULL }; - uint16_t timeout = 0; - PyObject *callback = NULL, *pystatus = NULL, *pyresponse = NULL; - XrdCl::XRootDStatus status; - - if ( !self->file->IsOpen() ) return FileClosedError(); - - if ( !PyArg_ParseTupleAndKeywords( args, kwds, "|HO:visa", - (char**) kwlist, &timeout, &callback ) ) - return NULL; - - if ( callback && callback != Py_None ) - { - XrdCl::ResponseHandler *handler = GetHandler( callback ); - if( !handler ) - return NULL; - async( status = self->file->Visa( handler, timeout ) ); - } - - else { - XrdCl::Buffer *response = 0; - async( status = self->file->Visa( response, timeout ) ); - pyresponse = ConvertType( response ); - delete response; - } - - pystatus = ConvertType( &status ); - PyObject *o = ( callback && callback != Py_None ) ? - Py_BuildValue( "O", pystatus ) : - Py_BuildValue( "OO", pystatus, pyresponse ); - Py_DECREF( pystatus ); - Py_XDECREF( pyresponse ); - return o; - } - - //---------------------------------------------------------------------------- - //! Check if the file is open - //---------------------------------------------------------------------------- - PyObject* File::IsOpen( File *self, PyObject *args, PyObject *kwds ) - { - if ( !PyArg_ParseTuple( args, ":is_open" ) ) return NULL; // Allow no args - return PyBool_FromLong(self->file->IsOpen()); - } - - //---------------------------------------------------------------------------- - //! Get property - //---------------------------------------------------------------------------- - PyObject* File::GetProperty( File *self, PyObject *args, PyObject *kwds ) - { - static const char *kwlist[] = { "name", NULL }; - char *name = 0; - std::string value; - - if ( !PyArg_ParseTupleAndKeywords( args, kwds, "s:get_property", - (char**) kwlist, &name ) ) return NULL; - - bool status = self->file->GetProperty( name, value ); - - return status ? Py_BuildValue( "s", value.c_str() ) : Py_None; - } - - //---------------------------------------------------------------------------- - //! Set property - //---------------------------------------------------------------------------- - PyObject* File::SetProperty( File *self, PyObject *args, PyObject *kwds ) - { - (void) FileType; // Suppress unused variable warning - - static const char *kwlist[] = { "name", "value", NULL }; - char *name = 0; - char *value = 0; - - if ( !PyArg_ParseTupleAndKeywords( args, kwds, "ss:set_property", - (char**) kwlist, &name, &value ) ) return NULL; - - bool status = self->file->SetProperty( name, value ); - return status ? Py_True : Py_False; - } -} diff --git a/bindings/python/src/PyXRootDFile.hh b/bindings/python/src/PyXRootDFile.hh deleted file mode 100644 index 99fdaeb63f2..00000000000 --- a/bindings/python/src/PyXRootDFile.hh +++ /dev/null @@ -1,250 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2012-2014 by European Organization for Nuclear Research (CERN) -// Author: Justin Salmon -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#ifndef PYXROOTDFILE_HH_ -#define PYXROOTDFILE_HH_ - -#include "PyXRootD.hh" -#include "Utils.hh" - -#include "XrdCl/XrdClFile.hh" - -#include - -namespace PyXRootD -{ - //---------------------------------------------------------------------------- - //! XrdCl::File binding class - //---------------------------------------------------------------------------- - class File - { - public: - static PyObject* Open( File *self, PyObject *args, PyObject *kwds ); - static PyObject* Close( File *self, PyObject *args, PyObject *kwds ); - static PyObject* Stat( File *self, PyObject *args, PyObject *kwds ); - static PyObject* Read( File *self, PyObject *args, PyObject *kwds ); - static PyObject* ReadLine( File *self, PyObject *args, PyObject *kwds ); - static PyObject* ReadLines( File *self, PyObject *args, PyObject *kwds ); - static XrdCl::Buffer* ReadChunk( File *self, uint64_t offset, uint32_t size ); - static PyObject* ReadChunks( File *self, PyObject *args, PyObject *kwds ); - static PyObject* Write( File *self, PyObject *args, PyObject *kwds ); - static PyObject* Sync( File *self, PyObject *args, PyObject *kwds ); - static PyObject* Truncate( File *self, PyObject *args, PyObject *kwds ); - static PyObject* VectorRead( File *self, PyObject *args, PyObject *kwds ); - static PyObject* Fcntl( File *self, PyObject *args, PyObject *kwds ); - static PyObject* Visa( File *self, PyObject *args, PyObject *kwds ); - static PyObject* IsOpen( File *self, PyObject *args, PyObject *kwds ); - static PyObject* GetProperty( File *self, PyObject *args, PyObject *kwds ); - static PyObject* SetProperty( File *self, PyObject *args, PyObject *kwds ); - - public: - PyObject_HEAD - XrdCl::File *file; - uint64_t currentOffset; - }; - - PyDoc_STRVAR(file_type_doc, "File object (internal)"); - - //---------------------------------------------------------------------------- - //! Set exception and return null if I/O op on closed file is attempted - //---------------------------------------------------------------------------- - static PyObject* FileClosedError() - { - PyErr_SetString( PyExc_ValueError, "I/O operation on closed file" ); - return NULL; - } - - //---------------------------------------------------------------------------- - //! __init__() equivalent - //---------------------------------------------------------------------------- - static int File_init( File *self, PyObject *args ) - { - self->file = new XrdCl::File(); - self->currentOffset = 0; - return 0; - } - - //---------------------------------------------------------------------------- - //! Deallocation function, called when object is deleted - //---------------------------------------------------------------------------- - static void File_dealloc( File *self ) - { - delete self->file; - Py_TYPE(self)->tp_free( (PyObject*) self ); - } - - //---------------------------------------------------------------------------- - //! __iter__ - //---------------------------------------------------------------------------- - static PyObject* File_iter( File *self ) - { - if ( !self->file->IsOpen() ) return FileClosedError(); - - //-------------------------------------------------------------------------- - // Return ourselves for iteration - //-------------------------------------------------------------------------- - Py_INCREF( self ); - return (PyObject*) self; - } - - //---------------------------------------------------------------------------- - //! __iternext__ - //---------------------------------------------------------------------------- - static PyObject* File_iternext( File *self ) - { - if ( !self->file->IsOpen() ) return FileClosedError(); - - PyObject *line = PyObject_CallMethod( (PyObject*) self, - const_cast("readline"), NULL ); - if( !line ) return NULL; - //-------------------------------------------------------------------------- - // Raise StopIteration if the line we just read is empty - //-------------------------------------------------------------------------- - if ( PyBytes_Size( line ) == 0 ) { - PyErr_SetNone( PyExc_StopIteration ); - return NULL; - } - - return line; - } - - //---------------------------------------------------------------------------- - //! __enter__ - //---------------------------------------------------------------------------- - static PyObject* File_enter( File *self ) - { - Py_INCREF( self ); - return (PyObject*) self; - } - - //---------------------------------------------------------------------------- - //! __exit__ - //---------------------------------------------------------------------------- - static PyObject* File_exit( File *self ) - { - PyObject *ret = PyObject_CallMethod( (PyObject*) self, - const_cast("close"), NULL ); - if ( !ret ) return NULL; - Py_DECREF( ret ); - Py_RETURN_NONE ; - } - - //---------------------------------------------------------------------------- - //! Visible method definitions - //---------------------------------------------------------------------------- - static PyMethodDef FileMethods[] = - { - { "open", - (PyCFunction) PyXRootD::File::Open, METH_VARARGS | METH_KEYWORDS, NULL }, - { "close", - (PyCFunction) PyXRootD::File::Close, METH_VARARGS | METH_KEYWORDS, NULL }, - { "stat", - (PyCFunction) PyXRootD::File::Stat, METH_VARARGS | METH_KEYWORDS, NULL }, - { "read", - (PyCFunction) PyXRootD::File::Read, METH_VARARGS | METH_KEYWORDS, NULL }, - { "readline", - (PyCFunction) PyXRootD::File::ReadLine, METH_VARARGS | METH_KEYWORDS, NULL }, - { "readlines", - (PyCFunction) PyXRootD::File::ReadLines, METH_VARARGS | METH_KEYWORDS, NULL }, - { "readchunks", - (PyCFunction) PyXRootD::File::ReadChunks, METH_VARARGS | METH_KEYWORDS, NULL }, - { "write", - (PyCFunction) PyXRootD::File::Write, METH_VARARGS | METH_KEYWORDS, NULL }, - { "sync", - (PyCFunction) PyXRootD::File::Sync, METH_VARARGS | METH_KEYWORDS, NULL }, - { "truncate", - (PyCFunction) PyXRootD::File::Truncate, METH_VARARGS | METH_KEYWORDS, NULL }, - { "vector_read", - (PyCFunction) PyXRootD::File::VectorRead, METH_VARARGS | METH_KEYWORDS, NULL }, - { "fcntl", - (PyCFunction) PyXRootD::File::Fcntl, METH_VARARGS | METH_KEYWORDS, NULL }, - { "visa", - (PyCFunction) PyXRootD::File::Visa, METH_VARARGS | METH_KEYWORDS, NULL }, - { "is_open", - (PyCFunction) PyXRootD::File::IsOpen, METH_VARARGS | METH_KEYWORDS, NULL }, - { "get_property", - (PyCFunction) PyXRootD::File::GetProperty, METH_VARARGS | METH_KEYWORDS, NULL }, - { "set_property", - (PyCFunction) PyXRootD::File::SetProperty, METH_VARARGS | METH_KEYWORDS, NULL }, - {"__enter__", - (PyCFunction) File_enter, METH_NOARGS, NULL}, - {"__exit__", - (PyCFunction) File_exit, METH_VARARGS, NULL}, - - { NULL } /* Sentinel */ - }; - - //---------------------------------------------------------------------------- - //! Visible member definitions - //---------------------------------------------------------------------------- - static PyMemberDef FileMembers[] = - { - { NULL } /* Sentinel */ - }; - - //---------------------------------------------------------------------------- - //! File binding type object - //---------------------------------------------------------------------------- - static PyTypeObject FileType = { - PyVarObject_HEAD_INIT(NULL, 0) - "pyxrootd.File", /* tp_name */ - sizeof(File), /* tp_basicsize */ - 0, /* tp_itemsize */ - (destructor) File_dealloc, /* tp_dealloc */ - 0, /* tp_print */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_compare */ - 0, /* tp_repr */ - 0, /* tp_as_number */ - 0, /* tp_as_sequence */ - 0, /* tp_as_mapping */ - 0, /* tp_hash */ - 0, /* tp_call */ - 0, /* tp_str */ - 0, /* tp_getattro */ - 0, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE - | Py_TPFLAGS_HAVE_ITER, /* tp_flags */ - file_type_doc, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - (getiterfunc) File_iter, /* tp_iter */ - (iternextfunc) File_iternext, /* tp_iternext */ - FileMethods, /* tp_methods */ - FileMembers, /* tp_members */ - 0, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - (initproc) File_init, /* tp_init */ - }; -} - -#endif /* PYXROOTDFILE_HH_ */ diff --git a/bindings/python/src/PyXRootDFileSystem.cc b/bindings/python/src/PyXRootDFileSystem.cc deleted file mode 100644 index 7fbfd7ab06f..00000000000 --- a/bindings/python/src/PyXRootDFileSystem.cc +++ /dev/null @@ -1,699 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2012-2014 by European Organization for Nuclear Research (CERN) -// Author: Justin Salmon -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#include "PyXRootD.hh" -#include "PyXRootDFileSystem.hh" -#include "PyXRootDCopyProcess.hh" -#include "AsyncResponseHandler.hh" -#include "Utils.hh" - -#include "XrdCl/XrdClFileSystem.hh" - -namespace PyXRootD -{ - //---------------------------------------------------------------------------- - //! Copy a file - //---------------------------------------------------------------------------- - PyObject* FileSystem::Copy( FileSystem *self, PyObject *args, PyObject *kwds ) - { - static const char *kwlist[] = { "source", "target", "force", NULL }; - const char *source, *target; - bool force = false; - PyObject *pystatus = NULL; - CopyProcess *copyprocess = NULL; - - if ( !PyArg_ParseTupleAndKeywords( args, kwds, "ss|i:copy", - (char**) kwlist, &source, &target, &force ) ) return NULL; - - CopyProcessType.tp_new = PyType_GenericNew; - if ( PyType_Ready( &CopyProcessType ) < 0 ) return NULL; - - copyprocess = (CopyProcess*) - PyObject_CallObject( (PyObject *) &CopyProcessType, NULL ); - if ( !copyprocess ) return NULL; - - copyprocess->AddJob( copyprocess, args, kwds ); - pystatus = copyprocess->Prepare( copyprocess, NULL, NULL ); - if ( !pystatus ) return NULL; - if ( PyDict_GetItemString( pystatus, "ok" ) == Py_False ) - { - PyObject *tuple = PyTuple_New(2); - PyTuple_SetItem(tuple, 0, pystatus); - Py_INCREF(Py_None); - PyTuple_SetItem(tuple, 1, Py_None); - return tuple; - } - - pystatus = copyprocess->Run( copyprocess, PyTuple_New(0), PyDict_New() ); - if ( !pystatus ) return NULL; - - Py_DECREF( copyprocess ); - return pystatus; - } - - //---------------------------------------------------------------------------- - //! Locate a file - //---------------------------------------------------------------------------- - PyObject* FileSystem::Locate( FileSystem *self, PyObject *args, PyObject *kwds ) - { - static const char *kwlist[] = { "path", "flags", "timeout", "callback", - NULL }; - const char *path; - XrdCl::OpenFlags::Flags flags = XrdCl::OpenFlags::None; - uint16_t timeout = 0; - PyObject *callback = NULL, *pyresponse = NULL, *pystatus = NULL; - XrdCl::XRootDStatus status; - - if ( !PyArg_ParseTupleAndKeywords( args, kwds, "sH|HO:locate", - (char**) kwlist, &path, &flags, &timeout, &callback ) ) return NULL; - - if ( callback && callback != Py_None ) { - XrdCl::ResponseHandler *handler = GetHandler( callback ); - if ( !handler ) return NULL; - async( status = self->filesystem->Locate( path, flags, handler, timeout ) ); - } - - else { - XrdCl::LocationInfo *response = 0; - async( status = self->filesystem->Locate( path, flags, response, timeout ) ); - pyresponse = ConvertType( response ); - delete response; - } - - pystatus = ConvertType( &status ); - PyObject *o = ( callback && callback != Py_None ) ? - Py_BuildValue( "O", pystatus ) : - Py_BuildValue( "OO", pystatus, pyresponse ); - Py_DECREF( pystatus ); - Py_XDECREF( pyresponse ); - return o; - } - - //---------------------------------------------------------------------------- - //! Locate a file, recursively locate all disk servers - //---------------------------------------------------------------------------- - PyObject* FileSystem::DeepLocate( FileSystem *self, PyObject *args, PyObject *kwds ) - { - static const char *kwlist[] = { "path", "flags", "timeout", "callback", - NULL }; - const char *path; - XrdCl::OpenFlags::Flags flags = XrdCl::OpenFlags::None; - uint16_t timeout = 0; - PyObject *callback = NULL, *pyresponse = NULL, *pystatus = NULL; - XrdCl::XRootDStatus status; - - if ( !PyArg_ParseTupleAndKeywords( args, kwds, "sH|HO:deeplocate", - (char**) kwlist, &path, &flags, &timeout, &callback ) ) return NULL; - - if ( callback && callback != Py_None ) { - XrdCl::ResponseHandler *handler = GetHandler( callback ); - if ( !handler ) return NULL; - async( status = self->filesystem->DeepLocate( path, flags, handler, timeout ) ); - } - - else { - XrdCl::LocationInfo *response = 0; - async( status = self->filesystem->DeepLocate( path, flags, response, timeout ) ); - pyresponse = ConvertType( response ); - delete response; - } - - pystatus = ConvertType( &status ); - PyObject *o = ( callback && callback != Py_None ) ? - Py_BuildValue( "O", pystatus ) : - Py_BuildValue( "OO", pystatus, pyresponse ); - Py_DECREF( pystatus ); - Py_XDECREF( pyresponse ); - return o; - } - - //---------------------------------------------------------------------------- - //! Move a directory or a file - //---------------------------------------------------------------------------- - PyObject* FileSystem::Mv( FileSystem *self, PyObject *args, PyObject *kwds ) - { - static const char *kwlist[] = { "source", "dest", "timeout", "callback", - NULL }; - const char *source; - const char *dest; - uint16_t timeout = 0; - PyObject *callback = NULL, *pystatus = NULL; - XrdCl::XRootDStatus status; - - if ( !PyArg_ParseTupleAndKeywords( args, kwds, "ss|HO:mv", (char**) kwlist, - &source, &dest, &timeout, &callback ) ) return NULL; - - if ( callback && callback != Py_None ) { - XrdCl::ResponseHandler *handler = GetHandler( callback ); - if ( !handler ) return NULL; - async( status = self->filesystem->Mv( source, dest, handler, timeout ) ); - } - - else { - async( status = self->filesystem->Mv( source, dest, timeout ) ); - } - - pystatus = ConvertType( &status ); - PyObject *o = ( callback && callback != Py_None ) ? - Py_BuildValue( "O", pystatus ) : - Py_BuildValue( "OO", pystatus, Py_BuildValue( "" ) ); - Py_DECREF( pystatus ); - return o; - } - - //---------------------------------------------------------------------------- - //! Obtain server information - //---------------------------------------------------------------------------- - PyObject* FileSystem::Query( FileSystem *self, PyObject *args, PyObject *kwds ) - { - static const char *kwlist[] = { "querycode", "arg", "timeout", - "callback", NULL }; - const char *arg; - uint16_t timeout = 0; - PyObject *callback = NULL, *pyresponse = NULL, *pystatus = NULL; - XrdCl::QueryCode::Code queryCode; - XrdCl::XRootDStatus status; - XrdCl::Buffer argbuffer; - - if ( !PyArg_ParseTupleAndKeywords( args, kwds, "is|HO:query", - (char**) kwlist, &queryCode, &arg, &timeout, &callback ) ) return NULL; - - argbuffer.FromString(arg); - - if ( callback && callback != Py_None ) { - XrdCl::ResponseHandler *handler = GetHandler( callback ); - if ( !handler ) return NULL; - async( status = self->filesystem->Query( queryCode, argbuffer, handler, timeout ) ); - } - - else { - XrdCl::Buffer *response = 0; - async( status = self->filesystem->Query( queryCode, argbuffer, response, timeout ) ); - pyresponse = ConvertType( response ); - delete response; - } - - pystatus = ConvertType( &status ); - PyObject *o = ( callback && callback != Py_None ) ? - Py_BuildValue( "O", pystatus ) : - Py_BuildValue( "OO", pystatus, pyresponse ); - Py_DECREF( pystatus ); - Py_XDECREF( pyresponse ); - return o; - } - - //---------------------------------------------------------------------------- - //! Truncate a file - //---------------------------------------------------------------------------- - PyObject* FileSystem::Truncate( FileSystem *self, PyObject *args, PyObject *kwds ) - { - static const char *kwlist[] = { "path", "size", "timeout", "callback", NULL }; - const char *path; - uint64_t size = 0; - uint16_t timeout = 0; - PyObject *callback = NULL, *pystatus = NULL; - XrdCl::XRootDStatus status; - - if ( !PyArg_ParseTupleAndKeywords( args, kwds, "sK|HO:truncate", - (char**) kwlist, &path, &size, &timeout, &callback ) ) return NULL; - - if ( callback && callback != Py_None ) { - XrdCl::ResponseHandler *handler = GetHandler( callback ); - if ( !handler ) return NULL; - async( status = self->filesystem->Truncate( path, size, handler, timeout ) ); - } - - else { - async( status = self->filesystem->Truncate( path, size, timeout ) ); - } - - pystatus = ConvertType( &status ); - PyObject *o = ( callback && callback != Py_None ) ? - Py_BuildValue( "O", pystatus ) : - Py_BuildValue( "OO", pystatus, Py_BuildValue( "" ) ); - Py_DECREF( pystatus ); - return o; - } - - //---------------------------------------------------------------------------- - //! Remove a file - //---------------------------------------------------------------------------- - PyObject* FileSystem::Rm( FileSystem *self, PyObject *args, PyObject *kwds ) - { - static const char *kwlist[] = { "path", "timeout", "callback", NULL }; - const char *path; - uint16_t timeout = 0; - PyObject *callback = NULL, *pystatus = NULL; - XrdCl::XRootDStatus status; - - if ( !PyArg_ParseTupleAndKeywords( args, kwds, "s|HO:rm", (char**) kwlist, - &path, &timeout, &callback ) ) return NULL; - - if ( callback && callback != Py_None ) { - XrdCl::ResponseHandler *handler = GetHandler( callback ); - if ( !handler ) return NULL; - async( status = self->filesystem->Rm( path, handler, timeout ) ); - } - - else { - async( status = self->filesystem->Rm( path, timeout ) ); - } - - pystatus = ConvertType( &status ); - PyObject *o = ( callback && callback != Py_None ) ? - Py_BuildValue( "O", pystatus ) : - Py_BuildValue( "OO", pystatus, Py_BuildValue( "" ) ); - Py_DECREF( pystatus ); - return o; - } - - //---------------------------------------------------------------------------- - //! Create a directory - //---------------------------------------------------------------------------- - PyObject* FileSystem::MkDir( FileSystem *self, PyObject *args, PyObject *kwds ) - { - static const char *kwlist[] = { "path", "flags", "mode", "timeout", - "callback", NULL }; - const char *path; - XrdCl::MkDirFlags::Flags flags = XrdCl::MkDirFlags::None; - XrdCl::Access::Mode mode = XrdCl::Access::None; - uint16_t timeout = 0; - PyObject *callback = NULL, *pystatus = NULL; - XrdCl::XRootDStatus status; - - if ( !PyArg_ParseTupleAndKeywords( args, kwds, "s|HHHO:mkdir", (char**) kwlist, - &path, &flags, &mode, &timeout, &callback ) ) return NULL; - - if ( callback && callback != Py_None ) { - XrdCl::ResponseHandler *handler = GetHandler( callback ); - if ( !handler ) return NULL; - async( status = self->filesystem->MkDir( path, flags, mode, handler, timeout ) ); - } - - else { - async( status = self->filesystem->MkDir( path, flags, mode, timeout ) ); - } - - pystatus = ConvertType( &status ); - PyObject *o = ( callback && callback != Py_None ) ? - Py_BuildValue( "O", pystatus ) : - Py_BuildValue( "OO", pystatus, Py_BuildValue( "" ) ); - Py_DECREF( pystatus ); - return o; - } - - //---------------------------------------------------------------------------- - //! Remove a directory - //---------------------------------------------------------------------------- - PyObject* FileSystem::RmDir( FileSystem *self, PyObject *args, PyObject *kwds ) - { - static const char *kwlist[] = { "path", "timeout", "callback", NULL }; - const char *path; - uint16_t timeout = 0; - PyObject *callback = NULL, *pystatus = NULL; - XrdCl::XRootDStatus status; - - if ( !PyArg_ParseTupleAndKeywords( args, kwds, "s|HO:rmdir", (char**) kwlist, - &path, &timeout, &callback ) ) return NULL; - - if ( callback && callback != Py_None ) { - XrdCl::ResponseHandler *handler = GetHandler( callback ); - if ( !handler ) return NULL; - async( status = self->filesystem->RmDir( path, handler, timeout ) ); - } - - else { - async( status = self->filesystem->RmDir( path, timeout ) ); - } - - pystatus = ConvertType( &status ); - PyObject *o = ( callback && callback != Py_None ) ? - Py_BuildValue( "O", pystatus ) : - Py_BuildValue( "OO", pystatus, Py_BuildValue( "" ) ); - Py_DECREF( pystatus ); - return o; - } - - //---------------------------------------------------------------------------- - //! Change access mode on a directory or a file - //---------------------------------------------------------------------------- - PyObject* FileSystem::ChMod( FileSystem *self, PyObject *args, PyObject *kwds ) - { - static const char *kwlist[] = { "path", "mode", "timeout", "callback", NULL }; - const char *path; - XrdCl::Access::Mode mode = XrdCl::Access::None; - uint16_t timeout = 0; - PyObject *callback = NULL, *pystatus = NULL; - XrdCl::XRootDStatus status; - - if ( !PyArg_ParseTupleAndKeywords( args, kwds, "sH|HO:chmod", (char**) kwlist, - &path, &mode, &timeout, &callback ) ) return NULL; - - if ( callback && callback != Py_None ) { - XrdCl::ResponseHandler *handler = GetHandler( callback ); - if ( !handler ) return NULL; - async( status = self->filesystem->ChMod( path, mode, handler, timeout ) ); - } - - else { - async( status = self->filesystem->ChMod( path, mode, timeout ) ); - } - - pystatus = ConvertType( &status ); - PyObject *o = ( callback && callback != Py_None ) ? - Py_BuildValue( "O", pystatus ) : - Py_BuildValue( "OO", pystatus, Py_BuildValue( "" ) ); - Py_DECREF( pystatus ); - return o; - } - - //---------------------------------------------------------------------------- - // Check if the server is alive - //---------------------------------------------------------------------------- - PyObject* FileSystem::Ping( FileSystem *self, PyObject *args, PyObject *kwds ) - { - static const char *kwlist[] = { "timeout", "callback", NULL }; - uint16_t timeout = 0; - PyObject *callback = NULL, *pystatus = NULL; - XrdCl::XRootDStatus status; - - if ( !PyArg_ParseTupleAndKeywords( args, kwds, "|HO:ping", (char**) kwlist, - &timeout, &callback ) ) return NULL; - - if ( callback && callback != Py_None ) { - XrdCl::ResponseHandler *handler = GetHandler( callback ); - if ( !handler ) return NULL; - async( status = self->filesystem->Ping( handler, timeout ) ); - } - - else { - async( status = self->filesystem->Ping( timeout ) ); - } - - pystatus = ConvertType( &status ); - PyObject *o = ( callback && callback != Py_None ) ? - Py_BuildValue( "O", pystatus ) : - Py_BuildValue( "OO", pystatus, Py_BuildValue( "" ) ); - Py_DECREF( pystatus ); - return o; - } - - //---------------------------------------------------------------------------- - //! Obtain status information for a path - //---------------------------------------------------------------------------- - PyObject* FileSystem::Stat( FileSystem *self, PyObject *args, PyObject *kwds ) - { - static const char *kwlist[] = { "path", "timeout", "callback", NULL }; - const char *path; - uint16_t timeout = 0; - PyObject *callback = NULL, *pyresponse = NULL, *pystatus = NULL; - XrdCl::XRootDStatus status; - - if ( !PyArg_ParseTupleAndKeywords( args, kwds, "s|HO:stat", (char**) kwlist, - &path, &timeout, &callback ) ) return NULL; - - if ( callback && callback != Py_None ) { - XrdCl::ResponseHandler *handler = GetHandler( callback ); - if ( !handler ) return NULL; - async( status = self->filesystem->Stat( path, handler, timeout ) ); - } - - else { - XrdCl::StatInfo *response = 0; - async( status = self->filesystem->Stat( path, response, timeout ) ); - pyresponse = ConvertType( response ); - delete response; - } - - pystatus = ConvertType( &status ); - PyObject *o = ( callback && callback != Py_None ) ? - Py_BuildValue( "O", pystatus ) : - Py_BuildValue( "OO", pystatus, pyresponse ); - Py_DECREF( pystatus ); - Py_XDECREF( pyresponse ); - return o; - } - - //---------------------------------------------------------------------------- - //! Obtain status information for a Virtual File System - //---------------------------------------------------------------------------- - PyObject* FileSystem::StatVFS( FileSystem *self, PyObject *args, PyObject *kwds ) - { - static const char *kwlist[] = { "path", "timeout", "callback", NULL }; - const char *path; - uint16_t timeout = 0; - PyObject *callback = NULL, *pyresponse = NULL, *pystatus = NULL; - XrdCl::XRootDStatus status; - - if ( !PyArg_ParseTupleAndKeywords( args, kwds, "s|HO:statvfs", (char**) kwlist, - &path, &timeout, &callback ) ) return NULL; - - if ( callback && callback != Py_None ) { - XrdCl::ResponseHandler *handler = GetHandler( callback ); - if ( !handler ) return NULL; - async( status = self->filesystem->StatVFS( path, handler, timeout ) ); - } - - else { - XrdCl::StatInfoVFS *response = 0; - async( status = self->filesystem->StatVFS( path, response, timeout ) ); - pyresponse = ConvertType( response ); - delete response; - } - - pystatus = ConvertType( &status ); - PyObject *o = ( callback && callback != Py_None ) ? - Py_BuildValue( "O", pystatus ) : - Py_BuildValue( "OO", pystatus, pyresponse ); - Py_DECREF( pystatus ); - Py_XDECREF( pyresponse ); - return o; - } - - //---------------------------------------------------------------------------- - //! Obtain server protocol information - //---------------------------------------------------------------------------- - PyObject* FileSystem::Protocol( FileSystem *self, PyObject *args, PyObject *kwds ) - { - static const char *kwlist[] = { "timeout", "callback", NULL }; - uint16_t timeout = 0; - PyObject *callback = NULL, *pyresponse = NULL, *pystatus = NULL; - XrdCl::XRootDStatus status; - - if ( !PyArg_ParseTupleAndKeywords( args, kwds, "|HO:protocol", (char**) kwlist, - &timeout, &callback ) ) return NULL; - - if ( callback && callback != Py_None ) { - XrdCl::ResponseHandler *handler = GetHandler( callback ); - if ( !handler ) return NULL; - async( status = self->filesystem->Protocol( handler, timeout ) ); - } - - else { - XrdCl::ProtocolInfo *response = 0; - async( status = self->filesystem->Protocol( response, timeout ) ); - pyresponse = ConvertType( response ); - delete response; - } - - pystatus = ConvertType( &status ); - PyObject *o = ( callback && callback != Py_None ) ? - Py_BuildValue( "O", pystatus ) : - Py_BuildValue( "OO", pystatus, pyresponse ); - Py_DECREF( pystatus ); - Py_XDECREF( pyresponse ); - return o; - } - - //---------------------------------------------------------------------------- - //! List entries of a directory - //---------------------------------------------------------------------------- - PyObject* FileSystem::DirList( FileSystem *self, PyObject *args, PyObject *kwds ) - { - static const char *kwlist[] = { "path", "flags", "timeout", - "callback", NULL }; - const char *path; - XrdCl::DirListFlags::Flags flags = XrdCl::DirListFlags::None; - uint16_t timeout = 0; - PyObject *callback = NULL, *pyresponse = NULL, *pystatus = NULL; - XrdCl::XRootDStatus status; - - if ( !PyArg_ParseTupleAndKeywords( args, kwds, "s|bHO:dirlist", - (char**) kwlist, &path, &flags, &timeout, &callback ) ) return NULL; - - if ( callback && callback != Py_None ) { - XrdCl::ResponseHandler *handler = GetHandler( callback ); - if ( !handler ) return NULL; - async( status = self->filesystem->DirList( path, flags, handler, timeout ) ); - } - - else { - XrdCl::DirectoryList *list = 0; - async( status = self->filesystem->DirList( path, flags, list, timeout ) ); - pyresponse = ConvertType( list ); - delete list; - } - - pystatus = ConvertType( &status ); - PyObject *o = ( callback && callback != Py_None ) ? - Py_BuildValue( "O", pystatus ) : - Py_BuildValue( "OO", pystatus, pyresponse ); - Py_DECREF( pystatus ); - Py_XDECREF( pyresponse ); - return o; - } - - //---------------------------------------------------------------------------- - //! Send info to the server (up to 1024 characters) - //---------------------------------------------------------------------------- - PyObject* FileSystem::SendInfo( FileSystem *self, PyObject *args, PyObject *kwds ) - { - static const char *kwlist[] = { "info", "timeout", "callback", NULL }; - const char *info; - uint16_t timeout = 0; - PyObject *callback = NULL, *pyresponse = NULL, *pystatus = NULL; - XrdCl::XRootDStatus status; - - if ( !PyArg_ParseTupleAndKeywords( args, kwds, "s|HO:sendinfo", - (char**) kwlist, &info, &timeout, &callback ) ) return NULL; - - if ( callback && callback != Py_None ) { - XrdCl::ResponseHandler *handler = GetHandler( callback ); - if ( !handler ) return NULL; - async( status = self->filesystem->SendInfo( info, handler, timeout ) ); - } - - else { - XrdCl::Buffer *response = 0; - async( status = self->filesystem->SendInfo( info, response, timeout ) ); - pyresponse = ConvertType( response ); - delete response; - } - - pystatus = ConvertType( &status ); - PyObject *o = ( callback && callback != Py_None ) ? - Py_BuildValue( "O", pystatus ) : - Py_BuildValue( "OO", pystatus, pyresponse ); - Py_DECREF( pystatus ); - Py_XDECREF( pyresponse ); - return o; - } - - //---------------------------------------------------------------------------- - //! Prepare one or more files for access - //---------------------------------------------------------------------------- - PyObject* FileSystem::Prepare( FileSystem *self, PyObject *args, PyObject *kwds ) - { - static const char *kwlist[] = { "files", "flags", "priority", - "timeout", "callback", NULL }; - XrdCl::PrepareFlags::Flags flags; - uint8_t priority = 0; - uint16_t timeout = 0; - PyObject *pyfiles = NULL, *callback = NULL; - PyObject *pyresponse = NULL, *pystatus = NULL; - XrdCl::XRootDStatus status; - - if ( !PyArg_ParseTupleAndKeywords( args, kwds, "Ob|bHO:prepare", - (char**) kwlist, &pyfiles, &flags, &priority, &timeout, &callback ) ) - return NULL; - - if ( !PyList_Check( pyfiles ) ) { - PyErr_SetString( PyExc_TypeError, "files parameter must be a list" ); - return NULL; - } - - std::vector files; - const char *file; - PyObject *pyfile; - - // Convert python list to stl vector - for ( int i = 0; i < PyList_Size( pyfiles ); ++i ) { - pyfile = PyList_GetItem( pyfiles, i ); - if ( !pyfile ) return NULL; - file = PyBytes_AsString( pyfile ); - files.push_back( std::string( file ) ); - } - - if ( callback && callback != Py_None ) { - XrdCl::ResponseHandler *handler = GetHandler( callback ); - if ( !handler ) return NULL; - async( status = self->filesystem->Prepare( files, flags, priority, - handler, timeout ) ); - } - - else { - XrdCl::Buffer *response = 0; - async( status = self->filesystem->Prepare( files, flags, priority, response, - timeout ) ); - pyresponse = ConvertType( response ); - delete response; - } - - (void) FileSystemType; // Suppress unused variable warning - - pystatus = ConvertType( &status ); - PyObject *o = ( callback && callback != Py_None ) ? - Py_BuildValue( "O", pystatus ) : - Py_BuildValue( "OO", pystatus, pyresponse ); - Py_DECREF( pystatus ); - Py_XDECREF( pyresponse ); - return o; - } - - //---------------------------------------------------------------------------- - //! Get property - //---------------------------------------------------------------------------- - PyObject* FileSystem::GetProperty( FileSystem *self, - PyObject *args, - PyObject *kwds ) - { - static const char *kwlist[] = { "name", NULL }; - char *name = 0; - std::string value; - - if ( !PyArg_ParseTupleAndKeywords( args, kwds, "s:get_property", - (char**) kwlist, &name ) ) return NULL; - - bool status = self->filesystem->GetProperty( name, value ); - return status ? Py_BuildValue( "s", value.c_str() ) : Py_None; - } - - //---------------------------------------------------------------------------- - //! Set property - //---------------------------------------------------------------------------- - PyObject* FileSystem::SetProperty( FileSystem *self, - PyObject *args, - PyObject *kwds ) - { - static const char *kwlist[] = { "name", "value", NULL }; - char *name = 0; - char *value = 0; - - if ( !PyArg_ParseTupleAndKeywords( args, kwds, "ss:set_property", - (char**) kwlist, &name, &value ) ) return NULL; - - bool status = self->filesystem->SetProperty( name, value ); - return status ? Py_True : Py_False; - } -} diff --git a/bindings/python/src/PyXRootDFileSystem.hh b/bindings/python/src/PyXRootDFileSystem.hh deleted file mode 100644 index 31ace840cf7..00000000000 --- a/bindings/python/src/PyXRootDFileSystem.hh +++ /dev/null @@ -1,193 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2012-2014 by European Organization for Nuclear Research (CERN) -// Author: Justin Salmon -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#ifndef PYXROOTD_FILESYSTEM_HH_ -#define PYXROOTD_FILESYSTEM_HH_ - -#include "PyXRootD.hh" -#include "PyXRootDURL.hh" -#include "Conversions.hh" - -#include "XrdCl/XrdClFileSystem.hh" - -namespace PyXRootD -{ - //---------------------------------------------------------------------------- - //! XrdCl::FileSystem binding class - //---------------------------------------------------------------------------- - class FileSystem - { - public: - static PyObject* Copy( FileSystem *self, PyObject *args, PyObject *kwds ); - static PyObject* Locate( FileSystem *self, PyObject *args, PyObject *kwds ); - static PyObject* DeepLocate( FileSystem *self, PyObject *args, PyObject *kwds ); - static PyObject* Mv( FileSystem *self, PyObject *args, PyObject *kwds ); - static PyObject* Query( FileSystem *self, PyObject *args, PyObject *kwds ); - static PyObject* Truncate( FileSystem *self, PyObject *args, PyObject *kwds ); - static PyObject* Rm( FileSystem *self, PyObject *args, PyObject *kwds ); - static PyObject* MkDir( FileSystem *self, PyObject *args, PyObject *kwds ); - static PyObject* RmDir( FileSystem *self, PyObject *args, PyObject *kwds ); - static PyObject* ChMod( FileSystem *self, PyObject *args, PyObject *kwds ); - static PyObject* Ping( FileSystem *self, PyObject *args, PyObject *kwds ); - static PyObject* Stat( FileSystem *self, PyObject *args, PyObject *kwds ); - static PyObject* StatVFS( FileSystem *self, PyObject *args, PyObject *kwds ); - static PyObject* Protocol( FileSystem *self, PyObject *args, PyObject *kwds ); - static PyObject* DirList( FileSystem *self, PyObject *args, PyObject *kwds ); - static PyObject* SendInfo( FileSystem *self, PyObject *args, PyObject *kwds ); - static PyObject* Prepare( FileSystem *self, PyObject *args, PyObject *kwds ); - static PyObject* GetProperty( FileSystem *self, PyObject *args, PyObject *kwds ); - static PyObject* SetProperty( FileSystem *self, PyObject *args, PyObject *kwds ); - - public: - PyObject_HEAD - URL *url; - XrdCl::FileSystem *filesystem; - }; - - PyDoc_STRVAR(filesystem_type_doc, "FileSystem object (internal)"); - - //---------------------------------------------------------------------------- - //! Visible method definitions - //---------------------------------------------------------------------------- - static PyMethodDef FileSystemMethods[] = - { - { "copy", - (PyCFunction) PyXRootD::FileSystem::Copy, METH_VARARGS | METH_KEYWORDS, NULL }, - { "locate", - (PyCFunction) PyXRootD::FileSystem::Locate, METH_VARARGS | METH_KEYWORDS, NULL }, - { "deeplocate", - (PyCFunction) PyXRootD::FileSystem::DeepLocate, METH_VARARGS | METH_KEYWORDS, NULL }, - { "mv", - (PyCFunction) PyXRootD::FileSystem::Mv, METH_VARARGS | METH_KEYWORDS, NULL }, - { "query", - (PyCFunction) PyXRootD::FileSystem::Query, METH_VARARGS | METH_KEYWORDS, NULL }, - { "truncate", - (PyCFunction) PyXRootD::FileSystem::Truncate, METH_VARARGS | METH_KEYWORDS, NULL }, - { "rm", - (PyCFunction) PyXRootD::FileSystem::Rm, METH_VARARGS | METH_KEYWORDS, NULL }, - { "mkdir", - (PyCFunction) PyXRootD::FileSystem::MkDir, METH_VARARGS | METH_KEYWORDS, NULL }, - { "rmdir", - (PyCFunction) PyXRootD::FileSystem::RmDir, METH_VARARGS | METH_KEYWORDS, NULL }, - { "chmod", - (PyCFunction) PyXRootD::FileSystem::ChMod, METH_VARARGS | METH_KEYWORDS, NULL }, - { "ping", - (PyCFunction) PyXRootD::FileSystem::Ping, METH_VARARGS | METH_KEYWORDS, NULL }, - { "stat", - (PyCFunction) PyXRootD::FileSystem::Stat, METH_VARARGS | METH_KEYWORDS, NULL }, - { "statvfs", - (PyCFunction) PyXRootD::FileSystem::StatVFS, METH_VARARGS | METH_KEYWORDS, NULL }, - { "protocol", - (PyCFunction) PyXRootD::FileSystem::Protocol, METH_VARARGS | METH_KEYWORDS, NULL }, - { "dirlist", - (PyCFunction) PyXRootD::FileSystem::DirList, METH_VARARGS | METH_KEYWORDS, NULL }, - { "sendinfo", - (PyCFunction) PyXRootD::FileSystem::SendInfo, METH_VARARGS | METH_KEYWORDS, NULL }, - { "prepare", - (PyCFunction) PyXRootD::FileSystem::Prepare, METH_VARARGS | METH_KEYWORDS, NULL }, - { "get_property", - (PyCFunction) PyXRootD::FileSystem::GetProperty, METH_VARARGS | METH_KEYWORDS, NULL }, - { "set_property", - (PyCFunction) PyXRootD::FileSystem::SetProperty, METH_VARARGS | METH_KEYWORDS, NULL }, - { NULL } /* Sentinel */ - }; - - //---------------------------------------------------------------------------- - //! __init__() equivalent - //---------------------------------------------------------------------------- - static int FileSystem_init( FileSystem *self, PyObject *args ) - { - self->url = (URL *) PyObject_CallObject( (PyObject*) &URLType, args ); - - if ( !self->url ) - return -1; - - self->filesystem = new XrdCl::FileSystem( *self->url->url ); - return 0; - } - - //---------------------------------------------------------------------------- - //! Deallocation function, called when object is deleted - //---------------------------------------------------------------------------- - static void FileSystem_dealloc( FileSystem *self ) - { - delete self->filesystem; - Py_XDECREF( self->url ); - Py_TYPE(self)->tp_free( (PyObject*) self ); - } - - //---------------------------------------------------------------------------- - //! Visible member definitions - //---------------------------------------------------------------------------- - static PyMemberDef FileSystemMembers[] = - { - { const_cast("url"), T_OBJECT_EX, offsetof(FileSystem, url), 0, - const_cast("Server URL") }, - { NULL } /* Sentinel */ - }; - - //---------------------------------------------------------------------------- - //! FileSystem binding type object - //---------------------------------------------------------------------------- - static PyTypeObject FileSystemType = - { PyVarObject_HEAD_INIT(NULL, 0) - "pyxrootd.FileSystem", /* tp_name */ - sizeof(FileSystem), /* tp_basicsize */ - 0, /* tp_itemsize */ - (destructor) FileSystem_dealloc, /* tp_dealloc */ - 0, /* tp_print */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_compare */ - 0, /* tp_repr */ - 0, /* tp_as_number */ - 0, /* tp_as_sequence */ - 0, /* tp_as_mapping */ - 0, /* tp_hash */ - 0, /* tp_call */ - 0, /* tp_str */ - 0, /* tp_getattro */ - 0, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ - filesystem_type_doc, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - FileSystemMethods, /* tp_methods */ - FileSystemMembers, /* tp_members */ - 0, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - (initproc) FileSystem_init, /* tp_init */ - }; -} - -#endif /* PYXROOTD_FILESYSTEM_HH_ */ diff --git a/bindings/python/src/PyXRootDModule.cc b/bindings/python/src/PyXRootDModule.cc deleted file mode 100644 index 52626792f8d..00000000000 --- a/bindings/python/src/PyXRootDModule.cc +++ /dev/null @@ -1,141 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2012-2013 by European Organization for Nuclear Research (CERN) -// Author: Justin Salmon -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#include "PyXRootD.hh" -#include "PyXRootDFileSystem.hh" -#include "PyXRootDFile.hh" -#include "PyXRootDCopyProcess.hh" -#include "PyXRootDURL.hh" - -namespace PyXRootD -{ - // Global module object - PyObject* ClientModule; - - PyDoc_STRVAR(client_module_doc, "XRootD Client extension module"); - - //---------------------------------------------------------------------------- - //! Visible module-level method declarations - //---------------------------------------------------------------------------- - static PyMethodDef module_methods[] = - { - { NULL } /* Sentinel */ - }; - -#if PY_MAJOR_VERSION >= 3 - //---------------------------------------------------------------------------- - //! Module properties - //---------------------------------------------------------------------------- - static struct PyModuleDef moduledef = { - PyModuleDef_HEAD_INIT, - "client", /* m_name */ - client_module_doc, /* m_doc */ - -1, /* m_size */ - module_methods, /* m_methods */ - NULL, /* m_reload */ - NULL, /* m_traverse */ - NULL, /* m_clear */ - NULL, /* m_free */ - }; -#endif - - //---------------------------------------------------------------------------- - //! Module initialization function - //---------------------------------------------------------------------------- -#ifdef IS_PY3K - PyMODINIT_FUNC PyInit_client( void ) -#else - PyMODINIT_FUNC initclient( void ) -#endif - { - // Ensure GIL state is initialized - Py_Initialize(); - if ( !PyEval_ThreadsInitialized() ) { - PyEval_InitThreads(); - } - - FileSystemType.tp_new = PyType_GenericNew; - if ( PyType_Ready( &FileSystemType ) < 0 ) { -#ifdef IS_PY3K - return NULL; -#else - return; -#endif - } - Py_INCREF( &FileSystemType ); - - FileType.tp_new = PyType_GenericNew; - if ( PyType_Ready( &FileType ) < 0 ) { -#ifdef IS_PY3K - return NULL; -#else - return; -#endif - } - Py_INCREF( &FileType ); - - URLType.tp_new = PyType_GenericNew; - if ( PyType_Ready( &URLType ) < 0 ) { -#ifdef IS_PY3K - return NULL; -#else - return; -#endif - } - Py_INCREF( &URLType ); - - CopyProcessType.tp_new = PyType_GenericNew; - if ( PyType_Ready( &CopyProcessType ) < 0 ) { -#ifdef IS_PY3K - return NULL; -#else - return; -#endif - } - Py_INCREF( &CopyProcessType ); - -#ifdef IS_PY3K - ClientModule = PyModule_Create(&moduledef); -#else - ClientModule = Py_InitModule3("client", module_methods, client_module_doc); -#endif - - if (ClientModule == NULL) { -#ifdef IS_PY3K - return NULL; -#else - return; -#endif - } - - PyModule_AddObject( ClientModule, "FileSystem", (PyObject *) &FileSystemType ); - PyModule_AddObject( ClientModule, "File", (PyObject *) &FileType ); - PyModule_AddObject( ClientModule, "URL", (PyObject *) &URLType ); - PyModule_AddObject( ClientModule, "CopyProcess", (PyObject *) &CopyProcessType ); - -#ifdef IS_PY3K - return ClientModule; -#endif - } -} diff --git a/bindings/python/src/PyXRootDURL.cc b/bindings/python/src/PyXRootDURL.cc deleted file mode 100644 index ee957258087..00000000000 --- a/bindings/python/src/PyXRootDURL.cc +++ /dev/null @@ -1,208 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2012-2013 by European Organization for Nuclear Research (CERN) -// Author: Justin Salmon -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#include "PyXRootD.hh" -#include "PyXRootDURL.hh" - -namespace PyXRootD -{ - //---------------------------------------------------------------------------- - //! Is the url valid - //---------------------------------------------------------------------------- - PyObject* URL::IsValid( URL *self ) - { - return PyBool_FromLong( self->url->IsValid() ); - } - - //---------------------------------------------------------------------------- - //! Get the host part of the URL (user:password\@host:port) - //---------------------------------------------------------------------------- - PyObject* URL::GetHostId( URL *self, void *closure ) - { - return PyUnicode_FromString( self->url->GetHostId().c_str() ); - } - - //---------------------------------------------------------------------------- - //! Get the protocol - //---------------------------------------------------------------------------- - PyObject* URL::GetProtocol( URL *self, void *closure ) - { - return PyUnicode_FromString( self->url->GetProtocol().c_str() ); - } - - //---------------------------------------------------------------------------- - //! Set protocol - //---------------------------------------------------------------------------- - int URL::SetProtocol( URL *self, PyObject *protocol, void *closure ) - { - if ( !PyBytes_Check( protocol ) ) { - PyErr_SetString( PyExc_TypeError, "protocol must be string" ); - return -1; - } - - self->url->SetProtocol( std::string ( PyBytes_AsString( protocol ) ) ); - return 0; - } - - //---------------------------------------------------------------------------- - //! Get the username - //---------------------------------------------------------------------------- - PyObject* URL::GetUserName( URL *self, void *closure ) - { - return PyUnicode_FromString( self->url->GetUserName().c_str() ); - } - - //---------------------------------------------------------------------------- - //! Set the username - //---------------------------------------------------------------------------- - int URL::SetUserName( URL *self, PyObject *username, void *closure ) - { - if ( !PyBytes_Check( username ) ) { - PyErr_SetString( PyExc_TypeError, "username must be string" ); - return -1; - } - - self->url->SetUserName( std::string( PyBytes_AsString( username ) ) ); - return 0; - } - - //---------------------------------------------------------------------------- - //! Get the password - //---------------------------------------------------------------------------- - PyObject* URL::GetPassword( URL *self, void *closure ) - { - return PyUnicode_FromString( self->url->GetPassword().c_str() ); - } - - //---------------------------------------------------------------------------- - //! Set the password - //---------------------------------------------------------------------------- - int URL::SetPassword( URL *self, PyObject *password, void *closure ) - { - if ( !PyBytes_Check( password ) ) { - PyErr_SetString( PyExc_TypeError, "password must be string" ); - return -1; - } - - self->url->SetPassword( std::string( PyBytes_AsString( password ) ) ); - return 0; - } - - //---------------------------------------------------------------------------- - //! Get the name of the target host - //---------------------------------------------------------------------------- - PyObject* URL::GetHostName( URL *self, void *closure ) - { - return PyUnicode_FromString( self->url->GetHostName().c_str() ); - } - - //---------------------------------------------------------------------------- - //! Set the host name - //---------------------------------------------------------------------------- - int URL::SetHostName( URL *self, PyObject *hostname, void *closure ) - { - if ( !PyBytes_Check( hostname ) ) { - PyErr_SetString( PyExc_TypeError, "hostname must be string" ); - return -1; - } - - self->url->SetHostName( std::string( PyBytes_AsString( hostname ) ) ); - return 0; - } - - //---------------------------------------------------------------------------- - //! Get the target port - //---------------------------------------------------------------------------- - PyObject* URL::GetPort( URL *self, void *closure ) - { -#ifdef IS_PY3K - return PyLong_FromLong( self->url->GetPort() ); -#else - return PyInt_FromLong( self->url->GetPort() ); -#endif - } - - //---------------------------------------------------------------------------- - //! Is the url valid - //---------------------------------------------------------------------------- - int URL::SetPort( URL *self, PyObject *port, void *closure ) - { -#ifdef IS_PY3K - if ( !PyLong_Check( port ) ) { -#else - if ( !PyInt_Check( port ) ) { -#endif - PyErr_SetString( PyExc_TypeError, "port must be int" ); - return -1; - } - -#ifdef IS_PY3K - self->url->SetPort( PyLong_AsLong( port ) ); -#else - self->url->SetPort( PyInt_AsLong( port ) ); -#endif - return 0; - } - - //---------------------------------------------------------------------------- - //! Get the path - //---------------------------------------------------------------------------- - PyObject* URL::GetPath( URL *self, void *closure ) - { - return PyUnicode_FromString( self->url->GetPath().c_str() ); - } - - //---------------------------------------------------------------------------- - //! Set the path - //---------------------------------------------------------------------------- - int URL::SetPath( URL *self, PyObject *path, void *closure ) - { - if ( !PyBytes_Check( path ) ) { - PyErr_SetString( PyExc_TypeError, "path must be string" ); - return -1; - } - - self->url->SetPath( std::string( PyBytes_AsString( path ) ) ); - return 0; - } - - //---------------------------------------------------------------------------- - //! Get the path with params - //---------------------------------------------------------------------------- - PyObject* URL::GetPathWithParams( URL *self, void *closure ) - { - return PyUnicode_FromString( self->url->GetPathWithParams().c_str() ); - } - - //---------------------------------------------------------------------------- - //! Clear the url - //---------------------------------------------------------------------------- - PyObject* URL::Clear( URL *self ) - { - (void) URLType; // Suppress unused variable warning - - self->url->Clear(); - Py_RETURN_NONE ; - } -} diff --git a/bindings/python/src/PyXRootDURL.hh b/bindings/python/src/PyXRootDURL.hh deleted file mode 100644 index 47a7385e994..00000000000 --- a/bindings/python/src/PyXRootDURL.hh +++ /dev/null @@ -1,176 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2012-2013 by European Organization for Nuclear Research (CERN) -// Author: Justin Salmon -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#ifndef PYXROOTD_URL_HH_ -#define PYXROOTD_URL_HH_ - -#include "PyXRootD.hh" - -#include "XrdCl/XrdClURL.hh" - -namespace PyXRootD -{ - //---------------------------------------------------------------------------- - //! XrdCl::URL binding class - //---------------------------------------------------------------------------- - class URL - { - public: - static PyObject* IsValid( URL *self ); - static PyObject* GetHostId( URL *self, void *closure ); - static PyObject* GetProtocol( URL *self, void *closure ); - static int SetProtocol( URL *self, PyObject *protocol, void *closure ); - static PyObject* GetUserName( URL *self, void *closure ); - static int SetUserName( URL *self, PyObject *username, void *closure ); - static PyObject* GetPassword( URL *self, void *closure ); - static int SetPassword( URL *self, PyObject *password, void *closure ); - static PyObject* GetHostName( URL *self, void *closure ); - static int SetHostName( URL *self, PyObject *hostname, void *closure ); - static PyObject* GetPort( URL *self, void *closure ); - static int SetPort( URL *self, PyObject *port, void *closure ); - static PyObject* GetPath( URL *self, void *closure ); - static int SetPath( URL *self, PyObject *path, void *closure ); - static PyObject* GetPathWithParams( URL *self, void *closure ); - static PyObject* Clear( URL *self ); - - public: - PyObject_HEAD - XrdCl::URL *url; - }; - - PyDoc_STRVAR(url_type_doc, "URL object (internal)"); - - //---------------------------------------------------------------------------- - //! __init__() equivalent - //---------------------------------------------------------------------------- - static int URL_init( URL *self, PyObject *args ) - { - const char *url; - - if ( !PyArg_ParseTuple( args, "s", &url ) ) - return -1; - - self->url = new XrdCl::URL( url ); - return 0; - } - - //---------------------------------------------------------------------------- - //! Deallocation function, called when object is deleted - //---------------------------------------------------------------------------- - static void URL_dealloc( URL *self ) - { - delete self->url; - Py_TYPE(self)->tp_free( (PyObject*) self ); - } - - //---------------------------------------------------------------------------- - //! __str__() equivalent - //---------------------------------------------------------------------------- - static PyObject* URL_str( URL *self ) - { - return PyUnicode_FromString( self->url->GetURL().c_str() ); - } - - //---------------------------------------------------------------------------- - //! Visible member definitions - //---------------------------------------------------------------------------- - static PyMemberDef URLMembers[] = - { - { NULL } /* Sentinel */ - }; - - static PyGetSetDef URLGetSet[] = - { - { const_cast("hostid"), - (getter) URL::GetHostId, NULL, NULL, NULL }, - { const_cast("protocol"), - (getter) URL::GetProtocol, (setter) URL::SetProtocol, NULL, NULL }, - { const_cast("username"), - (getter) URL::GetUserName, (setter) URL::SetUserName, NULL, NULL }, - { const_cast("password"), - (getter) URL::GetPassword, (setter) URL::SetPassword, NULL, NULL }, - { const_cast("hostname"), - (getter) URL::GetHostName, (setter) URL::SetHostName, NULL, NULL }, - { const_cast("port"), - (getter) URL::GetPort, (setter) URL::SetPort, NULL, NULL }, - { const_cast("path"), - (getter) URL::GetPath, (setter) URL::SetPath, NULL, NULL }, - { const_cast("path_with_params"), - (getter) URL::GetPathWithParams, NULL, NULL, NULL }, - { NULL } /* Sentinel */ - }; - - //---------------------------------------------------------------------------- - //! Visible method definitions - //---------------------------------------------------------------------------- - static PyMethodDef URLMethods[] = { - { "is_valid", (PyCFunction) URL::IsValid, METH_NOARGS, NULL }, - { "clear", (PyCFunction) URL::Clear, METH_NOARGS, NULL }, - { NULL } /* Sentinel */ - }; - - //---------------------------------------------------------------------------- - //! URL binding type object - //---------------------------------------------------------------------------- - static PyTypeObject URLType = { - PyVarObject_HEAD_INIT(NULL, 0) - "pyxrootd.URL", /* tp_name */ - sizeof(URL), /* tp_basicsize */ - 0, /* tp_itemsize */ - (destructor) URL_dealloc, /* tp_dealloc */ - 0, /* tp_print */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_compare */ - 0, /* tp_repr */ - 0, /* tp_as_number */ - 0, /* tp_as_sequence */ - 0, /* tp_as_mapping */ - 0, /* tp_hash */ - 0, /* tp_call */ - (reprfunc) URL_str, /* tp_str */ - 0, /* tp_getattro */ - 0, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ - url_type_doc, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - URLMethods, /* tp_methods */ - URLMembers, /* tp_members */ - URLGetSet, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - (initproc) URL_init, /* tp_init */ - }; -} - -#endif /* PYXROOTD_URL_HH_ */ diff --git a/bindings/python/src/Utils.cc b/bindings/python/src/Utils.cc deleted file mode 100644 index 5240ac48957..00000000000 --- a/bindings/python/src/Utils.cc +++ /dev/null @@ -1,201 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2012-2013 by European Organization for Nuclear Research (CERN) -// Author: Justin Salmon -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#include "Utils.hh" -#include "PyXRootDURL.hh" - -namespace PyXRootD -{ - //---------------------------------------------------------------------------- - // Check that the given callback is actually callable. - //---------------------------------------------------------------------------- - bool IsCallable( PyObject *callable ) - { - if ( !PyCallable_Check( callable ) ) { - PyErr_SetString( PyExc_TypeError, - "callback must be callable function, class or lambda" ); - return false; - } - // We need to keep this callback - Py_INCREF( callable ); - return true; - } - - //---------------------------------------------------------------------------- - // Initialize the Python types for the extension. - //---------------------------------------------------------------------------- - int InitTypes() - { - URLType.tp_new = PyType_GenericNew; - if ( PyType_Ready( &URLType ) < 0 ) return -1; - - Py_INCREF( &URLType ); - return 0; - } - - //---------------------------------------------------------------------------- - // Convert PyInt to unsigned long (uint64_t) - //---------------------------------------------------------------------------- - int PyIntToUlong(PyObject *py_val, unsigned long *val, const char *name) - { -#ifdef IS_PY3K - const long tmp = PyLong_AsLong(py_val); -#else - const long tmp = PyInt_AsLong(py_val); -#endif - - if (tmp == -1 && PyErr_Occurred()) - { - if (PyErr_ExceptionMatches(PyExc_OverflowError)) - PyErr_Format(PyExc_OverflowError, "%s too big for unsigned long", name); - return -1; - } - - if (tmp < 0) - { - PyErr_Format(PyExc_OverflowError, - "negative %s cannot be converted to unsigned long", name); - return -1; - } - - *val = tmp; - return 0; - } - - //---------------------------------------------------------------------------- - // Convert Python object to unsigned long (uint64_t) - //---------------------------------------------------------------------------- - int PyObjToUlong(PyObject *py_val, unsigned long *val, const char *name) - { -#ifdef IS_PY3K - if (PyLong_Check(py_val)) -#else - if (PyInt_Check(py_val)) -#endif - return PyIntToUlong(py_val, val, name); - - if (!PyLong_Check(py_val)) - { - PyErr_Format(PyExc_TypeError, "expected integer %s", name); - return -1; - } - - const unsigned long tmp = PyLong_AsUnsignedLong(py_val); - - if (PyErr_Occurred()) - { - if (PyErr_ExceptionMatches(PyExc_OverflowError)) - PyErr_Format(PyExc_OverflowError, "%s too big for unsigned long", name); - return -1; - } - - *val = tmp; - return 0; - } - - //---------------------------------------------------------------------------- - // Convert Python object to unsigned int (uint32_t) - //---------------------------------------------------------------------------- - int PyObjToUint(PyObject *py_val, unsigned int *val, const char *name) - { - unsigned long tmp; - - if (PyObjToUlong(py_val, &tmp, name)) - return -1; - - if (tmp > UINT_MAX) - { - PyErr_Format(PyExc_OverflowError, "%s too big for unsigned int (uint32_t)", - name); - return -1; - } - - *val = tmp; - return 0; - } - - //---------------------------------------------------------------------------- - // Convert Python object to unsigned short int (uint16_t) - //---------------------------------------------------------------------------- - int PyObjToUshrt(PyObject *py_val, unsigned short int *val, const char *name) - { - unsigned int tmp; - - if (PyObjToUint(py_val, &tmp, name)) - return -1; - - if (tmp > USHRT_MAX) - { - PyErr_Format(PyExc_OverflowError, "%s too big for unsigned short int " - "(uint16_t)", name); - return -1; - } - - *val = tmp; - return 0; - } - - //---------------------------------------------------------------------------- - // Convert Python object to unsigned long long (uint64_t) - //---------------------------------------------------------------------------- - int PyObjToUllong(PyObject *py_val, unsigned PY_LONG_LONG *val, - const char *name) - { -#ifdef IS_PY3K - if (PyLong_Check(py_val)) -#else - if (PyInt_Check(py_val)) -#endif - { - unsigned long tmp; - - if (!PyIntToUlong(py_val, &tmp, name)) - { - *val = tmp; - return 0; - } - - return -1; - } - - if (!PyLong_Check(py_val)) - { - PyErr_Format(PyExc_TypeError, "integer argument expected for %s", name); - return -1; - } - - const unsigned PY_LONG_LONG tmp = PyLong_AsUnsignedLongLong(py_val); - - if ((tmp == (unsigned long long) -1) && PyErr_Occurred()) - { - if (PyErr_ExceptionMatches(PyExc_OverflowError)) - PyErr_Format(PyExc_OverflowError, - "%s too big for unsigned long long", name); - return -1; - } - - *val = tmp; - return 0; - } -} diff --git a/bindings/python/src/Utils.hh b/bindings/python/src/Utils.hh deleted file mode 100644 index c8df4e05580..00000000000 --- a/bindings/python/src/Utils.hh +++ /dev/null @@ -1,100 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2012-2013 by European Organization for Nuclear Research (CERN) -// Author: Justin Salmon -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#ifndef UTILS_HH_ -#define UTILS_HH_ - -#include "PyXRootD.hh" - -#include "XrdCl/XrdClXRootDResponses.hh" - -namespace PyXRootD -{ - //---------------------------------------------------------------------------- - //! Check that the given callback is actually callable. - //---------------------------------------------------------------------------- - bool IsCallable( PyObject *callable ); - - //---------------------------------------------------------------------------- - //! Initialize the Python types for the extension. - //---------------------------------------------------------------------------- - int InitTypes(); - - //---------------------------------------------------------------------------- - //! Convert PyInt to unsigned long (uint64_t) - //! - //! @param py_val python object to be converted - //! @param val converted value - //! @param name name of the object - //! - //! return 0 if successful, otherwise failed - //---------------------------------------------------------------------------- - int PyIntToUlong(PyObject *py_val, unsigned long *val, const char *name); - - //---------------------------------------------------------------------------- - //! Convert Python object to unsigned long (uint64_t) - //! - //! @param py_val python object to be converted - //! @param val converted value - //! @param name name of the object - //! - //! return 0 if successful, otherwise failed - //---------------------------------------------------------------------------- - int PyObjToUlong(PyObject *py_val, unsigned long *val, const char *name); - - //---------------------------------------------------------------------------- - //! Convert Python object to unsigned int (uint32_t) - //! - //! @param py_val python object to be converted - //! @param val converted value - //! @param name name of the object - //! - //! return 0 if successful, otherwise failed - //---------------------------------------------------------------------------- - int PyObjToUint(PyObject *py_val, unsigned int *val, const char *name); - - //---------------------------------------------------------------------------- - //! Convert Python object to unsigned short int (uint16_t) - //! - //! @param py_val python object to be converted - //! @param val converted value - //! @param name name of the object - //! - //! return 0 if successful, otherwise failed - //---------------------------------------------------------------------------- - int PyObjToUshrt(PyObject *py_val, unsigned short int *val, const char *name); - - //---------------------------------------------------------------------------- - //! Convert Python object to unsigned long long (uint64_t) - //! - //! @param py_val python object to be converted - //! @param val converted value - //! @param name name of the object - //! - //! return 0 if successful, otherwise failed - //---------------------------------------------------------------------------- - int PyObjToUllong(PyObject *py_val, unsigned PY_LONG_LONG *val, const char *name); -} - -#endif /* UTILS_HH_ */ diff --git a/bindings/python/src/__init__.py b/bindings/python/src/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/bindings/python/tests/env.py b/bindings/python/tests/env.py deleted file mode 100644 index a13978d82bc..00000000000 --- a/bindings/python/tests/env.py +++ /dev/null @@ -1,6 +0,0 @@ -SERVER_URL = 'root://localhost/' -smallfile = SERVER_URL + '/tmp/spam' -smallcopy = SERVER_URL + '/tmp/eggs' -smallbuffer = 'gre\0en\neggs\nand\nham\n' -bigfile = SERVER_URL + '/tmp/bigfile' -bigcopy = SERVER_URL + '/tmp/bigcopy' \ No newline at end of file diff --git a/bindings/python/tests/test_copy.py b/bindings/python/tests/test_copy.py deleted file mode 100644 index 1faffe6b296..00000000000 --- a/bindings/python/tests/test_copy.py +++ /dev/null @@ -1,80 +0,0 @@ -from XRootD import client -from XRootD.client.flags import OpenFlags -from env import * - -def test_copy_smallfile(): - - f = client.File() - s, r = f.open(smallfile, OpenFlags.DELETE ) - assert s.ok - f.write(smallbuffer) - size1 = f.stat(force=True)[1].size - f.close() - - c = client.CopyProcess() - c.add_job( source=smallfile, target=smallcopy, force=True ) - s = c.prepare() - assert s.ok - s, __ = c.run() - assert s.ok - - f = client.File() - s, r = f.open(smallcopy, OpenFlags.READ) - size2 = f.stat()[1].size - - assert size1 == size2 - f.close() - -def test_copy_bigfile(): - f = client.File() - s, r = f.open(bigfile) - assert s.ok - size1 = f.stat(force=True)[1].size - f.close() - - c = client.CopyProcess() - c.add_job( source=bigfile, target=bigcopy, force=True ) - s = c.prepare() - assert s.ok - s, __ = c.run() - assert s.ok - - f = client.File() - s, r = f.open(bigcopy, OpenFlags.READ) - size2 = f.stat()[1].size - - assert size1 == size2 - f.close() - -def test_copy_nojobs(): - c = client.CopyProcess() - s = c.prepare() - assert s.ok - s, __ = c.run() - assert s.ok - -def test_copy_noprep(): - c = client.CopyProcess() - c.add_job( source=bigfile, target=bigcopy, force=True ) - s, __ = c.run() - assert s.ok - -class TestProgressHandler(object): - def begin(self, id, total, source, target): - print '+++ begin(): %d, total: %d' % (id, total) - print '+++ source: %s' % source - print '+++ target: %s' % target - - def end(self, jobId, status): - print '+++ end(): jobId: %s, status: %s' % (jobId, status) - - def update(self, jobId, processed, total): - print '+++ update(): jobid: %s, processed: %d, total: %d' % (jobId, processed, total) - -def test_copy_progress_handler(): - c = client.CopyProcess() - c.add_job( source=bigfile, target=bigcopy, force=True ) - c.prepare() - - h = TestProgressHandler() - c.run(handler=h) diff --git a/bindings/python/tests/test_file.py b/bindings/python/tests/test_file.py deleted file mode 100644 index b03291c0988..00000000000 --- a/bindings/python/tests/test_file.py +++ /dev/null @@ -1,452 +0,0 @@ -from XRootD import client -from XRootD.client.utils import AsyncResponseHandler -from XRootD.client.flags import OpenFlags, AccessMode -from env import * - -import pytest -import sys -import os - -# Global open mode -open_mode = (AccessMode.UR | AccessMode.UW | - AccessMode.GR | AccessMode.GW | - AccessMode.OR | AccessMode.OW) - -def test_write_sync(): - f = client.File() - pytest.raises(ValueError, "f.write(smallbuffer)") - status, __ = f.open(smallfile, OpenFlags.DELETE, open_mode ) - assert status.ok - - # Write - status, __ = f.write(smallbuffer) - assert status.ok - - # Read - status, response = f.read() - assert status.ok - assert len(response) == len(smallbuffer) - - buffer = 'eggs and ham\n' - status, __ = f.write(buffer, offset=13, size=len(buffer) - 2) - assert status.ok - status, response = f.read() - assert status.ok - assert len(response) == len(buffer * 2) - 2 - f.close() - -def test_write_async(): - f = client.File() - status, __ = f.open(smallfile, OpenFlags.DELETE, open_mode) - assert status.ok - - # Write async - handler = AsyncResponseHandler() - status = f.write(smallbuffer, callback=handler) - status, __, __ = handler.wait() - assert status.ok - - # Read sync - status, response = f.read() - assert status.ok - assert len(response) == len(smallbuffer) - f.close() - -def test_open_close_sync(): - f = client.File() - pytest.raises(ValueError, "f.stat()") - status, __ = f.open(smallfile, OpenFlags.READ) - assert status.ok - assert f.is_open() - - # Close - status, __ = f.close() - assert status.ok - assert f.is_open() == False - pytest.raises(ValueError, "f.stat()") - f.close() - f.close() - -def test_open_close_async(): - f = client.File() - handler = AsyncResponseHandler() - status = f.open(smallfile, OpenFlags.READ, callback=handler) - assert status.ok - status, __, __ = handler.wait() - assert status.ok - assert f.is_open() - - # Close async - handler = AsyncResponseHandler() - status = f.close(callback=handler) - assert status.ok - status, __, __ = handler.wait() - assert status.ok - assert f.is_open() == False - -def test_io_limits(): - f = client.File() - pytest.raises(ValueError, 'f.read()') - status, __ = f.open(smallfile, OpenFlags.UPDATE) - assert status.ok - status, __ = f.stat() - assert status.ok - - # Test read limits - pytest.raises(TypeError, 'f.read(0, [1, 2])') - pytest.raises(TypeError, 'f.read([1, 2], 0)') - pytest.raises(TypeError, 'f.read(0, 10, [0, 1, 2])') - pytest.raises(OverflowError, 'f.read(0, -10)') - pytest.raises(OverflowError, 'f.read(-1, 1)') - pytest.raises(OverflowError, 'f.read(0, 1, -1)') - pytest.raises(OverflowError, 'f.read(0, 10**11)') - pytest.raises(OverflowError, 'f.read(0, 10, 10**6)') - - # Test readline limits - pytest.raises(TypeError, 'f.readline([0, 1], 1)') - pytest.raises(TypeError, 'f.readline(0, [0, 1])') - pytest.raises(TypeError, 'f.readline(0, 10, [0, 1])') - pytest.raises(OverflowError, 'f.readline(-1, 1)') - pytest.raises(OverflowError, 'f.readline(0, -1)') - pytest.raises(OverflowError, 'f.readline(0, 1, -1)') - pytest.raises(OverflowError, 'f.readline(0, 10**11)') - pytest.raises(OverflowError, 'f.readline(0, 10, 10**11)') - - # Test write limits - data = "data that will never get written" - pytest.raises(TypeError, 'f.write(data, 0, [1, 2])') - pytest.raises(TypeError, 'f.write(data, [1, 2], 0)') - pytest.raises(TypeError, 'f.write(data, 0, 10, [0, 1, 2])') - pytest.raises(OverflowError, 'f.write(data, 0, -10)') - pytest.raises(OverflowError, 'f.write(data, -1, 1)') - pytest.raises(OverflowError, 'f.write(data, 0, 1, -1)') - pytest.raises(OverflowError, 'f.write(data, 0, 10**11)') - pytest.raises(OverflowError, 'f.write(data, 0, 10, 10**6)') - - # Test vector_read limits - pytest.raises(TypeError, 'f.vector_read(chunks=100)') - pytest.raises(TypeError, 'f.vector_read(chunks=[1,2,3])') - pytest.raises(TypeError, 'f.vector_read(chunks=[("lol", "cakes")])') - pytest.raises(TypeError, 'f.vector_read(chunks=[(1), (2)])') - pytest.raises(TypeError, 'f.vector_read(chunks=[(1, 2), (3)])') - pytest.raises(OverflowError, 'f.vector_read(chunks=[(-1, -100), (-100, -100)])') - pytest.raises(OverflowError, 'f.vector_read(chunks=[(0, 10**10*10)])') - - # Test truncate limits - pytest.raises(TypeError, 'f.truncate(0, [1, 2])') - pytest.raises(TypeError, 'f.truncate([1, 2], 0)') - pytest.raises(OverflowError, 'f.truncate(-1)') - pytest.raises(OverflowError, 'f.truncate(100, -10)') - pytest.raises(OverflowError, 'f.truncate(0, 10**6)') - status, __ = f.close() - assert status.ok - -def test_write_big_async(): - f = client.File() - pytest.raises(ValueError, 'f.read()') - status, __ = f.open(bigfile, OpenFlags.DELETE, open_mode) - assert status.ok - - rand_data = os.urandom(64 * 1024) - max_size = 512 * 1024 # 512 K - offset = 0 - lst_handlers = [] - - while offset <= max_size: - status, __ = f.write(smallbuffer) - assert status.ok - handler = AsyncResponseHandler() - lst_handlers.append(handler) - status = f.write(rand_data, offset, callback=handler) - assert status.ok - offset = offset + len(smallbuffer) + len(rand_data) - - # Wait for async write responses - for handler in lst_handlers: - status, __, __ = handler.wait() - assert status.ok - - f.close() - -def test_read_sync(): - f = client.File() - pytest.raises(ValueError, 'f.read()') - status, response = f.open(bigfile, OpenFlags.READ) - assert status.ok - status, response = f.stat() - size = response.size - - status, response = f.read() - assert status.ok - assert len(response) == size - f.close() - -def test_read_async(): - f = client.File() - status, response = f.open(bigfile, OpenFlags.READ) - assert status.ok - status, response = f.stat() - size = response.size - - handler = AsyncResponseHandler() - status = f.read(callback=handler) - assert status.ok - status, response, hostlist = handler.wait() - assert status.ok - assert len(response) == size - f.close() - -def test_iter_small(): - f = client.File() - status, __ = f.open(smallfile, OpenFlags.DELETE) - assert status.ok - status, __ = f.write(smallbuffer) - assert status.ok - - size = f.stat(force=True)[1].size - pylines = open('/tmp/spam').readlines() - total = 0 - - for i, line in enumerate(f): - total += len(line) - if pylines[i].endswith('\n'): - assert line.endswith('\n') - - assert total == size - f.close() - -def test_iter_big(): - f = client.File() - status, __ = f.open(bigfile, OpenFlags.READ) - assert status.ok - - size = f.stat()[1].size - pylines = open('/tmp/bigfile').readlines() - total = 0 - - for i, line in enumerate(f): - total += len(line) - if pylines[i].endswith('\n'): - assert line.endswith('\n') - - assert total == size - f.close() - -def test_readline(): - f = client.File() - f.open(smallfile, OpenFlags.DELETE, open_mode) - f.write(smallbuffer) - - response = f.readline(offset=0, size=100) - assert response == 'gre\0en\n' - response = f.readline() - assert response == 'eggs\n' - response = f.readline() - assert response == 'and\n' - response = f.readline() - assert response == 'ham\n' - f.close() - - f = client.File() - f.open(smallfile, OpenFlags.DELETE, open_mode) - f.write(smallbuffer[:-1]) - f.readline() - f.readline() - f.readline() - response = f.readline() - assert response == 'ham' - f.close() - -def test_readlines_small(): - f = client.File() - f.open(smallfile, OpenFlags.DELETE, open_mode) - f.write(smallbuffer) - f.close() - pylines = open('/tmp/spam').readlines() - - for i in range(1, 100): - f = client.File() - f.open(smallfile) - response = f.readlines(offset=0, chunksize=i) - assert len(response) == 4 - for j, line in enumerate(response): - if pylines[j].endswith('\n'): - assert line.endswith('\n') - f.close() - -def test_readlines_big(): - f = client.File() - f.open(bigfile, OpenFlags.READ) - size = f.stat()[1].size - - lines = f.readlines() - pylines = open('/tmp/bigfile').readlines() - assert len(lines) == len(pylines) - - nlines = len(pylines) - - total = 0 - for i, l in enumerate(lines): - total += len(l) - if l != pylines[i]: - print '!!!!!', total, i - print '+++++ py: %r' % pylines[i] - print '+++++ me: %r' % l - break - if pylines[i].endswith('\n'): - assert l.endswith('\n') - - assert total == size - f.close() - -def test_readchunks_small(): - f = client.File() - f.open(smallfile, OpenFlags.READ) - size = f.stat()[1].size - - total = 0 - chunks = ['gre', '\0en', '\neg', 'gs\n', 'and', '\nha', 'm\n'] - for i, chunk in enumerate(f.readchunks(chunksize=3)): - assert chunk == chunks[i] - total += len(chunk) - - assert total == size - f.close() - -def test_readchunks_big(): - f = client.File() - f.open(bigfile, OpenFlags.READ) - size = f.stat()[1].size - - total = 0 - for chunk in f.readchunks(chunksize=1024 * 1024 * 2): - total += len(chunk) - - assert total == size - f.close() - -def test_vector_read_sync(): - v = [(0, 100), (101, 200), (201, 200)] - vlen = sum([vec[1] for vec in v]) - - f = client.File() - status, __ = f.open(bigfile, OpenFlags.READ) - assert status.ok - status, stat_info = f.stat() - assert status.ok - status, response = f.vector_read(chunks=v) - - # If big enough file everything shoud be ok - if (stat_info.size > max([off + sz for (off, sz) in v])): - assert status.ok - assert response.size == vlen - else: - # If file not big enough this should fail - status, __ = f.vector_read(chunks=v) - assert not status.ok - - f.close() - -def test_vector_read_async(): - v = [(0, 100), (101, 200), (201, 200)] - vlen = sum([vec[1] for vec in v]) - f = client.File() - status, __ = f.open(bigfile, OpenFlags.READ) - assert status.ok - status, stat_info = f.stat() - assert status.ok - handler = AsyncResponseHandler() - status = f.vector_read(chunks=v, callback=handler) - assert status.ok - - # If big enough file everything shoud be ok - if (stat_info.size > max([off + sz for (off, sz) in v])): - status, response, hostlist = handler.wait() - assert status.ok - assert response.size == vlen - else: - status, __, __ = handler.wait() - assert not status.ok - - f.close() - -def test_stat_sync(): - f = client.File() - pytest.raises(ValueError, 'f.stat()') - status, __ = f.open(bigfile) - assert status.ok - status, __ = f.stat() - assert status.ok - f.close() - -def test_stat_async(): - f = client.File() - status, response = f.open(bigfile) - assert status.ok - - handler = AsyncResponseHandler() - status = f.stat(callback=handler) - assert status.ok - status, __, __ = handler.wait() - assert status.ok - f.close() - -def test_sync_sync(): - f = client.File() - pytest.raises(ValueError, 'f.sync()') - status, __ = f.open(bigfile) - assert status.ok - status, __ = f.sync() - assert status.ok - f.close() - -def test_sync_async(): - f = client.File() - status, response = f.open(bigfile) - assert status.ok - - handler = AsyncResponseHandler() - status = f.sync(callback=handler) - status, __, __ = handler.wait() - assert status.ok - f.close() - -def test_truncate_sync(): - f = client.File() - pytest.raises(ValueError, 'f.truncate(10000)') - status, __ = f.open(smallfile, OpenFlags.DELETE) - assert status.ok - - status, __ = f.truncate(size=10000) - assert status.ok - f.close() - -def test_truncate_async(): - f = client.File() - status, __ = f.open(smallfile, OpenFlags.DELETE) - assert status.ok - - handler = AsyncResponseHandler() - status = f.truncate(size=10000, callback=handler) - assert status.ok - status, __, __ = handler.wait() - assert status.ok - f.close() - -def test_misc(): - f = client.File() - assert not f.is_open() - - # Open - status, response = f.open(smallfile, OpenFlags.READ) - assert status.ok - assert f.is_open() - - # Set/get file properties - f.set_property("ReadRecovery", "true") - f.set_property("WriteRecovery", "true") - assert f.get_property("DataServer") - - # Testing context manager - f.close() - assert not f.is_open() diff --git a/bindings/python/tests/test_filesystem.py b/bindings/python/tests/test_filesystem.py deleted file mode 100644 index 029c873658f..00000000000 --- a/bindings/python/tests/test_filesystem.py +++ /dev/null @@ -1,201 +0,0 @@ -from XRootD import client -from XRootD.client.utils import AsyncResponseHandler -from XRootD.client.flags import OpenFlags, QueryCode, MkDirFlags, AccessMode, \ - DirListFlags, PrepareFlags -from env import * -import pytest -import sys -import os -import inspect - -def test_filesystem(): - c = client.FileSystem(SERVER_URL) - - funcspecs = [(c.locate, ('/tmp', OpenFlags.REFRESH), True), - (c.deeplocate, ('/tmp', OpenFlags.REFRESH), True), - (c.query, (QueryCode.SPACE, '/tmp'), True), - (c.truncate, ('/tmp/spam', 1000), False), - (c.mv, ('/tmp/spam', '/tmp/ham'), False), - (c.chmod, ('/tmp/ham', AccessMode.UR | AccessMode.UW), False), - (c.rm, ('/tmp/ham',), False), - (c.mkdir, ('/tmp/somedir', MkDirFlags.MAKEPATH), False), - (c.rmdir, ('/tmp/somedir',), False), - (c.ping, (), False), - (c.stat, ('/tmp',), True), - (c.statvfs, ('/tmp',), True), - (c.protocol, (), True), - (c.dirlist, ('/tmp', DirListFlags.STAT), True), - (c.sendinfo, ('important info',), False), - (c.prepare, (['/tmp/foo'], PrepareFlags.STAGE), True), - ] - - for func, args, hasReturnObject in funcspecs: - sync (func, args, hasReturnObject) - - # Create new temp file - f = client.File() - status, response = f.open(smallfile, OpenFlags.NEW) - - for func, args, hasReturnObject in funcspecs: - async(func, args, hasReturnObject) - -def sync(func, args, hasReturnObject): - status, response = func(*args) - print status - assert status.ok - if hasReturnObject: - print response - assert response - -def async(func, args, hasReturnObject): - handler = AsyncResponseHandler() - status = func(callback=handler, *args) - print status - assert status.ok - status, response, hostlist = handler.wait() - - assert status.ok - if response: - assert response - - for host in hostlist: - assert host.url - print host.url - - if hasReturnObject: - assert response - -def test_copy_sync(): - c = client.FileSystem(SERVER_URL) - f = client.File() - status, response = f.open(smallfile, OpenFlags.DELETE) - assert status.ok - - status, response = c.copy(smallfile, '/tmp/eggs', force=True) - assert status.ok - - status, response = c.copy('/tmp/nonexistent', '/tmp/eggs') - assert not status.ok - - try: - os.remove('/tmp/eggs') - except OSError, __: - pass - -def test_locate_sync(): - c = client.FileSystem(SERVER_URL) - status, response = c.locate('/tmp', OpenFlags.REFRESH) - assert status.ok - - for item in response: - assert item - -def test_locate_async(): - c = client.FileSystem(SERVER_URL) - handler = AsyncResponseHandler() - response = c.locate('/tmp', OpenFlags.REFRESH, callback=handler) - - status, response, hostlist = handler.wait() - assert status.ok - - for item in response: - assert item - -def test_deeplocate_sync(): - c = client.FileSystem(SERVER_URL) - status, response = c.deeplocate('/tmp', OpenFlags.REFRESH) - assert status.ok - - for item in response: - assert item - -def test_deeplocate_async(): - c = client.FileSystem(SERVER_URL) - handler = AsyncResponseHandler() - response = c.deeplocate('/tmp', OpenFlags.REFRESH, callback=handler) - - status, response, hostlist = handler.wait() - assert status.ok - - for item in response: - assert item - -def test_dirlist_sync(): - c = client.FileSystem(SERVER_URL) - status, response = c.dirlist('/tmp', DirListFlags.STAT) - assert status.ok - - for item in response: - assert item.name - print item.statinfo - assert item.statinfo - assert item.hostaddr - - status, response = c.dirlist('invalid', DirListFlags.STAT) - assert not status.ok - -def test_dirlist_async(): - c = client.FileSystem(SERVER_URL) - handler = AsyncResponseHandler() - status = c.dirlist('/tmp', DirListFlags.STAT, callback=handler) - assert status.ok - status, response, hostlist = handler.wait() - assert status.ok - - for h in hostlist: - print h.url - - for item in response: - assert item.name - print item.statinfo - assert item.statinfo - assert item.hostaddr - - assert hostlist - -def test_query_sync(): - c = client.FileSystem(SERVER_URL) - status, response = c.query(QueryCode.STATS, 'a') - assert status.ok - assert response - print response - -def test_query_async(): - c = client.FileSystem(SERVER_URL) - handler = AsyncResponseHandler() - status = c.query(QueryCode.STATS, 'a', callback=handler) - assert status.ok - - status, response, hostlist = handler.wait() - assert status.ok - assert response - print response - -def test_mkdir_flags(): - c = client.FileSystem(SERVER_URL) - status, response = c.mkdir('/tmp/dir1/dir2', MkDirFlags.MAKEPATH) - assert status.ok - c.rm('/tmp/dir1/dir2') - c.rm('/tmp/dir1') - - -def test_args(): - c = client.FileSystem(url=SERVER_URL) - assert c - - pytest.raises(TypeError, "c = client.FileSystem(foo='root://localhost')") - pytest.raises(TypeError, "c = client.FileSystem(path='root://localhost', foo='bar')") - -def test_creation(): - c = client.FileSystem(SERVER_URL) - assert c.url is not None - -def test_deletion(): - c = client.FileSystem(SERVER_URL) - del c - - if sys.hexversion > 0x03000000: - pytest.raises(UnboundLocalError, 'assert c') - else: - pytest.raises(NameError, 'assert c') - diff --git a/bindings/python/tests/test_threads.py b/bindings/python/tests/test_threads.py deleted file mode 100644 index 0783d2ca999..00000000000 --- a/bindings/python/tests/test_threads.py +++ /dev/null @@ -1,36 +0,0 @@ -from XRootD import client -from XRootD.client.flags import OpenFlags -from threading import Thread -from env import * - -class TestThread(Thread): - def __init__(self, file, id): - Thread.__init__(self) - self.file = file - self.id = id - def run(self): - self.file.open(smallfile, OpenFlags.DELETE) - assert self.file.is_open() - - s, _ = self.file.write(smallbuffer) - assert s.ok - - print '+++ thread %d says: %s' % (self.id, self.file.read()) - - for line in self.file: - print '+++ thread %d says: %s' % (self.id, line) - - self.file.close() - -def test_threads(): - f = client.File() -# f.open(smallfile, OpenFlags.DELETE) -# assert f.is_open() -# f.write(smallbuffer) - - for i in xrange(3): - tt = TestThread(f, i) - tt.start() - tt.join() - -# f.close() diff --git a/bindings/python/tests/test_url.py b/bindings/python/tests/test_url.py deleted file mode 100644 index 7bcf5df5a4e..00000000000 --- a/bindings/python/tests/test_url.py +++ /dev/null @@ -1,53 +0,0 @@ -from XRootD import client -import pytest, sys -from env import * - -def test_creation(): - u = client.FileSystem(SERVER_URL).url - assert u is not None - -def test_deletion(): - u = client.FileSystem(SERVER_URL).url - del u - - if sys.hexversion > 0x03000000: - pytest.raises(UnboundLocalError, 'assert u') - else: - pytest.raises(NameError, 'assert u') - -def test_valid(): - u = client.FileSystem(SERVER_URL).url - assert u.is_valid() - -def test_invalid(): - u = client.FileSystem('root://').url - assert u.is_valid() == False - -def test_getters(): - u = client.FileSystem("root://user1:passwd1@host1:123//path?param1=val1¶m2=val2").url - assert u.is_valid() - assert u.hostid == 'user1:passwd1@host1:123' - assert u.protocol == 'root' - assert u.username == 'user1' - assert u.password == 'passwd1' - assert u.hostname == 'host1' - assert u.port == 123 - assert u.path == '/path' - assert u.path_with_params == '/path?param1=val1¶m2=val2' - -def test_setters(): - u = client.FileSystem(SERVER_URL).url - u.protocol = 'root' - assert u.protocol == 'root' - u.username = 'user1' - assert u.username == 'user1' - u.password = 'passwd1' - assert u.password == 'passwd1' - u.hostname = 'host1' - assert u.hostname == 'host1' - u.port = 123 - assert u.port == 123 - u.path = '/path' - assert u.path == '/path' - u.clear() - assert str(u) == '' diff --git a/cmake/FindKerberos5.cmake b/cmake/FindKerberos5.cmake deleted file mode 100644 index 345c3729d3e..00000000000 --- a/cmake/FindKerberos5.cmake +++ /dev/null @@ -1,40 +0,0 @@ -include( FindPackageHandleStandardArgs ) - -if( KERBEROS5_INCLUDE_DIR AND KERBEROS5_LIBRARIES ) - set( KERBEROS5_FOUND TRUE ) -else() - find_path( - KERBEROS5_INCLUDE_DIR - NAMES krb5.h - HINTS - ${KERBEROS5_ROOT_DIR} - PATH_SUFFIXES - include ) - - find_library( - KERBEROS5_LIBRARY - NAMES krb5 - HINTS - ${KERBEROS5_ROOT_DIR} - PATH_SUFFIXES - ${LIBRARY_PATH_PREFIX} - ${LIB_SEARCH_OPTIONS}) - - find_library( - COM_ERR_LIBRARY - NAMES com_err - HINTS - ${KERBEROS5_ROOT_DIR} - PATH_SUFFIXES - ${LIBRARY_PATH_PREFIX} - ${LIB_SEARCH_OPTIONS}) - - set( KERBEROS5_LIBRARIES ${KERBEROS5_LIBRARY} ${COM_ERR_LIBRARY} ) - - find_package_handle_standard_args( - KERBEROS5 - DEFAULT_MSG - KERBEROS5_LIBRARIES KERBEROS5_INCLUDE_DIR ) - - mark_as_advanced( KERBEROS5_INCLUDE_DIR KERBEROS5_LIBRARIES ) -endif() diff --git a/cmake/FindOpenSSL.cmake b/cmake/FindOpenSSL.cmake deleted file mode 100644 index 5d02bbe0b35..00000000000 --- a/cmake/FindOpenSSL.cmake +++ /dev/null @@ -1,83 +0,0 @@ -include( FindPackageHandleStandardArgs ) - -if( OPENSSL_INCLUDE_DIR AND OPENSSL_LIBRARIES ) - set( OPENSSL_FOUND TRUE ) -else() - find_path( - OPENSSL_INCLUDE_DIR - NAMES openssl/ssl.h - HINTS - ${OPENSSL_ROOT_DIR} - PATH_SUFFIXES - include ) - - find_library( - OPENSSL_SSL_LIBRARY - NAMES ssl - HINTS - ${OPENSSL_ROOT_DIR} - PATH_SUFFIXES - ${LIBRARY_PATH_PREFIX} - ${LIB_SEARCH_OPTIONS}) - - find_library( - OPENSSL_CRYPTO_LIBRARY - NAMES crypto - HINTS - ${OPENSSL_ROOT_DIR} - PATH_SUFFIXES - ${LIBRARY_PATH_PREFIX} - ${LIB_SEARCH_OPTIONS}) - - set( OPENSSL_LIBRARIES ${OPENSSL_SSL_LIBRARY} ${OPENSSL_CRYPTO_LIBRARY} ) - - find_package_handle_standard_args( - OpenSSL - DEFAULT_MSG - OPENSSL_LIBRARIES OPENSSL_INCLUDE_DIR ) - - mark_as_advanced( OPENSSL_INCLUDE_DIR OPENSSL_LIBRARIES ) -endif() - - -#------------------------------------------------------------------------------- -# Check for the TLS support in the OpenSSL version that is available -#------------------------------------------------------------------------------- - -set ( CMAKE_REQUIRED_LIBRARIES ${OPENSSL_LIBRARIES} ) - -check_function_exists(TLS_method HAVE_TLS_FUNC) -check_symbol_exists( - TLS_method - ${OPENSSL_INCLUDE_DIR}/openssl/ssl.h - HAVE_TLS_SYMB) -if( HAVE_TLS_FUNC AND HAVE_TLS_SYMB ) - add_definitions( -DHAVE_TLS ) -endif() - -check_function_exists(TLSv1_2_method HAVE_TLS12_FUNC) -check_symbol_exists( - TLSv1_2_method - ${OPENSSL_INCLUDE_DIR}/openssl/ssl.h - HAVE_TLS12_SYMB) -if( HAVE_TLS12_FUNC AND HAVE_TLS12_SYMB ) - add_definitions( -DHAVE_TLS12 ) -endif() - -check_function_exists(TLSv1_1_method HAVE_TLS11_FUNC) -check_symbol_exists( - TLSv1_1_method - ${OPENSSL_INCLUDE_DIR}/openssl/ssl.h - HAVE_TLS11_SYMB) -if( HAVE_TLS11_FUNC AND HAVE_TLS11_SYMB ) - add_definitions( -DHAVE_TLS11 ) -endif() - -check_function_exists(TLSv1_method HAVE_TLS1_FUNC) -check_symbol_exists( - TLSv1_method - ${OPENSSL_INCLUDE_DIR}/openssl/ssl.h - HAVE_TLS1_SYMB) -if( HAVE_TLS1_FUNC AND HAVE_TLS1_SYMB ) - add_definitions( -DHAVE_TLS1 ) -endif() diff --git a/cmake/FindReadline.cmake b/cmake/FindReadline.cmake deleted file mode 100644 index 61885fae65b..00000000000 --- a/cmake/FindReadline.cmake +++ /dev/null @@ -1,64 +0,0 @@ -include( FindPackageHandleStandardArgs ) -include( CheckCXXSourceCompiles ) - -if( READLINE_INCLUDE_DIR AND READLINE_LIBRARY ) - set( READLINE_FOUND TRUE ) -else( READLINE_INCLUDE_DIR AND READLINE_LIBRARY ) - find_path( READLINE_INCLUDE_DIR readline/readline.h /usr/include/readline ) - - find_library( - READLINE_LIB - NAMES readline - HINTS - ${READLINE_ROOT_DIR} - PATH_SUFFIXES - ${LIBRARY_PATH_PREFIX} - ${LIB_SEARCH_OPTIONS}) - - #----------------------------------------------------------------------------- - # Check if we need ncurses - a hack required for SLC5 - #----------------------------------------------------------------------------- - if( READLINE_LIB ) - - set( CMAKE_REQUIRED_LIBRARIES ${READLINE_LIB} ) - set( CMAKE_REQUIRED_INCLUDES ${READLINE_INCLUDE_DIR} ) - check_cxx_source_compiles( - " - #include - #include - int main() - { - char shell_prompt[100]; - readline(shell_prompt); - return 0; - } - " - READLINE_OK ) - - if( READLINE_OK ) - set( READLINE_LIBRARY ${READLINE_LIB} ) - else() - find_library( - NCURSES_LIBRARY - NAMES ncurses - HINTS - ${READLINE_ROOT_DIR} - PATH_SUFFIXES - ${LIBRARY_PATH_PREFIX} - ${LIB_SEARCH_OPTIONS}) - - if( NCURSES_LIBRARY ) - set( READLINE_LIBRARY "${READLINE_LIB};${NCURSES_LIBRARY}" ) - endif() - - endif() - - endif() - - find_package_handle_standard_args( - READLINE - DEFAULT_MSG - READLINE_LIBRARY READLINE_INCLUDE_DIR ) - - mark_as_advanced( READLINE_INCLUDE_DIR READLINE_LIBRARY ) -endif( READLINE_INCLUDE_DIR AND READLINE_LIBRARY) diff --git a/cmake/FindSystemd.cmake b/cmake/FindSystemd.cmake deleted file mode 100644 index 3068ec25084..00000000000 --- a/cmake/FindSystemd.cmake +++ /dev/null @@ -1,30 +0,0 @@ -# Try to find systemd -# Once done, this will define -# -# SYSTEMD_FOUND - system has systemd -# SYSTEMD_INCLUDE_DIRS - the systemd include directories -# SYSTEMD_LIBRARIES - systemd libraries directories - -find_path( SYSTEMD_INCLUDE_DIR systemd/sd-daemon.h - HINTS - ${SYSTEMD_DIR} - $ENV{SYSTEMD_DIR} - /usr - /opt - PATH_SUFFIXES include -) - -find_library( SYSTEMD_LIBRARY systemd - HINTS - ${SYSTEMD_DIR} - $ENV{SYSTEMD_DIR} - /usr - /opt - PATH_SUFFIXES lib -) - -set(SYSTEMD_INCLUDE_DIRS ${SYSTEMD_INCLUDE_DIR}) -set(SYSTEMD_LIBRARIES ${SYSTEMD_LIBRARY}) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(systemd DEFAULT_MSG SYSTEMD_INCLUDE_DIRS SYSTEMD_LIBRARIES) diff --git a/cmake/FindXRootD.cmake b/cmake/FindXRootD.cmake new file mode 100644 index 00000000000..5b4208dcec0 --- /dev/null +++ b/cmake/FindXRootD.cmake @@ -0,0 +1,31 @@ +# Try to find XRootD +# Once done, this will define +# +# XROOTD_FOUND - system has xrootd +# XROOTD_INCLUDE_DIRS - the xrootd include directories +# XROOTD_LIBRARIES - xrootd libraries directories + +find_path( XROOTD_INCLUDE_DIRS XrdSfs/XrdSfsAio.hh + HINTS + ${XROOTD_DIR} + $ENV{XROOTD_DIR} + /usr + /opt + PATH_SUFFIXES include/xrootd +) + +find_library( XROOTD_LIBRARIES XrdUtils + HINTS + ${XROOTD_DIR} + $ENV{XROOTD_DIR} + /usr + /opt + PATH_SUFFIXES lib +) + +set(XROOTD_INCLUDE_DIR ${XROOTD_INCLUDE_DIRS}) +set(XROOTD_LIBRARY ${XROOTD_LIBRARIES}) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(xrootd DEFAULT_MSG XROOTD_INCLUDE_DIRS XROOTD_LIBRARIES) + diff --git a/cmake/Findfuse.cmake b/cmake/Findfuse.cmake deleted file mode 100644 index 8c137e4d81b..00000000000 --- a/cmake/Findfuse.cmake +++ /dev/null @@ -1,23 +0,0 @@ -# Try to find fuse (devel) -# Once done, this will define -# -# FUSE_FOUND - system has fuse -# FUSE_INCLUDE_DIRS - the fuse include directories -# FUSE_LIBRARIES - fuse libraries directories - -if(FUSE_INCLUDE_DIRS AND FUSE_LIBRARIES) -set(FUSE_FIND_QUIETLY TRUE) -endif(FUSE_INCLUDE_DIRS AND FUSE_LIBRARIES) - -find_path(FUSE_INCLUDE_DIR fuse/fuse_lowlevel.h) -find_library(FUSE_LIBRARY fuse) - -set(FUSE_INCLUDE_DIRS ${FUSE_INCLUDE_DIR}) -set(FUSE_LIBRARIES ${FUSE_LIBRARY}) - -# handle the QUIETLY and REQUIRED arguments and set FUSE_FOUND to TRUE if -# all listed variables are TRUE -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(fuse DEFAULT_MSG FUSE_INCLUDE_DIR FUSE_LIBRARY) - -mark_as_advanced(FUSE_INCLUDE_DIR FUSE_LIBRARY) diff --git a/cmake/XRootDCommon.cmake b/cmake/XRootDCommon.cmake deleted file mode 100644 index 76257ca48af..00000000000 --- a/cmake/XRootDCommon.cmake +++ /dev/null @@ -1,7 +0,0 @@ - -if( READLINE_FOUND ) - include_directories( ${READLINE_INCLUDE_DIR} ) -endif() - -include_directories( ../ ./ ${ZLIB_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/src ${CMAKE_BINARY_DIR}/src - /usr/local/include ) diff --git a/cmake/XRootDDefaults.cmake b/cmake/XRootDDefaults.cmake index 9554515293d..7df08ab6f2f 100644 --- a/cmake/XRootDDefaults.cmake +++ b/cmake/XRootDDefaults.cmake @@ -10,13 +10,5 @@ if( "${CMAKE_BUILD_TYPE}" STREQUAL "" ) endif() define_default( PLUGIN_VERSION 4 ) -define_default( ENABLE_FUSE TRUE ) -define_default( ENABLE_CRYPTO TRUE ) -define_default( ENABLE_KRB5 TRUE ) -define_default( ENABLE_READLINE TRUE ) -define_default( ENABLE_XRDCL TRUE ) define_default( ENABLE_TESTS FALSE ) -define_default( ENABLE_HTTP TRUE ) define_default( ENABLE_CEPH TRUE ) -define_default( ENABLE_PYTHON TRUE ) -define_default( XRD_PYTHON_REQ_VERSION 2.4 ) diff --git a/cmake/XRootDFindLibs.cmake b/cmake/XRootDFindLibs.cmake index b1e23550446..93b4a8fbf32 100644 --- a/cmake/XRootDFindLibs.cmake +++ b/cmake/XRootDFindLibs.cmake @@ -1,62 +1,10 @@ #------------------------------------------------------------------------------- # Find the required libraries #------------------------------------------------------------------------------- -find_package( ZLIB REQUIRED) -if( ENABLE_READLINE ) - find_package( Readline ) - if( READLINE_FOUND ) - add_definitions( -DHAVE_READLINE ) - else() - set( READLINE_LIBRARY "" ) - set( NCURSES_LIBRARY "" ) - endif() -endif() - -if( ZLIB_FOUND ) - add_definitions( -DHAVE_LIBZ ) -endif() - -find_package( LibXml2 ) -if( LIBXML2_FOUND ) - add_definitions( -DHAVE_XML2 ) -endif() - -find_package( Systemd ) -if( SYSTEMD_FOUND ) - add_definitions( -DHAVE_SYSTEMD ) -endif() - -if( ENABLE_CRYPTO ) - find_package( OpenSSL ) - if( OPENSSL_FOUND ) - add_definitions( -DHAVE_XRDCRYPTO ) - add_definitions( -DHAVE_SSL ) - set( BUILD_CRYPTO TRUE ) - else() - set( BUILD_CRYPTO FALSE ) - endif() -endif() - -if( ENABLE_KRB5 ) - find_package( Kerberos5 ) - if( KERBEROS5_FOUND ) - set( BUILD_KRB5 TRUE ) - else() - set( BUILD_KRB5 FALSE ) - endif() -endif() +find_package( XRootD REQUIRED ) -# mac fuse not supported -if( ENABLE_FUSE AND Linux ) - find_package( fuse ) - if( FUSE_FOUND ) - add_definitions( -DHAVE_FUSE ) - set( BUILD_FUSE TRUE ) - else() - set( BUILD_FUSE FALSE ) - endif() -endif() +find_package( ceph REQUIRED ) if( ENABLE_TESTS ) find_package( CPPUnit ) @@ -66,31 +14,3 @@ if( ENABLE_TESTS ) set( BUILD_TESTS FALSE ) endif() endif() - -if( ENABLE_HTTP ) - if( OPENSSL_FOUND AND BUILD_CRYPTO ) - set( BUILD_HTTP TRUE ) - else() - set( BUILD_HTTP FALSE ) - endif() -endif() - -if( ENABLE_CEPH ) - find_package( ceph ) - if( CEPH_FOUND ) - set( BUILD_CEPH TRUE ) - else() - set( BUILD_CEPH FALSE ) - endif() -endif() - -if( ENABLE_PYTHON AND (Linux OR APPLE) ) - find_package( PythonLibs ${XRD_PYTHON_REQ_VERSION} ) - find_package( PythonInterp ${XRD_PYTHON_REQ_VERSION} ) - if( PYTHONINTERP_FOUND AND PYTHONLIBS_FOUND ) - set( BUILD_PYTHON TRUE ) - set( PYTHON_FOUND TRUE ) - else() - set( BUILD_PYTHON FALSE ) - endif() -endif() diff --git a/cmake/XRootDOSDefs.cmake b/cmake/XRootDOSDefs.cmake index d130c9e5af7..e5ffaf94abb 100644 --- a/cmake/XRootDOSDefs.cmake +++ b/cmake/XRootDOSDefs.cmake @@ -4,18 +4,9 @@ include( CheckCXXSourceRuns ) -set( Linux FALSE ) -set( MacOSX FALSE ) -set( Solaris FALSE ) - add_definitions( -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 ) set( LIBRARY_PATH_PREFIX "lib" ) -if( NOT DEFINED USE_LIBC_SEMAPHORE ) - set(USE_LIBC_SEMAPHORE 0) -endif() -add_definitions( -DUSE_LIBC_SEMAPHORE=${USE_LIBC_SEMAPHORE} ) - #------------------------------------------------------------------------------- # Enable c++0x / c++11 #------------------------------------------------------------------------------- @@ -51,87 +42,8 @@ endif() #------------------------------------------------------------------------------- # Linux #------------------------------------------------------------------------------- -if( ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" ) - set( Linux TRUE ) - include( GNUInstallDirs ) - add_definitions( -D__linux__=1 ) - set( EXTRA_LIBS rt ) -endif() - -#------------------------------------------------------------------------------- -# MacOSX -#------------------------------------------------------------------------------- -if( APPLE ) - set( MacOSX TRUE ) - - # this is here because of Apple deprecating openssl and krb5 - set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated-declarations" ) - - add_definitions( -D__macos__=1 ) - add_definitions( -DLT_MODULE_EXT=".dylib" ) - set( CMAKE_INSTALL_LIBDIR "lib" ) - set( CMAKE_INSTALL_BINDIR "bin" ) - set( CMAKE_INSTALL_MANDIR "share/man" ) - set( CMAKE_INSTALL_INCLUDEDIR "include" ) - set( CMAKE_INSTALL_DATADIR "share" ) -endif() - -#------------------------------------------------------------------------------- -# Solaris -#------------------------------------------------------------------------------- -if( ${CMAKE_SYSTEM_NAME} STREQUAL "SunOS" ) - define_default( FORCE_32BITS FALSE ) - set( CMAKE_INSTALL_LIBDIR "lib" ) - set( CMAKE_INSTALL_BINDIR "bin" ) - set( CMAKE_INSTALL_MANDIR "man" ) - set( CMAKE_INSTALL_INCLUDEDIR "include" ) - set( CMAKE_INSTALL_DATADIR "share" ) - set( Solaris TRUE ) - add_definitions( -D__solaris__=1 ) - add_definitions( -DSUNCC -D_REENTRANT -D_POSIX_PTHREAD_SEMANTICS ) - set( EXTRA_LIBS rt Crun Cstd ) - - set( CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fast" ) - set( CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -fast" ) - - define_solaris_flavor() +set( Linux TRUE ) +include( GNUInstallDirs ) +add_definitions( -D__linux__=1 ) +set( EXTRA_LIBS rt ) - #----------------------------------------------------------------------------- - # Define solaris version - #----------------------------------------------------------------------------- - execute_process( COMMAND uname -r - OUTPUT_VARIABLE SOLARIS_VER ) - string( REPLACE "." ";" SOLARIS_VER_LIST ${SOLARIS_VER} ) - list( GET SOLARIS_VER_LIST 1 SOLARIS_VERSION ) - string( REPLACE "\n" "" SOLARIS_VERSION ${SOLARIS_VERSION} ) - add_definitions( -DSOLARIS_VERSION=${SOLARIS_VERSION} ) - - #----------------------------------------------------------------------------- - # AMD64 (opteron) - #----------------------------------------------------------------------------- - if( ${SOLARIS_VERSION} STREQUAL "10" AND SOLARIS_AMD64 AND NOT FORCE_32BITS ) - set( CMAKE_CXX_FLAGS " -m64 -xtarget=opteron -xs ${CMAKE_CXX_FLAGS} " ) - set( CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -G" ) - set( CMAKE_LIBRARY_PATH "/lib/64;/usr/lib/64" ) - add_definitions( -DSUNX86 ) - set( LIB_SEARCH_OPTIONS NO_DEFAULT_PATH ) - set( LIBRARY_PATH_PREFIX "lib/64" ) - endif() - - #----------------------------------------------------------------------------- - # Check if the SunCC compiler can do optimizations - #----------------------------------------------------------------------------- - check_cxx_source_runs( - " - int main() - { - #if __SUNPRO_CC > 0x5100 - return 0; - #else - return 1; - #endif - } - " - SUNCC_CAN_DO_OPTS ) - -endif() diff --git a/cmake/XRootDSummary.cmake b/cmake/XRootDSummary.cmake index f5fc4419a3e..0fa0a49500c 100644 --- a/cmake/XRootDSummary.cmake +++ b/cmake/XRootDSummary.cmake @@ -2,15 +2,9 @@ # Print the configuration summary #------------------------------------------------------------------------------- set( TRUE_VAR TRUE ) -component_status( READLINE ENABLE_READLINE READLINE_FOUND ) -component_status( FUSE BUILD_FUSE FUSE_FOUND ) -component_status( CRYPTO BUILD_CRYPTO OPENSSL_FOUND ) -component_status( KRB5 BUILD_KRB5 KERBEROS5_FOUND ) -component_status( XRDCL ENABLE_XRDCL TRUE_VAR ) +component_status( CEPH TRUE_VAR CEPH_FOUND ) +component_status( XROOTD TRUE_VAR XROOTD_FOUND ) component_status( TESTS BUILD_TESTS CPPUNIT_FOUND ) -component_status( HTTP BUILD_HTTP OPENSSL_FOUND ) -component_status( CEPH BUILD_CEPH CEPH_FOUND ) -component_status( PYTHON BUILD_PYTHON PYTHON_FOUND ) message( STATUS "----------------------------------------" ) message( STATUS "Installation path: " ${CMAKE_INSTALL_PREFIX} ) @@ -19,13 +13,7 @@ message( STATUS "C++ Compiler: " ${CMAKE_CXX_COMPILER} ) message( STATUS "Build type: " ${CMAKE_BUILD_TYPE} ) message( STATUS "Plug-in version: " ${PLUGIN_VERSION} ) message( STATUS "" ) -message( STATUS "Readline support: " ${STATUS_READLINE} ) -message( STATUS "Fuse support: " ${STATUS_FUSE} ) -message( STATUS "Crypto support: " ${STATUS_CRYPTO} ) -message( STATUS "Kerberos5 support: " ${STATUS_KRB5} ) -message( STATUS "XrdCl: " ${STATUS_XRDCL} ) +message( STATUS "CEPH: " ${STATUS_CEPH} ) +message( STATUS "XRootD: " ${STATUS_XROOTD} ) message( STATUS "Tests: " ${STATUS_TESTS} ) -message( STATUS "HTTP support: " ${STATUS_HTTP} ) -message( STATUS "CEPH support: " ${STATUS_CEPH} ) -message( STATUS "Python support: " ${STATUS_PYTHON} ) message( STATUS "----------------------------------------" ) diff --git a/cmake/XRootDSystemCheck.cmake b/cmake/XRootDSystemCheck.cmake deleted file mode 100644 index 800366c1334..00000000000 --- a/cmake/XRootDSystemCheck.cmake +++ /dev/null @@ -1,130 +0,0 @@ -#------------------------------------------------------------------------------- -# Probe the system libraries -#------------------------------------------------------------------------------- - -include( CheckFunctionExists ) -include( CheckSymbolExists ) -include( CheckLibraryExists ) -include( CheckIncludeFile ) -include( CheckCXXSourceRuns ) -include( XRootDUtils ) - -#------------------------------------------------------------------------------- -# OS stuff -#------------------------------------------------------------------------------- -check_function_exists( setresuid HAVE_SETRESUID ) -compiler_define_if_found( HAVE_SETRESUID HAVE_SETRESUID ) - -check_function_exists( strlcpy HAVE_STRLCPY ) -compiler_define_if_found( HAVE_STRLCPY HAVE_STRLCPY ) - -check_function_exists( fstatat HAVE_FSTATAT ) -compiler_define_if_found( HAVE_FSTATAT HAVE_FSTATAT ) - -check_function_exists( sigwaitinfo HAVE_SIGWTI ) -compiler_define_if_found( HAVE_SIGWTI HAVE_SIGWTI ) -if( NOT HAVE_SIGWTI ) - check_library_exists( rt sigwaitinfo "" HAVE_SIGWTI_IN_RT ) - compiler_define_if_found( HAVE_SIGWTI_IN_RT HAVE_SIGWTI ) -endif() - -check_include_file( shadow.h HAVE_SHADOWPW ) -compiler_define_if_found( HAVE_SHADOWPW HAVE_SHADOWPW ) - -#------------------------------------------------------------------------------- -# Some socket related functions -#------------------------------------------------------------------------------- -check_function_exists( getifaddrs HAVE_GETIFADDRS ) -compiler_define_if_found( HAVE_GETIFADDRS HAVE_GETIFADDRS ) -check_function_exists( getnameinfo HAVE_NAMEINFO ) -compiler_define_if_found( HAVE_NAMEINFO HAVE_NAMEINFO ) -if( NOT HAVE_NAMEINFO ) - check_library_exists( socket getnameinfo "" HAVE_NAMEINFO_IN_SOCKET ) - compiler_define_if_found( HAVE_NAMEINFO_IN_SOCKET HAVE_NAMEINFO ) -endif() - -check_function_exists( getprotobyname_r HAVE_PROTOR ) -compiler_define_if_found( HAVE_PROTOR HAVE_PROTOR ) -if( NOT HAVE_PROTOR ) - check_library_exists( socket getprotobyname_r "" HAVE_PROTOR_IN_SOCKET ) - compiler_define_if_found( HAVE_PROTOR_IN_SOCKET HAVE_PROTOR ) -endif() - -check_function_exists( gethostbyaddr_r HAVE_GETHBYXR ) -compiler_define_if_found( HAVE_GETHBYXR HAVE_GETHBYXR ) -if( NOT HAVE_GETHBYXR ) - check_library_exists( socket gethostbyaddr_r "" HAVE_GETHBYXR_IN_SOCKET ) - compiler_define_if_found( HAVE_GETHBYXR_IN_SOCKET HAVE_GETHBYXR ) -endif() - -if( HAVE_GETHBYXR_IN_SOCKET OR HAVE_PROTOR_IN_SOCKET OR HAVE_NAMEINFO_IN_SOCKET ) - set( SOCKET_LIBRARY "-lsocket" ) -else() - set( SOCKET_LIBRARY "" ) -endif() - -#------------------------------------------------------------------------------- -# Sendfile -#------------------------------------------------------------------------------- -if( NOT MacOSX ) - check_function_exists( sendfile HAVE_SENDFILE ) - compiler_define_if_found( HAVE_SENDFILE HAVE_SENDFILE ) - set( SENDFILE_LIBRARY "" ) - if( NOT HAVE_SENDFILE ) - check_library_exists( sendfile sendfile "" HAVE_SENDFILE_IN_SENDFILE ) - compiler_define_if_found( HAVE_SENDFILE_IN_SENDFILE HAVE_SENDFILE ) - - if( HAVE_SENDFILE_IN_SENDFILE ) - set( SENDFILE_LIBRARY "sendfile" ) - endif() - endif() -endif() - -#------------------------------------------------------------------------------- -# Check for libcrypt -#------------------------------------------------------------------------------- -check_function_exists( crypt HAVE_CRYPT ) -compiler_define_if_found( HAVE_CRYPT HAVE_CRYPT ) -if( NOT HAVE_CRYPT ) - check_library_exists( crypt crypt "" HAVE_CRYPT_IN_CRYPT ) - compiler_define_if_found( HAVE_CRYPT_IN_CRYPT HAVE_CRYPT ) - set( CRYPT_LIBRARY "-lcrypt" ) -endif() -if( NOT HAVE_CRYPT AND NOT HAVE_CRYPT_IN_CRYPT ) - set( CRYPT_LIBRARY "" ) -endif() - -check_include_file( et/com_err.h HAVE_ET_COM_ERR_H ) -compiler_define_if_found( HAVE_ET_COM_ERR_H HAVE_ET_COM_ERR_H ) - -#------------------------------------------------------------------------------- -# Check for the atomics -#------------------------------------------------------------------------------- -check_cxx_source_runs( -" - int main() - { - unsigned long long val = 111, *mem = &val; - - if (__sync_fetch_and_add(&val, 111) != 111 || val != 222) return 1; - if (__sync_add_and_fetch(&val, 111) != 333) return 1; - if (__sync_sub_and_fetch(&val, 111) != 222) return 1; - if (__sync_fetch_and_sub(&val, 111) != 222 || val != 111) return 1; - - if (__sync_fetch_and_or (&val, 0) != 111 || val != 111) return 1; - if (__sync_fetch_and_and(&val, 0) != 111 || val != 0 ) return 1; - - if (__sync_bool_compare_and_swap(mem, 0, 444) == 0 || val != 444) - return 1; - - return 0; - } -" -HAVE_ATOMICS ) -option(EnableAtomicsIfPresent "EnableAtomicsIfPresent" ON) -if ( EnableAtomicsIfPresent ) - compiler_define_if_found( HAVE_ATOMICS HAVE_ATOMICS ) -endif () - - - diff --git a/cmake/XRootDUtils.cmake b/cmake/XRootDUtils.cmake index 740a423d3a5..6fbdf0fc9e7 100644 --- a/cmake/XRootDUtils.cmake +++ b/cmake/XRootDUtils.cmake @@ -2,12 +2,6 @@ #------------------------------------------------------------------------------- # Add a compiler define flag if a variable is defined #------------------------------------------------------------------------------- -function( compiler_define_if_found predicate name ) - if( ${predicate} ) - add_definitions( -D${name} ) - endif() -endfunction() - macro( define_default variable value ) if( NOT DEFINED ${variable} ) set( ${variable} ${value} ) @@ -43,31 +37,3 @@ function( CheckBuildDirectory ) endif() endfunction() - -#------------------------------------------------------------------------------- -# Detect what kind of solaris machine we're running -#------------------------------------------------------------------------------- -macro( define_solaris_flavor ) - execute_process( COMMAND isainfo - OUTPUT_VARIABLE SOLARIS_ARCH ) - string( REPLACE " " ";" SOLARIS_ARCH_LIST ${SOLARIS_ARCH} ) - - # amd64 (opteron) - list( FIND SOLARIS_ARCH_LIST amd64 SOLARIS_AMD64 ) - if( SOLARIS_AMD64 EQUAL -1 ) - set( SOLARIS_AMD64 FALSE ) - else() - set( SOLARIS_AMD64 TRUE ) - endif() - -endmacro() - -#------------------------------------------------------------------------------- -# Install headers from a directory -#------------------------------------------------------------------------------- -function( install_headers destination files ) - foreach( file ${files} ) - string( REGEX MATCH "^(.+)/(.+)$" fileAr ${file} ) - install( FILES ${file} DESTINATION ${destination}/${CMAKE_MATCH_1} ) - endforeach() -endfunction() \ No newline at end of file diff --git a/docs/README_IPV4_To_IPV6 b/docs/README_IPV4_To_IPV6 deleted file mode 100644 index 78f5fcc1e41..00000000000 --- a/docs/README_IPV4_To_IPV6 +++ /dev/null @@ -1,80 +0,0 @@ -This major release of xrootd/cmsd provides full IPV6 support with IPV4 -compatibility. As such, it is no longer ABI compatible in two major respects: -1) Classes dealing with sockets and the network interfaces have substantially - changed, and -2) The security interface has changed so as to provide a consistent connection - context. - -*** Socket and Network Interfaces *** - -The XRDSysDNS class is now deprecated and essentially obsolete. It is still -provided for backward compatibly but is no longer supported. This class works -only in IPV4 contexts. It has been replaced by four address-format agnostic -classes: XrdNetAddrInfo, XrdNetAddr, XrdNetSockAddr, and XrdNetUtils. All uses -of XrdSysDNS should convert to using one or more of the new classes. - -The XrdNetLink and XrdNetWork classes have been deleted from the distribution. -These appeared to be never used and it was easier to remove them than to -upgrade them. - -The XrdNetPeer class is also deprecated but exists for backward compatibility. -However, this class embedded an IPV4 structure, sockaddr, and was changed to -use XrdNetSockAddr which defines a larger structure suitable for IPV6. To -prevent programs from unknowingly using the smaller structure, the variable -name used for the sockaddr structure was changed to trigger a compilation error. -You should make sure you are not taking the size of the smaller structure when -copying network addresses. For instance, the variable InetAddr referred to the -sockaddr structure. This has change so that now Intet.Addr refers to the same -structure. - -In general, interfaces using the XrdNetPeer classes should switch to using -equivalent interfaces based on the XrdNetAddr class. The XrdNetAddr class -provides comprehensive address handling while XrdNetPeer did not. - -In a less important area, the class XrdProtocol_Config which also contained -the socket address describing the server’s IP address using the variable -myAddr has been sized for IPV6 using a union. While the variable name has not -changed, programs referring to this variable should use the urAddr variable -instead. However, it was deemed unlikely that this would cause a problem. - -*** Security Interfaces *** - -The security interfaces have substantially changed. All interfaces that used -to accept a sockaddr structure now only accept the XrdNetAddrInfo object. One -can obtain the hostname using the XrdNetAddrInfo object however, that use is -discouraged unless absolutely necessary. This is to prevent defeating the -“nodnr” network directive option. Additional details on how to accomplish this -follow later. - -The XrdSecEntity object has changed, as follows: -1) It now includes a pointer to the XrdNetAddrInfo object that describes the - connection details of the end-point that is associated with the entity - description as member addrInfo. -2) In order to better accommodate this additions, the layout has slightly - changed. - -In order to assist authorization and other host address sensitive plug-ins, -each supported security plug-in now sets the XrdNetAddrInfo member addrInfo -to point to a copy of the XrdNetAddrInfo object passed when requesting a new -instance of the protocol. The default authorization plug-in has been changed -to capitalize on this new information. Similar changes should be made to all -private plug-ins. Failure to set the addrInfo member will likely result in -a SEGV. - -*** Summary Of Required Plug-In Changes *** - -The changes that you should make to your plug-ins are: -1) Substitute XrdNetAddr for any use of sockaddr. If that is not possible, at - the very least, use XrdNetSockAddr instead. -2) After converting remove, if possible, redundant include files “arpa/inet.h”, - “netinet/in.h”, “sys/socket.hh” and similar include files. -3) Convert from using XrdSysDNS to a combination of XrdNetAddr and XrdNetUtils. -4) Private security plug-ins must set the addrInfo member. -5) If one of your plug-ins relied on the host member in XrdSecEntity to - contain an actual host name, it should be changed to get the actual host - using the addrInfo field. Please be aware that the host member never - consistently pointed to a real host name as it was sensitive to the presence - of the nodnr option on the xrd.network directive. -6) All plug-ins must now contain version information. This has become mandatory. - Use the XrdVERSIONINFO macro defined in XrdVersion.hh to include version - information in your plug-in. diff --git a/docs/man/XrdCnsd.8 b/docs/man/XrdCnsd.8 deleted file mode 100644 index dd96a7ff66f..00000000000 --- a/docs/man/XrdCnsd.8 +++ /dev/null @@ -1,34 +0,0 @@ -.TH XrdCnsd 8 "__VERSION__" -.SH NAME -XrdCnsd - Cluster Name Space daemon -.SH SYNOPSIS -.nf - -\fBXrdCnsd\fR [\fIoptions\fR] - -.fi -.br -.ad l -.SH DESCRIPTION -The \fBXrdCnsd\fR daemon creates and maintains a file inventory for a data -server. This is known as a server-side inventory (\fBssi\fR) service. -The daemon can also be run as a one-time command to repair inventory problems. -Complete HTML documentation can be found at - -.ce -http://xrootd.org/doc/prod/cms_config.htm -.SH NOTES -The \fBcns_ssi\fR utility is used to display a server's inventory of files. -Documentation for all components associated with \fBXrdCnsd\fR can be found at -http://xrootd.org/docs.html -.SH DIAGNOSTICS -Configuration errors yield an error message and a non-zero exit status. -The daemon is normally started as piped command via an xrootd daemon -configuration directive. -.SH LICENSE -License terms can be displayed by typing "\fBxrootd -H\fR". -.SH SUPPORT LEVEL -The \fBXrdCnsd\fR daemon is supported by the xrootd collaboration. -Contact information can be found at -.ce -http://xrootd.org/contact.html diff --git a/docs/man/cmsd.8 b/docs/man/cmsd.8 deleted file mode 100644 index e1bc1690fac..00000000000 --- a/docs/man/cmsd.8 +++ /dev/null @@ -1,32 +0,0 @@ -.TH cmsd 8 "__VERSION__" -.SH NAME -cmsd - Cluster Management Services daemon -.SH SYNOPSIS -.nf - -\fBcmsd\fR [\fIoptions\fR] \fB-c\fR \fIconfigfile\fR - -.fi -.br -.ad l -.SH DESCRIPTION -The \fBcmsd\fR daemon provides cluster services to xrootd data servers. -Usage synopsis can be displayed by typing "\fBcmsd -h\fR". -Complete HTML documentation can be found at - -.ce -http://xrootd.org/doc/prod/cms_config.htm -.SH NOTES -Documentation for all components associated with \fBcmsd\fR can be found at -http://xrootd.org/docs.html -.SH DIAGNOSTICS -Configuration errors yield an error message and a non-zero exit status. -The program never exits upon success. -Use the kill command or "/etc/init.d/cmsd stop" to terminate the program. -.SH LICENSE -License terms can be displayed by typing "\fBcmsd -H\fR". -.SH SUPPORT LEVEL -The \fBcmsd\fR daemon is supported by the xrootd collaboration. -Contact information can be found at -.ce -http://xrootd.org/contact.html diff --git a/docs/man/cns_ssi.8 b/docs/man/cns_ssi.8 deleted file mode 100644 index e10ab42caba..00000000000 --- a/docs/man/cns_ssi.8 +++ /dev/null @@ -1,30 +0,0 @@ -.TH cns_ssi 8 "__VERSION__" -.SH NAME -cns_ssi - administer a server side inventory -.SH SYNOPSIS -.nf - -\fBcns_ssi\fR \fIcommand\fR [\fIoptions\fR] \fIdirpath\fR - -.fi -.br -.ad l -.SH DESCRIPTION -The \fBcns_ssi\fR utility displays and maintains server side inventory files -created by the \fBXrdCnsd\fR daemon. -Complete HTML documentation can be found at - -.ce -http://xrootd.org/doc/prod/cms_config.htm -.SH NOTES -Documentation for all components associated with \fBcns_ssi\fR can be found at -http://xrootd.org/docs.html -.SH DIAGNOSTICS -Errors yield an error message and a non-zero exit status. -.SH LICENSE -License terms can be displayed by typing "\fBxrootd -H\fR". -.SH SUPPORT LEVEL -The \fBcns_ssi\fR command is supported by the xrootd collaboration. -Contact information can be found at -.ce -http://xrootd.org/contact.html diff --git a/docs/man/frm_admin.8 b/docs/man/frm_admin.8 deleted file mode 100644 index ecdf1d165df..00000000000 --- a/docs/man/frm_admin.8 +++ /dev/null @@ -1,33 +0,0 @@ -.TH frm_admin 8 "__VERSION__" -.SH NAME -frm_admin - administer file residency parameters -.SH SYNOPSIS -.nf - -\fBfrm_admin\fR [\fIoptions\fR] [\fIcommand\fR [\fIcmdopts\fR]] - -.fi -.br -.ad l -.SH DESCRIPTION -The \fBfrm_admin\fR utility displays and alters data server space attributes -associated with file residency. These attributes are used by the -\fBfrm_purged\fR, \fBfrm_xfrd\fR, and \fBxrootd\fR daemons to manage -file residency. -Usage synopsis can be displayed by typing "\fBfrm_admin -h\fR". -Complete HTML documentation can be found at - -.ce -http://xrootd.org/doc/prod/frm_config.htm -.SH NOTES -Documentation for all components associated with \fBfrm_admin\fR can be found at -http://xrootd.org/docs.html -.SH DIAGNOSTICS -Configuration errors yield an error message and a non-zero exit status. -.SH LICENSE -License terms can be displayed by typing "\fBxrootd -H\fR". -.SH SUPPORT LEVEL -The \fBfrm_admin\fR command is supported by the xrootd collaboration. -Contact information can be found at -.ce -http://xrootd.org/contact.html diff --git a/docs/man/frm_purged.8 b/docs/man/frm_purged.8 deleted file mode 100644 index 72885c00950..00000000000 --- a/docs/man/frm_purged.8 +++ /dev/null @@ -1,34 +0,0 @@ -.TH frm_purged 8 "__VERSION__" -.SH NAME -frm_purged - File Residency Manager purge daemon -.SH SYNOPSIS -.nf - -\fBfrm_purged\fR [\fIoptions\fR] [\fIparameters\fR] - -.fi -.br -.ad l -.SH DESCRIPTION -The \fBfrm_purged\fR daemon automatically removes disk resident files based -on a set of local rules. -It is part of the File Residency Manager software suite. -Usage synopsis can be displayed by typing "\fBfrm_purged -h\fR". -Complete HTML documentation can be found at - -.ce -http://xrootd.org/doc/prod/frm_config.htm -.SH NOTES -Documentation for all components associated with \fBfrm_purged\fR can be found at -http://xrootd.org/docs.html -.SH DIAGNOSTICS -Configuration errors yield an error message and a non-zero exit status. -The program never exits upon success. -Use the kill command or "/etc/init.d/frm_purged stop" to terminate the program. -.SH LICENSE -License terms can be displayed by typing "\fBxrootd -H\fR". -.SH SUPPORT LEVEL -The \fBfrm_purged\fR daemon is supported by the xrootd collaboration. -Contact information can be found at -.ce -http://xrootd.org/contact.html diff --git a/docs/man/frm_xfragent.8 b/docs/man/frm_xfragent.8 deleted file mode 100644 index 54fd1062315..00000000000 --- a/docs/man/frm_xfragent.8 +++ /dev/null @@ -1,31 +0,0 @@ -.TH frm_xfragent 8 "__VERSION__" -.SH NAME -frm_xfragent - File Residency Manager Transfer agent -.SH SYNOPSIS -.nf - -\fBfrm_xfragent\fR [\fIoptions\fR] - -.fi -.br -.ad l -.SH DESCRIPTION -The \fBfrm_xfragent\fR utility sends file transfer requests to the \fBfrm_xfrd\fR -daemon. It is part of the File Residency Manager software suite. -Usage synopsis can be displayed by typing "\fBfrm_xfragent -h\fR". -Complete HTML documentation can be found at - -.ce -http://xrootd.org/doc/prod/frm_config.htm -.SH NOTES -Documentation for all components associated with \fBfrm_xfragent\fR can be found at -http://xrootd.org/docs.html -.SH DIAGNOSTICS -Configuration errors yield an error message and a non-zero exit status. -.SH LICENSE -License terms can be displayed by typing "\fBxrootd -H\fR". -.SH SUPPORT LEVEL -The \fBfrm_xfragent\fR daemon is supported by the xrootd collaboration. -Contact information can be found at -.ce -http://xrootd.org/contact.html diff --git a/docs/man/frm_xfrd.8 b/docs/man/frm_xfrd.8 deleted file mode 100644 index f550b627589..00000000000 --- a/docs/man/frm_xfrd.8 +++ /dev/null @@ -1,33 +0,0 @@ -.TH frm_xfrd 8 "__VERSION__" -.SH NAME -frm_xfrd - File Residency Manager transfer daemon -.SH SYNOPSIS -.nf - -\fBfrm_xfrd\fR [\fIoptions\fR] - -.fi -.br -.ad l -.SH DESCRIPTION -The \fBfrm_xfrd\fR daemon automatically copies files in and out -of the data server. It is part of the File Residency Manager software suite. -Usage synopsis can be displayed by typing "\fBfrm_xfrd -h\fR". -Complete HTML documentation can be found at - -.ce -http://xrootd.org/doc/prod/frm_config.htm -.SH NOTES -Documentation for all components associated with \fBfrm_xfrd\fR can be found at -http://xrootd.org/docs.html -.SH DIAGNOSTICS -Configuration errors yield an error message and a non-zero exit status. -The program never exits upon success. -Use the kill command or "/etc/init.d/frm_xfrd stop" to terminate the program. -.SH LICENSE -License terms can be displayed by typing "\fBxrootd -H\fR". -.SH SUPPORT LEVEL -The \fBfrm_xfrd\fR daemon is supported by the xrootd collaboration. -Contact information can be found at -.ce -http://xrootd.org/contact.html diff --git a/docs/man/mpxstats.8 b/docs/man/mpxstats.8 deleted file mode 100644 index 722fd1412d7..00000000000 --- a/docs/man/mpxstats.8 +++ /dev/null @@ -1,34 +0,0 @@ -.TH mpxstats 8 "__VERSION__" -.SH NAME -mpxstats - Multiplexing Monitor Statistics daemon -.SH SYNOPSIS -.nf - -\fBmpxstats\fR [\fIoptions\fR] \fB-p\fR \fIport\fR - -.fi -.br -.ad l -.SH DESCRIPTION -The \fBmpxstats\fR daemon multiplexes xrootd and cmsd monitoring data streams -into a single sequential format suitable for ingestion by a monitoring agent -(e.g. ganglia, MonaLisa, nagios, etc.). -Usage synopsis can be displayed by typing "\fBmpxstats\fR". -Complete HTML documentation can be found at - -.ce -http://xrootd.org/doc/prod/xrd_monitoring.htm -.SH NOTES -Documentation for all components associated with \fBmpxstats\fR can be found at -http://xrootd.org/docs.html -.SH DIAGNOSTICS -Errors yield an error message and a non-zero exit status. -The program never exits upon success. Use the kill command to terminate the -program. -.SH LICENSE -License terms can be displayed by typing "\fBxrootd -H\fR". -.SH SUPPORT LEVEL -The \fBmpxstats\fR daemon is supported by the xrootd collaboration. -Contact information can be found at -.ce -http://xrootd.org/contact.html diff --git a/docs/man/xprep.1 b/docs/man/xprep.1 deleted file mode 100644 index e6c8891e79e..00000000000 --- a/docs/man/xprep.1 +++ /dev/null @@ -1,96 +0,0 @@ -.TH xprep 1 "__VERSION__" -.SH NAME -xprep - prepare one or more files for access -.SH SYNOPSIS -.nf - -\fBxprep\fR [\fIoptions\fR] \fItarget\fR [\fIpaths\fR] - -\fIoptions\fR: [\fB-d\fR \fIn\fR] [\fB-f\fR \fIfn\fR] [\fB-p\fR \fIprty\fR] [\fB-s\fR] [\fB-S\fR] [\fB-w\fR] - -\fItarget\fR: \fIhost\fR[\fB:\fR\fIport\fR][,\fItarget\fR] - -\fIpaths\fR: \fIpath\fR [\fIpaths\fR] -.fi -.br -.ad l -.SH DESCRIPTION -The \fBxprep\fR utility prepares one or more files for subsequent access -by an application. Usage synopsis can be displayed by typing "\fBxprep\fR". -Currently, only the usage synopsis is available as documentation. -.SH OPTIONS -\fB-d\fR \fIlvl\fR -.RS 5 -debug level: 1 (low), 2 (medium), 3 (high) - -.RE -\fB-f\fR \fIfname\fR -.RS 5 -specifies that the file, \fIfname\fR, contains the list of files to prepare. -Each file name must be on a separate line. - -.RE -\fB-p \fIprty\fR -.RS 5 -schedule the request the the specified priority, \fIprty\fR. The lowest -priority is 0. - Depending on the system configuration, priorities may or may -not be honored. - -.RE -\fB-s\fR -.RS 5 -requests that the file be copied into the cluster should it not be found -online (i.e. stage). -Depending on the system configuration, staging may or may not be honored. - -.RE -\fB-S\fR -.RS 5 -requests that the file be copied into the cluster should it not be found -on the same server as the first file in the specified list of files -When the file is copied it is co-located with the first -file in the list of files, if sufficient space exists on the target server. -Depending on the system configuration, co-location may or may not be honored. - -.RE -\fB-w\fR -.RS 5 -when staging or co-locating the file, copy the file to a space that allows -file modification (i.e. the file will be modified). -Depending on the system configuration, stage to writable space may or may not be honored. - -.RE -.SH OPERANDS -\fItarget\fR -.RS 5 -is the redirector's hostname and optional port number where the prepare is -to occur. If the prepare is to occur on more than one redirector, specified -each one separated by a comma. - -.RE -\fIpaths\fR -.RS 5 -are the files to be prepared. Specify one or more files separated by a space. -to occur. If the prepare is to occur on more than one redirector, specified -each one separated by a comma. If fB-f\fR is specified, you need not specify -any files on the command line. Files specified on the command line are always -processed first. - -.RE -.SH NOTES -Unless the \fB-s\fR or \fB-S\fR option is specified, file preparation merely -seeds the redirector's location cache. This allows the look-ups for prepared -files to progress much faster in the future. - -Documentation for all components associated with \fBxprep\fR can be found at -http://xrootd.org/docs.html -.SH DIAGNOSTICS -Errors yield an error message and a non-zero exit status. -.SH LICENSE -License terms can be displayed by typing "\fBxrootd -H\fR". -.SH SUPPORT LEVEL -The \fBxprep\fR command is supported by the xrootd collaboration. -Contact information can be found at -.ce -http://xrootd.org/contact.html diff --git a/docs/man/xrd.1 b/docs/man/xrd.1 deleted file mode 100644 index 71057e33ca4..00000000000 --- a/docs/man/xrd.1 +++ /dev/null @@ -1,52 +0,0 @@ -.TH xrd 1 "__VERSION__" -.SH NAME -xrd - xrootd file and directory meta-data utility -.SH SYNOPSIS -.nf - -\fBxrd\fR [\fIhost\fR] [\fIoptions\fR] [\fIcommand\fR] - -\fIoptions\fR: [\fB-DS\fR\fIpname stringval\fR] [\fB-DI\fR\fIpname numberval\fR] [\fIoptions\fR] - [\fB-O\fR\fIopaqueinfo\fR] [\fB-h\fR] -.fi -.br -.ad l -.SH DESCRIPTION -\fBThis tool is DEPRECATED. Please use xrdfs instead.\fR - - -The \fBxrd\fR utility executes meta-data oriented operations -(e.g., ls, mv, rm, etc.) on one or more xrootd servers. -Usage synopsis can be displayed by typing "\fBxrd -h\fR". Command help -is available by invoking \fBxrd\fR with no command line options or parameters -and then typing "help" in response to the input prompt. -.SH OPTIONS -\fB-DS\fR\fIpname stringval\fR -.RS 3 -set the internal parameter \fIpname\fR with the string value \fIstringval\fR. - -.RE -\fB-DI\fR\fIpname numberval\fR -.RS 3 -set the internal parameter \fIpname\fR with the numeric value \fInumberval\fR. - -.RE -\fB-O\fR\fIopaqueinfo\fR -.RS 3 -add opaque information \fIopaqueinfo\fR to any path. - -.RE -\fB-h\fR display usage information. -.SH NOTES -See XrdClientConst.hh for a list of parameters. -Documentation for all components associated with \fBxrd\fR can be found at -http://xrootd.org/docs.html -.SH DIAGNOSTICS -Errors yield an error message and a non-zero exit status. -.SH LICENSE -License terms can be displayed by typing "\fBxrootd -H\fR". -.SH SUPPORT LEVEL -The \fBxrd\fR command is supported by the xrootd collaboration. -Contact information can be found at -.ce -http://xrootd.org/contact.html diff --git a/docs/man/xrdadler32.1 b/docs/man/xrdadler32.1 deleted file mode 100644 index 146a641447d..00000000000 --- a/docs/man/xrdadler32.1 +++ /dev/null @@ -1,28 +0,0 @@ -.TH xrdadler32 1 "__VERSION__" -.SH NAME -xrdadler32 - compute and display an adler32 checksum -.SH SYNOPSIS -.nf - -\fBxrdadler32\fR [\fIfile\fR] - -.fi -.br -.ad l -.SH DESCRIPTION -The \fBxrdadler32\fR utiliity -computes and displays an adler32 checksum value for a file. -Usage synopsis can be displayed by typing "\fBxrdadler32 -h\fR". -Currently, only the usage synopsis is available as documentation. -.SH NOTES -Documentation for all components associated with \fBxrdadler32\fR can be found at -http://xrootd.org/docs.html -.SH DIAGNOSTICS -Errors yield an error message and a non-zero exit status. -.SH LICENSE -License terms can be displayed by typing "\fBxrootd -H\fR". -.SH SUPPORT LEVEL -The \fBxrdadler32\fR command is supported by the xrootd collaboration. -Contact information can be found at -.ce -http://xrootd.org/contact.html diff --git a/docs/man/xrdcp-old.1 b/docs/man/xrdcp-old.1 deleted file mode 100644 index ff72df99815..00000000000 --- a/docs/man/xrdcp-old.1 +++ /dev/null @@ -1,253 +0,0 @@ -.TH xrdcp-old 1 "__VERSION__" -.SH NAME -xrdcp-old - copy files -.SH SYNOPSIS -.nf - -\fBxrdcp-old\fR [\fIoptions\fR] \fIsource\fR \fIdestination\fR - -\fIoptions\fR: [\fB--cksum\fR \fIargs\fR] [\fB--debug\fR \fIlvl\fR] [\fB--coerce\fR] [\fB--force\fR] -[\fB--help\fR] [\fB--license\fR] [\fB--nopbar\fR] [\fB--posc\fR] [\fB--proxy \fIipaddr\fB:\fIport\fR] -[\fB--recursive\fR] [\fB--retry\fR \fItime\fR] [\fB--server\fR] [\fB--silent\fR] -[\fB--sources\fR \fInum\fR] [\fB--streams\fR \fInum\fR] [\fB--tpc\fR] [\fB--verbose\fR] -[\fB--version\fR] [\fB--xrate\fR \fIrate\fR] - -\fIlegacy options\fR: [\fB-adler\fR] [\fB-DS\fR\fIparm string\fR] [\fB-DI\fR\fIparm number\fR] -[\fB-md5\fR] [\fB-np\fR] [\fB-OD\fR\fIcgi\fR] [\fB-OS\fR\fIcgi\fR] [\fB-x\fR] - -.fi -.br -.ad l -.SH DESCRIPTION - - -\fBThis tool is DEPRECATED. It is what used to be xrdcp prior to xrootd 4.0.0 release. -Please use xrdcp instead.\fR - - -The \fBxrdcp-old\fR utility copies one or more files from one location to -another. The data source and destination may be a local -or remote file or directory. Additionally, the data source may also reside -on multiple servers. -.SH OPTIONS -{\fB-C | --cksum\fR} \fItype\fR[\fB:\fR{\fIvalue\fR|\fBprint\fR}] -.RS 5 -obtains the checksum of \fItype\fR (i.e. adler32, crc32, or md5) from the source, -computes the checksum at the destination, and verifies that they are the same. If a \fIvalue\fR -is specified, it is used as the source checksum. When \fBprint\fR -is specified, the checksum at the destination is printed but is \fInot\fR verified. - -.RE -{\fB-d\fR | \fB--debug\fR} \fIlvl\fR -.RS 5 -debug level: 1 (low), 2 (medium), 3 (high) - -.RE -{\fB-F\fR | --coerce} -.RS 5 -ignores locking semantics on the destination file. This option may lead to -file corruption if not properly used. - -.RE -{\fB-f\fR | \fB--force\fR} -.RS 5 -re-creates a file if it is already present. - -.RE -{\fB-h\fR | --help} -.RS 5 -displays usage information. - -.RE -{\fB-H\fR | --license} -.RS 5 -displays license terms and conditions. - -.RE -{\fB-N\fR | \fB--nopbar\fR} -.RS 5 -does not display the progress bar. - -.RE -{\fB-P\fR | \fB--posc\fR} -.RS 5 -requests POSC (persist-on-successful-close) processing -to create a new file. Files are automatically deleted should they not be -successfully closed. - -.RE -{\fB-D\fR | \fB--proxy\fR} \fIproxyaddr\fB:\fIproxyport\fR -.RS 5 -use \fIproxyaddr\fB:\fIproxyport\fR as a SOCKS4 proxy. Only numerical addresses are supported. - -.RE -{\fB-r\fR | \fB--recursive\fR} -.RS 5 -recursively copy all files starting at the given source directory. This option is -\fIonly\fR supported for local files. - -.RE -\fB--server\fR -.RS 5 -runs as if in a server environment. Used only for server-side -third party copy support. - -.RE -{\fB-s\fR | \fB--silent\fR} -.RS 5 -neither produces summary information nor displays the progress bar. - -.RE -{\fB-y\fR | \fB--sources\fR} \fInum\fR -.RS 5 -uses up to \fInum\fR sources to copy the file. - -.RE -{\fB-S\fR | \fB--streams\fR} \fInum\fR -.RS 5 -uses \fInum\fR additional parallel streams to do the transfer. -The maximum value is 15. The default is 0 (i.e., use only the main stream). - -.RE -\fB--tpc\fR -.RS 5 -copies the file from remote server to remote server using third-party-copy -protocol (i.e., data flows from server to server). The source and destination -servers must support third party copies. Additional security restrictions -may apply and may cause the copy to fail if they cannot be satisfied. - -.RE -{\fB-v\fR | \fB--verbose\fR} -.RS 5 -displays summary output. - -.RE -{\fB-V\fR | \fB-version\fR} -.RS 5 -displays version information and immediately exits. - -.RE -{\fB-X\fR | \fB--xrate\fR} \fIrate\fR -.RS 5 -limits the copy speed to the specified \fIrate\fB. The rate may be qualified -with the letter \fBk\fR, \fBm\fR, or \fBg\fR to indicate kilo, mega, or giga -bytes, respectively. The option only applies when the source or destination is -local. - -.SH LEGACY OPTIONS -Legacy options are provided for backward compatability. These are now -deprecated and should be avoided. -.RE -\fB--adler\fR -.RS 5 -equivalent to "\fB--cksum adler32:print\fR". - -.RE -\fB-DI\fR\fIpname numberval\fR -.RS 5 -set the internal parameter \fIpname\fR with the numeric value \fInumberval\fR. - -.RE -\fB-DS\fR\fIpname stringval\fR -.RS 5 -set the internal parameter \fIpname\fR with the string value \fIstringval\fR. - -.RE -\fB-md5\fR -.RS 5 -equivalent to "\fB--cksum md5:print\fR". - -.RE -\fB-np\fR -.RS 5 -equivalent to "\fB--nopbar\fR". - -.RE -\fB-OD\fR\fIcgi\fR -.RS 5 -add cgi information \fIcgi\fR to any destination xrootd URL. -You should specify the opaque information directly on the destination URL. - -.RE -\fB-OS\fR\fIcgi\fR -.RS 5 -add cgi information \fIcgi\fR to any source xrootd URL. - -.RE -\fB-x\fR -.RS 5 -equivalent to "\fB--sources 12\fR". - -.RE -.SH OPERANDS -\fIsource\fR -.RS 5 -a local file, a local directory name suffixed by /, or -an xrootd URL in the form of -.ce 1 -\fBxroot://[\fIuser\fB@\fR]\fIhost[\fB:\fIport\fR]\fB/\fIabsolutepath\fR -The \fIabsolutepath\fR can be a directory. - -.RE -\fIdestination\fR -.RS 5 -a dash (i.e. \fB-\fR) indicating stanard out, a local file, a local directory -name suffixed by /, or an xrootd URL in the form -.ce 1 -\fBxroot://[\fIuser\fB@\fR]\fIhost[\fB:\fIport\fR]\fB/\fIabsolutepath\fR -The \fIabsolutepath\fR can be a directory. - -.RE -.SH EXAMPLES (GENERIC) -.TP -local file upload: -.br -.B $ xrdcp-old /tmp/myfile xroot://foo.bar.com//data/myfile -.br -.TP -remote file download: -.br -.B $ xrdcp-old xroot://foo.bar.com//data/myfile /tmp/myfile -.br -.TP -remote-remote copy: -.br -.B $ xrdcp-old xroot://foo.bar.com//data/myfile1 xroot://foo.bar.com//data/myfile2 -.br -.fi -.br -.ad l -.SH EXAMPLES (CASTOR) -.TP -local file upload to a Castor instance using a specific service class and stager: -.br -.B $ xrdcp-old /tmp/myfile root://.cern.ch//castor/cern.ch/data/myfile -ODstagerHost=$STAGE_HOST&svcClass=$STAGE_SVCCLASS -.br -[ you need to escape the '&' in the shell ] -.TP -remote file download from a Castor instance using a specific stager: -.br -.B $ xrdcp-old /tmp/myfile root://.cern.ch//castor/cern.ch/data/myfile -OSstagerHost=$STAGE_HOST -.TP -client-proxy copy to-/from- a Castor instance: -.br -.B $ xrdcp-old -.br -.B root://.cern.ch//castor/cern.ch/data/stagerA/myfile -OSstagerHost= -.br -.B root://.cern.ch//castor/cern.ch/data/stagerB/myfile -ODstagerHost= -.br -.br -[ you cannot copy between stagers using the same logical filename in the castor namespace! ] -.SH NOTES -Documentation for all components associated with \fBxrdcp-old\fR can be found at -http://xrootd.org/docs.html -.SH DIAGNOSTICS -Errors yield an error message and a non-zero exit status. -.SH LICENSE -License terms can be displayed by typing "\fBxrootd -H\fR". -.SH SUPPORT LEVEL -The \fBxrdcp\fR command is supported by the xrootd collaboration. -Contact information can be found at -.ce -http://xrootd.org/contact.html diff --git a/docs/man/xrdcp.1 b/docs/man/xrdcp.1 deleted file mode 100644 index 813811dec27..00000000000 --- a/docs/man/xrdcp.1 +++ /dev/null @@ -1,480 +0,0 @@ -.TH xrdcopy 1 "__VERSION__" -.SH NAME -xrdcp - copy files -.SH SYNOPSIS -.nf - -\fBxrdcp\fR [\fIoptions\fR] \fIsource\fR \fIdestination\fR - -\fIoptions\fR: [\fB--cksum\fR \fIargs\fR] [\fB--debug\fR \fIlvl\fR] -[\fB--coerce\fR] [\fB--force\fR] [\fB--help\fR] [\fB--license\fR] -[\fB--nopbar\fR] [\fB--posc\fR] [\fB--proxy \fIipaddr\fB:\fIport\fR] -[\fB--recursive\fR] [\fB--retry\fR \fItime\fR] [\fB--server\fR] -[\fB--silent\fR] [\fB--sources\fR \fInum\fR] [\fB--streams\fR \fInum\fR] -[\fB--tpc\fR \fIfirst\fR|\fIonly\fR] [\fB--verbose\fR] [\fB--version\fR] -[\fB--xrate\fR \fIrate\fR] [\fB--zip\fR \fIfile\fR] - -\fIlegacy options\fR: [\fB-adler\fR] [\fB-DS\fR\fIparm string\fR] [\fB-DI\fR\fIparm number\fR] -[\fB-md5\fR] [\fB-np\fR] [\fB-OD\fR\fIcgi\fR] [\fB-OS\fR\fIcgi\fR] [\fB-x\fR] - -.fi -.br -.ad l -.SH DESCRIPTION -The \fBxrdcp\fR utility copies one or more files from one location to -another. The data source and destination may be a local -or remote file or directory. Additionally, the data source may also reside -on multiple servers. -.SH OPTIONS -\fB-C\fR | \fB--cksum\fR \fItype\fR[\fB:\fR\fIvalue\fR|\fIprint\fR|\fIsource\fR] -.RS 5 -obtains the checksum of \fItype\fR (i.e. adler32, crc32, or md5) from the source, -computes the checksum at the destination, and verifies that they are the same. If a \fIvalue\fR -is specified, it is used as the source checksum. When \fIprint\fR -is specified, the checksum at the destination is printed but is \fInot\fR verified. - -.RE -\fB-d\fR | \fB--debug\fR \fIlvl\fR -.RS 5 -debug level: 1 (low), 2 (medium), 3 (high) - -.RE -\fB-F\fR | \fB--coerce\fR -.RS 5 -ignores locking semantics on the destination file. This option may lead to -file corruption if not properly used. - -.RE -\fB-f\fR | \fB--force\fR -.RS 5 -re-creates a file if it is already present. - -.RE -\fB-h\fR | \fB--help\fR -.RS 5 -displays usage information. - -.RE -\fB-H\fR | \fB--license\fR -.RS 5 -displays license terms and conditions. - -.RE -\fB-N\fR | \fB--nopbar\fR -.RS 5 -does not display the progress bar. - -.RE -\fB-P\fR | \fB--posc\fR -.RS 5 -requests POSC (persist-on-successful-close) processing -to create a new file. Files are automatically deleted should they not be -successfully closed. - -.RE -\fB-D\fR | \fB--proxy\fR \fIproxyaddr\fB:\fIproxyport\fR -.RS 5 -[NOT YET IMPLEMENTED] - -use \fIproxyaddr\fB:\fIproxyport\fR as a SOCKS4 proxy. Only numerical addresses are supported. - -.RE -\fB-r\fR | \fB--recursive\fR -.RS 5 -recursively copy all files starting at the given source directory. This option is -\fIonly\fR supported for local files. - -.RE -\fB--server\fR -.RS 5 -runs as if in a server environment. Used only for server-side -third party copy support. - -.RE -\fB-s\fR | \fB--silent\fR -.RS 5 -neither produces summary information nor displays the progress bar. - -.RE -\fB-y\fR | \fB--sources\fR \fInum\fR -.RS 5 -[NOT YET IMPLEMENTED] - -uses up to \fInum\fR sources to copy the file. - -.RE -\fB-S\fR | \fB--streams\fR \fInum\fR -.RS 5 -uses \fInum\fR additional parallel streams to do the transfer. -The maximum value is 15. The default is 0 (i.e., use only the main stream). - -.RE -\fB--tpc\fR \fIfirst|only\fR -.RS 5 -copies the file from remote server to remote server using third-party-copy -protocol (i.e., data flows from server to server). The source and destination -servers must support third party copies. Additional security restrictions -may apply and may cause the copy to fail if they cannot be satisfied. -Argument \fI'first'\fR tries tpc and if it fails, does a normal copy; -while \fI'only'\fR fails the copy unless tpc succeeds. - -.RE -\fB-v\fR | \fB--verbose\fR -.RS 5 -displays summary output. - -.RE -\fB-V\fR | \fB-version\fR -.RS 5 -displays version information and immediately exits. - -.RE -\fB-z\fR | \fB--zip\fR -.RS 5 -copy given file from a ZIP archive. - -.RE -\fB-X\fR | \fB--xrate\fR \fIrate\fR -.RS 5 -[NOT YET IMPLEMENTED] - -limits the copy speed to the specified \fIrate\fB. The rate may be qualified -with the letter \fBk\fR, \fBm\fR, or \fBg\fR to indicate kilo, mega, or giga -bytes, respectively. The option only applies when the source or destination is -local. - -.SH LEGACY OPTIONS -Legacy options are provided for backward compatability. These are now -deprecated and should be avoided. -.RE -\fB-adler\fR -.RS 5 -equivalent to "\fB--cksum adler32:source\fR". - -.RE -\fB-DI\fR\fIpname numberval\fR -.RS 5 -set the internal parameter \fIpname\fR with the numeric value \fInumberval\fR. - -.RE -\fB-DS\fR\fIpname stringval\fR -.RS 5 -set the internal parameter \fIpname\fR with the string value \fIstringval\fR. - -.RE -\fB-md5\fR -.RS 5 -equivalent to "\fB--cksum md5:source\fR". - -.RE -\fB-np\fR -.RS 5 -equivalent to "\fB--nopbar\fR". - -.RE -\fB-OD\fR\fIcgi\fR -.RS 5 -add cgi information \fIcgi\fR to any destination xrootd URL. -You should specify the opaque information directly on the destination URL. - -.RE -\fB-OS\fR\fIcgi\fR -.RS 5 -add cgi information \fIcgi\fR to any source xrootd URL. - -.RE -\fB-x\fR -.RS 5 -equivalent to "\fB--sources 12\fR". - -.RE -.SH OPERANDS -\fIsource\fR -.RS 5 -a dash (i.e. \fB-\fR) indicating stanard in, a local file, a local directory name suffixed by /, or -an xrootd URL in the form of -.ce 1 -\fBxroot://[\fIuser\fB@\fR]\fIhost[\fB:\fIport\fR]\fB/\fIabsolutepath\fR -The \fIabsolutepath\fR can be a directory. - -.RE -\fIdestination\fR -.RS 5 -a dash (i.e. \fB-\fR) indicating stanard out, a local file, a local directory -name suffixed by /, or an xrootd URL in the form -.ce 1 -\fBxroot://[\fIuser\fB@\fR]\fIhost[\fB:\fIport\fR]\fB/\fIabsolutepath\fR -The \fIabsolutepath\fR can be a directory. - -.RE - -.SH ENVIRONMENT -The following environment variables are supported. They apply to xrdfs and any -other application using the libXrdCl library, unless specified otherwise. The -text in the brackets is a name of the corresponding xrdcp commandline parameter. -.br - -XRD_LOGLEVEL -.RS 5 -Detemines the amout of diagnostics that should be printed. Valid values are: -\fIDump\fR, \fIDebug\fR, \fIInfo\fR, \fIWarning\fR, and \fIError\fR. -.RE - -XRD_LOGFILE -.RS 5 -If set, the diagnostics will be printed to the specified file instead of stderr. -.RE - -XRD_LOGMASK -.RS 5 -Determines which diagnostics topics should be printed at all levels. It's a -"|" separated list of topics. The first element may be "All" in which case -all the topics are enabled and the subsequent elements may turn them off, or -"None" in which case all the topics are disabled and the subsequent flags may -turn them on. If the topic name is prefixed with "^", then it means that -the topic should be disabled. If the topic name is not prefixed, then it means -that the topic should be enabled. -.br - -The log mask may as well be handled for each diagnostic level separately by -setting one or more of the following variables: \fIXRD_LOGMASK_ERROR\fR, -\fIXRD_LOGMASK_WARNING\fR, \fIXRD_LOGMASK_INFO\fR, \fIXRD_LOGMASK_DEBUG\fR, -and \fIXRD_LOGMASK_DUMP\fR. The default for each level is "All", except -for the \fIDump\fR level, where the default is "All|^PollerMsg". This means -that, at the \fIDump\fR level, all the topics but "PollerMsg" are enabled. -.br - -Available topics: AppMsg, UtilityMsg, FileMsg, PollerMsg, PostMasterMsg, -XRootDTransportMsg, TaskMgrMsg, XRootDMsg, FileSystemMsg, AsyncSockMsg -.RE - -XRD_PARALLELEVTLOOP -.RS 5 -The number of event loops. -.RE - -XRD_READRECOVERY -.RS 5 -Determines if read recovery should be enabled or disabled (enabled by default). -.RE - -XRD_WRITERECOVERY -.RS 5 -Determines if write recovery should be enabled or disabled (enabled by default). -.RE - -XRD_CONNECTIONWINDOW (-DIConnectionWindow) -.RS 5 -A time window for the connection establishment. A connection failure is declared if -the connection is not established within the time window. If a connection failure -happens earlier then another connection attempt will only be made at the beginning -of the next window. -.RE - -XRD_CONNECTIONRETRY (-DIConnectionRetry) -.RS 5 -Number of connection attempts that should be made (number of available connection -windows) before declaring a permanent failure. -.RE - -XRD_REQUESTTIMEOUT (-DIRequestTimeout) -.RS 5 -Default value for the time after which an error is declared if it was impossible -to get a response to a request. -.RE - -XRD_STREAMTIMEOUT (-DIStreamTimeout) -.RS 5 -Default value for the time after which a connection error is declared (and a -recovery attempted) if there are unfulfilled requests and there is no socket -activity or a registered wait timeout. -.RE - -XRD_SUBSTREAMSPERCHANNEL (-DISubStreamsPerChannel) -.RS 5 -Number of streams per session. -.RE - -XRD_TIMEOUTRESOLUTION (-DITimeoutResolution) -.RS 5 -Resolution for the timeout events. Ie. timeout events will be -processed only every XRD_TIMEOUTRESOLUTION seconds. -.RE - -XRD_STREAMERRORWINDOW (-DIStreamErrorWindow) -.RS 5 -Time after which the permanent failure flags are cleared out and a new connection -may be attempted if needed. -.RE - -XRD_RUNFORKHANDLER (-DIRunForkHandler) -.RS 5 -Determines whether the fork handlers should be enabled, making the API fork safe. -.RE - -XRD_REDIRECTLIMIT (-DIRedirectLimit) -.RS 5 -Maximum number of allowed redirections. -.RE - -XRD_POLLERPREFERENCE (-DSPollerPreference) -.RS 5 -A comma separated list of poller implementations in order of preference. The -default is: built-in. -.RE - -XRD_CLIENTMONITOR (-DSClientMonitor) -.RS 5 -Path to the client monitor library. -.RE - -XRD_CLIENTMONITORPARAM (-DSClientMonitorParam) -.RS 5 -Additional optional parameters that will be passed to the monitoring object -on initialization. -.RE - -XRD_WORKERTHREADS (-DIWorkerThreads) -.RS 5 -Number of threads processing user callbacks. -.RE - -XRD_CPPARALLELCHUNKS (-DICPParallelChunks) -.RS 5 -Maximum number of asynchronous requests being processed by the xrdcp command -at any given time. -.RE - -XRD_CPCHUNKSIZE (-DICPChunkSize) -.RS 5 -Size of a single data chunk handled by xrdcp. -.RE - -XRD_NETWORKSTACK (-DSNetworkStack) -.RS 5 -The network stack that the client should use to connect to the server. Possible -values are: - -.B IPAuto -- automatically detect which IP stack to use - -.B IPAll -- use IPv6 stack (AF_INET6 sockets) and both IPv6 and IPv4 (mapped to IPv6) -addresses - -.B IPv6 -- use only IPv6 stack and addresses - -.B IPv4 -- use only IPv4 stack (AF_INET sockets) and addresses - -.B IPv4Mapped6 -- use IPv6 stack and mapped IPv4 addresses -.RE - -XRD_DATASERVERTTL (-DIDataServerTTL) -.RS 5 -Time period after which an idle connection to a data server should be -closed. -.RE - -XRD_LOADBALANCERTTL (-DILoadBalancerTTL) -.RS 5 -Time period after which an idle connection to a manager or a load balancer -should be closed. -.RE - -XRD_APPNAME (-DSAppName) -.RS 5 -Override the application name reported to the server. -.RE - -XRD_PLUGINCONFDIR -.RS 5 -A custom location containing client plug-in config files. -.RE - -XRD_PLUGIN -.RS 5 -A default client plug-in to be used. -.RE - -XRD_CPINITTIMEOUT (-DICPInitTimeout) -.RS 5 -Maximum time allowed for the copy process to initialize, ie. open the source -and destination files. -.RE - -XRD_CPTPCTIMEOUT (-DICPTPCTimeout) -.RS 5 -Maximum time allowed for a third-party copy operation to finish. -.RE - -XRD_TCPKEEPALIVE (-DITCPKeepAlive) -.RS 5 -Enable/Disable the TCP keep alive functionality -.RE - -XRD_TCPKEEPALIVETIME (-DITCPKeepAliveTime) -.RS 5 -Time between last data packet sent and the first keepalive probe (Linux only) -.RE - -XRD_TCPKEEPALIVEINTERVAL (-DITCPKeepAliveInterval) -.RS 5 -Interval between subsequent keepalive probes (Linux only) -.RE - -XRD_TCPKEEPALIVEPROBES (-DITCPKeepProbes) -.RS 5 -Number of unacknowledged probes before considering the connection dead -(Linux only) -.RE - -XRD_METALINKPROCESSING -.RS 5 -Enable/Disable Metalink processing (enabled by default) -.RE - -XRD_LOCALMETALINKFILE -.RS 5 -Enable/Disable local Metalink file processing (by convention the following URL schema has to be used: root://localfile//path/filename.meta4) -The 'localfile' semantic is now deprecated, use file://localhost/path/filename.meta4 instead! -.RE - -XRD_GLFNREDIRECTOR -.RS 5 -The redirector will be used as a last resort if the GLFN tag is specified in a Metalink file. -.RE - -XRD_XCPBLOCKSIZE -.RS 5 -Maximu size of a data block assigned to a single source in case of an extreme copy transfer. -.RE - -XRD_NODELAY -.RS 5 -Disables the Nagle algorithm if set to 1 (default), enables it if set to 0. -.RE - -XRD_PREFERIPV4 -.RS 5 -If set the client tries first IPv4 address (turned off by default). -.RE - -.SH NOTES -Documentation for all components associated with \fBxrdcp\fR can be found at -http://xrootd.org/docs.html - -.SH DIAGNOSTICS -Errors yield an error message and a non-zero exit status. - -.SH LICENSE -LGPL - -.SH SUPPORT LEVEL -The \fBxrdcp\fR command is supported by the xrootd collaboration. -Contact information can be found at: - -.ce -http://xrootd.org/contact.html diff --git a/docs/man/xrdfs.1 b/docs/man/xrdfs.1 deleted file mode 100644 index 99afa8eb4d9..00000000000 --- a/docs/man/xrdfs.1 +++ /dev/null @@ -1,227 +0,0 @@ -.TH xrdfs 1 "__VERSION__" -.SH NAME -xrdfs - xrootd file and directory meta-data utility -.SH SYNOPSIS -.nf - -\fBxrdfs\fR \fI[--no-cwd]\fR \fIhost[:port]\fR \fI[command [args]]\fR - -\fBcommand\fR: help, chmod, ls, locate, mkdir, mv, stat, statvfs, query, rm, rmdir, - truncate, prepare, cat, tail, spaceinfo -.fi -.br -.ad l -.SH DESCRIPTION -The \fBxrdfs\fR utility executes meta-data oriented operations -(e.g., ls, mv, rm, etc.) on one or more xrootd servers. -Command help is available by invoking \fBxrdfs\fR with no command -line options or parameters and then typing "help" in response to the -input prompt. - -.SH OPTIONS -\fB--no-cwd\fR -.RS 3 -No CWD is being preset in interactive mode. - -.SH COMMANDS -\fBchmod\fR \fIpath\fR \fI\fR -.RS 3 -Modify permissions of the \fIpath\fR. Permission string example: -\fIrwxr-x--x\fR - -.RE -\fBls\fR \fI[-l]\fR \fI[-u]\fR \fI[-R]\fR \fI[dirname]\fR -.RS 3 -Get directory listing. -.br -\fI-l\fR stat every entry and pring long listing -.br -\fI-u\fR print paths as URLs -.br -\fI-R\fR list subdirectories recursively -.br -\fI-D\fR show duplicate entries - -.RE -\fBlocate\fR \fI[-n]\fR \fI[-r]\fR \fI[-d]\fR \fI\fR -.RS 3 -Get the locations of the path. -.br -\fI-r\fR refresh, don't use cached locations -.br -\fI-n\fR make the server return the response immediately even though it may be incomplete -.br -\fI-d\fR do a recursive, deep locate in order to find data servers -.br -\fI-m\fR prefer host names to IP addresses -.br -\fI-i\fR ignore network dependencies (IPv6/IPv4) - - -.RE -\fBmkdir\fR \fI[-p] [-m] \fR -.RS 3 -Creates a directory/tree of directories. -.br -\fI-p\fR create the entire directory tree recursively -.br -\fI-m\fR\fB\fR permissions for newly created directories - -.RE -\fBmv\fR \fI \fR -.RS 3 -Move path1 to path2 locally on the same server. - -.RE -\fBstat\fR \fI\fR -.RS 3 -Get info about the file or directory. -.br -\fI-q\fR query optional flag query parameter that makes -xrdfs return error code to the shell if the -requested flag combination is not present; -flags may be combined together using '|' or '&' -.br -Available flags: -\fBXBitSet\fR, \fBIsDir\fR, \fBOther\fR, \fBOffline\fR, \fBPOSCPending\fR, -\fBIsReadable\fR, \fBIsWriteable\fR - -.RE -\fBstatvfs\fR \fI\fR -.RS 3 -Get info about a virtual file system. - -.RE -\fBquery\fR \fI \fR -.RS 3 -Obtain server information. Query codes: -.br -\fIconfig\fR \fB\fR Server configuration; is one of the following: -.RS 5 -bind_max - the maximum number of parallel streams -.br -chksum - the supported checksum -.br -pio_max - maximum number of parallel I/O requests -.br -readv_ior_max - maximum size of a readv element -.br -readv_iov_max - maximum number of readv entries -.br -tpc - support for third party copies -.br -wan_port - the port to use for wan copies -.br -wan_window - the wan_port window size -.br -window - the tcp window size -.br -cms - the status of the cmsd -.br -role - the role in a cluster -.br -sitename - the site name -.br -version - the version of the server -.br -.RE -\fIchecksumcancel\fR \fB\fR File checksum cancelation -.br -\fIchecksum\fR \fB\fR File checksum -.br -\fIopaque\fR \fB\fR Implementation dependent -.br -\fIopaquefile\fR \fB\fR Implementation dependent -.br -\fIspace\fR \fB\fR Logical space stats -.br -\fIstats\fR \fB\fR Server stats; is a list of letters -indicating information to be returned: -.RS 5 -a - all statistics -.br -p - protocol statistics -.br -b - buffer usage statistics -.br -s - scheduling statistics -.br -d - device polling statistics -.br -u - usage statistics -.br -i - server identification -.br -z - synchronized statistics -.br -l - connection statistics -.br -.RE -\fIxattr\fR \fB\fR Extended attributes - -.RE -\fBrm\fR \fI\fR -.RS 3 -Remove a file. - -.RE -\fBrmdir\fR \fI\fR -.RS 3 -Remove a directory. - -.RE -\fBtruncate\fR \fI \fR -.RS 3 -Truncate a file. - -.RE -\fBprepare\fR \fI[-c]\fR \fI[-f]\fR \fI[-s]\fR \fI[-w]\fR \fI[-p priority]\fR \fIfilenames\fR -.RS 3 -Prepare one or more files for access. -.br -\fI-c\fR co-locate staged files if possible -.br -\fI-f\fR refresh file access time even if the location is known -.br -\fI-s\fR stage the files to disk if they are not online -.br -\fI-w\fR whe files will be accessed for modification -.br -\fI-p\fR priority of the request, 0 (lowest) - 3 (highest) - -.RE -\fBcat\fR \fI[-o localfile]\fR \fIfile\fR -.RS 3 -Print contents of a file to stdout -.br -\fI-o\fR print to the specified local file - -.RE -\fBtail\fR \fI[-c bytes] [-f]\fR \fIfile\fR -.RS 3 -Output last part of files to stdout. -.br -\fI-c\fR num_bytes out last num_bytes -.br -\fI-f\fR output appended data as file grows - -.RE -\fBspaceinfo\fR \fIpath\fR -.RS 3 -Get space statistics for given path. - -.SH NOTES -For the list of available environment variables please refere to xrdcopy(1) - -.SH DIAGNOSTICS -Errors yield an error message and a non-zero exit status. - -.SH LICENSE -LGPL - -.SH SUPPORT LEVEL -The \fBxrdfs\fR command is supported by the XRootD Collaboration. -Contact information can be found at - -.ce -http://xrootd.org/contact.html diff --git a/docs/man/xrdgsiproxy.1 b/docs/man/xrdgsiproxy.1 deleted file mode 100644 index ade5272331c..00000000000 --- a/docs/man/xrdgsiproxy.1 +++ /dev/null @@ -1,66 +0,0 @@ -.TH xrdgsiproxy 1 "__VERSION__" -.SH NAME -xrdgsiproxy - generate a proxy X.509 certificate -.SH SYNOPSIS -.nf - -\fBxrdgsiproxy\fR [\fB-h\fR] [\fImode\fR] [\fB-debug\fR] [\fB-f\fR [\fB-out\fR] \fIfile\fR] -.fi -.br -.ad l -.SH DESCRIPTION -The \fBxrdgsiproxy\fR utility displays, creates, and destroys X.509 user proxy -certificates used by the grid security infrastructure -(\fBgsi\fR) security protocol. -.SH OPERANDS -\fImode\fR -.RS 5 -\fBinit\fR create proxy certificate and related proxy file -.br -\fBinfo\fR display content of existing proxy file -.br -\fBdestroy\fR delete existing proxy file -.RE -.br -.SH OPTIONS -.B -h -display help -.TP -\fB-f\fR [\fB-out\fR] \fIfile\fR -Non-standard location of proxy file -.TP -.BI init \0 mode \0 only: -.TP -\fB-certdir\fR \fIdir\fR -Non-standard location of directory with information about known CAs -.TP -\fB-cert\fR \fIfile\fR -Non-standard location of certificate for which proxies are wanted -.TP -\fB-key\fR \fIfile\fR -Non-standard location of private key to be used to sign the proxy -.TP -\fB-bits\fR \fIn\fR -Strength in bits of the key [default 512] -.TP -\fB-valid\fR \fIhh:mm\fR -Time validity of the proxy certificate [default 12:00] -.TP -\fB-path-length\fR \fIlen\fR -Max number of descendent levels below this proxy [default 0] -.SH NOTES -Complete HTML documentation can be found at -.ce 1 -http://xrootd.org/doc/prod/sec_config.htm -.br -Documentation for all components associated with \fBxrdgsiproxy\fR can be found at -http://xrootd.org/docs.html -.SH DIAGNOSTICS -Errors yield an error message and a non-zero exit status. -.SH LICENSE -License terms can be displayed by typing "\fBxrootd -H\fR". -.SH SUPPORT LEVEL -The \fBxrdgsiproxy\fR command is supported by the xrootd collaboration. -Contact information can be found at -.ce -http://xrootd.org/contact.html diff --git a/docs/man/xrdgsitest.1 b/docs/man/xrdgsitest.1 deleted file mode 100644 index ee59e006238..00000000000 --- a/docs/man/xrdgsitest.1 +++ /dev/null @@ -1,173 +0,0 @@ -.TH xrdgsitest 1 "__VERSION__" -.SH NAME -xrdgsitest - test crypto functionality relevant for the GSI implementation -.SH SYNOPSIS -.nf - -\fBxrdgsitest\fR [\fB-h\fR, \fB--help\fR] [\fB-v\fR, \fB--verbose\fR] -.fi -.br -.ad l -.SH DESCRIPTION -The \fBxrdgsitest\fR utility runs a few tests of the crypto functionality implemented in XrdCrypto relevant -for the XrdSecgsi module, i.e. handling of certificates, proxies, chains, verification and similar actions. -.br -.SH OPTIONS -.B -h, --help -display help -.TP -.B -v, --verbose -Print very detailed information about the tests. - -.SH FILES -The program needs access to a user certificate file and its private key, and the related CA file(s); the CRL -is downloaded using the information found in the CA certificate. -The location of the files are the standard ones and they can modified by the standard environment variables: -.TP 3 -X509_USER_CERT [$HOME/.globus/usercert.pem] user certificate -.TP 3 -X509_USER_KEY [$HOME/.globus/userkey.pem] user private key -.TP 3 -X509_USER_PROXY [/tmp/x509up_u] user proxy -.TP 3 -X509_CERT_DIR [/etc/grid-security/certificates/] CA certificates and CRL directories -.SH OUTPUT -The output is a list of PASSED/FAILED test similar to -.TP -$ xrdgsitest -.br -|| --------------------------------------------------------------------------------- -.br -|| Crypto functionality tests for GSI ---------------------------------------------- -.br -|| --------------------------------------------------------------------------------- -.br -|| Loading EEC ............................................................. PASSED -.br -|| Loading User Proxy ...................................................... PASSED -.br -|| --------------------------------------------------------------------------------- -.br -|| Recreate the proxy certificate -------------------------------------------------- -.br -Enter PEM pass phrase: -.br -|| Recreating User Proxy ................................................... PASSED -.br -|| --------------------------------------------------------------------------------- -.br -|| Load CA certificates ------------------------------------------------------------ -.br -|| Loading CA certificate .................................................. PASSED -.br -|| Loading CA certificate .................................................. PASSED -.br -|| --------------------------------------------------------------------------------- -.br -|| Testing ParseFile --------------------------------------------------------------- -.br -|| Chain reorder: ......................................................... PASSED -.br -|| Chain verify: .......................................................... PASSED -.br -|| --------------------------------------------------------------------------------- -.br -|| Testing ExportChain ------------------------------------------------------------- -.br -|| Attach to X509ExportChain ............................................... PASSED -.br -|| --------------------------------------------------------------------------------- -.br -|| Testing Chain Import ------------------------------------------------------------ -.br -|| Chain reorder: ......................................................... PASSED -.br -|| Chain verify: .......................................................... PASSED -.br -|| --------------------------------------------------------------------------------- -.br -|| Testing GSI chain import and verification --------------------------------------- -.br -|| GSI chain verify: ...................................................... PASSED -.br -|| --------------------------------------------------------------------------------- -.br -|| Testing GSI chain copy ---------------------------------------------------------- -.br -|| GSI chain verify: ...................................................... PASSED -.br -|| --------------------------------------------------------------------------------- -.br -|| Testing Cert verification ------------------------------------------------------- -.br -|| verify cert: EE signed by CA ............................................ PASSED -.br -|| verify cert: PX signed by EE ............................................ PASSED -.br -|| verify cert: PX not signed by CA ........................................ PASSED -.br -|| --------------------------------------------------------------------------------- -.br -|| Testing request creation -------------------------------------------------------- -.br -|| Creating request ........................................................ PASSED -.br -|| --------------------------------------------------------------------------------- -.br -|| Testing request signature ------------------------------------------------------- -.br -|| Check proxyCertInfo extension ........................................... PASSED -.br -|| --------------------------------------------------------------------------------- -.br -|| Testing export of signed proxy -------------------------------------------------- -.br -|| Saving signed proxy chain to file ....................................... PASSED -.br -|| --------------------------------------------------------------------------------- -.br -|| Testing CRL identification ------------------------------------------------------ -.br -|| Check CRL distribution points extension OK .............................. PASSED -.br -|| --------------------------------------------------------------------------------- -.br -|| Testing CRL loading ------------------------------------------------------------- -.br ---2016-12-12 19:31:36-- http://cafiles.cern.ch/cafiles/crl/CERN%20Root%20Certification%20Authority%202.crl -.br -Resolving cafiles.cern.ch (cafiles.cern.ch)... 137.138.4.52, 2001:1458:201:96::100:26 -.br -Connecting to cafiles.cern.ch (cafiles.cern.ch)|137.138.4.52|:80... connected. -.br -HTTP request sent, awaiting response... 200 OK -.br -Length: 1097 (1.1K) [application/pkix-crl] -.br -Saving to: ‘/tmp/5168735f.0.crltmp’ -.br - -.br -/tmp/5168735f.0.crltmp 100%[========================================================================>] 1.07K --.-KB/s in 0s -.br - -.br -2016-12-12 19:31:36 (383 MB/s) - ‘/tmp/5168735f.0.crltmp’ saved [1097/1097] -.br - -.br -|| Loading CA1 crl ......................................................... PASSED -.br -|| CRL signature OK ........................................................ PASSED -.br -|| --------------------------------------------------------------------------------- - -.TP -The result of each test can be interleaved with details when the verbose option is chosen. -.SH LICENSE -License terms can be displayed by typing "\fBxrootd -H\fR". -.SH SUPPORT LEVEL -The \fBxrdgsitest\fR command is supported by the xrootd collaboration. -Contact information can be found at -.ce -http://xrootd.org/contact.html diff --git a/docs/man/xrdpfc_print.8 b/docs/man/xrdpfc_print.8 deleted file mode 100644 index 925cde298aa..00000000000 --- a/docs/man/xrdpfc_print.8 +++ /dev/null @@ -1,54 +0,0 @@ -.TH xrdpfc_print 8 "__VERSION__" -.SH NAME -xrdpfc_print - print content of ProxyFileCache meta data -.SH SYNOPSIS -.nf - -\fBxrdpfc_print\fR [\fIoptions\fR] \fRpath ...\fR - -\fIoptions\fR: [\fB--config\fR \fIargs\fR] [\fB--verbose\fR] [\fB--help\fR] - -.fi -.br -.ad l -.SH DESCRIPTION -The \fBxrdpfc_print\fR prints status of downloaded file blocks and access statistics of cached files -.SH OPTIONS - -\fB-c\fR | \fB--config\fR -.RS 5 -xrootd configuration file. Used to load non-default file system (directive ofs.osslib) and prefix for the location of cached files (directive oss.localroot) - -.RE -\fB-v\fR | \fB--verbose\fR -.RS 5 -prints additional info for each downloaded file block - -.RE -\fB-h\fR | \fB--help\fR -.RS 5 -displays usage information. - -.RE - - -.RE -.SH OPERANDS -\fRpath\fR -.RS 5 -Path to a file or directory for which the information is to be printed. Path can be relative or absoulte. If the path begins with root:/ the path is assumed to be a LFN and gets translated via the standard OSS rules (in the least, it gets prefixed by the oss.localroot). In this case --config option is mandatory. - -.RE - -.SH NOTES -Documentation for all components associated with \fBxrdpfc_print\fR can be found at -http://xrootd.org/docs.html -.SH DIAGNOSTICS -Errors yield an error message and a non-zero exit status. -.SH LICENSE -License terms can be displayed by typing "\fBxrootd -H\fR". -.SH SUPPORT LEVEL -The \fBxrdpfc_print\fR command is supported by the xrootd collaboration. -Contact information can be found at -.ce -http://xrootd.org/contact.html diff --git a/docs/man/xrdpwdadmin.8 b/docs/man/xrdpwdadmin.8 deleted file mode 100644 index aff35737ab8..00000000000 --- a/docs/man/xrdpwdadmin.8 +++ /dev/null @@ -1,30 +0,0 @@ -.TH xrdpwdadmin 8 "__VERSION__" -.SH NAME -xrdpwdadmin - administer pwd security protocol passwords -.SH SYNOPSIS -.nf - -\fBxrdpwdadmin\fR [\fB-m\fR \fImode\fR] \fIcommand\fR [\fIoptions\fR] - -.fi -.br -.ad l -.SH DESCRIPTION -The \fBxrdpwdadmin\fR utility displays and maintains password and auto-login -files used by the password (\fBpwd\fR) security protocol. -Complete HTML documentation can be found at - -.ce -http://xrootd.org/doc/prod/sec_config.htm -.SH NOTES -Documentation for all components associated with \fBxrdpwdadmin\fR can be found at -http://xrootd.org/docs.html -.SH DIAGNOSTICS -Errors yield an error message and a non-zero exit status. -.SH LICENSE -License terms can be displayed by typing "\fBxrootd -H\fR". -.SH SUPPORT LEVEL -The \fBxrdpwdadmin\fR command is supported by the xrootd collaboration. -Contact information can be found at -.ce -http://xrootd.org/contact.html diff --git a/docs/man/xrdsssadmin.8 b/docs/man/xrdsssadmin.8 deleted file mode 100644 index 235d4ed5636..00000000000 --- a/docs/man/xrdsssadmin.8 +++ /dev/null @@ -1,30 +0,0 @@ -.TH xrdsssadmin 8 "__VERSION__" -.SH NAME -xrdsssadmin - administer simple shared secret keytables -.SH SYNOPSIS -.nf - -\fBxrdsssadmin\fR [\fIoptions\fR] \fIcommand\fR [\fIfile\fR] - -.fi -.br -.ad l -.SH DESCRIPTION -The \fBxrdsssadmin\fR utility displays and maintains keytables for the -simple shared secret (\fBsss\fR) security protocol. -Complete HTML documentation can be found at - -.ce -http://xrootd.org/doc/prod/sec_config.htm -.SH NOTES -Documentation for all components associated with \fBxrdsssadmin\fR can be found at -http://xrootd.org/docs.html -.SH DIAGNOSTICS -Errors yield an error message and a non-zero exit status. -.SH LICENSE -License terms can be displayed by typing "\fBxrootd -H\fR". -.SH SUPPORT LEVEL -The \fBxrdsssadmin\fR command is supported by the xrootd collaboration. -Contact information can be found at -.ce -http://xrootd.org/contact.html diff --git a/docs/man/xrdstagetool.1 b/docs/man/xrdstagetool.1 deleted file mode 100644 index ef1baf661ed..00000000000 --- a/docs/man/xrdstagetool.1 +++ /dev/null @@ -1,31 +0,0 @@ -.TH xrdstagetool 1 "__VERSION__" -.SH NAME -xrdstagetool - stage one or more files -.SH SYNOPSIS -.nf - -\fBxrdstagetool\fR [\fIoptions\fR] \fIpaths\fR - -\fBxrdstagetool\fR [\fIoptions\fR] \fItarget\fR \fB-S\fR \fIsource\fR - -.fi -.br -.ad l -.SH DESCRIPTION -The \fBxrdstagetool\fR utility stages missing files into an xrootd server; -optionally using a specific source server. -The \fBxprep\fR command provides a similar, though not identical, set of features. -Usage synopsis can be displayed by typing "\fBxrdstagetool\fR". -Currently, only the usage synopsis is available as documentation. -.SH NOTES -Documentation for all components associated with \fBxrdstagetool\fR can be found at -http://xrootd.org/docs.html -.SH DIAGNOSTICS -Errors yield an error message and a non-zero exit status. -.SH LICENSE -License terms can be displayed by typing "\fBxrootd -H\fR". -.SH SUPPORT LEVEL -The \fBxrdstagetool\fR command is supported by the xrootd collaboration. -Contact information can be found at -.ce -http://xrootd.org/contact.html diff --git a/docs/man/xrootd.8 b/docs/man/xrootd.8 deleted file mode 100644 index 022318ae98a..00000000000 --- a/docs/man/xrootd.8 +++ /dev/null @@ -1,33 +0,0 @@ -.TH xrootd 8 "__VERSION__" -.SH NAME -xrootd - eXtended ROOT daemon -.SH SYNOPSIS -.nf - -\fBxrootd\fR [\fIoptions\fR] [\fIexports\fR] - -.fi -.br -.ad l -.SH DESCRIPTION -The \fBxrootd\fR daemon provides LAN/WAN access to data in -stand-alone nodes, clustered nodes, as well as globally federated clusters. -Usage synopsis can be displayed by typing "\fBxrootd -h\fR". -Complete HTML documentation can be found at - -.ce -http://xrootd.org/doc/prod/xrd_config.htm -.SH NOTES -Documentation for all components associated with \fBxrootd\fR can be found at -http://xrootd.org/docs.html -.SH DIAGNOSTICS -Configuration errors yield an error message and a non-zero exit status. -The program never exits upon success. -Use the kill command or "/etc/init.d/xrootd stop" to terminate the program. -.SH LICENSE -License terms can be displayed by typing "\fBxrootd -H\fR". -.SH SUPPORT LEVEL -The \fBxrootd\fR daemon is supported by the xrootd collaboration. -Contact information can be found at -.ce -http://xrootd.org/contact.html diff --git a/docs/man/xrootdfs.1 b/docs/man/xrootdfs.1 deleted file mode 100644 index f404dc23d61..00000000000 --- a/docs/man/xrootdfs.1 +++ /dev/null @@ -1,123 +0,0 @@ -.TH xrootdfs 8 "__VERSION__" -.SH NAME -xrootdfs - xrootd FUSE file system daemon -.SH SYNOPSIS -.nf - -\fBxrootdfs\fR [\fIoptions\fR] \fIparameters\fR - -.fi -.br -.ad l -.SH DESCRIPTION -The \fBxrootdfs\fR daemon provides a file system view of an xrootd cluster -using FUSE. -Usage synopsis can be displayed by typing "\fBxrootdfs -h\fR". -Short documentation can be found in a README file in the src/XrdFfs source -directory. - -.SH EXAMPLES -Assuming the redirector is -.B rdr:port - -run from command line with debugging output -.RS -xrootdfs -d -o rdr=root://rdr:port//data,uid=daemon /mnt -.RE - -use in /etc/fstab -.RS -xrootdfs /mnt fuse rdr=root://rdr:port//data,uid=daemon 1 2 -.RE - -use with autofs -.RS -1. add a line to /etc/auto.master -.br -/\- /etc/auto.fuse - -2. create /etc/auto.fuse with the following one line -.br -/mnt \-fstype=fuse,uid=2,rdr=root://rdr\\:port//data :xrootdfs.sh - -3. create script /usr/bin/xrootdfs.sh (make sure +x bit is set) -.br -#!/bin/sh -.br -exec /usr/bin/xrootdfs $@ >/dev/null 2>&1 - -.SH NOTES -Documentation for all components associated with \fBxrootdfs\fR can be found at -http://xrootd.org/docs.html - -xrootdfs allows users and administators to query and change the internal -parameters on the fly via the filesystem extened attributes - -getfattr -n attribute_name /mount_point -.br -setfattr -n attribute_name [ -v value ] /mount_point - -attribute_name: -.RS -.B xroot.url: -query the actual ROOT url of the file (this is an old one) -.br -.B xrootdfs.fs.nworkers: -query or change the number of threads working in parallel on -operations such as stat(), unlink()/rmdir(), readdir(), statvfs(), etc. -.br -.B xrootdfs.fs.dataserverlist: -query or refresh the list of all data servers known to this xrootdfs -instance (or "kill -USR1 pid" to refresh) - -.SH SECURITY -By default, XrootdFS does not send individual user identity to the Xrootd storage servers. -So Xrootd storage thinks that all operations from an XrootdFS instance come from the user -that runs the XrootdFS instance. When the Xrootd "sss" security module (Simple Shared Security) -is enabled at both XrootdFS and Xrootd storage system, XrootdFS will send individual user -identity infomation to the Xrootd storage servers. This info can be used along with the Xrootd ACL -to control file/directory access. - -To use "sss" security module, both Xrootd data servers and XrootdFS should be -configured to use "sss" in a particular way, e.g. both sides should use a -key file that contains the same key generated by the xrdsssadmin program in the -following way: - -xrdsssadmin -k my_key_name -u anybody -g usrgroup add keyfile - -(change only "my_key_name" and "keyfile"). Please refer to environment variable -"XrdSecsssKT" in Xrootd "Authentication & Access Control Configuration Reference" -for more information on the location of the keyfile and its unix permission bits. That -same document also describes the Xrootd ACL DB file. - -To enable "sss" with XrootdFS, use the sss=/keyfile option with XrootdFS. - -The following example shows how to use both unix and sss security modules with the Xrootd -data servers. - -.RS - xrootd.seclib /usr/lib64/libXrdSec.so -.br - sec.protocol /usr/lib64 sss -s /keyfile -.br - sec.protocol /usr/lib64 unix -.br - acc.authdb /your_xrootd_ACL_auth_db_file -.br - acc.authrefresh 300 -.br - ofs.authorize - -.SH DIAGNOSTICS -Errors yield an error message and a non-zero exit status. -The program never exits upon success. Use the umount command to terminate the -program. - -Additional logging information can be found in syslog (/var/log/messages) -.SH LICENSE -License terms can be displayed by typing "\fBxrootd -H\fR". -.SH SUPPORT LEVEL -The \fBxrootdfs\fR daemon is supported by the xrootd collaboration. -Contact information can be found at -.ce -http://xrootd.org/contact.html diff --git a/dopy.sh b/dopy.sh deleted file mode 100755 index 2f870ad8dc2..00000000000 --- a/dopy.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -# Writes the xrootd version to bindings/python/VERSION_INFO, -# generates and uploads a source distribution for the python bindings. -./genversion.sh --print-only | sed "s/v//" > bindings/python/VERSION_INFO -cd bindings/python -cp setup_pypi.py setup.py -python setup.py sdist -twine upload dist/* -rm setup.py dist/* - diff --git a/packaging/common/client-plugin.conf.example b/packaging/common/client-plugin.conf.example deleted file mode 100644 index 260e7300361..00000000000 --- a/packaging/common/client-plugin.conf.example +++ /dev/null @@ -1,16 +0,0 @@ -#------------------------------------------------------------------------------- -# Example client plug-in configuration file. Its name needs to end wiht '.conf' -# in order to be taken into account while the plug-in environment is set up. -# -# It needs to contain at least three lines with the following key-value pairs: -# 1) url - denoting a semicolon separated list of URLs the plug-in applies -# to -# 2) lib - path to the library containing the plug-in factory -# 3) enable - telling the system whether the plugin for the specified URLs -# should be processed, or disabled, if it has already been -# registered by one of the previusly processed files -#------------------------------------------------------------------------------- - -url = root://eosatlas.cern.ch:1094 -lib = /usr/lib/libXrdEosClient.so -enable = true diff --git a/packaging/common/client.conf b/packaging/common/client.conf deleted file mode 100644 index c3c47525bdf..00000000000 --- a/packaging/common/client.conf +++ /dev/null @@ -1,142 +0,0 @@ -#------------------------------------------------------------------------------- -# XRootD client configuration file -# -# Uncomment the line containing the variable you want to change to make it -# effective. -# -# Settings defined in /etc/xrootd/client.conf will be overriden by the -# ones defined in ~/.xrootd/client.conf or by environment variable (see -# man xrdcp for details). -# -# All the timeout values are in seconds. -#------------------------------------------------------------------------------- - -#------------------------------------------------------------------------------- -# A time window for the connection establishment. A connection failure is -# declared if the connection is not established within the time window. If -# a connection failure happens earlier then another connection attempt will -# only be made at the beginning of the next window. -# -# ConnectionWindow = 120 -#------------------------------------------------------------------------------- -# Number of connection attempts that should be made (number of available -# connection windows) before declaring a permanent failure. -# -# ConnectionRetry = 5 -#------------------------------------------------------------------------------- -# Default value for the time after which an error is declared if it was -# impossible to get a response to a request. -# -# RequestTimeout = 1800 -#------------------------------------------------------------------------------- -# Default value for the time after which a connection error is declared (and a -# recovery attempted) if there are unfulfilled requests and there is no socket -# activity or a registered wait timeout. -# -# StreamTimeout = 60 -#------------------------------------------------------------------------------- -# Number of streams per session. -# -# SubStreamsPerChannel = 1 -#------------------------------------------------------------------------------- -# Resolution for the timeout events. Ie. timeout events will be processed only -# every TimeoutResolution seconds. -# -# TimeoutResolution = 15 -#------------------------------------------------------------------------------- -# Time after which the permanent failure flags are cleared out and a new -# connection may be attempted if needed. -# -# StreamErrorWindow = 1800 -#------------------------------------------------------------------------------- -# Determines whether the fork handlers should be enabled, making the API fork -# safe - this has performance implications, so probably you want to set it -# on per-process level via an environment variable. -# -# RunForkHandler = 0 -#------------------------------------------------------------------------------- -# Maximum number of allowed redirections. -# -# RedirectLimit = 16 -#------------------------------------------------------------------------------- -# Number of threads processing user callbacks. -# -# WorkerThreads = 3 -#------------------------------------------------------------------------------- -# Size of a single data chunk handled by xrdcopy. -# -# CPChunkSize = 16777216 -#------------------------------------------------------------------------------- -# Maximum number of asynchronous requests being processed by the xrdcopy -# command at any given time. -# -# CPParallelChunks = 4 -#------------------------------------------------------------------------------- -# Time period after which an idle connection to a data server should be closed. -# -# DataServerTTL = 300 -#------------------------------------------------------------------------------- -# Time period after which an idle connection to a manager or a load balancer -# should be closed. -# -# LoadBalancerTTL = 1200 -#------------------------------------------------------------------------------- -# Maximum time allowed for the copy process to initialize, ie. open the source -# and destination files. -# -# CPInitTimeout = 600 -#------------------------------------------------------------------------------- -# Maximum time allowed for a third-party copy operation to finish. -# -# CPTPCTimeout = 1800 -#------------------------------------------------------------------------------- -# Enable/Disable the TCP keep alive functionality -# -# TCPKeepAlive = 0 -#------------------------------------------------------------------------------- -# Time between last data packet sent and the first keepalive probe (Linux only) -# -# TCPKeepAliveTime = 7200 -#------------------------------------------------------------------------------- -# Interval between subsequent keepalive probes (Linux only) -# -# TCPKeepAliveInterval = 75 -#------------------------------------------------------------------------------- -# Number of unacknowledged probes before considering the connection dead -# (Linux only) -# -# TCPKeepProbes = 9 -#------------------------------------------------------------------------------- -# A comma separated list of poller implementations in order of preference. -# -# PollerPreference = built-in -#------------------------------------------------------------------------------- -# The network stack that the client should use to connect to the server. -# Possible values are: -# -# IPAuto - automatically detect which IP stack to use -# IPAll - use IPv6 stack (AF_INET6 sockets) and both IPv6 and IPv4 -# (mapped to IPv6) addresses -# IPv6 - use only IPv6 stack and addresses -# IPv4 - use only IPv4 stack (AF_INET sockets) and addresses -# IPv4Mapped6 - use IPv6 stack and mapped IPv4 addresses -# -# NetworkStack = IPAuto -#------------------------------------------------------------------------------- -# Path to the client monitor library. -# -# ClientMonitor = -#------------------------------------------------------------------------------- -# Additional optional parameters that will be passed to the monitoring object -# on initialization. -# -# ClientMonitorParam = -#------------------------------------------------------------------------------- -# A custom location containing client plug-in config files. -# -# PlugInConfDir = -#------------------------------------------------------------------------------- -# A default client plug-in to be used. -# -# PlugIn = -#------------------------------------------------------------------------------- diff --git a/packaging/common/cmsd@.service b/packaging/common/cmsd@.service deleted file mode 100644 index 32ddd7f3eb7..00000000000 --- a/packaging/common/cmsd@.service +++ /dev/null @@ -1,20 +0,0 @@ -[Unit] -Description=XRootD cmsd deamon instance %I -Documentation=man:cmsd(8) -Documentation=http://xrootd.org/docs.html -Requires=network-online.target -After=network-online.target - -[Service] -ExecStart=/usr/bin/cmsd -l /var/log/xrootd/cmsd.log -c /etc/xrootd/xrootd-%i.cfg -k fifo -s /var/run/xrootd/cmsd-%i.pid -n %i -User=xrootd -Group=xrootd -Type=simple -Restart=on-abort -RestartSec=0 -KillMode=control-group -LimitNOFILE=65536 -WorkingDirectory=/var/spool/xrootd - -[Install] -RequiredBy=multi-user.target diff --git a/packaging/common/frm_purged@.service b/packaging/common/frm_purged@.service deleted file mode 100644 index c2f398a0299..00000000000 --- a/packaging/common/frm_purged@.service +++ /dev/null @@ -1,19 +0,0 @@ -[Unit] -Description=XRootD frm_purged deamon instance %I -Documentation=man:frm_purged(8) -Documentation=http://xrootd.org/docs.html -Requires=network-online.target -After=network-online.target - -[Service] -ExecStart=/usr/bin/frm_purged -l /var/log/xrootd/frm_purged.log -c /etc/xrootd/xrootd-%i.cfg -k fifo -s /var/run/xrootd/frm_purged-%i.pid -n %i -User=xrootd -Group=xrootd -Type=simple -Restart=on-abort -RestartSec=0 -KillMode=control-group -LimitNOFILE=65536 - -[Install] -RequiredBy=multi-user.target diff --git a/packaging/common/frm_xfrd@.service b/packaging/common/frm_xfrd@.service deleted file mode 100644 index 3176c25baba..00000000000 --- a/packaging/common/frm_xfrd@.service +++ /dev/null @@ -1,19 +0,0 @@ -[Unit] -Description=XRootD frm_xfrd deamon instance %I -Documentation=man:frm_xrfd(8) -Documentation=http://xrootd.org/docs.html -Requires=network-online.target -After=network-online.target - -[Service] -ExecStart=/usr/bin/frm_xfrd -l /var/log/xrootd/frm_xfrd.log -c /etc/xrootd/xrootd-%i.cfg -k fifo -s /var/run/xrootd/frm_xfrd-%i.pid -n %i -User=xrootd -Group=xrootd -Type=simple -Restart=on-abort -RestartSec=0 -KillMode=control-group -LimitNOFILE=65536 - -[Install] -RequiredBy=multi-user.target diff --git a/packaging/common/xrdhttp@.socket b/packaging/common/xrdhttp@.socket deleted file mode 100644 index 0657df949e2..00000000000 --- a/packaging/common/xrdhttp@.socket +++ /dev/null @@ -1,9 +0,0 @@ -[Unit] -Description=XrdHttp socket - -[Socket] -ListenStream=80 -Service=xrootd@%i.service - -[Install] -WantedBy=sockets.target diff --git a/packaging/common/xrootd-clustered.cfg b/packaging/common/xrootd-clustered.cfg deleted file mode 100644 index d3c0d84e4e7..00000000000 --- a/packaging/common/xrootd-clustered.cfg +++ /dev/null @@ -1,53 +0,0 @@ -########################################################################### -# This is a very simple sample configuration file sufficient to start an # -# xrootd data server using the default port 1094 and its companion cmsd. # -# Trying to use the xrootd will cause the client to simply wait because # -# there is no redirector and this configuration file is insufficient to # -# start one. Consult the reference manuals on how to create a usable # -# configuration file to completely describe a functional xrootd cluster. # -# # -# On start-up the xrootd will complain about not connecting to the pipe # -# named '/var/spool/xrootd/.olb/olbd.admin'. This will continue until the # -# cmsd starts. When the cmsd start is will say ' Waiting for primary # -# server to login.' Once xrootd is started and connects to the cmsd, the # -# cmsd will complain 'Unable to connect socket to localhost' because # -# there is no redirector. However, this shows that xrootd and cmsd have # -# been correctly installed. # -# # -# Note: You should always create a *single* configuration file and use it # -# when starting each daemon that you need to run in the cluster! # -########################################################################### - -# The export directive indicates which paths are to be exported. While the -# default is '/tmp', we indicate it anyway and add the 'stages attribute -# to allow you to start the frm_xfrd to bring in missing files into '/tmp'. -# Remove this attribute if you don't want to enable this feature. -# -all.export /tmp stage - -# The role directive tells xrootd to run as a data server as part of a -# cluster. The causes the xrootd to try to contact the local cmsd which -# needs to started as part of of initialization. As a side note, a -# redirector would have a manager role. -# -all.role server - -# The cmsd running on a data server node needs to know where the redirector -# (i.e. manager) is running. In this generic config we simply say that it -# is on this host to allow initialization to succeed. However, the final -# result is not practically usable. -# -all.manager localhost 3121 - -# The copycmd directive tells the frm_xfrd what to use to copy files into -# an exported path with the 'stage' attribute. Here we just say this will -# be '/bin/cp' to allow the frm_xfrd to actual start to show that it works. -# Here missing files are created in /tmp as zero-length files. -# -frm.xfr.copycmd /bin/cp /dev/null $PFN - -# The adminpath and pidpath variables indicate where the pid and various -# IPC files should be placed -# -all.adminpath /var/spool/xrootd -all.pidpath /var/run/xrootd diff --git a/packaging/common/xrootd-filecache-clustered.cfg b/packaging/common/xrootd-filecache-clustered.cfg deleted file mode 100644 index 22609fd803c..00000000000 --- a/packaging/common/xrootd-filecache-clustered.cfg +++ /dev/null @@ -1,90 +0,0 @@ -########################################################################### -# This is a very simple sample configuration file sufficient to start an # -# xrootd file caching data server using the default port 1094 and its # -# companion cmsd. Trying to use the xrootd will cause the client to # -# simply wait there is no redirector and this configuration file is # -# insufficient to start one. Consult the reference manuals on how to # -# create a usable configuration file to completely describe a functional # -# xrootd cluster. # -# # -# On start-up the xrootd will complain about not connecting to the pipe # -# named '/var/spool/xrootd/.olb/olbd.admin'. This will continue until the # -# cmsd starts. When the cmsd start is will say ' Waiting for primary # -# server to login.' Once xrootd is started and connects to the cmsd, the # -# cmsd will complain 'Unable to connect socket to localhost' because # -# there is no redirector. However, this shows that xrootd and cmsd have # -# been correctly installed. # -# # -# Note: You should always create a *single* configuration file and use it # -# when starting each daemon that you need to run in the cluster! # -########################################################################### -# Tell everyone who the manager is -# -all.manager redirector:1213 - -# The redirector and all cmsd’s export /data red-only with the stage option. The stage -# option requests that if the file isn’t found in the cluster the redirector should send -# the client to a PFC server with enough space to cache the file. -# -all.export /data stage r/o - -# Configuration is different for the redirector, the server cmsd, and -# for the server xrootd. We break those out in the if-else-fi clauses. -# -if redirector - -all.role manager - -# Export with stage option - if the file isn’t found in the cluster the -# redirector sends the client to a PFC server with enough free space. -# - -all.export /data stage r/o - -# Server’s cmsd configuration – all PFC’s are virtual data servers -# - -else if exec cmsd - -all.role server - -# Export with stage option - this tells manager cmsd we can pull files from the origin -# -all.export /data stage r/o - -# The cmsd uses the standard oss plug-in to locate files in the cache. -# oss.localroot directive should be the same as for the server. -# - -oss.localroot /pfc-cache - -# Server’s xrootd configuration – all PFC’s are virtual data servers -# -else - -all.role server - -# For xrootd, load the proxy plugin and the disk caching plugin. -# -ofs.osslib libXrdPss.so -pss.cachelib libFileCache.so - -# The server needs to write to disk, stage not relevant -# -all.export /data rw - - -# Tell the proxy where the data is coming from (arbitrary). -# -pss.origin someserver.domain.org:1094 - -# Tell the PFC’s where the disk cache resides (arbitrary). -# -oss.localroot /pfc-cache - -# Tell the PFC’s available RAM -# -pfc.ram 100g - -fi - diff --git a/packaging/common/xrootd-filecache-standalone.cfg b/packaging/common/xrootd-filecache-standalone.cfg deleted file mode 100644 index 79f7a6923c1..00000000000 --- a/packaging/common/xrootd-filecache-standalone.cfg +++ /dev/null @@ -1,37 +0,0 @@ -########################################################################### -# This is a very simple sample configuration file sufficient to start an # -# xrootd file caching proxy server using the default port 1094. This # -# server runs by itself (stand-alone) and does not assume it is part of a # -# cluster. You can then connect to this server to access files in '/tmp'. # -# Consult the the reference manuals on how to create more complicated # -# configurations. # -# # -# On successful start-up you will see 'initialization completed' in the # -# last message. You can now connect to the xrootd server. # -# # -# Note: You should always create a *single* configuration file for all # -# daemons related to xrootd. # -########################################################################### - -# Allow access to path with given prefix. -# -all.export /test/ - -# Load the proxy plugin and the disk caching plugin. -# -ofs.osslib libXrdPss.so -pss.cachelib libXrdFileCache.so - -# Tell the proxy where the data is coming from (arbitrary). -# -pss.origin source.edu:1094 - -# Specify where the local file system name space is actually rooted. -# -oss.localroot /data/xrd - -# Tell maximum allowed RAM usage. -# -pfc.ram 16g - - diff --git a/packaging/common/xrootd-http.cfg b/packaging/common/xrootd-http.cfg deleted file mode 100644 index 087a7ee99c7..00000000000 --- a/packaging/common/xrootd-http.cfg +++ /dev/null @@ -1,36 +0,0 @@ -########################################################################### -# This is a very simple sample configuration file sufficient to start an # -# xrootd data server using the default port 1094 plus http protocol on # -# port 80. This server runs by itself (stand-alone) and does not assume # -# it is part of a cluster. You can then connect to this server to access # -# files in '/tmp'. Consult the the reference manuals on how to create # -# more complicated configurations and set the host cert and key for http. # -# # -# On successful start-up you will see 'initialization completed' in the # -# last message. You can now connect to the xrootd server. # -# # -# Note: You should always create a *single* configuration file for all # -# daemons related to xrootd. # -########################################################################### - -# The export directive indicates which paths are to be exported. While the -# default is '/tmp', we indicate it anyway to show you this directive. -# -all.export /tmp - -# The adminpath and pidpath variables indicate where the pid and various -# IPC files should be placed -# -all.adminpath /var/spool/xrootd -all.pidpath /var/run/xrootd - -# Load the http protocol, indicate that it should be seved on port 80. -# The socket bound to port 80 has to be preallocated by the systemd -# xrdhttp.socket (requires systemd!). -# -# In order to enable the xrdhttp.socket run: -# systemd enable xrdhttp.socket -# In order to start the xrdhttp.socket run: -# systemd start xrdhttp.socket -# -xrd.protocol XrdHttp:80 /usr/lib64/libXrdHttp-4.so diff --git a/packaging/common/xrootd-standalone.cfg b/packaging/common/xrootd-standalone.cfg deleted file mode 100644 index 7837b22b9ab..00000000000 --- a/packaging/common/xrootd-standalone.cfg +++ /dev/null @@ -1,25 +0,0 @@ -########################################################################### -# This is a very simple sample configuration file sufficient to start an # -# xrootd data server using the default port 1094. This server runs by # -# itself (stand-alone) and does not assume it is part of a cluster. You # -# can then connect to this server to access files in '/tmp'. # -# Consult the the reference manuals on how to create more complicated # -# configurations. # -# # -# On successful start-up you will see 'initialization completed' in the # -# last message. You can now connect to the xrootd server. # -# # -# Note: You should always create a *single* configuration file for all # -# daemons related to xrootd. # -########################################################################### - -# The export directive indicates which paths are to be exported. While the -# default is '/tmp', we indicate it anyway to show you this directive. -# -all.export /tmp - -# The adminpath and pidpath variables indicate where the pid and various -# IPC files should be placed -# -all.adminpath /var/spool/xrootd -all.pidpath /var/run/xrootd diff --git a/packaging/common/xrootd.logrotate b/packaging/common/xrootd.logrotate deleted file mode 100644 index 91babfff6a0..00000000000 --- a/packaging/common/xrootd.logrotate +++ /dev/null @@ -1,28 +0,0 @@ -/var/log/xrootd/*/*.log /var/log/xrootd/*.log -{ - # if the xrootd log rotate is enabled don't do anything - # (by convention if xrootd log rotate is enabled the - # .lock file exists) - prerotate - LOCK=`dirname $1`/.lock - if [ -f $LOCK ]; then - exit 1 - else - exit 0 - fi - endscript - dateext - missingok - nomail - nocreate - rotate 100 - notifempty - daily - compress - postrotate - PIPE=`dirname $1`/.`basename $1` - if [ -p "$PIPE" ]; then - /usr/bin/expect -c "set timeout 2; spawn /bin/sh -c \"echo ping > $PIPE\"; expect timeout { exit 1 } eof { exit 0 }" > /dev/null; - fi - endscript -} diff --git a/packaging/common/xrootd.te b/packaging/common/xrootd.te deleted file mode 100644 index 3f47fad2154..00000000000 --- a/packaging/common/xrootd.te +++ /dev/null @@ -1,22 +0,0 @@ - policy_module(xrootd, 4) - - -#============= logrotate_t ============== -optional_policy(` - - gen_require(` - type logrotate_t; - type var_log_t; - type ptmx_t; - type tmpfs_t; - type devpts_t; - class fifo_file { write open getattr }; - class chr_file { open read write ioctl getattr }; - class filesystem getattr; - ') - allow logrotate_t var_log_t:fifo_file { write open getattr }; - allow logrotate_t ptmx_t:chr_file { open read write ioctl }; - allow logrotate_t tmpfs_t:filesystem getattr; - allow logrotate_t devpts_t:filesystem getattr; - allow logrotate_t devpts_t:chr_file { getattr open read write ioctl }; -') diff --git a/packaging/common/xrootd@.service b/packaging/common/xrootd@.service deleted file mode 100644 index a0db4f718e2..00000000000 --- a/packaging/common/xrootd@.service +++ /dev/null @@ -1,20 +0,0 @@ -[Unit] -Description=XRootD xrootd deamon instance %I -Documentation=man:xrootd(8) -Documentation=http://xrootd.org/docs.html -Requires=network-online.target -After=network-online.target - -[Service] -ExecStart=/usr/bin/xrootd -l /var/log/xrootd/xrootd.log -c /etc/xrootd/xrootd-%i.cfg -k fifo -s /var/run/xrootd/xrootd-%i.pid -n %i -User=xrootd -Group=xrootd -Type=simple -Restart=on-abort -RestartSec=0 -KillMode=control-group -LimitNOFILE=65536 -WorkingDirectory=/var/spool/xrootd - -[Install] -RequiredBy=multi-user.target diff --git a/packaging/common/xrootd@.socket b/packaging/common/xrootd@.socket deleted file mode 100644 index 0ede097e292..00000000000 --- a/packaging/common/xrootd@.socket +++ /dev/null @@ -1,9 +0,0 @@ -[Unit] -Description=XRootD socket - -[Socket] -ListenStream=1094 -Service=xrootd@%i.service - -[Install] -WantedBy=sockets.target diff --git a/packaging/makesrpm.sh b/packaging/makesrpm.sh index 945b88da720..b74b90fd471 100755 --- a/packaging/makesrpm.sh +++ b/packaging/makesrpm.sh @@ -177,7 +177,7 @@ echo "[i] RPM compliant version: $VERSION-$RELEASE" # exit on any error set -e -TEMPDIR=`mktemp -d /tmp/xrootd.srpm.XXXXXXXXXX` +TEMPDIR=`mktemp -d /tmp/xrootd-ceph.srpm.XXXXXXXXXX` RPMSOURCES=$TEMPDIR/rpmbuild/SOURCES mkdir -p $RPMSOURCES mkdir -p $TEMPDIR/rpmbuild/SRPMS @@ -199,12 +199,12 @@ fi #------------------------------------------------------------------------------- # Generate the spec file #------------------------------------------------------------------------------- -if test ! -r rhel/xrootd.spec.in; then +if test ! -r rhel/xrootd-ceph.spec.in; then echo "[!] The specfile template does not exist!" 1>&2 exit 7 fi -cat rhel/xrootd.spec.in | sed "s/__VERSION__/$VERSION/" | \ - sed "s/__RELEASE__/$RELEASE/" > $TEMPDIR/xrootd.spec +cat rhel/xrootd-ceph.spec.in | sed "s/__VERSION__/$VERSION/" | \ + sed "s/__RELEASE__/$RELEASE/" > $TEMPDIR/xrootd-ceph.spec #------------------------------------------------------------------------------- # Make a tarball of the latest commit on the branch @@ -221,33 +221,14 @@ if test $? -ne 0; then exit 5 fi -git archive --prefix=xrootd/ --format=tar $COMMIT | gzip -9fn > \ - $RPMSOURCES/xrootd.tar.gz +git archive --prefix=xrootd-ceph/ --format=tar $COMMIT | gzip -9fn > \ + $RPMSOURCES/xrootd-ceph.tar.gz if test $? -ne 0; then echo "[!] Unable to create the source tarball" 1>&2 exit 6 fi -#------------------------------------------------------------------------------- -# Check if we need some other versions -#------------------------------------------------------------------------------- -OTHER_VERSIONS=`cat $TEMPDIR/xrootd.spec | \ - egrep '^Source[0-9]+:[[:space:]]*xrootd-.*.gz$' |\ - awk '{ print $2; }'` - -for VER in $OTHER_VERSIONS; do - VER=${VER/xrootd-/} - VER=${VER/.tar.gz/} - - git archive --prefix=xrootd-$VER/ --format=tar v$VER | gzip -9fn > \ - $RPMSOURCES/xrootd-$VER.tar.gz - - if test $? -ne 0; then - echo "[!] Unable to create the source tarball" 1>&2 - exit 9 - fi -done cd $CWD #------------------------------------------------------------------------------- @@ -263,13 +244,13 @@ eval "rpmbuild --define \"_topdir $TEMPDIR/rpmbuild\" \ --define \"_source_filedigest_algorithm md5\" \ --define \"_binary_filedigest_algorithm md5\" \ ${USER_DEFINE} \ - -bs $TEMPDIR/xrootd.spec > $TEMPDIR/log" + -bs $TEMPDIR/xrootd-ceph.spec > $TEMPDIR/log" if test $? -ne 0; then echo "[!] RPM creation failed" 1>&2 exit 8 fi -cp $TEMPDIR/rpmbuild/SRPMS/xrootd*.src.rpm $OUTPUTPATH +cp $TEMPDIR/rpmbuild/SRPMS/xrootd-ceph*.src.rpm $OUTPUTPATH rm -rf $TEMPDIR echo "[i] Done." diff --git a/packaging/rhel/cmsd.init b/packaging/rhel/cmsd.init deleted file mode 100644 index 3773655c685..00000000000 --- a/packaging/rhel/cmsd.init +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/sh -# -# /etc/init.d/cmsd - Start/stop the cmsd service -# -# The following two lines allow this script to be managed by Fedora's -# chkconfig program. -# -# chkconfig: - 80 30 -# description: cmsd is a manager for the xrootd cluster file system. - -# Source function library. -. /etc/rc.d/init.d/xrootd.functions - -if [ -e /etc/sysconfig/xrootd ]; then - . /etc/sysconfig/xrootd -fi - -COMMAND=$1 -shift - -case "$COMMAND" in - 'start') - handleDaemons start cmsd $@ - ;; - 'stop') - handleDaemons stop cmsd $@ - ;; - 'status') - handleDaemons status cmsd $@ - ;; - 'reload' | 'restart') - handleDaemons stop cmsd $@ - handleDaemons start cmsd $@ - ;; - 'condrestart') - handleDaemons condrestart cmsd $@ - ;; - *) - echo "usage: $0 {start|stop|status|restart|condrestart}" - ;; -esac - -exit $? diff --git a/packaging/rhel/frm_purged.init b/packaging/rhel/frm_purged.init deleted file mode 100644 index 59a8e834d8e..00000000000 --- a/packaging/rhel/frm_purged.init +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/sh -# -# /etc/init.d/frm_purged - Start/stop the frm_purged service -# -# The following two lines allow this script to be managed by Fedora's -# chkconfig program. -# -# chkconfig: - 80 30 -# description: frm_purged manages cache eviction in the xrootd system. - -# Source function library. -. /etc/rc.d/init.d/xrootd.functions - -if [ -e /etc/sysconfig/xrootd ]; then - . /etc/sysconfig/xrootd -fi - -COMMAND=$1 -shift - -case "$COMMAND" in - 'start') - handleDaemons start frm_purged $@ - ;; - 'stop') - handleDaemons stop frm_purged $@ - ;; - 'status') - handleDaemons status frm_purged $@ - ;; - 'reload' | 'restart') - handleDaemons stop frm_purged $@ - handleDaemons start frm_purged $@ - ;; - 'condrestart') - handleDaemons condrestart frm_purged $@ - ;; - *) - echo "usage: $0 {start|stop|status|restart|condrestart}" - ;; -esac - -exit $? diff --git a/packaging/rhel/frm_xfrd.init b/packaging/rhel/frm_xfrd.init deleted file mode 100644 index cd37494f1e6..00000000000 --- a/packaging/rhel/frm_xfrd.init +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/sh -# -# /etc/init.d/frm_xfrd - Start/stop the frm_xfrd service -# -# The following two lines allow this script to be managed by Fedora's -# chkconfig program. -# -# chkconfig: - 80 30 -# description: frm_xfrd is a caching daemon for the xrootd system. - -# Source function library. -. /etc/rc.d/init.d/xrootd.functions - -if [ -e /etc/sysconfig/xrootd ]; then - . /etc/sysconfig/xrootd -fi - -COMMAND=$1 -shift - -case "$COMMAND" in - 'start') - handleDaemons start frm_xfrd $@ - ;; - 'stop') - handleDaemons stop frm_xfrd $@ - ;; - 'status') - handleDaemons status frm_xfrd $@ - ;; - 'reload' | 'restart') - handleDaemons stop frm_xfrd $@ - handleDaemons start frm_xfrd $@ - ;; - 'condrestart') - handleDaemons condrestart frm_xfrd $@ - ;; - *) - echo "usage: $0 {start|stop|status|restart|condrestart}" - ;; -esac - -exit $? diff --git a/packaging/rhel/xrootd-ceph.spec.in b/packaging/rhel/xrootd-ceph.spec.in new file mode 100644 index 00000000000..f080da293a3 --- /dev/null +++ b/packaging/rhel/xrootd-ceph.spec.in @@ -0,0 +1,147 @@ +#------------------------------------------------------------------------------- +# Helper macros +#------------------------------------------------------------------------------- +%if %{?rhel:1}%{!?rhel:0} + %if %{rhel} >= 7 + %define use_systemd 1 + %else + %define use_systemd 0 + %endif +%else + %if %{?fedora}%{!?fedora:0} >= 19 + %define use_systemd 1 + %else + %define use_systemd 0 + %endif +%endif + +%if %{?fedora}%{!?fedora:0} >= 22 + %define use_libc_semaphore 1 +%else + %define use_libc_semaphore 0 +%endif + +%if %{?_with_ceph11:1}%{!?_with_ceph11:0} + %define _with_ceph 1 +%endif + +#------------------------------------------------------------------------------- +# Package definitions +#------------------------------------------------------------------------------- +Name: xrootd-ceph +Epoch: 1 +Version: __VERSION__ +Release: __RELEASE__%{?dist}%{?_with_clang:.clang} +Summary: CEPH plug-in for XRootD +Group: System Environment/Daemons +License: LGPLv3+ +URL: http://xrootd.org/ + +# git clone http://xrootd.org/repo/xrootd.git xrootd +# cd xrootd +# git-archive master | gzip -9 > ~/rpmbuild/SOURCES/xrootd.tgz +Source0: xrootd-ceph.tar.gz + +BuildRoot: %{_tmppath}/%{name}-root + +BuildRequires: cmake + +%if %{?_with_tests:1}%{!?_with_tests:0} +BuildRequires: cppunit-devel +%endif + +BuildRequires: librados-devel >= 11.0 +BuildRequires: libradosstriper-devel >= 11.0 + +%if %{?_with_clang:1}%{!?_with_clang:0} +BuildRequires: clang +%endif + +BuildRequires: xrootd-server-devel >= %{version}-%{release} +BuildRequires: xrootd-private-devel >= %{version}-%{release} + +%description +The xrootd-ceph is an OSS layer plug-in for the XRootD server for interfacing +with the Ceph storage platform. + +#------------------------------------------------------------------------------- +# tests +#------------------------------------------------------------------------------- +%if %{?_with_tests:1}%{!?_with_tests:0} +%package tests +Summary: CPPUnit tests +Group: Development/Tools +Requires: %{name}-client = %{epoch}:%{version}-%{release} +%description tests +This package contains a set of CPPUnit tests for xrootd. +%endif + +#------------------------------------------------------------------------------- +# Build instructions +#------------------------------------------------------------------------------- +%prep +%setup -c -n xrootd-ceph + +%build +cd xrootd-ceph + +%if %{?_with_clang:1}%{!?_with_clang:0} +export CC=clang +export CXX=clang++ +%endif + +mkdir build +pushd build +cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=RelWithDebInfo \ +%if %{?_with_tests:1}%{!?_with_tests:0} + -DENABLE_TESTS=TRUE \ +%else + -DENABLE_TESTS=FALSE \ +%endif + ../ + +make -i VERBOSE=1 %{?_smp_mflags} +popd + +#------------------------------------------------------------------------------- +# Installation +#------------------------------------------------------------------------------- +%install +rm -rf $RPM_BUILD_ROOT + +#------------------------------------------------------------------------------- +# Install 4.x.y +#------------------------------------------------------------------------------- +pushd xrootd-ceph +pushd build +make install DESTDIR=$RPM_BUILD_ROOT +popd + +# ceph posix unversioned so +rm -f $RPM_BUILD_ROOT%{_libdir}/libXrdCephPosix.so + + +%clean +rm -rf $RPM_BUILD_ROOT + +#------------------------------------------------------------------------------- +# Files +#------------------------------------------------------------------------------- +%files +%defattr(-,root,root,-) +%{_libdir}/libXrdCeph-4.so +%{_libdir}/libXrdCephXattr-4.so +%{_libdir}/libXrdCephPosix.so* + +%if %{?_with_tests:1}%{!?_with_tests:0} +%files tests +%defattr(-,root,root,-) +%{_libdir}/libXrdCephTests*.so +%endif + +#------------------------------------------------------------------------------- +# Changelog +#------------------------------------------------------------------------------- +%changelog +* Thu Mar 08 2018 Michal Simon +- initial release diff --git a/packaging/rhel/xrootd.functions b/packaging/rhel/xrootd.functions deleted file mode 100644 index f502cb15dd3..00000000000 --- a/packaging/rhel/xrootd.functions +++ /dev/null @@ -1,305 +0,0 @@ -#------------------------------------------------------------------------------- -# Library for handling xrootd daemons on RHEL -# Author: Lukasz Janyst (09.03.2011) -#------------------------------------------------------------------------------- - -. /etc/rc.d/init.d/functions - -#------------------------------------------------------------------------------- -# Add the user accounts if needed and set up the directory ownership -#------------------------------------------------------------------------------- -function setupInstallation() -{ - XROOTD_USER=$1 - XROOTD_GROUP=$2 - getent group $XROOTD_GROUP >/dev/null || groupadd -r $XROOTD_GROUP - getent passwd $XROOTD_USER >/dev/null || \ - useradd -r -g $XROOTD_GROUP -c "XRootD runtime user" \ - -s /sbin/nologin -d /var/spool/xrootd $XROOTD_USER - - chown $XROOTD_USER:$XROOTD_GROUP -R /var/spool/xrootd - chown $XROOTD_USER:$XROOTD_GROUP -R /var/log/xrootd -} - -#------------------------------------------------------------------------------- -# Check if a file has the right permissions -#------------------------------------------------------------------------------- -function checkFile() -{ - FILE=$1 - XROOTD_USER=$2 - XROOTD_GROUP=$3 - - if test x"`stat -c %U $FILE`" != x$XROOTD_USER; then - echo "$FILE has wrong user ownership: \"`stat -c %U $FILE`\" instead of \"$XROOTD_USER\"" - return 1 - fi -} - -#------------------------------------------------------------------------------- -# Check a directory and it's permissions -#------------------------------------------------------------------------------- -function checkDirectory() -{ - DIRECTORY=$1 - XROOTD_USER=$2 - XROOTD_GROUP=$3 - - if test ! -d $DIRECTORY; then - echo "Directory: $DIRECTORY does not exist" - return 1 - fi - - for i in `find $DIRECTORY`; do - checkFile $i $XROOTD_USER $XROOTD_GROUP - if test $? -ne 0; then - return 1 - fi - done - return 0 -} - -#------------------------------------------------------------------------------- -# Check if the installation is in a sane state -#------------------------------------------------------------------------------- -function checkSanity() -{ - XROOTD_USER=$1 - XROOTD_GROUP=$2 - - #----------------------------------------------------------------------------- - # Check if the user account exist - #----------------------------------------------------------------------------- - getent passwd $XROOTD_USER >/dev/null - if test $? -ne 0; then - echo "User account for: $XROOTD_USER doesn't exist" - return 1 - fi - - getent group $XROOTD_GROUP >/dev/null - if test $? -ne 0; then - echo "Group account for: $XROOTD_GROUP doesn't exist" - return 2 - fi - - #----------------------------------------------------------------------------- - # We need these directories to be owned by the xroot user for the init - # scripts to work properly, and we can safely change the ownership if - # it is wrong. - #----------------------------------------------------------------------------- - checkDirectory /var/spool/xrootd $XROOTD_USER $XROOTD_GROUP - if test $? -ne 0; then - chown $XROOTD_USER:$XROOTD_GROUP -R /var/spool/xrootd - fi - - mkdir -p /var/run/xrootd - chown $XROOTD_USER:$XROOTD_GROUP -R /var/run/xrootd - checkDirectory /var/run/xrootd $XROOTD_USER $XROOTD_GROUP - if test $? -ne 0; then - chown $XROOTD_USER:$XROOTD_GROUP -R /var/run/xrootd - fi -} - -#------------------------------------------------------------------------------- -# Start a daemon -#------------------------------------------------------------------------------- -function startDaemon() -{ - ulimit -n 65536 - - DAEMON=$1 - EX=$2 - XROOTD_USER=$3 - XROOTD_GROUP=$4 - INSTANCE=$5 - PIDFILE="/var/run/xrootd/$DAEMON-$INSTANCE.pid" - - # check sanity of the installation - checkSanity $XROOTD_USER $XROOTD_GROUP - if test $? -ne 0; then - echo "Please run: service xrootd setup" - return 1 - fi - - echo -n "Starting xrootd ($DAEMON, $INSTANCE): " - shift 5 - - # useful for storing coredumps - cd /var/spool/xrootd - daemon --user $XROOTD_USER --pidfile $PIDFILE $EX $@ -b -s $PIDFILE -n $INSTANCE - RETVAL=$? - echo - return $RETVAL -} - -#------------------------------------------------------------------------------- -# Stop a daemon -#------------------------------------------------------------------------------- -function stopDaemon() -{ - echo -n "Shutting down xrootd ($1, $5): " - PIDFILE="/var/run/xrootd/$1-$5.pid" - if [ -e $PIDFILE ]; then - killproc -p $PIDFILE $1 - RETVAL=$? - echo - return $RETVAL - fi - echo -n "$1-$5 not running" - echo - return 0 -} - -#------------------------------------------------------------------------------- -# Get the status of a daemon -#------------------------------------------------------------------------------- -function statusOfTheDaemon() -{ - PIDFILE="/var/run/xrootd/$1-$5.pid" - echo -n "[$5] " - status -p $PIDFILE $1 - return $RETVAL -} - -#------------------------------------------------------------------------------- -# Conditionally restart a daemon -#------------------------------------------------------------------------------- -function condrestartDaemon() -{ - PIDFILE="/var/run/xrootd/$1-$5.pid" - status -p $PIDFILE $1 > /dev/null - if test $? -ne 0; then - return 0 - fi - - stopDaemon $@ - if test $? -ne 0; then - return 1 - fi - - startDaemon $@ - if test $? -ne 0; then - return 2 - fi - - return 0 -} - -#------------------------------------------------------------------------------- -# Do things to daemons -#------------------------------------------------------------------------------- -function handleDaemons() -{ - #----------------------------------------------------------------------------- - # Check if the user account is specified - #----------------------------------------------------------------------------- - if test x"$XROOTD_USER" = x; then - XROOTD_USER="daemon" - fi - - if test x"$XROOTD_GROUP" = x; then - XROOTD_GROUP="daemon" - fi - - #----------------------------------------------------------------------------- - # Determine the command to be run - #----------------------------------------------------------------------------- - COMMAND=$1; - shift - case "$COMMAND" in - 'start') - CMD_HANDLER=startDaemon - ;; - 'stop') - CMD_HANDLER=stopDaemon - ;; - 'status') - CMD_HANDLER=statusOfTheDaemon - ;; - 'condrestart') - CMD_HANDLER=condrestartDaemon - ;; - 'setup') - setupInstallation $XROOTD_USER $XROOTD_GROUP - return $? - ;; - *) - echo "Unrecognized command: $COMMAND" - return 1 - ;; - esac - - #----------------------------------------------------------------------------- - # Select the daemon to be started - #----------------------------------------------------------------------------- - DAEMON=$1; - shift - - case "$DAEMON" in - 'xrootd' | 'cmsd') - EXEC=/usr/bin/$DAEMON - CONFIG_NAME=`echo $DAEMON | tr '[[:lower:]]' '[[:upper:]]'` - ;; - 'frm_purged') - EXEC=/usr/bin/$DAEMON - CONFIG_NAME=PURD - ;; - 'frm_xfrd') - EXEC=/usr/bin/$DAEMON - CONFIG_NAME=XFRD - ;; - *) - echo "Unrecognized daemon: $DAEMON" - return 1 - ;; - esac - - #----------------------------------------------------------------------------- - # Select the instances to run - #----------------------------------------------------------------------------- - if test $# -eq 0; then - eval INSTANCES=\$${CONFIG_NAME}_INSTANCES - else - INSTANCES=$@ - fi - - eval INSTANCES_XROOTD3=\$${CONFIG_NAME}3_INSTANCES - - INSTANCES=`echo $INSTANCES | tr '[[:upper:]]' '[[:lower:]]'` - INSTANCES_XROOTD3=`echo $INSTANCES_XROOTD3 | tr '[[:upper:]]' '[[:lower:]]'` - - #----------------------------------------------------------------------------- - # Exec the command on the instances - #----------------------------------------------------------------------------- - STATUS=0 - for INSTANCE in $INSTANCES; do - INSTANCE_UPPER=`echo $INSTANCE | tr '[[:lower:]]' '[[:upper:]]'` - eval OPTS=\$${CONFIG_NAME}_${INSTANCE_UPPER}_OPTIONS - - #--------------------------------------------------------------------------- - # Check if the instance is xrootd3 or xrootd4, if a xrootd3 binary is - # installed - #--------------------------------------------------------------------------- - EXEC_RUN=$EXEC - if test -x $EXEC-3; then - for INS in $INSTANCES_XROOTD3; do - if test $INS = $INSTANCE; then - EXEC_RUN=$EXEC-3 - fi - done - fi - - #--------------------------------------------------------------------------- - # Run the instance - #--------------------------------------------------------------------------- - if test x"$OPTS" = x; then - continue - fi - $CMD_HANDLER $DAEMON $EXEC_RUN "$XROOTD_USER" "$XROOTD_GROUP" $INSTANCE "$OPTS" - STATUS=$? - if test $? -ne 0 && "$CMD_HANDLER" != "statusOfTheDaemon"; then - STATUS=1 - fi - done - return $STATUS -} diff --git a/packaging/rhel/xrootd.functions-slc4 b/packaging/rhel/xrootd.functions-slc4 deleted file mode 100644 index ce870a2a50e..00000000000 --- a/packaging/rhel/xrootd.functions-slc4 +++ /dev/null @@ -1,301 +0,0 @@ -#------------------------------------------------------------------------------- -# Library for handling xrootd daemons on antique RHEL -# Author: Lukasz Janyst (18.03.2011) -#------------------------------------------------------------------------------- - -. /etc/rc.d/init.d/functions - -#------------------------------------------------------------------------------- -# Add the user accounts if needed and set up the directory ownership -#------------------------------------------------------------------------------- -function setupInstallation() -{ - XROOTD_USER=$1 - XROOTD_GROUP=$2 - getent group $XROOTD_GROUP >/dev/null || groupadd -r $XROOTD_GROUP - getent passwd $XROOTD_USER >/dev/null || \ - useradd -r -g $XROOTD_GROUP -c "XRootD runtime user" \ - -s /sbin/nologin -d /var/spool/xrootd $XROOTD_USER - - chown $XROOTD_USER:$XROOTD_GROUP -R /var/spool/xrootd - chown $XROOTD_USER:$XROOTD_GROUP -R /var/log/xrootd -} - -#------------------------------------------------------------------------------- -# Check if a file has the right permissions -#------------------------------------------------------------------------------- -function checkFile() -{ - FILE=$1 - XROOTD_USER=$2 - XROOTD_GROUP=$3 - - if test x"`stat -c %U $FILE`" != x$XROOTD_USER; then - echo "$FILE has wrong user ownership: \"`stat -c %U $FILE`\" instead of \"$XROOTD_USER\"" - return 1 - fi -} - -#------------------------------------------------------------------------------- -# Check a directory and it's permissions -#------------------------------------------------------------------------------- -function checkDirectory() -{ - DIRECTORY=$1 - XROOTD_USER=$2 - XROOTD_GROUP=$3 - - if test ! -d $DIRECTORY; then - echo "Directory: $DIRECTORY does not exist" - return 1 - fi - - for i in `find $DIRECTORY`; do - checkFile $i $XROOTD_USER $XROOTD_GROUP - if test $? -ne 0; then - return 1 - fi - done - return 0 -} - -#------------------------------------------------------------------------------- -# Check if the installation is in a sane state -#------------------------------------------------------------------------------- -function checkSanity() -{ - XROOTD_USER=$1 - XROOTD_GROUP=$2 - - #----------------------------------------------------------------------------- - # Check if the user account exist - #----------------------------------------------------------------------------- - getent passwd $XROOTD_USER >/dev/null - if test $? -ne 0; then - echo "User account for: $XROOTD_USER doesn't exist" - return 1 - fi - - getent group $XROOTD_GROUP >/dev/null - if test $? -ne 0; then - echo "Group account for: $XROOTD_GROUP doesn't exist" - return 2 - fi - - #----------------------------------------------------------------------------- - # We need these directories to be owned by the xroot user for the init - # scripts to work properly, and we can safely change the ownership if - # it is wrong. - #----------------------------------------------------------------------------- - checkDirectory /var/spool/xrootd $XROOTD_USER $XROOTD_GROUP - if test $? -ne 0; then - chown $XROOTD_USER:$XROOTD_GROUP -R /var/spool/xrootd - fi - - mkdir -p /var/run/xrootd - chown $XROOTD_USER:$XROOTD_GROUP -R /var/run/xrootd - checkDirectory /var/run/xrootd $XROOTD_USER $XROOTD_GROUP - if test $? -ne 0; then - chown $XROOTD_USER:$XROOTD_GROUP -R /var/run/xrootd - fi -} - -#------------------------------------------------------------------------------- -# Start a daemon -#------------------------------------------------------------------------------- -function startDaemon() -{ - ulimit -n 65536 - DAEMON=$1 - EXEC=$2 - XROOTD_USER=$3 - XROOTD_GROUP=$4 - INSTANCE=$5 - PIDFILE="/var/run/xrootd/$DAEMON-$INSTANCE.pid" - - # check sanity of the installation - checkSanity $XROOTD_USER $XROOTD_GROUP - if test $? -ne 0; then - echo "Please run: service xrootd setup" - return 1 - fi - - echo -n "Starting xrootd ($DAEMON, $INSTANCE): " - statusOfTheDaemon $@ > /dev/null - if test $? -ne 0; then - shift 5 - - # change the CWD to have some room for core dumps - cd /var/spool/xrootd - daemon --user $XROOTD_USER $EXEC $@ -b -s $PIDFILE -n $INSTANCE - fi - RETVAL=$? - echo - return $RETVAL -} - -#------------------------------------------------------------------------------- -# Stop a daemon -#------------------------------------------------------------------------------- -function stopDaemon() -{ - echo -n "Shutting down xrootd ($1, $5): " - PIDFILE="/var/run/xrootd/$1-$5.pid" - - if test -r "$PIDFILE"; then - PID=`cat "$PIDFILE"` - INST=`ps aux | grep $2 | grep $PID | grep $5` - if test x"$INST" != x; then - kill -TERM "$PID" && success || failure - rm -f $PIDFILE - else - failure - fi - else - failure - fi - RETVAL=$? - echo - return $RETVAL -} - -#------------------------------------------------------------------------------- -# Get the status of a daemon -#------------------------------------------------------------------------------- -function statusOfTheDaemon() -{ - PIDFILE="/var/run/xrootd/$1-$5.pid" - echo -n "[$5] " - - if test -r "$PIDFILE"; then - PID=`cat $PIDFILE` - INST=`ps aux | grep $2 | grep $PID | grep $5` - if test x"$INST" != x; then - echo "$1 (pid $PID) is running..." - return 0 - else - echo "$1 is stopped" - return 1 - fi - else - echo "$1 is stopped" - return 1 - fi - return 0 -} - -#------------------------------------------------------------------------------- -# Conditionally restart a daemon -#------------------------------------------------------------------------------- -function condrestartDaemon() -{ - statusOfTheDaemon $@ > /dev/null - if test $? -ne 0; then - return 0 - fi - - stopDaemon $@ - if test $? -ne 0; then - return 1 - fi - - startDaemon $@ - if test $? -ne 0; then - return 2 - fi - - return 0 -} - -#------------------------------------------------------------------------------- -# Do things to daemons -#------------------------------------------------------------------------------- -function handleDaemons() -{ - #----------------------------------------------------------------------------- - # Check if the user account is specified - #----------------------------------------------------------------------------- - if test x"$XROOTD_USER" = x; then - XROOTD_USER="daemon" - fi - - if test x"$XROOTD_GROUP" = x; then - XROOTD_GROUP="daemon" - fi - - #----------------------------------------------------------------------------- - # Determine the command to be run - #----------------------------------------------------------------------------- - COMMAND=$1; - shift - case "$COMMAND" in - 'start') - CMD_HANDLER=startDaemon - ;; - 'stop') - CMD_HANDLER=stopDaemon - ;; - 'status') - CMD_HANDLER=statusOfTheDaemon - ;; - 'condrestart') - CMD_HANDLER=condrestartDaemon - ;; - 'setup') - setupInstallation $XROOTD_USER $XROOTD_GROUP - return $? - ;; - *) - echo "Unrecognized command: $COMMAND" - return 1 - ;; - esac - - #----------------------------------------------------------------------------- - # Select the daemon to be started - #----------------------------------------------------------------------------- - DAEMON=$1; - shift - - case "$DAEMON" in - 'xrootd' | 'cmsd') - EXEC=/usr/bin/$DAEMON - CONFIG_NAME=`echo $DAEMON | tr '[[:lower:]]' '[[:upper:]]'` - ;; - 'frm_purged' | 'frm_xfrd') - EXEC=/usr/bin/$DAEMON - CONFIG_NAME=FRMD - ;; - *) - echo "Unrecognized daemon: $DAEMON" - return 1 - ;; - esac - - #----------------------------------------------------------------------------- - # Select the instances to run - #----------------------------------------------------------------------------- - if test $# -eq 0; then - eval INSTANCES=\$${CONFIG_NAME}_INSTANCES - else - INSTANCES=$@ - fi - INSTANCES=`echo $INSTANCES | tr '[[:upper:]]' '[[:lower:]]'` - - #----------------------------------------------------------------------------- - # Exec the command on the instances - #----------------------------------------------------------------------------- - STATUS=0 - for INSTANCE in $INSTANCES; do - INSTANCE_UPPER=`echo $INSTANCE | tr '[[:lower:]]' '[[:upper:]]'` - eval OPTS=\$${CONFIG_NAME}_${INSTANCE_UPPER}_OPTIONS - if test x"$OPTS" = x; then - continue - fi - $CMD_HANDLER $DAEMON $EXEC "$XROOTD_USER" "$XROOTD_GROUP" $INSTANCE "$OPTS" - if test $? -ne 0; then - STATUS=1 - fi - done - return $STATUS -} diff --git a/packaging/rhel/xrootd.init b/packaging/rhel/xrootd.init deleted file mode 100644 index 53d8a261498..00000000000 --- a/packaging/rhel/xrootd.init +++ /dev/null @@ -1,47 +0,0 @@ -#!/bin/sh -# -# /etc/init.d/xrootd - Start/stop the xrootd services -# -# The following two lines allow this script to be managed by Fedora's -# chkconfig program. -# -# chkconfig: - 80 30 -# description: xrootd is a cluster file system. - -# Source function library. -. /etc/rc.d/init.d/xrootd.functions - -if [ -e /etc/sysconfig/xrootd ]; then - . /etc/sysconfig/xrootd -fi - -COMMAND=$1 -shift - -case "$COMMAND" in - 'start') - handleDaemons start xrootd $@ - ;; - 'stop') - handleDaemons stop xrootd $@ - ;; - 'status') - handleDaemons status xrootd $@ - ;; - 'reload' | 'restart') - handleDaemons stop xrootd $@ - handleDaemons start xrootd $@ - ;; - 'condrestart') - handleDaemons condrestart xrootd $@ - ;; - 'setup') - handleDaemons setup xrootd - ;; - *) - echo "usage: $0 {start|stop|status|restart|condrestart|setup}" - ;; -esac - -exit $? - diff --git a/packaging/rhel/xrootd.spec.in b/packaging/rhel/xrootd.spec.in deleted file mode 100644 index f90fc483a40..00000000000 --- a/packaging/rhel/xrootd.spec.in +++ /dev/null @@ -1,944 +0,0 @@ -#------------------------------------------------------------------------------- -# Helper macros -#------------------------------------------------------------------------------- -%if %{?rhel:1}%{!?rhel:0} - %if %{rhel} >= 7 - %define use_systemd 1 - %else - %define use_systemd 0 - %endif -%else - %if %{?fedora}%{!?fedora:0} >= 19 - %define use_systemd 1 - %else - %define use_systemd 0 - %endif -%endif - -%if %{?fedora}%{!?fedora:0} >= 22 - %define use_libc_semaphore 1 -%else - %define use_libc_semaphore 0 -%endif - -%if %{?_with_ceph11:1}%{!?_with_ceph11:0} - %define _with_ceph 1 -%endif - -# Remove default rpm python bytecompiling scripts -%global __os_install_post \ - %(echo '%{__os_install_post}' | \ - sed -e 's!/usr/lib[^[:space:]]*/brp-python-bytecompile[[:space:]].*$!!g \ - s!/usr/lib[^[:space:]]*/brp-python-hardlink[[:space:]].*$!!g') - -#------------------------------------------------------------------------------- -# Package definitions -#------------------------------------------------------------------------------- -Name: xrootd -Epoch: 1 -Version: __VERSION__ -Release: __RELEASE__%{?dist}%{?_with_clang:.clang} -Summary: Extended ROOT file server -Group: System Environment/Daemons -License: LGPLv3+ -URL: http://xrootd.org/ - -# git clone http://xrootd.org/repo/xrootd.git xrootd -# cd xrootd -# git-archive master | gzip -9 > ~/rpmbuild/SOURCES/xrootd.tgz -Source0: xrootd.tar.gz - -%if %{?_with_compat:1}%{!?_with_compat:0} -Source1: xrootd-3.3.6.tar.gz -%endif - -BuildRoot: %{_tmppath}/%{name}-root - -BuildRequires: cmake -BuildRequires: krb5-devel -BuildRequires: readline-devel -BuildRequires: fuse-devel -BuildRequires: libxml2-devel -BuildRequires: krb5-devel -BuildRequires: zlib-devel -BuildRequires: ncurses-devel - -BuildRequires: python2-devel -%if %{?fedora}%{!?fedora:0} >= 13 -BuildRequires: python3-devel -%else -BuildRequires: python34-devel -%endif - -BuildRequires: openssl-devel - -BuildRequires: selinux-policy-devel - -%if %{?_with_tests:1}%{!?_with_tests:0} -BuildRequires: cppunit-devel -%endif - -%if %{?_with_ceph:1}%{!?_with_ceph:0} - %if %{?_with_ceph11:1}%{!?_with_ceph11:0} -BuildRequires: librados-devel >= 11.0 -BuildRequires: libradosstriper-devel >= 11.0 - %else -BuildRequires: ceph-devel >= 0.87 - %endif -%endif - -BuildRequires: doxygen -BuildRequires: graphviz -%if %{?rhel}%{!?rhel:0} == 5 -BuildRequires: graphviz-gd -%endif - -%if %{?_with_clang:1}%{!?_with_clang:0} -BuildRequires: clang -%endif - -Requires: %{name}-server%{?_isa} = %{epoch}:%{version}-%{release} -Requires: %{name}-selinux = %{epoch}:%{version}-%{release} - -%if %{use_systemd} -BuildRequires: systemd -BuildRequires: systemd-devel -Requires(pre): systemd -Requires(post): systemd -Requires(preun): systemd -Requires(postun): systemd -%else -Requires(pre): shadow-utils -Requires(pre): chkconfig -Requires(post): chkconfig -Requires(preun): chkconfig -Requires(preun): initscripts -Requires(postun): initscripts -%endif - -%description -The Extended root file server consists of a file server called xrootd -and a cluster management server called cmsd. - -The xrootd server was developed for the root analysis framework to -serve root files. However, the server is agnostic to file types and -provides POSIX-like access to any type of file. - -The cmsd server is the next generation version of the olbd server, -originally developed to cluster and load balance Objectivity/DB AMS -database servers. It provides enhanced capability along with lower -latency and increased throughput. - -#------------------------------------------------------------------------------- -# libs -#------------------------------------------------------------------------------- -%package libs -Summary: Libraries used by xrootd servers and clients -Group: System Environment/Libraries - -%description libs -This package contains libraries used by the xrootd servers and clients. - -#------------------------------------------------------------------------------- -# devel -#------------------------------------------------------------------------------ -%package devel -Summary: Development files for xrootd -Group: Development/Libraries -Requires: %{name}-libs%{?_isa} = %{epoch}:%{version}-%{release} - -%description devel -This package contains header files and development libraries for xrootd -development. - -#------------------------------------------------------------------------------- -# client-libs -#------------------------------------------------------------------------------- -%package client-libs -Summary: Libraries used by xrootd clients -Group: System Environment/Libraries -Requires: %{name}-libs%{?_isa} = %{epoch}:%{version}-%{release} -%if %{use_libc_semaphore} -Requires: glibc >= 2.21 -%endif - -%description client-libs -This package contains libraries used by xrootd clients. - -#------------------------------------------------------------------------------- -# client-devel -#------------------------------------------------------------------------------- -%package client-devel -Summary: Development files for xrootd clients -Group: Development/Libraries -Requires: %{name}-devel%{?_isa} = %{epoch}:%{version}-%{release} -Requires: %{name}-client-libs%{?_isa} = %{epoch}:%{version}-%{release} - -%description client-devel -This package contains header files and development libraries for xrootd -client development. - -#------------------------------------------------------------------------------- -# server-libs -#------------------------------------------------------------------------------- -%package server-libs -Summary: Libraries used by xrootd servers -Group: System Environment/Libraries -Requires: %{name}-libs%{?_isa} = %{epoch}:%{version}-%{release} -Requires: %{name}-client-libs%{?_isa} = %{epoch}:%{version}-%{release} - -%description server-libs -This package contains libraries used by xrootd servers. - -#------------------------------------------------------------------------------- -# server-devel -#------------------------------------------------------------------------------- -%package server-devel -Summary: Development files for xrootd servers -Group: Development/Libraries -Requires: %{name}-devel%{?_isa} = %{epoch}:%{version}-%{release} -Requires: %{name}-client-devel%{?_isa} = %{epoch}:%{version}-%{release} -Requires: %{name}-server-libs%{?_isa} = %{epoch}:%{version}-%{release} - -%description server-devel -This package contains header files and development libraries for xrootd -server development. - -#------------------------------------------------------------------------------- -# private devel -#------------------------------------------------------------------------------- -%package private-devel -Summary: Legacy xrootd headers -Group: Development/Libraries -Requires: %{name}-libs = %{epoch}:%{version}-%{release} - -%description private-devel -This package contains some private xrootd headers. The use of these -headers is strongly discouraged. Backward compatibility between -versions is not guaranteed for these headers. - -#------------------------------------------------------------------------------- -# client -#------------------------------------------------------------------------------- -%package client -Summary: Xrootd command line client tools -Group: Applications/Internet -Requires: %{name}-libs%{?_isa} = %{epoch}:%{version}-%{release} -Requires: %{name}-client-libs%{?_isa} = %{epoch}:%{version}-%{release} - -%description client -This package contains the command line tools used to communicate with -xrootd servers. - -#------------------------------------------------------------------------------- -# server -#------------------------------------------------------------------------------- -%package server -Summary: Extended ROOT file server -Group: System Environment/Daemons -Requires: %{name}-libs = %{epoch}:%{version}-%{release} -Requires: %{name}-client-libs = %{epoch}:%{version}-%{release} -Requires: %{name}-server-libs = %{epoch}:%{version}-%{release} -Requires: expect - -%description server -XRootD server binaries - -#------------------------------------------------------------------------------- -# fuse -#------------------------------------------------------------------------------- -%package fuse -Summary: Xrootd FUSE tool -Group: Applications/Internet -Requires: %{name}-libs%{?_isa} = %{epoch}:%{version}-%{release} -Requires: %{name}-client-libs%{?_isa} = %{epoch}:%{version}-%{release} -Requires: fuse - -%description fuse -This package contains the FUSE (file system in user space) xrootd mount -tool. - -#------------------------------------------------------------------------------- -# python2 -#------------------------------------------------------------------------------- -%package -n python2-%{name} -Summary: Python 2 bindings for XRootD -Group: Development/Libraries -%if %{?fedora}%{!?fedora:0} >= 13 -%{?python_provide:%python_provide python2-%{name}} -%else -Provides: python-%{name} -%endif -Provides: %{name}-python = %{epoch}:%{version}-%{release} -Obsoletes: %{name}-python < 1:4.8.0-1 -Requires: %{name}-client-libs%{?_isa} = %{epoch}:%{version}-%{release} - -%description -n python2-xrootd -Python 2 bindings for XRootD - -#------------------------------------------------------------------------------- -# python3 -#------------------------------------------------------------------------------- -%package -n python3-%{name} -Summary: Python 3 bindings for XRootD -Group: Development/Libraries -%if %{?fedora}%{!?fedora:0} >= 13 -%{?python_provide:%python_provide python3-%{name}} -%endif -Requires: %{name}-client-libs%{?_isa} = %{epoch}:%{version}-%{release} - -%description -n python3-xrootd -Python 3 bindings for XRootD - -#------------------------------------------------------------------------------- -# doc -#------------------------------------------------------------------------------- -%package doc -Summary: Developer documentation for the xrootd libraries -Group: Documentation -%if %{?fedora}%{!?fedora:0} >= 10 || %{?rhel}%{!?rhel:0} >= 6 -BuildArch: noarch -%endif - -%description doc -This package contains the API documentation of the xrootd libraries. - -#------------------------------------------------------------------------------- -# selinux -#------------------------------------------------------------------------------- -%package selinux -Summary: SELinux policy extensions for xrootd. -Group: System Environment/Base -%if %{?fedora}%{!?fedora:0} >= 10 || %{?rhel}%{!?rhel:0} >= 6 -BuildArch: noarch -%endif -Requires(post): policycoreutils -Requires(postun): policycoreutils -Requires: selinux-policy - -%description selinux -SELinux policy extensions for running xrootd while in enforcing mode. - -#------------------------------------------------------------------------------- -# ceph -#------------------------------------------------------------------------------- -%if %{?_with_ceph:1}%{!?_with_ceph:0} -%package ceph -Summary: Ceph back-end plug-in for XRootD -Group: Development/Tools -Requires: %{name}-server = %{epoch}:%{version}-%{release} -%description ceph -Ceph back-end plug-in for XRootD. -%endif - -#------------------------------------------------------------------------------- -# tests -#------------------------------------------------------------------------------- -%if %{?_with_tests:1}%{!?_with_tests:0} -%package tests -Summary: CPPUnit tests -Group: Development/Tools -Requires: %{name}-client = %{epoch}:%{version}-%{release} -%description tests -This package contains a set of CPPUnit tests for xrootd. -%endif - -%if %{?_with_compat:1}%{!?_with_compat:0} -#------------------------------------------------------------------------------- -# client-compat -#------------------------------------------------------------------------------- -%package client-compat -Summary: XRootD 3 compatibility client libraries -Group: System Environment/Libraries - -%description client-compat -This package contains compatibility libraries for xrootd 3 clients. - -#------------------------------------------------------------------------------- -# server-compat -#------------------------------------------------------------------------------- -%package server-compat -Summary: XRootD 3 compatibility server binaries -Group: System Environment/Daemons -Requires: %{name}-libs%{?_isa} = %{epoch}:%{version}-%{release} - -%description server-compat -This package contains compatibility binaries for xrootd 3 servers. - -%endif - -#------------------------------------------------------------------------------- -# Build instructions -#------------------------------------------------------------------------------- -%prep -%setup -c -n xrootd - -%if %{?_with_compat:1}%{!?_with_compat:0} -%setup -T -D -n %{name} -a 1 -%endif - -%build -cd xrootd - -%if %{?_with_clang:1}%{!?_with_clang:0} -export CC=clang -export CXX=clang++ -%endif - -mkdir build -pushd build -cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=RelWithDebInfo \ -%if %{?_with_tests:1}%{!?_with_tests:0} - -DENABLE_TESTS=TRUE \ -%else - -DENABLE_TESTS=FALSE \ -%endif -%if %{?_with_ceph:1}%{!?_with_ceph:0} - -DENABLE_CEPH=TRUE \ -%else - -DENABLE_CEPH=FALSE \ -%endif - -DUSE_LIBC_SEMAPHORE=%{use_libc_semaphore} ../ - -make -i VERBOSE=1 %{?_smp_mflags} -popd - -pushd packaging/common -make -f /usr/share/selinux/devel/Makefile -popd - -doxygen Doxyfile - -%if %{?_with_compat:1}%{!?_with_compat:0} -pushd ../xrootd-3.3.6 -mkdir build -pushd build -cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=RelWithDebInfo -DENABLE_PERL=FALSE ../ -make VERBOSE=1 %{?_smp_mflags} -popd -popd -%endif - -# build python3 bindings -pushd build/bindings/python -%py3_build -popd - -#------------------------------------------------------------------------------- -# Installation -#------------------------------------------------------------------------------- -%install -rm -rf $RPM_BUILD_ROOT - -#------------------------------------------------------------------------------- -# Install 3.3.6 compat -#------------------------------------------------------------------------------- -%if %{?_with_compat:1}%{!?_with_compat:0} -pushd xrootd-3.3.6/build -make install DESTDIR=$RPM_BUILD_ROOT -rm -rf $RPM_BUILD_ROOT%{_includedir} -rm -rf $RPM_BUILD_ROOT%{_datadir} -rm -f $RPM_BUILD_ROOT%{_bindir}/{cconfig,cns_ssi,frm_admin,frm_xfragent,mpxstats} -rm -f $RPM_BUILD_ROOT%{_bindir}/{wait41,xprep,xrd,xrdadler32,XrdCnsd,xrdcopy} -rm -f $RPM_BUILD_ROOT%{_bindir}/{xrdcp,xrdcp-old,xrdfs,xrdgsiproxy,xrdpwdadmin} -rm -f $RPM_BUILD_ROOT%{_bindir}/{xrdqstats,xrdsssadmin,xrdstagetool,xrootdfs} -rm -f $RPM_BUILD_ROOT%{_libdir}/libXrdAppUtils.so -rm -f $RPM_BUILD_ROOT%{_libdir}/{libXrdClient.so,libXrdCl.so,libXrdCryptoLite.so} -rm -f $RPM_BUILD_ROOT%{_libdir}/{libXrdCrypto.so,libXrdFfs.so,libXrdMain.so} -rm -f $RPM_BUILD_ROOT%{_libdir}/{libXrdOfs.so,libXrdPosixPreload.so,libXrdPosix.so} -rm -f $RPM_BUILD_ROOT%{_libdir}/{libXrdServer.so,libXrdUtils.so} - -for i in cmsd frm_purged frm_xfrd xrootd; do - mv $RPM_BUILD_ROOT%{_bindir}/$i $RPM_BUILD_ROOT%{_bindir}/${i}-3 -done -popd -%endif - -#------------------------------------------------------------------------------- -# Install 4.x.y -#------------------------------------------------------------------------------- -pushd xrootd -pushd build -make install DESTDIR=$RPM_BUILD_ROOT -cat PYTHON_INSTALLED | sed -e "s|$RPM_BUILD_ROOT||g" > PYTHON_INSTALLED_FILES -popd - -# configuration stuff -rm -rf $RPM_BUILD_ROOT%{_sysconfdir}/xrootd/* - -# ceph posix unversioned so -rm -f $RPM_BUILD_ROOT%{_libdir}/libXrdCephPosix.so - -# var paths -mkdir -p $RPM_BUILD_ROOT%{_var}/log/xrootd -mkdir -p $RPM_BUILD_ROOT%{_var}/run/xrootd -mkdir -p $RPM_BUILD_ROOT%{_var}/spool/xrootd - -# init stuff -mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/xrootd - -%if %{use_systemd} - -mkdir -p $RPM_BUILD_ROOT%{_unitdir} -install -m 644 packaging/common/xrootd@.service $RPM_BUILD_ROOT%{_unitdir} -install -m 644 packaging/common/xrdhttp@.socket $RPM_BUILD_ROOT%{_unitdir} -install -m 644 packaging/common/xrootd@.socket $RPM_BUILD_ROOT%{_unitdir} -install -m 644 packaging/common/cmsd@.service $RPM_BUILD_ROOT%{_unitdir} -install -m 644 packaging/common/frm_xfrd@.service $RPM_BUILD_ROOT%{_unitdir} -install -m 644 packaging/common/frm_purged@.service $RPM_BUILD_ROOT%{_unitdir} - -# tmpfiles.d -mkdir -p $RPM_BUILD_ROOT%{_tmpfilesdir} -install -m 0644 packaging/rhel/xrootd.tmpfiles $RPM_BUILD_ROOT%{_tmpfilesdir}/%{name}.conf - -%else - -mkdir -p $RPM_BUILD_ROOT%{_initrddir} -mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/sysconfig -install -m 644 packaging/rhel/xrootd.sysconfig $RPM_BUILD_ROOT%{_sysconfdir}/sysconfig/xrootd - -install -m 755 packaging/rhel/cmsd.init $RPM_BUILD_ROOT%{_initrddir}/cmsd -install -m 755 packaging/rhel/frm_purged.init $RPM_BUILD_ROOT%{_initrddir}/frm_purged -install -m 755 packaging/rhel/frm_xfrd.init $RPM_BUILD_ROOT%{_initrddir}/frm_xfrd -install -m 755 packaging/rhel/xrootd.init $RPM_BUILD_ROOT%{_initrddir}/xrootd -install -m 755 packaging/rhel/xrootd.functions $RPM_BUILD_ROOT%{_initrddir}/xrootd.functions - -%endif - -# logrotate -mkdir $RPM_BUILD_ROOT%{_sysconfdir}/logrotate.d -install -p -m 644 packaging/common/xrootd.logrotate $RPM_BUILD_ROOT%{_sysconfdir}/logrotate.d/xrootd - -install -m 644 packaging/common/xrootd-clustered.cfg $RPM_BUILD_ROOT%{_sysconfdir}/xrootd/xrootd-clustered.cfg -install -m 644 packaging/common/xrootd-standalone.cfg $RPM_BUILD_ROOT%{_sysconfdir}/xrootd/xrootd-standalone.cfg -install -m 644 packaging/common/xrootd-filecache-clustered.cfg $RPM_BUILD_ROOT%{_sysconfdir}/xrootd/xrootd-filecache-clustered.cfg -install -m 644 packaging/common/xrootd-filecache-standalone.cfg $RPM_BUILD_ROOT%{_sysconfdir}/xrootd/xrootd-filecache-standalone.cfg -%if %{use_systemd} -install -m 644 packaging/common/xrootd-http.cfg $RPM_BUILD_ROOT%{_sysconfdir}/xrootd/xrootd-http.cfg -%endif - -# client plug-in config -mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/xrootd/client.plugins.d -install -m 644 packaging/common/client-plugin.conf.example $RPM_BUILD_ROOT%{_sysconfdir}/xrootd/client.plugins.d/client-plugin.conf.example - -# client config -install -m 644 packaging/common/client.conf $RPM_BUILD_ROOT%{_sysconfdir}/xrootd/client.conf - -# documentation -mkdir -p %{buildroot}%{_docdir}/%{name}-%{version} -cp -pr doxydoc/html %{buildroot}%{_docdir}/%{name}-%{version} - -# selinux -mkdir -p %{buildroot}%{_datadir}/selinux/packages/%{name} -install -m 644 -p packaging/common/xrootd.pp \ - %{buildroot}%{_datadir}/selinux/packages/%{name}/%{name}.pp - -# install python3 bindings -pushd build/bindings/python -%py3_install -popd - -%clean -rm -rf $RPM_BUILD_ROOT - -#------------------------------------------------------------------------------- -# RPM scripts -#------------------------------------------------------------------------------- -%post libs -p /sbin/ldconfig -%postun libs -p /sbin/ldconfig - -%post client-libs -p /sbin/ldconfig -%postun client-libs -p /sbin/ldconfig - -%post server-libs -p /sbin/ldconfig -%postun server-libs -p /sbin/ldconfig - -%pre server - -getent group xrootd >/dev/null || groupadd -r xrootd -getent passwd xrootd >/dev/null || \ - useradd -r -g xrootd -c "XRootD runtime user" \ - -s /sbin/nologin -d %{_localstatedir}/spool/xrootd xrootd -exit 0 - -%if %{use_systemd} - -%post server -if [ $1 -eq 1 ] ; then - /usr/bin/systemctl daemon-reload >/dev/null 2>&1 || : -fi - -%preun server -if [ $1 -eq 0 ] ; then - for DAEMON in xrootd cmsd frm_purged frm xfrd; do - for INSTANCE in `/usr/bin/systemctl | grep $DAEMON@ | awk '{print $1;}'`; do - /usr/bin/systemctl --no-reload disable $INSTANCE > /dev/null 2>&1 || : - /usr/bin/systemctl stop $INSTANCE > /dev/null 2>&1 || : - done - done -fi - -%postun server -if [ $1 -ge 1 ] ; then - /usr/bin/systemctl daemon-reload >/dev/null 2>&1 || : - for DAEMON in xrootd cmsd frm_purged frm xfrd; do - for INSTANCE in `/usr/bin/systemctl | grep $DAEMON@ | awk '{print $1;}'`; do - /usr/bin/systemctl try-restart $INSTANCE >/dev/null 2>&1 || : - done - done -fi - -%else - -%post server -if [ $1 -eq 1 ]; then - /sbin/chkconfig --add xrootd - /sbin/chkconfig --add cmsd - /sbin/chkconfig --add frm_purged - /sbin/chkconfig --add frm_xfrd -fi - -%preun server -if [ $1 -eq 0 ]; then - /sbin/service xrootd stop >/dev/null 2>&1 || : - /sbin/service cmsd stop >/dev/null 2>&1 || : - /sbin/service frm_purged stop >/dev/null 2>&1 || : - /sbin/service frm_xfrd stop >/dev/null 2>&1 || : - /sbin/chkconfig --del xrootd - /sbin/chkconfig --del cmsd - /sbin/chkconfig --del frm_purged - /sbin/chkconfig --del frm_xfrd -fi - -%postun server -if [ $1 -ge 1 ]; then - /sbin/service xrootd condrestart >/dev/null 2>&1 || : - /sbin/service cmsd condrestart >/dev/null 2>&1 || : - /sbin/service frm_purged condrestart >/dev/null 2>&1 || : - /sbin/service frm_xfrd condrestart >/dev/null 2>&1 || : -fi - -%endif - -#------------------------------------------------------------------------------- -# Add a new user and group if necessary -#------------------------------------------------------------------------------- -%pre fuse -getent group xrootd >/dev/null || groupadd -r xrootd -getent passwd xrootd >/dev/null || \ - useradd -r -g xrootd -c "XRootD runtime user" \ - -s /sbin/nologin -d %{_localstatedir}/spool/xrootd xrootd -exit 0 - -#------------------------------------------------------------------------------- -# Selinux -#------------------------------------------------------------------------------- -%post selinux -/usr/sbin/semodule -i %{_datadir}/selinux/packages/%{name}/%{name}.pp >/dev/null 2>&1 || : - -%postun selinux -if [ $1 -eq 0 ] ; then - /usr/sbin/semodule -r %{name} >/dev/null 2>&1 || : -fi - -#------------------------------------------------------------------------------- -# Files -#------------------------------------------------------------------------------- -%files -# empty - -%files server -%defattr(-,root,root,-) -%{_bindir}/cconfig -%{_bindir}/cmsd -%{_bindir}/cns_ssi -%{_bindir}/frm_admin -%{_bindir}/frm_purged -%{_bindir}/frm_xfragent -%{_bindir}/frm_xfrd -%{_bindir}/mpxstats -%{_bindir}/wait41 -%{_bindir}/XrdCnsd -%{_bindir}/xrdpwdadmin -%{_bindir}/xrdsssadmin -%{_bindir}/xrdmapc -%{_bindir}/xrootd -%{_bindir}/xrdpfc_print -%{_bindir}/xrdacctest -%{_mandir}/man8/cmsd.8* -%{_mandir}/man8/cns_ssi.8* -%{_mandir}/man8/frm_admin.8* -%{_mandir}/man8/frm_purged.8* -%{_mandir}/man8/frm_xfragent.8* -%{_mandir}/man8/frm_xfrd.8* -%{_mandir}/man8/mpxstats.8* -%{_mandir}/man8/XrdCnsd.8* -%{_mandir}/man8/xrdpwdadmin.8* -%{_mandir}/man8/xrdsssadmin.8* -%{_mandir}/man8/xrootd.8* -%{_mandir}/man8/xrdpfc_print.8* -%{_datadir}/xrootd -%attr(-,xrootd,xrootd) %config(noreplace) %{_sysconfdir}/xrootd/xrootd-clustered.cfg -%attr(-,xrootd,xrootd) %config(noreplace) %{_sysconfdir}/xrootd/xrootd-standalone.cfg -%attr(-,xrootd,xrootd) %config(noreplace) %{_sysconfdir}/xrootd/xrootd-filecache-clustered.cfg -%attr(-,xrootd,xrootd) %config(noreplace) %{_sysconfdir}/xrootd/xrootd-filecache-standalone.cfg -%if %{use_systemd} -%attr(-,xrootd,xrootd) %config(noreplace) %{_sysconfdir}/xrootd/xrootd-http.cfg -%endif -%attr(-,xrootd,xrootd) %dir %{_var}/log/xrootd -%attr(-,xrootd,xrootd) %dir %{_var}/run/xrootd -%attr(-,xrootd,xrootd) %dir %{_var}/spool/xrootd -%config(noreplace) %{_sysconfdir}/logrotate.d/xrootd - -%if %{use_systemd} -%{_unitdir}/* -%{_tmpfilesdir}/%{name}.conf -%else -%config(noreplace) %{_sysconfdir}/sysconfig/xrootd -%{_initrddir}/* -%endif - -%files libs -%defattr(-,root,root,-) -%{_libdir}/libXrdAppUtils.so.1* -%{_libdir}/libXrdClProxyPlugin-4.so -%{_libdir}/libXrdCks*-4.so -%{_libdir}/libXrdCrypto.so.1* -%{_libdir}/libXrdCryptoLite.so.1* -%{_libdir}/libXrdCryptossl-4.so -%{_libdir}/libXrdSec*-4.so -%{_libdir}/libXrdUtils.so.* -%{_libdir}/libXrdXml.so.* - -%files devel -%defattr(-,root,root,-) -%dir %{_includedir}/xrootd -%{_bindir}/xrootd-config -%{_includedir}/xrootd/XProtocol -%{_includedir}/xrootd/Xrd -%{_includedir}/xrootd/XrdCks -%{_includedir}/xrootd/XrdNet -%{_includedir}/xrootd/XrdOuc -%{_includedir}/xrootd/XrdSec -%{_includedir}/xrootd/XrdSys -%{_includedir}/xrootd/XrdVersion.hh -%{_libdir}/libXrdAppUtils.so -%{_libdir}/libXrdCrypto.so -%{_libdir}/libXrdCryptoLite.so -%{_libdir}/libXrdUtils.so -%{_libdir}/libXrdXml.so -%{_includedir}/xrootd/XrdXml/XrdXmlReader.hh - -%files client-libs -%defattr(-,root,root,-) -%{_libdir}/libXrdCl.so.2* -%{_libdir}/libXrdClient.so.2* -%{_libdir}/libXrdFfs.so.2* -%{_libdir}/libXrdPosix.so.2* -%{_libdir}/libXrdPosixPreload.so.1* -%{_sysconfdir}/xrootd/client.plugins.d/client-plugin.conf.example -%config(noreplace) %{_sysconfdir}/xrootd/client.conf -# This lib may be used for LD_PRELOAD so the .so link needs to be included -%{_libdir}/libXrdPosixPreload.so - -%files client-devel -%defattr(-,root,root,-) -%{_bindir}/xrdgsitest -%{_includedir}/xrootd/XrdCl -%{_includedir}/xrootd/XrdClient -%{_includedir}/xrootd/XrdPosix -%{_libdir}/libXrdCl.so -%{_libdir}/libXrdClient.so -%{_libdir}/libXrdFfs.so -%{_libdir}/libXrdPosix.so -%{_mandir}/man1/xrdgsitest.1* - -%files server-libs -%defattr(-,root,root,-) -%{_libdir}/libXrdBwm-4.so -%{_libdir}/libXrdPss-4.so -%{_libdir}/libXrdXrootd-4.so -%{_libdir}/libXrdFileCache-4.so -%{_libdir}/libXrdBlacklistDecision-4.so -%{_libdir}/libXrdHttp-4.so -%{_libdir}/libXrdN2No2p-4.so -%{_libdir}/libXrdOssSIgpfsT-4.so -%{_libdir}/libXrdServer.so.* -%{_libdir}/libXrdSsi-4.so -%{_libdir}/libXrdSsiLib.so.* -%{_libdir}/libXrdSsiLog-4.so -%{_libdir}/libXrdSsiShMap.so.* -%{_libdir}/libXrdThrottle-4.so - -%files server-devel -%defattr(-,root,root,-) -%{_includedir}/xrootd/XrdAcc -%{_includedir}/xrootd/XrdCms -%{_includedir}/xrootd/XrdFileCache -%{_includedir}/xrootd/XrdOss -%{_includedir}/xrootd/XrdSfs -%{_includedir}/xrootd/XrdXrootd -%{_includedir}/xrootd/XrdHttp -%{_libdir}/libXrdServer.so - -%files private-devel -%defattr(-,root,root,-) -%{_includedir}/xrootd/private -%{_libdir}/libXrdSsiLib.so -%{_libdir}/libXrdSsiShMap.so - -%files client -%defattr(-,root,root,-) -%{_bindir}/xprep -%{_bindir}/xrd -%{_bindir}/xrdadler32 -%{_bindir}/xrdcopy -%{_bindir}/xrdcp -%{_bindir}/xrdcp-old -%{_bindir}/xrdfs -%{_bindir}/xrdgsiproxy -%{_bindir}/xrdstagetool -%{_mandir}/man1/xprep.1* -%{_mandir}/man1/xrd.1* -%{_mandir}/man1/xrdadler32.1* -%{_mandir}/man1/xrdcopy.1* -%{_mandir}/man1/xrdcp.1* -%{_mandir}/man1/xrdcp-old.1* -%{_mandir}/man1/xrdfs.1* -%{_mandir}/man1/xrdgsiproxy.1* -%{_mandir}/man1/xrdstagetool.1* - -%files fuse -%defattr(-,root,root,-) -%{_bindir}/xrootdfs -%{_mandir}/man1/xrootdfs.1* -%dir %{_sysconfdir}/xrootd - -%files -n python2-%{name} -f xrootd/build/PYTHON_INSTALLED_FILES -%defattr(-,root,root,-) - -%files -n python3-%{name} -%defattr(-,root,root,-) -%{python3_sitearch}/* - -%files doc -%defattr(-,root,root,-) -%doc %{_docdir}/%{name}-%{version} - -%if %{?_with_ceph:1}%{!?_with_ceph:0} -%files ceph -%defattr(-,root,root,-) -%{_libdir}/libXrdCeph-4.so -%{_libdir}/libXrdCephXattr-4.so -%{_libdir}/libXrdCephPosix.so* -%endif - -%if %{?_with_tests:1}%{!?_with_tests:0} -%files tests -%defattr(-,root,root,-) -%{_bindir}/text-runner -%{_bindir}/xrdshmap -%{_libdir}/libXrdClTests.so -%{_libdir}/libXrdClTestsHelper.so -%{_libdir}/libXrdClTestMonitor*.so - -%if %{?_with_ceph:1}%{!?_with_ceph:0} -%{_libdir}/libXrdCephTests*.so -%endif -%endif - -%files selinux -%defattr(-,root,root) -%{_datadir}/selinux/packages/%{name}/%{name}.pp - -%if %{?_with_compat:1}%{!?_with_compat:0} -%files client-compat -%defattr(-,root,root,-) -%{_libdir}/libXrdAppUtils.so.0* -%{_libdir}/libXrdCksCalczcrc32.so* -%{_libdir}/libXrdClient.so.1* -%{_libdir}/libXrdCl.so.1* -%{_libdir}/libXrdCryptoLite.so.0* -%{_libdir}/libXrdCrypto.so.0* -%{_libdir}/libXrdCryptossl.so* -%{_libdir}/libXrdFfs.so.1* -%{_libdir}/libXrdPosixPreload.so.0* -%{_libdir}/libXrdPosix.so.1* -%{_libdir}/libXrdSecgsiAuthzVO.so* -%{_libdir}/libXrdSecgsiGMAPDN.so* -%{_libdir}/libXrdSecgsi.so* -%{_libdir}/libXrdSeckrb5.so* -%{_libdir}/libXrdSecpwd.so* -%{_libdir}/libXrdSec.so* -%{_libdir}/libXrdSecsss.so* -%{_libdir}/libXrdSecunix.so* -%{_libdir}/libXrdUtils.so.1* - -%files server-compat -%defattr(-,root,root,-) -%{_bindir}/cmsd-3 -%{_bindir}/frm_purged-3 -%{_bindir}/frm_xfrd-3 -%{_bindir}/xrootd-3 -%{_libdir}/libXrdBwm.so* -%{_libdir}/libXrdMain.so.1* -%{_libdir}/libXrdOfs.so.1* -%{_libdir}/libXrdPss.so* -%{_libdir}/libXrdServer.so.1* -%{_libdir}/libXrdXrootd.so* -%endif - -#------------------------------------------------------------------------------- -# Changelog -#------------------------------------------------------------------------------- -%changelog -* Fri Nov 10 2017 Michal Simon - 1:4.8.0-1 -- Add python3 sub-package -- Rename python sub-package - -* Tue Dec 13 2016 Gerardo Ganis -- Add xrdgsitest to xrootd-client-devel - -* Mon Mar 16 2015 Lukasz Janyst -- create the python package - -* Wed Mar 11 2015 Lukasz Janyst -- create the xrootd-ceph package - -* Thu Oct 30 2014 Lukasz Janyst -- update for 4.1 and introduce 3.3.6 compat packages - -* Thu Aug 28 2014 Lukasz Janyst -- add support for systemd - -* Wed Aug 27 2014 Lukasz Janyst -- use generic selinux policy build mechanisms - -* Tue Apr 01 2014 Lukasz Janyst -- correct the license field (LGPLv3+) -- rename to xrootd4 -- add 'conflicts' statements -- remove 'provides' and 'obsoletes' - -* Mon Mar 31 2014 Lukasz Janyst -- Add selinux policy - -* Fri Jan 24 2014 Lukasz Janyst -- Import XrdHttp - -* Fri Jun 7 2013 Lukasz Janyst -- adopt the EPEL RPM layout by Mattias Ellert - -* Tue Apr 2 2013 Lukasz Janyst -- remove perl - -* Thu Nov 1 2012 Justin Salmon -- add tests package - -* Fri Oct 21 2011 Lukasz Janyst 3.1.0-1 -- bump the version to 3.1.0 - -* Mon Apr 11 2011 Lukasz Janyst 3.0.3-1 -- the first RPM release - version 3.0.3 -- the detailed release notes are available at: - http://xrootd.org/download/ReleaseNotes.html diff --git a/packaging/rhel/xrootd.sysconfig b/packaging/rhel/xrootd.sysconfig deleted file mode 100644 index aa367d037ab..00000000000 --- a/packaging/rhel/xrootd.sysconfig +++ /dev/null @@ -1,66 +0,0 @@ -#------------------------------------------------------------------------------- -# Define the instances of xrootd, cmsd and frmd here and specify the option you -# need. For example, use the -d flag to send debug output to the logfile, -# the options responsible for daemonizing, pidfiles and instance naming will -# be appended automatically. -#------------------------------------------------------------------------------- - -#------------------------------------------------------------------------------- -# Define the user account name which will be used to start the daemons. -# These may have many unexpected side effects, so be sure you know what you're -# doing before playing with them. -#------------------------------------------------------------------------------- -XROOTD_USER=xrootd -XROOTD_GROUP=xrootd - -#------------------------------------------------------------------------------- -# Define the commandline options for the instances of the daemons. -# The format is: -# DAEMON_NAME_OPTIONS, where: -# DAEMON - the daemon name, the valid values are: XROOTD, CMSD, XFRD and PURD -# NAME - the name of the instance, any uppercase alphanumeric string -# without whitespaces is valid -#------------------------------------------------------------------------------- -XROOTD_DEFAULT_OPTIONS="-l /var/log/xrootd/xrootd.log -c /etc/xrootd/xrootd-clustered.cfg -k fifo" -#XROOTD_DEFAULT_OPTIONS="-l /var/log/xrootd/xrootd.log -c /etc/xrootd/xrootd-standalone.cfg -k fifo" -CMSD_DEFAULT_OPTIONS="-l /var/log/xrootd/cmsd.log -c /etc/xrootd/xrootd-clustered.cfg -k fifo" -PURD_DEFAULT_OPTIONS="-l /var/log/xrootd/purged.log -c /etc/xrootd/xrootd-clustered.cfg -k fifo" -XFRD_DEFAULT_OPTIONS="-l /var/log/xrootd/xfrd.log -c /etc/xrootd/xrootd-clustered.cfg -k fifo" - -#------------------------------------------------------------------------------- -# Names of the instances to be started by default, the case doesn't matter, -# the names will be converted to lowercase automatically, use space as a -# separator -#------------------------------------------------------------------------------- -XROOTD_INSTANCES="default" -CMSD_INSTANCES="default" -PURD_INSTANCES="default" -XFRD_INSTANCES="default" - -#------------------------------------------------------------------------------- -# Names of the instances that should run XRootD 3 versions of the executables. -# By default XRootD 4 versions are run, however it is possible to run XRootD 3 -# if the appropriate compat packages are installed. -#------------------------------------------------------------------------------- -#XROOTD3_INSTANCES="default" -#CMSD3_INSTANCES="default" -#PURD3_INSTANCES="default" -#XFRD3_INSTANCES="default" - -#------------------------------------------------------------------------------- -# Use a custom memory allocator when the one provided with the Standard C -# Library (glibc) is eating up too much system memory. This is almost always the -# case for RHEL >= 6, so you may consider installing jemalloc or tcmalloc and -# using one of them instead. -# -# In order to do that, install either one of the packages: -# -# yum install jemalloc # for jemalloc -# or -# yum install gperftools-libs # for tcmalloc -# -# and uncomment one of the following lines. You may need to adjust the library -# paths depending on the type of your system. -#------------------------------------------------------------------------------- -#export LD_PRELOAD=/usr/lib64/libjemalloc.so.1 -#export LD_PRELOAD=/usr/lib64/libtcmalloc.so.4 diff --git a/packaging/rhel/xrootd.tmpfiles b/packaging/rhel/xrootd.tmpfiles deleted file mode 100644 index 1d61c24e6d4..00000000000 --- a/packaging/rhel/xrootd.tmpfiles +++ /dev/null @@ -1 +0,0 @@ -d /var/run/xrootd - xrootd xrootd - \ No newline at end of file diff --git a/packaging/tgz/README b/packaging/tgz/README deleted file mode 100644 index 47e35774275..00000000000 --- a/packaging/tgz/README +++ /dev/null @@ -1,37 +0,0 @@ - - $Id$ - -This directory contains start and stop scripts for the cmsd, olbd (deprecated) -and xrootd, as well as a monitoring script for the cmsd when you wish to use -load balanced clustering. Please note that the cmsd and olbd provide the same -services but you must use one or the other everywhere. You may not mix them. The -cmsd is the prefered daemon (the olbd is provided for backward compatability). - -The documentation for each script is contained within the script itself. Please -read the header comments in each script before using the script! - -The StartCMS script starts the cmsd and the StartXRD script starts xrootd. Both -scripts must have a StartXRD.cf file installed in the same directory as the -start script. This distribution includes a StartXRD.cf.example file to guide you -through setting the various variables to the appropriate values. Please create -your own StartXRD.cf file from this example file. - -The StartXRD.cf.example and the xrootd.cf.example files provided here use simple -defaults, which allow the xrootd daemon to start locally. They are useful to -have something to start with and to get inspiration about the envvar usage. - -To start a cmsd daemon, all you need to do is to is to fill in the name of the -manager host in the places where it should go inside the xrootd.cf file. The -default configuration files is provided as an easy way to have something trivial -which starts immediately after having compiled the package. These scripts can -also be used to set up a full cluster, but may be not adequate for particularily -complex installations. In this case, please refer to the example scripts and to -the documentation for support. - -You need not use the provided start/stop scripts. However, they are provided -here as a helpful aid in managing the servers. The StartXrd.cf.example is the -simple one that shoud be good enough to get you going in less that 15 minutes! - -Don't forget to modify your init scripts to start the servers at boot time! - -Full documentation can be found at http://xrootd.slac.stanford.edu/ diff --git a/packaging/tgz/StartCMS b/packaging/tgz/StartCMS deleted file mode 100755 index 9dc2860f1b8..00000000000 --- a/packaging/tgz/StartCMS +++ /dev/null @@ -1,199 +0,0 @@ -#!/bin/sh -# -# $Id$ -# -# (C) 2005 by the Board of Trustees of the Leland Stanford, Jr., University -# All Rights Reserved -# Produced by Andrew Hanushevsky for Stanford University under contract -# DE-AC03-76-SFO0515 with the Deprtment of Energy -# Modified by Fabrizio Furano 12/7/2007 - -# Syntax: StartCMS [-c cfile] [-D] [-t] [-v] [oth] - -# Where: -c specifies the configuration file to be used. -# -D turns on internal debugging. -# -t types the commands and does not execute them -# -v produces verbose output. -# oth Any other options to be passed to the executable. - -# -# Set default values from the StartXRD.cf file -# -. `dirname $0`/StartXRD.cf - -VERBOSE=0 - -# Set TEST to equal /bin/echo to only display lines to be executed -# -TEST= - -umask 002 - -############################################################################## -# s u b r o u t i n e s # -############################################################################## - -Debug () { - if test $VERBOSE -eq 1; then - echo $1 - fi - } - -MustExist () { - Debug "Checking existence of $1 $3 ..." - if test ! -${2} $3 ; then - Notify "$1 $3 not found." - fi - } - -Writable () { - Debug "Checking writability of $1 $2 ..." - if test ! -w $2 ; then - Notify "$1 $2 is not writable by $MYNAME." - fi - } - -Wait4File () { - Debug "Checking file $2 ..." - tcnt=$count - until test -${1} $2 -o $tcnt -eq 0; do - echo StartCMS: File $2 not found\; waiting $time seconds... - sleep $time - tcnt=`/usr/bin/expr $tcnt - 1` - done - if [ $tcnt -le 0 ]; then - Notify "File $2 not found." - fi - } - -Notify () { - echo StartCMS: $1 - exit 4 - } - -############################################################################## -# m a i n p r o g r a m # -############################################################################## - -# Pick up options -# -CMSOPTS= -CMSPARM=$* - -while test -n "$1"; do - if [ "$1" = "-c" ]; then - if [ -z "$2" ]; then - Notify "Configuration file not specified." - fi - if [ ! -r "$2" ]; then - Notify "Configuration file '$2' not found." - fi - CMSCONFIGFN=$2 - shift - elif [ "$1" = "-D" ]; then - set -x - elif [ "$1" = "-t" ]; then - TEST=echo - elif [ "$1" = "-v" ]; then - VERBOSE=1 - else - CMSOPTS="$CMSOPTS $1" - fi - shift - done - -# Establish location of the CMS executables -# - XRDBASE=`(cd $XRDBASE; pwd)` - Debug "XRDBASE has been set to '$XRDBASE'" - if [ $? != 0 ]; then - exit 4 - fi - - CMSPROG=$XRDBASE/bin/$XRDARCH/cmsd - XRDLIBBASE=$XRDBASE/lib/$XRDARCH - PROGRAM=$XRDCFG/$PROGRAM - if [ "$CMSCONFIGFN" = "" ]; then - CMSCONFIGFN=$XRDCFG/$XRDCONFIG - fi - -# Establish log file name -# - CMSLOGFILE=$XRDLOGDIR/$CMSLOGFN - -# Verify that we are not executing this script as root (if we are, switch) -# -if [ $MYNAME = root ]; then - if [ $XRDUSER != root ]; then - $TEST exec su $XRDUSER -c "$PROGRAM $CMSPARM" - fi - elif [ $MYNAME != $XRDUSER ]; then - Notify "Attempt to start $CMSUSER cmsd as user $MYNAME." - fi - -# Just in case we don't have the basic directories, try to create them -# -$TEST mkdir -m 0744 -p $XRDLOGDIR $CMSHOMEDIR 2> /dev/null - -# Verify that all required directories are present -# -for FN in $XRDBASE $XRDLOGDIR $CMSHOMEDIR - do - MustExist Directory d $FN -done - -# Verify that all owned directories are writable -# -for FN in $XRDLOGDIR $CMSHOMEDIR - do - Writable Directory $FN -done - -# Verify that all required readable files are present -# -for FN in $XRDCFG - do - Wait4File r $FN -done - -# Verify that all required executable files are present -# -XLIST="$CMSPROG" -for FN in $XLIST - do - Wait4File x $FN -done - -# Export the variables required in the config file -# -$TEST export XRDCFG -$TEST export XRDBASE -$TEST export XRDLIBBASE - -# Set appropriate limits -# -$TEST ulimit -n $MAXFD -$TEST ulimit -c unlimited - -# Add our "lib" directory to LD_LIBRARY_PATH -# - CMSLIBBASE=$XRDBASE/lib/$XRDARCH - if [ -z "$LD_LIBRARY_PATH" ]; then - LD_LIBRARY_PATH=$CMSLIBBASE - else - LD_LIBRARY_PATH=$CMSLIBBASE:$LD_LIBRARY_PATH - fi - export LD_LIBRARY_PATH - -# Now start the daemon -# -echo Starting cmsd ... -$TEST cd $CMSHOMEDIR -$TEST $CMSPROG $CMSOPTS -l $CMSLOGFILE -c $CMSCONFIGFN & -stat=$? - -# Check if we were successful -# -if test $stat -gt 0 ; then - Notify "$CMSPROG returned a status of ${stat}." - fi diff --git a/packaging/tgz/StartFRM b/packaging/tgz/StartFRM deleted file mode 100755 index 9143dae6af5..00000000000 --- a/packaging/tgz/StartFRM +++ /dev/null @@ -1,175 +0,0 @@ -#!/bin/sh -# Usage: StartFRM [-d] [-f] [-p] [-t] [-x] - -# Where: -d turns on debugging -# -f forces the start irrespective of the hold file or status, if any. -# -p starts only the purge daemon -# -t types the start commands and does not execute them -# -x starts only the transfer daemon -# No option cause everything to be started. - -# Set defaults -#******************************************************************************# -#* D e f a u l t s *# -#******************************************************************************# - -TEST= -EXITRC=0 -FORCE= -DOALL=1 -ONLYP=0 -ONLYX=0 - -# Source he config file to set additional options -# -. `dirname $0`/StartXRD.cf - - -#******************************************************************************# -#* S u b r o u t i n e s *# -#******************************************************************************# - -Emsg () { - echo StartFRM: $1 - } - -isOK () { - thePGM=$1 - theHLD=$2 - if [ -z "$1" -o -z "$2" ]; then - REASON="$1 not configured." - return 0; - fi - if [ ! -x $1 ]; then - REASON="$1 missing." - return 0 - fi - if [ ! -z "$FORCE" ]; then - rm -f $theHLD - elif [ -f $theHLD ]; then - REASON="$theHLD exists." - return 0 - fi - xPID $thePGM - if [ $? -ne 0 ]; then - return 0 - fi - return 1 - } - -xPID () { - pidPGM=$1 - set -- '' - set -- `ps -e -o pid -o args | grep $pidPGM | awk '$0 !~ /grep/ {print $1}'` - - if [ -z "$1" ]; then - return 0 - else - REASON="already running as pid $1." - fi - return 1 - } - -Xeq () { - $TEST $* & - if [ $? -ne 0 ]; then - EXITRC=1 - fi - } - -#******************************************************************************# -#* M a i n *# -#******************************************************************************# - -# Pick up options -# - -XRDPARMS=$* - -while test -n "$1"; do - if [ "$1" = "-d" ]; then - set -x - OPTS="-d $OPTS" - elif [ "$1" = "-f" ]; then - FORCE=1 - elif [ "$1" = "-p" ]; then - DOALL=0 - ONLYP=1 - elif [ "$1" = "-t" ]; then - TEST=echo - elif [ "$1" = "-x" ]; then - DOALL=0 - ONLYX=1 - else - Emsg "Invalid option - $1." - echo 'Usage: StartFRM [-d] [-f] [-p] [-t] [-x]' - exit 1 - fi - shift - done - -# Determine hostname and username -# -MYHOST=`/bin/hostname` - -if [ -x /usr/bin/whoami ]; then - MYNAME=`/usr/bin/whoami` -elif [ -x /usr/ucb/whoami ]; then - MYNAME=`/usr/ucb/whoami` -else - Emsg "Unable to determine username." - exit 1 -fi - -# See if we should su to the right user for the frm component. On Solaris -# /bin/su doesn't forward the current environment variables and one has to -# su to the XRDUSER and run the Start script again. - -PROGRAM=$XRDCFG/$PROGRAM -if [ $MYNAME = root ]; then - if [ $XRDUSER != root ]; then - $TEST export LD_LIBRARY_PATH - $TEST exec su $XRDUSER -c "$PROGRAM $XRDPARMS" - fi -elif [ $MYNAME != $XRDUSER ]; then - Emsg "$MYNAME cannot start $XRDUSER FRM subsystems!" -fi - -# Now set the LD_LIBRARY_PATH variable -# - if [ -z "$LD_LIBRARY_PATH" ]; then - LD_LIBRARY_PATH=$LDLIBPATH - else - LD_LIBRARY_PATH=$LDLIBPATH:$LD_LIBRARY_PATH - fi - export LD_LIBRARY_PATH - -# Now start up the purge daemon -# -if [ ! -z "$START_PURGE" -a "$DOALL" = "1" -o "$ONLYP" = "1" ]; then - isOK $START_PURGE $HFILE_PURGE - if [ $? -eq 1 ]; then - Emsg "Starting $XRDUSER purge daemon. . ." - $TEST cd $HOMED_PURGE - Xeq "$START_PURGE $OPTS $PARMS_PURGE" - else - Emsg "$XRDUSER purge daemon cannot be started on $MYHOST; $REASON" - EXITRC=1 - fi -fi - -# Now start up the transfer daemon -# -if [ "$DOALL" = "1" -o "$ONLYX" = "1" ]; then - isOK $START_TRANSFER $HFILE_TRANSFER - if [ $? -eq 1 ]; then - Emsg "Starting $XRDUSER transfer daemon. . ." - $TEST cd $HOMED_TRANSFER - Xeq "$START_TRANSFER $OPTS $PARMS_TRANSFER" - else - Emsg "$XRDUSER transfer daemon cannot be started on $MYHOST; $REASON" - EXITRC=1 - fi -fi - -exit $EXITRC diff --git a/packaging/tgz/StartOLB b/packaging/tgz/StartOLB deleted file mode 100755 index 2afea54e016..00000000000 --- a/packaging/tgz/StartOLB +++ /dev/null @@ -1,228 +0,0 @@ -#!/bin/sh -# -# $Id$ -# -# (C) 2005 by the Board of Trustees of the Leland Stanford, Jr., University -# All Rights Reserved -# Produced by Andrew Hanushevsky for Stanford University under contract -# DE-AC03-76-SFO0515 with the Deprtment of Energy - -# Syntax: StartOLB [-c cfile] [-d] [-D] [-m] [-s] [oth] [-v] [ver] - -# Where: -c specifies the configuration file to be used. -# -d passes the -d option to olbd -# -D turns on internal debugging. -# -m specifies manager mode (default in StartOLB.cf) -# -s specifies server mode (default in StartOLB.cf) -# -t types the commands and does not execute them -# -v produces verbose output. -# oth Any other single letter options to be passed to the executable. - -# ver is version number to use. This is the string that is put in -# $OLBBASE//bin, the default is prod. -# -# Set default values -# -. $0.cf - -VERBOSE=0 -# Set TEST to equal /bin/echo to only display lines to be executed -# -TEST= - -umask 002 - -############################################################################## -# s u b r o u t i n e s # -############################################################################## - -Debug () { - if test $VERBOSE -eq 1; then - /bin/echo $1 - fi - } - -MustExist () { - Debug "Checking existence of $1 $3 ..." - if test ! -${2} $3 ; then - Notify "$1 $3 not found." - fi - } - -Writable () { - Debug "Checking writability of $1 $2 ..." - if test -r $2 ; then - if test ! -w $2 ; then - Notify "$1 $2 is not writable by $MYNAME." - fi - fi - } - -Wait4File () { - Debug "Checking file $2 ..." - tcnt=$count - until test -${1} $2 -o $tcnt -eq 0; do - /bin/echo StartOLB: File $2 not found\; waiting $time seconds... - /bin/sleep $time - tcnt=`/usr/bin/expr $tcnt - 1` - done - if [ $tcnt -le 0 ]; then - Notify "File $2 not found." - fi - } - -Notify () { - /bin/echo StartOLB: $1 - exit 4 - } - -############################################################################## -# m a i n p r o g r a m # -############################################################################## - -# Pick up options -# -HAVEVER= -OLBOPTS= -OLBPARM=$* -OLBMS=0 - -while test -n "$1"; do - isopt=0 - case $1 in [-]*) isopt=1; - esac - if [ "$isopt" = "0" -a -z "$HAVEVER" ]; then - OLBVER=$1 - HAVEVER=1 - elif [ "$1" = "-c" ]; then - if [ -z "$2" ]; then - Notify "Configuration file not specified." - fi - if [ ! -r "$2" ]; then - Notify "Configuration file '$2' not found." - fi - OLBCONFIGFN=$2 - shift - elif [ "$1" = "-D" ]; then - set -x - elif [ "$1" = "-m" ]; then - if [ "$OLBMS" = "1" ]; then - OLBMODE="-m -s" - OLBTYPE='supervisor' - else - OLBMODE="-m" - OLBTYPE='manager' - fi - OLBMS=1 - elif [ "$1" = "-n" ]; then - shift - OLBOPTS="$OLBOPTS -n $1" - elif [ "$1" = "-s" ]; then - if [ "$OLBMS" = "1" ]; then - OLBMODE="-m -s" - OLBTYPE='supervisor' - else - OLBMODE="-s" - OLBTYPE='server' - fi - OLBMS=1 - elif [ "$1" = "-t" ]; then - TEST=echo - elif [ "$1" = "-v" ]; then - VERBOSE=1 - else - OLBOPTS="$OLBOPTS $1" - fi - shift - done - -# Establish location of the OLB executables -# - OLBBASE=`(cd $OLBBASE/$OLBVER; pwd)` - if [ $? != 0 ]; then - exit 4 - fi - - OLBPROG=$OLBBASE/bin/$OLBPROG - PROGRAM=$OLBBASE/etc/$PROGRAM - if [ "$OLBCONFIGFN" = "" ]; then - OLBCONFIGFN=$OLBBASE/etc/$OLBCONFIG - fi - -# Establish log file name -# - OLBLOGFILE=$OLBLOGDIR/$OLBLOGFN - if [ "x$OLBMODE" = "x-m -s" ]; then - OLBLOGFILE=$OLBLOGFILE.super - fi - -# Verify that we are not executing this script as root (if we are, switch) -# -if [ $MYNAME = root ]; then - if [ $OLBUSER != root ]; then - $TEST exec /bin/su $OLBUSER -c "$PROGRAM $OLBPARM" - fi - elif [ $MYNAME != $OLBUSER ]; then - Notify "Attempt to start $OLBUSER OLB as user $MYNAME." - fi - -# Verify that all required directories are present -# -for FN in $OLBBASE $OLBLOGDIR $OLBHOMEDIR - do - MustExist Directory d $FN -done - -# Verify that all owned directories are writable -# -for FN in $OLBLOGDIR $OLBHOMEDIR - do - Writable Directory $FN -done - -# Verify that we can overwrite the pid file -# -Writable File $OLBPIDFILE - -# Verify that all required readable files are present -# -for FN in $OLBCONFIGFN - do - Wait4File r $FN -done - -# Verify that all required executable files are present -# -XLIST="$OLBPROG" -for FN in $XLIST - do - Wait4File x $FN -done - -# Set appropriate limits -# -$TEST ulimit -n $MAXFD -$TEST ulimit -c unlimited - -# Add our "lib" directory to LD_LIBRARY_PATH -# - OLBLIBBASE=$OLBBASE/lib - if [ -z "$LD_LIBRARY_PATH" ]; then - LD_LIBRARY_PATH=$OLBLIBBASE - else - LD_LIBRARY_PATH=$OLBLIBBASE:$LD_LIBRARY_PATH - fi - export LD_LIBRARY_PATH - -# Now start the daemon -# -/bin/echo Starting $OLBTYPE OLB ... -$TEST cd $OLBHOMEDIR -$TEST $OLBPROG $OLBOPTS $OLBMODE -l $OLBLOGFILE -c $OLBCONFIGFN & -stat=$? - -# Check if we were successful -# -if test $stat -gt 0 ; then - Notify "$OLBPROG returned a status of ${stat}." - fi diff --git a/packaging/tgz/StartOLB.cf.example b/packaging/tgz/StartOLB.cf.example deleted file mode 100644 index 846c21469e8..00000000000 --- a/packaging/tgz/StartOLB.cf.example +++ /dev/null @@ -1,76 +0,0 @@ -#!/bin/sh -# -# $Id$ -# - -# Set 'time' to be number of seconds to wait for a required file to appear -# This is only meaningful for files in AFS or NFS -# -time=60 - -# Set 'count to be the maximum number of times to wait for a required file -# This is only meaningful for files in AFS or NFS -# -count=30 - -# Set OLBUSER to be the username to "su" to if the script is run as root -# -OLBUSER=$USER - -# Set OLBBASE to be the base directory where olbd version directories have been -# installed. They all must be in the same base directory. -# -OLBBASE=/opt/xrootd - -# Set OLBVER to be the name of the default version directory. This directory -# must be installed in the OLBBASE directory. It contains bin, etc, and lib. -# -OLBVER=prod - -# Set OLBPROG to be the name of the executable in the "bin" directory. The -# start script uses '$OLBBASE/$OLBVER/bin/$OLBPROG' as the executable. -# -OLBPROG=olbd - -# Set OLBCONFIG the default config file name. Normally, this would be -# '$OLBBASE/$OLBVER/etc/xrootd.cf'. -# -OLBCONFIGFN=$OLBBASE/$OLBVER/etc/xrootd.cf - -# Set 'OLBHOMEDIR' to be the working directory when olbd is started -# -OLBHOMEDIR=/var/adm/olbd/core - -# Set 'OLBLOGDIR' to be the directory where log files are placed and -# Set 'OLBLOGFN' to be the base log file name. -# -OLBLOGDIR=/var/adm/xrootd/logs -OLBLOGFN=olbdlog - -# Set 'OLBPIDFILE' to be where the pid file goes (it is check for w-access) -# -OLBPIDFILE=/tmp/olbd.pid - -#-#-#-#-#-#-#-#-# E N D O F C O N F I G U R A T I O N #-#-#-#-#-#-#-#-# - -# The following logic tries to set variables as follows: - -# MYOS - the current os name -# MAXFD - the file descriptor limit -# MYNAME - the current username -# PROGRAM - the name of the start script -# -MYOS=`/bin/uname | /bin/awk '{print $1}'` -if [ "$MYOS" = "SunOS" ]; then -MAXFD=`ulimit -H -n` -MYNAME=`/usr/ucb/whoami` -else -MAXFD=`ulimit -H -n | /bin/grep files | /bin/awk '{print $3}'` -if [ "$MAXFD" = "" ]; then -MAXFD=`ulimit -H -n` -fi -MYNAME=`/usr/bin/whoami` -fi - -############################################ -PROGRAM=`echo $0 | /bin/awk '{n=split($0,x,"/"); print x[n]}'` diff --git a/packaging/tgz/StartXRD b/packaging/tgz/StartXRD deleted file mode 100755 index 88c77c58c57..00000000000 --- a/packaging/tgz/StartXRD +++ /dev/null @@ -1,204 +0,0 @@ -#!/bin/sh -# -# $Id$ -# -# (C) 2003 by the Board of Trustees of the Leland Stanford, Jr., University -# All Rights Reserved -# Produced by Andrew Hanushevsky for Stanford University under contract -# DE-AC03-76-SFO0515 with the Deprtment of Energy - -# Syntax: StartXRD [-c cfile] [-D] [-v] [oth] - -# Where: -c specifies the configuration file to be used. -# -D turns on script debugging. -# -f does not use an arbitrary port even when it is possible to do so. -# -s is the service name whose port number is to be used by xrootd. -# -v produces verbose output. -# oth Any other single letter options to be passed to the executable. - -# -# Set default values -# -. $0.cf - -VERBOSE=0 - -# Change TEST to be /bin/echo to see what will be executed -# -TEST= - -umask 002 - -############################################################################## -# s u b r o u t i n e s # -############################################################################## - -Debug () { - if test $VERBOSE -eq 1; then - echo $1 - fi - } - -MustExist () { - Debug "Checking existence of $1 $3 ..." - if test ! -${2} $3 ; then - Notify "$1 $3 not found." - fi - } - -Writable () { - Debug "Checking writability of $1 $2 ..." - if test ! -w $2 ; then - Notify "$1 $2 is not writable by $MYNAME." - fi - } - -Wait4File () { - Debug "Checking file $2 ..." - tcnt=$count - until test -${1} $2 -o $tcnt -eq 0; do - echo StartXRD: File $2 not found\; waiting $time seconds... - sleep $time - tcnt=`/usr/bin/expr $tcnt - 1` - done - if [ $tcnt -le 0 ]; then - Notify "File $2 not found." - fi - } - -Notify () { - echo StartXRD: $1 - exit 4 - } - -############################################################################## -# m a i n p r o g r a m # -############################################################################## - -# Pick up options -# -XRDOPTIONS= -XRDPARMS=$* -XRDPORT= -XRDLOGSFX= - -while test -n "$1"; do - if [ "$1" = "-c" ]; then - if [ -z "$2" ]; then - Notify "Configuration file not specified." - fi - if [ ! -r "$2" ]; then - Notify "Configuration file '$2' not found." - fi - XRDCONFIGFN=$2 - shift - elif [ "$1" = "-D" ]; then - set -x - elif [ "$1" = "-t" ]; then - TEST=echo - elif [ "$1" = "-v" ]; then - VERBOSE=1 - else - XRDOPTIONS="$XRDOPTIONS $1" - fi - shift - done - -# Establish location of the XRD executable and libraries -# - XRDBASE=`(cd $XRDBASE; pwd)` - Debug "XRDBASE has been set to '$XRDBASE'" - if [ $? != 0 ]; then - exit 4 - fi - - XRDPROG=$XRDBASE/bin/$XRDARCH/xrootd - XRDLIBBASE=$XRDBASE/lib/$XRDARCH - PROGRAM=$XRDCFG/$PROGRAM - if [ "$XRDCONFIGFN" = "" ]; then - XRDCONFIGFN=$XRDCFG/$XRDCONFIG - fi - - if [ -z "$LD_LIBRARY_PATH" ]; then - LD_LIBRARY_PATH=$XRDLIBBASE - else - LD_LIBRARY_PATH=$XRDLIBBASE:$LD_LIBRARY_PATH - fi - export LD_LIBRARY_PATH - -# Verify that we are not executing this script as root (if we are, switch) -# -if [ $MYNAME = root ]; then - if [ $XRDUSER != root ]; then - $TEST export LD_LIBRARY_PATH - $TEST exec su $XRDUSER -c "$PROGRAM $XRDPARMS" - fi - elif [ $MYNAME != $XRDUSER ]; then - Notify "Attempt to start $XRDUSER XRD as user $MYNAME." -fi - -# Establish the port number to be used. Treat this as an explicit override of the main config file. -# - if [ "x$XRDPORT" != "x" ]; then - XRDPORT="-p $XRDPORT" - fi - -# Set the log file name -# -XRDLOGFILE=$XRDLOGDIR/$XRDLOGFN - -# Just in case we don't have the basic directories, try to create them -# -$TEST mkdir -m 0744 -p $XRDLOGDIR $XRDHOMEDIR 2> /dev/null - -# Verify that all required directories are present -# -for FN in $XRDLIBBASE $XRDLOGDIR $XRDHOMEDIR - do - MustExist Directory d $FN -done - -# Verify that all owned directories are writable -# -for FN in $XRDLOGDIR $XRDHOMEDIR - do - Writable Directory $FN -done - -# Verify that all required readable files are present -# -for FN in $XRDCONFIGFN - do - Wait4File r $FN -done - -# Verify that all required executable files are present -# -for FN in $XRDPROG - do - Wait4File x $FN -done - -# Export the variables required in the config file -# -$TEST export XRDCFG -$TEST export XRDBASE -$TEST export XRDLIBBASE - -# Set appropriate limits -# -$TEST ulimit -n $MAXFD -$TEST ulimit -c unlimited - -# Now start the daemon -# -echo Starting xrootd ... -$TEST cd $XRDHOMEDIR -$TEST $XRDPROG $XRDOPTIONS $XRDPORT -l $XRDLOGFILE -c $XRDCONFIGFN & -stat=$? - -# Check if we were successful -# -if test $stat -gt 0 ; then - Notify "$XRDPROG returned a status of ${stat}." - fi diff --git a/packaging/tgz/StartXRD.cf.example b/packaging/tgz/StartXRD.cf.example deleted file mode 100644 index d2fe34d10df..00000000000 --- a/packaging/tgz/StartXRD.cf.example +++ /dev/null @@ -1,134 +0,0 @@ -#!/bin/sh -# -# $Id$ -# - -# Set 'time' to be number of seconds to wait for a required file to appear -# This is only meaningful for files in AFS or NFS -# -time=60 - -# Set 'count' to be the maximum number of times to wait for a required file -# This is only meaningful for files in AFS or NFS -# -count=30 - -# Set XRDUSER to be the username to "su" to if the script is run as root -# -XRDUSER=fabrizio - -# Set XRDBASE to be the base directory where xrootd has -# been installed or compiled. -# The default is the upper dir with respect to where this script resides -# -parent=`dirname $0` -parent=`cd $parent ; pwd` -XRDBASE=`dirname $parent` - -# In the case where configure.classic has not been invoked with the -# option --no-arch-subdirs, we have arch subdirs. -# Set XRDARCH to the architecture you want to start xrootd on -# This default behavior takes the default architecture if it detects such a schema -XRDARCH= -if test -x $XRDBASE/bin/arch ; then - XRDARCH=arch - else - if test -x $XRDBASE/bin/arch_dbg ; then - XRDARCH=arch_dbg - fi -fi - -# Set XRDCFG to be the name of the default directory where config data and -# scripts are to be found -XRDCFG=$XRDBASE/etc - -# Set XRDCONFIG the default config file name. The start script uses -# '$XRDCFG/$XRDCONFIG' as the configuration file. -# -XRDCONFIG=xrootd.cf - -# Set 'XRDHOMEDIR' to be the working directory when xrootd is started -# Set 'CMSHOMEDIR' to be the working directory when cmsd is started -# -XRDHOMEDIR=$XRDBASE/core -CMSHOMEDIR=$XRDBASE/core - -# Set 'XRDLOGDIR' to be the directory where log files are placed and -# Set 'XRDLOGFN' to be the base log file name for xrootd. -# Set 'CMSLOGFN' to be the base log file name for cmsd. -# ---> If you want to use the automatic logfile rotation sheme, set each LOGFN -# variable to ' -k num | sz[k|m|g]'. For example, a 7 day rotation -# for cmslog: CMSLOGFN='cmslog -k 7'. The -k option is documented in the -# XRD/Xrootd and cmsd configuration reference under command line options. -# -XRDLOGDIR=$XRDBASE/logs -XRDLOGFN=xrdlog -CMSLOGFN=cmslog - -#-#-#-#-#-#-#-# F I L E R E S I D E N C Y M A N A G E R #-#-#-#-#-#-#-#-# - -# The following sets the start-up command and command line parameters for -# each daemon. Add as needed (e.g., log file options, instance name, etc) - -# The following are common options -# -LDLIBPATH=$XRDBASE/lib/$XRDARCH - -# Options that need to be set: -# -# CONFG_ -> What configuration file to use -# HFILE_ -> The file who's presence prevents the component's [re]start -# HOMED_ -> Where the home directory is (where core files will go) -# LOGFN_ -> Where the log is to be written (full path with filename) -# START_ -> What program to start - -# Set options for purge -# -HFILE_PURGE='/var/adm/frm/HOLD_PURGE' -HOMED_PURGE='/var/adm/frm/core/purg' -LOGFN_PURGE='/var/adm/frm/logs/purglog' -START_PURGE="$XRDBASE/bin/$XRDARCH/frm_purged" -PARMS_PURGE="-c $XRDCFG/$XRDCONFIG -l $LOGFN_PURGE" - -# Set options for xfrd -# -HFILE_TRANSFER='/var/adm/frm/HOLD_TRANSFER' -HOMED_TRANSFER='/var/adm/frm/core/xfr' -LOGFN_TRANSFER='/var/adm/frm/logs/xfrlog' -START_TRANSFER="$XRDBASE/bin/$XRDARCH/frm_xfrd" -PARMS_TRANSFER="-c $XRDCFG/$XRDCONFIG -l $LOGFN_TRANSFER" - -#-#-#-#-#-#-#-#-# E N D O F C O N F I G U R A T I O N #-#-#-#-#-#-#-#-# - - -# The following logic tries to set variables as follows: - -# MYOS - the current os name -# MAXFD - the file descriptor limit -# MYNAME - the current username -# PROGRAM - the name of the start script -# -MYOS=`uname | awk '{print $1}'` -if [ "$MYOS" = "SunOS" ]; then - -MAXFD=`ulimit -H -n` -MYNAME=`/usr/ucb/whoami` -else - -if [ "$MYOS" = "Darwin" ]; then -MAXFD=1024 -else - -MAXFD=`ulimit -H -n | grep files | awk '{print $3}'` -if [ "$MAXFD" = "" ]; then -MAXFD=`ulimit -H -n` -fi - -fi - -MYNAME=`whoami` -fi - -############################################ -PROGRAM=`echo $0 | awk '{n=split($0,x,"/"); print x[n]}'` - diff --git a/packaging/tgz/StopCMS b/packaging/tgz/StopCMS deleted file mode 100755 index 68dc970ac8a..00000000000 --- a/packaging/tgz/StopCMS +++ /dev/null @@ -1,131 +0,0 @@ -#!/bin/sh -# -# $Id$ -# -# (C) 2003 by the Board of Trustees of the Leland Stanford, Jr., University -# All Rights Reserved -# Produced by Andrew Hanushevsky for Stanford University under contract -# DE-AC03-76-SFO0515 with the Deprtment of Energy -#Syntax: StopCMS - -# The following snippet is from the StartXRD.cf file. -# -MYOS=`uname | awk '{print $1}'` -if [ "$MYOS" = "SunOS" ]; then -MYNAME=`/usr/ucb/whoami` -else -MYNAME=`whoami` -fi -CONDCHK=0 - -############################################################################## -# s u b r o u t i n e s # -############################################################################## - -Emsg () { - echo StopCMS: $1 - exit 4 - } - -Terminate() { - cmspid=$1 - killpid=$2 - - # Verify that we can kill this process - # - if [ $MYNAME != root ]; then - set -- `ps -p $cmspid -o user` - if [ $2 != $MYNAME ]; then - Emsg "User $MYNAME can't kill process $cmspid started by $2." - fi - fi - - # Now kill the process - # - set -- `kill -9 $killpid 2>&1` - if [ $? -ne 0 ]; then - shift 2 - Emsg "Unable to kill process $cmspid; $*." - fi - } - -Check(){ - cmspid=$1 - - # Check if the process is indeed dead - # - FOO=`ps -p $cmspid` - if [ $? -eq 0 ]; then - sleep 1 - FOO=`ps -p $cmspid` - if [ $? -eq 0 ]; then - echo "pid $cmspid is still alive..." - echo $FOO - fi - fi - - } - -############################################################################## -# m a i n p r o g r a m # -############################################################################## - -# Pick up options -# -while test -n "$1"; do - if test -n "'$1'" -a "'$1'" = "'-D'"; then - set -x - elif test -n "'$1'" -a "'$1'" = "'-c'"; then - CONDCHK=1 - else - echo StopCMS: Invalid option - $1. - fi - shift - done - -# find the process number assigned to the CMS -# - set -- `ps -e -o pid -o comm | grep '.*cmsd$' | awk '{print $1}'` - - if [ -z "$1" ]; then - msg="Unable to find CMS process number" - if [ $CONDCHK -ne 1 ]; then - Emsg "$msg; is it running?" - fi - echo StopCMS: $msg\; continuing. - exit 0 - fi - -# Kill each process that we have found -# - for cmspid do - Terminate $cmspid $cmspid - done - -# Make Sure they are dead -# - sleep 1 - for cmspid do - Check $cmspid - done - -# find and kill the process numbers assigned to the performance monitor -# - set -- '' - set -- `ps -e -o pid -o args | grep 'XrdOlbMonPerf' | grep -v grep | awk '{print $1}'` - - if [ ! -z "$1" ]; then - for cmspid do - Terminate $cmspid -$cmspid - done - fi - set -- '' - set -- `ps -e -o pid -o args | grep 'XrdCmsMonPerf' | grep -v grep | awk '{print $1}'` - - if [ ! -z "$1" ]; then - for cmspid do - Terminate $cmspid -$cmspid - done - fi - - echo StopCMS: CMS stopped. diff --git a/packaging/tgz/StopFRM b/packaging/tgz/StopFRM deleted file mode 100755 index 8c90b6a4e46..00000000000 --- a/packaging/tgz/StopFRM +++ /dev/null @@ -1,156 +0,0 @@ -#!/bin/sh -# Usage: StopFRM [-h] [-p] [-t] [-x] - -# Where: -h holds the daemon (i.e., prevents it from restarting) -# -p stops only the purge daemon -# -t types the stop commands and does not execute them -# -x stops only the transfer daemon -# No option cause everything to be started. - -# Set defaults -#******************************************************************************# -#* D e f a u l t s *# -#******************************************************************************# - -TEST= -EXITRC=0 -DOALL=1 -HOLD= -ONLYP=0 -ONLYX=0 - -# Source he config file to set additional options -# -. `dirname $0`/StartXRD.cf - -#******************************************************************************# -#* S u b r o u t i n e s *# -#******************************************************************************# - -Emsg () { - echo StopFRM: $1 - } - -Check(){ - frmwho=$1 - frmpid=$2 - - # Check if the process is indeed dead - # - FOO=`ps -p $frmpid` - if [ $? -eq 0 ]; then - sleep 1 - FOO=`ps -p $frmpid` - if [ $? -eq 0 ]; then - Emsg "$XRDUSER $frmwho pid $frmpid is still alive..." - echo $FOO - fi - fi - - } - -Kill() { - frmwho=$1 - frmpid=$2 - - # Verify that we can kill this process - # - if [ $MYNAME != root ]; then - set -- `ps -p $frmpid -o user` - if [ $2 != $MYNAME ]; then - Emsg "User $MYNAME can't kill $frmwho process $frmpid started by $2." - fi - fi - - # Now kill the process - # - kCMD="kill -9 $frmpid" - if [ -z "$TEST" ]; then - $TEST $kCMD - else - set -- `$kCMD 2>&1` - fi - - # Chek if we were successful - # - if [ $? -ne 0 ]; then - shift 2 - Emsg "Unable to kill $frmwho process $frmpid; $*." - else - Check $frmwho $frmpid - fi - } - -Stop() { - frmwho=$1 - frmpgm=$2 - frmfnx=$3 - set -- '' - set -- `ps -e -o pid -o args | grep $frmpgm | awk '$0 !~ /grep/ {print $1}'` - - if [ -z "$1" ]; then - Emsg "Unable to find $XRDUSER $frmwho daemon." - else - for frmpid do - Kill $frmwho $frmpid - done - fi - if [ "$HOLD" = "1" ]; then - set -- `touch $frmfnx` - fi - } - -#******************************************************************************# -#* M a i n *# -#******************************************************************************# - -# Pick up options -# -while test -n "$1"; do - if [ "$1" = "-h" ]; then - HOLD=1 - elif [ "$1" = "-p" ]; then - DOALL=0 - ONLYP=1 - elif [ "$1" = "-t" ]; then - TEST=echo - OPTS=$OPTS" -t" - elif [ "$1" = "-x" ]; then - DOALL=0 - ONLYX=1 - else - Emsg "Invalid option - $1." - echo 'Usage: StopFRM [-h] [-p] [-t] [-x]' - exit 1 - fi - shift - done - -# Determine hostname and username -# -MYHOST=`/bin/hostname` - -if [ -x /usr/bin/whoami ]; then - MYNAME=`/usr/bin/whoami` -elif [ -x /usr/ucb/whoami ]; then - MYNAME=`/usr/ucb/whoami` -else - Emsg "Unable to determine username." - exit 1 -fi - -# Now stop the purge daemon -# -if [ ! -z "$START_PURGE" -a "$DOALL" = "1" -o "$ONLYP" = "1" ]; then - Emsg "Stopping $XRDUSER purge daemon. . ." - Stop purge $START_PURGE $HFILE_PURGE -fi - -# Now stop the transfer daemon -# -if [ "$DOALL" = "1" -o "$ONLYX" = "1" ]; then - Emsg "Stopping $XRDUSER transfer daemon. . ." - Stop transfer $START_TRANSFER $HFILE_TRANSFER -fi - -exit $EXITRC diff --git a/packaging/tgz/StopOLB b/packaging/tgz/StopOLB deleted file mode 100755 index 09756aff050..00000000000 --- a/packaging/tgz/StopOLB +++ /dev/null @@ -1,123 +0,0 @@ -#!/bin/sh -# -# $Id$ -# -# (C) 2003 by the Board of Trustees of the Leland Stanford, Jr., University -# All Rights Reserved -# Produced by Andrew Hanushevsky for Stanford University under contract -# DE-AC03-76-SFO0515 with the Deprtment of Energy -#Syntax: StopOLB - -# The following snippet is from the StartOLB.cf file. -# -MYOS=`/bin/uname | /bin/awk '{print $1}'` -if [ "$MYOS" = "SunOS" ]; then -MYNAME=`/usr/ucb/whoami` -else -MYNAME=`/usr/bin/whoami` -fi -CONDCHK=0 - -############################################################################## -# s u b r o u t i n e s # -############################################################################## - -Emsg () { - /bin/echo StopOLB: $1 - exit 4 - } - -Terminate() { - olbpid=$1 - killpid=$2 - - # Verify that we can kill this process - # - if [ $MYNAME != root ]; then - set -- `/bin/ps -p $olbpid -o user` - if [ $2 != $MYNAME ]; then - Emsg "User $MYNAME can't kill process $olbpid started by $2." - fi - fi - - # Now kill the process - # - set -- `/bin/kill -9 $killpid 2>&1` - if [ $? -ne 0 ]; then - shift 2 - Emsg "Unable to kill process $olbpid; $*." - fi - } - -Check(){ - olbpid=$1 - - # Check if the process is indeed dead - # - FOO=`/bin/ps -p $olbpid` - if [ $? -eq 0 ]; then - sleep 1 - FOO=`/bin/ps -p $olbpid` - if [ $? -eq 0 ]; then - /bin/echo "pid $olbpid is still alive..." - /bin/echo $FOO - fi - fi - - } - -############################################################################## -# m a i n p r o g r a m # -############################################################################## - -# Pick up options -# -while test -n "$1"; do - if test -n "'$1'" -a "'$1'" = "'-d'"; then - set -x - elif test -n "'$1'" -a "'$1'" = "'-c'"; then - CONDCHK=1 - else - /bin/echo StopOLB: Invalid option - $1. - fi - shift - done - -# find the process number assigned to the OLB -# - set -- `ps -e -o pid -o comm | grep '.*olbd$' | awk '{print $1}'` - - if [ -z "$1" ]; then - msg="Unable to find OLB process number" - if [ $CONDCHK -ne 1 ]; then - Emsg "$msg; is it running?" - fi - /bin/echo StopOLB: $msg\; continuing. - exit 0 - fi - -# Kill each process that we have found -# - for olbpid do - Terminate $olbpid $olbpid - done - -# Make Sure they are dead -# - sleep 1 - for olbpid do - Check $olbpid - done - -# find and kill the process numbers assigned to the performance monitor -# - set -- '' - set -- `ps -e -o pid -o args | grep 'XrdOlbMonPerf' | grep -v grep | awk '{print $1}'` - - if [ ! -z "$1" ]; then - for olbpid do - Terminate $olbpid -$olbpid - done - fi - - /bin/echo StopOLB: OLB stopped. diff --git a/packaging/tgz/StopXRD b/packaging/tgz/StopXRD deleted file mode 100755 index bc3d7cc46fc..00000000000 --- a/packaging/tgz/StopXRD +++ /dev/null @@ -1,118 +0,0 @@ -#!/bin/sh -# -# $Id$ -# -# (C) 2003 by the Board of Trustees of the Leland Stanford, Jr., University -# All Rights Reserved -# Produced by Andrew Hanushevsky for Stanford University under contract -# DE-AC03-76-SFO0515 with the Deprtment of Energy -#Syntax: StopXRD - -# The following snippet is from the StartXRD.cf file. -# -MYOS=`uname | awk '{print $1}'` -if [ "$MYOS" = "SunOS" ]; then -MYNAME=`/usr/ucb/whoami` -else -MYNAME=`whoami` -fi - -CONDCHK=0 - -############################################################################## -# s u b r o u t i n e s # -############################################################################## - -Debug () { - if test $VERBOSE -eq 1; then - echo $1 - fi - } - -Emsg () { - echo StopXRD: $1 - exit 4 - } - -Terminate() { - xrdpid=$1 - - # Verify that we can kill this process - # - if [ $MYNAME != root ]; then - set -- `ps -p $xrdpid -o user` - if [ $2 != $MYNAME ]; then - Emsg "User $MYNAME can't kill process $xrdpid started by $2." - fi - fi - - # Now kill the process - # - set -- `kill -9 $xrdpid 2>&1` - if [ $? -ne 0 ]; then - shift 2 - Emsg "Unable to kill process $xrdpid; $*." - fi - } - -Check(){ - xrdpid=$1 - - # Check if the process is indeed dead - # - FOO=`ps -p $xrdpid` - if [ $? -eq 0 ]; then - sleep 1 - FOO=`ps -p $xrdpid` - if [ $? -eq 0 ]; then - echo "pid $xrdpid is still alive..." - echo $FOO - fi - fi - - } - -############################################################################## -# m a i n p r o g r a m # -############################################################################## - -# Pick up options -# -while test -n "$1"; do - if test -n "'$1'" -a "'$1'" = "'-D'"; then - set -x - elif test -n "'$1'" -a "'$1'" = "'-c'"; then - CONDCHK=1 - else - Notify "Invalid option '$1'." - fi - shift - done - -# find the process number assigned to the XRD -# - set -- `ps -e -o pid -o comm | grep '.*xrootd$' | awk '{print $1}'` - - if [ -z "$1" ]; then - msg="Unable to find XRD process number" - if [ $CONDCHK -ne 1 ]; then - Emsg "$msg; is it running?" - fi - echo StopXRD: $msg\; continuing. - exit 0 - fi - -# Kill each process that we have found -# - for xrdpid do - Terminate $xrdpid - done - -# Make Sure they are dead -# - sleep 1 - for xrdpid do - Check $xrdpid - done - - echo StopXRD: XRD stopped. diff --git a/packaging/tgz/xrootd.cf.example b/packaging/tgz/xrootd.cf.example deleted file mode 100644 index 9f14b8b97fe..00000000000 --- a/packaging/tgz/xrootd.cf.example +++ /dev/null @@ -1,100 +0,0 @@ -# -# A very simple config script to start a local xrootd+cmsd node -# -# 20071219, Fabrizio Furano - - - -################## -# -# Variables. Some of them are taken from the environment -# and set by the StartXXX scripts - -// Leading path for the installed distribution -set MyXrootdPath=$XRDBASE - -// Leading path for the xrootd libraries -set MyXrootdLibBase=$XRDLIBBASE - -// where to find alternate scripts and cfg file -set MyScriptsPath=$XRDCFG - - -################## - - -# # -# COMMON section # -# # - -# This is a sample script, so any path is good to export -all.export / r/w - -all.role server - -# Note: if-fi does not support nesting - -# The global meta manager (if any) should match this -if metamanagerhost* -all.role meta manager -all.manager meta metamanagerhost 1213 -fi - -# The manager node of the local cluster we are dealing with -if managerhost* -all.role manager -all.manager managerhost 1213 - -# Uncomment and complete if you have a meta manager -#all.manager meta metamanagerhost 1213 -fi - -# All (and only) the data servers have to match this -# Use the proper aggergatingn2n lib to set up a facultative prefix for paths -# in the case you want to aggregate clusters with different storage path prefixes, -# and making all of them subscribe to a global meta manager -# Uncomment and fill the manager host name if you run a manager in your cluster -if * -all.role server - -# Preferred for clusters -#xrd.port any - -#all.manager managerhost 1213 - -#oss.namelib libXrdAggregatingName2Name.so /my/funny/facultative/local/cluster/storage/prefix -fi - - -# # -# XRD Daemon section # -# # - -xrd.protocol xrootd * - - -# # -# XROOTD Section # -# # - -xrootd.fslib $(MyXrootdLibBase)/libXrdOfs.so - -# # -# CMSD Section # -# # - - -cms.delay servers 1 startup 10 -cms.sched cpu 10 io 90 -cms.perf int 3m pgm $(MyXrootdPath)/etc/XrdOlbMonPerf 110 - -# Specify how server are selected for file creation -olb.space linger 0 recalc 10 min 1g 2g - -# # -# OFS and OSS Section # -# # - -oss.alloc * * 80 - - diff --git a/packaging/tgz/xrootd.cf.example2 b/packaging/tgz/xrootd.cf.example2 deleted file mode 100644 index e34fc2b8f8c..00000000000 --- a/packaging/tgz/xrootd.cf.example2 +++ /dev/null @@ -1,134 +0,0 @@ -# -# $Id$ -# -# The following is a sample xrootd configuration. The relevant prefixes are: -# -# acc - Access Control -# cms - Cluster Management Service -# ofs - Open File System -# oss - Open Storage System -# sec - Security -# xrd - Extended Request Daemon -# xrootd - Xrootd Service - -#----------------------------------------------- - -# The Common Section (applies to every component) -# - -# Tell everyone who is the redirection manager -# -all.manager kanrdr-a+ 3121 - -# Identify which machines will be redirecting clients -# -all.role manager if kanrdr-a+ - -# Identify which machines clients will be redirected to (need not be inclusive) -# -all.role server if kan0* - -# Finally, we need to indicate processing options by path. For instance, -# which ones are exported (i.e., visible) and which ones are migratable (which -# will indicate the logical paths (i.e., lfn's) that should exist in the MSS.) -# -all.export /store nodread mig - -#----------------------------------------------- - -# The Open Storage System Section -# - -# Indicate where we have mounted filesystems that can be used for space -# The cache directive will also be used by the cmsd. So, we need not repeat it. -# -oss.cache public /kanga/cache* - -# While we highly recommend that you avoid path prefixing, here we indicate -# the actual way files are physically name (i.e., the lfn to pfn mapping). The -# localroot is how we name this on the data server while remoteroot tells the -# system the corresponding name in the Mass Storage System. -# -oss.localroot /kanga -oss.remoteroot /kanga - -# Since we are using a Mass Storage System we need to indicate how MSS meta-data -# information is retrieved (mssgwcmd) and how files are retrieved (stagecmd). -# The xfr directive will limit the sumber of simultaneous stages to eight. -# -oss.msscmd /usr/etc/ooss/pudc -f -c /usr/etc/ooss/rxhpss.cf -oss.stagecmd /usr/etc/ooss/ooss_Stage -oss.xfr 8 - -#----------------------------------------------- - -# The XRD Section -# -# Generally, xrd defaults are fine. So, no need to change them. -# - -#----------------------------------------------- - -# The Xrootd Section -# -# Here we load the extended file system support for xrootd -# -xrootd.fslib /opt/xrootd/lib/libXrdOfs.so - -#----------------------------------------------- - -# The Cluster Management Section -# - -# Managers use the allow and sched directives. -# Servers use the allow and perf directives. -# -# Indicate which hosts are allowed to connect to the cmsd (even if localhost) -# -cms.allow host kan*.slac.stanford.edu -cms.allow host bbr-rdr*.slac.stanford.edu - -# To use load based scheduling, specify a load formula using sched -# -cms.sched cpu 100 - -# To effect load based scheduling, we must start a performance monitor -# -cms.perf int 180 pgm /opt/xrootd/etc/XrdOlbMonPerf 120 - -#----------------------------------------------- - -# The MPS Section -# -# Here code the relevant dorectives to control migration, purge, and staging -# Most of the defaults are likely wrong. The typical ones to specify are: -# -# Where error messages go via mail and how often they should go -# -mps.adminuser = sys-hpss - -# For migration, how long to wait between runs, how long a failure is to be -# recognized (after which the operation is retried). how often purge should -# run, and the high/low purge thesholds. -# -mps.migr.rmfail_time = 129600 -mps.migr.waittime = 600 -mps.purg.waittime = 3600 -mps.purg.lousedpct = 80 -mps.purg.hiusedpct = 80 - -# For pre-stage, we generally want to indicate the maximum number allowed -# at one time and the command to use to transfer data from the mss -# -mps.pstg.max_pstg_proc = 3 -mps.pstg.pstgcmd = /opt/xrootd/utils/mps_Stage -i - -# Generally, for all component, we need to indicate who the MSS transfer user -# is, the target host and port. Usually, we limit retries to two. -# -mps.xfrhost = babarmss -mps.xfruser = objysrv -mps.xfrport = 2021 -mps.stage.max_retry = 2 - -#----------------------------------------------- diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 60cb227d377..9960e8273e4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,92 +2,5 @@ #------------------------------------------------------------------------------- # Include the subcomponents #------------------------------------------------------------------------------- -include( XrdUtils ) -include( XrdCrypto ) +include( XrdCeph ) -include( XrdSec ) - -if( BUILD_CRYPTO ) - include( XrdSecgsi ) -endif() - -if( BUILD_KRB5 ) - include( XrdSeckrb5 ) -endif() - -include( XrdClient ) - -if( ENABLE_XRDCL ) - add_subdirectory( XrdCl ) -endif() - -include( XrdServer ) -include( XrdDaemons ) -include( XrdFrm ) - -include( XrdXml ) -include( XrdPosix ) -include( XrdFfs ) -include( XrdPlugins ) -include( XrdSsi ) -include( XrdApps ) -include( XrdCns ) -include( XrdHeaders ) - -include( XrdFileCache ) - -if( BUILD_HTTP ) - include( XrdHttp ) -endif() - -if( BUILD_CEPH ) - include( XrdCeph ) -endif() - -#------------------------------------------------------------------------------- -# Install the utility scripts -#------------------------------------------------------------------------------- -install( - FILES - ${CMAKE_SOURCE_DIR}/utils/XrdCmsNotify.pm - ${CMAKE_SOURCE_DIR}/utils/netchk - ${CMAKE_SOURCE_DIR}/utils/XrdOlbMonPerf - DESTINATION ${CMAKE_INSTALL_DATADIR}/xrootd/utils - PERMISSIONS - OWNER_EXECUTE OWNER_WRITE OWNER_READ - GROUP_EXECUTE GROUP_READ - WORLD_EXECUTE WORLD_READ ) - -#------------------------------------------------------------------------------- -# Install xrootd-config -#------------------------------------------------------------------------------- -install( - CODE " - EXECUTE_PROCESS( - COMMAND cat ${CMAKE_SOURCE_DIR}/utils/xrootd-config - COMMAND sed -e \"s/__VERSION__/${XROOTD_VERSION}/\" - COMMAND sed -e \"s|__INCLUDEDIR__|${CMAKE_INSTALL_INCLUDEDIR}|\" - COMMAND sed -e \"s/__PLUGIN_VERSION__/${PLUGIN_VERSION}/\" - COMMAND sed -e \"s|__PREFIX__|${CMAKE_INSTALL_PREFIX}|\" - OUTPUT_FILE \$ENV{DESTDIR}/${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/xrootd-config ) - EXECUTE_PROCESS( - COMMAND chmod 755 \$ENV{DESTDIR}/${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/xrootd-config )" -) - -#------------------------------------------------------------------------------- -# Post process man pages -#------------------------------------------------------------------------------- -install( - CODE " - FILE(GLOB MANPAGES - \"\$ENV{DESTDIR}/${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_MANDIR}/man[1,8]/*.[1,8]\" ) - FOREACH(MANPAGE \${MANPAGES}) - MESSAGE( \"-- Processing: \" \${MANPAGE} ) - EXECUTE_PROCESS( - COMMAND cat \${MANPAGE} - COMMAND sed -e \"s/__VERSION__/${XROOTD_VERSION}/\" - OUTPUT_FILE \${MANPAGE}.new ) - EXECUTE_PROCESS( - COMMAND mv -f \${MANPAGE}.new \${MANPAGE} ) - ENDFOREACH()" -) diff --git a/src/XProtocol/README b/src/XProtocol/README deleted file mode 100644 index 69454b46376..00000000000 --- a/src/XProtocol/README +++ /dev/null @@ -1,19 +0,0 @@ - - $Id$ - - To insure that changes to the protocol are backward-consistent with -pre-existing servers/clients, the following rules should be followed: - -a) Any new status/request codes need to be added to the end of the list of - existing codes. - -b) Only reserved fields may contain new information (i.e., we can't change - the definition of existing fields except to add new status bits to an - existing flag). - -c) By extension, structure sizes or layout may not change for any existing - structure passed to the server. - -Any other addition is fair game since the server doesn't really care. - - diff --git a/src/XProtocol/XProtocol.cc b/src/XProtocol/XProtocol.cc deleted file mode 100644 index 352d7d04cc6..00000000000 --- a/src/XProtocol/XProtocol.cc +++ /dev/null @@ -1,124 +0,0 @@ -/******************************************************************************/ -/* */ -/* X P r o t o c o l . c c */ -/* */ -/* (c) 2016 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include - -#include "XProtocol/XProtocol.hh" - -/******************************************************************************/ -/* G l o b a l s */ -/******************************************************************************/ - -namespace -{ -const char *errNames[kXR_ERRFENCE-kXR_ArgInvalid] = - {"Invalid argument", // kXR_ArgInvalid = 3000, - "Missing agument", // kXR_ArgMissing - "Argument is too long", // kXR_ArgTooLong - "File or object is locked", // kXR_FileLocked - "File or object not open", // kXR_FileNotOpen - "Filesystem error", // kXR_FSError - "Invalid request", // kXR_InvalidRequest - "I/O error", // kXR_IOError - "Insufficient memory", // kXR_NoMemory - "Insufficient space", // kXR_NoSpace - "Request not authorized", // kXR_NotAuthorized - "File or object not found", // kXR_NotFound - "Internal server error", // kXR_ServerError - "Unsupported request", // kXR_Unsupported - "No serves available", // kXR_noserver - "Target is not a file", // kXR_NotFile - "Target is a directory", // kXR_isDirectory - "Request cancelled", // kXR_Cancelled - "Checksum length error", // kXR_ChkLenErr - "Checksum is invalid", // kXR_ChkSumErr - "Request in progress", // kXR_inProgress - "Quota exceeded", // kXR_overQuota - "Invalid signature", // kXR_SigVerErr - "Decryption failed" // kXR_DecryptErr - }; - -const char *reqNames[kXR_REQFENCE-kXR_auth] = - {"auth", "query", "chmod", "close", - "dirlist", "getfile", "protocol", "login", - "mkdir", "mv", "open", "ping", - "putfile", "read", "rm", "rmdir", - "sync", "stat", "set", "write", - "admin", "prepare", "statx", "endsess", - "bind", "readv", "verifyw", "locate", - "truncate", "sigver", "decrypt", "writev" - }; - -// Following value is used to determine if the error or request code is -// host byte or network byte order making use of the fact each starts at 3000 -// -union Endianness {kXR_unt16 xyz; unsigned char Endian[2];} little = {1}; -} - -/******************************************************************************/ -/* e r r N a m e */ -/******************************************************************************/ - -const char *XProtocol::errName(kXR_int32 errCode) -{ -// Mangle request code if the byte orderdoesn't match our host order -// - if ((errCode < 0 || errCode > kXR_ERRFENCE) && little.Endian[0]) - errCode = ntohl(errCode); - -// Validate the request code -// - if (errCode < kXR_ArgInvalid || errCode >= kXR_ERRFENCE) - return "!undefined error"; - -// Return the proper table -// - return errNames[errCode - kXR_ArgInvalid]; -} - -/******************************************************************************/ -/* r e q N a m e */ -/******************************************************************************/ - -const char *XProtocol::reqName(kXR_unt16 reqCode) -{ -// Mangle request code if the byte orderdoesn't match our host order -// - if (reqCode > kXR_REQFENCE && little.Endian[0]) reqCode = ntohs(reqCode); - -// Validate the request code -// - if (reqCode < kXR_auth || reqCode >= kXR_REQFENCE) return "!unknown"; - -// Return the proper table -// - return reqNames[reqCode - kXR_auth]; -} diff --git a/src/XProtocol/XProtocol.hh b/src/XProtocol/XProtocol.hh deleted file mode 100644 index 208a477de5a..00000000000 --- a/src/XProtocol/XProtocol.hh +++ /dev/null @@ -1,939 +0,0 @@ -#ifndef __XPROTOCOL_H -#define __XPROTOCOL_H -/******************************************************************************/ -/* */ -/* X P r o t o c o l . h h */ -/* */ -/* (c) 2012 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -//#ifndef __GNUC__ -//#define __attribute__(x) -//#ifdef SUNCC -//#pragma pack(4) -//#endif -//#endif - -#ifdef __CINT__ -#define __attribute__(x) -#endif - -// The following is the binary representation of the protocol version here. -// Protocol version is repesented as three base10 digits x.y.z with x having no -// upper limit (i.e. n.9.9 + 1 -> n+1.0.0). The kXR_PROTSIGNVERSION defines the -// protocol version where request signing became available. -// -#define kXR_PROTOCOLVERSION 0x00000400 -#define kXR_PROTSIGNVERSION 0x00000310 -#define kXR_PROTOCOLVSTRING "4.0.0" - -#include "XProtocol/XPtypes.hh" - -// KINDS of SERVERS -// -// -#define kXR_DataServer 1 -#define kXR_LBalServer 0 - -// The below are defined for protocol version 2.9.7 or higher -// These are the flag value in the kXR_protool response -// -#define kXR_isManager 0x00000002 -#define kXR_isServer 0x00000001 -#define kXR_attrMeta 0x00000100 -#define kXR_attrProxy 0x00000200 -#define kXR_attrSuper 0x00000400 - -#define kXR_maxReqRetry 10 - -// Kind of error inside a XTNetFile's routine (temporary) -// -enum XReqErrorType { - kGENERICERR = 0, // Generic error - kREAD, // Error while reading from stream - kWRITE, // Error while writing to stream - kREDIRCONNECT, // Error redirecting to a given host - kOK, // Everything seems ok - kNOMORESTREAMS // No more available stream IDs for - // async processing -}; - -//______________________________________________ -// PROTOCOL DEFINITION: CLIENT'S REQUESTS TYPES -//______________________________________________ -// -enum XRequestTypes { - kXR_auth = 3000, - kXR_query, // 3001 - kXR_chmod, // 3002 - kXR_close, // 3003 - kXR_dirlist, // 3004 - kXR_getfile, // 3005 - kXR_protocol,// 3006 - kXR_login, // 3007 - kXR_mkdir, // 3008 - kXR_mv, // 3009 - kXR_open, // 3010 - kXR_ping, // 3011 - kXR_putfile, // 3012 - kXR_read, // 3013 - kXR_rm, // 3014 - kXR_rmdir, // 3015 - kXR_sync, // 3016 - kXR_stat, // 3017 - kXR_set, // 3018 - kXR_write, // 3019 - kXR_admin, // 3020 - kXR_prepare, // 3021 - kXR_statx, // 3022 - kXR_endsess, // 3023 - kXR_bind, // 3024 - kXR_readv, // 3025 - kXR_verifyw, // 3026 - kXR_locate, // 3027 - kXR_truncate,// 3028 - kXR_sigver, // 3029 - kXR_decrypt, // 3030 - kXR_writev, // 3031 - kXR_REQFENCE // Always last valid request code +1 -}; - -// OPEN MODE FOR A REMOTE FILE -enum XOpenRequestMode { - kXR_ur = 0x100, - kXR_uw = 0x080, - kXR_ux = 0x040, - kXR_gr = 0x020, - kXR_gw = 0x010, - kXR_gx = 0x008, - kXR_or = 0x004, - kXR_ow = 0x002, - kXR_ox = 0x001 -}; - -enum XMkdirOptions { - kXR_mknone = 0, - kXR_mkdirpath = 1 -}; - -// this is a bitmask -enum XLoginAbility { - kXR_nothing = 0, - kXR_fullurl = 1, - kXR_multipr = 3, - kXR_readrdok= 4, - kXR_hasipv64= 8, - kXR_onlyprv4= 16, - kXR_onlyprv6= 32 -}; - -// this is a bitmask -enum XLoginCapVer { - kXR_lcvnone = 0, - kXR_vermask = 63, - kXR_asyncap = 128 -}; - -// this is a single number that goes into capver as the version -// -enum XLoginVersion { - kXR_ver000 = 0, // Old clients predating history - kXR_ver001 = 1, // Generally implemented 2005 protocol - kXR_ver002 = 2, // Same as 1 but adds asyncresp recognition - kXR_ver003 = 3, // The 2011-2012 rewritten client - kXR_ver004 = 4 // The 2016 sign-capable client -}; - -enum XStatRequestOption { - kXR_vfs = 1 -}; - -enum XStatRespFlags { - kXR_file = 0, - kXR_xset = 1, - kXR_isDir = 2, - kXR_other = 4, - kXR_offline = 8, - kXR_readable=16, - kXR_writable=32, - kXR_poscpend=64, - kXR_bkpexist=128 -}; - -enum XDirlistRequestOption { - kXR_online = 1, - kXR_dstat = 2 -}; - -enum XOpenRequestOption { - kXR_compress = 1, // also locate (return unique hosts) - kXR_delete = 2, - kXR_force = 4, - kXR_new = 8, - kXR_open_read= 16, - kXR_open_updt= 32, - kXR_async = 64, - kXR_refresh = 128, // also locate - kXR_mkpath = 256, - kXR_prefname = 256, // only locate - kXR_open_apnd= 512, - kXR_retstat = 1024, - kXR_replica = 2048, - kXR_posc = 4096, - kXR_nowait = 8192, // also locate - kXR_seqio =16384, - kXR_open_wrto=32768 -}; - -enum XProtocolRequestFlags { - kXR_secreqs = 1 // Return security requirements -}; - -enum XQueryType { - kXR_QStats = 1, - kXR_QPrep = 2, - kXR_Qcksum = 3, - kXR_Qxattr = 4, - kXR_Qspace = 5, - kXR_Qckscan= 6, - kXR_Qconfig= 7, - kXR_Qvisa = 8, - kXR_Qopaque=16, - kXR_Qopaquf=32, - kXR_Qopaqug=64 -}; - -enum XVerifyType { - kXR_nocrc = 0, - kXR_crc32 = 1 -}; - -enum XLogonType { - kXR_useruser = 0, - kXR_useradmin = 1 -}; - -enum XPrepRequestOption { - kXR_cancel = 1, - kXR_notify = 2, - kXR_noerrs = 4, - kXR_stage = 8, - kXR_wmode = 16, - kXR_coloc = 32, - kXR_fresh = 64 -}; - -// Version used for kXR_decrypt and kXR_sigver and is set in -// Set in SigverRequest::version, DecryptRequest::version and -// ServerResponseReqs_Protocol::secver -#define kXR_secver_0 0 - -// Flags for kXR_decrypt and kXR_sigver -enum XSecFlags { - kXR_nodata = 1 // Request payload was not hashed or encrypted -}; - -// Cryptography used for kXR_sigver SigverRequest::crypto -enum XSecCrypto { - kXR_SHA256 = 0x01, // Hash used - kXR_HashMask = 0x0f, // Mak to extract the hash type - kXR_rsaKey = 0x80 // The rsa key was used -}; - -//_______________________________________________ -// PROTOCOL DEFINITION: SERVER'S RESPONSES TYPES -//_______________________________________________ -// -enum XResponseType { - kXR_ok = 0, - kXR_oksofar = 4000, - kXR_attn, - kXR_authmore, - kXR_error, - kXR_redirect, - kXR_wait, - kXR_waitresp, - kXR_noResponsesYet = 10000 -}; - -//_______________________________________________ -// PROTOCOL DEFINITION: SERVER"S ATTN CODES -//_______________________________________________ - -enum XActionCode { - kXR_asyncab = 5000, - kXR_asyncdi, - kXR_asyncms, - kXR_asyncrd, - kXR_asyncwt, - kXR_asyncav, - kXR_asynunav, - kXR_asyncgo, - kXR_asynresp -}; - -//_______________________________________________ -// PROTOCOL DEFINITION: SERVER'S ERROR CODES -//_______________________________________________ -// -enum XErrorCode { - kXR_ArgInvalid = 3000, - kXR_ArgMissing, - kXR_ArgTooLong, - kXR_FileLocked, - kXR_FileNotOpen, - kXR_FSError, - kXR_InvalidRequest, - kXR_IOError, - kXR_NoMemory, - kXR_NoSpace, - kXR_NotAuthorized, - kXR_NotFound, - kXR_ServerError, - kXR_Unsupported, - kXR_noserver, - kXR_NotFile, - kXR_isDirectory, - kXR_Cancelled, - kXR_ChkLenErr, - kXR_ChkSumErr, - kXR_inProgress, - kXR_overQuota, - kXR_SigVerErr, - kXR_DecryptErr, - kXR_Overloaded, - kXR_ERRFENCE, // Always last valid errcode + 1 - kXR_noErrorYet = 10000 -}; - - -//______________________________________________ -// PROTOCOL DEFINITION: CLIENT'S REQUESTS STRUCTS -//______________________________________________ -// -// We need to pack structures sent all over the net! -// __attribute__((packed)) assures no padding bytes. -// -// Nice bodies of the headers for the client requests. -// Note that the protocol specifies these values to be in network -// byte order when sent -// -// G.Ganis: use of flat structures to avoid packing options - -struct ClientAdminRequest { - kXR_char streamid[2]; - kXR_unt16 requestid; - kXR_char reserved[16]; - kXR_int32 dlen; -}; -struct ClientAuthRequest { - kXR_char streamid[2]; - kXR_unt16 requestid; - kXR_char reserved[12]; - kXR_char credtype[4]; - kXR_int32 dlen; -}; -struct ClientBindRequest { - kXR_char streamid[2]; - kXR_unt16 requestid; - kXR_char sessid[16]; - kXR_int32 dlen; -}; -struct ClientChmodRequest { - kXR_char streamid[2]; - kXR_unt16 requestid; - kXR_char reserved[14]; - kXR_unt16 mode; - kXR_int32 dlen; -}; -struct ClientCloseRequest { - kXR_char streamid[2]; - kXR_unt16 requestid; - kXR_char fhandle[4]; - kXR_int64 fsize; - kXR_char reserved[4]; - kXR_int32 dlen; -}; -struct ClientDecryptRequest { - kXR_char streamid[2]; - kXR_unt16 requestid; - kXR_unt16 expectrid; // Request code of subsequent request - kXR_char version; // Security version being used (see enum XSecVersion) - kXR_char flags; // One or more flags defined in enum XSecFlags - kXR_char reserved[12]; - kXR_int32 dlen; -}; -struct ClientDirlistRequest { - kXR_char streamid[2]; - kXR_unt16 requestid; - kXR_char reserved[15]; - kXR_char options[1]; - kXR_int32 dlen; -}; -struct ClientEndsessRequest { - kXR_char streamid[2]; - kXR_unt16 requestid; - kXR_char sessid[16]; - kXR_int32 dlen; -}; -struct ClientGetfileRequest { - kXR_char streamid[2]; - kXR_unt16 requestid; - kXR_int32 options; - kXR_char reserved[8]; - kXR_int32 buffsz; - kXR_int32 dlen; -}; -struct ClientLocateRequest { - kXR_char streamid[2]; - kXR_unt16 requestid; - kXR_unt16 options; - kXR_char reserved[14]; - kXR_int32 dlen; -}; -struct ClientLoginRequest { - kXR_char streamid[2]; - kXR_unt16 requestid; - kXR_int32 pid; - kXR_char username[8]; - kXR_char reserved; - kXR_char ability; // See XLoginAbility enum flags - kXR_char capver[1]; // See XLoginCapVer enum flags - kXR_char role[1]; - kXR_int32 dlen; -}; -struct ClientMkdirRequest { - kXR_char streamid[2]; - kXR_unt16 requestid; - kXR_char options[1]; - kXR_char reserved[13]; - kXR_unt16 mode; - kXR_int32 dlen; -}; -struct ClientMvRequest { - kXR_char streamid[2]; - kXR_unt16 requestid; - kXR_char reserved[14]; - kXR_int16 arg1len; - kXR_int32 dlen; -}; -struct ClientOpenRequest { - kXR_char streamid[2]; - kXR_unt16 requestid; - kXR_unt16 mode; - kXR_unt16 options; - kXR_char reserved[12]; - kXR_int32 dlen; -}; - -struct ClientPingRequest { - kXR_char streamid[2]; - kXR_unt16 requestid; - kXR_char reserved[16]; - kXR_int32 dlen; -}; -struct ClientProtocolRequest { - kXR_char streamid[2]; - kXR_unt16 requestid; - kXR_int32 clientpv; // 2.9.7 or higher - kXR_char flags; // 3.1.0 or higher - kXR_char reserved[11]; - kXR_int32 dlen; -}; -struct ClientPrepareRequest { - kXR_char streamid[2]; - kXR_unt16 requestid; - kXR_char options; - kXR_char prty; - kXR_unt16 port; // 2.9.9 or higher - kXR_char reserved[12]; - kXR_int32 dlen; -}; -struct ClientPutfileRequest { - kXR_char streamid[2]; - kXR_unt16 requestid; - kXR_int32 options; - kXR_char reserved[8]; - kXR_int32 buffsz; - kXR_int32 dlen; -}; -struct ClientQueryRequest { - kXR_char streamid[2]; - kXR_unt16 requestid; - kXR_unt16 infotype; - kXR_char reserved1[2]; - kXR_char fhandle[4]; - kXR_char reserved2[8]; - kXR_int32 dlen; -}; -struct ClientReadRequest { - kXR_char streamid[2]; - kXR_unt16 requestid; - kXR_char fhandle[4]; - kXR_int64 offset; - kXR_int32 rlen; - kXR_int32 dlen; -}; -struct ClientReadVRequest { - kXR_char streamid[2]; - kXR_unt16 requestid; - kXR_char reserved[15]; - kXR_char pathid; - kXR_int32 dlen; -}; -struct ClientRmRequest { - kXR_char streamid[2]; - kXR_unt16 requestid; - kXR_char reserved[16]; - kXR_int32 dlen; -}; -struct ClientRmdirRequest { - kXR_char streamid[2]; - kXR_unt16 requestid; - kXR_char reserved[16]; - kXR_int32 dlen; -}; -struct ClientSetRequest { - kXR_char streamid[2]; - kXR_unt16 requestid; - kXR_char reserved[15]; - kXR_char modifier; // For security purposes, should be zero - kXR_int32 dlen; -}; -struct ClientSigverRequest { - kXR_char streamid[2]; - kXR_unt16 requestid; - kXR_unt16 expectrid; // Request code of subsequent request - kXR_char version; // Security version being used (see XSecVersion) - kXR_char flags; // One or more flags defined in enum (see XSecFlags) - kXR_unt64 seqno; // Monotonically increasing number (part of hash) - kXR_char crypto; // Cryptography used (see XSecCrypto) - kXR_char rsvd2[3]; - kXR_int32 dlen; -}; -struct ClientStatRequest { - kXR_char streamid[2]; - kXR_unt16 requestid; - kXR_char options; - kXR_char reserved[11]; - kXR_char fhandle[4]; - kXR_int32 dlen; -}; -struct ClientSyncRequest { - kXR_char streamid[2]; - kXR_unt16 requestid; - kXR_char fhandle[4]; - kXR_char reserved[12]; - kXR_int32 dlen; -}; -struct ClientTruncateRequest { - kXR_char streamid[2]; - kXR_unt16 requestid; - kXR_char fhandle[4]; - kXR_int64 offset; - kXR_char reserved[4]; - kXR_int32 dlen; -}; -struct ClientWriteRequest { - kXR_char streamid[2]; - kXR_unt16 requestid; - kXR_char fhandle[4]; - kXR_int64 offset; - kXR_char pathid; - kXR_char reserved[3]; - kXR_int32 dlen; -}; -struct ClientWriteVRequest { - kXR_char streamid[2]; - kXR_unt16 requestid; - kXR_char options; // See static const ints below - kXR_char reserved[15]; - kXR_int32 dlen; - - static const kXR_int32 doSync = 0x01; -}; -struct ClientVerifywRequest { - kXR_char streamid[2]; - kXR_unt16 requestid; - kXR_char fhandle[4]; - kXR_int64 offset; - kXR_char pathid; - kXR_char vertype; // One of XVerifyType - kXR_char reserved[2]; - kXR_int32 dlen; // Includes crc length -}; - -struct ClientRequestHdr { - kXR_char streamid[2]; - kXR_unt16 requestid; - kXR_char body[16]; - kXR_int32 dlen; -}; - -typedef union { - struct ClientRequestHdr header; - struct ClientAdminRequest admin; - struct ClientAuthRequest auth; - struct ClientBindRequest bind; - struct ClientChmodRequest chmod; - struct ClientCloseRequest close; - struct ClientDecryptRequest decrypt; - struct ClientDirlistRequest dirlist; - struct ClientEndsessRequest endsess; - struct ClientGetfileRequest getfile; - struct ClientLocateRequest locate; - struct ClientLoginRequest login; - struct ClientMkdirRequest mkdir; - struct ClientMvRequest mv; - struct ClientOpenRequest open; - struct ClientPingRequest ping; - struct ClientPrepareRequest prepare; - struct ClientProtocolRequest protocol; - struct ClientPutfileRequest putfile; - struct ClientQueryRequest query; - struct ClientReadRequest read; - struct ClientReadVRequest readv; - struct ClientRmRequest rm; - struct ClientRmdirRequest rmdir; - struct ClientSetRequest set; - struct ClientSigverRequest sigver; - struct ClientStatRequest stat; - struct ClientSyncRequest sync; - struct ClientTruncateRequest truncate; - struct ClientWriteRequest write; - struct ClientWriteVRequest writev; -} ClientRequest; - -typedef union { - struct ClientRequestHdr header; - struct ClientDecryptRequest decrypt; - struct ClientSigverRequest sigver; -} SecurityRequest; - -struct readahead_list { - kXR_char fhandle[4]; - kXR_int32 rlen; - kXR_int64 offset; -}; - -struct read_args { - kXR_char pathid; - kXR_char reserved[7]; - // his struct is followed by an array of readahead_list -}; - -// New additions are placed in a specia namespace to avoid conflicts -// -namespace XrdProto -{ -struct read_list { - kXR_char fhandle[4]; - kXR_int32 rlen; - kXR_int64 offset; -}; - -struct write_list { - kXR_char fhandle[4]; - kXR_int32 wlen; - kXR_int64 offset; -}; -} - -//_____________________________________________________________________ -// PROTOCOL DEFINITION: SERVER'S RESPONSE -//_____________________________________________________________________ -// - -// Nice header for the server response. -// Note that the protocol specifies these values to be in network -// byte order when sent -// -// G.Ganis: The following structures never need padding bytes: -// no need of packing options - -struct ServerResponseHeader { - kXR_char streamid[2]; - kXR_unt16 status; - kXR_int32 dlen; -}; - -// Body for the kXR_bind response... useful -struct ServerResponseBody_Bind { - kXR_char substreamid; -}; - -// Body for the kXR_open response... useful -struct ServerResponseBody_Open { - kXR_char fhandle[4]; - kXR_int32 cpsize; // cpsize & cptype returned if kXR_compress *or* - kXR_char cptype[4]; // kXR_retstat is specified -}; // info will follow if kXR_retstat is specified - -// The following information is returned in the response body when kXR_secreqs -// is set in ClientProtocolRequest::flags. Note that the size of secvec is -// defined by secvsz and will not be present when secvsz == 0. -struct ServerResponseSVec_Protocol { - kXR_char reqindx; // Request index - kXR_char reqsreq; // Request signing requirement -}; - -struct ServerResponseReqs_Protocol { - kXR_char theTag; // Always the character 'S' to identify struct - kXR_char rsvd; // Reserved for the future (always 0 for now) - kXR_char secver; // Security version - kXR_char secopt; // Security options - kXR_char seclvl; // Security level when secvsz == 0 - kXR_char secvsz; // Number of items in secvec (i.e. its length/2) - ServerResponseSVec_Protocol secvec; -}; - -// Options reflected in protocol response ServerResponseReqs_Protocol::secopt -// -#define kXR_secOData 0x01 -#define kXR_secOFrce 0x02 - -// Security level definitions (these are predefined but can be over-ridden) -// -#define kXR_secNone 0 -#define kXR_secCompatible 1 -#define kXR_secStandard 2 -#define kXR_secIntense 3 -#define kXR_secPedantic 4 - -// Requirements one of which set in each ServerResponseReqs_Protocol::secvec -// -#define kXR_signIgnore 0 -#define kXR_signLikely 1 -#define kXR_signNeeded 2 - -// Body for the kXR_protocol response... useful -struct ServerResponseBody_Protocol { - kXR_int32 pval; - kXR_int32 flags; - ServerResponseReqs_Protocol secreq; // Only for V3.1.0+ && if requested -}; - -// Handy definition of the size of the protocol response when the security -// information is not present. -// -#define kXR_ShortProtRespLen sizeof(ServerResponseBody_Protocol)-\ - sizeof(ServerResponseReqs_Protocol) - -struct ServerResponseBody_Login { - kXR_char sessid[16]; - kXR_char sec[4096]; // Should be sufficient for every use -}; - -struct ServerResponseBody_Redirect { - kXR_int32 port; - char host[4096]; // Should be sufficient for every use -}; - -struct ServerResponseBody_Error { - kXR_int32 errnum; - char errmsg[4096]; // Should be sufficient for every use -}; - -struct ServerResponseBody_Wait { - kXR_int32 seconds; - char infomsg[4096]; // Should be sufficient for every use -}; - -struct ServerResponseBody_Waitresp { - kXR_int32 seconds; -}; - -struct ServerResponseBody_Attn { - kXR_int32 actnum; - char parms[4096]; // Should be sufficient for every use -}; - -struct ServerResponseBody_Attn_asyncrd { - kXR_int32 actnum; - kXR_int32 port; - char host[4092]; -}; - -struct ServerResponseBody_Attn_asynresp { - kXR_int32 actnum; - char reserved[4]; - ServerResponseHeader resphdr; - char respdata[4096]; -}; - -struct ServerResponseBody_Attn_asyncwt { - kXR_int32 actnum; - kXR_int32 wsec; -}; - -struct ServerResponseBody_Attn_asyncdi { - kXR_int32 actnum; - kXR_int32 wsec; - kXR_int32 msec; -}; - -struct ServerResponseBody_Authmore { - char data[4096]; -}; - -struct ServerResponseBody_Buffer { - char data[4096]; -}; - -struct ServerResponse -{ - ServerResponseHeader hdr; - union - { - ServerResponseBody_Error error; - ServerResponseBody_Authmore authmore; - ServerResponseBody_Wait wait; - ServerResponseBody_Waitresp waitresp; - ServerResponseBody_Redirect redirect; - ServerResponseBody_Attn attn; - ServerResponseBody_Protocol protocol; - ServerResponseBody_Login login; - ServerResponseBody_Buffer buffer; - ServerResponseBody_Bind bind; - } body; -}; - -void ServerResponseHeader2NetFmt(struct ServerResponseHeader *srh); - -// The fields to be sent as initial handshake -struct ClientInitHandShake { - kXR_int32 first; - kXR_int32 second; - kXR_int32 third; - kXR_int32 fourth; - kXR_int32 fifth; -}; - -// The body received after the first handshake's header -struct ServerInitHandShake { - kXR_int32 msglen; - kXR_int32 protover; - kXR_int32 msgval; -}; - - - -typedef kXR_int32 ServerResponseType; - -struct ALIGN_CHECK {char chkszreq[25-sizeof(ClientRequest)]; - char chkszrsp[ 9-sizeof(ServerResponseHeader)]; -}; - -/******************************************************************************/ -/* X P r o t o c o l U t i l i t i e s */ -/******************************************************************************/ - -#include -#if defined(WIN32) -#if !defined(ENOTBLK) -# define ENOTBLK 15 -#endif -#if !defined(ETXTBSY) -#define ETXTBSY 26 -#endif -#if !defined(ENOBUFS) -#define ENOBUFS 105 -#endif -#if !defined(ENETUNREACH) -#define ENETUNREACH 114 -#endif -#endif - -class XProtocol -{ -public: - -// mapError() is the occicial mapping from errno to xrootd protocol error. -// -static int mapError(int rc) - {if (rc < 0) rc = -rc; - switch(rc) - {case ENOENT: return kXR_NotFound; - case EPERM: return kXR_NotAuthorized; - case EACCES: return kXR_NotAuthorized; - case EIO: return kXR_IOError; - case ENOMEM: return kXR_NoMemory; - case ENOBUFS: return kXR_NoMemory; - case ENOSPC: return kXR_NoSpace; - case ENAMETOOLONG: return kXR_ArgTooLong; - case ENETUNREACH: return kXR_noserver; - case ENOTBLK: return kXR_NotFile; - case EISDIR: return kXR_isDirectory; - case EEXIST: return kXR_InvalidRequest; - case ETXTBSY: return kXR_inProgress; - case ENODEV: return kXR_FSError; - case EFAULT: return kXR_ServerError; - case EDOM: return kXR_ChkSumErr; - case EDQUOT: return kXR_overQuota; - case EILSEQ: return kXR_SigVerErr; - case ERANGE: return kXR_DecryptErr; - case EUSERS: return kXR_Overloaded; - default: return kXR_FSError; - } - } - -static int toErrno( int xerr ) -{ - switch(xerr) - {case kXR_ArgInvalid: return EINVAL; - case kXR_ArgMissing: return EINVAL; - case kXR_ArgTooLong: return ENAMETOOLONG; - case kXR_FileLocked: return EDEADLK; - case kXR_FileNotOpen: return EBADF; - case kXR_FSError: return EIO; - case kXR_InvalidRequest:return EEXIST; - case kXR_IOError: return EIO; - case kXR_NoMemory: return ENOMEM; - case kXR_NoSpace: return ENOSPC; - case kXR_NotAuthorized: return EACCES; - case kXR_NotFound: return ENOENT; - case kXR_ServerError: return ENOMSG; - case kXR_Unsupported: return ENOSYS; - case kXR_noserver: return EHOSTUNREACH; - case kXR_NotFile: return ENOTBLK; - case kXR_isDirectory: return EISDIR; - case kXR_Cancelled: return ECANCELED; - case kXR_ChkLenErr: return EDOM; - case kXR_ChkSumErr: return EDOM; - case kXR_inProgress: return EINPROGRESS; - case kXR_overQuota: return EDQUOT; - case kXR_SigVerErr: return EILSEQ; - case kXR_DecryptErr: return ERANGE; - case kXR_Overloaded: return EUSERS; - default: return ENOMSG; - } -} - -static const char *errName(kXR_int32 errCode); - -static const char *reqName(kXR_unt16 reqCode); -}; -#endif diff --git a/src/XProtocol/XPtypes.hh b/src/XProtocol/XPtypes.hh deleted file mode 100644 index d01ebe7426c..00000000000 --- a/src/XProtocol/XPtypes.hh +++ /dev/null @@ -1,74 +0,0 @@ -#ifndef __XPTYPES_H -#define __XPTYPES_H -/******************************************************************************/ -/* */ -/* X P t y p e s . h h */ -/* */ -/* (c) 2012 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -// Full range type compatibility work done by Gerardo Ganis, CERN. - -// Typical data types -// -// Only char and short are truly portable types -typedef unsigned char kXR_char; -typedef short kXR_int16; -typedef unsigned short kXR_unt16; - -// Signed integer 4 bytes -// -#ifndef XR__INT16 -# if defined(LP32) || defined(__LP32) || defined(__LP32__) || \ - defined(BORLAND) -# define XR__INT16 -# endif -#endif -#ifndef XR__INT64 -# if defined(ILP64) || defined(__ILP64) || defined(__ILP64__) -# define XR__INT64 -# endif -#endif -#if defined(XR__INT16) -typedef long kXR_int32; -typedef unsigned long kXR_unt32; -#elif defined(XR__INT64) -typedef int32 kXR_int32; -typedef unsigned int32 kXR_unt32; -#else -typedef int kXR_int32; -typedef unsigned int kXR_unt32; -#endif - -// Signed integer 8 bytes -// -//#if defined(_WIN32) -//typedef __int64 kXR_int64; -//#else -typedef long long kXR_int64; -typedef unsigned long long kXR_unt64; -//#endif -#endif diff --git a/src/XProtocol/YProtocol.hh b/src/XProtocol/YProtocol.hh deleted file mode 100644 index dc16ae036b7..00000000000 --- a/src/XProtocol/YProtocol.hh +++ /dev/null @@ -1,622 +0,0 @@ -#ifndef __YPROTOCOL_H -#define __YPROTOCOL_H -/******************************************************************************/ -/* */ -/* Y P r o t o c o l . h h */ -/* */ -/* (c) 2012 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#ifdef __CINT__ -#define __attribute__(x) -#endif - -#include "XProtocol/XPtypes.hh" - -// We need to pack structures sent all over the net! -// __attribute__((packed)) assures no padding bytes. -// -// Note all binary values shall be in network byte order. -// -// Data is serialized as explained in XrdOucPup. - -/******************************************************************************/ -/* C o m m o n R e q u e s t S e c t i o n */ -/******************************************************************************/ - -namespace XrdCms -{ - -static const unsigned char kYR_Version = 3; - -struct CmsRRHdr -{ kXR_unt32 streamid; // Essentially opaque - kXR_char rrCode; // Request or Response code - kXR_char modifier; // RR dependent - kXR_unt16 datalen; -}; - -enum CmsReqCode // Request Codes -{ kYR_login = 0, // Same as kYR_data - kYR_chmod = 1, - kYR_locate = 2, - kYR_mkdir = 3, - kYR_mkpath = 4, - kYR_mv = 5, - kYR_prepadd = 6, - kYR_prepdel = 7, - kYR_rm = 8, - kYR_rmdir = 9, - kYR_select = 10, - kYR_stats = 11, - kYR_avail = 12, - kYR_disc = 13, - kYR_gone = 14, - kYR_have = 15, - kYR_load = 16, - kYR_ping = 17, - kYR_pong = 18, - kYR_space = 19, - kYR_state = 20, - kYR_statfs = 21, - kYR_status = 22, - kYR_trunc = 23, - kYR_try = 24, - kYR_update = 25, - kYR_usage = 26, - kYR_xauth = 27, - kYR_MaxReq // Count of request numbers (highest + 1) -}; - -// The hopcount is used for forwarded requests. It is incremented upon each -// forwarding until it wraps to zero. At this point the forward is not done. -// Forwarding applies to: chmod, have, mkdir, mkpath, mv, prepdel, rm, and -// rmdir. Any other modifiers must be encoded in the low order 6 bits. -// -enum CmsFwdModifier -{ kYR_hopcount = 0xc0, - kYR_hopincr = 0x40 -}; - -enum CmsReqModifier -{ kYR_raw = 0x20, // Modifier: Unmarshalled data - kYR_dnf = 0x10 // Modifier: mv, rm, rmdir (do not forward) -}; - -/******************************************************************************/ -/* C o m m o n R e s p o n s e S e c t i o n */ -/******************************************************************************/ - -enum CmsRspCode // Response codes -{ kYR_data = 0, // Same as kYR_login - kYR_error = 1, - kYR_redirect= 2, - kYR_wait = 3, - kYR_waitresp= 4, - kYR_yauth = 5 -}; - -enum YErrorCode -{ kYR_ENOENT = 1, - kYR_EPERM, - kYR_EACCES, - kYR_EINVAL, - kYR_EIO, - kYR_ENOMEM, - kYR_ENOSPC, - kYR_ENAMETOOLONG, - kYR_ENETUNREACH, - kYR_ENOTBLK, - kYR_EISDIR, - kYR_FSError, - kYR_SrvError -}; - -struct CmsResponse -{ CmsRRHdr Hdr; - -enum {kYR_async = 128 // Modifier: Reply to prev waitresp - }; - - kXR_unt32 Val; // Port, Wait val, rc, asyncid -// kXR_char Data[Hdr.datalen-4];// Target host, more data, or emessage -}; - -/******************************************************************************/ -/* a v a i l R e q u e s t */ -/******************************************************************************/ - -// Request: avail -// Respond: n/a -// -struct CmsAvailRequest -{ CmsRRHdr Hdr; -// kXR_int32 diskFree; -// kXR_int32 diskUtil; -}; - -/******************************************************************************/ -/* c h m o d R e q u e s t */ -/******************************************************************************/ - -// Request: chmod -// Respond: n/a -// -struct CmsChmodRequest -{ CmsRRHdr Hdr; -// kXR_string Ident; -// kXR_string Mode; -// kXR_string Path; -}; - -/******************************************************************************/ -/* d i s c R e q u e s t */ -/******************************************************************************/ - -// Request: disc -// Respond: n/a -// -struct CmsDiscRequest -{ CmsRRHdr Hdr; -}; - -/******************************************************************************/ -/* g o n e R e q u e s t */ -/******************************************************************************/ - -// Request: gone -// Respond: n/a -// -struct CmsGoneRequest -{ CmsRRHdr Hdr; -// kXR_string Path; -}; - -/******************************************************************************/ -/* h a v e R e q u e s t */ -/******************************************************************************/ - -// Request: have -// Respond: n/a -// -struct CmsHaveRequest -{ CmsRRHdr Hdr; - enum {Online = 1, Pending = 2}; // Modifiers -// kXR_string Path; -}; - -/******************************************************************************/ -/* l o c a t e R e q u e s t */ -/******************************************************************************/ - -struct CmsLocateRequest -{ CmsRRHdr Hdr; -// kXR_string Ident; -// kXR_unt32 Opts; - -enum {kYR_refresh = 0x0001, - kYR_retname = 0x0002, - kYR_retuniq = 0x0004, - kYR_asap = 0x0080, - kYR_retipv4 = 0x0000, // Client is only IPv4 - kYR_retipv46= 0x1000, // Client is IPv4 IPv6 - kYR_retipv6 = 0x2000, // Client is only IPv6 - kYR_retipv64= 0x3000, // Client is IPv6 IPv4 - kYR_retipmsk= 0x3000, // Mask to isolate retipcxx bits - kYR_retipsft= 12, // Shift to convert retipcxx bits - kYR_listall = 0x4000, // List everything regardless of other settings - kYR_prvtnet = 0x8000 // Client is using a private address - }; -// kXR_string Path; - -static const int RHLen =266; // Max length of each host response item -}; - -/******************************************************************************/ -/* l o g i n R e q u e s t */ -/******************************************************************************/ - -// Request: login -// Respond: xauth -// login -// - -struct CmsLoginData -{ kXR_unt16 Size; // Temp area for packing purposes - kXR_unt16 Version; - kXR_unt32 Mode; // From LoginMode - kXR_int32 HoldTime; // Hold time in ms(managers) - kXR_unt32 tSpace; // Tot Space GB (servers) - kXR_unt32 fSpace; // Free Space MB (servers) - kXR_unt32 mSpace; // Minf Space MB (servers) - kXR_unt16 fsNum; // File Systems (servers /supervisors) - kXR_unt16 fsUtil; // FS Utilization (servers /supervisors) - kXR_unt16 dPort; // Data port (servers /supervisors) - kXR_unt16 sPort; // Subs port (managers/supervisors) - kXR_char *SID; // Server ID (servers/ supervisors) - kXR_char *Paths; // Exported paths (servers/ supervisors) - kXR_char *ifList; // Exported interfaces - kXR_char *envCGI; // Exported environment - - enum LoginMode - {kYR_director= 0x00000001, - kYR_manager = 0x00000002, - kYR_peer = 0x00000004, - kYR_server = 0x00000008, - kYR_proxy = 0x00000010, - kYR_subman = 0x00000020, - kYR_blredir = 0x00000040, // Supports or is bl redir - kYR_suspend = 0x00000100, // Suspended login - kYR_nostage = 0x00000200, // Staging unavailable - kYR_trying = 0x00000400, // Extensive login retries - kYR_debug = 0x80000000, - kYR_share = 0x7f000000, // Mask to isolate share - kYR_shift = 24, // Share shift position - kYR_tzone = 0x00f80000, // Mask to isolate time zone - kYR_shifttz = 19 // TZone shift position - }; -}; - -struct CmsLoginRequest -{ CmsRRHdr Hdr; - CmsLoginData Data; -}; - -struct CmsLoginResponse -{ CmsRRHdr Hdr; - CmsLoginData Data; -}; - -/******************************************************************************/ -/* l o a d R e q u e s t */ -/******************************************************************************/ - -// Request: load -// Respond: n/a -// -struct CmsLoadRequest -{ CmsRRHdr Hdr; - enum {cpuLoad=0, netLoad, xeqLoad, memLoad, pagLoad, dskLoad, - numLoad}; -// kXR_char theLoad[numload]; -// kXR_int dskFree; -}; - -/******************************************************************************/ -/* m k d i r R e q u e s t */ -/******************************************************************************/ - -// Request: mkdir -// Respond: n/a -// -struct CmsMkdirRequest -{ CmsRRHdr Hdr; -// kXR_string Ident; -// kXR_string Mode; -// kXR_string Path; -}; - -/******************************************************************************/ -/* m k p a t h R e q u e s t */ -/******************************************************************************/ - -// Request: mkpath -// Respond: n/a -// -struct CmsMkpathRequest -{ CmsRRHdr Hdr; -// kXR_string Ident; -// kXR_string Mode; -// kXR_string Path; -}; - -/******************************************************************************/ -/* m v R e q u e s t */ -/******************************************************************************/ - -// Request: mv -// Respond: n/a -// -struct CmsMvRequest { - CmsRRHdr Hdr; // Subject to kYR_dnf modifier! -// kXR_string Ident; -// kXR_string Old_Path; -// kXR_string New_Path; -}; - -/******************************************************************************/ -/* p i n g R e q u e s t */ -/******************************************************************************/ - -// Request: ping -// Respond: n/a -// -struct CmsPingRequest { - CmsRRHdr Hdr; -}; - -/******************************************************************************/ -/* p o n g R e q u e s t */ -/******************************************************************************/ - -// Request: pong -// Respond: n/a -// -struct CmsPongRequest { - CmsRRHdr Hdr; -}; - -/******************************************************************************/ -/* p r e p a d d R e q u e s t */ -/******************************************************************************/ - -// Request: prepadd \n -// Respond: No response. -// -struct CmsPrepAddRequest -{ CmsRRHdr Hdr; // Modifier used with following options - -enum {kYR_stage = 0x0001, // Stage the data - kYR_write = 0x0002, // Prepare for writing - kYR_coloc = 0x0004, // Prepare for co-location - kYR_fresh = 0x0008, // Prepare by time refresh - kYR_metaman = 0x0010 // Prepare via meta-manager - }; -// kXR_string Ident; -// kXR_string reqid; -// kXR_string user; -// kXR_string prty; -// kXR_string mode; -// kXR_string Path; -// kXR_string Opaque; // Optional -}; - -/******************************************************************************/ -/* p r e p d e l R e q u e s t */ -/******************************************************************************/ - -// Request: prepdel -// Respond: No response. -// -struct CmsPrepDelRequest -{ CmsRRHdr Hdr; -// kXR_string Ident; -// kXR_string reqid; -}; - -/******************************************************************************/ -/* r m R e q u e s t */ -/******************************************************************************/ - -// Request: rm -// Respond: n/a -// -struct CmsRmRequest -{ CmsRRHdr Hdr; // Subject to kYR_dnf modifier! -// kXR_string Ident; -// kXR_string Path; -}; - -/******************************************************************************/ -/* r m d i r R e q u e s t */ -/******************************************************************************/ - -// Request: rmdir -// Respond: n/a -// -struct CmsRmdirRequest -{ CmsRRHdr Hdr; // Subject to kYR_dnf modifier! -// kXR_string Ident; -// kXR_string Path; -}; - -/******************************************************************************/ -/* s e l e c t R e q u e s t */ -/******************************************************************************/ - -// Request: select[s] {c | d | m | r | w | s | t | x} [-host] - -// Note: selects - requests a cache refresh for -// kYR_refresh - refresh file location cache -// kYR_create c - file will be created -// kYR_delete d - file will be created or truncated -// kYR_metaop m - inod will only be modified -// kYR_read r - file will only be read -// kYR_replica - file will replicated -// kYR_write w - file will be read and writen -// kYR_stats s - only stat information will be obtained -// kYR_online x - consider only online files -// may be combined with kYR_stats (file must be resident) -// - - the host failed to deliver the file. - - -struct CmsSelectRequest -{ CmsRRHdr Hdr; -// kXR_string Ident; -// kXR_unt32 Opts; - -enum {kYR_refresh = 0x00000001, - kYR_create = 0x00000002, // May combine with trunc -> delete - kYR_online = 0x00000004, - kYR_read = 0x00000008, // Default - kYR_trunc = 0x00000010, // -> write - kYR_write = 0x00000020, - kYR_stat = 0x00000040, // Exclsuive - kYR_metaop = 0x00000080, - kYR_replica = 0x00000100, // Only in combination with create - kYR_mwfiles = 0x00000200, // Multiple writables files are OK - kYR_retipv4 = 0x00000000, // Client is only IPv4 - kYR_retipv46= 0x00001000, // Client is IPv4 IPv6 - kYR_retipv6 = 0x00002000, // Client is only IPv6 - kYR_retipv64= 0x00003000, // Client is IPv6 IPv4 - kYR_retipmsk= 0x00003000, // Mask to isolate retipcxx bits - kYR_retipsft= 12, // Shift to convert retipcxx bits - kYR_prvtnet = 0x00008000, // Client is using a private address - - kYR_tryMISS = 0x00000000, // Retry due to missing file (triedrc=enoent) - kYR_tryIOER = 0x00010000, // Retry due to I/O error (triedrc=ioerr) - kYR_tryFSER = 0x00020000, // Retry due to FS error (triedrc=fserr) - kYR_trySVER = 0x00030000, // Retry due to server error (triedrc=srverr) - kYR_tryMASK = 0x00030000, // Mask to isolate retry reason - kYR_trySHFT = 16, // Amount to shift right - kYR_tryRSEL = 0x00040000, // Retry for reselection (triedrc=resel) - kYR_aWeak = 0x00100000, // Affinity: weak - kYR_aStrong = 0x00200000, // Affinity: strong - kYR_aStrict = 0x00300000, // Affinity: strict - kYR_aNone = 0x00400000, // Affinity: none - kYR_aSpec = 0x00700000, // Mask to test if any affinity specified - kYR_aPack = 0x00300000, // Mask to test if the affinity packs choice - kYR_aWait = 0x00200000 // Mask to test if the affinity must wait - }; -// kXR_string Path; -// kXR_string Opaque; // Optional -// kXR_string Host; // Optional -}; - -/******************************************************************************/ -/* s p a c e R e q u e s t */ -/******************************************************************************/ - -// Request: space -// - -struct CmsSpaceRequest -{ CmsRRHdr Hdr; -}; - -/******************************************************************************/ -/* s t a t e R e q u e s t */ -/******************************************************************************/ - -// Request: state -// - -struct CmsStateRequest -{ CmsRRHdr Hdr; -// kXR_string Path; - -enum {kYR_refresh = 0x01, // Modifier - kYR_noresp = 0x02, - kYR_metaman = 0x08 - }; -}; - -/******************************************************************************/ -/* s t a t f s R e q u e s t */ -/******************************************************************************/ - -// Request: statfs -// - -struct CmsStatfsRequest -{ CmsRRHdr Hdr; // Modifier used with following options -// kXR_string Path; - -enum {kYR_qvfs = 0x0001 // Virtual file system query - }; -}; - -/******************************************************************************/ -/* s t a t s R e q u e s t */ -/******************************************************************************/ - -// Request: stats or statsz (determined by modifier) -// - -struct CmsStatsRequest -{ CmsRRHdr Hdr; - -enum {kYR_size = 1 // Modifier - }; -}; - -/******************************************************************************/ -/* s t a t u s R e q u e s t */ -/******************************************************************************/ - -// Request: status -// -struct CmsStatusRequest -{ CmsRRHdr Hdr; - -enum {kYR_Stage = 0x01, kYR_noStage = 0x02, // Modifier - kYR_Resume = 0x04, kYR_Suspend = 0x08, - kYR_Reset = 0x10 // Exclusive - }; -}; - -/******************************************************************************/ -/* t r u n c R e q u e s t */ -/******************************************************************************/ - -// Request: trunc -// Respond: n/a -// -struct CmsTruncRequest -{ CmsRRHdr Hdr; -// kXR_string Ident; -// kXR_string Size; -// kXR_string Path; -}; - -/******************************************************************************/ -/* t r y R e q u e s t */ -/******************************************************************************/ - -// Request: try -// -struct CmsTryRequest -{ CmsRRHdr Hdr; - kXR_unt16 sLen; // This is the string length in PUP format - -// kYR_string {ipaddr:port}[up to STMax]; - -enum {kYR_permtop = 0x01 // Modifier Permanent redirect to top level - }; -}; - -/******************************************************************************/ -/* u p d a t e R e q u e s t */ -/******************************************************************************/ - -// Request: update -// -struct CmsUpdateRequest -{ CmsRRHdr Hdr; -}; - -/******************************************************************************/ -/* u s a g e R e q u e s t */ -/******************************************************************************/ - -// Request: usage -// -struct CmsUsageRequest -{ CmsRRHdr Hdr; -}; - -}; // namespace XrdCms -#endif diff --git a/src/Xrd/XrdBuffXL.cc b/src/Xrd/XrdBuffXL.cc deleted file mode 100644 index c3b9392422e..00000000000 --- a/src/Xrd/XrdBuffXL.cc +++ /dev/null @@ -1,252 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d B u f f X L . c c */ -/* */ -/* (c) 2015 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#if !defined(__APPLE__) && !defined(__FreeBSD__) -#include -#endif -#include -#include -#include - -#include "XrdOuc/XrdOucUtils.hh" -#include "XrdSys/XrdSysPlatform.hh" -#include "XrdBuffXL.hh" - -/******************************************************************************/ -/* L o c a l V a l u e s */ -/******************************************************************************/ - -namespace -{ -static const int maxBuffSz = 1 << 30; //1 GB -static const int iniBuffSz = 1 << (XRD_BUSHIFT+XRD_BUCKETS-1); -static const int minBuffSz = 1 << (XRD_BUSHIFT+XRD_BUCKETS); -static const int minBShift = (XRD_BUSHIFT+XRD_BUCKETS); -static const int isBigBuff = 0x40000000; -} - -/******************************************************************************/ -/* C o n s t r u c t o r */ -/******************************************************************************/ - -XrdBuffXL::XrdBuffXL() : bucket(0), totalo(0), pagsz(getpagesize()), slots(0), - maxsz(1<<(XRD_BUSHIFT+XRD_BUCKETS-1)), totreq(0) -{ } - -/******************************************************************************/ -/* I n i t */ -/******************************************************************************/ - -void XrdBuffXL::Init(int maxMSZ) -{ - int lg2, chunksz; - -// If this is a duplicate call, delete the previous setup -// - if (bucket) {delete [] bucket; bucket = 0;} - -// Check if this is too small for us -// - if (maxMSZ <= iniBuffSz) {maxsz = iniBuffSz; return;} - -// Check if this is too large for us (1GB limit) and adjust -// - if (maxMSZ > maxBuffSz) maxMSZ = maxBuffSz; - -// Calculate how many buckets we need to have (note we trim this down -// - chunksz = maxMSZ >> minBShift; - lg2 = XrdOucUtils::Log2(chunksz); - chunksz = 1<<(lg2+minBShift); - if (chunksz < maxMSZ) {lg2++; maxsz = chunksz << 1;} - else maxsz = chunksz; - -// Allocate a bucket array -// - bucket = new BuckVec[lg2+1]; - slots = lg2+1; -} - -/******************************************************************************/ -/* O b t a i n */ -/******************************************************************************/ - -XrdBuffer *XrdBuffXL::Obtain(int sz) -{ - XrdBuffer *bp; - char *memp; - int mk, buffSz, bindex = 0; - -// Make sure the request is within our limits -// - if (sz <= 0 || sz > maxsz) return 0; - -// Calculate bucket index. This is log2(shifted size) rounded up if need be. -// If the shift results in zero we know the request fits in the slot 0 buffer. -// - mk = sz >> minBShift; - if (!mk) buffSz = minBuffSz; - else {bindex = XrdOucUtils::Log2(mk); - buffSz = (bindex ? minBuffSz << bindex : minBuffSz); - if (buffSz < sz) {bindex++; buffSz = buffSz << 1;} - } - if (bindex >= slots) return 0; // Should never happen! - -// Obtain a lock on the bucket array and try to give away an existing buffer -// - slotXL.Lock(); - totreq++; - bucket[bindex].numreq++; - if ((bp = bucket[bindex].bnext)) - {bucket[bindex].bnext = bp->next; bucket[bindex].numbuf--;} - slotXL.UnLock(); - -// Check if we really allocated a buffer -// - if (bp) return bp; - -// Allocate a chunk of aligned memory -// - if (!(memp = static_cast(memalign(pagsz, buffSz)))) return 0; - -// Wrap the memory with a buffer object -// - if (!(bp = new XrdBuffer(memp, buffSz, bindex|isBigBuff))) - {free(memp); return 0;} - -// Update statistics -// - slotXL.Lock(); totalo += buffSz; totbuf++; slotXL.UnLock(); - -// Return the buffer -// - return bp; -} - -/******************************************************************************/ -/* R e c a l c */ -/******************************************************************************/ - -int XrdBuffXL::Recalc(int sz) -{ - int buffSz, mk, bindex = 0; - -// Make sure the request is within our limits -// - if (sz <= 0 || sz > maxsz) return 0; - -// Calculate bucket size corresponding to the desired size -// - mk = sz >> minBShift; - if (!mk) buffSz = minBuffSz; - else {bindex = XrdOucUtils::Log2(mk); - buffSz = (bindex ? minBuffSz << bindex : minBuffSz); - if (buffSz < sz) {bindex++; buffSz = buffSz << 1;} - } - if (bindex >= slots) return 0; // Should never happen! - -// All done, return the actual size we would have allocated -// - return buffSz; -} - -/******************************************************************************/ -/* R e l e a s e */ -/******************************************************************************/ - -void XrdBuffXL::Release(XrdBuffer *bp) -{ - int bindex = bp->bindex & ~isBigBuff; - -// Obtain a lock on the bucket array and reclaim the buffer -// - slotXL.Lock(); - bp->next = bucket[bindex].bnext; - bucket[bindex].bnext = bp; - bucket[bindex].numbuf++; - slotXL.UnLock(); -} - -/******************************************************************************/ -/* S t a t s */ -/******************************************************************************/ - -int XrdBuffXL::Stats(char *buff, int blen, int do_sync) -{ - static char statfmt[] = "%d" - "%lld%d"; - int nlen; - -// If only size wanted, return it -// - if (!buff) return sizeof(statfmt) + 16*3; - -// Return formatted stats -// - if (do_sync) slotXL.Lock(); - nlen = snprintf(buff, blen, statfmt, totreq, totalo, totbuf); - if (do_sync) slotXL.UnLock(); - return nlen; -} - -/******************************************************************************/ -/* T r i m */ -/******************************************************************************/ - -void XrdBuffXL::Trim() -{ - XrdBuffer *bP; - int n, m; - -// Obtain the lock -// - slotXL.Lock(); - -// Run through all our slots looking for buffers to release -// - for (int i = 0; i < slots; i++) - {if (bucket[i].numbuf > 1 && bucket[i].numbuf > bucket[i].numreq) - {n = bucket[i].numbuf - bucket[i].numreq; - m = bucket[i].numbuf/2; - if (m < n) n = m; - while(n-- && (bP = bucket[i].bnext)) - {bucket[i].bnext = bP->next; - bucket[i].numbuf--; - totalo -= bP->bsize; totbuf--; - delete bP; - } - } - bucket[i].numreq = 0; - } - -// Release the lock -// - slotXL.UnLock(); -} diff --git a/src/Xrd/XrdBuffXL.hh b/src/Xrd/XrdBuffXL.hh deleted file mode 100644 index 46385a07476..00000000000 --- a/src/Xrd/XrdBuffXL.hh +++ /dev/null @@ -1,80 +0,0 @@ -#ifndef __XrdBuffXL_H__ -#define __XrdBuffXL_H__ -/******************************************************************************/ -/* */ -/* X r d B u f f X L . h h */ -/* */ -/* (c) 2015 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdBuffer.hh" -#include "XrdSys/XrdSysPthread.hh" - -// There should be only one instance of this class. -// -class XrdBuffXL -{ -public: - -void Init(int maxMSZ); - -XrdBuffer *Obtain(int bsz); - -int Recalc(int bsz); - -void Release(XrdBuffer *bp); - -int MaxSize() {return maxsz;} - -void Trim(); - -int Stats(char *buff, int blen, int do_sync=0); - - XrdBuffXL(); - - ~XrdBuffXL() {} // The buffmanager is never deleted - -private: - -XrdSysMutex slotXL; - -struct BuckVec - {XrdBuffer *bnext; - int numbuf; - int numreq; - BuckVec() : bnext(0), numbuf(0), numreq(0) {} - ~BuckVec() {} // Never gets deleted - }; - - BuckVec *bucket; // 4M**(0 ... slots-1) sized buffers - long long totalo; - -const int pagsz; - int slots; - int maxsz; - int totreq; - int totbuf; -}; -#endif diff --git a/src/Xrd/XrdBuffer.cc b/src/Xrd/XrdBuffer.cc deleted file mode 100644 index aed0881dbee..00000000000 --- a/src/Xrd/XrdBuffer.cc +++ /dev/null @@ -1,347 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d B u f f e r . c c */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#if !defined(__APPLE__) && !defined(__FreeBSD__) -#include -#endif -#include -#include -#include - -#include "XrdOuc/XrdOucUtils.hh" -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysPlatform.hh" -#include "XrdSys/XrdSysTimer.hh" -#include "Xrd/XrdBuffer.hh" -#include "XrdBuffXL.hh" - -#define XRD_TRACE XrdTrace-> -#include "Xrd/XrdTrace.hh" - -/******************************************************************************/ -/* E x t e r n a l L i n k a g e s */ -/******************************************************************************/ - -void *XrdReshaper(void *pp) -{ - XrdBuffManager *bmp = (XrdBuffManager *)pp; - bmp->Reshape(); - return (void *)0; -} - -/******************************************************************************/ -/* G l o b a l s */ -/******************************************************************************/ - -const char *XrdBuffManager::TraceID = "BuffManager"; - -namespace -{ -static const int minBuffSz = 1 << XRD_BUSHIFT; -} - -namespace XrdGlobal -{ -XrdBuffXL xlBuff; -} - -using namespace XrdGlobal; - -/******************************************************************************/ -/* C o n s t r u c t o r */ -/******************************************************************************/ - -XrdBuffManager::XrdBuffManager(XrdSysError *lP, XrdOucTrace *tP, int minrst) : - XrdTrace(tP), - XrdLog(lP), - slots(XRD_BUCKETS), - shift(XRD_BUSHIFT), - pagsz(getpagesize()), - maxsz(1<<(XRD_BUSHIFT+XRD_BUCKETS-1)), - Reshaper(0, "buff reshaper") -{ - -// Clear everything to zero -// - totbuf = 0; - totreq = 0; - totalo = 0; - totadj = 0; -#ifdef _SC_PHYS_PAGES - maxalo = static_cast(pagsz)/8 - * static_cast(sysconf(_SC_PHYS_PAGES)); -#else - maxalo = 0x7ffffff; -#endif - rsinprog = 0; - minrsw = minrst; - memset(static_cast(bucket), 0, sizeof(bucket)); -} - -/******************************************************************************/ -/* D e s t r u c t o r */ -/******************************************************************************/ - -XrdBuffManager::~XrdBuffManager() -{ - XrdBuffer *bP; - - for (int i = 0; i < XRD_BUCKETS; i++) - {while((bP = bucket[i].bnext)) - {bucket[i].bnext = bP->next; - delete bP; - } - bucket[i].numbuf = 0; - } -} - -/******************************************************************************/ -/* I n i t */ -/******************************************************************************/ - -void XrdBuffManager::Init() -{ - pthread_t tid; - int rc; - -// Start the reshaper thread -// - if ((rc = XrdSysThread::Run(&tid, XrdReshaper, static_cast(this), 0, - "Buffer Manager reshaper"))) - XrdLog->Emsg("BuffManager", rc, "create reshaper thread"); -} - -/******************************************************************************/ -/* O b t a i n */ -/******************************************************************************/ - -XrdBuffer *XrdBuffManager::Obtain(int sz) -{ - XrdBuffer *bp; - char *memp; - int mk, pk, bindex; - -// Make sure the request is within our limits -// - if (sz <= 0) return 0; - if (sz > maxsz) return xlBuff.Obtain(sz); - -// Calculate bucket index -// - mk = sz >> shift; - bindex = XrdOucUtils::Log2(mk); - mk = minBuffSz << bindex; - if (mk < sz) {bindex++; mk = mk << 1;} - if (bindex >= slots) return 0; // Should never happen! - -// Obtain a lock on the bucket array and try to give away an existing buffer -// - Reshaper.Lock(); - totreq++; - bucket[bindex].numreq++; - if ((bp = bucket[bindex].bnext)) - {bucket[bindex].bnext = bp->next; bucket[bindex].numbuf--;} - Reshaper.UnLock(); - -// Check if we really allocated a buffer -// - if (bp) return bp; - -// Allocate a chunk of aligned memory -// - pk = (mk < pagsz ? mk : pagsz); - if (!(memp = static_cast(memalign(pk, mk)))) return 0; - -// Wrap the memory with a buffer object -// - if (!(bp = new XrdBuffer(memp, mk, bindex))) {free(memp); return 0;} - -// Update statistics -// - Reshaper.Lock(); - totbuf++; - if ((totalo += mk) > maxalo && !rsinprog) - {rsinprog = 1; Reshaper.Signal();} - Reshaper.UnLock(); - return bp; -} - -/******************************************************************************/ -/* R e c a l c */ -/******************************************************************************/ - -int XrdBuffManager::Recalc(int sz) -{ - int mk, bindex; - -// Make sure the request is within our limits -// - if (sz <= 0) return 0; - if (sz > maxsz) return xlBuff.Recalc(sz); - -// Calculate bucket index -// - mk = sz >> shift; - bindex = XrdOucUtils::Log2(mk); - mk = minBuffSz << bindex; - if (mk < sz) {bindex++; mk = mk << 1;} - if (bindex >= slots) return 0; // Should never happen! - -// All done, return the actual size we would have allocated -// - return mk; -} - -/******************************************************************************/ -/* R e l e a s e */ -/******************************************************************************/ - -void XrdBuffManager::Release(XrdBuffer *bp) -{ - int bindex = bp->bindex; - -// Check if we should release this via the big buffer object -// - if (bindex >= slots) {xlBuff.Release(bp); return;} - -// Obtain a lock on the bucket array and reclaim the buffer -// - Reshaper.Lock(); - bp->next = bucket[bp->bindex].bnext; - bucket[bp->bindex].bnext = bp; - bucket[bindex].numbuf++; - Reshaper.UnLock(); -} - -/******************************************************************************/ -/* R e s h a p e */ -/******************************************************************************/ - -void XrdBuffManager::Reshape() -{ -int i, bufprof[XRD_BUCKETS], numfreed; -time_t delta, lastshape = time(0); -long long memslot, memhave, memtarget = (long long)(.80*(float)maxalo); -XrdSysTimer Timer; -float requests, buffers; -XrdBuffer *bp; - -// This is an endless loop to periodically reshape the buffer pool -// -while(1) - {Reshaper.Lock(); - while(Reshaper.Wait(minrsw) && totalo <= maxalo) - {TRACE(MEM, "Reshaper has " <<(totalo>>10) <<"K; target " <<(memtarget>>10) <<"K");} - if ((delta = (time(0) - lastshape)) < minrsw) - {Reshaper.UnLock(); - Timer.Wait((minrsw-delta)*1000); - Reshaper.Lock(); - } - - // We have the lock so compute the request profile - // - if (totreq > slots) - {requests = (float)totreq; - buffers = (float)totbuf; - for (i = 0; i < slots; i++) - {bufprof[i] = (int)(buffers*(((float)bucket[i].numreq)/requests)); - bucket[i].numreq = 0; - } - totreq = 0; memhave = totalo; - } else memhave = 0; - Reshaper.UnLock(); - - // Reshape the buffer pool to agree with the request profile - // - memslot = maxsz; numfreed = 0; - for (i = slots-1; i >= 0 && memhave > memtarget; i--) - {Reshaper.Lock(); - while(bucket[i].numbuf > bufprof[i]) - if ((bp = bucket[i].bnext)) - {bucket[i].bnext = bp->next; - delete bp; - bucket[i].numbuf--; numfreed++; - memhave -= memslot; totalo -= memslot; - totbuf--; - } else {bucket[i].numbuf = 0; break;} - Reshaper.UnLock(); - memslot = memslot>>1; - } - - // All done - // - totadj += numfreed; - TRACE(MEM, "Pool reshaped; " <>10) <<"K; target " <<(memtarget>>10) <<"K"); - lastshape = time(0); - rsinprog = 0; // No need to lock, we're the only ones now setting it - - xlBuff.Trim(); // Trim big buffers - } -} - -/******************************************************************************/ -/* S e t */ -/******************************************************************************/ - -void XrdBuffManager::Set(int maxmem, int minw) -{ - -// Obtain a lock and set the values -// - Reshaper.Lock(); - if (maxmem > 0) maxalo = (long long)maxmem; - if (minw > 0) minrsw = minw; - Reshaper.UnLock(); -} - -/******************************************************************************/ -/* S t a t s */ -/******************************************************************************/ - -int XrdBuffManager::Stats(char *buff, int blen, int do_sync) -{ - static char statfmt[] = "%d" - "%lld%d%d%s"; - char xlStats[1024]; - int nlen; - -// If only size wanted, return it -// - if (!buff) return sizeof(statfmt) + 16*4 + xlBuff.Stats(0,0); - -// Return formatted stats -// - if (do_sync) Reshaper.Lock(); - xlBuff.Stats(xlStats, sizeof(xlStats), do_sync); - nlen = snprintf(buff,blen,statfmt,totreq,totalo,totbuf,totadj,xlStats); - if (do_sync) Reshaper.UnLock(); - return nlen; -} diff --git a/src/Xrd/XrdBuffer.hh b/src/Xrd/XrdBuffer.hh deleted file mode 100644 index a08532e6792..00000000000 --- a/src/Xrd/XrdBuffer.hh +++ /dev/null @@ -1,124 +0,0 @@ -#ifndef __XrdBuffer_H__ -#define __XrdBuffer_H__ -/******************************************************************************/ -/* */ -/* X r d B u f f e r . h h */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include "XrdSys/XrdSysPthread.hh" - -/******************************************************************************/ -/* x r d _ B u f f e r */ -/******************************************************************************/ - -class XrdBuffer -{ -public: - -char * buff; // -> buffer -int bsize; // size of this buffer - - XrdBuffer(char *bp, int sz, int ix) - {buff = bp; bsize = sz; bindex = ix; next = 0;} - - ~XrdBuffer() {if (buff) free(buff);} - - friend class XrdBuffManager; - friend class XrdBuffXL; -private: - -int bindex; -XrdBuffer *next; -static int pagesz; -}; - -/******************************************************************************/ -/* x r d _ B u f f M a n a g e r */ -/******************************************************************************/ - -#define XRD_BUCKETS 12 -#define XRD_BUSHIFT 10 - -// There should be only one instance of this class per buffer pool. -// -class XrdOucTrace; -class XrdSysError; - -class XrdBuffManager -{ -public: - -void Init(); - -XrdBuffer *Obtain(int bsz); - -int Recalc(int bsz); - -void Release(XrdBuffer *bp); - -int MaxSize() {return maxsz;} - -void Reshape(); - -void Set(int maxmem=-1, int minw=-1); - -int Stats(char *buff, int blen, int do_sync=0); - - XrdBuffManager(XrdSysError *lP, XrdOucTrace *tP, int minrst=20*60); - - ~XrdBuffManager(); // The buffmanager is never deleted - -private: - -XrdOucTrace *XrdTrace; -XrdSysError *XrdLog; - -const int slots; -const int shift; -const int pagsz; -const int maxsz; - -struct {XrdBuffer *bnext; - int numbuf; - int numreq; - } bucket[XRD_BUCKETS]; // 1K to 1<<(szshift+slots-1)M buffers - -int totreq; -int totbuf; -long long totalo; -long long maxalo; -int minrsw; -int rsinprog; -int totadj; - -XrdSysCondVar Reshaper; -static const char *TraceID; -}; -#endif diff --git a/src/Xrd/XrdConfig.cc b/src/Xrd/XrdConfig.cc deleted file mode 100644 index dcf254c2cc2..00000000000 --- a/src/Xrd/XrdConfig.cc +++ /dev/null @@ -1,1920 +0,0 @@ -/*******************************************************************************/ -/* */ -/* X r d C o n f i g . c c */ -/* */ -/* (c) 2011 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Deprtment of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -/* - The default port number comes from: - 1) The command line option, - 2) The config file, - 3) The /etc/services file for service corresponding to the program name. -*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "XrdVersion.hh" - -#include "Xrd/XrdBuffXL.hh" -#include "Xrd/XrdConfig.hh" -#include "Xrd/XrdInfo.hh" -#include "Xrd/XrdLink.hh" -#include "Xrd/XrdPoll.hh" -#include "Xrd/XrdStats.hh" - -#include "XrdNet/XrdNetAddr.hh" -#include "XrdNet/XrdNetIF.hh" -#include "XrdNet/XrdNetSecurity.hh" -#include "XrdNet/XrdNetUtils.hh" - -#include "XrdOuc/XrdOuca2x.hh" -#include "XrdOuc/XrdOucEnv.hh" -#include "XrdOuc/XrdOucLogging.hh" -#include "XrdOuc/XrdOucSiteName.hh" -#include "XrdOuc/XrdOucStream.hh" -#include "XrdOuc/XrdOucUtils.hh" - -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdSys/XrdSysTimer.hh" -#include "XrdSys/XrdSysUtils.hh" - -#ifdef __linux__ -#include -#endif -#ifdef __APPLE__ -#include -#endif - -/******************************************************************************/ -/* S t a t i c O b j e c t s */ -/******************************************************************************/ - - const char *XrdConfig::TraceID = "Config"; - -namespace XrdNetSocketCFG -{ -extern int ka_Idle; -extern int ka_Itvl; -extern int ka_Icnt; -}; - -namespace XrdGlobal -{ -extern XrdBuffXL xlBuff; -} - -namespace -{ -XrdOucEnv theEnv; -}; - -/******************************************************************************/ -/* d e f i n e s */ -/******************************************************************************/ - -#define TS_Xeq(x,m) if (!strcmp(x,var)) return m(eDest, Config); - -#ifndef S_IAMB -#define S_IAMB 0x1FF -#endif - -/******************************************************************************/ -/* L o c a l C l a s s e s */ -/******************************************************************************/ -/******************************************************************************/ -/* X r d C o n f i g P r o t */ -/******************************************************************************/ - -class XrdConfigProt -{ -public: - -XrdConfigProt *Next; -char *proname; -char *libpath; -char *parms; -int port; -int wanopt; - - XrdConfigProt(char *pn, char *ln, char *pp, int np=-1, int wo=0) - {Next = 0; proname = pn; libpath = ln; parms = pp; - port=np; wanopt = wo; - } - ~XrdConfigProt() - {free(proname); - if (libpath) free(libpath); - if (parms) free(parms); - } -}; - -/******************************************************************************/ -/* C o n s t r u c t o r */ -/******************************************************************************/ - -XrdConfig::XrdConfig() : Log(&Logger, "Xrd"), Trace(&Log), Sched(&Log, &Trace), - BuffPool(&Log, &Trace) -{ - -// Preset all variables with common defaults -// - PortTCP = -1; - PortUDP = -1; - PortWAN = 0; - ConfigFN = 0; - myInsName= 0; - mySitName= 0; - AdminPath= strdup("/tmp"); - HomePath = 0; - AdminMode= 0700; - HomeMode = 0700; - Police = 0; - Net_Blen = 0; // Accept OS default (leave Linux autotune in effect) - Net_Opts = XRDNET_KEEPALIVE; - Wan_Blen = 1024*1024; // Default window size 1M - Wan_Opts = XRDNET_KEEPALIVE; - repDest[0] = 0; - repDest[1] = 0; - repInt = 600; - repOpts = 0; - ppNet = 0; - NetTCPlep = -1; - NetADM = 0; - coreV = 1; - memset(NetTCP, 0, sizeof(NetTCP)); - - Firstcp = Lastcp = 0; - - ProtInfo.eDest = &Log; // Stable -> Error Message/Logging Handler - ProtInfo.NetTCP = 0; // Stable -> Network Object - ProtInfo.BPool = &BuffPool; // Stable -> Buffer Pool Manager - ProtInfo.Sched = &Sched; // Stable -> System Scheduler - ProtInfo.ConfigFN= 0; // We will fill this in later - ProtInfo.Stats = 0; // We will fill this in later - ProtInfo.Trace = &Trace; // Stable -> Trace Information - ProtInfo.AdmPath = AdminPath; // Stable -> The admin path - ProtInfo.AdmMode = AdminMode; // Stable -> The admin path mode - ProtInfo.theEnv = &theEnv; // Additional information - - ProtInfo.Format = XrdFORMATB; - ProtInfo.WANPort = 0; - ProtInfo.WANWSize = 0; - ProtInfo.WSize = 0; - ProtInfo.ConnMax = -1; // Max connections (fd limit) - ProtInfo.readWait = 3*1000; // Wait time for data before we reschedule - ProtInfo.idleWait = 0; // Seconds connection may remain idle (0=off) - ProtInfo.hailWait =30*1000; // Wait time for data before we drop connection - ProtInfo.DebugON = 0; // 1 if started with -d - ProtInfo.argc = 0; - ProtInfo.argv = 0; - - XrdNetAddr::SetCache(3*60*60); // Cache address resolutions for 3 hours -} - -/******************************************************************************/ -/* C o n f i g u r e */ -/******************************************************************************/ - -int XrdConfig::Configure(int argc, char **argv) -{ -/* - Function: Establish configuration at start up time. - - Input: None. - - Output: 0 upon success or !0 otherwise. -*/ - const char *xrdInst="XRDINSTANCE="; - - int retc, NoGo = 0, clPort = -1, optbg = 0; - const char *temp; - char c, buff[512], *dfltProt, *libProt = 0; - uid_t myUid = 0; - gid_t myGid = 0; - extern char *optarg; - extern int optind, opterr; - struct XrdOucLogging::configLogInfo LogInfo; - int pipeFD[2] = {-1, -1}; - const char *pidFN = 0; - static const int myMaxc = 80; - char **urArgv, *myArgv[myMaxc], argBuff[myMaxc*3+8]; - char *argbP = argBuff, *argbE = argbP+sizeof(argBuff)-4; - char *ifList = 0; - int myArgc = 1, urArgc = argc, i; - bool noV6, ipV4 = false, ipV6 = false; - -// Obtain the protocol name we will be using -// - retc = strlen(argv[0]); - while(retc--) if (argv[0][retc] == '/') break; - myProg = dfltProt = &argv[0][retc+1]; - -// Setup the initial required protocol. The program name matches the protocol -// name but may be arbitrarily suffixed. We need to ignore this suffix. So we -// look for it here and it it exists we duplicate argv[0] (yes, loosing some -// bytes - sorry valgrind) without the suffix. -// - {char *p = dfltProt; - while(*p && (*p == '.' || *p == '-')) p++; - if (*p) - {char *dot = index(p, '.'), *dash = index(p, '-'), sepc; - if (dot && (dot < dash || !dash)) p = dot; - else if (dash) p = dash; - else p = 0; - if (p) {sepc = *p; *p = '\0'; dfltProt = strdup(dfltProt); *p = sepc;} - } - } - myArgv[0] = argv[0]; - -// Prescan the argument list to see if there is a passthrough option. In any -// case, we will set the ephemeral argv/arg in the environment. -// - i = 1; - while(i < argc) - {if (*(argv[i]) == '-' && *(argv[i]+1) == '+') - {int n = strlen(argv[i]+2), j = i+1, k = 1; - if (urArgc == argc) urArgc = i; - if (n) memcpy(buff, argv[i]+2, (n > 256 ? 256 : n)); - strcpy(&(buff[n]), ".argv**"); - while(j < argc && (*(argv[j]) != '-' || *(argv[j]+1) != '+')) j++; - urArgv = new char*[j-i+1]; - urArgv[0] = argv[0]; - i++; - while(i < j) urArgv[k++] = argv[i++]; - urArgv[k] = 0; - theEnv.PutPtr(buff, urArgv); - strcpy(&(buff[n]), ".argc"); - theEnv.PutInt(buff, static_cast(k)); - } else i++; - } - theEnv.PutPtr("argv[0]", argv[0]); - -// Process the options. Note that we cannot passthrough long options or -// options that take arguments because getopt permutes the arguments. -// - opterr = 0; - if (argc > 1 && '-' == *argv[1]) - while ((c = getopt(urArgc,argv,":bc:dhHI:k:l:L:n:p:P:R:s:S:vz")) - && ((unsigned char)c != 0xff)) - { switch(c) - { - case 'b': optbg = 1; - break; - case 'c': if (ConfigFN) free(ConfigFN); - ConfigFN = strdup(optarg); - break; - case 'd': Trace.What |= TRACE_ALL; - ProtInfo.DebugON = 1; - XrdOucEnv::Export("XRDDEBUG", "1"); - break; - case 'h': Usage(0); - break; - case 'H': Usage(-1); - break; - case 'I': if (!strcmp("v4", optarg)) {ipV4 = true; ipV6 = false;} - else if (!strcmp("v6", optarg)) {ipV4 = false; ipV6 = true;} - else {Log.Emsg("Config", "Invalid -I argument -",optarg); - Usage(1); - } - break; - case 'k': if (!(LogInfo.keepV = Log.logger()->ParseKeep(optarg))) - {Log.Emsg("Config","Invalid -k argument -",optarg); - Usage(1); - } - break; - case 'l': LogInfo.logArg = optarg; - break; - case 'L': if (!*optarg) - {Log.Emsg("Config", "Protocol library path not specified."); - Usage(1); - } - if (libProt) free(libProt); - libProt = strdup(optarg); - break; - case 'n': myInsName = (!strcmp(optarg,"anon")||!strcmp(optarg,"default") - ? 0 : optarg); - break; - case 'p': if ((clPort = yport(&Log, "tcp", optarg)) < 0) Usage(1); - break; - case 'P': dfltProt = optarg; - break; - case 'R': if (!(getUG(optarg, myUid, myGid))) Usage(1); - break; - case 's': pidFN = optarg; - break; - case 'S': mySitName = optarg; - break; - case ':': buff[0] = '-'; buff[1] = optopt; buff[2] = 0; - Log.Emsg("Config", buff, "parameter not specified."); - Usage(1); - break; - case 'v': cerr <= myMaxc || argbP >= argbE) - {Log.Emsg("Config", "Too many command line arguments."); - Usage(1); - } - myArgv[myArgc++] = argbP; - *argbP++ = '-'; *argbP++ = optopt; *argbP++ = 0; - break; - } - } - -// The first thing we must do is to set the correct networking mode -// - noV6 = XrdNetAddr::IPV4Set(); - if (ipV4) XrdNetAddr::SetIPV4(); - else if (ipV6){if (noV6) Log.Say("Config warning: ipV6 appears to be broken;" - " forced ipV6 mode not advised!"); - XrdNetAddr::SetIPV6(); - } - else if (noV6) Log.Say("Config warning: ipV6 is misconfigured or " - "unavailable; reverting to ipV4."); - -// Set the site name if we have one -// - if (mySitName) mySitName = XrdOucSiteName::Set(mySitName, 63); - -// Drop into non-privileged state if so requested -// - if (myGid && setegid(myGid)) - {Log.Emsg("Config", errno, "set effective gid"); exit(17);} - if (myUid && seteuid(myUid)) - {Log.Emsg("Config", errno, "set effective uid"); exit(17);} - -// Pass over any parameters -// - if (urArgc-optind+2 >= myMaxc) - {Log.Emsg("Config", "Too many command line arguments."); - Usage(1); - } - for ( ; optind < urArgc; optind++) myArgv[myArgc++] = argv[optind]; - -// Record the actual arguments that we will pass on -// - myArgv[myArgc] = 0; - ProtInfo.argc = myArgc; - ProtInfo.argv = myArgv; - -// Resolve background/foreground issues -// - if (optbg) - { -#ifdef WIN32 - XrdOucUtils::Undercover(&Log, !LogInfo.logArg); -#else - if (pipe( pipeFD ) == -1) - {Log.Emsg("Config", errno, "create a pipe"); exit(17);} - XrdOucUtils::Undercover(Log, !LogInfo.logArg, pipeFD); -#endif - } - -// Get the full host name. We must define myIPAddr here because we may need to -// run in v4 mode and that doesn't get set until after the options are scanned. -// - static XrdNetAddr *myIPAddr = new XrdNetAddr((int)0); - if (!(myName = myIPAddr->Name(0, &temp))) myName = ""; - -// Get our IP address and FQN -// - ProtInfo.myName = myName; - ProtInfo.myAddr = myIPAddr->SockAddr(); - ProtInfo.myInst = XrdOucUtils::InstName(myInsName); - ProtInfo.myProg = myProg; - -// Set the Environmental variable to hold the instance name -// XRDINSTANCE= @ -// XrdOucEnv::Export("XRDINSTANCE") -// - sprintf(buff,"%s%s %s@%s", xrdInst, myProg, ProtInfo.myInst, myName); - myInstance = strdup(buff); - putenv(myInstance); // XrdOucEnv::Export("XRDINSTANCE",...) - myInstance += strlen(xrdInst); - XrdOucEnv::Export("XRDHOST", myName); - XrdOucEnv::Export("XRDNAME", ProtInfo.myInst); - XrdOucEnv::Export("XRDPROG", myProg); - -// Bind the log file if we have one -// - if (LogInfo.logArg) - {LogInfo.xrdEnv = &theEnv; - LogInfo.iName = myInsName; - LogInfo.cfgFn = ConfigFN; - if (!XrdOucLogging::configLog(Log, LogInfo)) _exit(16); - Log.logger()->AddMsg(XrdBANNER); - } - -// We now test for host name. In theory, we should always get some kind of name. -// We can't really continue without some kind of name at this point. Note that -// vriable temp should still be valid from the previous NetAddr call. -// - if (!(*myName)) - {Log.Emsg("Config", "Unable to determine host name; ", - (temp ? temp : "reason unknown"), - "; execution terminated."); - _exit(16); - } - -// Tell NetIF what logger to use as it's been properly setup by now. -// - XrdNetIF::SetMsgs(&Log); - -// Put out the herald -// - strcpy(buff, "Starting on "); - retc = strlen(buff); - XrdSysUtils::FmtUname(buff+retc, sizeof(buff)-retc); - Log.Say(0, buff); - Log.Say(XrdBANNER); - -// Verify that we have a real name. We've had problems with people setting up -// bad /etc/hosts files that can cause connection failures if "allow" is used. -// Otherwise, determine our domain name. -// - if (!myIPAddr->isRegistered()) - {Log.Emsg("Config",myName,"does not appear to be registered in the DNS."); - Log.Emsg("Config","Verify that the '/etc/hosts' file is correct and " - "this machine is registered in DNS."); - Log.Emsg("Config", "Execution continues but connection failures may occur."); - myDomain = 0; - } else if (!(myDomain = index(myName, '.'))) - Log.Say("Config warning: this hostname, ", myName, - ", is registered without a domain qualification."); - -// Setup the initial required protocol. -// - Firstcp = Lastcp = new XrdConfigProt(strdup(dfltProt), libProt, 0); - -// Let start it up! -// - Log.Say("++++++ ", myInstance, " initialization started."); - -// Process the configuration file, if one is present -// - if (ConfigFN && *ConfigFN) - {Log.Say("Config using configuration file ", ConfigFN); - ProtInfo.ConfigFN = ConfigFN; - setCFG(); - NoGo = ConfigProc(); - } - if (clPort >= 0) PortTCP = clPort; - if (ProtInfo.DebugON) - {Trace.What = TRACE_ALL; - XrdSysThread::setDebug(&Log); - } - -// Put largest buffer size in the env -// - theEnv.PutInt("MaxBuffSize", XrdGlobal::xlBuff.MaxSize()); - -// Export the network interface list at this point -// - if (ppNet && XrdNetIF::GetIF(ifList, 0, true)) - XrdOucEnv::Export("XRDIFADDRS",ifList); - -// Configure network routing -// - if (!XrdInet::netIF.SetIF(myIPAddr, ifList)) - {Log.Emsg("Config", "Unable to determine interface addresses!"); - NoGo = 1; - } - -// Now initialize the default protocl -// - if (!NoGo) NoGo = Setup(dfltProt); - -// If we hae a net name change the working directory -// - if ((myInsName || HomePath) - && !XrdOucUtils::makeHome(Log, myInsName, HomePath, HomeMode)) NoGo = 1; - - // if we call this it means that the daemon has forked and we are - // in the child process -#ifndef WIN32 - if (optbg) - { - if (pidFN && !XrdOucUtils::PidFile(Log, pidFN ) ) - NoGo = 1; - - int status = NoGo ? 1 : 0; - if(write( pipeFD[1], &status, sizeof( status ) )) {}; - close( pipeFD[1]); - } -#endif - -// Establish a pid/manifest file for auto-collection -// - if (!NoGo) Manifest(pidFN); - -// All done, close the stream and return the return code. -// - temp = (NoGo ? " initialization failed." : " initialization completed."); - sprintf(buff, "%s:%d", myInstance, PortTCP); - Log.Say("------ ", buff, temp); - if (LogInfo.logArg) - {strcat(buff, " running "); - retc = strlen(buff); - XrdSysUtils::FmtUname(buff+retc, sizeof(buff)-retc); - Log.logger()->AddMsg(buff); - } - return NoGo; -} - -/******************************************************************************/ -/* C o n f i g X e q */ -/******************************************************************************/ - -int XrdConfig::ConfigXeq(char *var, XrdOucStream &Config, XrdSysError *eDest) -{ - int dynamic; - - // Determine whether is is dynamic or not - // - if (eDest) dynamic = 1; - else {dynamic = 0; eDest = &Log;} - - // Process common items - // - TS_Xeq("buffers", xbuf); - TS_Xeq("network", xnet); - TS_Xeq("sched", xsched); - TS_Xeq("trace", xtrace); - - // Process items that can only be processed once - // - if (!dynamic) - { - TS_Xeq("adminpath", xapath); - TS_Xeq("allow", xallow); - TS_Xeq("homepath", xhpath); - TS_Xeq("port", xport); - TS_Xeq("protocol", xprot); - TS_Xeq("report", xrep); - TS_Xeq("sitename", xsit); - TS_Xeq("timeout", xtmo); - } - - // No match found, complain. - // - eDest->Say("Config warning: ignoring unknown xrd directive '",var,"'."); - Config.Echo(); - return 0; -} - -/******************************************************************************/ -/* P r i v a t e F u n c t i o n s */ -/******************************************************************************/ -/******************************************************************************/ -/* A S o c k e t */ -/******************************************************************************/ - -int XrdConfig::ASocket(const char *path, const char *fname, mode_t mode) -{ - char xpath[MAXPATHLEN+8], sokpath[108]; - int plen = strlen(path), flen = strlen(fname); - int rc; - -// Make sure we can fit everything in our buffer -// - if ((plen + flen + 3) > (int)sizeof(sokpath)) - {Log.Emsg("Config", "admin path", path, "too long"); - return 1; - } - -// Create the directory path -// - strcpy(xpath, path); - if ((rc = XrdOucUtils::makePath(xpath, mode))) - {Log.Emsg("Config", rc, "create admin path", xpath); - return 1; - } - -// *!*!* At this point we do not yet support the admin path for xrd. -// sp we comment out all of the following code. - -/* -// Construct the actual socket name -// - if (sokpath[plen-1] != '/') sokpath[plen++] = '/'; - strcpy(&sokpath[plen], fname); - -// Create an admin network -// - NetADM = new XrdInet(&Log); - if (myDomain) NetADM->setDomain(myDomain); - -// Bind the netwok to the named socket -// - if (!NetADM->Bind(sokpath)) return 1; - -// Set the mode and return -// - chmod(sokpath, mode); // This may fail on some platforms -*/ - return 0; -} - -/******************************************************************************/ -/* C o n f i g P r o c */ -/******************************************************************************/ - -int XrdConfig::ConfigProc() -{ - char *var; - int cfgFD, retc, NoGo = 0; - XrdOucEnv myEnv; - XrdOucStream Config(&Log, myInstance, &myEnv, "=====> "); - -// Try to open the configuration file. -// - if ( (cfgFD = open(ConfigFN, O_RDONLY, 0)) < 0) - {Log.Emsg("Config", errno, "open config file", ConfigFN); - return 1; - } - Config.Attach(cfgFD); - -// Now start reading records until eof. -// - while((var = Config.GetMyFirstWord())) - if (!strncmp(var, "xrd.", 4) - || !strcmp (var, "all.adminpath") - || !strcmp (var, "all.sitename" )) - if (ConfigXeq(var+4, Config)) {Config.Echo(); NoGo = 1;} - -// Now check if any errors occured during file i/o -// - if ((retc = Config.LastError())) - NoGo = Log.Emsg("Config", retc, "read config file", ConfigFN); - Config.Close(); - -// Return final return code -// - return NoGo; -} - -/******************************************************************************/ -/* g e t U G */ -/******************************************************************************/ - -int XrdConfig::getUG(char *parm, uid_t &newUid, gid_t &newGid) -{ - struct passwd *pp; - -// Get the userid entry -// - if (!(*parm)) - {Log.Emsg("Config", "-R user not specified."); return 0;} - - if (isdigit(*parm)) - {if (!(newUid = atol(parm))) - {Log.Emsg("Config", "-R", parm, "is invalid"); return 0;} - pp = getpwuid(newUid); - } - else pp = getpwnam(parm); - -// Make sure it is valid and acceptable -// - if (!pp) - {Log.Emsg("Config", errno, "retrieve -R user password entry"); - return 0; - } - if (!(newUid = pp->pw_uid)) - {Log.Emsg("Config", "-R", parm, "is still unacceptably a superuser!"); - return 0; - } - newGid = pp->pw_gid; - return 1; -} - -/******************************************************************************/ -/* M a n i f e s t */ -/******************************************************************************/ - -void XrdConfig::Manifest(const char *pidfn) -{ - const char *Slash; - char envBuff[8192], pwdBuff[1024], manBuff[1024], *pidP, *sP, *xP; - int envFD, envLen; - -// Get the current working directory -// - if (!getcwd(pwdBuff, sizeof(pwdBuff))) - {Log.Emsg("Config", "Unable to get current working directory!"); - return; - } - -// Prepare for symlinks -// - strcpy(envBuff, ProtInfo.AdmPath); - envLen = strlen(envBuff); - if (envBuff[envLen-1] != '/') {envBuff[envLen] = '/'; envLen++;} - strcpy(envBuff+envLen, ".xrd/"); - xP = envBuff+envLen+5; - -// Create a symlink to the configuration file -// - if ((sP = getenv("XRDCONFIGFN"))) - {sprintf(xP, "=/conf/%s.cf", myProg); - XrdOucUtils::ReLink(envBuff, sP); - } - -// Create a symlink to where core files will be found -// - sprintf(xP, "=/core/%s", myProg); - XrdOucUtils::ReLink(envBuff, pwdBuff); - -// Create a symlink to where log files will be found -// - if ((sP = getenv("XRDLOGDIR"))) - {sprintf(xP, "=/logs/%s", myProg); - XrdOucUtils::ReLink(envBuff, sP); - } - -// Create a symlink to out proc information (Linux only) -// -#ifdef __linux__ - sprintf(xP, "=/proc/%s", myProg); - sprintf(manBuff, "/proc/%d", getpid()); - XrdOucUtils::ReLink(envBuff, manBuff); -#endif - -// Create environment string -// - envLen = snprintf(envBuff, sizeof(envBuff), "pid=%d&host=%s&inst=%s&ver=%s" - "&cfgfn=%s&cwd=%s&apath=%s&logfn=%s\n", - static_cast(getpid()), ProtInfo.myName, - ProtInfo.myInst, XrdVSTRING, - (getenv("XRDCONFIGFN") ? getenv("XRDCONFIGFN") : ""), - pwdBuff, ProtInfo.AdmPath, Log.logger()->xlogFN()); - -// Find out where we should write this -// - if (pidfn && (Slash = rindex(pidfn, '/'))) - {strncpy(manBuff, pidfn, Slash-pidfn); pidP = manBuff+(Slash-pidfn);} - else {strcpy(manBuff, "/tmp"); pidP = manBuff+4;} - -// Construct the pid file name for ourselves -// - snprintf(pidP, sizeof(manBuff)-(pidP-manBuff), "/%s.%s.env", - ProtInfo.myProg, ProtInfo.myInst); - -// Open the file -// - if ((envFD = open(manBuff, O_WRONLY|O_CREAT|O_TRUNC, 0664)) < 0) - {Log.Emsg("Config", errno, "create envfile", manBuff); - return; - } - -// Write out environmental information -// - if (write(envFD, envBuff, envLen) < 0) - Log.Emsg("Config", errno, "write to envfile", manBuff); - -// All done -// - close(envFD); -} - -/******************************************************************************/ -/* s e t C F G */ -/******************************************************************************/ - -void XrdConfig::setCFG() -{ - char cwdBuff[1024], *cfnP = cwdBuff; - int n; - -// If the config file is absolute, export it as is -// - if (*ConfigFN == '/') - {XrdOucEnv::Export("XRDCONFIGFN", ConfigFN); - return; - } - -// Prefix current working directory to the config file -// - if (!getcwd(cwdBuff, sizeof(cwdBuff)-strlen(ConfigFN)-2)) cfnP = ConfigFN; - else {n = strlen(cwdBuff); - if (cwdBuff[n-1] != '/') cwdBuff[n++] = '/'; - strcpy(cwdBuff+n, ConfigFN); - } - -// Export result -// - XrdOucEnv::Export("XRDCONFIGFN", cfnP); -} - -/******************************************************************************/ -/* s e t F D L */ -/******************************************************************************/ - -int XrdConfig::setFDL() -{ - static const int maxFD = 65535; // was 1048576 see XrdNetAddrInfo::sockNum - struct rlimit rlim; - char buff[100]; - -// Get the resource limit -// - if (getrlimit(RLIMIT_NOFILE, &rlim) < 0) - return Log.Emsg("Config", errno, "get FD limit"); - -// Set the limit to the maximum allowed -// - if (rlim.rlim_max == RLIM_INFINITY) rlim.rlim_cur = maxFD; - else rlim.rlim_cur = rlim.rlim_max; -#if (defined(__APPLE__) && defined(MAC_OS_X_VERSION_10_5)) - if (rlim.rlim_cur > OPEN_MAX) rlim.rlim_max = rlim.rlim_cur = OPEN_MAX; -#endif - if (setrlimit(RLIMIT_NOFILE, &rlim) < 0) - return Log.Emsg("Config", errno,"set FD limit"); - -// Obtain the actual limit now -// - if (getrlimit(RLIMIT_NOFILE, &rlim) < 0) - return Log.Emsg("Config", errno, "get FD limit"); - -// Establish operating limit -// - ProtInfo.ConnMax = rlim.rlim_cur; - sprintf(buff, "%d", ProtInfo.ConnMax); - Log.Say("Config maximum number of connections restricted to ", buff); - -// Set core limit and but Solaris -// -#if !defined( __solaris__ ) && defined(RLIMIT_CORE) - if (coreV >= 0) - {if (getrlimit(RLIMIT_CORE, &rlim) < 0) - Log.Emsg("Config", errno, "get core limit"); - else {rlim.rlim_cur = (coreV ? rlim.rlim_max : 0); - if (setrlimit(RLIMIT_CORE, &rlim) < 0) - Log.Emsg("Config", errno,"set core limit"); - } - } -#endif - -// The scheduler will have already set the thread limit. We just report it -// -#if defined(__linux__) && defined(RLIMIT_NPROC) - -// Obtain the actual limit now (Scheduler construction sets this to rlim_max) -// - if (getrlimit(RLIMIT_NPROC, &rlim) < 0) - return Log.Emsg("Config", errno, "get thread limit"); - -// Establish operating limit -// - int nthr = static_cast(rlim.rlim_cur); - if (nthr < 8192 || ProtInfo.DebugON) - {sprintf(buff, "%d", static_cast(rlim.rlim_cur)); - Log.Say("Config maximum number of threads restricted to ", buff); - } -#endif - - return 0; -} - -/******************************************************************************/ -/* S e t u p */ -/******************************************************************************/ - -int XrdConfig::Setup(char *dfltp) -{ - XrdInet *NetWAN; - XrdConfigProt *cp; - int i, wsz, arbNet; - -// Establish the FD limit -// - if (setFDL()) return 1; - -// Special handling for Linux sendfile() -// -#if defined(__linux__) && defined(TCP_CORK) -{ int sokFD, setON = 1; - if ((sokFD = socket(PF_INET, SOCK_STREAM, 0)) >= 0) - {setsockopt(sokFD, XrdNetUtils::ProtoID("tcp"), TCP_NODELAY, - &setON, sizeof(setON)); - if (setsockopt(sokFD, SOL_TCP, TCP_CORK, &setON, sizeof(setON)) < 0) - XrdLink::sfOK = 0; - close(sokFD); - } -} -#endif - -// Indicate how sendfile is being handled -// - TRACE(NET,"sendfile " <<(XrdLink::sfOK ? "enabled." : "disabled!")); - -// Initialize the buffer manager -// - BuffPool.Init(); - -// Start the scheduler -// - Sched.Start(); - -// Setup the link and socket polling infrastructure -// - XrdLink::Init(&Log, &Trace, &Sched); - XrdPoll::Init(&Log, &Trace, &Sched); - if (!XrdLink::Setup(ProtInfo.ConnMax, ProtInfo.idleWait) - || !XrdPoll::Setup(ProtInfo.ConnMax)) return 1; - -// Modify the AdminPath to account for any instance name. Note that there is -// a negligible memory leak under ceratin path combinations. Not enough to -// warrant a lot of logic to get around. -// - if (myInsName) ProtInfo.AdmPath = XrdOucUtils::genPath(AdminPath,myInsName); - else ProtInfo.AdmPath = AdminPath; - XrdOucEnv::Export("XRDADMINPATH", ProtInfo.AdmPath); - AdminPath = XrdOucUtils::genPath(AdminPath, myInsName, ".xrd"); - -// Setup admin connection now -// - if (ASocket(AdminPath, "admin", (mode_t)AdminMode)) return 1; - -// Determine the default port number (only for xrootd) if not specified. -// - if (PortTCP < 0) - {if ((PortTCP = XrdNetUtils::ServPort(dfltp))) PortUDP = PortTCP; - else PortTCP = -1; - } - -// We now go through all of the protocols and get each respective port number. -// - XrdProtLoad::Init(&Log, &Trace); cp = Firstcp; - while(cp) - {ProtInfo.Port = (cp->port < 0 ? PortTCP : cp->port); - XrdOucEnv::Export("XRDPORT", ProtInfo.Port); - if ((cp->port = XrdProtLoad::Port(cp->libpath, cp->proname, - cp->parms, &ProtInfo)) < 0) return 1; - cp = cp->Next; - } - -// Allocate the statistics object. This is akward since we only know part -// of the current configuration. The object will figure this out later. -// - ProtInfo.Stats = new XrdStats(&Log, &Sched, &BuffPool, - ProtInfo.myName, Firstcp->port, - ProtInfo.myInst, ProtInfo.myProg, mySitName); - -// Allocate a WAN port number of we need to -// - if (PortWAN && (NetWAN = new XrdInet(&Log, &Trace, Police))) - {if (Wan_Opts || Wan_Blen) NetWAN->setDefaults(Wan_Opts, Wan_Blen); - if (myDomain) NetWAN->setDomain(myDomain); - if (NetWAN->BindSD((PortWAN > 0 ? PortWAN : 0), "tcp")) return 1; - PortWAN = NetWAN->Port(); - wsz = NetWAN->WSize(); - Wan_Blen = (wsz < Wan_Blen || !Wan_Blen ? wsz : Wan_Blen); - TRACE(NET,"WAN port " <port)) i = arbNet; - else for (i = 0; i < XrdProtLoad::ProtoMax && NetTCP[i]; i++) - {if (cp->port == NetTCP[i]->Port()) break;} - if (i >= XrdProtLoad::ProtoMax || !NetTCP[i]) - {NetTCP[++NetTCPlep] = new XrdInet(&Log, &Trace, Police); - if (Net_Opts || Net_Blen) - NetTCP[NetTCPlep]->setDefaults(Net_Opts, Net_Blen); - if (myDomain) NetTCP[NetTCPlep]->setDomain(myDomain); - if (NetTCP[NetTCPlep]->BindSD(cp->port, "tcp")) return 1; - ProtInfo.Port = NetTCP[NetTCPlep]->Port(); - ProtInfo.NetTCP = NetTCP[NetTCPlep]; - wsz = NetTCP[NetTCPlep]->WSize(); - ProtInfo.WSize = (wsz < Net_Blen || !Net_Blen ? wsz : Net_Blen); - TRACE(NET,"LCL port " <wanopt) - {ProtInfo.WANPort = PortWAN; - ProtInfo.WANWSize= Wan_Blen; - } else ProtInfo.WANPort = ProtInfo.WANWSize = 0; - if (!(cp->port)) arbNet = NetTCPlep; - if (!NetTCPlep) XrdLink::Init(NetTCP[0]); - XrdOucEnv::Export("XRDPORT", ProtInfo.Port); - } - if (!XrdProtLoad::Load(cp->libpath,cp->proname,cp->parms,&ProtInfo)) - return 1; - Firstcp = cp->Next; delete cp; - } - -// Leave the env port number to be the first used port number. This may -// or may not be the same as the default port number. -// - ProtInfo.Port = NetTCP[0]->Port(); - PortTCP = ProtInfo.Port; - XrdOucEnv::Export("XRDPORT", PortTCP); - -// Now check if we have to setup automatic reporting -// - if (repDest[0] != 0 && repOpts) - ProtInfo.Stats->Report(repDest, repInt, repOpts); - -// All done -// - return 0; -} - -/******************************************************************************/ -/* U s a g e */ -/******************************************************************************/ - -void XrdConfig::Usage(int rc) -{ - extern const char *XrdLicense; - - if (rc < 0) cerr <] [-d] [-h] [-H] [-I {v4|v6}]\n" - "[-k {n|sz|sig}] [-l [=]] [-n name] [-p ] [-P ] [-L ]\n" - "[-R] [-s pidfile] [-S site] [-v] [-z] []" < 0 ? rc : 0); -} - -/******************************************************************************/ -/* x a p a t h */ -/******************************************************************************/ - -/* Function: xapath - - Purpose: To parse the directive: adminpath [group] - - the path of the FIFO to use for admin requests. - - group allows group access to the admin path - - Note: A named socket is created //.xrd/admin - - Output: 0 upon success or !0 upon failure. -*/ - -int XrdConfig::xapath(XrdSysError *eDest, XrdOucStream &Config) -{ - char *pval, *val; - mode_t mode = S_IRWXU; - -// Get the path -// - pval = Config.GetWord(); - if (!pval || !pval[0]) - {eDest->Emsg("Config", "adminpath not specified"); return 1;} - -// Make sure it's an absolute path -// - if (*pval != '/') - {eDest->Emsg("Config", "adminpath not absolute"); return 1;} - -// Record the path -// - if (AdminPath) free(AdminPath); - AdminPath = strdup(pval); - -// Get the optional access rights -// - if ((val = Config.GetWord()) && val[0]) - {if (!strcmp("group", val)) mode |= S_IRWXG; - else {eDest->Emsg("Config", "invalid admin path modifier -", val); - return 1; - } - } - AdminMode = ProtInfo.AdmMode = mode; - return 0; -} - -/******************************************************************************/ -/* x a l l o w */ -/******************************************************************************/ - -/* Function: xallow - - Purpose: To parse the directive: allow {host | netgroup} - - The dns name of the host that is allowed to connect or the - netgroup name the host must be a member of. For DNS names, - a single asterisk may be specified anywhere in the name. - - Output: 0 upon success or !0 upon failure. -*/ - -int XrdConfig::xallow(XrdSysError *eDest, XrdOucStream &Config) -{ - char *val; - int ishost; - - if (!(val = Config.GetWord())) - {eDest->Emsg("Config", "allow type not specified"); return 1;} - - if (!strcmp(val, "host")) ishost = 1; - else if (!strcmp(val, "netgroup")) ishost = 0; - else {eDest->Emsg("Config", "invalid allow type -", val); - return 1; - } - - if (!(val = Config.GetWord())) - {eDest->Emsg("Config", "allow target name not specified"); return 1;} - - if (!Police) {Police = new XrdNetSecurity(); - if (Trace.What == TRACE_ALL) Police->Trace(&Trace); - } - if (ishost) Police->AddHost(val); - else Police->AddNetGroup(val); - - return 0; -} - -/******************************************************************************/ -/* x h p a t h */ -/******************************************************************************/ - -/* Function: xhpath - - Purpose: To parse the directive: homepath [group] - - the path of the home director to be made as the cwd. - - group allows group access to the home path - - Output: 0 upon success or !0 upon failure. -*/ - -int XrdConfig::xhpath(XrdSysError *eDest, XrdOucStream &Config) -{ - -// Free existing home path, if any -// - if (HomePath) {free(HomePath); HomePath = 0;} - -// Parse the home path and return success or failure -// - HomePath = XrdOucUtils::parseHome(*eDest, Config, HomeMode); - return (HomePath ? 0 : 1); -} - -/******************************************************************************/ -/* x b u f */ -/******************************************************************************/ - -/* Function: xbuf - - Purpose: To parse the directive: buffers [maxbsz ] [] - - maximum size of an individualbuffer. The default is 2m. - Specify any value 2m < bsz <= 1g; if specified, it must - appear before the and becomes optional. - maximum amount of memory devoted to buffers - minimum buffer reshape interval in seconds - - Output: 0 upon success or !0 upon failure. -*/ -int XrdConfig::xbuf(XrdSysError *eDest, XrdOucStream &Config) -{ - static const long long minBSZ = 1024*1024*2+1; // 2mb - static const long long maxBSZ = 1024*1024*1024; // 1gb - int bint = -1; - long long blim; - char *val; - - if (!(val = Config.GetWord())) - {eDest->Emsg("Config", "buffer memory limit not specified"); return 1;} - - if (!strcmp("maxbsz", val)) - {if (!(val = Config.GetWord())) - {eDest->Emsg("Config", "max buffer size not specified"); return 1;} - if (XrdOuca2x::a2sz(*eDest,"maxbz value",val,&blim,minBSZ,maxBSZ)) - return 1; - XrdGlobal::xlBuff.Init(blim); - if (!(val = Config.GetWord())) return 0; - } - - if (XrdOuca2x::a2sz(*eDest,"buffer limit value",val,&blim, - (long long)1024*1024)) return 1; - - if ((val = Config.GetWord())) - if (XrdOuca2x::a2tm(*eDest,"reshape interval", val, &bint, 300)) - return 1; - - BuffPool.Set((int)blim, bint); - return 0; -} - -/******************************************************************************/ -/* x n e t */ -/******************************************************************************/ - -/* Function: xnet - - Purpose: To parse directive: network [wan] [[no]keepalive] [buffsz ] - [kaparms parms] [cache ] [[no]dnr] - [routes [use ,]] - [[no]rpipa] - - : split | common | local - - wan parameters apply only to the wan port - keepalive do [not] set the socket keepalive option. - kaparms keepalive paramters as specfied by parms. - is the socket's send/rcv buffer size. - Seconds to cache address to name resolutions. - [no]dnr do [not] perform a reverse DNS lookup if not needed. - routes specifies the network configuration (see reference) - [no]rpipa do [not] resolve private IP addresses. - - Output: 0 upon success or !0 upon failure. -*/ - -int XrdConfig::xnet(XrdSysError *eDest, XrdOucStream &Config) -{ - char *val; - int i, n, V_keep = -1, V_nodnr = 0, V_iswan = 0, V_blen = -1, V_ct = -1, V_assumev4; - int v_rpip = -1; - long long llp; - struct netopts {const char *opname; int hasarg; int opval; - int *oploc; const char *etxt;} - ntopts[] = - { - {"assumev4", 0, 1, &V_assumev4, "option"}, - {"keepalive", 0, 1, &V_keep, "option"}, - {"nokeepalive",0, 0, &V_keep, "option"}, - {"kaparms", 4, 0, &V_keep, "option"}, - {"buffsz", 1, 0, &V_blen, "network buffsz"}, - {"cache", 2, 0, &V_ct, "cache time"}, - {"dnr", 0, 0, &V_nodnr, "option"}, - {"nodnr", 0, 1, &V_nodnr, "option"}, - {"routes", 3, 1, 0, "routes"}, - {"rpipa", 0, 1, &v_rpip, "rpipa"}, - {"norpipa", 0, 0, &v_rpip, "norpipa"}, - {"wan", 0, 1, &V_iswan, "option"} - }; - int numopts = sizeof(ntopts)/sizeof(struct netopts); - - if (!(val = Config.GetWord())) - {eDest->Emsg("Config", "net option not specified"); return 1;} - - while (val) - {for (i = 0; i < numopts; i++) - if (!strcmp(val, ntopts[i].opname)) - {if (!ntopts[i].hasarg) *ntopts[i].oploc = ntopts[i].opval; - else {if (!(val = Config.GetWord())) - {eDest->Emsg("Config", "network", - ntopts[i].opname, "argument missing"); - return 1; - } - if (ntopts[i].hasarg == 4) - {if (xnkap(eDest, val)) return 1; - break; - } - if (ntopts[i].hasarg == 3) - { if (!strcmp(val, "split")) - XrdNetIF::Routing(XrdNetIF::netSplit); - else if (!strcmp(val, "common")) - XrdNetIF::Routing(XrdNetIF::netCommon); - else if (!strcmp(val, "local")) - XrdNetIF::Routing(XrdNetIF::netLocal); - else {eDest->Emsg("Config","Invalid routes argument -",val); - return 1; - } - if (!(val = Config.GetWord())|| !(*val)) break; - if (strcmp(val, "use")) continue; - if (!(val = Config.GetWord())|| !(*val)) - {eDest->Emsg("Config", "network routes i/f names " - "not specified."); - return 1; - } - if (!XrdNetIF::SetIFNames(val)) return 1; - ppNet = 1; - break; - } - if (ntopts[i].hasarg == 2) - {if (XrdOuca2x::a2tm(*eDest,ntopts[i].etxt,val,&n,0)) - return 1; - *ntopts[i].oploc = n; - } else { - if (XrdOuca2x::a2sz(*eDest,ntopts[i].etxt,val,&llp,0)) - return 1; - *ntopts[i].oploc = (int)llp; - } - } - break; - } - if (i >= numopts) - eDest->Say("Config warning: ignoring invalid net option '",val,"'."); - else if (!val) break; - val = Config.GetWord(); - } - - if (V_iswan) - {if (V_blen >= 0) Wan_Blen = V_blen; - if (V_keep >= 0) Wan_Opts = (V_keep ? XRDNET_KEEPALIVE : 0); - Wan_Opts |= (V_nodnr ? XRDNET_NORLKUP : 0); - if (!PortWAN) PortWAN = -1; - } else { - if (V_blen >= 0) Net_Blen = V_blen; - if (V_keep >= 0) Net_Opts = (V_keep ? XRDNET_KEEPALIVE : 0); - Net_Opts |= (V_nodnr ? XRDNET_NORLKUP : 0); - } - - if (V_ct >= 0) XrdNetAddr::SetCache(V_ct); - if (v_rpip >= 0) XrdInet::netIF.SetRPIPA(v_rpip != 0); - if (V_assumev4 >= 0) XrdInet::SetAssumeV4(true); - return 0; -} - -/******************************************************************************/ -/* x n k a p */ -/******************************************************************************/ - -/* Function: xnkap - - Purpose: To parse the directive: kaparms idle[,itvl[,cnt]] - - idle Seconds the connection needs to remain idle before TCP - should start sending keepalive probes. - itvl Seconds between individual keepalive probes. - icnt Maximum number of keepalive probes TCP should send - before dropping the connection, -*/ - -int XrdConfig::xnkap(XrdSysError *eDest, char *val) -{ - char *karg, *comma; - int knum; - -// Get the first parameter, idle seconds -// - karg = val; - if ((comma = index(val, ','))) {val = comma+1; *comma = 0;} - else val = 0; - if (XrdOuca2x::a2tm(*eDest,"kaparms idle", karg, &knum, 0)) return 1; - XrdNetSocketCFG::ka_Idle = knum; - -// Get the second parameter, interval seconds -// - if (!(karg = val)) return 0; - if ((comma = index(val, ','))) {val = comma+1; *comma = 0;} - else val = 0; - if (XrdOuca2x::a2tm(*eDest,"kaparms interval", karg, &knum, 0)) return 1; - XrdNetSocketCFG::ka_Itvl = knum; - -// Get the third parameter, count -// - if (!val) return 0; - if (XrdOuca2x::a2i(*eDest,"kaparms count", val, &knum, 0)) return 1; - XrdNetSocketCFG::ka_Icnt = knum; - -// All done -// - return 0; -} - -/******************************************************************************/ -/* x p o r t */ -/******************************************************************************/ - -/* Function: xport - - Purpose: To parse the directive: port [wan] - [if [] [named ]] - - wan apply this to the wan port - number of the tcp port for incomming requests - list of applicable host patterns - list of applicable instance names. - - Output: 0 upon success or !0 upon failure. -*/ -int XrdConfig::xport(XrdSysError *eDest, XrdOucStream &Config) -{ int rc, iswan = 0, pnum = 0; - char *val, cport[32]; - - do {if (!(val = Config.GetWord())) - {eDest->Emsg("Config", "tcp port not specified"); return 1;} - if (strcmp("wan", val) || iswan) break; - iswan = 1; - } while(1); - - strncpy(cport, val, sizeof(cport)-1); cport[sizeof(cport)-1] = '\0'; - - if ((val = Config.GetWord()) && !strcmp("if", val)) - if ((rc = XrdOucUtils::doIf(eDest,Config, "port directive", myName, - ProtInfo.myInst, myProg)) <= 0) - {if (!rc) Config.noEcho(); return (rc < 0);} - - if ((pnum = yport(eDest, "tcp", cport)) < 0) return 1; - if (iswan) PortWAN = pnum; - else PortTCP = PortUDP = pnum; - - return 0; -} - -/******************************************************************************/ - -int XrdConfig::yport(XrdSysError *eDest, const char *ptype, const char *val) -{ - int pnum; - if (!strcmp("any", val)) return 0; - - const char *invp = (*ptype == 't' ? "tcp port" : "udp port" ); - const char *invs = (*ptype == 't' ? "Unable to find tcp service" : - "Unable to find udp service" ); - - if (isdigit(*val)) - {if (XrdOuca2x::a2i(*eDest,invp,val,&pnum,1,65535)) return 0;} - else if (!(pnum = XrdNetUtils::ServPort(val, (*ptype != 't')))) - {eDest->Emsg("Config", invs, val); - return -1; - } - return pnum; -} - -/******************************************************************************/ -/* x p r o t */ -/******************************************************************************/ - -/* Function: xprot - - Purpose: To parse the directive: protocol [wan] [:] [] - - wan The protocol is WAN optimized - The name of the protocol (e.g., rootd) - Port binding for the protocol, if not the default. - The shared library in which it is located. - A one line parameter to be passed to the protocol. - - Output: 0 upon success or !0 upon failure. -*/ - -int XrdConfig::xprot(XrdSysError *eDest, XrdOucStream &Config) -{ - XrdConfigProt *cpp; - char *val, *parms, *lib, proname[64], buff[1024]; - int vlen, bleft = sizeof(buff), portnum = -1, wanopt = 0; - - do {if (!(val = Config.GetWord())) - {eDest->Emsg("Config", "protocol name not specified"); return 1;} - if (wanopt || strcmp("wan", val)) break; - wanopt = 1; - } while(1); - - if (strlen(val) > sizeof(proname)-1) - {eDest->Emsg("Config", "protocol name is too long"); return 1;} - strcpy(proname, val); - - if (!(val = Config.GetWord())) - {eDest->Emsg("Config", "protocol library not specified"); return 1;} - if (strcmp("*", val)) lib = strdup(val); - else lib = 0; - - parms = buff; - while((val = Config.GetWord())) - {vlen = strlen(val); bleft -= (vlen+1); - if (bleft <= 0) - {eDest->Emsg("Config", "Too many parms for protocol", proname); - return 1; - } - *parms = ' '; parms++; strcpy(parms, val); parms += vlen; - } - if (parms != buff) parms = strdup(buff+1); - else parms = 0; - - if ((val = index(proname, ':'))) - {if ((portnum = yport(&Log, "tcp", val+1)) < 0) return 1; - else *val = '\0'; - } - - if (wanopt && !PortWAN) PortWAN = 1; - - if ((cpp = Firstcp)) - do {if (!strcmp(proname, cpp->proname)) - {if (cpp->libpath) free(cpp->libpath); - if (cpp->parms) free(cpp->parms); - cpp->libpath = lib; - cpp->parms = parms; - cpp->wanopt = wanopt; - return 0; - } - } while((cpp = cpp->Next)); - - if (lib) - {cpp = new XrdConfigProt(strdup(proname), lib, parms, portnum, wanopt); - if (Lastcp) Lastcp->Next = cpp; - else Firstcp = cpp; - Lastcp = cpp; - } - - return 0; -} - -/******************************************************************************/ -/* x r e p */ -/******************************************************************************/ - -/* Function: xrep - - Purpose: To parse the directive: report [,] - [every ] - - where a UDP based report is to be sent. It may be a - or a local named UDP pipe (i.e., "/..."). - - A secondary destination. - - the reporting interval. The default is 10 minutes. - - What to report. "all" is the default. - - Output: 0 upon success or !0 upon failure. -*/ - -int XrdConfig::xrep(XrdSysError *eDest, XrdOucStream &Config) -{ - static struct repopts {const char *opname; int opval;} rpopts[] = - { - {"all", XRD_STATS_ALL}, - {"buff", XRD_STATS_BUFF}, - {"info", XRD_STATS_INFO}, - {"link", XRD_STATS_LINK}, - {"poll", XRD_STATS_POLL}, - {"process", XRD_STATS_PROC}, - {"protocols",XRD_STATS_PROT}, - {"prot", XRD_STATS_PROT}, - {"sched", XRD_STATS_SCHD}, - {"sgen", XRD_STATS_SGEN}, - {"sync", XRD_STATS_SYNC}, - {"syncwp", XRD_STATS_SYNCA} - }; - int i, neg, numopts = sizeof(rpopts)/sizeof(struct repopts); - char *val, *cp; - - if (!(val = Config.GetWord())) - {eDest->Emsg("Config", "report parameters not specified"); return 1;} - -// Cleanup to start anew -// - if (repDest[0]) {free(repDest[0]); repDest[0] = 0;} - if (repDest[1]) {free(repDest[1]); repDest[1] = 0;} - repOpts = 0; - repInt = 600; - -// Decode the destination -// - if ((cp = (char *)index(val, ','))) - {if (!*(cp+1)) - {eDest->Emsg("Config","malformed report destination -",val); return 1;} - else { repDest[1] = cp+1; *cp = '\0';} - } - repDest[0] = val; - for (i = 0; i < 2; i++) - {if (!(val = repDest[i])) break; - if (*val != '/' && (!(cp = index(val, (int)':')) || !atoi(cp+1))) - {eDest->Emsg("Config","report dest port missing or invalid in",val); - return 1; - } - repDest[i] = strdup(val); - } - -// Make sure dests differ -// - if (repDest[0] && repDest[1] && !strcmp(repDest[0], repDest[1])) - {eDest->Emsg("Config", "Warning, report dests are identical."); - free(repDest[1]); repDest[1] = 0; - } - -// Get optional "every" -// - if (!(val = Config.GetWord())) {repOpts = XRD_STATS_ALL; return 0;} - if (!strcmp("every", val)) - {if (!(val = Config.GetWord())) - {eDest->Emsg("Config", "report every value not specified"); return 1;} - if (XrdOuca2x::a2tm(*eDest,"report every",val,&repInt,1)) return 1; - val = Config.GetWord(); - } - -// Get reporting options -// - while(val) - {if (!strcmp(val, "off")) repOpts = 0; - else {if ((neg = (val[0] == '-' && val[1]))) val++; - for (i = 0; i < numopts; i++) - {if (!strcmp(val, rpopts[i].opname)) - {if (neg) repOpts &= ~rpopts[i].opval; - else repOpts |= rpopts[i].opval; - break; - } - } - if (i >= numopts) - eDest->Say("Config warning: ignoring invalid report option '",val,"'."); - } - val = Config.GetWord(); - } - -// All done -// - if (!(repOpts & XRD_STATS_ALL)) repOpts = XRD_STATS_ALL & ~XRD_STATS_INFO; - return 0; -} - -/******************************************************************************/ -/* x s c h e d */ -/******************************************************************************/ - -/* Function: xsched - - Purpose: To parse directive: sched [mint ] [maxt ] [avlt ] - [idle ] [stksz ] [core ] - - is the minimum number of threads that we need. Once - this number of threads is created, it does not decrease. - maximum number of threads that may be created. The - actual number of threads will vary between and - . - Are the number of threads that must be available for - immediate dispatch. These threads are never bound to a - connection (i.e., made stickied). Any available threads - above will be allowed to stick to a connection. - asis - leave current value alone. - max - set value to maximum allowed (hard limit). - off - turn off core files. - The time (in time spec) between checks for underused - threads. Those found will be terminated. Default is 780. - The thread stack size in bytes or K, M, or G. - - Output: 0 upon success or 1 upon failure. -*/ - -int XrdConfig::xsched(XrdSysError *eDest, XrdOucStream &Config) -{ - char *val; - long long lpp; - int i, ppp = 0; - int V_mint = -1, V_maxt = -1, V_idle = -1, V_avlt = -1; - struct schedopts {const char *opname; int minv; int *oploc; - const char *opmsg;} scopts[] = - { - {"stksz", 0, 0, "sched stksz"}, - {"mint", 1, &V_mint, "sched mint"}, - {"maxt", 1, &V_maxt, "sched maxt"}, - {"avlt", 1, &V_avlt, "sched avlt"}, - {"core", 1, 0, "sched core"}, - {"idle", 0, &V_idle, "sched idle"} - }; - int numopts = sizeof(scopts)/sizeof(struct schedopts); - - if (!(val = Config.GetWord())) - {eDest->Emsg("Config", "sched option not specified"); return 1;} - - while (val) - {for (i = 0; i < numopts; i++) - if (!strcmp(val, scopts[i].opname)) - {if (!(val = Config.GetWord())) - {eDest->Emsg("Config", "sched", scopts[i].opname, - "value not specified"); - return 1; - } - if (*scopts[i].opname == 'i') - {if (XrdOuca2x::a2tm(*eDest, scopts[i].opmsg, val, - &ppp, scopts[i].minv)) return 1; - } - else if (*scopts[i].opname == 'c') - { if (!strcmp("asis", val)) coreV = -1; - else if (!strcmp("max", val)) coreV = 1; - else if (!strcmp("off", val)) coreV = 0; - else {eDest->Emsg("Config","invalid sched core value -",val); - return 1; - } - } - else if (*scopts[i].opname == 's') - {if (XrdOuca2x::a2sz(*eDest, scopts[i].opmsg, val, - &lpp, scopts[i].minv)) return 1; - XrdSysThread::setStackSize((size_t)lpp); - break; - } - else if (XrdOuca2x::a2i(*eDest, scopts[i].opmsg, val, - &ppp,scopts[i].minv)) return 1; - *scopts[i].oploc = ppp; - break; - } - if (i >= numopts) - eDest->Say("Config warning: ignoring invalid sched option '",val,"'."); - val = Config.GetWord(); - } - -// Make sure specified quantities are consistent -// - if (V_maxt > 0) - {if (V_mint > 0 && V_mint > V_maxt) - {eDest->Emsg("Config", "sched mint must be less than maxt"); - return 1; - } - if (V_avlt > 0 && V_avlt > V_maxt) - {eDest->Emsg("Config", "sched avlt must be less than maxt"); - return 1; - } - } - -// Establish scheduler options -// - Sched.setParms(V_mint, V_maxt, V_avlt, V_idle); - return 0; -} - -/******************************************************************************/ -/* x s i t */ -/******************************************************************************/ - -/* Function: xsit - - Purpose: To parse directive: sitename - - is the 1- to 15-character site name to be included in - monitoring information. This can also come from the - command line -N option. The first such name is used. - - Output: 0 upon success or 1 upon failure. -*/ - -int XrdConfig::xsit(XrdSysError *eDest, XrdOucStream &Config) -{ - char *val; - - if (!(val = Config.GetWord())) - {eDest->Emsg("Config", "sitename value not specified"); return 1;} - - if (mySitName) eDest->Emsg("Config", "sitename already specified, using '", - mySitName, "'."); - else mySitName = XrdOucSiteName::Set(val, 63); - return 0; -} - -/******************************************************************************/ -/* x t m o */ -/******************************************************************************/ - -/* Function: xtmo - - Purpose: To parse directive: timeout [read ] [hail ] - [idle ] [kill ] - - is the maximum number of seconds to wait for pending - data to arrive before we reschedule the link - (default is 5 seconds). - is the maximum number of seconds to wait for the initial - data after a connection (default is 30 seconds) - is the minimum number of seconds a connection may remain - idle before it is closed (default is 5400 = 90 minutes) - is the minimum number of seconds to wait after killing a - connection for it to end (default is 3 seconds) - - Output: 0 upon success or 1 upon failure. -*/ - -int XrdConfig::xtmo(XrdSysError *eDest, XrdOucStream &Config) -{ - char *val; - int i, ppp, rc; - int V_read = -1, V_idle = -1, V_hail = -1, V_kill = -1; - struct tmoopts { const char *opname; int istime; int minv; - int *oploc; const char *etxt;} - tmopts[] = - { - {"read", 1, 1, &V_read, "timeout read"}, - {"hail", 1, 1, &V_hail, "timeout hail"}, - {"idle", 1, 0, &V_idle, "timeout idle"}, - {"kill", 1, 0, &V_kill, "timeout kill"} - }; - int numopts = sizeof(tmopts)/sizeof(struct tmoopts); - - if (!(val = Config.GetWord())) - {eDest->Emsg("Config", "timeout option not specified"); return 1;} - - while (val) - {for (i = 0; i < numopts; i++) - if (!strcmp(val, tmopts[i].opname)) - {if (!(val = Config.GetWord())) - {eDest->Emsg("Config","timeout", tmopts[i].opname, - "value not specified"); - return 1; - } - rc = (tmopts[i].istime ? - XrdOuca2x::a2tm(*eDest,tmopts[i].etxt,val,&ppp, - tmopts[i].minv) : - XrdOuca2x::a2i (*eDest,tmopts[i].etxt,val,&ppp, - tmopts[i].minv)); - if (rc) return 1; - *tmopts[i].oploc = ppp; - break; - } - if (i >= numopts) - eDest->Say("Config warning: ignoring invalid timeout option '",val,"'."); - val = Config.GetWord(); - } - -// Set values and return -// - if (V_read > 0) ProtInfo.readWait = V_read*1000; - if (V_hail >= 0) ProtInfo.hailWait = V_hail*1000; - if (V_idle >= 0) ProtInfo.idleWait = V_idle; - XrdLink::setKWT(V_read, V_kill); - return 0; -} - -/******************************************************************************/ -/* x t r a c e */ -/******************************************************************************/ - -/* Function: xtrace - - Purpose: To parse the directive: trace - - the blank separated list of events to trace. Trace - directives are cummalative. - - Output: 0 upon success or 1 upon failure. -*/ - -int XrdConfig::xtrace(XrdSysError *eDest, XrdOucStream &Config) -{ - char *val; - static struct traceopts {const char *opname; int opval;} tropts[] = - { - {"all", TRACE_ALL}, - {"off", TRACE_NONE}, - {"none", TRACE_NONE}, - {"conn", TRACE_CONN}, - {"debug", TRACE_DEBUG}, - {"mem", TRACE_MEM}, - {"net", TRACE_NET}, - {"poll", TRACE_POLL}, - {"protocol", TRACE_PROT}, - {"sched", TRACE_SCHED} - }; - int i, neg, trval = 0, numopts = sizeof(tropts)/sizeof(struct traceopts); - - if (!(val = Config.GetWord())) - {eDest->Emsg("Config", "trace option not specified"); return 1;} - while (val) - {if (!strcmp(val, "off")) trval = 0; - else {if ((neg = (val[0] == '-' && val[1]))) val++; - for (i = 0; i < numopts; i++) - {if (!strcmp(val, tropts[i].opname)) - {if (neg) - if (tropts[i].opval) trval &= ~tropts[i].opval; - else trval = TRACE_ALL; - else if (tropts[i].opval) trval |= tropts[i].opval; - else trval = TRACE_NONE; - break; - } - } - if (i >= numopts) - eDest->Say("Config warning: ignoring invalid trace option '",val,"'."); - } - val = Config.GetWord(); - } - Trace.What = trval; - return 0; -} diff --git a/src/Xrd/XrdConfig.hh b/src/Xrd/XrdConfig.hh deleted file mode 100644 index 006b0c546cd..00000000000 --- a/src/Xrd/XrdConfig.hh +++ /dev/null @@ -1,125 +0,0 @@ -#ifndef _XRD_CONFIG_H -#define _XRD_CONFIG_H -/******************************************************************************/ -/* */ -/* X r d C o n f i g . h h */ -/* */ -/* (C) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Deprtment of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "Xrd/XrdBuffer.hh" -#include "Xrd/XrdInet.hh" -#include "Xrd/XrdProtLoad.hh" -#include "Xrd/XrdProtocol.hh" -#include "Xrd/XrdScheduler.hh" -#define XRD_TRACE Trace. -#include "Xrd/XrdTrace.hh" - -#include "XrdOuc/XrdOucTrace.hh" -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysLogger.hh" - -class XrdNetSecurity; -class XrdOucStream; -class XrdConfigProt; - -class XrdConfig -{ -public: - -int Configure(int argc, char **argv); - -int ConfigXeq(char *var, XrdOucStream &Config, XrdSysError *eDest=0); - - XrdConfig(); - ~XrdConfig() {} - -XrdProtocol_Config ProtInfo; -XrdInet *NetADM; -XrdInet *NetTCP[XrdProtLoad::ProtoMax+1]; - -private: - -int ASocket(const char *path, const char *fname, mode_t mode); -int ConfigProc(void); -int getUG(char *parm, uid_t &theUid, gid_t &theGid); -void Manifest(const char *pidfn); -void setCFG(); -int setFDL(); -int Setup(char *dfltp); -void Usage(int rc); -int xallow(XrdSysError *edest, XrdOucStream &Config); -int xapath(XrdSysError *edest, XrdOucStream &Config); -int xhpath(XrdSysError *edest, XrdOucStream &Config); -int xbuf(XrdSysError *edest, XrdOucStream &Config); -int xnet(XrdSysError *edest, XrdOucStream &Config); -int xnkap(XrdSysError *edest, char *val); -int xlog(XrdSysError *edest, XrdOucStream &Config); -int xport(XrdSysError *edest, XrdOucStream &Config); -int xprot(XrdSysError *edest, XrdOucStream &Config); -int xrep(XrdSysError *edest, XrdOucStream &Config); -int xsched(XrdSysError *edest, XrdOucStream &Config); -int xsit(XrdSysError *edest, XrdOucStream &Config); -int xtrace(XrdSysError *edest, XrdOucStream &Config); -int xtmo(XrdSysError *edest, XrdOucStream &Config); -int yport(XrdSysError *edest, const char *ptyp, const char *pval); - -static const char *TraceID; - -XrdSysLogger Logger; -XrdSysError Log; -XrdOucTrace Trace; -XrdScheduler Sched; -XrdBuffManager BuffPool; -XrdNetSecurity *Police; -const char *myProg; -const char *myName; -const char *myDomain; -const char *mySitName; -const char *myInsName; -char *myInstance; -char *AdminPath; -char *HomePath; -char *ConfigFN; -char *repDest[2]; -XrdConfigProt *Firstcp; -XrdConfigProt *Lastcp; -int Net_Blen; -int Net_Opts; -int Wan_Blen; -int Wan_Opts; - -int PortTCP; // TCP Port to listen on -int PortUDP; // UDP Port to listen on (currently unsupported) -int PortWAN; // TCP port to listen on for WAN connections -int NetTCPlep; -int AdminMode; -int HomeMode; -int repInt; -char repOpts; -char ppNet; -signed char coreV; -}; -#endif diff --git a/src/Xrd/XrdInet.cc b/src/Xrd/XrdInet.cc deleted file mode 100644 index c381447e1e8..00000000000 --- a/src/Xrd/XrdInet.cc +++ /dev/null @@ -1,253 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d I n e t . c c */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include - -#ifdef HAVE_SYSTEMD -#include -#include -#endif - -#include "XrdSys/XrdSysError.hh" - -#include "Xrd/XrdInet.hh" -#include "Xrd/XrdLink.hh" - -#define XRD_TRACE XrdTrace-> -#include "Xrd/XrdTrace.hh" - -#include "XrdNet/XrdNetAddr.hh" -#include "XrdNet/XrdNetOpts.hh" -#include "XrdNet/XrdNetSecurity.hh" - -#ifdef HAVE_SYSTEMD -#include "XrdNet/XrdNetBuffer.hh" -#include "XrdNet/XrdNetSocket.hh" -#endif - -/******************************************************************************/ -/* G l o b a l s */ -/******************************************************************************/ - - bool XrdInet::AssumeV4 = false; - - const char *XrdInet::TraceID = "Inet"; - - XrdNetIF XrdInet::netIF; - -/******************************************************************************/ -/* A c c e p t */ -/******************************************************************************/ - -XrdLink *XrdInet::Accept(int opts, int timeout, XrdSysSemaphore *theSem) -{ - static const char *unk = "unkown.endpoint"; - XrdNetAddr myAddr; - XrdLink *lp; - int anum=0, lnkopts = (opts & XRDNET_MULTREAD ? XRDLINK_RDLOCK : 0); - -// Perform regular accept. This will be a unique TCP socket. We loop here -// until the accept succeeds as it should never fail at this stage. -// - while(!XrdNet::Accept(myAddr, opts | XRDNET_NORLKUP, timeout)) - {if (timeout >= 0) - {if (theSem) theSem->Post(); - return (XrdLink *)0; - } - sleep(1); anum++; - if (!(anum%60)) eDest->Emsg("Accept", "Unable to accept connections!"); - } - -// If authorization was defered, tell call we accepted the connection but -// will be doing a background check on this connection. -// - if (theSem) theSem->Post(); - if (!(netOpts & XRDNET_NORLKUP)) myAddr.Name(); - -// Authorize by ip address or full (slow) hostname format. We defer the check -// so that the next accept can occur before we do any DNS resolution. -// - if (Patrol) - {if (!Patrol->Authorize(myAddr)) - {char ipbuff[512]; - myAddr.Format(ipbuff, sizeof(ipbuff), XrdNetAddr::fmtAuto, - XrdNetAddrInfo::noPort); - eDest->Emsg("Accept",EACCES,"accept TCP connection from",ipbuff); - close(myAddr.SockFD()); - return (XrdLink *)0; - } - } - -// Allocate a new network object -// - if (!(lp = XrdLink::Alloc(myAddr, lnkopts))) - {eDest->Emsg("Accept", ENOMEM, "allocate new link for", myAddr.Name(unk)); - close(myAddr.SockFD()); - } else { - TRACE(NET, "Accepted connection from " <(port); - -// Get correct socket type -// - if (*contype != 'u') PortType = SOCK_STREAM; - else {PortType = SOCK_DGRAM; - opts |= XRDNET_UDPSOCKET; - } - -// For each fd, check if it is a socket that we should have bound to. Make -// allowances for UDP sockets. Otherwise, make sure the socket is listening. -// - for (int i = 0; i < nSD; i++) - {int sdFD = SD_LISTEN_FDS_START + i; - if (sd_is_socket_inet(sdFD, v4Sock, PortType, -1, sdPort) > 0 - || sd_is_socket_inet(sdFD, v6Sock, PortType, -1, sdPort) > 0) - {iofd = sdFD; - Portnum = port; - XrdNetSocket::setOpts(sdFD, opts, eDest); - if (PortType == SOCK_DGRAM) - {BuffSize = (Windowsz ? Windowsz : XRDNET_UDPBUFFSZ); - BuffQ = new XrdNetBufferQ(BuffSize); - } else { - if (sd_is_socket(sdFD,v4Sock,PortType,0) > 0 - || sd_is_socket(sdFD,v6Sock,PortType,0) > 0) return Listen(); - } - return 0; - } - } -#endif - -// Either we have no systemd process or no acceptable FD available. Do an -// old-style bind() call to setup the socket. -// - return Bind(port, contype); -} - -/******************************************************************************/ -/* C o n n e c t */ -/******************************************************************************/ - -XrdLink *XrdInet::Connect(const char *host, int port, int opts, int tmo) -{ - static const char *unk = "unkown.endpoint"; - XrdNetAddr myAddr; - XrdLink *lp; - int lnkopts = (opts & XRDNET_MULTREAD ? XRDLINK_RDLOCK : 0); - -// Try to do a connect. This will be a unique TCP socket. -// - if (!XrdNet::Connect(myAddr, host, port, opts, tmo)) return (XrdLink *)0; - -// Return a link object -// - if (!(lp = XrdLink::Alloc(myAddr, lnkopts))) - {eDest->Emsg("Connect", ENOMEM, "allocate new link to", myAddr.Name(unk)); - close(myAddr.SockFD()); - } else { - TRACE(NET, "Connected to " <Emsg("Bind", erc, eBuff); - } - -// Return an error -// - return -erc; -} - -/******************************************************************************/ -/* S e c u r e */ -/******************************************************************************/ - -void XrdInet::Secure(XrdNetSecurity *secp) -{ - -// If we don't have a Patrol object then use the one supplied. Otherwise -// merge the supplied object into the existing object. -// - if (Patrol) Patrol->Merge(secp); - else Patrol = secp; -} diff --git a/src/Xrd/XrdInet.hh b/src/Xrd/XrdInet.hh deleted file mode 100644 index 492dc9c3004..00000000000 --- a/src/Xrd/XrdInet.hh +++ /dev/null @@ -1,79 +0,0 @@ -#ifndef __XRD_INET_H__ -#define __XRD_INET_H__ -/******************************************************************************/ -/* */ -/* X r d I n e t . h h */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -#include "XrdNet/XrdNet.hh" -#include "XrdNet/XrdNetIF.hh" - -// The XrdInet class defines a generic network where we can define common -// initial tcp/ip and udp operations. It is based on the generalized network -// support framework. However, Accept and Connect have been augmented to -// provide for more scalable communications handling. -// -class XrdOucTrace; -class XrdSysError; -class XrdSysSemaphore; -class XrdNetSecurity; -class XrdLink; - -class XrdInet : public XrdNet -{ -public: - -XrdLink *Accept(int opts=0, int timeout=-1, XrdSysSemaphore *theSem=0); - -int BindSD(int port, const char *contype="tcp"); - -XrdLink *Connect(const char *host, int port, int opts=0, int timeout=-1); - -void Secure(XrdNetSecurity *secp); - - XrdInet(XrdSysError *erp, XrdOucTrace *tP, XrdNetSecurity *secp=0) - : XrdNet(erp,0), Patrol(secp), XrdTrace(tP) {} - ~XrdInet() {} - -static void SetAssumeV4(bool newVal) {AssumeV4 = newVal;} - -static bool GetAssumeV4() {return AssumeV4;} - -static -XrdNetIF netIF; - -private: -int Listen(); - -XrdNetSecurity *Patrol; -XrdOucTrace *XrdTrace; -static const char *TraceID; -static bool AssumeV4; -}; -#endif diff --git a/src/Xrd/XrdInfo.cc b/src/Xrd/XrdInfo.cc deleted file mode 100644 index 76ba4b56d48..00000000000 --- a/src/Xrd/XrdInfo.cc +++ /dev/null @@ -1,41 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d I n f o . c c */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved. Scroll to end for Terms and Conditions of use */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "Xrd/XrdInfo.hh" - -/******************************************************************************/ -/* L i c e n s e T e r m a n d C o n d i t i o n s */ -/* */ -/* License terms are contained in the LICENSE file in the base directory. */ -/******************************************************************************/ - -const char *XrdLicense = -#include "../../LICENSE" -; diff --git a/src/Xrd/XrdInfo.hh b/src/Xrd/XrdInfo.hh deleted file mode 100644 index d9a6c9c01a9..00000000000 --- a/src/Xrd/XrdInfo.hh +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef __XRD_INFO_H__ -#define __XRD_INFO_H__ -/******************************************************************************/ -/* */ -/* X r d I n f o . h h */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdVersion.hh" - -#define XrdFORMAT "2.0.0" - -#define XrdFORMATB 0x00000200 - -#define XrdBANNER "Copr. 2004-2012 Stanford University, xrd version " XrdVSTRING -#endif diff --git a/src/Xrd/XrdJob.hh b/src/Xrd/XrdJob.hh deleted file mode 100644 index 5c68696c649..00000000000 --- a/src/Xrd/XrdJob.hh +++ /dev/null @@ -1,58 +0,0 @@ -#ifndef ___XRD_JOB_H___ -#define ___XRD_JOB_H___ -/******************************************************************************/ -/* */ -/* X r d J o b . h h */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include - -// The XrdJob class is a super-class that is inherited by any class that needs -// to schedule work on behalf of itself. The XrdJob class is optimized for -// queue processing since that's where it spends a lot of time. This class -// should not be depedent on any other class. - -class XrdJob -{ -friend class XrdScheduler; -public: -XrdJob *NextJob; // -> Next job in the queue (zero if last) -const char *Comment; // -> Description of work for debugging (static!) - -virtual void DoIt() = 0; - - XrdJob(const char *desc="") - {Comment = desc; NextJob = 0; SchedTime = 0;} -virtual ~XrdJob() {} - -private: -time_t SchedTime; // -> Time job is to be scheduled -}; -#endif diff --git a/src/Xrd/XrdLink.cc b/src/Xrd/XrdLink.cc deleted file mode 100644 index bd4683338e7..00000000000 --- a/src/Xrd/XrdLink.cc +++ /dev/null @@ -1,1410 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d L i n k . c c */ -/* */ -/* (c) 2011 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include - -#ifdef __linux__ -#include -#if !defined(TCP_CORK) -#undef HAVE_SENDFILE -#endif -#endif - -#ifdef HAVE_SENDFILE - -#ifndef __APPLE__ -#include -#endif - -#endif - -#include "XrdSys/XrdSysAtomics.hh" -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysFD.hh" -#include "XrdSys/XrdSysPlatform.hh" - -#include "Xrd/XrdBuffer.hh" -#include "Xrd/XrdLink.hh" -#include "Xrd/XrdInet.hh" -#include "Xrd/XrdPoll.hh" -#include "Xrd/XrdScheduler.hh" -#include "Xrd/XrdSendQ.hh" - -#define TRACELINK this -#define XRD_TRACE XrdTrace-> -#include "Xrd/XrdTrace.hh" - -/******************************************************************************/ -/* L o c a l C l a s s e s */ -/******************************************************************************/ -/******************************************************************************/ -/* C l a s s x r d _ L i n k S c a n */ -/******************************************************************************/ - -class XrdLinkScan : XrdJob -{ -public: - -void DoIt() {idleScan();} - - XrdLinkScan(XrdSysError *eP, XrdOucTrace *tP, XrdScheduler *sP, - int im, int it, const char *lt="idle link scan") - : XrdJob(lt), XrdLog(eP), XrdTrace(tP), XrdSched(sP), - idleCheck(im), idleTicks(it) {} - ~XrdLinkScan() {} - -private: - -void idleScan(); - -XrdSysError *XrdLog; -XrdOucTrace *XrdTrace; -XrdScheduler *XrdSched; -int idleCheck; -int idleTicks; - -static const char *TraceID; -}; - -/******************************************************************************/ -/* S t a t i c s */ -/******************************************************************************/ - - XrdSysError *XrdLink::XrdLog = 0; - XrdOucTrace *XrdLink::XrdTrace = 0; - XrdScheduler *XrdLink::XrdSched = 0; - XrdInet *XrdLink::XrdNetTCP= 0; - -#if defined(HAVE_SENDFILE) - int XrdLink::sfOK = 1; -#else - int XrdLink::sfOK = 0; -#endif - - XrdLink **XrdLink::LinkTab; - char *XrdLink::LinkBat; - unsigned int XrdLink::LinkAlloc; - int XrdLink::LTLast = -1; - XrdSysMutex XrdLink::LTMutex; - - const char *XrdLink::TraceID = "Link"; - - long long XrdLink::LinkBytesIn = 0; - long long XrdLink::LinkBytesOut = 0; - long long XrdLink::LinkConTime = 0; - long long XrdLink::LinkCountTot = 0; - int XrdLink::LinkCount = 0; - int XrdLink::LinkCountMax = 0; - int XrdLink::LinkTimeOuts = 0; - int XrdLink::LinkStalls = 0; - int XrdLink::LinkSfIntr = 0; - int XrdLink::maxFD = 0; - XrdSysMutex XrdLink::statsMutex; - - const char *XrdLinkScan::TraceID = "LinkScan"; - int XrdLink::devNull = XrdSysFD_Open("/dev/null", O_RDONLY); - short XrdLink::killWait= 3; // Kill then wait - short XrdLink::waitKill= 4; // Wait then kill - -// The following values are defined for LinkBat[]. We assume that FREE is 0 -// -#define XRDLINK_FREE 0x00 -#define XRDLINK_USED 0x01 -#define XRDLINK_IDLE 0x02 - -/******************************************************************************/ -/* C o n s t r u c t o r */ -/******************************************************************************/ - -XrdLink::XrdLink() : XrdJob("connection"), IOSemaphore(0, "link i/o") -{ - Etext = 0; - HostName = 0; - Reset(); -} - -void XrdLink::Reset() -{ - FD = -1; - if (Etext) {free(Etext); Etext = 0;} - if (HostName) {free(HostName); HostName = 0;} - Uname[sizeof(Uname)-1] = '@'; - Uname[sizeof(Uname)-2] = '?'; - Lname[0] = '?'; - Lname[1] = '\0'; - ID = &Uname[sizeof(Uname)-2]; - Comment = ID; -#if !defined( __linux__ ) && !defined( __solaris__ ) - Next = 0; -#endif - sendQ = 0; - Protocol = 0; - ProtoAlt = 0; - conTime = time(0); - stallCnt = stallCntTot = 0; - tardyCnt = tardyCntTot = 0; - SfIntr = 0; - InUse = 1; - Poller = 0; - PollEnt = 0; - isEnabled= 0; - isIdle = 0; - inQ = 0; - isBridged= 0; - BytesOut = BytesIn = BytesOutTot = BytesInTot = 0; - doPost = 0; - LockReads= 0; - KeepFD = 0; - Instance = 0; - KillcvP = 0; - KillCnt = 0; -} - -/******************************************************************************/ -/* A l l o c */ -/******************************************************************************/ - -XrdLink *XrdLink::Alloc(XrdNetAddr &peer, int opts) -{ - static XrdSysMutex instMutex; - static unsigned int myInstance = 1; - XrdLink *lp; - char hName[1024], *unp, buff[16]; - int bl, peerFD = peer.SockFD(); - -// Make sure that the incomming file descriptor can be handled -// - if (peerFD < 0 || peerFD >= maxFD) - {snprintf(hName, sizeof(hName), "%d", peerFD); - XrdLog->Emsg("Link", "attempt to alloc out of range FD -",hName); - return (XrdLink *)0; - } - -// Make sure that the link slot is available -// - LTMutex.Lock(); - if (LinkBat[peerFD]) - {LTMutex.UnLock(); - XrdLog->Emsg("Link", "attempt to reuse active link"); - return (XrdLink *)0; - } - -// Check if we already have a link object in this slot. If not, allocate -// a quantum of link objects and put them in the table. -// - if (!(lp = LinkTab[peerFD])) - {unsigned int i; - XrdLink **blp, *nlp = new XrdLink[LinkAlloc](); - if (!nlp) - {LTMutex.UnLock(); - XrdLog->Emsg("Link", ENOMEM, "create link"); - return (XrdLink *)0; - } - blp = &LinkTab[peerFD/LinkAlloc*LinkAlloc]; - for (i = 0; i < LinkAlloc; i++, blp++) *blp = &nlp[i]; - lp = LinkTab[peerFD]; - } - else lp->Reset(); - LinkBat[peerFD] = XRDLINK_USED; - if (peerFD > LTLast) LTLast = peerFD; - LTMutex.UnLock(); - -// Establish the instance number of this link. This is will prevent us from -// sending asynchronous responses to the wrong client when the file descriptor -// gets reused for connections to the same host. -// - instMutex.Lock(); - lp->Instance = myInstance++; - instMutex.UnLock(); - -// Establish the address and connection name of this link -// - peer.Format(hName, sizeof(hName), XrdNetAddr::fmtAuto, - XrdNetAddr::old6Map4 | XrdNetAddr::noPort); - lp->HostName = strdup(hName); - lp->HNlen = strlen(hName); - XrdNetTCP->Trim(hName); - lp->Addr = peer; - strlcpy(lp->Lname, hName, sizeof(lp->Lname)); - bl = sprintf(buff, "?:%d", peerFD); - unp = lp->Uname + sizeof(Uname) - bl - 1; - memcpy(unp, buff, bl); - lp->ID = unp; - lp->FD = peerFD; - lp->Comment = (const char *)unp; - -// Set options as needed -// - lp->LockReads = (0 != (opts & XRDLINK_RDLOCK)); - lp->KeepFD = (0 != (opts & XRDLINK_NOCLOSE)); - -// Update statistics and return the link. We need to actually get the stats -// mutex even when using atomics because we need to use compound operations. -// The atomics will keep reporters from seeing partial results. -// - statsMutex.Lock(); - AtomicInc(LinkCountTot); // LinkCountTot++ - if (LinkCountMax <= AtomicInc(LinkCount)) LinkCountMax = LinkCount; - statsMutex.UnLock(); - return lp; -} - -/******************************************************************************/ -/* B a c k l o g */ -/******************************************************************************/ - -int XrdLink::Backlog() -{ - XrdSysMutexHelper lck(wrMutex); - -// Return backlog information -// - return (sendQ ? sendQ->Backlog() : 0); -} - -/******************************************************************************/ -/* C l i e n t */ -/******************************************************************************/ - -int XrdLink::Client(char *nbuf, int nbsz) -{ - int ulen; - -// Generate full client name -// - if (nbsz <= 0) return 0; - ulen = (Lname - ID); - if ((ulen + HNlen) >= nbsz) ulen = 0; - else {strncpy(nbuf, ID, ulen); - strcpy(nbuf+ulen, HostName); - ulen += HNlen; - } - return ulen; -} - -/******************************************************************************/ -/* C l o s e */ -/******************************************************************************/ - -int XrdLink::Close(int defer) -{ XrdSysMutexHelper opHelper(opMutex); - int csec, fd, rc = 0; - -// If a defer close is requested, we can close the descriptor but we must -// keep the slot number to prevent a new client getting the same fd number. -// Linux is peculiar in that any in-progress operations will remain in that -// state even after the FD is closed unless there is some activity either on -// the connection or an event occurs that causes an operation restart. We -// portably solve this problem by issuing a shutdown() on the socket prior -// closing it. On most platforms, this informs readers that the connection is -// gone (though not on old (i.e. <= 2.3) versions of Linux, sigh). Also, if -// nonblocking mode is enabled, we need to do this in a separate thread as -// a shutdown may block for a pretty long time is lot of messages are queued. -// We will ask the SendQ object to schedule the shutdown for us before it -// commits suicide. -// Note that we can hold the opMutex while we also get the wrMutex. -// - if (defer) - {if (!sendQ) Shutdown(false); - else {TRACEI(DEBUG, "Shutdown FD only via SendQ"); - InUse++; - FD = -FD; - wrMutex.Lock(); - sendQ->Terminate(this); - sendQ = 0; - wrMutex.UnLock(); - } - return 0; - } - -// If we got here then this is not a defered close so we just need to check -// if there is a sendq appendage we need to get rid of. -// - if (sendQ) - {wrMutex.Lock(); - sendQ->Terminate(); - sendQ = 0; - wrMutex.UnLock(); - } - -// Multiple protocols may be bound to this link. If it is in use, defer the -// actual close until the use count drops to one. -// - while(InUse > 1) - {opHelper.UnLock(); - TRACEI(DEBUG, "Close defered, use count=" <Recycle(this, csec, Etext); Protocol = 0;} - if (ProtoAlt) {ProtoAlt->Recycle(this, csec, Etext); ProtoAlt = 0;} - if (Etext) {free(Etext); Etext = 0;} - InUse = 0; - -// At this point we can have no lock conflicts, so if someone is waiting for -// us to terminate let them know about it. Note that we will get the condvar -// mutex while we hold the opMutex. This is the required order! We will also -// zero out the pointer to the condvar while holding the opmutex. -// - if (KillcvP) {KillcvP-> Lock(); KillcvP->Signal(); - KillcvP->UnLock(); KillcvP = 0; - } - -// Remove ourselves from the poll table and then from the Link table. We may -// not hold on to the opMutex when we acquire the LTMutex. However, the link -// table needs to be cleaned up prior to actually closing the socket. So, we -// do some fancy footwork to prevent multiple closes of this link. -// - fd = (FD < 0 ? -FD : FD); - if (FD != -1) - {if (Poller) {XrdPoll::Detach(this); Poller = 0;} - FD = -1; - opHelper.UnLock(); - LTMutex.Lock(); - LinkBat[fd] = XRDLINK_FREE; - if (fd == LTLast) while(LTLast && !(LinkBat[LTLast])) LTLast--; - LTMutex.UnLock(); - } else opHelper.UnLock(); - -// Close the file descriptor if it isn't being shared. Do it as the last -// thing because closes and accepts and not interlocked. -// - if (fd >= 2) {if (KeepFD) rc = 0; - else rc = (close(fd) < 0 ? errno : 0); - } - if (rc) XrdLog->Emsg("Link", rc, "close", ID); - return rc; -} - -/******************************************************************************/ -/* D o I t */ -/******************************************************************************/ - -void XrdLink::DoIt() -{ - int rc; - -// The Process() return code tells us what to do: -// < 0 -> Stop getting requests, -// -EINPROGRESS leave link disabled but otherwise all is well -// -n Error, disable and close the link -// = 0 -> OK, get next request, if allowed, o/w enable the link -// > 0 -> Slow link, stop getting requests and enable the link -// - if (Protocol) - do {rc = Protocol->Process(this);} while (!rc && XrdSched->canStick()); - else {XrdLog->Emsg("Link", "Dispatch on closed link", ID); - return; - } - -// Either re-enable the link and cycle back waiting for a new request, leave -// disabled, or terminate the connection. -// - if (rc >= 0) {if (Poller && !Poller->Enable(this)) Close();} - else if (rc != -EINPROGRESS) Close(); -} - -/******************************************************************************/ -/* E n a b l e */ -/******************************************************************************/ - -void XrdLink::Enable() -{ - if (Poller) Poller->Enable(this); -} - -/******************************************************************************/ -/* F i n d */ -/******************************************************************************/ - -// Warning: curr must be set to a value of 0 or less on the initial call and -// not touched therafter unless a null pointer is returned. When an -// actual link object pointer is returned, it's refcount is increased. -// The count is automatically decreased on the next call to Find(). -// -XrdLink *XrdLink::Find(int &curr, XrdLinkMatch *who) -{ - XrdLink *lp; - const int MaxSeek = 16; - unsigned int myINS; - int i, seeklim = MaxSeek; - -// Do initialization -// - LTMutex.Lock(); - if (curr >= 0 && LinkTab[curr]) LinkTab[curr]->setRef(-1); - else curr = -1; - -// Find next matching link. Since this may take some time, we periodically -// release the LTMutex lock which drives up overhead but will still allow -// other critical operations to occur. -// - for (i = curr+1; i <= LTLast; i++) - {if ((lp = LinkTab[i]) && LinkBat[i] && lp->HostName) - if (!who - || who->Match(lp->ID,lp->Lname-lp->ID-1,lp->HostName,lp->HNlen)) - {myINS = lp->Instance; - LTMutex.UnLock(); - lp->setRef(1); - curr = i; - if (myINS == lp->Instance) return lp; - LTMutex.Lock(); - } - if (!seeklim--) {LTMutex.UnLock(); seeklim = MaxSeek; LTMutex.Lock();} - } - -// Done scanning the table -// - LTMutex.UnLock(); - curr = -1; - return 0; -} - -/******************************************************************************/ -/* g e t N a m e */ -/******************************************************************************/ - -// Warning: curr must be set to a value of 0 or less on the initial call and -// not touched therafter unless null is returned. Returns the length -// the name in nbuf. -// -int XrdLink::getName(int &curr, char *nbuf, int nbsz, XrdLinkMatch *who) -{ - XrdLink *lp; - const int MaxSeek = 16; - int i, ulen = 0, seeklim = MaxSeek; - -// Find next matching link. Since this may take some time, we periodically -// release the LTMutex lock which drives up overhead but will still allow -// other critical operations to occur. -// - LTMutex.Lock(); - for (i = curr+1; i <= LTLast; i++) - {if ((lp = LinkTab[i]) && LinkBat[i] && lp->HostName) - if (!who - || who->Match(lp->ID,lp->Lname-lp->ID-1,lp->HostName,lp->HNlen)) - {ulen = lp->Client(nbuf, nbsz); - LTMutex.UnLock(); - curr = i; - return ulen; - } - if (!seeklim--) {LTMutex.UnLock(); seeklim = MaxSeek; LTMutex.Lock();} - } - LTMutex.UnLock(); - -// Done scanning the table -// - curr = -1; - return 0; -} - -/******************************************************************************/ -/* P e e k */ -/******************************************************************************/ - -int XrdLink::Peek(char *Buff, int Blen, int timeout) -{ - XrdSysMutexHelper theMutex; - struct pollfd polltab = {FD, POLLIN|POLLRDNORM, 0}; - ssize_t mlen; - int retc; - -// Lock the read mutex if we need to, the helper will unlock it upon exit -// - if (LockReads) theMutex.Lock(&rdMutex); - -// Wait until we can actually read something -// - isIdle = 0; - do {retc = poll(&polltab, 1, timeout);} while(retc < 0 && errno == EINTR); - if (retc != 1) - {if (retc == 0) return 0; - return XrdLog->Emsg("Link", -errno, "poll", ID); - } - -// Verify it is safe to read now -// - if (!(polltab.revents & (POLLIN|POLLRDNORM))) - {XrdLog->Emsg("Link", XrdPoll::Poll2Text(polltab.revents), - "polling", ID); - return -1; - } - -// Do the peek. -// - do {mlen = recv(FD, Buff, Blen, MSG_PEEK);} - while(mlen < 0 && errno == EINTR); - -// Return the result -// - if (mlen >= 0) return int(mlen); - XrdLog->Emsg("Link", errno, "peek on", ID); - return -1; -} - -/******************************************************************************/ -/* R e c v */ -/******************************************************************************/ - -int XrdLink::Recv(char *Buff, int Blen) -{ - ssize_t rlen; - -// Note that we will read only as much as is queued. Use Recv() with a -// timeout to receive as much data as possible. -// - if (LockReads) rdMutex.Lock(); - isIdle = 0; - do {rlen = read(FD, Buff, Blen);} while(rlen < 0 && errno == EINTR); - if (rlen > 0) AtomicAdd(BytesIn, rlen); - if (LockReads) rdMutex.UnLock(); - - if (rlen >= 0) return int(rlen); - if (FD >= 0) XrdLog->Emsg("Link", errno, "receive from", ID); - return -1; -} - -/******************************************************************************/ - -int XrdLink::Recv(char *Buff, int Blen, int timeout) -{ - XrdSysMutexHelper theMutex; - struct pollfd polltab = {FD, POLLIN|POLLRDNORM, 0}; - ssize_t rlen, totlen = 0; - int retc; - -// Lock the read mutex if we need to, the helper will unlock it upon exit -// - if (LockReads) theMutex.Lock(&rdMutex); - -// Wait up to timeout milliseconds for data to arrive -// - isIdle = 0; - while(Blen > 0) - {do {retc = poll(&polltab,1,timeout);} while(retc < 0 && errno == EINTR); - if (retc != 1) - {if (retc == 0) - {tardyCnt++; - if (totlen) - {if ((++stallCnt & 0xff) == 1) TRACEI(DEBUG,"read timed out"); - AtomicAdd(BytesIn, totlen); - } - return int(totlen); - } - return (FD >= 0 ? XrdLog->Emsg("Link", -errno, "poll", ID) : -1); - } - - // Verify it is safe to read now - // - if (!(polltab.revents & (POLLIN|POLLRDNORM))) - {XrdLog->Emsg("Link", XrdPoll::Poll2Text(polltab.revents), - "polling", ID); - return -1; - } - - // Read as much data as you can. Note that we will force an error - // if we get a zero-length read after poll said it was OK. - // - do {rlen = recv(FD, Buff, Blen, 0);} while(rlen < 0 && errno == EINTR); - if (rlen <= 0) - {if (!rlen) return -ENOMSG; - return (FD<0 ? -1 : XrdLog->Emsg("Link",-errno,"receive from",ID)); - } - totlen += rlen; Blen -= rlen; Buff += rlen; - } - - AtomicAdd(BytesIn, totlen); - return int(totlen); -} - - -/******************************************************************************/ -/* R e c v A l l */ -/******************************************************************************/ - -int XrdLink::RecvAll(char *Buff, int Blen, int timeout) -{ - struct pollfd polltab = {FD, POLLIN|POLLRDNORM, 0}; - ssize_t rlen; - int retc; - -// Check if timeout specified. Notice that the timeout is the max we will -// for some data. We will wait forever for all the data. Yeah, it's weird. -// - if (timeout >= 0) - {do {retc = poll(&polltab,1,timeout);} while(retc < 0 && errno == EINTR); - if (retc != 1) - {if (!retc) return -ETIMEDOUT; - XrdLog->Emsg("Link",errno,"poll",ID); - return -1; - } - if (!(polltab.revents & (POLLIN|POLLRDNORM))) - {XrdLog->Emsg("Link",XrdPoll::Poll2Text(polltab.revents),"polling",ID); - return -1; - } - } - -// Note that we will block until we receive all he bytes. -// - if (LockReads) rdMutex.Lock(); - isIdle = 0; - do {rlen = recv(FD,Buff,Blen,MSG_WAITALL);} while(rlen < 0 && errno == EINTR); - if (rlen > 0) AtomicAdd(BytesIn, rlen); - if (LockReads) rdMutex.UnLock(); - - if (int(rlen) == Blen) return Blen; - if (!rlen) {TRACEI(DEBUG, "No RecvAll() data; errno=" < 0) XrdLog->Emsg("RecvAll","Premature end from", ID); - else if (FD >= 0) XrdLog->Emsg("Link",errno,"recieve from",ID); - return -1; -} - -/******************************************************************************/ -/* S e n d */ -/******************************************************************************/ - -int XrdLink::Send(const char *Buff, int Blen) -{ - ssize_t retc = 0, bytesleft = Blen; - -// Get a lock -// - wrMutex.Lock(); - isIdle = 0; - AtomicAdd(BytesOut, Blen); - -// Do non-blocking writes if we are setup to do so. -// - if (sendQ) - {retc = sendQ->Send(Buff, Blen); - wrMutex.UnLock(); - return retc; - } - -// Write the data out -// - while(bytesleft) - {if ((retc = write(FD, Buff, bytesleft)) < 0) - {if (errno == EINTR) continue; - else break; - } - bytesleft -= retc; Buff += retc; - } - -// All done -// - wrMutex.UnLock(); - if (retc >= 0) return Blen; - XrdLog->Emsg("Link", errno, "send to", ID); - return -1; -} - -/******************************************************************************/ - -int XrdLink::Send(const struct iovec *iov, int iocnt, int bytes) -{ - ssize_t bytesleft, n, retc = 0; - const char *Buff; - int i; - -// Add up bytes if they were not given to us -// - if (!bytes) for (i = 0; i < iocnt; i++) bytes += iov[i].iov_len; - -// Get a lock and assume we will be successful (statistically we are) -// - wrMutex.Lock(); - isIdle = 0; - AtomicAdd(BytesOut, bytes); - -// Do non-blocking writes if we are setup to do so. -// - if (sendQ) - {retc = sendQ->Send(iov, iocnt, bytes); - wrMutex.UnLock(); - return retc; - } - -// Write the data out. On some version of Unix (e.g., Linux) a writev() may -// end at any time without writing all the bytes when directed to a socket. -// So, we attempt to resume the writev() using a combination of write() and -// a writev() continuation. This approach slowly converts a writev() to a -// series of writes if need be. We must do this inline because we must hold -// the lock until all the bytes are written or an error occurs. -// - bytesleft = static_cast(bytes); - while(bytesleft) - {do {retc = writev(FD, iov, iocnt);} while(retc < 0 && errno == EINTR); - if (retc >= bytesleft || retc < 0) break; - bytesleft -= retc; - while(retc >= (n = static_cast(iov->iov_len))) - {retc -= n; iov++; iocnt--;} - Buff = (const char *)iov->iov_base + retc; n -= retc; iov++; iocnt--; - while(n) {if ((retc = write(FD, Buff, n)) < 0) - {if (errno == EINTR) continue; - else break; - } - n -= retc; Buff += retc; - } - if (retc < 0 || iocnt < 1) break; - } - -// All done -// - wrMutex.UnLock(); - if (retc >= 0) return bytes; - XrdLog->Emsg("Link", errno, "send to", ID); - return -1; -} - -/******************************************************************************/ -int XrdLink::Send(const sfVec *sfP, int sfN) -{ -#if !defined(HAVE_SENDFILE) || defined(__APPLE__) - return -1; -#else -// Make sure we have valid vector count -// - if (sfN < 1 || sfN > XrdOucSFVec::sfMax) - {XrdLog->Emsg("Link", EINVAL, "send file to", ID); - return -1; - } - -#ifdef __solaris__ - sendfilevec_t vecSF[XrdOucSFVec::sfMax], *vecSFP = vecSF; - size_t xframt, totamt, bytes = 0; - ssize_t retc; - int i = 0; - -// Construct the sendfilev() vector -// - for (i = 0; i < sfN; sfP++, i++) - {if (sfP->fdnum < 0) - {vecSF[i].sfv_fd = SFV_FD_SELF; - vecSF[i].sfv_off = (off_t)sfP->buffer; - } else { - vecSF[i].sfv_fd = sfP->fdnum; - vecSF[i].sfv_off = sfP->offset; - } - vecSF[i].sfv_flag = 0; - vecSF[i].sfv_len = sfP->sendsz; - bytes += sfP->sendsz; - } - totamt = bytes; - -// Lock the link, issue sendfilev(), and unlock the link. The documentation -// is very spotty and inconsistent. We can only retry this operation under -// very limited conditions. -// - wrMutex.Lock(); - isIdle = 0; -do{retc = sendfilev(FD, vecSFP, sfN, &xframt); - -// Check if all went well and return if so (usual case) -// - if (xframt == bytes) - {AtomicAdd(BytesOut, bytes); - wrMutex.UnLock(); - return totamt; - } - -// The only one we will recover from is EINTR. We cannot legally get EAGAIN. -// - if (retc < 0 && errno != EINTR) break; - -// Try to resume the transfer -// - if (xframt > 0) - {AtomicAdd(BytesOut, xframt); bytes -= xframt; SfIntr++; - while(xframt > 0 && sfN) - {if ((ssize_t)xframt < (ssize_t)vecSFP->sfv_len) - {vecSFP->sfv_off += xframt; vecSFP->sfv_len -= xframt; break;} - xframt -= vecSFP->sfv_len; vecSFP++; sfN--; - } - } - } while(sfN > 0); - -// See if we can recover without destroying the connection -// - retc = (retc < 0 ? errno : ECANCELED); - wrMutex.UnLock(); - XrdLog->Emsg("Link", retc, "send file to", ID); - return -1; - -#elif defined(__linux__) - - static const int setON = 1, setOFF = 0; - ssize_t retc = 0, bytesleft; - off_t myOffset; - int i, xfrbytes = 0, uncork = 1, xIntr = 0; - -// lock the link -// - wrMutex.Lock(); - isIdle = 0; - -// In linux we need to cork the socket. On permanent errors we do not uncork -// the socket because it will be closed in short order. -// - if (setsockopt(FD, SOL_TCP, TCP_CORK, &setON, sizeof(setON)) < 0) - {XrdLog->Emsg("Link", errno, "cork socket for", ID); - uncork = 0; sfOK = 0; - } - -// Send the header first -// - for (i = 0; i < sfN; sfP++, i++) - {if (sfP->fdnum < 0) retc = sendData(sfP->buffer, sfP->sendsz); - else {myOffset = sfP->offset; bytesleft = sfP->sendsz; - while(bytesleft - && (retc=sendfile(FD,sfP->fdnum,&myOffset,bytesleft)) > 0) - {bytesleft -= retc; xIntr++;} - } - if (retc < 0 && errno == EINTR) continue; - if (retc <= 0) break; - xfrbytes += sfP->sendsz; - } - -// Diagnose any sendfile errors -// - if (retc <= 0) - {if (retc == 0) errno = ECANCELED; - wrMutex.UnLock(); - XrdLog->Emsg("Link", errno, "send file to", ID); - return -1; - } - -// Now uncork the socket -// - if (uncork && setsockopt(FD, SOL_TCP, TCP_CORK, &setOFF, sizeof(setOFF)) < 0) - XrdLog->Emsg("Link", errno, "uncork socket for", ID); - -// All done -// - if (xIntr > sfN) SfIntr += (xIntr - sfN); - AtomicAdd(BytesOut, xfrbytes); - wrMutex.UnLock(); - return xfrbytes; -#endif -#endif -} - -/******************************************************************************/ -/* private s e n d D a t a */ -/******************************************************************************/ - -int XrdLink::sendData(const char *Buff, int Blen) -{ - ssize_t retc = 0, bytesleft = Blen; - -// Write the data out -// - while(bytesleft) - {if ((retc = write(FD, Buff, bytesleft)) < 0) - {if (errno == EINTR) continue; - else break; - } - bytesleft -= retc; Buff += retc; - } - -// All done -// - return retc; -} - -/******************************************************************************/ -/* s e t E t e x t */ -/******************************************************************************/ - -int XrdLink::setEtext(const char *text) -{ - opMutex.Lock(); - if (Etext) free(Etext); - Etext = (text ? strdup(text) : 0); - opMutex.UnLock(); - return -1; -} - -/******************************************************************************/ -/* s e t I D */ -/******************************************************************************/ - -void XrdLink::setID(const char *userid, int procid) -{ - char buff[sizeof(Uname)], *bp, *sp; - int ulen; - - snprintf(buff, sizeof(buff), "%s.%d:%d", userid, procid, FD); - ulen = strlen(buff); - sp = buff + ulen - 1; - bp = &Uname[sizeof(Uname)-1]; - if (ulen > (int)sizeof(Uname)) ulen = sizeof(Uname); - *bp = '@'; bp--; - while(ulen--) {*bp = *sp; bp--; sp--;} - ID = bp+1; - Comment = (const char *)ID; -} - -/******************************************************************************/ -/* s e t N B */ -/******************************************************************************/ - -bool XrdLink::setNB() -{ -// We don't support non-blocking output except for Linux at the moment -// -#if !defined(__linux__) - return false; -#else -// Trace this request -// - TRACEI(DEBUG,"enabling non-blocking output"); - -// If we don't already have a sendQ object get one. This is a one-time call -// so to optimize checking if this object exists we also get the opMutex.' -// - opMutex.Lock(); - if (!sendQ) - {wrMutex.Lock(); - sendQ = new XrdSendQ(*this, wrMutex); - wrMutex.UnLock(); - } - opMutex.UnLock(); - return true; -#endif -} - -/******************************************************************************/ -/* S e t u p */ -/******************************************************************************/ - -int XrdLink::Setup(int maxfds, int idlewait) -{ - int numalloc, iticks, ichk; - -// Compute the number of link objects we should allocate at a time. Generally, -// we like to allocate 8k of them at a time but always as a power of two. -// - maxFD = maxfds; - numalloc = 8192 / sizeof(XrdLink); - LinkAlloc = 1; - while((numalloc = numalloc/2)) LinkAlloc = LinkAlloc*2; - TRACE(DEBUG, "Allocating " <Emsg("Link", ENOMEM, "create LinkTab"); return 0;} - memset((void *)LinkTab, 0, maxfds*sizeof(XrdLink *)); - -// Create the slot status table -// - if (!(LinkBat = (char *)malloc(maxfds*sizeof(char)+LinkAlloc))) - {XrdLog->Emsg("Link", ENOMEM, "create LinkBat"); return 0;} - memset((void *)LinkBat, XRDLINK_FREE, maxfds*sizeof(char)); - -// Create an idle connection scan job -// - if (idlewait) - {if (!(ichk = idlewait/3)) {iticks = 1; ichk = idlewait;} - else iticks = 3; - XrdLinkScan *ls = new XrdLinkScan(XrdLog,XrdTrace,XrdSched,ichk,iticks); - XrdSched->Schedule((XrdJob *)ls, ichk+time(0)); - } - -// Initialize the send queue -// - XrdSendQ::Init(XrdLog, XrdSched); - -// All done -// - return 1; -} - -/******************************************************************************/ -/* S e r i a l i z e */ -/******************************************************************************/ - -void XrdLink::Serialize() -{ - -// This is meant to make sure that no protocol objects are refering to this -// link so that we can safely run in psuedo single thread mode for critical -// functions. -// - opMutex.Lock(); - if (InUse <= 1) opMutex.UnLock(); - else {doPost++; - opMutex.UnLock(); - TRACEI(DEBUG, "Waiting for link serialization; use=" < 0) waitKill = static_cast(wkSec); - if (kwSec > 0) killWait = static_cast(kwSec); -} - -/******************************************************************************/ -/* s e t P r o t o c o l */ -/******************************************************************************/ - -XrdProtocol *XrdLink::setProtocol(XrdProtocol *pp) -{ - -// Set new protocol. -// - opMutex.Lock(); - XrdProtocol *op = Protocol; - Protocol = pp; - opMutex.UnLock(); - return op; -} - -/******************************************************************************/ -/* s e t R e f */ -/******************************************************************************/ - -void XrdLink::setRef(int use) -{ - opMutex.Lock(); - TRACEI(DEBUG,"Setting ref to " <Emsg("Link", "Negative use count for", ID); - } - else opMutex.UnLock(); -} - -/******************************************************************************/ -/* S h u t d o w n */ -/******************************************************************************/ - -void XrdLink::Shutdown(bool getLock) -{ - int temp, theFD; - -// Trace the entry -// - TRACEI(DEBUG, (getLock ? "Async" : "Sync") <<" link shutdown in progress"); - -// Get the lock if we need too (external entry via another thread) -// - if (getLock) opMutex.Lock(); - -// If there is something to do, do it now -// - temp = Instance; Instance = 0; - if (!KeepFD) - {theFD = (FD < 0 ? -FD : FD); - shutdown(theFD, SHUT_RDWR); - if (dup2(devNull, theFD) < 0) - {Instance = temp; - XrdLog->Emsg("Link", errno, "shutdown FD for", ID); - } - } - -// All done -// - if (getLock) opMutex.UnLock(); -} - -/******************************************************************************/ -/* S t a t s */ -/******************************************************************************/ - -int XrdLink::Stats(char *buff, int blen, int do_sync) -{ - static const char statfmt[] = "%d" - "%d%lld%lld%lld" - "%lld%d%d" - "%d"; - int i, myLTLast; - -// Check if actual length wanted -// - if (!buff) return sizeof(statfmt)+17*6; - -// We must synchronize the statistical counters -// - if (do_sync) - {LTMutex.Lock(); myLTLast = LTLast; LTMutex.UnLock(); - for (i = 0; i <= myLTLast; i++) - if (LinkBat[i] == XRDLINK_USED && LinkTab[i]) - LinkTab[i]->syncStats(); - } - -// Obtain lock on the stats area and format it -// - AtomicBeg(statsMutex); - i = snprintf(buff, blen, statfmt, AtomicGet(LinkCount), - AtomicGet(LinkCountMax), - AtomicGet(LinkCountTot), - AtomicGet(LinkBytesIn), - AtomicGet(LinkBytesOut), - AtomicGet(LinkConTime), - AtomicGet(LinkTimeOuts), - AtomicGet(LinkStalls), - AtomicGet(LinkSfIntr)); - AtomicEnd(statsMutex); - return i; -} - -/******************************************************************************/ -/* s y n c S t a t s */ -/******************************************************************************/ - -void XrdLink::syncStats(int *ctime) -{ - long long tmpLL; - int tmpI4; - -// If this is dynamic, get the opMutex lock -// - if (!ctime) opMutex.Lock(); - -// Either the caller has the opMutex or this is called out of close. In either -// case, we need to get the read and write mutexes; each followed by the stats -// mutex. This order is important because we should not hold the stats mutex -// for very long and the r/w mutexes may take a long time to acquire. If we -// must maintain the link count we need to actually acquire the stats mutex as -// we will be doing compound operations. Atomics are still used to keep other -// threads from seeing partial results. -// - AtomicBeg(rdMutex); - - if (ctime) - {*ctime = time(0) - conTime; - AtomicAdd(LinkConTime, *ctime); - statsMutex.Lock(); - if (LinkCount > 0) AtomicDec(LinkCount); - statsMutex.UnLock(); - } - - AtomicBeg(statsMutex); - - tmpLL = AtomicFAZ(BytesIn); - AtomicAdd(LinkBytesIn, tmpLL); AtomicAdd(BytesInTot, tmpLL); - tmpI4 = AtomicFAZ(tardyCnt); - AtomicAdd(LinkTimeOuts, tmpI4); AtomicAdd(tardyCntTot, tmpI4); - tmpI4 = AtomicFAZ(stallCnt); - AtomicAdd(LinkStalls, tmpI4); AtomicAdd(stallCntTot, tmpI4); - AtomicEnd(statsMutex); AtomicEnd(rdMutex); - - AtomicBeg(wrMutex); AtomicBeg(statsMutex); - tmpLL = AtomicFAZ(BytesOut); - AtomicAdd(LinkBytesOut, tmpLL); AtomicAdd(BytesOutTot, tmpLL); - tmpI4 = AtomicFAZ(SfIntr); - AtomicAdd(LinkSfIntr, tmpI4); - AtomicEnd(statsMutex); AtomicEnd(wrMutex); - -// Make sure the protocol updates it's statistics as well -// - if (Protocol) Protocol->Stats(0, 0, 1); - -// Clear our local counters -// - if (!ctime) opMutex.UnLock(); -} - -/******************************************************************************/ -/* T e r m i n a t e */ -/******************************************************************************/ - -int XrdLink::Terminate(const XrdLink *owner, int fdnum, unsigned int inst) -{ - XrdSysCondVar killDone(0); - XrdLink *lp; - char buff[1024], *cp; - int wTime, didKW = KillCnt & KillXwt; - -// Find the correspodning link -// - KillCnt = KillCnt & KillMsk; - if (!(lp = fd2link(fdnum, inst))) return (didKW ? -EPIPE : -ESRCH); - -// If this is self termination, then indicate that to the caller -// - if (lp == owner) return 0; - -// Serialize the target link -// - lp->Serialize(); - lp->opMutex.Lock(); - -// If this link is now dead, simply ignore the request. Typically, this -// indicates a race condition that the server won. -// - if ( lp->FD != fdnum || lp->Instance != inst - || !(lp->Poller) || !(lp->Protocol)) - {lp->opMutex.UnLock(); - return -EPIPE; - } - -// Verify that the owner of this link is making the request -// - if (owner - && (!(cp = index(owner->ID, ':')) - || strncmp(lp->ID, owner->ID, cp-(owner->ID)) - || strcmp(owner->Lname, lp->Lname))) - {lp->opMutex.UnLock(); - return -EACCES; - } - -// Check if we have too many tries here -// - if (lp->KillCnt > KillMax) - {lp->opMutex.UnLock(); - return -ETIME; - } - wTime = lp->KillCnt++; - -// Make sure we can disable this link. Of not, then force the caller to wait -// a tad more than the read timeout interval. -// - if (!(lp->isEnabled) || lp->InUse > 1 || lp->KillcvP) - {wTime = wTime*2+waitKill; - KillCnt |= KillXwt; - lp->opMutex.UnLock(); - return (wTime > 60 ? 60: wTime); - } - -// Set the pointer to our condvar. We are holding the opMutex to prevent a race. -// - lp->KillcvP = &killDone; - killDone.Lock(); - -// We can now disable the link and schedule a close -// - snprintf(buff, sizeof(buff), "ended by %s", ID); - buff[sizeof(buff)-1] = '\0'; - lp->Poller->Disable(lp, buff); - lp->opMutex.UnLock(); - -// Now wait for the link to shutdown. This avoids lock problems. -// - if (killDone.Wait(int(killWait))) {wTime += killWait; KillCnt |= KillXwt;} - else wTime = -EPIPE; - killDone.UnLock(); - -// Reobtain the opmutex so that we can zero out the pointer the condvar pntr -// This is really stupid code but because we don't have a way of associating -// an arbitrary mutex with a condvar. But since this code is rarely executed -// the ugliness is sort of tolerable. -// - lp->opMutex.Lock(); lp->KillcvP = 0; lp->opMutex.UnLock(); - -// Do some tracing -// - TRACEI(DEBUG,"Terminate " << (wTime <= 0 ? "complete ":"timeout ") <opMutex.Lock(); - if (lp->isIdle) tmo++; - lp->isIdle++; - if ((int(lp->isIdle)) < idleTicks) {lp->opMutex.UnLock(); continue;} - lp->isIdle = 0; - if (!(lp->Poller) || !(lp->isEnabled)) - XrdLog->Emsg("LinkScan","Link",lp->ID,"is disabled and idle."); - else if (lp->InUse == 1) - {lp->Poller->Disable(lp, "idle timeout"); - tmod++; - } - lp->opMutex.UnLock(); - } - -// Trace what we did -// - TRACE(CONN, lnum <<" links; " <Schedule((XrdJob *)this, idleCheck+time(0)); -} diff --git a/src/Xrd/XrdLink.hh b/src/Xrd/XrdLink.hh deleted file mode 100644 index 08c3e4af612..00000000000 --- a/src/Xrd/XrdLink.hh +++ /dev/null @@ -1,337 +0,0 @@ -#ifndef __XRD_LINK_H__ -#define __XRD_LINK_H__ -/******************************************************************************/ -/* */ -/* X r d L i n k . h h */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include - -#include "XrdNet/XrdNetAddr.hh" -#include "XrdOuc/XrdOucSFVec.hh" -#include "XrdSys/XrdSysPthread.hh" - -#include "Xrd/XrdJob.hh" -#include "Xrd/XrdLinkMatch.hh" -#include "Xrd/XrdProtocol.hh" - -/******************************************************************************/ -/* X r d L i n k O p t i o n s */ -/******************************************************************************/ - -#define XRDLINK_RDLOCK 0x0001 -#define XRDLINK_NOCLOSE 0x0002 - -/******************************************************************************/ -/* C l a s s D e f i n i t i o n */ -/******************************************************************************/ - -class XrdInet; -class XrdNetAddr; -class XrdPoll; -class XrdOucTrace; -class XrdScheduler; -class XrdSendQ; -class XrdSysError; - -class XrdLink : XrdJob -{ -public: -friend class XrdLinkScan; -friend class XrdPoll; -friend class XrdPollPoll; -friend class XrdPollDev; -friend class XrdPollE; - -//----------------------------------------------------------------------------- -//! Obtain the address information for this link. -//! -//! @return Pointer to the XrdAddrInfo object. The pointer is valid while the -//! end-point is connected. -//----------------------------------------------------------------------------- -inline -XrdNetAddrInfo *AddrInfo() {return (XrdNetAddrInfo *)&Addr;} - -//----------------------------------------------------------------------------- -//! Allocate a new link object. -//! -//! @param peer The connection information for the endpoint. -//! @param opts Processing options: -//! XRDLINK_NOCLOSE - do not close the FD upon recycling. -//! XRDLINK_RDLOCK - obtain a lock prior to reading data. -//! -//! @return !0 The pointer to the new object. -//! =0 A new link object could not be allocated. -//----------------------------------------------------------------------------- - -static XrdLink *Alloc(XrdNetAddr &peer, int opts=0); - -int Backlog(); - -void Bind() {} // Obsolete -void Bind(pthread_t tid) { (void)tid; } // Obsolete - -int Client(char *buff, int blen); - -int Close(int defer=0); - -void DoIt(); - -void Enable(); - -int FDnum() {int fd = FD; return (fd < 0 ? -fd : fd);} - -static XrdLink *fd2link(int fd) - {if (fd < 0) fd = -fd; - return (fd <= LTLast && LinkBat[fd] ? LinkTab[fd] : 0); - } - -static XrdLink *fd2link(int fd, unsigned int inst) - {if (fd < 0) fd = -fd; - if (fd <= LTLast && LinkBat[fd] && LinkTab[fd] - && LinkTab[fd]->Instance == inst) return LinkTab[fd]; - return (XrdLink *)0; - } - -static XrdLink *Find(int &curr, XrdLinkMatch *who=0); - - int getIOStats(long long &inbytes, long long &outbytes, - int &numstall, int &numtardy) - { inbytes = BytesIn + BytesInTot; - outbytes = BytesOut+BytesOutTot; - numstall = stallCnt + stallCntTot; - numtardy = tardyCnt + tardyCntTot; - return InUse; - } - -static int getName(int &curr, char *bname, int blen, XrdLinkMatch *who=0); - -XrdProtocol *getProtocol() {return Protocol;} // opmutex must be locked - -void Hold(int lk) {(lk ? opMutex.Lock() : opMutex.UnLock());} - -//----------------------------------------------------------------------------- -//! Get the fully qualified name of the endpoint. -//! -//! @return Pointer to fully qualified host name. The contents are valid -//! while the endpoint is connected. -//----------------------------------------------------------------------------- - -const char *Host() {return (const char *)HostName;} - -char *ID; // This is referenced a lot - -static void Init(XrdSysError *eP, XrdOucTrace *tP, XrdScheduler *sP) - {XrdLog = eP; XrdTrace = tP; XrdSched = sP;} - -static void Init(XrdInet *iP) {XrdNetTCP = iP;} - -//----------------------------------------------------------------------------- -//! Obtain the link's instance number. -//! -//! @return The link's instance number. -//----------------------------------------------------------------------------- -inline -unsigned int Inst() {return Instance;} - -//----------------------------------------------------------------------------- -//! Indicate whether or not the link has an outstanding error. -//! -//! @return True the link has an outstanding error. -//! the link has no outstanding error. -//----------------------------------------------------------------------------- -inline -bool isFlawed() {return Etext != 0;} - -//----------------------------------------------------------------------------- -//! Indicate whether or not this link is of a particular instance. -//! only be used for display and not for security purposes. -//! -//! @param inst the expected instance number. -//! -//! @return True the link matches the instance number. -//! the link differs the instance number. -//----------------------------------------------------------------------------- -inline -bool isInstance(unsigned int inst) - {return FD >= 0 && Instance == inst;} - -//----------------------------------------------------------------------------- -//! Obtain the domain trimmed name of the end-point. The returned value should -//! only be used for display and not for security purposes. -//! -//! @return Pointer to the name that remains valid during the link's lifetime. -//----------------------------------------------------------------------------- -inline -const char *Name() {return (const char *)Lname;} - -//----------------------------------------------------------------------------- -//! Obtain the network address object for this link. The returned value is -//! valid as long as the end-point is connected. Otherwise, it may change. -//! -//! @return Pointer to the object and remains valid during the link's lifetime. -//----------------------------------------------------------------------------- -inline const -XrdNetAddr *NetAddr() {return &Addr;} - -int Peek(char *buff, int blen, int timeout=-1); - -int Recv(char *buff, int blen); -int Recv(char *buff, int blen, int timeout); - -int RecvAll(char *buff, int blen, int timeout=-1); - -int Send(const char *buff, int blen); -int Send(const struct iovec *iov, int iocnt, int bytes=0); - -static int sfOK; // True if Send(sfVec) enabled - -typedef XrdOucSFVec sfVec; - -int Send(const sfVec *sdP, int sdn); // Iff sfOK > 0 - -void Serialize(); // ASYNC Mode - -int setEtext(const char *text); - -void setID(const char *userid, int procid); - -static void setKWT(int wkSec, int kwSec); - -void setLocation(XrdNetAddrInfo::LocInfo &loc) {Addr.SetLocation(loc);} - -bool setNB(); - -XrdProtocol *setProtocol(XrdProtocol *pp); - -void setRef(int cnt); // ASYNC Mode - -static int Setup(int maxfd, int idlewait); - - void Shutdown(bool getLock); - -static int Stats(char *buff, int blen, int do_sync=0); - - void syncStats(int *ctime=0); - - int Terminate(const XrdLink *owner, int fdnum, unsigned int inst); - -time_t timeCon() {return conTime;} - -int UseCnt() {return InUse;} - -void armBridge() {isBridged = 1;} -int hasBridge() {return isBridged;} - - XrdLink(); - ~XrdLink() {} // Is never deleted! - -private: - -void Reset(); -int sendData(const char *Buff, int Blen); - -static XrdSysError *XrdLog; -static XrdOucTrace *XrdTrace; -static XrdScheduler *XrdSched; -static XrdInet *XrdNetTCP; - -static XrdSysMutex LTMutex; // For the LinkTab only LTMutex->IOMutex allowed -static XrdLink **LinkTab; -static char *LinkBat; -static unsigned int LinkAlloc; -static int LTLast; -static const char *TraceID; -static int devNull; -static short killWait; -static short waitKill; - -// Statistical area (global and local) -// -static long long LinkBytesIn; -static long long LinkBytesOut; -static long long LinkConTime; -static long long LinkCountTot; -static int LinkCount; -static int LinkCountMax; -static int LinkTimeOuts; -static int LinkStalls; -static int LinkSfIntr; -static int maxFD; - long long BytesIn; - long long BytesInTot; - long long BytesOut; - long long BytesOutTot; - int stallCnt; - int stallCntTot; - int tardyCnt; - int tardyCntTot; - int SfIntr; -static XrdSysMutex statsMutex; - -// Identification section -// -XrdNetAddr Addr; -char Uname[24]; // Uname and Lname must be adjacent! -char Lname[232]; -char *HostName; -int HNlen; -#if defined( __linux__ ) || defined( __solaris__ ) -pthread_t TID; // Hack to keep abi compatability -#else -XrdLink *Next; // Only used by PollPoll.icc -#endif -XrdSysMutex opMutex; -XrdSysMutex rdMutex; -XrdSysMutex wrMutex; -XrdSysSemaphore IOSemaphore; -XrdSysCondVar *KillcvP; // Protected by opMutex! -XrdSendQ *sendQ; // Protected by wrMutex && opMutex -XrdProtocol *Protocol; -XrdProtocol *ProtoAlt; -XrdPoll *Poller; -struct pollfd *PollEnt; -char *Etext; -int FD; -unsigned int Instance; -time_t conTime; -int InUse; -int doPost; -char LockReads; -char KeepFD; -char isEnabled; -char isIdle; -char inQ; // Only used by PollPoll.icc -char isBridged; -char KillCnt; // Protected by opMutex! -static const char KillMax = 60; -static const char KillMsk = 0x7f; -static const char KillXwt = 0x80; -}; -#endif diff --git a/src/Xrd/XrdLinkMatch.cc b/src/Xrd/XrdLinkMatch.cc deleted file mode 100644 index 4ed9110c369..00000000000 --- a/src/Xrd/XrdLinkMatch.cc +++ /dev/null @@ -1,124 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d L i n k M a t c h . c c */ -/* */ -/* (c) 2005 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -#include "Xrd/XrdLinkMatch.hh" -#include "XrdSys/XrdSysPlatform.hh" - -/******************************************************************************/ -/* M a t c h */ -/******************************************************************************/ - -int XrdLinkMatch::Match(const char *uname, int unlen, - const char *hname, int hnlen) -{ - -// Check if we should try to match the username -// - if (Unamelen && (Unamelen > unlen+1 || strncmp(uname,Uname,Unamelen))) return 0; - -// Check if we should match the full host name -// - if (HnameL && !HnamelenL) return !strcmp(HnameL, hname); - -// Check if prefix suffix matching might succeed -// - if (HnamelenL > hnlen) return 0; - -// Check if we should match the host name prefix -// - if (HnameL && strncmp(HnameL, hname, HnamelenL)) return 0; - -// Check if we should match the host name suffix -// - if (!HnameR) return 1; - return !strcmp(hname+hnlen-HnamelenR, hname); -} - -/******************************************************************************/ -/* S e t */ -/******************************************************************************/ - -void XrdLinkMatch::Set(const char *target) -{ - char *theast; - -// Free any existing target -// - if (!target || !strcmp(target, "*")) - {Uname = HnameL = HnameR = 0; - Unamelen = HnamelenL = HnamelenR = 0; - return; - } - strlcpy(Buff, target, sizeof(Buff)-1); - Uname = Buff; - -// Find the '@' as the pivot in this name -// - if (!(HnameL = index(Uname, '@'))) - {if ((Unamelen = strlen(Uname))) - {if (Uname[Unamelen-1] == '*') Unamelen--; - else if (index(Uname, ':')) Uname[Unamelen++] = '@'; - else if (index(Uname, '.')) Uname[Unamelen++] = ':'; - else Uname[Unamelen++] = '.'; - } - HnameR = 0; - return; - } - -// We have a form of @ -// - *HnameL++ = '\0'; - if ((Unamelen = strlen(Uname))) - {if (Uname[Unamelen-1] == '*') Unamelen--; - else if (index(Uname, ':')) Uname[Unamelen++] = '@'; - else if (index(Uname, '.')) Uname[Unamelen++] = ':'; - else Uname[Unamelen++] = '.'; - } - -// The post string may have an asterisk. -// - if (!(theast = index(HnameL, '*'))) - {HnamelenL = 0; - HnameR = 0; - return; - } - -// Indicate how much of the prefix should match -// - *theast = '\0'; - if (!(HnamelenL = strlen(HnameL))) HnameL = 0; - -// Indicate how much of the suffix should match -// - if ((HnamelenR = strlen(theast))) HnameR = theast+1; - else HnameR = 0; - Hnamelen = HnamelenL+HnamelenR; -} diff --git a/src/Xrd/XrdLinkMatch.hh b/src/Xrd/XrdLinkMatch.hh deleted file mode 100644 index cfa0a3ac81c..00000000000 --- a/src/Xrd/XrdLinkMatch.hh +++ /dev/null @@ -1,69 +0,0 @@ -#ifndef __LINK_MATCH__ -#define __LINK_MATCH__ -/******************************************************************************/ -/* */ -/* X r d L i n k M a t c h . h h */ -/* */ -/* (c) 2005 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -class XrdLinkMatch -{ -public: - - -int Match(const char *uname, int unlen, - const char *hname, int hnlen); -inline int Match(const char *uname, int unlen, - const char *hname) - {return Match(uname, unlen, hname, strlen(hname));} - -// Target: [][*][@[][*][]] -// - void Set(const char *target); - - XrdLinkMatch(const char *target=0) - {Uname = HnameL = HnameR = 0; - Unamelen = Hnamelen = 0; - if (target) Set(target); - } - - ~XrdLinkMatch() {} - -private: - -char Buff[256]; -int Unamelen; -char *Uname; -int HnamelenL; -char *HnameL; -int HnamelenR; -char *HnameR; -int Hnamelen; -}; -#endif diff --git a/src/Xrd/XrdMain.cc b/src/Xrd/XrdMain.cc deleted file mode 100644 index 8b0c3520be7..00000000000 --- a/src/Xrd/XrdMain.cc +++ /dev/null @@ -1,216 +0,0 @@ -/**************************************************************************************/ -/* */ -/* X r d M a i n . c c */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -/* This is the XRootd server. The syntax is: - - xrootd [options] - - options: [-b] [-c ] [-d] [-h] [-l ] [-p ] [] - -Where: - -b forces background execution. - - -c specifies the configuration file. This may also come from the - XrdCONFIGFN environmental variable. - - -d Turns on debugging mode (equivalent to xrd.trace all) - - -h Displays usage line and exits. - - -l Specifies location of the log file. This may also come from the - XrdOucLOGFILE environmental variable or from the oofs layer. By - By default, error messages go to standard error. - - -p Is the port to use either as a service name or an actual port number. - The default port is 1094. - - Are other protocol specific options. - -*/ - -/******************************************************************************/ -/* i n c l u d e f i l e s */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "Xrd/XrdConfig.hh" -#include "Xrd/XrdInet.hh" -#include "Xrd/XrdLink.hh" -#include "Xrd/XrdProtLoad.hh" -#include "Xrd/XrdScheduler.hh" - -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdSys/XrdSysPthread.hh" -#include "XrdSys/XrdSysUtils.hh" - -/******************************************************************************/ -/* L o c a l C l a s s e s */ -/******************************************************************************/ - -class XrdMain : XrdJob -{ -public: - -XrdSysSemaphore *theSem; -XrdProtocol *theProt; -XrdInet *theNet; -int thePort; -static XrdConfig Config; - -void DoIt() {XrdLink *newlink; - if ((newlink = theNet->Accept(0, -1, theSem))) - {newlink->setProtocol(theProt); - newlink->DoIt(); - } - } - - XrdMain() : XrdJob("main accept"), theSem(0), theProt(0), - theNet(0), thePort(0) {} - XrdMain(XrdInet *nP) : XrdJob("main accept"), theSem(0), - theProt(0), theNet(nP), thePort(nP->Port()) {} - ~XrdMain() {} -}; - -XrdConfig XrdMain::Config; - -/******************************************************************************/ -/* E x t e r n a l T h r e a d I n t e r f a c e s */ -/******************************************************************************/ - -void *mainAccept(void *parg) -{ XrdMain *Parms = (XrdMain *)parg; - XrdScheduler *mySched = Parms->Config.ProtInfo.Sched; - XrdProtLoad ProtSelect(Parms->thePort); - XrdSysSemaphore accepted(0); - -// Complete the parms -// - Parms->theSem = &accepted; - Parms->theProt = (XrdProtocol *)&ProtSelect; - -// Simply schedule new accepts -// - while(1) {mySched->Schedule((XrdJob *)Parms); - accepted.Wait(); - } - - return (void *)0; -} - -/******************************************************************************/ -/* m a i n A d m i n */ -/******************************************************************************/ - -void *mainAdmin(void *parg) -{ XrdMain *Parms = (XrdMain *)parg; - XrdInet *NetADM = Parms->theNet; - XrdLink *newlink; -// static XrdProtocol_Admin ProtAdmin; - int ProtAdmin; - -// At this point we should be able to accept new connections. Noe that we don't -// support admin connections as of yet so the following code is superflous. -// - while(1) if ((newlink = NetADM->Accept())) - {newlink->setProtocol((XrdProtocol *)&ProtAdmin); - Parms->Config.ProtInfo.Sched->Schedule((XrdJob *)newlink); - } - return (void *)0; -} - -/******************************************************************************/ -/* m a i n */ -/******************************************************************************/ - -int main(int argc, char *argv[]) -{ - XrdMain Main; - pthread_t tid; - char buff[128]; - int i, retc; - -// Turn off sigpipe and host a variety of others before we start any threads -// - XrdSysUtils::SigBlock(); - -// Set the default stack size here -// - if (sizeof(long) > 4) XrdSysThread::setStackSize((size_t)1048576); - else XrdSysThread::setStackSize((size_t)786432); - -// Process configuration file -// - if (Main.Config.Configure(argc, argv)) _exit(1); - -// Start the admin thread if an admin network is defined -// - if (Main.Config.NetADM && (retc = XrdSysThread::Run(&tid, mainAdmin, - (void *)new XrdMain(Main.Config.NetADM), - XRDSYSTHREAD_BIND, "Admin handler"))) - {Main.Config.ProtInfo.eDest->Emsg("main", retc, "create admin thread"); - _exit(3); - } - -// At this point we should be able to accept new connections. Spawn a -// thread for each network except the first. The main thread will handle -// that network as some implementations require a main active thread. -// - for (i = 1; i <= XrdProtLoad::ProtoMax; i++) - if (Main.Config.NetTCP[i]) - {XrdMain *Parms = new XrdMain(Main.Config.NetTCP[i]); - sprintf(buff, "Port %d handler", Parms->thePort); - if (Parms->theNet == Main.Config.NetTCP[XrdProtLoad::ProtoMax]) - Parms->thePort = -(Parms->thePort); - if ((retc = XrdSysThread::Run(&tid, mainAccept, (void *)Parms, - XRDSYSTHREAD_BIND, strdup(buff)))) - {Main.Config.ProtInfo.eDest->Emsg("main", retc, "create", buff); - _exit(3); - } - } - -// Finally, start accepting connections on the main port -// - Main.theNet = Main.Config.NetTCP[0]; - Main.thePort = Main.Config.NetTCP[0]->Port(); - mainAccept((void *)&Main); - -// We should never get here -// - pthread_exit(0); -} diff --git a/src/Xrd/XrdObject.hh b/src/Xrd/XrdObject.hh deleted file mode 100644 index a625d2689a9..00000000000 --- a/src/Xrd/XrdObject.hh +++ /dev/null @@ -1,142 +0,0 @@ -#ifndef __XRD_OBJECT_H__ -#define __XRD_OBJECT_H__ -/******************************************************************************/ -/* */ -/* X r d O b j e c t . h h */ -/* */ -/*(c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/*Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Deprtment of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include - -#include "Xrd/XrdJob.hh" - -// The classes here are templates for singly linked list handling that allows -// elements to be added to either end but be removed only from the front. Most -// objects in this package are managed in queues of this type. - -/******************************************************************************/ -/* x r d _ O b j e c t */ -/******************************************************************************/ - -template -class XrdObjectQ; - -template -class XrdObject -{ -public: -friend class XrdObjectQ; - - -// Item() supplies the item value associated with itself (used with Next()). -// -T *objectItem() {return Item;} - -// Next() supplies the next list node. -// -XrdObject *nextObject() {return Next;} - -// Set the item pointer -// -void setItem(T *ival) {Item = ival;} - - XrdObject(T *ival=0) {Next = 0; Item = ival; QTime = 0;} - ~XrdObject() {} - -private: -XrdObject *Next; -T *Item; -time_t QTime; // Only used for time-managed objects -}; - -/******************************************************************************/ -/* x r d _ O b j e c t Q */ -/******************************************************************************/ - -// Note to properly cleanup this type of queue you must call Set() at least -// once to cause the time element to be sceduled. - -class XrdOucTrace; -class XrdScheduler; - -template -class XrdObjectQ : public XrdJob -{ -public: - -inline T *Pop() {XrdObject *Node; - QMutex.Lock(); - if ((Node = First)) {First = First->Next; Count--;} - QMutex.UnLock(); - if (Node) return Node->Item; - return (T *)0; - } - -inline void Push(XrdObject *Node) - {Node->QTime = Curage; - QMutex.Lock(); - if (Count >= MaxinQ) delete Node->Item; - else {Node->Next = First; - First = Node; - Count++; - } - QMutex.UnLock(); - } - - void Set(int inQMax, time_t agemax=1800); - - void Set(XrdScheduler *sp, XrdOucTrace *tp, int TraceChk=0) - {Sched = sp; Trace = tp; TraceON = TraceChk;} - - void DoIt(); - - XrdObjectQ(const char *id, const char *desc) : XrdJob(desc) - {Curage = Count = 0; Maxage = 0; TraceID = id; - MaxinQ = 32; MininQ = 16; First = 0; - } - - ~XrdObjectQ() {} - -private: - -XrdSysMutex QMutex; -XrdObject *First; -int Count; -int Curage; -int MininQ; -int MaxinQ; -time_t Maxage; -XrdScheduler *Sched; -XrdOucTrace *Trace; -int TraceON; -const char *TraceID; -}; - -#include "Xrd/XrdObject.icc" -#endif diff --git a/src/Xrd/XrdObject.icc b/src/Xrd/XrdObject.icc deleted file mode 100644 index aec84b0fe2f..00000000000 --- a/src/Xrd/XrdObject.icc +++ /dev/null @@ -1,104 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d O b j e c t . i c c */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "Xrd/XrdScheduler.hh" -#include "XrdOuc/XrdOucTrace.hh" - -/******************************************************************************/ -/* D o I t */ -/******************************************************************************/ - -template -void XrdObjectQ::DoIt() -{ - XrdObject *pp, *p; - int oldcnt, agemax; - -// Lock the anchor and see if we met the threshold for deletion -// - QMutex.Lock(); - agemax = Maxage; - if ((oldcnt = Count) > MininQ) - { - // Prepare to scan down the queue. - // - if ((pp = First)) p = pp->Next; - else p = 0; - - // Find the first object that's been idle for too long - // - while(p && (p->QTime >= Curage)) {pp = p; p = p->Next;} - - // Now delete half of the idle objects. The object queue element must be - // part of the actual object being deleted for this to properly work. - // - if (pp) while(p) - {pp->Next = p->Next; delete p->Item; - Count--; - p = ((pp = pp->Next) ? pp->Next : 0); - } - } - -// Increase the age and unlock the queue -// - Curage++; - QMutex.UnLock(); - -// Trace as needed -// - if (TraceON && Trace->Tracing(TraceON)) - {Trace->Beg(TraceID); - cerr <End(); - } - -// Reschedule ourselves if we must do so -// - if (agemax > 0) Sched->Schedule((XrdJob *)this, agemax+time(0)); - } - -/******************************************************************************/ -/* S e t */ -/******************************************************************************/ - -template -void XrdObjectQ::Set(int inQMax, time_t agemax) -{ - -// Lock the data area and set the values -// - QMutex.Lock(); - MaxinQ = inQMax; Maxage = agemax; - if (!(MininQ = inQMax/2)) MininQ = 1; - QMutex.UnLock(); - -// Schedule ourselves using the new values -// - if (agemax > 0) Sched->Schedule((XrdJob *)this, agemax+time(0)); -} diff --git a/src/Xrd/XrdPoll.cc b/src/Xrd/XrdPoll.cc deleted file mode 100644 index 7479c8408ae..00000000000 --- a/src/Xrd/XrdPoll.cc +++ /dev/null @@ -1,367 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d P o l l . c c */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include - -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysFD.hh" -#include "XrdSys/XrdSysPlatform.hh" -#include "XrdSys/XrdSysPthread.hh" -#include "Xrd/XrdLink.hh" -#include "Xrd/XrdProtocol.hh" - -#define XRD_TRACE XrdTrace-> -#define TRACELINK lp -#include "Xrd/XrdTrace.hh" - -#if defined( __solaris__ ) -#include "Xrd/XrdPollDev.hh" -#elif defined( __linux__ ) -#include "Xrd/XrdPollE.hh" -#else -#include "Xrd/XrdPollPoll.hh" -#endif - -/******************************************************************************/ -/* L o c a l C l a s s e s */ -/******************************************************************************/ - -class XrdPoll_End : public XrdProtocol -{ -public: - -void DoIt() {} - -XrdProtocol *Match(XrdLink *lp) {return (XrdProtocol *)0;} - -int Process(XrdLink *lp) {return -1;} - -void Recycle(XrdLink *lp, int x, const char *y) {} - -int Stats(char *buff, int blen, int do_sync=0) {return 0;} - - XrdPoll_End() : XrdProtocol("link termination") {} - ~XrdPoll_End() {} -}; - -/******************************************************************************/ -/* G l o b a l D a t a */ -/******************************************************************************/ - - XrdPoll *XrdPoll::Pollers[XRD_NUMPOLLERS] = {0, 0, 0}; - - XrdSysMutex XrdPoll::doingAttach; - - const char *XrdPoll::TraceID = "Poll"; - - XrdOucTrace *XrdPoll::XrdTrace = 0; - XrdSysError *XrdPoll::XrdLog = 0; - XrdScheduler *XrdPoll::XrdSched = 0; - -/******************************************************************************/ -/* T h r e a d S t a r t u p I n t e r f a c e */ -/******************************************************************************/ - -struct XrdPollArg - {XrdPoll *Poller; - int retcode; - XrdSysSemaphore PollSync; - - XrdPollArg() : PollSync(0, "poll sync") {} - ~XrdPollArg() {} - }; - - -void *XrdStartPolling(void *parg) -{ - struct XrdPollArg *PArg = (struct XrdPollArg *)parg; - PArg->Poller->Start(&(PArg->PollSync), PArg->retcode); - return (void *)0; -} - -/******************************************************************************/ -/* C o n s t r u c t o r */ -/******************************************************************************/ - -XrdPoll::XrdPoll() -{ - int fildes[2]; - - TID=0; - numAttached=numEnabled=numEvents=numInterrupts=0; - - if (XrdSysFD_Pipe(fildes) == 0) - {CmdFD = fildes[1]; - ReqFD = fildes[0]; - } else { - CmdFD = ReqFD = -1; - XrdLog->Emsg("Poll", errno, "create poll pipe"); - } - PipeBuff = 0; - PipeBlen = 0; - PipePoll.fd = ReqFD; - PipePoll.events = POLLIN | POLLRDNORM; -} - -/******************************************************************************/ -/* A t t a c h */ -/******************************************************************************/ - -int XrdPoll__Attach(XrdLink *lp) {return XrdPoll::Attach(lp);} - -int XrdPoll::Attach(XrdLink *lp) -{ - int i; - XrdPoll *pp; - -// We allow only one attach at a time to simplify the processing -// - doingAttach.Lock(); - -// Find a poller with the smallest number of entries -// - pp = Pollers[0]; - for (i = 1; i < XRD_NUMPOLLERS; i++) - if (pp->numAttached > Pollers[i]->numAttached) pp = Pollers[i]; - -// Include this FD into the poll set of the poller -// - if (!pp->Include(lp)) {doingAttach.UnLock(); return 0;} - -// Complete the link setup -// - lp->Poller = pp; - pp->numAttached++; - doingAttach.UnLock(); - TRACEI(POLL, "FD " <FD <<" attached to poller " <PID <<"; num=" <numAttached); - return 1; -} - -/******************************************************************************/ -/* D e t a c h */ -/******************************************************************************/ - -void XrdPoll::Detach(XrdLink *lp) -{ - XrdPoll *pp; - -// If link is not attached, simply return -// - if (!(pp = lp->Poller)) return; - -// Exclude this link from the associated poll set -// - pp->Exclude(lp); - -// Make sure we are consistent -// - doingAttach.Lock(); - if (!pp->numAttached) - {XrdLog->Emsg("Poll","Underflow detaching", lp->ID); abort();} - pp->numAttached--; - doingAttach.UnLock(); - TRACEI(POLL, "FD " <FDnum() <<" detached from poller " <PID - <<"; num=" <numAttached); -} - -/******************************************************************************/ -/* F i n i s h */ -/******************************************************************************/ - -int XrdPoll::Finish(XrdLink *lp, const char *etxt) -{ - static XrdPoll_End LinkEnd; - -// If this link is already scheduled for termination, ignore this call. -// - if (lp->Protocol == &LinkEnd) - {TRACEI(POLL, "Link " <FD <<" already terminating; " - <<(etxt ? etxt : "") <<" request ignored."); - return 0; - } - -// Set the protocol pointer to be link termination -// - lp->ProtoAlt = lp->Protocol; - lp->Protocol = static_cast(&LinkEnd); - if (etxt) - {if (lp->Etext) free(lp->Etext); - lp->Etext = strdup(etxt); - } else etxt = "reason unknown"; - TRACEI(POLL, "Link " <FD <<" terminating: " <Emsg("Poll", errno, "read from request pipe"); - return 0; - } - -// Check if all the data has arrived. If not all the data is present, defer -// this request until more data arrives. -// - if (!(PipeBlen -= rlen)) return 1; - PipeBuff += rlen; - TRACE(POLL, "Poller " <PID = i; - - // Now start a thread to handle this poller object - // - PArg.Poller = Pollers[i]; - PArg.retcode= 0; - TRACE(POLL, "Starting poller " <Emsg("Poll", retc, "create poller thread"); return 0;} - Pollers[i]->TID = tid; - PArg.PollSync.Wait(); - if (PArg.retcode) - {XrdLog->Emsg("Poll", PArg.retcode, "start poller"); - return 0; - } - } - -// All done -// - return 1; -} - -/******************************************************************************/ -/* S t a t s */ -/******************************************************************************/ - -int XrdPoll::Stats(char *buff, int blen, int do_sync) -{ - static const char statfmt[] = "%d" - "%d%d%d"; - int i, numatt = 0, numen = 0, numev = 0, numint = 0; - XrdPoll *pp; - -// Return number of bytes if so wanted -// - if (!buff) return (sizeof(statfmt)+(4*16))*XRD_NUMPOLLERS; - -// Get statistics. While we wish we could honor do_sync, doing so would be -// costly and hardly worth it. So, we do not include code such as: -// x = pp->y; if (do_sync) while(x != pp->y) x = pp->y; tot += x; -// - for (i = 0; i < XRD_NUMPOLLERS; i++) - {pp = Pollers[i]; - numatt += pp->numAttached; - numen += pp->numEnabled; - numev += pp->numEvents; - numint += pp->numInterrupts; - } - -// Format and return -// - return snprintf(buff, blen, statfmt, numatt, numen, numev, numint); -} - -/******************************************************************************/ -/* I m p l e m e n t a t i o n S p e c i f i c s */ -/******************************************************************************/ -#if defined( __solaris__ ) -#include "Xrd/XrdPollDev.icc" -#elif defined( __linux__ ) -#include "Xrd/XrdPollE.icc" -#else -#include "Xrd/XrdPollPoll.icc" -#endif diff --git a/src/Xrd/XrdPoll.hh b/src/Xrd/XrdPoll.hh deleted file mode 100644 index 076d641d53d..00000000000 --- a/src/Xrd/XrdPoll.hh +++ /dev/null @@ -1,152 +0,0 @@ -#ifndef __XRD_POLL_H__ -#define __XRD_POLL_H__ -/******************************************************************************/ -/* */ -/* X r d P o l l . h h */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include "XrdSys/XrdSysPthread.hh" - -#define XRD_NUMPOLLERS 3 - -class XrdOucTrace; -class XrdSysError; -class XrdLink; -class XrdScheduler; -class XrdSysSemaphore; - -class XrdPoll -{ -public: - -// Attach() is called when a new link needs to be assigned to a poller -// -static int Attach(XrdLink *lp); // Implementation supplied - -// Detach() is called when a link is being discarded -// -static void Detach(XrdLink *lp); // Implementation supplied - -// Disable() is called when we need to mask interrupts from a link -// -virtual void Disable(XrdLink *lp, const char *etxt=0) = 0; - -// Enable() is called when we want to receive interrupts from a link -// -virtual int Enable(XrdLink *lp) = 0; - -// Finish() is called to allow a link to gracefully terminate when scheduled -// -static int Finish(XrdLink *lp, const char *etxt=0); //Implementation supplied - -// Init() is called to set pointers to external interfaces at config time. -// -static void Init(XrdSysError *eP, XrdOucTrace *tP, XrdScheduler *sP) - {XrdLog = eP; XrdTrace = tP; XrdSched = sP;} - -// Poll2Text() converts bits in an revents item to text -// -static char *Poll2Text(short events); // Implementation supplied - -// Setup() is called at config time to perform poller configuration -// -static int Setup(int numfd); // Implementation supplied - -// Start() is called via a thread for each poller that was created -// -virtual void Start(XrdSysSemaphore *syncp, int &rc) = 0; - -// Stats() is called to provide statistics on polling -// -static int Stats(char *buff, int blen, int do_sync=0); - -// Identification of the thread handling this object -// - int PID; // Poller ID - pthread_t TID; // Thread ID - -// The following table reference the pollers in effect -// -static XrdPoll *Pollers[XRD_NUMPOLLERS]; - - XrdPoll(); -virtual ~XrdPoll() {} - -protected: - -static const char *TraceID; // For tracing -static XrdOucTrace *XrdTrace; -static XrdSysError *XrdLog; -static XrdScheduler *XrdSched; - -// Gets the next request on the poll pipe. This is common to all implentations. -// - int getRequest(); // Implementation supplied - -// Exclude() called to exclude a link from a poll set -// -virtual void Exclude(XrdLink *lp) = 0; - -// Include() called to include a link in a poll set -// -virtual int Include(XrdLink *lp) = 0; - -// newPoller() called to get a new poll object at initialization time -// Even though static, an implementation must be supplied. -// -static XrdPoll *newPoller(int pollid, int numfd) /* = 0 */; - -// The following is common to all implementations -// -XrdSysMutex PollPipe; -struct pollfd PipePoll; -int CmdFD; // FD to send PipeData commands -int ReqFD; // FD to recv PipeData requests -struct PipeData {union {XrdSysSemaphore *theSem; - struct {int fd; - int ent;} Arg; - } Parms; - enum cmd {EnFD, DiFD, RmFD, Post}; - cmd req; - }; - PipeData ReqBuff; -char *PipeBuff; -int PipeBlen; - -// The following are statistical counters each implementation must maintain -// - int numEnabled; // Count of Enable() calls - int numEvents; // Count of poll fd's dispatched - int numInterrupts; // Number of interrupts (e.g., signals) - -private: - -static XrdSysMutex doingAttach; - int numAttached; // Number of fd's attached to poller -}; -#endif diff --git a/src/Xrd/XrdPollDev.hh b/src/Xrd/XrdPollDev.hh deleted file mode 100644 index 0f09db57e11..00000000000 --- a/src/Xrd/XrdPollDev.hh +++ /dev/null @@ -1,64 +0,0 @@ -#ifndef __XRD_POLLDEV_H__ -#define __XRD_POLLDEV_H__ -/******************************************************************************/ -/* */ -/* X r d P o l l D e v . h h */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -#include "Xrd/XrdPoll.hh" - -class XrdPollDev : public XrdPoll -{ -public: - - void Disable(XrdLink *lp, const char *etxt=0); - - int Enable(XrdLink *lp); - - void Start(XrdSysSemaphore *syncp, int &rc); - - XrdPollDev(struct pollfd *ptab, int numfd, int pfd) - {PollTab = ptab; PollMax = numfd; PollDfd = pfd;} - ~XrdPollDev(); - -protected: - void Exclude(XrdLink *lp); - int Include(XrdLink *lp) {return 1;} - -private: - -void doRequests(int maxreq); -void LogEvent(struct pollfd *pp); -int sendCmd(char *cmdbuff, int cmdblen); - -struct pollfd *PollTab; - int PollDfd; - int PollMax; -}; -#endif diff --git a/src/Xrd/XrdPollDev.icc b/src/Xrd/XrdPollDev.icc deleted file mode 100644 index 9d810fd3a8f..00000000000 --- a/src/Xrd/XrdPollDev.icc +++ /dev/null @@ -1,314 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d P o l l D e v . i c c */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include - -#include "XrdSys/XrdSysError.hh" -#include "Xrd/XrdLink.hh" -#include "Xrd/XrdPollPoll.hh" -#include "Xrd/XrdScheduler.hh" - -/******************************************************************************/ -/* n e w P o l l e r */ -/******************************************************************************/ - -XrdPoll *XrdPoll::newPoller(int pollid, int maxfd) -{ - int pfd, bytes, alignment, pagsz = getpagesize(); - struct pollfd *pp; - -// Open the /dev/poll driver -// - if ((pfd = open("/dev/poll", O_RDWR)) < 0) - {XrdLog->Emsg("Poll", errno, "open /dev/poll"); return 0;} - fcntl(pfd, F_SETFD, FD_CLOEXEC); - -// Calculate the size of the poll table and allocate it -// - bytes = maxfd * sizeof(struct pollfd); - alignment = (bytes < pagsz ? 1024 : pagsz); - if (!(pp = (struct pollfd *)memalign(alignment, bytes))) - {XrdLog->Emsg("Poll", ENOMEM, "create poll table"); - close(pfd); - return 0; - } - -// Create new poll object -// - memset((void *)pp, 0, bytes); - return (XrdPoll *)new XrdPollDev(pp, maxfd, pfd); -} - -/******************************************************************************/ -/* D e s t r u c t o r */ -/******************************************************************************/ - -XrdPollDev::~XrdPollDev() -{ - if (PollTab) free(PollTab); - if (PollDfd >= 0) close(PollDfd); -} - -/******************************************************************************/ -/* D i s a b l e */ -/******************************************************************************/ - -void XrdPollDev::Disable(XrdLink *lp, const char *etxt) -{ - XrdSysSemaphore mySem(0); - PipeData cmdbuff[2]; - int myerrno = 0; - -// Simply return if the link is already disabled -// - if (!lp->isEnabled) return; - -// Trace this event -// - TRACEI(POLL, "Poller " <FD); - -// Send a disable request to the poller thread handling this link. We need to -// wait until the operation is actually completed before returning. -// - cmdbuff[0].req = PipeData::DiFD; - cmdbuff[0].Parms.Arg.fd = lp->FD; - cmdbuff[1].req = PipeData::Post; - cmdbuff[1].Parms.theSem = &mySem; - myerrno = sendCmd((char *)&cmdbuff, sizeof(cmdbuff)); - -// Verify that all went well and if termination wanted, terminate the link -// Warning! The link's opMutex must be held if termination is requested! -// - if (myerrno) XrdLog->Emsg("Poll", myerrno, "disable link", lp->ID); - else {mySem.Wait(); - if (etxt && Finish(lp, etxt)) XrdSched->Schedule((XrdJob *)lp); - } -} - -/******************************************************************************/ -/* E n a b l e */ -/******************************************************************************/ - -int XrdPollDev::Enable(XrdLink *lp) -{ - PipeData cmdbuff; - int nogo; - -// Simply return if the link is already enabled -// - if (lp->isEnabled) return 1; - -// Send an enable request to the poller thread handling this link -// - cmdbuff.req = PipeData::EnFD; - cmdbuff.Parms.Arg.fd = lp->FD; - nogo = sendCmd((char *)&cmdbuff, sizeof(cmdbuff)); - -// Verify that all went well -// - if (nogo) XrdLog->Emsg("Poll", nogo, "enable link", lp->ID); - return !nogo; -} - -/******************************************************************************/ -/* E x c l u d e */ -/******************************************************************************/ - -void XrdPollDev::Exclude(XrdLink *lp) -{ - -// Make sure this link is not enabled -// - if (lp->isEnabled) - {XrdLog->Emsg("Poll", "Detach of enabled link", lp->ID); - Disable(lp); - } -} - -/******************************************************************************/ -/* s e n d C m d */ -/******************************************************************************/ - -int XrdPollDev::sendCmd(char *cmdbuff, int cmdblen) -{ - int wlen, myerrno = 0; - - PollPipe.Lock(); - do {if ((wlen = write(CmdFD, cmdbuff, cmdblen)) < 0) - if (errno == EINTR) wlen = 0; - else {myerrno = errno; break;} - cmdbuff += wlen; cmdblen -= wlen; - } while(cmdblen > 0); - PollPipe.UnLock(); - - return myerrno; -} - -/******************************************************************************/ -/* S t a r t */ -/******************************************************************************/ - -void XrdPollDev::Start(XrdSysSemaphore *syncsem, int &retcode) -{ - int i, xReq, numpolled, num2sched, AOK = 0; - XrdJob *jfirst, *jlast; - const short pollOK = POLLIN | POLLRDNORM; - struct dvpoll dopoll = {PollTab, PollMax, -1}; - XrdLink *lp; - -// If we have a com pipe, add it to our set -// - {struct pollfd ptab = {ReqFD, POLLIN | POLLRDNORM, 0}; - if (PollDfd < 0) XrdLog->Emsg("Poll", "poll pipe not allocated"); - else if (write(PollDfd,&ptab,sizeof(struct pollfd))!=sizeof(struct pollfd)) - XrdLog->Emsg("Poll", errno, "add pipe to poll set"); - else AOK = 1; - } - -// Indicate to the starting thread that all went well -// - retcode = (AOK ? 0 : -1); - syncsem->Post(); - if (!AOK) return; - -// Now start dispatching links that are ready -// - do {do {numpolled = ioctl(PollDfd, DP_POLL, &dopoll);} - while (numpolled < 0 && errno == EINTR); - if (numpolled == 0) continue; - if (numpolled < 0) - {XrdLog->Emsg("Poll", errno, "poll for events"); - abort(); - } - numEvents += numpolled; - - // Checkout which links must be dispatched (no need to lock) - // - jfirst = jlast = 0; num2sched = 0; xReq = 0; - for (i = 0; i < numpolled; i++) - {if (PollTab[i].fd == ReqFD) {xReq = 1; continue;} - if (lp = XrdLink::fd2link(PollTab[i].fd)) - if (!(lp->isEnabled)) - XrdLog->Emsg("Poll", "Disabled event occured for", lp->ID); - else {lp->isEnabled = 0; - if (!(PollTab[i].revents & pollOK)) - Finish(lp, Poll2Text(PollTab[i].revents)); - lp->NextJob = jfirst; jfirst = (XrdJob *)lp; - if (!jlast) jlast=(XrdJob *)lp; - num2sched++; - } else LogEvent(&PollTab[i]); - PollTab[i].events = POLLREMOVE; - PollTab[i].revents = 0; - } - - // Disable all of the polled fd's - // - if (write(PollDfd, PollTab, numpolled*sizeof(struct pollfd)) - != static_cast(numpolled*sizeof(struct pollfd))) - XrdLog->Emsg("Poll", errno, "remove an fd from /dev/poll"); - - // Schedule the polled links - // - if (num2sched == 1) XrdSched->Schedule(jfirst); - else if (num2sched) XrdSched->Schedule(num2sched, jfirst, jlast); - - // Handle the queued pipe last - // - if (xReq) doRequests(numpolled); - } while(1); -} - -/******************************************************************************/ -/* P r i v a t e M e t h o d s */ -/******************************************************************************/ -/******************************************************************************/ -/* d o R e q u e s t s */ -/******************************************************************************/ - -void XrdPollDev::doRequests(int maxreq) -{ - struct pollfd ptab = {0, 0, 0}; - XrdLink *lp; - const char *act; - char buff[16], edval; - int num2do; - -// To keep ourselves from being swamped, base request read-aheads on the number -// of pending poll events. -// - num2do = (maxreq < 3 ? 3 : maxreq); - -// Now process all poll table manipulation requests -// - while(num2do-- && getRequest()) - { - if (ReqBuff.req == PipeData::Post) - {ReqBuff.Parms.theSem->Post(); - continue; - } - if (!(lp = XrdLink::fd2link(ReqBuff.Parms.Arg.fd))) - {sprintf(buff, "%d", ReqBuff.Parms.Arg.fd); - XrdLog->Emsg("Poll", "FD", buff, "does not map to a link"); - ptab.events = POLLREMOVE; act = " remove fd "; - } - else if (ReqBuff.req == PipeData::EnFD) - {ptab.events = POLLIN | POLLRDNORM; - act = " enable fd "; edval = 0x01; numEnabled++; - } - else if (ReqBuff.req == PipeData::DiFD) - {ptab.events = POLLREMOVE; - num2do++; - act = " disable fd "; edval = 0x00; - } - else {XrdLog->Emsg("Poll", "Received an invalid poll pipe request"); - continue; - } - ptab.fd = ReqBuff.Parms.Arg.fd; - TRACE(POLL, "Poller " <Emsg("Poll", errno, act); - if (lp) lp->isEnabled = edval; - } -} - -/******************************************************************************/ -/* L o g u v e n t */ -/******************************************************************************/ - -void XrdPollDev::LogEvent(struct pollfd *pp) -{ - char buff[32]; - sprintf(buff,"%.4x fd=%d",pp->revents, pp->fd); - XrdLog->Emsg("Poll", "Received unexpected event", buff); -} diff --git a/src/Xrd/XrdPollE.hh b/src/Xrd/XrdPollE.hh deleted file mode 100644 index 385b60d130e..00000000000 --- a/src/Xrd/XrdPollE.hh +++ /dev/null @@ -1,74 +0,0 @@ -#ifndef __XRD_POLLDEV_H__ -#define __XRD_POLLDEV_H__ -/******************************************************************************/ -/* */ -/* X r d P o l l D e v . h h */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -#include "Xrd/XrdPoll.hh" - -#ifndef EPOLLRDHUP -#define EPOLLRDHUP 0 -#endif - -class XrdPollE : public XrdPoll -{ -public: - - void Disable(XrdLink *lp, const char *etxt=0); - - int Enable(XrdLink *lp); - - void Start(XrdSysSemaphore *syncp, int &rc); - - XrdPollE(struct epoll_event *ptab, int numfd, int pfd) - {PollTab = ptab; PollMax = numfd; PollDfd = pfd;} - ~XrdPollE(); - -protected: - void Exclude(XrdLink *lp); - int Include(XrdLink *lp); -const char *x2Text(unsigned int evf, char *buff); - -private: -void remFD(XrdLink *lp, unsigned int events); - -#ifdef EPOLLONESHOT - static const int ePollOneShot = EPOLLONESHOT; -#else - static const int ePollOneShot = 0; -#endif - static const int ePollEvents = EPOLLIN | EPOLLHUP | EPOLLPRI | EPOLLERR | - EPOLLRDHUP | ePollOneShot; - -struct epoll_event *PollTab; - int PollDfd; - int PollMax; -}; -#endif diff --git a/src/Xrd/XrdPollE.icc b/src/Xrd/XrdPollE.icc deleted file mode 100644 index 2fbf694bb75..00000000000 --- a/src/Xrd/XrdPollE.icc +++ /dev/null @@ -1,280 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d P o l l E . i c c */ -/* */ -/* (c) 2008 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#ifndef __APPLE__ -#include -#endif -#include -#include -#include -#include -#include -#include - -#include "XrdSys/XrdSysError.hh" -#include "Xrd/XrdLink.hh" -#include "Xrd/XrdPollE.hh" -#include "Xrd/XrdScheduler.hh" - -/******************************************************************************/ -/* n e w P o l l e r */ -/******************************************************************************/ - -XrdPoll *XrdPoll::newPoller(int pollid, int maxfd) -{ - int pfd, bytes, alignment, pagsz = getpagesize(); - struct epoll_event *pp; - -// Open the /dev/poll driver -// -#ifndef EPOLL_CLOEXEC - if ((pfd = epoll_create(maxfd)) >= 0) fcntl(pfd, F_SETFD, FD_CLOEXEC); - else -#else - if ((pfd = epoll_create1(EPOLL_CLOEXEC)) < 0) -#endif - {XrdLog->Emsg("Poll", errno, "create epoll device"); return 0;} - -// Calculate the size of the poll table and allocate it -// - bytes = maxfd * sizeof(struct epoll_event); - alignment = (bytes < pagsz ? 1024 : pagsz); - if (!(pp = (struct epoll_event *)memalign(alignment, bytes))) - {XrdLog->Emsg("Poll", ENOMEM, "create poll table"); - close(pfd); - return 0; - } - -// Create new poll object -// - memset((void *)pp, 0, bytes); - return (XrdPoll *)new XrdPollE(pp, maxfd, pfd); -} - -/******************************************************************************/ -/* D e s t r u c t o r */ -/******************************************************************************/ - -XrdPollE::~XrdPollE() -{ - if (PollTab) free(PollTab); - if (PollDfd >= 0) close(PollDfd); -} - -/******************************************************************************/ -/* D i s a b l e */ -/******************************************************************************/ - -void XrdPollE::Disable(XrdLink *lp, const char *etxt) -{ - -// Simply return if the link is already disabled -// - if (!lp->isEnabled) return; - -// If Linux 2.6.9 we use EPOLLONESHOT to automatically disable a polled fd. -// So, the Disable() method need not do anything. Prior kernels did not have -// this mechanism so we need to do this manually. -// -#ifndef EPOLLONESHOT - struct epoll_event myEvents = {0, (void *)lp}; - -// Enable this fd. Unlike solaris, epoll_ctl() does not block when the pollfd -// is being waited upon by another thread. -// - if (epoll_ctl(PollDfd, EPOLL_CTL_MOD, lp->FDnum(), &myEvents)) - {XrdLog->Emsg("Poll", errno, "disable link", lp->ID); return;} -#endif - -// Trace this event -// - lp->isEnabled = 0; - TRACEI(POLL, "Poller " <FD); - -// Check if this link needs to be rescheduled. If so, the caller better have -// the link opMutex lock held for this to work! -// - if (etxt && Finish(lp, etxt)) XrdSched->Schedule((XrdJob *)lp); -} - -/******************************************************************************/ -/* E n a b l e */ -/******************************************************************************/ - -int XrdPollE::Enable(XrdLink *lp) -{ - struct epoll_event myEvents = {ePollEvents, {(void *)lp}}; - -// Simply return if the link is already enabled -// - if (lp->isEnabled) return 1; - -// Enable this fd. Unlike solaris, epoll_ctl() does not block when the pollfd -// is being waited upon by another thread. -// - lp->isEnabled = 1; - if (epoll_ctl(PollDfd, EPOLL_CTL_MOD, lp->FDnum(), &myEvents)) - {XrdLog->Emsg("Poll", errno, "enable link", lp->ID); - lp->isEnabled = 0; - return 0; - } - -// Do final processing -// - TRACE(POLL, "Poller " <ID); - numEnabled++; - return 1; -} - -/******************************************************************************/ -/* E x c l u d e */ -/******************************************************************************/ - -void XrdPollE::Exclude(XrdLink *lp) -{ - -// Make sure this link is not enabled -// - if (lp->isEnabled) - {XrdLog->Emsg("Poll", "Detach of enabled link", lp->ID); - Disable(lp); - } -} - -/******************************************************************************/ -/* I n c l u d e */ -/******************************************************************************/ - -int XrdPollE::Include(XrdLink *lp) -{ - struct epoll_event myEvent = {0, {(void *)lp}}; - int rc; - -// Add this fd to the poll set -// - if ((rc = epoll_ctl(PollDfd, EPOLL_CTL_ADD, lp->FDnum(), &myEvent)) < 0) - XrdLog->Emsg("Poll", errno, "include link", lp->ID); - -// All done -// - return rc == 0; -} - -/******************************************************************************/ -/* r e m F D */ -/******************************************************************************/ - -void XrdPollE::remFD(XrdLink *lp, unsigned int events) -{ - struct epoll_event myEvents = {0, {(void *)lp}}; - static const char *why; - -// It works out that ONESHOT mode or even CTL_MOD requests do not necessarily -// prevent epoll_wait() from returning on an error event. So, we must manually -// remove the fd from the set and assume the logic was actually correct. If it -// wasn't then the client will eventually timeout and retry the request. -// - if (events & (EPOLLHUP | EPOLLRDHUP)) why = "Sever"; - else if (events & EPOLLERR) why = "Error"; - else why = "Disabled"; - XrdLog->Emsg("Poll", why, "event occured for", lp->ID); - - if (epoll_ctl(PollDfd, EPOLL_CTL_DEL, lp->FDnum(), &myEvents)) - XrdLog->Emsg("Poll", errno, "exclude link", lp->ID); -} - -/******************************************************************************/ -/* S t a r t */ -/******************************************************************************/ - -void XrdPollE::Start(XrdSysSemaphore *syncsem, int &retcode) -{ - char eBuff[64]; - int i, numpolled, num2sched; - XrdJob *jfirst, *jlast; - const short pollOK = EPOLLIN | EPOLLPRI; - XrdLink *lp; - -// Indicate to the starting thread that all went well -// - retcode = 0; - syncsem->Post(); - -// Now start dispatching links that are ready -// - do {do {numpolled = epoll_wait(PollDfd, PollTab, PollMax, -1);} - while (numpolled < 0 && errno == EINTR); - if (numpolled == 0) continue; - if (numpolled < 0) - {XrdLog->Emsg("Poll", errno, "poll for events"); - abort(); - } - numEvents += numpolled; - - // Checkout which links must be dispatched (no need to lock) - // - jfirst = jlast = 0; num2sched = 0; - for (i = 0; i < numpolled; i++) - {if ((lp = (XrdLink *)PollTab[i].data.ptr)) - if (!(lp->isEnabled)) remFD(lp, PollTab[i].events); - else {lp->isEnabled = 0; - if (!(PollTab[i].events & pollOK)) - Finish(lp, x2Text(PollTab[i].events, eBuff)); - lp->NextJob = jfirst; jfirst = (XrdJob *)lp; - if (!jlast) jlast=(XrdJob *)lp; - num2sched++; -#ifndef EPOLLONESHOT - PollTab[i].events = 0; - if (epoll_ctl(PollDfd,EPOLL_CTL_MOD,lp->FDnum(),&PollTab[i])) - XrdLog->Emsg("Poll", errno, "disable link", lp->ID); -#endif - } else XrdLog->Emsg("Poll", "null link event!!!!"); - } - - // Schedule the polled links - // - if (num2sched == 1) XrdSched->Schedule(jfirst); - else if (num2sched) XrdSched->Schedule(num2sched, jfirst, jlast); - } while(1); -} - -/******************************************************************************/ -/* x 2 T e x t */ -/******************************************************************************/ - -const char *XrdPollE::x2Text(unsigned int events, char *buff) -{ - if (events & EPOLLERR) return "socket error"; - - if (events & (EPOLLHUP | EPOLLRDHUP)) return "client disconnected"; - - sprintf(buff, "unusual event (%.4x)", events); - return buff; -} diff --git a/src/Xrd/XrdPollPoll.hh b/src/Xrd/XrdPollPoll.hh deleted file mode 100644 index e76c0c39512..00000000000 --- a/src/Xrd/XrdPollPoll.hh +++ /dev/null @@ -1,72 +0,0 @@ -#ifndef __XRD_POLLPOLL_H__ -#define __XRD_POLLPOLL_H__ -/******************************************************************************/ -/* */ -/* X r d P o l l P o l l . h h */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -#include "Xrd/XrdPoll.hh" - -class XrdLink; - -class XrdPollPoll : XrdPoll -{ -public: - - void Detach(XrdLink *lp); - - void Disable(XrdLink *lp, const char *etxt=0); - - int Enable(XrdLink *lp); - - void Start(XrdSysSemaphore *syncp, int &rc); - - XrdPollPoll(struct pollfd *pp, int numfd); - ~XrdPollPoll(); - -protected: - void doDetach(int pti); - void Exclude(XrdLink *lp); - int Include(XrdLink *lp); - -private: - -void doRequests(int maxreq); -void dqLink(XrdLink *lp); -void LogEvent(int req, int pollfd, int cmdfd); -void Recover(int numleft); -void Restart(int ecode); - -struct pollfd *PollTab; //<--- - int PollTNum; // PollMutex protects these elements - XrdLink *PollQ; //<--- - XrdSysMutex PollMutex; - int maxent; -}; -#endif diff --git a/src/Xrd/XrdPollPoll.icc b/src/Xrd/XrdPollPoll.icc deleted file mode 100644 index d5ab165c07e..00000000000 --- a/src/Xrd/XrdPollPoll.icc +++ /dev/null @@ -1,509 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d P o l l P o l l . i c c */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#if !defined(__APPLE__) && !defined(__FreeBSD__) -#include -#endif -#include -#include -#include - -#include "XrdSys/XrdSysError.hh" -#include "Xrd/XrdLink.hh" -#include "Xrd/XrdPollPoll.hh" -#include "Xrd/XrdScheduler.hh" - -/******************************************************************************/ -/* n e w P o l l e r */ -/******************************************************************************/ - -XrdPoll *XrdPoll::newPoller(int pollid, int maxfd) -{ - int bytes, alignment, pagsz = getpagesize(); - struct pollfd *pp; - -// Calculate the size of the poll table and allocate it -// - bytes = maxfd * sizeof(struct pollfd); - alignment = (bytes < pagsz ? 1024 : pagsz); - if (!(pp = (struct pollfd *)memalign(alignment, bytes))) - {XrdLog->Emsg("Poll", ENOMEM, "create poll table"); - return 0; - } - -// Create new poll object -// - memset((void *)pp, 0, bytes); - return (XrdPoll *)new XrdPollPoll(pp, maxfd); -} - -/******************************************************************************/ -/* C o n s t r c u t o r */ -/******************************************************************************/ - -XrdPollPoll::XrdPollPoll(struct pollfd *pp, int numfd) -{ - -// Initialize the standard stuff -// - PollTab = pp; - PollTNum= 0; - PollQ = 0; - maxent = numfd; -} - -/******************************************************************************/ -/* D e s t r u c t o r */ -/******************************************************************************/ - -XrdPollPoll::~XrdPollPoll() -{ - if (PollTab) free(PollTab); -} - -/******************************************************************************/ -/* I n c l u d e */ -/******************************************************************************/ - -int XrdPollPoll::Include(XrdLink *lp) -{ - struct pollfd *pfd; - int ptnum; - -// Lock down the poll data structure -// - PollMutex.Lock(); - -// Get the next entry to be used -// - ptnum = 0; - while((ptnum < PollTNum) && (PollTab[ptnum].fd != -1)) ptnum++; - -// Make sure we have enough table entries to add this link -// - if (ptnum > maxent) - {XrdLog->Emsg("Attach","Attach",lp->ID,"failed; poll table overflow."); - PollMutex.UnLock(); - return 0; - } - -// Initialize the polltable entry -// - pfd = &(PollTab[ptnum]); - pfd->fd = -lp->FD; - pfd->events = POLLIN | POLLRDNORM; - pfd->revents = 0; - -// Record relevant information in the link -// - lp->PollEnt = pfd; - if (ptnum == PollTNum) PollTNum++; - -// All done -// - PollMutex.UnLock(); - return 1; -} - -/******************************************************************************/ -/* D i s a b l e */ -/******************************************************************************/ - -void XrdPollPoll::Disable(XrdLink *lp, const char *etxt) -{ - XrdSysSemaphore mySem(0); - PipeData cmdbuff[2]; - int myerrno = 0; - -// Check if this link is in the pollQ. If so, remove it. -// - if (lp->inQ) dqLink(lp); - -// Simply return if the link is already disabled -// - if (!lp->isEnabled) return; - -// Trace this event -// - TRACEI(POLL, "Poller " <FD); - -// Send a disable request to the poller thread handling this link. We need to -// wait until the operation is actually completed before returning. -// - memset(&cmdbuff, 0, sizeof(cmdbuff)); - cmdbuff[0].req = PipeData::DiFD; - cmdbuff[0].Parms.Arg.fd = lp->FD; - cmdbuff[0].Parms.Arg.ent = lp->PollEnt - PollTab; - cmdbuff[1].req = PipeData::Post; - cmdbuff[1].Parms.theSem = &mySem; - PollPipe.Lock(); - if (write(CmdFD, &cmdbuff, sizeof(cmdbuff)) < 0) myerrno = errno; - PollPipe.UnLock(); - -// Verify that all went well and if termination wanted, terminate the link -// - if (myerrno) XrdLog->Emsg("Poll", myerrno, "disable link", lp->ID); - else {mySem.Wait(); - if (etxt && Finish(lp, etxt)) XrdSched->Schedule((XrdJob *)lp); - } -} - -/******************************************************************************/ -/* E n a b l e */ -/******************************************************************************/ - -int XrdPollPoll::Enable(XrdLink *lp) -{ - PipeData cmdbuff; - int myerrno = 0; - -// Simply return if the link is already enabled -// - if (lp->isEnabled) return 1; - -// Add this link element to the queue -// - PollMutex.Lock(); - lp->Next = PollQ; - PollQ = lp; - lp->inQ = 1; - PollMutex.UnLock(); - -// Send an enable request to the poller thread handling this link -// - TRACEI(POLL, "sending poller " <FD); - cmdbuff.req = PipeData::EnFD; - cmdbuff.Parms.Arg.fd = lp->FD; - cmdbuff.Parms.Arg.ent = lp->PollEnt - PollTab; - PollPipe.Lock(); - if (write(CmdFD, &cmdbuff, sizeof(cmdbuff)) < 0) myerrno = errno; - PollPipe.UnLock(); - -// Verify that all went well. Note that the link stays in the pollQ. -// - if (myerrno) - {XrdLog->Emsg("Poll", myerrno, "enable link", lp->ID); return 0;} - -// All done -// - return 1; -} - -/******************************************************************************/ -/* E x c l u d e */ -/******************************************************************************/ - -void XrdPollPoll::Exclude(XrdLink *lp) -{ - XrdSysSemaphore mySem(0); - PipeData cmdbuff[2]; - int myerrno = 0; - -// Make sure this link is not enabled -// - if (lp->isEnabled) - {XrdLog->Emsg("Poll", "Detach of enabled link", lp->ID); - Disable(lp); - } - else if (lp->inQ) dqLink(lp); - -// Send a deatch request to the poller thread handling this link -// - TRACEI(POLL, "sending poller " <FD); - cmdbuff[0].req = PipeData::RmFD; - cmdbuff[0].Parms.Arg.fd = lp->FD; - cmdbuff[0].Parms.Arg.ent = lp->PollEnt - PollTab; - cmdbuff[1].req = PipeData::Post; - cmdbuff[1].Parms.theSem = &mySem; - PollPipe.Lock(); - if (write(CmdFD, &cmdbuff, sizeof(cmdbuff)) < 0) myerrno = errno; - PollPipe.UnLock(); - -// Verify that all went well and if termination wanted, terminate the link -// - if (myerrno) XrdLog->Emsg("Poll", myerrno, "detach link", lp->ID); - else mySem.Wait(); -} - -/******************************************************************************/ -/* S t a r t */ -/******************************************************************************/ - -void XrdPollPoll::Start(XrdSysSemaphore *syncsem, int &retcode) -{ - int numpolled, num2sched; - XrdJob *jfirst, *jlast; - XrdLink *plp, *lp, *nlp; - short pollevents; - const short pollOK = POLLIN | POLLRDNORM; - -// Set up he first entry in the poll table to be our communications port -// - PollTab[0].fd = ReqFD; - PollTab[0].events = pollOK; - PollTab[0].revents = 0; - PollTNum = 1; - -// Signal the caller to continue -// - retcode = 0; - syncsem->Post(); - -// Now do the main poll loop -// - do {do {numpolled = poll(PollTab, PollTNum, -1);} - while(numpolled < 0 && (errno == EAGAIN || errno == EINTR)); - - // Check if we had a polling error - // - if (numpolled < 0) - {if (errno != EINTR) Restart(errno); - else numInterrupts++; - continue; - } - numEvents += numpolled; - - // Check out base poll table entry, we can do this without a lock - // - if (PollTab[0].revents & pollOK) - {doRequests(numpolled); - if (--numpolled <= 0) continue; - } - - // Checkout which links must be dispatched (do this locked) - // - PollMutex.Lock(); - plp = 0; nlp = PollQ; jfirst = jlast = 0; num2sched = 0; - while ((lp = nlp) && numpolled > 0) - {if ((pollevents = lp->PollEnt->revents)) - {lp->PollEnt->fd = -lp->PollEnt->fd; - if (plp) nlp = plp->Next = lp->Next; - else nlp = PollQ = lp->Next; - numpolled--; lp->inQ = 0; - if (!(pollevents & pollOK)) - Finish(lp, Poll2Text(pollevents)); - if (!(lp->isEnabled)) - XrdLog->Emsg("Poll", "Disabled event occured for", lp->ID); - else {lp->isEnabled = 0; - lp->NextJob = jfirst; jfirst = (XrdJob *)lp; - if (!jlast) jlast=(XrdJob *)lp; - num2sched++; - continue; - } - } - plp = lp; nlp = lp->Next; - } - if (numpolled) Recover(numpolled); - PollMutex.UnLock(); - - // Schedule the polled links - // - if (num2sched == 1) XrdSched->Schedule(jfirst); - else if (num2sched) XrdSched->Schedule(num2sched, jfirst, jlast); - } while(1); -} - -/******************************************************************************/ -/* P r i v a t e M e t h o d s */ -/******************************************************************************/ -/******************************************************************************/ -/* d o D e t a c h */ -/******************************************************************************/ - -void XrdPollPoll::doDetach(int pti) -{ - int lastent; - -// Get some starting values -// - PollMutex.Lock(); - if ((lastent = PollTNum-1) < 0) - {XrdLog->Emsg("Poll","Underflow during detach"); abort();} - - if (pti == lastent) - do {PollTNum--;} while(PollTNum && PollTab[PollTNum-1].fd == -1); - PollMutex.UnLock(); -} - -/******************************************************************************/ -/* d o R e q u e s t s */ -/******************************************************************************/ - -void XrdPollPoll::doRequests(int maxreq) -{ - const char *act; - int pti, ptfd, num2do; - XrdLink *lp; - -// To keep ourselves from being swamped, base request read-aheads on the number -// of pending poll events. -// - num2do = (maxreq < 3 ? -1 : maxreq); - -// Now process all poll table manipulation requests -// - while(num2do-- && getRequest()) - { if (ReqBuff.req == PipeData::Post) - {ReqBuff.Parms.theSem->Post(); - continue; - } - pti = ReqBuff.Parms.Arg.ent; - if ((ptfd = abs(PollTab[pti].fd)) != ReqBuff.Parms.Arg.fd) - {LogEvent(ReqBuff.req, PollTab[pti].fd, ReqBuff.Parms.Arg.fd); - continue; - } - if (!(lp = XrdLink::fd2link(ptfd))) - {LogEvent(ReqBuff.req, -1, ptfd); continue;} - if (ReqBuff.req == PipeData::EnFD) - {PollTab[pti].events = POLLIN | POLLRDNORM; - PollTab[pti].fd = ptfd; - lp->isEnabled = 1; numEnabled++; - act = " enabled fd "; - } - else if (ReqBuff.req == PipeData::DiFD) - {PollTab[pti].fd = -ptfd; - act = " disabled fd "; - lp->isEnabled = 0; - } - else if (ReqBuff.req == PipeData::RmFD) - {PollTab[pti].fd = -1; - doDetach(pti); - act = " detached fd "; - lp->isEnabled = 0; - } - else {XrdLog->Emsg("Poll", "Received an invalid poll pipe request"); - continue; - } - TRACE(POLL, "Poller " <inQ = 0; - plp = 0; nlp = PollQ; - while (nlp && (lp != nlp)) {plp=nlp; nlp = nlp->Next;} - -// If we found the link, remove it. Otherwise complain -// - if (nlp) {if (plp) plp->Next = nlp->Next; - else PollQ = nlp->Next; - PollMutex.UnLock(); - } - else {PollMutex.UnLock(); - XrdLog->Emsg("dqLink", "Link not found in Q", lp->ID); - } -} - -/******************************************************************************/ -/* L o g E v e n t */ -/******************************************************************************/ - -void XrdPollPoll::LogEvent(int req, int pollfd, int cmdfd) -{ - const char *opn, *id1, *id2; - char buff[4096]; - XrdLink *lp; - - if (ReqBuff.req == PipeData::EnFD) opn = "enable"; - else if (ReqBuff.req == PipeData::DiFD) opn = "disable"; - else if (ReqBuff.req == PipeData::RmFD) opn = "detach"; - else opn = "???"; - - if (pollfd < 0) - {sprintf(buff, "poll %d failed; FD %d", PID, cmdfd); - XrdLog->Emsg("Poll", opn, buff, "does not map to a link"); - return; - } - - if ((lp = XrdLink::fd2link(pollfd))) id1 = lp->ID; - else id1 = "unknown"; - if ((lp = XrdLink::fd2link(cmdfd))) id2 = lp->ID; - else id2 = "unknown"; - snprintf(buff, sizeof(buff)-1, - "%d poll fd=%d (%s) not equal %s cmd fd=%d (%s).", - PID, pollfd, id1, opn, cmdfd, id2); - - XrdLog->Emsg("Poll", "cmd/poll mismatch:", buff); -} - -/******************************************************************************/ -/* R e c o v e r */ -/******************************************************************************/ - -void XrdPollPoll::Recover(int numleft) -{ - int i; - XrdLink *lp; - -// Turn off any unaccounted links -// - for (i = 1; i < PollTNum; i++) - if (PollTab[i].revents) - {if (!(lp = XrdLink::fd2link(PollTab[i].fd))) PollTab[i].fd = -1; - else {lp->isEnabled = 0; PollTab[i].fd = -PollTab[i].fd; - XrdLog->Emsg("Poll","Improper poll event for", lp->ID); - } - } -} - -/******************************************************************************/ -/* R e s t a r t */ -/******************************************************************************/ - -void XrdPollPoll::Restart(int ecode) -{ - XrdLink *lp; - -// Issue error message -// - TRACE(POLL, PID <<'-' <Emsg("Poll", errno, "poll"); - -// For any outstanding link here, close the link and detach it -// - PollMutex.Lock(); - while((lp = PollQ)) - {PollQ = lp->Next; - lp->PollEnt->fd = -1; - Finish(lp, "Unexpected polling error"); - XrdSched->Schedule((XrdJob *)lp); - } - PollMutex.UnLock(); -} diff --git a/src/Xrd/XrdProtLoad.cc b/src/Xrd/XrdProtLoad.cc deleted file mode 100644 index c736302df05..00000000000 --- a/src/Xrd/XrdProtLoad.cc +++ /dev/null @@ -1,306 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d P r o t L o a d . c c */ -/* */ -/* (c) 2006 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdOuc/XrdOucPinLoader.hh" -#include "XrdSys/XrdSysError.hh" - -#include "Xrd/XrdLink.hh" -#include "Xrd/XrdPoll.hh" -#include "Xrd/XrdProtLoad.hh" - -#include "XrdVersion.hh" - -#define XRD_TRACE XrdTrace-> -#include "Xrd/XrdTrace.hh" - -/******************************************************************************/ -/* G l o b a l O b j e c t s */ -/******************************************************************************/ - -XrdSysError *XrdProtLoad::XrdLog = 0; -XrdOucTrace *XrdProtLoad::XrdTrace = 0; - -XrdProtocol *XrdProtLoad::ProtoWAN[ProtoMax] = {0}; -XrdProtocol *XrdProtLoad::Protocol[ProtoMax] = {0}; -char *XrdProtLoad::ProtName[ProtoMax] = {0}; -int XrdProtLoad::ProtPort[ProtoMax] = {0}; - -int XrdProtLoad::ProtoCnt = 0; -int XrdProtLoad::ProtWCnt = 0; - -namespace -{ -char *liblist[XrdProtLoad::ProtoMax]; -XrdOucPinLoader *libhndl[XrdProtLoad::ProtoMax]; -int libcnt = 0; -} - -/******************************************************************************/ -/* C o n s t r u c t o r a n d D e s t r u c t o r */ -/******************************************************************************/ - - XrdProtLoad::XrdProtLoad(int port) : - XrdProtocol("protocol loader"), myPort(port) {} - - XrdProtLoad::~XrdProtLoad() {} - -/******************************************************************************/ -/* L o a d */ -/******************************************************************************/ - -int XrdProtLoad::Load(const char *lname, const char *pname, - char *parms, XrdProtocol_Config *pi) -{ - XrdProtocol *xp; - int i, j, port = pi->Port; - int wanopt = pi->WANPort; - -// Trace this load if so wanted -// - if (TRACING(TRACE_DEBUG)) - {XrdTrace->Beg("Protocol"); - cerr <<"getting protocol object " <End(); - } - -// First check to see that we haven't exceeded our protocol count -// - if (ProtoCnt >= ProtoMax) - {XrdLog->Emsg("Protocol", "Too many protocols have been defined."); - return 0; - } - -// Obtain an instance of this protocol -// - xp = getProtocol(lname, pname, parms, pi); - if (!xp) {XrdLog->Emsg("Protocol","Protocol", pname, "could not be loaded"); - return 0; - } - -// If this is a WAN enabled protocol then add it to the WAN table -// - if (wanopt) ProtoWAN[ProtWCnt++] = xp; - -// Find a port associated slot in the table -// - for (i = ProtoCnt-1; i >= 0; i--) if (port == ProtPort[i]) break; - for (j = ProtoCnt-1; j > i; j--) - {ProtName[j+1] = ProtName[j]; - ProtPort[j+1] = ProtPort[j]; - Protocol[j+1] = Protocol[j]; - } - -// Add protocol to our table of protocols -// - ProtName[j+1] = strdup(pname); - ProtPort[j+1] = port; - Protocol[j+1] = xp; - ProtoCnt++; - return 1; -} - -/******************************************************************************/ -/* P o r t */ -/******************************************************************************/ - -int XrdProtLoad::Port(const char *lname, const char *pname, - char *parms, XrdProtocol_Config *pi) -{ - int port; - -// Trace this load if so wanted -// - if (TRACING(TRACE_DEBUG)) - {XrdTrace->Beg("Protocol"); - cerr <<"getting port from protocol " <End(); - } - -// Obtain the port number to be used by this protocol -// - port = getProtocolPort(lname, pname, parms, pi); - if (port < 0) XrdLog->Emsg("Protocol","Protocol", pname, - "port number could not be determined"); - return port; -} - -/******************************************************************************/ -/* P r o c e s s */ -/******************************************************************************/ - -int XrdProtLoad::Process(XrdLink *lp) -{ - XrdProtocol *pp = 0; - int i; - -// Check if this is a WAN lookup or standard lookup -// - if (myPort < 0) - {for (i = 0; i < ProtWCnt; i++) - if ((pp = ProtoWAN[i]->Match(lp))) break; - else if (lp->isFlawed()) return -1; - } else { - for (i = 0; i < ProtoCnt; i++) - if (myPort == ProtPort[i] && (pp = Protocol[i]->Match(lp))) break; - else if (lp->isFlawed()) return -1; - } - if (!pp) {lp->setEtext("matching protocol not found"); return -1;} - -// Now attach the new protocol object to the link -// - lp->setProtocol(pp); - -// Trace this load if so wanted -// x - if (TRACING(TRACE_DEBUG)) - {XrdTrace->Beg("Protocol"); - cerr <<"matched protocol " <End(); - } - -// Attach this link to the appropriate poller -// - if (!XrdPoll::Attach(lp)) {lp->setEtext("attach failed"); return -1;} - -// Take a short-cut and process the initial request as a sticky request -// - return pp->Process(lp); -} - -/******************************************************************************/ -/* R e c y c l e */ -/******************************************************************************/ - -void XrdProtLoad::Recycle(XrdLink *lp, int ctime, const char *reason) -{ - -// Document non-protocol errors -// - if (lp && reason) - XrdLog->Emsg("Protocol", lp->ID, "terminated", reason); -} - -/******************************************************************************/ -/* S t a t i s t i c s */ -/******************************************************************************/ - -int XrdProtLoad::Statistics(char *buff, int blen, int do_sync) -{ - int i, k, totlen = 0; - - for (i = 0; i < ProtoCnt && (blen > 0 || !buff); i++) - {k = Protocol[i]->Stats(buff, blen, do_sync); - totlen += k; buff += k; blen -= k; - } - - return totlen; -} - -/******************************************************************************/ -/* P r i v a t e M e t h o d s */ -/******************************************************************************/ -/******************************************************************************/ -/* g e t P r o t o c o l */ -/******************************************************************************/ - -extern "C" XrdProtocol *XrdgetProtocol(const char *pname, char *parms, - XrdProtocol_Config *pi); - -XrdProtocol *XrdProtLoad::getProtocol(const char *lname, - const char *pname, - char *parms, - XrdProtocol_Config *pi) -{ - XrdProtocol *(*ep)(const char *, char *, XrdProtocol_Config *); - const char *xname = (lname ? lname : ""); - void *epvoid; - int i; - -// If this is a builtin protocol getthe protocol object directly -// - if (!lname) return XrdgetProtocol(pname, parms, pi); - -// Find the matching library. It must be here because getPort was already called -// - for (i = 0; i < libcnt; i++) if (!strcmp(xname, liblist[i])) break; - if (i >= libcnt) - {XrdLog->Emsg("Protocol", pname, "was lost during loading", lname); - return 0; - } - -// Obtain an instance of the protocol object and return it -// - if (!(epvoid = libhndl[i]->Resolve("XrdgetProtocol"))) return 0; - ep = (XrdProtocol *(*)(const char*,char*,XrdProtocol_Config*))epvoid; - return ep(pname, parms, pi); -} - -/******************************************************************************/ -/* g e t P r o t o c o l P o r t */ -/******************************************************************************/ - - extern "C" int XrdgetProtocolPort(const char *pname, char *parms, - XrdProtocol_Config *pi); - -int XrdProtLoad::getProtocolPort(const char *lname, - const char *pname, - char *parms, - XrdProtocol_Config *pi) -{ - static XrdVERSIONINFODEF(myVer, xrd, XrdVNUMBER, XrdVERSION); - const char *xname = (lname ? lname : ""); - int (*ep)(const char *, char *, XrdProtocol_Config *); - void *epvoid; - int i; - -// If this is for the builtin protocol then get the port directly -// - if (!lname) return XrdgetProtocolPort(pname, parms, pi); - -// See if the library is already opened, if not open it -// - for (i = 0; i < libcnt; i++) if (!strcmp(xname, liblist[i])) break; - if (i >= libcnt) - {if (libcnt >= ProtoMax) - {XrdLog->Emsg("Protocol", "Too many protocols have been defined."); - return -1; - } - if (!(libhndl[i] = new XrdOucPinLoader(XrdLog,&myVer,"protocol",lname))) - return -1; - liblist[i] = strdup(xname); - libcnt++; - } - -// Get the port number to be used -// - if (!(epvoid = libhndl[i]->Resolve("XrdgetProtocolPort", 2))) - return (pi->Port < 0 ? 0 : pi->Port); - ep = (int (*)(const char*,char*,XrdProtocol_Config*))epvoid; - return ep(pname, parms, pi); -} diff --git a/src/Xrd/XrdProtLoad.hh b/src/Xrd/XrdProtLoad.hh deleted file mode 100644 index ca321c2f98d..00000000000 --- a/src/Xrd/XrdProtLoad.hh +++ /dev/null @@ -1,85 +0,0 @@ -#ifndef __XrdProtLoad_H__ -#define __XrdProtLoad_H__ -/******************************************************************************/ -/* */ -/* X r d P r o t L o a d . h h */ -/* */ -/* (c) 2006 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "Xrd/XrdProtocol.hh" - -// This class load and allows the selection of the appropriate link protocol. -// -class XrdProtLoad : public XrdProtocol -{ -public: - -void DoIt() {} - -static void Init(XrdSysError *eP, XrdOucTrace *tP) - {XrdLog = eP; XrdTrace = tP;} - -static int Load(const char *lname, const char *pname, char *parms, - XrdProtocol_Config *pi); - -static int Port(const char *lname, const char *pname, char *parms, - XrdProtocol_Config *pi); - -XrdProtocol *Match(XrdLink *) {return 0;} - -int Process(XrdLink *lp); - -void Recycle(XrdLink *lp, int ctime, const char *txt); - -int Stats(char *buff, int blen, int do_sync=0) {return 0;} - -static int Statistics(char *buff, int blen, int do_sync=0); - - XrdProtLoad(int port=-1); - ~XrdProtLoad(); - -static const int ProtoMax = 8; - -private: - -static XrdProtocol *getProtocol (const char *lname, const char *pname, - char *parms, XrdProtocol_Config *pi); -static int getProtocolPort(const char *lname, const char *pname, - char *parms, XrdProtocol_Config *pi); - -static XrdSysError *XrdLog; -static XrdOucTrace *XrdTrace; - -static char *ProtName[ProtoMax]; // ->Supported protocol names -static XrdProtocol *Protocol[ProtoMax]; // ->Supported protocol objects -static int ProtPort[ProtoMax]; // ->Supported protocol ports -static XrdProtocol *ProtoWAN[ProtoMax]; // ->Supported protocol objects WAN -static int ProtoCnt; // Number in table (at least 1) -static int ProtWCnt; // Number in table (WAN may be 0) - - int myPort; -}; -#endif diff --git a/src/Xrd/XrdProtocol.cc b/src/Xrd/XrdProtocol.cc deleted file mode 100644 index 3872ac7b721..00000000000 --- a/src/Xrd/XrdProtocol.cc +++ /dev/null @@ -1,73 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d P r o t o c o l . c c */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include - -#include "XrdNet/XrdNetSockAddr.hh" -#include "Xrd/XrdProtocol.hh" - -/******************************************************************************/ -/* X r d P r o t o c o l _ C o n f i g C o p y C o n s t r u c t o r */ -/******************************************************************************/ - -XrdProtocol_Config::XrdProtocol_Config(XrdProtocol_Config &rhs) -{ -eDest = rhs.eDest; -NetTCP = rhs.NetTCP; -BPool = rhs.BPool; -Sched = rhs.Sched; -Stats = rhs.Stats; -theEnv = rhs.theEnv; -Trace = rhs.Trace; - -ConfigFN = rhs.ConfigFN ? strdup(rhs.ConfigFN) : 0; -Format = rhs.Format; -Port = rhs.Port; -WSize = rhs.WSize; -AdmPath = rhs.AdmPath ? strdup(rhs.AdmPath) : 0; -AdmMode = rhs.AdmMode; -myInst = rhs.myInst ? strdup(rhs.myInst) : 0; -myName = rhs.myName ? strdup(rhs.myName) : 0; - if (!rhs.urAddr) urAddr = 0; - else {urAddr = (XrdNetSockAddr *)malloc(sizeof(XrdNetSockAddr)); - memcpy((void *)urAddr, rhs.urAddr, sizeof(XrdNetSockAddr)); - } -ConnMax = rhs.ConnMax; -readWait = rhs.readWait; -idleWait = rhs.idleWait; -hailWait = rhs.hailWait; -argc = rhs.argc; -argv = rhs.argv; -DebugON = rhs.DebugON; -WANPort = rhs.WANPort; -WANWSize = rhs.WANWSize; -myProg = rhs.myProg; -} diff --git a/src/Xrd/XrdProtocol.hh b/src/Xrd/XrdProtocol.hh deleted file mode 100644 index ff6c9014020..00000000000 --- a/src/Xrd/XrdProtocol.hh +++ /dev/null @@ -1,213 +0,0 @@ -#ifndef __XrdProtocol_H__ -#define __XrdProtocol_H__ -/******************************************************************************/ -/* */ -/* X r d P r o t o c o l . h h */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "Xrd/XrdJob.hh" - -/******************************************************************************/ -/* X r d P r o t o c o l _ C o n f i g */ -/******************************************************************************/ - -// The following class is passed to the XrdgetProtocol() and XrdgetProtocolPort() -// functions to properly configure the protocol. This object is not stable and -// the protocol must copy out any values it desires to keep. It may copy the -// whole object using the supplied copy constructor. - -class XrdSysError; -union XrdNetSockAddr; -class XrdOucEnv; -class XrdOucTrace; -class XrdBuffManager; -class XrdInet; -class XrdScheduler; -class XrdStats; - -struct sockaddr; - -class XrdProtocol_Config -{ -public: - -// The following pointers may be copied; they are stable. -// -XrdSysError *eDest; // Stable -> Error Message/Logging Handler -XrdInet *NetTCP; // Stable -> Network Object (@ XrdgetProtocol) -XrdBuffManager *BPool; // Stable -> Buffer Pool Manager -XrdScheduler *Sched; // Stable -> System Scheduler -XrdStats *Stats; // Stable -> System Statistics (@ XrdgetProtocol) -XrdOucEnv *theEnv; // Stable -> Additional environmental information -XrdOucTrace *Trace; // Stable -> Trace Information - -// The following information must be duplicated; it is unstable. -// -char *ConfigFN; // -> Configuration file -int Format; // Binary format of this server -int Port; // Port number -int WSize; // Window size for Port -const char *AdmPath; // Admin path -int AdmMode; // Admin path mode -const char *myInst; // Instance name -const char *myName; // Host name -const char *myProg; // Program name -union { -const -XrdNetSockAddr *urAddr; // Host Address (the actual structure/union) -const -struct sockaddr *myAddr; // Host address - }; -int ConnMax; // Max connections -int readWait; // Max milliseconds to wait for data -int idleWait; // Max milliseconds connection may be idle -int argc; // Number of arguments -char **argv; // Argument array (prescreened) -char DebugON; // True if started with -d option -int WANPort; // Port prefered for WAN connections (0 if none) -int WANWSize; // Window size for the WANPort -int hailWait; // Max milliseconds to wait for data after accept - - XrdProtocol_Config(XrdProtocol_Config &rhs); - XrdProtocol_Config() {} - ~XrdProtocol_Config() {} -}; - -/******************************************************************************/ -/* X r d P r o t o c o l */ -/******************************************************************************/ - -// This class is used by the Link object to process the input stream on a link. -// At least one protocol object exists per Link object. Specific protocols are -// derived from this pure abstract class since a link can use one of several -// protocols. Indeed, startup and shutdown are handled by specialized protocols. - -// System configuration obtains an instance of a protocol by calling -// XrdgetProtocol(), which must exist in the shared library. -// This instance is used as the base pointer for Alloc(), Configure(), and -// Match(). Unfortuantely, they cannot be static given the silly C++ rules. - -class XrdLink; - -class XrdProtocol : public XrdJob -{ -public: - -// Match() is invoked when a new link is created and we are trying -// to determine if this protocol can handle the link. It must -// return a protocol object if it can and NULL (0), otherwise. -// -virtual XrdProtocol *Match(XrdLink *lp) = 0; - -// Process() is invoked when a link has data waiting to be read -// -virtual int Process(XrdLink *lp) = 0; - -// Recycle() is invoked when this object is no longer needed. The method is -// passed the number of seconds the protocol was connected to the -// link and the reason for the disconnection, if any. -// -virtual void Recycle(XrdLink *lp=0,int consec=0,const char *reason=0)=0; - -// Stats() is invoked when we need statistics about all instances of the -// protocol. If a buffer is supplied, it must return a null -// terminated string in the supplied buffer and the return value -// is the number of bytes placed in the buffer defined by C99 for -// snprintf(). If no buffer is supplied, the method should return -// the maximum number of characters that could have been returned. -// Regardless of the buffer value, if do_sync is true, the method -// should include any local statistics in the global data (if any) -// prior to performing any action. -// -virtual int Stats(char *buff, int blen, int do_sync=0) = 0; - - XrdProtocol(const char *jname): XrdJob(jname) {} -virtual ~XrdProtocol() {} -}; - -/******************************************************************************/ -/* X r d g e t P r o t o c o l */ -/******************************************************************************/ - -/* This extern "C" function must be defined in the shared library plug-in - implementing your protocol. It is called to obtain an instance of your - protocol. This allows protocols to live outside of the protocol driver - (i.e., to be loaded at run-time). The call is made after the call to - XrdgetProtocolPort() to determine the port to be used (see below) which - allows e network object (NetTCP) to be proerly defined and it's pointer - is passed in the XrdProtocol_Config object for your use. - - Required return values: - Success: Pointer to XrdProtocol object. - Failure: Null pointer (i.e. 0) which causes the program to exit. - -extern "C" // This is in a comment! -{ - XrdProtocol *XrdgetProtocol(const char *protocol_name, char *parms, - XrdProtocol_Config *pi) {....} -} -*/ - -/******************************************************************************/ -/* X r d g e t P r o t o c o l P o r t */ -/******************************************************************************/ - -/* This extern "C" function must be defined for statically linked protocols - but is optional for protocols defined as a shared library plug-in if the - rules determining which port number to use is sufficient for your protocol. - The function is called to obtain the actual port number to be used by the - the protocol. The default port number is noted in XrdProtocol_Config Port. - Initially, it has one of the fllowing values: - <0 -> No port was specified. - =0 -> An erbitrary port will be assigned. - >0 -> This port number was specified. - - XrdgetProtoclPort() must return: - <0 -> Failure is indicated and we terminate - =0 -> Use an arbitrary port (even if this equals Port) - >0 -> The returned port number must be used (even if it equals Port) - - When we finally call XrdgetProtocol(), the actual port number is indicated - in Port and the network object is defined in NetTCP and bound to the port. - - Final Caveats: 1. The network object (NetTCP) is not defined until - XrdgetProtocol() is called. - - 2. The statistics object (Stats) is not defined until - XrdgetProtocol() is called. - - 3. When the protocol is loaded from a shared library, you need - need not define XrdgetProtocolPort() if the standard port - determination scheme is sufficient. - -extern "C" // This is in a comment! -{ - int XrdgetProtocolPort(const char *protocol_name, char *parms, - XrdProtocol_Config *pi) {....} -} -*/ -#endif diff --git a/src/Xrd/XrdScheduler.cc b/src/Xrd/XrdScheduler.cc deleted file mode 100644 index 4a5dae63375..00000000000 --- a/src/Xrd/XrdScheduler.cc +++ /dev/null @@ -1,669 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S c h e d u l e r . c c */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#ifdef __APPLE__ -#include -#endif - -#include "Xrd/XrdJob.hh" -#include "Xrd/XrdScheduler.hh" -#include "XrdSys/XrdSysError.hh" - -#define XRD_TRACE XrdTrace-> -#include "Xrd/XrdTrace.hh" - -/******************************************************************************/ -/* S t a t i c O b j e c t s */ -/******************************************************************************/ - - const char *XrdScheduler::TraceID = "Sched"; - -/******************************************************************************/ -/* L o c a l C l a s s e s */ -/******************************************************************************/ - -class XrdSchedulerPID - {public: - XrdSchedulerPID *next; - pid_t pid; - - XrdSchedulerPID(pid_t newpid, XrdSchedulerPID *prev) - {next = prev; pid = newpid;} - ~XrdSchedulerPID() {} - }; - -/******************************************************************************/ -/* E x t e r n a l T h r e a d I n t e r f a c e s */ -/******************************************************************************/ - -void *XrdStartReaper(void *carg) - {XrdScheduler *sp = (XrdScheduler *)carg; - sp->Reaper(); - return (void *)0; - } - -void *XrdStartTSched(void *carg) - {XrdScheduler *sp = (XrdScheduler *)carg; - sp->TimeSched(); - return (void *)0; - } - -void *XrdStartWorking(void *carg) - {XrdScheduler *sp = (XrdScheduler *)carg; - sp->Run(); - return (void *)0; - } - -/******************************************************************************/ -/* C o n s t r u c t o r */ -/******************************************************************************/ - -XrdScheduler::XrdScheduler(XrdSysError *eP, XrdOucTrace *tP, - int minw, int maxw, int maxi) - : XrdJob("underused thread monitor"), - WorkAvail(0, "sched work") -{ - struct rlimit rlim; - - XrdLog = eP; - XrdTrace = tP; - min_Workers = minw; - max_Workers = maxw; - max_Workidl = maxi; - num_Workers = 0; - num_JobsinQ = 0; - stk_Workers = maxw - (maxw/4*3); - idl_Workers = 0; - num_Jobs = 0; - max_QLength = 0; - num_TCreate = 0; - num_TDestroy= 0; - num_Layoffs = 0; - num_Limited = 0; - firstPID = 0; - WorkFirst = WorkLast = TimerQueue = 0; - -// Make sure we are using the maximum number of threads allowed (Linux only) -// -#if defined(__linux__) && defined(RLIMIT_NPROC) - -// First determine the absolute maximum we can have -// - rlim_t theMax = MAX_SCHED_PROCS; - int pdFD, rdsz; - if ((pdFD = open("/proc/sys/kernel/pid_max", O_RDONLY)) >= 0) - {char pmBuff[32]; - if ((rdsz = read(pdFD, pmBuff, sizeof(pmBuff))) > 0) - {rdsz = atoi(pmBuff); - if (rdsz < 16384) theMax = 16384; // This is unlikely - else if (rdsz < MAX_SCHED_PROCS) - theMax = static_cast(rdsz-2000); - } - close(pdFD); - } - -// Get the resource thread limit and set to maximum. In Linux this may be -1 -// to indicate useless infnity, so we have to come up with a number, sigh. -// - if (!getrlimit(RLIMIT_NPROC, &rlim)) - {if (rlim.rlim_max == RLIM_INFINITY || rlim.rlim_max > theMax) - {rlim.rlim_cur = theMax; - setrlimit(RLIMIT_NPROC, &rlim); - } else { - if (rlim.rlim_cur != rlim.rlim_max) - {rlim.rlim_cur = rlim.rlim_max; - setrlimit(RLIMIT_NPROC, &rlim); - } - } - } - -// Readjust our internal maximum to be the actual maximum -// - if (!getrlimit(RLIMIT_NPROC, &rlim)) - {if (rlim.rlim_cur == RLIM_INFINITY || rlim.rlim_cur > theMax) - max_Workers = static_cast(theMax); - else max_Workers = static_cast(rlim.rlim_cur); - } -#endif - -} - -/******************************************************************************/ -/* D e s t r u c t o r */ -/******************************************************************************/ - -XrdScheduler::~XrdScheduler() // The scheduler is never deleted! -{ -} - -/******************************************************************************/ -/* C a n c e l */ -/******************************************************************************/ - -void XrdScheduler::Cancel(XrdJob *jp) -{ - XrdJob *p, *pp = 0; - -// Lock the queue -// - TimerMutex.Lock(); - -// Find the matching job, if any -// - p = TimerQueue; - while(p && p != jp) {pp = p; p = p->NextJob;} - -// Delete the job element -// - if (p) - {if (pp) pp->NextJob = p->NextJob; - else TimerQueue = p->NextJob; - TRACE(SCHED, "time event " <Comment <<" cancelled"); - } - -// All done -// - TimerMutex.UnLock(); -} - -/******************************************************************************/ -/* D o I t */ -/******************************************************************************/ - -void XrdScheduler::DoIt() -{ - int num_kill, num_idle; - -// Now check if there are too many idle threads (kill them if there are) -// - if (!num_JobsinQ) - {DispatchMutex.Lock(); num_idle = idl_Workers; DispatchMutex.UnLock(); - num_kill = num_idle - min_Workers; - TRACE(SCHED, num_Workers <<" threads; " < 0) - {if (num_kill > 1) num_kill = num_kill/2; - SchedMutex.Lock(); - num_Layoffs = num_kill; - while(num_kill--) WorkAvail.Post(); - SchedMutex.UnLock(); - } - } - -// Check if we should reschedule ourselves -// - if (max_Workidl > 0) Schedule((XrdJob *)this, max_Workidl+time(0)); -} - -/******************************************************************************/ -/* F o r k */ -/******************************************************************************/ - -// This entry exists solely so that we can start a reaper thread for processes -// -pid_t XrdScheduler::Fork(const char *id) -{ - static int retc, ReaperStarted = 0; - pthread_t tid; - pid_t pid; - -// Fork -// - if ((pid = fork()) < 0) - {XrdLog->Emsg("Scheduler",errno,"fork to handle",id); - return pid; - } - if (!pid) return pid; - -// Obtain the status of the reaper thread. -// - ReaperMutex.Lock(); - firstPID = new XrdSchedulerPID(pid, firstPID); - retc = ReaperStarted; - ReaperStarted = 1; - ReaperMutex.UnLock(); - -// Start the reaper thread if it has not started. -// - if (!retc) - if ((retc = XrdSysThread::Run(&tid, XrdStartReaper, (void *)this, - 0, "Process reaper"))) - {XrdLog->Emsg("Scheduler", retc, "create reaper thread"); - ReaperStarted = 0; - } - - return pid; -} - -/******************************************************************************/ -/* R e a p e r */ -/******************************************************************************/ - -void *XrdScheduler::Reaper() -{ - int status; - pid_t pid; - XrdSchedulerPID *tp, *ptp, *xtp; -#if defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_5) - struct timespec ts = { 1, 0 }; -#else - sigset_t Sset; - int signum; - -// Set up for signal handling. Note: main() must block this signal at start) -// - sigemptyset(&Sset); - sigaddset(&Sset, SIGCHLD); -#endif - -// Wait for all outstanding children -// - do {ReaperMutex.Lock(); - tp = firstPID; ptp = 0; - while(tp) - {do {pid = waitpid(tp->pid, &status, WNOHANG);} - while (pid < 0 && errno == EINTR); - if (pid > 0) - {if (TRACING(TRACE_SCHED)) traceExit(pid, status); - xtp = tp; tp = tp->next; - if (ptp) ptp->next = tp; - else firstPID = tp; - delete xtp; - } else {ptp = tp; tp = tp->next;} - } - ReaperMutex.UnLock(); -#if defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_5) - // Mac OS X sigwait() is broken on <= 10.4. - } while (nanosleep(&ts, 0) <= 0); -#else - } while(sigwait(&Sset, &signum) >= 0); -#endif - return (void *)0; -} - -/******************************************************************************/ -/* R u n */ -/******************************************************************************/ - -void XrdScheduler::Run() -{ - int waiting; - XrdJob *jp; - -// Wait for work then do it (an endless task for a worker thread) -// - do {do {DispatchMutex.Lock(); idl_Workers++;DispatchMutex.UnLock(); - WorkAvail.Wait(); - DispatchMutex.Lock();waiting = --idl_Workers;DispatchMutex.UnLock(); - SchedMutex.Lock(); - if ((jp = WorkFirst)) - {if (!(WorkFirst = jp->NextJob)) WorkLast = 0; - if (num_JobsinQ) num_JobsinQ--; - else XrdLog->Emsg("Scheduler","Job queue count underflow!"); - } else { - num_JobsinQ = 0; - if (num_Layoffs > 0) - {num_Layoffs--; - if (waiting) - {num_TDestroy++; num_Workers--; - TRACE(SCHED, "terminating thread; workers=" <Comment) != '.') - {TRACE(SCHED, "running " <Comment <<" inq=" <DoIt(); - } while(1); -} - -/******************************************************************************/ -/* S c h e d u l e */ -/******************************************************************************/ - -void XrdScheduler::Schedule(XrdJob *jp) -{ -// Lock down our data area -// - SchedMutex.Lock(); - -// Place the request on the queue and broadcast it -// - jp->NextJob = 0; - if (WorkFirst) - {WorkLast->NextJob = jp; - WorkLast = jp; - } else { - WorkFirst = jp; - WorkLast = jp; - } - WorkAvail.Post(); - -// Calculate statistics -// - num_Jobs++; - num_JobsinQ++; - if (num_JobsinQ > max_QLength) max_QLength = num_JobsinQ; - -// Unlock the data area and return -// - SchedMutex.UnLock(); -} - -/******************************************************************************/ - -void XrdScheduler::Schedule(int numjobs, XrdJob *jfirst, XrdJob *jlast) -{ - -// Lock down our data area -// - SchedMutex.Lock(); - -// Place the request list on the queue -// - jlast->NextJob = 0; - if (WorkFirst) - {WorkLast->NextJob = jfirst; - WorkLast = jlast; - } else { - WorkFirst = jfirst; - WorkLast = jlast; - } - -// Calculate statistics -// - num_Jobs += numjobs; - num_JobsinQ += numjobs; - if (num_JobsinQ > max_QLength) max_QLength = num_JobsinQ; - -// Indicate number of jobs to work on -// - while(numjobs--) WorkAvail.Post(); - -// Unlock the data area and return -// - SchedMutex.UnLock(); -} - -/******************************************************************************/ - -void XrdScheduler::Schedule(XrdJob *jp, time_t atime) -{ - XrdJob *pp = 0, *p; - -// Cancel this event, if scheduled -// - Cancel(jp); - -// Lock the queue -// - if (TRACING(TRACE_SCHED) && *(jp->Comment) != '.') - {TRACE(SCHED, "scheduling " <Comment <<" in " <SchedTime = atime; - TimerMutex.Lock(); - -// Find the insertion point for the work element -// - p = TimerQueue; - while(p && p->SchedTime <= atime) {pp = p; p = p->NextJob;} - -// Insert the job element -// - jp->NextJob = p; - if (pp) pp->NextJob = jp; - else {TimerQueue = jp; TimerRings.Signal();} - -// All done -// - TimerMutex.UnLock(); -} - -/******************************************************************************/ -/* s e t P a r m s */ -/******************************************************************************/ - -void XrdScheduler::setParms(int minw, int maxw, int avlw, int maxi, int once) -{ - static int isSet = 0; - -// Lock the data area and check for 1-time set -// - SchedMutex.Lock(); - if (once && isSet) {SchedMutex.UnLock(); return;} - isSet = 1; - -// get a consistent view of all the values -// - if (maxw <= 0) maxw = max_Workers; - if (minw < 0) minw = min_Workers; - if (minw > maxw) minw = maxw; - if (avlw < 0) avlw = maxw/4*3; - else if (avlw > maxw) avlw = maxw; - -// Set the values -// - min_Workers = minw; - max_Workers = maxw; - stk_Workers = maxw - avlw; - if (maxi >=0) max_Workidl = maxi; - -// Unlock the data area -// - SchedMutex.UnLock(); - -// If we have an idle interval, schedule the idle check -// - if (maxi > 0) - {Cancel((XrdJob *)this); - Schedule((XrdJob *)this, (time_t)maxi+time(0)); - } - -// Debug the info -// - TRACE(SCHED,"Set min_Workers=" <Emsg("Scheduler", retc, "create time scheduler thread"); - -// If we an idle interval, schedule the idle check -// - if (max_Workidl > 0) Schedule((XrdJob *)this, (time_t)max_Workidl+time(0)); - -// Start 1/3 of the minimum number of threads -// - if (!(numw = min_Workers/3)) numw = 2; - while(numw--) hireWorker(0); - -// Unlock the data area -// - TRACE(SCHED, "Starting with " <%d" - "%d%d" - "%d%d" - "%d%d" - "%d"; - -// If only length wanted, do so -// - if (!buff) return sizeof(statfmt) + 16*8; - -// Get values protected by the Dispatch lock (avoid lock if no sync needed) -// - if (do_sync) DispatchMutex.Lock(); - cnt_idl = idl_Workers; - if (do_sync) DispatchMutex.UnLock(); - -// Get values protected by the Scheduler lock (avoid lock if no sync needed) -// - if (do_sync) SchedMutex.Lock(); - cnt_Workers = num_Workers; - cnt_Jobs = num_Jobs; - cnt_JobsinQ = num_JobsinQ; - xam_QLength = max_QLength; - cnt_TCreate = num_TCreate; - cnt_TDestroy= num_TDestroy; - cnt_Limited = num_Limited; - if (do_sync) SchedMutex.UnLock(); - -// Format the stats and return them -// - return snprintf(buff, blen, statfmt, cnt_Jobs, cnt_JobsinQ, xam_QLength, - cnt_Workers, cnt_idl, cnt_TCreate, cnt_TDestroy, - cnt_Limited); -} - -/******************************************************************************/ -/* T i m e S c h e d */ -/******************************************************************************/ - -void XrdScheduler::TimeSched() -{ - XrdJob *jp; - int wtime; - -// Continuous loop until we find some work here -// - do {TimerMutex.Lock(); - if (TimerQueue) wtime = TimerQueue->SchedTime-time(0); - else wtime = 60*60; - if (wtime > 0) - {TimerMutex.UnLock(); - TimerRings.Wait(wtime); - } else { - jp = TimerQueue; - TimerQueue = jp->NextJob; - Schedule(jp); - TimerMutex.UnLock(); - } - } while(1); -} - -/******************************************************************************/ -/* P r i v a t e M e t h o d s */ -/******************************************************************************/ -/******************************************************************************/ -/* h i r e W o r k e r */ -/******************************************************************************/ - -void XrdScheduler::hireWorker(int dotrace) -{ - pthread_t tid; - int retc; - -// First check if we reached the maximum number of workers -// - SchedMutex.Lock(); - if (num_Workers >= max_Workers) - {num_Limited++; - if ((num_Limited & 4095) == 1) - XrdLog->Emsg("Scheduler","Thread limit has been reached!"); - SchedMutex.UnLock(); - return; - } - num_Workers++; - num_TCreate++; - SchedMutex.UnLock(); - -// Start a new thread. We do this without the schedMutex to avoid hang-ups. If -// we can't start a new thread, we recalculate the maximum number we can. -// - retc = XrdSysThread::Run(&tid, XrdStartWorking, (void *)this, 0, "Worker"); - -// Now check the results and correct if we couldn't start the thread -// - if (retc) - {XrdLog->Emsg("Scheduler", retc, "create worker thread"); - SchedMutex.Lock(); - num_Workers--; - num_TCreate--; - max_Workers = num_Workers; - min_Workers = (max_Workers/10 ? max_Workers/10 : 1); - stk_Workers = max_Workers/4*3; - SchedMutex.UnLock(); - } else if (dotrace) TRACE(SCHED, "Now have " <. */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -#include "XrdSys/XrdSysPthread.hh" -#include "Xrd/XrdJob.hh" - -class XrdOucTrace; -class XrdSchedulerPID; -class XrdSysError; - -#define MAX_SCHED_PROCS 30000 - -class XrdScheduler : public XrdJob -{ -public: - -int Active() {return num_Workers - idl_Workers + num_JobsinQ;} - -void Cancel(XrdJob *jp); - -inline int canStick() {return num_Workers < stk_Workers - || (num_Workers-idl_Workers) < stk_Workers;} - -void DoIt(); - -pid_t Fork(const char *id); - -void *Reaper(); - -void Run(); - -void Schedule(XrdJob *jp); -void Schedule(int num, XrdJob *jfirst, XrdJob *jlast); -void Schedule(XrdJob *jp, time_t atime); - -void setParms(int minw, int maxw, int avlt, int maxi, int once=0); - -void Start(); - -int Stats(char *buff, int blen, int do_sync=0); - -void TimeSched(); - -// Statistical information -// -int num_TCreate; // Number of threads created -int num_TDestroy;// Number of threads destroyed -int num_Jobs; // Number of jobs scheduled -int max_QLength; // Longest queue length we had -int num_Limited; // Number of times max was reached - -// Constructor and destructor -// - XrdScheduler(XrdSysError *eP, XrdOucTrace *tP, - int minw=8, int maxw=8192, int maxi=780); - - ~XrdScheduler(); - -private: -XrdSysError *XrdLog; -XrdOucTrace *XrdTrace; - -XrdSysMutex DispatchMutex; // Disp: Protects above area -int idl_Workers; // Disp: Number of idle workers - -int min_Workers; // Sched: Min threads we need to have -int max_Workers; // Sched: Max threads we can start -int max_Workidl; // Sched: Max idle time for threads above min_Workers -int num_Workers; // Sched: Number of threads we have -int stk_Workers; // Sched: Number of sticky workers we can have -int num_JobsinQ; // Sched: Number of outstanding jobs in the queue -int num_Layoffs; // Sched: Number of threads to terminate - -XrdJob *WorkFirst; // Pending work -XrdJob *WorkLast; -XrdSysSemaphore WorkAvail; -XrdSysMutex SchedMutex; // Protects private area - -XrdJob *TimerQueue; // Pending work -XrdSysCondVar TimerRings; -XrdSysMutex TimerMutex; // Protects scheduler area - -XrdSchedulerPID *firstPID; -XrdSysMutex ReaperMutex; - -void hireWorker(int dotrace=1); -void Monitor(); -void traceExit(pid_t pid, int status); -static const char *TraceID; -}; -#endif diff --git a/src/Xrd/XrdSendQ.cc b/src/Xrd/XrdSendQ.cc deleted file mode 100644 index 9cb1c366d55..00000000000 --- a/src/Xrd/XrdSendQ.cc +++ /dev/null @@ -1,403 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S e n d Q . c c */ -/* */ -/* (c) 2016 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include - -#include "Xrd/XrdLink.hh" -#include "Xrd/XrdScheduler.hh" -#include "Xrd/XrdSendQ.hh" - -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysPthread.hh" - -/******************************************************************************/ -/* L o c a l C l a s s e s */ -/******************************************************************************/ - -class LinkShutdown : public XrdJob -{ -public: - -virtual void DoIt() {myLink->Shutdown(true); - myLink->setRef(-1); - delete this; - } - - LinkShutdown(XrdLink *link) - : XrdJob("SendQ Shutdown"), myLink(link) {} - -virtual ~LinkShutdown() {} - -private: - -XrdLink *myLink; -}; - -/******************************************************************************/ -/* S t a t i c O b j e c t s */ -/******************************************************************************/ - -XrdScheduler *XrdSendQ::Sched = 0; -XrdSysError *XrdSendQ::Say = 0; -unsigned int XrdSendQ::qWarn = 3; -unsigned int XrdSendQ::qMax = 0xffffffff; -bool XrdSendQ::qPerm = false; - -/******************************************************************************/ -/* C o n s t r u c t o r */ -/******************************************************************************/ - -XrdSendQ::XrdSendQ(XrdLink &lP, XrdSysMutex &mP) - : XrdJob("sendQ runner"), - mLink(lP), wMutex(mP), - fMsg(0), lMsg(0), delQ(0), theFD(lP.FDnum()), - inQ(0), qWmsg(qWarn), discards(0), - active(false), terminate(false) {} - -/******************************************************************************/ -/* D o I t */ -/******************************************************************************/ - -void XrdSendQ::DoIt() -{ - mBuff *theMsg; - int myFD, rc; - bool theEnd; - -// Obtain the lock -// - wMutex.Lock(); - -// Before we start check if we should delete any messages -// - if (delQ) {RelMsgs(delQ); delQ = 0;} - -// Send all queued messages (we can use a blocking send here) -// - while(!terminate && (theMsg = fMsg)) - {if (!(fMsg = fMsg->next)) lMsg = 0; - inQ--; myFD = theFD; - wMutex.UnLock(); - rc = send(myFD, theMsg->mData, theMsg->mLen, 0); - free(theMsg); - wMutex.Lock(); - if (rc < 0) {Scuttle(); break;} - } - -// Before we exit check if we should delete any messages -// - if (delQ) {RelMsgs(delQ); delQ = 0;} - if ((theEnd = terminate) && fMsg) RelMsgs(fMsg); - active = false; - qWmsg = qWarn; - -// Release any messages that need to be released. Note that we may have been -// deleted at this point so we cannot reference anything via "this" once we -// unlock the mutex. We may also need to delete ourselves. -// - wMutex.UnLock(); - if (theEnd) delete this; -} - -/******************************************************************************/ -/* Private: Q M s g */ -/******************************************************************************/ - -bool XrdSendQ::QMsg(XrdSendQ::mBuff *theMsg) -{ -// Check if we reached the max number of messages -// - if (inQ >= qMax) - {discards++; - if ((discards & 0xff) == 0x01) - {char qBuff[80]; - snprintf(qBuff, sizeof(qBuff), - "%u) reached; %hu message(s) discarded!", qMax, discards); - Say->Emsg("SendQ", mLink.Host(), - "appears to be slow; queue limit (", qBuff); - } - return false; - } - -// Add the message at the end of the queue -// - theMsg->next = 0; - if (lMsg) lMsg->next = theMsg; - else fMsg = theMsg; - lMsg = theMsg; - inQ++; - -// If there is no active thread handling this queue, schedule one -// - if (!active) - {Sched->Schedule((XrdJob *)this); - active = true; - } - -// Check if we should issue a warning. -// - if (inQ >= qWmsg) - {char qBuff[32]; - qWmsg += qWarn; - snprintf(qBuff, sizeof(qBuff), "%ud messages queued!", inQ); - Say->Emsg("SendQ", mLink.Host(), "appears to be slow;", qBuff); - } else { - if (inQ < qWarn && qWmsg != qWarn) qWmsg = qWarn; - } - -// All done -// - return true; -} - -/******************************************************************************/ -/* Private: R e l M s g s */ -/******************************************************************************/ - -void XrdSendQ::RelMsgs(XrdSendQ::mBuff *mP) -{ - mBuff *freeMP; - - while((freeMP = mP)) - {mP = mP->next; - free(freeMP); - } -} - -/******************************************************************************/ -/* Private: S c u t t l e */ -/******************************************************************************/ - -void XrdSendQ::Scuttle() // qMutex must be locked! -{ -// Simply move any outsanding messages to the deletion queue -// - if (fMsg) - {lMsg->next = delQ; - delQ = fMsg; - fMsg = lMsg = 0; - inQ = 0; - } -} - -/******************************************************************************/ -/* S e n d */ -/******************************************************************************/ - -// Called with wMutex locked. - -int XrdSendQ::Send(const char *buff, int blen) -{ - mBuff *theMsg; - int bleft, bsent; - -// If there is an active thread handling messages then we have to queue it. -// Otherwise try to send it. We need to hold the lock here to prevent messing -// up the message is only part of it could be sent. This is a non-blocking call. -// - if (active) bleft = blen; - else if ((bleft = SendNB(buff, blen)) <= 0) return (bleft ? -1 : blen); - -// Allocate buffer for the message -// - if (!(theMsg = (mBuff *)malloc(sizeof(mBuff) + bleft))) - {errno = ENOMEM; return -1;} - -// Copy the unsent message fragment -// - bsent = blen - bleft; - memcpy(theMsg->mData, buff+bsent, bleft); - theMsg->mLen = bleft; - -// Queue the message. -// - return (QMsg(theMsg) ? blen : -1); -} - -/******************************************************************************/ - -// Called with wMutex locked. - -int XrdSendQ::Send(const struct iovec *iov, int iovcnt, int iotot) -{ - mBuff *theMsg; - char *body; - int bleft, bmore, iovX; - -// If there is an active thread handling messages then we have to queue it. -// Otherwise try to send it. We need to hold the lock here to prevent messing -// up the message is only part of it could be sent. This is a non-blocking call. -// - if (active) - {bleft = 0; - for (iovX = 0; iovX < iovcnt; iovX++) - if ((bleft = iov[iovX].iov_len)) break; - if (!bleft) return iotot; - } else { - if ((bleft = SendNB(iov, iovcnt, iotot, iovX)) <= 0) - return (bleft ? -1 : 0); - } - -// Readjust the total amount not sent based on where we stopped in the iovec. -// - bmore = bleft; - for (int i = iovX+1; i < iovcnt; i++) bmore += iov[i].iov_len; - -// Copy the unsent message (for simplicity we will copy the whole iovec stop). -// - if (!(theMsg = (mBuff *)malloc(bmore+sizeof(mBuff)))) - {errno = ENOMEM; return -1;} - -// Setup the message length -// - theMsg->mLen = bmore; - -// Copy the first fragment (it cannot be zero length) -// - body = theMsg->mData; - memcpy(body, ((char *)iov[iovX].iov_base)+(iov[iovX].iov_len-bleft), bleft); - body += bleft; - -// All remaining items -// - for (int i = iovX+1; i < iovcnt; i++) - {if (iov[i].iov_len) - {memcpy(body, iov[i].iov_base, iov[i].iov_len); - body += iov[i].iov_len; - } - } - -// Queue the message. -// - return (QMsg(theMsg) ? iotot : 0); -} - -/******************************************************************************/ -/* S e n d N B */ -/******************************************************************************/ - -// Called with wMutex locked. - -int XrdSendQ::SendNB(const char *Buff, int Blen) -{ -#if !defined(__linux__) - return -1; -#else - ssize_t retc = 0, bytesleft = Blen; - -// Write the data out -// - while(bytesleft) - {do {retc = send(theFD, Buff, bytesleft, MSG_DONTWAIT);} - while(retc < 0 && errno == EINTR); - if (retc <= 0) break; - bytesleft -= retc; Buff += retc; - } - -// All done -// - if (retc <= 0) - {if (!retc || errno == EAGAIN || retc == EWOULDBLOCK) return bytesleft; - Say->Emsg("SendQ", errno, "send to", mLink.ID); - return -1; - } - return bytesleft; -#endif -} - -/******************************************************************************/ - -// Called with wMutex locked. - -int XrdSendQ::SendNB(const struct iovec *iov, int iocnt, int bytes, int &iovX) -{ - -#if !defined(__linux__) - return -1; -#else - char *msgP; - ssize_t retc; - int msgL, msgF = MSG_DONTWAIT|MSG_MORE, ioLast = iocnt-1; - -// Write the data out. The following code only works in Linux as we use the -// new POSIX flags deined for send() which currently is only implemented in -// Linux. This allows us to selectively use non-blocking I/O. -// - for (iovX = 0; iovX < iocnt; iovX++) - {msgP = (char *)iov[iovX].iov_base; - msgL = iov[iovX].iov_len; - if (iovX == ioLast) msgF &= ~MSG_MORE; - while(msgL) - {do {retc = send(theFD, msgP, msgL, msgF);} - while(retc < 0 && errno == EINTR); - if (retc <= 0) - {if (!retc || errno == EAGAIN || retc == EWOULDBLOCK) - return msgL; - Say->Emsg("SendQ", errno, "send to", mLink.ID); - return -1; - } - msgL -= retc; - } - } - -// All done -// - return 0; -#endif -} - -/******************************************************************************/ -/* T e r m i n a t e */ -/******************************************************************************/ - -// This must be called with wMutex locked! - -void XrdSendQ::Terminate(XrdLink *lP) -{ -// First step is to see if we need to schedule a shutdown prior to quiting -// - if (lP) Sched->Schedule((XrdJob *)new LinkShutdown(lP)); - -// If there is an active thread then we need to let the thread handle the -// termination of this object. Otherwise, we can do it now. -// - if (active) - {Scuttle(); - terminate = true; - theFD =-1; - } else { - if (fMsg) {RelMsgs(fMsg); fMsg = lMsg = 0;} - if (delQ) {RelMsgs(delQ); delQ = 0;} - delete this; - } -} diff --git a/src/Xrd/XrdSendQ.hh b/src/Xrd/XrdSendQ.hh deleted file mode 100644 index 42400c5ebaa..00000000000 --- a/src/Xrd/XrdSendQ.hh +++ /dev/null @@ -1,102 +0,0 @@ -#ifndef __XRDSENDQ__H -#define __XRDSENDQ__H -/******************************************************************************/ -/* */ -/* X r d S e n d Q . h h */ -/* */ -/* (c) 2016 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include - -#include "Xrd/XrdJob.hh" - -class XrdLink; -class XrdSysMutex; - -class XrdSendQ : public XrdJob -{ -public: - -unsigned int Backlog() {return inQ;} - -virtual void DoIt(); - -static void Init(XrdSysError *eP, XrdScheduler *sP) {Say = eP; Sched = sP;} - - int Send(const char *buff, int blen); - - int Send(const struct iovec *iov, int iovcnt, int iotot); - -static void SetAQ(bool onoff) {qPerm = onoff;} - -static void SetQM(unsigned int qmVal) {qMax = qmVal;} - -static void SetQW(unsigned int qwVal) {qWarn = qwVal;} - - void Terminate(XrdLink *lP=0); - - XrdSendQ(XrdLink &lP, XrdSysMutex &mP); - -private: - -virtual ~XrdSendQ() {} - -int SendNB(const char *Buff, int Blen); -int SendNB(const struct iovec *iov, int iocnt, int bytes, int &iovX); - -struct mBuff -{ -mBuff *next; -int mLen; -char mData[4]; // Always made long enough -}; - -bool QMsg(mBuff *theMsg); -void RelMsgs(mBuff *mP); -void Scuttle(); - -static XrdScheduler *Sched; -static XrdSysError *Say; -static unsigned int qWarn; -static unsigned int qMax; -static bool qPerm; -XrdLink &mLink; -XrdSysMutex &wMutex; - -mBuff *fMsg; -mBuff *lMsg; -mBuff *delQ; -int theFD; -unsigned int inQ; -unsigned int qWmsg; -unsigned short discards; -bool active; -bool terminate; -}; -#endif diff --git a/src/Xrd/XrdStats.cc b/src/Xrd/XrdStats.cc deleted file mode 100644 index 7889a67d949..00000000000 --- a/src/Xrd/XrdStats.cc +++ /dev/null @@ -1,329 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S t a t s . c c */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#if !defined(__APPLE__) && !defined(__FreeBSD__) -#include -#endif -#include -#include -#include - -#include "XrdVersion.hh" -#include "Xrd/XrdBuffer.hh" -#include "Xrd/XrdJob.hh" -#include "Xrd/XrdLink.hh" -#include "Xrd/XrdPoll.hh" -#include "Xrd/XrdProtLoad.hh" -#include "Xrd/XrdScheduler.hh" -#include "Xrd/XrdStats.hh" -#include "XrdNet/XrdNetMsg.hh" -#include "XrdSys/XrdSysPlatform.hh" -#include "XrdSys/XrdSysTimer.hh" - -/******************************************************************************/ -/* S t a t i c O b j e c t s */ -/******************************************************************************/ - - long XrdStats::tBoot = static_cast(time(0)); - -/******************************************************************************/ -/* L o c a l C l a s s X r d S t a t s J o b */ -/******************************************************************************/ - -class XrdStatsJob : XrdJob -{ -public: - - void DoIt() {Stats->Report(); - Sched->Schedule((XrdJob *)this, time(0)+iVal); - } - - XrdStatsJob(XrdScheduler *schP, XrdStats *sP, int iV) - : XrdJob("stats reporter"), - Sched(schP), Stats(sP), iVal(iV) - {Sched->Schedule((XrdJob *)this, time(0)+iVal);} - ~XrdStatsJob() {} -private: -XrdScheduler *Sched; -XrdStats *Stats; -int iVal; -}; - -/******************************************************************************/ -/* C o n s t r c u t o r */ -/******************************************************************************/ - -XrdStats::XrdStats(XrdSysError *eP, XrdScheduler *sP, XrdBuffManager *bP, - const char *hname, int port, - const char *iname, const char *pname, const char *site) -{ - static const char *head = - ""; - char myBuff[1024]; - - XrdLog = eP; - XrdSched = sP; - BuffPool = bP; - - Hlen = sprintf(myBuff, head, hname, port, tBoot, pname, iname, - static_cast(getpid()), (site ? site : "")); - Head = strdup(myBuff); - buff = 0; - blen = 0; - myHost = hname; - myName = iname; - myPort = port; -} - -/******************************************************************************/ -/* R e p o r t */ -/******************************************************************************/ - -void XrdStats::Report(char **Dest, int iVal, int Opts) -{ - static XrdNetMsg *netDest[2] = {0,0}; - static int autoSync, repOpts = Opts; - const char *Data; - int theOpts, Dlen; - -// If we have dest then this is for initialization -// - if (Dest) - // Establish up to two destinations - // - {if (Dest[0]) netDest[0] = new XrdNetMsg(XrdLog, Dest[0]); - if (Dest[1]) netDest[1] = new XrdNetMsg(XrdLog, Dest[1]); - if (!(repOpts & XRD_STATS_ALL)) repOpts |= XRD_STATS_ALL; - autoSync = repOpts & XRD_STATS_SYNCA; - - // Get and schedule a new job to report - // - if (netDest[0]) new XrdStatsJob(XrdSched, this, iVal); - return; - } - -// This is a re-entry for reporting purposes, establish the sync flag -// - if (!autoSync || XrdSched->Active() <= 30) theOpts = repOpts; - else theOpts = repOpts & ~XRD_STATS_SYNC; - -// Now get the statistics -// - statsMutex.Lock(); - if ((Data = GenStats(Dlen, theOpts))) - {netDest[0]->Send(Data, Dlen); - if (netDest[1]) netDest[1]->Send(Data, Dlen); - } - statsMutex.UnLock(); -} - -/******************************************************************************/ -/* S t a t s */ -/******************************************************************************/ - -void XrdStats::Stats(XrdStats::CallBack *cbP, int opts) -{ - const char *info; - int sz; - -// Lock the buffer, -// - statsMutex.Lock(); - -// Obtain the stats, if we have some, do the callback -// - if ((info = GenStats(sz, opts))) cbP->Info(info, sz); - -// Unlock the buffer -// - statsMutex.UnLock(); -} - -/******************************************************************************/ -/* P r i v a t e M e t h o d s */ -/******************************************************************************/ -/******************************************************************************/ -/* G e n S t a t s */ -/******************************************************************************/ - -const char *XrdStats::GenStats(int &rsz, int opts) // statsMutex must be locked! -{ - static const char *sgen = "" - "%d%lu%ld"; - static const char *tail = ""; - static const char *snul = "" - ""; - - static const int snulsz = strlen(snul); - static const int ovrhed = 256+strlen(sgen)+strlen(tail); - XrdSysTimer myTimer; - char *bp; - int n, bl, sz, do_sync = (opts & XRD_STATS_SYNC ? 1 : 0); - -// If buffer is not allocated, do it now. We must defer buffer allocation -// until all components that can provide statistics have been loaded -// - if (!(bp = buff)) - {blen = InfoStats(0,0) + BuffPool->Stats(0,0) + XrdLink::Stats(0,0) - + ProcStats(0,0) + XrdSched->Stats(0,0) + XrdPoll::Stats(0,0) - + XrdProtLoad::Statistics(0,0) + ovrhed + Hlen; - buff = (char *)memalign(getpagesize(), blen+256); - if (!(bp = buff)) {rsz = snulsz; return snul;} - } - bl = blen; - -// Start the time if need be -// - if (opts & XRD_STATS_SGEN) myTimer.Reset(); - -// Insert the heading -// - sz = sprintf(buff, Head, static_cast(time(0))); - bl -= sz; bp += sz; - -// Extract out the statistics, as needed -// - if (opts & XRD_STATS_INFO) - {sz = InfoStats(bp, bl, do_sync); - bp += sz; bl -= sz; - } - - if (opts & XRD_STATS_BUFF) - {sz = BuffPool->Stats(bp, bl, do_sync); - bp += sz; bl -= sz; - } - - if (opts & XRD_STATS_LINK) - {sz = XrdLink::Stats(bp, bl, do_sync); - bp += sz; bl -= sz; - } - - if (opts & XRD_STATS_POLL) - {sz = XrdPoll::Stats(bp, bl, do_sync); - bp += sz; bl -= sz; - } - - if (opts & XRD_STATS_PROC) - {sz = ProcStats(bp, bl, do_sync); - bp += sz; bl -= sz; - } - - if (opts & XRD_STATS_PROT) - {sz = XrdProtLoad::Statistics(bp, bl, do_sync); - bp += sz; bl -= sz; - } - - if (opts & XRD_STATS_SCHD) - {sz = XrdSched->Stats(bp, bl, do_sync); - bp += sz; bl -= sz; - } - - if (opts & XRD_STATS_SGEN) - {unsigned long totTime = 0; - myTimer.Report(totTime); - sz = snprintf(bp,bl,sgen,do_sync==0,totTime,static_cast(time(0))); - bp += sz; bl -= sz; - } - - sz = bp - buff; - if (bl > 0) n = strlcpy(bp, tail, bl); - else n = 0; - rsz = sz + (n >= bl ? bl : n); - return buff; -} - -/******************************************************************************/ -/* I n f o S t a t s */ -/******************************************************************************/ - -int XrdStats::InfoStats(char *bfr, int bln, int do_sync) -{ - static const char statfmt[] = "%s" - "%d%s"; - -// Check if actual length wanted -// - if (!bfr) return sizeof(statfmt)+24 + strlen(myHost); - -// Format the statistics -// - return snprintf(bfr, bln, statfmt, myHost, myPort, myName); -} - -/******************************************************************************/ -/* P r o c S t a t s */ -/******************************************************************************/ - -int XrdStats::ProcStats(char *bfr, int bln, int do_sync) -{ - static const char statfmt[] = "" - "%lld%lld" - "%lld%lld" - ""; - struct rusage r_usage; - long long utime_sec, utime_usec, stime_sec, stime_usec; -// long long ru_maxrss, ru_majflt, ru_nswap, ru_inblock, ru_oublock; -// long long ru_msgsnd, ru_msgrcv, ru_nsignals; - -// Check if actual length wanted -// - if (!bfr) return sizeof(statfmt)+16*13; - -// Get the statistics -// - if (getrusage(RUSAGE_SELF, &r_usage)) return 0; - -// Convert fields to correspond to the format we are using. Commented out fields -// are either not uniformaly reported or are incorrectly reported making them -// useless across multiple platforms. -// -// - utime_sec = static_cast(r_usage.ru_utime.tv_sec); - utime_usec = static_cast(r_usage.ru_utime.tv_usec); - stime_sec = static_cast(r_usage.ru_stime.tv_sec); - stime_usec = static_cast(r_usage.ru_stime.tv_usec); -// ru_maxrss = static_cast(r_usage.ru_maxrss); -// ru_majflt = static_cast(r_usage.ru_majflt); -// ru_nswap = static_cast(r_usage.ru_nswap); -// ru_inblock = static_cast(r_usage.ru_inblock); -// ru_oublock = static_cast(r_usage.ru_oublock); -// ru_msgsnd = static_cast(r_usage.ru_msgsnd); -// ru_msgrcv = static_cast(r_usage.ru_msgrcv); -// ru_nsignals = static_cast(r_usage.ru_nsignals); - -// Format the statistics -// - return snprintf(bfr, bln, statfmt, - utime_sec, utime_usec, stime_sec, stime_usec -// ru_maxrss, ru_majflt, ru_nswap, ru_inblock, ru_oublock, -// ru_msgsnd, ru_msgrcv, ru_nsignals - ); -} diff --git a/src/Xrd/XrdStats.hh b/src/Xrd/XrdStats.hh deleted file mode 100644 index 32b66f19398..00000000000 --- a/src/Xrd/XrdStats.hh +++ /dev/null @@ -1,93 +0,0 @@ -#ifndef __XRD_STATS_H__ -#define __XRD_STATS_H__ -/******************************************************************************/ -/* */ -/* X r d S t a t s . h h */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -#include "XrdSys/XrdSysPthread.hh" - -#define XRD_STATS_ALL 0x000000FF -#define XRD_STATS_INFO 0x00000001 -#define XRD_STATS_BUFF 0x00000002 -#define XRD_STATS_LINK 0x00000004 -#define XRD_STATS_POLL 0x00000008 -#define XRD_STATS_PROC 0x00000010 -#define XRD_STATS_PROT 0x00000020 -#define XRD_STATS_SCHD 0x00000040 -#define XRD_STATS_SGEN 0x00000080 -#define XRD_STATS_SYNC 0x40000000 -#define XRD_STATS_SYNCA 0x20000000 - -class XrdScheduler; -class XrdBuffManager; - -class XrdStats -{ -public: - -void Report(char **Dest=0, int iVal=600, int Opts=0); - -class CallBack - {public: virtual void Info(const char *data, int dlen) = 0; - CallBack() {} - virtual ~CallBack() {} - }; - -virtual -void Stats(CallBack *InfoBack, int opts); - - XrdStats(XrdSysError *eP, XrdScheduler *sP, XrdBuffManager *bP, - const char *hn, int port, const char *in, const char *pn, - const char *sn); - -virtual ~XrdStats() {if (buff) free(buff);} - -private: - -const char *GenStats(int &rsz, int opts); -int InfoStats(char *buff, int blen, int dosync=0); -int ProcStats(char *buff, int blen, int dosync=0); - -static long tBoot; // Time at boot time - -XrdScheduler *XrdSched; -XrdSysError *XrdLog; -XrdBuffManager *BuffPool; -XrdSysMutex statsMutex; - -char *buff; // Used by all callers -int blen; -int Hlen; -char *Head; -const char *myHost; -const char *myName; -int myPort; -}; -#endif diff --git a/src/Xrd/XrdTrace.hh b/src/Xrd/XrdTrace.hh deleted file mode 100644 index 5cde63539be..00000000000 --- a/src/Xrd/XrdTrace.hh +++ /dev/null @@ -1,71 +0,0 @@ -#ifndef _XRD_TRACE_H -#define _XRD_TRACE_H -/******************************************************************************/ -/* */ -/* X r d T r a c e . h h */ -/* */ -/* (C) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Deprtment of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -// Trace flags -// -#define TRACE_NONE 0x0000 -#define TRACE_ALL 0x0fff -#define TRACE_DEBUG 0x0001 -#define TRACE_CONN 0x0002 -#define TRACE_MEM 0x0004 -#define TRACE_NET 0x0008 -#define TRACE_POLL 0x0010 -#define TRACE_PROT 0x0020 -#define TRACE_SCHED 0x0040 - -#ifndef NODEBUG - -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdOuc/XrdOucTrace.hh" - -#ifndef XRD_TRACE -#define XRD_TRACE XrdTrace. -#endif - -#define TRACE(act, x) \ - if (XRD_TRACE What & TRACE_ ## act) \ - {XRD_TRACE Beg(TraceID); cerr <ID); cerr <. */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include - -#include "XrdVersion.hh" - -#include "XrdAcc/XrdAccAccess.hh" -#include "XrdAcc/XrdAccCapability.hh" -#include "XrdAcc/XrdAccConfig.hh" -#include "XrdAcc/XrdAccGroups.hh" -#include "XrdNet/XrdNetAddrInfo.hh" -#include "XrdOuc/XrdOucUtils.hh" -#include "XrdSys/XrdSysPlugin.hh" - -/******************************************************************************/ -/* E x t e r n a l R e f e r e n c e s */ -/******************************************************************************/ - -extern unsigned long XrdOucHashVal2(const char *KeyVal, int KeyLen); - -/******************************************************************************/ -/* G l o b a l C o n f i g u r a t i o n O b j e c t */ -/******************************************************************************/ - -extern XrdAccConfig XrdAccConfiguration; - -/******************************************************************************/ -/* Autorization Object Creation via XrdAccDefaultAuthorizeObject */ -/******************************************************************************/ - -XrdAccAuthorize *XrdAccDefaultAuthorizeObject(XrdSysLogger *lp, - const char *cfn, - const char *parm, - XrdVersionInfo &urVer) -{ - static XrdVERSIONINFODEF(myVer, XrdAcc, XrdVNUMBER, XrdVERSION); - static XrdSysError Eroute(lp, "acc_"); - -// Verify version compatability -// - if (urVer.vNum != myVer.vNum && !XrdSysPlugin::VerCmp(urVer,myVer)) - return 0; - -// Configure the authorization system -// - if (XrdAccConfiguration.Configure(Eroute, cfn)) return (XrdAccAuthorize *)0; - -// All is well, return the actual pointer to the object -// - return (XrdAccAuthorize *)XrdAccConfiguration.Authorization; -} - -/******************************************************************************/ -/* C o n s t r u c t o r */ -/******************************************************************************/ - -XrdAccAccess::XrdAccAccess(XrdSysError *erp) -{ -// Get the audit option that we should use -// - Auditor = XrdAccAuditObject(erp); -} - -/******************************************************************************/ -/* A c c e s s */ -/******************************************************************************/ - -XrdAccPrivs XrdAccAccess::Access(const XrdSecEntity *Entity, - const char *path, - const Access_Operation oper, - XrdOucEnv *Env) -{ - const char *xP; - char *gname, xBuff[64]; - XrdAccGroupList *glp; - XrdAccPrivCaps caps; - XrdAccCapability *cp; - const int plen = strlen(path); - const long phash = XrdOucHashVal2(path, plen); - const char *id = (Entity->name ? (const char *)Entity->name : "*"); - const char *host = 0; - int n, isuser = (*id && (*id != '*' || id[1])); - -// Get a shared context for these potentially long running routines -// - Access_Context.Lock(xs_Shared); - -// Run through the exclusive list first as only one rule will apply -// - XrdAccAccess_ID *xlP = Atab.SXList; - while (xlP) - {if (xlP->Applies(Entity)) - {xlP->caps->Privs(caps, path, plen, phash); - Access_Context.UnLock(xs_Shared); - return Access(caps, Entity, path, oper); - } - xlP = xlP->next; - } - -// Check if we really need to resolve the host name -// - if (Atab.D_List || Atab.H_Hash || Atab.N_Hash) host = Resolve(Entity); - -// Establish default privileges -// - if (Atab.Z_List) Atab.Z_List->Privs(caps, path, plen, phash); - -// Next add in the host domain privileges -// - if (Atab.D_List && host && (cp = Atab.D_List->Find(host))) - cp->Privs(caps, path, plen, phash); - -// Next add in the host-specific privileges -// - if (Atab.H_Hash && host && (cp = Atab.H_Hash->Find(host))) - cp->Privs(caps, path, plen, phash); - -// Check for user fungible privileges -// - if (isuser && Atab.X_List) Atab.X_List->Privs(caps, path, plen, phash, id); - -// Add in specific user privileges -// - if (isuser && Atab.U_Hash && (cp = Atab.U_Hash->Find(id))) - cp->Privs(caps, path, plen, phash); - -// Next add in the group privileges. The group list either comes from the -// credentials, in which case we need not have a username, or from the -// standard unix-username group mapping. -// - if (Atab.G_Hash) - {if (Entity->grps) - {xP = Entity->grps; - while((n = XrdOucUtils::Token(&xP, ' ', xBuff, sizeof(xBuff)))) - {if (n < (int)sizeof(xBuff) && (cp = Atab.G_Hash->Find(xBuff))) - cp->Privs(caps, path, plen, phash); - } - } else if (isuser && (glp=XrdAccConfiguration.GroupMaster.Groups(id))) - {while((gname = (char *)glp->Next())) - if ((cp = Atab.G_Hash->Find((const char *)gname))) - cp->Privs(caps, path, plen, phash); - delete glp; - } - } - -// Now add in the netgroup privileges -// - if (Atab.N_Hash && id && host && - (glp = XrdAccConfiguration.GroupMaster.NetGroups(id, host))) - {while((gname = (char *)glp->Next())) - if ((cp = Atab.N_Hash->Find((const char *)gname))) - cp->Privs(caps, path, plen, phash); - delete glp; - } - -// Next add in the org-specific privileges -// - if (Atab.O_Hash && Entity->vorg) - {xP = Entity->vorg; - while((n = XrdOucUtils::Token(&xP, ' ', xBuff, sizeof(xBuff)))) - {if (n < (int)sizeof(xBuff) && (cp = Atab.O_Hash->Find(xBuff))) - cp->Privs(caps, path, plen, phash); - } - } - -// Next add in the role-specific privileges -// - if (Atab.R_Hash && Entity->role) - {xP = Entity->role; - while((n = XrdOucUtils::Token(&xP, ' ', xBuff, sizeof(xBuff)))) - {if (n < (int)sizeof(xBuff) && (cp = Atab.R_Hash->Find(xBuff))) - cp->Privs(caps, path, plen, phash); - } - } - -// Finally run through the inclusive list and apply arr relevant rules -// - XrdAccAccess_ID *ylP = Atab.SYList; - while (ylP) - {if (ylP->Applies(Entity)) ylP->caps->Privs(caps, path, plen, phash); - ylP = ylP->next; - } - -// We are now done with looking at changeable data -// - Access_Context.UnLock(xs_Shared); - -// Return the privileges as needed -// - return Access(caps, Entity, path, oper); -} - -/******************************************************************************/ - -XrdAccPrivs XrdAccAccess::Access( XrdAccPrivCaps &caps, - const XrdSecEntity *Entity, - const char *path, - const Access_Operation oper - ) -{ - XrdAccPrivs myprivs; - XrdAccAudit_Options audits = (XrdAccAudit_Options)Auditor->Auditing(); - int accok; - -// Compute composite privileges and see if privs need to be returned -// - myprivs = (XrdAccPrivs)(caps.pprivs & ~caps.nprivs); - if (!oper) return (XrdAccPrivs)myprivs; - -// Check if auditing is enabled or whether we can do a fastaroo test -// - if (!audits) return (XrdAccPrivs)Test(myprivs, oper); - if ((accok = Test(myprivs, oper)) && !(audits & audit_grant)) - return (XrdAccPrivs)accok; - -// Call the auditing routine and exit -// - return (XrdAccPrivs)Audit(accok, Entity, path, oper); -} - -/******************************************************************************/ -/* A u d i t */ -/******************************************************************************/ - -int XrdAccAccess::Audit(const int accok, - const XrdSecEntity *Entity, - const char *path, - const Access_Operation oper, - XrdOucEnv *Env) -{ -// Warning! This table must be in 1-to-1 correspondence with Access_Operation -// - static const char *Opername[] = {"any", // 0 - "chmod", // 1 - "chown", // 2 - "create", // 3 - "delete", // 4 - "insert", // 5 - "lock", // 6 - "mkdir", // 7 - "read", // 8 - "readdir", // 9 - "rename", // 10 - "stat", // 10 - "update" // 12 - }; - const char *opname = (oper > AOP_LastOp ? "???" : Opername[oper]); - const char *id = (Entity->name ? (const char *)Entity->name : "*"); - const char *host = (Entity->host ? (const char *)Entity->host : "?"); - char atype[XrdSecPROTOIDSIZE+1]; - -// Get the protocol type in a printable format -// - strncpy(atype, Entity->prot, XrdSecPROTOIDSIZE); - atype[XrdSecPROTOIDSIZE] = '\0'; - -// Route the message appropriately -// - if (accok) Auditor->Grant(opname, Entity->tident, atype, id, host, path); - else Auditor->Deny( opname, Entity->tident, atype, id, host, path); - -// All done, finally -// - return accok; -} - -/******************************************************************************/ -/* R e s o l v e */ -/******************************************************************************/ - -const char *XrdAccAccess::Resolve(const XrdSecEntity *Entity) -{ -// Make a quick test for IPv6 (as that's the future) and a minimal one for ipV4 -// to see if we have to do a DNS lookup. -// - if (Entity->host == 0 || *(Entity->host) == '[' || isdigit(*(Entity->host))) - return Entity->addrInfo->Name("?"); - return Entity->host; -} - -/******************************************************************************/ -/* S w a p T a b s */ -/******************************************************************************/ - -#define XrdAccSWAP(x) oldtab.x = Atab.x; Atab.x = newtab.x; \ - newtab.x = oldtab.x; oldtab.x = 0; - -void XrdAccAccess::SwapTabs(struct XrdAccAccess_Tables &newtab) -{ - struct XrdAccAccess_Tables oldtab; - -// Get an exclusive context to change the table pointers -// - Access_Context.Lock(xs_Exclusive); - -// Save the old pointer while replacing it with the new pointer -// - XrdAccSWAP(D_List); - XrdAccSWAP(E_List); - XrdAccSWAP(G_Hash); - XrdAccSWAP(H_Hash); - XrdAccSWAP(N_Hash); - XrdAccSWAP(O_Hash); - XrdAccSWAP(R_Hash); - XrdAccSWAP(S_Hash); - XrdAccSWAP(T_Hash); - XrdAccSWAP(U_Hash); - XrdAccSWAP(X_List); - XrdAccSWAP(Z_List); - XrdAccSWAP(SXList); - XrdAccSWAP(SYList); - -// When we set new access tables, we should purge the group cache -// - XrdAccConfiguration.GroupMaster.PurgeCache(); - -// We can now let loose new table searchers -// - Access_Context.UnLock(xs_Exclusive); -} - -/******************************************************************************/ -/* T e s t */ -/******************************************************************************/ - -int XrdAccAccess::Test(const XrdAccPrivs priv,const Access_Operation oper) -{ - -// Warning! This table must be in 1-to-1 correspondence with Access_Operation -// - static XrdAccPrivs need[] = {XrdAccPriv_None, // 0 - XrdAccPriv_Chmod, // 1 - XrdAccPriv_Chown, // 2 - XrdAccPriv_Create, // 3 - XrdAccPriv_Delete, // 4 - XrdAccPriv_Insert, // 5 - XrdAccPriv_Lock, // 6 - XrdAccPriv_Mkdir, // 7 - XrdAccPriv_Read, // 8 - XrdAccPriv_Readdir, // 9 - XrdAccPriv_Rename, // 10 - XrdAccPriv_Lookup, // 11 - XrdAccPriv_Update // 12 - }; - if (oper < 0 || oper > AOP_LastOp) return 0; - return (int)(need[oper] & priv) == need[oper]; -} - -/******************************************************************************/ -/* X r d A c c A c c e s s _ I D : : A p p l i e s */ -/******************************************************************************/ - -bool XrdAccAccess_ID::Applies(const XrdSecEntity *Entity) -{ - const char *hName, *gList, *gEnd; - int eLen; - -// Check single value items in the most probable use order -// - if (org && (!Entity->vorg || strcmp(org, Entity->vorg))) return false; - if (role && (!Entity->role || strcmp(role, Entity->role))) return false; - if (user && (!Entity->name || strcmp(user, Entity->name))) return false; - -// The check is more complicated as the host field may be an address. We make -// a quick test for IPv6 (as that's the future) and take the long road for ipV4. -// - if (host) - {hName = XrdAccAccess::Resolve(Entity); - if (*host == '.') - {eLen = strlen(hName); - if (eLen <= hlen) return false; - hName = hName + eLen - hlen; - } - if (strcmp(host, hName)) return false; - } - -// Groups are most problematic as there may be many of them. So it's last. -// - if (!grp) return true; - if (!Entity->grps) return false; - eLen = strlen(Entity->grps); - if (eLen < glen) return false; - -// Search through the group list -// - gList = Entity->grps; - while(true) - {if (!strncmp(grp, Entity->grps, glen)) - {gEnd = Entity->grps + glen; - if (*gEnd == ' ' || *gEnd == 0) return true; - } - if(!(gList = index(gList, ' '))) break; - do {gList++;} while(*gList == ' '); - } - -// This entry is not applicable -// - return false; -} diff --git a/src/XrdAcc/XrdAccAccess.hh b/src/XrdAcc/XrdAccAccess.hh deleted file mode 100644 index 29a5842399d..00000000000 --- a/src/XrdAcc/XrdAccAccess.hh +++ /dev/null @@ -1,169 +0,0 @@ -#ifndef __ACC_ACCESS__ -#define __ACC_ACCESS__ -/******************************************************************************/ -/* */ -/* X r d A c c A c c e s s . h h */ -/* */ -/* (c) 2003 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdAcc/XrdAccAudit.hh" -#include "XrdAcc/XrdAccAuthorize.hh" -#include "XrdAcc/XrdAccCapability.hh" -#include "XrdSec/XrdSecEntity.hh" -#include "XrdOuc/XrdOucHash.hh" -#include "XrdSys/XrdSysXSLock.hh" -#include "XrdSys/XrdSysPlatform.hh" - -/******************************************************************************/ -/* S e t T a b s P a r a m e t e r */ -/******************************************************************************/ - -struct XrdAccAccess_ID - {char *name; - char *grp; - char *host; - char *org; - char *role; - char *user; - XrdAccCapability *caps; - XrdAccAccess_ID *next; - int rule; - short hlen; - short glen; - - bool Applies(const XrdSecEntity *Entity); - - XrdAccAccess_ID *Export() - {XrdAccAccess_ID *xID; - xID = new XrdAccAccess_ID; - *xID = *this; - name = grp = host = org = role = user = 0; - caps = 0; - return xID; - } - - XrdAccAccess_ID(const char *Name=0) - : name(Name ? strdup(Name) : 0), - grp(0), host(0), org(0), role(0), user(0), - caps(0), next(0), rule(0), hlen(0), glen(0) {} - ~XrdAccAccess_ID() {if (name) free(name); - if (grp) free(grp); - if (host) free(host); - if (org) free(org); - if (role) free(role); - if (user) free(user); - if (caps) delete caps; - } - }; - -struct XrdAccAccess_Tables - {XrdOucHash *G_Hash; // Groups - XrdOucHash *H_Hash; // Hosts - XrdOucHash *N_Hash; // Netgroups - XrdOucHash *O_Hash; // Organizations - XrdOucHash *R_Hash; // Roles - XrdOucHash *S_Hash; // Sets - XrdOucHash *T_Hash; // Templates - XrdOucHash *U_Hash; // Users - XrdAccCapName *D_List; // Domains - XrdAccCapName *E_List; // Domains (end of list) - XrdAccCapability *X_List; // Fungable capbailities - XrdAccCapability *Z_List; // Default capbailities - XrdAccAccess_ID *SXList; // 's' exclusive list - XrdAccAccess_ID *SYList; // 's' inclusive list - - XrdAccAccess_Tables() {G_Hash = 0; H_Hash = 0; N_Hash = 0; - O_Hash = 0; R_Hash = 0; - S_Hash = 0; T_Hash = 0; U_Hash = 0; - D_List = 0; E_List = 0; - X_List = 0; Z_List = 0; - SXList = 0; SYList = 0; - } - ~XrdAccAccess_Tables() {if (G_Hash) delete G_Hash; - if (H_Hash) delete H_Hash; - if (N_Hash) delete N_Hash; - if (O_Hash) delete O_Hash; - if (R_Hash) delete R_Hash; - if (S_Hash) delete S_Hash; //Deletes SX & SYList - if (T_Hash) delete T_Hash; - if (U_Hash) delete U_Hash; - if (X_List) delete X_List; - if (Z_List) delete Z_List; - } - }; - -/******************************************************************************/ -/* X r d A c c A c c e s s */ -/******************************************************************************/ - -class xrdOucError; - -class XrdAccAccess : public XrdAccAuthorize -{ -public: - -friend class XrdAccConfig; - - XrdAccPrivs Access(const XrdSecEntity *Entity, - const char *path, - const Access_Operation oper, - XrdOucEnv *Env=0); - - int Audit(const int accok, - const XrdSecEntity *Entity, - const char *path, - const Access_Operation oper, - XrdOucEnv *Env=0); - -static -const char *Resolve(const XrdSecEntity *Entity); - -// SwapTabs() is used by the configuration object to establish new access -// control tables. It may be called whenever the tables change. -// -void SwapTabs(struct XrdAccAccess_Tables &newtab); - - int Test(const XrdAccPrivs priv, const Access_Operation oper); - - XrdAccAccess(XrdSysError *erp); - - ~XrdAccAccess() {} // The access object is never deleted - -private: - -XrdAccPrivs Access( XrdAccPrivCaps &caps, - const XrdSecEntity *Entity, - const char *path, - const Access_Operation oper); - -struct XrdAccAccess_Tables Atab; - -XrdSysXSLock Access_Context; - -XrdAccAudit *Auditor; -}; -#endif diff --git a/src/XrdAcc/XrdAccAudit.cc b/src/XrdAcc/XrdAccAudit.cc deleted file mode 100644 index 9c15a3e50b8..00000000000 --- a/src/XrdAcc/XrdAccAudit.cc +++ /dev/null @@ -1,99 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d A c c A u d i t . c c */ -/* */ -/* (c) 2003 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -#include "XrdAcc/XrdAccAudit.hh" -#include "XrdSys/XrdSysError.hh" - -/******************************************************************************/ -/* C o n s t r u c t o r */ -/******************************************************************************/ - -XrdAccAudit::XrdAccAudit(XrdSysError *erp) -{ - -// Set default -// - auditops = audit_none; - mDest = erp; -} - -/******************************************************************************/ -/* D e n y */ -/******************************************************************************/ - -void XrdAccAudit::Deny(const char *opname, - const char *tident, - const char *atype, - const char *id, - const char *host, - const char *path) -{if (auditops & audit_deny) - {char buff[2048]; - snprintf(buff, sizeof(buff)-1, "%s deny %s %s@%s %s %s", - (tident ? tident : ""), atype, id, host, opname, path); - buff[sizeof(buff)-1] = '\0'; - mDest->Emsg("Audit", buff); - } -} - -/******************************************************************************/ -/* G r a n t */ -/******************************************************************************/ - -void XrdAccAudit::Grant(const char *opname, - const char *tident, - const char *atype, - const char *id, - const char *host, - const char *path) -{if (auditops & audit_deny) - {char buff[2048]; - snprintf(buff, sizeof(buff)-1, "%s grant %s %s@%s %s %s", - (tident ? tident : ""), atype, id, host, opname, path); - buff[sizeof(buff)-1] = '\0'; - mDest->Emsg("Audit", buff); - } -} - -/******************************************************************************/ -/* A u d i t O b j e c t G e n e r a t o r */ -/******************************************************************************/ - -XrdAccAudit *XrdAccAuditObject(XrdSysError *erp) -{ -static XrdAccAudit AuditObject(erp); - -// Simply return the default audit object -// - return &AuditObject; -} diff --git a/src/XrdAcc/XrdAccAudit.hh b/src/XrdAcc/XrdAccAudit.hh deleted file mode 100644 index ade54718165..00000000000 --- a/src/XrdAcc/XrdAccAudit.hh +++ /dev/null @@ -1,107 +0,0 @@ -#ifndef __ACC_AUDIT__ -#define __ACC_AUDIT__ -/******************************************************************************/ -/* */ -/* X r d A c c A u d i t . h h */ -/* */ -/* (c) 2003 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -/******************************************************************************/ -/* A u d i t _ O p t i o n s */ -/******************************************************************************/ - -enum XrdAccAudit_Options {audit_none = 0, - audit_deny = 1, - audit_grant = 2, - audit_all = 3 - }; - -/******************************************************************************/ -/* X r d A c c A u d i t */ -/******************************************************************************/ - -// This class is really meant to be replaced by anyone who care about auditing. -// Effective auditing is required to meet DOD class C security requirments. - -// This class should be placed in a shared library so that an installation can -// easily replace it and routine auditsdits as needed. We supply a brain-dead -// audit that simply issues a message: -// deny -// yymmdd hh:mm:ss acc_Audit: grant atype id@host opername path - -// Enabling/disabling is done via the method setAudit(). - -// The external routine XrdAccAuditObject() returns the real audit object -// used by Access(). Developers should derive a class from this class and -// return the object of there choosing up-cast to this object. See the -// routine XrdAccAudit.C for the particulars. - -class XrdSysError; - -class XrdAccAudit -{ -public: - - int Auditing(const XrdAccAudit_Options ops=audit_all) - {return auditops & ops;} - -virtual void Deny(const char *opname, - const char *tident, - const char *atype, - const char *id, - const char *host, - const char *path); - -virtual void Grant(const char *opname, - const char *tident, - const char *atype, - const char *id, - const char *host, - const char *path); - -// setAudit() is used to set the auditing options: audit_none turns audit off -// (the default), audit_deny audit access denials, audit_grant audits access -// grants, and audit_all audits both. See XrdAccAudit.h for more information. -// -void setAudit(XrdAccAudit_Options aops) {auditops = aops;} - - XrdAccAudit(XrdSysError *erp); -virtual ~XrdAccAudit() {} - -private: - -XrdAccAudit_Options auditops; -XrdSysError *mDest; -}; - -/******************************************************************************/ -/* o o a c c _ A u d i t _ O b j e c t */ -/******************************************************************************/ - -extern XrdAccAudit *XrdAccAuditObject(XrdSysError *erp); - -#endif diff --git a/src/XrdAcc/XrdAccAuthDB.hh b/src/XrdAcc/XrdAccAuthDB.hh deleted file mode 100644 index 8617fac7704..00000000000 --- a/src/XrdAcc/XrdAccAuthDB.hh +++ /dev/null @@ -1,112 +0,0 @@ -#ifndef __ACC_AUTHDB__ -#define __ACC_AUTHDB__ -/******************************************************************************/ -/* */ -/* X r d A c c A u t h D B . h h */ -/* */ -/* (c) 2003 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdSys/XrdSysError.hh" - -// This class is provided for obtaining capability information from some source. -// Derive a class to provide an actual source for the information. The -// interface is similar to the set/get/endpwent enumeration interface: - -// setDBpath() is used to establish the location of the database. - -// Open() establishes the start of the database operation. It also obtains -// an exclusive mutex to be mt-safe. True is returned upon success. - -// getRec() get the next database record. It returns the record type as well -// as a pointer to the record name. False is returned at the end -// of the database. - -// getPP() gets the next path-priv or template name. It returns a pointer -// to each one. True is returned until end-of-record. - -// Close() terminates database processing and releases the associated lock. -// It also return FALSE if any errors occured during processing. - -// Changed() Returns 1 id the current authorization file has changed since -// the last time it was opened. - - -/******************************************************************************/ -/* D a t a b a s e R e c o r d T y p e s */ -/******************************************************************************/ - -// The following are the 1-letter id types that we support -// -// g -> unix group name -// h -> host name -// n -> NIS netgroup name -// s -> set name -// t -> template name -// u -> user name - -// The syntax for each database record is: - -// {| } [{ }] [...] - -// = [ [....]] - -// Continuation records are signified by an ending backslash (\). Blank records -// and comments (i.e., lines with the first non-blank being a pound sign) are -// allowed. Word separators may be spaces or tabs. - -/******************************************************************************/ -/* X r d A c c A u t h D B C l a s s */ -/******************************************************************************/ - -class XrdAccAuthDB -{ -public: - -virtual int Open(XrdSysError &eroute, const char *path=0) = 0; - -virtual char getRec(char **recname) = 0; - -virtual char getID(char **id) = 0; - -virtual int getPP(char **path, char **priv, bool &istmplt) = 0; - -virtual int Close() = 0; - -virtual int Changed(const char *path=0) = 0; - - XrdAccAuthDB() {} -virtual ~XrdAccAuthDB() {} - -}; - -/******************************************************************************/ -/* X r d A c c X u t h D B _ O b j e c t */ -/******************************************************************************/ - -extern XrdAccAuthDB *XrdAccAuthDBObject(XrdSysError *erp); - -#endif diff --git a/src/XrdAcc/XrdAccAuthFile.cc b/src/XrdAcc/XrdAccAuthFile.cc deleted file mode 100644 index f16fc902c43..00000000000 --- a/src/XrdAcc/XrdAccAuthFile.cc +++ /dev/null @@ -1,396 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d A c c A u t h F i l e . c c */ -/* */ -/* (c) 2003 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include - -#include "XrdAcc/XrdAccAuthFile.hh" - -/******************************************************************************/ -/* X r d A c c A u t h D B _ O b j e c t */ -/******************************************************************************/ - -XrdAccAuthDB *XrdAccAuthDBObject(XrdSysError *erp) -{ - static XrdAccAuthFile mydatabase(erp); - - return (XrdAccAuthDB *)&mydatabase; -} - -/******************************************************************************/ -/* C o n s t r u c t o r */ -/******************************************************************************/ - -XrdAccAuthFile::XrdAccAuthFile(XrdSysError *erp) -{ - -// Set starting values -// - authfn = 0; - flags = Noflags; - modtime = 0; - Eroute = erp; - -// Setup for an error in the first record -// - strcpy(path_buff, "start of file"); -} - -/******************************************************************************/ -/* D e s t r u c t o r */ -/******************************************************************************/ - -XrdAccAuthFile::~XrdAccAuthFile() -{ - -// If the file is open, close it -// - if (flags &isOpen) Close(); - -// Free the authfn string -// - if (authfn) free(authfn); -} - -/******************************************************************************/ -/* C h a n g e d */ -/******************************************************************************/ - -int XrdAccAuthFile::Changed(const char *dbfn) -{ - struct stat statbuff; - -// If no file here, indicate nothing changed -// - if (!authfn || !*authfn) return 0; - -// If file paths differ, indicate that something has changed -// - if (dbfn && strcmp(dbfn, authfn)) return 1; - -// Get the modification timestamp for this file -// - if (stat(authfn, &statbuff)) - {Eroute->Emsg("AuthFile", errno, "find", authfn); - return 0; - } - -// Indicate whether or not the file has changed -// - return (modtime < statbuff.st_mtime); -} - -/******************************************************************************/ -/* C l o s e */ -/******************************************************************************/ - -int XrdAccAuthFile::Close() -{ -// Return is the file is not open -// - if (!(flags & isOpen)) return 1; - -// Close the stream -// - DBfile.Close(); - -// Unlock the protecting mutex -// - DBcontext.UnLock(); - -// Indicate file is no longer open -// - flags = (DBflags)(flags & ~isOpen); - -// Return indicator of whether we had any errors -// - if (flags & dbError) return 0; - return 1; -} - -/******************************************************************************/ -/* g e t I D */ -/******************************************************************************/ - -char XrdAccAuthFile::getID(char **id) -{ - char *pp, idcode[2] = {0,0}; - -// If a record has not been read, return end of record (i.e., 0) -// - if (!(flags & inRec)) return 0; - -// Read the next word from the record (if none, simulate end of record) -// - if (!(pp = DBfile.GetWord())) - {flags = (DBflags)(flags & ~inRec); - return 0; - } - -// Id's are of the form 'c:', make sure we have that (don't validate it) -// - if (strlen(pp) != 2 || !index("ghoru", *pp)) - {Eroute->Emsg("AuthFile", "Invalid ID sprecifier -", pp); - flags = (DBflags)(flags | dbError); - return 0; - } - idcode[0] = *pp; - -// Now get the actual id associated with it -// - if (!(pp = DBfile.GetWord())) - {flags = (DBflags)(flags & ~inRec); - Eroute->Emsg("AuthFile", "ID value missing after", idcode); - flags = (DBflags)(flags | dbError); - return 0; - } - -// Copy the value since the stream buffer might get overlaid. -// - Copy(path_buff, pp, sizeof(path_buff)-1); - -// Return result -// - *id = path_buff; - return idcode[0]; -} - -/******************************************************************************/ -/* g e t P P */ -/******************************************************************************/ - -int XrdAccAuthFile::getPP(char **path, char **priv, bool &istmplt) -{ -// char *pp, *bp; - char *pp; - -// If a record has not been read, return end of record (i.e., 0) -// - if (!(flags & inRec)) return 0; - -// read the next word from the record (if none, simulate end of record) -// - if (!(pp = DBfile.GetWord())) - {flags = (DBflags)(flags & ~inRec); - return 0; - } - -// Check of objectid specification -// - istmplt = false; - *path = path_buff; - if (*pp == '\\') - {if (*(pp+1)) pp++; - else {Eroute->Emsg("AuthFile", "Object ID missing after '\\'"); - *path = 0; - flags = (DBflags)(flags | dbError); - } - } else if (*pp != '/') istmplt = true; - -// Copy the value since the stream buffer might get overlaid. -// -// bp = Copy(path_buff, pp, sizeof(path_buff)-1); - if (path) Copy(path_buff, pp, sizeof(path_buff)-1); - -// Check if this is really a path or a template -// - if (istmplt) {*priv = (char *)0; return 1;} - -// Verify that the path ends correctly (normally we would force a slash to -// appear at the end but that prevents caps on files. So, we commented the -// code out until we decide that maybe we really need to do this, sigh. -// -// bp--; -// if (*bp != '/') {bp++; *bp = '/'; bp++; *bp = '\0';} - -// Get the next word which should be the privilege string -// - if (!(pp = DBfile.GetWord())) - {flags = (DBflags)(flags & ~inRec); - Eroute->Emsg("AuthFile", "Privileges missing after", path_buff); - flags = (DBflags)(flags | dbError); - *priv = (char *)0; - return 0; - } - -// All done here -// - *priv = pp; - return 1; -} - -/******************************************************************************/ -/* g e t R e c */ -/******************************************************************************/ - -char XrdAccAuthFile::getRec(char **recname) -{ - char *pp; - int idok; - -// Do this until we get a vlaid record -// - while(1) - { - // If we arer still in the middle of a record, flush it - // - if (flags & inRec) while(DBfile.GetWord()) {} - else flags = (DBflags)(flags | inRec); - - // Get the next word, the record type - // - if (!(pp = DBfile.GetWord())) - {*recname = (char *)0; return '\0';} - - // Verify the id-type - // - idok = 0; - if (strlen(pp) == 1) - switch(*pp) - {case 'g': - case 'h': - case 's': - case 'n': - case 'o': - case 'r': - case 't': - case 'u': - case 'x': - case '=': idok = 1; - break; - default: break; - } - - // Check if the record type was valid - // - if (!idok) {Eroute->Emsg("AuthFile", "Invalid id type -", pp); - flags = (DBflags)(flags | dbError); - continue; - } - rectype = *pp; - - // Get the record name. It must exist - // - if (!(pp = DBfile.GetWord())) - {Eroute->Emsg("AuthFile","Record name is missing after",path_buff); - flags = (DBflags)(flags | dbError); - continue; - } - - // Copy the record name - // - Copy(recname_buff, pp, sizeof(recname_buff)); - *recname = recname_buff; - return rectype; - } - return '\0'; // Keep the compiler happy :-) -} - -/******************************************************************************/ -/* O p e n */ -/******************************************************************************/ - -int XrdAccAuthFile::Open(XrdSysError &eroute, const char *path) -{ - struct stat statbuff; - int authFD; - -// Enter the DB context (serialize use of this database) -// - DBcontext.Lock(); - Eroute = &eroute; - -// Use whichever path is the more recent -// - if (path) - {if (authfn) free(authfn); authfn = strdup(path);} - if( !authfn || !*authfn) return Bail(0, "Authorization file not specified."); - -// Get the modification timestamp for this file -// - if (stat(authfn, &statbuff)) return Bail(errno, "find", authfn); - -// Try to open the authorization file. -// - if ( (authFD = open(authfn, O_RDONLY, 0)) < 0) - return Bail(errno,"open authorization file",authfn); - -// Copy in all the relevant information -// - modtime = statbuff.st_mtime; - flags = isOpen; - DBfile.SetEroute(Eroute); - DBfile.Tabs(0); - -// Attach the file to the stream -// - if (DBfile.Attach(authFD)) - return Bail(DBfile.LastError(), "initialize stream for", authfn); - return 1; -} - -/******************************************************************************/ -/* P r i v a t e M e t h o d s */ -/******************************************************************************/ -/******************************************************************************/ -/* B a i l */ -/******************************************************************************/ - -int XrdAccAuthFile::Bail(int retc, const char *txt1, const char *txt2) -{ -// This routine is typically used by open and the DBcontext lock must be held -// - flags = (DBflags)(flags & ~isOpen); - DBcontext.UnLock(); - if (retc) Eroute->Emsg("AuthFile", retc, txt1, txt2); - else Eroute->Emsg("AuthFile", txt1, txt2); - return 0; -} - -/******************************************************************************/ -/* C o p y */ -/******************************************************************************/ - -// This routine is used instead of strncpy because, frankly, it's a lot smarter - -char *XrdAccAuthFile::Copy(char *dp, char *sp, int dplen) -{ - // Copy one less that the size of the buffer so that we have room for null - // - while(--dplen && *sp) {*dp = *sp; dp++; sp++;} - -// Insert a null character and return a pointer to it. -// - *dp = '\0'; - return dp; -} diff --git a/src/XrdAcc/XrdAccAuthFile.hh b/src/XrdAcc/XrdAccAuthFile.hh deleted file mode 100644 index 9b83285a848..00000000000 --- a/src/XrdAcc/XrdAccAuthFile.hh +++ /dev/null @@ -1,80 +0,0 @@ -#ifndef __ACC_AUTHFILE__ -#define __ACC_AUTHFILE__ -/******************************************************************************/ -/* */ -/* X r d A c c A u t h F i l e . h h */ -/* */ -/* (c) 2003 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysPthread.hh" -#include "XrdOuc/XrdOucStream.hh" -#include "XrdAcc/XrdAccAuthDB.hh" - -// This class is provided for obtaining capability information from a file. -// -class XrdAccAuthFile : public XrdAccAuthDB -{ -public: - -int Open(XrdSysError &eroute, const char *path=0); - -char getRec(char **recname); - -char getID(char **id); - -int getPP(char **path, char **priv, bool &istmplt); - -int Close(); - -int Changed(const char *dbpath); - - XrdAccAuthFile(XrdSysError *erp); - ~XrdAccAuthFile(); - -private: - -int Bail(int retc, const char *txt1, const char *txt2=0); -char *Copy(char *dp, char *sp, int dplen); - -enum DBflags {Noflags=0, inRec=1, isOpen=2, dbError=4}; // Values combined - -XrdSysError *Eroute; -DBflags flags; -XrdOucStream DBfile; -char *authfn; -char rectype; -time_t modtime; -XrdSysMutex DBcontext; - -char recname_buff[MAXHOSTNAMELEN+1]; // Max record name by default -char path_buff[PATH_MAX+2]; // Max path name -}; -#endif diff --git a/src/XrdAcc/XrdAccAuthorize.hh b/src/XrdAcc/XrdAccAuthorize.hh deleted file mode 100644 index f5fb7a21399..00000000000 --- a/src/XrdAcc/XrdAccAuthorize.hh +++ /dev/null @@ -1,183 +0,0 @@ -#ifndef __ACC_AUTHORIZE__ -#define __ACC_AUTHORIZE__ -/******************************************************************************/ -/* */ -/* X r d A c c A u t h o r i z e . h h */ -/* */ -/* (c) 2000 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdAcc/XrdAccPrivs.hh" - -/******************************************************************************/ -/* A c c e s s _ O p e r a t i o n */ -/******************************************************************************/ - -//! The following are supported operations - -enum Access_Operation {AOP_Any = 0, //!< Special for getting privs - AOP_Chmod = 1, //!< chmod() - AOP_Chown = 2, //!< chown() - AOP_Create = 3, //!< open() with create - AOP_Delete = 4, //!< rm() or rmdir() - AOP_Insert = 5, //!< mv() for target - AOP_Lock = 6, //!< n/a - AOP_Mkdir = 7, //!< mkdir() - AOP_Read = 8, //!< open() r/o, prepare() - AOP_Readdir = 9, //!< opendir() - AOP_Rename = 10, //!< mv() for source - AOP_Stat = 11, //!< exists(), stat() - AOP_Update = 12, //!< open() r/w or append - AOP_LastOp = 12 // For limits testing - }; - -/******************************************************************************/ -/* X r d A c c A u t h o r i z e */ -/******************************************************************************/ - -class XrdOucEnv; -class XrdSecEntity; - -class XrdAccAuthorize -{ -public: - -//------------------------------------------------------------------------------ -//! Check whether or not the client is permitted specified access to a path. -//! -//! @param Entity -> Authentication information -//! @param path -> The logical path which is the target of oper -//! @param oper -> The operation being attempted (see the enum above). -//! If the oper is AOP_Any, then the actual privileges -//! are returned and the caller may make subsequent -//! tests using Test(). -//! @param Env -> Environmental information at the time of the -//! operation as supplied by the path CGI string. -//! This is optional and the pointer may be zero. -//! -//! @return Permit: a non-zero value (access is permitted) -//! Deny: zero (access is denied) -//------------------------------------------------------------------------------ - -virtual XrdAccPrivs Access(const XrdSecEntity *Entity, - const char *path, - const Access_Operation oper, - XrdOucEnv *Env=0) = 0; - -//------------------------------------------------------------------------------ -//! Route an audit message to the appropriate audit exit routine. See -//! XrdAccAudit.h for more information on how the default implementation works. -//! Currently, this method is not called by the ofs but should be used by the -//! implementation to record denials or grants, as warranted. -//! -//! @param accok -> True is access was grated; false otherwise. -//! @param Entity -> Authentication information -//! @param path -> The logical path which is the target of oper -//! @param oper -> The operation being attempted (see above) -//! @param Env -> Environmental information at the time of the -//! operation as supplied by the path CGI string. -//! This is optional and the pointer may be zero. -//! -//! @return Success: !0 information recorded. -//! Failure: 0 information could not be recorded. -//------------------------------------------------------------------------------ - -virtual int Audit(const int accok, - const XrdSecEntity *Entity, - const char *path, - const Access_Operation oper, - XrdOucEnv *Env=0) = 0; - -//------------------------------------------------------------------------------ -//! Check whether the specified operation is permitted. -//! -//! @param priv -> the privileges as returned by Access(). -//! @param oper -> The operation being attempted (see above) -//! -//! @return Permit: a non-zero value (access is permitted) -//! Deny: zero (access is denied) -//------------------------------------------------------------------------------ - -virtual int Test(const XrdAccPrivs priv, - const Access_Operation oper) = 0; - -//------------------------------------------------------------------------------ -//! Constructor -//------------------------------------------------------------------------------ - - XrdAccAuthorize() {} - -//------------------------------------------------------------------------------ -//! Destructor -//------------------------------------------------------------------------------ - -virtual ~XrdAccAuthorize() {} -}; - -/******************************************************************************/ -/* X r d A c c A u t h o r i z e O b j e c t */ -/******************************************************************************/ - -//------------------------------------------------------------------------------ -//! Obtain an authorization object. -//! -//! XrdAccAuthorizeObject() is an extern "C" function that is called to obtain -//! an instance of the auth object that will be used for all subsequent -//! authorization decisions. It must be defined in the plug-in shared library. -//! All the following extern symbols must be defined at file level! -//! -//! @param lp -> XrdSysLogger to be tied to an XrdSysError object for messages -//! @param cfn -> The name of the configuration file -//! @param parm -> Parameters specified on the authlib directive. If none it -//! is zero. -//! -//! @return Success: A pointer to the authorization object. -//! Failure: Null pointer which causes initialization to fail. -//------------------------------------------------------------------------------ - -/*! extern "C" XrdAccAuthorize *XrdAccAuthorizeObject(XrdSysLogger *lp, - const char *cfn, - const char *parm) {...} -*/ - -//------------------------------------------------------------------------------ -//! Specify the compilation version. -//! -//! Additionally, you *should* declare the xrootd version you used to compile -//! your plug-in. While not currently required, it is highly recommended to -//! avoid execution issues should the class definition change. Declare it as: -//------------------------------------------------------------------------------ - -/*! #include "XrdVersion.hh" - XrdVERSIONINFO(XrdAccAuthorizeObject,); - - where is a 1- to 15-character unquoted name identifying your plugin. - - For the default statically linked authorization framework, the non-extern C - XrdAccDefaultAuthorizeObject() is called instead so as to not conflict with - that symbol in a shared library plug-in. -*/ -#endif diff --git a/src/XrdAcc/XrdAccCapability.cc b/src/XrdAcc/XrdAccCapability.cc deleted file mode 100644 index ccd53962f6b..00000000000 --- a/src/XrdAcc/XrdAccCapability.cc +++ /dev/null @@ -1,170 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d A c c C a p a b i l i t y . c c */ -/* */ -/* (c) 2003 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdAcc/XrdAccCapability.hh" - -/******************************************************************************/ -/* E x t e r n a l R e f e r e n c e s */ -/******************************************************************************/ - -extern unsigned long XrdOucHashVal2(const char *KeyVal, int KeyLen); - -/******************************************************************************/ -/* C o n s t r u c t o r */ -/******************************************************************************/ - -XrdAccCapability::XrdAccCapability(char *pathval, XrdAccPrivCaps &privval) -{ - int i; - -// Do common initialization -// - next = 0; ctmp = 0; - priv.pprivs = privval.pprivs; priv.nprivs = privval.nprivs; - plen = strlen(pathval); pins = 0; prem = 0; - pkey = XrdOucHashVal2((const char *)pathval, plen); - path = strdup(pathval); - -// Now set up for @= insertions. We do this eventhough it might never be used -// - for (i = 0; i < plen; i++) - if (path[i] == '@' && path[i+1] == '=') - {pins = i; prem = plen - i - 2; break;} -} - -/******************************************************************************/ -/* D e s t r u c t o r */ -/******************************************************************************/ - -// This is a tricky destructor because deleting any item in the list must -// delete all subsequent items in the list (but only once). -// -XrdAccCapability::~XrdAccCapability() -{ - XrdAccCapability *cp, *np = next; - - if (path) {free(path); path = 0;} - - while(np) {cp = np; np = np->next; cp->next = 0; delete cp;} - next = 0; -} -/******************************************************************************/ -/* P r i v s */ -/******************************************************************************/ - -int XrdAccCapability::Privs( XrdAccPrivCaps &pathpriv, - const char *pathname, - const int pathlen, - const unsigned long pathhash, - const char *pathsub) -{XrdAccCapability *cp=this; - const int psl = (pathsub ? strlen(pathsub) : 0); - - do {if (cp->ctmp) - {if (cp->ctmp->Privs(pathpriv,pathname,pathlen,pathhash,pathsub)) - return 1; - } - else if (pathlen >= cp->plen) - if ((!pathsub && !strncmp(pathname, cp->path, cp->plen)) - || (pathsub && cp->Subcomp(pathname,pathlen,pathsub,psl))) - {pathpriv.pprivs = (XrdAccPrivs)(pathpriv.pprivs | - cp->priv.pprivs); - pathpriv.nprivs = (XrdAccPrivs)(pathpriv.nprivs | - cp->priv.nprivs); - return 1; - } - } while ((cp = cp->next)); - return 0; -} - -/******************************************************************************/ -/* S u b c o m p */ -/******************************************************************************/ - -int XrdAccCapability::Subcomp(const char *pathname, const int pathlen, - const char *pathsub, const int sublen) -{ int ncmp; - -// First check if the prefix matches -// - if (strncmp(pathname, path, pins)) return 0; - -// Now, check if the substitution appears in the source path -// - if (strncmp(&pathname[pins], pathsub, sublen)) return 0; - -// Now check if we can match the tail -// - ncmp = pins + sublen; - if ((pathlen - ncmp) < prem) return 0; - -// Return the results of matching the tail (prem should never be 0, but hey) -// - if (prem) return !strncmp(&path[pins+2], &pathname[ncmp], prem); - return 1; -} - -/******************************************************************************/ -/* X r d A c c C a p N a m e */ -/******************************************************************************/ -/******************************************************************************/ -/* D e s t r u c t o r */ -/******************************************************************************/ - -XrdAccCapName::~XrdAccCapName() -{ - XrdAccCapName *cp, *np = next; - -// Free regular storage -// - next = 0; - if (CapName) free(CapName); - if (C_List) delete C_List; - -// Delete list in a non-recursive way -// - while(np) {cp = np; np = np->next; cp->next = 0; delete cp;} -} - -/******************************************************************************/ -/* F i n d */ -/******************************************************************************/ - -XrdAccCapability *XrdAccCapName::Find(const char *name) -{ - int nlen = strlen(name); - XrdAccCapName *ncp = this; - - do {if (ncp->CNlen <= nlen && !strcmp(ncp->CapName,name+(nlen - ncp->CNlen))) - return ncp->C_List; - ncp = ncp->next; - } while(ncp); - return (XrdAccCapability *)0; -} diff --git a/src/XrdAcc/XrdAccCapability.hh b/src/XrdAcc/XrdAccCapability.hh deleted file mode 100644 index 59aaa20c96c..00000000000 --- a/src/XrdAcc/XrdAccCapability.hh +++ /dev/null @@ -1,123 +0,0 @@ -#ifndef __ACC_CAPABILITY__ -#define __ACC_CAPABILITY__ -/******************************************************************************/ -/* */ -/* X r d A c c C a p a b i l i t y . h h */ -/* */ -/* (c) 2003 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include - -#include "XrdAcc/XrdAccPrivs.hh" - -/******************************************************************************/ -/* X r d A c c C a p a b i l i t y */ -/******************************************************************************/ - -class XrdAccCapability -{ -public: -void Add(XrdAccCapability *newcap) {next = newcap;} - -XrdAccCapability *Next() {return next;} - -// Privs() searches the associated capability for a prefix matching path. If one -// is found, the privileges are or'd into the passed XrdAccPrivCaps struct and -// a 1 is returned. Otherwise, 0 is returned and XrdAccPrivCaps is unchanged. -// -int Privs( XrdAccPrivCaps &pathpriv, - const char *pathname, - const int pathlen, - const unsigned long pathhash, - const char *pathsub=0); - -int Privs( XrdAccPrivCaps &pathpriv, - const char *pathname, - const int pathlen, - const char *pathsub=0) - {extern unsigned long XrdOucHashVal2(const char *,int); - return Privs(pathpriv, pathname, pathlen, - XrdOucHashVal2(pathname,(int)pathlen),pathsub);} - -int Privs( XrdAccPrivCaps &pathpriv, - const char *pathname, - const char *pathsub=0) - {extern unsigned long XrdOucHashVal2(const char *,int); - int pathlen = strlen(pathname); - return Privs(pathpriv, pathname, pathlen, - XrdOucHashVal2(pathname, pathlen), pathsub);} - -int Subcomp(const char *pathname, const int pathlen, - const char *pathsub, const int sublen); - - XrdAccCapability(char *pathval, XrdAccPrivCaps &privval); - - XrdAccCapability(XrdAccCapability *taddr) - {next = 0; ctmp = taddr; - pkey = 0; path = 0; plen = 0; pins = 0; prem = 0; - } - - ~XrdAccCapability(); -private: -XrdAccCapability *next; // -> Next capability -XrdAccCapability *ctmp; // -> Capability template - -/*----------- The below fields are valid when template is zero -----------*/ - -XrdAccPrivCaps priv; -unsigned long pkey; -char *path; -int plen; -int pins; // index of @= -int prem; // remaining length after @= -}; - -/******************************************************************************/ -/* X r d A c c C a p N a m e */ -/******************************************************************************/ - -class XrdAccCapName -{ -public: -void Add(XrdAccCapName *cnp) {next = cnp;} - -XrdAccCapability *Find(const char *name); - - XrdAccCapName(char *name, XrdAccCapability *cap) - {next = 0; CapName = strdup(name); CNlen = strlen(name); - C_List = cap; - } - ~XrdAccCapName(); -private: -XrdAccCapName *next; -char *CapName; -int CNlen; -XrdAccCapability *C_List; -}; -#endif diff --git a/src/XrdAcc/XrdAccConfig.cc b/src/XrdAcc/XrdAccConfig.cc deleted file mode 100644 index a6a4bda156d..00000000000 --- a/src/XrdAcc/XrdAccConfig.cc +++ /dev/null @@ -1,841 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d A c c C o n f i g . c c */ -/* */ -/* (C) 2003 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Deprtment of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "XrdOuc/XrdOucLock.hh" -#include "XrdOuc/XrdOucEnv.hh" -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdOuc/XrdOucStream.hh" -#include "XrdAcc/XrdAccAccess.hh" -#include "XrdAcc/XrdAccAudit.hh" -#include "XrdAcc/XrdAccConfig.hh" -#include "XrdAcc/XrdAccGroups.hh" -#include "XrdAcc/XrdAccCapability.hh" - -/******************************************************************************/ -/* G l o b a l C o n f i g u r a t i o n O b j e c t */ -/******************************************************************************/ - -// The following is the single configuration object. Other objects needing -// access to this object should simply declare an extern to it. -// -XrdAccConfig XrdAccConfiguration; - -/******************************************************************************/ -/* d e f i n e s */ -/******************************************************************************/ - -#define TS_Xeq(x,m) if (!strcmp(x,var)) return m(Config,Eroute); - -#define TS_Str(x,m) if (!strcmp(x,var)) {free(m); m = strdup(val); return 0;} - -#define TS_Chr(x,m) if (!strcmp(x,var)) {m = val[0]; return 0;} - -#define TS_Bit(x,m,v) if (!strcmp(x,var)) {m |= v; return 0;} - -#define ACC_PGO 0x0001 - -/******************************************************************************/ -/* E x t e r n a l F u n c t i o n s */ -/******************************************************************************/ -/******************************************************************************/ -/* o o a c c _ C o n f i g _ R e f r e s h */ -/******************************************************************************/ - -void *XrdAccConfig_Refresh( void *start_data ) -{ - XrdSysError *Eroute = (XrdSysError *)start_data; - -// Get the number of seconds between refreshes -// - struct timespec naptime = {(time_t)XrdAccConfiguration.AuthRT, 0}; - -// Now loop until the bitter end -// - while(1) - {nanosleep(&naptime, 0); XrdAccConfiguration.ConfigDB(1, *Eroute);} - return (void *)0; -} - -/******************************************************************************/ -/* C o n s t r u c t o r */ -/******************************************************************************/ - -XrdAccConfig::XrdAccConfig() -{ - -// Initialize path value and databse pointer to nil -// - dbpath = strdup("/opt/xrd/etc/Authfile"); - Database = 0; - Authorization = 0; - -// Establish other defaults -// - ConfigDefaults(); -} - -/******************************************************************************/ -/* C o n f i g u r e */ -/******************************************************************************/ - -int XrdAccConfig::Configure(XrdSysError &Eroute, const char *cfn) { -/* - Function: Establish default values using a configuration file. - - Input: None. - - Output: 0 upon success or !0 otherwise. -*/ - char *var; - int retc, NoGo = 0, Cold = (Database == 0); - pthread_t reftid; - -// Print warm-up message -// - Eroute.Say("++++++ Authorization system initialization started."); - -// Process the configuration file and authorization database -// - if (!(Authorization = new XrdAccAccess(&Eroute)) - || (NoGo = ConfigFile(Eroute, cfn)) - || (NoGo = ConfigDB(0, Eroute))) - {if (Authorization) {delete Authorization, Authorization = 0;} - NoGo = 1; - } - -// Start a refresh thread unless this was a refresh thread call -// - if (Cold && !NoGo) - {if ((retc=XrdSysThread::Run(&reftid,XrdAccConfig_Refresh,(void *)&Eroute))) - Eroute.Emsg("ConfigDB",retc,"start refresh thread."); - } - -// All done -// - var = (NoGo > 0 ? (char *)"failed." : (char *)"completed."); - Eroute.Say("------ Authorization system initialization ", var); - return (NoGo > 0); -} - -/******************************************************************************/ -/* C o n f i g D B */ -/******************************************************************************/ - -int XrdAccConfig::ConfigDB(int Warm, XrdSysError &Eroute) -{ -/* - Function: Establish default values using a configuration file. - - Input: None. - - Output: 0 upon success or !0 otherwise. -*/ - char buff[128]; - int retc, anum = 0, NoGo = 0; - struct XrdAccAccess_Tables tabs; - XrdOucLock cdb_Lock(&Config_Context); - -// Indicate type of start we are doing -// - if (!Database) NoGo = !(Database = XrdAccAuthDBObject(&Eroute)); - else if (Warm && !Database->Changed(dbpath)) return 0; - -// Try to open the authorization database -// - if (!Database || !Database->Open(Eroute, dbpath)) return 1; - -// Allocate new hash tables -// - if (!(tabs.G_Hash = new XrdOucHash()) || - !(tabs.H_Hash = new XrdOucHash()) || - !(tabs.N_Hash = new XrdOucHash()) || - !(tabs.O_Hash = new XrdOucHash()) || - !(tabs.R_Hash = new XrdOucHash()) || - !(tabs.T_Hash = new XrdOucHash()) || - !(tabs.U_Hash = new XrdOucHash()) ) - {Eroute.Emsg("ConfigDB","Insufficient storage for id tables."); - Database->Close(); return 1; - } - -// Now start processing records until eof. -// - rulenum = 0; - while((retc = ConfigDBrec(Eroute, tabs))) {NoGo |= retc < 0; anum++;} - snprintf(buff, sizeof(buff), "%d auth entries processed in ", anum); - Eroute.Say("Config ", buff, dbpath); - -// All done, close the database and return if we failed -// - if (!Database->Close() || NoGo) return 1; - -// Do final setup for special identifiers (this will correctly order them) -// - if (tabs.SYList) idChk(Eroute, tabs.SYList, tabs); - -// Set the access control tables -// - if (!tabs.G_Hash->Num()) {delete tabs.G_Hash; tabs.G_Hash=0;} - if (!tabs.H_Hash->Num()) {delete tabs.H_Hash; tabs.H_Hash=0;} - if (!tabs.N_Hash->Num()) {delete tabs.N_Hash; tabs.N_Hash=0;} - if (!tabs.O_Hash->Num()) {delete tabs.O_Hash; tabs.O_Hash=0;} - if (!tabs.R_Hash->Num()) {delete tabs.R_Hash; tabs.R_Hash=0;} - if (!tabs.T_Hash->Num()) {delete tabs.T_Hash; tabs.T_Hash=0;} - if (!tabs.U_Hash->Num()) {delete tabs.U_Hash; tabs.U_Hash=0;} - Authorization->SwapTabs(tabs); - -// All done -// - return NoGo; -} - -/******************************************************************************/ -/* P r i v a t e F u n c t i o n s */ -/******************************************************************************/ -/******************************************************************************/ -/* C o n f i g F i l e P r o c e s s i n g M e t h o d s */ -/******************************************************************************/ - -int XrdAccConfig::ConfigFile(XrdSysError &Eroute, const char *ConfigFN) { -/* - Function: Establish default values using a configuration file. - - Input: None. - - Output: 1 - Processing failed. - 0 - Processing completed successfully. - -1 = Security is to be disabled by request. -*/ - char *var; - int cfgFD, retc, NoGo = 0, recs = 0; - XrdOucEnv myEnv; - XrdOucStream Config(&Eroute, getenv("XRDINSTANCE"), &myEnv, "=====> "); - -// If there is no config file, complain -// - if( !ConfigFN || !*ConfigFN) - {Eroute.Emsg("Config", "Authorization configuration file not specified."); - return 1; - } - -// Check if security is to be disabled -// - if (!strcmp(ConfigFN, "none")) - {Eroute.Emsg("Config", "Authorization system deactivated."); - return -1; - } - -// Try to open the configuration file. -// - if ( (cfgFD = open(ConfigFN, O_RDONLY, 0)) < 0) - {Eroute.Emsg("Config", errno, "open config file", ConfigFN); - return 1; - } - Eroute.Emsg("Config","Authorization system using configuration in",ConfigFN); - -// Now start reading records until eof. -// - ConfigDefaults(); Config.Attach(cfgFD); Config.Tabs(0); - while((var = Config.GetMyFirstWord())) - {if (!strncmp(var, "acc.", 2)) - {recs++; - if (ConfigXeq(var+4, Config, Eroute)) {Config.Echo(); NoGo = 1;} - } - } - -// Now check if any errors occured during file i/o -// - if ((retc = Config.LastError())) - NoGo = Eroute.Emsg("Config",-retc,"read config file",ConfigFN); - else {char buff[128]; - snprintf(buff, sizeof(buff), - "%d authorization directives processed in ", recs); - Eroute.Say("Config ", buff, ConfigFN); - } - Config.Close(); - -// Set external options, as needed -// - if (options & ACC_PGO) GroupMaster.SetOptions(Primary_Only); - -// All done -// - return NoGo; -} - -/******************************************************************************/ -/* C o n f i g D e f a u l t s */ -/******************************************************************************/ - -void XrdAccConfig::ConfigDefaults() -{ - AuthRT = 60*60*12; - options = 0; -} - -/******************************************************************************/ -/* C o n f i g X e q */ -/******************************************************************************/ - -int XrdAccConfig::ConfigXeq(char *var, XrdOucStream &Config, XrdSysError &Eroute) -{ - -// Fan out based on the variable -// - TS_Xeq("audit", xaud); - TS_Xeq("authdb", xdbp); - TS_Xeq("authrefresh", xart); - TS_Xeq("gidlifetime", xglt); - TS_Xeq("gidretran", xgrt); - TS_Xeq("nisdomain", xnis); - TS_Bit("pgo", options, ACC_PGO); - -// No match found, complain. -// - Eroute.Emsg("Config", "unknown directive", var); - Config.Echo(); - return 1; -} - -/******************************************************************************/ -/* x a u d */ -/******************************************************************************/ - -/* Function: xaud - - Purpose: To parse the directive: audit - - options: - - deny audit access denials. - grant audit access grants. - none audit is disabled. - - Output: 0 upon success or !0 upon failure. -*/ - -int XrdAccConfig::xaud(XrdOucStream &Config, XrdSysError &Eroute) -{ - static struct auditopts {const char *opname; int opval;} audopts[] = - { - {"deny", (int)audit_deny}, - {"grant", (int)audit_grant} - }; - int i, audval = 0, numopts = sizeof(audopts)/sizeof(struct auditopts); - char *val; - - val = Config.GetWord(); - if (!val || !val[0]) - {Eroute.Emsg("Config", "audit option not specified"); return 1;} - while (val && val[0]) - {if (!strcmp(val, "none")) audval = (int)audit_none; - else for (i = 0; i < numopts; i++) - {if (!strcmp(val, audopts[i].opname)) - {audval |= audopts[i].opval; break;} - if (i >= numopts) - {Eroute.Emsg("Config","invalid audit option -",val); - return 1; - } - } - val = Config.GetWord(); - } - Authorization->Auditor->setAudit((XrdAccAudit_Options)audval); - return 0; -} - -/******************************************************************************/ -/* x a r t */ -/******************************************************************************/ - -/* Function: xart - - Purpose: To parse the directive: authrefresh - - minimum number of seconds between aythdb refreshes. - - Output: 0 upon success or !0 upon failure. -*/ - -int XrdAccConfig::xart(XrdOucStream &Config, XrdSysError &Eroute) -{ - char *val; - int reft; - - val = Config.GetWord(); - if (!val || !val[0]) - {Eroute.Emsg("Config","authrefresh value not specified");return 1;} - if (XrdOuca2x::a2tm(Eroute,"authrefresh value",val,&reft,60)) - return 1; - AuthRT = reft; - return 0; -} - -/******************************************************************************/ -/* x d b p */ -/******************************************************************************/ - -/* Function: xdbp - - Purpose: To parse the directive: authdb - - is the path to the authorization database. - - Output: 0 upon success or !0 upon failure. -*/ - -int XrdAccConfig::xdbp(XrdOucStream &Config, XrdSysError &Eroute) -{ - char *val; - - val = Config.GetWord(); - if (!val || !val[0]) - {Eroute.Emsg("Config","authdb path not specified");return 1;} - dbpath = strdup(val); - return 0; -} - -/******************************************************************************/ -/* x g l t */ -/******************************************************************************/ - -/* Function: xglt - - Purpose: To parse the directive: gidlifetime - - maximum number of seconds to cache gid information. - - Output: 0 upon success or !0 upon failure. -*/ - -int XrdAccConfig::xglt(XrdOucStream &Config, XrdSysError &Eroute) -{ - char *val; - int reft; - - val = Config.GetWord(); - if (!val || !val[0]) - {Eroute.Emsg("Config","gidlifetime value not specified");return 1;} - if (XrdOuca2x::a2tm(Eroute,"gidlifetime value",val,&reft,60)) - return 1; - GroupMaster.SetLifetime(reft); - return 0; -} - -/******************************************************************************/ -/* x g r t */ -/******************************************************************************/ - -/* Function: xgrt - - Purpose: To parse the directive: gidretran - - is a list of blank separated gid's that must be - retranslated. - - Output: 0 upon success or !0 upon failure. -*/ - -int XrdAccConfig::xgrt(XrdOucStream &Config, XrdSysError &Eroute) -{ - char *val; - int gid; - - val = Config.GetWord(); - if (!val || !val[0]) - {Eroute.Emsg("Config","gidretran value not specified"); return 1;} - - while (val && val[0]) - {if (XrdOuca2x::a2i(Eroute, "gid", val, &gid, 0)) return 1; - if (GroupMaster.Retran((gid_t)gid) < 0) - {Eroute.Emsg("Config", "to many gidretran gid's"); return 1;} - val = Config.GetWord(); - } - return 0; -} - -/******************************************************************************/ -/* x n i s */ -/******************************************************************************/ - -/* Function: xnis - - Purpose: To parse the directive: nisdomain - - the NIS domain to be used for nis look-ups. - - Output: 0 upon success or !0 upon failure. -*/ - -int XrdAccConfig::xnis(XrdOucStream &Config, XrdSysError &Eroute) -{ - char *val; - - val = Config.GetWord(); - if (!val || !val[0]) - {Eroute.Emsg("Config","nisdomain value not specified");return 1;} - GroupMaster.SetDomain(strdup(val)); - return 0; -} - -/******************************************************************************/ -/* D a t a b a s e P r o c e s s i n g */ -/******************************************************************************/ -/******************************************************************************/ -/* C o n f i g D B r e c */ -/******************************************************************************/ - -int XrdAccConfig::ConfigDBrec(XrdSysError &Eroute, - struct XrdAccAccess_Tables &tabs) -{ -// The following enum is here for convenience -// - enum DB_RecType { Group_ID = 'g', - Host_ID = 'h', - Netgrp_ID = 'n', - Org_ID = 'o', - Role_ID = 'r', - Set_ID = 's', - Template_ID = 't', - User_ID = 'u', - Xxx_ID = 'x', - Def_ID = '=', - No_ID = 0 - }; - char *authid, rtype, *path, *privs; - int alluser = 0, anyuser = 0, domname = 0, NoGo = 0; - DB_RecType rectype; - XrdAccAccess_ID *sp = 0; - XrdOucHash *hp; - XrdAccGroupType gtype = XrdAccNoGroup; - XrdAccPrivCaps xprivs; - XrdAccCapability mycap((char *)"", xprivs), *currcap, *lastcap = &mycap; - XrdAccCapName *ncp; - bool istmplt, isDup, xclsv = false; - - // Prepare the next record in the database - // - if (!(rtype = Database->getRec(&authid))) return 0; - rectype = (DB_RecType)rtype; - - // Set up to handle the particular record - // - switch(rectype) - {case Group_ID: hp = tabs.G_Hash; - gtype=XrdAccUnixGroup; - break; - case Host_ID: hp = tabs.H_Hash; - domname = (authid[0] == '.'); - break; - case Set_ID: hp = 0; - break; - case Netgrp_ID: hp = tabs.N_Hash; - gtype=XrdAccNetGroup; - break; - case Org_ID: hp = tabs.O_Hash; - break; - case Role_ID: hp = tabs.R_Hash; - break; - case Template_ID: hp = tabs.T_Hash; - break; - case User_ID: hp = tabs.U_Hash; - alluser = (authid[0] == '*' && !authid[1]); - anyuser = (authid[0] == '=' && !authid[1]); - break; - case Xxx_ID: hp = 0; xclsv = true; - break; - case Def_ID: return idDef(Eroute, tabs, authid); - break; - default: char badtype[2] = {rtype, '\0'}; - Eroute.Emsg("ConfigXeq", "Invalid id type -", - badtype); - return -1; - break; - } - - // Check if this id is already defined in the table. For 's' rules the id - // must have been previously defined. - // - if (domname) - isDup = tabs.D_List && tabs.D_List->Find((const char *)authid); - else if (alluser) isDup = tabs.Z_List != 0; - else if (anyuser) isDup = tabs.X_List != 0; - else if (hp) isDup = hp->Find(authid) != 0; - else {if (!(sp = tabs.S_Hash->Find(authid))) - {Eroute.Emsg("ConfigXeq", "Missing id definition -", authid); - return -1; - } - isDup = sp->caps != 0; - sp->rule = (xclsv ? rulenum++ : -1); - } - - if (isDup) - {Eroute.Emsg("ConfigXeq", "duplicate rule for id -", authid); - return -1; - } - - // Add this ID to the appropriate group object constants table - // - if (gtype) GroupMaster.AddName(gtype, (const char *)authid); - - // Now start getting pairs until we hit the logical end - // - while(1) {NoGo = 0; - if (!Database->getPP(&path, &privs, istmplt)) break; - if (!path) continue; // Skip pathless entries - NoGo = 1; - if (istmplt) - {if ((currcap = tabs.T_Hash->Find(path))) - currcap = new XrdAccCapability(currcap); - else {Eroute.Emsg("ConfigXeq", "Missing template -", path); - break; - } - } else { - if (!privs) - {Eroute.Emsg("ConfigXeq", "Missing privs for path", path); - break; - } - if (!PrivsConvert(privs, xprivs)) - {Eroute.Emsg("ConfigXeq", "Invalid privs -", privs); - break; - } - currcap = new XrdAccCapability(path, xprivs); - } - lastcap->Add(currcap); - lastcap = currcap; - } - - // Check if all went well - // - if (NoGo) return -1; - - // Check if any capabilities were specified - // - if (!mycap.Next()) - {Eroute.Emsg("ConfigXeq", "no capabilities specified for", authid); - return -1; - } - - // Insert the capability into the appropriate table/list - // - if (sp) sp->caps = mycap.Next(); - else if (domname) - {if (!(ncp = new XrdAccCapName(authid, mycap.Next()))) - {Eroute.Emsg("ConfigXeq","unable to add id",authid); return -1;} - if (tabs.E_List) tabs.E_List->Add(ncp); - else tabs.D_List = ncp; - tabs.E_List = ncp; - } - else if (anyuser) tabs.X_List = mycap.Next(); - else if (alluser) tabs.Z_List = mycap.Next(); - else hp->Add(authid, mycap.Next()); - - // All done - // - mycap.Add((XrdAccCapability *)0); - return 1; -} - -/******************************************************************************/ -/* Private: i d C h k */ -/******************************************************************************/ - -void XrdAccConfig::idChk(XrdSysError &Eroute, - XrdAccAccess_ID *idList, - XrdAccAccess_Tables &tabs) -{ - std::map idMap; - XrdAccAccess_ID *idPN, *xList = 0, *yList = 0; - -// Run through the list to make everything was used. We also, sort these items -// in the order the associated rule appeared. -// - while(idList) - {idPN = idList->next; - if (idList->caps == 0) - Eroute.Say("Config ","Warning, unused identifier definition '", - idList->name, "'."); - else if (idList->rule >= 0) idMap[idList->rule] = idList; - else {idList->next = yList; yList = idList;} - idList = idPN; - } - -// Place 'x' rules in the order they were used. The ;s; rules are in the -// order the id's were defined which is OK because the are inclusive. -// - std::map::reverse_iterator rit; - for (rit = idMap.rbegin(); rit != idMap.rend(); ++rit) - {rit->second->next = xList; - xList = rit->second; - } - -// Set the new lists in the supplied tabs structure -// - tabs.SXList = xList; - tabs.SYList = yList; -} - -/******************************************************************************/ -/* Private: i d D e f */ -/******************************************************************************/ - -int XrdAccConfig::idDef(XrdSysError &Eroute, - struct XrdAccAccess_Tables &tabs, - const char *idName) -{ - XrdAccAccess_ID *xID, theID(idName); - char *idname, buff[80], idType; - bool haveID = false, idDup = false; - -// Now start getting pairs until we hit the logical end -// - while(!idDup) - {if (!(idType = Database->getID(&idname))) break; - haveID = true; - switch(idType) - {case 'g': if (theID.grp) idDup = true; - else{theID.grp = strdup(idname); - theID.glen = strlen(idname); - } - break; - case 'h': if (theID.host) idDup = true; - else{theID.host = strdup(idname); - theID.hlen = strlen(idname); - } - break; - case 'o': if (theID.org) idDup = true; - else theID.org = strdup(idname); - break; - case 'r': if (theID.role) idDup = true; - else theID.role = strdup(idname); - break; - case 'u': if (theID.user) idDup = true; - else theID.user = strdup(idname); - break; - default: snprintf(buff, sizeof(buff), "'%c: %s' for", - idType, idname); - Eroute.Emsg("ConfigXeq", "Invalid id selector -", - buff, theID.name); - return -1; - break; - } - if (idDup) - {snprintf(buff, sizeof(buff), - "id selector '%c' specified twice for", idType); - Eroute.Emsg("ConfigXeq", buff, theID.name); - return -1; - } - } - -// Make sure some kind of id was specified -// - if (!haveID) - {Eroute.Emsg("ConfigXeq", "No id selectors specified for", theID.name); - return -1; - } - -// Make sure this name has not been specified before -// - if (!tabs.S_Hash) tabs.S_Hash = new XrdOucHash; - else if (tabs.S_Hash->Find(theID.name)) - {Eroute.Emsg("ConfigXeq","duplicate id definition -",theID.name); - return -1; - } - -// Export the id definition and add it to the S_Hash -// - xID = theID.Export(); - tabs.S_Hash->Add(xID->name, xID); - -// Place this FIFO in SYList (they reordered later based on rule usage) -// - xID->next = tabs.SYList; - tabs.SYList = xID; - -// All done -// - return 1; -} - -/******************************************************************************/ -/* P r i v s C o n v e r t */ -/******************************************************************************/ - -int XrdAccConfig::PrivsConvert(char *privs, XrdAccPrivCaps &ctab) -{ - int i = 0; - XrdAccPrivs ptab[] = {XrdAccPriv_None, XrdAccPriv_None}; // Speed conversion here - - // Convert the privs - // - while(*privs) - {switch((XrdAccPrivSpec)(*privs)) - {case All_Priv: - ptab[i] = (XrdAccPrivs)(ptab[i]|XrdAccPriv_All); - break; - case Delete_Priv: - ptab[i] = (XrdAccPrivs)(ptab[i]|XrdAccPriv_Delete); - break; - case Insert_Priv: - ptab[i] = (XrdAccPrivs)(ptab[i]|XrdAccPriv_Insert); - break; - case Lock_Priv: - ptab[i] = (XrdAccPrivs)(ptab[i]|XrdAccPriv_Lock); - break; - case Lookup_Priv: - ptab[i] = (XrdAccPrivs)(ptab[i]|XrdAccPriv_Lookup); - break; - case Rename_Priv: - ptab[i] = (XrdAccPrivs)(ptab[i]|XrdAccPriv_Rename); - break; - case Read_Priv: - ptab[i] = (XrdAccPrivs)(ptab[i]|XrdAccPriv_Read); - break; - case Write_Priv: - ptab[i] = (XrdAccPrivs)(ptab[i]|XrdAccPriv_Write); - break; - case Neg_Priv: if (i) return 0; i++; break; - default: return 0; - } - privs++; - } - ctab.pprivs = ptab[0]; ctab.nprivs = ptab[1]; - return 1; -} diff --git a/src/XrdAcc/XrdAccConfig.hh b/src/XrdAcc/XrdAccConfig.hh deleted file mode 100644 index bcb8f1c0715..00000000000 --- a/src/XrdAcc/XrdAccConfig.hh +++ /dev/null @@ -1,117 +0,0 @@ -#ifndef _ACC_CONFIG_H -#define _ACC_CONFIG_H -/******************************************************************************/ -/* */ -/* X r d A c c C o n f i g . h h */ -/* */ -/* (C) 2003 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Deprtment of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -#include "XrdOuc/XrdOuca2x.hh" -#include "XrdSys/XrdSysError.hh" -#include "XrdOuc/XrdOucHash.hh" -#include "XrdSys/XrdSysPthread.hh" -#include "XrdOuc/XrdOucStream.hh" -#include "XrdAcc/XrdAccAccess.hh" -#include "XrdAcc/XrdAccAuthDB.hh" -#include "XrdAcc/XrdAccCapability.hh" -#include "XrdAcc/XrdAccGroups.hh" - -/******************************************************************************/ -/* X r d A c c G l i s t */ -/******************************************************************************/ - -struct XrdAccGlist -{ - struct XrdAccGlist *next; /* Null if this is the last one */ - char *name; /* -> null terminated name */ - - XrdAccGlist(const char *Name, struct XrdAccGlist *Next=0) - {name = strdup(Name); next = Next;} - ~XrdAccGlist() - {if (name) free(name);} -}; - -/******************************************************************************/ -/* X r d A c c C o n f i g */ -/******************************************************************************/ - -class XrdAccConfig -{ -public: - -// Configure() is called during initialization. -// -int Configure(XrdSysError &Eroute, const char *cfn); - -// ConfigDB() simply refreshes the in-core authorization database. When the -// Warm is true, a check is made whether the database actually changed and the -// refresh is skipped if it has not changed. -// -int ConfigDB(int Warm, XrdSysError &Eroute); - -XrdAccAccess *Authorization; -XrdAccGroups GroupMaster; - -int AuthRT; - - XrdAccConfig(); - ~XrdAccConfig() {} // Configuration is never destroyed! - -private: - -struct XrdAccGlist *addGlist(gid_t Gid, const char *Gname, - struct XrdAccGlist *Gnext); -int ConfigDBrec(XrdSysError &Eroute, - struct XrdAccAccess_Tables &tabs); -void ConfigDefaults(void); -int ConfigFile(XrdSysError &Eroute, const char *cfn); -int ConfigXeq(char *, XrdOucStream &, XrdSysError &); -void idChk(XrdSysError &Eroute, XrdAccAccess_ID *idList, - XrdAccAccess_Tables &tabs); -int idDef(XrdSysError &Eroute, XrdAccAccess_Tables &tabs, - const char *idName); -int PrivsConvert(char *privs, XrdAccPrivCaps &ctab); - -int xaud(XrdOucStream &Config, XrdSysError &Eroute); -int xart(XrdOucStream &Config, XrdSysError &Eroute); -int xdbp(XrdOucStream &Config, XrdSysError &Eroute); -int xglt(XrdOucStream &Config, XrdSysError &Eroute); -int xgrt(XrdOucStream &Config, XrdSysError &Eroute); -int xnis(XrdOucStream &Cofig, XrdSysError &Eroute); - -XrdAccAuthDB *Database; -char *dbpath; - -XrdSysMutex Config_Context; -XrdSysThread Config_Refresh; - -int options; -int rulenum; -}; -#endif diff --git a/src/XrdAcc/XrdAccGroups.cc b/src/XrdAcc/XrdAccGroups.cc deleted file mode 100644 index d698a43308d..00000000000 --- a/src/XrdAcc/XrdAccGroups.cc +++ /dev/null @@ -1,412 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d A c c G r o u p s . c c */ -/* */ -/* (c) 2003 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdSys/XrdSysPwd.hh" -#include "XrdAcc/XrdAccCapability.hh" -#include "XrdAcc/XrdAccGroups.hh" -#include "XrdAcc/XrdAccPrivs.hh" - -// Additionally, this routine does not support a user in more than -// NGROUPS_MAX groups. This is a standard unix limit defined in limits.h. - -/******************************************************************************/ -/* G l o b a l G r o u p s O b j e c t */ -/******************************************************************************/ - -// There is only one Groups object that handles group memberships. Others -// needing access to this object should declare an extern to this object. -// -XrdAccGroups XrdAccGroupMaster; - -/******************************************************************************/ -/* G r o u p C o n s t r u c t i o n A r g u m e n t s */ -/******************************************************************************/ - -struct XrdAccGroupArgs {const char *user; - const char *host; - int gtabi; - const char *Gtab[NGROUPS_MAX]; - }; - -/******************************************************************************/ -/* C o n s t r u c t o r */ -/******************************************************************************/ - -XrdAccGroups::XrdAccGroups() -{ - -// Do standard initialization -// - retrancnt = 0; - HaveGroups = 0; - HaveNetGroups = 0; - options = No_Group_Opt; - domain = 0; - LifeTime = 60*60*12; -} - -/******************************************************************************/ -/* A d d N a m e */ -/******************************************************************************/ - -char *XrdAccGroups::AddName(const XrdAccGroupType gtype, const char *name) -{ - char *np; - XrdOucHash *hp; - -// Prepare to add a group name -// - if (gtype == XrdAccNetGroup) {hp = &NetGroup_Names; HaveNetGroups = 1;} - else {hp = &Group_Names; HaveGroups = 1;} - -// Lock the Name hash table -// - Group_Name_Context.Lock(); - -// Add a name into the name hash table. We need to only keep a single -// read/only copy of the group name to speed multi-threading. -// - if (!(np = hp->Find(name))) - {hp->Add(name, 0, 0, Hash_data_is_key); - if (!(np = hp->Find(name))) - cerr <<"XrdAccGroups: Unable to add group " <First()) glist = new XrdAccGroupList(*glist); - else glist = 0; - Group_Cache_Context.UnLock(); - return glist; - } - Group_Cache_Context.UnLock(); - -// If the user has no password file entry, then we have no groups for user. -// All code that tries to construct a group list is protected by the -// Group_Build_Context mutex, obtained after we get the pwd entry. -// - XrdSysPwd thePwd(user, &pw); - if (pw == NULL) return (XrdAccGroupList *)0; - -// Build first entry for the primary group. We will ignore the primary group -// listing later. We do this to ensure that the user has at least one group -// regardless of what the groups file actually says. -// - Group_Build_Context.Lock(); - gtabi = addGroup(user, pw->pw_gid, 0, Gtab, 0); - -// Now run through all of the group entries getting the list of user's groups -// Do this only when Primary_Only is not turned on (i.e., SVR5 semantics) -// - if (!(options & Primary_Only)) - { - setgrent() ; - while ((gr = getgrent())) - { - if (pw->pw_gid == gr->gr_gid) continue; /*Already have this one.*/ - for (cp = gr->gr_mem; cp && *cp; cp++) - if (strcmp(*cp, user) == 0) - gtabi = addGroup(user, gr->gr_gid, - Dotran(gr->gr_gid,gr->gr_name), - Gtab, gtabi); - } - endgrent(); - } - -// All done with non mt-safe routines -// - Group_Build_Context.UnLock(); - -// Allocate a new GroupList object -// - glist = new XrdAccGroupList(gtabi, (const char **)Gtab); - -// Add this user to the group cache to speed things up the next time -// - Group_Cache_Context.Lock(); - Group_Cache.Add(user, glist, LifeTime); - Group_Cache_Context.UnLock(); - -// Return a copy of the group list since the original may be deleted -// - if (!gtabi) return (XrdAccGroupList *)0; - return new XrdAccGroupList(gtabi, (const char **)Gtab); -} - -/******************************************************************************/ -/* N e t G r o u p s ( u s e r , h o s t ) */ -/******************************************************************************/ - -XrdAccGroupList *XrdAccGroups::NetGroups(const char *user, const char *host) -{ -XrdAccGroupList *glist; -int i, j; -char uh_key[MAXHOSTNAMELEN+96]; -struct XrdAccGroupArgs GroupTab; -int XrdAccCheckNetGroup(const char *netgroup, char *key, void *Arg); - -// Check if we have any Netgroups -// - if (!HaveNetGroups) return (XrdAccGroupList *)0; - -// Construct the key for this user -// - i = strlen(user); j = strlen(host); - if (i+j+2 > (int)sizeof(uh_key)) return (XrdAccGroupList *)0; - strcpy(uh_key, user); - uh_key[i] = '@'; - strcpy(&uh_key[i+1], host); - -// Check if we already have this user in the group cache. Since we may be -// modifying the cache, we need to have exclusive control over it. We must -// copy the group cache entry because the original may be deleted at any time. -// - NetGroup_Cache_Context.Lock(); - if ((glist = NetGroup_Cache.Find(uh_key))) - {if (glist->First()) glist = new XrdAccGroupList(*glist); - else glist = 0; - NetGroup_Cache_Context.UnLock(); - return glist; - } - NetGroup_Cache_Context.UnLock(); - -// For each known netgroup, check to see if the user is in the netgroup. -// - GroupTab.user = user; - GroupTab.host = host; - GroupTab.gtabi = 0; - Group_Name_Context.Lock(); - NetGroup_Names.Apply(XrdAccCheckNetGroup, (void *)&GroupTab); - Group_Name_Context.UnLock(); - -// Allocate a new GroupList object -// - glist = new XrdAccGroupList(GroupTab.gtabi, - (const char **)GroupTab.Gtab); - -// Add this user to the group cache to speed things up the next time -// - NetGroup_Cache_Context.Lock(); - NetGroup_Cache.Add((const char *)uh_key, glist, LifeTime); - NetGroup_Cache_Context.UnLock(); - -// Return a copy of the group list -// - if (!GroupTab.gtabi) return (XrdAccGroupList *)0; - return new XrdAccGroupList(GroupTab.gtabi, - (const char **)GroupTab.Gtab); -} - -/******************************************************************************/ -/* P u r g e C a c h e */ -/******************************************************************************/ - -void XrdAccGroups::PurgeCache() -{ - -// Purge the group cache -// - Group_Cache_Context.Lock(); - Group_Cache.Purge(); - Group_Cache_Context.UnLock(); - -// Purge the netgroup cache -// - NetGroup_Cache_Context.Lock(); - NetGroup_Cache.Purge(); - NetGroup_Cache_Context.UnLock(); -} - -/******************************************************************************/ -/* R e t r a n */ -/******************************************************************************/ - -int XrdAccGroups::Retran(const gid_t gid) -{ - if ((int)gid < 0) retrancnt = 0; - else {if (retrancnt > (int)(sizeof(retrangid)/sizeof(gid_t))) return -1; - retrangid[retrancnt++] = gid; - } - return 0; -} - -/******************************************************************************/ -/* P r i v a t e M e t h o d s */ -/******************************************************************************/ - -/******************************************************************************/ -/* a d d G r o u p */ -/******************************************************************************/ - -int XrdAccGroups::addGroup(const char *user, const gid_t gid, char *gname, - char **Gtab, int gtabi) -{ - char *gp; - -// Check if we have room to add another group. We can squeek by such errors -// because all it means is that the user normally has fewer privs (which is -// not always true, sigh). -// - if (gtabi >= NGROUPS_MAX) - {if (gtabi == NGROUPS_MAX) - cerr <<"XrdAccGroups: More than " <gr_name; - } - -// Check if we have this group registered. Only a handful of groups are -// actually relevant. Ignore the unreferenced groups. If registered, we -// need the persistent name because of multi-threading issues. -// - if (!(gp = Group_Names.Find(gname)) ) return gtabi; - -// Add the groupname to the table of groups for the user -// - Gtab[gtabi++] = gp; - return gtabi; -} - -/******************************************************************************/ -/* D o t r a n */ -/******************************************************************************/ - -char *XrdAccGroups::Dotran(const gid_t gid, char *gname) -{ - int i; - - // See if the groupname needs to be retranslated. This is necessary - // When multiple groups share the same gid due to NIS constraints. - // - for (i = 0; i < retrancnt; i++) if (retrangid[i] == gid) return (char *)0; - return gname; -} - -/******************************************************************************/ -/* E x t e r n a l F u n c t i o n s */ -/******************************************************************************/ - -/******************************************************************************/ -/* o o a c c _ C h e c k N e t G r o u p */ -/******************************************************************************/ - -int XrdAccCheckNetGroup(const char *netgroup, char *key, void *Arg) -{ - struct XrdAccGroupArgs *grp = static_cast(Arg); - - // Check if this netgroup, user, host, domain combination exists. - // - if (innetgr(netgroup, (const char *)grp->host, (const char *)grp->user, - XrdAccGroupMaster.Domain())) - {if (grp->gtabi >= NGROUPS_MAX) - {if (grp->gtabi == NGROUPS_MAX) - cerr <<"XrdAccGroups: More than " <gtabi <<"netgroups for " <user <Gtab[grp->gtabi] = netgroup; grp->gtabi++; - } - return 0; -} diff --git a/src/XrdAcc/XrdAccGroups.hh b/src/XrdAcc/XrdAccGroups.hh deleted file mode 100644 index 0dabcdb6b5d..00000000000 --- a/src/XrdAcc/XrdAccGroups.hh +++ /dev/null @@ -1,174 +0,0 @@ -#ifndef _ACC_GROUPS_H -#define _ACC_GROUPS_H -/******************************************************************************/ -/* */ -/* X r d A c c G r o u p s . h h */ -/* */ -/* (C) 2003 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Deprtment of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -#include "XrdOuc/XrdOucHash.hh" -#include "XrdSys/XrdSysPthread.hh" - -/******************************************************************************/ -/* X r d A c c G r o u p L i s t */ -/******************************************************************************/ - -class XrdAccGroupList -{ -public: - -const char *First() {return grouptab[0];} - -const char *Next() {if (grouptab[nextgroup]) return grouptab[nextgroup++]; - return (const char *)0; - } - - void Reset() {nextgroup = 0;} - - XrdAccGroupList(const int cnt=0, const char **gtable=0) - {int j = (cnt > NGROUPS_MAX ? NGROUPS_MAX : cnt); - if (cnt){memcpy((void *)grouptab, (const void *)gtable, - (size_t)(j * sizeof(char *))); - } - memset((void *)&grouptab[cnt], 0, - (size_t)((NGROUPS_MAX-j+1)*sizeof(char *))); - nextgroup = 0; - } - - XrdAccGroupList(XrdAccGroupList & rv) - {memcpy((void *)grouptab,(const void *)rv.grouptab,sizeof(grouptab)); - nextgroup = 0; - } - - ~XrdAccGroupList() {} - -private: -const char *grouptab[NGROUPS_MAX+1]; - int nextgroup; -}; - -/******************************************************************************/ -/* G r o u p s O p t i o n s */ -/******************************************************************************/ - -enum XrdAccGroups_Options { Primary_Only = 0x0001, - Groups_Debug = 0x8000, - No_Group_Opt = 0x0000 - }; - -/******************************************************************************/ -/* G r o u p T y p e s */ -/******************************************************************************/ - -enum XrdAccGroupType {XrdAccNoGroup = 0, XrdAccUnixGroup, XrdAccNetGroup}; - -/******************************************************************************/ -/* X r d A c c G r o u p s */ -/******************************************************************************/ - -class XrdAccGroups -{ -public: - -// Domain() returns whatever we have for the NIS domain. -// -const char *Domain() {return domain;} - -// AddName() registers a name in the static name table. This allows us to -// avoid copying the strings a table points to when returning a table copy. -// If the name was added successfully, a pointer to the name is returned. -// Otherwise, zero is returned. -// -char *AddName(const XrdAccGroupType gtype, const char *name); - -// FindName() looks up a name in the static name table. -// -char *FindName(const XrdAccGroupType gtype, const char *name); - -// Groups() returns all of the relevant groups that a user belongs to. A -// null pointer may be returned if no groups are applicable. -// -XrdAccGroupList *Groups(const char *user); - -// NetGroups() returns all of the relevant netgroups that the user/host -// combination belongs to. A null pointer may be returned is no netgroups -// are applicable. -// -XrdAccGroupList *NetGroups(const char *user, const char *host); - -// PurgeCache() removes all entries in the various caches. It is called -// whenever a new set of access tables has been instantiated. -// -void PurgeCache(); - -// Use by the configuration object to set group id's that must be looked up. -// -int Retran(const gid_t gid); - -// Use by the configuration object to establish the netgroup domain. -// -void SetDomain(const char *dname) {domain = dname;} - -// Used by the configuration object to set the cache lifetime. -// -void SetLifetime(const int seconds) {LifeTime = (int)seconds;} - -// Used by the configuration object to set various options -// -void SetOptions(XrdAccGroups_Options opts) {options = opts;} - - XrdAccGroups(); - - ~XrdAccGroups() {} // The group object never gets deleted!! - -private: - -int addGroup(const char *user, const gid_t gid, char *gname, - char **Gtab, int gtabi); -char *Dotran(const gid_t gid, char *gname); - -gid_t retrangid[128]; // Up to 128 retranslatable gids -int retrancnt; // Number of used entries -time_t LifeTime; // Seconds we can keep something in the cache -const char *domain; // NIS netgroup domain to use - -XrdAccGroups_Options options;// Various option values. -int HaveGroups; -int HaveNetGroups; - -XrdSysMutex Group_Build_Context, Group_Name_Context; -XrdSysMutex Group_Cache_Context, NetGroup_Cache_Context; - -XrdOucHash NetGroup_Cache; -XrdOucHash Group_Cache; -XrdOucHash Group_Names; -XrdOucHash NetGroup_Names; -}; -#endif diff --git a/src/XrdAcc/XrdAccPrivs.hh b/src/XrdAcc/XrdAccPrivs.hh deleted file mode 100644 index 279af72a4ce..00000000000 --- a/src/XrdAcc/XrdAccPrivs.hh +++ /dev/null @@ -1,86 +0,0 @@ -#ifndef __ACC_PRIVS__ -#define __ACC_PRIVS__ -/******************************************************************************/ -/* */ -/* X r d A c c P r i v s . h h */ -/* */ -/* (c) 2003 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -/******************************************************************************/ -/* X r d A c c P r i v s */ -/******************************************************************************/ - -// Recognized privileges -// -enum XrdAccPrivs {XrdAccPriv_All = 0x07f, - XrdAccPriv_Chmod = 0x063, // Insert + Open r/w + Delete - XrdAccPriv_Chown = 0x063, // Insert + Open r/w + Delete - XrdAccPriv_Create = 0x062, // Insert + Open r/w - XrdAccPriv_Delete = 0x001, - XrdAccPriv_Insert = 0x002, - XrdAccPriv_Lock = 0x004, - XrdAccPriv_Mkdir = 0x002, // Insert - XrdAccPriv_Lookup = 0x008, - XrdAccPriv_Rename = 0x010, - XrdAccPriv_Read = 0x020, - XrdAccPriv_Readdir= 0x020, - XrdAccPriv_Write = 0x040, - XrdAccPriv_Update = 0x060, - XrdAccPriv_None = 0x000 - }; - -/******************************************************************************/ -/* X r d A c c P r i v S p e c */ -/******************************************************************************/ - -// The following are the 1-letter privileges that we support. -// -enum XrdAccPrivSpec { All_Priv = 'a', - Delete_Priv = 'd', - Insert_Priv = 'i', - Lock_Priv = 'k', - Lookup_Priv = 'l', - Rename_Priv = 'n', - Read_Priv = 'r', - Write_Priv = 'w', - Neg_Priv = '-' - }; - -/******************************************************************************/ -/* X r d A c c P r i v C a p s */ -/******************************************************************************/ - -struct XrdAccPrivCaps {XrdAccPrivs pprivs; // Positive privileges - XrdAccPrivs nprivs; // Negative privileges - - XrdAccPrivCaps() {pprivs = XrdAccPriv_None; - nprivs = XrdAccPriv_None; - } - ~XrdAccPrivCaps() {} - - }; -#endif diff --git a/src/XrdApps.cmake b/src/XrdApps.cmake deleted file mode 100644 index dadd2806279..00000000000 --- a/src/XrdApps.cmake +++ /dev/null @@ -1,180 +0,0 @@ - -include( XRootDCommon ) - -#------------------------------------------------------------------------------- -# Modules -#------------------------------------------------------------------------------- -set( LIB_XRDCL_PROXY_PLUGIN XrdClProxyPlugin-${PLUGIN_VERSION} ) - -#------------------------------------------------------------------------------- -# Shared library version -#------------------------------------------------------------------------------- -set( XRD_APP_UTILS_VERSION 1.0.0 ) -set( XRD_APP_UTILS_SOVERSION 1 ) - -#------------------------------------------------------------------------------- -# xrdadler32 -#------------------------------------------------------------------------------- -add_executable( - xrdadler32 - XrdApps/Xrdadler32.cc ) - -target_link_libraries( - xrdadler32 - XrdPosix - XrdUtils - pthread - ${ZLIB_LIBRARY} ) - -#------------------------------------------------------------------------------- -# cconfig -#------------------------------------------------------------------------------- -add_executable( - cconfig - XrdApps/XrdAppsCconfig.cc ) - -target_link_libraries( - cconfig - XrdUtils ) - -#------------------------------------------------------------------------------- -# mpxstats -#------------------------------------------------------------------------------- -add_executable( - mpxstats - XrdApps/XrdMpxStats.cc ) - -target_link_libraries( - mpxstats - XrdAppUtils - XrdUtils - ${EXTRA_LIBS} - pthread - ${SOCKET_LIBRARY} ) - -#------------------------------------------------------------------------------- -# wait41 -#------------------------------------------------------------------------------- -add_executable( - wait41 - XrdApps/XrdWait41.cc ) - -target_link_libraries( - wait41 - XrdUtils - pthread - ${EXTRA_LIBS} ) - -#------------------------------------------------------------------------------- -# xrdacctest -#------------------------------------------------------------------------------- -add_executable( - xrdacctest - XrdApps/XrdAccTest.cc ) - -target_link_libraries( - xrdacctest - XrdServer - XrdUtils ) - -#------------------------------------------------------------------------------- -# xrdmapc -#------------------------------------------------------------------------------- -add_executable( - xrdmapc - XrdApps/XrdMapCluster.cc ) - -target_link_libraries( - xrdmapc - XrdCl - XrdUtils ) - -#------------------------------------------------------------------------------- -# xrdqstats -#------------------------------------------------------------------------------- -add_executable( - xrdqstats - XrdApps/XrdQStats.cc ) - -target_link_libraries( - xrdqstats - XrdCl - XrdAppUtils - XrdUtils - ${EXTRA_LIBS} ) - -#------------------------------------------------------------------------------- -# AppUtils -#------------------------------------------------------------------------------- -add_library( - XrdAppUtils - SHARED - XrdApps/XrdCpConfig.cc XrdApps/XrdCpConfig.hh - XrdApps/XrdCpFile.cc XrdApps/XrdCpFile.hh - XrdApps/XrdMpxXml.cc XrdApps/XrdMpxXml.hh ) - -target_link_libraries( - XrdAppUtils - XrdUtils ) - -set_target_properties( - XrdAppUtils - PROPERTIES - VERSION ${XRD_APP_UTILS_VERSION} - SOVERSION ${XRD_APP_UTILS_SOVERSION} ) - -#------------------------------------------------------------------------------- -# xrdCp -#------------------------------------------------------------------------------- -add_executable( - xrdcp-old - XrdApps/XrdCpy.cc - XrdClient/XrdcpXtremeRead.cc XrdClient/XrdcpXtremeRead.hh - XrdClient/XrdCpMthrQueue.cc XrdClient/XrdCpMthrQueue.hh - XrdClient/XrdCpWorkLst.cc XrdClient/XrdCpWorkLst.hh ) - -target_link_libraries( - xrdcp-old - XrdClient - XrdUtils - XrdAppUtils - dl - pthread - ${EXTRA_LIBS} ) - -#------------------------------------------------------------------------------- -# XrdClProxyPlugin library -#------------------------------------------------------------------------------- -add_library( - ${LIB_XRDCL_PROXY_PLUGIN} - MODULE - XrdApps/XrdClProxyPlugin/ProxyPrefixPlugin.cc - XrdApps/XrdClProxyPlugin/ProxyPrefixFile.cc) - -target_link_libraries(${LIB_XRDCL_PROXY_PLUGIN} XrdCl) - -set_target_properties( - ${LIB_XRDCL_PROXY_PLUGIN} - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) - -#------------------------------------------------------------------------------- -# Install -#------------------------------------------------------------------------------- -install( - TARGETS xrdadler32 cconfig mpxstats wait41 xrdcp-old XrdAppUtils xrdmapc - xrdacctest ${LIB_XRDCL_PROXY_PLUGIN} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) - -install( - FILES - ${PROJECT_SOURCE_DIR}/docs/man/xrdadler32.1 - ${PROJECT_SOURCE_DIR}/docs/man/xrdcp-old.1 - DESTINATION ${CMAKE_INSTALL_MANDIR}/man1 ) - -install( - FILES - ${PROJECT_SOURCE_DIR}/docs/man/mpxstats.8 - DESTINATION ${CMAKE_INSTALL_MANDIR}/man8 ) diff --git a/src/XrdApps/XrdAccTest.cc b/src/XrdApps/XrdAccTest.cc deleted file mode 100644 index 6274da90305..00000000000 --- a/src/XrdApps/XrdAccTest.cc +++ /dev/null @@ -1,363 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d A c c T e s t . c c */ -/* */ -/* (c) 2017 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "XrdVersion.hh" - -#include "XrdAcc/XrdAccAuthorize.hh" -#include "XrdAcc/XrdAccConfig.hh" -#include "XrdAcc/XrdAccGroups.hh" -#include "XrdAcc/XrdAccPrivs.hh" -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdSys/XrdSysLogger.hh" -#include "XrdNet/XrdNetAddr.hh" -#include "XrdOuc/XrdOucEnv.hh" -#include "XrdOuc/XrdOucStream.hh" - -/******************************************************************************/ -/* G l o b a l O b j e c t s */ -/******************************************************************************/ - -char *PrivsConvert(XrdAccPrivCaps &ctab, char *buff, int blen); - -XrdAccAuthorize *Authorize; - -int extra; - -XrdSysLogger myLogger; - -XrdSysError eroute(&myLogger, "acc_"); - -namespace -{ -XrdSecEntity Entity("host"); - -XrdNetAddr netAddr; - -bool v2 = false; -} - -/******************************************************************************/ -/* O p e r a t i o n T a b l e */ -/******************************************************************************/ -typedef struct {const char *opname; Access_Operation oper;} optab_t; -optab_t optab[] = - {{"?", AOP_Any}, - {"cm", AOP_Chmod}, - {"co", AOP_Chown}, - {"cr", AOP_Create}, - {"rm", AOP_Delete}, - {"lk", AOP_Lock}, - {"mk", AOP_Mkdir}, - {"mv", AOP_Rename}, - {"rd", AOP_Read}, - {"ls", AOP_Readdir}, - {"st", AOP_Stat}, - {"wr", AOP_Update} - }; - -int opcnt = sizeof(optab)/sizeof(optab[0]); - -/******************************************************************************/ -/* U s a g e */ -/******************************************************************************/ - -void Usage(const char *msg) -{ - if (msg) cerr <<"xrdacctest: " <] [ | ] \n\n"; - cerr <<": -a -g -h -o -r -u \n"; - cerr <<": [ [...]]\n"; - cerr <<": cr - create mv - rename st - status lk - lock\n"; - cerr <<" rd - read wr - write ls - readdir rm - remove\n"; - cerr <<" * - zap args ? - display privs\n"; - cerr <= argc) - {sprintf(buff, "%s option value not specified.", opc); - Usage(buff); - } - opv = argv[argpnt++]; - if (strlen(opc) != 2) - {sprintf(buff, "%s option is invalid.", opc); - Usage(buff); - } - switch(*(opc+1)) - {case 'a': {size_t size = sizeof(Entity.prot)-1; - strncpy(Entity.prot, opv, size); - Entity.prot[size] = '\0'; - } - v2 = true; break; - case 'g': SetID(Entity.grps, opv); v2 = true; break; - case 'h': SetID(Entity.host, opv); v2 = true; break; - case 'o': SetID(Entity.vorg, opv); v2 = true; break; - case 'r': SetID(Entity.role, opv); v2 = true; break; - case 'u': SetID(Entity.name, opv); v2 = true; break; - default: sprintf(buff, "%s option is invalid.", opc); - Usage(buff); - break; - } - } - -// Make sure user and host specified if v1 version being used -// - if (!v2) - {if (argpnt >= argc) Usage("user not specified."); - Entity.name = argv[argpnt++]; - if (argpnt >= argc) Usage("host not specified."); - Entity.host = argv[argpnt++]; - } - -// Make sure op specified unless we are v2 -// - if (argpnt >= argc) - {if (v2) return 0; - else Usage("operation not specified."); - } - if (!strcmp(argv[argpnt], "*")) - {ZapEntity(); - return 0; - } - optype = cmd2op(argv[argpnt++]); - -// Make sure path specified -// - if (argpnt >= argc) Usage("path not specified."); - -// Set host, ignore errors -// - if (Entity.host) netAddr.Set(Entity.host, 0); - -// Process each path, as needed -// - while(argpnt < argc) - {path = argv[argpnt++]; - auth = Authorize->Access((const XrdSecEntity *)&Entity, - (const char *)path, - optype); - if (optype != AOP_Any) result=(auth?(char *)"allowed":(char *)"denied"); - else {pargs.pprivs = auth; pargs.nprivs = XrdAccPriv_None; - result = PrivsConvert(pargs, buff, sizeof(buff)); - } - cout <. */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include - -#include "XrdNet/XrdNetAddr.hh" -#include "XrdOuc/XrdOucEnv.hh" -#include "XrdOuc/XrdOucNList.hh" -#include "XrdOuc/XrdOucStream.hh" -#include "XrdOuc/XrdOucUtils.hh" -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysLogger.hh" -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdSys/XrdSysPthread.hh" - -/******************************************************************************/ -/* i n L i s t */ -/******************************************************************************/ - -int inList(const char *var, const char **Vec) -{ - int i = 0; - while(Vec[i] && strcmp(Vec[i],var)) i++; - return (Vec[i] != 0); -} - -/******************************************************************************/ -/* U s a g e */ -/******************************************************************************/ - -void Usage(int rc) -{ - cerr <<"\n Usage: cconfig -c [-h ] [-n ] [-x ] []" - "\n: [[pfx]*] | [*[sfx]] []" < 1 && '-' == *argv[1]) - while ((c = getopt(argc,argv,":c:h:n:x:")) && ((unsigned char)c != 0xff)) - { switch(c) - { - case 'c': Cfn = optarg; - break; - case 'h': Host= optarg; - break; - case 'n': Name= optarg; - break; - case 'x': Xeq = optarg; - break; - default: sprintf(buff,"'%c'", optopt); - if (c == ':') Say.Say(Pgm, buff, " value not specified."); - else Say.Say(Pgm, buff, " option is invalid."); - Usage(1); - } - } - -// Make sure config file has been specified -// - if (!Cfn) {Say.Say(Pgm, "Config file not specified."); Usage(1);} - -// Get full host name -// - if (!Host) Host = theAddr.Name(); - else if (!theAddr.Set(Host,0)) Host = theAddr.Name(); - if (!Host) {Say.Say(Pgm, "Unable to determine host name."); exit(3);} - -// Prepare all selector arguments -// - for (i = optind; i < argc; i++) DirQ.Replace(argv[i],0); - chkQ = (DirQ.First() != 0); - -// Open the config file -// - if ( (cfgFD = open(Cfn, O_RDONLY, 0)) < 0) - {Say.Say(Pgm, strerror(errno), " opening config file ", Cfn); - exit(4); - } - -// Construct instance name and stream -// - Name = XrdOucUtils::InstName(Name); - sprintf(buff,"%s %s@%s", Xeq, Name, Host); - Config = new XrdOucStream(&Say, strdup(buff), &myEnv, ""); - Config->Attach(cfgFD); - -// Now start reading records until eof. -// - while((var = Config->GetMyFirstWord())) - {if (chkQ && !DirQ.Find(var)) {Config->noEcho(); continue;} - if (inList(var, noSub)) - {if (inList(var, slChk)) - while((var = Config->GetWord()) && *var != '/') {} - oldEnv = Config->SetEnv(0); - if (var) Config->GetRest(buff, sizeof(buff)); - Config->SetEnv(oldEnv); - } - else if (inList(var, ifChk)) - {while((var = Config->GetWord()) && strcmp(var, "if")) {} - if (var && !XrdOucUtils::doIf(&Say, *Config, "directive", - Host, Name, Xeq)) - {Config->noEcho(); continue;} - } - else Config->GetRest(buff, sizeof(buff)); - Config->Echo(); - } - -// Now check if any errors occured during file i/o -// - if ((retc = Config->LastError())) - {Say.Say(Pgm, strerror(retc), " reading config file ", Cfn); retc = 8;} - Config->Close(); - -// Should never get here -// - exit(retc); -} diff --git a/src/XrdApps/XrdClProxyPlugin/ProxyPrefixFile.cc b/src/XrdApps/XrdClProxyPlugin/ProxyPrefixFile.cc deleted file mode 100644 index 57e653dcfb6..00000000000 --- a/src/XrdApps/XrdClProxyPlugin/ProxyPrefixFile.cc +++ /dev/null @@ -1,228 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2017 by European Organization for Nuclear Research (CERN) -// Author: Elvin Sindrilaru -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#include "ProxyPrefixFile.hh" -#include -#include -#include -#include -#include -#include "XrdCl/XrdClLog.hh" -#include -#include - -namespace xrdcl_proxy -{ -//------------------------------------------------------------------------------ -// Constructor -//------------------------------------------------------------------------------ -ProxyPrefixFile::ProxyPrefixFile(): - mIsOpen(false), - pFile(0) -{} - -//------------------------------------------------------------------------------ -// Destructor -//------------------------------------------------------------------------------ -ProxyPrefixFile::~ProxyPrefixFile() -{ - if (pFile) { - delete pFile; - } -} - -//------------------------------------------------------------------------------ -// Open -//------------------------------------------------------------------------------ -XRootDStatus -ProxyPrefixFile::Open(const std::string& url, - OpenFlags::Flags flags, - Access::Mode mode, - ResponseHandler* handler, - uint16_t timeout) -{ - XRootDStatus st; - - if (mIsOpen) { - st = XRootDStatus(stError, errInvalidOp); - return st; - } - - pFile = new XrdCl::File(false); - std::string open_url = ConstructFinalUrl(url); - st = pFile->Open(open_url, flags, mode, handler, timeout); - - if (st.IsOK()) { - mIsOpen = true; - } - - return st; -} - -//------------------------------------------------------------------------------ -// Get proxy prefix Url -//------------------------------------------------------------------------------ -std::string -ProxyPrefixFile::GetPrefixUrl() const -{ - std::string url_prefix = (getenv("XROOT_PROXY") ? getenv("XROOT_PROXY") : ""); - - // Try out also the lower-case one - if (url_prefix.empty()) { - url_prefix = (getenv("xroot_proxy") ? getenv("xroot_proxy") : ""); - } - - return url_prefix; -} - -//------------------------------------------------------------------------------ -// Trim whitespaces from both ends for a string -//------------------------------------------------------------------------------ -std::string -ProxyPrefixFile::trim(const std::string& in) const -{ - std::string::const_iterator wsfront, wsback; - std::string::const_reverse_iterator rwsback; - wsfront = in.begin(); - rwsback = in.rbegin(); - - while (*wsfront == ' ') { - ++wsfront; - } - - while (*rwsback == ' ') { - ++rwsback; - } - - wsback = rwsback.base(); - return (wsback <= wsfront ? std::string() : std::string(wsfront, wsback)); - /* TODO: To be used when C++11 is available - auto wsfront = std::find_if_not(in.begin(), in.end(), - [](int c) -> bool {return std::isspace(c);}); - auto wsback = std::find_if_not(in.rbegin(), in.rend(), - [](int c) -> bool {return std::isspace(c);}).base(); - return (wsback <= wsfront ? std::string() : std::string(wsfront, wsback)); - */ -} - -//------------------------------------------------------------------------------ -// Get list of domains which are NOT to be prefixed -//------------------------------------------------------------------------------ -std::list -ProxyPrefixFile::GetExclDomains() const -{ - std::string excl_domains = (getenv("XROOT_PROXY_EXCL_DOMAINS") ? - getenv("XROOT_PROXY_EXCL_DOMAINS") : ""); - - if (excl_domains.empty()) { - return std::list(); - } - - char delim = ','; - std::string item; - std::list lst; - std::stringstream ss(excl_domains); - - while (getline(ss, item, delim)) { - lst.push_back(trim(item)); - } - - return lst; -} - -//------------------------------------------------------------------------------ -// Construct final Url -//------------------------------------------------------------------------------ -std::string -ProxyPrefixFile::ConstructFinalUrl(const std::string& orig_surl) const -{ - std::string final_surl = orig_surl; - std::string url_prefix = GetPrefixUrl(); - XrdCl::Log* log = DefaultEnv::GetLog(); - log->Debug(1, "url=%s, prefix_url=%s", orig_surl.c_str(), url_prefix.c_str()); - - if (!url_prefix.empty()) { - bool exclude = false; - std::list lst_excl = GetExclDomains(); - XrdCl::URL orig_url(orig_surl); - std::string orig_host = orig_url.GetHostId(); - // Remove port if present - size_t pos = orig_host.find(':'); - - if (pos != std::string::npos) { - orig_host = orig_host.substr(0, pos); - } - - orig_host = GetFqdn(orig_host); - - for (std::list::iterator it_excl = lst_excl.begin(); - it_excl != lst_excl.end(); ++it_excl) { - if (url_prefix.size() < it_excl->size()) { - continue; - } - - if (std::equal(it_excl->rbegin(), it_excl->rend(), orig_host.rbegin())) { - exclude = true; - break; - } - } - - if (!exclude) { - final_surl.insert(0, url_prefix); - } - } - - log->Debug(1, "final_url=%s", final_surl.c_str()); - return final_surl; -} - -//------------------------------------------------------------------------------ -// Get FQDN for specified host -//------------------------------------------------------------------------------ -std::string -ProxyPrefixFile::GetFqdn(const std::string& hostname) const -{ - XrdCl::Log* log = DefaultEnv::GetLog(); - std::string fqdn = hostname; - struct addrinfo hints, *info; - int gai_result; - memset(&hints, 0, sizeof hints); - hints.ai_family = AF_UNSPEC; /*either IPV4 or IPV6*/ - hints.ai_socktype = SOCK_STREAM; - hints.ai_flags = AI_CANONNAME; - - if ((gai_result = getaddrinfo(hostname.c_str(), NULL, &hints, &info)) != 0) { - log->Error(1, "getaddrinfo: %s", gai_strerror(gai_result)); - return fqdn; - } - - if (info) { - fqdn = info->ai_canonname; - } - - freeaddrinfo(info); - return fqdn; -} - -} // namespace xrdcl_proxy diff --git a/src/XrdApps/XrdClProxyPlugin/ProxyPrefixFile.hh b/src/XrdApps/XrdClProxyPlugin/ProxyPrefixFile.hh deleted file mode 100644 index 730d5c038c9..00000000000 --- a/src/XrdApps/XrdClProxyPlugin/ProxyPrefixFile.hh +++ /dev/null @@ -1,224 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2017 by European Organization for Nuclear Research (CERN) -// Author: Elvin Sindrilaru -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#pragma once -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdCl/XrdClPlugInInterface.hh" - -using namespace XrdCl; - -namespace xrdcl_proxy -{ -//------------------------------------------------------------------------------ -//! XrdClFile plugin that appends an URL prefix to the given URL. The URL -//! prefix is set as an environment variable XRD_URL_PREFIX. -//------------------------------------------------------------------------------ -class ProxyPrefixFile: public XrdCl::FilePlugIn -{ -public: - //---------------------------------------------------------------------------- - //! Constructor - //---------------------------------------------------------------------------- - ProxyPrefixFile(); - - //---------------------------------------------------------------------------- - //! Destructor - //---------------------------------------------------------------------------- - virtual ~ProxyPrefixFile(); - - //---------------------------------------------------------------------------- - //! Open - //---------------------------------------------------------------------------- - virtual XRootDStatus Open(const std::string& url, - OpenFlags::Flags flags, - Access::Mode mode, - ResponseHandler* handler, - uint16_t timeout); - - //---------------------------------------------------------------------------- - //! Close - //---------------------------------------------------------------------------- - virtual XRootDStatus Close(ResponseHandler* handler, - uint16_t timeout) - { - return pFile->Close(handler, timeout); - } - - //---------------------------------------------------------------------------- - //! Stat - //---------------------------------------------------------------------------- - virtual XRootDStatus Stat(bool force, - ResponseHandler* handler, - uint16_t timeout) - { - return pFile->Stat(force, handler, timeout); - } - - - //---------------------------------------------------------------------------- - //! Read - //---------------------------------------------------------------------------- - virtual XRootDStatus Read(uint64_t offset, - uint32_t size, - void* buffer, - ResponseHandler* handler, - uint16_t timeout) - { - return pFile->Read(offset, size, buffer, handler, timeout); - } - - //---------------------------------------------------------------------------- - //! Write - //---------------------------------------------------------------------------- - virtual XRootDStatus Write(uint64_t offset, - uint32_t size, - const void* buffer, - ResponseHandler* handler, - uint16_t timeout) - { - return pFile->Write(offset, size, buffer, handler, timeout); - } - - //---------------------------------------------------------------------------- - //! Sync - //---------------------------------------------------------------------------- - virtual XRootDStatus Sync(ResponseHandler* handler, - uint16_t timeout) - { - return pFile->Sync(handler, timeout); - } - - //---------------------------------------------------------------------------- - //! Truncate - //---------------------------------------------------------------------------- - virtual XRootDStatus Truncate(uint64_t size, - ResponseHandler* handler, - uint16_t timeout) - { - return pFile->Truncate(size, handler, timeout); - } - - //---------------------------------------------------------------------------- - //! VectorRead - //---------------------------------------------------------------------------- - virtual XRootDStatus VectorRead(const ChunkList& chunks, - void* buffer, - ResponseHandler* handler, - uint16_t timeout) - { - return pFile->VectorRead(chunks, buffer, handler, timeout); - } - - //---------------------------------------------------------------------------- - //! Fcntl - //---------------------------------------------------------------------------- - virtual XRootDStatus Fcntl(const Buffer& arg, - ResponseHandler* handler, - uint16_t timeout) - { - return pFile->Fcntl(arg, handler, timeout); - } - - //---------------------------------------------------------------------------- - //! Visa - //---------------------------------------------------------------------------- - virtual XRootDStatus Visa(ResponseHandler* handler, - uint16_t timeout) - { - return pFile->Visa(handler, timeout); - } - - //---------------------------------------------------------------------------- - //! IsOpen - //---------------------------------------------------------------------------- - virtual bool IsOpen() const - { - return pFile->IsOpen(); - } - - //---------------------------------------------------------------------------- - //! SetProperty - //---------------------------------------------------------------------------- - virtual bool SetProperty(const std::string& name, - const std::string& value) - { - return pFile->SetProperty(name, value); - } - - //---------------------------------------------------------------------------- - //! GetProperty - //---------------------------------------------------------------------------- - virtual bool GetProperty(const std::string& name, - std::string& value) const - { - return pFile->GetProperty(name, value); - } - -private: - - //---------------------------------------------------------------------------- - //! Trim whitespaces from both ends of a string - //! - //! @return trimmed string - //---------------------------------------------------------------------------- - inline std::string trim(const std::string& in) const; - - //---------------------------------------------------------------------------- - //! Get proxy prefix URL from the environment - //! - //! @return proxy prefix RUL - //---------------------------------------------------------------------------- - inline std::string GetPrefixUrl() const; - - //---------------------------------------------------------------------------- - //! Get list of domains which are NOT to be prefixed - //! - //! @return list of excluded domains - //---------------------------------------------------------------------------- - std::list GetExclDomains() const; - - //---------------------------------------------------------------------------- - //! Construct final URL if there is a proxy prefix URL specified and if the - //! exclusion list is satisfied - //! - //! @param orig_url original url - //! - //! @return final URL - //---------------------------------------------------------------------------- - std::string ConstructFinalUrl(const std::string& orig_url) const; - - //---------------------------------------------------------------------------- - //! Get FQDN for specified host - //! - //! @param hostname hostname without domain - //! - //! @return FQDN - //---------------------------------------------------------------------------- - std::string GetFqdn(const std::string& hostname) const; - - bool mIsOpen; - XrdCl::File* pFile; -}; - -} // namespace xrdcl_proxy diff --git a/src/XrdApps/XrdClProxyPlugin/ProxyPrefixPlugin.cc b/src/XrdApps/XrdClProxyPlugin/ProxyPrefixPlugin.cc deleted file mode 100644 index 1534ed0bdfd..00000000000 --- a/src/XrdApps/XrdClProxyPlugin/ProxyPrefixPlugin.cc +++ /dev/null @@ -1,106 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2017 by European Organization for Nuclear Research (CERN) -// Author: Elvin Sindrilaru -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#include "ProxyPrefixPlugin.hh" -#include "ProxyPrefixFile.hh" -#include "XrdVersion.hh" -#include "XrdSys/XrdSysDNS.hh" -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdCl/XrdClLog.hh" -#include -#include - -XrdVERSIONINFO(XrdClGetPlugIn, XrdClGetPlugIn) - -extern "C" -{ - void* XrdClGetPlugIn(const void* arg) - { - const std::map* config = - static_cast< const std::map* >(arg); - return static_cast(new xrdcl_proxy::ProxyFactory(config)); - } -} - -namespace xrdcl_proxy -{ -//------------------------------------------------------------------------------ -// Construtor -//------------------------------------------------------------------------------ -ProxyFactory::ProxyFactory(const std::map* config) -{ - XrdCl::Log* log = XrdCl::DefaultEnv::GetLog(); - // If any of the parameters specific to this plugin are present then export - // them as env variables to be used later on if not already set. - if (config) { - // When C++11 is here: - // std::list lst_envs {"XROOT_PROXY", "xroot_proxy", - // "XROOT_PROXY_EXCL_DOMAINS", - // "xroot_proxy_excl_domains}; - std::list lst_envs; - lst_envs.push_back("XROOT_PROXY"); - lst_envs.push_back("xroot_proxy"); - lst_envs.push_back("XROOT_PROXY_EXCL_DOMAINS"); - lst_envs.push_back("xroot_proxy_excl_domains"); - - for (std::list::iterator it_env = lst_envs.begin(); - it_env != lst_envs.end(); ++it_env) { - std::map::const_iterator it_map = - config->find(*it_env); - - if (it_map != config->end() && !it_map->second.empty()) { - if (setenv(it_map->first.c_str(), it_map->second.c_str(), 0)) { - log->Error(1, "Failed to set env variable %s from the configuration" - " file", it_map->first.c_str()); - } - } - } - } -} - -//------------------------------------------------------------------------------ -// Destructor -//------------------------------------------------------------------------------ -ProxyFactory::~ProxyFactory() {} - -//------------------------------------------------------------------------------ -// Create a file plug-in for the given URL -//------------------------------------------------------------------------------ -XrdCl::FilePlugIn* -ProxyFactory::CreateFile(const std::string& url) -{ - return static_cast(new ProxyPrefixFile()); -} - -//------------------------------------------------------------------------------ -// Create a file system plug-in for the given URL -//------------------------------------------------------------------------------ -XrdCl::FileSystemPlugIn* -ProxyFactory::CreateFileSystem(const std::string& url) -{ - XrdCl::Log* log = XrdCl::DefaultEnv::GetLog(); - log->Error(1, "FileSystem plugin implementation not suppoted"); - return static_cast(0); -} -} // namespace xrdcl_proxy diff --git a/src/XrdApps/XrdClProxyPlugin/ProxyPrefixPlugin.hh b/src/XrdApps/XrdClProxyPlugin/ProxyPrefixPlugin.hh deleted file mode 100644 index d2bf668fe7c..00000000000 --- a/src/XrdApps/XrdClProxyPlugin/ProxyPrefixPlugin.hh +++ /dev/null @@ -1,59 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2017 by European Organization for Nuclear Research (CERN) -// Author: Elvin Sindrilaru -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#pragma once -#include "XrdCl/XrdClPlugInInterface.hh" - -namespace xrdcl_proxy -{ -//------------------------------------------------------------------------------ -//! XrdCl proxy prefix plugin factory -//------------------------------------------------------------------------------ -class ProxyFactory: public XrdCl::PlugInFactory -{ -public: - //---------------------------------------------------------------------------- - //! Construtor - //! - //! @param config map containing configuration parameters - //---------------------------------------------------------------------------- - ProxyFactory(const std::map* config); - - //---------------------------------------------------------------------------- - //! Destructor - //---------------------------------------------------------------------------- - virtual ~ProxyFactory(); - - //---------------------------------------------------------------------------- - //! Create a file plug-in for the given URL - //---------------------------------------------------------------------------- - virtual XrdCl::FilePlugIn* CreateFile(const std::string& url); - - //---------------------------------------------------------------------------- - //! Create a file system plug-in for the given URL - //---------------------------------------------------------------------------- - virtual XrdCl::FileSystemPlugIn* CreateFileSystem(const std::string& url); -}; - -} // namespace xrdcl_proxy diff --git a/src/XrdApps/XrdClProxyPlugin/README.md b/src/XrdApps/XrdClProxyPlugin/README.md deleted file mode 100644 index 1cbd186f858..00000000000 --- a/src/XrdApps/XrdClProxyPlugin/README.md +++ /dev/null @@ -1,31 +0,0 @@ -# XrdClProxyPrefix Plugin - -This XRootD Client Plugin can be used to tunnel traffic through an XRootD Proxy machine. The proxy endpoint is specifed as an environment variable. To enable this plugin the **XRD_PLUGIN** environment variable needs to point to the **libXrdClProxyPlugin.so** library. - -For example: - -```bash -XRD_PLUGIN=/usr/lib64/libXrdClProxyPlugin.so \ -XROOT_PROXY=root://esvm000:2010// \ -xrdcp -f -d 1 root://esvm000//tmp/file1.dat /tmp/dump -[1.812kB/1.812kB][100%][==================================================][1.812kB/s] -``` - -This will first redirect the client to the XRootD server on port 2010 which is a forwarding proxy and then the request will be served by the default XRootD server on port 1094. - -The user can also specify a list of exclusion domains, for which the original URL will not be modified even if the plugin is enabled. For example: - -```bash -XRD_PLUGIN=/usr/lib64/libXrdClProxyPlugin.so \ -XROOT_PROXY=root://esvm000.cern.ch:2010// \ -XROOT_PROXY_EXCL_DOMAINS="some.domain, some.other.domain, cern.ch " \ -xrdcp -f -d 1 root://esvm000.cern.ch//tmp/file1.dat /tmp/dump -``` - -This will not redirect the traffic since the original url "root://esmv000.cern.ch//" contains the "cern.ch" domain which is in the list of excluded domains. There are several environment variables that control the behaviour of this XRootD Client plugin: - -**XROOT_PROXY/xroot_proxy** - XRootD endpoint through which all traffic is tunnelled - -**XROOT_PROXY_EXCL_DOMAINS** - list of comma separated domains which are excluded from being tunnelled through the proxy endpoint - -**XRD_PLUGIN** - default environment variable used by the XRootD Client plugin loading mechanism which needs to point to the library implementation of the plugin diff --git a/src/XrdApps/XrdCpConfig.cc b/src/XrdApps/XrdCpConfig.cc deleted file mode 100644 index 2f266791a54..00000000000 --- a/src/XrdApps/XrdCpConfig.cc +++ /dev/null @@ -1,923 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C p C o n f i g . c c */ -/* */ -/* (c) 2011 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "XrdVersion.hh" -#include "XrdApps/XrdCpConfig.hh" -#include "XrdApps/XrdCpFile.hh" -#include "XrdCks/XrdCksCalc.hh" -#include "XrdCks/XrdCksManager.hh" -#include "XrdOuc/XrdOucStream.hh" -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdSys/XrdSysLogger.hh" - -using namespace std; - -/******************************************************************************/ -/* D e f i n e M a c r o s */ -/******************************************************************************/ - -#define EMSG(x) cerr <Next; delete pNow;} - - while((dP = intDefs)) {intDefs = dP->Next; delete dP;} - while((dP = strDefs)) {strDefs = dP->Next; delete dP;} - -} - -/******************************************************************************/ -/* C o n f i g */ -/******************************************************************************/ - -void XrdCpConfig::Config(int aCnt, char **aVec, int opts) -{ - extern char *optarg; - extern int optind, opterr; - static int pgmSet = 0; - char Buff[128], *Path, opC; - XrdCpFile pBase; - int i, rc; - -// Allocate a parameter vector -// - if (parmVal) free(parmVal); - parmVal = (char **)malloc(aCnt*sizeof(char *)); - -// Preset handling options -// - Argv = aVec; - Argc = aCnt; - Opts = opts; - opterr = 0; - optind = 1; - opC = 0; - -// Set name of executable for error messages -// - if (!pgmSet) - {char *Slash = rindex(aVec[0], '/'); - pgmSet = 1; - Pgm = (Slash ? Slash+1 : aVec[0]); - Log->SetPrefix(Pgm); - } - -// Process legacy options first before atempting normal options -// -do{while(optind < Argc && Legacy(optind)) {} - if ((opC = getopt_long(Argc, Argv, opLetters, opVec, &i)) != (char)-1) - switch(opC) - {case OpCksum: defCks(optarg); - break; - case OpCoerce: OpSpec |= DoCoerce; - break; - case OpDebug: OpSpec |= DoDebug; - if (!a2i(optarg, &Dlvl, 0, 3)) Usage(22); - break; - case OpDynaSrc: OpSpec |= DoDynaSrc; - break; - case OpForce: OpSpec |= DoForce; - break; - case OpZip: OpSpec |= DoZip; - if (zipFile) free(zipFile); - zipFile = strdup(optarg); - break; - case OpHelp: Usage(0); - break; - case OpIfile: if (inFile) free(inFile); - inFile = strdup(optarg); - OpSpec |= DoIfile; - break; - case OpLicense: License(); - break; - case OpNoPbar: OpSpec |= DoNoPbar; - break; - case OpPath: OpSpec |= DoPath; - break; - case OpPosc: OpSpec |= DoPosc; - break; - case OpProxy: OpSpec |= DoProxy; - defPxy(optarg); - break; - case OpRecurse: OpSpec |= DoRecurse; - break; - case OpRecursv: OpSpec |= DoRecurse; - break; - case OpRetry: OpSpec |= DoRetry; - if (!a2i(optarg, &Retry, 0, -1)) Usage(22); - break; - case OpServer: OpSpec |= DoServer|DoSilent|DoNoPbar|DoForce; - break; - case OpSilent: OpSpec |= DoSilent|DoNoPbar; - break; - case OpSources: OpSpec |= DoSources; - if (!a2i(optarg, &nSrcs, 1, 32)) Usage(22); - break; - case OpStreams: OpSpec |= DoStreams; - if (!a2i(optarg, &nStrm, 1, 15)) Usage(22); - break; - case OpTpc: OpSpec |= DoTpc; - if (!strcmp("only", optarg)) OpSpec|= DoTpcOnly; - else if (strcmp("first", optarg)) - {optind--; - UMSG("Invalid option, '" < 1) - UMSG("Third party copy requires a single source."); - -// Check for conflicts with ZIP archive -// - if( OpSpec & DoZip & DoCksrc ) - UMSG("Cannot calculate source checksum for a file in ZIP archive."); - - if( ( OpSpec & DoZip & DoCksum ) && !CksData.HasValue() ) - UMSG("Cannot calculate source checksum for a file in ZIP archive."); - -// Turn off verbose if we are in server mode -// - if (OpSpec & DoServer) - {OpSpec &= ~DoVerbose; - Verbose = 0; - } - -// Turn on auto-path creation if requested via envar -// - if (getenv("XRD_MAKEPATH")) OpSpec |= DoPath; - - if( parmCnt > 1 ) - { -// Process the destination first as it is special -// - dstFile = new XrdCpFile(parmVal[--parmCnt], rc); - if (rc) FMSG("Invalid url, '" <Path <<"'.", 22); - -// Do a protocol check -// - if (dstFile->Protocol != XrdCpFile::isFile - && dstFile->Protocol != XrdCpFile::isStdIO - && dstFile->Protocol != XrdCpFile::isXroot) - {FMSG(dstFile->ProtName <<"file protocol is not supported.", 22)} - -// Resolve this file if it is a local file -// - isLcl = (dstFile->Protocol == XrdCpFile::isFile) - | (dstFile->Protocol == XrdCpFile::isStdIO); - if (isLcl && (rc = dstFile->Resolve())) - {if (rc != ENOENT || (Argc - optind - 1) > 1 || OpSpec & DoRecurse) - FMSG(strerror(rc) <<" processing " <Path, 2); - } - } - -// Now pick up all the source files from the command line -// - pLast = &pBase; - for (i = 0; i < parmCnt; i++) ProcFile(parmVal[i]); - -// If an input file list was specified, process it as well -// - if (inFile) - {XrdOucStream inList(Log); - char *fname; - int inFD = open(inFile, O_RDONLY); - if (inFD < 0) FMSG(strerror(errno) <<" opening infiles " < 1) - FMSG("Only a single source is allowed.", 2); - srcFile = pBase.Next; - -// Check if we have an appropriate destination -// - if (dstFile->Protocol == XrdCpFile::isFile && (numFiles > 1 - || (OpSpec & DoRecurse && srcFile->Protocol != XrdCpFile::isFile))) - FMSG("Destination is neither remote nor a directory.", 2); - -// Do the dumb check -// - if (isLcl && Opts & optNoLclCp) - FMSG("All files are local; use 'cp' instead!", 1); - -// Check for checksum spec conflicts -// - if (OpSpec & DoCksum) - {if (CksData.Length && numFiles > 1) - FMSG("Checksum with fixed value requires a single input file.", 2); - if (CksData.Length && OpSpec & DoRecurse) - FMSG("Checksum with fixed value conflicts with '--recursive'.", 2); - } - -// Now extend all local sources if recursive is in effect -// - if (OpSpec & DoRecurse && !(Opts & optNoXtnd)) - {pPrev = &pBase; pBase.Next = srcFile; - while((pFile = pPrev->Next)) - {if (pFile->Protocol != XrdCpFile::isDir) pPrev = pFile; - else {Path = pFile->Path; - pPrev->Next = pFile->Next; - if (Verbose) EMSG("Indexing files in " <Extend(&pLast, numFiles, totBytes))) - FMSG(strerror(rc) <<" indexing " <Next) - {pLast->Next = pPrev->Next; - pPrev->Next = pFile->Next; - } - delete pFile; - } - } - if (!(srcFile = pBase.Next)) - FMSG("No regular files found to copy!", 2); - if (Verbose) EMSG("Copying " <= " <= 0 && *val > maxv) - ZMSG("'" <= " <= 0 && *val > maxv) - ZMSG("'" <= " <= 0 && *val > maxv) - ZMSG("'" <= " <= 0 && *val > maxv) - ZMSG("'" <= XrdCksData::NameSize) - UMSG("Invalid checksum type, '" <Object(CksData.Name))) - UMSG("Invalid checksum type, '" <Type(CksLen); - -// Reset checksum information -// - CksData.Length = 0; - OpSpec &= ~(DoCkprt | DoCksrc | DoCksum); - -// Check for any additional arguments -// - if (Colon) - {Colon++; - if (!(*Colon)) UMSG(CksData.Name <<" argument missing after ':'."); - if (!strcmp(Colon, "print")) OpSpec |= (DoCkprt | DoCksum); - else if (!strcmp(Colon, "source")) OpSpec |= (DoCkprt | DoCksrc); - else {n = strlen(Colon); - if (n != CksLen*2 || !CksData.Set(Colon, n)) - UMSG("Invalid " <Next = dP; intDend = dP;} - } else { - dP = new defVar(vName, theArg); - if (!strDend) strDefs = strDend = dP; - else {strDend->Next = dP; strDend = dP;} - } - -// Convert the argument -// - return 2; -} - -/******************************************************************************/ -/* Private: d e f P x y */ -/******************************************************************************/ - -void XrdCpConfig::defPxy(const char *opval) -{ - const char *Colon = index(opval, ':'); - char *eP; - int n; - -// Make sure the host was specified -// - if (Colon == opval) UMSG("Proxy host not specified."); - -// Make sure the port was specified -// - if (!Colon || !(*(Colon+1))) UMSG("Proxy port not specified."); - -// Make sure the port is a valid number that is not too big -// - errno = 0; - pPort = strtol(Colon+1, &eP, 10); - if (errno || *eP || pPort < 1 || pPort > 65535) - UMSG("Invalid proxy port, '" <= 1024; i++) - inval = inval/1024; - - snprintf(Buff, Blen, "%lld%s", inval, sfx[i]); - return Buff; -} - -/******************************************************************************/ -/* Private: L e g a c y */ -/******************************************************************************/ - -int XrdCpConfig::Legacy(int oIndex) -{ - extern int optind; - char *oArg; - int rc; - -// if (!Argv[oIndex]) return 0; - - while(oIndex < Argc && (*Argv[oIndex] != '-' || *(Argv[oIndex]+1) == '\0')) - parmVal[parmCnt++] = Argv[oIndex++]; - if (oIndex >= Argc) return 0; - - if (oIndex+1 >= Argc || *Argv[oIndex+1] == '-') oArg = 0; - else oArg = Argv[oIndex+1]; - if (!(rc = Legacy(Argv[oIndex], oArg))) return 0; - optind = oIndex + rc; - - return 1; -} - -/******************************************************************************/ - -int XrdCpConfig::Legacy(const char *theOp, const char *theArg) -{ - if (!strcmp(theOp, "-adler")) return defCks("adler32:source"); - - if (!strncmp(theOp, "-DI", 3) || !strncmp(theOp, "-DS", 3)) - return defOpt(theOp, theArg); - - if (!strcmp(theOp, "-extreme") || !strcmp(theOp, "-x")) - {if (nSrcs <= 1) {nSrcs = dfltSrcs; OpSpec |= DoSources;} - return 1; - } - - if (!strcmp(theOp, "-np")) {OpSpec |= DoNoPbar; return 1;} - - if (!strcmp(theOp, "-md5")) return defCks("md5:source"); - - if (!strncmp(theOp,"-OD",3) || !strncmp(theOp,"-OS",3)) return defOpq(theOp); - - if (!strcmp(theOp, "-version")) {cerr <Next = pFile = new XrdCpFile(fname, rc); - if (rc) FMSG("Invalid url, '" <Protocol == XrdCpFile::isFile && (rc = pFile->Resolve())) - FMSG(strerror(rc) <<" processing " <Path, 2); - -// Process file based on type (local or remote) -// - if (pFile->Protocol == XrdCpFile::isFile) totBytes += pFile->fSize; - else if (pFile->Protocol == XrdCpFile::isDir) - {if (!(OpSpec & DoRecurse)) - FMSG(pFile->Path <<" is a directory.", 2); - } - else if (pFile->Protocol == XrdCpFile::isStdIO) - {if (Opts & optNoStdIn) - FMSG("Using stdin as a source is disallowed.", 22); - if (numFiles) - FMSG("Multiple sources disallowed with stdin.", 22); - } - else if (pFile->Protocol != XrdCpFile::isXroot) - {FMSG(pFile->ProtName <<" file protocol is not supported.", 22)} - else if (OpSpec & DoRecurse && !(Opts & optRmtRec)) - {FMSG("Recursive copy from a remote host is not supported.",22)} - else isLcl = 0; - -// Update last pointer and we are done if this is stdin -// - numFiles++; - pLast = pFile; -} - -/******************************************************************************/ -/* U s a g e */ -/******************************************************************************/ - -void XrdCpConfig::Usage(int rc) -{ - static const char *Syntax = "\n" - "Usage: xrdcp [] [ [. . .]] \n"; - - static const char *Syntax1= "\n" - "Usage: xrdcp [] \n"; - - static const char *Options= "\n" - "Options: [--cksum ] [--debug ] [--coerce] [--dynamic-src]\n" - " [--force] [--help] [--infiles ] [--license] [--nopbar]\n" - " [--path] [--posc] [--proxy :] [--recursive]\n" - " [--retry ] [--server] [--silent] [--sources ] [--streams ]\n" - " [--tpc {first|only}] [--verbose] [--version] [--xrate ]\n" - " [--parallel ] [--zip ]"; - - static const char *Syntax2= "\n" - ": [[x]root://[:]/] | -"; - - static const char *Syntay2= "\n" - ": [[x]root://[:]/]"; - - static const char *Syntax3= "\n" - ": [[x]root://[:]/] | -"; - - static const char *Detail = "\n" - "-C | --cksum verifies the checksum at the destination as provided\n" - " by the source server or locally computed. The args are\n" - " {adler32 | crc32 | md5}[:{|print|source}]\n" - " If the hex value of the checksum is given, it is used.\n" - " Otherwise, the server's checksum is used for remote files\n" - " and computed for local files. Specifying print merely\n" - " prints the checksum but does not verify it.\n" - "-d | --debug sets the debug level: 0 off, 1 low, 2 medium, 3 high\n" - "-Z | --dynamic-src file size may change during the copy\n" - "-F | --coerce coerces the copy by ignoring file locking semantics\n" - "-f | --force replaces any existing output file\n" - "-h | --help prints this information\n" - "-H | --license prints license terms and conditions\n" - "-I | --infiles specifies the file that contains a list of input files\n" - "-N | --nopbar does not print the progress bar\n" - "-p | --path automatically create remote destination path\n" - "-P | --posc enables persist on successful close semantics\n" - "-D | --proxy uses the specified SOCKS4 proxy connection\n" - "-r | --recursive recursively copies all source files\n" - "-t | --retry maximum number of times to retry rejected connections\n" - " --server runs in a server environment with added operations\n" - "-s | --silent produces no output other than error messages\n" - "-y | --sources uses up to the number of sources specified in parallel\n" - "-S | --streams copies using the specified number of TCP connections\n" - "-T | --tpc uses third party copy mode between the src and dest.\n" - " Both the src and dest must allow tpc mode. Argument\n" - " 'first' tries tpc and if it fails, does a normal copy;\n" - " while 'only' fails the copy unless tpc succeeds.\n" - "-v | --verbose produces more information about the copy\n" - "-V | --version prints the version number\n" - "-X | --xrate limits the transfer to the specified rate. You can\n" - " suffix the value with 'k', 'm', or 'g'\n" - " --parallel number of copy jobs to be run simultaneously\n\n" - "-z | --zip treat the source as a ZIP archive containing given file\n" - "Legacy options: [-adler] [-DI ] [-DS ] [-np]\n" - " [-md5] [-OD] [-OS] [-version] [-x]"; - - cerr <<(Opts & opt1Src ? Syntax1 : Syntax) <. */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdCks/XrdCksData.hh" - -#include - -struct option; -class XrdCks; -class XrdCksCalc; -class XrdCpFile; -class XrdSysError; - -class XrdCpConfig -{ -public: - -struct defVar - { defVar *Next; // -> Next such definition, 0 if no more - const char *vName; // -> Variable name - union {const char *strVal; // -> String value if in strDefs - int intVal; // Integer value if in intDefs - }; - defVar(const char *vn, const char *vl) - : Next(0), vName(vn), strVal(vl) {} - defVar(const char *vn, int vl) - : Next(0), vName(vn), intVal(vl) {} - }; - - defVar *intDefs; // -> -DI settings - defVar *strDefs; // -> -DS settings - const char *dstOpq; // -> -OD setting (dest opaque) - const char *srcOpq; // -> -OS setting (src opaque) - const char *Pgm; // -> Program name - long long xRate; // -xrate value in bytes/sec (0 if not set) - int Parallel; // Number of simultaneous copy ops (1 to 4) - char *pHost; // -> SOCKS4 proxy hname (0 if none) - int pPort; // SOCKS4 proxy port - int OpSpec; // Bit mask of set options (see Doxxxx) - int Dlvl; // Debug level (0 to 3) - int nSrcs; // Number of sources wanted (dflt 1) - int nStrm; // Number of streams wanted (dflt 1) - int Retry; // Max times to retry connects (<0->use dflt) - int Verbose; // True if --verbose specified - int CksLen; // Binary length of checksum, if any - - int numFiles; // Number of source files - long long totBytes; // Total number of bytes for local files - -XrdCksData CksData; // Checksum information -XrdCks *CksMan; // -> Checksum manager -XrdCksCalc *CksObj; // -> Cks computation object (0 if no cks) -const char *CksVal; // -> Cks argument (0 if none) - -XrdCpFile *srcFile; // List of source files -XrdCpFile *dstFile; // The destination for the copy - -char *zipFile; // The file name if the URL points to a ZIP archive - -static XrdSysError *Log; // -> Error message object - -static const int OpCksum = 'C'; // -adler -MD5 legacy -> DoCksrc -static const int DoCksrc = 0x00000001; // --cksum :source -static const int DoCksum = 0x00000002; // --cksum -static const int DoCkprt = 0x00000004; // --cksum :print - -static const int OpCoerce = 'F'; -static const int DoCoerce = 0x00000008; // -F | --coerce - -static const int OpDebug = 'd'; -static const int DoDebug = 0x00000010; // -d | --debug - -static const int OpForce = 'f'; -static const int DoForce = 0x00000020; // -f | --force - -static const int OpHelp = 'h'; -static const int DoHelp = 0x00000040; // -h | --help - -static const int OpIfile = 'I'; -static const int DoIfile = 0x00000080; // -I | --infiles - -static const int OpLicense = 'H'; // -H | --license - -static const int OpNoPbar = 'N'; -static const int DoNoPbar = 0x00000100; // -N | --nopbar | -np {legacy} - -static const int OpPath = 'p'; -static const int DoPath = 0x00800000; // -p | --path - -static const int OpPosc = 'P'; -static const int DoPosc = 0x00000200; // -P | --posc - -static const int OpProxy = 'D'; -static const int DoProxy = 0x00000400; // -D | --proxy - -static const int OpRecurse = 'r'; -static const int OpRecursv = 'R'; -static const int DoRecurse = 0x00000800; // -r | --recursive | -R {legacy} - -static const int OpRetry = 't'; -static const int DoRetry = 0x00001000; // -t | --retry - -static const int OpServer = 0x03; -static const int DoServer = 0x00002000; // --server - -static const int OpSilent = 's'; -static const int DoSilent = 0x00004000; // -s | --silent - -static const int OpSources = 'y'; -static const int DoSources = 0x00008000; // -y | --sources - -static const int OpStreams = 'S'; -static const int DoStreams = 0x00010000; // -S | --streams - -static const int OpTpc = 'T'; -static const int DoTpc = 0x00020000; // -T | --tpc {first | only} -static const int DoTpcOnly = 0x00100000; // -T | --tpc only - -static const int OpVerbose = 'v'; -static const int DoVerbose = 0x00040000; // -v | --verbose - -static const int OpVersion = 'V'; // -V | --version - -static const int OpXrate = 'X'; -static const int DoXrate = 0x00080000; // -X | --xrate - -static const int OpParallel = 0x04; -static const int DoParallel = 0x00200000; // --parallel - -static const int OpDynaSrc = 'Z'; -static const int DoDynaSrc = 0x00400000; // --dynamic-src - -static const int OpZip = 'z'; -static const int DoZip = 0x01000000;// --zip - -// Call Config with the parameters passed to main() to fill out this object. If -// the method returns then no errors have been found. Otherwise, it exits. -// The following options may be passed (largely to support legacy stuff): -// -static const int opt1Src = 0x00000001; // Only one source is allowed -static const int optNoXtnd = 0x00000002; // Do not index source directories -static const int optRmtRec = 0x00000004; // Allow remote recursive copy -static const int optNoStdIn = 0x00000008; // Disallow '-' as src for stdin -static const int optNoLclCp = 0x00000010; // Disallow local/local copy - - void Config(int argc, char **argv, int Opts=0); - -// Method to check for setting -// -inline int Want(int What) {return (OpSpec & What) != 0;} - - XrdCpConfig(const char *pgname); - ~XrdCpConfig(); - -private: - int a2i(const char *item, int *val, int minv, int maxv=-1); - int a2l(const char *item, long long *val, - long long minv, long long maxv=-1); - int a2t(const char *item, int *val, int minv, int maxv=-1); - int a2x(const char *Val, char *Buff, int Vlen); - int a2z(const char *item, long long *val, - long long minv, long long maxv=-1); - int defCks(const char *opval); - int defOpq(const char *theOp); - int defOpt(const char *theOp, const char *theArg); - void defPxy(const char *opval); - const char *Human(long long Val, char *Buff, int Blen); - int Legacy(int oIndex); - int Legacy(const char *theOp, const char *theArg); - void License(); - const char *OpName(); - void ProcFile(const char *fname); - void Usage(int rc=0); - - static void toLower( char cstr[] ) - { - for( int i = 0; cstr[i]; ++i ) - cstr[i] = tolower( cstr[i] ); - } - - - const char *PName; - int Opts; - int Argc; - char **Argv; - defVar *intDend; - defVar *strDend; - -static const char *opLetters; -static struct option opVec[]; - -static const int dfltSrcs = 12; - - XrdCpFile *pFile; - XrdCpFile *pLast; - XrdCpFile *pPrev; - char *inFile; - char **parmVal; - int parmCnt; - int isLcl; -}; -#endif diff --git a/src/XrdApps/XrdCpFile.cc b/src/XrdApps/XrdCpFile.cc deleted file mode 100644 index 4af1be1d1f2..00000000000 --- a/src/XrdApps/XrdCpFile.cc +++ /dev/null @@ -1,175 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C p F i l e . c c */ -/* */ -/* (c) 2012 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include - -#include "XrdApps/XrdCpFile.hh" -#include "XrdOuc/XrdOucNSWalk.hh" - -/******************************************************************************/ -/* S t a t i c M e m b e r s */ -/******************************************************************************/ - -const char *XrdCpFile::mPfx = 0; - -/******************************************************************************/ -/* C o n s t r u c t o r */ -/******************************************************************************/ - -XrdCpFile::XrdCpFile(const char *FSpec, int &badURL) -{ - static struct proto {const char *pHdr; int pHsz; PType pVal;} - pTab[] = {{"xroot://", 8, isXroot}, - { "root://", 7, isXroot}, - { "http://", 7, isHttp}, - {"https://", 8, isHttps} - }; - static int pTnum = sizeof(pTab)/sizeof(struct proto); - const char *Slash; - int i; - -// Do some common initialization -// - Doff = 0; - Dlen = 0; - Next = 0; - fSize = 0; - badURL= 0; - memset(ProtName, 0, sizeof(ProtName)); - -// Copy out the path and remove trailing slashes (except the last one) -// - Path = strdup(FSpec); - i = strlen(Path); - while(i) if (Path[i-1] != '/' || (i > 1 && Path[i-2] != '/')) break; - else Path[--i] = 0; - -// Check for stdin stdout spec -// - if (!strcmp(Path, "-")) - {Protocol = isStdIO; - return; - } - -// Dtermine protocol of the incomming spec -// - for (i = 0; i < pTnum; i++) - {if (!strncmp(FSpec, pTab[i].pHdr, pTab[i].pHsz)) - {Protocol = pTab[i].pVal; - strncpy(ProtName, pTab[i].pHdr, pTab[i].pHsz-3); - return; - } - } - -// See if this is a file -// - Protocol = isFile; - if (!strncmp(Path, "file://", 7)) - {char *pP = Path + 7; - if (!strncmp(pP, "localhost", 9)) strcpy(Path, pP + 9); - else if (*pP == '/') strcpy(Path, pP); - else {Protocol = isOther; - strcpy(ProtName, "remote"); - return; - } - } - -// Set the default Doff and Dlen assuming non-recursive copy -// - if ((Slash = rindex(Path, '/'))) Dlen = Doff = Slash - Path + 1; -} - -/******************************************************************************/ - -XrdCpFile::XrdCpFile(char *FSpec, struct stat &Stat, short doff, short dlen) - : Next(0), Path(FSpec), Doff(doff), Dlen(dlen), - Protocol(isFile), fSize(Stat.st_size) - {strcpy(ProtName, "file");} - -/******************************************************************************/ -/* E x t e n d */ -/******************************************************************************/ - -int XrdCpFile::Extend(XrdCpFile **pLast, int &nFile, long long &nBytes) -{ - XrdOucNSWalk nsObj(0, Path, 0, XrdOucNSWalk::retFile|XrdOucNSWalk::Recurse); - XrdOucNSWalk::NSEnt *nP, *nnP; - XrdCpFile *fP, *pP = this; - int rc; - short dlen, doff = strlen(Path); - - nsObj.setMsgOn(mPfx); - - while((nP = nsObj.Index(rc)) && rc == 0) - {do {dlen = nP->Plen - doff; - fP = new XrdCpFile(nP->Path, nP->Stat, doff, dlen); - nFile++; nBytes += nP->Stat.st_size; nP->Path = 0; - pP->Next = fP; pP = fP; - nnP = nP->Next; delete nP; - } while((nP = nnP)); - } - - if (pLast) *pLast = pP; - return rc; -} - -/******************************************************************************/ -/* R e s o l v e */ -/******************************************************************************/ - -int XrdCpFile::Resolve() -{ - struct stat Stat; - -// Ignore this call if this is not a file -// - if (Protocol != isFile) return 0; - -// This should exist but it might not, the caller will determine what to do -// - if (stat(Path, &Stat)) return errno; - -// Find out what this really is -// - if (S_ISREG(Stat.st_mode)) fSize = Stat.st_size; - else if (S_ISDIR(Stat.st_mode)) Protocol = isDir; - else if (!strcmp(Path, "/dev/null")) Protocol = isDevNull; - else if (!strcmp(Path, "/dev/zero")) Protocol = isDevZero; - else return ENOTSUP; - -// All is well -// - return 0; -} diff --git a/src/XrdApps/XrdCpFile.hh b/src/XrdApps/XrdCpFile.hh deleted file mode 100644 index d0d06696b47..00000000000 --- a/src/XrdApps/XrdCpFile.hh +++ /dev/null @@ -1,72 +0,0 @@ -#ifndef __XRDCPFILE_HH__ -#define __XRDCPFILE_HH__ -/******************************************************************************/ -/* */ -/* X r d C p F i l e . h h */ -/* */ -/* (c) 2012 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -class XrdCpFile -{ -public: - -enum PType {isOther = 0, isDir, isFile, isStdIO, - isXroot, isHttp, isHttps, isDevNull, isDevZero - }; - -XrdCpFile *Next; // -> Next file in list -char *Path; // -> Absolute path to the file -short Doff; // Offset to directory extension in Path -short Dlen; // Length of directory extension (0 if none) - // The length includes the trailing slash. -PType Protocol; // Protocol type -char ProtName[8]; // Protocol name -long long fSize; // Size of file - -int Extend(XrdCpFile **pLast, int &nFile, long long &nBytes); - -int Resolve(); - -static void SetMsgPfx(const char *pfx) {mPfx = pfx;} - - XrdCpFile() : Next(0), Path(0), Doff(0), Dlen(0), - Protocol(isOther), fSize(0) {*ProtName = 0;} - - XrdCpFile(const char *FSpec, int &badURL); - - XrdCpFile( char *FSpec, struct stat &Stat, - short doff, short dlen); - - ~XrdCpFile() {if (Path) free(Path);} -private: - -static const char *mPfx; -}; -#endif diff --git a/src/XrdApps/XrdCpy.cc b/src/XrdApps/XrdCpy.cc deleted file mode 100644 index e85f74a38bf..00000000000 --- a/src/XrdApps/XrdCpy.cc +++ /dev/null @@ -1,1694 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C p y . c c */ -/* */ -/* (c) 2012 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Author: Fabrizio Furano (INFN Padova, 2004) */ -/* Modified by Andrew Hanushevsky (2012) under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/* Author: Fabrizio Furano (INFN Padova, 2004) */ -/* Modified by Andrew Hanushevsky (2012) under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// A cp-like command line tool for xrootd environments // -// // -////////////////////////////////////////////////////////////////////////// - -#include "XrdClient/XrdClientUrlInfo.hh" -#include "XrdClient/XrdClientReadCache.hh" -#include "XrdSys/XrdSysPthread.hh" -#include "XrdClient/XrdClient.hh" -#include "XrdClient/XrdCpMthrQueue.hh" -#include "XrdClient/XrdClientConn.hh" -#include "XrdClient/XrdClientDebug.hh" -#include "XrdClient/XrdCpWorkLst.hh" -#include "XrdClient/XrdClientEnv.hh" -#include "XrdSys/XrdSysPlatform.hh" - -#include "XrdClient/XrdClientAbsMonIntf.hh" -#include "XrdClient/XrdcpXtremeRead.hh" - -#include "XrdCks/XrdCks.hh" -#include "XrdCks/XrdCksCalc.hh" -#include "XrdCks/XrdCksData.hh" - -#include "XrdApps/XrdCpConfig.hh" -#include "XrdApps/XrdCpFile.hh" - -#include "XrdOuc/XrdOucTokenizer.hh" -#include "XrdOuc/XrdOucTPC.hh" - -#include -#include -#include -#include -#ifndef WIN32 -#include -#include -#include -#endif -#include -#include -#include - -#ifdef HAVE_LIBZ -#include -#endif - -/******************************************************************************/ -/* G l o b a l C o n f i g u r a t i o n */ -/******************************************************************************/ - -namespace XrdCopy -{ -XrdCpConfig Config("xrdcp"); -XrdCksData srcCksum, dstCksum; -XrdCksCalc *csObj; -XrdClient *tpcSrc; -char tpcKey[32]; -long long tpcFileSize; -pthread_t tpcTID; -int tpcPB; -int isSrv; -int isTPC; -int getCks; -int lenCks; -int prtCks; -int setCks; -int verCks; -int xeqCks; -int lclCks; -static const int rwMode = kXR_ur | kXR_uw | kXR_gw | kXR_gr | kXR_or; -} - -using namespace XrdCopy; - -#define EMSG(x) {if (isSrv) cout < 0) { - COUT(("[xrdcp] # Eff.Copy. Rate[MB/s] : %f\n",bytesread/abs_time/1000.0)); - } - if (xeqCks) - {static const int Bsz = 64; - char Buff[Bsz]; - dstCksum.Get(Buff,Bsz); - COUT(("[xrdcp] # %8s : %s\n", dstCksum.Name, Buff)); - } - COUT(("[xrdcp] #################################################################\n")); -} - -/******************************************************************************/ -/* p r i n t _ p r o g b a r */ -/******************************************************************************/ - -void print_progbar(unsigned long long bytesread, unsigned long long size) { - CERR(("[xrootd] Total %.02f MB\t|",(float)size/1024/1024)); - for (int l=0; l< 20;l++) { - if (l< ( (int)(20.0*bytesread/size))) - CERR(("=")); - if (l==( (int)(20.0*bytesread/size))) - CERR((">")); - if (l> ( (int)(20.0*bytesread/size))) - CERR((".")); - } - - float abs_time=((float)((abs_stop_time.tv_sec - abs_start_time.tv_sec) *1000 + - (abs_stop_time.tv_usec - abs_start_time.tv_usec) / 1000)); - CERR(("| %.02f %% [%.01f MB/s]\r",100.0*bytesread/size,bytesread/abs_time/1000.0)); -} - -/******************************************************************************/ -/* p r i n t _ c h k s u m */ -/******************************************************************************/ - -void print_chksum(const char* src, unsigned long long bytesread) -{ - const char *csName; - char Buff[64]; - int csLen; - XrdOucString xsrc(src); - xsrc.erase(xsrc.rfind('?')); - - if (lclCks && csObj) - {const void *csVal = csObj->Final(); - csName = csObj->Type(csLen); - srcCksum.Set(csVal, csLen); - srcCksum.Get(Buff, sizeof(Buff)); - } else { - dstCksum.Get(Buff, sizeof(Buff)); - csName = dstCksum.Name; - } - cout <GetUrl().c_str()); - const char *fName = dUrl->File.c_str(); - long long fSize; - long id, flags, mtime; - -// Prevent cancelation as the admin client can't handle that -// - pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, 0); - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, 0); - -// Open a path to the destination -// - if (!Adm.Connect()) return 0; - -// Print the progress bar until we are canceled -// - while(Adm.Stat(fName, id, fSize, flags, mtime)) - {gettimeofday(&abs_stop_time,&tz); - print_progbar(fSize, tpcFileSize); - pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, 0); - pthread_testcancel(); - sleep(3); - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, 0); - } - -// All done -// - return 0; -} - -/******************************************************************************/ -/* u n d o P r o g B a r */ -/******************************************************************************/ - -void undoProgBar(int isOK) -{ - void *thret; - - if (tpcPB) - {tpcPB = 0; - pthread_cancel(tpcTID); - pthread_join(tpcTID, &thret); - if (isOK) - {gettimeofday(&abs_stop_time,&tz); - print_progbar(tpcFileSize, tpcFileSize); - } - cerr <GetCurrentUrl().Host.c_str(); - - EMSG(Msg <= (int)sizeof(fBuff)) n = sizeof(fBuff)-1; - strncpy(fBuff, Url, n); - fBuff[n] = 0; - return fBuff; -} - -/******************************************************************************/ -/* R e a d e r T h r e a d _ x r d */ -/******************************************************************************/ - -// The body of a thread which reads from the global -// XrdClient and keeps the queue filled -//____________________________________________________________________________ -void *ReaderThread_xrd(void *) -{ - - Info(XrdClientDebug::kHIDEBUG, - "ReaderThread_xrd", - "Reader Thread starting."); - - pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, 0); - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, 0); - - - void *buf; - long long offs = 0; - int nr = 1; - long long bread = 0, len = 0; - long blksize; - - len = cpnfo.len; - - while ((nr > 0) && (offs < len)) { - buf = malloc(XRDCP_BLOCKSIZE); - if (!buf) { - EMSG("Copy failed; out of memory."); - _exit(13); - } - - - blksize = xrdmin(XRDCP_BLOCKSIZE, len-offs); - - if ( (nr = cpnfo.XrdCli->Read(buf, offs, blksize)) ) { - cpnfo.queue.PutBuffer(buf, offs, nr); - cpnfo.XrdCli->RemoveDataFromCache(offs, offs+nr-1, false); - bread += nr; - offs += nr; - } - - pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, 0); - pthread_testcancel(); - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, 0); - } - - cpnfo.bread = bread; - - // This ends the transmission... bye bye - cpnfo.queue.PutBuffer(0, 0, 0); - - return 0; -} - -/******************************************************************************/ -/* R e a d e r T h r e a d _ x r d _ x t r e m e */ -/******************************************************************************/ - -// The body of a thread which reads from the global -// XrdClient and keeps the queue filled -// This is the thread for extreme reads, in this case we may have multiple of these -// threads, reading the same file from different server endpoints -//____________________________________________________________________________ -struct xtreme_threadnfo { - XrdXtRdFile *xtrdhandler; - - // The client used by this thread - XrdClient *cli; - - // A unique integer identifying the client instance - int clientidx; - - // The block from which to start prefetching/reading - int startfromblk; - - // Max convenient number of outstanding blks - int maxoutstanding; -}; -void *ReaderThread_xrd_xtreme(void *parm) -{ - - Info(XrdClientDebug::kHIDEBUG, - "ReaderThread_xrd_xtreme", - "Reader Thread starting."); - - pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, 0); - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, 0); - - void *buf; - - int nr = 1; - int noutstanding = 0; - - - // Which block to read - XrdXtRdBlkInfo *blknfo = 0; - xtreme_threadnfo *thrnfo = (xtreme_threadnfo *)parm; - - // Block to prefetch - int lastprefetched = thrnfo->startfromblk; - int lastread = lastprefetched; - - thrnfo->cli->Open(0, 0, true); - - thrnfo->cli->SetCacheParameters(XRDCP_BLOCKSIZE*4*thrnfo->maxoutstanding*2, 0, XrdClientReadCache::kRmBlk_FIFO); - if (thrnfo->cli->IsOpen_wait()) - while (nr > 0) { - - // Keep always some blocks outstanding from the point of view of this reader - while (noutstanding < thrnfo->maxoutstanding) { - int lp; - lp = thrnfo->xtrdhandler->GetBlkToPrefetch(lastprefetched, thrnfo->clientidx, blknfo); - if (lp >= 0) { - //cout << "cli: " << thrnfo->clientidx << " prefetch: " << lp << " offs: " << blknfo->offs << " len: " << blknfo->len << endl; - if ( thrnfo->cli->Read_Async(blknfo->offs, blknfo->len) == kOK ) { - lastprefetched = lp; - noutstanding++; - } - else break; - } - else break; - } - - int lr = thrnfo->xtrdhandler->GetBlkToRead(lastread, thrnfo->clientidx, blknfo); - if (lr >= 0) { - - buf = malloc(blknfo->len); - if (!buf) { - EMSG("Copy failed; out of memory."); - _exit(13); - } - - //cout << "cli: " << thrnfo->clientidx << " read: " << lr << " offs: " << blknfo->offs << " len: " << blknfo->len << endl; - - // It is very important that the search for a blk to read starts from the first block upwards - nr = thrnfo->cli->Read(buf, blknfo->offs, blknfo->len); - if ( nr >= 0 ) { - lastread = lr; - noutstanding--; - - // If this block was stolen by somebody else then this client has to be penalized - // If this client stole the blk to some other client, then this client has to be rewarded - int reward = thrnfo->xtrdhandler->MarkBlkAsRead(lr); - if (reward >= 0) - // Enqueue the block only if it was not already read - cpnfo.queue.PutBuffer(buf, blknfo->offs, nr); - - if (reward > 0) { - thrnfo->maxoutstanding++; - thrnfo->maxoutstanding = xrdmin(20, thrnfo->maxoutstanding); - thrnfo->cli->SetCacheParameters(XRDCP_BLOCKSIZE*4*thrnfo->maxoutstanding*2, 0, XrdClientReadCache::kRmBlk_FIFO); - } - if (reward < 0) { - thrnfo->maxoutstanding--; - free(buf); - } - - if (thrnfo->maxoutstanding <= 0) { - sleep(1); - thrnfo->maxoutstanding = 1; - } - - } - - // It is very important that the search for a blk to read starts from the first block upwards - thrnfo->cli->RemoveDataFromCache(blknfo->offs, blknfo->offs+blknfo->len-1, false); - } - else { - - if (thrnfo->xtrdhandler->AllDone()) break; - pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, 0); - sleep(1); - } - - - pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, 0); - pthread_testcancel(); - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, 0); - } - - // We get here if there are no more blocks to read or to steal from other readers - // This ends the transmission... bye bye - cpnfo.queue.PutBuffer(0, 0, 0); - - return 0; -} - -/******************************************************************************/ -/* R e a d e r T h r e a d _ l o c */ -/******************************************************************************/ - -// The body of a thread which reads from the global filehandle -// and keeps the queue filled -//____________________________________________________________________________ -void *ReaderThread_loc(void *) { - - Info(XrdClientDebug::kHIDEBUG, - "ReaderThread_loc", - "Reader Thread starting."); - - pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, 0); - pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, 0); - - void *buf; - long long offs = 0; - int nr = 1; - long long bread = 0; - - while (nr > 0) { - buf = malloc(XRDCP_BLOCKSIZE); - if (!buf) { - EMSG("Copy failed; out of memory."); - _exit(13); - } - - //------------------------------------------------------------------------ - // If this read fails it means that either the program logic is - // flawed, or there was a low level hardware failure. In either case - // continuing may cause more harm than good. - //------------------------------------------------------------------------ - nr = read( cpnfo.localfile, buf, XRDCP_BLOCKSIZE ); - if( nr < 0 ) - { - EMSG(strerror( errno ) <<" reading local file."); - _exit(17); - } - if( nr > 0) - { - cpnfo.queue.PutBuffer(buf, offs, nr); - bread += nr; - offs += nr; - } - } - - cpnfo.bread = bread; - - // This ends the transmission... bye bye - cpnfo.queue.PutBuffer(0, 0, 0); - - return 0; -} - -/******************************************************************************/ -/* C r e a t e D e s t P a t h _ l o c */ -/******************************************************************************/ - -int CreateDestPath_loc(XrdOucString path, bool isdir) { - // We need the path name without the file - if (!isdir) { - int pos = path.rfind('/'); - - if (pos != STR_NPOS) - path.erase(pos); - else path = ""; - - - } - - if (path != "") - return ( MAKEDIR( - path.c_str(), - S_IRUSR | S_IWUSR | S_IXUSR | - S_IRGRP | S_IWGRP | S_IXGRP | - S_IROTH | S_IXOTH) - ); - else - return 0; - -} - -/******************************************************************************/ -/* g e t C k s u m */ -/******************************************************************************/ - -int getCksum(XrdCksData &cksData, const char *Path) -{ - const char *Lfn; - char *csResp, *tP; - -// Point to absolute path -// 0123456 - if (!strcmp(Path, "root://")) Lfn = Path + 7; - else Lfn = Path + 8; - if ((Lfn = index(Lfn, '/'))) Lfn++; - else Lfn = Path; - -// Get the checksum from the server -// - XrdClientAdmin Adm(Path); - if (!(Adm.Connect()) || !(Adm.GetChecksum((kXR_char *)Lfn,(kXR_char **)&csResp))) - {EMSG("Unable to obtain checksum for '"<< getFName(Path) <<"'."); - EMSG(Adm.LastServerError()->errmsg); - return 0; - } - -// Get checksum name and make sure it matches -// - XrdOucTokenizer csData(csResp); - csData.GetLine(); - if ((tP = csData.GetToken()) && strcmp(tP, Config.CksData.Name)) - {EMSG("Only " <Query(kXR_Qconfig, qArg, respBuff, sizeof(respBuff)) - && isdigit(*respBuff) && atoi((const char *)respBuff) > 0) return 1; - -// Nope, we don't support this -// - EMSG("Host " <GetCurrentUrl().Host.c_str() - <<" does not support third party copies."); - -// If we are the destination, unlink any partially created file -// - if (isDest) - {XrdClientAdmin Adm(cObj->GetCurrentUrl().GetUrl().c_str()); - if (Adm.Connect()) Adm.Rm(cObj->GetCurrentUrl().File.c_str()); - // cerr <GetCurrentUrl().GetUrl().c_str() <GetCurrentUrl().File.c_str() <GetCurrentUrl().Host.c_str() << ":"; - o << xrdsrc->GetCurrentUrl().Port; - cgiP = XrdOucTPC::cgiC2Dst(tpcKey, o.str().c_str(), - lfnBuff, cksVal, cgiBuff, sizeof(cgiBuff)); - if (*cgiP == '!') - {EMSG("Unable to setup destination url. " <Stat(&stat); - tpcFileSize = static_cast(stat.size); - sprintf(aszBuff, "?oss.asize=%lld&", tpcFileSize); - dCGI = aszBuff; - -// Add all other information -// - if (Config.dstOpq) {dCGI += Config.dstOpq; dCGI += '&';} - dCGI += cgiBuff; -// cerr <<"Dest url: " <GetCurrentUrl().GetUrl().c_str()); - XrdOucString *rCGI, dstUrl; - int xTTL = -1; - const char *cgiP; - char cgiBuff[1024]; - -// Append any redirection cgi information to our source spec -// - rCGI = &(tpcSrc->GetClientConn()->fRedirCGI); - if (rCGI->length() > 0) - {if (sUrl.find("?") == STR_NPOS) sUrl += '?'; - else sUrl += '&'; - sUrl += *rCGI; - } - -//cerr <<"tpc: bfr src=" <GetCurrentUrl(); -//cerr <<"tpc: pbr dcl=" <Sync()) return cpFatal("rendezvous", 0, xrddest); - -// One more sync will start the copy -// - gettimeofday(&abs_start_time,&tz); - if (!xrddest->Sync()) return cpFatal("sync", 0, xrddest); - -// Stop the progress bar -// - if (tpcPB) undoProgBar(1); - -// Close the file -// - if(!xrddest->Close()) return cpFatal("close", 0, xrddest); - -// Do checksum processing -// - if (xeqCks && prtCks) - {if (!getCksum(dstCksum, dst)) {EMSG("Unable to print checksum!")} - else print_chksum(src, tpcFileSize); - if (summary) print_summary(src, dst, tpcFileSize); - } - -// All done -// - return 0; -} - -/******************************************************************************/ -/* d o C p _ x r d 2 x r d */ -/******************************************************************************/ - -int doCp_xrd2xrd(XrdClient **xrddest, const char *src, const char *dst) { - // ----------- xrd to xrd affair - pthread_t myTID; - XrdClientVector myTIDVec; - - void *thret; - XrdClientStatInfo stat; - int retvalue = 0; - -// If we need to verify checksums then we will need to get the checksum -// from the source unless a specific checksum has been specified. -// - if (lclCks) csObj = Config.CksObj; - else if (verCks && getCks && !getCksum(srcCksum, src)) return -ENOTSUP; - - gettimeofday(&abs_start_time,&tz); - - // Open the input file (xrdc) - // If Xrdcli is non-null, the correct src file has already been opened - if (!cpnfo.XrdCli) - {cpnfo.XrdCli = new XrdClient(src); - const char *hName = cpnfo.XrdCli->GetCurrentUrl().Host.c_str(); - if ( ( !cpnfo.XrdCli->Open(0, kXR_async) || - (cpnfo.XrdCli->LastServerResp()->status != kXR_ok) ) ) - {cpFatal("open", cpnfo.XrdCli, 0, hName); - delete cpnfo.XrdCli; - cpnfo.XrdCli = 0; - return 1; - } - } - - cpnfo.XrdCli->Stat(&stat); - cpnfo.len = stat.size; - - XrdOucString dest = AddSizeHint( dst, stat.size ); - - // if xrddest if nonzero, then the file is already opened for writing - if (!*xrddest) { - *xrddest = new XrdClient(dest.c_str()); - const char *hName = (*xrddest)->GetCurrentUrl().Host.c_str(); - - if (!PedanticOpen4Write(*xrddest, rwMode, xrd_wr_flags)) - {cpFatal("open", 0, *xrddest, hName); - delete cpnfo.XrdCli; - delete *xrddest; - *xrddest = 0; - cpnfo.XrdCli = 0; - return -1; - } - - } - - // If the Extreme Copy flag is set, we try to find more sources for this file - // Each source gets assigned to a different reader thread - XrdClientVector xtremeclients; - XrdXtRdFile *xrdxtrdfile = 0; - - if (doXtremeCp) - XrdXtRdFile::GetListOfSources(cpnfo.XrdCli, XtremeCpRdr, - xtremeclients, Config.nSrcs); - - // Start reader on xrdc - if (doXtremeCp && (xtremeclients.GetSize() > 1)) { - - // Beware... with the extreme copy the normal read ahead mechanism - // makes no sense at all. - //EnvPutInt(NAME_REMUSEDCACHEBLKS, 1); - xrdxtrdfile = new XrdXtRdFile(XRDCP_BLOCKSIZE*4, cpnfo.len); - - for (int iii = 0; iii < xtremeclients.GetSize(); iii++) { - xtreme_threadnfo *nfo = new(xtreme_threadnfo); - nfo->xtrdhandler = xrdxtrdfile; - nfo->cli = xtremeclients[iii]; - nfo->clientidx = xrdxtrdfile->GimmeANewClientIdx(); - nfo->startfromblk = iii*xrdxtrdfile->GetNBlks() / xtremeclients.GetSize(); - nfo->maxoutstanding = xrdmin( 5, xrdxtrdfile->GetNBlks() / xtremeclients.GetSize() ); - if (nfo->maxoutstanding < 1) nfo->maxoutstanding = 1; - - XrdSysThread::Run(&myTID, ReaderThread_xrd_xtreme, - (void *)nfo, XRDSYSTHREAD_HOLD); - myTIDVec.Push_back(myTID); - } - - } - else { - XrdSysThread::Run(&myTID,ReaderThread_xrd,(void *)&cpnfo,XRDSYSTHREAD_HOLD); - myTIDVec.Push_back(myTID); - } - - int len = 1; - void *buf; - long long offs = 0; - long long bytesread=0; - long long size = cpnfo.len; - bool draining = false; - - // Loop to write until ended or timeout err - while (1) { - - if (xrdxtrdfile && xrdxtrdfile->AllDone()) draining = true; - if (draining && !cpnfo.queue.GetLength()) break; - - if ( cpnfo.queue.GetBuffer(&buf, offs, len) ) { - - if (len && buf) { - - bytesread+=len; - if (progbar) { - gettimeofday(&abs_stop_time,&tz); - print_progbar(bytesread,size); - } - - if (csObj) csObj->Update((const char *)buf,len); - - if (!(*xrddest)->Write(buf, offs, len)) { - cpFatal("write", 0, *xrddest); - retvalue = 11; - break; - } - - if (cpnfo.mon) - cpnfo.mon->PutProgressInfo(bytesread, cpnfo.len, (float)bytesread / cpnfo.len * 100.0); - - free(buf); - - } - else - if (!xrdxtrdfile && ( ((buf == 0) && (len == 0)) || (bytesread >= size))) { - if (buf) free(buf); - break; - } - - } - else { - EMSG("Critical read timeout. Unable to read data from the source."); - retvalue = 17; - break; - } - - buf = 0; - } - - if (cpnfo.mon) - cpnfo.mon->PutProgressInfo(bytesread, cpnfo.len, (float)bytesread / cpnfo.len * 100.0, 1); - - if(progbar) { - cout << endl; - } - - if (cpnfo.len != bytesread) { - EMSG("File length mismatch. Read:" << bytesread << " Length:" << cpnfo.len); - retvalue = 13; - } - - for (int i = 0; i < myTIDVec.GetSize(); i++) { - pthread_cancel(myTIDVec[i]); - pthread_join(myTIDVec[i], &thret); - } - - delete cpnfo.XrdCli; - cpnfo.XrdCli = 0; - - if(!(*xrddest)->Close()) return cpFatal("close", 0, *xrddest); - - delete *xrddest; - *xrddest = 0; - - if (!retvalue && xeqCks) - {if (!getCksum(dstCksum, dst)) retvalue = -ENOTSUP; - else if (verCks && srcCksum != dstCksum) - {EMSG(getFName(dst) <<' ' < myTIDVec; - - void *thret; - XrdClientStatInfo stat; - int f; - int retvalue = 0; - -// If we need to verify checksums then we will need to get the checksum -// from the source unless a specific checksum has been specified. -// - if (xeqCks && getCks && !getCksum(srcCksum, src)) return -ENOTSUP; - - gettimeofday(&abs_start_time,&tz); - - // Open the input file (xrdc) - // If Xrdcli is non-null, the correct src file has already been opened - if (!cpnfo.XrdCli) - {cpnfo.XrdCli = new XrdClient(src); - const char *hName = cpnfo.XrdCli->GetCurrentUrl().Host.c_str(); - if ( ( !cpnfo.XrdCli->Open(0, kXR_async) || - (cpnfo.XrdCli->LastServerResp()->status != kXR_ok) ) ) - {cpFatal("open", cpnfo.XrdCli, 0, hName); - delete cpnfo.XrdCli; - cpnfo.XrdCli = 0; - return 1; - } - } - - // Open the output file (loc) - cpnfo.XrdCli->Stat(&stat); - cpnfo.len = stat.size; - - if (strcmp(dst, "-")) - // Copy to local fs - //unlink(dst); - {f = open(getFName(dst), loc_wr_flags, - S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); - if (f < 0) - {EMSG(strerror(errno) <<" creating '" <Close(); - delete cpnfo.XrdCli; - cpnfo.XrdCli = 0; - return -1; - } - if (verCks || lclCks) csObj = Config.CksObj; - } else { - f = STDOUT_FILENO; // Copy to stdout - } - - // If the Extreme Copy flag is set, we try to find more sources for this file - // Each source gets assigned to a different reader thread - XrdClientVector xtremeclients; - XrdXtRdFile *xrdxtrdfile = 0; - - if (doXtremeCp) - XrdXtRdFile::GetListOfSources(cpnfo.XrdCli, XtremeCpRdr, - xtremeclients, Config.nSrcs); - - // Start reader on xrdc - if (doXtremeCp && (xtremeclients.GetSize() > 1)) { - - // Beware... with the extreme copy the normal read ahead mechanism - // makes no sense at all. - - xrdxtrdfile = new XrdXtRdFile(XRDCP_BLOCKSIZE*4, cpnfo.len); - - for (int iii = 0; iii < xtremeclients.GetSize(); iii++) { - xtreme_threadnfo *nfo = new(xtreme_threadnfo); - nfo->xtrdhandler = xrdxtrdfile; - nfo->cli = xtremeclients[iii]; - nfo->clientidx = xrdxtrdfile->GimmeANewClientIdx(); - nfo->startfromblk = iii*xrdxtrdfile->GetNBlks() / xtremeclients.GetSize(); - nfo->maxoutstanding = xrdmax(xrdmin( 3, xrdxtrdfile->GetNBlks() / xtremeclients.GetSize() ), 1); - - XrdSysThread::Run(&myTID, ReaderThread_xrd_xtreme, - (void *)nfo, XRDSYSTHREAD_HOLD); - myTIDVec.Push_back(myTID); - } - - } - else { - doXtremeCp = false; - XrdSysThread::Run(&myTID,ReaderThread_xrd,(void *)&cpnfo,XRDSYSTHREAD_HOLD); - myTIDVec.Push_back(myTID); - } - - int len = 1; - void *buf; - long long bytesread=0, offs = 0; - long long size = cpnfo.len; - bool draining = false; - - // Loop to write until ended or timeout err - while (1) { - - if (xrdxtrdfile && xrdxtrdfile->AllDone()) draining = true; - if (draining && !cpnfo.queue.GetLength()) break; - - if ( cpnfo.queue.GetBuffer(&buf, offs, len) ) { - - if (len && buf) { - - bytesread+=len; - if (progbar) { - gettimeofday(&abs_stop_time,&tz); - print_progbar(bytesread,size); - } - - if (csObj) csObj->Update((const char *)buf,len); - - if (doXtremeCp && (f != STDOUT_FILENO) && lseek(f, offs, SEEK_SET) < 0) { - EMSG(strerror(errno) <<" while seeking in '" << getFName(dst) <<"'."); - retvalue = 10; - break; - } - if (write(f, buf, len) <= 0) { - EMSG(strerror(errno) <<" writing to '" << getFName(dst) <<"'."); - retvalue = 10; - break; - } - - if (cpnfo.mon) - cpnfo.mon->PutProgressInfo(bytesread, cpnfo.len, (float)bytesread / cpnfo.len * 100.0); - - free(buf); - - } - else - if (!xrdxtrdfile && ( ((buf == 0) && (len == 0)) || (bytesread >= size)) ) { - if (buf) free(buf); - break; - } - - - } - else { - EMSG("Critical read timeout. Unable to read data from the source."); - retvalue = 17; - break; - } - - buf = 0; - - } - - if (cpnfo.mon) - cpnfo.mon->PutProgressInfo(bytesread, cpnfo.len, (float)bytesread / cpnfo.len * 100.0, 1); - - if(progbar) { - cout << endl; - } - - if (cpnfo.len != bytesread) - {cpFatal("read", cpnfo.XrdCli, 0); - retvalue = 13; - } - - if (close(f)) - {EMSG(strerror(errno) <<" closing '" <Calc(dst, dstCksum, setCks); - else {char *csVal = csObj->Final(); - if (!dstCksum.Set((const void *)csVal, Config.CksLen)) - retvalue = -EINVAL; - } - if (retvalue) - {retvalue = (retvalue < 0 ? -retvalue : retvalue); - EMSG(strerror(retvalue) <<" calculating " - <GetCurrentUrl().Host.c_str(); - if (!PedanticOpen4Write(*xrddest, rwMode, xrd_wr_flags) ) - {cpFatal("open", 0 , *xrddest, hName); - close(cpnfo.localfile); - delete *xrddest; - *xrddest = 0; - cpnfo.localfile = 0; - return -1; - } - } - - // Start reader on loc - XrdSysThread::Run(&myTID,ReaderThread_loc,(void *)&cpnfo,XRDSYSTHREAD_HOLD); - - int len = 1; - void *buf; - long long offs = 0; - unsigned long long bytesread=0; - unsigned long long size = stat.st_size; - int blkcnt = 0; - -// If we need to verify checksums then we will need to get the checksum -// from the source unless a specific checksum has been specified. -// - if ((xeqCks && verCks && getCks) || lclCks) csObj = Config.CksObj; - - // Loop to write until ended or timeout err - while(len > 0) - {if ( cpnfo.queue.GetBuffer(&buf, offs, len) ) - {if (len && buf) - {bytesread+=len; - if (progbar) - {gettimeofday(&abs_stop_time,&tz); - print_progbar(bytesread,size); - } - if (csObj) csObj->Update((const char *)buf,len); - if ( !(*xrddest)->Write(buf, offs, len) ) - {cpFatal("write", 0 , *xrddest); - retvalue = 12; - break; - } - if (cpnfo.mon) - cpnfo.mon->PutProgressInfo(bytesread, cpnfo.len, (float)bytesread / cpnfo.len * 100.0); - free(buf); - } else { - // If we get len == 0 then we have to stop - if (buf) free(buf); - break; - } - } else { - EMSG("Critical read timeout. Unable to read data from the source."); - retvalue = 17; - break; - } - buf = 0; blkcnt++; - } - - if (cpnfo.mon) - cpnfo.mon->PutProgressInfo(bytesread, cpnfo.len, (float)bytesread / cpnfo.len * 100.0, 1); - - if(progbar) cout << endl; - - if (size != bytesread) retvalue = 13; - - pthread_cancel(myTID); - pthread_join(myTID, &thret); - - if(!(*xrddest)->Close()) return cpFatal("close", 0, *xrddest); - - delete *xrddest; - *xrddest = 0; - - close(cpnfo.localfile); - cpnfo.localfile = 0; - - if (!retvalue && xeqCks) - {if (!getCksum(dstCksum, dst)) retvalue = -ENOTSUP; - if (csObj) - {char *csVal = csObj->Final(); - if (!srcCksum.Set((const void *)csVal, Config.CksLen)) - retvalue = -EINVAL; - } - if (retvalue) - {retvalue = (retvalue < 0 ? -retvalue : retvalue); - EMSG(strerror(retvalue) <<" calculating " - < " << dest); - -// Preprocess cksum calculation desires -// - if (xeqCks) - {srcCksum = Config.CksData; - dstCksum = Config.CksData; - if (Config.CksObj) Config.CksObj->Init(); - } - csObj = 0; - -// Handle when source is xrootd -// - if (rmtSrc) - {if (Config.srcOpq) {src += "?"; src += Config.srcOpq;} - if (rmtDst) - {XrdOucString d = dest; - if (Config.dstOpq) {d += "?"; d += Config.dstOpq;} - if (isTPC) return doCp_xrd3xrd( xrddest, src.c_str(), d.c_str()); - else return doCp_xrd2xrd(&xrddest, src.c_str(), d.c_str()); - } - return doCp_xrd2loc(src.c_str(), dest.c_str()); - } - -// Handle when source is the local filesystem -// - if (rmtDst) - {XrdOucString d = dest; - if (Config.dstOpq) {d += "?"; d += Config.dstOpq;} - return doCp_loc2xrd(&xrddest, src.c_str(), d.c_str()); - } - -// We should never get here -// - EMSG("Better to use cp for this copy."); - return 2; -} - -/******************************************************************************/ -/* m a i n */ -/******************************************************************************/ - -int main(int argc, char**argv) -{ - const char *Opaque; - char *hName, *srcpath = 0, *destpath = 0; - -// Preset globals -// - tpcPB = 0; - -#ifdef WIN32 - WORD wVersionRequested; - WSADATA wsaData; - int err; - wVersionRequested = MAKEWORD( 2, 2 ); - err = WSAStartup( wVersionRequested, &wsaData ); -#endif - -// Invoke config; it it returns then all went well. -// - Config.Config(argc, argv, XrdCpConfig::opt1Src|XrdCpConfig::optNoStdIn - |XrdCpConfig::optNoXtnd|XrdCpConfig::optNoLclCp); - -// Turn off any blab from the client -// - DebugSetLevel(-1); - -// We want this tool to be able to copy from/to everywhere -// Note that the side effect of these calls here is to initialize the -// XrdClient environment. -// This is crucial if we want to later override its default values -// - EnvPutString( NAME_REDIRDOMAINALLOW_RE, "*" ); - EnvPutString( NAME_CONNECTDOMAINALLOW_RE, "*" ); - EnvPutString( NAME_REDIRDOMAINDENY_RE, "" ); - EnvPutString( NAME_CONNECTDOMAINDENY_RE, "" ); - - EnvPutInt( NAME_READAHEADSIZE, XRDCP_XRDRASIZE); - EnvPutInt( NAME_READCACHESIZE, 2*XRDCP_XRDRASIZE ); - EnvPutInt( NAME_READCACHEBLKREMPOLICY, XrdClientReadCache::kRmBlk_LeastOffs ); - EnvPutInt( NAME_PURGEWRITTENBLOCKS, 1 ); - - EnvPutInt( NAME_DEBUG, -1); - -// Extract out config information and set global vars (that's how it was done) -// - if ((Verbose = Config.Verbose)) summary = true; - if (Config.Want(XrdCpConfig::DoSilent )) summary = progbar = false; - if (Config.Want(XrdCpConfig::DoNoPbar )) progbar = false; - if (Config.Want(XrdCpConfig::DoRecurse)) recurse = true; - if (Config.Want(XrdCpConfig::DoCoerce )) xrd_wr_flags |= kXR_force; - if (Config.Want(XrdCpConfig::DoPosc )) xrd_wr_flags |= kXR_posc; - if (Config.Want(XrdCpConfig::DoForce ) - || Config.Want(XrdCpConfig::DoServer )) - {xrd_wr_flags &= ~kXR_new; - xrd_wr_flags |= kXR_delete; - loc_wr_flags = LOC_WR_FLAGS_FORCE; // Flags for the local fs - } - if (Config.Want(XrdCpConfig::DoRetry ) && Config.Retry >= 0) - {EnvPutInt(NAME_CONNECTTIMEOUT , 60); - EnvPutInt(NAME_FIRSTCONNECTMAXCNT, Config.Retry); - } - - if (Config.strDefs) - {XrdCpConfig::defVar *dvP = Config.strDefs; - do {EnvPutString(dvP->vName, dvP->strVal);} - while((dvP = dvP->Next)); - } - - if (Config.intDefs) - {XrdCpConfig::defVar *dvP = Config.intDefs; - do {EnvPutInt(dvP->vName, dvP->intVal);} - while((dvP = dvP->Next)); - } - - if (Config.Want(XrdCpConfig::DoProxy )) - {EnvPutString(NAME_SOCKS4HOST, Config.pHost); - EnvPutInt(NAME_SOCKS4PORT, Config.pPort); - } - - if (Config.Dlvl > 0) EnvPutInt( NAME_DEBUG, Config.Dlvl); - - if (Config.Want(XrdCpConfig::DoStreams)) - EnvPutInt(NAME_MULTISTREAMCNT, Config.nStrm); - - isTPC = (Config.Want(XrdCpConfig::DoTpc) ? 1 : 0); - - destpath = Config.dstFile->Path; - srcpath = Config.srcFile->Path; - -// Do some debugging -// - DebugSetLevel(EnvGetLong(NAME_DEBUG)); - Info(XrdClientDebug::kUSERDEBUG, "main", XRDCP_VERSION); - -// Prehandle extreme copy -// - if (Config.Want(XrdCpConfig::DoSources) && Config.nSrcs > 1) - {doXtremeCp = true; - XtremeCpRdr = srcpath; - if (Verbose) EMSG("Extreme Copy enabled."); - } - -// Establish checksum processing -// - setCks = 0; - lclCks = Config.Want(XrdCpConfig::DoCksrc); - xeqCks = Config.Want(XrdCpConfig::DoCksum); - prtCks =(xeqCks || lclCks) && Config.Want(XrdCpConfig::DoCkprt); - getCks = xeqCks && (Config.CksData.Length == 0); - verCks = xeqCks && !prtCks; - -// Force certain defaults when in server mode -// - if (Config.Want(XrdCpConfig::DoServer)) - {summary = progbar = false; - setCks = true; - isSrv = true; - } else { - isSrv = false; - // if (dup2(STDOUT_FILENO, STDERR_FILENO)) cerr <<"??? " <SetSrc(&cpnfo.XrdCli, srcpath, Config.srcOpq, recurse, 1)) - {cpFatal("open", cpnfo.XrdCli, 0, hName); - exit(1); - } - -// Generate destination opaque data now -// - if (!isTPC) Opaque = Config.dstOpq; - else {if (!(Opaque = genDestCgi(cpnfo.XrdCli, srcpath))) exit(4); - tpcSrc = cpnfo.XrdCli; - } - -// Extract source host if present -// - if (hName) free(hName); - if (strncmp(destpath, "root://", 7) || strncmp(destpath, "xroot://", 8) ) - {XrdClientUrlInfo dUrl(destpath); - hName = (dUrl.IsValid() ? strdup(dUrl.Host.c_str()) : 0); - } else hName = 0; - -// Verify the correctness of the destination -// - if (wklst->SetDest(&xrddest, destpath, Opaque, xrd_wr_flags, 1)) - {cpFatal("open", 0, xrddest, hName); - exit(1); - } - if (hName) {free(hName); hName = 0;} - - // Initialize monitoring client, if a plugin is present - cpnfo.mon = 0; -#ifndef WIN32 - void *monhandle = dlopen (monlibname.c_str(), RTLD_LAZY); - - if (monhandle) { - XrdClientMonIntfHook monlibhook = (XrdClientMonIntfHook)dlsym(monhandle, "XrdClientgetMonIntf"); - - const char *err = 0; - if ((err = dlerror())) { - EMSG(err <<" loading library " << monhandle); - dlclose(monhandle); - monhandle = 0; - } - else - cpnfo.mon = (XrdClientAbsMonIntf *)monlibhook(src.c_str(), dest.c_str()); - } -#endif - - if (cpnfo.mon) { - - char *name=0, *ver=0, *rem=0; - if (!cpnfo.mon->GetMonLibInfo(&name, &ver, &rem)) { - Info(XrdClientDebug::kUSERDEBUG, - "main", "Monitoring client plugin found. Name:'" << name << - "' Ver:'" << ver << "' Remarks:'" << rem << "'"); - } - else { - delete cpnfo.mon; - cpnfo.mon = 0; - } - - } - -#ifndef WIN32 - if (!cpnfo.mon && monhandle) { - dlclose(monhandle); - monhandle = 0; - } -#endif - -// From here, we will have: -// the knowledge if the dest is a dir name or file name -// an open instance of xrdclient if it's a file -// - int retval = 0; - while(!retval && wklst->GetCpJob(src, dest)) - {if (cpnfo.mon) - {cpnfo.mon->Init(src.c_str(), dest.c_str(), (DebugLevel() > 0) ); - cpnfo.mon->PutProgressInfo(0, cpnfo.len, 0, 1); - } - retval = doCp(src, dest, xrddest); - if (cpnfo.mon) cpnfo.mon->DeInit(); - } - -// Delete the monitor object -// - delete cpnfo.mon; - cpnfo.mon = 0; -#ifndef WIN32 - if (monhandle) dlclose(monhandle); - monhandle = 0; -#endif - -// All done -// - if (retval < 0) retval = -retval; - _exit(retval); - return retval; -} diff --git a/src/XrdApps/XrdMapCluster.cc b/src/XrdApps/XrdMapCluster.cc deleted file mode 100644 index 2dd1fb5f300..00000000000 --- a/src/XrdApps/XrdMapCluster.cc +++ /dev/null @@ -1,588 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d M a p C l u s t e r . c c */ -/* */ -/* (c) 2013 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -/* This utility maps the connections in a cluster starting at some node. It - can also, optionally, check for file existence at each point. Syntax: - - xrdmapc : [] - -*/ - -/******************************************************************************/ -/* i n c l u d e f i l e s */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "XProtocol/XProtocol.hh" -#include "XrdCl/XrdClEnv.hh" -#include "XrdCl/XrdClFileSystem.hh" -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdNet/XrdNetAddr.hh" -#include "XrdOuc/XrdOucHash.hh" -#include "XrdSys/XrdSysHeaders.hh" - -/******************************************************************************/ -/* L o c a l D e f i n i t i o n s */ -/******************************************************************************/ - -#define EMSG(x) cerr <<"xrdmapc: " < clHash; -}; - -/******************************************************************************/ -/* M a k e U R L */ -/******************************************************************************/ - -namespace -{ -const char *MakeURL(const char *name, char *buff, int blen) -{ - snprintf(buff, blen, "xroot://%s//", name); - return buff; -} -}; - -/******************************************************************************/ -/* M a p C o d e */ -/******************************************************************************/ - -namespace -{ -void MapCode(XrdCl::XRootDStatus &Status, clMap *node, bool nspl=false) -{ - char buff[128]; - - node->verfile = '?'; - - if (Status.code == XrdCl::errErrorResponse) - {switch(Status.errNo) - {case kXR_FSError: node->state = " [fs error]"; break; - case kXR_IOError: node->state = " [io error]"; break; - case kXR_NoMemory: node->state = " [no memory]"; break; - case kXR_NotAuthorized: node->state = " [not authorized]";break; - case kXR_NotFound: if (nspl) - node->state = " [no subscribers]"; - else {node->state = ""; - node->verfile = '-'; - } - break; - case kXR_NotFile: node->state = " [not a file]"; break; - default: sprintf(buff, " [xrootd error %d]", Status.errNo); - node->state = strdup(buff); - break; - } - return; - } - - switch(Status.code) - {case XrdCl::errInvalidAddr: node->state = " [invalid addr]"; - break; - case XrdCl::errSocketError: node->state = " [socket error]"; - break; - case XrdCl::errSocketTimeout: node->state = " [timeout]"; - break; - case XrdCl::errSocketDisconnected: node->state = " [disconnect]"; - break; - case XrdCl::errStreamDisconnect: node->state = " [disconnect]"; - break; - case XrdCl::errConnectionError: node->state = " [connect error]"; - break; - case XrdCl::errHandShakeFailed: node->state = " [handshake failed]"; - break; - case XrdCl::errLoginFailed: node->state = " [login failed]"; - break; - case XrdCl::errAuthFailed: node->state = " [auth failed]"; - break; - case XrdCl::errOperationExpired: node->state = " [op expired]"; - break; - case XrdCl::errRedirectLimit: node->state = " [redirect loop]"; - break; - default: if (!(*Status.ToStr().c_str())) - sprintf(buff, " [client error %d]", Status.code); - else snprintf(buff, sizeof(buff), " [%s]", - Status.ToStr().c_str()); - node->state = strdup(buff); - break; - } -} -}; - -/******************************************************************************/ -/* M a p C l u s t e r */ -/******************************************************************************/ - -namespace -{ -void MapCluster(clMap *node, clMap *origin) -{ - static XrdCl::OpenFlags::Flags flags = XrdCl::OpenFlags::None; - char buff[2048]; - XrdCl::URL theURL((const std::string)MakeURL(node->name,buff,sizeof(buff))); - XrdCl::FileSystem xrdFS(theURL); - XrdCl::XRootDStatus Status; - XrdCl::LocationInfo *info = 0; - XrdCl::LocationInfo::Iterator it; - XrdCl::LocationInfo::LocationType locType; - clMap *clmP, *branch; - -// Issue a locate -// - Status = xrdFS.Locate((const std::string)"*", flags, info, theTO); - -// Make sure all went well -// - if (!Status.IsOK()) - {if (Status.errNo != kXR_NotFound && !doHush) - EMSG("Unable to get " <name <<" subscribers; " - <valid = 0; - return; - } - -// Grab all of the information -// - for( it = info->Begin(); it != info->End(); ++it ) - {clmP = new clMap(it->GetAddress().c_str()); - locType = it->GetType(); - if (locType == XrdCl::LocationInfo::ServerOnline - || locType == XrdCl::LocationInfo::ServerPending) - {clmP->nextSrv = node->nextSrv; - node->nextSrv = clmP; - } else { - clmP->nextMan = node->nextMan; - node->nextMan = clmP; - clmP->isMan = 1; - } - clHash.Add(clmP->key, clmP, 0, Hash_keep); - } - -// Now map all managers -// - clmP = node->nextMan; - while(clmP) - {branch = new clMap(clmP->name); - MapCluster(branch, clmP); -// if (branch->nextSrv || branch->nextMan || node->valid) - clmP->nextLvl = branch; -// else delete branch; - clmP = clmP->nextMan; - } - -// All done -// - delete info; -} -}; - -/******************************************************************************/ -/* M a p P a t h */ -/******************************************************************************/ - -namespace -{ -void MapPath(clMap *node, const char *Path, bool doRefresh=false) -{ - XrdCl::OpenFlags::Flags flags = XrdCl::OpenFlags::None; - char buff[2048]; - XrdCl::URL theURL((const std::string)MakeURL(node->name,buff,sizeof(buff))); - XrdCl::FileSystem xrdFS(theURL); - XrdCl::XRootDStatus Status; - XrdCl::LocationInfo *info = 0; - XrdCl::LocationInfo::Iterator it; - clMap *clmP; - -// Insert refresh is so wanted -// - if (doRefresh) flags = XrdCl::OpenFlags::Refresh; - -// Issue a locate -// - Status = xrdFS.Locate((const std::string)Path, flags, info, theTO); - -// Make sure all went well -// - if (!Status.IsOK()) - {if (Status.errNo != kXR_NotFound && !doHush) - EMSG("Unable to query " <name <<" about path; " - <Begin(); it != info->End(); ++it ) - {const char *clAddr = it->GetAddress().c_str(); - if ((clmP = clHash.Find(clAddr))) - {clmP->hasfile = '>'; - if (clmP->isMan) MapPath(clmP, Path); - } else { - clmP = new clMap(clAddr); - clmP->nextSrv = clLost; - clLost = clmP; - } - } - -// All done here -// - delete info; -} -}; - -/******************************************************************************/ -/* O p N a m e */ -/******************************************************************************/ - -namespace -{ -const char *OpName(char *Argv[]) -{ - static char oName[4] = {'-', 0, 0, 0}; - - if (!optopt || optopt == '-' || *(Argv[optind-1]+1) == '-') - return Argv[optind-1]; - oName[1] = optopt; - return oName; -} -}; - -/******************************************************************************/ -/* P a t h C h k */ -/******************************************************************************/ - -namespace -{ -void PathChk(clMap *node) -{ - char buff[2048]; - XrdCl::URL theURL((const std::string)MakeURL(node->name,buff,sizeof(buff))); - XrdCl::FileSystem xrdFS(theURL); - XrdCl::XRootDStatus Status; - XrdCl::StatInfo *info = 0; - -// Issue a stat for the file -// - Status = xrdFS.Stat((const std::string)Path, info); - -// Make sure all went well -// - if (!Status.IsOK()) MapCode(Status, node); - else node->verfile = '+'; - -// All done here -// - delete info; -} -}; - -/******************************************************************************/ -/* P r i n t M a p */ -/******************************************************************************/ - -namespace -{ -void PrintMap(clMap *clmP, int lvl) -{ - clMap *clnow; - const char *pfx = ""; - char *pfxbuff = 0; - int n; - -// Compute index spacing -// - if ((n = lvl*5)) - {pfxbuff = (char *)malloc(n+1); - memset(pfxbuff, ' ', n); pfxbuff[n] = 0; - pfx = pfxbuff; - } - -// Print all of the servers first -// - if (listSrv) - {clnow = clmP->nextSrv; - while(clnow) - {if (doVerify) PathChk(clnow); - if (lvl) - {pfxbuff[1] = clnow->hasfile; - pfxbuff[2] = clnow->verfile; - } - cout <<' ' <name <state <nextSrv; - } - } - -// Now recursively print the managers -// - if (listMan) - {clnow = clmP->nextMan; - if (lvl) pfxbuff[2] = ' '; - while(clnow) - {if (lvl) pfxbuff[1] = clnow->hasfile; - cout <name <state <valid && clnow->nextLvl) PrintMap(clnow->nextLvl,lvl+1); - clnow = clnow->nextMan; - } - } - -// All done -// - if (lvl) free(pfxbuff); -} -}; - -/******************************************************************************/ -/* S e t E n v */ -/******************************************************************************/ - -namespace -{ -int cwValue = 10; -int crValue = 0; -int trValue = 5; - -void SetEnv() -{ - XrdCl::Env *env = XrdCl::DefaultEnv::GetEnv(); - - env->PutInt("ConnectionWindow", cwValue); - env->PutInt("ConnectionRetry", crValue); - env->PutInt("TimeoutResolution",trValue); -} -}; - -/******************************************************************************/ -/* U s a g e */ -/******************************************************************************/ - -namespace -{ -void Usage(const char *emsg) -{ - if (emsg) EMSG(emsg); - cerr <<"Usage: xrdmapc [] : []\n" - <<": [--help] [--list {all|m|s}] [--quiet] [--refresh] [--verify]" < existence status at each server.\n" -" when specified, uses : to determine the locations\n" -" of path and does optional verification." - <= argc) Usage("Initial node not specified."); - -// Establish starting point -// - if ((eMsg = sPoint.Set(argv[optind]))) - {EMSG("Unable to validate initial node; " <name <state <name - <<" referred to the following unconnected node:" <name <nextSrv; - } - } - -// All done -// - exit(0); -} diff --git a/src/XrdApps/XrdMpxStats.cc b/src/XrdApps/XrdMpxStats.cc deleted file mode 100644 index 8e7a03bbd8e..00000000000 --- a/src/XrdApps/XrdMpxStats.cc +++ /dev/null @@ -1,301 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d M p x S t a t s . c c */ -/* */ -/* (c) 2009 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include - -#include "XrdApps/XrdMpxXml.hh" -#include "XrdNet/XrdNetAddr.hh" -#include "XrdNet/XrdNetOpts.hh" -#include "XrdNet/XrdNetSocket.hh" -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysLogger.hh" -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdSys/XrdSysPlatform.hh" -#include "XrdSys/XrdSysPthread.hh" - -/******************************************************************************/ -/* G l o b a l V a r i a b l e s */ -/******************************************************************************/ - -namespace XrdMpx -{ - XrdSysLogger Logger; - - XrdSysError Say(&Logger, "mpxstats"); - -static const int addSender = 0x0001; - - int Opts; -}; - -using namespace XrdMpx; - -/******************************************************************************/ -/* X r d M p x O u t */ -/******************************************************************************/ - -class XrdMpxOut -{ -public: - -struct statsBuff - {statsBuff *Next; - XrdNetSockAddr From; - int Dlen; - char Data[8190]; - char Pad[2]; - }; - -void Add(statsBuff *sbP); - -statsBuff *getBuff(); - -void *Run(XrdMpxXml *xP); - - XrdMpxOut() : Ready(0), inQ(0), Free(0) {} - ~XrdMpxOut() {} - -private: - -XrdSysMutex myMutex; -XrdSysSemaphore Ready; - -statsBuff *inQ; -statsBuff *Free; -}; - -/******************************************************************************/ -/* X r d M p x O u t : : A d d */ -/******************************************************************************/ - -void XrdMpxOut::Add(statsBuff *sbP) -{ - -// Add this to the queue and signal the processing thread -// - myMutex.Lock(); - sbP->Next = inQ; - inQ = sbP; - Ready.Post(); - myMutex.UnLock(); -} - -/******************************************************************************/ -/* X r d M p x O u t : : g e t B u f f */ -/******************************************************************************/ - -XrdMpxOut::statsBuff *XrdMpxOut::getBuff() -{ - statsBuff *sbP; - -// Use an available buffer or allocate one -// - myMutex.Lock(); - if ((sbP = Free)) Free = sbP->Next; - else sbP = new statsBuff; - myMutex.UnLock(); - return sbP; -} - -/******************************************************************************/ -/* X r d M p x O u t : : R u n */ -/******************************************************************************/ - -void *XrdMpxOut::Run(XrdMpxXml *xP) -{ - XrdNetAddr theAddr; - const char *Host = 0; - char *bP, obuff[sizeof(statsBuff)*2]; - statsBuff *sbP; - int wLen, rc; - -// Simply loop formating and outputing the buffers -// - while(1) - {Ready.Wait(); - myMutex.Lock(); - if ((sbP = inQ)) inQ = sbP->Next; - myMutex.UnLock(); - if (!sbP) continue; - if (xP) - {if (!(Opts & addSender)) Host = 0; - else if (theAddr.Set(&(sbP->From.Addr))) Host = 0; - else Host = theAddr.Name(); - wLen = xP->Format(Host, sbP->Data, obuff); - bP = obuff; - } else { - bP = sbP->Data; - *(bP + sbP->Dlen) = '\n'; - wLen = sbP->Dlen+1; - } - - while(wLen > 0) - {do {rc = write(STDOUT_FILENO, bP, wLen);} - while(rc < 0 && errno == EINTR); - wLen -= rc; bP += rc; - } - - myMutex.Lock(); sbP->Next = Free; Free = sbP; myMutex.UnLock(); - } - -// Should never get here -// - return (void *)0; -} - -/******************************************************************************/ -/* G l o b a l O b j e c t s */ -/******************************************************************************/ - -namespace XrdMpx -{ -XrdMpxOut statsQ; -}; - -/******************************************************************************/ -/* T h r e a d I n t e r f a c e s */ -/******************************************************************************/ - -void *mainOutput(void *parg) -{ - XrdMpxXml *xP = static_cast(parg); - return statsQ.Run(xP); -} - -/******************************************************************************/ -/* U s a g e */ -/******************************************************************************/ - -void Usage(int rc) -{ - cerr <<"\nUsage: mpxstats [-f {cgi|flat|xml}] -p [-s]" < 1 && '-' == *argv[1]) - while ((c = getopt(argc,argv,"df:p:s")) && ((unsigned char)c != 0xff)) - { switch(c) - { - case 'd': Debug = true; - break; - case 'f': if (!strcmp(optarg, "cgi" )) fType = XrdMpxXml::fmtCGI; - else if (!strcmp(optarg, "flat")) fType = XrdMpxXml::fmtFlat; - else if (!strcmp(optarg, "xml" )) fType = XrdMpxXml::fmtXML; - else {Say.Emsg(":", "Invalid format - ", optarg); Usage(1);} - break; - case 'h': Usage(0); - break; - case 'p': if (!(Port = atoi(optarg))) - {Say.Emsg(":", "Invalid port number - ", optarg); Usage(1);} - break; - case 's': Opts |= addSender; - break; - default: sprintf(buff,"'%c'", optopt); - if (c == ':') Say.Emsg(":", buff, "value not specified."); - else Say.Emsg(0, buff, "option is invalid"); - Usage(1); - break; - } - } - -// Make sure port has been specified -// - if (!Port) {Say.Emsg(":", "Port has not been specified."); Usage(1);} - -// Turn off sigpipe and host a variety of others before we start any threads -// - signal(SIGPIPE, SIG_IGN); // Solaris optimization - sigemptyset(&myset); - sigaddset(&myset, SIGPIPE); - sigaddset(&myset, SIGCHLD); - pthread_sigmask(SIG_BLOCK, &myset, NULL); - -// Set the default stack size here -// - if (sizeof(long) > 4) XrdSysThread::setStackSize((size_t)1048576); - else XrdSysThread::setStackSize((size_t)786432); - -// Create a UDP socket and bind it to a port -// - if (mySocket.Open(0, Port, XRDNET_SERVER|XRDNET_UDPSOCKET, 0) < 0) - {Say.Emsg(":", -mySocket.LastError(), "create udp socket"); exit(4);} - udpFD = mySocket.Detach(); - -// Establish format -// - if (fType != XrdMpxXml::fmtXML) xP = new XrdMpxXml(fType, Debug); - -// Now run a thread to output whatever we get -// - if ((retc = XrdSysThread::Run(&tid, mainOutput, (void *)xP, - XRDSYSTHREAD_BIND, "Output"))) - {Say.Emsg(":", retc, "create output thread"); exit(4);} - -// Now simply wait for the messages -// - fromLen = sizeof(sbP->From); - while(1) - {sbP = statsQ.getBuff(); - retc = recvfrom(udpFD, sbP->Data, sizeof(sbP->Data), 0, - &sbP->From.Addr, &fromLen); - if (retc < 0) {Say.Emsg(":", retc, "recv udp message"); exit(8);} - sbP->Dlen = retc; - statsQ.Add(sbP); - } - -// Should never get here -// - return 0; -} diff --git a/src/XrdApps/XrdMpxXml.cc b/src/XrdApps/XrdMpxXml.cc deleted file mode 100644 index 5f91419bc13..00000000000 --- a/src/XrdApps/XrdMpxXml.cc +++ /dev/null @@ -1,372 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d M p x X m l . c c */ -/* */ -/* (c) 2018 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "XrdApps/XrdMpxXml.hh" -#include "XrdOuc/XrdOucTokenizer.hh" - -using namespace std; - -/******************************************************************************/ -/* v n M a p D e f i n i t i o n */ -/******************************************************************************/ - -namespace -{ -struct vCmp - {bool operator()(const char *a, const char *b) - const {return strcmp(a,b) < 0;} - }; - -std::map vnMap { - -{"src", "Server location:"}, -{"tod", "~Statistics:"}, -{"tos", "~Server started: "}, -{"pgm", "Server program: "}, -{"ins", "Server instance:"}, -{"pid", "Server process: "}, -{"ver", "Server version: "}, -{"info.host", "Host name:"}, -{"info.port", "Port:"}, -{"info.name", "Instance name:"}, -{"buff.reqs", "Buffer requests:"}, -{"buff.mem", "Buffer bytes:"}, -{"buff.buffs", "Buffer count:"}, -{"buff.adj", "Buffer adjustments:"}, -{"buff.xlreqs", "Buffer XL requests:"}, -{"buff.xlmem", "Buffer XL bytes:"}, -{"buff.xlbuffs", "Buffer XL count:"}, -{"link.num", "Current connections:"}, -{"link.maxn", "Maximum connections:"}, -{"link.tot", "Overall connections:"}, -{"link.in", "Bytes received:"}, -{"link.out", "Bytes sent:"}, -{"link.ctime", "Total connect seconds:"}, -{"link.tmo", "Read request timeouts:"}, -{"link.stall", "Number of partial reads:"}, -{"link.sfps", "Number of partial sends:"}, -{"poll.att", "Poll sockets:"}, -{"poll.en", "Poll enables:"}, -{"poll.ev", "Poll events: "}, -{"poll.int", "Poll events unsolicited:"}, -{"proc.usr.s", "Seconds user time:"}, -{"proc.usr.u", "Micros user time:"}, -{"proc.sys.s", "Seconds sys time:"}, -{"proc.sys.u", "Micros sys time:"}, -{"xrootd.num", "XRootD protocol loads:"}, -{"xrootd.ops.open", "XRootD opens:"}, -{"xrootd.ops.rf", "XRootD cache refreshes:"}, -{"xrootd.ops.rd", "XRootD reads:"}, -{"xrootd.ops.pr", "XRootD preads:"}, -{"xrootd.ops.rv", "XRootD readv's:"}, -{"xrootd.ops.rs", "XRootD readv segments:"}, -{"xrootd.ops.wr", "XRootD writes:"}, -{"xrootd.ops.sync", "XRootD syncs:"}, -{"xrootd.ops.getf", "XRootD getfiles:"}, -{"xrootd.ops.putf", "XRootD putfiles:"}, -{"xrootd.ops.misc", "XRootD misc requests:"}, -{"xrootd.sig.ok", "XRootD ok signatures:"}, -{"xrootd.sig.bad", "XRootD bad signatures:"}, -{"xrootd.sig.ign", "XRootD ign signatures:"}, -{"xrootd.aio.num", "XRootD aio requests:"}, -{"xrootd.aio.max", "XRootD aio max requests:"}, -{"xrootd.aio.rej", "XRootD aio rejections:"}, -{"xrootd.err", "XRootD request failures:"}, -{"xrootd.rdr", "XRootD request redirects:"}, -{"xrootd.dly", "XRootD request delays:"}, -{"xrootd.lgn.num", "XRootD login total count:"}, -{"xrootd.lgn.af", "XRootD login auths bad: "}, -{"xrootd.lgn.au", "XRootD login auths good: "}, -{"xrootd.lgn.ua", "XRootD login auths none: "}, -{"ofs.role", "Server role:"}, -{"ofs.opr", "Ofs reads:"}, -{"ofs.opw", "Ofs writes:"}, -{"ofs.opp", "POSC files now open:"}, -{"ofs.ups", "POSC files deleted:"}, -{"ofs.han", "Ofs handles:"}, -{"ofs.rdr", "Ofs redirects:"}, -{"ofs.bxq", "Ofs background tasks:"}, -{"ofs.rep", "Ofs callbacks:"}, -{"ofs.err", "Ofs errors:"}, -{"ofs.dly", "Ofs delays:"}, -{"ofs.sok", "Ofs ok events:"}, -{"ofs.ser", "Ofs bad events:"}, -{"ofs.tpc.grnt", "TPC grants:"}, -{"ofs.tpc.deny", "TPC denials:"}, -{"ofs.tpc.err", "TPC errors:"}, -{"ofs.tpc.exp", "TPC expires:"}, -{"oss.paths", "Oss exports:"}, -{"oss.space", "Oss space:"}, -{"sched.jobs", "Tasks scheduled: "}, -{"sched.inq", "Tasks now queued:"}, -{"sched.maxinq", "Max tasks queued:"}, -{"sched.threads", "Threads in pool:"}, -{"sched.idle", "Threads idling: "}, -{"sched.tcr", "Threads created:"}, -{"sched.tde", "Threads deleted:"}, -{"sched.tlimr", "Threads unavail:"}, -{"sgen.as", "Unsynchronized stats:"}, -{"sgen.et", "Mills to collect stats:"}, -{"sgen.toe", "~Time when stats collected:"} -}; -} - -/******************************************************************************/ -/* L o c a l C l a s s e s */ -/******************************************************************************/ -/******************************************************************************/ -/* X r d M p x V a r */ -/******************************************************************************/ - -class XrdMpxVar -{ -public: - - int Pop(const char *vName); - - int Push(const char *vName); - - void Reset() {vEnd = vBuff; vNum = -1; *vBuff = 0;} - -const char *Var() {return vBuff;} - - XrdMpxVar(bool dbg=false) - : vFence(vBuff + sizeof(vBuff) - 1), Debug(dbg) {Reset();} - ~XrdMpxVar() {} - -private: -static const int vMax = 15; - char *vEnd, *vFence, *vStack[vMax+1], vBuff[1024]; - int vNum; - bool Debug; -}; - -/******************************************************************************/ -/* X r d M p x V a r : : P o p */ -/******************************************************************************/ - -int XrdMpxVar::Pop(const char *vName) -{ - if (Debug) cerr <<"Pop: " <<(vName ? vName : "") <<"; var=" <' -// - if (!(lP = (char *)index(lP, '>'))) - return xmlErr("Invalid xml stream: ", ibuff); - *lP++ = '\n'; - -// Now make the input tokenizable -// - while(*lP) - {if (*lP == '>' || (*lP == '<' && *(lP+1) == '/')) *lP = ' '; - lP++; - } - -// The first token better be '' in xml stream."); - getVars(Data, vTail); - if (vTail[0].Data) oP = Add(oP, vTail[0].Name, vTail[0].Data); - if (*(oP-1) == '&') oP--; - *oP++ = '\n'; - return oP - obuff; -} - -/******************************************************************************/ -/* X r d M p x X m l : : A d d */ -/******************************************************************************/ - -char *XrdMpxXml::Add(char *Buff, const char *Var, const char *Val) -{ - char tmBuff[256]; - - if (noZed && !strcmp("0", Val)) return Buff; - - if (doV2T) - {std::map::iterator it; - it = vnMap.find(Var); - if (it != vnMap.end()) - {Var = it->second; - if (*Var == '~') - {time_t tod = atoi(Val); - Var++; - if (tod) - {struct tm *tInfo = localtime(&tod); - strftime(tmBuff, sizeof(tmBuff), "%a %F %T", tInfo); - Val = tmBuff; - } - } - } - } - - strcpy(Buff, Var); Buff += strlen(Var); - *Buff++ = vSep; - strcpy(Buff, Val); Buff += strlen(Val); - *Buff++ = vSfx; - return Buff; -} - -/******************************************************************************/ -/* */ -/* X r d M p x X m l : : g e t V a r s */ -/* */ -/******************************************************************************/ - -void XrdMpxXml::getVars(XrdOucTokenizer &Data, VarInfo Var[]) -{ - char *tVar, *tVal; - int i; - -// Initialize the data pointers to null -// - i = 0; - while(Var[i].Name) Var[i++].Data = 0; - -// Get all of the variables/values and return where possible -// - while((tVar = Data.GetToken()) && *tVar != '<' && *tVar != '/') - {if (!(tVal = (char *)index(tVar, '='))) continue; - *tVal++ = '\0'; - if (*tVal == '"') - {tVal++, i = strlen(tVal); - if (*(tVal+i-1) == '"') *(tVal+i-1) = '\0'; - } - i = 0; - while(Var[i].Name) - {if (!strcmp(Var[i].Name, tVar)) {Var[i].Data = tVal; break;} - else i++; - } - } - if (tVar && (*tVar == '<' || *tVar == '/')) Data.RetToken(); -} - -/******************************************************************************/ -/* X r d M p x X m l : : x m l E r r */ -/******************************************************************************/ - -int XrdMpxXml::xmlErr(const char *t1, const char *t2, const char *t3) -{ - cerr <<"XrdMpxXml: " <. */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -class XrdOucTokenizer; - -class XrdMpxXml -{ -public: - -enum fmtType {fmtCGI, fmtFlat, fmtText, fmtXML}; - -int Format(const char *Host, char *ibuff, char *obuff); - - XrdMpxXml(fmtType ft, bool nz=false, bool dbg=false) - : fType(ft), Debug(dbg), noZed(nz) - {if (ft == fmtCGI) {vSep = '='; vSfx = '&';} - else {vSep = ' '; vSfx = '\n';} - doV2T = ft == fmtText; - } - ~XrdMpxXml() {} - -private: - -struct VarInfo - {const char *Name; - char *Data; - }; - -char *Add(char *Buff, const char *Var, const char *Val); -void getVars(XrdOucTokenizer &Data, VarInfo Var[]); -int xmlErr(const char *t1, const char *t2=0, const char *t3=0); - -fmtType fType; -char vSep; -char vSfx; -bool Debug; -bool noZed; -bool doV2T; -}; -#endif diff --git a/src/XrdApps/XrdQStats.cc b/src/XrdApps/XrdQStats.cc deleted file mode 100644 index 711d29605a7..00000000000 --- a/src/XrdApps/XrdQStats.cc +++ /dev/null @@ -1,207 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d M p x S t a t s . c c */ -/* */ -/* (c) 2009 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include - -#include "XrdApps/XrdMpxXml.hh" -#include "XrdCl/XrdClBuffer.hh" -#include "XrdCl/XrdClFileSystem.hh" -#include "XrdCl/XrdClURL.hh" -#include "XrdCl/XrdClXRootDResponses.hh" - -using namespace std; - -/******************************************************************************/ -/* F a t a l */ -/******************************************************************************/ - -void Fatal(const XrdCl::XRootDStatus &Status) -{ - std::string eText; - -// If this is an xrootd error then get the xrootd generated error -// - eText = (Status.code == XrdCl::errErrorResponse ? - eText = Status.GetErrorMessage() : Status.ToStr()); - -// Blither and exit -// - cerr <<"xrdqstats: Unable to obtain statistic - " <[:]\n" - "\nopts: -f {cgi|flat|xml} -h -i -n -s what -z\n" - "\n-f specify display format (default is wordy text format)." - "\n-i number of seconds to wait before between redisplays, default 10." - "\n-n number of redisplays; if -s > 0 and -n unspecified goes forever." - "\n-z does not display items with a zero value (wordy text only).\n" - "\nwhat: one or more of the following letters to select statistics:" - "\na - All (default) b - Buffer usage d - Device polling" - "\ni - Identification c - Connections p - Protocols" - "\ns - Scheduling u - Usage data z - Synchronized info" - < 1 && '-' == *argv[1]) - while ((c = getopt(argc,argv,valOpts)) && ((unsigned char)c != 0xff)) - { switch(c) - { - case 'd': Debug = true; - break; - case 'f': if (!strcmp(optarg, "cgi" )) fType = XrdMpxXml::fmtCGI; - else if (!strcmp(optarg, "flat")) fType = XrdMpxXml::fmtFlat; - else if (!strcmp(optarg, "xml" )) fType = XrdMpxXml::fmtXML; - else {cerr <= argc) - {cerr <GetBuffer() <Format(0, theStats->GetBuffer(), obuff); - char *bP = obuff; - while(wLen > 0) - {do {rc = write(STDOUT_FILENO, bP, wLen);} - while(rc < 0 && errno == EINTR); - wLen -= rc; bP += rc; - } - } - delete theStats; - if (WTime) sleep(WTime); - if (Count) cout <<"\n"; - } - -// All done -// - return 0; -} diff --git a/src/XrdApps/XrdWait41.cc b/src/XrdApps/XrdWait41.cc deleted file mode 100644 index 3df14bb6272..00000000000 --- a/src/XrdApps/XrdWait41.cc +++ /dev/null @@ -1,301 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d W a i t 4 1 . c c */ -/* */ -/* (c) 2009 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - - - -/* This unitily waits for the first of n file locks. The syntax is: - - wait41 [ [. . .]] - -*/ - -/******************************************************************************/ -/* i n c l u d e f i l e s */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "XrdOuc/XrdOucTList.hh" -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdSys/XrdSysPlatform.hh" -#include "XrdSys/XrdSysPthread.hh" - -/******************************************************************************/ -/* L o c a l C l a s s e s */ -/******************************************************************************/ - -class XrdW41Gate -{ -public: - -static void Serialize(XrdOucTList *gfP, int Wait=1); - -static int Wait41(XrdOucTList *fP); - - XrdW41Gate() {} - ~XrdW41Gate() {} - -private: -static XrdSysMutex gateMutex; -static XrdSysSemaphore gateSem; -static int gateOpen; -}; - -XrdSysMutex XrdW41Gate::gateMutex; -XrdSysSemaphore XrdW41Gate::gateSem(0); -int XrdW41Gate::gateOpen = 0; - -class XrdW41Dirs -{ -public: - -static XrdOucTList *Expand(const char *Path, XrdOucTList *ptl); -}; - -/******************************************************************************/ -/* E x t e r n a l T h r e a d I n t e r f a c e s */ -/******************************************************************************/ - -namespace XrdWait41 -{ -void *GateWait(void *parg) -{ - XrdOucTList *fP = (XrdOucTList *)parg; - -// Serialize -// - XrdW41Gate::Serialize(fP); - return (void *)0; -} -} - -using namespace XrdWait41; - -/******************************************************************************/ -/* m a i n */ -/******************************************************************************/ - -int main(int argc, char *argv[]) -{ - sigset_t myset; - XrdOucTList *gateFiles = 0; - struct stat Stat; - const char *eText; - char buff[8]; - int i; - -// Turn off sigpipe and host a variety of others before we start any threads -// - signal(SIGPIPE, SIG_IGN); // Solaris optimization - sigemptyset(&myset); - sigaddset(&myset, SIGPIPE); - sigaddset(&myset, SIGCHLD); - pthread_sigmask(SIG_BLOCK, &myset, NULL); - -// Set the default stack size here -// - if (sizeof(long) > 4) XrdSysThread::setStackSize((size_t)1048576); - else XrdSysThread::setStackSize((size_t)786432); - -// Construct a list of files. For each directory, expand that to a list -// - for (i = 1; i < argc; i++) - {if (stat(argv[i], &Stat)) - {eText = strerror(errno); - cerr <<"wait41: " <d_name, ".") || !strcmp(dp->d_name, "..")) continue; - strcpy(sfxDir, dp->d_name); - if (stat(buff, &Stat)) - {eText = strerror(errno); - cerr <<"wait41: " <val, Act, &lock_args);} while(rc == -1 && errno == EINTR); - -// Determine result -// - if (rc != -1) rc = 0; - else {rc = errno; - cerr <<"Serialize: " <text <val); - else gateOpen = 1; - gateSem.Post(); - gateMutex.UnLock(); -} - -/******************************************************************************/ -/* W a i t 4 1 */ -/******************************************************************************/ - -int XrdW41Gate::Wait41(XrdOucTList *gfP) -{ - static const int AMode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; - pthread_t tid; - const char *eTxt; - int rc, Num = 0; - -// Run through the chain of files setting up a wait. We try to do a fast -// redispatch in case we get a lock early. -// - while(gfP) - {if (Num) - {gateMutex.Lock(); - if (gateOpen) {gateMutex.UnLock(); return 1;} - gateMutex.UnLock(); - } - if ((gfP->val = open(gfP->text, O_CREAT|O_RDWR, AMode)) < 0) - {eTxt = strerror(errno); - cerr <<"Wait41: " <text <text <val); - } else Num++; - gfP = gfP->next; - } - -// At this point we will have to wait for the lock if we have any threads -// - while(Num--) - {gateSem.Wait(); - gateMutex.Lock(); - if (gateOpen) {gateMutex.UnLock(); return 1;} - gateMutex.UnLock(); - } - -// No such luck, every thread failed -// - return 0; -} diff --git a/src/XrdApps/Xrdadler32.cc b/src/XrdApps/Xrdadler32.cc deleted file mode 100644 index ae5be1a2448..00000000000 --- a/src/XrdApps/Xrdadler32.cc +++ /dev/null @@ -1,248 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d a d l e r 3 2 . c c */ -/* */ -/* (c) 2009 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Wei Yang for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -/************************************************************************/ -/* Calculating Adler32 checksum of a local unix file (including stdin) */ -/* and file on a remote xrootd data server. Support using XROOTD_VMP. */ -/************************************************************************/ - -#define _FILE_OFFSET_BITS 64 - -#include -#include -#include -#include -#include -#include -#include -#include -#ifdef __linux__ - #include -#endif -#include - -#include "XrdPosix/XrdPosixXrootd.hh" -#include "XrdPosix/XrdPosixXrootdPath.hh" -#include "XrdOuc/XrdOucString.hh" - -#include "XrdCks/XrdCksXAttr.hh" -#include "XrdOuc/XrdOucXAttr.hh" - -void fSetXattrAdler32(const char *path, int fd, const char* attr, char *value) -{ - XrdOucXAttr xCS; - struct stat st; - - if (fstat(fd, &st) || strlen(value) != 8) return; - - if (!xCS.Attr.Cks.Set("adler32") || !xCS.Attr.Cks.Set(value,8)) return; - - xCS.Attr.Cks.fmTime = static_cast(st.st_mtime); - xCS.Attr.Cks.csTime = static_cast(time(0) - st.st_mtime); - - xCS.Set("", fd); - -// Remove any old attribute at this point -// -#if defined(__linux__) - fremovexattr(fd, attr); -#elif defined(__solaris__) - int attrfd; - attrfd = openat(fd, attr, O_XATTR|O_RDONLY); - if (attrfd >= 0) - {unlinkat(attrfd, attr, 0); close(attrfd);} -#endif -} - -int fGetXattrAdler32(int fd, const char* attr, char *value) -{ - struct stat st; - char mtime[12], attr_val[25], *p; - int rc; - - if (fstat(fd, &st)) return 0; - sprintf(mtime, "%ld", st.st_mtime); - -#if defined(__linux__) - rc = fgetxattr(fd, attr, attr_val, 25); -#elif defined(__solaris__) - int attrfd; - attrfd = openat(fd, attr, O_XATTR|O_RDONLY); - if (attrfd < 0) return(0); - - rc = read(attrfd, attr_val, 25); - close(attrfd); -#else - return(0); -#endif - - if (rc == -1 || attr_val[8] != ':') return(0); - attr_val[8] = '\0'; - attr_val[rc] = '\0'; - p = attr_val + 9; - - if (strcmp(p, mtime)) return(0); - - strcpy(value, attr_val); - - return(strlen(value)); -} - -int fGetXattrAdler32(const char *path, int fd, const char* attr, char *value) -{ - XrdOucXAttr xCS; - struct stat st; - - if (!xCS.Attr.Cks.Set("adler32") || xCS.Get(path, fd) <= 0 - || strcmp(xCS.Attr.Cks.Name, "adler32")) - {int rc = fGetXattrAdler32(fd, attr, value); - if (rc == 8) fSetXattrAdler32(path, fd, attr, value); - return rc; - } - - if (fstat(fd, &st) - || xCS.Attr.Cks.fmTime != static_cast(st.st_mtime)) return 0; - - xCS.Attr.Cks.Get(value, 9); - return 8; -} - -/* the rooturl should point to the data server, not redirector */ -char getchksum(const char *rooturl, char *chksum) -{ - char csBuff[256]; - int csLen; - -// Obtain the checksum (this is the default checksum) -// - csLen = XrdPosixXrootd::Getxattr(rooturl, "xroot.cksum", - csBuff, sizeof(csBuff)); - if (csLen < 0) return -1; - if (csLen == 0) return 0; // Server doesn't have the checksum - -// Verify that the checksum returned is "adler32" -// - if (strncmp("adler32 ", csBuff, 8)) return 0; - -// Return the checksum value (this is really bad code) -// - strcpy(chksum, csBuff+8); - return strlen(csBuff+8); -} - -#define N 1024*1024 /* reading block size */ - -int main(int argc, char *argv[]) -{ - char path[2048], chksum[128], buf[N], adler_str[9]; - const char attr[] = "user.checksum.adler32"; - struct stat stbuf; - int fd, len, rc; - uLong adler; - adler = adler32(0L, Z_NULL, 0); - - if (argc == 2 && ! strcmp(argv[1], "-h")) - { - printf("Usage: %s file. Calculating adler32 checksum of a given file.\n", argv[0]); - printf("A file can be local file, stdin (if omitted), or root URL (including via XROOTD_VMP)\n"); - return 0; - } - - path[0] = '\0'; - if (argc > 1) /* trying to convert to root URL */ - { - if (!strncmp(argv[1], "root://", 7)) - strcpy(path, argv[1]); - else {XrdPosixXrootPath xrdPath; - xrdPath.URL(argv[1], path, sizeof(path)); - } - } - if (argc == 1 || path[0] == '\0') - { /* this is a local file */ - if (argc > 1) - { - strcpy(path, argv[1]); - rc = stat(path, &stbuf); - if (rc != 0 || ! S_ISREG(stbuf.st_mode) || - (fd = open(path,O_RDONLY)) < 0) - { - printf("Error_accessing %s\n", path); - return 1; - } - else /* see if the adler32 is saved in attribute already */ - if (fGetXattrAdler32(path, fd, attr, adler_str) == 8) - { - printf("%s %s\n", adler_str, path); - return 0; - } - } - else - { - fd = STDIN_FILENO; - strcpy(path, "-"); - } - while ( (len = read(fd, buf, N)) > 0 ) - adler = adler32(adler, (const Bytef*)buf, len); - - if (fd != STDIN_FILENO) - { /* try saving adler32 to attribute before close() */ - sprintf(adler_str, "%08lx", adler); - fSetXattrAdler32(path, fd, attr, adler_str); - close(fd); - } - printf("%08lx %s\n", adler, path); - return 0; - } - else - { /* this is a Xrootd file */ - if (getchksum(path, chksum) > 0) - { /* server implements checksum */ - printf("%s %s\n", chksum, argv[1]); - return (strcmp(chksum, "Error_accessing:") ? 0 : 1); - } - else - { /* need to read the file and calculate */ - XrdPosixXrootd myPFS(-8, 8, 1); - rc = XrdPosixXrootd::Stat(path, &stbuf); - if (rc != 0 || ! S_ISREG(stbuf.st_mode) || - (fd = XrdPosixXrootd::Open(path, O_RDONLY, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)) < 0) - { - printf("Error_accessing: %s\n", argv[1]); - return 1; - } - while ( (len = XrdPosixXrootd::Read(fd, buf, N)) > 0 ) - adler = adler32(adler, (const Bytef*)buf, len); - - XrdPosixXrootd::Close(fd); - printf("%08lx %s\n", adler, argv[1]); - return 0; - } - } -} diff --git a/src/XrdBwm/XrdBwm.cc b/src/XrdBwm/XrdBwm.cc deleted file mode 100644 index 3e96727da31..00000000000 --- a/src/XrdBwm/XrdBwm.cc +++ /dev/null @@ -1,1039 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d B w m . c c */ -/* */ -/* (c) 2008 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Deprtment of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "XrdVersion.hh" - -#include "XrdBwm/XrdBwm.hh" -#include "XrdBwm/XrdBwmTrace.hh" - -#include "XrdAcc/XrdAccAuthorize.hh" - -#include "XrdNet/XrdNetAddr.hh" - -#include "XrdOuc/XrdOucEnv.hh" -#include "XrdOuc/XrdOucUtils.hh" -#include "XrdOuc/XrdOucTrace.hh" - -#include "XrdSec/XrdSecEntity.hh" - -#include "XrdSfs/XrdSfsAio.hh" -#include "XrdSfs/XrdSfsInterface.hh" - -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdSys/XrdSysLogger.hh" -#include "XrdSys/XrdSysPlatform.hh" -#include "XrdSys/XrdSysPthread.hh" - -/******************************************************************************/ -/* E r r o r R o u t i n g O b j e c t */ -/******************************************************************************/ - -XrdSysError BwmEroute(0); - -XrdOucTrace BwmTrace(&BwmEroute); - -/******************************************************************************/ -/* S t a t i c O b j e c t s */ -/******************************************************************************/ - -XrdBwmHandle *XrdBwm::dummyHandle; - -/******************************************************************************/ -/* F i l e S y s t e m O b j e c t */ -/******************************************************************************/ - -XrdVERSIONINFO(XrdSfsGetFileSystem,XrdBwm); - -XrdBwm XrdBwmFS; - -/******************************************************************************/ -/* X r d B w m C o n s t r u c t o r */ -/******************************************************************************/ - -XrdBwm::XrdBwm() -{ - XrdNetAddr myAddr(0); - char buff[256], *bp; - int myPort, i; - -// Establish defaults -// - Authorization = 0; - Authorize = 0; - AuthLib = 0; - AuthParm = 0; - Logger = 0; - PolLib = 0; - PolParm = 0; - PolSlotsIn = 1; - PolSlotsOut = 1; - -// Obtain port number we will be using -// - myPort = (bp = getenv("XRDPORT")) ? strtol(bp, (char **)NULL, 10) : 0; - -// Establish our hostname and address -// - myAddr.Port(myPort); - HostName = strdup(myAddr.Name("*unknown*")); - myAddr.Format(buff, sizeof(buff), XrdNetAddr::fmtAdv6, XrdNetAddr::old6Map4); - locResp = strdup(buff); locRlen = strlen(buff); - for (i = 0; HostName[i] && HostName[i] != '.'; i++) {} - HostName[i] = '\0'; - HostPref = strdup(HostName); - HostName[i] = '.'; - myDomain = &HostName[i+1]; - myDomLen = strlen(myDomain); - myVersion = &XrdVERSIONINFOVAR(XrdSfsGetFileSystem); - -// Set the configuration file name abd dummy handle -// - ConfigFN = 0; - dummyHandle = XrdBwmHandle::Alloc("*", "/", "?", "?", 0); -} - -/******************************************************************************/ -/* X r d B w m F i l e C o n s t r u c t o r */ -/******************************************************************************/ - -XrdBwmFile::XrdBwmFile(const char *user, int monid) : XrdSfsFile(user, monid) -{ - oh = XrdBwm::dummyHandle; - tident = (user ? user : ""); -} - -/******************************************************************************/ -/* G e t F i l e S y s t e m */ -/******************************************************************************/ - -extern "C" -{ -XrdSfsFileSystem *XrdSfsGetFileSystem(XrdSfsFileSystem *native_fs, - XrdSysLogger *lp, - const char *configfn) -{ -// Do the herald thing -// - BwmEroute.SetPrefix("bwm_"); - BwmEroute.logger(lp); - BwmEroute.Say("Copr. 2008 Stanford University, Bwm Version " XrdVSTRING); - -// Initialize the subsystems -// - XrdBwmFS.ConfigFN = (configfn && *configfn ? strdup(configfn) : 0); - if ( XrdBwmFS.Configure(BwmEroute) ) return 0; - -// All done, we can return the callout vector to these routines. -// - return &XrdBwmFS; -} -} - -/******************************************************************************/ -/* */ -/* D i r e c t o r y O b j e c t I n t e r f a c e s */ -/* */ -/******************************************************************************/ -/******************************************************************************/ -/* o p e n */ -/******************************************************************************/ - -int XrdBwmDirectory::open(const char *dir_path, // In - const XrdSecEntity *client, // In - const char *info) // In -/* - Function: Open the directory `path' and prepare for reading. - - Input: path - The fully qualified name of the directory to open. - client - Authentication credentials, if any. - info - Opaque information to be used as seen fit. - - Output: Returns SFS_OK upon success, otherwise SFS_ERROR. - - Notes: 1. Currently, function not supported. -*/ -{ -// Return an error -// - return XrdBwmFS.Emsg("opendir", error, ENOTDIR, "open directory", dir_path); -} - -/******************************************************************************/ -/* n e x t E n t r y */ -/******************************************************************************/ - -const char *XrdBwmDirectory::nextEntry() -/* - Function: Read the next directory entry. - - Input: n/a - - Output: n/a -*/ -{ -// Return an error -// - XrdBwmFS.Emsg("readdir", error, EBADF, "read directory"); - return 0; -} - -/******************************************************************************/ -/* c l o s e */ -/******************************************************************************/ - -int XrdBwmDirectory::close() -/* - Function: Close the directory object. - - Input: n/a - - Output: Returns SFS_OK upon success and SFS_ERROR upon failure. -*/ -{ -// Return an error -// - XrdBwmFS.Emsg("closedir", error, EBADF, "close directory"); - return SFS_ERROR; -} - -/******************************************************************************/ -/* */ -/* F i l e O b j e c t I n t e r f a c e s */ -/* */ -/******************************************************************************/ -/******************************************************************************/ -/* o p e n */ -/******************************************************************************/ - -int XrdBwmFile::open(const char *path, // In - XrdSfsFileOpenMode open_mode, // In - mode_t Mode, // In - const XrdSecEntity *client, // In - const char *info) // In -/* - Function: Open the file `path' in the mode indicated by `open_mode'. - - Input: path - The fully qualified name of the file to open. - The path must start with "/_bwm_" and the lfn that - will eventually be opened start at the next slash. - open_mode - One of the following flag values: - SFS_O_RDONLY - Open file for reading. - SFS_O_WRONLY - Open file for writing. n/a - SFS_O_RDWR - Open file for update n/a - SFS_O_CREAT - Create the file open in RW mode n/a - SFS_O_TRUNC - Trunc the file open in RW mode n/a - Mode - The Posix access mode bits to be assigned to the file. - These bits are ignored. - client - Authentication credentials, if any. - info - Opaque information: - bwm.src= - bwm.dst= - - Output: Returns SFS_OK upon success, otherwise SFS_ERROR is returned. -*/ -{ - EPNAME("open"); - XrdBwmHandle *hP; - int incomming; - const char *miss, *theUsr, *theSrc, *theDst=0, *theLfn=0, *lclNode, *rmtNode; - XrdOucEnv Open_Env(info); - -// Trace entry -// - ZTRACE(calls,std::hex <Access(client, path, AOP_Update, &Open_Env)) - return XrdBwmFS.Emsg("open", error, EACCES, "open", path); - -// Make sure that all of the relevant information is present -// - if (!(theSrc = Open_Env.Get("bwm.src"))) miss = "bwm.src"; - else if (!(theDst = Open_Env.Get("bwm.dst"))) miss = "bwm.dst"; - else if (!(theLfn = index(path+1,'/')) - || !(*(theLfn+1))) miss = "lfn"; - else miss = 0; - - if (miss) return XrdBwmFS.Emsg("open", error, miss, "open", path); - theUsr = error.getErrUser(); - -// Determine the direction of flow -// - if (XrdOucUtils::endsWith(theSrc,XrdBwmFS.myDomain,XrdBwmFS.myDomLen)) - {incomming = 0; lclNode = theSrc; rmtNode = theDst;} - else if (XrdOucUtils::endsWith(theDst,XrdBwmFS.myDomain,XrdBwmFS.myDomLen)) - {incomming = 1; lclNode = theDst; rmtNode = theSrc;} - else return XrdBwmFS.Emsg("open", error, EREMOTE, "open", path); - -// Get a handle for this file. -// - if (!(hP = XrdBwmHandle::Alloc(theUsr,theLfn,lclNode,rmtNode,incomming))) - return XrdBwmFS.Stall(error, 13, path); - -// All done -// - XrdBwmFS.ocMutex.Lock(); oh = hP; XrdBwmFS.ocMutex.UnLock(); - return SFS_OK; -} - -/******************************************************************************/ -/* c l o s e */ -/******************************************************************************/ - -int XrdBwmFile::close() // In -/* - Function: Close the file object. - - Input: n/a - - Output: Returns SFS_OK upon success and SFS_ERROR upon failure. -*/ -{ - EPNAME("close"); - XrdBwmHandle *hP; - -// Trace the call -// - FTRACE(calls, "close" <Name()); - -// Verify the handle (we briefly maintain a global lock) -// - XrdBwmFS.ocMutex.Lock(); - if (oh == XrdBwm::dummyHandle) - {XrdBwmFS.ocMutex.UnLock(); return SFS_OK;} - hP = oh; oh = XrdBwm::dummyHandle; - XrdBwmFS.ocMutex.UnLock(); - -// Now retire it and possibly return the token -// - hP->Retire(); - -// All done -// - return SFS_OK; -} - -/******************************************************************************/ -/* f c t l */ -/******************************************************************************/ - -int XrdBwmFile::fctl(const int cmd, - const char *args, - XrdOucErrInfo &out_error) -/* - Function: perform request control operation. - - Input: cmd - The operation: - SFS_FCTL_GETFD - not supported. - SFS_FCTL_STATV - returns visa information - args - Dependent on the cmd. - out_error - Place where response goes. - - Output: Returns SFS_OK upon success and SFS_ERROR o/w. -*/ -{ - -// Make sure the file is open -// - if (oh == XrdBwm::dummyHandle) - return XrdBwmFS.Emsg("fctl", out_error, EBADF, "fctl file"); - -// Scan through the fctl operations -// - switch(cmd) - {case SFS_FCTL_GETFD: out_error.setErrInfo(-1,""); - return SFS_OK; - case SFS_FCTL_STATV: return oh->Activate(out_error); - default: break; - } - -// Invalid fctl -// - out_error.setErrInfo(EINVAL, "invalid fctl command"); - return SFS_ERROR; -} - -/******************************************************************************/ -/* r e a d */ -/******************************************************************************/ - -int XrdBwmFile::read(XrdSfsFileOffset offset, // In - XrdSfsXferSize blen) // In -/* - Function: Preread `blen' bytes at `offset' - - Input: offset - The absolute byte offset at which to start the read. - blen - The amount to preread. - - Output: Returns SFS_OK upon success and SFS_ERROR o/w. -*/ -{ - EPNAME("read"); - -// Perform required tracing -// - FTRACE(calls,"preread " <Result = this->read((XrdSfsFileOffset)aiop->sfsAio.aio_offset, - (char *)aiop->sfsAio.aio_buf, - (XrdSfsXferSize)aiop->sfsAio.aio_nbytes); - aiop->doneRead(); - return 0; -} - -/******************************************************************************/ -/* w r i t e */ -/******************************************************************************/ - -XrdSfsXferSize XrdBwmFile::write(XrdSfsFileOffset offset, // In - const char *buff, // Out - XrdSfsXferSize blen) // In -/* - Function: Write `blen' bytes at `offset' from 'buff' and return the actual - number of bytes written. - - Input: offset - The absolute byte offset at which to start the write. - buff - Address of the buffer from which to get the data. - blen - The size of the buffer. This is the maximum number - of bytes that will be written to 'fd'. - - Output: Returns the number of bytes written upon success and SFS_ERROR o/w. - - Notes: 1. An error return may be delayed until the next write(), close(), or - sync() call. - 2. Currently, we do not accept write activated commands. -*/ -{ - EPNAME("write"); - -// Perform any required tracing -// - FTRACE(calls, blen <<"@" <Result = this->write((XrdSfsFileOffset)aiop->sfsAio.aio_offset, - (char *)aiop->sfsAio.aio_buf, - (XrdSfsXferSize)aiop->sfsAio.aio_nbytes); - aiop->doneWrite(); - return 0; -} - -/******************************************************************************/ -/* g e t M m a p */ -/******************************************************************************/ - -int XrdBwmFile::getMmap(void **Addr, off_t &Size) // Out -/* - Function: Return memory mapping for file, if any. - - Output: Addr - Address of memory location - Size - Size of the file or zero if not memory mapped. - Returns SFS_OK upon success and SFS_ERROR upon failure. -*/ -{ - -// Mapping is not supported -// - *Addr= 0; - Size = 0; - - return SFS_OK; -} - -/******************************************************************************/ -/* s t a t */ -/******************************************************************************/ - -int XrdBwmFile::stat(struct stat *buf) // Out -/* - Function: Return file status information - - Input: buf - The stat structiure to hold the results - - Output: Returns SFS_OK upon success and SFS_ERROR upon failure. -*/ -{ - EPNAME("fstat"); - static unsigned int myInode = 0; - union {long long Fill; - int Xor[2]; - XrdBwmFile *fP; - dev_t Num; - } theDev; - -// Perform any required tracing -// - FTRACE(calls, FName()); - -// Develop the device number -// - theDev.Fill = 0; theDev.fP = this; theDev.Xor[0] ^= theDev.Xor[1]; - -// Fill out the stat structure for this pseudo file -// - memset(buf, 0, sizeof(struct stat)); - buf->st_ino = myInode++; - buf->st_dev = theDev.Num; - buf->st_blksize = 4096; - buf->st_mode = S_IFBLK; - return SFS_OK; -} - -/******************************************************************************/ -/* s y n c */ -/******************************************************************************/ - -int XrdBwmFile::sync() // In -/* - Function: Commit all unwritten bytes to physical media. - - Input: n/a - - Output: Returns SFS_OK upon success and SFS_ERROR upon failure. -*/ -{ - EPNAME("sync"); - -// Perform any required tracing -// - FTRACE(calls,""); - -// We always succeed -// - return SFS_OK; -} - -/******************************************************************************/ -/* s y n c A I O */ -/******************************************************************************/ - -// For now, reverts to synchronous case -// -int XrdBwmFile::sync(XrdSfsAio *aiop) -{ - aiop->Result = this->sync(); - aiop->doneWrite(); - return 0; -} - -/******************************************************************************/ -/* t r u n c a t e */ -/******************************************************************************/ - -int XrdBwmFile::truncate(XrdSfsFileOffset flen) // In -/* - Function: Set the length of the file object to 'flen' bytes. - - Input: flen - The new size of the file. - - Output: Returns SFS_OK upon success and SFS_ERROR upon failure. - - Notes: 1. Truncate is not supported. -*/ -{ - EPNAME("trunc"); - -// Lock the file handle and perform any tracing -// - FTRACE(calls, "len=" <Name()); -} - -/******************************************************************************/ -/* g e t C X i n f o */ -/******************************************************************************/ - -int XrdBwmFile::getCXinfo(char cxtype[4], int &cxrsz) -/* - Function: Set the length of the file object to 'flen' bytes. - - Input: n/a - - Output: cxtype - Compression algorithm code - cxrsz - Compression region size - - Returns SFS_OK upon success and SFS_ERROR upon failure. -*/ -{ - -// Indicate not compressed -// - cxrsz = 0; - cxtype[0] = cxtype[1] = cxtype[2] = cxtype[3] = 0; - return SFS_OK; -} - -/******************************************************************************/ -/* */ -/* F i l e S y s t e m O b j e c t I n t e r f a c e s */ -/* */ -/******************************************************************************/ -/******************************************************************************/ -/* c h m o d */ -/******************************************************************************/ - -int XrdBwm::chmod(const char *path, // In - XrdSfsMode Mode, // In - XrdOucErrInfo &einfo, // Out - const XrdSecEntity *client, // In - const char *info) // In -/* - Function: Change the mode on a file or directory. - - Input: path - Is the fully qualified name of the file to be removed. - einfo - Error information object to hold error details. - client - Authentication credentials, if any. - info - Opaque information to be used as seen fit. - - Output: Returns SFS_OK upon success and SFS_ERROR upon failure. -*/ -{ -// Return an error -// - return XrdBwmFS.Emsg("chmod", einfo, ENOTSUP, "change", path); -} - -/******************************************************************************/ -/* e x i s t s */ -/******************************************************************************/ - -int XrdBwm::exists(const char *path, // In - XrdSfsFileExistence &file_exists, // Out - XrdOucErrInfo &einfo, // Out - const XrdSecEntity *client, // In - const char *info) // In -/* - Function: Determine if file 'path' actually exists. - - Input: path - Is the fully qualified name of the file to be tested. - file_exists - Is the address of the variable to hold the status of - 'path' when success is returned. The values may be: - XrdSfsFileExistsIsDirectory - file not found but path is valid. - XrdSfsFileExistsIsFile - file found. - XrdSfsFileExistsIsNo - neither file nor directory. - einfo - Error information object holding the details. - client - Authentication credentials, if any. - info - Opaque information to be used as seen fit. - - Output: Returns SFS_OK upon success and SFS_ERROR upon failure. - - Notes: When failure occurs, 'file_exists' is not modified. -*/ -{ - - file_exists=XrdSfsFileExistNo; - return SFS_OK; -} - -/******************************************************************************/ -/* f s c t l */ -/******************************************************************************/ - -int XrdBwm::fsctl(const int cmd, - const char *args, - XrdOucErrInfo &einfo, - const XrdSecEntity *client) -/* - Function: Perform filesystem operations: - - Input: cmd - Operation command (currently supported): - None. - arg - Command dependent argument: - - STATXV: The file handle - einfo - Error/Response information structure. - client - Authentication credentials, if any. - - Output: Returns SFS_OK upon success and SFS_ERROR upon failure. -*/ -{ -// Operation is not supported -// - return XrdBwmFS.Emsg("fsctl", einfo, ENOTSUP, "fsctl", args); -} - -/******************************************************************************/ -/* g e t V e r s i o n */ -/******************************************************************************/ - -const char *XrdBwm::getVersion() {return XrdVERSION;} - -/******************************************************************************/ -/* m k d i r */ -/******************************************************************************/ - -int XrdBwm::mkdir(const char *path, // In - XrdSfsMode Mode, // In - XrdOucErrInfo &einfo, // Out - const XrdSecEntity *client, // In - const char *info) // In -/* - Function: Create a directory entry. - - Input: path - Is the fully qualified name of the file to be removed. - Mode - Is the POSIX mode value the directory is to have. - Additionally, Mode may contain SFS_O_MKPTH if the - full dircectory path should be created. - einfo - Error information object to hold error details. - client - Authentication credentials, if any. - info - Opaque information to be used as seen fit. - - Output: Returns SFS_OK upon success and SFS_ERROR upon failure. -*/ -{ -// Return an error -// - return XrdBwmFS.Emsg("mkdir", einfo, ENOTSUP, "mkdir", path); -} - -/******************************************************************************/ -/* p r e p a r e */ -/******************************************************************************/ - -int XrdBwm::prepare( XrdSfsPrep &pargs, // In - XrdOucErrInfo &out_error, // Out - const XrdSecEntity *client) // In -{ - return 0; -} - -/******************************************************************************/ -/* r e m o v e */ -/******************************************************************************/ - -int XrdBwm::remove(const char type, // In - const char *path, // In - XrdOucErrInfo &einfo, // Out - const XrdSecEntity *client, // In - const char *info) // In -/* - Function: Delete a file from the namespace and release it's data storage. - - Input: type - 'f' for file and 'd' for directory. - path - Is the fully qualified name of the file to be removed. - einfo - Error information object to hold error details. - client - Authentication credentials, if any. - info - Opaque information to be used as seen fit. - - Output: Returns SFS_OK upon success and SFS_ERROR upon failure. -*/ -{ -// Return an error -// - return XrdBwmFS.Emsg("remove", einfo, ENOTSUP, "remove", path); -} - -/******************************************************************************/ -/* r e n a m e */ -/******************************************************************************/ - -int XrdBwm::rename(const char *old_name, // In - const char *new_name, // In - XrdOucErrInfo &einfo, //Out - const XrdSecEntity *client, // In - const char *infoO, // In - const char *infoN) // In -/* - Function: Renames a file with name 'old_name' to 'new_name'. - - Input: old_name - Is the fully qualified name of the file to be renamed. - new_name - Is the fully qualified name that the file is to have. - einfo - Error information structure, if an error occurs. - client - Authentication credentials, if any. - infoO - old_name opaque information to be used as seen fit. - infoN - new_name opaque information to be used as seen fit. - - Output: Returns SFS_OK upon success and SFS_ERROR upon failure. -*/ -{ -// Return an error -// - return XrdBwmFS.Emsg("rename", einfo, ENOTSUP, "rename", old_name); -} - -/******************************************************************************/ -/* s t a t */ -/******************************************************************************/ - -int XrdBwm::stat(const char *path, // In - struct stat *buf, // Out - XrdOucErrInfo &einfo, // Out - const XrdSecEntity *client, // In - const char *info) // In -/* - Function: Return file status information - - Input: path - The path for which status is wanted - buf - The stat structure to hold the results - einfo - Error information structure, if an error occurs. - client - Authentication credentials, if any. - info - opaque information to be used as seen fit. - - Output: Returns SFS_OK upon success and SFS_ERROR upon failure. -*/ -{ -// Return an error -// - return XrdBwmFS.Emsg("stat", einfo, ENOTSUP, "locate", path); -} - -/******************************************************************************/ - -int XrdBwm::stat(const char *path, // In - mode_t &mode, // Out - XrdOucErrInfo &einfo, // Out - const XrdSecEntity *client, // In - const char *info) // In -/* - Function: Return file status information (resident files only) - - Input: path - The path for which status is wanted - mode - The stat mode entry (faked -- do not trust it) - einfo - Error information structure, if an error occurs. - client - Authentication credentials, if any. - info - opaque information to be used as seen fit. - - Output: Always returns SFS_ERROR if a delay needs to be imposed. Otherwise, - SFS_OK is returned and mode is appropriately, if inaccurately, set. - If file residency cannot be determined, mode is set to -1. -*/ -{ -// Return an error -// - return XrdBwmFS.Emsg("stat", einfo, ENOTSUP, "locate", path); -} - -/******************************************************************************/ -/* t r u n c a t e */ -/******************************************************************************/ - -int XrdBwm::truncate(const char *path, // In - XrdSfsFileOffset Size, // In - XrdOucErrInfo &einfo, // Out - const XrdSecEntity *client, // In - const char *info) // In -/* - Function: Change the mode on a file or directory. - - Input: path - Is the fully qualified name of the file to be removed. - Size - the size the file should have. - einfo - Error information object to hold error details. - client - Authentication credentials, if any. - info - Opaque information to be used as seen fit. - - Output: Returns SFS_OK upon success and SFS_ERROR upon failure. -*/ -{ -// Return an error -// - return XrdBwmFS.Emsg("truncate", einfo, ENOTSUP, "truncate", path); -} - -/******************************************************************************/ -/* E m s g */ -/******************************************************************************/ - -int XrdBwm::Emsg(const char *pfx, // Message prefix value - XrdOucErrInfo &einfo, // Place to put text & error code - int ecode, // The error code - const char *op, // Operation being performed - const char *target) // The target (e.g., fname) -{ - char *etext, buffer[MAXPATHLEN+80], unkbuff[64]; - -// Get the reason for the error -// - if (ecode < 0) ecode = -ecode; - if (!(etext = BwmEroute.ec2text(ecode))) - {sprintf(unkbuff, "reason unknown (%d)", ecode); etext = unkbuff;} - -// Format the error message -// - snprintf(buffer,sizeof(buffer),"Unable to %s %s; %s", op, target, etext); - -// Print it out if debugging is enabled -// -#ifndef NODEBUG - BwmEroute.Emsg(pfx, einfo.getErrUser(), buffer); -#endif - -// Place the error message in the error object and return -// - einfo.setErrInfo(ecode, buffer); - return SFS_ERROR; -} - -/******************************************************************************/ - -int XrdBwm::Emsg(const char *pfx, // Message prefix value - XrdOucErrInfo &einfo, // Place to put text & error code - const char *item, // What is missing - const char *op, // Operation being performed - const char *target) // The target (e.g., fname) -{ - char buffer[MAXPATHLEN+80]; - -// Format the error message -// - snprintf(buffer,sizeof(buffer),"Unable to %s %s; %s missing", - op, target, item); - -// Print it out if debugging is enabled -// -#ifndef NODEBUG - BwmEroute.Emsg(pfx, einfo.getErrUser(), buffer); -#endif - -// Place the error message in the error object and return -// - einfo.setErrInfo(EINVAL, buffer); - return SFS_ERROR; -} - -/******************************************************************************/ -/* S t a l l */ -/******************************************************************************/ - -int XrdBwm::Stall(XrdOucErrInfo &einfo, // Error text & code - int stime, // Seconds to stall - const char *path) // The path to stall on -{ - EPNAME("Stall") -#ifndef NODEBUG - const char *tident = einfo.getErrUser(); -#endif - -// Trace the stall -// - ZTRACE(delay, "Stall " <. */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include - -#include "XrdBwm/XrdBwmHandle.hh" -#include "XrdSys/XrdSysPthread.hh" -#include "XrdSfs/XrdSfsInterface.hh" - -class XrdOucEnv; -class XrdSysError; -class XrdSysLogger; -class XrdOucStream; -class XrdSfsAio; - -struct XrdVersionInfo; - -/******************************************************************************/ -/* X r d B w m D i r e c t o r y */ -/******************************************************************************/ - -class XrdBwmDirectory : public XrdSfsDirectory -{ -public: - - int open(const char *dirName, - const XrdSecEntity *client, - const char *opaque = 0); - - const char *nextEntry(); - - int close(); - -inline void copyError(XrdOucErrInfo &einfo) {einfo = error;} - -const char *FName() {return "";} - - XrdBwmDirectory(const char *user, int monid) - : XrdSfsDirectory(user, monid), - tident(user ? user : "") {} - -virtual ~XrdBwmDirectory() {} - -protected: -const char *tident; -}; - -/******************************************************************************/ -/* X r d B w m F i l e */ -/******************************************************************************/ - -class XrdBwmFile : public XrdSfsFile -{ -public: - - int open(const char *fileName, - XrdSfsFileOpenMode openMode, - mode_t createMode, - const XrdSecEntity *client, - const char *opaque = 0); - - int close(); - - using XrdSfsFile::fctl; - - int fctl(const int cmd, - const char *args, - XrdOucErrInfo &out_error); - - const char *FName() {return (oh ? oh->Name() : "?");} - - int getMmap(void **Addr, off_t &Size); - - int read(XrdSfsFileOffset fileOffset, // Preread only - XrdSfsXferSize amount); - - XrdSfsXferSize read(XrdSfsFileOffset fileOffset, - char *buffer, - XrdSfsXferSize buffer_size); - - int read(XrdSfsAio *aioparm); - - XrdSfsXferSize write(XrdSfsFileOffset fileOffset, - const char *buffer, - XrdSfsXferSize buffer_size); - - int write(XrdSfsAio *aioparm); - - int sync(); - - int sync(XrdSfsAio *aiop); - - int stat(struct stat *buf); - - int truncate(XrdSfsFileOffset fileOffset); - - int getCXinfo(char cxtype[4], int &cxrsz); - - XrdBwmFile(const char *user, int monid); - -virtual ~XrdBwmFile() {if (oh) close();} - -protected: - const char *tident; - -private: - -XrdBwmHandle *oh; -}; - -/******************************************************************************/ -/* C l a s s X r d B w m */ -/******************************************************************************/ - -class XrdAccAuthorize; -class XrdBwmLogger; -class XrdBwmPolicy; - -class XrdBwm : public XrdSfsFileSystem -{ -friend class XrdBwmDirectory; -friend class XrdBwmFile; - -public: - -// Object allocation -// - XrdSfsDirectory *newDir(char *user=0, int monid=0) - {return (XrdSfsDirectory *)new XrdBwmDirectory(user,monid);} - - XrdSfsFile *newFile(char *user=0, int monid=0) - {return (XrdSfsFile *)new XrdBwmFile(user,monid);} - -// Other functions -// - int chmod(const char *Name, - XrdSfsMode Mode, - XrdOucErrInfo &out_error, - const XrdSecEntity *client, - const char *opaque = 0); - - int exists(const char *fileName, - XrdSfsFileExistence &exists_flag, - XrdOucErrInfo &out_error, - const XrdSecEntity *client, - const char *opaque = 0); - - int fsctl(const int cmd, - const char *args, - XrdOucErrInfo &out_error, - const XrdSecEntity *client); - - int getStats(char *buff, int blen) {return 0;} - -const char *getVersion(); - - int mkdir(const char *dirName, - XrdSfsMode Mode, - XrdOucErrInfo &out_error, - const XrdSecEntity *client, - const char *opaque = 0); - - int prepare( XrdSfsPrep &pargs, - XrdOucErrInfo &out_error, - const XrdSecEntity *client = 0); - - int rem(const char *path, - XrdOucErrInfo &out_error, - const XrdSecEntity *client, - const char *info = 0) - {return remove('f', path, out_error, client, info);} - - int remdir(const char *dirName, - XrdOucErrInfo &out_error, - const XrdSecEntity *client, - const char *info = 0) - {return remove('d',dirName,out_error,client,info);} - - int rename(const char *oldFileName, - const char *newFileName, - XrdOucErrInfo &out_error, - const XrdSecEntity *client, - const char *infoO = 0, - const char *infoN = 0); - - int stat(const char *Name, - struct stat *buf, - XrdOucErrInfo &out_error, - const XrdSecEntity *client, - const char *opaque = 0); - - int stat(const char *Name, - mode_t &mode, - XrdOucErrInfo &out_error, - const XrdSecEntity *client, - const char *opaque = 0); - - int truncate(const char *Name, - XrdSfsFileOffset fileOffset, - XrdOucErrInfo &out_error, - const XrdSecEntity *client = 0, - const char *opaque = 0); -// Management functions -// -virtual int Configure(XrdSysError &); - - XrdBwm(); -virtual ~XrdBwm() {} // Too complicate to delete :-) - -/******************************************************************************/ -/* C o n f i g u r a t i o n V a l u e s */ -/******************************************************************************/ - -XrdVersionInfo *myVersion; // ->Version compiled with - -char *ConfigFN; // ->Configuration filename -char *HostName; // ->Our hostname -char *HostPref; // ->Our hostname with domain removed -char *myDomain; // ->Our domain name -int myDomLen; // -char Authorize; -char Reserved[7]; - -/******************************************************************************/ -/* P r o t e c t e d I t e m s */ -/******************************************************************************/ - -protected: - -virtual int ConfigXeq(char *var, XrdOucStream &, XrdSysError &); - int Emsg(const char *, XrdOucErrInfo &, int, - const char *, const char *y=""); - int Emsg(const char *, XrdOucErrInfo &, const char *, - const char *, const char *y=""); - int Stall(XrdOucErrInfo &, int, const char *); - -/******************************************************************************/ -/* P r i v a t e C o n f i g u r a t i o n */ -/******************************************************************************/ - -private: - -XrdAccAuthorize *Authorization; // ->Authorization Service -char *AuthLib; // ->Authorization Library -char *AuthParm; // ->Authorization Parameters -XrdBwmLogger *Logger; // ->Logger -XrdBwmPolicy *Policy; // ->Policy -char *PolLib; -char *PolParm; -char *locResp; // ->Locate Response -int locRlen; // Length of locResp; -int PolSlotsIn; -int PolSlotsOut; - -static XrdBwmHandle *dummyHandle; -XrdSysMutex ocMutex; // Global mutex for open/close - -/******************************************************************************/ -/* O t h e r D a t a */ -/******************************************************************************/ - -int remove(const char type, const char *path, - XrdOucErrInfo &out_error, const XrdSecEntity *client, - const char *opaque); -// Function used during Configuration -// -int setupAuth(XrdSysError &); -int setupPolicy(XrdSysError &); -int xalib(XrdOucStream &, XrdSysError &); -int xlog(XrdOucStream &, XrdSysError &); -int xpol(XrdOucStream &, XrdSysError &); -int xtrace(XrdOucStream &, XrdSysError &); -}; -#endif diff --git a/src/XrdBwm/XrdBwmConfig.cc b/src/XrdBwm/XrdBwmConfig.cc deleted file mode 100644 index 15610522a56..00000000000 --- a/src/XrdBwm/XrdBwmConfig.cc +++ /dev/null @@ -1,428 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d B w m C o n f i g . c c */ -/* */ -/* (C) 2010 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Deprtment of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "XrdBwm/XrdBwm.hh" -#include "XrdBwm/XrdBwmLogger.hh" -#include "XrdBwm/XrdBwmPolicy.hh" -#include "XrdBwm/XrdBwmPolicy1.hh" -#include "XrdBwm/XrdBwmTrace.hh" - -#include "XrdOuc/XrdOuca2x.hh" -#include "XrdOuc/XrdOucEnv.hh" -#include "XrdOuc/XrdOucPinLoader.hh" -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdOuc/XrdOucStream.hh" -#include "XrdOuc/XrdOucTrace.hh" - -#include "XrdAcc/XrdAccAuthorize.hh" - -/******************************************************************************/ -/* d e f i n e s */ -/******************************************************************************/ - -#define TS_Xeq(x,m) if (!strcmp(x,var)) return m(Config,Eroute); - -#define TS_Str(x,m) if (!strcmp(x,var)) {free(m); m = strdup(val); return 0;} - -#define TS_PList(x,m) if (!strcmp(x,var)) \ - {m.Insert(new XrdOucPList(val,1)); return 0;} - -#define TS_Chr(x,m) if (!strcmp(x,var)) {m = val[0]; return 0;} - -#define TS_Bit(x,m,v) if (!strcmp(x,var)) {m |= v; Config.Echo(); return 0;} - -#define Max(x,y) (x > y ? x : y) - -/******************************************************************************/ -/* C o n f i g u r e */ -/******************************************************************************/ - -int XrdBwm::Configure(XrdSysError &Eroute) { -/* - Function: Establish default values using a configuration file. - - Input: None. - - Output: 0 upon success or !0 otherwise. -*/ - char *var; - int cfgFD, retc, NoGo = 0; - XrdOucEnv myEnv; - XrdOucStream Config(&Eroute, getenv("XRDINSTANCE"), &myEnv, "=====> "); - -// Print warm-up message -// - Eroute.Say("++++++ Bwm initialization started."); - -// Get the debug level from the command line -// - if (getenv("XRDDEBUG")) BwmTrace.What = TRACE_ALL; - -// If there is no config file, return with the defaults sets. -// - if( !ConfigFN || !*ConfigFN) - Eroute.Emsg("Config", "Configuration file not specified."); - else { - // Try to open the configuration file. - // - if ( (cfgFD = open(ConfigFN, O_RDONLY, 0)) < 0) - return Eroute.Emsg("Config", errno, "open config file", - ConfigFN); - Config.Attach(cfgFD); - - // Now start reading records until eof. - // - while((var = Config.GetMyFirstWord())) - {if (!strncmp(var, "bwm.", 4)) - if (ConfigXeq(var+4,Config,Eroute)) {Config.Echo();NoGo=1;} - } - - // Now check if any errors occured during file i/o - // - if ((retc = Config.LastError())) - NoGo = Eroute.Emsg("Config", -retc, "read config file", - ConfigFN); - Config.Close(); - } - -// Determine whether we should initialize authorization -// - if (Authorize) NoGo |= setupAuth(Eroute); - -// Establish scheduling policy -// - if (PolLib) NoGo |= setupPolicy(Eroute); - else Policy = new XrdBwmPolicy1(PolSlotsIn, PolSlotsOut); - -// Start logger object -// - if (!NoGo && Logger) NoGo = Logger->Start(&Eroute); - -// Inform the handle of the policy and logger -// - if (!NoGo) XrdBwmHandle::setPolicy(Policy, Logger); - -// All done -// - Eroute.Say("------ Bwm initialization ", (NoGo ? "failed." : "completed.")); - return NoGo; -} - -/******************************************************************************/ -/* p r i v a t e f u n c t i o n s */ -/******************************************************************************/ -/******************************************************************************/ -/* C o n f i g X e q */ -/******************************************************************************/ - -int XrdBwm::ConfigXeq(char *var, XrdOucStream &Config, - XrdSysError &Eroute) -{ - TS_Bit("authorize", Authorize, 1); - TS_Xeq("authlib", xalib); - TS_Xeq("log", xlog); - TS_Xeq("policy", xpol); - TS_Xeq("trace", xtrace); - - // No match found, complain. - // - Eroute.Say("Config warning: ignoring unknown directive '",var,"'."); - Config.Echo(); - return 0; -} - -/******************************************************************************/ -/* x a l i b */ -/******************************************************************************/ - -/* Function: xalib - - Purpose: To parse the directive: authlib [] - - the path of the authorization library to be used. - optional parms to be passed - - Output: 0 upon success or !0 upon failure. -*/ - -int XrdBwm::xalib(XrdOucStream &Config, XrdSysError &Eroute) -{ - char *val, parms[1024]; - -// Get the path -// - if (!(val = Config.GetWord()) || !val[0]) - {Eroute.Emsg("Config", "authlib not specified"); return 1;} - -// Record the path -// - if (AuthLib) free(AuthLib); - AuthLib = strdup(val); - -// Record any parms -// - if (!Config.GetRest(parms, sizeof(parms))) - {Eroute.Emsg("Config", "authlib parameters too long"); return 1;} - if (AuthParm) free(AuthParm); - AuthParm = (*parms ? strdup(parms) : 0); - return 0; -} - -/******************************************************************************/ -/* x l o g */ -/******************************************************************************/ - -/* Function: xlog - - Purpose: Parse directive: log {* | <|prog> | <>path>} - - - is the program to execute and dynamically feed messages - about the indicated events. Messages are piped to prog. - - is the udp named socket to receive the message. The - server creates the path if it's not present. If - is an asterisk, then messages are written to standard - log file. - - Output: 0 upon success or !0 upon failure. -*/ -int XrdBwm::xlog(XrdOucStream &Config, XrdSysError &Eroute) -{ - char *val, parms[1024]; - - if (!(val = Config.GetWord())) - {Eroute.Emsg("Config", "log parameters not specified"); return 1;} - -// Get the remaining parameters -// - Config.RetToken(); - if (!Config.GetRest(parms, sizeof(parms))) - {Eroute.Emsg("Config", "log parameters too long"); return 1;} - val = (*parms == '|' ? parms+1 : parms); - -// Create a log object -// - if (Logger) delete Logger; - Logger = new XrdBwmLogger(val); - -// All done -// - return 0; -} - -/******************************************************************************/ -/* x p o l */ -/******************************************************************************/ - -/* Function: xpol - - Purpose: To parse the directive: policy args - - Args: {maxslots | lib []} - - maximum number of slots available. - if preceeded by lib, the path of the policy library to - be used; otherwise, the file that describes policy. - optional parms to be passed - - Output: 0 upon success or !0 upon failure. -*/ - -int XrdBwm::xpol(XrdOucStream &Config, XrdSysError &Eroute) -{ - char *val, parms[2048]; - int pl; - -// Get next token -// - if (!(val = Config.GetWord()) || !val[0]) - {Eroute.Emsg("Config", "policy not specified"); return 1;} - -// Start afresh -// - if (PolLib) {free(PolLib); PolLib = 0;} - if (PolParm) {free(PolParm); PolParm = 0;} - PolSlotsIn = PolSlotsOut = 0; - -// If the word maxslots then this is a simple policy -// - if (!strcmp("maxslots", val)) - {if (!(val = Config.GetWord()) || !val[0]) - {Eroute.Emsg("Config", "policy in slots not specified"); return 1;} - if (XrdOuca2x::a2i(Eroute,"policy in slots",val,&pl,0,32767)) return 1; - PolSlotsIn = pl; - if (!(val = Config.GetWord()) || !val[0]) - {Eroute.Emsg("Config", "policy out slots not specified"); return 1;} - if (XrdOuca2x::a2i(Eroute,"policy out slots",val,&pl,0,32767)) return 1; - PolSlotsOut = pl; - return 0; - } - -// Make sure the word is lib -// - if (strcmp("lib", val)) - {Eroute.Emsg("Config", "invalid policy keyword -", val); return 1;} - if (!(val = Config.GetWord()) || !val[0]) - {Eroute.Emsg("Config", "policy library not specified"); return 1;} - -// Set the library -// - PolLib = strdup(val); - -// Get any parameters -// - if (!Config.GetRest(parms, sizeof(parms))) - {Eroute.Emsg("Config", "policy lib parameters too long"); return 1;} - PolParm = (*parms ? strdup(parms) : 0); - -// All done -// - return 0; -} - -/******************************************************************************/ -/* x t r a c e */ -/******************************************************************************/ - -/* Function: xtrace - - Purpose: To parse the directive: trace - - the blank separated list of events to trace. Trace - directives are cummalative. - - Output: 0 upon success or !0 upon failure. -*/ - -int XrdBwm::xtrace(XrdOucStream &Config, XrdSysError &Eroute) -{ - static struct traceopts {const char *opname; int opval;} tropts[] = - { - {"all", TRACE_ALL}, - {"calls", TRACE_calls}, - {"debug", TRACE_debug}, - {"delay", TRACE_delay}, - {"sched", TRACE_sched}, - {"tokens", TRACE_tokens} - }; - int i, neg, trval = 0, numopts = sizeof(tropts)/sizeof(struct traceopts); - char *val; - - if (!(val = Config.GetWord())) - {Eroute.Emsg("Config", "trace option not specified"); return 1;} - while (val) - {if (!strcmp(val, "off")) trval = 0; - else {if ((neg = (val[0] == '-' && val[1]))) val++; - for (i = 0; i < numopts; i++) - {if (!strcmp(val, tropts[i].opname)) - {if (neg) trval &= ~tropts[i].opval; - else trval |= tropts[i].opval; - break; - } - } - if (i >= numopts) - Eroute.Say("Config warning: ignoring invalid trace option '",val,"'."); - } - val = Config.GetWord(); - } - BwmTrace.What = trval; - -// All done -// - return 0; -} - -/******************************************************************************/ -/* s e t u p A u t h */ -/******************************************************************************/ - -int XrdBwm::setupAuth(XrdSysError &Eroute) -{ - extern XrdAccAuthorize *XrdAccDefaultAuthorizeObject(XrdSysLogger *lp, - const char *cfn, - const char *parm, - XrdVersionInfo &); - XrdOucPinLoader *myLib; - XrdAccAuthorize *(*ep)(XrdSysLogger *, const char *, const char *); - -// Authorization comes from the library or we use the default -// - if (!AuthLib) return 0 == (Authorization = XrdAccDefaultAuthorizeObject - (Eroute.logger(),ConfigFN,AuthParm,*myVersion)); - -// Create a pluin object (we will throw this away without deletion because -// the library must stay open but we never want to reference it again). -// - if (!(myLib = new XrdOucPinLoader(&Eroute, myVersion, "authlib", AuthLib))) - return 1; - -// Now get the entry point of the object creator -// - ep = (XrdAccAuthorize *(*)(XrdSysLogger *, const char *, const char *)) - (myLib->Resolve("XrdAccAuthorizeObject")); - if (!ep) return 1; - -// Get the Object now -// - if (!(Authorization = ep(Eroute.logger(), ConfigFN, AuthParm))) - myLib->Unload(); - delete myLib; - return (Authorization == 0); -} - -/******************************************************************************/ -/* s e t u p P o l i c y */ -/******************************************************************************/ - -int XrdBwm::setupPolicy(XrdSysError &Eroute) -{ - XrdOucPinLoader myLib(&Eroute, myVersion, "policylib", PolLib); - XrdBwmPolicy *(*ep)(XrdSysLogger *, const char *, const char *); - -// Now get the entry point of the object creator -// - ep = (XrdBwmPolicy *(*)(XrdSysLogger *, const char *, const char *)) - (myLib.Resolve("XrdBwmPolicyObject")); - if (!ep) {myLib.Unload(); return 1;} - -// Get the Object now -// - if (!(Policy = ep(Eroute.logger(), ConfigFN, PolParm))) myLib.Unload(); - return (Policy == 0); -} diff --git a/src/XrdBwm/XrdBwmHandle.cc b/src/XrdBwm/XrdBwmHandle.cc deleted file mode 100644 index 99b185caba1..00000000000 --- a/src/XrdBwm/XrdBwmHandle.cc +++ /dev/null @@ -1,395 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d B w m H a n d l e . c c */ -/* */ -/* (c) 2008 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -#include "XrdBwm/XrdBwmHandle.hh" -#include "XrdBwm/XrdBwmLogger.hh" -#include "XrdBwm/XrdBwmTrace.hh" -#include "XrdSfs/XrdSfsInterface.hh" -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysPlatform.hh" - -#include "XProtocol/XProtocol.hh" - -/******************************************************************************/ -/* S t a t i c O b j e c t s */ -/******************************************************************************/ - -XrdBwmLogger *XrdBwmHandle::Logger = 0; -XrdBwmPolicy *XrdBwmHandle::Policy = 0; -XrdBwmHandle *XrdBwmHandle::Free = 0; -unsigned int XrdBwmHandle::numQueued = 0; - -extern XrdSysError BwmEroute; - -/******************************************************************************/ -/* L o c a l C l a s s e s */ -/******************************************************************************/ - -class XrdBwmHandleCB : public XrdOucEICB, public XrdOucErrInfo -{ -public: - -static -XrdBwmHandleCB *Alloc() - {XrdBwmHandleCB *mP; - xMutex.Lock(); - if (!(mP = Free)) mP = new XrdBwmHandleCB; - else Free = mP->Next; - xMutex.UnLock(); - return mP; - } - -void Done(int &Results, XrdOucErrInfo *eInfo, const char *Path=0) - {xMutex.Lock(); - Next = Free; - Free = this; - xMutex.UnLock(); - } - -int Same(unsigned long long arg1, unsigned long long arg2) {return 0;} - - XrdBwmHandleCB() : Next(0) {} - ~XrdBwmHandleCB() {} - -private: - XrdBwmHandleCB *Next; -static XrdSysMutex xMutex; -static XrdBwmHandleCB *Free; -}; - -XrdSysMutex XrdBwmHandleCB::xMutex; -XrdBwmHandleCB *XrdBwmHandleCB::Free = 0; - -/******************************************************************************/ -/* E x t e r n a l L i n k a g e s */ -/******************************************************************************/ - -void *XrdBwmHanXeq(void *pp) -{ - return XrdBwmHandle::Dispatch(); -} - -/******************************************************************************/ -/* c l a s s X r d B w m H a n d l e */ -/******************************************************************************/ -/******************************************************************************/ -/* A c t i v a t e */ -/******************************************************************************/ - -#define tident Parms.Tident - -int XrdBwmHandle::Activate(XrdOucErrInfo &einfo) -{ - EPNAME("Activate"); - XrdSysMutexHelper myHelper(hMutex); - char *rBuff; - int rSize, rc; - -// Check the status of this request. -// - if (Status != Idle) - {if (Status == Scheduled) - einfo.setErrInfo(kXR_inProgress, "Request already scheduled."); - else einfo.setErrInfo(kXR_InvalidRequest, "Visa already issued."); - return SFS_ERROR; - } - -// Try to schedule this request. -// - qTime = time(0); - rBuff = einfo.getMsgBuff(rSize); - if (!(rc = Policy->Schedule(rBuff, rSize, Parms))) return SFS_ERROR; - -// If resource immediately available, let client run -// - if (rc > 0) - {rHandle = rc; - Status = Dispatched; - rTime = time(0); - ZTRACE(sched,"Run " < ") - < ") - <Parms.Tident = theUsr; // Always available - hP->Parms.Lfn = strdup(thePath); - hP->Parms.LclNode = strdup(LclNode); - hP->Parms.RmtNode = strdup(RmtNode); - hP->Parms.Direction = (Incomming ? XrdBwmPolicy::Incomming - : XrdBwmPolicy::Outgoing); - hP->Status = Idle; - hP->qTime = 0; - hP->rTime = 0; - hP->xSize = 0; - hP->xTime = 0; - } - -// All done -// - return hP; -} - -/******************************************************************************/ -/* private A l l o c # 2 */ -/******************************************************************************/ - -XrdBwmHandle *XrdBwmHandle::Alloc(XrdBwmHandle *old_hP) -{ - static const int minAlloc = 4096/sizeof(XrdBwmHandle); - static XrdSysMutex aMutex; - XrdBwmHandle *hP; - -// No handle currently in the table. Get a new one off the free list or -// return one to the free list. -// - aMutex.Lock(); - if (old_hP) {old_hP->Next = Free; Free = old_hP; hP = 0;} - else {if (!Free && (hP = new XrdBwmHandle[minAlloc])) - {int i = minAlloc; while(i--) {hP->Next = Free; Free = hP; hP++;}} - if ((hP = Free)) Free = hP->Next; - } - aMutex.UnLock(); - - return hP; -} - -/******************************************************************************/ -/* D i s p a t c h */ -/******************************************************************************/ - -#define tident hP->Parms.Tident - -void *XrdBwmHandle::Dispatch() -{ - EPNAME("Dispatch"); - XrdBwmHandleCB *erP = XrdBwmHandleCB::Alloc(); - XrdBwmHandle *hP; - char *RespBuff; - int RespSize, readyH, Result, Err; - -// Dispatch ready requests in an endless loop -// - do { - -// Setup buffer -// - RespBuff = erP->getMsgBuff(RespSize); - *RespBuff = '\0'; - erP->setErrCode(0); - -// Get next ready request and test if it ended with an error -// - if ((Err = (readyH = Policy->Dispatch(RespBuff, RespSize)) < 0)) - readyH = -readyH; - -// Find the matching handle -// - if (!(hP = refHandle(readyH))) - {sprintf(RespBuff, "%d", readyH); - BwmEroute.Emsg("Dispatch", "Lost handle from", RespBuff); - if (!Err) Policy->Done(readyH); - continue; - } - -// Lock the handle and make sure it can be dispatched -// - hP->hMutex.Lock(); - if (hP->Status != Scheduled) - {BwmEroute.Emsg("Dispatch", "ref to unscheduled handle", - hP->Parms.Tident, hP->Parms.Lfn); - if (!Err) Policy->Done(readyH); - } else { - hP->myEICB.Wait(); hP->rTime = time(0); - erP->setErrCB((XrdOucEICB *)erP, hP->ErrCBarg); - if (Err) {hP->Status = Idle; Result = SFS_ERROR;} - else {hP->Status = Dispatched; - erP->setErrCode(strlen(RespBuff)); - Result = (*RespBuff ? SFS_DATA : SFS_OK); - } - ZTRACE(sched,(Err?"Err ":"Run ") <Parms.Lfn <<' ' <Parms.LclNode - <<(hP->Parms.Direction == XrdBwmPolicy::Incomming ? " <- ":" -> ") - <Parms.RmtNode); - hP->ErrCB->Done(Result, (XrdOucErrInfo *)erP); - erP = XrdBwmHandleCB::Alloc(); - } - hP->hMutex.UnLock(); - } while(1); - -// Keep the compiler happy -// - return (void *)0; -} - -#undef tident - -/******************************************************************************/ -/* private r e f H a n d l e */ -/******************************************************************************/ - -XrdBwmHandle *XrdBwmHandle::refHandle(int refID, XrdBwmHandle *hP) -{ - static XrdSysMutex tMutex; - static struct {XrdBwmHandle *First; - XrdBwmHandle *Last; - } hTab[256] = {{0,0}}; - XrdBwmHandle *pP = 0; - int i = refID % 256; - -// If we have a handle passed, add the handle to the table -// - tMutex.Lock(); - if (hP) - {hP->Next = 0; - if (hTab[i].Last) {hTab[i].Last->Next = hP; hTab[i].Last = hP;} - else {hTab[i].First = hTab[i].Last = hP; hP->Next = 0;} - numQueued++; - } else { - hP = hTab[i].First; - while(hP && hP->rHandle != refID) {pP = hP; hP = hP->Next;} - if (hP) - {if (pP) pP->Next = hP->Next; - else hTab[i].First = hP->Next; - if (hTab[i].Last == hP) hTab[i].Last = pP; - numQueued--; - } - } - tMutex.UnLock(); - -// All done. -// - return hP; -} - -/******************************************************************************/ -/* public R e t i r e */ -/******************************************************************************/ - -// The handle must be locked upon entry! It is unlocked upon exit. - -void XrdBwmHandle::Retire() -{ - XrdSysMutexHelper myHelper(hMutex); - -// Get the global lock as the links field can only be manipulated with it. -// If not idle, cancel the resource. If scheduled, remove it from the table. -// - if (Status != Idle) - {Policy->Done(rHandle); - if (Status == Scheduled && !refHandle(rHandle, this)) - BwmEroute.Emsg("Retire", "Lost handle to", Parms.Tident, Parms.Lfn); - Status = Idle; rHandle = 0; - } - -// If we have a logger, then log this event -// - if (Logger && qTime) - {XrdBwmLogger::Info myInfo; - myInfo.Tident = Parms.Tident; - myInfo.Lfn = Parms.Lfn; - myInfo.lclNode = Parms.LclNode; - myInfo.rmtNode = Parms.RmtNode; - myInfo.ATime = qTime; - myInfo.BTime = rTime; - myInfo.CTime = time(0); - myInfo.Size = xSize; - myInfo.ESec = xTime; - myInfo.Flow = (Parms.Direction == XrdBwmPolicy::Incomming ? 'I':'O'); - Policy->Status(myInfo.numqIn, myInfo.numqOut, myInfo.numqXeq); - Logger->Event(myInfo); - } - -// Free storage appendages and recycle handle -// - if (Parms.Lfn) {free(Parms.Lfn); Parms.Lfn = 0;} - if (Parms.LclNode) {free(Parms.LclNode); Parms.LclNode = 0;} - if (Parms.RmtNode) {free(Parms.RmtNode); Parms.RmtNode = 0;} - Alloc(this); -} - -/******************************************************************************/ -/* s e t P o l i c y */ -/******************************************************************************/ - -int XrdBwmHandle::setPolicy(XrdBwmPolicy *pP, XrdBwmLogger *lP) -{ - pthread_t tid; - int rc, startThread = (Policy == 0); - -// Set the policy and then start a thread to do dispatching if we have none -// - Policy = pP; - if (startThread) - if ((rc = XrdSysThread::Run(&tid, XrdBwmHanXeq, (void *)0, - 0, "Handle Dispatcher"))) - {BwmEroute.Emsg("setPolicy", rc, "create handle dispatch thread"); - return 1; - } - -// All done -// - Logger = lP; - return 0; -} diff --git a/src/XrdBwm/XrdBwmHandle.hh b/src/XrdBwm/XrdBwmHandle.hh deleted file mode 100644 index 6dc8074c02e..00000000000 --- a/src/XrdBwm/XrdBwmHandle.hh +++ /dev/null @@ -1,109 +0,0 @@ -#ifndef __BWM_HANDLE__ -#define __BWM_HANDLE__ -/******************************************************************************/ -/* */ -/* X r d B w m H a n d l e . h h */ -/* */ -/* (c) 2008 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -#include "XrdBwm/XrdBwmPolicy.hh" -#include "XrdOuc/XrdOucErrInfo.hh" -#include "XrdSys/XrdSysPthread.hh" - -class XrdBwmLogger; - -class XrdBwmHandle -{ -public: - -enum HandleState {Idle = 0, Scheduled, Dispatched}; - - HandleState Status; - - int Activate(XrdOucErrInfo &einfo); - -static XrdBwmHandle *Alloc(const char *theUsr, const char *thePath, - const char *lclNode, const char *rmtNode, - int Incomming); - -static void *Dispatch(); - -inline const char *Name() {return Parms.Lfn;} - - void Retire(); - -static int setPolicy(XrdBwmPolicy *pP, XrdBwmLogger *lP); - - XrdBwmHandle() : Status(Idle), Next(0), qTime(0), rTime(0), - xSize(0), xTime(0) - {} - - ~XrdBwmHandle() {} - -private: -static XrdBwmHandle *Alloc(XrdBwmHandle *oldHandle=0); -static XrdBwmHandle *refHandle(int refID, XrdBwmHandle *hP=0); - -static XrdBwmPolicy *Policy; -static XrdBwmLogger *Logger; -static XrdBwmHandle *Free; // List of free handles -static unsigned int numQueued; - - XrdSysMutex hMutex; -XrdBwmPolicy::SchedParms Parms; - XrdBwmHandle *Next; - XrdOucEICB *ErrCB; - unsigned long long ErrCBarg; - time_t qTime; - time_t rTime; - long long xSize; - long xTime; - int rHandle; - -class theEICB : public XrdOucEICB -{ -public: - - void Done(int &Result, XrdOucErrInfo *eInfo, const char *Path=0) - {mySem.Post();} - - int Same(unsigned long long arg1, unsigned long long arg2) - {return arg1 == arg2;} - - void Wait() {mySem.Wait();} - - theEICB() : mySem(0) {} - -virtual ~theEICB() {} - -private: -XrdSysSemaphore mySem; -} myEICB; -}; -#endif diff --git a/src/XrdBwm/XrdBwmLogger.cc b/src/XrdBwm/XrdBwmLogger.cc deleted file mode 100644 index ec42519c37d..00000000000 --- a/src/XrdBwm/XrdBwmLogger.cc +++ /dev/null @@ -1,315 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d B w m L o g g e r . c c */ -/* */ -/* (c) 2008 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include - -#include "XrdBwm/XrdBwmLogger.hh" -#include "XrdSys/XrdSysError.hh" -#include "XrdOuc/XrdOucProg.hh" -#include "XrdOuc/XrdOucStream.hh" -#include "XrdNet/XrdNetOpts.hh" -#include "XrdNet/XrdNetSocket.hh" -#include "XrdSys/XrdSysPlatform.hh" - -/******************************************************************************/ -/* L o c a l C l a s s e s */ -/******************************************************************************/ - -class XrdBwmLoggerMsg -{ -public: - -static const int msgSize = 2048; - -XrdBwmLoggerMsg *next; -char Text[msgSize]; -int Tlen; - - XrdBwmLoggerMsg() : next(0), Tlen(0) {} - - ~XrdBwmLoggerMsg() {} -}; - -/******************************************************************************/ -/* E x t e r n a l L i n k a g e s */ -/******************************************************************************/ - -void *XrdBwmLoggerSend(void *pp) -{ - XrdBwmLogger *lP = (XrdBwmLogger *)pp; - lP->sendEvents(); - return (void *)0; -} - -/******************************************************************************/ -/* C o n s t r u c t o r */ -/******************************************************************************/ - -XrdBwmLogger::XrdBwmLogger(const char *Target) -{ - -// Set common variables -// - theTarget = strdup(Target); - eDest = 0; - theProg = 0; - msgFirst = msgLast = msgFree = 0; - tid = 0; - msgFD = 0; - endIT = 0; - theEOL= '\n'; - msgsInQ = 0; -} - -/******************************************************************************/ -/* D e s t r u c t o r */ -/******************************************************************************/ - -XrdBwmLogger::~XrdBwmLogger() -{ - XrdBwmLoggerMsg *tp; - -// Kill the notification thread. This may cause a msg block to be orphaned -// but, in practice, this object does not really get deleted after being -// started. So, the problem is moot. -// - endIT = 1; - if (tid) XrdSysThread::Kill(tid); - -// Release all queued message bocks -// - qMut.Lock(); - while ((tp = msgFirst)) {msgFirst = tp->next; delete tp;} - if (theTarget) free(theTarget); - if (msgFD >= 0) close(msgFD); - if (theProg) delete theProg; - qMut.UnLock(); - -// Release all free message blocks -// - fMut.Lock(); - while ((tp = msgFree)) {msgFree = tp->next; delete tp;} - fMut.UnLock(); -} - -/******************************************************************************/ -/* E v e n t */ -/******************************************************************************/ - -void XrdBwmLogger::Event(Info &eInfo) -{ - static int warnings = 0; - XrdBwmLoggerMsg *tp; - -// Get a message block -// - if (!(tp = getMsg())) - {if ((++warnings & 0xff) == 1) - eDest->Emsg("Notify", "Ran out of logger message objects;", - eInfo.Tident, "event not logged."); - return; - } - -// Format the message -// - tp->Tlen = snprintf(tp->Text, XrdBwmLoggerMsg::msgSize, - "%s%s" - "%s%s%c" - "%ld%ld%ld" - "%d%d%d" - "%lld%d%c", - eInfo.Tident, eInfo.Lfn, eInfo.lclNode, eInfo.rmtNode, - eInfo.Flow, eInfo.ATime, eInfo.BTime, eInfo.CTime, - eInfo.numqIn, eInfo.numqOut, eInfo.numqXeq, eInfo.Size, - eInfo.ESec, theEOL); - -// Either log this or put the message on the queue and return -// - tp->next = 0; - qMut.Lock(); - if (msgLast) {msgLast->next = tp; msgLast = tp;} - else msgFirst = msgLast = tp; - qMut.UnLock(); - qSem.Post(); -} - -/******************************************************************************/ -/* s e n d E v e n t s */ -/******************************************************************************/ - -void XrdBwmLogger::sendEvents(void) -{ - XrdBwmLoggerMsg *tp; - const char *theData[2] = {0,0}; - int theDlen[2] = {0,0}; - -// This is an endless loop that just gets things off the event queue and -// send them out. This allows us to only hang a simgle thread should the -// receiver get blocked, instead of the whole process. -// - while(1) - {qSem.Wait(); - qMut.Lock(); - if (endIT) break; - if ((tp = msgFirst) && !(msgFirst = tp->next)) msgLast = 0; - qMut.UnLock(); - if (tp) - {if (!theProg) Feed(tp->Text, tp->Tlen); - else {theData[0] = tp->Text; theDlen[0] = tp->Tlen; - theProg->Feed(theData, theDlen); - - } - retMsg(tp); - } - } - qMut.UnLock(); -} - -/******************************************************************************/ -/* S t a r t */ -/******************************************************************************/ - -int XrdBwmLogger::Start(XrdSysError *eobj) -{ - int rc; - -// Set the error object pointer -// - eDest = eobj; - -// Check if we need to create a socket to a path -// - if (!strcmp("*", theTarget)) {msgFD = -1; theEOL = '\0';} - else if (*theTarget == '>') - {XrdNetSocket *msgSock; - if (!(msgSock = XrdNetSocket::Create(eobj, theTarget+1, 0, 0660, - XRDNET_FIFO))) return -1; - msgFD = msgSock->Detach(); - delete msgSock; - } - else {// Allocate a new program object if we don't have one - // - if (theProg) return 0; - theProg = new XrdOucProg(eobj); - - // Setup the program - // - if (theProg->Setup(theTarget, eobj)) return -1; - if ((rc = theProg->Start())) - {eobj->Emsg("Logger", rc, "start event collector"); return -1;} - } - -// Now start a thread to get messages and send them to the collector -// - if ((rc = XrdSysThread::Run(&tid, XrdBwmLoggerSend, static_cast(this), - 0, "Log message sender"))) - {eobj->Emsg("Logger", rc, "create log message sender thread"); - return -1; - } - -// All done -// - return 0; -} - -/******************************************************************************/ -/* P r i v a t e M e t h o d s */ -/******************************************************************************/ -/******************************************************************************/ -/* F e e d */ -/******************************************************************************/ - -int XrdBwmLogger::Feed(const char *data, int dlen) -{ - int retc; - -// Send message to the log if need be -// - if (msgFD < 0) {eDest->Say("", data); return 0;} - -// Write the data. since this is a udp socket all the data goes or none does -// - do { retc = write(msgFD, (const void *)data, (size_t)dlen);} - while (retc < 0 && errno == EINTR); - if (retc < 0) - {eDest->Emsg("Feed", errno, "write to logger socket", theTarget); - return -1; - } - -// All done -// - return 0; -} - -/******************************************************************************/ -/* g e t M s g */ -/******************************************************************************/ - -XrdBwmLoggerMsg *XrdBwmLogger::getMsg() -{ - XrdBwmLoggerMsg *tp; - -// Lock the free queue -// - fMut.Lock(); - -// Get message object but don't give out too many -// - if (msgsInQ >= maxmInQ) tp = 0; - else {if ((tp = msgFree)) msgFree = tp->next; - else tp = new XrdBwmLoggerMsg(); - msgsInQ++; - } - -// Unlock and return result -// - fMut.UnLock(); - return tp; -} - -/******************************************************************************/ -/* r e t M s g */ -/******************************************************************************/ - -void XrdBwmLogger::retMsg(XrdBwmLoggerMsg *tp) -{ - -// Lock the free queue, return message, unlock the queue -// - fMut.Lock(); - tp->next = msgFree; - msgFree = tp; - msgsInQ--; - fMut.UnLock(); -} diff --git a/src/XrdBwm/XrdBwmLogger.hh b/src/XrdBwm/XrdBwmLogger.hh deleted file mode 100644 index 1c23ec5adbe..00000000000 --- a/src/XrdBwm/XrdBwmLogger.hh +++ /dev/null @@ -1,92 +0,0 @@ -#ifndef __XRDBWMLOGGER_H__ -#define __XRDBWMLOGGER_H__ -/******************************************************************************/ -/* */ -/* X r d B w m L o g g e r . h h */ -/* */ -/* (c) 2008 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include "XrdSys/XrdSysPthread.hh" - -class XrdBwmLoggerMsg; -class XrdOucProg; -class XrdSysError; - -class XrdBwmLogger -{ -public: - -struct Info - {const char *Tident; - const char *Lfn; - const char *lclNode; - const char *rmtNode; - time_t ATime; // Arrival - time_t BTime; // Begin - time_t CTime; // Complete - int numqIn; - int numqOut; - int numqXeq; - long long Size; - int ESec; - char Flow; // I or O - }; - -void Event(Info &eInfo); - -const char *Prog() {return theTarget;} - -void sendEvents(void); - -int Start(XrdSysError *eobj); - - XrdBwmLogger(const char *Target); - ~XrdBwmLogger(); - -private: -int Feed(const char *data, int dlen); -XrdBwmLoggerMsg *getMsg(); -void retMsg(XrdBwmLoggerMsg *tp); - -pthread_t tid; -char *theTarget; -XrdSysError *eDest; -XrdOucProg *theProg; -XrdSysMutex qMut; -XrdSysSemaphore qSem; -XrdBwmLoggerMsg *msgFirst; -XrdBwmLoggerMsg *msgLast; -XrdSysMutex fMut; -XrdBwmLoggerMsg *msgFree; -int msgFD; -int endIT; -int msgsInQ; -static const int maxmInQ = 256; -char theEOL; -}; -#endif diff --git a/src/XrdBwm/XrdBwmPolicy.hh b/src/XrdBwm/XrdBwmPolicy.hh deleted file mode 100644 index 2848aac87c7..00000000000 --- a/src/XrdBwm/XrdBwmPolicy.hh +++ /dev/null @@ -1,160 +0,0 @@ -#ifndef __BWM_POLICY__ -#define __BWM_POLICY__ -/******************************************************************************/ -/* */ -/* X r d B w m P o l i c y . h h */ -/* */ -/* (c) 2008 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -class XrdBwmPolicy -{ -public: - -/* General note: Each request is to be identified by an int-sized handle. - The value of the handle is unique with respect to all of the - requests that are active and queued. Once a request leaves - the system (i.e., cancelled or released) the handle may be - re-used. Handle signs are immaterial. That is the property - "n == abs(-n) == " always must hold. Note that - Schedule() uses negative handles to merely indicate queuing. -*/ - -/* Dispatch() returns the handle of the next request that may become active - because the resources are now available or that must be terminated - because resources are not available. The returned value must have the - the following property: "Dispatch() == abs(Schedule()) == ". - Hence, the handle returned by Dispatch() must be one previously returned by - Schedule() that was negative to indicate that the request was queued. The - sign of the returned handle indicates success or failure: - - returns < 0: The associated previously scheduled request cannot obtain - the resource. RespBuff, of size RespSize, should contain - null terminated text describing the failure. Done() will not - called for the returned handle. - returns >= 0: The associated previously scheduled request can now be - dispatched as resources are available. RespBuff, of size - RespSize, should contain any visa information, as an - ASCII null terminated string to be sent to client. If none, - it should contain a null string (i.e., zero byte). Done() - will be called for the returned handle when the resource is no - longer needed. - - Dispatch() blocks until a request is ready or has failed. -*/ - -virtual int Dispatch(char *RespBuff, int RespSize) = 0; - -/* Done() indicates that the resources with a previous request associated with - the handle, as returned by Dispatch() and Schedule(). When Done() is called - with a handle referring to a queued request, the request should be cancelled - and removed from the queue. If the handle refers to an active request (i.e., - a non-negative one that was returned by Dispatch()), the resources associated - with the dispatched request are no longer needed and are to be made available - to another request. The value returned by Done() indicates what happened: - - returns < 0: The queued request was cancelled. - returns = 0: No request matching the handle was found. - returns > 0: The resources associated with the dispatched request returned. - - The handle itself may be a positive or negative, as returned by Dispatch() - and Schedule(). Note that "n == abs(-n) == ", so the sign - of the handle should be immaterial to Done(). Negative handles returned by - Dispatch() indicate failure and thus Done() will not be called for such - handles. Handles returned by Schedule() may be postive or negative. -*/ - -virtual int Done(int rHandle) = 0; - -/* Schedule() is invoked when the caller wishes to obtain resources controlled - by the policy. The caller passes a pointer to a response buffer, RespBuff, - of size contained in RespSize, to hold hold any response. Additionally. a - reference to the SchedParms struct that contains information about the - nature of the request. Schedule() may choose to immediately allow the - resourse to be used, fail the request, or to defer the request. - This is indicated by the returned int, as follows: - - returns < 0: The request has been queued. The returned value is the handle - for the request and is to be used as the argument to Done() to - cancel the queued request. - - returns = 0: The request failed. The RespBuff should contain any error text - or a null byte if no text is present. - - returns > 0: The request succeeded and the resource can be used. The returned - value is the handle for the request and is to be used as the - argument to Done() to release the associated request resource. - - RespBuff should contain any visa information, as an ASCII null - terminated string to be sent to client. If none, it - must contain a null string (i.e., zero byte). -*/ -enum Flow {Incomming = 0, Outgoing}; - -struct SchedParms -{ -const char *Tident; // In: -> Client's trace identity - char *Lfn; // In: -> Logical File Name - char *LclNode; // In: -> Local node involved in the request - char *RmtNode; // In: -> Remote node involved in the request - Flow Direction; // In: -> Data flow relative to Lclpoint (see enum) -}; - -virtual int Schedule(char *RespBuff, int RespSize, SchedParms &Parms) = 0; - -/* Status() returns the number of requests as three items via parameters: - numqIn - Number of incomming data requests queued - numqOut - Number of outgoing data requests queued - numXeq - Number of requests that are active (in or out). -*/ - -virtual void Status(int &numqIn, int &numqOut, int &numXeq) = 0; - - XrdBwmPolicy() {} - -virtual ~XrdBwmPolicy() {} -}; - -/******************************************************************************/ -/* X r d B w m P o l i c y O b j e c t */ -/******************************************************************************/ - -class XrdSysLogger; - -/* XrdBwmPolicyObject() is called to obtain an instance of the policy object - that will be used for all subsequent policy scheduling requests. If it - returns a null pointer; initialization fails and the program exits. - The args are: - - lp -> XrdSysLogger to be tied to an XrdSysError object for messages - cfn -> The name of the configuration file - parm -> Parameters specified on the policy lib directive. If none it's zero. -*/ - -extern "C" XrdBwmPolicy *XrdBwmPolicyObject(XrdSysLogger *lp, - const char *cfn, - const char *parm); -#endif diff --git a/src/XrdBwm/XrdBwmPolicy1.cc b/src/XrdBwm/XrdBwmPolicy1.cc deleted file mode 100644 index e1e1d86f85d..00000000000 --- a/src/XrdBwm/XrdBwmPolicy1.cc +++ /dev/null @@ -1,159 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d B w m P o l i c y 1 . c c */ -/* */ -/* (c) 2008 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -#include "XrdBwm/XrdBwmPolicy1.hh" - -/******************************************************************************/ -/* C o n s t r u c t o r */ -/******************************************************************************/ - -XrdBwmPolicy1::XrdBwmPolicy1(int inslots, int outslots) -{ -// Initialize values -// - theQ[In ].maxSlots = theQ[In ].curSlots = inslots; - theQ[Out].maxSlots = theQ[Out].curSlots = outslots; - theQ[Xeq].maxSlots = theQ[Xeq].curSlots = 0; - refID = 1; -} - -/******************************************************************************/ -/* D i s p a t c h */ -/******************************************************************************/ - -int XrdBwmPolicy1::Dispatch(char *RespBuff, int RespSize) -{ - refReq *rP; - int rID; - -// Obtain mutex and check if we have any queued requests -// - do {pMutex.Lock(); - if ((rP = theQ[In].Next()) || (rP = theQ[Out].Next())) - {theQ[Xeq].Add(rP); - rID = rP->refID; *RespBuff = '\0'; - pMutex.UnLock(); - return rID; - } - pMutex.UnLock(); - pSem.Wait(); - } while(1); - -// Should never get here -// - strcpy(RespBuff, "Fatal logic error!"); - return 0; -} - -/******************************************************************************/ -/* D o n e */ -/******************************************************************************/ - -int XrdBwmPolicy1::Done(int rHandle) -{ - refReq *rP; - int rc; - -// Make sure we have a positive value here -// - if (rHandle < 0) rHandle = -rHandle; - -// Remove the element from whichever queue it is in -// - pMutex.Lock(); - if ((rP = theQ[Xeq].Yank(rHandle))) - {if (theQ[rP->Way].curSlots++ == 0) pSem.Post(); - rc = 1; - } else { - if ((rP=theQ[In].Yank(rHandle)) || (rP=theQ[Out].Yank(rHandle))) rc = -1; - else rc = 0; - } - pMutex.UnLock(); - -// delete the element and return -// - if (rP) delete rP; - return rc; -} - -/******************************************************************************/ -/* S c h e d u l e */ -/******************************************************************************/ - -int XrdBwmPolicy1::Schedule(char *RespBuff, int RespSize, SchedParms &Parms) -{ - static const char *theWay[] = {"Incomming", "Outgoing"}; - refReq *rP; - int myID; - -// Get the global lock and generate a reference ID -// - *RespBuff = '\0'; - pMutex.Lock(); - myID = ++refID; - rP = new refReq(myID, Parms.Direction); - -// Check if we can immediately schedule this requestor must defer it -// - if (theQ[rP->Way].curSlots > 0) - {theQ[rP->Way].curSlots--; - theQ[Xeq].Add(rP); - } - else if (theQ[rP->Way].maxSlots) - {theQ[rP->Way].Add(rP); myID = -myID;} - else {strcpy(RespBuff, theWay[rP->Way]); - strcat(RespBuff, " requests are not allowed."); - delete rP; - myID = 0; - } - -// All done -// - pMutex.UnLock(); - return myID; -} - -/******************************************************************************/ -/* S t a t u s */ -/******************************************************************************/ - -void XrdBwmPolicy1::Status(int &numqIn, int &numqOut, int &numXeq) -{ - -// Get the global lock and return the values -// - pMutex.Lock(); - numqIn = theQ[In ].Num; - numqOut = theQ[Out].Num; - numXeq = theQ[Xeq].Num; - pMutex.UnLock(); -} diff --git a/src/XrdBwm/XrdBwmPolicy1.hh b/src/XrdBwm/XrdBwmPolicy1.hh deleted file mode 100644 index 82436a57a92..00000000000 --- a/src/XrdBwm/XrdBwmPolicy1.hh +++ /dev/null @@ -1,108 +0,0 @@ -#ifndef __BWM_POLICY1_HH__ -#define __BWM_POLICY1_HH__ -/******************************************************************************/ -/* */ -/* X r d B w m P o l i c y 1 . h h */ -/* */ -/* (c) 2008 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdBwm/XrdBwmPolicy.hh" -#include "XrdSys/XrdSysPthread.hh" - -class XrdBwmPolicy1 : public XrdBwmPolicy -{ -public: - -int Dispatch(char *RespBuff, int RespSize); - -int Done(int rHandle); - -int Schedule(char *RespBuff, int RespSize, SchedParms &Parms); - -void Status(int &numqIn, int &numqOut, int &numXeq); - - XrdBwmPolicy1(int inslots, int outslots); - ~XrdBwmPolicy1() {} - -enum Flow {In = 0, Out = 1, Xeq = 2, IOX = 3}; - -struct refReq - {refReq *Next; - int refID; - Flow Way; - - refReq(int id, XrdBwmPolicy::Flow xF) : Next(0), refID(id), - Way(xF == XrdBwmPolicy::Incomming ? In : Out) {} - ~refReq() {} - }; - -private: - -class refSch - {public: - - refReq *First; - refReq *Last; - int Num; - int curSlots; - int maxSlots; - - void Add(refReq *rP) - {if ((rP->Next = Last)) Last = rP; - else First= Last = rP; - Num++; - } - - refReq *Next() {refReq *rP; - if ((rP = First) && curSlots) - {if (!(First = First->Next)) Last = 0; - Num--; curSlots--; - } - return rP; - } - - refReq *Yank(int rID) - {refReq *pP = 0, *rP = First; - while(rP && rID != rP->refID) {pP = rP; rP = rP->Next;} - if (rP) - {if (pP) pP->Next = rP->Next; - else First = rP->Next; - if (rP == Last) Last = pP; - Num--; - } - return rP; - } - - refSch() : First(0), Last(0), Num(0) {} - ~refSch() {} // Never deleted! - } theQ[IOX]; - -XrdSysSemaphore pSem; -XrdSysMutex pMutex; -int refID; -}; -#endif diff --git a/src/XrdBwm/XrdBwmTrace.hh b/src/XrdBwm/XrdBwmTrace.hh deleted file mode 100644 index d321e814d5e..00000000000 --- a/src/XrdBwm/XrdBwmTrace.hh +++ /dev/null @@ -1,80 +0,0 @@ -#ifndef ___BWM_TRACE_H___ -#define ___BWM_TRACE_H___ -/******************************************************************************/ -/* */ -/* X r d B w m T r a c e . h h */ -/* */ -/* (C) 2008 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Deprtment of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#ifndef NODEBUG - -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdOuc/XrdOucTrace.hh" - -extern XrdOucTrace BwmTrace; - -#define GTRACE(act) BwmTrace.What & TRACE_ ## act - -#define TRACES(x) \ - {BwmTrace.Beg(epname,tident); cerr <. */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdCks/XrdCksData.hh" - -class XrdCks; -class XrdCksCalc; -class XrdOucStream; -class XrdSysError; -class XrdSysPlugin; - -/*! This class defines the checksum management interface. It should be used as - the base class for a plugin. When used that way, the shared library holding - the plugin must define a "C" entry point named XrdCksInit() as described at - the end of this include file. Note that you can also base you plugin on the - native implementation, XrdCks, and replace only selected methods. -*/ - -class XrdCks -{ -public: - -//------------------------------------------------------------------------------ -//! Calculate a new checksum for a physical file using the checksum algorithm -//! named in the Cks parameter. -//! -//! @param Pfn The physical name of the file to be checksumed. -//! @param Cks For input, it specifies the checksum algorithm to be used. -//! For output, the checksum value is returned upon success. -//! @param doSet When true, the new value must replace any existing value -//! in the Pfn's extended file attributes. -//! -//! @return Success: zero with Cks structure holding the checksum value. -//! Failure: -errno (see significant error numbers below). -//------------------------------------------------------------------------------ -virtual -int Calc( const char *Pfn, XrdCksData &Cks, int doSet=1) = 0; - -//------------------------------------------------------------------------------ -//! Delete the checksum from the Pfn's xattrs. -//! -//! @return Success: 0 -//! Failure: -errno (see significant error numbers below). -//------------------------------------------------------------------------------ -virtual -int Del( const char *Pfn, XrdCksData &Cks) = 0; - -//------------------------------------------------------------------------------ -//! Retreive the checksum from the Pfn's xattrs and return it and indicate -//! whether or not it is stale (i.e. the file modification has changed or the -//! name and length are not the expected values). -//! -//! @param Pfn The physical name of the file to be checksumed. -//! @param Cks For input, it specifies the checksum type to return. -//! For output, the checksum value is returned upon success. -//! -//! @return Success: The length of the binary checksum in the Cks structure. -//! Failure: -errno (see significant error numbers below). -//------------------------------------------------------------------------------ -virtual -int Get( const char *Pfn, XrdCksData &Cks) = 0; - -//------------------------------------------------------------------------------ -//! Parse a configuration directives specific to the checksum manager. -//! -//! @param Token Points to the directive that triggered the call. -//! @param Line All the characters after the directive. -//! -//! @return Success: 1 -//! Failure: 0 -//------------------------------------------------------------------------------ -virtual -int Config(const char *Token, char *Line) = 0; - -//------------------------------------------------------------------------------ -//! Fully initialize the manager which includes loading any plugins. -//! -//! @param ConfigFN Points to the configuration file path. -//! @param DfltCalc Is the default checksum and should be defaulted if NULL. -//! The default implementation defaults this to adler32. A -//! default is only needed should the checksum name in the -//! XrdCksData object be omitted. -//! -//!@return Success: 1 -//! Failure: 0 -//------------------------------------------------------------------------------ -virtual -int Init(const char *ConfigFN, const char *DfltCalc=0) = 0; - -//------------------------------------------------------------------------------ -//! List names of the checksums associated with a Pfn or all supported ones. -//! -//! @param Pfn The name of the physical file whose checksum names are to -//! be returned. When Pfn is null, return all supported -//! checksum algorithm names. -//! @param Buff Points to a buffer, at least 64 bytes in length, to hold -//! a "Sep" separated list of checksum names. -//! @param Blen The length of the buffer. -//! @param Sep The separation character to be used between adjacent names. -//! -//! @return Success: Pointer to Buff holding at least one checksum name. -//! Failure: A nil pointer is returned. -//------------------------------------------------------------------------------ -virtual -char *List(const char *Pfn, char *Buff, int Blen, char Sep=' ') = 0; - -//------------------------------------------------------------------------------ -//! Get the name of the checksums associated with a sequence number. Note that -//! Name() may be called prior to final config to see if there are any chksums -//! to configure and avoid unintended errors. -//! -//! @param seqNum The sequence number. Zero signifies the default name. -//! Higher numbers are alternates. -//! @return Success: Pointer to the name. -//! Failure: A nil pointer is returned (no more alternates exist). -//------------------------------------------------------------------------------ -virtual const -char *Name(int seqNum=0) = 0; - -//------------------------------------------------------------------------------ -//! Get a new XrdCksCalc object that can calculate the checksum corresponding to -//! the specified name or the default object if name is a null pointer. The -//! object can be used to compute checksums on the fly. The object's Recycle() -//! method must be used to delete it. -//! -//! @param name The name of the checksum algorithm. If null, use the -//! default one. -//! -//! @return Success: A pointer to the object is returned. -//! Failure: Zero if no corresponding object exists. -//------------------------------------------------------------------------------ -virtual -XrdCksCalc *Object(const char *name) -{ - (void)name; - return 0; -} - -//------------------------------------------------------------------------------ -//! Get the binary length of the checksum with the corresponding name. -//! -//! @param Name The checksum algorithm name. If null, use the default name. -//! -//! @return Success: checksum length. -//! Failure: Zero if the checksum name does not exist. -//------------------------------------------------------------------------------ -virtual -int Size( const char *Name=0) = 0; - -//------------------------------------------------------------------------------ -//! Set a file's checksum in the extended attributes along with the file's mtime -//! and the time of setting. -//! -//! @param Pfn The physical name of the file to be set. -//! @param Cks Specifies the checksum name and value. -//! @param myTime When true then the fmTime and gmTime in the Cks structure -//! are to be used; as opposed to the current time. -//! -//! @return Success: zero is returned. -//! Failure: -errno (see significant error numbers below). -//------------------------------------------------------------------------------ -virtual -int Set( const char *Pfn, XrdCksData &Cks, int myTime=0) = 0; - -//------------------------------------------------------------------------------ -//! Retreive the checksum from the Pfn's xattrs and compare it to the supplied -//! checksum. If the checksum is not available or is stale, a new checksum is -//! calculated and written to the extended attributes. -//! -//! @param Pfn The physical name of the file to be verified. -//! @param Cks Specifies the checksum name and value. -//! -//! @return Success: True -//! Failure: False (the checksums do not match) or -errno indicating -//! that verification could not be performed (see significant -//! error numbers below). -//------------------------------------------------------------------------------ -virtual -int Ver( const char *Pfn, XrdCksData &Cks) = 0; - -//------------------------------------------------------------------------------ -//! Constructor -//------------------------------------------------------------------------------ - - XrdCks(XrdSysError *erP) : eDest(erP) {} - -//------------------------------------------------------------------------------ -//! Destructor -//------------------------------------------------------------------------------ -virtual ~XrdCks() {} - -/*! Significant errno values: - - -EDOM The supplied checksum length is invalid for the checksum name. - -ENOTSUP The supplied or default checksum name is not supported. - -ESRCH Checksum does not exist for file. - -ESTALE The file's checksum is no longer valid. -*/ - -protected: - -XrdSysError *eDest; -}; - -/******************************************************************************/ -/* X r d C k s I n i t */ -/******************************************************************************/ - -#define XRDCKSINITPARMS XrdSysError *, const char *, const char * - -//------------------------------------------------------------------------------ -//! Obtain an instance of the checksum manager. -//! -//! XrdCksInit() is an extern "C" function that is called to obtain an instance -//! of a checksum manager object that will be used for all subsequent checksums. -//! This is useful when checksums come from an alternate source (e.g. database). -//! The proxy server uses this mechanism to obtain checksums from the underlying -//! data server. You can also defined plugins for specific checksum calculations -//! (see XrdCksCalc.hh). The function must be defined in the plug-in shared -//! library. All the following extern symbols must be defined at file level! -//! -//! @param eDest-> The XrdSysError object for messages. -//! @param cfn -> The name of the configuration file -//! @param parm -> Parameters specified on the ckslib directive. If none it is -//! zero. -//! -//! @return Success: A pointer to the checksum manager object. -//! Failure: Null pointer which causes initialization to fail. -//------------------------------------------------------------------------------ - -/*! extern "C" XrdCks *XrdCksInit(XrdSysError *eDest, - const char *cFN, - const char *Parms - ); -*/ -//------------------------------------------------------------------------------ -//! Declare the compilation version number. -//! -//! Additionally, you *should* declare the xrootd version you used to compile -//! your plug-in. While not currently required, it is highly recommended to -//! avoid execution issues should the class definition change. Declare it as: -//------------------------------------------------------------------------------ - -/*! #include "XrdVersion.hh" - XrdVERSIONINFO(XrdCksInit,); - - where is a 1- to 15-character unquoted name identifying your plugin. -*/ -#endif diff --git a/src/XrdCks/XrdCksAssist.cc b/src/XrdCks/XrdCksAssist.cc deleted file mode 100644 index f0fc6394dbe..00000000000 --- a/src/XrdCks/XrdCksAssist.cc +++ /dev/null @@ -1,201 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C k s A s s i s t . c c */ -/* */ -/* (c) 2017 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "XrdCks/XrdCksData.hh" - -/******************************************************************************/ -/* S t a t i c s */ -/******************************************************************************/ - -namespace -{ -struct csTable {const char *csName; int csLenC; int csLenB;} csTab[] - = {{"adler32", 8, 4}, - {"crc32", 8, 4}, - {"crc64", 16, 8}, - {"md5", 32, 16}, - {"sha1", 40, 20}, - {"sha2", 64, 32}, - {"sha256", 64, 32}, - {"sha512", 128, 64} - }; - -static const int csNum = sizeof(csTab)/sizeof(csTable); - -bool LowerCase(const char *src, char *dst, int dstlen) -{ - int n = strlen(src); - -// Verify that the result will fit in the supplied buffer with a null -// - if (n >= dstlen) return false; - -// Convert to ower case with trailing nulls -// - memset(dst+n, 0, dstlen-n); - for (int i = 0; i < n; i++) dst[i] = tolower(src[i]); - return true; -} -} - -/******************************************************************************/ -/* X r d C k s A t t r D a t a */ -/******************************************************************************/ - -// Return the data portion of a checksum attribute so that it can be use -// to set an attribute value. - -std::vector XrdCksAttrData(const char *cstype, - const char *csval, time_t mtime) -{ - std::vector cksError; // Null vector - XrdCksData cksData; - char csName[XrdCksData::NameSize]; - int n = strlen(cstype); - -// Convert the checksum type to lower case -// - if (!LowerCase(cstype, csName, sizeof(csName))) - {errno = ENAMETOOLONG; return cksError;} - -// For cheksums we know, verify that the legnth of the input string -// corresponds to the checksum type. -// - n = strlen(csval); - for (int i = 0; i < csNum; i++) - {if (!strcmp(csTab[i].csName, csName) && csTab[i].csLenC != n) - {errno = EINVAL; return cksError;} - } - -// we simply fill out the cksdata structure with the provided information -// - if (!cksData.Set(csName)) {errno = ENAMETOOLONG; return cksError;} - if (!cksData.Set(csval, n)) {errno = EOVERFLOW; return cksError;} - cksData.fmTime = mtime; - cksData.csTime = time(0) - mtime; - -// Convert the checksum data to a string of bytes and return the vector -// - return std::vector( (char *)&cksData, - ((char *)&cksData) + sizeof(cksData)); -} - -/******************************************************************************/ -/* X r d C k s A t t r N a m e */ -/******************************************************************************/ - -// Return the extended attribute variable name for a particular checksum type. - -std::string XrdCksAttrName(const char *cstype, const char *nspfx) -{ - std::string xaName; - char csName[XrdCksData::NameSize]; - int pfxlen = strlen(nspfx); - -// Do some easy checks for this we know are common -// - if (!pfxlen) - {if (!strcmp(cstype, "adler32")) return std::string("XrdCks.adler32"); - if (!strcmp(cstype, "md5" )) return std::string("XrdCks.md5"); - if (!strcmp(cstype, "crc32" )) return std::string("XrdCks.crc32"); - } - -// Convert the checksum type to lower case -// - if (!LowerCase(cstype, csName, sizeof(csName))) - {errno = ENAMETOOLONG; return xaName;} - -// Reserve storage for the string and construct the variable name -// - xaName.reserve(strlen(nspfx) + strlen(cstype) + 8); - if (pfxlen) - {xaName = nspfx; - if (nspfx[pfxlen-1] != '.') xaName += '.'; - } - xaName += "XrdCks."; - xaName += csName; - -// Return the variable name -// - return xaName; -} - -/******************************************************************************/ -/* X r d C k s A t t r V a l u e */ -/******************************************************************************/ - -std::string XrdCksAttrValue(const char *cstype, - const char *csbuff, int csblen) -{ - XrdCksData cksData; - std::string csError; - char csBuff[XrdCksData::ValuSize*2+1]; - -// Verify that the length matches our object length -// - if (csblen != (int)sizeof(cksData)) {errno = EMSGSIZE; return csError;} - -// Copy the buffer into the object -// - memcpy(&cksData, csbuff, sizeof(cksData)); - -// Now verify that all the fields are consistent -// - if (strncasecmp(cksData.Name, cstype, XrdCksData::NameSize)) - {errno = ENOENT; return csError;} - if (cksData.Length <= 0 || cksData.Length > XrdCksData::ValuSize) - {errno = EINVAL; return csError;} - -// For known checksum values make sure the length matches -// - for (int i = 0; i < csNum; i++) - {if (!strcmp(csTab[i].csName, cstype) - && csTab[i].csLenB != int(cksData.Length)) - {errno = EINVAL; return csError;} - } - -// Convert value to a hex string -// - if (!cksData.Get(csBuff, sizeof(csBuff))) - {errno = EOVERFLOW; return csError;} - -// Return string version of the hex string -// - return std::string(csBuff); -} diff --git a/src/XrdCks/XrdCksAssist.hh b/src/XrdCks/XrdCksAssist.hh deleted file mode 100644 index ccd9d21a9b8..00000000000 --- a/src/XrdCks/XrdCksAssist.hh +++ /dev/null @@ -1,108 +0,0 @@ -#ifndef __XRDCKSASSIST_HH__ -#define __XRDCKSASSIST_HH__ -/******************************************************************************/ -/* */ -/* X r d C k s A s s i s t . h h */ -/* */ -/* (c) 2017 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -//------------------------------------------------------------------------------ -//! This header file defines linkages to various XRootD checksum assistants. -//! The functions described here are located in libXrdUtils.so. -//------------------------------------------------------------------------------ - -//------------------------------------------------------------------------------ -//! Generate the extended attribute data for a particular checksum that can -//! be used to set the corresponding checksum attribute variable. -//! -//! @param cstype A null terminated string holding the checksum type -//! (e.g. "adler32", "md5", "sha2", etc). -//! @param csval A null terminated string holding the corresonding -//! checksum value. This must be an ASCII hex representation -//! of the value and must be of appropriate length. -//! @param mtime The subject file's modification time. -//! -//! @return A vector of bytes that should be usedto set the attribute variable. -//! If the size of the vector is zero, then the supplied parameters -//! were incorrect and valid data cannot be generated; errno is: -//! EINVAL - csval length is incorrect for checksum type or -//! contains a non-hex digit. -//! ENAMETOOLONG - checksum type is too long. -//! EOVERFLOW - csval could not be represented in the data. -//------------------------------------------------------------------------------ - -extern std::vector XrdCksAttrData(const char *cstype, - const char *csval, time_t mtime); - -//------------------------------------------------------------------------------ -//! Generate the extended attribute variable name for a particular checksum. -//! -//! @param cstype A null terminated string holding the checksum type -//! (e.g. "adler32", "md5", "sha2", etc). -//! @param nspfx Is the namespace prefix to add to the variable name. -//! By default no prefix os used. Certain platforms and/or -//! filesystems require that user attributes start with a -//! particular prefix (e.g. Linux requires 'user.') others -//! do not. If your are going to use the variable name to get -//! or set an attribute you should specify any required -//! prefix. If specified and it does not end with a dot, a -//! dot is automatically added to the nspfx. -//! -//! @return A string holding the variable name that should be used to get or -//! set the extended attribute holding the correspnding checksum. If -//! a null string is returned, the variable could not be generated; -//! errno is set to: -//! ENAMETOOLONG - checksum type is too long. -//------------------------------------------------------------------------------ - -extern std::string XrdCksAttrName(const char *cstype, const char *nspfx=""); - -//------------------------------------------------------------------------------ -//! Extract th checksum value from checksum extended attribute data. -//! -//! @param cstype A null terminated string holding the checksum type -//! (e.g. "adler32", "md5", "sha2", etc). -//! @param csbuff A pointer to a buffer hlding the checksum data. -//! @param csblen The length of the checksum data (i.e. the length of the -//! retrieved extended attribute). -//! -//! @return A string holding the ASCII hexstring correspoding to the checksum -//! value. If a null string is returned then the checksum data was -//! invalid or did not correspond to the specified checksum type, the -//! errno is set to: -//! EINVAL - the checksum length in csbuff is incorrect. -//! EMSGSIZE - csblen was not the expected value. -//! ENOENT - the specified cstype did not match the one in csbuff. -//! EOVERFLOW - checksum value could not be generated from csbuff. -//------------------------------------------------------------------------------ - -extern std::string XrdCksAttrValue(const char *cstype, - const char *csbuff, int csblen); -#endif diff --git a/src/XrdCks/XrdCksCalc.hh b/src/XrdCks/XrdCksCalc.hh deleted file mode 100644 index a73f105dc59..00000000000 --- a/src/XrdCks/XrdCksCalc.hh +++ /dev/null @@ -1,171 +0,0 @@ -#ifndef __XRDCKSCALC_HH__ -#define __XRDCKSCALC_HH__ -/******************************************************************************/ -/* */ -/* X r d C k s C a l c . h h */ -/* */ -/* (c) 2011 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -/*! This class defines the interface to a checksum computation. When this class - is used to define a plugin computation, the initial XrdCksCalc computation - object is created by the XrdCksCalcInit() function defined at the end of - this file. -*/ - -class XrdCksCalc -{ -public: - -//------------------------------------------------------------------------------ -//! Calculate a one-time checksum. The obvious default implementation is -//! provided and assumes that Init() may be called more than once. -//! -//! @param Buff -> Data to be checksummed. -//! @param BLen -> Length of the data in Buff. -//! -//! @return the checksum value in binary format. The pointer to the value -//! becomes invalid once the associated object is deleted. -//------------------------------------------------------------------------------ - -virtual char *Calc(const char *Buff, int BLen) - {Init(); Update(Buff, BLen); return Final();} - -//------------------------------------------------------------------------------ -//! Get the current binary checksum value (defaults to final). However, the -//! final checksum result is not affected. -//! -//! @return the checksum value in binary format. The pointer to the value -//! becomes invalid once the associated object is deleted. -//------------------------------------------------------------------------------ - -virtual char *Current() {return Final();} - -//------------------------------------------------------------------------------ -//! Get the actual checksum in binary format. -//! -//! @return the checksum value in binary format. The pointer to the value -//! becomes invalid once the associated object is deleted. -//------------------------------------------------------------------------------ - -virtual char *Final() = 0; - -//------------------------------------------------------------------------------ -//! Initializes data structures (must be called by constructor). This is always -//! called to reuse the object for a new checksum. -//------------------------------------------------------------------------------ - -virtual void Init() = 0; - -//------------------------------------------------------------------------------ -//! Get a new instance of the underlying checksum calculation object. -//! -//! @return the checksum calculation object. -//------------------------------------------------------------------------------ -virtual -XrdCksCalc *New() = 0; - -//------------------------------------------------------------------------------ -//! Recycle the checksum object as it is no longer needed. A default is given. -//------------------------------------------------------------------------------ - -virtual void Recycle() {delete this;} - -//------------------------------------------------------------------------------ -//! Get the checksum object algorithm name and the number bytes (i.e. size) -//! required for the checksum value. -//! -//! @param csSize -> Parameter to hold the size of the checksum value. -//! -//! @return the checksum algorithm's name. The name persists event after the -//! checksum object is deleted. -//------------------------------------------------------------------------------ - -virtual const char *Type(int &csSize) = 0; - -//------------------------------------------------------------------------------ -//! Compute a running checksum. This method may be called repeatedly for data -//! segments; with Final() returning the full checksum. -//! -//! @param Buff -> Data to be checksummed. -//! @param BLen -> Length of the data in Buff. -//------------------------------------------------------------------------------ - -virtual void Update(const char *Buff, int BLen) = 0; - -//------------------------------------------------------------------------------ -//! Constructor -//------------------------------------------------------------------------------ - - XrdCksCalc() {} - -//------------------------------------------------------------------------------ -//! Destructor -//------------------------------------------------------------------------------ - -virtual ~XrdCksCalc() {} -}; - -/******************************************************************************/ -/* C h e c k s u m O b j e c t C r e a t o r */ -/******************************************************************************/ - -//------------------------------------------------------------------------------ -//! Obtain an instance of the checksum calculation object. -//! -//! XrdCksCalcInit() is an extern "C" function that is called to obtain an -//! initial instance of a checksum calculation object. You may create custom -//! checksum calculation and use them as plug-ins to the checksum manager -//! (see XrdCks.hh). The function must be defined in the plug-in shared library. -//! All the following extern symbols must be defined at file level! -//! -//! @param eDest -> The XrdSysError object for messages. -//! @param csName -> The name of the checksum algorithm. -//! @param cFN -> The name of the configuration file -//! @param Parms -> Parameters specified on the ckslib directive. If none it is -//! zero. -//------------------------------------------------------------------------------ - -/*! extern "C" XrdCksCalc *XrdCksCalcInit(XrdSysError *eDest, - const char *csName, - const char *cFN, - const char *Parms); -*/ - -//------------------------------------------------------------------------------ -//! Declare the compilation version number. -//! -//! Additionally, you *should* declare the xrootd version you used to compile -//! your plug-in. While not currently required, it is highly recommended to -//! avoid execution issues should the class definition change. Declare it as: -//------------------------------------------------------------------------------ - -/*! #include "XrdVersion.hh" - XrdVERSIONINFO(XrdCksCalcInit,); - - where is a 1- to 15-character unquoted name identifying your plugin. -*/ -#endif diff --git a/src/XrdCks/XrdCksCalcadler32.hh b/src/XrdCks/XrdCksCalcadler32.hh deleted file mode 100644 index 71e488d3336..00000000000 --- a/src/XrdCks/XrdCksCalcadler32.hh +++ /dev/null @@ -1,126 +0,0 @@ -#ifndef __XRDCKSCALCADLER32_HH__ -#define __XRDCKSCALCADLER32_HH__ -/******************************************************************************/ -/* */ -/* X r d C k s C a l c a d l e r 3 2 . h h */ -/* */ -/* (c) 2011 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include - -#include "XrdCks/XrdCksCalc.hh" -#include "XrdSys/XrdSysPlatform.hh" - -/* The following implementation of adler32 was derived from zlib and is - * Copyright (C) 1995-1998 Mark Adler - Below are the zlib license terms for this implementation. -*/ - -/* zlib.h -- interface of the 'zlib' general purpose compression library - version 1.1.4, March 11th, 2002 - - Copyright (C) 1995-2002 Jean-loup Gailly and Mark Adler - - This software is provided 'as-is', without any express or implied - warranty. In no event will the authors be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - 3. This notice may not be removed or altered from any source distribution. - - Jean-loup Gailly Mark Adler - jloup@gzip.org madler@alumni.caltech.edu - - - The data format used by the zlib library is described by RFCs (Request for - Comments) 1950 to 1952 in the files ftp://ds.internic.net/rfc/rfc1950.txt - (zlib format), rfc1951.txt (deflate format) and rfc1952.txt (gzip format). -*/ - -#define DO1(buf) {unSum1 += *buf++; unSum2 += unSum1;} -#define DO2(buf) DO1(buf); DO1(buf); -#define DO4(buf) DO2(buf); DO2(buf); -#define DO8(buf) DO4(buf); DO4(buf); -#define DO16(buf) DO8(buf); DO8(buf); - -class XrdCksCalcadler32 : public XrdCksCalc -{ -public: - -char *Final() - {AdlerValue = (unSum2 << 16) | unSum1; -#ifndef Xrd_Big_Endian - AdlerValue = htonl(AdlerValue); -#endif - return (char *)&AdlerValue; - } - -void Init() {unSum1 = AdlerStart; unSum2 = 0;} - -XrdCksCalc *New() {return (XrdCksCalc *)new XrdCksCalcadler32;} - -void Update(const char *Buff, int BLen) - {int k; - unsigned char *buff = (unsigned char *)Buff; - while(BLen > 0) - {k = (BLen < AdlerNMax ? BLen : AdlerNMax); - BLen -= k; - while(k >= 16) {DO16(buff); k -= 16;} - if (k != 0) do {DO1(buff);} while (--k); - unSum1 %= AdlerBase; unSum2 %= AdlerBase; - } - } - -const char *Type(int &csSize) {csSize = sizeof(AdlerValue); return "adler32";} - - XrdCksCalcadler32() {Init();} -virtual ~XrdCksCalcadler32() {} - -private: - -static const unsigned int AdlerBase = 0xFFF1; -static const unsigned int AdlerStart = 0x0001; -static const int AdlerNMax = 5552; - -/* NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 */ - - unsigned int AdlerValue; - unsigned int unSum1; - unsigned int unSum2; -}; -#endif diff --git a/src/XrdCks/XrdCksCalccrc32.cc b/src/XrdCks/XrdCksCalccrc32.cc deleted file mode 100644 index b72ef7eda3e..00000000000 --- a/src/XrdCks/XrdCksCalccrc32.cc +++ /dev/null @@ -1,178 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C k s C a l c c r c 3 2 . c c */ -/* */ -/* (c) 2011 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdCks/XrdCksCalccrc32.hh" - -/* - C++ implementation of CRC-32 checksums. Code is based - upon and utilizes algorithm published by Ross Williams - as initially implemented by Eric Durbin. - - This file contains: - CRC lookup table - function CalcCRC32 for calculating CRC-32 checksum - - Provided by: - Eric Durbin - Kentucky Cancer Registry - University of Kentucky - October 14, 1998 - - Status: - Public Domain -*/ - -/*****************************************************************/ -/* */ -/* CRC LOOKUP TABLE */ -/* ================ */ -/* The following CRC lookup table was generated automagically */ -/* by the Rocksoft^tm Model CRC Algorithm Table Generation */ -/* Program V1.0 using the following model parameters: */ -/* */ -/* Width : 4 bytes. */ -/* Poly : 0x04C11DB7L */ -/* Reverse : FALSE */ -/* */ -/* For more information on the Rocksoft^tm Model CRC Algorithm, */ -/* see the document titled "A Painless Guide to CRC Error */ -/* Detection Algorithms" by Ross Williams */ -/* (ross@guest.adelaide.edu.au.). This document is likely to be */ -/* in the FTP archive "ftp.adelaide.edu.au/pub/rocksoft". */ -/* */ -/*****************************************************************/ - -unsigned int XrdCksCalccrc32::crctable[256] = -{ - 0x00000000L, 0x04C11DB7L, 0x09823B6EL, 0x0D4326D9L, - 0x130476DCL, 0x17C56B6BL, 0x1A864DB2L, 0x1E475005L, - 0x2608EDB8L, 0x22C9F00FL, 0x2F8AD6D6L, 0x2B4BCB61L, - 0x350C9B64L, 0x31CD86D3L, 0x3C8EA00AL, 0x384FBDBDL, - 0x4C11DB70L, 0x48D0C6C7L, 0x4593E01EL, 0x4152FDA9L, - 0x5F15ADACL, 0x5BD4B01BL, 0x569796C2L, 0x52568B75L, - 0x6A1936C8L, 0x6ED82B7FL, 0x639B0DA6L, 0x675A1011L, - 0x791D4014L, 0x7DDC5DA3L, 0x709F7B7AL, 0x745E66CDL, - 0x9823B6E0L, 0x9CE2AB57L, 0x91A18D8EL, 0x95609039L, - 0x8B27C03CL, 0x8FE6DD8BL, 0x82A5FB52L, 0x8664E6E5L, - 0xBE2B5B58L, 0xBAEA46EFL, 0xB7A96036L, 0xB3687D81L, - 0xAD2F2D84L, 0xA9EE3033L, 0xA4AD16EAL, 0xA06C0B5DL, - 0xD4326D90L, 0xD0F37027L, 0xDDB056FEL, 0xD9714B49L, - 0xC7361B4CL, 0xC3F706FBL, 0xCEB42022L, 0xCA753D95L, - 0xF23A8028L, 0xF6FB9D9FL, 0xFBB8BB46L, 0xFF79A6F1L, - 0xE13EF6F4L, 0xE5FFEB43L, 0xE8BCCD9AL, 0xEC7DD02DL, - 0x34867077L, 0x30476DC0L, 0x3D044B19L, 0x39C556AEL, - 0x278206ABL, 0x23431B1CL, 0x2E003DC5L, 0x2AC12072L, - 0x128E9DCFL, 0x164F8078L, 0x1B0CA6A1L, 0x1FCDBB16L, - 0x018AEB13L, 0x054BF6A4L, 0x0808D07DL, 0x0CC9CDCAL, - 0x7897AB07L, 0x7C56B6B0L, 0x71159069L, 0x75D48DDEL, - 0x6B93DDDBL, 0x6F52C06CL, 0x6211E6B5L, 0x66D0FB02L, - 0x5E9F46BFL, 0x5A5E5B08L, 0x571D7DD1L, 0x53DC6066L, - 0x4D9B3063L, 0x495A2DD4L, 0x44190B0DL, 0x40D816BAL, - 0xACA5C697L, 0xA864DB20L, 0xA527FDF9L, 0xA1E6E04EL, - 0xBFA1B04BL, 0xBB60ADFCL, 0xB6238B25L, 0xB2E29692L, - 0x8AAD2B2FL, 0x8E6C3698L, 0x832F1041L, 0x87EE0DF6L, - 0x99A95DF3L, 0x9D684044L, 0x902B669DL, 0x94EA7B2AL, - 0xE0B41DE7L, 0xE4750050L, 0xE9362689L, 0xEDF73B3EL, - 0xF3B06B3BL, 0xF771768CL, 0xFA325055L, 0xFEF34DE2L, - 0xC6BCF05FL, 0xC27DEDE8L, 0xCF3ECB31L, 0xCBFFD686L, - 0xD5B88683L, 0xD1799B34L, 0xDC3ABDEDL, 0xD8FBA05AL, - 0x690CE0EEL, 0x6DCDFD59L, 0x608EDB80L, 0x644FC637L, - 0x7A089632L, 0x7EC98B85L, 0x738AAD5CL, 0x774BB0EBL, - 0x4F040D56L, 0x4BC510E1L, 0x46863638L, 0x42472B8FL, - 0x5C007B8AL, 0x58C1663DL, 0x558240E4L, 0x51435D53L, - 0x251D3B9EL, 0x21DC2629L, 0x2C9F00F0L, 0x285E1D47L, - 0x36194D42L, 0x32D850F5L, 0x3F9B762CL, 0x3B5A6B9BL, - 0x0315D626L, 0x07D4CB91L, 0x0A97ED48L, 0x0E56F0FFL, - 0x1011A0FAL, 0x14D0BD4DL, 0x19939B94L, 0x1D528623L, - 0xF12F560EL, 0xF5EE4BB9L, 0xF8AD6D60L, 0xFC6C70D7L, - 0xE22B20D2L, 0xE6EA3D65L, 0xEBA91BBCL, 0xEF68060BL, - 0xD727BBB6L, 0xD3E6A601L, 0xDEA580D8L, 0xDA649D6FL, - 0xC423CD6AL, 0xC0E2D0DDL, 0xCDA1F604L, 0xC960EBB3L, - 0xBD3E8D7EL, 0xB9FF90C9L, 0xB4BCB610L, 0xB07DABA7L, - 0xAE3AFBA2L, 0xAAFBE615L, 0xA7B8C0CCL, 0xA379DD7BL, - 0x9B3660C6L, 0x9FF77D71L, 0x92B45BA8L, 0x9675461FL, - 0x8832161AL, 0x8CF30BADL, 0x81B02D74L, 0x857130C3L, - 0x5D8A9099L, 0x594B8D2EL, 0x5408ABF7L, 0x50C9B640L, - 0x4E8EE645L, 0x4A4FFBF2L, 0x470CDD2BL, 0x43CDC09CL, - 0x7B827D21L, 0x7F436096L, 0x7200464FL, 0x76C15BF8L, - 0x68860BFDL, 0x6C47164AL, 0x61043093L, 0x65C52D24L, - 0x119B4BE9L, 0x155A565EL, 0x18197087L, 0x1CD86D30L, - 0x029F3D35L, 0x065E2082L, 0x0B1D065BL, 0x0FDC1BECL, - 0x3793A651L, 0x3352BBE6L, 0x3E119D3FL, 0x3AD08088L, - 0x2497D08DL, 0x2056CD3AL, 0x2D15EBE3L, 0x29D4F654L, - 0xC5A92679L, 0xC1683BCEL, 0xCC2B1D17L, 0xC8EA00A0L, - 0xD6AD50A5L, 0xD26C4D12L, 0xDF2F6BCBL, 0xDBEE767CL, - 0xE3A1CBC1L, 0xE760D676L, 0xEA23F0AFL, 0xEEE2ED18L, - 0xF0A5BD1DL, 0xF464A0AAL, 0xF9278673L, 0xFDE69BC4L, - 0x89B8FD09L, 0x8D79E0BEL, 0x803AC667L, 0x84FBDBD0L, - 0x9ABC8BD5L, 0x9E7D9662L, 0x933EB0BBL, 0x97FFAD0CL, - 0xAFB010B1L, 0xAB710D06L, 0xA6322BDFL, 0xA2F33668L, - 0xBCB4666DL, 0xB8757BDAL, 0xB5365D03L, 0xB1F740B4L -}; - -/*****************************************************************/ -/* End of CRC Lookup Table */ -/*****************************************************************/ - -/* Calculate CRC-32 Checksum for NAACCR Record, - skipping area of record containing checksum field. - - Uses non-reflected table driven method documented by Ross Williams. - - PARAMETERS: - unsigned char *p Record Buffer - unsigned long reclen Record Length - - RETURNS: - checksum value - - Author: - Eric Durbin 1998-10-14 - Simplified by Andrew Hanushevsky 2007-07-20 - - Status: - Public Domain - - Changes: - Compute CRC for complete buffer - Use unsigned int instead of long to insure 32 bit values. - Include length bits at the end to correspond to the Posix 1003.2 spec. - Make this a C++ class. -*/ -void XrdCksCalccrc32::Update(const char *p, int reclen) -{ - -// Process each byte -// - TotLen += reclen; - while(reclen-- > 0) - C32Result = (C32Result<<8) - ^ crctable[(unsigned char)((C32Result>>24)^*p++)]; -} diff --git a/src/XrdCks/XrdCksCalccrc32.hh b/src/XrdCks/XrdCksCalccrc32.hh deleted file mode 100644 index 4c427231098..00000000000 --- a/src/XrdCks/XrdCksCalccrc32.hh +++ /dev/null @@ -1,78 +0,0 @@ -#ifndef __XRDCKSCALCCRC32_HH__ -#define __XRDCKSCALCCRC32_HH__ -/******************************************************************************/ -/* */ -/* X r d C k s C a l c c r c 3 2 . h h */ -/* */ -/* (c) 2011 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include - -#include "XrdCks/XrdCksCalc.hh" -#include "XrdSys/XrdSysPlatform.hh" - -class XrdCksCalccrc32 : public XrdCksCalc -{ -public: - -char *Final() {char buff[sizeof(long long)]; - long long tLcs = TotLen; - int i = 0; - if (tLcs) - {while(tLcs) {buff[i++] = tLcs & 0xff ; tLcs >>= 8;} - Update(buff, i); - } - TheResult = C32Result ^ CRC32_XOROT; -#ifndef Xrd_Big_Endian - TheResult = htonl(TheResult); -#endif - return (char *)&TheResult; - } - -void Init() {C32Result = CRC32_XINIT; TotLen = 0;} - -XrdCksCalc *New() {return (XrdCksCalc *)new XrdCksCalccrc32;} - -void Update(const char *Buff, int BLen); - -const char *Type(int &csSz) {csSz = sizeof(TheResult); return "crc32";} - - XrdCksCalccrc32() {Init();} -virtual ~XrdCksCalccrc32() {} - -private: -static const unsigned int CRC32_XINIT = 0; -static const unsigned int CRC32_XOROT = 0xffffffff; -static unsigned int crctable[256]; - unsigned int C32Result; - unsigned int TheResult; - long long TotLen; -}; -#endif diff --git a/src/XrdCks/XrdCksCalcmd5.cc b/src/XrdCks/XrdCksCalcmd5.cc deleted file mode 100644 index b522fc5d9a8..00000000000 --- a/src/XrdCks/XrdCksCalcmd5.cc +++ /dev/null @@ -1,304 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C k s C a l c m d 5 . c c */ -/* */ -/* (c) 2011 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -#include "XrdCks/XrdCksCalcmd5.hh" -#include "XrdSys/XrdSysPlatform.hh" - -/* - * This code implements the MD5 message-digest algorithm. - * The algorithm is due to Ron Rivest. This code was - * written by Colin Plumb in 1993, no copyright is claimed. - * This code is in the public domain; do with it what you wish. - * - * Equivalent code is available from RSA Data Security, Inc. - * This code has been tested against that, and is equivalent, - * except that you don't need to include two pages of legalese - * with every copy. - * - * To compute the message digest of a chunk of bytes, declare an - * MD5Context structure, pass it to MD5Init, call MD5Update as - * needed on buffers full of bytes, and then call MD5Final, which - * will fill a supplied 16-byte array with the digest. - */ - -/******************************************************************************/ -/* B y t e R e v e r s e */ -/******************************************************************************/ - -#ifndef Xrd_Big_Endian -void XrdCksCalcmd5::byteReverse(unsigned char *buf, unsigned longs) {} /* Nothing */ -#else -#ifndef ASM_MD5 -void XrdCksCalcmd5::byteReverse(unsigned char *buf, unsigned longs) -{ - unsigned int t; - do {t = (unsigned int) ((unsigned) buf[3] << 8 | buf[2]) << 16 | - ((unsigned) buf[1] << 8 | buf[0]); - *(unsigned int *) buf = t; - buf += 4; - } while (--longs); -} -#endif -#endif - -/******************************************************************************/ -/* I n i t */ -/******************************************************************************/ - -/******************************************************************************/ -/* M D 5 I n i t */ -/******************************************************************************/ - -/* Start MD5 accumulation. Set bit count to 0 and buffer to mysterious - initialization constants. -*/ -void XrdCksCalcmd5::Init() -{ - myContext.buf[0] = 0x67452301; - myContext.buf[1] = 0xefcdab89; - myContext.buf[2] = 0x98badcfe; - myContext.buf[3] = 0x10325476; - - myContext.bits[0] = 0; - myContext.bits[1] = 0; -} - -/******************************************************************************/ -/* M D 5 U p d a t e */ -/******************************************************************************/ - -/* Update context to reflect the concatenation of another buffer full of bytes. -*/ -void XrdCksCalcmd5::MD5Update(unsigned char const *buf, unsigned int len) -{ - unsigned int t; - -// Update bitcount -// - t = myContext.bits[0]; - if ((myContext.bits[0] = t + ((unsigned int) len << 3)) < t) - -// Carry from low to high -// - myContext.bits[1]++; - myContext.bits[1] += len >> 29; - -// Bytes already in shsInfo->data -// - t = (t >> 3) & 0x3f; - -// Handle any leading odd-sized chunks -// - if (t) {unsigned char *p = (unsigned char *) myContext.in + t; - t = 64 - t; - if (len < t) {memcpy(p, buf, len); return;} - memcpy(p, buf, t); - byteReverse(myContext.in, 16); - MD5Transform(myContext.buf, (unsigned int *) myContext.in); - buf += t; - len -= t; - } - -// Process data in 64-byte chunks -// - while(len >= 64) - {memcpy(myContext.in, buf, 64); - byteReverse(myContext.in, 16); - MD5Transform(myContext.buf, (unsigned int *) myContext.in); - buf += 64; - len -= 64; - } - -// Handle any remaining bytes of data. - - memcpy(myContext.in, buf, len); -} - -/******************************************************************************/ -/* F i n a l */ -/******************************************************************************/ - -/* Final wrapup - pad to 64-byte boundary with the bit pattern - 1 0* (64-bit count of bits processed, MSB-first) -*/ -char *XrdCksCalcmd5::Final() -{ - unsigned count; - unsigned char *p; - -// Compute number of bytes mod 64 -// - count = (myContext.bits[0] >> 3) & 0x3F; - -// Set the first char of padding to 0x80. This is safe since there is -// always at least one byte free. -// - p = myContext.in + count; - *p++ = 0x80; - -// Bytes of padding needed to make 64 bytes -// - count = 64 - 1 - count; - -// Pad out to 56 mod 64 -// - if (count < 8) // Two lots of padding: Pad the first block to 64 bytes - {memset(p, 0, count); - byteReverse(myContext.in, 16); - MD5Transform(myContext.buf, (unsigned int *) myContext.in); - memset(myContext.in, 0, 56); // Now fill the next block with 56 bytes - } else memset(p, 0, count - 8); // Else pad block to 56 bytes - - byteReverse(myContext.in, 14); - -// Append length in bits and transform (original code in comments) -// -// ((unsigned int *) myContext.in)[14] = myContext.bits[0]; -// ((unsigned int *) myContext.in)[15] = myContext.bits[1]; - myContext.i64[7] = myContext.b64; - - MD5Transform(myContext.buf, (unsigned int *) myContext.in); - byteReverse((unsigned char *) myContext.buf, 4); - -// Copy to a separate buffer and return ASCII value if so wanted -// - memcpy(myDigest, myContext.buf, 16); - return (char *)myDigest; -} - -/******************************************************************************/ -/* M D 5 T r a n s f o r m */ -/******************************************************************************/ - -#ifndef ASM_MD5 - -/* The four core functions - F1 is optimized somewhat */ - -// #define F1(x, y, z) (x & y | ~x & z) -// -#define F1(x, y, z) (z ^ (x & (y ^ z))) -#define F2(x, y, z) F1(z, x, y) -#define F3(x, y, z) (x ^ y ^ z) -#define F4(x, y, z) (y ^ (x | ~z)) - -// This is the central step in the MD5 algorithm. -// -#define MD5STEP(f, w, x, y, z, data, s) \ - ( w += f(x, y, z) + data, w = w<>(32-s), w += x ) - -/* The core of the MD5 algorithm, this alters an existing MD5 hash to - reflect the addition of 16 longwords of new data. MD5Update blocks - the data and converts bytes into longwords for this routine. -*/ -void XrdCksCalcmd5::MD5Transform(unsigned int buf[4], unsigned int const in[16]) -{ - unsigned int a, b, c, d; - - a = buf[0]; - b = buf[1]; - c = buf[2]; - d = buf[3]; - - MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); - MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); - MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); - MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); - MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); - MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); - MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); - MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); - MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); - MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); - MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); - MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); - MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); - MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); - MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); - MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); - - MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); - MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); - MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); - MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); - MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); - MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); - MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); - MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); - MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); - MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); - MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); - MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); - MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); - MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); - MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); - MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); - - MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); - MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); - MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); - MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); - MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); - MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); - MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); - MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); - MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); - MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); - MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); - MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); - MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); - MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); - MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); - MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); - - MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); - MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); - MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); - MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); - MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); - MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); - MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); - MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); - MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); - MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); - MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); - MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); - MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); - MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); - MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); - MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); - - buf[0] += a; - buf[1] += b; - buf[2] += c; - buf[3] += d; -} -#endif diff --git a/src/XrdCks/XrdCksCalcmd5.hh b/src/XrdCks/XrdCksCalcmd5.hh deleted file mode 100644 index 48f192f3750..00000000000 --- a/src/XrdCks/XrdCksCalcmd5.hh +++ /dev/null @@ -1,84 +0,0 @@ -#ifndef __XRDCKSCALCMD5_HH__ -#define __XRDCKSCALCMD5_HH__ -/******************************************************************************/ -/* */ -/* X r d C k s C a l c m d 5 . h h */ -/* */ -/* (c) 2011 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -#include "XrdCks/XrdCksCalc.hh" - -class XrdCksCalcmd5 : public XrdCksCalc -{ -public: - -char *Current() - {MD5Context saveCTX = myContext; - char *md5P = Final(); - myContext = saveCTX; - return (char *)md5P; - } - -void Init(); - -XrdCksCalc *New() {return (XrdCksCalc *)new XrdCksCalcmd5;} - -char *Final(); - -void Update(const char *Buff, int BLen) - {MD5Update((unsigned char *)Buff,(unsigned)BLen);} - -const char *Type(int &csSz) {csSz = sizeof(myDigest); return "md5";} - - XrdCksCalcmd5() {Init();} - ~XrdCksCalcmd5() {} - -private: - -struct MD5Context - {unsigned int buf[4]; -union {long long b64; - unsigned int bits[2]; - }; -union {long long i64[8]; - unsigned char in[64]; - }; - }; - -MD5Context myContext; -unsigned char myDigest[16]; - -void byteReverse(unsigned char *buf, unsigned longs); -void MD5Update(unsigned char const *buf, unsigned int len); - -#ifndef ASM_MD5 -void MD5Transform(unsigned int buf[4], unsigned int const in[16]); -#endif -}; -#endif diff --git a/src/XrdCks/XrdCksCalczcrc32.cc b/src/XrdCks/XrdCksCalczcrc32.cc deleted file mode 100644 index 5724ecc5881..00000000000 --- a/src/XrdCks/XrdCksCalczcrc32.cc +++ /dev/null @@ -1,117 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C k s C a l c z c r c 3 2 . h h */ -/* */ -/* Copyright (c) 2012 by European Organization of Nuclear Research (CERN) */ -/* Produced by Lukasz Janyst */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#ifndef __XRDCKSCALCZCRC32_HH__ -#define __XRDCKSCALCZCRC32_HH__ - -#include "XrdCks/XrdCksCalc.hh" -#include "XrdSys/XrdSysError.hh" -#include "XrdVersion.hh" -#include -#include - -//------------------------------------------------------------------------------ -// CRC32 checkum according to the algorithm implemented in zlib -//------------------------------------------------------------------------------ -class XrdCksCalczcrc32: public XrdCksCalc -{ - public: - - //-------------------------------------------------------------------------- - //! Constructor - //-------------------------------------------------------------------------- - XrdCksCalczcrc32() - { - Init(); - } - - //-------------------------------------------------------------------------- - //! Destructor - //-------------------------------------------------------------------------- - virtual ~XrdCksCalczcrc32() - { - } - - //-------------------------------------------------------------------------- - //! Final checksum - //-------------------------------------------------------------------------- - char *Final() - { - return (char *)&pCheckSum; - } - - //-------------------------------------------------------------------------- - //! Initialize - //-------------------------------------------------------------------------- - void Init() - { - pCheckSum = crc32( 0L, Z_NULL, 0 ); - } - - //-------------------------------------------------------------------------- - //! Virtual constructor - //-------------------------------------------------------------------------- - XrdCksCalc *New() - { - return new XrdCksCalczcrc32(); - } - - //-------------------------------------------------------------------------- - //! Update current checksum - //-------------------------------------------------------------------------- - void Update( const char *Buff, int BLen ) - { - pCheckSum = crc32( pCheckSum, (const Bytef*)Buff, BLen ); - } - - //-------------------------------------------------------------------------- - //! Checksum algorithm name - //-------------------------------------------------------------------------- - const char *Type(int &csSz) - { - csSz = 4; return "zcrc32"; - } - - private: - uint32_t pCheckSum; -}; - -//------------------------------------------------------------------------------ -// Plugin callback -//------------------------------------------------------------------------------ -extern "C" XrdCksCalc *XrdCksCalcInit(XrdSysError *eDest, - const char *csName, - const char *cFN, - const char *Parms) -{ - return new XrdCksCalczcrc32(); -} - -XrdVERSIONINFO(XrdCksCalcInit, zcrc32); - -#endif // __XRDCKSCALCZCRC32_HH__ diff --git a/src/XrdCks/XrdCksConfig.cc b/src/XrdCks/XrdCksConfig.cc deleted file mode 100644 index 6512c7a6206..00000000000 --- a/src/XrdCks/XrdCksConfig.cc +++ /dev/null @@ -1,216 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C k s C o n f i g . c c */ -/* */ -/* (c) 2011 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include - -#include "XrdVersion.hh" - -#include "XrdCks/XrdCks.hh" -#include "XrdCks/XrdCksData.hh" -#include "XrdCks/XrdCksConfig.hh" -#include "XrdCks/XrdCksManager.hh" -#include "XrdCks/XrdCksManOss.hh" -#include "XrdOuc/XrdOucPinLoader.hh" -#include "XrdOuc/XrdOucStream.hh" -#include "XrdOuc/XrdOucUtils.hh" -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysPlugin.hh" - -/******************************************************************************/ -/* C o n s t r u c t o r */ -/******************************************************************************/ - -XrdCksConfig::XrdCksConfig(const char *cFN, XrdSysError *Eroute, int &aOK, - XrdVersionInfo &vInfo) - : eDest(Eroute), cfgFN(cFN), CksLib(0), CksParm(0), - CksList(0), CksLast(0), myVersion(vInfo) -{ - static XrdVERSIONINFODEF(myVer, XrdCks, XrdVNUMBER, XrdVERSION); - -// Verify caller's version against ours -// - if (vInfo.vNum <= 0 || vInfo.vNum == myVer.vNum - || XrdSysPlugin::VerCmp(vInfo, myVer)) aOK = 1; - else aOK = 0; -} - -/******************************************************************************/ -/* C o n f i g u r e */ -/******************************************************************************/ - -XrdCks *XrdCksConfig::Configure(const char *dfltCalc, int rdsz, XrdOss *ossP) -{ - XrdCks *myCks = getCks(ossP, rdsz); - XrdOucTList *tP = CksList; - int NoGo = 0; - -// Check if we have a cks object -// - if (!myCks) return 0; - -// Configure the object -// - while(tP) {NoGo |= myCks->Config("ckslib", tP->text); tP = tP->next;} - -// Configure if all went well -// - if (!NoGo) NoGo = !myCks->Init(cfgFN, dfltCalc); - -// All done -// - if (NoGo) {delete myCks; myCks = 0;} - return myCks; -} - -/******************************************************************************/ -/* g e t C k s */ -/******************************************************************************/ - -XrdCks *XrdCksConfig::getCks(XrdOss *ossP, int rdsz) -{ - XrdOucPinLoader *myLib; - XrdCks *(*ep)(XRDCKSINITPARMS); - -// Authorization comes from the library or we use the default -// - if (!CksLib) - {if (ossP) return (XrdCks *)new XrdCksManOss (ossP,eDest,rdsz,myVersion); - else return (XrdCks *)new XrdCksManager( eDest,rdsz,myVersion); - } - -// Create a plugin object (we will throw this away without deletion because -// the library must stay open but we never want to reference it again). -// - if (!(myLib = new XrdOucPinLoader(eDest, &myVersion, "ckslib", CksLib))) - return 0; - -// Now get the entry point of the object creator -// - ep = (XrdCks *(*)(XRDCKSINITPARMS))(myLib->Resolve("XrdCksInit")); - if (!ep) {myLib->Unload(true); return 0;} - -// Get the Object now -// - delete myLib; - return ep(eDest, cfgFN, CksParm); -} - -/******************************************************************************/ -/* M a n a g e r */ -/******************************************************************************/ - -/* Function: Manager - - Purpose: Reset the manager plugin library path and parameters. - - path to the library. - optional parms to be passed - - Output: 0 upon success or !0 upon failure. -*/ - -int XrdCksConfig::Manager(const char *Path, const char *Parms) -{ -// Replace the library path and parameters -// - if (CksLib) free(CksLib); - CksLib = strdup(Path); - if (CksParm) free(CksParm); - CksParm = (Parms && *Parms ? strdup(Parms) : 0); - return 0; -} - -/******************************************************************************/ -/* P a r s e L i b */ -/******************************************************************************/ - -/* Function: ParseLib - - Purpose: To parse the directive: ckslib [] - - the name of the checksum. The special name "*" is used - load the checksum manager library. - the path of the checksum library to be used. - optional parms to be passed - - Output: 0 upon success or !0 upon failure. -*/ - -int XrdCksConfig::ParseLib(XrdOucStream &Config) -{ - static const int nameSize = XrdCksData::NameSize; - static const int pathSize = MAXPATHLEN; - static const int parmSize = 1024; - XrdOucTList *tP; - char *val, buff[nameSize + pathSize + parmSize + 8], parms[parmSize], *bP; - int n; - -// Get the digest -// - if (!(val = Config.GetWord()) || !val[0]) - {eDest->Emsg("Config", "ckslib digest not specified"); return 1;} - n = strlen(val); - if (n >= nameSize) - {eDest->Emsg("Config", "ckslib digest name too long -", val); return 1;} - strcpy(buff, val); XrdOucUtils::toLower(buff); bP = buff+n; *bP++ = ' '; - -// Get the path -// - if (!(val = Config.GetWord()) || !val[0]) - {eDest->Emsg("Config", "ckslib path not specified for", buff); return 1;} - n = strlen(val); - if (n > pathSize) - {eDest->Emsg("Config", "ckslib path name too long -", val); return 1;} - strcpy(bP, val); bP += n; - -// Record any parms -// - *parms = 0; - if (!Config.GetRest(parms, parmSize)) - {eDest->Emsg("Config", "ckslib parameters too long for", buff); return 1;} - -// Check if this is for the manager -// - if (*buff == '*' && *(buff+1) == ' ') return Manager(buff+2, parms); - -// Add this digest to the list of digests -// - *bP++ = ' '; strcpy(bP, parms); - tP = new XrdOucTList(buff); - if (CksLast) CksLast->next = tP; - else CksList = tP; - CksLast = tP; - return 0; -} diff --git a/src/XrdCks/XrdCksConfig.hh b/src/XrdCks/XrdCksConfig.hh deleted file mode 100644 index c40701a8ee8..00000000000 --- a/src/XrdCks/XrdCksConfig.hh +++ /dev/null @@ -1,76 +0,0 @@ -#ifndef __XRDCKSCONFIG_HH__ -#define __XRDCKSCONFIG_HH__ -/******************************************************************************/ -/* */ -/* X r d C k s C o n f i g . h h */ -/* */ -/* (c) 2011 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdOuc/XrdOucTList.hh" - -class XrdCks; -class XrdOss; -class XrdOucStream; -class XrdSysError; - -struct XrdVersionInfo; - -class XrdCksConfig -{ -public: - -XrdCks *Configure(const char *dfltCalc=0, int rdsz=0, XrdOss *ossP=0); - -int Manager() {return CksLib != 0;} - -int Manager(const char *Path, const char *Parms); - -const -char *ManLib() {return CksLib;} - -int ParseLib(XrdOucStream &Config); - - XrdCksConfig(const char *cFN, XrdSysError *Eroute, int &aOK, - XrdVersionInfo &vInfo); - ~XrdCksConfig() {XrdOucTList *tP; - if (CksLib) free(CksLib); - if (CksParm) free(CksParm); - while((tP = CksList)) {CksList = tP->next; delete tP;} - } - -private: -XrdCks *getCks(XrdOss *ossP, int rdsz); - -XrdSysError *eDest; -const char *cfgFN; -char *CksLib; -char *CksParm; -XrdOucTList *CksList; -XrdOucTList *CksLast; -XrdVersionInfo &myVersion; -}; -#endif diff --git a/src/XrdCks/XrdCksData.hh b/src/XrdCks/XrdCksData.hh deleted file mode 100644 index d305ff524a7..00000000000 --- a/src/XrdCks/XrdCksData.hh +++ /dev/null @@ -1,125 +0,0 @@ -#ifndef __XRDCKSDATA_HH__ -#define __XRDCKSDATA_HH__ -/******************************************************************************/ -/* */ -/* X r d C k s D a t a . h h */ -/* */ -/* (c) 2011 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -class XrdCksData -{ -public: - -static const int NameSize = 16; // Max name length is NameSize - 1 -static const int ValuSize = 64; // Max value length is 512 bits - -char Name[NameSize]; // Checksum algorithm name -long long fmTime; // File's mtime when checksum was computed. -int csTime; // Delta from fmTime when checksum was computed. -short Rsvd1; // Reserved field -char Rsvd2; // Reserved field -char Length; // Length, in bytes, of the checksum value -char Value[ValuSize]; // The binary checksum value - -inline -int operator==(const XrdCksData &oth) - {return (!strncmp(Name, oth.Name, NameSize) - && Length == oth.Length - && !memcmp(Value, oth.Value, Length)); - } - -inline -int operator!=(const XrdCksData &oth) - {return (strncmp(Name, oth.Name, NameSize) - || Length != oth.Length - || memcmp(Value, oth.Value, Length)); - } - -int Get(char *Buff, int Blen) - {const char *hv = "0123456789abcdef"; - int i, j = 0; - if (Blen < Length*2+1) return 0; - for (i = 0; i < Length; i++) - {Buff[j++] = hv[(Value[i] >> 4) & 0x0f]; - Buff[j++] = hv[ Value[i] & 0x0f]; - } - Buff[j] = '\0'; - return Length*2; - } - -int Set(const char *csName) - {size_t len = strlen(csName); - if (len >= sizeof(Name)) return 0; - memcpy(Name, csName, len); - Name[len]=0; - return 1; - } - -int Set(const void *csVal, int csLen) - {if (csLen > ValuSize || csLen < 1) return 0; - memcpy(Value, csVal, csLen); - Length = csLen; - return 1; - } - -int Set(const char *csVal, int csLen) - {int n, i = 0, Odd = 0; - if (csLen > (int)sizeof(Value)*2 || (csLen & 1)) return 0; - Length = csLen/2; - while(csLen--) - { if (*csVal >= '0' && *csVal <= '9') n = *csVal-48; - else if (*csVal >= 'a' && *csVal <= 'f') n = *csVal-87; - else if (*csVal >= 'A' && *csVal <= 'F') n = *csVal-55; - else return 0; - if (Odd) Value[i++] |= n; - else Value[i ] = n << 4; - csVal++; Odd = ~Odd; - } - return 1; - } - - void Reset() - {memset(Name, 0, sizeof(Name)); - memset(Value,0, sizeof(Value)); - fmTime = 0; - csTime = 0; - Rsvd1 = 0; - Rsvd2 = 0; - Length = 0; - } - - XrdCksData() - {Reset();} - -bool HasValue() - { - return *Value; - } -}; -#endif diff --git a/src/XrdCks/XrdCksLoad.hh b/src/XrdCks/XrdCksLoad.hh deleted file mode 100644 index 7caaeccc4b1..00000000000 --- a/src/XrdCks/XrdCksLoad.hh +++ /dev/null @@ -1,112 +0,0 @@ -#ifndef __XRDCKSLOADER_HH__ -#define __XRDCKSLOADER_HH__ -/******************************************************************************/ -/* */ -/* X r d C k s L o a d e r . h h */ -/* */ -/* (c) 2012 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "sys/types.h" - -#include "XrdCks/XrdCks.hh" -#include "XrdCks/XrdCksData.hh" - -/* This class defines the checksum loader interface. It is intended to be used - by xrootd clients to obtain an instance of a checksum calculation object. - This object may be builtin or may come from a shared library. -*/ - -class XrdCksCalc; -class XrdSysError; -struct XrdVersionInfo; - -class XrdCksLoader : public XrdCks -{ -public: -virtual int Calc( const char *Pfn, XrdCksData &Cks, int doSet=1); - -virtual int Config(const char *Token, char *Line); - -virtual int Del( const char *Pfn, XrdCksData &Cks); - -virtual int Get( const char *Pfn, XrdCksData &Cks); - -virtual int Init(const char *ConfigFN, const char *AddCalc=0); - -virtual char *List(const char *Pfn, char *Buff, int Blen, char Sep=' '); - -virtual const char *Name(int seqNum=0); - -virtual XrdCksCalc *Object(const char *name); - -virtual int Size( const char *Name=0); - -virtual int Set( const char *Pfn, XrdCksData &Cks, int myTime=0); - -virtual int Ver( const char *Pfn, XrdCksData &Cks); - - XrdCksLoader(XrdSysError *erP, int iosz, - XrdVersionInfo *vInfo); -virtual ~XrdCksLoader(); - -protected: - -/* Calc() returns 0 if the checksum was successfully calculated using the - supplied CksObj and places the file's modification time in MTime. - Otherwise, it returns -errno. The default implementation uses - open(), fstat(), mmap(), and unmap() to calculate the results. -*/ -virtual int Calc(const char *Pfn, time_t &MTime, XrdCksCalc *CksObj); - -/* ModTime() returns 0 and places file's modification time in MTime. Otherwise, - it return -errno. The default implementation uses stat(). -*/ -virtual int ModTime(const char *Pfn, time_t &MTime); - -private: - -struct csInfo - {char Name[XrdCksData::NameSize]; - XrdCksCalc *Obj; - char *Path; - char *Parms; - XrdSysPlugin *Plugin; - int Len; - csInfo() : Obj(0), Path(0), Parms(0), Plugin(0), Len(0) - {memset(Name, 0, sizeof(Name));} - }; - -int Config(const char *cFN, csInfo &Info); -csInfo *Find(const char *Name); - -static const int csMax = 4; -csInfo csTab[csMax]; -int csLast; -int segSize; -XrdVersionInfo *myVersion; -}; -#endif diff --git a/src/XrdCks/XrdCksLoader.cc b/src/XrdCks/XrdCksLoader.cc deleted file mode 100644 index 6899b972d6f..00000000000 --- a/src/XrdCks/XrdCksLoader.cc +++ /dev/null @@ -1,212 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C k s L o a d e r . c c */ -/* */ -/* (c) 2012 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include - -#include "XrdCks/XrdCksCalc.hh" -#include "XrdCks/XrdCksCalcadler32.hh" -#include "XrdCks/XrdCksCalccrc32.hh" -#include "XrdCks/XrdCksCalcmd5.hh" -#include "XrdCks/XrdCksLoader.hh" - -#include "XrdOuc/XrdOucPinLoader.hh" - -#include "XrdSys/XrdSysPlugin.hh" -#include "XrdSys/XrdSysPthread.hh" - -#include "XrdVersion.hh" - -/******************************************************************************/ -/* C o n s t r u c t o r */ -/******************************************************************************/ - -XrdCksLoader::XrdCksLoader(XrdVersionInfo &vInfo, const char *libPath) -{ - static XrdVERSIONINFODEF(myVersion, XrdCks, XrdVNUMBER, XrdVERSION); - static const char libSfx[] = "/libXrdCksCalc%s.so"; - int k, n; - -// Verify that versions are compatible. -// - if (vInfo.vNum != myVersion.vNum - && !XrdSysPlugin::VerCmp(vInfo, myVersion, true)) - {char buff[1024]; - snprintf(buff, sizeof(buff), "Version %s is incompatible with %s.", - vInfo.vStr, myVersion.vStr); - verMsg = strdup(buff); urVersion = 0; - return; - } - urVersion = &vInfo; - verMsg = 0; - -// Prefill the native digests we support -// - csTab[0].Name = strdup("adler32"); - csTab[1].Name = strdup("crc32"); - csTab[2].Name = strdup("md5"); - csLast = 2; - -// Record the over-ride loader path -// - if (libPath) - {n = strlen(libPath); - ldPath = (char *)malloc(n+sizeof(libSfx)+1); - k = (libPath[n-1] == '/'); - strcpy(ldPath, libPath); - strcpy(ldPath+n, libSfx+k); - } else ldPath = strdup(libSfx+1); -} - -/******************************************************************************/ -/* D e s t r u c t o r */ -/******************************************************************************/ - -XrdCksLoader::~XrdCksLoader() -{ - int i; - for (i = 0; i <= csLast; i++) - {if (csTab[i].Name) free( csTab[i].Name); - if (csTab[i].Obj) csTab[i].Obj->Recycle(); - if (csTab[i].Plugin) delete csTab[i].Plugin; - } - if (ldPath) free(ldPath); - if (verMsg) free(verMsg); -} - -/******************************************************************************/ -/* L o a d */ -/******************************************************************************/ - -#define XRDOSSCKSLIBARGS XrdSysError *, const char *, const char *, const char * - -XrdCksCalc *XrdCksLoader::Load(const char *csName, const char *csParms, - char *eBuff, int eBlen, bool orig) -{ - static XrdSysMutex myMutex; - XrdSysMutexHelper ldMutex(myMutex); - XrdCksCalc *(*ep)(XRDOSSCKSLIBARGS); - XrdCksCalc *csObj; - XrdOucPinLoader *myPin; - csInfo *csIP; - char ldBuff[2048]; - int n; - -// Verify that version checking succeeded -// - if (verMsg) {if (eBuff) strncpy(eBuff, verMsg, eBlen); return 0;} - -// First check if we have loaded this before -// - if ((csIP = Find(csName))) - {if (!(csIP->Obj)) - { if (!strcmp("adler32", csIP->Name)) - csIP->Obj = new XrdCksCalcadler32; - else if (!strcmp("crc32", csIP->Name)) - csIP->Obj = new XrdCksCalccrc32; - else if (!strcmp("md5", csIP->Name)) - csIP->Obj = new XrdCksCalcmd5; - else {if (eBuff) snprintf(eBuff, eBlen, "Logic error configuring %s " - "checksum.", csName); - return 0; - } - } - return (orig ? csIP->Obj : csIP->Obj->New()); - } - -// Check if we can add a new entry -// - if (csLast+1 >= csMax) - {if (eBuff) strncpy(eBuff, "Maximum number of checksums loaded.", eBlen); - return 0; - } - -// Get the path where this object lives -// - snprintf(ldBuff, sizeof(ldBuff), ldPath, csName); - -// Get the plugin loader -// - if (!(myPin = new XrdOucPinLoader(eBuff,eBlen,urVersion,"ckslib",ldBuff))) - return 0; - -// Find the entry point -// - if (!(ep = (XrdCksCalc *(*)(XRDOSSCKSLIBARGS)) - (myPin->Resolve("XrdCksCalcInit")))) - {myPin->Unload(true); return 0;} - -// Get the initial object -// - if (!(csObj = ep(0, 0, csName, csParms))) - {if (eBuff) - snprintf(eBuff, eBlen, "%s checksum initialization failed.", csName); - myPin->Unload(true); - return 0; - } - -// Verify the object -// - if (strcmp(csName, csObj->Type(n))) - {if (eBuff) - snprintf(eBuff, eBlen, "%s cksum plugin returned wrong name - %s", - csName, csObj->Type(n)); - delete csObj; - myPin->Unload(true); - return 0; - } - -// Allocate a new entry in the table and initialize it -// - csLast++; - csTab[csLast].Name = strdup(csName); - csTab[csLast].Obj = csObj; - csTab[csLast].Plugin = myPin->Export(); - -// Return new instance of this object -// - return (orig ? csObj : csObj->New()); -} - -/******************************************************************************/ -/* F i n d */ -/******************************************************************************/ - -XrdCksLoader::csInfo *XrdCksLoader::Find(const char *Name) -{ - int i; - for (i = 0; i <= csLast; i++) - if (!strcmp(Name, csTab[i].Name)) return &csTab[i]; - return 0; -} diff --git a/src/XrdCks/XrdCksLoader.hh b/src/XrdCks/XrdCksLoader.hh deleted file mode 100644 index 09e9bc2d13f..00000000000 --- a/src/XrdCks/XrdCksLoader.hh +++ /dev/null @@ -1,116 +0,0 @@ -#ifndef __XRDCKSLOADER_HH__ -#define __XRDCKSLOADER_HH__ -/******************************************************************************/ -/* */ -/* X r d C k s L o a d e r . h h */ -/* */ -/* (c) 2012 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -class XrdCksCalc; -class XrdSysPlugin; -struct XrdVersionInfo; - -/*! This class defines the checksum loader interface. It is intended to be used - by xrootd clients to obtain an instance of a checksum calculation object. - This object may be builtin or may come from a shared library. -*/ - -class XrdCksLoader -{ -public: - -//------------------------------------------------------------------------------ -//! Get a new XrdCksCalc object that can calculate the checksum corresponding to -//! the specified name. The object can be used to compute checksums on the fly. -//! The object's Recycle() method must be used to delete it. The adler32, crc32, -//! and md5 checksums are natively supported. Up to five more checksum -//! algorithms can be loaded from shared libraries. -//! -//! @param csNme The name of the checksum algorithm (e.g. md5). -//! @param csParms Any parameters that might be needed by the checksum -//! algorithm should it be loaded from a shared library. -//! @param eBuff Optional pointer to a buffer to receive the reason for a -//! load failure as a null terminated string. -//! @param eBlen The length of the buffer. -//! @param orig Returns the original object not a new instance of it. -//! This is usually used by CksManager during an autoload. -//! -//! @return Success: A pointer to a new checksum calculation object. -//! Failure: Zero if the corresponding checksum object could not be -//! loaded. If eBuff was supplied, it holds the reason. -//------------------------------------------------------------------------------ - -XrdCksCalc *Load(const char *csName, const char *csParms=0, - char *eBuff=0, int eBlen=0, bool orig=false); - -//------------------------------------------------------------------------------ -//! Constructor -//! -//! @param vInfo Is the reference to the version information corresponding -//! to the xrootd version you compiled with. You define this -//! information using the XrdVERSIONINFODEF macro defined in -//! XrdVersion.hh. You must supply your version information -//! and it must be compatible with the loader and any shared -//! libraries that it might load on your behalf. -//! -//! @param libPath The path where dynamic checksum calculators are to be -//! found and dynamically loaded, if need be. If libPath is -//! nil then the default loader search order is used. -//! The name of the shared library must follow the naming -//! convention "libXrdCksCalc.so" where is the -//! checksum name. So, an sha256 checksum would try to load -//! libXrdCksCalcsha256.so shared library. -//------------------------------------------------------------------------------ - - XrdCksLoader(XrdVersionInfo &vInfo, const char *libPath=0); - -//------------------------------------------------------------------------------ -//! Destructor -//------------------------------------------------------------------------------ - - ~XrdCksLoader(); - -private: - -struct csInfo - {char *Name; - XrdCksCalc *Obj; - XrdSysPlugin *Plugin; - csInfo() : Name(0), Obj(0), Plugin(0) {} - ~csInfo() {} - }; - -csInfo *Find(const char *Name); - -char *verMsg; // This member must be the 1st member -XrdVersionInfo *urVersion; // This member must be the 2nd member -char *ldPath; -static const int csMax = 8; - csInfo csTab[csMax]; - int csLast; -}; -#endif diff --git a/src/XrdCks/XrdCksManOss.cc b/src/XrdCks/XrdCksManOss.cc deleted file mode 100644 index 15885492954..00000000000 --- a/src/XrdCks/XrdCksManOss.cc +++ /dev/null @@ -1,268 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C k s M a n O s s . c c */ -/* */ -/* (c) 2014 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "XrdCks/XrdCksCalc.hh" -#include "XrdCks/XrdCksManOss.hh" -#include "XrdOss/XrdOss.hh" -#include "XrdOuc/XrdOucEnv.hh" -#include "XrdSys/XrdSysError.hh" - -/******************************************************************************/ -/* L o c a l S t a t i c s */ -/******************************************************************************/ - -namespace -{ -XrdOss *ossP = 0; -int rdSz = 67108864; -} - -/******************************************************************************/ -/* L o c a l C l a s s e s */ -/******************************************************************************/ - -class LfnPfn - {public: - const char *Lfn; - char Pfn[MAXPATHLEN+8]; - - LfnPfn(const char *lfn, int &rc) : Lfn(lfn) - {rc = ossP->Lfn2Pfn(lfn, Pfn, MAXPATHLEN); - if (rc > 0) rc = -rc; - } - ~LfnPfn() {} - }; - -namespace -{ -const char *Pfn2Lfn(const char *Lfn) - {LfnPfn *Xfn = (LfnPfn *)(Lfn-sizeof(const char *)); - return Xfn->Lfn; - } -} - -/******************************************************************************/ -/* C o n s t r u c t o r */ -/******************************************************************************/ - -XrdCksManOss::XrdCksManOss(XrdOss *ossX, XrdSysError *erP, int iosz, - XrdVersionInfo &vInfo, bool autoload) - : XrdCksManager(erP, rdSz, vInfo, autoload) - {if (rdSz <= 65536) rdSz = 67108864; - else rdSz = ((rdSz/65536) + (rdSz%65536 != 0)) * 65536; - eDest = erP; - ossP = ossX; - } - -/******************************************************************************/ -/* C a l c */ -/******************************************************************************/ - -int XrdCksManOss::Calc(const char *Lfn, XrdCksData &Cks, int doSet) -{ - int rc; - LfnPfn Xfn(Lfn, rc); - -// If lfn conversion failed, bail out -// - if (rc) return rc; - -// Return the result -// - return XrdCksManager::Calc(Xfn.Pfn, Cks, doSet); -} - -/******************************************************************************/ - -int XrdCksManOss::Calc(const char *Pfn, time_t &MTime, XrdCksCalc *csP) -{ - class inFile - {public: - XrdOssDF *fP; - inFile() {fP = ossP->newFile("ckscalc");} - ~inFile() {if (fP) delete fP;} - } In; - XrdOucEnv openEnv; - const char *Lfn = Pfn2Lfn(Pfn); - struct stat Stat; - char *buffP; - off_t Offset=0, fileSize; - size_t ioSize, calcSize; - int rc; - -// Open the input file -// - if ((rc = In.fP->Open(Lfn,O_RDONLY,0,openEnv))) return (rc > 0 ? -rc : rc); - -// Get the file characteristics -// - if ((rc = In.fP->Fstat(&Stat))) return (rc > 0 ? -rc : rc); - if (!(Stat.st_mode & S_IFREG)) return -EPERM; - calcSize = fileSize = Stat.st_size; - MTime = Stat.st_mtime; - -// Compute read size and allocate a buffer -// - ioSize = (fileSize < (off_t)rdSz ? fileSize : rdSz); rc = 0; - buffP = (char *)malloc(ioSize); - if (!buffP) return -ENOMEM; - -// We now compute checksum 64MB at a time using mmap I/O -// - while(calcSize) - {if ((rc= In.fP->Read(buffP, Offset, ioSize)) < 0) break; - csP->Update(buffP, ioSize); - calcSize -= ioSize; Offset += ioSize; - if (calcSize < (size_t)ioSize) ioSize = calcSize; - } - -// Issue error message if we have an error -// - if (rc < 0) eDest->Emsg("Cks", rc, "read", Pfn); - -// Return -// - return (rc < 0 ? rc : 0); -} - -/******************************************************************************/ -/* D e l */ -/******************************************************************************/ - -int XrdCksManOss::Del(const char *Lfn, XrdCksData &Cks) -{ - int rc; - LfnPfn Xfn(Lfn, rc); - -// If lfn conversion failed, bail out -// - if (rc) return rc; - -// Delete the attribute and return the result -// - return XrdCksManager::Del(Xfn.Pfn, Cks); -} - -/******************************************************************************/ -/* G e t */ -/******************************************************************************/ - -int XrdCksManOss::Get(const char *Lfn, XrdCksData &Cks) -{ - int rc; - LfnPfn Xfn(Lfn, rc); - -// If lfn conversion failed, bail out -// - if (rc) return rc; - -// Return result -// - return XrdCksManager::Get(Xfn.Pfn, Cks); -} - -/******************************************************************************/ -/* L i s t */ -/******************************************************************************/ - -char *XrdCksManOss::List(const char *Lfn, char *Buff, int Blen, char Sep) -{ - int rc; - LfnPfn Xfn(Lfn, rc); - -// If lfn conversion failed, bail out -// - if (rc) return 0; - -// Simply invoke the base class list -// - return XrdCksManager::List(Xfn.Pfn, Buff, Blen,Sep); -} - -/******************************************************************************/ -/* M o d T i m e */ -/******************************************************************************/ - -int XrdCksManOss::ModTime(const char *Pfn, time_t &MTime) -{ - const char *Lfn = Pfn2Lfn(Pfn); - struct stat Stat; - int rc; - - if (!(rc = ossP->Stat(Lfn, &Stat))) MTime = Stat.st_mtime; - - return (rc > 0 ? -rc : 0); -} - -/******************************************************************************/ -/* S e t */ -/******************************************************************************/ - -int XrdCksManOss::Set(const char *Lfn, XrdCksData &Cks, int myTime) -{ - int rc; - LfnPfn Xfn(Lfn, rc); - -// If lfn conversion failed, bail out -// - if (rc) return rc; - -// Now set the checksum information in the extended attribute object -// - return XrdCksManager::Set(Xfn.Pfn, Cks, myTime); -} - -/******************************************************************************/ -/* V e r */ -/******************************************************************************/ - -int XrdCksManOss::Ver(const char *Lfn, XrdCksData &Cks) -{ - int rc; - LfnPfn Xfn(Lfn, rc); - -// If lfn conversion failed, bail out -// - if (rc) return rc; - -// Return result invoking the base class -// - return XrdCksManager::Ver(Lfn, Cks); -} diff --git a/src/XrdCks/XrdCksManOss.hh b/src/XrdCks/XrdCksManOss.hh deleted file mode 100644 index 5005d4902a6..00000000000 --- a/src/XrdCks/XrdCksManOss.hh +++ /dev/null @@ -1,71 +0,0 @@ -#ifndef __XRDCKSMANOSS_HH__ -#define __XRDCKSMANOSS_HH__ -/******************************************************************************/ -/* */ -/* X r d k s M a n O s s . h h */ -/* */ -/* (c) 2014 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "sys/types.h" - -#include "XrdCks/XrdCksManager.hh" - -/* This class defines the checksum management interface using the oss plugin. - It is used internally to provide checksums for oss-based storage systems. -*/ - -class XrdOss; - -class XrdCksManOss : public XrdCksManager -{ -public: -virtual int Calc(const char *Lfn, XrdCksData &Cks, int doSet=1); - -virtual int Del( const char *Lfn, XrdCksData &Cks); - -virtual int Get( const char *Lfn, XrdCksData &Cks); - -virtual char *List(const char *Lfn, char *Buff, int Blen, char Sep=' '); - -virtual int Set( const char *Lfn, XrdCksData &Cks, int myTime=0); - -virtual int Ver( const char *Lfn, XrdCksData &Cks); - - XrdCksManOss(XrdOss *ossX, XrdSysError *erP, int iosz, - XrdVersionInfo &vInfo, bool autoload=false); - -virtual ~XrdCksManOss() {} - -protected: -virtual int Calc(const char *Lfn, time_t &MTime, XrdCksCalc *CksObj); -virtual int ModTime(const char *Pfn, time_t &MTime); - -private: - -int buffSZ; -}; -#endif diff --git a/src/XrdCks/XrdCksManager.cc b/src/XrdCks/XrdCksManager.cc deleted file mode 100644 index fda2512fb39..00000000000 --- a/src/XrdCks/XrdCksManager.cc +++ /dev/null @@ -1,640 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d O s s C k s M a n a g e r . c c */ -/* */ -/* (c) 2011 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "XrdCks/XrdCksCalc.hh" -#include "XrdCks/XrdCksCalcadler32.hh" -#include "XrdCks/XrdCksCalccrc32.hh" -#include "XrdCks/XrdCksCalcmd5.hh" -#include "XrdCks/XrdCksLoader.hh" -#include "XrdCks/XrdCksManager.hh" -#include "XrdCks/XrdCksXAttr.hh" -#include "XrdOuc/XrdOucPinLoader.hh" -#include "XrdOuc/XrdOucTokenizer.hh" -#include "XrdOuc/XrdOucUtils.hh" -#include "XrdOuc/XrdOucXAttr.hh" -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysFAttr.hh" -#include "XrdSys/XrdSysPlugin.hh" -#include "XrdSys/XrdSysPthread.hh" - -/******************************************************************************/ -/* C o n s t r u c t o r */ -/******************************************************************************/ - -XrdCksManager::XrdCksManager(XrdSysError *erP, int rdsz, XrdVersionInfo &vInfo, - bool autoload) - : XrdCks(erP), myVersion(vInfo) -{ - -// Get a dynamic loader if so wanted -// - if (autoload) cksLoader = new XrdCksLoader(vInfo); - else cksLoader = 0; - -// Prefill the native digests we support -// - strcpy(csTab[0].Name, "adler32"); - strcpy(csTab[1].Name, "crc32"); - strcpy(csTab[2].Name, "md5"); - csLast = 2; - -// Compute the i/o size -// - if (rdsz <= 65536) segSize = 67108864; - else segSize = ((rdsz/65536) + (rdsz%65536 != 0)) * 65536; -} - -/******************************************************************************/ -/* D e s t r u c t o r */ -/******************************************************************************/ - -XrdCksManager::~XrdCksManager() -{ - int i; - for (i = 0; i <= csLast; i++) - {if (csTab[i].Obj && csTab[i].doDel) csTab[i].Obj->Recycle(); - if (csTab[i].Path) free( csTab[i].Path); - if (csTab[i].Parms) free( csTab[i].Parms); - if (csTab[i].Plugin) delete csTab[i].Plugin; - } - if (cksLoader) delete cksLoader; -} - -/******************************************************************************/ -/* C a l c */ -/******************************************************************************/ - -int XrdCksManager::Calc(const char *Pfn, XrdCksData &Cks, int doSet) -{ - XrdCksCalc *csP; - csInfo *csIP = &csTab[0]; - time_t MTime; - int rc; - -// Determine which checksum to get -// - if (csLast < 0) return -ENOTSUP; - if (!(*Cks.Name)) Cks.Set(csIP->Name); - else if (!(csIP = Find(Cks.Name))) return -ENOTSUP; - -// If we need not set the checksum then see if we can get it from the -// extended attributes. - -// Obtain a new checksum object -// - if (!(csP = csIP->Obj->New())) return -ENOMEM; - -// Use the calculator to get and possibly set the checksum -// - if (!(rc = Calc(Pfn, MTime, csP))) - {memcpy(Cks.Value, csP->Final(), csIP->Len); - Cks.fmTime = static_cast(MTime); - Cks.csTime = static_cast(time(0) - MTime); - Cks.Length = csIP->Len; - csP->Recycle(); - if (doSet) - {XrdOucXAttr xCS; - memcpy(&xCS.Attr.Cks, &Cks, sizeof(xCS.Attr.Cks)); - if ((rc = xCS.Set(Pfn))) return -rc; - } - } - -// All done -// - return rc; -} - -/******************************************************************************/ - -int XrdCksManager::Calc(const char *Pfn, time_t &MTime, XrdCksCalc *csP) -{ - class ioFD - {public: - int FD; - ioFD() : FD(-1) {} - ~ioFD() {if (FD >= 0) close(FD);} - } In; - struct stat Stat; - char *inBuff; - off_t Offset=0, fileSize; - size_t ioSize, calcSize; - int rc; - -// Open the input file -// - if ((In.FD = open(Pfn, O_RDONLY)) < 0) return -errno; - -// Get the file characteristics -// - if (fstat(In.FD, &Stat)) return -errno; - if (!(Stat.st_mode & S_IFREG)) return -EPERM; - calcSize = fileSize = Stat.st_size; - MTime = Stat.st_mtime; - -// We now compute checksum 64MB at a time using mmap I/O -// - ioSize = (fileSize < (off_t)segSize ? fileSize : segSize); rc = 0; - while(calcSize) - {if ((inBuff = (char *)mmap(0, ioSize, PROT_READ, - MAP_NORESERVE|MAP_PRIVATE, In.FD, Offset)) == MAP_FAILED) - {rc = errno; eDest->Emsg("Cks", rc, "memory map", Pfn); break;} - madvise(inBuff, ioSize, MADV_SEQUENTIAL); - csP->Update(inBuff, ioSize); - calcSize -= ioSize; Offset += ioSize; - if (munmap(inBuff, ioSize) < 0) - {rc = errno; eDest->Emsg("Cks",rc,"unmap memory for",Pfn); break;} - if (calcSize < (size_t)segSize) ioSize = calcSize; - } - -// Return if we failed -// - if (calcSize) return (rc ? -rc : -EIO); - return 0; -} - -/******************************************************************************/ -/* C o n f i g */ -/******************************************************************************/ -/* - Purpose: To parse the directive: ckslib [] - - the name of the checksum. - the path of the checksum library to be used. - optional parms to be passed - - Output: 0 upon success or !0 upon failure. -*/ -int XrdCksManager::Config(const char *Token, char *Line) -{ - XrdOucTokenizer Cfg(Line); - char *val, *path = 0, name[XrdCksData::NameSize], *parms; - int i; - -// Get the the checksum name -// - Cfg.GetLine(); - if (!(val = Cfg.GetToken()) || !val[0]) - {eDest->Emsg("Config", "checksum name not specified"); return 1;} - if (int(strlen(val)) >= XrdCksData::NameSize) - {eDest->Emsg("Config", "checksum name too long"); return 1;} - strcpy(name, val); XrdOucUtils::toLower(name); - -// Get the path and optional parameters -// - val = Cfg.GetToken(&parms); - if (val && val[0]) path = strdup(val); - else {eDest->Emsg("Config","library path missing for ckslib digest",name); - return 1; - } - -// Check if this replaces an existing checksum -// - for (i = 0; i < csMax; i++) - if (!(*csTab[i].Name) || !strcmp(csTab[i].Name, name)) break; - -// See if we can insert a new checksum (or replace one) -// - if (i >= csMax) - {eDest->Emsg("Config", "too many checksums specified"); - if (path) free(path); - return 1; - } else if (!(*csTab[i].Name)) csLast = i; - -// Insert the new checksum -// - strcpy(csTab[i].Name, name); - if (csTab[i].Path) free(csTab[i].Path); - csTab[i].Path = path; - if (csTab[i].Parms) free(csTab[i].Parms); - csTab[i].Parms = (parms && *parms ? strdup(parms) : 0); - -// All done -// - return 0; -} - -/******************************************************************************/ -/* I n i t */ -/******************************************************************************/ - -int XrdCksManager::Init(const char *ConfigFN, const char *DfltCalc) -{ - int i; - -// See if we need to set the default calculation -// - if (DfltCalc) - {for (i = 0; i < csLast; i++) if (!strcmp(csTab[i].Name, DfltCalc)) break; - if (i >= csMax) - {eDest->Emsg("Config", DfltCalc, "cannot be made the default; " - "not supported."); - return 0; - } - if (i) {csInfo Temp = csTab[i]; csTab[i] = csTab[0]; csTab[0] = Temp;} - } - -// See if there are any chacksums to configure -// - if (csLast < 0) - {eDest->Emsg("Config", "No checksums defined; cannot configure!"); - return 0; - } - -// Complete the checksum table -// - for (i = 0; i <= csLast; i++) - {if (csTab[i].Path) {if (!(Config(ConfigFN, csTab[i]))) return 0;} - else { if (!strcmp("adler32", csTab[i].Name)) - csTab[i].Obj = new XrdCksCalcadler32; - else if (!strcmp("crc32", csTab[i].Name)) - csTab[i].Obj = new XrdCksCalccrc32; - else if (!strcmp("md5", csTab[i].Name)) - csTab[i].Obj = new XrdCksCalcmd5; - else {eDest->Emsg("Config", "Invalid native checksum -", - csTab[i].Name); - return 0; - } - csTab[i].Obj->Type(csTab[i].Len); - } - } - -// All done -// - return 1; -} - -/******************************************************************************/ - -#define XRDOSSCKSLIBARGS XrdSysError *, const char *, const char *, const char * - -int XrdCksManager::Config(const char *cFN, csInfo &Info) -{ - XrdOucPinLoader myPin(eDest, &myVersion, "ckslib", Info.Path); - XrdCksCalc *(*ep)(XRDOSSCKSLIBARGS); - int n; - -// Find the entry point -// - Info.Plugin = 0; - if (!(ep = (XrdCksCalc *(*)(XRDOSSCKSLIBARGS)) - (myPin.Resolve("XrdCksCalcInit")))) - {eDest->Emsg("Config", "Unable to configure cksum", Info.Name); - myPin.Unload(); - return 0; - } - -// Get the initial object -// - if (!(Info.Obj = ep(eDest,cFN,Info.Name,(Info.Parms ? Info.Parms : "")))) - {eDest->Emsg("Config", Info.Name, "checksum initialization failed"); - myPin.Unload(); - return 0; - } - -// Verify the object -// - if (strcmp(Info.Name, Info.Obj->Type(n))) - {eDest->Emsg("Config",Info.Name,"cksum plugin returned wrong name -", - Info.Obj->Type(n)); - myPin.Unload(); - return 0; - } - if (n > XrdCksData::ValuSize || n <= 0) - {eDest->Emsg("Config",Info.Name,"cksum plugin has an unsupported " - "checksum length"); - myPin.Unload(); - return 0; - } - -// All is well -// - Info.Plugin = myPin.Export(); - Info.Len = n; - return 1; -} - -/******************************************************************************/ -/* F i n d */ -/******************************************************************************/ - -XrdCksManager::csInfo *XrdCksManager::Find(const char *Name) -{ - static XrdSysMutex myMutex; - XrdCksCalc *myCalc; - int i; - -// Find the pre-loaded checksum -// - for (i = 0; i <= csLast; i++) - if (!strcmp(Name, csTab[i].Name)) return &csTab[i]; - -// If we have loader see if we can auto-load this object -// - if (!cksLoader) return 0; - myMutex.Lock(); - -// An entry could have been added as we were running unlocked -// - for (i = 0; i <= csLast; i++) - if (!strcmp(Name, csTab[i].Name)) - {myMutex.UnLock(); - return &csTab[i]; - } - -// Check if we have room in the table -// - if (csLast >= csMax) - {myMutex.UnLock(); - eDest->Emsg("CksMan","Unable to load",Name,"; checksum limit reached."); - return 0; - } - -// Attempte to dynamically load this object -// -{ char buff[2048]; - *buff = 0; - if (!(myCalc = cksLoader->Load(Name, 0, buff, sizeof(buff), true))) - {myMutex.UnLock(); - eDest->Emsg("CksMan", "Unable to load", Name); - if (*buff) eDest->Emsg("CksMan", buff); - return 0; - } -} - -// Fill out the table -// - i = csLast + 1; - strncpy(csTab[i].Name, Name, XrdCksData::NameSize); - csTab[i].Obj = myCalc; - csTab[i].Path = 0; - csTab[i].Parms = 0; - csTab[i].Plugin = 0; - csTab[i].doDel = false; - myCalc->Type(csTab[i].Len); - -// Return the result -// - csLast = i; - myMutex.UnLock(); - return &csTab[i]; -} - -/******************************************************************************/ -/* D e l */ -/******************************************************************************/ - -int XrdCksManager::Del(const char *Pfn, XrdCksData &Cks) -{ - XrdOucXAttr xCS; - -// Set the checksum name -// - xCS.Attr.Cks.Set(Cks.Name); - -// Delete the attribute and return the result -// - return xCS.Del(Pfn); -} - -/******************************************************************************/ -/* G e t */ -/******************************************************************************/ - -int XrdCksManager::Get(const char *Pfn, XrdCksData &Cks) -{ - XrdOucXAttr xCS; - time_t MTime; - int rc, nFault; - -// Determine which checksum to get (we will accept unsupported ones as well) -// - if (csLast < 0) return -ENOTSUP; - if (!*Cks.Name) Cks.Set(csTab[0].Name); - if (!xCS.Attr.Cks.Set(Cks.Name)) return -ENOTSUP; - -// Retreive the attribute -// - if ((rc = xCS.Get(Pfn)) <= 0) return (rc ? rc : -ESRCH); - -// Mark state of the name and copy the attribute over -// - nFault = strcmp(xCS.Attr.Cks.Name, Cks.Name); - Cks = xCS.Attr.Cks; - -// Verify the file -// - if ((rc = ModTime(Pfn, MTime))) return rc; - -// Return result -// - return (Cks.fmTime != MTime || nFault - || Cks.Length > XrdCksData::ValuSize || Cks.Length <= 0 - ? -ESTALE : int(Cks.Length)); -} - -/******************************************************************************/ -/* L i s t */ -/******************************************************************************/ - -char *XrdCksManager::List(const char *Pfn, char *Buff, int Blen, char Sep) -{ - static const char *vPfx = "XrdCks."; - static const int vPln = strlen(vPfx); - XrdSysFAttr::AList *vP, *axP = 0; - char *bP = Buff; - int i, n; - -// Verify that the buffer is large enough -// - if (Blen < 2) return 0; - -// Check if the default list is wanted -// - if (!Pfn) - {if (csLast < 0) return 0; - i = 0; - while(i <= csLast && Blen > 1) - {n = strlen(csTab[i].Name); - if (n >= Blen) break; - if (bP != Buff) *bP++ = Sep; - strcpy(bP, csTab[i].Name); bP += n; *bP = 0; - } - return (bP == Buff ? 0 : Buff); - } - -// Get a list of attributes for this file -// - if (XrdSysFAttr::Xat->List(&axP, Pfn) < 0 || !axP) return 0; - -// Go through the list extracting what we are looking for -// - vP = axP; - while(vP) - {if (vP->Nlen > vPln && !strncmp(vP->Name, vPfx, vPln)) - {n = vP->Nlen - vPln; - if (n >= Blen) break; - if (bP != Buff) *bP++ = Sep; - strcpy(bP, vP->Name + vPln); bP += n; *bP = 0; - } - vP = vP->Next; - } - -// All done -// - XrdSysFAttr::Xat->Free(axP); - return (bP == Buff ? 0 : Buff); -} - -/******************************************************************************/ -/* M o d T i m e */ -/******************************************************************************/ - -int XrdCksManager::ModTime(const char *Pfn, time_t &MTime) -{ - struct stat Stat; - - if (stat(Pfn, &Stat)) return -errno; - - MTime = Stat.st_mtime; - return 0; -} - -/******************************************************************************/ -/* N a m e */ -/******************************************************************************/ - -const char *XrdCksManager::Name(int seqNum) -{ - - return (seqNum < 0 || seqNum > csLast ? 0 : csTab[seqNum].Name); -} - -/******************************************************************************/ -/* O b j e c t */ -/******************************************************************************/ - -XrdCksCalc *XrdCksManager::Object(const char *name) -{ - csInfo *csIP = &csTab[0]; - -// Return an object it at all possible -// - if (name && !(csIP = Find(name))) return 0; - return csIP->Obj->New(); -} - -/******************************************************************************/ -/* S i z e */ -/******************************************************************************/ - -int XrdCksManager::Size(const char *Name) -{ - csInfo *iP = (Name != 0 ? Find(Name) : &csTab[0]); - return (iP != 0 ? iP->Len : 0); -} - -/******************************************************************************/ -/* S e t */ -/******************************************************************************/ - -int XrdCksManager::Set(const char *Pfn, XrdCksData &Cks, int myTime) -{ - XrdOucXAttr xCS; - csInfo *csIP = &csTab[0]; - -// Verify the incomming checksum for correctness -// - if (csLast < 0 || (*Cks.Name && !(csIP = Find(Cks.Name)))) return -ENOTSUP; - if (Cks.Length != csIP->Len) return -EDOM; - memcpy(&xCS.Attr.Cks, &Cks, sizeof(xCS.Attr.Cks)); - -// Set correct times if need be -// - if (!myTime) - {time_t MTime; - int rc = ModTime(Pfn, MTime); - if (rc) return rc; - xCS.Attr.Cks.fmTime = static_cast(MTime); - xCS.Attr.Cks.csTime = static_cast(time(0) - MTime); - } - -// Now set the checksum information in the extended attribute object -// - return xCS.Set(Pfn); -} - -/******************************************************************************/ -/* V e r */ -/******************************************************************************/ - -int XrdCksManager::Ver(const char *Pfn, XrdCksData &Cks) -{ - XrdOucXAttr xCS; - time_t MTime; - csInfo *csIP = &csTab[0]; - int rc; - -// Determine which checksum to get -// - if (csLast < 0 || (*Cks.Name && !(csIP = Find(Cks.Name)))) return -ENOTSUP; - xCS.Attr.Cks.Set(csIP->Name); - -// Verify the file -// - if ((rc = ModTime(Pfn, MTime))) return rc; - -// Retreive the attribute. Return upon fatal error. -// - if ((rc = xCS.Get(Pfn)) < 0) return rc; - -// Verify the checksum and see if we need to recalculate it -// - if (!rc || xCS.Attr.Cks.fmTime != MTime - || strcmp(xCS.Attr.Cks.Name, csIP->Name) - || xCS.Attr.Cks.Length != csIP->Len) - {strcpy(xCS.Attr.Cks.Name, Cks.Name); - if ((rc = Calc(Pfn, xCS.Attr.Cks, 1)) < 0) return rc; - } - -// Compare the checksums -// - return (xCS.Attr.Cks.Length == Cks.Length - && !memcmp(xCS.Attr.Cks.Value, Cks.Value, csIP->Len)); -} diff --git a/src/XrdCks/XrdCksManager.hh b/src/XrdCks/XrdCksManager.hh deleted file mode 100644 index 0c03b210ca9..00000000000 --- a/src/XrdCks/XrdCksManager.hh +++ /dev/null @@ -1,116 +0,0 @@ -#ifndef __XRDCKSMANAGER_HH__ -#define __XRDCKSMANAGER_HH__ -/******************************************************************************/ -/* */ -/* X r d C k s M a n a g e r . h h */ -/* */ -/* (c) 2011 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "sys/types.h" - -#include "XrdCks/XrdCks.hh" -#include "XrdCks/XrdCksData.hh" - -/* This class defines the checksum management interface. It may also be used - as the base class for a plugin. This allows you to replace selected methods - which may be needed for handling certain filesystems (see protected ones). -*/ - -class XrdCksCalc; -class XrdCksLoader; -class XrdSysError; -struct XrdVersionInfo; - -class XrdCksManager : public XrdCks -{ -public: -virtual int Calc( const char *Pfn, XrdCksData &Cks, int doSet=1); - -virtual int Config(const char *Token, char *Line); - -virtual int Del( const char *Pfn, XrdCksData &Cks); - -virtual int Get( const char *Pfn, XrdCksData &Cks); - -virtual int Init(const char *ConfigFN, const char *AddCalc=0); - -virtual char *List(const char *Pfn, char *Buff, int Blen, char Sep=' '); - -virtual const char *Name(int seqNum=0); - -virtual XrdCksCalc *Object(const char *name); - -virtual int Size( const char *Name=0); - -virtual int Set( const char *Pfn, XrdCksData &Cks, int myTime=0); - -virtual int Ver( const char *Pfn, XrdCksData &Cks); - - XrdCksManager(XrdSysError *erP, int iosz, - XrdVersionInfo &vInfo, bool autoload=false); -virtual ~XrdCksManager(); - -protected: - -/* Calc() returns 0 if the checksum was successfully calculated using the - supplied CksObj and places the file's modification time in MTime. - Otherwise, it returns -errno. The default implementation uses - open(), fstat(), mmap(), and unmap() to calculate the results. -*/ -virtual int Calc(const char *Pfn, time_t &MTime, XrdCksCalc *CksObj); - -/* ModTime() returns 0 and places file's modification time in MTime. Otherwise, - it return -errno. The default implementation uses stat(). -*/ -virtual int ModTime(const char *Pfn, time_t &MTime); - -private: - -struct csInfo - {char Name[XrdCksData::NameSize]; - XrdCksCalc *Obj; - char *Path; - char *Parms; - XrdSysPlugin *Plugin; - int Len; - bool doDel; - csInfo() : Obj(0), Path(0), Parms(0), Plugin(0), Len(0), - doDel(true) - {memset(Name, 0, sizeof(Name));} - }; - -int Config(const char *cFN, csInfo &Info); -csInfo *Find(const char *Name); - -static const int csMax = 8; -csInfo csTab[csMax]; -int csLast; -int segSize; -XrdCksLoader *cksLoader; -XrdVersionInfo &myVersion; -}; -#endif diff --git a/src/XrdCks/XrdCksXAttr.hh b/src/XrdCks/XrdCksXAttr.hh deleted file mode 100644 index e7d6fe96e94..00000000000 --- a/src/XrdCks/XrdCksXAttr.hh +++ /dev/null @@ -1,90 +0,0 @@ -#ifndef __XRDCKSXATTR_HH__ -#define __XRDCKSXATTR_HH__ -/******************************************************************************/ -/* */ -/* X r d C k s X A t t r . h h */ -/* */ -/* (c) 2011 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include - -#include "XrdCks/XrdCksData.hh" -#include "XrdSys/XrdSysPlatform.hh" - -/* XrdCksXAttr encapsulates the extended attributes needed to save a checksum. -*/ - -class XrdCksXAttr -{ -public: - -XrdCksData Cks; // Check sum information - -/* postGet() will put fmTime and csTime in host byte order (see preSet()). -*/ - int postGet(int Result) - {if (Result > 0) - {Cks.fmTime = ntohll(Cks.fmTime); - Cks.csTime = ntohl (Cks.csTime); - } - return Result; - } - -/* preSet() will put fmTime and csTime in network byte order to allow the - attribute to be copied to different architectures and still work. -*/ - XrdCksXAttr *preSet(XrdCksXAttr &tmp) - {memcpy(&tmp.Cks, &Cks, sizeof(Cks)); - tmp.Cks.fmTime = htonll(Cks.fmTime); - tmp.Cks.csTime = htonl (Cks.csTime); - return &tmp; - } - -/* Name() returns the extended attribute name for this object. -*/ - const char *Name() {if (!(*VarName)) //01234567 - {strcpy(VarName, "XrdCks."); - strcpy(VarName+7, Cks.Name); - } - return VarName; - } - -/* sizeGet() and sizeSet() return the actual size of the object is used. -*/ - int sizeGet() {return sizeof(Cks);} - int sizeSet() {return sizeof(Cks);} - - XrdCksXAttr() {*VarName = 0;} - ~XrdCksXAttr() {} - -private: - -char VarName[XrdCksData::NameSize+8]; -}; -#endif diff --git a/src/XrdCl/CMakeLists.txt b/src/XrdCl/CMakeLists.txt deleted file mode 100644 index 0f44bb1d84a..00000000000 --- a/src/XrdCl/CMakeLists.txt +++ /dev/null @@ -1,182 +0,0 @@ - -include( XRootDCommon ) - -#------------------------------------------------------------------------------- -# Shared library version -#------------------------------------------------------------------------------- -set( XRD_CL_VERSION 2.0.0 ) -set( XRD_CL_SOVERSION 2 ) - -#------------------------------------------------------------------------------- -# The XrdCl lib -#------------------------------------------------------------------------------- -add_library( - XrdCl - SHARED - XrdClLog.cc XrdClLog.hh - XrdClUtils.cc XrdClUtils.hh - XrdClOptimizers.hh - XrdClConstants.hh - XrdClEnv.cc XrdClEnv.hh - XrdClDefaultEnv.cc XrdClDefaultEnv.hh - XrdClURL.cc XrdClURL.hh - XrdClStatus.cc XrdClStatus.hh - XrdClSocket.cc XrdClSocket.hh - XrdClPoller.hh - XrdClPollerFactory.cc XrdClPollerFactory.hh - XrdClPollerBuiltIn.cc XrdClPollerBuiltIn.hh - XrdClPostMaster.cc XrdClPostMaster.hh - XrdClPostMasterInterfaces.hh - XrdClChannel.cc XrdClChannel.hh - XrdClStream.cc XrdClStream.hh - XrdClXRootDTransport.cc XrdClXRootDTransport.hh - XrdClInQueue.cc XrdClInQueue.hh - XrdClOutQueue.cc XrdClOutQueue.hh - XrdClTaskManager.cc XrdClTaskManager.hh - XrdClSIDManager.cc XrdClSIDManager.hh - XrdClFileSystem.cc XrdClFileSystem.hh - XrdClXRootDMsgHandler.cc XrdClXRootDMsgHandler.hh - XrdClBuffer.hh - XrdClMessage.hh - XrdClMessageUtils.cc XrdClMessageUtils.hh - XrdClXRootDResponses.cc XrdClXRootDResponses.hh - XrdClRequestSync.hh - XrdClFile.cc XrdClFile.hh - XrdClFileStateHandler.cc XrdClFileStateHandler.hh - XrdClCopyProcess.cc XrdClCopyProcess.hh - XrdClClassicCopyJob.cc XrdClClassicCopyJob.hh - XrdClThirdPartyCopyJob.cc XrdClThirdPartyCopyJob.hh - XrdClAsyncSocketHandler.cc XrdClAsyncSocketHandler.hh - XrdClChannelHandlerList.cc XrdClChannelHandlerList.hh - XrdClForkHandler.cc XrdClForkHandler.hh - XrdClCheckSumManager.cc XrdClCheckSumManager.hh - XrdClTransportManager.cc XrdClTransportManager.hh - XrdClSyncQueue.hh - XrdClJobManager.cc XrdClJobManager.hh - XrdClResponseJob.hh - XrdClFileTimer.cc XrdClFileTimer.hh - XrdClUglyHacks.hh - XrdClPlugInInterface.hh - XrdClPlugInManager.cc XrdClPlugInManager.hh - XrdClPropertyList.hh - XrdClCopyJob.hh - XrdClFileSystemUtils.cc XrdClFileSystemUtils.hh - XrdClTPFallBackCopyJob.cc XrdClTPFallBackCopyJob.hh - XrdClMetalinkRedirector.cc XrdClMetalinkRedirector.hh - XrdClRedirectorRegistry.cc XrdClRedirectorRegistry.hh - XrdClZipArchiveReader.cc XrdClZipArchiveReader.hh - XrdClXCpCtx.cc XrdClXCpCtx.hh - XrdClXCpSrc.cc XrdClXCpSrc.hh - XrdClLocalFileHandler.cc XrdClLocalFileHandler.hh - XrdClLocalFileTask.cc XrdClLocalFileTask.hh -) - -target_link_libraries( - XrdCl - XrdXml - XrdUtils - pthread - dl) - -set_target_properties( - XrdCl - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" - VERSION ${XRD_CL_VERSION} - SOVERSION ${XRD_CL_SOVERSION} ) - -#------------------------------------------------------------------------------- -# xrdfs -#------------------------------------------------------------------------------- -add_executable( - xrdfs - XrdClFS.cc - XrdClFSExecutor.cc XrdClFSExecutor.hh ) - -target_link_libraries( - xrdfs - pthread - XrdCl - ${READLINE_LIBRARY} - ${NCURSES_LIBRARY} ) - -#------------------------------------------------------------------------------- -# xrdcopy -#------------------------------------------------------------------------------- -add_executable( - xrdcp - XrdClCopy.cc ) - -target_link_libraries( - xrdcp - XrdCl - XrdAppUtils ) - -#------------------------------------------------------------------------------- -# Install -#------------------------------------------------------------------------------- -install( - TARGETS XrdCl xrdfs xrdcp - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ) - -install( - FILES - XrdClAnyObject.hh - XrdClBuffer.hh - XrdClConstants.hh - XrdClCopyProcess.hh - XrdClDefaultEnv.hh - XrdClEnv.hh - XrdClFile.hh - XrdClFileSystem.hh - XrdClMessage.hh - XrdClMonitor.hh - XrdClPostMaster.hh - XrdClPostMasterInterfaces.hh - XrdClTransportManager.hh - XrdClStatus.hh - XrdClURL.hh - XrdClXRootDResponses.hh - XrdClPlugInInterface.hh - XrdClPlugInManager.hh - XrdClPropertyList.hh - XrdClFileSystemUtils.hh - XrdClLog.hh - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/xrootd/XrdCl ) - -install( - FILES - ${PROJECT_SOURCE_DIR}/docs/man/xrdfs.1 - ${PROJECT_SOURCE_DIR}/docs/man/xrdcp.1 - DESTINATION ${CMAKE_INSTALL_MANDIR}/man1 ) - -install( - CODE " - EXECUTE_PROCESS( - COMMAND ln -sf xrdcp xrdcopy - WORKING_DIRECTORY \$ENV{DESTDIR}/${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR} )" -) - -install( - CODE " - EXECUTE_PROCESS( - COMMAND ln -sf xrdcp.1 xrdcopy.1 - WORKING_DIRECTORY \$ENV{DESTDIR}/${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_MANDIR}/man1 )" -) - -install( - CODE " - FOREACH(MANPAGE xrdfs.1 xrdcp.1) - MESSAGE( \"-- Processing: \" \$ENV{DESTDIR}/${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_MANDIR}/man1/\${MANPAGE} ) - EXECUTE_PROCESS( - COMMAND cat \${MANPAGE} - COMMAND sed -e \"s/__VERSION__/${XROOTD_VERSION}/\" - OUTPUT_FILE \${MANPAGE}.new - WORKING_DIRECTORY \$ENV{DESTDIR}/${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_MANDIR}/man1 ) - EXECUTE_PROCESS( - COMMAND mv -f \${MANPAGE}.new \${MANPAGE} - WORKING_DIRECTORY \$ENV{DESTDIR}/${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_MANDIR}/man1 ) - ENDFOREACH()" -) diff --git a/src/XrdCl/XrdClAnyObject.hh b/src/XrdCl/XrdClAnyObject.hh deleted file mode 100644 index d0c4de02d50..00000000000 --- a/src/XrdCl/XrdClAnyObject.hh +++ /dev/null @@ -1,136 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_ANY_OBJECT_HH__ -#define __XRD_CL_ANY_OBJECT_HH__ - -#include -#include - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - //! Simple implementation of a type safe holder for any object pointer - //! It would have been a better idea to use boost::any here but we don't - //! want to depend on boost - //---------------------------------------------------------------------------- - class AnyObject - { - public: - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - AnyObject(): pHolder(0), pTypeInfo(0), pOwn( true ) {}; - - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - ~AnyObject() - { - if( pHolder && pOwn ) - pHolder->Delete(); - delete pHolder; - } - - //------------------------------------------------------------------------ - //! Grab an object - //! By default the ownership of the object is taken as well, ie. - //! the object will be deleted when the AnyObject holding it is deleted. - //! To release an object grab a zero pointer, ie. (int *)0 - //! - //! @param object object pointer - //! @param own take the ownership or not - //------------------------------------------------------------------------ - template void Set( Type object, bool own = true ) - { - if( !object ) - { - delete pHolder; - pHolder = 0; - pTypeInfo = 0; - return; - } - - delete pHolder; - pHolder = new ConcreteHolder( object ); - pOwn = own; - pTypeInfo = &typeid( Type ); - } - - //------------------------------------------------------------------------ - //! Retrieve the object being held - //------------------------------------------------------------------------ - template void Get( Type &object ) - { - if( !pHolder || (strcmp( pTypeInfo->name(), typeid( Type ).name() )) ) - { - object = 0; - return; - } - object = static_cast( pHolder->Get() ); - } - - //------------------------------------------------------------------------ - //! Check if we own the object being stored - //------------------------------------------------------------------------ - bool HasOwnership() const - { - return pOwn; - } - - private: - //------------------------------------------------------------------------ - // Abstract holder object - //------------------------------------------------------------------------ - class Holder - { - public: - virtual ~Holder() {} - virtual void Delete() = 0; - virtual void *Get() = 0; - }; - - //------------------------------------------------------------------------ - // Concrete holder - //------------------------------------------------------------------------ - template - class ConcreteHolder: public Holder - { - public: - ConcreteHolder( Type object ): pObject( object ) {} - virtual void Delete() - { - delete pObject; - } - - virtual void *Get() - { - return (void *)pObject; - } - - private: - Type pObject; - }; - - Holder *pHolder; - const std::type_info *pTypeInfo; - bool pOwn; - }; -} - -#endif // __XRD_CL_ANY_OBJECT_HH__ diff --git a/src/XrdCl/XrdClAsyncSocketHandler.cc b/src/XrdCl/XrdClAsyncSocketHandler.cc deleted file mode 100644 index 213f553bc14..00000000000 --- a/src/XrdCl/XrdClAsyncSocketHandler.cc +++ /dev/null @@ -1,1020 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#include "XrdCl/XrdClStream.hh" -#include "XrdCl/XrdClConstants.hh" -#include "XrdCl/XrdClLog.hh" -#include "XrdCl/XrdClMessage.hh" -#include "XrdCl/XrdClAsyncSocketHandler.hh" -#include "XrdCl/XrdClXRootDTransport.hh" -#include "XrdCl/XrdClXRootDMsgHandler.hh" -#include "XrdCl/XrdClOptimizers.hh" -#include - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - // Constructor - //---------------------------------------------------------------------------- - AsyncSocketHandler::AsyncSocketHandler( Poller *poller, - TransportHandler *transport, - AnyObject *channelData, - uint16_t subStreamNum ): - pPoller( poller ), - pTransport( transport ), - pChannelData( channelData ), - pSubStreamNum( subStreamNum ), - pStream( 0 ), - pSocket( 0 ), - pIncoming( 0 ), - pHSIncoming( 0 ), - pOutgoing( 0 ), - pSignature( 0 ), - pHSOutgoing( 0 ), - pHandShakeData( 0 ), - pHandShakeDone( false ), - pConnectionStarted( 0 ), - pConnectionTimeout( 0 ), - pHeaderDone( false ), - pOutMsgDone( false ), - pOutHandler( 0 ), - pIncMsgSize( 0 ), - pOutMsgSize( 0 ) - { - Env *env = DefaultEnv::GetEnv(); - - int timeoutResolution = DefaultTimeoutResolution; - env->GetInt( "TimeoutResolution", timeoutResolution ); - pTimeoutResolution = timeoutResolution; - - pSocket = new Socket(); - pSocket->SetChannelID( pChannelData ); - pIncHandler = std::make_pair( (IncomingMsgHandler*)0, false ); - pLastActivity = time(0); - } - - //---------------------------------------------------------------------------- - // Destructor - //---------------------------------------------------------------------------- - AsyncSocketHandler::~AsyncSocketHandler() - { - Close(); - delete pSocket; - delete pSignature; - } - - //---------------------------------------------------------------------------- - // Connect to given address - //---------------------------------------------------------------------------- - Status AsyncSocketHandler::Connect( time_t timeout ) - { - Log *log = DefaultEnv::GetLog(); - pLastActivity = pConnectionStarted = ::time(0); - pConnectionTimeout = timeout; - - //-------------------------------------------------------------------------- - // Initialize the socket - //-------------------------------------------------------------------------- - Status st = pSocket->Initialize( pSockAddr.Family() ); - if( !st.IsOK() ) - { - log->Error( AsyncSockMsg, "[%s] Unable to initialize socket: %s", - pStreamName.c_str(), st.ToString().c_str() ); - st.status = stFatal; - return st; - } - - //-------------------------------------------------------------------------- - // Set the keep-alive up - //-------------------------------------------------------------------------- - Env *env = DefaultEnv::GetEnv(); - - int keepAlive = DefaultTCPKeepAlive; - env->GetInt( "TCPKeepAlive", keepAlive ); - if( keepAlive ) - { - int param = 1; - Status st = pSocket->SetSockOpt( SOL_SOCKET, SO_KEEPALIVE, ¶m, - sizeof(param) ); - if( !st.IsOK() ) - log->Error( AsyncSockMsg, "[%s] Unable to turn on keepalive: %s", - st.ToString().c_str() ); - -#if defined(__linux__) && defined( TCP_KEEPIDLE ) && \ - defined( TCP_KEEPINTVL ) && defined( TCP_KEEPCNT ) - - param = DefaultTCPKeepAliveTime; - env->GetInt( "TCPKeepAliveTime", param ); - st = pSocket->SetSockOpt(SOL_TCP, TCP_KEEPIDLE, ¶m, sizeof(param)); - if( !st.IsOK() ) - log->Error( AsyncSockMsg, "[%s] Unable to set keepalive time: %s", - st.ToString().c_str() ); - - param = DefaultTCPKeepAliveInterval; - env->GetInt( "TCPKeepAliveInterval", param ); - st = pSocket->SetSockOpt(SOL_TCP, TCP_KEEPINTVL, ¶m, sizeof(param)); - if( !st.IsOK() ) - log->Error( AsyncSockMsg, "[%s] Unable to set keepalive interval: %s", - st.ToString().c_str() ); - - param = DefaultTCPKeepAliveProbes; - env->GetInt( "TCPKeepAliveProbes", param ); - st = pSocket->SetSockOpt(SOL_TCP, TCP_KEEPCNT, ¶m, sizeof(param)); - if( !st.IsOK() ) - log->Error( AsyncSockMsg, "[%s] Unable to set keepalive probes: %s", - st.ToString().c_str() ); -#endif - } - - pHandShakeDone = false; - - //-------------------------------------------------------------------------- - // Initiate async connection to the address - //-------------------------------------------------------------------------- - char nameBuff[256]; - pSockAddr.Format( nameBuff, sizeof(nameBuff), XrdNetAddrInfo::fmtAdv6 ); - log->Debug( AsyncSockMsg, "[%s] Attempting connection to %s", - pStreamName.c_str(), nameBuff ); - - st = pSocket->ConnectToAddress( pSockAddr, 0 ); - if( !st.IsOK() ) - { - log->Error( AsyncSockMsg, "[%s] Unable to initiate the connection: %s", - pStreamName.c_str(), st.ToString().c_str() ); - return st; - } - - pSocket->SetStatus( Socket::Connecting ); - - //-------------------------------------------------------------------------- - // We should get the ready to write event once we're really connected - // so we need to listen to it - //-------------------------------------------------------------------------- - if( !pPoller->AddSocket( pSocket, this ) ) - { - Status st( stFatal, errPollerError ); - pSocket->Close(); - return st; - } - - if( !pPoller->EnableWriteNotification( pSocket, true, pTimeoutResolution ) ) - { - Status st( stFatal, errPollerError ); - pPoller->RemoveSocket( pSocket ); - pSocket->Close(); - return st; - } - - return Status(); - } - - //---------------------------------------------------------------------------- - // Close the connection - //---------------------------------------------------------------------------- - Status AsyncSocketHandler::Close() - { - Log *log = DefaultEnv::GetLog(); - log->Debug( AsyncSockMsg, "[%s] Closing the socket", pStreamName.c_str() ); - - pTransport->Disconnect( *pChannelData, pStream->GetStreamNumber(), - pSubStreamNum ); - - pPoller->RemoveSocket( pSocket ); - pSocket->Close(); - - if( !pIncHandler.second ) - delete pIncoming; - - pIncoming = 0; - return Status(); - } - - //---------------------------------------------------------------------------- - // Set a stream object to be notified about the status of the operations - //---------------------------------------------------------------------------- - void AsyncSocketHandler::SetStream( Stream *stream ) - { - pStream = stream; - std::ostringstream o; - o << pStream->GetURL()->GetHostId(); - o << " #" << pStream->GetStreamNumber(); - o << "." << pSubStreamNum; - pStreamName = o.str(); - } - - //---------------------------------------------------------------------------- - // Handler a socket event - //---------------------------------------------------------------------------- - void AsyncSocketHandler::Event( uint8_t type, XrdCl::Socket */*socket*/ ) - { - //-------------------------------------------------------------------------- - // Read event - //-------------------------------------------------------------------------- - if( type & ReadyToRead ) - { - pLastActivity = time(0); - if( likely( pHandShakeDone ) ) - OnRead(); - else - OnReadWhileHandshaking(); - } - - //-------------------------------------------------------------------------- - // Read timeout - //-------------------------------------------------------------------------- - else if( type & ReadTimeOut ) - { - if( likely( pHandShakeDone ) ) - OnReadTimeout(); - else - OnTimeoutWhileHandshaking(); - } - - //-------------------------------------------------------------------------- - // Write event - //-------------------------------------------------------------------------- - if( type & ReadyToWrite ) - { - pLastActivity = time(0); - if( unlikely( pSocket->GetStatus() == Socket::Connecting ) ) - OnConnectionReturn(); - else if( likely( pHandShakeDone ) ) - OnWrite(); - else - OnWriteWhileHandshaking(); - } - - //-------------------------------------------------------------------------- - // Write timeout - //-------------------------------------------------------------------------- - else if( type & WriteTimeOut ) - { - if( likely( pHandShakeDone ) ) - OnWriteTimeout(); - else - OnTimeoutWhileHandshaking(); - } - } - - //---------------------------------------------------------------------------- - // Connect returned - //---------------------------------------------------------------------------- - void AsyncSocketHandler::OnConnectionReturn() - { - //-------------------------------------------------------------------------- - // Check whether we were able to connect - //-------------------------------------------------------------------------- - Log *log = DefaultEnv::GetLog(); - log->Debug( AsyncSockMsg, "[%s] Async connection call returned", - pStreamName.c_str() ); - - int errorCode = 0; - socklen_t optSize = sizeof( errorCode ); - Status st = pSocket->GetSockOpt( SOL_SOCKET, SO_ERROR, &errorCode, - &optSize ); - - //-------------------------------------------------------------------------- - // This is an internal error really (either logic or system fault), - // so we call it a day and don't retry - //-------------------------------------------------------------------------- - if( !st.IsOK() ) - { - log->Error( AsyncSockMsg, "[%s] Unable to get the status of the " - "connect operation: %s", pStreamName.c_str(), - strerror( errno ) ); - pStream->OnConnectError( pSubStreamNum, - Status( stFatal, errSocketOptError, errno ) ); - return; - } - - //-------------------------------------------------------------------------- - // We were unable to connect - //-------------------------------------------------------------------------- - if( errorCode ) - { - log->Error( AsyncSockMsg, "[%s] Unable to connect: %s", - pStreamName.c_str(), strerror( errorCode ) ); - pStream->OnConnectError( pSubStreamNum, - Status( stError, errConnectionError ) ); - return; - } - pSocket->SetStatus( Socket::Connected ); - - //-------------------------------------------------------------------------- - // Initialize the handshake - //-------------------------------------------------------------------------- - pHandShakeData = new HandShakeData( pStream->GetURL(), - pStream->GetStreamNumber(), - pSubStreamNum ); - pHandShakeData->serverAddr = &pSocket->GetServerAddress(); - pHandShakeData->clientName = pSocket->GetSockName(); - pHandShakeData->streamName = pStreamName; - - st = pTransport->HandShake( pHandShakeData, *pChannelData ); - ++pHandShakeData->step; - - if( !st.IsOK() ) - { - log->Error( AsyncSockMsg, "[%s] Connection negotiation failed", - pStreamName.c_str() ); - pStream->OnConnectError( pSubStreamNum, st ); - return; - } - - //-------------------------------------------------------------------------- - // Transport has given us something to send - //-------------------------------------------------------------------------- - if( pHandShakeData->out ) - { - pHSOutgoing = pHandShakeData->out; - pHandShakeData->out = 0; - } - - //-------------------------------------------------------------------------- - // Listen to what the server has to say - //-------------------------------------------------------------------------- - if( !pPoller->EnableReadNotification( pSocket, true, pTimeoutResolution ) ) - { - pStream->OnConnectError( pSubStreamNum, - Status( stFatal, errPollerError ) ); - return; - } - } - - //---------------------------------------------------------------------------- - // Got a write readiness event - //---------------------------------------------------------------------------- - void AsyncSocketHandler::OnWrite() - { - //-------------------------------------------------------------------------- - // Pick up a message if we're not in process of writing something - //-------------------------------------------------------------------------- - if( !pOutgoing ) - { - pOutMsgDone = false; - std::pair toBeSent; - toBeSent = pStream->OnReadyToWrite( pSubStreamNum ); - pOutgoing = toBeSent.first; pOutHandler = toBeSent.second; - - if( !pOutgoing ) - return; - - pOutgoing->SetCursor( 0 ); - pOutMsgSize = pOutgoing->GetSize(); - - //------------------------------------------------------------------------ - // Secure the message if necessary - //------------------------------------------------------------------------ - delete pSignature; pSignature = 0; - XRootDStatus st = GetSignature( pOutgoing, pSignature ); - if( !st.IsOK() ) - { - OnFault( st ); - return; - } - } - - //-------------------------------------------------------------------------- - // Try to write everything at once: signature, request and raw data - // (this is only supported if pOutHandler is an instance of XRootDMsgHandler) - //-------------------------------------------------------------------------- - Status st = WriteMessageAndRaw( pOutgoing, pSignature ); - if( !st.IsOK() && st.code == errNotSupported ) //< this part should go away - st = WriteSeparately( pOutgoing, pSignature ); //< once we can add GetMsgBody - //< to OutgoingMsgHandler interface !!! - if( !st.IsOK() ) - { - OnFault( st ); - return; - } - - if( st.code == suRetry ) - return; - - Log *log = DefaultEnv::GetLog(); - log->Dump( AsyncSockMsg, "[%s] Successfully sent message: %s (0x%x).", - pStreamName.c_str(), pOutgoing->GetDescription().c_str(), - pOutgoing ); - - pStream->OnMessageSent( pSubStreamNum, pOutgoing, pOutMsgSize ); - pOutgoing = 0; - - //-------------------------------------------------------------------------- - // Disable the respective substream if empty - //-------------------------------------------------------------------------- - pStream->DisableIfEmpty( pSubStreamNum ); - } - - //---------------------------------------------------------------------------- - // Got a write readiness event while handshaking - //---------------------------------------------------------------------------- - void AsyncSocketHandler::OnWriteWhileHandshaking() - { - Status st; - if( !pHSOutgoing ) - { - if( !(st = DisableUplink()).IsOK() ) - OnFaultWhileHandshaking( st ); - return; - } - - if( !(st = WriteCurrentMessage( pHSOutgoing )).IsOK() ) - { - OnFaultWhileHandshaking( st ); - return; - } - - if( st.code != suRetry ) - { - delete pHSOutgoing; - pHSOutgoing = 0; - if( !(st = DisableUplink()).IsOK() ) - OnFaultWhileHandshaking( st ); - return; - } - } - - //---------------------------------------------------------------------------- - // Write the current message - //---------------------------------------------------------------------------- - Status AsyncSocketHandler::WriteCurrentMessage( Message *toWrite ) - { - Log *log = DefaultEnv::GetLog(); - - //-------------------------------------------------------------------------- - // Try to write down the current message - //-------------------------------------------------------------------------- - Message *msg = toWrite; - uint32_t leftToBeWritten = msg->GetSize()-msg->GetCursor(); - - while( leftToBeWritten ) - { - int status = pSocket->Send( msg->GetBufferAtCursor(), leftToBeWritten ); - if( status <= 0 ) - { - //---------------------------------------------------------------------- - // Writing operation would block! So we are done for now, but we will - // return - //---------------------------------------------------------------------- - if( errno == EAGAIN || errno == EWOULDBLOCK ) - return Status( stOK, suRetry ); - - //---------------------------------------------------------------------- - // Actual socket error error! - //---------------------------------------------------------------------- - toWrite->SetCursor( 0 ); - return Status( stError, errSocketError, errno ); - } - msg->AdvanceCursor( status ); - leftToBeWritten -= status; - } - - //-------------------------------------------------------------------------- - // We have written the message successfully - //-------------------------------------------------------------------------- - log->Dump( AsyncSockMsg, "[%s] Wrote a message: %s (0x%x), %d bytes", - pStreamName.c_str(), toWrite->GetDescription().c_str(), - toWrite, toWrite->GetSize() ); - return Status(); - } - - //---------------------------------------------------------------------------- - // Write the message and its signature - //---------------------------------------------------------------------------- - Status AsyncSocketHandler::WriteVMessage( Message *toWrite, - Message *&sign, - ChunkList *chunks, - uint32_t *asyncOffset ) - { - if( !sign && !chunks ) return WriteCurrentMessage( toWrite ); - - Log *log = DefaultEnv::GetLog(); - - const size_t iovcnt = 1 + ( sign ? 1 : 0 ) + ( chunks ? chunks->size() : 0 ); - iovec iov[iovcnt]; - uint32_t leftToBeWritten = 0; - size_t i = 0; - - if( sign ) - { - ToIov( *sign, iov[i] ); - leftToBeWritten += iov[i].iov_len; - ++i; - } - - ToIov( *toWrite, iov[i] ); - leftToBeWritten += iov[i].iov_len; - ++i; - - uint32_t rawSize = 0; - if( chunks ) - { - rawSize = ToIov( chunks, asyncOffset, iov + i ); - leftToBeWritten += rawSize; - } - - while( leftToBeWritten ) - { - int bytesWritten = pSocket->WriteV( iov, iovcnt ); - if( bytesWritten <= 0 ) - { - //---------------------------------------------------------------------- - // Writing operation would block! So we are done for now, but we will - // return - //---------------------------------------------------------------------- - if( errno == EAGAIN || errno == EWOULDBLOCK ) - return Status( stOK, suRetry ); - - //---------------------------------------------------------------------- - // Actual socket error error! - //---------------------------------------------------------------------- - if( sign ) sign->SetCursor( 0 ); - toWrite->SetCursor( 0 ); - return Status( stError, errSocketError, errno ); - } - - leftToBeWritten -= bytesWritten; - if( sign ) - UpdateAfterWrite( *sign, iov[0], bytesWritten ); - - i = sign ? 1 : 0; - UpdateAfterWrite( *toWrite, iov[i], bytesWritten ); - - if( chunks && asyncOffset ) - UpdateAfterWrite( chunks, asyncOffset, iov + i + 1, bytesWritten ); - } - - //-------------------------------------------------------------------------- - // We have written the message successfully - //-------------------------------------------------------------------------- - if( sign ) - log->Dump( AsyncSockMsg, "[%s] WroteV a message signature : %s (0x%x), " - "%d bytes", - pStreamName.c_str(), sign->GetDescription().c_str(), - sign, sign->GetSize() ); - - log->Dump( AsyncSockMsg, "[%s] WroteV a message: %s (0x%x), %d bytes", - pStreamName.c_str(), toWrite->GetDescription().c_str(), - toWrite, toWrite->GetSize() ); - - if( chunks ) - log->Dump( AsyncSockMsg, "[%s] WroteV raw data: %d bytes", - pStreamName.c_str(), rawSize ); - - return Status(); - } - - Status AsyncSocketHandler::WriteMessageAndRaw( Message *toWrite, Message *&sign ) - { - // once we can add 'GetMessageBody' to OutgoingMsghandler - // interface we can get rid of the ugly dynamic_cast - static XRootDMsgHandler *xrdHandler = 0; - ChunkList *chunks = 0; - uint32_t *asyncOffset = 0; - - if( pOutHandler->IsRaw() ) - { - if( xrdHandler != pOutHandler ) - xrdHandler = dynamic_cast( pOutHandler ); - - if( !xrdHandler ) - return Status( stError, errNotSupported ); - - chunks = xrdHandler->GetMessageBody( asyncOffset ); - Log *log = DefaultEnv::GetLog(); - log->Dump( AsyncSockMsg, "[%s] Will write the payload in one go with " - "the header for message: %s (0x%x).", pStreamName.c_str(), - pOutgoing->GetDescription().c_str(), pOutgoing ); - } - - Status st = WriteVMessage( toWrite, sign, chunks, asyncOffset ); - if( st.IsOK() && st.code == suDone ) - { - if( asyncOffset ) - pOutMsgSize += *asyncOffset; - pOutMsgDone = true; - } - - return st; - } - - Status AsyncSocketHandler::WriteSeparately( Message *toWrite, Message *&sign ) - { - //------------------------------------------------------------------------ - // Write the message if not already written - //------------------------------------------------------------------------ - Status st; - if( !pOutMsgDone ) - { - if( !(st = WriteVMessage( toWrite, sign, 0, 0 )).IsOK() ) - return st; - - if( st.code == suRetry ) - return st; - - Log *log = DefaultEnv::GetLog(); - - if( pOutHandler && pOutHandler->IsRaw() ) - { - log->Dump( AsyncSockMsg, "[%s] Will call raw handler to write payload " - "for message: %s (0x%x).", pStreamName.c_str(), - pOutgoing->GetDescription().c_str(), pOutgoing ); - } - - pOutMsgDone = true; - } - - //------------------------------------------------------------------------ - // Check if the handler needs to be called - //------------------------------------------------------------------------ - if( pOutHandler && pOutHandler->IsRaw() ) - { - uint32_t bytesWritten = 0; - st = pOutHandler->WriteMessageBody( pSocket->GetFD(), bytesWritten ); - pOutMsgSize += bytesWritten; - if( !st.IsOK() ) - return st; - - if( st.code == suRetry ) - return st; - } - - return Status(); - } - - //---------------------------------------------------------------------------- - // Got a read readiness event - //---------------------------------------------------------------------------- - void AsyncSocketHandler::OnRead() - { - //-------------------------------------------------------------------------- - // There is no incoming message currently being processed so we create - // a new one - //-------------------------------------------------------------------------- - if( !pIncoming ) - { - pHeaderDone = false; - pIncoming = new Message(); - pIncHandler = std::make_pair( (IncomingMsgHandler*)0, false ); - pIncMsgSize = 0; - } - - Status st; - Log *log = DefaultEnv::GetLog(); - - //-------------------------------------------------------------------------- - // We need to read the header first - //-------------------------------------------------------------------------- - if( !pHeaderDone ) - { - st = pTransport->GetHeader( pIncoming, pSocket->GetFD() ); - if( !st.IsOK() ) - { - OnFault( st ); - return; - } - - if( st.code == suRetry ) - return; - - log->Dump( AsyncSockMsg, "[%s] Received message header for 0x%x size: %d", - pStreamName.c_str(), pIncoming, pIncoming->GetCursor() ); - pIncMsgSize = pIncoming->GetCursor(); - pHeaderDone = true; - std::pair raw; - pIncHandler = pStream->InstallIncHandler( pIncoming, pSubStreamNum ); - - if( pIncHandler.first ) - { - log->Dump( AsyncSockMsg, "[%s] Will use the raw handler to read body " - "of message 0x%x", pStreamName.c_str(), pIncoming ); - } - } - - //-------------------------------------------------------------------------- - // We need to call a raw message handler to get the data from the socket - //-------------------------------------------------------------------------- - if( pIncHandler.first ) - { - uint32_t bytesRead = 0; - st = pIncHandler.first->ReadMessageBody( pIncoming, pSocket->GetFD(), - bytesRead ); - if( !st.IsOK() ) - { - OnFault( st ); - return; - } - pIncMsgSize += bytesRead; - - if( st.code == suRetry ) - return; - } - //-------------------------------------------------------------------------- - // No raw handler, so we read the message to the buffer - //-------------------------------------------------------------------------- - else - { - st = pTransport->GetBody( pIncoming, pSocket->GetFD() ); - if( !st.IsOK() ) - { - OnFault( st ); - return; - } - - if( st.code == suRetry ) - return; - - pIncMsgSize = pIncoming->GetSize(); - } - - //-------------------------------------------------------------------------- - // Report the incoming message - //-------------------------------------------------------------------------- - log->Dump( AsyncSockMsg, "[%s] Received message 0x%x of %d bytes", - pStreamName.c_str(), pIncoming, pIncMsgSize ); - - pStream->OnIncoming( pSubStreamNum, pIncoming, pIncMsgSize ); - pIncoming = 0; - } - - //---------------------------------------------------------------------------- - // Got a read readiness event while handshaking - //---------------------------------------------------------------------------- - void AsyncSocketHandler::OnReadWhileHandshaking() - { - //-------------------------------------------------------------------------- - // Read the message and let the transport handler look at it when - // reading has finished - //-------------------------------------------------------------------------- - Status st = ReadMessage( pHSIncoming ); - if( !st.IsOK() ) - { - OnFaultWhileHandshaking( st ); - return; - } - - if( st.code != suDone ) - return; - - //-------------------------------------------------------------------------- - // OK, we have a new message, let's deal with it; - //-------------------------------------------------------------------------- - pHandShakeData->in = pHSIncoming; - pHSIncoming = 0; - st = pTransport->HandShake( pHandShakeData, *pChannelData ); - ++pHandShakeData->step; - delete pHandShakeData->in; - pHandShakeData->in = 0; - - if( !st.IsOK() ) - { - OnFaultWhileHandshaking( st ); - return; - } - - //-------------------------------------------------------------------------- - // The transport handler gave us something to write - //-------------------------------------------------------------------------- - if( pHandShakeData->out ) - { - pHSOutgoing = pHandShakeData->out; - pHandShakeData->out = 0; - Status st; - if( !(st = EnableUplink()).IsOK() ) - { - OnFaultWhileHandshaking( st ); - return; - } - } - - //-------------------------------------------------------------------------- - // The hand shake process is done - //-------------------------------------------------------------------------- - if( st.IsOK() && st.code == suDone ) - { - delete pHandShakeData; - if( !(st = EnableUplink()).IsOK() ) - { - OnFaultWhileHandshaking( Status( stFatal, errPollerError ) ); - return; - } - pHandShakeDone = true; - pStream->OnConnect( pSubStreamNum ); - } - - if( !st.IsOK() ) - { - OnFaultWhileHandshaking( st ); - return; - } - } - - //---------------------------------------------------------------------------- - // Read a message - //---------------------------------------------------------------------------- - Status AsyncSocketHandler::ReadMessage( Message *&toRead ) - { - if( !toRead ) - { - pHeaderDone = false; - toRead = new Message(); - } - - Status st; - Log *log = DefaultEnv::GetLog(); - if( !pHeaderDone ) - { - st = pTransport->GetHeader( toRead, pSocket->GetFD() ); - if( st.IsOK() && st.code == suDone ) - { - log->Dump( AsyncSockMsg, - "[%s] Received message header, size: %d", - pStreamName.c_str(), toRead->GetCursor() ); - pHeaderDone = true; - } - else - return st; - } - - st = pTransport->GetBody( toRead, pSocket->GetFD() ); - if( st.IsOK() && st.code == suDone ) - { - log->Dump( AsyncSockMsg, "[%s] Received a message of %d bytes", - pStreamName.c_str(), toRead->GetSize() ); - } - return st; - } - - //---------------------------------------------------------------------------- - // Handle fault - //---------------------------------------------------------------------------- - void AsyncSocketHandler::OnFault( Status st ) - { - Log *log = DefaultEnv::GetLog(); - log->Error( AsyncSockMsg, "[%s] Socket error encountered: %s", - pStreamName.c_str(), st.ToString().c_str() ); - - if( !pIncHandler.second ) - delete pIncoming; - - pIncoming = 0; - pOutgoing = 0; - pOutHandler = 0; - - pStream->OnError( pSubStreamNum, st ); - } - - //---------------------------------------------------------------------------- - // Handle fault while handshaking - //---------------------------------------------------------------------------- - void AsyncSocketHandler::OnFaultWhileHandshaking( Status st ) - { - Log *log = DefaultEnv::GetLog(); - log->Error( AsyncSockMsg, "[%s] Socket error while handshaking: %s", - pStreamName.c_str(), st.ToString().c_str() ); - delete pHSIncoming; - delete pHSOutgoing; - pHSIncoming = 0; - pHSOutgoing = 0; - - pStream->OnConnectError( pSubStreamNum, st ); - } - - //---------------------------------------------------------------------------- - // Handle write timeout - //---------------------------------------------------------------------------- - void AsyncSocketHandler::OnWriteTimeout() - { - pStream->OnWriteTimeout( pSubStreamNum ); - } - - //---------------------------------------------------------------------------- - // Handler read timeout - //---------------------------------------------------------------------------- - void AsyncSocketHandler::OnReadTimeout() - { - bool isBroken = false; - pStream->OnReadTimeout( pSubStreamNum, isBroken ); - - if( isBroken ) - { - // if we are here it means Stream::OnError has been - // called from inside of Stream::OnReadTimeout, this - // in turn means that the ownership of following - // pointers, has been transfered to the inQueue - pIncoming = 0; - pOutgoing = 0; - pOutHandler = 0; - } - } - - //---------------------------------------------------------------------------- - // Handle timeout while handshaking - //---------------------------------------------------------------------------- - void AsyncSocketHandler::OnTimeoutWhileHandshaking() - { - time_t now = time(0); - if( now > pConnectionStarted+pConnectionTimeout ) - OnFaultWhileHandshaking( Status( stError, errSocketTimeout ) ); - } - - //------------------------------------------------------------------------ - // Get signature for given message - //------------------------------------------------------------------------ - Status AsyncSocketHandler::GetSignature( Message *toSign, Message *&sign ) - { - // ideally the 'GetSignature' method should be in TransportHandler interface - // however due to ABI compatibility for the time being this workaround has to - // be employed - XRootDTransport *xrootdTransport = dynamic_cast( pTransport ); - if( !xrootdTransport ) return Status( stError, errNotSupported ); - return xrootdTransport->GetSignature( toSign, sign, *pChannelData ); - } - - //------------------------------------------------------------------------ - // Initialize the iovec with given message - //------------------------------------------------------------------------ - void AsyncSocketHandler::ToIov( Message &msg, iovec &iov ) - { - iov.iov_base = msg.GetBufferAtCursor(); - iov.iov_len = msg.GetSize() - msg.GetCursor(); - } - - //------------------------------------------------------------------------ - // Update iovec after write - //------------------------------------------------------------------------ - void AsyncSocketHandler::UpdateAfterWrite( Message &msg, - iovec &iov, - int &bytesWritten ) - { - size_t advance = ( bytesWritten < (int)iov.iov_len ) ? bytesWritten : iov.iov_len; - bytesWritten -= advance; - msg.AdvanceCursor( advance ); - ToIov( msg, iov ); - } - - //------------------------------------------------------------------------ - // Add chunks to the given iovec - //------------------------------------------------------------------------ - uint32_t AsyncSocketHandler::ToIov( ChunkList *chunks, - const uint32_t *offset, - iovec *iov ) - { - if( !chunks || !offset ) return 0; - - uint32_t off = *offset; - uint32_t size = 0; - - for( auto itr = chunks->begin(); itr != chunks->end(); ++itr ) - { - auto &chunk = *itr; - if( off > chunk.length ) - { - iov->iov_len = 0; - iov->iov_base = 0; - off -= chunk.length; - } - else if( off > 0 ) - { - iov->iov_len = chunk.length - off; - iov->iov_base = reinterpret_cast( chunk.buffer ) + off; - size += iov->iov_len; - off = 0; - } - else - { - iov->iov_len = chunk.length; - iov->iov_base = chunk.buffer; - size += iov->iov_len; - } - ++iov; - } - - return size; - } - - void AsyncSocketHandler::UpdateAfterWrite( ChunkList *chunks, - uint32_t *offset, - iovec *iov, - int &bytesWritten ) - { - *offset += bytesWritten; - bytesWritten = 0; - ToIov( chunks, offset, iov ); - } -} diff --git a/src/XrdCl/XrdClAsyncSocketHandler.hh b/src/XrdCl/XrdClAsyncSocketHandler.hh deleted file mode 100644 index 6254ecef057..00000000000 --- a/src/XrdCl/XrdClAsyncSocketHandler.hh +++ /dev/null @@ -1,263 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_ASYNC_SOCKET_HANDLER_HH__ -#define __XRD_CL_ASYNC_SOCKET_HANDLER_HH__ - -#include "XrdCl/XrdClSocket.hh" -#include "XrdCl/XrdClConstants.hh" -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdCl/XrdClPoller.hh" -#include "XrdCl/XrdClPostMasterInterfaces.hh" - -#include -#include - -namespace XrdCl -{ - class Stream; - - //---------------------------------------------------------------------------- - //! Utility class handling asynchronous socket interactions and forwarding - //! events to the parent stream. - //---------------------------------------------------------------------------- - class AsyncSocketHandler: public SocketHandler - { - public: - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - AsyncSocketHandler( Poller *poller, - TransportHandler *transport, - AnyObject *channelData, - uint16_t subStreamNum ); - - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - ~AsyncSocketHandler(); - - //------------------------------------------------------------------------ - //! Set address - //------------------------------------------------------------------------ - void SetAddress( const XrdNetAddr &address ) - { - pSockAddr = address; - } - - //------------------------------------------------------------------------ - //! Get the address that the socket is connected to - //------------------------------------------------------------------------ - const XrdNetAddr &GetAddress() const - { - return pSockAddr; - } - - //------------------------------------------------------------------------ - //! Connect to the currently set address - //------------------------------------------------------------------------ - Status Connect( time_t timeout ); - - //------------------------------------------------------------------------ - //! Close the connection - //------------------------------------------------------------------------ - Status Close(); - - //------------------------------------------------------------------------ - //! Set a stream object to be notified about the status of the operations - //------------------------------------------------------------------------ - void SetStream( Stream *stream ); - - //------------------------------------------------------------------------ - //! Handle a socket event - //------------------------------------------------------------------------ - virtual void Event( uint8_t type, XrdCl::Socket */*socket*/ ); - - //------------------------------------------------------------------------ - //! Enable uplink - //------------------------------------------------------------------------ - Status EnableUplink() - { - if( !pPoller->EnableWriteNotification( pSocket, true, pTimeoutResolution ) ) - return Status( stFatal, errPollerError ); - return Status(); - } - - //------------------------------------------------------------------------ - //! Disable uplink - //------------------------------------------------------------------------ - Status DisableUplink() - { - if( !pPoller->EnableWriteNotification( pSocket, false ) ) - return Status( stFatal, errPollerError ); - return Status(); - } - - //------------------------------------------------------------------------ - //! Get stream name - //------------------------------------------------------------------------ - const std::string &GetStreamName() - { - return pStreamName; - } - - //------------------------------------------------------------------------ - //! Get timestamp of last registered socket activity - //------------------------------------------------------------------------ - time_t GetLastActivity() - { - return pLastActivity; - } - - private: - - //------------------------------------------------------------------------ - // Connect returned - //------------------------------------------------------------------------ - void OnConnectionReturn(); - - //------------------------------------------------------------------------ - // Got a write readiness event - //------------------------------------------------------------------------ - void OnWrite(); - - //------------------------------------------------------------------------ - // Got a write readiness event while handshaking - //------------------------------------------------------------------------ - void OnWriteWhileHandshaking(); - - - Status WriteMessageAndRaw( Message *toWrite, Message *&sign ); - - Status WriteSeparately( Message *toWrite, Message *&sign ); - - //------------------------------------------------------------------------ - // Write the current message - //------------------------------------------------------------------------ - Status WriteCurrentMessage( Message *toWrite ); - - //------------------------------------------------------------------------ - // Write the message, its signature and its body - //------------------------------------------------------------------------ - Status WriteVMessage( Message *toWrite, - Message *&sign, - ChunkList *chunks, - uint32_t *asyncOffset ); - - //------------------------------------------------------------------------ - // Got a read readiness event - //------------------------------------------------------------------------ - void OnRead(); - - //------------------------------------------------------------------------ - // Got a read readiness event while handshaking - //------------------------------------------------------------------------ - void OnReadWhileHandshaking(); - - //------------------------------------------------------------------------ - // Read a message - //------------------------------------------------------------------------ - Status ReadMessage( Message *&toRead ); - - //------------------------------------------------------------------------ - // Handle fault - //------------------------------------------------------------------------ - void OnFault( Status st ); - - //------------------------------------------------------------------------ - // Handle fault while handshaking - //------------------------------------------------------------------------ - void OnFaultWhileHandshaking( Status st ); - - //------------------------------------------------------------------------ - // Handle write timeout event - //------------------------------------------------------------------------ - void OnWriteTimeout(); - - //------------------------------------------------------------------------ - // Handle read timeout event - //------------------------------------------------------------------------ - void OnReadTimeout(); - - //------------------------------------------------------------------------ - // Handle timeout event while handshaking - //------------------------------------------------------------------------ - void OnTimeoutWhileHandshaking(); - - //------------------------------------------------------------------------ - // Get signature for given message - //------------------------------------------------------------------------ - Status GetSignature( Message *toSign, Message *&sign ); - - //------------------------------------------------------------------------ - // Initialize the iovec with given message - //------------------------------------------------------------------------ - inline void ToIov( Message &msg, iovec &iov ); - - //------------------------------------------------------------------------ - // Update iovec after write - //------------------------------------------------------------------------ - inline void UpdateAfterWrite( Message &msg, iovec &iov, int &bytesRead ); - - //------------------------------------------------------------------------ - // Add chunks to the given iovec - //------------------------------------------------------------------------ - inline uint32_t ToIov( ChunkList *chunks, - const uint32_t *offset, - iovec *iov ); - - //------------------------------------------------------------------------ - // Update raw data after write - //------------------------------------------------------------------------ - inline void UpdateAfterWrite( ChunkList *chunks, - uint32_t *offset, - iovec *iov, - int &bytesWritten ); - - //------------------------------------------------------------------------ - // Data members - //------------------------------------------------------------------------ - Poller *pPoller; - TransportHandler *pTransport; - AnyObject *pChannelData; - uint16_t pSubStreamNum; - Stream *pStream; - std::string pStreamName; - Socket *pSocket; - Message *pIncoming; - Message *pHSIncoming; - Message *pOutgoing; - Message *pSignature; - Message *pHSOutgoing; - XrdNetAddr pSockAddr; - HandShakeData *pHandShakeData; - bool pHandShakeDone; - uint16_t pTimeoutResolution; - time_t pConnectionStarted; - time_t pConnectionTimeout; - bool pHeaderDone; - std::pair pIncHandler; - bool pOutMsgDone; - OutgoingMsgHandler *pOutHandler; - uint32_t pIncMsgSize; - uint32_t pOutMsgSize; - time_t pLastActivity; - }; -} - -#endif // __XRD_CL_ASYNC_SOCKET_HANDLER_HH__ diff --git a/src/XrdCl/XrdClBuffer.hh b/src/XrdCl/XrdClBuffer.hh deleted file mode 100644 index 65d36af1ca3..00000000000 --- a/src/XrdCl/XrdClBuffer.hh +++ /dev/null @@ -1,237 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_BUFFER_HH__ -#define __XRD_CL_BUFFER_HH__ - -#include -#include -#include -#include -#include - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - //! Binary blob representation - //---------------------------------------------------------------------------- - class Buffer - { - public: - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - Buffer( uint32_t size = 0 ): pBuffer(0), pSize(0), pCursor(0) - { - if( size ) - { - Allocate( size ); - } - } - - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - virtual ~Buffer() { Free(); } - - //------------------------------------------------------------------------ - //! Get the message buffer - //------------------------------------------------------------------------ - const char *GetBuffer( uint32_t offset = 0 ) const - { - return pBuffer+offset; - } - - //------------------------------------------------------------------------ - //! Get the message buffer - //------------------------------------------------------------------------ - char *GetBuffer( uint32_t offset = 0 ) - { - return pBuffer+offset; - } - - //------------------------------------------------------------------------ - //! Reallocate the buffer to a new location of a given size - //------------------------------------------------------------------------ - void ReAllocate( uint32_t size ) - { - pBuffer = (char *)realloc( pBuffer, size ); - if( !pBuffer ) - throw std::bad_alloc(); - pSize = size; - } - - //------------------------------------------------------------------------ - //! Free the buffer - //------------------------------------------------------------------------ - void Free() - { - free( pBuffer ); - pBuffer = 0; - pSize = 0; - pCursor = 0; - } - - //------------------------------------------------------------------------ - //! Allocate the buffer - //------------------------------------------------------------------------ - void Allocate( uint32_t size ) - { - if( !size ) - return; - - pBuffer = (char *)malloc( size ); - if( !pBuffer ) - throw std::bad_alloc(); - pSize = size; - } - - //------------------------------------------------------------------------ - //! Zero - //------------------------------------------------------------------------ - void Zero() - { - memset( pBuffer, 0, pSize ); - } - - //------------------------------------------------------------------------ - //! Get the size of the message - //------------------------------------------------------------------------ - uint32_t GetSize() const - { - return pSize; - } - - //------------------------------------------------------------------------ - //! Get append cursor - //------------------------------------------------------------------------ - uint32_t GetCursor() const - { - return pCursor; - } - - //------------------------------------------------------------------------ - //! Set the cursor - //------------------------------------------------------------------------ - void SetCursor( uint32_t cursor ) - { - pCursor = cursor; - } - - //------------------------------------------------------------------------ - //! Advance the cursor - //------------------------------------------------------------------------ - void AdvanceCursor( uint32_t delta ) - { - pCursor += delta; - } - - //------------------------------------------------------------------------ - //! Append data at the position pointed to by the append cursor - //------------------------------------------------------------------------ - void Append( const char *buffer, uint32_t size ) - { - uint32_t remaining = pSize-pCursor; - if( remaining < size ) - ReAllocate( pCursor+size ); - - memcpy( pBuffer+pCursor, buffer, size ); - pCursor += size; - } - - //------------------------------------------------------------------------ - //! Append data at the given offset - //------------------------------------------------------------------------ - void Append( const char *buffer, uint32_t size, uint32_t offset ) - { - uint32_t remaining = pSize-offset; - if( remaining < size ) - ReAllocate( offset+size ); - - memcpy( pBuffer+offset, buffer, size ); - } - - //------------------------------------------------------------------------ - //! Get the buffer pointer at the append cursor - //------------------------------------------------------------------------ - char *GetBufferAtCursor() - { - return GetBuffer( pCursor ); - } - - //------------------------------------------------------------------------ - //! Get the buffer pointer at the append cursor - //------------------------------------------------------------------------ - const char *GetBufferAtCursor() const - { - return GetBuffer( pCursor ); - } - - //------------------------------------------------------------------------ - //! Fill the buffer from a string - //------------------------------------------------------------------------ - void FromString( const std::string str ) - { - ReAllocate( str.length()+1 ); - memcpy( pBuffer, str.c_str(), str.length() ); - pBuffer[str.length()] = 0; - } - - //------------------------------------------------------------------------ - //! Convert the buffer to a string - //------------------------------------------------------------------------ - std::string ToString() const - { - char *bf = new char[pSize+1]; - bf[pSize] = 0; - memcpy( bf, pBuffer, pSize ); - std::string tmp = bf; - delete [] bf; - return tmp; - } - - //------------------------------------------------------------------------ - //! Grab a buffer allocated outside - //------------------------------------------------------------------------ - void Grab( char *buffer, uint32_t size ) - { - Free(); - pBuffer = buffer; - pSize = size; - } - - //------------------------------------------------------------------------ - //! Release the buffer - //------------------------------------------------------------------------ - char *Release() - { - char *buffer = pBuffer; - pBuffer = 0; - pSize = 0; - pCursor = 0; - return buffer; - } - - private: - char *pBuffer; - uint32_t pSize; - uint32_t pCursor; - }; -} - -#endif // __XRD_CL_BUFFER_HH__ diff --git a/src/XrdCl/XrdClChannel.cc b/src/XrdCl/XrdClChannel.cc deleted file mode 100644 index 454933990b9..00000000000 --- a/src/XrdCl/XrdClChannel.cc +++ /dev/null @@ -1,365 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2014 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#include "XrdCl/XrdClChannel.hh" -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdCl/XrdClStream.hh" -#include "XrdCl/XrdClSocket.hh" -#include "XrdCl/XrdClConstants.hh" -#include "XrdCl/XrdClLog.hh" -#include "XrdCl/XrdClUglyHacks.hh" -#include "XrdCl/XrdClRedirectorRegistry.hh" - -#include - -namespace -{ - //---------------------------------------------------------------------------- - // Filter handler - //---------------------------------------------------------------------------- - class FilterHandler: public XrdCl::IncomingMsgHandler - { - public: - //------------------------------------------------------------------------ - // Constructor - //------------------------------------------------------------------------ - FilterHandler( XrdCl::MessageFilter *filter ): - pSem( new XrdCl::Semaphore(0) ), pFilter( filter ), pMsg( 0 ) - { - } - - //------------------------------------------------------------------------ - // Destructor - //------------------------------------------------------------------------ - virtual ~FilterHandler() - { - delete pSem; - } - - //------------------------------------------------------------------------ - // Message handler - //------------------------------------------------------------------------ - virtual uint16_t Examine( XrdCl::Message *msg ) - { - if( pFilter->Filter( msg ) ) - return Take | RemoveHandler; - return Ignore; - } - - virtual void Process( XrdCl::Message *msg ) - { - pMsg = msg; - pSem->Post(); - } - - //------------------------------------------------------------------------ - // Handle a fault - //------------------------------------------------------------------------ - virtual uint8_t OnStreamEvent( StreamEvent event, - uint16_t streamNum, - XrdCl::Status status ) - { - if( event == Ready ) - return 0; - pStatus = status; - pSem->Post(); - return RemoveHandler; - } - - //------------------------------------------------------------------------ - // Wait for a status of the message - //------------------------------------------------------------------------ - XrdCl::Status WaitForStatus() - { - pSem->Wait(); - return pStatus; - } - - //------------------------------------------------------------------------ - // Wait for the arrival of the message - //------------------------------------------------------------------------ - XrdCl::Message *GetMessage() - { - return pMsg; - } - - //------------------------------------------------------------------------ - // Get underlying message filter sid - //------------------------------------------------------------------------ - uint16_t GetSid() const - { - if (pFilter) - return pFilter->GetSid(); - - return 0; - } - - private: - FilterHandler(const FilterHandler &other); - FilterHandler &operator = (const FilterHandler &other); - - XrdCl::Semaphore *pSem; - XrdCl::MessageFilter *pFilter; - XrdCl::Message *pMsg; - XrdCl::Status pStatus; - }; - - //---------------------------------------------------------------------------- - // Status handler - //---------------------------------------------------------------------------- - class StatusHandler: public XrdCl::OutgoingMsgHandler - { - public: - //------------------------------------------------------------------------ - // Constructor - //------------------------------------------------------------------------ - StatusHandler( XrdCl::Message *msg ): - pSem( new XrdCl::Semaphore(0) ), - pMsg( msg ) {} - - //------------------------------------------------------------------------ - // Destructor - //------------------------------------------------------------------------ - virtual ~StatusHandler() - { - delete pSem; - } - - //------------------------------------------------------------------------ - // Handle the status information - //------------------------------------------------------------------------ - void OnStatusReady( const XrdCl::Message *message, - XrdCl::Status status ) - { - if( pMsg == message ) - pStatus = status; - pSem->Post(); - } - - //------------------------------------------------------------------------ - // Wait for the status to be ready - //------------------------------------------------------------------------ - XrdCl::Status WaitForStatus() - { - pSem->Wait(); - return pStatus; - } - - private: - StatusHandler(const StatusHandler &other); - StatusHandler &operator = (const StatusHandler &other); - - XrdCl::Semaphore *pSem; - XrdCl::Status pStatus; - XrdCl::Message *pMsg; - }; - - class TickGeneratorTask: public XrdCl::Task - { - public: - //------------------------------------------------------------------------ - // Constructor - //------------------------------------------------------------------------ - TickGeneratorTask( XrdCl::Channel *channel, const std::string &hostId ): - pChannel( channel ) - { - std::string name = "TickGeneratorTask for: "; - name += hostId; - SetName( name ); - } - - //------------------------------------------------------------------------ - // Run the task - //------------------------------------------------------------------------ - time_t Run( time_t now ) - { - using namespace XrdCl; - pChannel->Tick( now ); - - Env *env = DefaultEnv::GetEnv(); - int timeoutResolution = DefaultTimeoutResolution; - env->GetInt( "TimeoutResolution", timeoutResolution ); - return now+timeoutResolution; - } - private: - XrdCl::Channel *pChannel; - }; -} - -namespace XrdCl -{ - - //---------------------------------------------------------------------------- - // Constructor - //---------------------------------------------------------------------------- - Channel::Channel( const URL &url, - Poller *poller, - TransportHandler *transport, - TaskManager *taskManager, - JobManager *jobManager ): - pUrl( url.GetHostId() ), - pPoller( poller ), - pTransport( transport ), - pTaskManager( taskManager ), - pTickGenerator( 0 ), - pJobManager( jobManager ) - { - Env *env = DefaultEnv::GetEnv(); - Log *log = DefaultEnv::GetLog(); - - int timeoutResolution = DefaultTimeoutResolution; - env->GetInt( "TimeoutResolution", timeoutResolution ); - - pTransport->InitializeChannel( pChannelData ); - uint16_t numStreams = transport->StreamNumber( pChannelData ); - log->Debug( PostMasterMsg, "Creating new channel to: %s %d stream(s)", - url.GetHostId().c_str(), numStreams ); - - pUrl.SetParams( url.GetParams() ); - - //-------------------------------------------------------------------------- - // Create the streams - //-------------------------------------------------------------------------- - pStreams.resize( numStreams ); - for( uint16_t i = 0; i < numStreams; ++i ) - { - pStreams[i] = new Stream( &pUrl, i ); - pStreams[i]->SetTransport( transport ); - pStreams[i]->SetPoller( poller ); - pStreams[i]->SetIncomingQueue( &pIncoming ); - pStreams[i]->SetTaskManager( taskManager ); - pStreams[i]->SetJobManager( jobManager ); - pStreams[i]->SetChannelData( &pChannelData ); - pStreams[i]->Initialize(); - } - - //-------------------------------------------------------------------------- - // Register the task generating timeout events - //-------------------------------------------------------------------------- - pTickGenerator = new TickGeneratorTask( this, pUrl.GetHostId() ); - pTaskManager->RegisterTask( pTickGenerator, ::time(0)+timeoutResolution ); - } - - //---------------------------------------------------------------------------- - // Destructor - //---------------------------------------------------------------------------- - Channel::~Channel() - { - pTaskManager->UnregisterTask( pTickGenerator ); - for( uint32_t i = 0; i < pStreams.size(); ++i ) - delete pStreams[i]; - pTransport->FinalizeChannel( pChannelData ); - } - - //---------------------------------------------------------------------------- - // Send a message synchronously - //---------------------------------------------------------------------------- - Status Channel::Send( Message *msg, bool stateful, time_t expires ) - { - StatusHandler sh( msg ); - Status sc = Send( msg, &sh, stateful, expires ); - if( !sc.IsOK() ) - return sc; - sc = sh.WaitForStatus(); - return sc; - } - - //---------------------------------------------------------------------------- - // Send the message asynchronously - //---------------------------------------------------------------------------- - Status Channel::Send( Message *msg, - OutgoingMsgHandler *handler, - bool stateful, - time_t expires ) - - { - PathID path = pTransport->Multiplex( msg, pChannelData ); - return pStreams[path.up]->Send( msg, handler, stateful, expires ); - } - - //---------------------------------------------------------------------------- - // Synchronously receive a message - blocks until a message matching - //---------------------------------------------------------------------------- - Status Channel::Receive( Message *&msg, - MessageFilter *filter, - time_t expires ) - { - FilterHandler fh( filter ); - Status sc = Receive( &fh, expires ); - if( !sc.IsOK() ) - return sc; - - sc = fh.WaitForStatus(); - if( sc.IsOK() ) - msg = fh.GetMessage(); - return sc; - } - - //---------------------------------------------------------------------------- - // Listen to incoming messages - //---------------------------------------------------------------------------- - Status Channel::Receive( IncomingMsgHandler *handler, time_t expires ) - { - pIncoming.AddMessageHandler( handler, expires ); - return Status(); - } - - //---------------------------------------------------------------------------- - // Handle a time event - //---------------------------------------------------------------------------- - void Channel::Tick( time_t now ) - { - std::vector::iterator it; - for( it = pStreams.begin(); it != pStreams.end(); ++it ) - (*it)->Tick( now ); - } - - //---------------------------------------------------------------------------- - // Query the transport handler - //---------------------------------------------------------------------------- - Status Channel::QueryTransport( uint16_t query, AnyObject &result ) - { - return pTransport->Query( query, result, pChannelData ); - } - - //---------------------------------------------------------------------------- - // Register channel event handler - //---------------------------------------------------------------------------- - void Channel::RegisterEventHandler( ChannelEventHandler *handler ) - { - std::vector::iterator it; - for( it = pStreams.begin(); it != pStreams.end(); ++it ) - (*it)->RegisterEventHandler( handler ); - } - - //------------------------------------------------------------------------ - // Remove a channel event handler - //------------------------------------------------------------------------ - void Channel::RemoveEventHandler( ChannelEventHandler *handler ) - { - std::vector::iterator it; - for( it = pStreams.begin(); it != pStreams.end(); ++it ) - (*it)->RemoveEventHandler( handler ); - } -} diff --git a/src/XrdCl/XrdClChannel.hh b/src/XrdCl/XrdClChannel.hh deleted file mode 100644 index 497f47064ab..00000000000 --- a/src/XrdCl/XrdClChannel.hh +++ /dev/null @@ -1,171 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_POST_CHANNEL_HH__ -#define __XRD_CL_POST_CHANNEL_HH__ - -#include -#include -#include - -#include "XrdCl/XrdClStatus.hh" -#include "XrdCl/XrdClURL.hh" -#include "XrdCl/XrdClPoller.hh" -#include "XrdCl/XrdClInQueue.hh" -#include "XrdCl/XrdClPostMasterInterfaces.hh" -#include "XrdCl/XrdClAnyObject.hh" -#include "XrdCl/XrdClTaskManager.hh" - -#include "XrdSys/XrdSysPthread.hh" - -namespace XrdCl -{ - class Stream; - class JobManager; - class VirtualRedirector; - - //---------------------------------------------------------------------------- - //! A communication channel between the client and the server - //---------------------------------------------------------------------------- - class Channel - { - public: - //------------------------------------------------------------------------ - //! Constructor - //! - //! @param url address of the server to connect to - //! @param poller poller object to be used for non-blocking IO - //! @param transport protocol specific transport handler - //! @param taskManager async task handler to be used by the channel - //! @param jobManager worker thread handler to be used by the channel - //------------------------------------------------------------------------ - Channel( const URL &url, - Poller *poller, - TransportHandler *transport, - TaskManager *taskManager, - JobManager *jobManager ); - - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - ~Channel(); - - //------------------------------------------------------------------------ - //! Get the URL - //------------------------------------------------------------------------ - const URL &GetURL() const - { - return pUrl; - } - - //------------------------------------------------------------------------ - //! Send a message synchronously - synchronously means that - //! it will block until the message is written to a socket - //! - //! @param msg message to be sent - //! @param expires expiration timestamp after which a failure should be - //! reported if sending was unsuccessful - //! @param stateful physical stream disconnection causes an error - //! @return success if the message has been pushed through the wire, - //! failure otherwise - //------------------------------------------------------------------------ - Status Send( Message *msg, bool stateful, time_t expires ); - - //------------------------------------------------------------------------ - //! Send the message asynchronously - the message is inserted into the - //! send queue and a listener is called when the message is successfully - //! pushed through the wire or when the timeout elapses - //! - //! @param msg message to be sent - //! @apram stateful physical stream disconnection causes an error - //! @param expires unix timestamp after which a failure is reported - //! to the listener - //! @param handler handler to be notified about the status - //! @param redirector virtual redirector to be used - //! @return success if the message was successfully inserted - //! into the send queues, failure otherwise - //------------------------------------------------------------------------ - Status Send( Message *msg, - OutgoingMsgHandler *handler, - bool stateful, - time_t expires ); - - //------------------------------------------------------------------------ - //! Synchronously receive a message - blocks until a message matching - //! a filter is found in the incoming queue or the timeout passes - //! - //! @param msg reference to a message pointer, the pointer will - //! point to the received message - //! @param filter filter object defining what to look for - //! @param expires expiration timestamp - //! @return success when the message has been received - //! successfully, failure otherwise - //------------------------------------------------------------------------ - Status Receive( Message *&msg, MessageFilter *filter, time_t expires ); - - //------------------------------------------------------------------------ - //! Listen to incoming messages, the listener is notified when a new - //! message arrives and when the timeout passes - //! - //! @param handler handler to be notified about new messages - //! @param expires expiration timestamp - //! @return success when the handler has been registered correctly - //------------------------------------------------------------------------ - Status Receive( IncomingMsgHandler *handler, time_t expires ); - - //------------------------------------------------------------------------ - //! Query the transport handler - //! - //! @param query the query as defined in the TransportQuery struct or - //! others that may be recognized by the protocol transport - //! @param result the result of the query - //! @return status of the query - //------------------------------------------------------------------------ - Status QueryTransport( uint16_t query, AnyObject &result ); - - //------------------------------------------------------------------------ - //! Register channel event handler - //------------------------------------------------------------------------ - void RegisterEventHandler( ChannelEventHandler *handler ); - - //------------------------------------------------------------------------ - //! Remove a channel event handler - //------------------------------------------------------------------------ - void RemoveEventHandler( ChannelEventHandler *handler ); - - //------------------------------------------------------------------------ - //! Handle a time event - //------------------------------------------------------------------------ - void Tick( time_t now ); - - private: - - URL pUrl; - Poller *pPoller; - TransportHandler *pTransport; - TaskManager *pTaskManager; - std::vector pStreams; - XrdSysMutex pMutex; - AnyObject pChannelData; - InQueue pIncoming; - Task *pTickGenerator; - JobManager *pJobManager; - }; -} - -#endif // __XRD_CL_POST_CHANNEL_HH__ diff --git a/src/XrdCl/XrdClChannelHandlerList.cc b/src/XrdCl/XrdClChannelHandlerList.cc deleted file mode 100644 index b6ae7e85366..00000000000 --- a/src/XrdCl/XrdClChannelHandlerList.cc +++ /dev/null @@ -1,70 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#include "XrdCl/XrdClChannelHandlerList.hh" -#include "XrdCl/XrdClPostMasterInterfaces.hh" - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - // Add a channel event handler - //---------------------------------------------------------------------------- - void ChannelHandlerList::AddHandler( ChannelEventHandler *handler ) - { - XrdSysMutexHelper scopedLock( pMutex ); - pHandlers.push_back( handler ); - } - - //---------------------------------------------------------------------------- - // Remove the channel event handler - //---------------------------------------------------------------------------- - void ChannelHandlerList::RemoveHandler( ChannelEventHandler *handler ) - { - XrdSysMutexHelper scopedLock( pMutex ); - std::list::iterator it; - for( it = pHandlers.begin(); it != pHandlers.end(); ++it ) - { - if( *it == handler ) - { - pHandlers.erase( it ); - return; - } - } - } - - //---------------------------------------------------------------------------- - // Report an event to the channel event handlers - //---------------------------------------------------------------------------- - void ChannelHandlerList::ReportEvent( - ChannelEventHandler::ChannelEvent event, - Status status, - uint16_t stream ) - { - XrdSysMutexHelper scopedLock( pMutex ); - std::list::iterator it; - for( it = pHandlers.begin(); it != pHandlers.end(); ) - { - bool st = (*it)->OnChannelEvent( event, status, stream ); - if( !st ) - it = pHandlers.erase( it ); - else - ++it; - } - } -} - diff --git a/src/XrdCl/XrdClChannelHandlerList.hh b/src/XrdCl/XrdClChannelHandlerList.hh deleted file mode 100644 index 39709a87f3c..00000000000 --- a/src/XrdCl/XrdClChannelHandlerList.hh +++ /dev/null @@ -1,59 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_CHANNEL_HANDLER_LIST_HH__ -#define __XRD_CL_CHANNEL_HANDLER_LIST_HH__ - -#include -#include -#include "XrdCl/XrdClPostMasterInterfaces.hh" -#include "XrdCl/XrdClStatus.hh" -#include - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - //! A helper for handling channel event handlers - //---------------------------------------------------------------------------- - class ChannelHandlerList - { - public: - //------------------------------------------------------------------------ - //! Add a channel event handler - //------------------------------------------------------------------------ - void AddHandler( ChannelEventHandler *handler ); - - //------------------------------------------------------------------------ - //! Remove the channel event handler - //------------------------------------------------------------------------ - void RemoveHandler( ChannelEventHandler *handler ); - - //------------------------------------------------------------------------ - //! Report an event to the channel event handlers - //------------------------------------------------------------------------ - void ReportEvent( ChannelEventHandler::ChannelEvent event, - Status status, - uint16_t stream ); - - private: - std::list pHandlers; - XrdSysMutex pMutex; - }; -} - -#endif // __XRD_CL_CHANNEL_HANDLER_LIST_HH__ diff --git a/src/XrdCl/XrdClCheckSumManager.cc b/src/XrdCl/XrdClCheckSumManager.cc deleted file mode 100644 index afdaaa97d03..00000000000 --- a/src/XrdCl/XrdClCheckSumManager.cc +++ /dev/null @@ -1,160 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#include "XrdCl/XrdClCheckSumManager.hh" -#include "XrdCl/XrdClLog.hh" -#include "XrdCl/XrdClUtils.hh" -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdCl/XrdClConstants.hh" -#include "XrdCl/XrdClUglyHacks.hh" -#include "XrdCks/XrdCksCalc.hh" -#include "XrdCks/XrdCksLoader.hh" -#include "XrdCks/XrdCksCalc.hh" -#include "XrdCks/XrdCksCalcmd5.hh" -#include "XrdCks/XrdCksCalccrc32.hh" -#include "XrdCks/XrdCksCalcadler32.hh" -#include "XrdVersion.hh" - -#include -#include -#include -#include - -XrdVERSIONINFOREF( XrdCl ); - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - // Constructor - //---------------------------------------------------------------------------- - CheckSumManager::CheckSumManager() - { - pLoader = new XrdCksLoader( XrdVERSIONINFOVAR( XrdCl ) ); - pCalculators["md5"] = new XrdCksCalcmd5(); - pCalculators["crc32"] = new XrdCksCalccrc32; - pCalculators["adler32"] = new XrdCksCalcadler32; - } - - //---------------------------------------------------------------------------- - // Destructor - //---------------------------------------------------------------------------- - CheckSumManager::~CheckSumManager() - { - CalcMap::iterator it; - for( it = pCalculators.begin(); it != pCalculators.end(); ++it ) - delete it->second; - delete pLoader; - } - - //---------------------------------------------------------------------------- - // Get a calculator - //---------------------------------------------------------------------------- - XrdCksCalc *CheckSumManager::GetCalculator( const std::string &algName ) - { - Log *log = DefaultEnv::GetLog(); - XrdSysMutexHelper scopedLock( pMutex ); - CalcMap::iterator it = pCalculators.find( algName ); - if( it == pCalculators.end() ) - { - char *errBuff = new char[1024]; - log->Dump( UtilityMsg, "Attempting to load a calculator for: %s", - algName.c_str() ); - XrdCksCalc *c = pLoader->Load( algName.c_str(), "", errBuff, 1024 ); - if( !c ) - { - log->Error( UtilityMsg, "Unable to load %s calculator: %s", - algName.c_str(), errBuff ); - delete [] errBuff; - return 0; - - } - delete [] errBuff; - - pCalculators[algName] = c; - return c->New(); - } - return it->second->New();; - } - - //---------------------------------------------------------------------------- - // Stop the manager - //---------------------------------------------------------------------------- - bool CheckSumManager::Calculate( XrdCksData &result, - const std::string &algName, - const std::string &filePath ) - { - //-------------------------------------------------------------------------- - // Get a calculator - //-------------------------------------------------------------------------- - Log *log = DefaultEnv::GetLog(); - XrdCksCalc *calc = GetCalculator( algName ); - - if( !calc ) - { - log->Error( UtilityMsg, "Unable to get a calculator for %s", - algName.c_str() ); - return false; - } - XRDCL_SMART_PTR_T calcPtr( calc ); - - //-------------------------------------------------------------------------- - // Open the file - //-------------------------------------------------------------------------- - log->Debug( UtilityMsg, "Opening %s for reading (checksum calc)", - filePath.c_str() ); - - int fd = open( filePath.c_str(), O_RDONLY ); - if( fd == -1 ) - { - log->Error( UtilityMsg, "Unable to open %s: %s", filePath.c_str(), - strerror( errno ) ); - return false; - } - - //-------------------------------------------------------------------------- - // Calculate the checksum - //-------------------------------------------------------------------------- - const uint32_t buffSize = 2*1024*1024; - char *buffer = new char[buffSize]; - int64_t bytesRead = 0; - - while( (bytesRead = read( fd, buffer, buffSize )) ) - { - if( bytesRead == -1 ) - { - log->Error( UtilityMsg, "Unable read from %s: %s", filePath.c_str(), - strerror( errno ) ); - close( fd ); - delete [] buffer; - return false; - } - calc->Update( buffer, bytesRead ); - } - - int size; - calc->Type( size ); - result.Set( (void*)calc->Final(), size ); - - //-------------------------------------------------------------------------- - // Clean up - //-------------------------------------------------------------------------- - delete [] buffer; - close( fd ); - return true; - } -} diff --git a/src/XrdCl/XrdClCheckSumManager.hh b/src/XrdCl/XrdClCheckSumManager.hh deleted file mode 100644 index f0653ebc0c0..00000000000 --- a/src/XrdCl/XrdClCheckSumManager.hh +++ /dev/null @@ -1,81 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2012-2014 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_CHECK_SUM_MANAGER_HH__ -#define __XRD_CL_CHECK_SUM_MANAGER_HH__ - -#include -#include -#include "XrdSys/XrdSysPthread.hh" -#include "XrdCks/XrdCksData.hh" - -class XrdCksLoader; -class XrdCksCalc; - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - //! Manage the checksum calc objects - //---------------------------------------------------------------------------- - class CheckSumManager - { - public: - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - CheckSumManager(); - - //------------------------------------------------------------------------ - // Destructor - //------------------------------------------------------------------------ - virtual ~CheckSumManager(); - - //------------------------------------------------------------------------ - //! Get the check sum calc object for a given checksum type - //! - //! @param algName name of the checksumming algorithm - //! @return the appropriate calc object (must be deleted by the user) - //! or 0 if a calculator cannot be obtained - //------------------------------------------------------------------------ - XrdCksCalc *GetCalculator( const std::string &algName ); - - //------------------------------------------------------------------------ - //! Calculate a checksum of for a given file - //------------------------------------------------------------------------ - bool Calculate( XrdCksData &result, - const std::string &algName, - const std::string &filePath ); - - private: - CheckSumManager(const CheckSumManager &other); - CheckSumManager &operator = (const CheckSumManager &other); - - typedef std::map CalcMap; - CalcMap pCalculators; - XrdCksLoader *pLoader; - XrdSysMutex pMutex; - }; -} - -#endif // __XRD_CL_CHECK_SUM_MANAGER_HH__ diff --git a/src/XrdCl/XrdClClassicCopyJob.cc b/src/XrdCl/XrdClClassicCopyJob.cc deleted file mode 100644 index c48e93274a5..00000000000 --- a/src/XrdCl/XrdClClassicCopyJob.cc +++ /dev/null @@ -1,1611 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2014 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#include "XrdCl/XrdClClassicCopyJob.hh" -#include "XrdCl/XrdClConstants.hh" -#include "XrdCl/XrdClLog.hh" -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdCl/XrdClFile.hh" -#include "XrdCl/XrdClMonitor.hh" -#include "XrdCl/XrdClUtils.hh" -#include "XrdCl/XrdClCheckSumManager.hh" -#include "XrdCks/XrdCksCalc.hh" -#include "XrdCl/XrdClUglyHacks.hh" -#include "XrdCl/XrdClRedirectorRegistry.hh" -#include "XrdCl/XrdClZipArchiveReader.hh" -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include "XrdClXCpCtx.hh" - -namespace -{ - //---------------------------------------------------------------------------- - //! Check sum helper for stdio - //---------------------------------------------------------------------------- - class CheckSumHelper - { - public: - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - CheckSumHelper( const std::string &name, - const std::string &ckSumType ): - pName( name ), - pCkSumType( ckSumType ), - pCksCalcObj( 0 ) - {}; - - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - virtual ~CheckSumHelper() - { - delete pCksCalcObj; - } - - //------------------------------------------------------------------------ - //! Initialize - //------------------------------------------------------------------------ - XrdCl::XRootDStatus Initialize() - { - using namespace XrdCl; - if( pCkSumType.empty() ) - return XRootDStatus(); - - Log *log = DefaultEnv::GetLog(); - CheckSumManager *cksMan = DefaultEnv::GetCheckSumManager(); - - if( !cksMan ) - { - log->Error( UtilityMsg, "Unable to get the checksum manager" ); - return XRootDStatus( stError, errInternal ); - } - - pCksCalcObj = cksMan->GetCalculator( pCkSumType ); - if( !pCksCalcObj ) - { - log->Error( UtilityMsg, "Unable to get a calculator for %s", - pCkSumType.c_str() ); - return XRootDStatus( stError, errCheckSumError ); - } - - return XRootDStatus(); - } - - //------------------------------------------------------------------------ - // Update the checksum - //------------------------------------------------------------------------ - void Update( const void *buffer, uint32_t size ) - { - if( pCksCalcObj ) - pCksCalcObj->Update( (const char *)buffer, size ); - } - - //------------------------------------------------------------------------ - // Get checksum - //------------------------------------------------------------------------ - XrdCl::XRootDStatus GetCheckSum( std::string &checkSum, - std::string &checkSumType ) - { - using namespace XrdCl; - Log *log = DefaultEnv::GetLog(); - - //---------------------------------------------------------------------- - // Sanity check - //---------------------------------------------------------------------- - if( !pCksCalcObj ) - { - log->Error( UtilityMsg, "Calculator for %s was not initialized", - pCkSumType.c_str() ); - return XRootDStatus( stError, errCheckSumError ); - } - - int calcSize = 0; - std::string calcType = pCksCalcObj->Type( calcSize ); - - if( calcType != checkSumType ) - { - log->Error( UtilityMsg, "Calculated checksum: %s, requested " - "checksum: %s", pCkSumType.c_str(), - checkSumType.c_str() ); - return XRootDStatus( stError, errCheckSumError ); - } - - //---------------------------------------------------------------------- - // Response - //---------------------------------------------------------------------- - XrdCksData ckSum; - ckSum.Set( checkSumType.c_str() ); - ckSum.Set( (void*)pCksCalcObj->Final(), calcSize ); - char *cksBuffer = new char[265]; - ckSum.Get( cksBuffer, 256 ); - checkSum = checkSumType + ":"; - checkSum += Utils::NormalizeChecksum( checkSumType, cksBuffer ); - delete [] cksBuffer; - - log->Dump( UtilityMsg, "Checksum for %s is: %s", pName.c_str(), - checkSum.c_str() ); - return XrdCl::XRootDStatus(); - } - - private: - std::string pName; - std::string pCkSumType; - XrdCksCalc *pCksCalcObj; - }; - - //---------------------------------------------------------------------------- - //! Abstract chunk source - //---------------------------------------------------------------------------- - class Source - { - public: - //------------------------------------------------------------------------ - // Destructor - //------------------------------------------------------------------------ - virtual ~Source() {}; - - //------------------------------------------------------------------------ - //! Initialize the source - //------------------------------------------------------------------------ - virtual XrdCl::XRootDStatus Initialize() = 0; - - //------------------------------------------------------------------------ - //! Get size - //------------------------------------------------------------------------ - virtual int64_t GetSize() = 0; - - //------------------------------------------------------------------------ - //! Get a data chunk from the source - //! - //! @param ci chunk information - //! @return status of the operation - //! suContinue - there are some chunks left - //! suDone - no chunks left - //------------------------------------------------------------------------ - virtual XrdCl::XRootDStatus GetChunk( XrdCl::ChunkInfo &ci ) = 0; - - //------------------------------------------------------------------------ - //! Get check sum - //------------------------------------------------------------------------ - virtual XrdCl::XRootDStatus GetCheckSum( std::string &checkSum, - std::string &checkSumType ) = 0; - }; - - //---------------------------------------------------------------------------- - //! Abstract chunk destination - //---------------------------------------------------------------------------- - class Destination - { - public: - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - Destination(): - pPosc( false ), pForce( false ), pCoerce( false ), pMakeDir( false ) {} - - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - virtual ~Destination() {} - - //------------------------------------------------------------------------ - //! Initialize the destination - //------------------------------------------------------------------------ - virtual XrdCl::XRootDStatus Initialize() = 0; - - //------------------------------------------------------------------------ - //! Finalize the destination - //------------------------------------------------------------------------ - virtual XrdCl::XRootDStatus Finalize() = 0; - - //------------------------------------------------------------------------ - //! Put a data chunk at a destination - //! - //! @param ci chunk information - //! @return status of the operation - //------------------------------------------------------------------------ - virtual XrdCl::XRootDStatus PutChunk( XrdCl::ChunkInfo &ci ) = 0; - - //------------------------------------------------------------------------ - //! Flush chunks that might have been queues - //------------------------------------------------------------------------ - virtual XrdCl::XRootDStatus Flush() = 0; - - //------------------------------------------------------------------------ - //! Get check sum - //------------------------------------------------------------------------ - virtual XrdCl::XRootDStatus GetCheckSum( std::string &checkSum, - std::string &checkSumType ) = 0; - - //------------------------------------------------------------------------ - //! Set POSC - //------------------------------------------------------------------------ - void SetPOSC( bool posc ) - { - pPosc = posc; - } - - //------------------------------------------------------------------------ - //! Set force - //------------------------------------------------------------------------ - void SetForce( bool force ) - { - pForce = force; - } - - //------------------------------------------------------------------------ - //! Set coerce - //------------------------------------------------------------------------ - void SetCoerce( bool coerce ) - { - pCoerce = coerce; - } - - //------------------------------------------------------------------------ - //! Set makedir - //------------------------------------------------------------------------ - void SetMakeDir( bool makedir ) - { - pMakeDir = makedir; - } - - protected: - bool pPosc; - bool pForce; - bool pCoerce; - bool pMakeDir; - }; - - //---------------------------------------------------------------------------- - //! StdIn source - //---------------------------------------------------------------------------- - class StdInSource: public Source - { - public: - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - StdInSource( const std::string &ckSumType, uint32_t chunkSize ): - pCkSumHelper(0), pCurrentOffset(0), pChunkSize( chunkSize ) - { - if( !ckSumType.empty() ) - pCkSumHelper = new CheckSumHelper( "stdin", ckSumType ); - } - - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - virtual ~StdInSource() - { - delete pCkSumHelper; - } - - //------------------------------------------------------------------------ - //! Initialize the source - //------------------------------------------------------------------------ - virtual XrdCl::XRootDStatus Initialize() - { - if( pCkSumHelper ) - return pCkSumHelper->Initialize(); - return XrdCl::XRootDStatus(); - } - - //------------------------------------------------------------------------ - //! Get size - //------------------------------------------------------------------------ - virtual int64_t GetSize() - { - return -1; - } - - //------------------------------------------------------------------------ - //! Get a data chunk from the source - //------------------------------------------------------------------------ - virtual XrdCl::XRootDStatus GetChunk( XrdCl::ChunkInfo &ci ) - { - using namespace XrdCl; - Log *log = DefaultEnv::GetLog(); - - uint32_t toRead = pChunkSize; - char *buffer = new char[toRead]; - - int64_t bytesRead = 0; - uint32_t offset = 0; - while( toRead ) - { - int64_t bRead = read( 0, buffer+offset, toRead ); - if( bRead == -1 ) - { - log->Debug( UtilityMsg, "Unable to read from stdin: %s", - strerror( errno ) ); - delete [] buffer; - return XRootDStatus( stError, errOSError, errno ); - } - - if( bRead == 0 ) - break; - - bytesRead += bRead; - offset += bRead; - toRead -= bRead; - } - - if( bytesRead == 0 ) - { - delete [] buffer; - return XRootDStatus( stOK, suDone ); - } - - if( pCkSumHelper ) - pCkSumHelper->Update( buffer, bytesRead ); - - ci.offset = pCurrentOffset; - ci.length = bytesRead; - ci.buffer = buffer; - pCurrentOffset += bytesRead; - return XRootDStatus( stOK, suContinue ); - } - - //------------------------------------------------------------------------ - //! Get check sum - //------------------------------------------------------------------------ - virtual XrdCl::XRootDStatus GetCheckSum( std::string &checkSum, - std::string &checkSumType ) - { - using namespace XrdCl; - if( pCkSumHelper ) - return pCkSumHelper->GetCheckSum( checkSum, checkSumType ); - return XRootDStatus( stError, errCheckSumError ); - } - - private: - StdInSource(const StdInSource &other); - StdInSource &operator = (const StdInSource &other); - - CheckSumHelper *pCkSumHelper; - uint64_t pCurrentOffset; - uint32_t pChunkSize; - }; - - //---------------------------------------------------------------------------- - //! XRootDSource - //---------------------------------------------------------------------------- - class XRootDSource: public Source - { - public: - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - XRootDSource( const XrdCl::URL *url, - uint32_t chunkSize, - uint8_t parallelChunks ): - pUrl( url ), pFile( new XrdCl::File() ), pSize( -1 ), - pCurrentOffset( 0 ), pChunkSize( chunkSize ), - pParallel( parallelChunks ) - { - } - - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - virtual ~XRootDSource() - { - CleanUpChunks(); - XrdCl::XRootDStatus status = pFile->Close(); - delete pFile; - } - - //------------------------------------------------------------------------ - //! Initialize the source - //------------------------------------------------------------------------ - virtual XrdCl::XRootDStatus Initialize() - { - using namespace XrdCl; - Log *log = DefaultEnv::GetLog(); - log->Debug( UtilityMsg, "Opening %s for reading", - pUrl->GetURL().c_str() ); - - std::string value; - DefaultEnv::GetEnv()->GetString( "ReadRecovery", value ); - pFile->SetProperty( "ReadRecovery", value ); - - XRootDStatus st = pFile->Open( pUrl->GetURL(), OpenFlags::Read ); - if( !st.IsOK() ) - return st; - - StatInfo *statInfo; - st = pFile->Stat( false, statInfo ); - if( !st.IsOK() ) - return st; - - pSize = statInfo->GetSize(); - delete statInfo; - - return XRootDStatus(); - } - - //------------------------------------------------------------------------ - //! Get size - //------------------------------------------------------------------------ - virtual int64_t GetSize() - { - return pSize; - } - - //------------------------------------------------------------------------ - //! Get a data chunk from the source - //! - //! @param buffer buffer for the data - //! @param ci chunk information - //! @return status of the operation - //! suContinue - there are some chunks left - //! suDone - no chunks left - //------------------------------------------------------------------------ - virtual XrdCl::XRootDStatus GetChunk( XrdCl::ChunkInfo &ci ) - { - //---------------------------------------------------------------------- - // Sanity check - //---------------------------------------------------------------------- - using namespace XrdCl; - Log *log = DefaultEnv::GetLog(); - - if( !pFile->IsOpen() ) - return XRootDStatus( stError, errUninitialized ); - - //---------------------------------------------------------------------- - // Fill the queue - //---------------------------------------------------------------------- - while( pChunks.size() < pParallel && pCurrentOffset < pSize ) - { - uint64_t chunkSize = pChunkSize; - if( pCurrentOffset + chunkSize > (uint64_t)pSize ) - chunkSize = pSize - pCurrentOffset; - - char *buffer = new char[chunkSize]; - ChunkHandler *ch = new ChunkHandler; - ch->chunk.offset = pCurrentOffset; - ch->chunk.length = chunkSize; - ch->chunk.buffer = buffer; - ch->status = pFile->Read( pCurrentOffset, chunkSize, buffer, ch ); - pChunks.push( ch ); - pCurrentOffset += chunkSize; - if( !ch->status.IsOK() ) - { - ch->sem->Post(); - break; - } - } - - //---------------------------------------------------------------------- - // Pick up a chunk from the front and wait for status - //---------------------------------------------------------------------- - if( pChunks.empty() ) - return XRootDStatus( stOK, suDone ); - - XRDCL_SMART_PTR_T ch( pChunks.front() ); - pChunks.pop(); - ch->sem->Wait(); - - if( !ch->status.IsOK() ) - { - log->Debug( UtilityMsg, "Unable read %d bytes at %ld from %s: %s", - ch->chunk.length, ch->chunk.offset, - pUrl->GetURL().c_str(), ch->status.ToStr().c_str() ); - delete [] (char *)ch->chunk.buffer; - CleanUpChunks(); - return ch->status; - } - - ci = ch->chunk; - return XRootDStatus( stOK, suContinue ); - } - - //------------------------------------------------------------------------ - // Clean up the chunks that are flying - //------------------------------------------------------------------------ - void CleanUpChunks() - { - while( !pChunks.empty() ) - { - ChunkHandler *ch = pChunks.front(); - pChunks.pop(); - ch->sem->Wait(); - delete [] (char *)ch->chunk.buffer; - delete ch; - } - } - - //------------------------------------------------------------------------ - // Get check sum - //------------------------------------------------------------------------ - virtual XrdCl::XRootDStatus GetCheckSum( std::string &checkSum, - std::string &checkSumType ) - { - if( pUrl->IsMetalink() ) - { - XrdCl::RedirectorRegistry ®istry = XrdCl::RedirectorRegistry::Instance(); - XrdCl::VirtualRedirector *redirector = registry.Get( *pUrl ); - checkSum = redirector->GetCheckSum( checkSumType ); - if( !checkSum.empty() ) return XrdCl::XRootDStatus(); - } - - if( pUrl->IsLocalFile() ) - return XrdCl::Utils::GetLocalCheckSum( checkSum, checkSumType, pUrl->GetPath() ); - - std::string dataServer; pFile->GetProperty( "DataServer", dataServer ); - std::string lastUrl; pFile->GetProperty( "LastURL", lastUrl ); - return XrdCl::Utils::GetRemoteCheckSum( checkSum, checkSumType, - dataServer, XrdCl::URL( lastUrl ).GetPath() ); - } - - private: - XRootDSource(const XRootDSource &other); - XRootDSource &operator = (const XRootDSource &other); - - //------------------------------------------------------------------------ - // Asynchronous chunk handler - //------------------------------------------------------------------------ - class ChunkHandler: public XrdCl::ResponseHandler - { - public: - ChunkHandler(): sem( new XrdCl::Semaphore(0) ) {} - virtual ~ChunkHandler() { delete sem; } - virtual void HandleResponse( XrdCl::XRootDStatus *statusval, - XrdCl::AnyObject *response ) - { - this->status = *statusval; - delete statusval; - if( response ) - { - XrdCl::ChunkInfo *resp = 0; - response->Get( resp ); - if( resp ) - chunk = *resp; - delete response; - } - sem->Post(); - } - - XrdCl::Semaphore *sem; - XrdCl::ChunkInfo chunk; - XrdCl::XRootDStatus status; - }; - const XrdCl::URL *pUrl; - XrdCl::File *pFile; - int64_t pSize; - int64_t pCurrentOffset; - uint32_t pChunkSize; - uint8_t pParallel; - std::queue pChunks; - }; - - //---------------------------------------------------------------------------- - //! XRootDSource - //---------------------------------------------------------------------------- - class XRootDSourceZip: public Source - { - public: - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - XRootDSourceZip( const std::string &filename, const XrdCl::URL *archive, - uint32_t chunkSize, - uint8_t parallelChunks ): - pArchiveUrl( archive ), pFilename( filename ), pZipArchive( new XrdCl::ZipArchiveReader() ), pSize( 0 ), - pCurrentOffset( 0 ), pChunkSize( chunkSize ), - pParallel( parallelChunks ) - { - } - - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - virtual ~XRootDSourceZip() - { - CleanUpChunks(); - XrdCl::XRootDStatus status = pZipArchive->Close(); - delete pZipArchive; - } - - //------------------------------------------------------------------------ - //! Initialize the source - //------------------------------------------------------------------------ - virtual XrdCl::XRootDStatus Initialize() - { - using namespace XrdCl; - Log *log = DefaultEnv::GetLog(); - log->Debug( UtilityMsg, "Opening %s for reading", - pArchiveUrl->GetURL().c_str() ); - - std::string value; - DefaultEnv::GetEnv()->GetString( "ReadRecovery", value ); - pZipArchive->SetProperty( "ReadRecovery", value ); - - XRootDStatus st = pZipArchive->Open( pArchiveUrl->GetURL() ); - if( !st.IsOK() ) - return st; - - return pZipArchive->GetSize( pFilename, pSize ); - } - - //------------------------------------------------------------------------ - //! Get size - //------------------------------------------------------------------------ - virtual int64_t GetSize() - { - return pSize; - } - - //------------------------------------------------------------------------ - //! Get a data chunk from the source - //! - //! @param buffer buffer for the data - //! @param ci chunk information - //! @return status of the operation - //! suContinue - there are some chunks left - //! suDone - no chunks left - //------------------------------------------------------------------------ - virtual XrdCl::XRootDStatus GetChunk( XrdCl::ChunkInfo &ci ) - { - //---------------------------------------------------------------------- - // Sanity check - //---------------------------------------------------------------------- - using namespace XrdCl; - Log *log = DefaultEnv::GetLog(); - - if( !pZipArchive->IsOpen() ) - return XRootDStatus( stError, errUninitialized ); - - //---------------------------------------------------------------------- - // Fill the queue - //---------------------------------------------------------------------- - while( pChunks.size() < pParallel && pCurrentOffset < pSize ) - { - uint64_t chunkSize = pChunkSize; - if( pCurrentOffset + chunkSize > (uint64_t)pSize ) - chunkSize = pSize - pCurrentOffset; - - char *buffer = new char[chunkSize]; - ChunkHandler *ch = new ChunkHandler; - ch->chunk.offset = pCurrentOffset; - ch->chunk.length = chunkSize; - ch->chunk.buffer = buffer; - ch->status = pZipArchive->Read( pFilename, pCurrentOffset, chunkSize, buffer, ch ); - pChunks.push( ch ); - pCurrentOffset += chunkSize; - if( !ch->status.IsOK() ) - { - ch->sem->Post(); - break; - } - } - - //---------------------------------------------------------------------- - // Pick up a chunk from the front and wait for status - //---------------------------------------------------------------------- - if( pChunks.empty() ) - return XRootDStatus( stOK, suDone ); - - XRDCL_SMART_PTR_T ch( pChunks.front() ); - pChunks.pop(); - ch->sem->Wait(); - - if( !ch->status.IsOK() ) - { - log->Debug( UtilityMsg, "Unable read %d bytes at %ld from %s: %s", - ch->chunk.length, ch->chunk.offset, - pArchiveUrl->GetURL().c_str(), ch->status.ToStr().c_str() ); - delete [] (char *)ch->chunk.buffer; - CleanUpChunks(); - return ch->status; - } - - ci = ch->chunk; - return XRootDStatus( stOK, suContinue ); - } - - //------------------------------------------------------------------------ - // Clean up the chunks that are flying - //------------------------------------------------------------------------ - void CleanUpChunks() - { - while( !pChunks.empty() ) - { - ChunkHandler *ch = pChunks.front(); - pChunks.pop(); - ch->sem->Wait(); - delete [] (char *)ch->chunk.buffer; - delete ch; - } - } - - //------------------------------------------------------------------------ - // Get check sum - //------------------------------------------------------------------------ - virtual XrdCl::XRootDStatus GetCheckSum( std::string &checkSum, - std::string &checkSumType ) - { - return XrdCl::XRootDStatus( XrdCl::stError, XrdCl::errNotSupported ); - } - - private: - XRootDSourceZip(const XRootDSource &other); - XRootDSourceZip &operator = (const XRootDSource &other); - - //------------------------------------------------------------------------ - // Asynchronous chunk handler - //------------------------------------------------------------------------ - class ChunkHandler: public XrdCl::ResponseHandler - { - public: - ChunkHandler(): sem( new XrdCl::Semaphore(0) ) {} - virtual ~ChunkHandler() { delete sem; } - virtual void HandleResponse( XrdCl::XRootDStatus *statusval, - XrdCl::AnyObject *response ) - { - this->status = *statusval; - delete statusval; - if( response ) - { - XrdCl::ChunkInfo *resp = 0; - response->Get( resp ); - if( resp ) - chunk = *resp; - delete response; - } - sem->Post(); - } - - XrdCl::Semaphore *sem; - XrdCl::ChunkInfo chunk; - XrdCl::XRootDStatus status; - }; - const XrdCl::URL *pArchiveUrl; - const std::string pFilename; - XrdCl::ZipArchiveReader *pZipArchive; - uint32_t pSize; - int64_t pCurrentOffset; - uint32_t pChunkSize; - uint8_t pParallel; - std::queue pChunks; - }; - - //---------------------------------------------------------------------------- - //! XRootDSourceDynamic - //---------------------------------------------------------------------------- - class XRootDSourceDynamic: public Source - { - public: - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - XRootDSourceDynamic( const XrdCl::URL *url, - uint32_t chunkSize ): - pUrl( url ), pFile( new XrdCl::File() ), pCurrentOffset( 0 ), - pChunkSize( chunkSize ), pDone( false ) - { - } - - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - virtual ~XRootDSourceDynamic() - { - XrdCl::XRootDStatus status = pFile->Close(); - delete pFile; - } - - //------------------------------------------------------------------------ - //! Initialize the source - //------------------------------------------------------------------------ - virtual XrdCl::XRootDStatus Initialize() - { - using namespace XrdCl; - Log *log = DefaultEnv::GetLog(); - log->Debug( UtilityMsg, "Opening %s for reading", - pUrl->GetURL().c_str() ); - - std::string value; - DefaultEnv::GetEnv()->GetString( "ReadRecovery", value ); - pFile->SetProperty( "ReadRecovery", value ); - - XRootDStatus st = pFile->Open( pUrl->GetURL(), OpenFlags::Read ); - if( !st.IsOK() ) - return st; - - return XRootDStatus(); - } - - //------------------------------------------------------------------------ - //! Get size - //------------------------------------------------------------------------ - virtual int64_t GetSize() - { - return -1; - } - - //------------------------------------------------------------------------ - //! Get a data chunk from the source - //! - //! @param buffer buffer for the data - //! @param ci chunk information - //! @return status of the operation - //! suContinue - there are some chunks left - //! suDone - no chunks left - //------------------------------------------------------------------------ - virtual XrdCl::XRootDStatus GetChunk( XrdCl::ChunkInfo &ci ) - { - //---------------------------------------------------------------------- - // Sanity check - //---------------------------------------------------------------------- - using namespace XrdCl; - - if( !pFile->IsOpen() ) - return XRootDStatus( stError, errUninitialized ); - - if( pDone ) - return XRootDStatus( stOK, suDone ); - - //---------------------------------------------------------------------- - // Fill the queue - //---------------------------------------------------------------------- - char *buffer = new char[pChunkSize]; - uint32_t bytesRead = 0; - - XRootDStatus st = pFile->Read( pCurrentOffset, pChunkSize, buffer, - bytesRead ); - - if( !st.IsOK() ) - { - delete [] buffer; - return st; - } - - if( !bytesRead ) - { - delete [] buffer; - return XRootDStatus( stOK, suDone ); - } - - if( bytesRead < pChunkSize ) - pDone = true; - - ci.offset = pCurrentOffset; - ci.length = bytesRead; - ci.buffer = buffer; - - pCurrentOffset += bytesRead; - - return XRootDStatus( stOK, suContinue ); - } - - //------------------------------------------------------------------------ - // Get check sum - //------------------------------------------------------------------------ - virtual XrdCl::XRootDStatus GetCheckSum( std::string &checkSum, - std::string &checkSumType ) - { - if( pUrl->IsMetalink() ) - { - XrdCl::RedirectorRegistry ®istry = XrdCl::RedirectorRegistry::Instance(); - XrdCl::VirtualRedirector *redirector = registry.Get( *pUrl ); - checkSum = redirector->GetCheckSum( checkSumType ); - if( !checkSum.empty() ) return XrdCl::XRootDStatus(); - } - - std::string dataServer; pFile->GetProperty( "DataServer", dataServer ); - std::string lastUrl; pFile->GetProperty( "LastURL", lastUrl ); - return XrdCl::Utils::GetRemoteCheckSum( checkSum, checkSumType, dataServer, - XrdCl::URL( lastUrl ).GetPath() ); - } - - private: - XRootDSourceDynamic(const XRootDSourceDynamic &other); - XRootDSourceDynamic &operator = (const XRootDSourceDynamic &other); - const XrdCl::URL *pUrl; - XrdCl::File *pFile; - int64_t pCurrentOffset; - uint32_t pChunkSize; - bool pDone; - }; - - //---------------------------------------------------------------------------- - //! XRootDSourceDynamic - //---------------------------------------------------------------------------- - class XRootDSourceXCp: public Source - { - public: - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - XRootDSourceXCp( const XrdCl::URL* url, uint32_t chunkSize, uint16_t parallelChunks, int32_t nbSrc, uint64_t blockSize ): - pXCpCtx( 0 ), pUrl( url ), pChunkSize( chunkSize ), pParallelChunks( parallelChunks ), pNbSrc( nbSrc ), pBlockSize( blockSize ) - { - } - - ~XRootDSourceXCp() - { - if( pXCpCtx ) - pXCpCtx->Delete(); - } - - //------------------------------------------------------------------------ - //! Initialize the source - //------------------------------------------------------------------------ - virtual XrdCl::XRootDStatus Initialize() - { - XrdCl::Log *log = XrdCl::DefaultEnv::GetLog(); - int64_t fileSize = -1; - - if( pUrl->IsMetalink() ) - { - XrdCl::RedirectorRegistry ®istry = XrdCl::RedirectorRegistry::Instance(); - XrdCl::VirtualRedirector *redirector = registry.Get( *pUrl ); - fileSize = redirector->GetSize(); - pReplicas = redirector->GetReplicas(); - } - else - { - XrdCl::LocationInfo *li = 0; - XrdCl::FileSystem fs( *pUrl ); - XrdCl::XRootDStatus st = fs.DeepLocate( pUrl->GetPath(), XrdCl::OpenFlags::Compress | XrdCl::OpenFlags::PrefName, li ); - if( !st.IsOK() ) return st; - - XrdCl::LocationInfo::Iterator itr; - for( itr = li->Begin(); itr != li->End(); ++itr) - { - std::string url = "root://" + itr->GetAddress() + "/" + pUrl->GetPath(); - pReplicas.push_back( url ); - } - - delete li; - } - - std::stringstream ss; - ss << "XCp sources: "; - - std::vector::iterator itr; - for( itr = pReplicas.begin() ; itr != pReplicas.end() ; ++itr ) - { - ss << *itr << ", "; - } - log->Debug( XrdCl::UtilityMsg, ss.str().c_str() ); - - pXCpCtx = new XrdCl::XCpCtx( pReplicas, pBlockSize, pNbSrc, pChunkSize, pParallelChunks, fileSize ); - - return pXCpCtx->Initialize(); - } - - //------------------------------------------------------------------------ - //! Get size - //------------------------------------------------------------------------ - virtual int64_t GetSize() - { - return pXCpCtx->GetSize(); - } - - //------------------------------------------------------------------------ - //! Get a data chunk from the source - //! - //! @param buffer buffer for the data - //! @param ci chunk information - //! @return status of the operation - //! suContinue - there are some chunks left - //! suDone - no chunks left - //------------------------------------------------------------------------ - virtual XrdCl::XRootDStatus GetChunk( XrdCl::ChunkInfo &ci ) - { - XrdCl::XRootDStatus st; - do - { - st = pXCpCtx->GetChunk( ci ); - } - while( st.IsOK() && st.code == XrdCl::suRetry ); - return st; - } - - //------------------------------------------------------------------------ - // Get check sum - //------------------------------------------------------------------------ - virtual XrdCl::XRootDStatus GetCheckSum( std::string &checkSum, - std::string &checkSumType ) - { - if( pUrl->IsMetalink() ) - { - XrdCl::RedirectorRegistry ®istry = XrdCl::RedirectorRegistry::Instance(); - XrdCl::VirtualRedirector *redirector = registry.Get( *pUrl ); - checkSum = redirector->GetCheckSum( checkSumType ); - if( !checkSum.empty() ) return XrdCl::XRootDStatus(); - } - - std::vector::iterator itr; - for( itr = pReplicas.begin() ; itr != pReplicas.end() ; ++itr ) - { - XrdCl::URL url( *itr ); - XrdCl::XRootDStatus st = XrdCl::Utils::GetRemoteCheckSum( checkSum, - checkSumType, url.GetHostId(), url.GetPath() ); - if( st.IsOK() ) return st; - } - - return XrdCl::XRootDStatus( XrdCl::stError, XrdCl::errNoMoreReplicas ); - } - - private: - - - XrdCl::XCpCtx *pXCpCtx; - const XrdCl::URL *pUrl; - std::vector pReplicas; - uint32_t pChunkSize; - uint16_t pParallelChunks; - int32_t pNbSrc; - uint64_t pBlockSize; - }; - - //---------------------------------------------------------------------------- - //! SrdOut destination - //---------------------------------------------------------------------------- - class StdOutDestination: public Destination - { - public: - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - StdOutDestination( const std::string &ckSumType ): - pCkSumHelper( "stdout", ckSumType ), pCurrentOffset(0) - { - } - - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - virtual ~StdOutDestination() - { - } - - //------------------------------------------------------------------------ - //! Initialize the destination - //------------------------------------------------------------------------ - virtual XrdCl::XRootDStatus Initialize() - { - return pCkSumHelper.Initialize(); - } - - //------------------------------------------------------------------------ - //! Finalize the destination - //------------------------------------------------------------------------ - virtual XrdCl::XRootDStatus Finalize() - { - return XrdCl::XRootDStatus(); - } - - //------------------------------------------------------------------------ - //! Put a data chunk at a destination - //! - //! @param ci chunk information - //! @return status of the operation - //------------------------------------------------------------------------ - virtual XrdCl::XRootDStatus PutChunk( XrdCl::ChunkInfo &ci ) - { - using namespace XrdCl; - Log *log = DefaultEnv::GetLog(); - - if( pCurrentOffset != ci.offset ) - { - log->Error( UtilityMsg, "Got out-of-bounds chunk, expected offset:" - " %ld, got %ld", pCurrentOffset, ci.offset ); - return XRootDStatus( stError, errInternal ); - } - - int64_t wr = 0; - uint32_t length = ci.length; - char *cursor = (char*)ci.buffer; - do - { - wr = write( 1, cursor, length ); - if( wr == -1 ) - { - log->Debug( UtilityMsg, "Unable to write to stdout: %s", - strerror( errno ) ); - delete [] (char*)ci.buffer; ci.buffer = 0; - return XRootDStatus( stError, errOSError, errno ); - } - pCurrentOffset += wr; - cursor += wr; - length -= wr; - } - while( length ); - - pCkSumHelper.Update( ci.buffer, ci.length ); - delete [] (char*)ci.buffer; ci.buffer = 0; - return XRootDStatus(); - } - - //------------------------------------------------------------------------ - //! Flush chunks that might have been queues - //------------------------------------------------------------------------ - virtual XrdCl::XRootDStatus Flush() - { - return XrdCl::XRootDStatus(); - } - - //------------------------------------------------------------------------ - //! Get check sum - //------------------------------------------------------------------------ - virtual XrdCl::XRootDStatus GetCheckSum( std::string &checkSum, - std::string &checkSumType ) - { - return pCkSumHelper.GetCheckSum( checkSum, checkSumType ); - } - - private: - StdOutDestination(const StdOutDestination &other); - StdOutDestination &operator = (const StdOutDestination &other); - CheckSumHelper pCkSumHelper; - uint64_t pCurrentOffset; - }; - - //---------------------------------------------------------------------------- - //! XRootD destination - //---------------------------------------------------------------------------- - class XRootDDestination: public Destination - { - public: - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - XRootDDestination( const XrdCl::URL *url, uint8_t parallelChunks ): - pUrl( url ), pFile( new XrdCl::File( XrdCl::File::DisableVirtRedirect ) ), pParallel( parallelChunks ) - { - } - - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - virtual ~XRootDDestination() - { - CleanUpChunks(); - delete pFile; - } - - //------------------------------------------------------------------------ - //! Initialize the destination - //------------------------------------------------------------------------ - virtual XrdCl::XRootDStatus Initialize() - { - using namespace XrdCl; - Log *log = DefaultEnv::GetLog(); - log->Debug( UtilityMsg, "Opening %s for writing", - pUrl->GetURL().c_str() ); - - std::string value; - DefaultEnv::GetEnv()->GetString( "WriteRecovery", value ); - pFile->SetProperty( "WriteRecovery", value ); - - OpenFlags::Flags flags = OpenFlags::Update; - if( pForce ) - flags |= OpenFlags::Delete; - else - flags |= OpenFlags::New; - - if( pPosc ) - flags |= OpenFlags::POSC; - - if( pCoerce ) - flags |= OpenFlags::Force; - - if( pMakeDir) - flags |= OpenFlags::MakePath; - - Access::Mode mode = Access::UR|Access::UW|Access::GR|Access::OR; - - return pFile->Open( pUrl->GetURL(), flags, mode ); - } - - //------------------------------------------------------------------------ - //! Finalize the destination - //------------------------------------------------------------------------ - virtual XrdCl::XRootDStatus Finalize() - { - return pFile->Close(); - } - - //------------------------------------------------------------------------ - //! Put a data chunk at a destination - //! - //! @param ci chunk information - //! @return status of the operation - //------------------------------------------------------------------------ - virtual XrdCl::XRootDStatus PutChunk( XrdCl::ChunkInfo &ci ) - { - using namespace XrdCl; - if( !pFile->IsOpen() ) - return XRootDStatus( stError, errUninitialized ); - - //---------------------------------------------------------------------- - // If there is still place for this chunk to be sent send it - //---------------------------------------------------------------------- - if( pChunks.size() < pParallel ) - return QueueChunk( ci ); - - //---------------------------------------------------------------------- - // We wait for a chunk to be sent so that we have space for the current - // one - //---------------------------------------------------------------------- - XRDCL_SMART_PTR_T ch( pChunks.front() ); - pChunks.pop(); - ch->sem->Wait(); - delete [] (char*)ch->chunk.buffer; - if( !ch->status.IsOK() ) - { - Log *log = DefaultEnv::GetLog(); - log->Debug( UtilityMsg, "Unable write %d bytes at %ld from %s: %s", - ch->chunk.length, ch->chunk.offset, - pUrl->GetURL().c_str(), ch->status.ToStr().c_str() ); - CleanUpChunks(); - return ch->status; - } - return QueueChunk( ci ); - } - - //------------------------------------------------------------------------ - //! Clean up the chunks that are flying - //------------------------------------------------------------------------ - void CleanUpChunks() - { - while( !pChunks.empty() ) - { - ChunkHandler *ch = pChunks.front(); - pChunks.pop(); - ch->sem->Wait(); - delete [] (char *)ch->chunk.buffer; - delete ch; - } - } - - //------------------------------------------------------------------------ - //! Queue a chunk - //------------------------------------------------------------------------ - XrdCl::XRootDStatus QueueChunk( XrdCl::ChunkInfo &ci ) - { - ChunkHandler *ch = new ChunkHandler(ci); - XrdCl::XRootDStatus st; - st = pFile->Write( ci.offset, ci.length, ci.buffer, ch ); - if( !st.IsOK() ) - { - CleanUpChunks(); - delete [] (char*)ci.buffer; - ci.buffer = 0; - delete ch; - return st; - } - pChunks.push( ch ); - return XrdCl::XRootDStatus(); - } - - //------------------------------------------------------------------------ - //! Flush chunks that might have been queues - //------------------------------------------------------------------------ - virtual XrdCl::XRootDStatus Flush() - { - XrdCl::XRootDStatus st; - while( !pChunks.empty() ) - { - ChunkHandler *ch = pChunks.front(); - pChunks.pop(); - ch->sem->Wait(); - if( !ch->status.IsOK() ) - st = ch->status; - delete [] (char *)ch->chunk.buffer; - delete ch; - } - return st; - } - - //------------------------------------------------------------------------ - //! Get check sum - //------------------------------------------------------------------------ - virtual XrdCl::XRootDStatus GetCheckSum( std::string &checkSum, - std::string &checkSumType ) - { - if( pUrl->IsLocalFile() ) - return XrdCl::Utils::GetLocalCheckSum( checkSum, checkSumType, pUrl->GetPath() ); - - std::string dataServer; pFile->GetProperty( "DataServer", dataServer ); - return XrdCl::Utils::GetRemoteCheckSum( checkSum, checkSumType, - dataServer, pUrl->GetPath() ); - } - - private: - XRootDDestination(const XRootDDestination &other); - XRootDDestination &operator = (const XRootDDestination &other); - - //------------------------------------------------------------------------ - // Asynchronous chunk handler - //------------------------------------------------------------------------ - class ChunkHandler: public XrdCl::ResponseHandler - { - public: - ChunkHandler( XrdCl::ChunkInfo ci ): - sem( new XrdCl::Semaphore(0) ), - chunk(ci) {} - virtual ~ChunkHandler() { delete sem; } - virtual void HandleResponse( XrdCl::XRootDStatus *statusval, - XrdCl::AnyObject */*response*/ ) - { - this->status = *statusval; - delete statusval; - sem->Post(); - } - - XrdCl::Semaphore *sem; - XrdCl::ChunkInfo chunk; - XrdCl::XRootDStatus status; - }; - - const XrdCl::URL *pUrl; - XrdCl::File *pFile; - uint8_t pParallel; - std::queue pChunks; - }; -} - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - // Constructor - //---------------------------------------------------------------------------- - ClassicCopyJob::ClassicCopyJob( uint16_t jobId, - PropertyList *jobProperties, - PropertyList *jobResults ): - CopyJob( jobId, jobProperties, jobResults ) - { - Log *log = DefaultEnv::GetLog(); - log->Debug( UtilityMsg, "Creating a classic copy job, from %s to %s", - GetSource().GetURL().c_str(), GetTarget().GetURL().c_str() ); - } - - //---------------------------------------------------------------------------- - // Run the copy job - //---------------------------------------------------------------------------- - XRootDStatus ClassicCopyJob::Run( CopyProgressHandler *progress ) - { - Log *log = DefaultEnv::GetLog(); - - std::string checkSumMode; - std::string checkSumType; - std::string checkSumPreset; - std::string zipSource; - uint16_t parallelChunks; - uint32_t chunkSize; - uint64_t blockSize; - bool posc, force, coerce, makeDir, dynamicSource, zip, xcp; - int32_t nbXcpSources; - - pProperties->Get( "checkSumMode", checkSumMode ); - pProperties->Get( "checkSumType", checkSumType ); - pProperties->Get( "checkSumPreset", checkSumPreset ); - pProperties->Get( "parallelChunks", parallelChunks ); - pProperties->Get( "chunkSize", chunkSize ); - pProperties->Get( "posc", posc ); - pProperties->Get( "force", force ); - pProperties->Get( "coerce", coerce ); - pProperties->Get( "makeDir", makeDir ); - pProperties->Get( "dynamicSource", dynamicSource ); - pProperties->Get( "zipArchive", zip ); - pProperties->Get( "xcp", xcp ); - pProperties->Get( "xcpBlockSize", blockSize ); - - if( zip ) - pProperties->Get( "zipSource", zipSource ); - - if( xcp ) - pProperties->Get( "nbXcpSources", nbXcpSources ); - - //-------------------------------------------------------------------------- - // Initialize the source and the destination - //-------------------------------------------------------------------------- - XRDCL_SMART_PTR_T src; - if( xcp ) - src.reset( new XRootDSourceXCp( &GetSource(), chunkSize, parallelChunks, nbXcpSources, blockSize ) ); - else if( zip ) // TODO make zip work for xcp - src.reset( new XRootDSourceZip( zipSource, &GetSource(), chunkSize, parallelChunks ) ); - else if( GetSource().GetProtocol() == "stdio" ) - src.reset( new StdInSource( checkSumType, chunkSize ) ); - else - { - if( dynamicSource ) - src.reset( new XRootDSourceDynamic( &GetSource(), chunkSize ) ); - else - src.reset( new XRootDSource( &GetSource(), chunkSize, parallelChunks ) ); - } - - XRootDStatus st = src->Initialize(); - if( !st.IsOK() ) return st; - uint64_t size = src->GetSize() >= 0 ? src->GetSize() : 0; - - XRDCL_SMART_PTR_T dest; - URL newDestUrl( GetTarget() ); - - if( GetTarget().GetProtocol() == "stdio" ) - dest.reset( new StdOutDestination( checkSumType ) ); - //-------------------------------------------------------------------------- - // For xrootd destination build the oss.asize hint - //-------------------------------------------------------------------------- - else - { - if( src->GetSize() >= 0 ) - { - URL::ParamsMap params = newDestUrl.GetParams(); - std::ostringstream o; o << src->GetSize(); - params["oss.asize"] = o.str(); - newDestUrl.SetParams( params ); - // makeDir = true; // Backward compatability for xroot destinations!!! - } - dest.reset( new XRootDDestination( &newDestUrl, parallelChunks ) ); - } - - dest->SetForce( force ); - dest->SetPOSC( posc ); - dest->SetCoerce( coerce ); - dest->SetMakeDir( makeDir ); - st = dest->Initialize(); - if( !st.IsOK() ) return st; - - //-------------------------------------------------------------------------- - // Copy the chunks - //-------------------------------------------------------------------------- - ChunkInfo chunkInfo; - uint64_t processed = 0; - while( 1 ) - { - st = src->GetChunk( chunkInfo ); - if( !st.IsOK() ) - return st; - - if( st.IsOK() && st.code == suDone ) - break; - - st = dest->PutChunk( chunkInfo ); - - if( !st.IsOK() ) - return st; - - processed += chunkInfo.length; - if( progress ) progress->JobProgress( pJobId, processed, size ); - } - - st = dest->Flush(); - if( !st.IsOK() ) - return st; - - //-------------------------------------------------------------------------- - // The size of the source is known and not enough data has been transfered - // to the destination - //-------------------------------------------------------------------------- - if( src->GetSize() >= 0 && size != processed ) - { - log->Error( UtilityMsg, "The declared source size is %ld bytes, but " - "received %ld bytes.", size, processed ); - return XRootDStatus( stError, errDataError ); - } - pResults->Set( "size", processed ); - - //-------------------------------------------------------------------------- - // Finalize the destination - //-------------------------------------------------------------------------- - st = dest->Finalize(); - if( !st.IsOK() ) - return st; - - //-------------------------------------------------------------------------- - // Verify the checksums if needed - //-------------------------------------------------------------------------- - if( checkSumMode != "none" ) - { - log->Debug( UtilityMsg, "Attempting checksum calculation, mode: %s.", - checkSumMode.c_str() ); - std::string sourceCheckSum; - std::string targetCheckSum; - - //------------------------------------------------------------------------ - // Get the check sum at source - //------------------------------------------------------------------------ - timeval oStart, oEnd; - XRootDStatus st; - - if( checkSumMode == "end2end" || checkSumMode == "source" ) - { - gettimeofday( &oStart, 0 ); - if( !checkSumPreset.empty() ) - { - sourceCheckSum = checkSumType + ":"; - sourceCheckSum += Utils::NormalizeChecksum( checkSumType, - checkSumPreset ); - } - else - { - st = src->GetCheckSum( sourceCheckSum, checkSumType ); - } - gettimeofday( &oEnd, 0 ); - - if( !st.IsOK() ) - return st; - - pResults->Set( "sourceCheckSum", sourceCheckSum ); - } - - //------------------------------------------------------------------------ - // Get the check sum at destination - //------------------------------------------------------------------------ - timeval tStart, tEnd; - - if( checkSumMode == "end2end" || checkSumMode == "target" ) - { - gettimeofday( &tStart, 0 ); - st = dest->GetCheckSum( targetCheckSum, checkSumType ); - if( !st.IsOK() ) - return st; - gettimeofday( &tEnd, 0 ); - pResults->Set( "targetCheckSum", targetCheckSum ); - } - - //------------------------------------------------------------------------ - // Compare and inform monitoring - //------------------------------------------------------------------------ - if( checkSumMode == "end2end" ) - { - bool match = false; - if( sourceCheckSum == targetCheckSum ) - match = true; - - Monitor *mon = DefaultEnv::GetMonitor(); - if( mon ) - { - Monitor::CheckSumInfo i; - i.transfer.origin = &GetSource(); - i.transfer.target = &GetTarget(); - i.cksum = sourceCheckSum; - i.oTime = Utils::GetElapsedMicroSecs( oStart, oEnd ); - i.tTime = Utils::GetElapsedMicroSecs( tStart, tEnd ); - i.isOK = match; - mon->Event( Monitor::EvCheckSum, &i ); - } - - if( !match ) - return XRootDStatus( stError, errCheckSumError, 0 ); - } - } - return XRootDStatus(); - } -} diff --git a/src/XrdCl/XrdClClassicCopyJob.hh b/src/XrdCl/XrdClClassicCopyJob.hh deleted file mode 100644 index 644effd907f..00000000000 --- a/src/XrdCl/XrdClClassicCopyJob.hh +++ /dev/null @@ -1,47 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_CLASSIC_COPY_JOB_HH__ -#define __XRD_CL_CLASSIC_COPY_JOB_HH__ - -#include "XrdCl/XrdClCopyProcess.hh" -#include "XrdCl/XrdClCopyJob.hh" - -namespace XrdCl -{ - class ClassicCopyJob: public CopyJob - { - public: - //------------------------------------------------------------------------ - // Constructor - //------------------------------------------------------------------------ - ClassicCopyJob( uint16_t jobId, - PropertyList *jobProperties, - PropertyList *jobResults ); - - //------------------------------------------------------------------------ - //! Run the copy job - //! - //! @param progress the handler to be notified about the copy progress - //! @return status of the copy operation - //------------------------------------------------------------------------ - virtual XRootDStatus Run( CopyProgressHandler *progress = 0 ); - }; -} - -#endif // __XRD_CL_CLASSIC_COPY_JOB_HH__ diff --git a/src/XrdCl/XrdClConstants.hh b/src/XrdCl/XrdClConstants.hh deleted file mode 100644 index 4e2cd7c306d..00000000000 --- a/src/XrdCl/XrdClConstants.hh +++ /dev/null @@ -1,85 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_CONSTANTS_HH__ -#define __XRD_CL_CONSTANTS_HH__ - -#include - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - // Log message types - //---------------------------------------------------------------------------- - const uint64_t AppMsg = 0x0000000000000001ULL; - const uint64_t UtilityMsg = 0x0000000000000002ULL; - const uint64_t FileMsg = 0x0000000000000004ULL; - const uint64_t PollerMsg = 0x0000000000000008ULL; - const uint64_t PostMasterMsg = 0x0000000000000010ULL; - const uint64_t XRootDTransportMsg = 0x0000000000000020ULL; - const uint64_t TaskMgrMsg = 0x0000000000000040ULL; - const uint64_t XRootDMsg = 0x0000000000000080ULL; - const uint64_t FileSystemMsg = 0x0000000000000100ULL; - const uint64_t AsyncSockMsg = 0x0000000000000200ULL; - const uint64_t JobMgrMsg = 0x0000000000000400ULL; - const uint64_t PlugInMgrMsg = 0x0000000000000800ULL; - - //---------------------------------------------------------------------------- - // Environment settings - //---------------------------------------------------------------------------- - const int DefaultSubStreamsPerChannel = 1; - const int DefaultConnectionWindow = 120; - const int DefaultConnectionRetry = 5; - const int DefaultRequestTimeout = 1800; - const int DefaultStreamTimeout = 60; - const int DefaultTimeoutResolution = 15; - const int DefaultStreamErrorWindow = 1800; - const int DefaultRunForkHandler = 0; - const int DefaultRedirectLimit = 16; - const int DefaultWorkerThreads = 3; - const int DefaultCPChunkSize = 16777216; - const int DefaultCPParallelChunks = 4; - const int DefaultDataServerTTL = 300; - const int DefaultLoadBalancerTTL = 1200; - const int DefaultCPInitTimeout = 600; - const int DefaultCPTPCTimeout = 1800; - const int DefaultTCPKeepAlive = 0; - const int DefaultTCPKeepAliveTime = 7200; - const int DefaultTCPKeepAliveInterval = 75; - const int DefaultTCPKeepAliveProbes = 9; - const int DefaultMultiProtocol = 0; - const int DefaultParallelEvtLoop = 1; - const int DefaultMetalinkProcessing = 1; - const int DefaultLocalMetalinkFile = 0; - const int DefaultXCpBlockSize = 134217728; // DefaultCPChunkSize * DefaultCPParallelChunks * 2 - const int DefaultNoDelay = 1; - const int DefaultAioSignal = 1; - const int DefaultPreferIPv4 = 0; - - const char * const DefaultPollerPreference = "built-in"; - const char * const DefaultNetworkStack = "IPAuto"; - const char * const DefaultClientMonitor = ""; - const char * const DefaultClientMonitorParam = ""; - const char * const DefaultPlugInConfDir = ""; - const char * const DefaultPlugIn = ""; - const char * const DefaultReadRecovery = "true"; - const char * const DefaultWriteRecovery = "true"; - const char * const DefaultGlfnRedirector = ""; -} - -#endif // __XRD_CL_CONSTANTS_HH__ diff --git a/src/XrdCl/XrdClCopy.cc b/src/XrdCl/XrdClCopy.cc deleted file mode 100644 index 516f1cd0a07..00000000000 --- a/src/XrdCl/XrdClCopy.cc +++ /dev/null @@ -1,899 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2014 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#include "XrdApps/XrdCpConfig.hh" -#include "XrdApps/XrdCpFile.hh" -#include "XrdCl/XrdClConstants.hh" -#include "XrdCl/XrdClCopyProcess.hh" -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdCl/XrdClLog.hh" -#include "XrdCl/XrdClFileSystem.hh" -#include "XrdCl/XrdClUtils.hh" -#include "XrdCl/XrdClRedirectorRegistry.hh" -#include "XrdSys/XrdSysPthread.hh" - -#include -#include -#include -#include - -//------------------------------------------------------------------------------ -// Progress notifier -//------------------------------------------------------------------------------ -class ProgressDisplay: public XrdCl::CopyProgressHandler -{ - public: - //-------------------------------------------------------------------------- - //! Constructor - //-------------------------------------------------------------------------- - ProgressDisplay(): pPrevious(0), pPrintProgressBar(true), - pPrintSourceCheckSum(false), pPrintTargetCheckSum(false) - {} - - //-------------------------------------------------------------------------- - //! Begin job - //-------------------------------------------------------------------------- - virtual void BeginJob( uint16_t jobNum, - uint16_t jobTotal, - const XrdCl::URL *source, - const XrdCl::URL *destination ) - { - XrdSysMutexHelper scopedLock( pMutex ); - if( pPrintProgressBar ) - { - if( jobTotal > 1 ) - { - std::cerr << "Job: " << jobNum << "/" << jobTotal << std::endl; - std::cerr << "Source: " << source->GetURL() << std::endl; - std::cerr << "Target: " << destination->GetURL() << std::endl; - } - } - pPrevious = 0; - - JobData d; - d.started = time(0); - d.source = source; - d.target = destination; - pOngoingJobs[jobNum] = d; - } - - //-------------------------------------------------------------------------- - //! End job - //-------------------------------------------------------------------------- - virtual void EndJob( uint16_t jobNum, const XrdCl::PropertyList *results ) - { - XrdSysMutexHelper scopedLock( pMutex ); - - std::map::iterator it = pOngoingJobs.find( jobNum ); - if( it == pOngoingJobs.end() ) - return; - - JobData &d = it->second; - - // make sure the last available status was printed, which may not be - // the case when processing stdio since we throttle printing and don't - // know the total size - JobProgress( jobNum, d.bytesProcessed, d.bytesTotal ); - - if( pPrintProgressBar ) - { - if( pOngoingJobs.size() > 1 ) - std::cerr << "\r" << std::string(70, ' ') << "\r"; - else - std::cerr << std::endl; - } - - XrdCl::XRootDStatus st; - results->Get( "status", st ); - if( !st.IsOK() ) - { - pOngoingJobs.erase(it); - return; - } - - std::string checkSum; - uint64_t size; - results->Get( "size", size ); - if( pPrintSourceCheckSum ) - { - results->Get( "sourceCheckSum", checkSum ); - PrintCheckSum( d.source, checkSum, size ); - } - - if( pPrintTargetCheckSum ) - { - results->Get( "targetCheckSum", checkSum ); - PrintCheckSum( d.target, checkSum, size ); - } - - pOngoingJobs.erase(it); - } - - //-------------------------------------------------------------------------- - //! Get progress bar - //-------------------------------------------------------------------------- - std::string GetProgressBar( time_t now ) - { - JobData &d = pOngoingJobs.begin()->second; - - uint64_t speed = 0; - if( now-d.started ) - speed = d.bytesProcessed/(now-d.started); - else - speed = d.bytesProcessed; - - std::string bar; - int prog = 0; - int proc = 0; - - if( d.bytesTotal ) - { - prog = (int)((double)d.bytesProcessed/d.bytesTotal*50); - proc = (int)((double)d.bytesProcessed/d.bytesTotal*100); - } - else - { - prog = 50; - proc = 100; - } - bar.append( prog, '=' ); - if( prog < 50 ) - bar += ">"; - - std::ostringstream o; - o << "[" << XrdCl::Utils::BytesToString(d.bytesProcessed) << "B/"; - o << XrdCl::Utils::BytesToString(d.bytesTotal) << "B]"; - o << "[" << std::setw(3) << std::right << proc << "%]"; - o << "[" << std::setw(50) << std::left; - o << bar; - o << "]"; - o << "[" << XrdCl::Utils::BytesToString(speed) << "B/s] "; - return o.str(); - } - - //-------------------------------------------------------------------------- - //! Get sumary bar - //-------------------------------------------------------------------------- - std::string GetSummaryBar( time_t now ) - { - std::map::iterator it; - std::ostringstream o; - - for( it = pOngoingJobs.begin(); it != pOngoingJobs.end(); ++it ) - { - JobData &d = it->second; - uint16_t jobNum = it->first; - - uint64_t speed = 0; - if( now-d.started ) - speed = d.bytesProcessed/(now-d.started); - - int proc = 0; - if( d.bytesTotal ) - proc = (int)((double)d.bytesProcessed/d.bytesTotal*100); - else - proc = 100; - - o << "[#" << jobNum << ": "; - o << proc << "% "; - o << XrdCl::Utils::BytesToString(speed) << "B/s] "; - } - o << " "; - return o.str(); - } - - //-------------------------------------------------------------------------- - //! Job progress - //-------------------------------------------------------------------------- - virtual void JobProgress( uint16_t jobNum, - uint64_t bytesProcessed, - uint64_t bytesTotal ) - { - XrdSysMutexHelper scopedLock( pMutex ); - - if( pPrintProgressBar ) - { - time_t now = time(0); - if( (now - pPrevious < 1) && (bytesProcessed != bytesTotal) ) - return; - pPrevious = now; - - std::map::iterator it = pOngoingJobs.find( jobNum ); - if( it == pOngoingJobs.end() ) - return; - - JobData &d = it->second; - - d.bytesProcessed = bytesProcessed; - d.bytesTotal = bytesTotal; - - std::string progress; - if( pOngoingJobs.size() == 1 ) - progress = GetProgressBar( now ); - else - progress = GetSummaryBar( now ); - - std::cerr << "\r" << progress << std::flush; - } - } - - //-------------------------------------------------------------------------- - //! Print the checksum - //-------------------------------------------------------------------------- - void PrintCheckSum( const XrdCl::URL *url, - const std::string &checkSum, - uint64_t size ) - { - if( checkSum.empty() ) - return; - std::string::size_type i = checkSum.find( ':' ); - std::cerr << checkSum.substr( 0, i+1 ) << " "; - std::cerr << checkSum.substr( i+1, checkSum.length()-i ) << " "; - - if( url->IsLocalFile() ) - std::cerr << url->GetPath() << " "; - else - { - std::cerr << url->GetProtocol() << "://" << url->GetHostId(); - std::cerr << url->GetPath() << " "; - } - - std::cerr << size; - std::cerr << std::endl; - } - - //-------------------------------------------------------------------------- - // Printing flags - //-------------------------------------------------------------------------- - void PrintProgressBar( bool print ) { pPrintProgressBar = print; } - void PrintSourceCheckSum( bool print ) { pPrintSourceCheckSum = print; } - void PrintTargetCheckSum( bool print ) { pPrintTargetCheckSum = print; } - - private: - struct JobData - { - JobData(): bytesProcessed(0), bytesTotal(0), - started(0), source(0), target(0) {} - uint64_t bytesProcessed; - uint64_t bytesTotal; - time_t started; - const XrdCl::URL *source; - const XrdCl::URL *target; - }; - - time_t pPrevious; - bool pPrintProgressBar; - bool pPrintSourceCheckSum; - bool pPrintTargetCheckSum; - std::map pOngoingJobs; - XrdSysRecMutex pMutex; -}; - -//------------------------------------------------------------------------------ -// Check if we support all the specified user options -//------------------------------------------------------------------------------ -bool AllOptionsSupported( XrdCpConfig *config ) -{ - if( config->pHost ) - { - std::cerr << "SOCKS Proxies are not yet supported" << std::endl; - return false; - } - - if( config->xRate ) - { - std::cerr << "Limiting transfer rate is not yet supported" << std::endl; - return false; - } - - return true; -} - -//------------------------------------------------------------------------------ -// Append extra cgi info to existing URL -//------------------------------------------------------------------------------ -void AppendCGI( std::string &url, const char *newCGI ) -{ - if( !newCGI || !(*newCGI) ) - return; - - if( *newCGI == '&' ) - ++newCGI; - - if( url.find( '?' ) == std::string::npos ) - url += "?"; - - if( url.find( '&' ) == std::string::npos ) - url += "&"; - - url += newCGI; -} - -//------------------------------------------------------------------------------ -// Process commandline environment settings -//------------------------------------------------------------------------------ -void ProcessCommandLineEnv( XrdCpConfig *config ) -{ - XrdCl::Env *env = XrdCl::DefaultEnv::GetEnv(); - - XrdCpConfig::defVar *cursor = config->intDefs; - while( cursor ) - { - env->PutInt( cursor->vName, cursor->intVal ); - cursor = cursor->Next; - } - - cursor = config->strDefs; - while( cursor ) - { - env->PutString( cursor->vName, cursor->strVal ); - cursor = cursor->Next; - } -} - -//------------------------------------------------------------------------------ -// Translate file type to a string for diagnostics purposes -//------------------------------------------------------------------------------ -const char *FileType2String( XrdCpFile::PType type ) -{ - switch( type ) - { - case XrdCpFile::isDir: return "directory"; - case XrdCpFile::isFile: return "local file"; - case XrdCpFile::isXroot: return "xroot"; - case XrdCpFile::isHttp: return "http"; - case XrdCpFile::isHttps: return "https"; - case XrdCpFile::isStdIO: return "stdio"; - default: return "other"; - }; -} - -//------------------------------------------------------------------------------ -// Count the sources -//------------------------------------------------------------------------------ -uint32_t CountSources( XrdCpFile *file ) -{ - uint32_t count; - for( count = 0; file; file = file->Next, ++count ) {}; - return count; -} - -//------------------------------------------------------------------------------ -// Adjust file information for the cases when XrdCpConfig cannot do this -//------------------------------------------------------------------------------ -void AdjustFileInfo( XrdCpFile *file ) -{ - //---------------------------------------------------------------------------- - // If the file is url and the directory offset is not set we set it - // to the last slash - //---------------------------------------------------------------------------- - if( file->Doff == 0 ) - { - char *slash = file->Path; - for( ; *slash; ++slash ) {}; - for( ; *slash != '/' && slash > file->Path; --slash ) {}; - file->Doff = slash - file->Path; - } -}; - -//------------------------------------------------------------------------------ -// Get a list of files and a list of directories inside a remote directory -//------------------------------------------------------------------------------ -XrdCl::XRootDStatus GetDirList( XrdCl::FileSystem *fs, - const XrdCl::URL &url, - std::vector *&files, - std::vector *&directories ) -{ - using namespace XrdCl; - DirectoryList *list; - XRootDStatus status; - Log *log = DefaultEnv::GetLog(); - - status = fs->DirList( url.GetPath(), DirListFlags::Stat, list ); - if( !status.IsOK() ) - { - log->Error( AppMsg, "Error listing directory: %s", - status.ToStr().c_str()); - return status; - } - - for ( DirectoryList::Iterator it = list->Begin(); it != list->End(); ++it ) - { - if ( (*it)->GetStatInfo()->TestFlags( StatInfo::IsDir ) ) - { - std::string directory = (*it)->GetName(); - directories->push_back( directory ); - } - else - { - std::string file = (*it)->GetName(); - files->push_back( file ); - } - } - - delete list; - - return XRootDStatus(); -} - -//------------------------------------------------------------------------------ -// Recursively index all files and directories inside a remote directory -//------------------------------------------------------------------------------ -XrdCpFile *IndexRemote( XrdCl::FileSystem *fs, - std::string basePath, - uint16_t dirOffset ) -{ - using namespace XrdCl; - - Log *log = DefaultEnv::GetLog(); - log->Debug( AppMsg, "Indexing %s", basePath.c_str() ); - - DirectoryList *dirList = 0; - XRootDStatus st = fs->DirList( basePath, DirListFlags::Recursive, dirList ); - if( !st.IsOK() ) - { - log->Info( AppMsg, "Failed to get directory listing for %s: %s", - basePath.c_str(), - st.GetErrorMessage().c_str() ); - return 0; - } - - XrdCpFile start, *current = 0; - XrdCpFile *end = &start; - int badUrl = 0; - for( auto itr = dirList->Begin(); itr != dirList->End(); ++itr ) - { - DirectoryList::ListEntry *e = *itr; - std::string path = basePath + '/' + e->GetName(); - current = new XrdCpFile( path.c_str(), badUrl ); - if( badUrl ) - { - // TODO release memory - log->Error( AppMsg, "Bad URL: %s", current->Path ); - return 0; - } - - current->Doff = dirOffset; - end->Next = current; - end = current; - } - - delete dirList; - - return start.Next; -} - -//------------------------------------------------------------------------------ -// Clean up the copy job descriptors -//------------------------------------------------------------------------------ -void CleanUpResults( std::vector &results ) -{ - std::vector::iterator it; - for( it = results.begin(); it != results.end(); ++it ) - delete *it; -} - -//-------------------------------------------------------------------------- -// Let the show begin -//------------------------------------------------------------------------------ -int main( int argc, char **argv ) -{ - using namespace XrdCl; - - //---------------------------------------------------------------------------- - // Configure the copy command, if it returns then everything went well, ugly - //---------------------------------------------------------------------------- - XrdCpConfig config( argv[0] ); - config.Config( argc, argv, XrdCpConfig::optRmtRec ); - if( !AllOptionsSupported( &config ) ) - return 254; - ProcessCommandLineEnv( &config ); - - //---------------------------------------------------------------------------- - // Set options - //---------------------------------------------------------------------------- - CopyProcess process; - Log *log = DefaultEnv::GetLog(); - if( config.Dlvl ) - { - if( config.Dlvl == 1 ) log->SetLevel( Log::InfoMsg ); - else if( config.Dlvl == 2 ) log->SetLevel( Log::DebugMsg ); - else if( config.Dlvl == 3 ) log->SetLevel( Log::DumpMsg ); - } - - ProgressDisplay progress; - if( config.Want(XrdCpConfig::DoNoPbar) ) - progress.PrintProgressBar( false ); - - bool posc = false; - bool force = false; - bool coerce = false; - bool makedir = false; - bool dynSrc = false; - std::string thirdParty = "none"; - - if( config.Want( XrdCpConfig::DoPosc ) ) posc = true; - if( config.Want( XrdCpConfig::DoForce ) ) force = true; - if( config.Want( XrdCpConfig::DoCoerce ) ) coerce = true; - if( config.Want( XrdCpConfig::DoTpc ) ) thirdParty = "first"; - if( config.Want( XrdCpConfig::DoTpcOnly ) ) thirdParty = "only"; - if( config.Want( XrdCpConfig::DoRecurse ) ) makedir = true; - if( config.Want( XrdCpConfig::DoPath ) ) makedir = true; - if( config.Want( XrdCpConfig::DoDynaSrc ) ) dynSrc = true; - - //---------------------------------------------------------------------------- - // Checksums - //---------------------------------------------------------------------------- - std::string checkSumType; - std::string checkSumPreset; - std::string checkSumMode = "none"; - if( config.Want( XrdCpConfig::DoCksum ) ) - { - checkSumMode = "end2end"; - std::vector ckSumParams; - Utils::splitString( ckSumParams, config.CksVal, ":" ); - if( ckSumParams.size() > 1 ) - { - if( ckSumParams[1] == "print" ) - { - checkSumMode = "target"; - progress.PrintTargetCheckSum( true ); - } - else - checkSumPreset = ckSumParams[1]; - } - checkSumType = ckSumParams[0]; - } - - if( config.Want( XrdCpConfig::DoCksrc ) ) - { - checkSumMode = "source"; - std::vector ckSumParams; - Utils::splitString( ckSumParams, config.CksVal, ":" ); - if( ckSumParams.size() == 2 ) - { - checkSumMode = "source"; - checkSumType = ckSumParams[0]; - progress.PrintSourceCheckSum( true ); - } - else - { - std::cerr << "Invalid parameter: " << config.CksVal << std::endl; - return 254; - } - } - - //---------------------------------------------------------------------------- - // ZIP archive - //---------------------------------------------------------------------------- - std::string zipFile; - bool zip = false; - if( config.Want( XrdCpConfig::DoZip ) ) - { - zipFile = config.zipFile; - zip = true; - } - - //---------------------------------------------------------------------------- - // Extreme Copy - //---------------------------------------------------------------------------- - int nbSources = 0; - bool xcp = false; - if( config.Want( XrdCpConfig::DoSources ) ) - { - nbSources = config.nSrcs; - xcp = true; - } - - //---------------------------------------------------------------------------- - // Environment settings - //---------------------------------------------------------------------------- - XrdCl::Env *env = XrdCl::DefaultEnv::GetEnv(); - if( config.nStrm != 1 ) - env->PutInt( "SubStreamsPerChannel", config.nStrm ); - - int chunkSize = DefaultCPChunkSize; - env->GetInt( "CPChunkSize", chunkSize ); - - int blockSize = DefaultXCpBlockSize; - env->GetInt( "XCpBlockSize", blockSize ); - - int parallelChunks = DefaultCPParallelChunks; - env->GetInt( "CPParallelChunks", parallelChunks ); - if( parallelChunks < 1 || - parallelChunks > std::numeric_limits::max() ) - { - std::cerr << "Can only handle between 1 and "; - std::cerr << (int)std::numeric_limits::max(); - std::cerr << " chunks in parallel. You asked for " << parallelChunks; - std::cerr << "." << std::endl; - return 254; - } - - log->Dump( AppMsg, "Chunk size: %d, parallel chunks %d, streams: %d", - chunkSize, parallelChunks, config.nStrm ); - - //---------------------------------------------------------------------------- - // Build the URLs - //---------------------------------------------------------------------------- - std::vector resultVect; - - std::string dest; - if( config.dstFile->Protocol == XrdCpFile::isDir || - config.dstFile->Protocol == XrdCpFile::isFile ) - { - dest = "file://"; - - // if it is not an absolute path append cwd - if( config.dstFile->Path[0] != '/' ) - { - char buf[FILENAME_MAX]; - char *cwd = getcwd( buf, FILENAME_MAX ); - if( !cwd ) - { - std::cerr << strerror( errno ) << std::endl; - return errno; - } - dest += cwd; - dest += '/'; - } - } - dest += config.dstFile->Path; - - //---------------------------------------------------------------------------- - // We need to check whether our target is a file or a directory: - // 1) it's a file, so we can accept only one source - // 2) it's a directory, so: - // * we can accept multiple sources - // * we need to append the source name - //---------------------------------------------------------------------------- - bool targetIsDir = false; - if( config.dstFile->Protocol == XrdCpFile::isDir ) - targetIsDir = true; - else if( config.dstFile->Protocol == XrdCpFile::isXroot ) - { - URL target( dest ); - FileSystem fs( target ); - StatInfo *statInfo = 0; - XRootDStatus st = fs.Stat( target.GetPath(), statInfo ); - if( st.IsOK() ) - {if (statInfo->TestFlags( StatInfo::IsDir ) ) targetIsDir = true;} - else if (st.errNo == kXR_NotFound && config.Want( XrdCpConfig::DoPath )) - {int n = strlen(config.dstFile->Path); - if (config.dstFile->Path[n-1] == '/') targetIsDir = true; - } - - delete statInfo; - } - - //---------------------------------------------------------------------------- - // If we have multiple sources and target is neither a directory nor stdout - // then we cannot proceed - //---------------------------------------------------------------------------- - if( CountSources(config.srcFile) > 1 && !targetIsDir && - config.dstFile->Protocol != XrdCpFile::isStdIO ) - { - std::cerr << "Multiple sources were given but target is not a directory."; - std::cerr << std::endl; - return 255; - } - - //---------------------------------------------------------------------------- - // If we're doing remote recursive copy, chain all the files (if it's a - // directory) - //---------------------------------------------------------------------------- - if( config.Want( XrdCpConfig::DoRecurse ) && - config.srcFile->Protocol == XrdCpFile::isXroot ) - { - URL source( config.srcFile->Path ); - FileSystem *fs = new FileSystem( source ); - StatInfo *statInfo = 0; - - XRootDStatus st = fs->Stat( source.GetPath(), statInfo ); - if( st.IsOK() && statInfo->TestFlags( StatInfo::IsDir ) ) - { - //------------------------------------------------------------------------ - // Recursively index the remote directory - //------------------------------------------------------------------------ - delete config.srcFile; - config.srcFile = IndexRemote( fs, source.GetURL(), - source.GetURL().size() ); - if ( !config.srcFile ) - { - std::cerr << "Error indexing remote directory."; - return 255; - } - } - - delete fs; - delete statInfo; - } - - XrdCpFile *sourceFile = config.srcFile; - //---------------------------------------------------------------------------- - // Process the sources - //---------------------------------------------------------------------------- - while( sourceFile ) - { - AdjustFileInfo( sourceFile ); - - //-------------------------------------------------------------------------- - // Create a job for every source - //-------------------------------------------------------------------------- - PropertyList properties; - PropertyList *results = new PropertyList; - std::string source = sourceFile->Path; - if( sourceFile->Protocol == XrdCpFile::isFile ) - { - // make sure it is an absolute path - if( source[0] == '/' ) - source = "file://" + source; - else - { - char buf[FILENAME_MAX]; - char *cwd = getcwd( buf, FILENAME_MAX ); - if( !cwd ) - { - std::cerr << strerror( errno ) << std::endl; - return errno; - } - source = "file://" + std::string( cwd ) + '/' + source; - } - } - - AppendCGI( source, config.srcOpq ); - - log->Dump( AppMsg, "Processing source entry: %s, type %s, target file: %s", - sourceFile->Path, FileType2String( sourceFile->Protocol ), - dest.c_str() ); - - //-------------------------------------------------------------------------- - // Create a virtual redirector if it is a metalink file - //-------------------------------------------------------------------------- - URL src( source ); - if( src.IsMetalink() ) - { - RedirectorRegistry ®istry = RedirectorRegistry::Instance(); - XRootDStatus st = registry.RegisterAndWait( src ); - if( !st.IsOK() ) - { - std::cerr << "RedirectorRegistry::Register " << source << " -> " << dest << ": "; - std::cerr << st.ToStr() << std::endl; - resultVect.push_back( results ); - sourceFile = sourceFile->Next; - continue; - } - } - - //-------------------------------------------------------------------------- - // Set up the job - //-------------------------------------------------------------------------- - std::string target = dest; - if( targetIsDir) - { - target = dest + "/"; - // if it is a metalink we don't want to use the metalink name - if( zip ) - { - target += zipFile; - } - else if( src.IsMetalink() ) - { - XrdCl::RedirectorRegistry ®istry = XrdCl::RedirectorRegistry::Instance(); - VirtualRedirector *redirector = registry.Get( source ); - target += redirector->GetTargetName(); - } - else target += (sourceFile->Path+sourceFile->Doff); - } - - AppendCGI( target, config.dstOpq ); - - properties.Set( "source", source ); - properties.Set( "target", target ); - properties.Set( "force", force ); - properties.Set( "posc", posc ); - properties.Set( "coerce", coerce ); - properties.Set( "makeDir", makedir ); - properties.Set( "dynamicSource", dynSrc ); - properties.Set( "thirdParty", thirdParty ); - properties.Set( "checkSumMode", checkSumMode ); - properties.Set( "checkSumType", checkSumType ); - properties.Set( "checkSumPreset", checkSumPreset ); - properties.Set( "chunkSize", chunkSize ); - properties.Set( "parallelChunks", parallelChunks ); - properties.Set( "zipArchive", zip ); - properties.Set( "xcp", xcp ); - properties.Set( "xcpBlockSize", blockSize ); - - if( zip ) - properties.Set( "zipSource", zipFile ); - - if( xcp ) - properties.Set( "nbXcpSources", nbSources ); - - - XRootDStatus st = process.AddJob( properties, results ); - if( !st.IsOK() ) - { - std::cerr << "AddJob " << source << " -> " << target << ": "; - std::cerr << st.ToStr() << std::endl; - } - resultVect.push_back( results ); - sourceFile = sourceFile->Next; - } - - //---------------------------------------------------------------------------- - // Configure the copy process - //---------------------------------------------------------------------------- - PropertyList processConfig; - processConfig.Set( "jobType", "configuration" ); - processConfig.Set( "parallel", config.Parallel ); - process.AddJob( processConfig, 0 ); - - //---------------------------------------------------------------------------- - // Prepare and run the copy process - //---------------------------------------------------------------------------- - XRootDStatus st = process.Prepare(); - if( !st.IsOK() ) - { - CleanUpResults( resultVect ); - std::cerr << "Prepare: " << st.ToStr() << std::endl; - return st.GetShellCode(); - } - - st = process.Run( &progress ); - if( !st.IsOK() ) - { - if( resultVect.size() == 1 ) - std::cerr << "Run: " << st.ToStr() << std::endl; - else - { - std::vector::iterator it; - uint16_t i = 1; - uint16_t jobsRun = 0; - uint16_t errors = 0; - for( it = resultVect.begin(); it != resultVect.end(); ++it, ++i ) - { - if( !(*it)->HasProperty( "status" ) ) - continue; - - XRootDStatus st = (*it)->Get("status"); - if( !st.IsOK() ) - { - std::cerr << "Job #" << i << ": " << st.ToStr(); - ++errors; - } - ++jobsRun; - } - std::cerr << "Jobs total: " << resultVect.size(); - std::cerr << ", run: " << jobsRun; - std::cerr << ", errors: " << errors << std::endl; - } - CleanUpResults( resultVect ); - return st.GetShellCode(); - } - CleanUpResults( resultVect ); - return 0; -} - diff --git a/src/XrdCl/XrdClCopyJob.hh b/src/XrdCl/XrdClCopyJob.hh deleted file mode 100644 index 2732804f9ba..00000000000 --- a/src/XrdCl/XrdClCopyJob.hh +++ /dev/null @@ -1,108 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//----------------------------------------------------------------------------- -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_COPY_JOB_HH__ -#define __XRD_CL_COPY_JOB_HH__ - -#include "XrdCl/XrdClPropertyList.hh" - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - //! Copy job - //---------------------------------------------------------------------------- - class CopyJob - { - public: - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - CopyJob( uint16_t jobId, - PropertyList *jobProperties, - PropertyList *jobResults ): - pProperties( jobProperties ), - pResults( jobResults ), - pJobId( jobId ) - { - pProperties->Get( "source", pSource ); - pProperties->Get( "target", pTarget ); - } - - //------------------------------------------------------------------------ - //! Virtual destructor - //------------------------------------------------------------------------ - virtual ~CopyJob() - { - } - - //------------------------------------------------------------------------ - //! Run the copy job - //! - //! @param progress the handler to be notified about the copy progress - //! @return status of the copy operation - //------------------------------------------------------------------------ - virtual XRootDStatus Run( CopyProgressHandler *progress = 0 ) = 0; - - //------------------------------------------------------------------------ - //! Get the job properties - //------------------------------------------------------------------------ - PropertyList *GetProperties() - { - return pProperties; - } - - //------------------------------------------------------------------------ - //! Get the job results - //------------------------------------------------------------------------ - PropertyList *GetResults() - { - return pResults; - } - - //------------------------------------------------------------------------ - //! Get source - //------------------------------------------------------------------------ - const URL &GetSource() const - { - return pSource; - } - - //------------------------------------------------------------------------ - //! Get target - //------------------------------------------------------------------------ - const URL &GetTarget() const - { - return pTarget; - } - - protected: - PropertyList *pProperties; - PropertyList *pResults; - URL pSource; - URL pTarget; - uint16_t pJobId; - }; -} - -#endif // __XRD_CL_COPY_JOB_HH__ diff --git a/src/XrdCl/XrdClCopyProcess.cc b/src/XrdCl/XrdClCopyProcess.cc deleted file mode 100644 index ecb976e177e..00000000000 --- a/src/XrdCl/XrdClCopyProcess.cc +++ /dev/null @@ -1,426 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2014 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#include "XrdCl/XrdClCopyProcess.hh" -#include "XrdCl/XrdClConstants.hh" -#include "XrdCl/XrdClLog.hh" -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdCl/XrdClClassicCopyJob.hh" -#include "XrdCl/XrdClTPFallBackCopyJob.hh" -#include "XrdCl/XrdClFileSystem.hh" -#include "XrdCl/XrdClMonitor.hh" -#include "XrdCl/XrdClCopyJob.hh" -#include "XrdCl/XrdClUtils.hh" -#include "XrdCl/XrdClJobManager.hh" -#include "XrdCl/XrdClUglyHacks.hh" - -#include - -#include - -namespace -{ - class QueuedCopyJob: public XrdCl::Job - { - public: - QueuedCopyJob( XrdCl::CopyJob *job, - XrdCl::CopyProgressHandler *progress, - uint16_t currentJob, - uint16_t totalJobs, - XrdCl::Semaphore *sem = 0 ): - pJob(job), pProgress(progress), pCurrentJob(currentJob), - pTotalJobs(totalJobs), pSem(sem) {} - - //------------------------------------------------------------------------ - //! Run the job - //------------------------------------------------------------------------ - virtual void Run( void * ) - { - XrdCl::Monitor *mon = XrdCl::DefaultEnv::GetMonitor(); - timeval bTOD; - - //---------------------------------------------------------------------- - // Report beginning of the copy - //---------------------------------------------------------------------- - if( pProgress ) - pProgress->BeginJob( pCurrentJob, pTotalJobs, - &pJob->GetSource(), - &pJob->GetTarget() ); - - if( mon ) - { - XrdCl::Monitor::CopyBInfo i; - i.transfer.origin = &pJob->GetSource(); - i.transfer.target = &pJob->GetTarget(); - mon->Event( XrdCl::Monitor::EvCopyBeg, &i ); - } - - gettimeofday( &bTOD, 0 ); - - //---------------------------------------------------------------------- - // Do the copy - //---------------------------------------------------------------------- - XrdCl::XRootDStatus st = pJob->Run( pProgress ); - pJob->GetResults()->Set( "status", st ); - - //---------------------------------------------------------------------- - // Report end of the copy - //---------------------------------------------------------------------- - if( mon ) - { - std::vector sources; - pJob->GetResults()->Get( "sources", sources ); - XrdCl::Monitor::CopyEInfo i; - i.transfer.origin = &pJob->GetSource(); - i.transfer.target = &pJob->GetTarget(); - i.sources = sources.size(); - i.bTOD = bTOD; - gettimeofday( &i.eTOD, 0 ); - i.status = &st; - mon->Event( XrdCl::Monitor::EvCopyEnd, &i ); - } - - if( pProgress ) - pProgress->EndJob( pCurrentJob, pJob->GetResults() ); - - if( pSem ) - pSem->Post(); - } - - private: - XrdCl::CopyJob *pJob; - XrdCl::CopyProgressHandler *pProgress; - uint16_t pCurrentJob; - uint16_t pTotalJobs; - XrdCl::Semaphore *pSem; - }; -}; - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - // Destructor - //---------------------------------------------------------------------------- - CopyProcess::~CopyProcess() - { - CleanUpJobs(); - } - - //---------------------------------------------------------------------------- - // Add job - //---------------------------------------------------------------------------- - XRootDStatus CopyProcess::AddJob( const PropertyList &properties, - PropertyList *results ) - { - Env *env = DefaultEnv::GetEnv(); - - //-------------------------------------------------------------------------- - // Process a configuraion job - //-------------------------------------------------------------------------- - if( properties.HasProperty( "jobType" ) && - properties.Get( "jobType" ) == "configuration" ) - { - if( pJobProperties.size() > 0 && - pJobProperties.rbegin()->HasProperty( "jobType" ) && - pJobProperties.rbegin()->Get( "jobType" ) == "configuration" ) - { - PropertyList &config = *pJobProperties.rbegin(); - PropertyList::PropertyMap::const_iterator it; - for( it = properties.begin(); it != properties.end(); ++it ) - config.Set( it->first, it->second ); - } - else - pJobProperties.push_back( properties ); - return XRootDStatus(); - } - - //-------------------------------------------------------------------------- - // Validate properties - //-------------------------------------------------------------------------- - if( !properties.HasProperty( "source" ) ) - return XRootDStatus( stError, errInvalidArgs, 0, "source not specified" ); - - if( !properties.HasProperty( "target" ) ) - return XRootDStatus( stError, errInvalidArgs, 0, "target not specified" ); - - pJobProperties.push_back( properties ); - PropertyList &p = pJobProperties.back(); - - const char *bools[] = {"target", "force", "posc", "coerce", "makeDir", "zipArchive", "xcp", 0}; - for( int i = 0; bools[i]; ++i ) - if( !p.HasProperty( bools[i] ) ) - p.Set( bools[i], false ); - - if( !p.HasProperty( "thirdParty" ) ) - p.Set( "thirdParty", "none" ); - - if( !p.HasProperty( "checkSumMode" ) ) - p.Set( "checkSumMode", "none" ); - else - { - if( !p.HasProperty( "checkSumType" ) ) - { - pJobProperties.pop_back(); - return XRootDStatus( stError, errInvalidArgs, 0, - "checkSumType not specified" ); - } - else - { - //---------------------------------------------------------------------- - // Checksum type has to be case insensitive - //---------------------------------------------------------------------- - std::string checkSumType; - p.Get( "checkSumType", checkSumType ); - std::transform(checkSumType.begin(), checkSumType.end(), - checkSumType.begin(), ::tolower); - p.Set( "checkSumType", checkSumType ); - } - } - - if( !p.HasProperty( "parallelChunks" ) ) - { - int val = DefaultCPParallelChunks; - env->GetInt( "CPParallelChunks", val ); - p.Set( "parallelChunks", val ); - } - - if( !p.HasProperty( "chunkSize" ) ) - { - int val = DefaultCPChunkSize; - env->GetInt( "CPChunkSize", val ); - p.Set( "chunkSize", val ); - } - - if( !p.HasProperty( "xcpBlockSize" ) ) - { - int val = DefaultXCpBlockSize; - env->GetInt( "XCpBlockSize", val ); - p.Set( "xcpBlockSize", val ); - } - - if( !p.HasProperty( "initTimeout" ) ) - { - int val = DefaultCPInitTimeout; - env->GetInt( "CPInitTimeout", val ); - p.Set( "initTimeout", val ); - } - - if( !p.HasProperty( "tpcTimeout" ) ) - { - int val = DefaultCPTPCTimeout; - env->GetInt( "CPTPCTimeout", val ); - p.Set( "tpcTimeout", val ); - } - - if( !p.HasProperty( "dynamicSource" ) ) - p.Set( "dynamicSource", false ); - - //-------------------------------------------------------------------------- - // Insert the properties - //-------------------------------------------------------------------------- - Log *log = DefaultEnv::GetLog(); - Utils::LogPropertyList( log, UtilityMsg, "Adding job with properties: %s", - p ); - pJobResults.push_back( results ); - return XRootDStatus(); - } - - //---------------------------------------------------------------------------- - // Prepare the copy jobs - //---------------------------------------------------------------------------- - XRootDStatus CopyProcess::Prepare() - { - Log *log = DefaultEnv::GetLog(); - std::vector::iterator it; - - log->Debug( UtilityMsg, "CopyProcess: %d jobs to prepare", - pJobProperties.size() ); - - std::map targetFlags; - int i = 0; - for( it = pJobProperties.begin(); it != pJobProperties.end(); ++it, ++i ) - { - PropertyList &props = *it; - - if( props.HasProperty( "jobType" ) && - props.Get( "jobType" ) == "configuration" ) - continue; - - PropertyList *res = pJobResults[i]; - std::string tmp; - - props.Get( "source", tmp ); - URL source = tmp; - if( !source.IsValid() ) - return XRootDStatus( stError, errInvalidArgs, 0, "invalid source" ); - - props.Get( "target", tmp ); - URL target = tmp; - if( !target.IsValid() ) - return XRootDStatus( stError, errInvalidArgs, 0, "invalid target" ); - - bool tpc = false; - props.Get( "thirdParty", tmp ); - if( tmp != "none" ) - tpc = true; - - //------------------------------------------------------------------------ - // Check if we have all we need - //------------------------------------------------------------------------ - if( source.GetProtocol() != "stdio" && source.GetPath().empty() ) - { - log->Debug( UtilityMsg, "CopyProcess (job #%d): no source specified.", - i ); - CleanUpJobs(); - XRootDStatus st = XRootDStatus( stError, errInvalidArgs ); - res->Set( "status", st ); - return st; - } - - if( target.GetProtocol() != "stdio" && target.GetPath().empty() ) - { - log->Debug( UtilityMsg, "CopyProcess (job #%d): no target specified.", - i ); - CleanUpJobs(); - XRootDStatus st = XRootDStatus( stError, errInvalidArgs ); - res->Set( "status", st ); - return st; - } - - //------------------------------------------------------------------------ - // Check what kind of job we should do - //------------------------------------------------------------------------ - CopyJob *job = 0; - - if( tpc == true ) - job = new TPFallBackCopyJob( i+1, &props, res ); - else - job = new ClassicCopyJob( i+1, &props, res ); - - pJobs.push_back( job ); - } - return XRootDStatus(); - } - - //---------------------------------------------------------------------------- - // Run the copy jobs - //---------------------------------------------------------------------------- - XRootDStatus CopyProcess::Run( CopyProgressHandler *progress ) - { - //-------------------------------------------------------------------------- - // Get the configuration - //-------------------------------------------------------------------------- - uint8_t parallelThreads = 1; - if( pJobProperties.size() > 0 && - pJobProperties.rbegin()->HasProperty( "jobType" ) && - pJobProperties.rbegin()->Get( "jobType" ) == "configuration" ) - { - PropertyList &config = *pJobProperties.rbegin(); - if( config.HasProperty( "parallel" ) ) - parallelThreads = (uint8_t)config.Get( "parallel" ); - } - - //-------------------------------------------------------------------------- - // Run the show - //-------------------------------------------------------------------------- - std::vector::iterator it; - uint16_t currentJob = 1; - uint16_t totalJobs = pJobs.size(); - - //-------------------------------------------------------------------------- - // Single thread - //-------------------------------------------------------------------------- - if( parallelThreads == 1 ) - { - XRootDStatus err; - - for( it = pJobs.begin(); it != pJobs.end(); ++it ) - { - QueuedCopyJob j( *it, progress, currentJob, totalJobs ); - j.Run(0); - - XRootDStatus st = (*it)->GetResults()->Get( "status" ); - if( err.IsOK() && !st.IsOK() ) - { - err = st; - } - ++currentJob; - } - - if( !err.IsOK() ) return err; - } - //-------------------------------------------------------------------------- - // Multiple threads - //-------------------------------------------------------------------------- - else - { - uint16_t workers = std::min( (uint16_t)parallelThreads, - (uint16_t)pJobs.size() ); - JobManager jm( workers ); - jm.Initialize(); - if( !jm.Start() ) - return XRootDStatus( stError, errOSError, 0, - "Unable to start job manager" ); - - Semaphore *sem = new Semaphore(0); - std::vector queued; - for( it = pJobs.begin(); it != pJobs.end(); ++it ) - { - QueuedCopyJob *j = new QueuedCopyJob( *it, progress, currentJob, - totalJobs, sem ); - - queued.push_back( j ); - jm.QueueJob(j, 0); - ++currentJob; - } - - std::vector::iterator itQ; - for( itQ = queued.begin(); itQ != queued.end(); ++itQ ) - sem->Wait(); - delete sem; - - if( !jm.Stop() ) - return XRootDStatus( stError, errOSError, 0, - "Unable to stop job manager" ); - jm.Finalize(); - for( itQ = queued.begin(); itQ != queued.end(); ++itQ ) - delete *itQ; - - for( it = pJobs.begin(); it != pJobs.end(); ++it ) - { - XRootDStatus st = (*it)->GetResults()->Get( "status" ); - if( !st.IsOK() ) return st; - } - }; - return XRootDStatus(); - } - - void CopyProcess::CleanUpJobs() - { - std::vector::iterator itJ; - for( itJ = pJobs.begin(); itJ != pJobs.end(); ++itJ ) - delete *itJ; - pJobs.clear(); - } -} diff --git a/src/XrdCl/XrdClCopyProcess.hh b/src/XrdCl/XrdClCopyProcess.hh deleted file mode 100644 index d853e156700..00000000000 --- a/src/XrdCl/XrdClCopyProcess.hh +++ /dev/null @@ -1,183 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2014 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_COPY_PROCESS_HH__ -#define __XRD_CL_COPY_PROCESS_HH__ - -#include "XrdCl/XrdClURL.hh" -#include "XrdCl/XrdClXRootDResponses.hh" -#include "XrdCl/XrdClPropertyList.hh" -#include -#include - -namespace XrdCl -{ - class CopyJob; - - //---------------------------------------------------------------------------- - //! Interface for copy progress notification - //---------------------------------------------------------------------------- - class CopyProgressHandler - { - public: - virtual ~CopyProgressHandler() {} - - //------------------------------------------------------------------------ - //! Notify when a new job is about to start - //! - //! @param jobNum the job number of the copy job concerned - //! @param jobTotal total number of jobs being processed - //! @param source the source url of the current job - //! @param destination the destination url of the current job - //------------------------------------------------------------------------ - virtual void BeginJob( uint16_t jobNum, - uint16_t jobTotal, - const URL *source, - const URL *destination ) - { - (void)jobNum; (void)jobTotal; (void)source; (void)destination; - }; - - //------------------------------------------------------------------------ - //! Notify when the previous job has finished - //! - //! @param jobNum job number - //! @param result result of the job - //------------------------------------------------------------------------ - virtual void EndJob( uint16_t jobNum, - const PropertyList *result ) - { - (void)jobNum; (void)result; - }; - - //------------------------------------------------------------------------ - //! Notify about the progress of the current job - //! - //! @param jobNum job number - //! @param bytesProcessed bytes processed by the current job - //! @param bytesTotal total number of bytes to be processed by the - //! current job - //------------------------------------------------------------------------ - virtual void JobProgress( uint16_t jobNum, - uint64_t bytesProcessed, - uint64_t bytesTotal ) - { - (void)jobNum; (void)bytesProcessed; (void)bytesTotal; - }; - - //------------------------------------------------------------------------ - //! Determine whether the job should be canceled - //------------------------------------------------------------------------ - virtual bool ShouldCancel( uint16_t jobNum ) - { - (void)jobNum; - return false; - } - }; - - //---------------------------------------------------------------------------- - //! Copy the data from one point to another - //---------------------------------------------------------------------------- - class CopyProcess - { - public: - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - CopyProcess() {} - - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - virtual ~CopyProcess(); - - //------------------------------------------------------------------------ - //! Add job - //! - //! @param properties job configuration parameters - //! @param results placeholder for the results - //! - //! Configuration properties: - //! source [string] - original source URL - //! target [string] - target directory or file - //! sourceLimit [uint16_t] - maximum number sources - //! force [bool] - overwrite target if exists - //! posc [bool] - persistify only on successful close - //! coerce [bool] - ignore locking semantics on destination - //! makeDir [bool] - create path to the file if it doesn't - //! exist - //! thirdParty [string] - "first" try third party copy, if it fails - //! try normal copy; "only" only try third - //! party copy - //! checkSumMode [string] - "none" - no checksumming - //! "end2end" - end to end checksumming - //! "source" - calculate checksum at source - //! "target" - calculate checksum at target - //! checkSumType [string] - type of the checksum to be used - //! checkSumPreset [string] - checksum preset - //! chunkSize [uint32_t] - size of a copy chunks in bytes - //! parallelChunks [uint8_t] - number of chunks that should be requested - //! in parallel - //! initTimeout [uint16_t] - time limit for successfull initialization - //! of the copy job - //! tpcTimeout [uint16_t] - time limit for the actual copy to finish - //! dynamicSource [bool] - support for the case where the size source - //! file may change during reading process - //! - //! Configuration job - this is a job that that is supposed to configure - //! the copy process as a whole instead of adding a copy job: - //! - //! jobType [string] - "configuration" - for configuraion - //! parallel [uint8_t] - nomber of copy jobs to be run in parallel - //! - //! Results: - //! sourceCheckSum [string] - checksum at source, if requested - //! targetCheckSum [string] - checksum at target, if requested - //! size [uint64_t] - file size - //! status [XRootDStatus] - status of the copy operation - //! sources [vector] - all sources used - //! realTarget [string] - the actual disk server target - //------------------------------------------------------------------------ - XRootDStatus AddJob( const PropertyList &properties, - PropertyList *results ); - - //------------------------------------------------------------------------ - // Prepare the copy jobs - //------------------------------------------------------------------------ - XRootDStatus Prepare(); - - //------------------------------------------------------------------------ - //! Run the copy jobs - //------------------------------------------------------------------------ - XRootDStatus Run( CopyProgressHandler *handler ); - - private: - void CleanUpJobs(); - std::vector pJobProperties; - std::vector pJobResults; - std::vector pJobs; - }; -} - -#endif // __XRD_CL_COPY_PROCESS_HH__ diff --git a/src/XrdCl/XrdClDefaultEnv.cc b/src/XrdCl/XrdClDefaultEnv.cc deleted file mode 100644 index 298c37fee1b..00000000000 --- a/src/XrdCl/XrdClDefaultEnv.cc +++ /dev/null @@ -1,852 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdCl/XrdClConstants.hh" -#include "XrdCl/XrdClPostMaster.hh" -#include "XrdCl/XrdClLog.hh" -#include "XrdCl/XrdClForkHandler.hh" -#include "XrdCl/XrdClFileTimer.hh" -#include "XrdCl/XrdClUtils.hh" -#include "XrdCl/XrdClMonitor.hh" -#include "XrdCl/XrdClCheckSumManager.hh" -#include "XrdCl/XrdClTransportManager.hh" -#include "XrdCl/XrdClPlugInManager.hh" -#include "XrdCl/XrdClOptimizers.hh" -#include "XrdOuc/XrdOucPreload.hh" -#include "XrdSys/XrdSysAtomics.hh" -#include "XrdSys/XrdSysUtils.hh" -#include "XrdSys/XrdSysPwd.hh" -#include "XrdVersion.hh" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -XrdVERSIONINFO( XrdCl, client ); - -//------------------------------------------------------------------------------ -// Forking functions -//------------------------------------------------------------------------------ -extern "C" -{ - //---------------------------------------------------------------------------- - // Prepare for the forking - //---------------------------------------------------------------------------- - static void prepare() - { - using namespace XrdCl; - Log *log = DefaultEnv::GetLog(); - Env *env = DefaultEnv::GetEnv(); - ForkHandler *forkHandler = DefaultEnv::GetForkHandler(); - - log->Debug( UtilityMsg, "In the prepare fork handler for process %d", - getpid() ); - - //-------------------------------------------------------------------------- - // Run the fork handler if it's enabled - //-------------------------------------------------------------------------- - int runForkHandler = DefaultRunForkHandler; - env->GetInt( "RunForkHandler", runForkHandler ); - if( runForkHandler ) - forkHandler->Prepare(); - env->WriteLock(); - } - - //---------------------------------------------------------------------------- - // Parent handler - //---------------------------------------------------------------------------- - static void parent() - { - using namespace XrdCl; - Log *log = DefaultEnv::GetLog(); - Env *env = DefaultEnv::GetEnv(); - ForkHandler *forkHandler = DefaultEnv::GetForkHandler(); - env->UnLock(); - - pid_t pid = getpid(); - log->Debug( UtilityMsg, "In the parent fork handler for process %d", pid ); - - //-------------------------------------------------------------------------- - // Run the fork handler if it's enabled - //-------------------------------------------------------------------------- - int runForkHandler = DefaultRunForkHandler; - env->GetInt( "RunForkHandler", runForkHandler ); - if( runForkHandler ) - { - log->SetPid(pid); - forkHandler->Parent(); - } - } - - //---------------------------------------------------------------------------- - // Child handler - //---------------------------------------------------------------------------- - static void child() - { - using namespace XrdCl; - DefaultEnv::ReInitializeLogging(); - Log *log = DefaultEnv::GetLog(); - Env *env = DefaultEnv::GetEnv(); - ForkHandler *forkHandler = DefaultEnv::GetForkHandler(); - env->ReInitializeLock(); - - pid_t pid = getpid(); - log->Debug( UtilityMsg, "In the child fork handler for process %d", pid ); - - //-------------------------------------------------------------------------- - // Run the fork handler if it's enabled - //-------------------------------------------------------------------------- - int runForkHandler = DefaultRunForkHandler; - env->GetInt( "RunForkHandler", runForkHandler ); - if( runForkHandler ) - { - log->SetPid(pid); - forkHandler->Child(); - } - } -} - -namespace -{ - //---------------------------------------------------------------------------- - // Translate a string into a topic mask - //---------------------------------------------------------------------------- - struct MaskTranslator - { - //-------------------------------------------------------------------------- - // Initialize the translation array - //-------------------------------------------------------------------------- - MaskTranslator() - { - masks["AppMsg"] = XrdCl::AppMsg; - masks["UtilityMsg"] = XrdCl::UtilityMsg; - masks["FileMsg"] = XrdCl::FileMsg; - masks["PollerMsg"] = XrdCl::PollerMsg; - masks["PostMasterMsg"] = XrdCl::PostMasterMsg; - masks["XRootDTransportMsg"] = XrdCl::XRootDTransportMsg; - masks["TaskMgrMsg"] = XrdCl::TaskMgrMsg; - masks["XRootDMsg"] = XrdCl::XRootDMsg; - masks["FileSystemMsg"] = XrdCl::FileSystemMsg; - masks["AsyncSockMsg"] = XrdCl::AsyncSockMsg; - masks["JobMgrMsg"] = XrdCl::JobMgrMsg; - masks["PlugInMgrMsg"] = XrdCl::PlugInMgrMsg; - } - - //-------------------------------------------------------------------------- - // Translate the mask - //-------------------------------------------------------------------------- - uint64_t translateMask( const std::string mask ) - { - if( mask == "" ) - return 0xffffffffffffffffULL; - - std::vector topics; - std::vector::iterator it; - XrdCl::Utils::splitString( topics, mask, "|" ); - - uint64_t resultMask = 0; - std::map::iterator maskIt; - for( it = topics.begin(); it != topics.end(); ++it ) - { - //---------------------------------------------------------------------- - // Check for resetting pseudo topics - //---------------------------------------------------------------------- - if( *it == "All" ) - { - resultMask = 0xffffffffffffffffULL; - continue; - } - - if( *it == "None" ) - { - resultMask = 0ULL; - continue; - } - - //---------------------------------------------------------------------- - // Check whether given topic should be disabled or enabled - //---------------------------------------------------------------------- - std::string topic = *it; - bool disable = false; - if( !topic.empty() && topic[0] == '^' ) - { - disable = true; - topic = topic.substr( 1, topic.length()-1 ); - } - - maskIt = masks.find( topic ); - if( maskIt == masks.end() ) - continue; - - if( disable ) - resultMask &= (0xffffffffffffffffULL ^ maskIt->second); - else - resultMask |= maskIt->second; - } - - return resultMask; - } - - std::map masks; - }; - - //---------------------------------------------------------------------------- - // Helper for handling environment variables - //---------------------------------------------------------------------------- - template - struct EnvVarHolder - { - EnvVarHolder( const std::string &name_, const Item &def_ ): - name( name_ ), def( def_ ) {} - std::string name; - Item def; - }; -} - -#define REGISTER_VAR_INT( array, name, def ) \ - array.push_back( EnvVarHolder( name, def ) ) - -#define REGISTER_VAR_STR( array, name, def ) \ - array.push_back( EnvVarHolder( name, def ) ) - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - // Statics - //---------------------------------------------------------------------------- - XrdSysMutex DefaultEnv::sInitMutex; - Env *DefaultEnv::sEnv = 0; - PostMaster *DefaultEnv::sPostMaster = 0; - Log *DefaultEnv::sLog = 0; - ForkHandler *DefaultEnv::sForkHandler = 0; - FileTimer *DefaultEnv::sFileTimer = 0; - Monitor *DefaultEnv::sMonitor = 0; - XrdOucPinLoader *DefaultEnv::sMonitorLibHandle = 0; - bool DefaultEnv::sMonitorInitialized = false; - CheckSumManager *DefaultEnv::sCheckSumManager = 0; - TransportManager *DefaultEnv::sTransportManager = 0; - PlugInManager *DefaultEnv::sPlugInManager = 0; - - //---------------------------------------------------------------------------- - // Constructor - //---------------------------------------------------------------------------- - DefaultEnv::DefaultEnv() - { - Log *log = GetLog(); - - //-------------------------------------------------------------------------- - // Declate the variables to be processed - //-------------------------------------------------------------------------- - std::vector > varsInt; - std::vector > varsStr; - REGISTER_VAR_INT( varsInt, "ConnectionWindow", DefaultConnectionWindow ); - REGISTER_VAR_INT( varsInt, "ConnectionRetry", DefaultConnectionRetry ); - REGISTER_VAR_INT( varsInt, "RequestTimeout", DefaultRequestTimeout ); - REGISTER_VAR_INT( varsInt, "StreamTimeout", DefaultStreamTimeout ); - REGISTER_VAR_INT( varsInt, "SubStreamsPerChannel", DefaultSubStreamsPerChannel ); - REGISTER_VAR_INT( varsInt, "TimeoutResolution", DefaultTimeoutResolution ); - REGISTER_VAR_INT( varsInt, "StreamErrorWindow", DefaultStreamErrorWindow ); - REGISTER_VAR_INT( varsInt, "RunForkHandler", DefaultRunForkHandler ); - REGISTER_VAR_INT( varsInt, "RedirectLimit", DefaultRedirectLimit ); - REGISTER_VAR_INT( varsInt, "WorkerThreads", DefaultWorkerThreads ); - REGISTER_VAR_INT( varsInt, "CPChunkSize", DefaultCPChunkSize ); - REGISTER_VAR_INT( varsInt, "CPParallelChunks", DefaultCPParallelChunks ); - REGISTER_VAR_INT( varsInt, "DataServerTTL", DefaultDataServerTTL ); - REGISTER_VAR_INT( varsInt, "LoadBalancerTTL", DefaultLoadBalancerTTL ); - REGISTER_VAR_INT( varsInt, "CPInitTimeout", DefaultCPInitTimeout ); - REGISTER_VAR_INT( varsInt, "CPTPCTimeout", DefaultCPTPCTimeout ); - REGISTER_VAR_INT( varsInt, "TCPKeepAlive", DefaultTCPKeepAlive ); - REGISTER_VAR_INT( varsInt, "TCPKeepAliveTime", DefaultTCPKeepAliveTime ); - REGISTER_VAR_INT( varsInt, "TCPKeepAliveInterval", DefaultTCPKeepAliveInterval ); - REGISTER_VAR_INT( varsInt, "TCPKeepProbes", DefaultTCPKeepAliveProbes ); - REGISTER_VAR_INT( varsInt, "MultiProtocol", DefaultMultiProtocol ); - REGISTER_VAR_INT( varsInt, "ParallelEvtLoop", DefaultParallelEvtLoop ); - REGISTER_VAR_INT( varsInt, "MetalinkProcessing", DefaultMetalinkProcessing ); - REGISTER_VAR_INT( varsInt, "LocalMetalinkFile", DefaultLocalMetalinkFile ); - REGISTER_VAR_INT( varsInt, "XCpBlockSize", DefaultXCpBlockSize ); - REGISTER_VAR_INT( varsInt, "NoDelay", DefaultNoDelay ); - REGISTER_VAR_INT( varsInt, "AioSignal", DefaultAioSignal ); - REGISTER_VAR_INT( varsInt, "PreferIPv4", DefaultPreferIPv4 ); - - REGISTER_VAR_STR( varsStr, "PollerPreference", DefaultPollerPreference ); - REGISTER_VAR_STR( varsStr, "ClientMonitor", DefaultClientMonitor ); - REGISTER_VAR_STR( varsStr, "ClientMonitorParam", DefaultClientMonitorParam ); - REGISTER_VAR_STR( varsStr, "NetworkStack", DefaultNetworkStack ); - REGISTER_VAR_STR( varsStr, "PlugIn", DefaultPlugIn ); - REGISTER_VAR_STR( varsStr, "PlugInConfDir", DefaultPlugInConfDir ); - REGISTER_VAR_STR( varsStr, "ReadRecovery", DefaultReadRecovery ); - REGISTER_VAR_STR( varsStr, "WriteRecovery", DefaultWriteRecovery ); - REGISTER_VAR_STR( varsStr, "GlfnRedirector", DefaultGlfnRedirector ); - - //-------------------------------------------------------------------------- - // Process the configuration files - //-------------------------------------------------------------------------- - std::map config, userConfig; - Status st = Utils::ProcessConfig( config, "/etc/xrootd/client.conf" ); - - if( !st.IsOK() ) - log->Warning( UtilityMsg, "Unable to process global config file: %s", - st.ToString().c_str() ); - - XrdSysPwd pwdHandler; - passwd *pwd = pwdHandler.Get( getuid() ); - if( pwd ) - { - std::string userConfigFile = pwd->pw_dir; - userConfigFile += "/.xrootd/client.conf"; - - st = Utils::ProcessConfig( userConfig, userConfigFile ); - - if( !st.IsOK() ) - log->Debug( UtilityMsg, "Unable to process user config file: %s", - st.ToString().c_str() ); - } - else - log->Debug( UtilityMsg, "Unable to find user home directory." ); - - std::map::iterator it; - - for( it = config.begin(); it != config.end(); ++it ) - log->Dump( UtilityMsg, "[Global config] \"%s\" = \"%s\"", - it->first.c_str(), it->second.c_str() ); - - for( it = userConfig.begin(); it != userConfig.end(); ++it ) - { - config[it->first] = it->second; - log->Dump( UtilityMsg, "[User config] \"%s\" = \"%s\"", - it->first.c_str(), it->second.c_str() ); - } - - for( it = config.begin(); it != config.end(); ++it ) - log->Debug( UtilityMsg, "[Effective config] \"%s\" = \"%s\"", - it->first.c_str(), it->second.c_str() ); - - //-------------------------------------------------------------------------- - // Monitoring settings - //-------------------------------------------------------------------------- - char *tmp = strdup( XrdSysUtils::ExecName() ); - char *appName = basename( tmp ); - PutString( "AppName", appName ); - free( tmp ); - ImportString( "AppName", "XRD_APPNAME" ); - PutString( "MonInfo", "" ); - ImportString( "MonInfo", "XRD_MONINFO" ); - - //-------------------------------------------------------------------------- - // Process ints - //-------------------------------------------------------------------------- - for( size_t i = 0; i < varsInt.size(); ++i ) - { - PutInt( varsInt[i].name, varsInt[i].def ); - - it = config.find( varsInt[i].name ); - if( it != config.end() ) - { - char *endPtr = 0; - int value = (int)strtol( it->second.c_str(), &endPtr, 0 ); - if( *endPtr ) - log->Warning( UtilityMsg, "Unable to set %s to %s: not a proper " - "integer", varsInt[i].name.c_str(), - it->second.c_str() ); - else - PutInt( varsInt[i].name, value ); - } - - std::string name = "XRD_" + varsInt[i].name; - std::transform( name.begin(), name.end(), name.begin(), ::toupper ); - ImportInt( varsInt[i].name, name ); - } - - //-------------------------------------------------------------------------- - // Process strings - //-------------------------------------------------------------------------- - for( size_t i = 0; i < varsStr.size(); ++i ) - { - PutString( varsStr[i].name, varsStr[i].def ); - - it = config.find( varsStr[i].name ); - if( it != config.end() ) - PutString( varsStr[i].name, it->second ); - - std::string name = "XRD_" + varsStr[i].name; - std::transform( name.begin(), name.end(), name.begin(), ::toupper ); - ImportString( varsStr[i].name, name ); - } - - //-------------------------------------------------------------------------- - // Register fork handlers - //-------------------------------------------------------------------------- - pthread_atfork( prepare, parent, child ); - } - - //---------------------------------------------------------------------------- - // Get default client environment - //---------------------------------------------------------------------------- - Env *DefaultEnv::GetEnv() - { - return sEnv; - } - - //---------------------------------------------------------------------------- - // Get default post master - //---------------------------------------------------------------------------- - PostMaster *DefaultEnv::GetPostMaster() - { - PostMaster* postMaster = AtomicGet(sPostMaster); - - if( unlikely( !postMaster ) ) - { - XrdSysMutexHelper scopedLock( sInitMutex ); - postMaster = AtomicGet(sPostMaster); - - if( postMaster ) - return postMaster; - - postMaster = new PostMaster(); - - if( !postMaster->Initialize() ) - { - delete postMaster; - postMaster = 0; - return 0; - } - - if( !postMaster->Start() ) - { - postMaster->Finalize(); - delete postMaster; - postMaster = 0; - return 0; - } - - sForkHandler->RegisterPostMaster( postMaster ); - postMaster->GetTaskManager()->RegisterTask( sFileTimer, time(0), false ); - AtomicCAS(sPostMaster, sPostMaster, postMaster); - } - - return postMaster; - } - - //---------------------------------------------------------------------------- - // Get log - //---------------------------------------------------------------------------- - Log *DefaultEnv::GetLog() - { - return sLog; - } - - //---------------------------------------------------------------------------- - // Set log level - //---------------------------------------------------------------------------- - void DefaultEnv::SetLogLevel( const std::string &level ) - { - Log *log = GetLog(); - log->SetLevel( level ); - } - - //---------------------------------------------------------------------------- - // Set log file - //---------------------------------------------------------------------------- - bool DefaultEnv::SetLogFile( const std::string &filepath ) - { - Log *log = GetLog(); - LogOutFile *out = new LogOutFile(); - - if( out->Open( filepath ) ) - { - log->SetOutput( out ); - return true; - } - - delete out; - return false; - } - - //---------------------------------------------------------------------------- - //! Set log mask. - //------------------------------------------------------------------------ - void DefaultEnv::SetLogMask( const std::string &level, - const std::string &mask ) - { - Log *log = GetLog(); - MaskTranslator translator; - uint64_t topicMask = translator.translateMask( mask ); - - if( level == "All" ) - { - log->SetMask( Log::ErrorMsg, topicMask ); - log->SetMask( Log::WarningMsg, topicMask ); - log->SetMask( Log::InfoMsg, topicMask ); - log->SetMask( Log::DebugMsg, topicMask ); - log->SetMask( Log::DumpMsg, topicMask ); - return; - } - - log->SetMask( level, topicMask ); - } - - //---------------------------------------------------------------------------- - // Get fork handler - //---------------------------------------------------------------------------- - ForkHandler *DefaultEnv::GetForkHandler() - { - return sForkHandler; - } - - //---------------------------------------------------------------------------- - // Get fork handler - //---------------------------------------------------------------------------- - FileTimer *DefaultEnv::GetFileTimer() - { - return sFileTimer; - } - - //---------------------------------------------------------------------------- - // Get the monitor object - //---------------------------------------------------------------------------- - Monitor *DefaultEnv::GetMonitor() - { - if( unlikely( !sMonitorInitialized ) ) - { - XrdSysMutexHelper scopedLock( sInitMutex ); - if( !sMonitorInitialized ) - { - //---------------------------------------------------------------------- - // Check the environment settings - //---------------------------------------------------------------------- - Env *env = GetEnv(); - Log *log = GetLog(); - sMonitorInitialized = true; - std::string monitorLib = DefaultClientMonitor; - env->GetString( "ClientMonitor", monitorLib ); - if( monitorLib.empty() ) - { - log->Debug( UtilityMsg, "Monitor library name not set. No " - "monitoring" ); - return 0; - } - - std::string monitorParam = DefaultClientMonitorParam; - env->GetString( "ClientMonitorParam", monitorParam ); - - log->Debug( UtilityMsg, "Initializing monitoring, lib: %s, param: %s", - monitorLib.c_str(), monitorParam.c_str() ); - - //---------------------------------------------------------------------- - // Loading the plugin - //---------------------------------------------------------------------- - char *errBuffer = new char[4000]; - sMonitorLibHandle = new XrdOucPinLoader( - errBuffer, 4000, &XrdVERSIONINFOVAR( XrdCl ), - "monitor", monitorLib.c_str() ); - - typedef XrdCl::Monitor *(*MonLoader)(const char *, const char *); - MonLoader loader; - loader = (MonLoader)sMonitorLibHandle->Resolve( "XrdClGetMonitor", -1 ); - if( !loader ) - { - log->Error( UtilityMsg, "Unable to initialize user monitoring: %s", - errBuffer ); - delete [] errBuffer; - sMonitorLibHandle->Unload(); - delete sMonitorLibHandle; sMonitorLibHandle = 0; - return 0; - } - - //---------------------------------------------------------------------- - // Instantiating the monitor object - //---------------------------------------------------------------------- - const char *param = monitorParam.empty() ? 0 : monitorParam.c_str(); - sMonitor = (*loader)( XrdSysUtils::ExecName(), param ); - - if( !sMonitor ) - { - log->Error( UtilityMsg, "Unable to initialize user monitoring: %s", - errBuffer ); - delete [] errBuffer; - sMonitorLibHandle->Unload(); - delete sMonitorLibHandle; sMonitorLibHandle = 0; - return 0; - } - log->Debug( UtilityMsg, "Successfully initialized monitoring from: %s", - monitorLib.c_str() ); - delete [] errBuffer; - } - } - return sMonitor; - } - - //---------------------------------------------------------------------------- - // Get checksum manager - //---------------------------------------------------------------------------- - CheckSumManager *DefaultEnv::GetCheckSumManager() - { - if( unlikely( !sCheckSumManager ) ) - { - XrdSysMutexHelper scopedLock( sInitMutex ); - if( !sCheckSumManager ) - sCheckSumManager = new CheckSumManager(); - } - return sCheckSumManager; - } - - //---------------------------------------------------------------------------- - // Get transport manager - //---------------------------------------------------------------------------- - TransportManager *DefaultEnv::GetTransportManager() - { - if( unlikely( !sTransportManager ) ) - { - XrdSysMutexHelper scopedLock( sInitMutex ); - if( !sTransportManager ) - sTransportManager = new TransportManager(); - } - return sTransportManager; - } - - //---------------------------------------------------------------------------- - // Get plug-in manager - //---------------------------------------------------------------------------- - PlugInManager *DefaultEnv::GetPlugInManager() - { - return sPlugInManager; - } - - //---------------------------------------------------------------------------- - // Retrieve the plug-in factory for the given URL - //---------------------------------------------------------------------------- - PlugInFactory *DefaultEnv::GetPlugInFactory( const std::string url ) - { - return sPlugInManager->GetFactory( url ); - } - - //---------------------------------------------------------------------------- - // Initialize the environment - //---------------------------------------------------------------------------- - void DefaultEnv::Initialize() - { - sLog = new Log(); - SetUpLog(); - - sEnv = new DefaultEnv(); - sForkHandler = new ForkHandler(); - sFileTimer = new FileTimer(); - sPlugInManager = new PlugInManager(); - - sPlugInManager->ProcessEnvironmentSettings(); - sForkHandler->RegisterFileTimer( sFileTimer ); - - //-------------------------------------------------------------------------- - // MacOSX library loading is completely moronic. We cannot dlopen a library - // from a thread other than a main thread, so we-pre dlopen all the - // libraries that we may potentially want. - //-------------------------------------------------------------------------- -#ifdef __APPLE__ - char *errBuff = new char[1024]; - - const char *libs[] = - { - "libXrdSeckrb5.so", - "libXrdSecgsi.so", - "libXrdSecgsiAuthzVO.so", - "libXrdSecgsiGMAPDN.so", - "libXrdSecpwd.so", - "libXrdSecsss.so", - "libXrdSecunix.so", - 0 - }; - - for( int i = 0; libs[i]; ++i ) - { - sLog->Debug( UtilityMsg, "Attempting to pre-load: %s", libs[i] ); - bool ok = XrdOucPreload( libs[i], errBuff, 1024 ); - if( !ok ) - sLog->Error( UtilityMsg, "Unable to pre-load %s: %s", libs[i], errBuff ); - } - delete [] errBuff; -#endif - } - - //---------------------------------------------------------------------------- - // Finalize the environment - //---------------------------------------------------------------------------- - void DefaultEnv::Finalize() - { - if( sPostMaster ) - { - sPostMaster->Stop(); - sPostMaster->Finalize(); - delete sPostMaster; - sPostMaster = 0; - } - - delete sTransportManager; - sTransportManager = 0; - - delete sCheckSumManager; - sCheckSumManager = 0; - - delete sMonitor; - sMonitor = 0; - - if( sMonitorLibHandle ) - sMonitorLibHandle->Unload(); - - delete sMonitorLibHandle; - sMonitorLibHandle = 0; - - delete sForkHandler; - sForkHandler = 0; - - delete sFileTimer; - sFileTimer = 0; - - delete sPlugInManager; - sPlugInManager = 0; - - delete sEnv; - sEnv = 0; - - delete sLog; - sLog = 0; - } - - //---------------------------------------------------------------------------- - // Re-initialize the logging - //---------------------------------------------------------------------------- - void DefaultEnv::ReInitializeLogging() - { - delete sLog; - sLog = new Log(); - SetUpLog(); - } - - //---------------------------------------------------------------------------- - // Set up the log - //---------------------------------------------------------------------------- - void DefaultEnv::SetUpLog() - { - Log *log = GetLog(); - - //-------------------------------------------------------------------------- - // Check if the log level has been defined in the environment - //-------------------------------------------------------------------------- - char *level = getenv( "XRD_LOGLEVEL" ); - if( level ) - log->SetLevel( level ); - - //-------------------------------------------------------------------------- - // Check if we need to log to a file - //-------------------------------------------------------------------------- - char *file = getenv( "XRD_LOGFILE" ); - if( file ) - { - LogOutFile *out = new LogOutFile(); - if( out->Open( file ) ) - log->SetOutput( out ); - else - delete out; - } - - //-------------------------------------------------------------------------- - // Log mask defaults - //-------------------------------------------------------------------------- - MaskTranslator translator; - log->SetMask( Log::DumpMsg, translator.translateMask( "All|^PollerMsg" ) ); - - //-------------------------------------------------------------------------- - // Initialize the topic mask - //-------------------------------------------------------------------------- - char *logMask = getenv( "XRD_LOGMASK" ); - if( logMask ) - { - uint64_t mask = translator.translateMask( logMask ); - log->SetMask( Log::ErrorMsg, mask ); - log->SetMask( Log::WarningMsg, mask ); - log->SetMask( Log::InfoMsg, mask ); - log->SetMask( Log::DebugMsg, mask ); - log->SetMask( Log::DumpMsg, mask ); - } - - logMask = getenv( "XRD_LOGMASK_ERROR" ); - if( logMask ) log->SetMask( Log::ErrorMsg, translator.translateMask( logMask ) ); - - logMask = getenv( "XRD_LOGMASK_WARNING" ); - if( logMask ) log->SetMask( Log::WarningMsg, translator.translateMask( logMask ) ); - - logMask = getenv( "XRD_LOGMASK_INFO" ); - if( logMask ) log->SetMask( Log::InfoMsg, translator.translateMask( logMask ) ); - - logMask = getenv( "XRD_LOGMASK_DEBUG" ); - if( logMask ) log->SetMask( Log::DebugMsg, translator.translateMask( logMask ) ); - - logMask = getenv( "XRD_LOGMASK_DUMP" ); - if( logMask ) log->SetMask( Log::DumpMsg, translator.translateMask( logMask ) ); - - //-------------------------------------------------------------------------- - // Set up the topic strings - //-------------------------------------------------------------------------- - log->SetTopicName( AppMsg, "App" ); - log->SetTopicName( UtilityMsg, "Utility" ); - log->SetTopicName( FileMsg, "File" ); - log->SetTopicName( PollerMsg, "Poller" ); - log->SetTopicName( PostMasterMsg, "PostMaster" ); - log->SetTopicName( XRootDTransportMsg, "XRootDTransport" ); - log->SetTopicName( TaskMgrMsg, "TaskMgr" ); - log->SetTopicName( XRootDMsg, "XRootD" ); - log->SetTopicName( FileSystemMsg, "FileSystem" ); - log->SetTopicName( AsyncSockMsg, "AsyncSock" ); - log->SetTopicName( JobMgrMsg, "JobMgr" ); - log->SetTopicName( PlugInMgrMsg, "PlugInMgr" ); - } -} - - -//------------------------------------------------------------------------------ -// Static initialization and finalization -//------------------------------------------------------------------------------ -int EnvInitializer::counter = 0; - -//------------------------------------------------------------------------------ -// The constructor will be invoked in every translation unit -// that includes XrdClDefaultEnv.hh, but the DefaultEnv will -// be initialized only in the first one -//------------------------------------------------------------------------------ -EnvInitializer::EnvInitializer () -{ - if( counter++ == 0 ) XrdCl::DefaultEnv::Initialize(); -} - -//------------------------------------------------------------------------------ -// The destructor will be invoked in every translation unit -// that includes XrdClDefaultEnv.hh, but the DefaultEnv will -// be finalized only once in the last one -//------------------------------------------------------------------------------ -EnvInitializer::~EnvInitializer () -{ - if( --counter == 0 ) XrdCl::DefaultEnv::Finalize(); -} - diff --git a/src/XrdCl/XrdClDefaultEnv.hh b/src/XrdCl/XrdClDefaultEnv.hh deleted file mode 100644 index 11f8765850a..00000000000 --- a/src/XrdCl/XrdClDefaultEnv.hh +++ /dev/null @@ -1,183 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_DEFAULT_ENV_HH__ -#define __XRD_CL_DEFAULT_ENV_HH__ - -#include "XrdSys/XrdSysPthread.hh" -#include "XrdCl/XrdClEnv.hh" - -class XrdOucPinLoader; - -namespace XrdCl -{ - class PostMaster; - class Log; - class ForkHandler; - class Monitor; - class CheckSumManager; - class TransportManager; - class FileTimer; - class PlugInManager; - class PlugInFactory; - - //---------------------------------------------------------------------------- - //! Default environment for the client. Responsible for setting/importing - //! defaults for the variables used by the client. And holding other - //! global stuff. - //---------------------------------------------------------------------------- - class DefaultEnv: public Env - { - public: - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - DefaultEnv(); - - //------------------------------------------------------------------------ - //! Get default client environment - //------------------------------------------------------------------------ - static Env *GetEnv(); - - //------------------------------------------------------------------------ - //! Get default post master - //------------------------------------------------------------------------ - static PostMaster *GetPostMaster(); - - //------------------------------------------------------------------------ - //! Get default log - //------------------------------------------------------------------------ - static Log *GetLog(); - - //------------------------------------------------------------------------ - //! Set log level - //! - //! @param level Dump, Debug, Info, Warning or Error - //------------------------------------------------------------------------ - static void SetLogLevel( const std::string &level ); - - //------------------------------------------------------------------------ - //! Set log file - //! - //! @param filepath path to the log file - //------------------------------------------------------------------------ - static bool SetLogFile( const std::string &filepath ); - - //------------------------------------------------------------------------ - //! Set log mask. - //! Determines which diagnostics topics should be printed. It's a - //! "|" separated list of topics. The first element may be "All" in which - //! case all the topics are enabled and the subsequent elements may turn - //! them off, or "None" in which case all the topics are disabled and - //! the subsequent flags may turn them on. If the topic name is prefixed - //! with "^", then it means that the topic should be disabled. If the - //! topic name is not prefixed, then it means that the topic should be - //! enabled. - //! - //! The default for each level is "All", except for the "Dump" level, - //! where the default is "All|^PollerMsg". This means that, at the - //! "Dump" level, all the topics but "PollerMsg" are enabled. - //! - //! Available topics: AppMsg, UtilityMsg, FileMsg, PollerMsg, - //! PostMasterMsg, XRootDTransportMsg, TaskMgrMsg, XRootDMsg, - //! FileSystemMsg, AsyncSockMsg - //! - //! @param level log level or "All" for all levels - //! @param mask log mask - //------------------------------------------------------------------------ - static void SetLogMask( const std::string &level, - const std::string &mask ); - - //------------------------------------------------------------------------ - //! Get the fork handler - //------------------------------------------------------------------------ - static ForkHandler *GetForkHandler(); - - //------------------------------------------------------------------------ - //! Get file timer task - //------------------------------------------------------------------------ - static FileTimer *GetFileTimer(); - - //------------------------------------------------------------------------ - //! Get the monitor object - //------------------------------------------------------------------------ - static Monitor *GetMonitor(); - - //------------------------------------------------------------------------ - //! Get checksum manager - //------------------------------------------------------------------------ - static CheckSumManager *GetCheckSumManager(); - - //------------------------------------------------------------------------ - //! Get transport manager - //------------------------------------------------------------------------ - static TransportManager *GetTransportManager(); - - //------------------------------------------------------------------------ - //! Get plug-in manager - //------------------------------------------------------------------------ - static PlugInManager *GetPlugInManager(); - - //------------------------------------------------------------------------ - //! Retrieve the plug-in factory for the given URL - //! - //! @return you do not own the returned memory - //------------------------------------------------------------------------ - static PlugInFactory *GetPlugInFactory( const std::string url ); - - //------------------------------------------------------------------------ - //! Initialize the environment - //------------------------------------------------------------------------ - static void Initialize(); - - //------------------------------------------------------------------------ - //! Finalize the environment - //------------------------------------------------------------------------ - static void Finalize(); - - //------------------------------------------------------------------------ - //! Re-initialize the logging - //------------------------------------------------------------------------ - static void ReInitializeLogging(); - - private: - static void SetUpLog(); - - static XrdSysMutex sInitMutex; - static Env *sEnv; - static PostMaster *sPostMaster; - static Log *sLog; - static ForkHandler *sForkHandler; - static FileTimer *sFileTimer; - static Monitor *sMonitor; - static XrdOucPinLoader *sMonitorLibHandle; - static bool sMonitorInitialized; - static CheckSumManager *sCheckSumManager; - static TransportManager *sTransportManager; - static PlugInManager *sPlugInManager; - }; -} - -static struct EnvInitializer -{ - EnvInitializer(); - ~EnvInitializer(); - static int counter; -} initializer; - -#endif // __XRD_CL_DEFAULT_ENV_HH__ diff --git a/src/XrdCl/XrdClEnv.cc b/src/XrdCl/XrdClEnv.cc deleted file mode 100644 index 12c7ecf0127..00000000000 --- a/src/XrdCl/XrdClEnv.cc +++ /dev/null @@ -1,196 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#include - -#include "XrdCl/XrdClEnv.hh" -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdCl/XrdClLog.hh" -#include "XrdCl/XrdClConstants.hh" - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - // Get string - //---------------------------------------------------------------------------- - bool Env::GetString( const std::string &key, std::string &value ) - { - XrdSysRWLockHelper scopedLock( pLock ); - StringMap::iterator it; - it = pStringMap.find( key ); - if( it == pStringMap.end() ) - { - Log *log = DefaultEnv::GetLog(); - log->Debug( UtilityMsg, - "Env: trying to get a non-existent string entry: %s", - key.c_str() ); - return false; - } - value = it->second.first; - return true; - } - - //---------------------------------------------------------------------------- - // Put string - //---------------------------------------------------------------------------- - bool Env::PutString( const std::string &key, const std::string &value ) - { - XrdSysRWLockHelper scopedLock( pLock, false ); - - //-------------------------------------------------------------------------- - // Insert the string if it's not there yet - //-------------------------------------------------------------------------- - StringMap::iterator it; - it = pStringMap.find( key ); - if( it == pStringMap.end() ) - { - pStringMap[key] = std::make_pair( value, false ); - return true; - } - - //-------------------------------------------------------------------------- - // The entry exists and it has been imported from the shell - //-------------------------------------------------------------------------- - Log *log = DefaultEnv::GetLog(); - if( it->second.second ) - { - log->Debug( UtilityMsg, - "Env: trying to override a shell-imported string entry: %s", - key.c_str() ); - return false; - } - log->Debug( UtilityMsg, - "Env: overriding entry: %s=\"%s\" with \"%s\"", - key.c_str(), it->second.first.c_str(), value.c_str() ); - pStringMap[key] = std::make_pair( value, false ); - return true; - } - - //---------------------------------------------------------------------------- - // Get int - //---------------------------------------------------------------------------- - bool Env::GetInt( const std::string &key, int &value ) - { - XrdSysRWLockHelper scopedLock( pLock ); - IntMap::iterator it; - it = pIntMap.find( key ); - if( it == pIntMap.end() ) - { - Log *log = DefaultEnv::GetLog(); - log->Debug( UtilityMsg, - "Env: trying to get a non-existent integer entry: %s", - key.c_str() ); - return false; - } - value = it->second.first; - return true; - } - - //---------------------------------------------------------------------------- - // Put int - //---------------------------------------------------------------------------- - bool Env::PutInt( const std::string &key, int value ) - { - XrdSysRWLockHelper scopedLock( pLock, false ); - - //-------------------------------------------------------------------------- - // Insert the string if it's not there yet - //-------------------------------------------------------------------------- - IntMap::iterator it; - it = pIntMap.find( key ); - if( it == pIntMap.end() ) - { - pIntMap[key] = std::make_pair( value, false ); - return true; - } - - //-------------------------------------------------------------------------- - // The entry exists and it has been imported from the shell - //-------------------------------------------------------------------------- - Log *log = DefaultEnv::GetLog(); - if( it->second.second ) - { - log->Debug( UtilityMsg, - "Env: trying to override a shell-imported integer entry: %s", - key.c_str() ); - return false; - } - log->Debug( UtilityMsg, - "Env: overriding entry: %s=%d with %d", - key.c_str(), it->second.first, value ); - - pIntMap[key] = std::make_pair( value, false ); - return true; - } - - //---------------------------------------------------------------------------- - // Import int - //---------------------------------------------------------------------------- - bool Env::ImportInt( const std::string &key, const std::string &shellKey ) - { - XrdSysRWLockHelper scopedLock( pLock, false ); - std::string strValue = GetEnv( shellKey ); - if( strValue == "" ) - return false; - - Log *log = DefaultEnv::GetLog(); - char *endPtr; - int value = (int)strtol( strValue.c_str(), &endPtr, 0 ); - if( *endPtr ) - { - log->Error( UtilityMsg, - "Env: Unable to import %s as %s: %s is not a proper integer", - shellKey.c_str(), key.c_str(), strValue.c_str() ); - return false; - } - - log->Info( UtilityMsg, "Env: Importing from shell %s=%d as %s", - shellKey.c_str(), value, key.c_str() ); - - pIntMap[key] = std::make_pair( value, true ); - return true; - } - - //---------------------------------------------------------------------------- - // Import string - //---------------------------------------------------------------------------- - bool Env::ImportString( const std::string &key, const std::string &shellKey ) - { - XrdSysRWLockHelper scopedLock( pLock, false ); - std::string value = GetEnv( shellKey ); - if( value == "" ) - return false; - - Log *log = DefaultEnv::GetLog(); - log->Info( UtilityMsg, "Env: Importing from shell %s=%s as %s", - shellKey.c_str(), value.c_str(), key.c_str() ); - pStringMap[key] = std::make_pair( value, true ); - return true; - } - - //---------------------------------------------------------------------------- - // Get a string from the environment - //---------------------------------------------------------------------------- - std::string Env::GetEnv( const std::string &key ) - { - char *var = getenv( key.c_str() ); - if( !var ) - return ""; - return var; - } -} diff --git a/src/XrdCl/XrdClEnv.hh b/src/XrdCl/XrdClEnv.hh deleted file mode 100644 index c3d02ef208c..00000000000 --- a/src/XrdCl/XrdClEnv.hh +++ /dev/null @@ -1,128 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_ENV_HH__ -#define __XRD_CL_ENV_HH__ - -#include -#include -#include - -#include "XrdSys/XrdSysPthread.hh" - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - //! A simple key value store intended to hold global configuration. - //! It is able to import the settings from the shell environment, the - //! variables imported this way supersede these provided from the C++ - //! code. - //---------------------------------------------------------------------------- - class Env - { - public: - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - virtual ~Env() {} - - //------------------------------------------------------------------------ - //! Get a string associated to the given key - //! - //! @return true if the value was found, false otherwise - //------------------------------------------------------------------------ - bool GetString( const std::string &key, std::string &value ); - - //------------------------------------------------------------------------ - //! Associate a string with the given key - //! - //! @return false if there is already a shell-imported setting for this - //! key, true otherwise - //------------------------------------------------------------------------ - bool PutString( const std::string &key, const std::string &value ); - - //------------------------------------------------------------------------ - //! Get an int associated to the given key - //! - //! @return true if the value was found, false otherwise - //------------------------------------------------------------------------ - bool GetInt( const std::string &key, int &value ); - - //------------------------------------------------------------------------ - //! Associate an int with the given key - //! - //! @return false if there is already a shell-imported setting for this - //! key, true otherwise - //------------------------------------------------------------------------ - bool PutInt( const std::string &key, int value ); - - //------------------------------------------------------------------------ - //! Import an int from the shell environment. Any imported setting - //! takes precedence over the one set by other means. - //! - //! @return true if the setting exists in the shell, false otherwise - //------------------------------------------------------------------------ - bool ImportInt( const std::string &key, const std::string &shellKey ); - - //------------------------------------------------------------------------ - //! Import a string from the shell environment. Any imported setting - //! takes precedence over the one set by ther means. - //! - //! @return true if the setting exists in the shell, false otherwise - //------------------------------------------------------------------------ - bool ImportString( const std::string &key, const std::string &shellKey ); - - //------------------------------------------------------------------------ - // Lock the environment for writing - //------------------------------------------------------------------------ - void WriteLock() - { - pLock.WriteLock(); - } - - //------------------------------------------------------------------------ - // Unlock the environment - //------------------------------------------------------------------------ - void UnLock() - { - pLock.UnLock(); - } - - //------------------------------------------------------------------------ - // Re-initialize the lock - //------------------------------------------------------------------------ - void ReInitializeLock() - { - // this is really shaky, but seems to work on linux and fork safety - // is probably not required anywhere else - pLock.UnLock(); - pLock.ReInitialize(); - } - - private: - std::string GetEnv( const std::string &key ); - typedef std::map > StringMap; - typedef std::map > IntMap; - - XrdSysRWLock pLock; - StringMap pStringMap; - IntMap pIntMap; - }; -} - -#endif // __XRD_CL_ENV_HH__ diff --git a/src/XrdCl/XrdClFS.cc b/src/XrdCl/XrdClFS.cc deleted file mode 100644 index d48e3c93987..00000000000 --- a/src/XrdCl/XrdClFS.cc +++ /dev/null @@ -1,1879 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2014 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#include "XrdCl/XrdClFileSystem.hh" -#include "XrdCl/XrdClFileSystemUtils.hh" -#include "XrdCl/XrdClFSExecutor.hh" -#include "XrdCl/XrdClURL.hh" -#include "XrdCl/XrdClLog.hh" -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdCl/XrdClConstants.hh" -#include "XrdCl/XrdClUtils.hh" -#include "XrdCl/XrdClCopyProcess.hh" -#include "XrdCl/XrdClFile.hh" - -#include -#include -#include -#include - -#ifdef HAVE_READLINE -#include -#include -#endif - -using namespace XrdCl; - -//------------------------------------------------------------------------------ -// Build a path -//------------------------------------------------------------------------------ -XRootDStatus BuildPath( std::string &newPath, Env *env, - const std::string &path ) -{ - if( path.empty() ) - return XRootDStatus( stError, errInvalidArgs ); - - int noCwd = 0; - env->GetInt( "NoCWD", noCwd ); - - if( path[0] == '/' || noCwd ) - { - newPath = path; - return XRootDStatus(); - } - - std::string cwd = "/"; - env->GetString( "CWD", cwd ); - newPath = cwd; - newPath += "/"; - newPath += path; - - //---------------------------------------------------------------------------- - // Collapse the dots - //---------------------------------------------------------------------------- - std::list pathComponents; - std::list::iterator it; - XrdCl::Utils::splitString( pathComponents, newPath, "/" ); - newPath = "/"; - for( it = pathComponents.begin(); it != pathComponents.end(); ) - { - if( *it == "." ) - { - it = pathComponents.erase( it ); - continue; - } - - if( *it == ".." ) - { - if( it == pathComponents.begin() ) - return XRootDStatus( stError, errInvalidArgs ); - std::list::iterator it1 = it; - --it1; - it = pathComponents.erase( it1 ); - it = pathComponents.erase( it ); - continue; - } - ++it; - } - - newPath = "/"; - for( it = pathComponents.begin(); it != pathComponents.end(); ++it ) - { - newPath += *it; - newPath += "/"; - } - if( newPath.length() > 1 ) - newPath.erase( newPath.length()-1, 1 ); - - return XRootDStatus(); -} - -//------------------------------------------------------------------------------ -// Convert mode string to uint16_t -//------------------------------------------------------------------------------ -XRootDStatus ConvertMode( Access::Mode &mode, const std::string &modeStr ) -{ - if( modeStr.length() != 9 ) - return XRootDStatus( stError, errInvalidArgs ); - - mode = Access::None; - for( int i = 0; i < 3; ++i ) - { - if( modeStr[i] == 'r' ) - mode |= Access::UR; - else if( modeStr[i] == 'w' ) - mode |= Access::UW; - else if( modeStr[i] == 'x' ) - mode |= Access::UX; - else if( modeStr[i] != '-' ) - return XRootDStatus( stError, errInvalidArgs ); - } - for( int i = 3; i < 6; ++i ) - { - if( modeStr[i] == 'r' ) - mode |= Access::GR; - else if( modeStr[i] == 'w' ) - mode |= Access::GW; - else if( modeStr[i] == 'x' ) - mode |= Access::GX; - else if( modeStr[i] != '-' ) - return XRootDStatus( stError, errInvalidArgs ); - } - for( int i = 6; i < 9; ++i ) - { - if( modeStr[i] == 'r' ) - mode |= Access::OR; - else if( modeStr[i] == 'w' ) - mode |= Access::OW; - else if( modeStr[i] == 'x' ) - mode |= Access::OX; - else if( modeStr[i] != '-' ) - return XRootDStatus( stError, errInvalidArgs ); - } - return XRootDStatus(); -} - -//------------------------------------------------------------------------------ -// Change current working directory -//------------------------------------------------------------------------------ -XRootDStatus DoCD( FileSystem *fs, - Env *env, - const FSExecutor::CommandParams &args ) -{ - //---------------------------------------------------------------------------- - // Check up the args - //---------------------------------------------------------------------------- - Log *log = DefaultEnv::GetLog(); - if( args.size() != 2 ) - { - log->Error( AppMsg, "Invalid arguments. Expected a path." ); - return XRootDStatus( stError, errInvalidArgs ); - } - - //---------------------------------------------------------------------------- - // cd excludes NoCWD - //---------------------------------------------------------------------------- - env->PutInt( "NoCWD", 0 ); - - std::string newPath; - if( !BuildPath( newPath, env, args[1] ).IsOK() ) - { - log->Error( AppMsg, "Invalid path." ); - return XRootDStatus( stError, errInvalidArgs ); - } - - //---------------------------------------------------------------------------- - // Check if the path exist and is not a directory - //---------------------------------------------------------------------------- - StatInfo *info; - XRootDStatus st = fs->Stat( newPath, info ); - if( !st.IsOK() ) - { - log->Error( AppMsg, "Unable to stat the path: %s", st.ToStr().c_str() ); - return st; - } - - if( !info->TestFlags( StatInfo::IsDir ) ) - { - log->Error( AppMsg, "%s is not a directory.", newPath.c_str() ); - return XRootDStatus( stError, errInvalidArgs ); - } - - env->PutString( "CWD", newPath ); - delete info; - return XRootDStatus(); -} - -//------------------------------------------------------------------------------ -// List a directory -//------------------------------------------------------------------------------ -XRootDStatus DoLS( FileSystem *fs, - Env *env, - const FSExecutor::CommandParams &args ) -{ - //---------------------------------------------------------------------------- - // Check up the args - //---------------------------------------------------------------------------- - Log *log = DefaultEnv::GetLog(); - uint32_t argc = args.size(); - bool stats = false; - bool showUrls = false; - std::string path; - DirListFlags::Flags flags = DirListFlags::Locate | DirListFlags::Merge; - - if( argc > 6 ) - { - log->Error( AppMsg, "Too many arguments." ); - return XRootDStatus( stError, errInvalidArgs ); - } - - for( uint32_t i = 1; i < args.size(); ++i ) - { - if( args[i] == "-l" ) - { - stats = true; - flags |= DirListFlags::Stat; - } - else if( args[i] == "-u" ) - showUrls = true; - else if( args[i] == "-R" ) - { - flags |= DirListFlags::Recursive; - } - else if( args[i] == "-D" ) - { - // show duplicates - flags &= ~DirListFlags::Merge; - } - else - path = args[i]; - } - - if( showUrls ) - // we don't merge the duplicate entries - // in case we print the full URL - flags &= ~DirListFlags::Merge; - - std::string newPath = "/"; - if( path.empty() ) - env->GetString( "CWD", newPath ); - else - { - if( !BuildPath( newPath, env, path ).IsOK() ) - { - log->Error( AppMsg, "Invalid arguments. Invalid path." ); - return XRootDStatus( stError, errInvalidArgs ); - } - } - - log->Debug( AppMsg, "Attempting to list: %s", newPath.c_str() ); - - //---------------------------------------------------------------------------- - // Ask for the list - //---------------------------------------------------------------------------- - DirectoryList *list; - XRootDStatus st = fs->DirList( newPath, flags, list ); - if( !st.IsOK() ) - { - log->Error( AppMsg, "Unable to list the path: %s", st.ToStr().c_str() ); - return st; - } - - if( st.code == suPartial ) - { - std::cerr << "[!] Some of the requests failed. The result may be "; - std::cerr << "incomplete." << std::endl; - } - - //---------------------------------------------------------------------------- - // Print the results - //---------------------------------------------------------------------------- - DirectoryList::Iterator it; - for( it = list->Begin(); it != list->End(); ++it ) - { - if( stats ) - { - StatInfo *info = (*it)->GetStatInfo(); - if( !info ) - { - std::cout << "---- 0000-00-00 00:00:00 ? "; - } - else - { - if( info->TestFlags( StatInfo::IsDir ) ) - std::cout << "d"; - else - std::cout << "-"; - - if( info->TestFlags( StatInfo::IsReadable ) ) - std::cout << "r"; - else - std::cout << "-"; - - if( info->TestFlags( StatInfo::IsWritable ) ) - std::cout << "w"; - else - std::cout << "-"; - - if( info->TestFlags( StatInfo::XBitSet ) ) - std::cout << "x"; - else - std::cout << "-"; - - std::cout << " " << info->GetModTimeAsString(); - - std::cout << std::setw(12) << info->GetSize() << " "; - } - } - if( showUrls ) - std::cout << "root://" << (*it)->GetHostAddress() << "/"; - std::cout << list->GetParentName() << (*it)->GetName() << std::endl; - } - delete list; - return XRootDStatus(); -} - -//------------------------------------------------------------------------------ -// Create a directory -//------------------------------------------------------------------------------ -XRootDStatus DoMkDir( FileSystem *fs, - Env *env, - const FSExecutor::CommandParams &args ) -{ - //---------------------------------------------------------------------------- - // Check up the args - //---------------------------------------------------------------------------- - Log *log = DefaultEnv::GetLog(); - uint32_t argc = args.size(); - - if( argc < 2 || argc > 4 ) - { - log->Error( AppMsg, "Too few arguments." ); - return XRootDStatus( stError, errInvalidArgs ); - } - - MkDirFlags::Flags flags = MkDirFlags::None; - Access::Mode mode = Access::None; - std::string modeStr = "rwxr-x---"; - std::string path = ""; - - for( uint32_t i = 1; i < args.size(); ++i ) - { - if( args[i] == "-p" ) - flags |= MkDirFlags::MakePath; - else if( !args[i].compare( 0, 2, "-m" ) ) - modeStr = args[i].substr( 2, 9 ); - else - path = args[i]; - } - - XRootDStatus st = ConvertMode( mode, modeStr ); - if( !st.IsOK() ) - { - log->Error( AppMsg, "Invalid mode string." ); - return st; - } - - std::string newPath; - if( !BuildPath( newPath, env, path ).IsOK() ) - { - log->Error( AppMsg, "Invalid path." ); - return XRootDStatus( stError, errInvalidArgs ); - } - - //---------------------------------------------------------------------------- - // Run the query - //---------------------------------------------------------------------------- - st = fs->MkDir( newPath, flags, mode ); - if( !st.IsOK() ) - { - log->Error( AppMsg, "Unable create directory %s: %s", - newPath.c_str(), - st.ToStr().c_str() ); - return st; - } - - return XRootDStatus(); -} - -//------------------------------------------------------------------------------ -// Remove a directory -//------------------------------------------------------------------------------ -XRootDStatus DoRmDir( FileSystem *query, - Env *env, - const FSExecutor::CommandParams &args ) -{ - //---------------------------------------------------------------------------- - // Check up the args - //---------------------------------------------------------------------------- - Log *log = DefaultEnv::GetLog(); - uint32_t argc = args.size(); - - if( argc != 2 ) - { - log->Error( AppMsg, "Wrong number of arguments." ); - return XRootDStatus( stError, errInvalidArgs ); - } - - std::string fullPath; - if( !BuildPath( fullPath, env, args[1] ).IsOK() ) - { - log->Error( AppMsg, "Invalid path." ); - return XRootDStatus( stError, errInvalidArgs ); - } - - //---------------------------------------------------------------------------- - // Run the query - //---------------------------------------------------------------------------- - XRootDStatus st = query->RmDir( fullPath ); - if( !st.IsOK() ) - { - log->Error( AppMsg, "Unable remove directory %s: %s", - fullPath.c_str(), - st.ToStr().c_str() ); - return st; - } - - return XRootDStatus(); -} - -//------------------------------------------------------------------------------ -// Move a file or directory -//------------------------------------------------------------------------------ -XRootDStatus DoMv( FileSystem *fs, - Env *env, - const FSExecutor::CommandParams &args ) -{ - //---------------------------------------------------------------------------- - // Check up the args - //---------------------------------------------------------------------------- - Log *log = DefaultEnv::GetLog(); - uint32_t argc = args.size(); - - if( argc != 3 ) - { - log->Error( AppMsg, "Wrong number of arguments." ); - return XRootDStatus( stError, errInvalidArgs ); - } - - std::string fullPath1; - if( !BuildPath( fullPath1, env, args[1] ).IsOK() ) - { - log->Error( AppMsg, "Invalid source path." ); - return XRootDStatus( stError, errInvalidArgs ); - } - - std::string fullPath2; - if( !BuildPath( fullPath2, env, args[2] ).IsOK() ) - { - log->Error( AppMsg, "Invalid destination path." ); - return XRootDStatus( stError, errInvalidArgs ); - } - - //---------------------------------------------------------------------------- - // Run the query - //---------------------------------------------------------------------------- - XRootDStatus st = fs->Mv( fullPath1, fullPath2 ); - if( !st.IsOK() ) - { - log->Error( AppMsg, "Unable move %s to %s: %s", - fullPath1.c_str(), fullPath2.c_str(), - st.ToStr().c_str() ); - return st; - } - - return XRootDStatus(); -} - -//------------------------------------------------------------------------------ -// Remove a file -//------------------------------------------------------------------------------ -XRootDStatus DoRm( FileSystem *fs, - Env *env, - const FSExecutor::CommandParams &args ) -{ - //---------------------------------------------------------------------------- - // Check up the args - //---------------------------------------------------------------------------- - Log *log = DefaultEnv::GetLog(); - uint32_t argc = args.size(); - - if( argc != 2 ) - { - log->Error( AppMsg, "Wrong number of arguments." ); - return XRootDStatus( stError, errInvalidArgs ); - } - - std::string fullPath; - if( !BuildPath( fullPath, env, args[1] ).IsOK() ) - { - log->Error( AppMsg, "Invalid path." ); - return XRootDStatus( stError, errInvalidArgs ); - } - - //---------------------------------------------------------------------------- - // Run the query - //---------------------------------------------------------------------------- - XRootDStatus st = fs->Rm( fullPath ); - if( !st.IsOK() ) - { - log->Error( AppMsg, "Unable remove %s: %s", - fullPath.c_str(), - st.ToStr().c_str() ); - return st; - } - - return XRootDStatus(); -} - -//------------------------------------------------------------------------------ -// Truncate a file -//------------------------------------------------------------------------------ -XRootDStatus DoTruncate( FileSystem *fs, - Env *env, - const FSExecutor::CommandParams &args ) -{ - //---------------------------------------------------------------------------- - // Check up the args - //---------------------------------------------------------------------------- - Log *log = DefaultEnv::GetLog(); - uint32_t argc = args.size(); - - if( argc != 3 ) - { - log->Error( AppMsg, "Wrong number of arguments." ); - return XRootDStatus( stError, errInvalidArgs ); - } - - std::string fullPath; - if( !BuildPath( fullPath, env, args[1] ).IsOK() ) - { - log->Error( AppMsg, "Invalid path." ); - return XRootDStatus( stError, errInvalidArgs ); - } - - char *result; - uint64_t size = ::strtoll( args[2].c_str(), &result, 0 ); - if( *result != 0 ) - { - log->Error( AppMsg, "Size parameter needs to be an integer" ); - return XRootDStatus( stError, errInvalidArgs ); - } - - //---------------------------------------------------------------------------- - // Run the query - //---------------------------------------------------------------------------- - XRootDStatus st = fs->Truncate( fullPath, size ); - if( !st.IsOK() ) - { - log->Error( AppMsg, "Unable truncate %s: %s", - fullPath.c_str(), - st.ToStr().c_str() ); - return st; - } - - return XRootDStatus(); -} - -//------------------------------------------------------------------------------ -// Change the access rights to a file -//------------------------------------------------------------------------------ -XRootDStatus DoChMod( FileSystem *fs, - Env *env, - const FSExecutor::CommandParams &args ) -{ - //---------------------------------------------------------------------------- - // Check up the args - //---------------------------------------------------------------------------- - Log *log = DefaultEnv::GetLog(); - uint32_t argc = args.size(); - - if( argc != 3 ) - { - log->Error( AppMsg, "Wrong number of arguments." ); - return XRootDStatus( stError, errInvalidArgs ); - } - - std::string fullPath; - if( !BuildPath( fullPath, env, args[1] ).IsOK() ) - { - log->Error( AppMsg, "Invalid path." ); - return XRootDStatus( stError, errInvalidArgs ); - } - - Access::Mode mode = Access::None; - XRootDStatus st = ConvertMode( mode, args[2] ); - if( !st.IsOK() ) - { - log->Error( AppMsg, "Invalid mode string." ); - return st; - } - - //---------------------------------------------------------------------------- - // Run the query - //---------------------------------------------------------------------------- - st = fs->ChMod( fullPath, mode ); - if( !st.IsOK() ) - { - log->Error( AppMsg, "Unable change mode of %s: %s", - fullPath.c_str(), - st.ToStr().c_str() ); - return st; - } - - return XRootDStatus(); -} - -//------------------------------------------------------------------------------ -// Locate a path -//------------------------------------------------------------------------------ -XRootDStatus DoLocate( FileSystem *fs, - Env *env, - const FSExecutor::CommandParams &args ) -{ - //---------------------------------------------------------------------------- - // Check up the args - //---------------------------------------------------------------------------- - Log *log = DefaultEnv::GetLog(); - uint32_t argc = args.size(); - - if( argc > 4 ) - { - log->Error( AppMsg, "Wrong number of arguments." ); - return XRootDStatus( stError, errInvalidArgs ); - } - - OpenFlags::Flags flags = OpenFlags::None; - std::string path; - bool hasPath = false; - bool doDeepLocate = false; - for( uint32_t i = 1; i < argc; ++i ) - { - if( args[i] == "-n" ) - flags |= OpenFlags::NoWait; - else if( args[i] == "-r" ) - flags |= OpenFlags::Refresh; - else if( args[i] == "-m" || args[i] == "-h" ) - flags |= OpenFlags::PrefName; - else if( args[i] == "-i" ) - flags |= OpenFlags::Force; - else if( args[i] == "-d" ) - doDeepLocate = true; - else if( !hasPath ) - { - path = args[i]; - hasPath = true; - } - else - { - log->Error( AppMsg, "Invalid argument: %s.", args[i].c_str() ); - return XRootDStatus( stError, errInvalidArgs ); - } - } - - std::string fullPath; - if( path[0] == '*' ) - fullPath = path; - else - { - if( !BuildPath( fullPath, env, path ).IsOK() ) - { - log->Error( AppMsg, "Invalid path." ); - return XRootDStatus( stError, errInvalidArgs ); - } - } - - //---------------------------------------------------------------------------- - // Run the query - //---------------------------------------------------------------------------- - LocationInfo *info = 0; - XRootDStatus st; - if( doDeepLocate ) - st = fs->DeepLocate( fullPath, flags, info ); - else - st = fs->Locate( fullPath, flags, info ); - - if( !st.IsOK() ) - { - log->Error( AppMsg, "Unable locate %s: %s", - fullPath.c_str(), - st.ToStr().c_str() ); - return st; - } - - //---------------------------------------------------------------------------- - // Print the result - //---------------------------------------------------------------------------- - if( st.code == suPartial ) - { - std::cerr << "[!] Some of the requests failed. The result may be "; - std::cerr << "incomplete." << std::endl; - } - - LocationInfo::Iterator it; - for( it = info->Begin(); it != info->End(); ++it ) - { - std::cout << it->GetAddress() << " "; - switch( it->GetType() ) - { - case LocationInfo::ManagerOnline: - std::cout << "Manager "; - break; - case LocationInfo::ManagerPending: - std::cout << "ManagerPending "; - break; - case LocationInfo::ServerOnline: - std::cout << "Server "; - break; - case LocationInfo::ServerPending: - std::cout << "ServerPending "; - break; - default: - std::cout << "Unknown "; - }; - - switch( it->GetAccessType() ) - { - case LocationInfo::Read: - std::cout << "Read"; - break; - case LocationInfo::ReadWrite: - std::cout << "ReadWrite "; - break; - default: - std::cout << "Unknown "; - }; - std::cout << std::endl; - } - - delete info; - return XRootDStatus(); -} - -//------------------------------------------------------------------------------ -// Process stat query -//------------------------------------------------------------------------------ -XRootDStatus ProcessStatQuery( StatInfo *info, const std::string &query ) -{ - Log *log = DefaultEnv::GetLog(); - - //---------------------------------------------------------------------------- - // Process the query - //---------------------------------------------------------------------------- - bool isOrQuery = false; - bool status = true; - if( query.find( '|' ) != std::string::npos ) - { - isOrQuery = true; - status = false; - } - std::vector queryFlags; - if( isOrQuery ) - Utils::splitString( queryFlags, query, "|" ); - else - Utils::splitString( queryFlags, query, "&" ); - - //---------------------------------------------------------------------------- - // Initialize flag translation map and check the input flags - //---------------------------------------------------------------------------- - std::map flagMap; - flagMap["XBitSet"] = StatInfo::XBitSet; - flagMap["IsDir"] = StatInfo::IsDir; - flagMap["Other"] = StatInfo::Other; - flagMap["Offline"] = StatInfo::Offline; - flagMap["POSCPending"] = StatInfo::POSCPending; - flagMap["IsReadable"] = StatInfo::IsReadable; - flagMap["IsWritable"] = StatInfo::IsWritable; - flagMap["BackUpExists"] = StatInfo::BackUpExists; - - std::vector::iterator it; - for( it = queryFlags.begin(); it != queryFlags.end(); ++it ) - if( flagMap.find( *it ) == flagMap.end() ) - { - log->Error( AppMsg, "Flag '%s' is not recognized.", it->c_str() ); - return XRootDStatus( stError, errInvalidArgs ); - } - - //---------------------------------------------------------------------------- - // Process the query - //---------------------------------------------------------------------------- - if( isOrQuery ) - { - for( it = queryFlags.begin(); it != queryFlags.end(); ++it ) - if( info->TestFlags( flagMap[*it] ) ) - return XRootDStatus(); - } - else - { - for( it = queryFlags.begin(); it != queryFlags.end(); ++it ) - if( !info->TestFlags( flagMap[*it] ) ) - return XRootDStatus( stError, errResponseNegative ); - } - - if( status ) - return XRootDStatus(); - return XRootDStatus( stError, errResponseNegative ); -} - -//------------------------------------------------------------------------------ -// Stat a path -//------------------------------------------------------------------------------ -XRootDStatus DoStat( FileSystem *fs, - Env *env, - const FSExecutor::CommandParams &args ) -{ - //---------------------------------------------------------------------------- - // Check up the args - //---------------------------------------------------------------------------- - Log *log = DefaultEnv::GetLog(); - uint32_t argc = args.size(); - - if( argc != 2 && argc != 4 ) - { - log->Error( AppMsg, "Wrong number of arguments." ); - return XRootDStatus( stError, errInvalidArgs ); - } - - std::string path; - std::string query; - - for( uint32_t i = 1; i < args.size(); ++i ) - { - if( args[i] == "-q" ) - { - if( i < args.size()-1 ) - { - query = args[i+1]; - ++i; - } - else - { - log->Error( AppMsg, "Parameter '-q' requires an argument." ); - return XRootDStatus( stError, errInvalidArgs ); - } - } - else - path = args[i]; - } - - std::string fullPath; - if( !BuildPath( fullPath, env, path ).IsOK() ) - { - log->Error( AppMsg, "Invalid path." ); - return XRootDStatus( stError, errInvalidArgs ); - } - - //---------------------------------------------------------------------------- - // Run the query - //---------------------------------------------------------------------------- - StatInfo *info = 0; - XRootDStatus st = fs->Stat( fullPath, info ); - - if( !st.IsOK() ) - { - log->Error( AppMsg, "Unable stat %s: %s", - fullPath.c_str(), - st.ToStr().c_str() ); - return st; - } - - //---------------------------------------------------------------------------- - // Print the result - //---------------------------------------------------------------------------- - std::string flags; - - if( info->TestFlags( StatInfo::XBitSet ) ) - flags += "XBitSet|"; - if( info->TestFlags( StatInfo::IsDir ) ) - flags += "IsDir|"; - if( info->TestFlags( StatInfo::Other ) ) - flags += "Other|"; - if( info->TestFlags( StatInfo::Offline ) ) - flags += "Offline|"; - if( info->TestFlags( StatInfo::POSCPending ) ) - flags += "POSCPending|"; - if( info->TestFlags( StatInfo::IsReadable ) ) - flags += "IsReadable|"; - if( info->TestFlags( StatInfo::IsWritable ) ) - flags += "IsWritable|"; - if( info->TestFlags( StatInfo::BackUpExists ) ) - flags += "BackUpExists|"; - - if( !flags.empty() ) - flags.erase( flags.length()-1, 1 ); - - std::cout << "Path: " << fullPath << std::endl; - std::cout << "Id: " << info->GetId() << std::endl; - std::cout << "Size: " << info->GetSize() << std::endl; - std::cout << "MTime: " << info->GetModTimeAsString() << std::endl; - std::cout << "Flags: " << info->GetFlags() << " (" << flags << ")"; - std::cout << std::endl; - if( query.length() != 0 ) - { - st = ProcessStatQuery( info, query ); - std::cout << "Query: " << query << " " << std::endl; - } - - delete info; - return st; -} - -//------------------------------------------------------------------------------ -// Stat a VFS -//------------------------------------------------------------------------------ -XRootDStatus DoStatVFS( FileSystem *fs, - Env *env, - const FSExecutor::CommandParams &args ) -{ - //---------------------------------------------------------------------------- - // Check up the args - //---------------------------------------------------------------------------- - Log *log = DefaultEnv::GetLog(); - uint32_t argc = args.size(); - - if( argc != 2 ) - { - log->Error( AppMsg, "Wrong number of arguments." ); - return XRootDStatus( stError, errInvalidArgs ); - } - - std::string fullPath; - if( !BuildPath( fullPath, env, args[1] ).IsOK() ) - { - log->Error( AppMsg, "Invalid path." ); - return XRootDStatus( stError, errInvalidArgs ); - } - - //---------------------------------------------------------------------------- - // Run the query - //---------------------------------------------------------------------------- - StatInfoVFS *info = 0; - XRootDStatus st = fs->StatVFS( fullPath, info ); - - if( !st.IsOK() ) - { - log->Error( AppMsg, "Unable stat VFS at %s: %s", - fullPath.c_str(), - st.ToStr().c_str() ); - return st; - } - - //---------------------------------------------------------------------------- - // Print the result - //---------------------------------------------------------------------------- - std::cout << "Path: "; - std::cout << fullPath << std::endl; - std::cout << "Nodes with RW space: "; - std::cout << info->GetNodesRW() << std::endl; - std::cout << "Size of largest RW space (MB): "; - std::cout << info->GetFreeRW() << std::endl; - std::cout << "Utilization of RW space (%): "; - std::cout << (uint16_t)info->GetUtilizationRW() << std::endl; - std::cout << "Nodes with staging space: "; - std::cout << info->GetNodesStaging() << std::endl; - std::cout << "Size of largest staging space (MB): "; - std::cout << info->GetFreeStaging() << std::endl; - std::cout << "Utilization of staging space (%): "; - std::cout << (uint16_t)info->GetUtilizationStaging() << std::endl; - - delete info; - return XRootDStatus(); -} - -//------------------------------------------------------------------------------ -// Query the server -//------------------------------------------------------------------------------ -XRootDStatus DoQuery( FileSystem *fs, - Env *env, - const FSExecutor::CommandParams &args ) -{ - //---------------------------------------------------------------------------- - // Check up the args - //---------------------------------------------------------------------------- - Log *log = DefaultEnv::GetLog(); - uint32_t argc = args.size(); - - if( argc != 3 ) - { - log->Error( AppMsg, "Wrong number of arguments." ); - return XRootDStatus( stError, errInvalidArgs ); - } - - QueryCode::Code qCode; - if( args[1] == "config" ) - qCode = QueryCode::Config; - else if( args[1] == "checksumcancel" ) - qCode = QueryCode::ChecksumCancel; - else if( args[1] == "checksum" ) - qCode = QueryCode::Checksum; - else if( args[1] == "opaque" ) - qCode = QueryCode::Opaque; - else if( args[1] == "opaquefile" ) - qCode = QueryCode::OpaqueFile; - else if( args[1] == "prepare" ) - qCode = QueryCode::Prepare; - else if( args[1] == "space" ) - qCode = QueryCode::Space; - else if( args[1] == "stats" ) - qCode = QueryCode::Stats; - else if( args[1] == "xattr" ) - qCode = QueryCode::XAttr; - else - { - log->Error( AppMsg, "Invalid query code." ); - return XRootDStatus( stError, errInvalidArgs ); - } - - std::string strArg = args[2]; - if( qCode == QueryCode::ChecksumCancel || - qCode == QueryCode::Checksum || - qCode == QueryCode::XAttr ) - { - if( !BuildPath( strArg, env, args[2] ).IsOK() ) - { - log->Error( AppMsg, "Invalid path." ); - return XRootDStatus( stError, errInvalidArgs ); - } - } - - //---------------------------------------------------------------------------- - // Run the query - //---------------------------------------------------------------------------- - Buffer *response = 0; - Buffer arg( strArg.size() ); - arg.FromString( strArg ); - XRootDStatus st = fs->Query( qCode, arg, response ); - - if( !st.IsOK() ) - { - log->Error( AppMsg, "Unable run query %s %s: %s", - args[1].c_str(), strArg.c_str(), - st.ToStr().c_str() ); - return st; - } - - //---------------------------------------------------------------------------- - // Print the result - //---------------------------------------------------------------------------- - std::cout << response->ToString() << std::endl; - delete response; - return XRootDStatus(); -} - -//------------------------------------------------------------------------------ -// Query the server -//------------------------------------------------------------------------------ -XRootDStatus DoPrepare( FileSystem *fs, - Env *env, - const FSExecutor::CommandParams &args ) -{ - //---------------------------------------------------------------------------- - // Check up the args - //---------------------------------------------------------------------------- - Log *log = DefaultEnv::GetLog(); - uint32_t argc = args.size(); - - if( argc < 2 ) - { - log->Error( AppMsg, "Wrong number of arguments." ); - return XRootDStatus( stError, errInvalidArgs ); - } - - PrepareFlags::Flags flags = PrepareFlags::None; - std::vector files; - uint8_t priority = 0; - - for( uint32_t i = 1; i < args.size(); ++i ) - { - if( args[i] == "-p" ) - { - if( i < args.size()-1 ) - { - char *result; - int32_t param = ::strtol( args[i+1].c_str(), &result, 0 ); - if( *result != 0 || param > 3 || param < 0 ) - { - log->Error( AppMsg, "Size priotiry needs to be an integer between 0 " - "and 3" ); - return XRootDStatus( stError, errInvalidArgs ); - } - priority = (uint8_t)param; - ++i; - } - else - { - log->Error( AppMsg, "Parameter '-p' requires an argument." ); - return XRootDStatus( stError, errInvalidArgs ); - } - } - else if( args[i] == "-c" ) - flags |= PrepareFlags::Colocate; - else if( args[i] == "-f" ) - flags |= PrepareFlags::Fresh; - else if( args[i] == "-s" ) - flags |= PrepareFlags::Stage; - else if( args[i] == "-w" ) - flags |= PrepareFlags::WriteMode; - else - files.push_back( args[i] ); - } - - if( files.empty() ) - { - log->Error( AppMsg, "Filename missing." ); - return XRootDStatus( stError, errInvalidArgs ); - } - - //---------------------------------------------------------------------------- - // Run the command - //---------------------------------------------------------------------------- - Buffer *response = 0; - XRootDStatus st = fs->Prepare( files, flags, priority, response ); - if( !st.IsOK() ) - { - log->Error( AppMsg, "Prepare request failed: %s", st.ToStr().c_str() ); - return st; - } - delete response; - return XRootDStatus(); -} - -//------------------------------------------------------------------------------ -// Copy progress handler -//------------------------------------------------------------------------------ -class ProgressDisplay: public XrdCl::CopyProgressHandler -{ - public: - //-------------------------------------------------------------------------- - // Constructor - //-------------------------------------------------------------------------- - ProgressDisplay(): pBytesProcessed(0), pBytesTotal(0), pPrevious(0) - {} - - //-------------------------------------------------------------------------- - // End job - //-------------------------------------------------------------------------- - virtual void EndJob( uint16_t jobNum, const XrdCl::PropertyList *results ) - { - JobProgress( jobNum, pBytesProcessed, pBytesTotal ); - std::cerr << std::endl; - } - - //-------------------------------------------------------------------------- - // Job progress - //-------------------------------------------------------------------------- - virtual void JobProgress( uint16_t jobNum, - uint64_t bytesProcessed, - uint64_t bytesTotal ) - { - pBytesProcessed = bytesProcessed; - pBytesTotal = bytesTotal; - - time_t now = time(0); - if( (now - pPrevious < 1) && (bytesProcessed != bytesTotal) ) - return; - pPrevious = now; - - std::cerr << "\r"; - std::cerr << "Progress: "; - std::cerr << XrdCl::Utils::BytesToString(bytesProcessed) << "B "; - - if( bytesTotal ) - std::cerr << "(" << bytesProcessed*100/bytesTotal << "%)"; - - std::cerr << std::flush; - } - - private: - uint64_t pBytesProcessed; - uint64_t pBytesTotal; - time_t pPrevious; -}; - -//------------------------------------------------------------------------------ -// Cat a file -//------------------------------------------------------------------------------ -XRootDStatus DoCat( FileSystem *fs, - Env *env, - const FSExecutor::CommandParams &args ) -{ - //---------------------------------------------------------------------------- - // Check up the args - //---------------------------------------------------------------------------- - Log *log = DefaultEnv::GetLog(); - uint32_t argc = args.size(); - - if( argc != 2 && argc != 4 ) - { - log->Error( AppMsg, "Wrong number of arguments." ); - return XRootDStatus( stError, errInvalidArgs ); - } - - std::string server; - env->GetString( "ServerURL", server ); - if( server.empty() ) - { - log->Error( AppMsg, "Invalid address: \"%s\".", server.c_str() ); - return XRootDStatus( stError, errInvalidAddr ); - } - - std::string remote; - std::string local; - - for( uint32_t i = 1; i < args.size(); ++i ) - { - if( args[i] == "-o" ) - { - if( i < args.size()-1 ) - { - local = args[i+1]; - ++i; - } - else - { - log->Error( AppMsg, "Parameter '-o' requires an argument." ); - return XRootDStatus( stError, errInvalidArgs ); - } - } - else - remote = args[i]; - } - - std::string remoteFile; - if( !BuildPath( remoteFile, env, remote ).IsOK() ) - { - log->Error( AppMsg, "Invalid path." ); - return XRootDStatus( stError, errInvalidArgs ); - } - - URL remoteUrl( server ); - remoteUrl.SetPath( remoteFile ); - - //---------------------------------------------------------------------------- - // Fetch the data - //---------------------------------------------------------------------------- - CopyProgressHandler *handler = 0; ProgressDisplay d; - CopyProcess process; PropertyList props; PropertyList results; - - props.Set( "source", remoteUrl.GetURL() ); - if( !local.empty() ) - { - props.Set( "target", std::string( "file://" ) + local ); - handler = &d; - } - else - props.Set( "target", "stdio://-" ); - - props.Set( "dynamicSource", true ); - - XRootDStatus st = process.AddJob( props, &results ); - if( !st.IsOK() ) - { - log->Error( AppMsg, "Job adding failed: %s.", st.ToStr().c_str() ); - return st; - } - - st = process.Prepare(); - if( !st.IsOK() ) - { - log->Error( AppMsg, "Copy preparation failed: %s.", st.ToStr().c_str() ); - return st; - } - - st = process.Run(handler); - if( !st.IsOK() ) - { - log->Error( AppMsg, "Cope process failed: %s.", st.ToStr().c_str() ); - return st; - } - - return XRootDStatus(); -} - -//------------------------------------------------------------------------------ -// Tail a file -//------------------------------------------------------------------------------ -XRootDStatus DoTail( FileSystem *fs, - Env *env, - const FSExecutor::CommandParams &args ) -{ - //---------------------------------------------------------------------------- - // Check up the args - //---------------------------------------------------------------------------- - Log *log = DefaultEnv::GetLog(); - uint32_t argc = args.size(); - - if( argc < 2 || argc > 5 ) - { - log->Error( AppMsg, "Wrong number of arguments." ); - return XRootDStatus( stError, errInvalidArgs ); - } - - std::string server; - env->GetString( "ServerURL", server ); - if( server.empty() ) - { - log->Error( AppMsg, "Invalid address: \"%s\".", server.c_str() ); - return XRootDStatus( stError, errInvalidAddr ); - } - - std::string remote; - bool followMode = false; - uint32_t offset = 512; - - for( uint32_t i = 1; i < args.size(); ++i ) - { - if( args[i] == "-f" ) - followMode = true; - else if( args[i] == "-c" ) - { - if( i < args.size()-1 ) - { - char *result; - offset = ::strtol( args[i+1].c_str(), &result, 0 ); - if( *result != 0 ) - { - log->Error( AppMsg, "Offset from the end needs to be a number: %s", - args[i+1].c_str() ); - return XRootDStatus( stError, errInvalidArgs ); - } - ++i; - } - else - { - log->Error( AppMsg, "Parameter '-n' requires an argument." ); - return XRootDStatus( stError, errInvalidArgs ); - } - } - else - remote = args[i]; - } - - std::string remoteFile; - if( !BuildPath( remoteFile, env, remote ).IsOK() ) - { - log->Error( AppMsg, "Invalid path." ); - return XRootDStatus( stError, errInvalidArgs ); - } - - URL remoteUrl( server ); - remoteUrl.SetPath( remoteFile ); - - //---------------------------------------------------------------------------- - // Fetch the data - //---------------------------------------------------------------------------- - File file; - XRootDStatus st = file.Open( remoteUrl.GetURL(), OpenFlags::Read ); - if( !st.IsOK() ) - { - log->Error( AppMsg, "Unable to open file %s: %s", - remoteUrl.GetURL().c_str(), st.ToStr().c_str() ); - return st; - } - - StatInfo *info = 0; - uint64_t size = 0; - st = file.Stat( false, info ); - if (st.IsOK()) size = info->GetSize(); - - if( size < offset ) - offset = 0; - else - offset = size - offset; - - uint32_t chunkSize = 1*1024*1024; - char *buffer = new char[chunkSize]; - uint32_t bytesRead = 0; - while(1) - { - st = file.Read( offset, chunkSize, buffer, bytesRead ); - if( !st.IsOK() ) - { - log->Error( AppMsg, "Unable to read from %s: %s", - remoteUrl.GetURL().c_str(), st.ToStr().c_str() ); - delete [] buffer; - return st; - } - - offset += bytesRead; - int ret = write( 1, buffer, bytesRead ); - if( ret == -1 ) - { - log->Error( AppMsg, "Unable to write to stdout: %s", - strerror(errno) ); - delete [] buffer; - return st; - } - - if( bytesRead < chunkSize ) - { - if( !followMode ) - break; - sleep(1); - } - } - delete [] buffer; - - XRootDStatus stC = file.Close(); - - return XRootDStatus(); -} - -//------------------------------------------------------------------------------ -// Print statistics concerning given space -//------------------------------------------------------------------------------ -XRootDStatus DoSpaceInfo( FileSystem *fs, - Env *env, - const FSExecutor::CommandParams &args ) -{ - using namespace XrdCl; - - //---------------------------------------------------------------------------- - // Check up the args - //---------------------------------------------------------------------------- - Log *log = DefaultEnv::GetLog(); - uint32_t argc = args.size(); - - if( argc != 2 ) - { - log->Error( AppMsg, "Wrong number of arguments." ); - return XRootDStatus( stError, errInvalidArgs ); - } - - FileSystemUtils::SpaceInfo *i = 0; - - XRootDStatus st = FileSystemUtils::GetSpaceInfo( i, fs, args[1] ); - if( !st.IsOK() ) - return st; - - if( st.code == suPartial ) - { - std::cerr << "[!] Some of the requests failed. The result may be "; - std::cerr << "incomplete." << std::endl; - } - - std::cout << "Path: " << args[1] << std::endl; - std::cout << "Total: " << i->GetTotal() << std::endl; - std::cout << "Free: " << i->GetFree() << std::endl; - std::cout << "Used: " << i->GetUsed() << std::endl; - std::cout << "Largest free chunk: " << i->GetLargestFreeChunk() << std::endl; - - delete i; - return XRootDStatus(); -} - -//------------------------------------------------------------------------------ -// Print help -//------------------------------------------------------------------------------ -XRootDStatus PrintHelp( FileSystem *, Env *, - const FSExecutor::CommandParams & ) -{ - printf( "Usage:\n" ); - printf( " xrdfs [--no-cwd] host[:port] - interactive mode\n" ); - printf( " xrdfs host[:port] command args - batch mode\n\n" ); - - printf( "Available options:\n\n" ); - - printf( " --no-cwd no CWD is being preset\n\n" ); - - printf( "Available commands:\n\n" ); - - printf( " exit\n" ); - printf( " Exits from the program.\n\n" ); - - printf( " help\n" ); - printf( " This help screen.\n\n" ); - - printf( " cd \n" ); - printf( " Change the current working directory\n\n" ); - - printf( " chmod \n" ); - printf( " Modify permissions. Permission string example:\n" ); - printf( " rwxr-x--x\n\n" ); - - printf( " ls [-l] [-u] [dirname]\n" ); - printf( " Get directory listing.\n" ); - printf( " -l stat every entry and pring long listing\n" ); - printf( " -u print paths as URLs\n\n" ); - - printf( " locate [-n] [-r] [-d] \n" ); - printf( " Get the locations of the path.\n" ); - printf( " -r refresh, don't use cached locations\n" ); - printf( " -n make the server return the response immediately even\n" ); - printf( " though it may be incomplete\n" ); - printf( " -d do a recursive (deep) locate\n" ); - printf( " -m|-h prefer host names to IP addresses\n" ); - printf( " -i ignore network dependencies\n\n" ); - - printf( " mkdir [-p] [-m] \n" ); - printf( " Creates a directory/tree of directories.\n\n" ); - - printf( " mv \n" ); - printf( " Move path1 to path2 locally on the same server.\n\n" ); - - printf( " stat [-q query] \n" ); - printf( " Get info about the file or directory.\n" ); - printf( " -q query optional flag query parameter that makes\n" ); - printf( " xrdfs return error code to the shell if the\n" ); - printf( " requested flag combination is not present;\n" ); - printf( " flags may be combined together using '|' or '&'\n" ); - printf( " Available flags:\n" ); - printf( " XBitSet, IsDir, Other, Offline, POSCPending,\n" ); - printf( " IsReadable, IsWriteable\n\n" ); - - printf( " statvfs \n" ); - printf( " Get info about a virtual file system.\n\n" ); - - printf( " query \n" ); - printf( " Obtain server information. Query codes:\n\n" ); - - printf( " config Server configuration; is\n" ); - printf( " one of the following:\n" ); - printf( " bind_max - the maximum number of parallel streams\n" ); - printf( " chksum - the supported checksum\n" ); - printf( " pio_max - maximum number of parallel I/O requests\n" ); - printf( " readv_ior_max - maximum size of a readv element\n" ); - printf( " readv_iov_max - maximum number of readv entries\n" ); - printf( " tpc - support for third party copies\n" ); - printf( " wan_port - the port to use for wan copies\n" ); - printf( " wan_window - the wan_port window size\n" ); - printf( " window - the tcp window size\n" ); - printf( " cms - the status of the cmsd\n" ); - printf( " role - the role in a cluster\n" ); - printf( " sitename - the site name\n" ); - printf( " version - the version of the server\n" ); - printf( " checksumcancel File checksum cancellation\n" ); - printf( " checksum File checksum\n" ); - printf( " opaque Implementation dependent\n" ); - printf( " opaquefile Implementation dependent\n" ); - printf( " space Logical space stats\n" ); - printf( " stats Server stats; is a list\n" ); - printf( " of letters indicating information\n"); - printf( " to be returned:\n" ); - printf( " a - all statistics\n" ); - printf( " p - protocol statistics\n" ); - printf( " b - buffer usage statistics\n" ); - printf( " s - scheduling statistics\n" ); - printf( " d - device polling statistics\n" ); - printf( " u - usage statistics\n" ); - printf( " i - server identification\n" ); - printf( " z - synchronized statistics\n" ); - printf( " l - connection statistics\n" ); - printf( " xattr Extended attributes\n\n" ); - - printf( " rm \n" ); - printf( " Remove a file.\n\n" ); - - printf( " rmdir \n" ); - printf( " Remove a directory.\n\n" ); - - printf( " truncate \n" ); - printf( " Truncate a file.\n\n" ); - - printf( " prepare [-c] [-f] [-s] [-w] [-p priority] filenames\n" ); - printf( " Prepare one or more files for access.\n" ); - printf( " -c co-locate staged files if possible\n" ); - printf( " -f refresh file access time even if the location is known\n" ); - printf( " -s stage the files to disk if they are not online\n" ); - printf( " -w the files will be accessed for modification\n" ); - printf( " -p priority of the request, 0 (lowest) - 3 (highest)\n\n" ); - - printf( " cat [-o local file] file\n" ); - printf( " Print contents of a file to stdout.\n" ); - printf( " -o print to the specified local file\n\n" ); - - printf( " tail [-c bytes] [-f] file\n" ); - printf( " Output last part of files to stdout.\n" ); - printf( " -c num_bytes out last num_bytes\n" ); - printf( " -f output appended data as file grows\n\n" ); - - printf( " spaceinfo path\n" ); - printf( " Get space statistics for given path.\n\n" ); - - return XRootDStatus(); -} - -//------------------------------------------------------------------------------ -// Create the executor object -//------------------------------------------------------------------------------ -FSExecutor *CreateExecutor( const URL &url ) -{ - Env *env = new Env(); - env->PutString( "CWD", "/" ); - FSExecutor *executor = new FSExecutor( url, env ); - executor->AddCommand( "cd", DoCD ); - executor->AddCommand( "chmod", DoChMod ); - executor->AddCommand( "ls", DoLS ); - executor->AddCommand( "help", PrintHelp ); - executor->AddCommand( "stat", DoStat ); - executor->AddCommand( "statvfs", DoStatVFS ); - executor->AddCommand( "locate", DoLocate ); - executor->AddCommand( "mv", DoMv ); - executor->AddCommand( "mkdir", DoMkDir ); - executor->AddCommand( "rm", DoRm ); - executor->AddCommand( "rmdir", DoRmDir ); - executor->AddCommand( "query", DoQuery ); - executor->AddCommand( "truncate", DoTruncate ); - executor->AddCommand( "prepare", DoPrepare ); - executor->AddCommand( "cat", DoCat ); - executor->AddCommand( "tail", DoTail ); - executor->AddCommand( "spaceinfo", DoSpaceInfo ); - return executor; -} - -//------------------------------------------------------------------------------ -// Execute command -//------------------------------------------------------------------------------ -int ExecuteCommand( FSExecutor *ex, int argc, char **argv ) -{ - // std::vector args (argv, argv + argc); - std::vector args; - args.reserve(argc); - for (int i = 0; i < argc; ++i) - { - args.push_back(argv[i]); - } - XRootDStatus st = ex->Execute( args ); - if( !st.IsOK() ) - std::cerr << st.ToStr() << std::endl; - return st.GetShellCode(); -} - -//------------------------------------------------------------------------------ -// Define some functions required to function when build without readline -//------------------------------------------------------------------------------ -#ifndef HAVE_READLINE -char *readline(const char *prompt) -{ - std::cout << prompt << std::flush; - std::string input; - std::getline( std::cin, input ); - - if( !std::cin.good() ) - return 0; - - char *linebuf = (char *)malloc( input.size()+1 ); - strncpy( linebuf, input.c_str(), input.size()+1 ); - - return linebuf; -} - -void add_history( const char * ) -{ -} - -void rl_bind_key( char, uint16_t ) -{ -} - -uint16_t rl_insert = 0; - -int read_history( const char * ) -{ - return 0; -} - -int write_history( const char * ) -{ - return 0; -} -#endif - -//------------------------------------------------------------------------------ -// Build the command prompt -//------------------------------------------------------------------------------ -std::string BuildPrompt( Env *env, const URL &url ) -{ - std::ostringstream prompt; - std::string cwd = "/"; - env->GetString( "CWD", cwd ); - prompt << "[" << url.GetHostId() << "] " << cwd << " > "; - return prompt.str(); -} - -//------------------------------------------------------------------------ -//! parse command line -//! -//! @ result : command parameters -//! @ input : string containing the command line -//! @ return : true if the command has been completed, false otherwise -//------------------------------------------------------------------------ -bool getArguments (std::vector & result, const std::string &input) -{ - // the delimiter (space in the case of command line) - static const char delimiter = ' '; - // two types of quotes: single and double quotes - const char singleQuote = '\'', doubleQuote = '\"'; - // if the current character of the command has been - // quoted 'currentQuote' holds the type of quote, - // otherwise it holds the null character - char currentQuote = '\0'; - - std::string tmp; - for (std::string::const_iterator it = input.begin (); it != input.end (); ++it) - { - // if we encountered a quote character ... - if (*it == singleQuote || *it == doubleQuote) - { - // if we are not within quoted text ... - if (!currentQuote) - { - currentQuote = *it; // set the type of quote - continue; // and continue, the quote character itself is not a part of the parameter - } - // otherwise if it is the closing quote character ... - else if (currentQuote == *it) - { - currentQuote = '\0'; // reset the current quote type - continue; // and continue, the quote character itself is not a part of the parameter - } - } - // if we are within quoted text or the character is not a delimiter ... - if (currentQuote || *it != delimiter) - { - // concatenate it - tmp += *it; - } - else - { - // otherwise add a parameter and erase the tmp string - if (!tmp.empty ()) - { - result.push_back(tmp); - tmp.erase (); - } - } - } - // if the there are some remainders of the command add them - if (!tmp.empty()) - { - result.push_back(tmp); - } - // return true if the quotation has been closed - return currentQuote == '\0'; -} - -//------------------------------------------------------------------------------ -// Execute interactive -//------------------------------------------------------------------------------ -int ExecuteInteractive( const URL &url, bool noCwd = false ) -{ - //---------------------------------------------------------------------------- - // Set up the environment - //---------------------------------------------------------------------------- - std::string historyFile = getenv( "HOME" ); - historyFile += "/.xrdquery.history"; - rl_bind_key( '\t', rl_insert ); - read_history( historyFile.c_str() ); - FSExecutor *ex = CreateExecutor( url ); - - if( noCwd ) - ex->GetEnv()->PutInt( "NoCWD", 1 ); - - //---------------------------------------------------------------------------- - // Execute the commands - //---------------------------------------------------------------------------- - std::string cmdline; - while(1) - { - char *linebuf = 0; - // print new prompt only if the previous line was complete - // (a line is considered not to be complete if a quote has - // been opened but it has not been closed) - linebuf = readline( cmdline.empty() ? BuildPrompt( ex->GetEnv(), url ).c_str() : "> " ); - if( !linebuf || !strncmp( linebuf, "exit", 4 ) || !strncmp( linebuf, "quit", 4 ) ) - { - std::cout << "Goodbye." << std::endl << std::endl; - break; - } - if( !*linebuf) - { - free( linebuf ); - continue; - } - std::vector args; - cmdline += linebuf; - free( linebuf ); - if (getArguments( args, cmdline )) - { - XRootDStatus st = ex->Execute( args ); - add_history( cmdline.c_str() ); - cmdline.erase(); - if( !st.IsOK() ) - std::cerr << st.ToStr() << std::endl; - } - } - - //---------------------------------------------------------------------------- - // Cleanup - //---------------------------------------------------------------------------- - delete ex; - write_history( historyFile.c_str() ); - return 0; -} - -//------------------------------------------------------------------------------ -// Execute command -//------------------------------------------------------------------------------ -int ExecuteCommand( const URL &url, int argc, char **argv ) -{ - //---------------------------------------------------------------------------- - // Build the command to be executed - //---------------------------------------------------------------------------- - std::string commandline; - for( int i = 0; i < argc; ++i ) - { - commandline += argv[i]; - commandline += " "; - } - - FSExecutor *ex = CreateExecutor( url ); - ex->GetEnv()->PutInt( "NoCWD", 1 ); - int st = ExecuteCommand( ex, argc, argv ); - delete ex; - return st; -} - -//------------------------------------------------------------------------------ -// Start the show -//------------------------------------------------------------------------------ -int main( int argc, char **argv ) -{ - //---------------------------------------------------------------------------- - // Check the commandline parameters - //---------------------------------------------------------------------------- - XrdCl::FSExecutor::CommandParams params; - if( argc == 1 ) - { - PrintHelp( 0, 0, params ); - return 1; - } - - if( !strcmp( argv[1], "--help" ) || - !strcmp( argv[1], "-h" ) ) - { - PrintHelp( 0, 0, params ); - return 0; - } - - bool noCwd = false; - int urlIndex = 1; - if( !strcmp( argv[1], "--no-cwd") ) - { - ++urlIndex; - noCwd = true; - } - - URL url( argv[urlIndex] ); - if( !url.IsValid() ) - { - PrintHelp( 0, 0, params ); - return 1; - } - - if( argc == urlIndex + 1 ) - return ExecuteInteractive( url, noCwd ); - int shift = urlIndex + 1; - return ExecuteCommand( url, argc-shift, argv+shift ); -} diff --git a/src/XrdCl/XrdClFSExecutor.cc b/src/XrdCl/XrdClFSExecutor.cc deleted file mode 100644 index bc09532d74b..00000000000 --- a/src/XrdCl/XrdClFSExecutor.cc +++ /dev/null @@ -1,102 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#include "XrdCl/XrdClFSExecutor.hh" -#include "XrdCl/XrdClLog.hh" -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdCl/XrdClConstants.hh" - -#include - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - // Constructor - //---------------------------------------------------------------------------- - FSExecutor::FSExecutor( const URL &url, Env *env ): - pFS( 0 ) - { - pFS = new FileSystem( url ); - if( env ) - pEnv = env; - else - pEnv = new Env(); - - pEnv->PutString( "ServerURL", url.GetURL() ); - } - - //---------------------------------------------------------------------------- - // Destructor - //---------------------------------------------------------------------------- - FSExecutor::~FSExecutor() - { - delete pFS; - delete pEnv; - } - - //--------------------------------------------------------------------------- - // Add a command to the set of known commands - //--------------------------------------------------------------------------- - bool FSExecutor::AddCommand( const std::string &name, Command command ) - { - Log *log = DefaultEnv::GetLog(); - CommandMap::iterator it = pCommands.find( name ); - if( it != pCommands.end() ) - { - log->Error( AppMsg, "Unable to register command %s. Already exists.", - name.c_str() ); - return false; - } - pCommands.insert( std::make_pair( name, command ) ); - return true; - } - - XRootDStatus FSExecutor::Execute( const CommandParams & args) - { - std::stringstream cmdline; - std::ostream_iterator oit(cmdline, " "); - std::copy(args.begin(), args.end(), oit); - - Log *log = DefaultEnv::GetLog(); - log->Debug( AppMsg, "Executing: %s", cmdline.str().c_str() ); - - if( args.empty() ) - { - log->Dump( AppMsg, "Empty commandline." ); - return 1; - } - - CommandParams::const_iterator parIt; - int i = 0; - for( parIt = args.begin(); parIt != args.end(); ++parIt, ++i ) - log->Dump( AppMsg, " Param #%02d: '%s'", i, parIt->c_str() ); - - //-------------------------------------------------------------------------- - // Extract the command name - //-------------------------------------------------------------------------- - std::string commandName = args.front(); - CommandMap::iterator it = pCommands.find( commandName ); - if( it == pCommands.end() ) - { - log->Error( AppMsg, "Unknown command: %s", commandName.c_str() ); - return XRootDStatus( stError, errUnknownCommand ); - } - - return it->second( pFS, pEnv, args ); - } -} diff --git a/src/XrdCl/XrdClFSExecutor.hh b/src/XrdCl/XrdClFSExecutor.hh deleted file mode 100644 index e74a7ccd741..00000000000 --- a/src/XrdCl/XrdClFSExecutor.hh +++ /dev/null @@ -1,97 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_FS_EXECUTOR_HH__ -#define __XRD_CL_FS_EXECUTOR_HH__ - -#include "XrdCl/XrdClFileSystem.hh" -#include "XrdCl/XrdClEnv.hh" -#include "XrdCl/XrdClUtils.hh" -#include -#include -#include - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - //! Execute queries given as a commandline - //---------------------------------------------------------------------------- - class FSExecutor - { - public: - //------------------------------------------------------------------------ - //! Definition of command argument list - //------------------------------------------------------------------------ - typedef std::vector CommandParams; - - //------------------------------------------------------------------------ - //! Definition of a command - //------------------------------------------------------------------------ - typedef XRootDStatus (*Command)( FileSystem *fs, - Env *env, - const CommandParams &args ); - - //------------------------------------------------------------------------ - //! Constructor - //! - //! @param url the server that the executor should contact - //! @param env execution environment, the executor takes ownership over it - //------------------------------------------------------------------------ - FSExecutor( const URL &url, Env *env = 0 ); - - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - ~FSExecutor(); - - //------------------------------------------------------------------------ - //! Add a command to the set of known commands - //! - //! @param name name of the command - //! @param command function pointer - //! @return status - //------------------------------------------------------------------------ - bool AddCommand( const std::string &name, Command command ); - - //------------------------------------------------------------------------ - //! Execute the given commandline - //! - //! @param args : arguments for the commandline to be executed, - //! first of which is the command name - //! @return status of the execution - //------------------------------------------------------------------------ - XRootDStatus Execute( const CommandParams & args); - - //------------------------------------------------------------------------ - //! Get the environment - //------------------------------------------------------------------------ - Env *GetEnv() - { - return pEnv; - } - - private: - - typedef std::map CommandMap; - FileSystem *pFS; - Env *pEnv; - CommandMap pCommands; - }; -} - -#endif // __XRD_CL_FS_EXECUTOR_HH__ diff --git a/src/XrdCl/XrdClFile.cc b/src/XrdCl/XrdClFile.cc deleted file mode 100644 index ea5a81cba2e..00000000000 --- a/src/XrdCl/XrdClFile.cc +++ /dev/null @@ -1,484 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2014 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#include "XrdCl/XrdClLog.hh" -#include "XrdCl/XrdClUtils.hh" -#include "XrdCl/XrdClConstants.hh" -#include "XrdCl/XrdClFile.hh" -#include "XrdCl/XrdClFileStateHandler.hh" -#include "XrdCl/XrdClMessageUtils.hh" -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdCl/XrdClPlugInInterface.hh" -#include "XrdCl/XrdClPlugInManager.hh" -#include "XrdCl/XrdClDefaultEnv.hh" - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - // Constructor - //---------------------------------------------------------------------------- - File::File( bool enablePlugIns ): - pPlugIn(0), - pEnablePlugIns( enablePlugIns ) - { - pStateHandler = new FileStateHandler(); - } - - //---------------------------------------------------------------------------- - // Constructor - //---------------------------------------------------------------------------- - File::File( VirtRedirect virtRedirect, bool enablePlugIns ): - pPlugIn(0), - pEnablePlugIns( enablePlugIns ) - { - pStateHandler = new FileStateHandler( virtRedirect == EnableVirtRedirect ); - } - - //------------------------------------------------------------------------ - // Destructor - //------------------------------------------------------------------------ - File::~File() - { - //-------------------------------------------------------------------------- - // This, in principle, should never ever happen. Except for the case - // when we're interfaced with ROOT that may call this desctructor from - // its garbage collector, from its __cxa_finalize, ie. after the XrdCl lib - // has been finalized by the linker. So, if we don't have the log object - // at this point we just give up the hope. - //-------------------------------------------------------------------------- - if ( DefaultEnv::GetLog() && IsOpen() ) {XRootDStatus status = Close();} - delete pStateHandler; - delete pPlugIn; - } - - //---------------------------------------------------------------------------- - // Open the file pointed to by the given URL - async - //---------------------------------------------------------------------------- - XRootDStatus File::Open( const std::string &url, - OpenFlags::Flags flags, - Access::Mode mode, - ResponseHandler *handler, - uint16_t timeout ) - { - //-------------------------------------------------------------------------- - // Check if we need to install and run a plug-in for this URL - //-------------------------------------------------------------------------- - if( pEnablePlugIns && !pPlugIn ) - { - Log *log = DefaultEnv::GetLog(); - PlugInFactory *fact = DefaultEnv::GetPlugInManager()->GetFactory( url ); - if( fact ) - { - pPlugIn = fact->CreateFile( url ); - if( !pPlugIn ) - { - log->Error( FileMsg, "Plug-in factory failed to produce a plug-in " - "for %s, continuing without one", url.c_str() ); - } - } - } - - //-------------------------------------------------------------------------- - // Open the file - //-------------------------------------------------------------------------- - if( pPlugIn ) - return pPlugIn->Open( url, flags, mode, handler, timeout ); - - return pStateHandler->Open( url, flags, mode, handler, timeout ); - } - - //---------------------------------------------------------------------------- - // Open the file pointed to by the given URL - sync - //---------------------------------------------------------------------------- - XRootDStatus File::Open( const std::string &url, - OpenFlags::Flags flags, - Access::Mode mode, - uint16_t timeout ) - { - SyncResponseHandler handler; - Status st = Open( url, flags, mode, &handler, timeout ); - if( !st.IsOK() ) - return st; - - return MessageUtils::WaitForStatus( &handler ); - } - - //---------------------------------------------------------------------------- - // Close the file - async - //---------------------------------------------------------------------------- - XRootDStatus File::Close( ResponseHandler *handler, - uint16_t timeout ) - { - if( pPlugIn ) - return pPlugIn->Close( handler, timeout ); - - return pStateHandler->Close( handler, timeout ); - } - - - //---------------------------------------------------------------------------- - // Close the file - //---------------------------------------------------------------------------- - XRootDStatus File::Close( uint16_t timeout ) - { - SyncResponseHandler handler; - Status st = Close( &handler, timeout ); - if( !st.IsOK() ) - return st; - - return MessageUtils::WaitForStatus( &handler ); - } - - //---------------------------------------------------------------------------- - // Obtain status information for this file - async - //---------------------------------------------------------------------------- - XRootDStatus File::Stat( bool force, - ResponseHandler *handler, - uint16_t timeout ) - { - if( pPlugIn ) - return pPlugIn->Stat( force, handler, timeout ); - - return pStateHandler->Stat( force, handler, timeout ); - } - - //---------------------------------------------------------------------------- - // Obtain status information for this file - sync - //---------------------------------------------------------------------------- - XRootDStatus File::Stat( bool force, - StatInfo *&response, - uint16_t timeout ) - { - SyncResponseHandler handler; - Status st = Stat( force, &handler, timeout ); - if( !st.IsOK() ) - return st; - - return MessageUtils::WaitForResponse( &handler, response ); - } - - - //---------------------------------------------------------------------------- - // Read a data chunk at a given offset - sync - //---------------------------------------------------------------------------- - XRootDStatus File::Read( uint64_t offset, - uint32_t size, - void *buffer, - ResponseHandler *handler, - uint16_t timeout ) - { - if( pPlugIn ) - return pPlugIn->Read( offset, size, buffer, handler, timeout ); - - return pStateHandler->Read( offset, size, buffer, handler, timeout ); - } - - //---------------------------------------------------------------------------- - // Read a data chunk at a given offset - sync - //---------------------------------------------------------------------------- - XRootDStatus File::Read( uint64_t offset, - uint32_t size, - void *buffer, - uint32_t &bytesRead, - uint16_t timeout ) - { - SyncResponseHandler handler; - Status st = Read( offset, size, buffer, &handler, timeout ); - if( !st.IsOK() ) - return st; - - ChunkInfo *chunkInfo = 0; - XRootDStatus status = MessageUtils::WaitForResponse( &handler, chunkInfo ); - if( status.IsOK() ) - { - bytesRead = chunkInfo->length; - delete chunkInfo; - } - return status; - } - - //---------------------------------------------------------------------------- - // Write a data chunk at a given offset - async - //---------------------------------------------------------------------------- - XRootDStatus File::Write( uint64_t offset, - uint32_t size, - const void *buffer, - ResponseHandler *handler, - uint16_t timeout ) - { - if( pPlugIn ) - return pPlugIn->Write( offset, size, buffer, handler, timeout ); - - return pStateHandler->Write( offset, size, buffer, handler, timeout ); - } - - //---------------------------------------------------------------------------- - // Write a data chunk at a given offset - sync - //---------------------------------------------------------------------------- - XRootDStatus File::Write( uint64_t offset, - uint32_t size, - const void *buffer, - uint16_t timeout ) - { - SyncResponseHandler handler; - Status st = Write( offset, size, buffer, &handler, timeout ); - if( !st.IsOK() ) - return st; - - XRootDStatus status = MessageUtils::WaitForStatus( &handler ); - return status; - } - - //---------------------------------------------------------------------------- - // Commit all pending disk writes - async - //---------------------------------------------------------------------------- - XRootDStatus File::Sync( ResponseHandler *handler, - uint16_t timeout ) - { - if( pPlugIn ) - return pPlugIn->Sync( handler, timeout ); - - return pStateHandler->Sync( handler, timeout ); - } - - //---------------------------------------------------------------------------- - // Commit all pending disk writes - sync - //---------------------------------------------------------------------------- - XRootDStatus File::Sync( uint16_t timeout ) - { - SyncResponseHandler handler; - Status st = Sync( &handler, timeout ); - if( !st.IsOK() ) - return st; - - XRootDStatus status = MessageUtils::WaitForStatus( &handler ); - return status; - } - - //---------------------------------------------------------------------------- - // Truncate the file to a particular size - async - //---------------------------------------------------------------------------- - XRootDStatus File::Truncate( uint64_t size, - ResponseHandler *handler, - uint16_t timeout ) - { - if( pPlugIn ) - return pPlugIn->Truncate( size, handler, timeout ); - - return pStateHandler->Truncate( size, handler, timeout ); - } - - - //---------------------------------------------------------------------------- - // Truncate the file to a particular size - sync - //---------------------------------------------------------------------------- - XRootDStatus File::Truncate( uint64_t size, uint16_t timeout ) - { - SyncResponseHandler handler; - Status st = Truncate( size, &handler, timeout ); - if( !st.IsOK() ) - return st; - - XRootDStatus status = MessageUtils::WaitForStatus( &handler ); - return status; - } - - //---------------------------------------------------------------------------- - // Read scattered data chunks in one operation - async - //---------------------------------------------------------------------------- - XRootDStatus File::VectorRead( const ChunkList &chunks, - void *buffer, - ResponseHandler *handler, - uint16_t timeout ) - { - if( pPlugIn ) - return pPlugIn->VectorRead( chunks, buffer, handler, timeout ); - - return pStateHandler->VectorRead( chunks, buffer, handler, timeout ); - } - - //---------------------------------------------------------------------------- - // Read scattered data chunks in one operation - sync - //---------------------------------------------------------------------------- - XRootDStatus File::VectorRead( const ChunkList &chunks, - void *buffer, - VectorReadInfo *&vReadInfo, - uint16_t timeout ) - { - SyncResponseHandler handler; - Status st = VectorRead( chunks, buffer, &handler, timeout ); - if( !st.IsOK() ) - return st; - - return MessageUtils::WaitForResponse( &handler, vReadInfo ); - } - - //------------------------------------------------------------------------ - // Write scattered data chunks in one operation - async - //------------------------------------------------------------------------ - XRootDStatus File::VectorWrite( const ChunkList &chunks, - ResponseHandler *handler, - uint16_t timeout ) - { - if( pPlugIn ) - return XRootDStatus( stError, errNotSupported ); - - return pStateHandler->VectorWrite( chunks, handler, timeout ); - } - - //------------------------------------------------------------------------ - // Read scattered data chunks in one operation - sync - //------------------------------------------------------------------------ - XRootDStatus File::VectorWrite( const ChunkList &chunks, - uint16_t timeout ) - { - SyncResponseHandler handler; - Status st = VectorWrite( chunks, &handler, timeout ); - if( !st.IsOK() ) - return st; - - return MessageUtils::WaitForStatus( &handler ); - } - - //------------------------------------------------------------------------ - // Write scattered buffers in one operation - async - //------------------------------------------------------------------------ - XRootDStatus File::WriteV( uint64_t offset, - const struct iovec *iov, - int iovcnt, - ResponseHandler *handler, - uint16_t timeout ) - { - // TODO check pPlugIn - - return pStateHandler->WriteV( offset, iov, iovcnt, handler, timeout ); - } - - //------------------------------------------------------------------------ - // Write scattered buffers in one operation - sync - //------------------------------------------------------------------------ - XRootDStatus File::WriteV( uint64_t offset, - const struct iovec *iov, - int iovcnt, - uint16_t timeout ) - { - SyncResponseHandler handler; - Status st = WriteV( offset, iov, iovcnt, &handler, timeout ); - if( !st.IsOK() ) - return st; - - XRootDStatus status = MessageUtils::WaitForStatus( &handler ); - return status; - } - - - //---------------------------------------------------------------------------- - // Performs a custom operation on an open file, server implementation - // dependent - async - //---------------------------------------------------------------------------- - XRootDStatus File::Fcntl( const Buffer &arg, - ResponseHandler *handler, - uint16_t timeout ) - { - if( pPlugIn ) - return pPlugIn->Fcntl( arg, handler, timeout ); - - return pStateHandler->Fcntl( arg, handler, timeout ); - } - - //---------------------------------------------------------------------------- - // Performs a custom operation on an open file, server implementation - // dependent - sync - //---------------------------------------------------------------------------- - XRootDStatus File::Fcntl( const Buffer &arg, - Buffer *&response, - uint16_t timeout ) - { - SyncResponseHandler handler; - Status st = Fcntl( arg, &handler, timeout ); - if( !st.IsOK() ) - return st; - - return MessageUtils::WaitForResponse( &handler, response ); - } - - //------------------------------------------------------------------------ - //! Get access token to a file - async - //------------------------------------------------------------------------ - XRootDStatus File::Visa( ResponseHandler *handler, - uint16_t timeout ) - { - if( pPlugIn ) - return pPlugIn->Visa( handler, timeout ); - - return pStateHandler->Visa( handler, timeout ); - } - - //---------------------------------------------------------------------------- - // Get access token to a file - sync - //---------------------------------------------------------------------------- - XRootDStatus File::Visa( Buffer *&visa, - uint16_t timeout ) - { - SyncResponseHandler handler; - Status st = Visa( &handler, timeout ); - if( !st.IsOK() ) - return st; - - return MessageUtils::WaitForResponse( &handler, visa ); - } - - //---------------------------------------------------------------------------- - // Check if the file is open - //---------------------------------------------------------------------------- - bool File::IsOpen() const - { - if( pPlugIn ) - return pPlugIn->IsOpen(); - - return pStateHandler->IsOpen(); - } - - //---------------------------------------------------------------------------- - // Set file property - //---------------------------------------------------------------------------- - bool File::SetProperty( const std::string &name, const std::string &value ) - { - if( pPlugIn ) - return pPlugIn->SetProperty( name, value ); - - return pStateHandler->SetProperty( name, value ); - } - - //---------------------------------------------------------------------------- - // Get file property - //---------------------------------------------------------------------------- - bool File::GetProperty( const std::string &name, std::string &value ) const - { - if( pPlugIn ) - return pPlugIn->GetProperty( name, value ); - - return pStateHandler->GetProperty( name, value ); - } -} diff --git a/src/XrdCl/XrdClFile.hh b/src/XrdCl/XrdClFile.hh deleted file mode 100644 index a73b229d7c5..00000000000 --- a/src/XrdCl/XrdClFile.hh +++ /dev/null @@ -1,480 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2014 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_FILE_HH__ -#define __XRD_CL_FILE_HH__ - -#include "XrdCl/XrdClFileSystem.hh" -#include "XrdCl/XrdClXRootDResponses.hh" -#include "XrdOuc/XrdOucCompiler.hh" -#include -#include -#include -#include - -namespace XrdCl -{ - class FileStateHandler; - class FilePlugIn; - - //---------------------------------------------------------------------------- - //! A file - //---------------------------------------------------------------------------- - class File - { - public: - - enum VirtRedirect - { - EnableVirtRedirect, - DisableVirtRedirect - }; - - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - File( bool enablePlugIns = true ); - - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - File( VirtRedirect virtRedirect, bool enablePlugIns = true ); - - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - virtual ~File(); - - //------------------------------------------------------------------------ - //! Open the file pointed to by the given URL - async - //! - //! @param url url of the file to be opened - //! @param flags OpenFlags::Flags - //! @param mode Access::Mode for new files, 0 otherwise - //! @param handler handler to be notified about the status of the operation - //! @param timeout timeout value, if 0 the environment default will be - //! used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Open( const std::string &url, - OpenFlags::Flags flags, - Access::Mode mode, - ResponseHandler *handler, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Open the file pointed to by the given URL - sync - //! - //! @param url url of the file to be opened - //! @param flags OpenFlags::Flags - //! @param mode Access::Mode for new files, 0 otherwise - //! @param timeout timeout value, if 0 the environment default will be - //! used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Open( const std::string &url, - OpenFlags::Flags flags, - Access::Mode mode = Access::None, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Close the file - async - //! - //! @param handler handler to be notified about the status of the operation - //! @param timeout timeout value, if 0 the environment default will be - //! used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Close( ResponseHandler *handler, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Close the file - sync - //! - //! @param timeout timeout value, if 0 the environment default will be - //! used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Close( uint16_t timeout = 0 ) XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Obtain status information for this file - async - //! - //! @param force do not use the cached information, force re-stating - //! @param handler handler to be notified when the response arrives, - //! the response parameter will hold a StatInfo object - //! if the procedure is successful - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Stat( bool force, - ResponseHandler *handler, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Obtain status information for this file - sync - //! - //! @param force do not use the cached information, force re-stating - //! @param response the response (to be deleted by the user) - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Stat( bool force, - StatInfo *&response, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - - //------------------------------------------------------------------------ - //! Read a data chunk at a given offset - async - //! - //! @param offset offset from the beginning of the file - //! @param size number of bytes to be read - //! @param buffer a pointer to a buffer big enough to hold the data - //! or 0 if the buffer should be allocated by the system - //! @param handler handler to be notified when the response arrives, - //! the response parameter will hold a ChunkInfo object if - //! the procedure was successful - //! @param timeout timeout value, if 0 the environment default will be - //! used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Read( uint64_t offset, - uint32_t size, - void *buffer, - ResponseHandler *handler, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Read a data chunk at a given offset - sync - //! - //! @param offset offset from the beginning of the file - //! @param size number of bytes to be read - //! @param buffer a pointer to a buffer big enough to hold the data - //! @param bytesRead number of bytes actually read - //! @param timeout timeout value, if 0 the environment default will be - //! used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Read( uint64_t offset, - uint32_t size, - void *buffer, - uint32_t &bytesRead, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Write a data chunk at a given offset - async - //! The call interprets and returns the server response, which may be - //! either a success or a failure, it does not contain the number - //! of bytes that were actually written. - //! - //! @param offset offset from the beginning of the file - //! @param size number of bytes to be written - //! @param buffer a pointer to the buffer holding the data to be written - //! @param handler handler to be notified when the response arrives - //! @param timeout timeout value, if 0 the environment default will be - //! used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Write( uint64_t offset, - uint32_t size, - const void *buffer, - ResponseHandler *handler, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Write a data chunk at a given offset - sync - //! The call interprets and returns the server response, which may be - //! either a success or a failure, it does not contain the number - //! of bytes that were actually written. - //! - //! @param offset offset from the beginning of the file - //! @param size number of bytes to be written - //! @param buffer a pointer to the buffer holding the data to be - //! written - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Write( uint64_t offset, - uint32_t size, - const void *buffer, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Commit all pending disk writes - async - //! - //! @param handler handler to be notified when the response arrives - //! @param timeout timeout value, if 0 the environment default will be - //! used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Sync( ResponseHandler *handler, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - - //------------------------------------------------------------------------ - //! Commit all pending disk writes - sync - //! - //! @param timeout timeout value, if 0 the environment default will be - //! used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Sync( uint16_t timeout = 0 ) XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Truncate the file to a particular size - async - //! - //! @param size desired size of the file - //! @param handler handler to be notified when the response arrives - //! @param timeout timeout value, if 0 the environment default will be - //! used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Truncate( uint64_t size, - ResponseHandler *handler, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - - //------------------------------------------------------------------------ - //! Truncate the file to a particular size - sync - //! - //! @param size desired size of the file - //! @param timeout timeout value, if 0 the environment default will be - //! used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Truncate( uint64_t size, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Read scattered data chunks in one operation - async - //! - //! @param chunks list of the chunks to be read and buffers to put - //! the data in. The default maximum chunk size is - //! 2097136 bytes and the default maximum number - //! of chunks per request is 1024. The server - //! may be queried using FileSystem::Query for the - //! actual settings. - //! @param buffer if zero the buffer pointers in the chunk list - //! will be used, otherwise it needs to point to a - //! buffer big enough to hold the requested data - //! @param handler handler to be notified when the response arrives - //! @param timeout timeout value, if 0 then the environment default - //! will be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus VectorRead( const ChunkList &chunks, - void *buffer, - ResponseHandler *handler, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Read scattered data chunks in one operation - sync - //! - //! @param chunks list of the chunks to be read and buffers to put - //! the data in. The default maximum chunk size is - //! 2097136 bytes and the default maximum number - //! of chunks per request is 1024. The server - //! may be queried using FileSystem::Query for the - //! actual settings. - //! @param buffer if zero the buffer pointers in the chunk list - //! will be used, otherwise it needs to point to a - //! buffer big enough to hold the requested data - //! @param vReadInfo buffer size and chunk information - //! @param timeout timeout value, if 0 then the environment default - //! will be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus VectorRead( const ChunkList &chunks, - void *buffer, - VectorReadInfo *&vReadInfo, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Write scattered data chunks in one operation - async - //! - //! @param chunks list of the chunks to be written. - //! @param handler handler to be notified when the response arrives - //! @param timeout timeout value, if 0 then the environment default - //! will be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus VectorWrite( const ChunkList &chunks, - ResponseHandler *handler, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Write scattered data chunks in one operation - sync - //! - //! @param chunks list of the chunks to be written. - //! @param timeout timeout value, if 0 then the environment default - //! will be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus VectorWrite( const ChunkList &chunks, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Write scattered buffers in one operation - async - //! - //! @param offset offset from the beginning of the file - //! @param iov list of the buffers to be written - //! @param iovcnt number of buffers - //! @param handler handler to be notified when the response arrives - //! @param timeout timeout value, if 0 then the environment default - //! will be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus WriteV( uint64_t offset, - const struct iovec *iov, - int iovcnt, - ResponseHandler *handler, - uint16_t timeout = 0 ); - - //------------------------------------------------------------------------ - //! Write scattered buffers in one operation - sync - //! - //! @param offset offset from the beginning of the file - //! @param iov list of the buffers to be written - //! @param iovcnt number of buffers - //! @param handler handler to be notified when the response arrives - //! @param timeout timeout value, if 0 then the environment default - //! will be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus WriteV( uint64_t offset, - const struct iovec *iov, - int iovcnt, - uint16_t timeout = 0 ); - - //------------------------------------------------------------------------ - //! Performs a custom operation on an open file, server implementation - //! dependent - async - //! - //! @param arg query argument - //! @param handler handler to be notified when the response arrives, - //! the response parameter will hold a Buffer object - //! if the procedure is successful - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Fcntl( const Buffer &arg, - ResponseHandler *handler, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Performs a custom operation on an open file, server implementation - //! dependent - sync - //! - //! @param arg query argument - //! @param response the response (to be deleted by the user) - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Fcntl( const Buffer &arg, - Buffer *&response, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Get access token to a file - async - //! - //! @param handler handler to be notified when the response arrives, - //! the response parameter will hold a Buffer object - //! if the procedure is successful - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Visa( ResponseHandler *handler, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Get access token to a file - sync - //! - //! @param visa the access token (to be deleted by the user) - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Visa( Buffer *&visa, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Check if the file is open - //------------------------------------------------------------------------ - bool IsOpen() const; - - //------------------------------------------------------------------------ - //! Set file property - //! - //! File properties: - //! ReadRecovery [true/false] - enable/disable read recovery - //! WriteRecovery [true/false] - enable/disable write recovery - //! FollowRedirects [true/false] - enable/disable following redirections - //------------------------------------------------------------------------ - bool SetProperty( const std::string &name, const std::string &value ); - - //------------------------------------------------------------------------ - //! Get file property - //! - //! @see File::SetProperty for property list - //! - //! Read-only properties: - //! DataServer [string] - the data server the file is accessed at - //! LastURL [string] - final file URL with all the cgi information - //------------------------------------------------------------------------ - bool GetProperty( const std::string &name, std::string &value ) const; - - private: - FileStateHandler *pStateHandler; - FilePlugIn *pPlugIn; - bool pEnablePlugIns; - }; -} - -#endif // __XRD_CL_FILE_HH__ diff --git a/src/XrdCl/XrdClFileStateHandler.cc b/src/XrdCl/XrdClFileStateHandler.cc deleted file mode 100644 index 45ae1f2824b..00000000000 --- a/src/XrdCl/XrdClFileStateHandler.cc +++ /dev/null @@ -1,2052 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2014 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#include "XrdCl/XrdClFileStateHandler.hh" -#include "XrdCl/XrdClURL.hh" -#include "XrdCl/XrdClLog.hh" -#include "XrdCl/XrdClStatus.hh" -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdCl/XrdClForkHandler.hh" -#include "XrdCl/XrdClConstants.hh" -#include "XrdCl/XrdClMessageUtils.hh" -#include "XrdCl/XrdClXRootDTransport.hh" -#include "XrdCl/XrdClXRootDResponses.hh" -#include "XrdCl/XrdClMonitor.hh" -#include "XrdCl/XrdClFileTimer.hh" -#include "XrdCl/XrdClResponseJob.hh" -#include "XrdCl/XrdClJobManager.hh" -#include "XrdCl/XrdClUglyHacks.hh" -#include "XrdClRedirectorRegistry.hh" - -#include -#include -#include - -namespace -{ - //---------------------------------------------------------------------------- - // Object that does things to the FileStateHandler when kXR_open returns - // and then calls the user handler - //---------------------------------------------------------------------------- - class OpenHandler: public XrdCl::ResponseHandler - { - public: - //------------------------------------------------------------------------ - // Constructor - //------------------------------------------------------------------------ - OpenHandler( XrdCl::FileStateHandler *stateHandler, - XrdCl::ResponseHandler *userHandler ): - pStateHandler( stateHandler ), - pUserHandler( userHandler ) - { - } - - //------------------------------------------------------------------------ - // Handle the response - //------------------------------------------------------------------------ - virtual void HandleResponseWithHosts( XrdCl::XRootDStatus *status, - XrdCl::AnyObject *response, - XrdCl::HostList *hostList ) - { - using namespace XrdCl; - - //---------------------------------------------------------------------- - // Extract the statistics info - //---------------------------------------------------------------------- - OpenInfo *openInfo = 0; - if( status->IsOK() ) - response->Get( openInfo ); - - //---------------------------------------------------------------------- - // Notify the state handler and the client and say bye bye - //---------------------------------------------------------------------- - pStateHandler->OnOpen( status, openInfo, hostList ); - delete response; - if( pUserHandler ) - pUserHandler->HandleResponseWithHosts( status, 0, hostList ); - else - { - delete status; - delete hostList; - } - delete this; - } - - private: - XrdCl::FileStateHandler *pStateHandler; - XrdCl::ResponseHandler *pUserHandler; - }; - - //---------------------------------------------------------------------------- - // Object that does things to the FileStateHandler when kXR_close returns - // and then calls the user handler - //---------------------------------------------------------------------------- - class CloseHandler: public XrdCl::ResponseHandler - { - public: - //------------------------------------------------------------------------ - // Constructor - //------------------------------------------------------------------------ - CloseHandler( XrdCl::FileStateHandler *stateHandler, - XrdCl::ResponseHandler *userHandler, - XrdCl::Message *message ): - pStateHandler( stateHandler ), - pUserHandler( userHandler ), - pMessage( message ) - { - } - - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - virtual ~CloseHandler() - { - delete pMessage; - } - - //------------------------------------------------------------------------ - // Handle the response - //------------------------------------------------------------------------ - virtual void HandleResponseWithHosts( XrdCl::XRootDStatus *status, - XrdCl::AnyObject *response, - XrdCl::HostList *hostList ) - { - pStateHandler->OnClose( status ); - if( pUserHandler ) - pUserHandler->HandleResponseWithHosts( status, response, hostList ); - else - { - delete response; - delete status; - delete hostList; - } - - delete this; - } - - private: - XrdCl::FileStateHandler *pStateHandler; - XrdCl::ResponseHandler *pUserHandler; - XrdCl::Message *pMessage; - }; - - //---------------------------------------------------------------------------- - // Stateful message handler - //---------------------------------------------------------------------------- - class StatefulHandler: public XrdCl::ResponseHandler - { - public: - //------------------------------------------------------------------------ - // Constructor - //------------------------------------------------------------------------ - StatefulHandler( XrdCl::FileStateHandler *stateHandler, - XrdCl::ResponseHandler *userHandler, - XrdCl::Message *message, - const XrdCl::MessageSendParams &sendParams ): - pStateHandler( stateHandler ), - pUserHandler( userHandler ), - pMessage( message ), - pSendParams( sendParams ) - { - } - - //------------------------------------------------------------------------ - // Destructor - //------------------------------------------------------------------------ - virtual ~StatefulHandler() - { - delete pMessage; - delete pSendParams.chunkList; - } - - //------------------------------------------------------------------------ - // Handle the response - //------------------------------------------------------------------------ - virtual void HandleResponseWithHosts( XrdCl::XRootDStatus *status, - XrdCl::AnyObject *response, - XrdCl::HostList *hostList ) - { - using namespace XrdCl; - XRDCL_SMART_PTR_T responsePtr( response ); - pSendParams.hostList = hostList; - - //---------------------------------------------------------------------- - // Houston we have a problem... - //---------------------------------------------------------------------- - if( !status->IsOK() ) - { - pStateHandler->OnStateError( status, pMessage, this, pSendParams ); - return; - } - - //---------------------------------------------------------------------- - // We're clear - //---------------------------------------------------------------------- - responsePtr.release(); - pStateHandler->OnStateResponse( status, pMessage, response, hostList ); - pUserHandler->HandleResponseWithHosts( status, response, hostList ); - delete this; - } - - //------------------------------------------------------------------------ - //! Get the user handler - //------------------------------------------------------------------------ - XrdCl::ResponseHandler *GetUserHandler() - { - return pUserHandler; - } - - private: - XrdCl::FileStateHandler *pStateHandler; - XrdCl::ResponseHandler *pUserHandler; - XrdCl::Message *pMessage; - XrdCl::MessageSendParams pSendParams; - }; -} - -namespace XrdCl -{ - //------------------------------------------------------------------------ - //! Holds a reference to a ResponceHandler - //! and allows to safely delete it - //------------------------------------------------------------------------ - class ResponseHandlerHolder : public ResponseHandler - { - public: - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - ResponseHandlerHolder( ResponseHandler * handler ) : pHandler( handler ), pReferenceCounter( 1 ) {} - //------------------------------------------------------------------------ - //! Destructor is private - use 'Destroy' in order to delete the object - //! Always destroys the actual ResponseHandler and deletes itself only - //! if this is the last reference - //------------------------------------------------------------------------ - void Destroy() - { - XrdSysMutexHelper scopedLock( pMutex ); - // delete the actual handler - if( pHandler ) - { - delete pHandler; - pHandler = 0; - } - // and than destroy myself if this is the last reference - DestroyMyself( scopedLock ); - } - //------------------------------------------------------------------------ - //! Increment reference counter - //------------------------------------------------------------------------ - ResponseHandlerHolder* Self() - { - XrdSysMutexHelper scopedLock( pMutex ); - ++pReferenceCounter; - return this; - } - //------------------------------------------------------------------------ - // Handle the response - //------------------------------------------------------------------------ - virtual void HandleResponseWithHosts( XrdCl::XRootDStatus *status, - XrdCl::AnyObject *response, - XrdCl::HostList *hostList ) - { - XrdSysMutexHelper scopedLock( pMutex ); - // delegate the job to the actual handler - if( pHandler ) - { - pHandler->HandleResponseWithHosts( status, response, hostList ); - // after handling a response the handler destroys itself, - // so we need to nullify the pointer - pHandler = 0; - } - else - { - delete status; - delete response; - delete hostList; - } - // destroy the object if it is - DestroyMyself( scopedLock ); - } - - private: - //------------------------------------------------------------------------ - //! Deletes itself only if this is the last reference - //------------------------------------------------------------------------ - void DestroyMyself( XrdSysMutexHelper &lck ) - { - // decrement the reference counter - --pReferenceCounter; - // if the object is not used anymore delete it - if( pReferenceCounter == 0) - { - lck.UnLock(); - delete this; - } - } - //------------------------------------------------------------------------ - //! Private Destructor (use 'Destroy' method) - //------------------------------------------------------------------------ - ~ResponseHandlerHolder() {} - //------------------------------------------------------------------------ - // The actual handler - //------------------------------------------------------------------------ - ResponseHandler* pHandler; - //------------------------------------------------------------------------ - // Reference counter - //------------------------------------------------------------------------ - size_t pReferenceCounter; - //------------------------------------------------------------------------ - // and respective mutex - //------------------------------------------------------------------------ - mutable XrdSysRecMutex pMutex; - }; - - //---------------------------------------------------------------------------- - // Constructor - //---------------------------------------------------------------------------- - FileStateHandler::FileStateHandler(): - pFileState( Closed ), - pStatInfo( 0 ), - pFileUrl( 0 ), - pDataServer( 0 ), - pLoadBalancer( 0 ), - pStateRedirect( 0 ), - pFileHandle( 0 ), - pOpenMode( 0 ), - pOpenFlags( 0 ), - pSessionId( 0 ), - pDoRecoverRead( true ), - pDoRecoverWrite( true ), - pFollowRedirects( true ), - pUseVirtRedirector( true ), - pReOpenHandler( 0 ) - { - pFileHandle = new uint8_t[4]; - ResetMonitoringVars(); - DefaultEnv::GetForkHandler()->RegisterFileObject( this ); - DefaultEnv::GetFileTimer()->RegisterFileObject( this ); - pLFileHandler = new LocalFileHandler(); - } - - //------------------------------------------------------------------------ - //! Constructor - //! - //! @param useVirtRedirector if true Metalink files will be treated - //! as a VirtualRedirectors - //------------------------------------------------------------------------ - FileStateHandler::FileStateHandler( bool useVirtRedirector ): - pFileState( Closed ), - pStatInfo( 0 ), - pFileUrl( 0 ), - pDataServer( 0 ), - pLoadBalancer( 0 ), - pStateRedirect( 0 ), - pFileHandle( 0 ), - pOpenMode( 0 ), - pOpenFlags( 0 ), - pSessionId( 0 ), - pDoRecoverRead( true ), - pDoRecoverWrite( true ), - pFollowRedirects( true ), - pUseVirtRedirector( useVirtRedirector ), - pReOpenHandler( 0 ) - { - pFileHandle = new uint8_t[4]; - ResetMonitoringVars(); - DefaultEnv::GetForkHandler()->RegisterFileObject( this ); - DefaultEnv::GetFileTimer()->RegisterFileObject( this ); - pLFileHandler = new LocalFileHandler(); - } - - //---------------------------------------------------------------------------- - // Destructor - //---------------------------------------------------------------------------- - FileStateHandler::~FileStateHandler() - { - if( pReOpenHandler ) - pReOpenHandler->Destroy(); - - if( DefaultEnv::GetFileTimer() ) - DefaultEnv::GetFileTimer()->UnRegisterFileObject( this ); - - if( DefaultEnv::GetForkHandler() ) - DefaultEnv::GetForkHandler()->UnRegisterFileObject( this ); - - if( pFileState != Closed && DefaultEnv::GetLog() ) - { - XRootDStatus st; - MonitorClose( &st ); - ResetMonitoringVars(); - } - - // check if the logger is still there, this is only for root, as root might - // have unload us already so in this case we don't want to do anything - if( DefaultEnv::GetLog() && pUseVirtRedirector && pFileUrl && pFileUrl->IsMetalink() ) - { - RedirectorRegistry& registry = RedirectorRegistry::Instance(); - registry.Release( *pFileUrl ); - } - - delete pStatInfo; - delete pFileUrl; - delete pDataServer; - delete pLoadBalancer; - delete [] pFileHandle; - delete pLFileHandler; - } - - //---------------------------------------------------------------------------- - // Open the file pointed to by the given URL - //---------------------------------------------------------------------------- - XRootDStatus FileStateHandler::Open( const std::string &url, - uint16_t flags, - uint16_t mode, - ResponseHandler *handler, - uint16_t timeout ) - { - XrdSysMutexHelper scopedLock( pMutex ); - - //-------------------------------------------------------------------------- - // Check if we can proceed - //-------------------------------------------------------------------------- - if( pFileState == Error ) - return pStatus; - - if( pFileState == OpenInProgress ) - return XRootDStatus( stError, errInProgress ); - - if( pFileState == CloseInProgress || pFileState == Opened || - pFileState == Recovering ) - return XRootDStatus( stError, errInvalidOp ); - - pFileState = OpenInProgress; - - //-------------------------------------------------------------------------- - // Check if the parameters are valid - //-------------------------------------------------------------------------- - Log *log = DefaultEnv::GetLog(); - - if (pFileUrl) - { - if( pUseVirtRedirector && pFileUrl->IsMetalink() ) - { - RedirectorRegistry& registry = RedirectorRegistry::Instance(); - registry.Release( *pFileUrl ); - } - delete pFileUrl; - pFileUrl = 0; - } - - pFileUrl = new URL( url ); - if( !pFileUrl->IsValid() ) - { - log->Error( FileMsg, "[0x%x@%s] Trying to open invalid url: %s", - this, pFileUrl->GetPath().c_str(), url.c_str() ); - pStatus = XRootDStatus( stError, errInvalidArgs ); - pFileState = Error; - return pStatus; - } - - //-------------------------------------------------------------------------- - // Check if the recovery procedures should be enabled - //-------------------------------------------------------------------------- - const URL::ParamsMap &urlParams = pFileUrl->GetParams(); - URL::ParamsMap::const_iterator it; - it = urlParams.find( "xrdcl.recover-reads" ); - if( (it != urlParams.end() && it->second == "false") || - !pDoRecoverRead ) - { - pDoRecoverRead = false; - log->Debug( FileMsg, "[0x%x@%s] Read recovery procedures are disabled", - this, pFileUrl->GetURL().c_str() ); - } - - it = urlParams.find( "xrdcl.recover-writes" ); - if( (it != urlParams.end() && it->second == "false") || - !pDoRecoverWrite ) - { - pDoRecoverWrite = false; - log->Debug( FileMsg, "[0x%x@%s] Write recovery procedures are disabled", - this, pFileUrl->GetURL().c_str() ); - } - - //-------------------------------------------------------------------------- - // Open the file - //-------------------------------------------------------------------------- - log->Debug( FileMsg, "[0x%x@%s] Sending an open command", this, - pFileUrl->GetURL().c_str() ); - - pOpenMode = mode; - pOpenFlags = flags; - OpenHandler *openHandler = new OpenHandler( this, handler ); - - bool redirect = pUseVirtRedirector && pFileUrl->IsMetalink(); - - //-------------------------------------------------------------------------- - // If we don't want to redirect and it's a local file simply delgate - // to the LocalFileHandler - //-------------------------------------------------------------------------- - if( pFileUrl->IsLocalFile() && !redirect ) - { - Status st = pLFileHandler->Open( pFileUrl->GetURL().c_str(), flags, - mode, openHandler, timeout ); - if( !st.IsOK() ) - { - delete openHandler; - pStatus = st; - pFileState = Error; - } - - return st; - } - - Message *msg; - ClientOpenRequest *req; - std::string path = pFileUrl->GetPathWithFilteredParams(); - MessageUtils::CreateRequest( msg, req, path.length() ); - - req->requestid = kXR_open; - req->mode = mode; - req->options = flags | kXR_async | kXR_retstat; - req->dlen = path.length(); - msg->Append( path.c_str(), path.length(), 24 ); - - XRootDTransport::SetDescription( msg ); - MessageSendParams params; params.timeout = timeout; - params.followRedirects = pFollowRedirects; - MessageUtils::ProcessSendParams( params ); - - Status st; - if( redirect ) - st = MessageUtils::RedirectMessage( *pFileUrl, msg, openHandler, - params, pLFileHandler ); - else - st = MessageUtils::SendMessage( *pFileUrl, msg, openHandler, - params, pLFileHandler ); - - if( !st.IsOK() ) - { - delete openHandler; - pStatus = st; - pFileState = Error; - return st; - } - return st; - } - - //---------------------------------------------------------------------------- - // Close the file object - //---------------------------------------------------------------------------- - XRootDStatus FileStateHandler::Close( ResponseHandler *handler, - uint16_t timeout ) - { - XrdSysMutexHelper scopedLock( pMutex ); - - //-------------------------------------------------------------------------- - // Check if we can proceed - //-------------------------------------------------------------------------- - if( pFileState == Error ) - return pStatus; - - if( pFileState == CloseInProgress ) - return XRootDStatus( stError, errInProgress ); - - if( pFileState == OpenInProgress || pFileState == Closed || - pFileState == Recovering || !pInTheFly.empty() ) - return XRootDStatus( stError, errInvalidOp ); - - pFileState = CloseInProgress; - - Log *log = DefaultEnv::GetLog(); - log->Debug( FileMsg, "[0x%x@%s] Sending a close command for handle 0x%x to " - "%s", this, pFileUrl->GetURL().c_str(), - *((uint32_t*)pFileHandle), pDataServer->GetHostId().c_str() ); - - //-------------------------------------------------------------------------- - // Close the file - //-------------------------------------------------------------------------- - Message *msg; - ClientCloseRequest *req; - MessageUtils::CreateRequest( msg, req ); - - req->requestid = kXR_close; - memcpy( req->fhandle, pFileHandle, 4 ); - - XRootDTransport::SetDescription( msg ); - msg->SetSessionId( pSessionId ); - CloseHandler *closeHandler = new CloseHandler( this, handler, msg ); - MessageSendParams params; - params.timeout = timeout; - params.followRedirects = false; - params.stateful = true; - MessageUtils::ProcessSendParams( params ); - - Status st; - if( pDataServer->IsLocalFile() ) - st = pLFileHandler->Close( closeHandler, timeout ); - else - st = MessageUtils::SendMessage( *pDataServer, msg, closeHandler, - params, pLFileHandler ); - - if( !st.IsOK() ) - { - delete closeHandler; - if( st.code == errInvalidSession && IsReadOnly() ) - { - pFileState = Closed; - return st; - } - - pStatus = st; - pFileState = Error; - return st; - } - return st; - } - - //---------------------------------------------------------------------------- - // Stat the file - //---------------------------------------------------------------------------- - XRootDStatus FileStateHandler::Stat( bool force, - ResponseHandler *handler, - uint16_t timeout ) - { - XrdSysMutexHelper scopedLock( pMutex ); - - if( pFileState != Opened && pFileState != Recovering ) - return XRootDStatus( stError, errInvalidOp ); - - //-------------------------------------------------------------------------- - // Return the cached info - //-------------------------------------------------------------------------- - if( !force ) - { - AnyObject *obj = new AnyObject(); - obj->Set( new StatInfo( *pStatInfo ) ); - handler->HandleResponseWithHosts( new XRootDStatus(), obj, - new HostList() ); - return XRootDStatus(); - } - - Log *log = DefaultEnv::GetLog(); - log->Debug( FileMsg, "[0x%x@%s] Sending a stat command for handle 0x%x to " - "%s", this, pFileUrl->GetURL().c_str(), - *((uint32_t*)pFileHandle), pDataServer->GetHostId().c_str() ); - - //-------------------------------------------------------------------------- - // Issue a new stat request - // stating a file handle doesn't work (fixed in 3.2.0) so we need to - // stat the pat - //-------------------------------------------------------------------------- - Message *msg; - ClientStatRequest *req; - std::string path = pFileUrl->GetPath(); - MessageUtils::CreateRequest( msg, req ); - - req->requestid = kXR_stat; - memcpy( req->fhandle, pFileHandle, 4 ); - - MessageSendParams params; - params.timeout = timeout; - params.followRedirects = false; - params.stateful = true; - MessageUtils::ProcessSendParams( params ); - - XRootDTransport::SetDescription( msg ); - StatefulHandler *stHandler = new StatefulHandler( this, handler, msg, params ); - - if( pDataServer->IsLocalFile() ) - { - XRootDStatus st = pLFileHandler->Stat( stHandler, timeout ); - return ExamineLocalResult( st, msg, stHandler ); - } - - return SendOrQueue( *pDataServer, msg, stHandler, params ); - } - - //---------------------------------------------------------------------------- - // Read a data chunk at a given offset - sync - //---------------------------------------------------------------------------- - XRootDStatus FileStateHandler::Read( uint64_t offset, - uint32_t size, - void *buffer, - ResponseHandler *handler, - uint16_t timeout ) - { - XrdSysMutexHelper scopedLock( pMutex ); - - if( pFileState != Opened && pFileState != Recovering ) - return XRootDStatus( stError, errInvalidOp ); - - Log *log = DefaultEnv::GetLog(); - log->Debug( FileMsg, "[0x%x@%s] Sending a read command for handle 0x%x to " - "%s", this, pFileUrl->GetURL().c_str(), - *((uint32_t*)pFileHandle), pDataServer->GetHostId().c_str() ); - - Message *msg; - ClientReadRequest *req; - MessageUtils::CreateRequest( msg, req ); - - req->requestid = kXR_read; - req->offset = offset; - req->rlen = size; - memcpy( req->fhandle, pFileHandle, 4 ); - - ChunkList *list = new ChunkList(); - list->push_back( ChunkInfo( offset, size, buffer ) ); - - XRootDTransport::SetDescription( msg ); - MessageSendParams params; - params.timeout = timeout; - params.followRedirects = false; - params.stateful = true; - params.chunkList = list; - MessageUtils::ProcessSendParams( params ); - - StatefulHandler *stHandler = new StatefulHandler( this, handler, msg, params ); - - if( pDataServer->IsLocalFile() ) - { - XRootDStatus st = pLFileHandler->Read( offset, size, buffer, stHandler, timeout ); - return ExamineLocalResult( st, msg, stHandler ); - } - - return SendOrQueue( *pDataServer, msg, stHandler, params ); - } - - //---------------------------------------------------------------------------- - // Write a data chunk at a given offset - async - //---------------------------------------------------------------------------- - XRootDStatus FileStateHandler::Write( uint64_t offset, - uint32_t size, - const void *buffer, - ResponseHandler *handler, - uint16_t timeout ) - { - XrdSysMutexHelper scopedLock( pMutex ); - - if( pFileState != Opened && pFileState != Recovering ) - return XRootDStatus( stError, errInvalidOp ); - - Log *log = DefaultEnv::GetLog(); - log->Debug( FileMsg, "[0x%x@%s] Sending a write command for handle 0x%x to " - "%s", this, pFileUrl->GetURL().c_str(), - *((uint32_t*)pFileHandle), pDataServer->GetHostId().c_str() ); - - Message *msg; - ClientWriteRequest *req; - MessageUtils::CreateRequest( msg, req ); - - req->requestid = kXR_write; - req->offset = offset; - req->dlen = size; - memcpy( req->fhandle, pFileHandle, 4 ); - - ChunkList *list = new ChunkList(); - list->push_back( ChunkInfo( 0, size, (char*)buffer ) ); - - MessageSendParams params; - params.timeout = timeout; - params.followRedirects = false; - params.stateful = true; - params.chunkList = list; - - MessageUtils::ProcessSendParams( params ); - - XRootDTransport::SetDescription( msg ); - StatefulHandler *stHandler = new StatefulHandler( this, handler, msg, params ); - - if( pDataServer->IsLocalFile() ) - { - XRootDStatus st = pLFileHandler->Write( offset, size, buffer, stHandler, timeout ); - return ExamineLocalResult( st, msg, stHandler ); - } - - return SendOrQueue( *pDataServer, msg, stHandler, params ); - } - - //---------------------------------------------------------------------------- - // Commit all pending disk writes - async - //---------------------------------------------------------------------------- - XRootDStatus FileStateHandler::Sync( ResponseHandler *handler, - uint16_t timeout ) - { - XrdSysMutexHelper scopedLock( pMutex ); - - if( pFileState != Opened && pFileState != Recovering ) - return XRootDStatus( stError, errInvalidOp ); - - Log *log = DefaultEnv::GetLog(); - log->Debug( FileMsg, "[0x%x@%s] Sending a sync command for handle 0x%x to " - "%s", this, pFileUrl->GetURL().c_str(), - *((uint32_t*)pFileHandle), pDataServer->GetHostId().c_str() ); - - Message *msg; - ClientSyncRequest *req; - MessageUtils::CreateRequest( msg, req ); - - req->requestid = kXR_sync; - memcpy( req->fhandle, pFileHandle, 4 ); - - MessageSendParams params; - params.timeout = timeout; - params.followRedirects = false; - params.stateful = true; - MessageUtils::ProcessSendParams( params ); - - XRootDTransport::SetDescription( msg ); - StatefulHandler *stHandler = new StatefulHandler( this, handler, msg, params ); - - if( pDataServer->IsLocalFile() ) - { - XRootDStatus st = pLFileHandler->Sync( stHandler, timeout ); - return ExamineLocalResult( st, msg, stHandler ); - } - - return SendOrQueue( *pDataServer, msg, stHandler, params ); - } - - //---------------------------------------------------------------------------- - // Truncate the file to a particular size - async - //---------------------------------------------------------------------------- - XRootDStatus FileStateHandler::Truncate( uint64_t size, - ResponseHandler *handler, - uint16_t timeout ) - { - XrdSysMutexHelper scopedLock( pMutex ); - - if( pFileState != Opened && pFileState != Recovering ) - return XRootDStatus( stError, errInvalidOp ); - - Log *log = DefaultEnv::GetLog(); - log->Debug( FileMsg, "[0x%x@%s] Sending a truncate command for handle 0x%x to " - "%s", this, pFileUrl->GetURL().c_str(), - *((uint32_t*)pFileHandle), pDataServer->GetHostId().c_str() ); - - Message *msg; - ClientTruncateRequest *req; - MessageUtils::CreateRequest( msg, req ); - - req->requestid = kXR_truncate; - memcpy( req->fhandle, pFileHandle, 4 ); - req->offset = size; - - MessageSendParams params; - params.timeout = timeout; - params.followRedirects = false; - params.stateful = true; - MessageUtils::ProcessSendParams( params ); - - XRootDTransport::SetDescription( msg ); - StatefulHandler *stHandler = new StatefulHandler( this, handler, msg, params ); - - if( pDataServer->IsLocalFile() ) - { - XRootDStatus st = pLFileHandler->Truncate( size, stHandler, timeout ); - return ExamineLocalResult( st, msg, stHandler ); - } - - return SendOrQueue( *pDataServer, msg, stHandler, params ); - } - - //---------------------------------------------------------------------------- - // Read scattered data chunks in one operation - async - //---------------------------------------------------------------------------- - XRootDStatus FileStateHandler::VectorRead( const ChunkList &chunks, - void *buffer, - ResponseHandler *handler, - uint16_t timeout ) - { - //-------------------------------------------------------------------------- - // Sanity check - //-------------------------------------------------------------------------- - XrdSysMutexHelper scopedLock( pMutex ); - - if( pFileState != Opened && pFileState != Recovering ) - return XRootDStatus( stError, errInvalidOp ); - - Log *log = DefaultEnv::GetLog(); - log->Debug( FileMsg, "[0x%x@%s] Sending a vector read command for handle " - "0x%x to %s", this, pFileUrl->GetURL().c_str(), - *((uint32_t*)pFileHandle), pDataServer->GetHostId().c_str() ); - - //-------------------------------------------------------------------------- - // Build the message - //-------------------------------------------------------------------------- - Message *msg; - ClientReadVRequest *req; - MessageUtils::CreateRequest( msg, req, sizeof(readahead_list)*chunks.size() ); - - req->requestid = kXR_readv; - req->dlen = sizeof(readahead_list)*chunks.size(); - - ChunkList *list = new ChunkList(); - char *cursor = (char*)buffer; - - //-------------------------------------------------------------------------- - // Copy the chunk info - //-------------------------------------------------------------------------- - readahead_list *dataChunk = (readahead_list*)msg->GetBuffer( 24 ); - for( size_t i = 0; i < chunks.size(); ++i ) - { - dataChunk[i].rlen = chunks[i].length; - dataChunk[i].offset = chunks[i].offset; - memcpy( dataChunk[i].fhandle, pFileHandle, 4 ); - - void *chunkBuffer; - if( cursor ) - { - chunkBuffer = cursor; - cursor += chunks[i].length; - } - else - chunkBuffer = chunks[i].buffer; - - list->push_back( ChunkInfo( chunks[i].offset, - chunks[i].length, - chunkBuffer ) ); - } - - //-------------------------------------------------------------------------- - // Send the message - //-------------------------------------------------------------------------- - MessageSendParams params; - params.timeout = timeout; - params.followRedirects = false; - params.stateful = true; - params.chunkList = list; - MessageUtils::ProcessSendParams( params ); - - XRootDTransport::SetDescription( msg ); - StatefulHandler *stHandler = new StatefulHandler( this, handler, msg, params ); - - if( pDataServer->IsLocalFile() ) - { - XRootDStatus st = pLFileHandler->VectorRead( *list, buffer, stHandler, timeout ); - return ExamineLocalResult( st, msg, stHandler ); - } - - return SendOrQueue( *pDataServer, msg, stHandler, params ); - } - - //------------------------------------------------------------------------ - // Write scattered data chunks in one operation - async - //------------------------------------------------------------------------ - XRootDStatus FileStateHandler::VectorWrite( const ChunkList &chunks, - ResponseHandler *handler, - uint16_t timeout ) - { - //-------------------------------------------------------------------------- - // Sanity check - //-------------------------------------------------------------------------- - XrdSysMutexHelper scopedLock( pMutex ); - - if( pFileState != Opened && pFileState != Recovering ) - return XRootDStatus( stError, errInvalidOp ); - - Log *log = DefaultEnv::GetLog(); - log->Debug( FileMsg, "[0x%x@%s] Sending a vector write command for handle " - "0x%x to %s", this, pFileUrl->GetURL().c_str(), - *((uint32_t*)pFileHandle), pDataServer->GetHostId().c_str() ); - - //-------------------------------------------------------------------------- - // Determine the size of the payload - //-------------------------------------------------------------------------- - - // the size of write vector - uint32_t payloadSize = sizeof(XrdProto::write_list) * chunks.size(); - - //-------------------------------------------------------------------------- - // Build the message - //-------------------------------------------------------------------------- - Message *msg; - ClientWriteVRequest *req; - MessageUtils::CreateRequest( msg, req, payloadSize ); - - req->requestid = kXR_writev; - req->dlen = sizeof(XrdProto::write_list) * chunks.size(); - - ChunkList *list = new ChunkList(); - - //-------------------------------------------------------------------------- - // Copy the chunk info - //-------------------------------------------------------------------------- - XrdProto::write_list *writeList = - reinterpret_cast( msg->GetBuffer( 24 ) ); - - - - for( size_t i = 0; i < chunks.size(); ++i ) - { - writeList[i].wlen = chunks[i].length; - writeList[i].offset = chunks[i].offset; - memcpy( writeList[i].fhandle, pFileHandle, 4 ); - - list->push_back( ChunkInfo( chunks[i].offset, - chunks[i].length, - chunks[i].buffer ) ); - } - - //-------------------------------------------------------------------------- - // Send the message - //-------------------------------------------------------------------------- - MessageSendParams params; - params.timeout = timeout; - params.followRedirects = false; - params.stateful = true; - params.chunkList = list; - MessageUtils::ProcessSendParams( params ); - - XRootDTransport::SetDescription( msg ); - StatefulHandler *stHandler = new StatefulHandler( this, handler, msg, params ); - - if( pDataServer->IsLocalFile() ) - { - XRootDStatus st = pLFileHandler->VectorWrite( *list, stHandler, timeout ); - return ExamineLocalResult( st, msg, stHandler ); - } - - return SendOrQueue( *pDataServer, msg, stHandler, params ); - } - - //------------------------------------------------------------------------ - // Write scattered buffers in one operation - async - //------------------------------------------------------------------------ - XRootDStatus FileStateHandler::WriteV( uint64_t offset, - const struct iovec *iov, - int iovcnt, - ResponseHandler *handler, - uint16_t timeout ) - { - XrdSysMutexHelper scopedLock( pMutex ); - - if( pFileState != Opened && pFileState != Recovering ) - return XRootDStatus( stError, errInvalidOp ); - - Log *log = DefaultEnv::GetLog(); - log->Debug( FileMsg, "[0x%x@%s] Sending a write command for handle 0x%x to " - "%s", this, pFileUrl->GetURL().c_str(), - *((uint32_t*)pFileHandle), pDataServer->GetHostId().c_str() ); - - Message *msg; - ClientWriteRequest *req; - MessageUtils::CreateRequest( msg, req ); - - ChunkList *list = new ChunkList(); - - uint32_t size = 0; - for( int i = 0; i < iovcnt; ++i ) - { - size += iov[i].iov_len; - list->push_back( ChunkInfo( 0, iov[i].iov_len, - (char*)iov[i].iov_base ) ); - } - - req->requestid = kXR_write; - req->offset = offset; - req->dlen = size; - memcpy( req->fhandle, pFileHandle, 4 ); - - - - MessageSendParams params; - params.timeout = timeout; - params.followRedirects = false; - params.stateful = true; - params.chunkList = list; - - MessageUtils::ProcessSendParams( params ); - - XRootDTransport::SetDescription( msg ); - StatefulHandler *stHandler = new StatefulHandler( this, handler, msg, params ); - - if( pDataServer->IsLocalFile() ) - { - XRootDStatus st = pLFileHandler->WriteV( offset, iov, iovcnt, stHandler, timeout ); - return ExamineLocalResult( st, msg, stHandler ); - } - - return SendOrQueue( *pDataServer, msg, stHandler, params ); - } - - //---------------------------------------------------------------------------- - // Performs a custom operation on an open file, server implementation - // dependent - async - //---------------------------------------------------------------------------- - XRootDStatus FileStateHandler::Fcntl( const Buffer &arg, - ResponseHandler *handler, - uint16_t timeout ) - { - XrdSysMutexHelper scopedLock( pMutex ); - - if( pFileState != Opened && pFileState != Recovering ) - return XRootDStatus( stError, errInvalidOp ); - - Log *log = DefaultEnv::GetLog(); - log->Debug( FileMsg, "[0x%x@%s] Sending a fcntl command for handle 0x%x to " - "%s", this, pFileUrl->GetURL().c_str(), - *((uint32_t*)pFileHandle), pDataServer->GetHostId().c_str() ); - - Message *msg; - ClientQueryRequest *req; - MessageUtils::CreateRequest( msg, req, arg.GetSize() ); - - req->requestid = kXR_query; - req->infotype = kXR_Qopaqug; - req->dlen = arg.GetSize(); - memcpy( req->fhandle, pFileHandle, 4 ); - msg->Append( arg.GetBuffer(), arg.GetSize(), 24 ); - - MessageSendParams params; - params.timeout = timeout; - params.followRedirects = false; - params.stateful = true; - MessageUtils::ProcessSendParams( params ); - - XRootDTransport::SetDescription( msg ); - StatefulHandler *stHandler = new StatefulHandler( this, handler, msg, params ); - - if( pDataServer->IsLocalFile() ) - { - XRootDStatus st = pLFileHandler->Fcntl( arg, stHandler, timeout ); - return ExamineLocalResult( st, msg, stHandler ); - } - - return SendOrQueue( *pDataServer, msg, stHandler, params ); - } - - //---------------------------------------------------------------------------- - // Get access token to a file - async - //---------------------------------------------------------------------------- - XRootDStatus FileStateHandler::Visa( ResponseHandler *handler, - uint16_t timeout ) - { - XrdSysMutexHelper scopedLock( pMutex ); - - if( pFileState != Opened && pFileState != Recovering ) - return XRootDStatus( stError, errInvalidOp ); - - Log *log = DefaultEnv::GetLog(); - log->Debug( FileMsg, "[0x%x@%s] Sending a visa command for handle 0x%x to " - "%s", this, pFileUrl->GetURL().c_str(), - *((uint32_t*)pFileHandle), pDataServer->GetHostId().c_str() ); - - Message *msg; - ClientQueryRequest *req; - MessageUtils::CreateRequest( msg, req ); - - req->requestid = kXR_query; - req->infotype = kXR_Qvisa; - memcpy( req->fhandle, pFileHandle, 4 ); - - MessageSendParams params; - params.timeout = timeout; - params.followRedirects = false; - params.stateful = true; - MessageUtils::ProcessSendParams( params ); - - XRootDTransport::SetDescription( msg ); - StatefulHandler *stHandler = new StatefulHandler( this, handler, msg, params ); - if( pFileUrl->IsLocalFile() ) - { - XRootDStatus st = pLFileHandler->Visa( stHandler, timeout ); - return ExamineLocalResult( st, msg, stHandler ); - } - - return SendOrQueue( *pDataServer, msg, stHandler, params ); - } - - //---------------------------------------------------------------------------- - // Check if the file is open - //---------------------------------------------------------------------------- - bool FileStateHandler::IsOpen() const - { - XrdSysMutexHelper scopedLock( pMutex ); - - if( pFileState == Opened || pFileState == Recovering ) - return true; - return false; - } - - //---------------------------------------------------------------------------- - // Set file property - //---------------------------------------------------------------------------- - bool FileStateHandler::SetProperty( const std::string &name, - const std::string &value ) - { - XrdSysMutexHelper scopedLock( pMutex ); - if( name == "ReadRecovery" ) - { - if( value == "true" ) pDoRecoverRead = true; - else pDoRecoverRead = false; - return true; - } - else if( name == "WriteRecovery" ) - { - if( value == "true" ) pDoRecoverWrite = true; - else pDoRecoverWrite = false; - return true; - } - else if( name == "FollowRedirects" ) - { - if( value == "true" ) pFollowRedirects = true; - else pFollowRedirects = false; - return true; - } - return false; - } - - //---------------------------------------------------------------------------- - // Get file property - //---------------------------------------------------------------------------- - bool FileStateHandler::GetProperty( const std::string &name, - std::string &value ) const - { - XrdSysMutexHelper scopedLock( pMutex ); - if( name == "ReadRecovery" ) - { - if( pDoRecoverRead ) value = "true"; - else value = "false"; - return true; - } - else if( name == "WriteRecovery" ) - { - if( pDoRecoverWrite ) value = "true"; - else value = "false"; - return true; - } - else if( name == "FollowRedirects" ) - { - if( pFollowRedirects ) value = "true"; - else value = "false"; - return true; - } - else if( name == "DataServer" && pDataServer ) - { value = pDataServer->GetHostId(); return true; } - else if( name == "LastURL" && pDataServer ) - { value = pDataServer->GetURL(); return true; } - value = ""; - return false; - } - - //---------------------------------------------------------------------------- - // Process the results of the opening operation - //---------------------------------------------------------------------------- - void FileStateHandler::OnOpen( const XRootDStatus *status, - const OpenInfo *openInfo, - const HostList *hostList ) - { - Log *log = DefaultEnv::GetLog(); - XrdSysMutexHelper scopedLock( pMutex ); - - //-------------------------------------------------------------------------- - // Assign the data server and the load balancer - //-------------------------------------------------------------------------- - std::string lastServer = pFileUrl->GetHostId(); - if( hostList ) - { - delete pDataServer; - delete pLoadBalancer; - pLoadBalancer = 0; - - pDataServer = new URL( hostList->back().url ); - pDataServer->SetParams( pFileUrl->GetParams() ); - if( !( pUseVirtRedirector && pFileUrl->IsMetalink() ) ) pDataServer->SetPath( pFileUrl->GetPath() ); - lastServer = pDataServer->GetHostId(); - HostList::const_iterator itC; - URL::ParamsMap params = pDataServer->GetParams(); - for( itC = hostList->begin(); itC != hostList->end(); ++itC ) - { - MessageUtils::MergeCGI( params, - itC->url.GetParams(), - true ); - } - pDataServer->SetParams( params ); - - HostList::const_reverse_iterator it; - for( it = hostList->rbegin(); it != hostList->rend(); ++it ) - if( it->loadBalancer ) - { - pLoadBalancer = new URL( it->url ); - break; - } - } - - log->Debug( FileMsg, "[0x%x@%s] Open has returned with status %s", - this, pFileUrl->GetURL().c_str(), status->ToStr().c_str() ); - - //-------------------------------------------------------------------------- - // We have failed - //-------------------------------------------------------------------------- - pStatus = *status; - if( !pStatus.IsOK() || !openInfo ) - { - log->Debug( FileMsg, "[0x%x@%s] Error while opening at %s: %s", - this, pFileUrl->GetURL().c_str(), lastServer.c_str(), - pStatus.ToStr().c_str() ); - FailQueuedMessages( pStatus ); - pFileState = Error; - - //------------------------------------------------------------------------ - // Report to monitoring - //------------------------------------------------------------------------ - Monitor *mon = DefaultEnv::GetMonitor(); - if( mon ) - { - Monitor::ErrorInfo i; - i.file = pFileUrl; - i.status = status; - i.opCode = Monitor::ErrorInfo::ErrOpen; - mon->Event( Monitor::EvErrIO, &i ); - } - } - //-------------------------------------------------------------------------- - // We have succeeded - //-------------------------------------------------------------------------- - else - { - //------------------------------------------------------------------------ - // Store the response info - //------------------------------------------------------------------------ - openInfo->GetFileHandle( pFileHandle ); - pSessionId = openInfo->GetSessionId(); - if( openInfo->GetStatInfo() ) - { - delete pStatInfo; - pStatInfo = new StatInfo( *openInfo->GetStatInfo() ); - } - - log->Debug( FileMsg, "[0x%x@%s] successfully opened at %s, handle: 0x%x, " - "session id: %ld", this, pFileUrl->GetURL().c_str(), - pDataServer->GetHostId().c_str(), *((uint32_t*)pFileHandle), - pSessionId ); - - //------------------------------------------------------------------------ - // Inform the monitoring about opening success - //------------------------------------------------------------------------ - gettimeofday( &pOpenTime, 0 ); - Monitor *mon = DefaultEnv::GetMonitor(); - if( mon ) - { - Monitor::OpenInfo i; - i.file = pFileUrl; - i.dataServer = pDataServer->GetHostId(); - i.oFlags = pOpenFlags; - i.fSize = pStatInfo ? pStatInfo->GetSize() : 0; - mon->Event( Monitor::EvOpen, &i ); - } - - //------------------------------------------------------------------------ - // Resend the queued messages if any - //------------------------------------------------------------------------ - ReSendQueuedMessages(); - pFileState = Opened; - } - } - - //---------------------------------------------------------------------------- - // Process the results of the closing operation - //---------------------------------------------------------------------------- - void FileStateHandler::OnClose( const XRootDStatus *status ) - { - Log *log = DefaultEnv::GetLog(); - XrdSysMutexHelper scopedLock( pMutex ); - - log->Debug( FileMsg, "[0x%x@%s] Close returned from %s with: %s", this, - pFileUrl->GetURL().c_str(), pDataServer->GetHostId().c_str(), - status->ToStr().c_str() ); - - log->Dump( FileMsg, "[0x%x@%s] Items in the fly %d, queued for recovery %d", - this, pFileUrl->GetURL().c_str(), pInTheFly.size(), - pToBeRecovered.size() ); - - MonitorClose( status ); - ResetMonitoringVars(); - - pStatus = *status; - pFileState = Closed; - } - - //---------------------------------------------------------------------------- - // Handle an error while sending a stateful message - //---------------------------------------------------------------------------- - void FileStateHandler::OnStateError( XRootDStatus *status, - Message *message, - ResponseHandler *userHandler, - MessageSendParams &sendParams ) - { - //-------------------------------------------------------------------------- - // It may be a redirection - //-------------------------------------------------------------------------- - if( !status->IsOK() && status->code == errRedirect && pFollowRedirects ) - { - std::string root = "root", xroot = "xroot"; - std::string msg = status->GetErrorMessage(); - if( !msg.compare( 0, root.size(), root ) || - !msg.compare( 0, xroot.size(), xroot ) ) - { - OnStateRedirection( msg, message, userHandler, sendParams ); - return; - } - } - - //-------------------------------------------------------------------------- - // Handle error - //-------------------------------------------------------------------------- - Log *log = DefaultEnv::GetLog(); - XrdSysMutexHelper scopedLock( pMutex ); - pInTheFly.erase( message ); - - log->Dump( FileMsg, "[0x%x@%s] File state error encountered. Message %s " - "returned with %s", this, pFileUrl->GetURL().c_str(), - message->GetDescription().c_str(), status->ToStr().c_str() ); - - //-------------------------------------------------------------------------- - // Report to monitoring - //-------------------------------------------------------------------------- - Monitor *mon = DefaultEnv::GetMonitor(); - if( mon ) - { - Monitor::ErrorInfo i; - i.file = pFileUrl; - i.status = status; - - ClientRequest *req = (ClientRequest*)message->GetBuffer(); - switch( req->header.requestid ) - { - case kXR_read: i.opCode = Monitor::ErrorInfo::ErrRead; break; - case kXR_readv: i.opCode = Monitor::ErrorInfo::ErrReadV; break; - case kXR_write: i.opCode = Monitor::ErrorInfo::ErrWrite; break; - // TODO - // once we do major release we can replace this with 'ErrWriteV' - case kXR_writev: i.opCode = Monitor::ErrorInfo::ErrWrite; break; - default: i.opCode = Monitor::ErrorInfo::ErrUnc; - } - - mon->Event( Monitor::EvErrIO, &i ); - } - - //-------------------------------------------------------------------------- - // The message is not recoverable - //-------------------------------------------------------------------------- - if( !IsRecoverable( *status ) ) - { - log->Error( FileMsg, "[0x%x@%s] Fatal file state error. Message %s " - "returned with %s", this, pFileUrl->GetURL().c_str(), - message->GetDescription().c_str(), status->ToStr().c_str() ); - - FailMessage( RequestData( message, userHandler, sendParams ), *status ); - delete status; - return; - } - - //-------------------------------------------------------------------------- - // Insert the message to the recovery queue and start the recovery - // procedure if we don't have any more message in the fly - //-------------------------------------------------------------------------- - pCloseReason = *status; - RecoverMessage( RequestData( message, userHandler, sendParams ) ); - delete status; - } - - //---------------------------------------------------------------------------- - // Handle stateful redirect - //---------------------------------------------------------------------------- - void FileStateHandler::OnStateRedirection( const std::string &redirectUrl, - Message *message, - ResponseHandler *userHandler, - MessageSendParams &sendParams ) - { - XrdSysMutexHelper scopedLock( pMutex ); - pInTheFly.erase( message ); - - //-------------------------------------------------------------------------- - // Register the state redirect url and append the new cgi information to - // the file URL - //-------------------------------------------------------------------------- - if( !pStateRedirect ) - { - std::ostringstream o; - pStateRedirect = new URL( redirectUrl ); - URL::ParamsMap params = pFileUrl->GetParams(); - MessageUtils::MergeCGI( params, - pStateRedirect->GetParams(), - false ); - pFileUrl->SetParams( params ); - } - - RecoverMessage( RequestData( message, userHandler, sendParams ) ); - } - - //---------------------------------------------------------------------------- - // Handle stateful response - //---------------------------------------------------------------------------- - void FileStateHandler::OnStateResponse( XRootDStatus *status, - Message *message, - AnyObject *response, - HostList */*urlList*/ ) - { - Log *log = DefaultEnv::GetLog(); - XrdSysMutexHelper scopedLock( pMutex ); - - log->Dump( FileMsg, "[0x%x@%s] Got state response for message %s", - this, pFileUrl->GetURL().c_str(), - message->GetDescription().c_str() ); - - //-------------------------------------------------------------------------- - // Since this message may be the last "in-the-fly" and no recovery - // is done if messages are in the fly, we may need to trigger recovery - //-------------------------------------------------------------------------- - pInTheFly.erase( message ); - RunRecovery(); - - //-------------------------------------------------------------------------- - // Play with the actual response before returning it. This is a good - // place to do caching in the future. - //-------------------------------------------------------------------------- - ClientRequest *req = (ClientRequest*)message->GetBuffer(); - switch( req->header.requestid ) - { - //------------------------------------------------------------------------ - // Cache the stat response - //------------------------------------------------------------------------ - case kXR_stat: - { - StatInfo *info = 0; - response->Get( info ); - delete pStatInfo; - pStatInfo = new StatInfo( *info ); - break; - } - - //------------------------------------------------------------------------ - // Handle read response - //------------------------------------------------------------------------ - case kXR_read: - { - ++pRCount; - pRBytes += req->read.rlen; - break; - } - - //------------------------------------------------------------------------ - // Handle readv response - //------------------------------------------------------------------------ - case kXR_readv: - { - ++pVRCount; - size_t segs = req->header.dlen/sizeof(readahead_list); - readahead_list *dataChunk = (readahead_list*)message->GetBuffer( 24 ); - for( size_t i = 0; i < segs; ++i ) - pVRBytes += dataChunk[i].rlen; - pVSegs += segs; - break; - } - - //------------------------------------------------------------------------ - // Handle write response - //------------------------------------------------------------------------ - case kXR_write: - { - ++pWCount; - pWBytes += req->write.dlen; - break; - } - - //------------------------------------------------------------------------ - // Handle writev response - //------------------------------------------------------------------------ - case kXR_writev: - { - ++pVWCount; - size_t size = req->header.dlen/sizeof(readahead_list); - XrdProto::write_list *wrtList = - reinterpret_cast( message->GetBuffer( 24 ) ); - for( size_t i = 0; i < size; ++i ) - pVWBytes += wrtList[i].wlen; - break; - } - }; - } - - //------------------------------------------------------------------------ - //! Tick - //------------------------------------------------------------------------ - void FileStateHandler::Tick( time_t now ) - { - if (pMutex.CondLock()) - {TimeOutRequests( now ); - pMutex.UnLock(); - } - } - - //---------------------------------------------------------------------------- - // Declare timeout on requests being recovered - //---------------------------------------------------------------------------- - void FileStateHandler::TimeOutRequests( time_t now ) - { - if( !pToBeRecovered.empty() ) - { - Log *log = DefaultEnv::GetLog(); - log->Dump( FileMsg, "[0x%x@%s] Got a timer event", this, - pFileUrl->GetURL().c_str() ); - RequestList::iterator it; - JobManager *jobMan = DefaultEnv::GetPostMaster()->GetJobManager(); - for( it = pToBeRecovered.begin(); it != pToBeRecovered.end(); ) - { - if( it->params.expires <= now ) - { - jobMan->QueueJob( new ResponseJob( - it->handler, - new XRootDStatus( stError, errOperationExpired ), - 0, it->params.hostList ) ); - it = pToBeRecovered.erase( it ); - } - else - ++it; - } - } - } - - //---------------------------------------------------------------------------- - // Called in the child process after the fork - //---------------------------------------------------------------------------- - void FileStateHandler::AfterForkChild() - { - Log *log = DefaultEnv::GetLog(); - - if( pFileState == Closed || pFileState == Error ) - return; - - if( (IsReadOnly() && pDoRecoverRead) || - (!IsReadOnly() && pDoRecoverWrite) ) - { - log->Debug( FileMsg, "[0x%x@%s] Putting the file in recovery state in " - "process %d", this, pFileUrl->GetURL().c_str(), getpid() ); - pFileState = Recovering; - pInTheFly.clear(); - pToBeRecovered.clear(); - } - else - pFileState = Error; - } - - //---------------------------------------------------------------------------- - // Send a message to a host or put it in the recovery queue - //---------------------------------------------------------------------------- - Status FileStateHandler::SendOrQueue( const URL &url, - Message *msg, - ResponseHandler *handler, - MessageSendParams &sendParams ) - { - //-------------------------------------------------------------------------- - // Recovering - //-------------------------------------------------------------------------- - if( pFileState == Recovering ) - { - return RecoverMessage( RequestData( msg, handler, sendParams ), false ); - } - - //-------------------------------------------------------------------------- - // Trying to send - //-------------------------------------------------------------------------- - if( pFileState == Opened ) - { - msg->SetSessionId( pSessionId ); - Status st = MessageUtils::SendMessage( *pDataServer, msg, handler, - sendParams, pLFileHandler ); - - //------------------------------------------------------------------------ - // Invalid session id means that the connection has been broken while we - // were idle so we haven't been informed about this fact earlier. - //------------------------------------------------------------------------ - if( !st.IsOK() && st.code == errInvalidSession && IsRecoverable( st ) ) - return RecoverMessage( RequestData( msg, handler, sendParams ), false ); - - if( st.IsOK() ) - pInTheFly.insert(msg); - else - delete handler; - return st; - } - return Status( stError, errInvalidOp ); - } - - //---------------------------------------------------------------------------- - // Check if the stateful error is recoverable - //---------------------------------------------------------------------------- - bool FileStateHandler::IsRecoverable( const XRootDStatus &status ) const - { - if( status.code == errSocketError || status.code == errInvalidSession ) - { - if( IsReadOnly() && !pDoRecoverRead ) - return false; - - if( !IsReadOnly() && !pDoRecoverWrite ) - return false; - - return true; - } - return false; - } - - //---------------------------------------------------------------------------- - // Check if the file is open for read only - //---------------------------------------------------------------------------- - bool FileStateHandler::IsReadOnly() const - { - if( (pOpenFlags & kXR_open_read) && !(pOpenFlags & kXR_open_updt) && - !(pOpenFlags & kXR_open_apnd ) ) - return true; - return false; - } - - //---------------------------------------------------------------------------- - // Recover a message - //---------------------------------------------------------------------------- - Status FileStateHandler::RecoverMessage( RequestData rd, - bool callbackOnFailure ) - { - pFileState = Recovering; - - Log *log = DefaultEnv::GetLog(); - log->Dump( FileMsg, "[0x%x@%s] Putting message %s in the recovery list", - this, pFileUrl->GetURL().c_str(), - rd.request->GetDescription().c_str() ); - - Status st = RunRecovery(); - if( st.IsOK() ) - { - pToBeRecovered.push_back( rd ); - return st; - } - - if( callbackOnFailure ) - FailMessage( rd, st ); - - return st; - } - - //---------------------------------------------------------------------------- - // Run the recovery procedure if appropriate - //---------------------------------------------------------------------------- - Status FileStateHandler::RunRecovery() - { - if( pFileState != Recovering ) - return Status(); - - if( !pInTheFly.empty() ) - return Status(); - - Log *log = DefaultEnv::GetLog(); - log->Debug( FileMsg, "[0x%x@%s] Running the recovery procedure", this, - pFileUrl->GetURL().c_str() ); - - Status st; - if( pStateRedirect ) - { - SendClose( 0 ); - st = ReOpenFileAtServer( *pStateRedirect, 0 ); - delete pStateRedirect; pStateRedirect = 0; - } - else if( IsReadOnly() && pLoadBalancer ) - st = ReOpenFileAtServer( *pLoadBalancer, 0 ); - else - st = ReOpenFileAtServer( *pDataServer, 0 ); - - if( !st.IsOK() ) - { - pFileState = Error; - FailQueuedMessages( st ); - } - - return st; - } - - //---------------------------------------------------------------------------- - // Send a close and ignore the response - //---------------------------------------------------------------------------- - Status FileStateHandler::SendClose( uint16_t timeout ) - { - Message *msg; - ClientCloseRequest *req; - MessageUtils::CreateRequest( msg, req ); - - req->requestid = kXR_close; - memcpy( req->fhandle, pFileHandle, 4 ); - - XRootDTransport::SetDescription( msg ); - msg->SetSessionId( pSessionId ); - NullResponseHandler *handler = new NullResponseHandler(); - MessageSendParams params; - params.timeout = timeout; - params.followRedirects = false; - params.stateful = true; - - MessageUtils::ProcessSendParams( params ); - - return MessageUtils::SendMessage( *pDataServer, msg, handler, - params, pLFileHandler ); - } - - //---------------------------------------------------------------------------- - // Re-open the current file at a given server - //---------------------------------------------------------------------------- - Status FileStateHandler::ReOpenFileAtServer( const URL &url, uint16_t timeout ) - { - Log *log = DefaultEnv::GetLog(); - log->Dump( FileMsg, "[0x%x@%s] Sending a recovery open command to %s", - this, pFileUrl->GetURL().c_str(), url.GetURL().c_str() ); - - //-------------------------------------------------------------------------- - // Remove the kXR_delete and kXR_new flags, as we don't want the recovery - // procedure to delete a file that has been partially updated or fail it - // because a partially uploaded file already exists. - //-------------------------------------------------------------------------- - if (pOpenFlags & kXR_delete) - { - pOpenFlags &= ~kXR_delete; - pOpenFlags |= kXR_open_updt; - } - - pOpenFlags &= ~kXR_new; - - Message *msg; - ClientOpenRequest *req; - URL u = url; - - if( !url.GetPath().empty() ) - u.SetPath( pFileUrl->GetPath() ); - - std::string path = u.GetPathWithFilteredParams(); - MessageUtils::CreateRequest( msg, req, path.length() ); - - req->requestid = kXR_open; - req->mode = pOpenMode; - req->options = pOpenFlags; - req->dlen = path.length(); - msg->Append( path.c_str(), path.length(), 24 ); - - // the handler has been removed from the queue - // (because we are here) so we can destroy it - if( pReOpenHandler ) - { - // in principle this should not happen because reopen - // is triggered only from StateHandler (Stat, Write, Read, etc.) - // but not from Open itself but it is better to be on the save side - pReOpenHandler->Destroy(); - pReOpenHandler = 0; - } - // create a new reopen handler - // (it is not assigned to 'pReOpenHandler' in order not to bump the reference counter - // until we know that 'SendMessage' was successful) - ResponseHandlerHolder *openHandler = new ResponseHandlerHolder( new OpenHandler( this, 0 ) ); - MessageSendParams params; params.timeout = timeout; - MessageUtils::ProcessSendParams( params ); - XRootDTransport::SetDescription( msg ); - - Status st; - //-------------------------------------------------------------------------- - // If a virtual redirector is in use, set it up in send parameters - // and redirect the message - //-------------------------------------------------------------------------- - if( pUseVirtRedirector && url.IsMetalink() ) - st = MessageUtils::RedirectMessage( url, msg, openHandler, - params, pLFileHandler ); - //-------------------------------------------------------------------------- - // Otherwise do an ordinary send - //-------------------------------------------------------------------------- - else - st = MessageUtils::SendMessage( url, msg, openHandler, - params, pLFileHandler ); - // if there was a problem destroy the open handler - if( !st.IsOK() ) - { - openHandler->Destroy(); - } - // otherwise keep the reference - else - { - pReOpenHandler = openHandler->Self(); - } - return st; - } - - //------------------------------------------------------------------------ - // Fail a message - //------------------------------------------------------------------------ - void FileStateHandler::FailMessage( RequestData rd, XRootDStatus status ) - { - Log *log = DefaultEnv::GetLog(); - log->Dump( FileMsg, "[0x%x@%s] Failing message %s with %s", - this, pFileUrl->GetURL().c_str(), - rd.request->GetDescription().c_str(), - status.ToStr().c_str() ); - - StatefulHandler *sh = dynamic_cast(rd.handler); - if( !sh ) - { - Log *log = DefaultEnv::GetLog(); - log->Error( FileMsg, "[0x%x@%s] Internal error while recovering %s", - this, pFileUrl->GetURL().c_str(), - rd.request->GetDescription().c_str() ); - return; - } - - JobManager *jobMan = DefaultEnv::GetPostMaster()->GetJobManager(); - ResponseHandler *userHandler = sh->GetUserHandler(); - jobMan->QueueJob( new ResponseJob( - userHandler, - new XRootDStatus( status ), - 0, rd.params.hostList ) ); - - delete sh; - } - - //---------------------------------------------------------------------------- - // Fail queued messages - //---------------------------------------------------------------------------- - void FileStateHandler::FailQueuedMessages( XRootDStatus status ) - { - RequestList::iterator it; - for( it = pToBeRecovered.begin(); it != pToBeRecovered.end(); ++it ) - FailMessage( *it, status ); - pToBeRecovered.clear(); - } - - //------------------------------------------------------------------------ - // Re-send queued messages - //------------------------------------------------------------------------ - void FileStateHandler::ReSendQueuedMessages() - { - RequestList::iterator it; - for( it = pToBeRecovered.begin(); it != pToBeRecovered.end(); ++it ) - { - it->request->SetSessionId( pSessionId ); - ReWriteFileHandle( it->request ); - Status st = MessageUtils::SendMessage( *pDataServer, it->request, - it->handler, it->params, - pLFileHandler ); - if( !st.IsOK() ) - FailMessage( *it, st ); - } - pToBeRecovered.clear(); - } - - //------------------------------------------------------------------------ - // Re-write file handle - //------------------------------------------------------------------------ - void FileStateHandler::ReWriteFileHandle( Message *msg ) - { - ClientRequestHdr *hdr = (ClientRequestHdr*)msg->GetBuffer(); - switch( hdr->requestid ) - { - case kXR_read: - { - ClientReadRequest *req = (ClientReadRequest*)msg->GetBuffer(); - memcpy( req->fhandle, pFileHandle, 4 ); - break; - } - case kXR_write: - { - ClientWriteRequest *req = (ClientWriteRequest*)msg->GetBuffer(); - memcpy( req->fhandle, pFileHandle, 4 ); - break; - } - case kXR_sync: - { - ClientSyncRequest *req = (ClientSyncRequest*)msg->GetBuffer(); - memcpy( req->fhandle, pFileHandle, 4 ); - break; - } - case kXR_truncate: - { - ClientTruncateRequest *req = (ClientTruncateRequest*)msg->GetBuffer(); - memcpy( req->fhandle, pFileHandle, 4 ); - break; - } - case kXR_readv: - { - ClientReadVRequest *req = (ClientReadVRequest*)msg->GetBuffer(); - readahead_list *dataChunk = (readahead_list*)msg->GetBuffer( 24 ); - for( size_t i = 0; i < req->dlen/sizeof(readahead_list); ++i ) - memcpy( dataChunk[i].fhandle, pFileHandle, 4 ); - break; - } - case kXR_writev: - { - ClientWriteVRequest *req = - reinterpret_cast( msg->GetBuffer() ); - XrdProto::write_list *wrtList = - reinterpret_cast( msg->GetBuffer( 24 ) ); - size_t size = req->dlen / sizeof(XrdProto::write_list); - for( size_t i = 0; i < size; ++i ) - memcpy( wrtList[i].fhandle, pFileHandle, 4 ); - break; - } - } - - Log *log = DefaultEnv::GetLog(); - log->Dump( FileMsg, "[0x%x@%s] Rewritten file handle for %s to 0x%x", - this, pFileUrl->GetURL().c_str(), msg->GetDescription().c_str(), - *((uint32_t*)pFileHandle) ); - XRootDTransport::SetDescription( msg ); - } - - //---------------------------------------------------------------------------- - // Dispatch monitoring information on close - //---------------------------------------------------------------------------- - void FileStateHandler::MonitorClose( const XRootDStatus *status ) - { - Monitor *mon = DefaultEnv::GetMonitor(); - if( mon ) - { - Monitor::CloseInfo i; - i.file = pFileUrl; - i.oTOD = pOpenTime; - gettimeofday( &i.cTOD, 0 ); - i.rBytes = pRBytes; - i.vBytes = pVRBytes; - i.wBytes = pWBytes + pVWBytes; //TODO once we can break ABI compatibility - i.vSegs = pVSegs; // we will add a special field for WriteV - i.rCount = pRCount; - i.vCount = pVRCount; - i.wCount = pWCount; - i.status = status; - mon->Event( Monitor::EvClose, &i ); - } - } - - XRootDStatus FileStateHandler::ExamineLocalResult( XRootDStatus &status, - Message *msg, - ResponseHandler *handler ) - { - if( status.IsOK() ) - pInTheFly.insert( msg ); - else - delete handler; - return status; - } -} diff --git a/src/XrdCl/XrdClFileStateHandler.hh b/src/XrdCl/XrdClFileStateHandler.hh deleted file mode 100644 index 6fa3e349940..00000000000 --- a/src/XrdCl/XrdClFileStateHandler.hh +++ /dev/null @@ -1,501 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2014 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_FILE_STATE_HANDLER_HH__ -#define __XRD_CL_FILE_STATE_HANDLER_HH__ - -#include "XrdCl/XrdClXRootDResponses.hh" -#include "XrdCl/XrdClPostMasterInterfaces.hh" -#include "XrdCl/XrdClFileSystem.hh" -#include "XrdCl/XrdClMessageUtils.hh" -#include "XrdSys/XrdSysPthread.hh" -#include "XrdCl/XrdClLocalFileHandler.hh" -#include -#include - -#include - -namespace XrdCl -{ - class ResponseHandlerHolder; - class Message; - - //---------------------------------------------------------------------------- - //! Handle the stateful operations - //---------------------------------------------------------------------------- - class FileStateHandler - { - public: - //------------------------------------------------------------------------ - //! State of the file - //------------------------------------------------------------------------ - enum FileStatus - { - Closed, //!< The file is closed - Opened, //!< Opening has succeeded - Error, //!< Opening has failed - Recovering, //!< Recovering from an error - OpenInProgress, //!< Opening is in progress - CloseInProgress //!< Closing operation is in progress - }; - - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - FileStateHandler(); - - //------------------------------------------------------------------------ - //! Constructor - //! - //! @param useVirtRedirector if true Metalink files will be treated - //! as a VirtualRedirectors - //------------------------------------------------------------------------ - FileStateHandler( bool useVirtRedirector ); - - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - ~FileStateHandler(); - - //------------------------------------------------------------------------ - //! Open the file pointed to by the given URL - //! - //! @param url url of the file to be opened - //! @param flags OpenFlags::Flags - //! @param mode Access::Mode for new files, 0 otherwise - //! @param handler handler to be notified about the status of the operation - //! @param timeout timeout value, if 0 the environment default will be - //! used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Open( const std::string &url, - uint16_t flags, - uint16_t mode, - ResponseHandler *handler, - uint16_t timeout = 0 ); - - //------------------------------------------------------------------------ - //! Close the file object - //! - //! @param handler handler to be notified about the status of the operation - //! @param timeout timeout value, if 0 the environment default will be - //! used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Close( ResponseHandler *handler, - uint16_t timeout = 0 ); - - //------------------------------------------------------------------------ - //! Obtain status information for this file - async - //! - //! @param force do not use the cached information, force re-stating - //! @param handler handler to be notified when the response arrives, - //! the response parameter will hold a StatInfo object - //! if the procedure is successful - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Stat( bool force, - ResponseHandler *handler, - uint16_t timeout = 0 ); - - - //------------------------------------------------------------------------ - //! Read a data chunk at a given offset - sync - //! - //! @param offset offset from the beginning of the file - //! @param size number of bytes to be read - //! @param buffer a pointer to a buffer big enough to hold the data - //! or 0 if the buffer should be allocated by the system - //! @param handler handler to be notified when the response arrives, - //! the response parameter will hold a buffer object if - //! the procedure was successful, if a preallocated - //! buffer was specified then the buffer object will - //! "wrap" this buffer - //! @param timeout timeout value, if 0 the environment default will be - //! used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Read( uint64_t offset, - uint32_t size, - void *buffer, - ResponseHandler *handler, - uint16_t timeout = 0 ); - - //------------------------------------------------------------------------ - //! Write a data chunk at a given offset - async - //! - //! @param offset offset from the beginning of the file - //! @param size number of bytes to be written - //! @param buffer a pointer to the buffer holding the data to be written - //! @param handler handler to be notified when the response arrives - //! @param timeout timeout value, if 0 the environment default will be - //! used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Write( uint64_t offset, - uint32_t size, - const void *buffer, - ResponseHandler *handler, - uint16_t timeout = 0 ); - - - //------------------------------------------------------------------------ - //! Commit all pending disk writes - async - //! - //! @param handler handler to be notified when the response arrives - //! @param timeout timeout value, if 0 the environment default will be - //! used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Sync( ResponseHandler *handler, - uint16_t timeout = 0 ); - - //------------------------------------------------------------------------ - //! Truncate the file to a particular size - async - //! - //! @param size desired size of the file - //! @param handler handler to be notified when the response arrives - //! @param timeout timeout value, if 0 the environment default will be - //! used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Truncate( uint64_t size, - ResponseHandler *handler, - uint16_t timeout = 0 ); - - //------------------------------------------------------------------------ - //! Read scattered data chunks in one operation - async - //! - //! @param chunks list of the chunks to be read - //! @param buffer a pointer to a buffer big enough to hold the data - //! @param handler handler to be notified when the response arrives - //! @param timeout timeout value, if 0 then the environment default - //! will be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus VectorRead( const ChunkList &chunks, - void *buffer, - ResponseHandler *handler, - uint16_t timeout = 0 ); - - //------------------------------------------------------------------------ - //! Write scattered data chunks in one operation - async - //! - //! @param chunks list of the chunks to be read - //! @param handler handler to be notified when the response arrives - //! @param timeout timeout value, if 0 then the environment default - //! will be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus VectorWrite( const ChunkList &chunks, - ResponseHandler *handler, - uint16_t timeout = 0 ); - - //------------------------------------------------------------------------ - //! Write scattered buffers in one operation - async - //! - //! @param offset offset from the beginning of the file - //! @param iov list of the buffers to be written - //! @param iovcnt number of buffers - //! @param handler handler to be notified when the response arrives - //! @param timeout timeout value, if 0 then the environment default - //! will be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus WriteV( uint64_t offset, - const struct iovec *iov, - int iovcnt, - ResponseHandler *handler, - uint16_t timeout = 0 ); - - //------------------------------------------------------------------------ - //! Performs a custom operation on an open file, server implementation - //! dependent - async - //! - //! @param arg query argument - //! @param handler handler to be notified when the response arrives, - //! the response parameter will hold a Buffer object - //! if the procedure is successful - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Fcntl( const Buffer &arg, - ResponseHandler *handler, - uint16_t timeout = 0 ); - - //------------------------------------------------------------------------ - //! Get access token to a file - async - //! - //! @param handler handler to be notified when the response arrives, - //! the response parameter will hold a Buffer object - //! if the procedure is successful - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Visa( ResponseHandler *handler, - uint16_t timeout = 0 ); - - //------------------------------------------------------------------------ - //! Process the results of the opening operation - //------------------------------------------------------------------------ - void OnOpen( const XRootDStatus *status, - const OpenInfo *openInfo, - const HostList *hostList ); - - //------------------------------------------------------------------------ - //! Process the results of the closing operation - //------------------------------------------------------------------------ - void OnClose( const XRootDStatus *status ); - - //------------------------------------------------------------------------ - //! Handle an error while sending a stateful message - //------------------------------------------------------------------------ - void OnStateError( XRootDStatus *status, - Message *message, - ResponseHandler *userHandler, - MessageSendParams &sendParams ); - - //------------------------------------------------------------------------ - //! Handle stateful redirect - //------------------------------------------------------------------------ - void OnStateRedirection( const std::string &redirectUrl, - Message *message, - ResponseHandler *userHandler, - MessageSendParams &sendParams ); - - //------------------------------------------------------------------------ - //! Handle stateful response - //------------------------------------------------------------------------ - void OnStateResponse( XRootDStatus *status, - Message *message, - AnyObject *response, - HostList *hostList ); - - //------------------------------------------------------------------------ - //! Check if the file is open - //------------------------------------------------------------------------ - bool IsOpen() const; - - //------------------------------------------------------------------------ - //! Set file property - //! - //! @see File::GetProperty for propert list - //------------------------------------------------------------------------ - bool SetProperty( const std::string &name, const std::string &value ); - - //------------------------------------------------------------------------ - //! Get file property - //! - //! @see File::SetProperty for property list - //------------------------------------------------------------------------ - bool GetProperty( const std::string &name, std::string &value ) const; - - //------------------------------------------------------------------------ - //! Lock the internal lock - //------------------------------------------------------------------------ - void Lock() - { - pMutex.Lock(); - } - - //------------------------------------------------------------------------ - //! Unlock the internal lock - //------------------------------------------------------------------------ - void UnLock() - { - pMutex.UnLock(); - } - - //------------------------------------------------------------------------ - //! Tick - //------------------------------------------------------------------------ - void Tick( time_t now ); - - //------------------------------------------------------------------------ - //! Declare timeout on requests being recovered - //------------------------------------------------------------------------ - void TimeOutRequests( time_t now ); - - //------------------------------------------------------------------------ - //! Called in the child process after the fork - //------------------------------------------------------------------------ - void AfterForkChild(); - - private: - //------------------------------------------------------------------------ - // Helper for queuing messages - //------------------------------------------------------------------------ - struct RequestData - { - RequestData(): request(0), handler(0) {} - RequestData( Message *r, ResponseHandler *h, - const MessageSendParams &p ): - request(r), handler(h), params(p) {} - Message *request; - ResponseHandler *handler; - MessageSendParams params; - }; - typedef std::list RequestList; - - //------------------------------------------------------------------------ - //! Send a message to a host or put it in the recovery queue - //------------------------------------------------------------------------ - Status SendOrQueue( const URL &url, - Message *msg, - ResponseHandler *handler, - MessageSendParams &sendParams ); - - //------------------------------------------------------------------------ - //! Check if the stateful error is recoverable - //------------------------------------------------------------------------ - bool IsRecoverable( const XRootDStatus &stataus ) const; - - //------------------------------------------------------------------------ - //! Recover a message - //! - //! @param rd request data associated with the message - //! @param callbackOnFailure should the current handler be called back - //! if the recovery procedure fails - //------------------------------------------------------------------------ - Status RecoverMessage( RequestData rd, bool callbackOnFailure = true ); - - //------------------------------------------------------------------------ - //! Run the recovery procedure if appropriate - //------------------------------------------------------------------------ - Status RunRecovery(); - - //------------------------------------------------------------------------ - // Send a close and ignore the response - //------------------------------------------------------------------------ - Status SendClose( uint16_t timeout ); - - //------------------------------------------------------------------------ - //! Check if the file is open for read only - //------------------------------------------------------------------------ - bool IsReadOnly() const; - - //------------------------------------------------------------------------ - //! Re-open the current file at a given server - //------------------------------------------------------------------------ - Status ReOpenFileAtServer( const URL &url, uint16_t timeout ); - - //------------------------------------------------------------------------ - //! Fail a message - //------------------------------------------------------------------------ - void FailMessage( RequestData rd, XRootDStatus status ); - - //------------------------------------------------------------------------ - //! Fail queued messages - //------------------------------------------------------------------------ - void FailQueuedMessages( XRootDStatus status ); - - //------------------------------------------------------------------------ - //! Re-send queued messages - //------------------------------------------------------------------------ - void ReSendQueuedMessages(); - - //------------------------------------------------------------------------ - //! Re-write file handle - //------------------------------------------------------------------------ - void ReWriteFileHandle( Message *msg ); - - //------------------------------------------------------------------------ - //! Reset monitoring vars - //------------------------------------------------------------------------ - void ResetMonitoringVars() - { - pOpenTime.tv_sec = 0; pOpenTime.tv_usec = 0; - pRBytes = 0; - pVRBytes = 0; - pWBytes = 0; - pVSegs = 0; - pRCount = 0; - pVRCount = 0; - pWCount = 0; - pCloseReason = Status(); - } - - //------------------------------------------------------------------------ - //! Dispatch monitoring information on close - //------------------------------------------------------------------------ - void MonitorClose( const XRootDStatus *status ); - - //------------------------------------------------------------------------ - //! - //------------------------------------------------------------------------ - inline XRootDStatus ExamineLocalResult( XRootDStatus &status, Message *msg, - ResponseHandler *handler ); - - mutable XrdSysMutex pMutex; - FileStatus pFileState; - XRootDStatus pStatus; - StatInfo *pStatInfo; - URL *pFileUrl; - URL *pDataServer; - URL *pLoadBalancer; - URL *pStateRedirect; - uint8_t *pFileHandle; - uint16_t pOpenMode; - uint16_t pOpenFlags; - RequestList pToBeRecovered; - std::set pInTheFly; - uint64_t pSessionId; - bool pDoRecoverRead; - bool pDoRecoverWrite; - bool pFollowRedirects; - bool pUseVirtRedirector; - - //------------------------------------------------------------------------ - // Monitoring variables - //------------------------------------------------------------------------ - timeval pOpenTime; - uint64_t pRBytes; - uint64_t pVRBytes; - uint64_t pWBytes; - uint64_t pVWBytes; - uint64_t pVSegs; - uint64_t pRCount; - uint64_t pVRCount; - uint64_t pWCount; - uint64_t pVWCount; - XRootDStatus pCloseReason; - - //------------------------------------------------------------------------ - // Holds the OpenHanlder used to issue reopen - // (there is only only OpenHandler reopening a file at a time) - //------------------------------------------------------------------------ - ResponseHandlerHolder *pReOpenHandler; - - //------------------------------------------------------------------------ - // Responsible for file:// operations on the local filesystem - //------------------------------------------------------------------------ - LocalFileHandler *pLFileHandler; - }; -} - -#endif // __XRD_CL_FILE_STATE_HANDLER_HH__ diff --git a/src/XrdCl/XrdClFileSystem.cc b/src/XrdCl/XrdClFileSystem.cc deleted file mode 100644 index 482de96cbfd..00000000000 --- a/src/XrdCl/XrdClFileSystem.cc +++ /dev/null @@ -1,1628 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2014 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#include "XrdCl/XrdClMessageUtils.hh" -#include "XrdCl/XrdClFileSystem.hh" -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdCl/XrdClLog.hh" -#include "XrdCl/XrdClConstants.hh" -#include "XrdCl/XrdClMessage.hh" -#include "XrdCl/XrdClXRootDResponses.hh" -#include "XrdCl/XrdClRequestSync.hh" -#include "XrdCl/XrdClXRootDTransport.hh" -#include "XrdCl/XrdClForkHandler.hh" -#include "XrdCl/XrdClPlugInInterface.hh" -#include "XrdCl/XrdClPlugInManager.hh" -#include "XrdSys/XrdSysPthread.hh" - -#include - -namespace -{ - - //---------------------------------------------------------------------------- - // Get delimiter for the opaque info - //---------------------------------------------------------------------------- - char GetCgiDelimiter( bool &hasCgi ) - { - if( !hasCgi ) - { - hasCgi = true; - return '?'; - } - - return '&'; - } - //---------------------------------------------------------------------------- - // Filters out client specific CGI - //---------------------------------------------------------------------------- - std::string FilterXrdClCgi( const std::string &path ) - { - // first check if there's an opaque info at all - size_t pos = path.find( '?' ); - if( pos == std::string::npos ) - return path; - - std::string filteredPath = path.substr( 0 , pos ); - std::string cgi = path.substr( pos + 1 ); - - bool hasCgi = false; - pos = 0; - size_t xrdcl = std::string::npos; - do - { - xrdcl = cgi.find( "xrdcl.", pos ); - - if( xrdcl == std::string:: npos ) - { - filteredPath += GetCgiDelimiter( hasCgi ); - filteredPath += cgi.substr( pos ); - pos = cgi.size(); - } - else - { - if( xrdcl != pos ) - { - filteredPath += GetCgiDelimiter( hasCgi ); - filteredPath += cgi.substr( pos, xrdcl - 1 - pos ); - } - - pos = cgi.find( '&', xrdcl ); - if( pos != std::string::npos ) - ++pos; - } - - } - while( pos < cgi.size() && pos != std::string::npos ); - - return filteredPath; - } - - //---------------------------------------------------------------------------- - //! Wrapper class used to delete FileSystem object - //---------------------------------------------------------------------------- - class DeallocFSHandler: public XrdCl::ResponseHandler - { - public: - //------------------------------------------------------------------------ - // Constructor and destructor - //------------------------------------------------------------------------ - DeallocFSHandler( XrdCl::FileSystem *fs, ResponseHandler *userHandler ): - pFS(fs), pUserHandler(userHandler) {} - - virtual ~DeallocFSHandler() - { - delete pFS; - } - - //------------------------------------------------------------------------ - // Handle the response - //------------------------------------------------------------------------ - virtual void HandleResponse( XrdCl::XRootDStatus *status, - XrdCl::AnyObject *response ) - { - pUserHandler->HandleResponse(status, response); - delete this; - } - - private: - XrdCl::FileSystem *pFS; - ResponseHandler *pUserHandler; - }; - - //---------------------------------------------------------------------------- - // Deep locate handler - //---------------------------------------------------------------------------- - class DeepLocateHandler: public XrdCl::ResponseHandler - { - public: - //------------------------------------------------------------------------ - // Constructor - //------------------------------------------------------------------------ - DeepLocateHandler( XrdCl::ResponseHandler *handler, - const std::string &path, - XrdCl::OpenFlags::Flags flags, - time_t expires ): - pFirstTime( true ), - pPartial( false ), - pOutstanding( 1 ), - pHandler( handler ), - pPath( path ), - pFlags( flags ), - pExpires(expires) - { - pLocations = new XrdCl::LocationInfo(); - } - - //------------------------------------------------------------------------ - // Destructor - //------------------------------------------------------------------------ - ~DeepLocateHandler() - { - delete pLocations; - } - - //------------------------------------------------------------------------ - // Handle the response - //------------------------------------------------------------------------ - virtual void HandleResponse( XrdCl::XRootDStatus *status, - XrdCl::AnyObject *response ) - { - XrdSysMutexHelper scopedLock( pMutex ); - using namespace XrdCl; - Log *log = DefaultEnv::GetLog(); - --pOutstanding; - - //---------------------------------------------------------------------- - // We've got an error, react accordingly - //---------------------------------------------------------------------- - if( !status->IsOK() ) - { - log->Dump( FileSystemMsg, "[0x%x@DeepLocate(%s)] Got error " - "response: %s", this, pPath.c_str(), - status->ToStr().c_str() ); - - //-------------------------------------------------------------------- - // We have failed with the first request - //-------------------------------------------------------------------- - if( pFirstTime ) - { - log->Debug( FileSystemMsg, "[0x%x@DeepLocate(%s)] Failed to get " - "the initial location list: %s", this, pPath.c_str(), - status->ToStr().c_str() ); - pHandler->HandleResponse( status, response ); - scopedLock.UnLock(); - delete this; - return; - } - - pPartial = true; - - //-------------------------------------------------------------------- - // We have no more outstanding requests, so let give to the client - // what we have - //-------------------------------------------------------------------- - if( !pOutstanding ) - { - log->Debug( FileSystemMsg, "[0x%x@DeepLocate(%s)] No outstanding " - "requests, give out what we've got", this, - pPath.c_str() ); - scopedLock.UnLock(); - HandleFinalResponse(); - } - delete status; - return; - } - pFirstTime = false; - - //---------------------------------------------------------------------- - // Extract the answer - //---------------------------------------------------------------------- - LocationInfo *info = 0; - response->Get( info ); - LocationInfo::Iterator it; - - log->Dump( FileSystemMsg, "[0x%x@DeepLocate(%s)] Got %d locations", - this, pPath.c_str(), info->GetSize() ); - - for( it = info->Begin(); it != info->End(); ++it ) - { - //-------------------------------------------------------------------- - // Add the location to the list - //-------------------------------------------------------------------- - if( it->IsServer() ) - { - pLocations->Add( *it ); - continue; - } - - //-------------------------------------------------------------------- - // Ask the manager for the location of servers - //-------------------------------------------------------------------- - if( it->IsManager() ) - { - FileSystem *fs = new FileSystem( it->GetAddress() ); - if( fs->Locate( pPath, pFlags, new DeallocFSHandler(fs, this), pExpires-::time(0)).IsOK() ) - ++pOutstanding; - } - } - - //---------------------------------------------------------------------- - // Clean up and check if we have anything else to do - //---------------------------------------------------------------------- - delete response; - delete status; - if( !pOutstanding ) - { - scopedLock.UnLock(); - HandleFinalResponse(); - } - } - - //------------------------------------------------------------------------ - // Build the response for the client - //------------------------------------------------------------------------ - void HandleFinalResponse() - { - using namespace XrdCl; - - //---------------------------------------------------------------------- - // Nothing found - //---------------------------------------------------------------------- - if( !pLocations->GetSize() ) - { - pHandler->HandleResponse( new XRootDStatus( stError, errErrorResponse, - kXR_NotFound, - "No valid location found" ), - 0 ); - } - //---------------------------------------------------------------------- - // We return an answer - //---------------------------------------------------------------------- - else - { - AnyObject *obj = new AnyObject(); - obj->Set( pLocations ); - pLocations = 0; - XRootDStatus *st = new XRootDStatus(); - if( pPartial ) st->code = suPartial; - pHandler->HandleResponse( st, obj ); - } - delete this; - } - - private: - bool pFirstTime; - bool pPartial; - uint16_t pOutstanding; - XrdCl::ResponseHandler *pHandler; - XrdCl::LocationInfo *pLocations; - std::string pPath; - XrdCl::OpenFlags::Flags pFlags; - time_t pExpires; - XrdSysMutex pMutex; - }; - - //---------------------------------------------------------------------------- - // Handle stat results for a dirlist request - //---------------------------------------------------------------------------- - class DirListStatHandler: public XrdCl::ResponseHandler - { - public: - //------------------------------------------------------------------------ - // Constructor - //------------------------------------------------------------------------ - DirListStatHandler( XrdCl::DirectoryList *list, - uint32_t index, - XrdCl::RequestSync *sync ): - pList( list ), - pIndex( index ), - pSync( sync ) - { - } - - //------------------------------------------------------------------------ - // Check if we were successful and if so put the StatInfo object - // in the appropriate entry info - //------------------------------------------------------------------------ - virtual void HandleResponse( XrdCl::XRootDStatus *status, - XrdCl::AnyObject *response ) - { - if( !status->IsOK() ) - { - delete status; - pSync->TaskDone( false ); - delete this; - return; - } - - XrdCl::StatInfo *info = 0; - response->Get( info ); - response->Set( (char*) 0 ); - pList->At( pIndex )->SetStatInfo( info ); - delete status; - delete response; - pSync->TaskDone(); - delete this; - } - - private: - XrdCl::DirectoryList *pList; - uint32_t pIndex; - XrdCl::RequestSync *pSync; - }; - - //---------------------------------------------------------------------------- - // Recursive dirlist common context for all handlers - //---------------------------------------------------------------------------- - struct RecursiveDirListCtx - { - RecursiveDirListCtx( const XrdCl::URL &url, const std::string &path, - XrdCl::DirListFlags::Flags flags, - XrdCl::ResponseHandler *handler, time_t expires ) : - pending( 1 ), dirList( new XrdCl::DirectoryList() ), - expires( expires ), handler( handler ), flags( flags ), - fs( new XrdCl::FileSystem( url ) ) - { - dirList->SetParentName( path ); - } - - ~RecursiveDirListCtx() - { - delete dirList; - delete fs; - } - - XrdCl::XRootDStatus status; - int pending; - XrdCl::DirectoryList *dirList; - time_t expires; - XrdCl::ResponseHandler *handler; - XrdCl::DirListFlags::Flags flags; - XrdCl::FileSystem *fs; - XrdSysMutex mtx; - }; - - //---------------------------------------------------------------------------- - // Exception for a recursive dirlist handler - //---------------------------------------------------------------------------- - struct RecDirLsErr - { - RecDirLsErr() : status( 0 ) { } - - RecDirLsErr( const XrdCl::XRootDStatus &st ) : - status( new XrdCl::XRootDStatus( st ) ) - { - - } - - XrdCl::XRootDStatus *status; - }; - - //---------------------------------------------------------------------------- - // Handle results for a recursive dirlist request - //---------------------------------------------------------------------------- - class RecursiveDirListHandler: public XrdCl::ResponseHandler - { - public: - - RecursiveDirListHandler( const XrdCl::URL &url, - const std::string &path, - XrdCl::DirListFlags::Flags flags, - XrdCl::ResponseHandler *handler, - time_t timeout ) - { - time_t expires = 0; - if( timeout ) - expires = ::time( 0 ) + timeout; - pCtx = new RecursiveDirListCtx( url, path, flags, - handler, expires ); - } - - RecursiveDirListHandler( RecursiveDirListCtx *ctx ) : pCtx( ctx ) - { - - } - - virtual void HandleResponse( XrdCl::XRootDStatus *status, - XrdCl::AnyObject *response ) - { - using namespace XrdCl; - - XrdSysMutexHelper scoped( pCtx->mtx ); - - try - { - // decrement the number of pending DieLists - --pCtx->pending; - - // check if the job hasn't failed somewhere else, - // if yes we just give up - if( !pCtx->status.IsOK() ) - throw RecDirLsErr(); - - // check if we failed, if yes update the global status - // for all call-backs and call the user handler - if( !status->IsOK() ) - throw RecDirLsErr( *status ); - - // check if we got a response ... - if( !response ) - throw RecDirLsErr( XRootDStatus( stError, errInternal ) ); - - // get the response - DirectoryList *dirList = 0; - response->Get( dirList ); - - // check if the response is not empty ... - if( !dirList ) - throw RecDirLsErr( XRootDStatus( stError, errInternal ) ); - - std::string parent = pCtx->dirList->GetParentName(); - - DirectoryList::Iterator itr; - for( itr = dirList->Begin(); itr != dirList->End(); ++itr ) - { - DirectoryList::ListEntry *entry = *itr; - StatInfo *info = entry->GetStatInfo(); - if( !info ) - throw RecDirLsErr( XRootDStatus( stError, errNotSupported ) ); - std::string path = dirList->GetParentName() + entry->GetName(); - - // check the prefix - if( path.find( parent ) != 0 ) - throw RecDirLsErr( XRootDStatus( stError, errInternal ) ); - - // add new entry to the result - path = path.substr( parent.size() ); - entry->SetStatInfo( 0 ); // StatInfo is no longer owned by dirList - DirectoryList::ListEntry *e = - new DirectoryList::ListEntry( entry->GetHostAddress(), path, info ); - pCtx->dirList->Add( e ); - - // if it's a directory do a recursive call - if( info->TestFlags( StatInfo::IsDir ) ) - { - // bump the pending counter - ++pCtx->pending; - // switch of the recursive flag, we will - // provide the respective handler ourself, - // make sure that stat is on - DirListFlags::Flags flags = ( pCtx->flags & (~DirListFlags::Recursive) ) - | DirListFlags::Stat; - // the recursive dir list handler - RecursiveDirListHandler *handler = new RecursiveDirListHandler( pCtx ); - // timeout - time_t timeout = 0; - if( pCtx->expires ) - { - timeout = pCtx->expires - ::time( 0 ); - if( timeout <= 0 ) - throw RecDirLsErr( XRootDStatus( stError, errOperationExpired ) ); - } - // send the request - XRootDStatus st = pCtx->fs->DirList( parent + path, flags, handler, timeout ); - if( !st.IsOK() ) - throw RecDirLsErr( st ); - } - } - - if( pCtx->pending == 0 ) - { - AnyObject *resp = new AnyObject(); - resp->Set( pCtx->dirList ); - pCtx->dirList = 0; // dirList is no longer owned by pCtx - pCtx->handler->HandleResponse( new XRootDStatus(), resp ); - } - } - catch( RecDirLsErr &ex ) - { - if( ex.status ) - { - pCtx->status = *ex.status; - pCtx->handler->HandleResponse( ex.status, 0 ); - } - } - - // clean up the context if necessary - bool delctx = ( pCtx->pending == 0 ); - scoped.UnLock(); - if( delctx ) - delete pCtx; - // clean up the arguments - delete status; - delete response; - // and finally commit suicide - delete this; - } - - - private: - - RecursiveDirListCtx *pCtx; - }; - - //---------------------------------------------------------------------------- - // Exception for a merge dirlist handler - //---------------------------------------------------------------------------- - struct MergeDirLsErr - { - MergeDirLsErr( XrdCl::XRootDStatus *&status, XrdCl::AnyObject *&response ) : - status( status ), response( response ) - { - status = 0; response = 0; - } - - MergeDirLsErr() : - status( new XrdCl::XRootDStatus( XrdCl::stError, XrdCl::errInternal ) ), - response( 0 ) - { - - } - - XrdCl::XRootDStatus *status; - XrdCl::AnyObject *response; - }; - - - - //---------------------------------------------------------------------------- - // Handle results for a merge dirlist request - //---------------------------------------------------------------------------- - class MergeDirListHandler: public XrdCl::ResponseHandler - { - public: - - MergeDirListHandler( XrdCl::ResponseHandler *handler ) : pHandler( handler ) - { - - } - - virtual void HandleResponse( XrdCl::XRootDStatus *status, - XrdCl::AnyObject *response ) - { - try - { - if( status->IsOK() ) - throw MergeDirLsErr( status, response ); - - if( !response ) - throw MergeDirLsErr(); - - XrdCl::DirectoryList *dirlist = 0; - response->Get( dirlist ); - - if( !dirlist ) - throw MergeDirLsErr(); - - Merge( dirlist ); - response->Set( dirlist ); - pHandler->HandleResponse( status, response ); - } - catch( const MergeDirLsErr &err ) - { - delete status; delete response; - pHandler->HandleResponse( err.status, err.response ); - } - - delete this; - } - - static void Merge( XrdCl::DirectoryList *&response ) - { - std::set unique( response->Begin(), response->End() ); - - XrdCl::DirectoryList *dirlist = new XrdCl::DirectoryList(); - dirlist->SetParentName( response->GetParentName() ); - for( auto itr = unique.begin(); itr != unique.end(); ++itr ) - { - ListEntry *entry = *itr; - dirlist->Add( new ListEntry( entry->GetHostAddress(), - entry->GetName(), - entry->GetStatInfo() ) ); - entry->SetStatInfo( 0 ); - } - - delete response; - response = dirlist; - } - - private: - - typedef XrdCl::DirectoryList::ListEntry ListEntry; - - struct less - { - bool operator() (const ListEntry *x, const ListEntry *y) const - { - if( x->GetName() != y->GetName() ) - return x->GetName() < y->GetName(); - - const XrdCl::StatInfo *xStatInfo = x->GetStatInfo(); - const XrdCl::StatInfo *yStatInfo = y->GetStatInfo(); - - if( xStatInfo == yStatInfo ) - return false; - - if( xStatInfo == 0 ) - return true; - - if( yStatInfo == 0 ) - return false; - - if( xStatInfo->GetSize() != yStatInfo->GetSize() ) - return xStatInfo->GetSize() < yStatInfo->GetSize(); - - if( xStatInfo->GetFlags() != yStatInfo->GetFlags() ) - return xStatInfo->GetFlags() < yStatInfo->GetFlags(); - - return false; - } - }; - - XrdCl::ResponseHandler *pHandler; - }; -} - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - //! Wrapper class used to assign a load balancer - //---------------------------------------------------------------------------- - class AssignLBHandler: public ResponseHandler - { - public: - //------------------------------------------------------------------------ - // Constructor and destructor - //------------------------------------------------------------------------ - AssignLBHandler( FileSystem *fs, ResponseHandler *userHandler ): - pFS(fs), pUserHandler(userHandler) {} - - virtual ~AssignLBHandler() {} - - //------------------------------------------------------------------------ - // Response callback - //------------------------------------------------------------------------ - virtual void HandleResponseWithHosts( XRootDStatus *status, - AnyObject *response, - HostList *hostList ) - { - if( status->IsOK() ) - { - HostList::reverse_iterator it; - for( it = hostList->rbegin(); it != hostList->rend(); ++it ) - if( it->loadBalancer ) - { - pFS->AssignLoadBalancer( it->url ); - break; - } - } - pUserHandler->HandleResponseWithHosts( status, response, hostList ); - delete this; - } - - private: - FileSystem *pFS; - ResponseHandler *pUserHandler; - }; - - - //---------------------------------------------------------------------------- - // Constructor - //---------------------------------------------------------------------------- - FileSystem::FileSystem( const URL &url, bool enablePlugIns ): - pLoadBalancerLookupDone( false ), - pFollowRedirects( true ), - pPlugIn(0) - { - pUrl = new URL( url.GetURL() ); - - //-------------------------------------------------------------------------- - // Check if we need to install a plug-in for this URL - //-------------------------------------------------------------------------- - if( enablePlugIns ) - { - Log *log = DefaultEnv::GetLog(); - std::string urlStr = url.GetURL(); - PlugInFactory *fact = DefaultEnv::GetPlugInManager()->GetFactory(urlStr); - if( fact ) - { - pPlugIn = fact->CreateFileSystem( urlStr ); - if( !pPlugIn ) - { - log->Error( FileMsg, "Plug-in factory failed to produce a plug-in " - "for %s, continuing without one", urlStr.c_str() ); - } - } - } - - if( !pPlugIn ) - DefaultEnv::GetForkHandler()->RegisterFileSystemObject( this ); - } - - //---------------------------------------------------------------------------- - // Destructor - //---------------------------------------------------------------------------- - FileSystem::~FileSystem() - { - if( !pPlugIn ) - { - if( DefaultEnv::GetForkHandler() ) - DefaultEnv::GetForkHandler()->UnRegisterFileSystemObject( this ); - } - - delete pUrl; - delete pPlugIn; - } - - //---------------------------------------------------------------------------- - // Locate a file - async - //---------------------------------------------------------------------------- - XRootDStatus FileSystem::Locate( const std::string &path, - OpenFlags::Flags flags, - ResponseHandler *handler, - uint16_t timeout ) - { - if( pPlugIn ) - return pPlugIn->Locate( path, flags, handler, timeout ); - - std::string fPath = FilterXrdClCgi( path ); - - Message *msg; - ClientLocateRequest *req; - MessageUtils::CreateRequest( msg, req, fPath.length() ); - - req->requestid = kXR_locate; - req->options = flags; - req->dlen = fPath.length(); - msg->Append( fPath.c_str(), fPath.length(), 24 ); - MessageSendParams params; params.timeout = timeout; - MessageUtils::ProcessSendParams( params ); - - XRootDTransport::SetDescription( msg ); - - return Send( msg, handler, params ); - } - - //---------------------------------------------------------------------------- - // Locate a file - sync - //---------------------------------------------------------------------------- - XRootDStatus FileSystem::Locate( const std::string &path, - OpenFlags::Flags flags, - LocationInfo *&response, - uint16_t timeout ) - { - SyncResponseHandler handler; - Status st = Locate( path, flags, &handler, timeout ); - if( !st.IsOK() ) - return st; - - return MessageUtils::WaitForResponse( &handler, response ); - } - - //---------------------------------------------------------------------------- - // Locate a file, recursively locate all disk servers - async - //---------------------------------------------------------------------------- - XRootDStatus FileSystem::DeepLocate( const std::string &path, - OpenFlags::Flags flags, - ResponseHandler *handler, - uint16_t timeout ) - { - return Locate( path, flags, - new DeepLocateHandler( handler, path, flags, - ::time(0)+timeout ), - timeout ); - } - - //---------------------------------------------------------------------------- - // Locate a file, recursively locate all disk servers - sync - //---------------------------------------------------------------------------- - XRootDStatus FileSystem::DeepLocate( const std::string &path, - OpenFlags::Flags flags, - LocationInfo *&response, - uint16_t timeout ) - { - SyncResponseHandler handler; - Status st = DeepLocate( path, flags, &handler, timeout ); - if( !st.IsOK() ) - return st; - - return MessageUtils::WaitForResponse( &handler, response ); - } - - //---------------------------------------------------------------------------- - // Move a directory or a file - async - //---------------------------------------------------------------------------- - XRootDStatus FileSystem::Mv( const std::string &source, - const std::string &dest, - ResponseHandler *handler, - uint16_t timeout ) - { - if( pPlugIn ) - return pPlugIn->Mv( source, dest, handler, timeout ); - - std::string fSource = FilterXrdClCgi( source ); - std::string fDest = FilterXrdClCgi( dest ); - - Message *msg; - ClientMvRequest *req; - MessageUtils::CreateRequest( msg, req, fSource.length() + fDest.length()+1 ); - - req->requestid = kXR_mv; - req->dlen = fSource.length() + fDest.length()+1; - req->arg1len = fSource.length(); - msg->Append( fSource.c_str(), fSource.length(), 24 ); - *msg->GetBuffer(24 + fSource.length()) = ' '; - msg->Append( fDest.c_str(), fDest.length(), 25 + fSource.length() ); - MessageSendParams params; params.timeout = timeout; - MessageUtils::ProcessSendParams( params ); - - XRootDTransport::SetDescription( msg ); - - return Send( msg, handler, params ); - } - - //---------------------------------------------------------------------------- - // Move a directory or a file - sync - //---------------------------------------------------------------------------- - XRootDStatus FileSystem::Mv( const std::string &source, - const std::string &dest, - uint16_t timeout ) - { - SyncResponseHandler handler; - Status st = Mv( source, dest, &handler, timeout ); - if( !st.IsOK() ) - return st; - - return MessageUtils::WaitForStatus( &handler ); - } - - //---------------------------------------------------------------------------- - // Obtain server information - async - //---------------------------------------------------------------------------- - XRootDStatus FileSystem::Query( QueryCode::Code queryCode, - const Buffer &arg, - ResponseHandler *handler, - uint16_t timeout ) - { - if( pPlugIn ) - return pPlugIn->Query( queryCode, arg, handler, timeout ); - - Message *msg; - ClientQueryRequest *req; - MessageUtils::CreateRequest( msg, req, arg.GetSize() ); - - req->requestid = kXR_query; - req->infotype = queryCode; - req->dlen = arg.GetSize(); - msg->Append( arg.GetBuffer(), arg.GetSize(), 24 ); - MessageSendParams params; params.timeout = timeout; - MessageUtils::ProcessSendParams( params ); - XRootDTransport::SetDescription( msg ); - - return Send( msg, handler, params ); - } - - //---------------------------------------------------------------------------- - // Obtain server information - sync - //---------------------------------------------------------------------------- - XRootDStatus FileSystem::Query( QueryCode::Code queryCode, - const Buffer &arg, - Buffer *&response, - uint16_t timeout ) - { - SyncResponseHandler handler; - Status st = Query( queryCode, arg, &handler, timeout ); - if( !st.IsOK() ) - return st; - - return MessageUtils::WaitForResponse( &handler, response ); - } - - //---------------------------------------------------------------------------- - // Truncate a file - async - //---------------------------------------------------------------------------- - XRootDStatus FileSystem::Truncate( const std::string &path, - uint64_t size, - ResponseHandler *handler, - uint16_t timeout ) - { - if( pPlugIn ) - return pPlugIn->Truncate( path, size, handler, timeout ); - - std::string fPath = FilterXrdClCgi( path ); - - Message *msg; - ClientTruncateRequest *req; - MessageUtils::CreateRequest( msg, req, fPath.length() ); - - req->requestid = kXR_truncate; - req->offset = size; - req->dlen = fPath.length(); - msg->Append( fPath.c_str(), fPath.length(), 24 ); - MessageSendParams params; params.timeout = timeout; - MessageUtils::ProcessSendParams( params ); - XRootDTransport::SetDescription( msg ); - - return Send( msg, handler, params ); - } - - //---------------------------------------------------------------------------- - // Truncate a file - sync - //---------------------------------------------------------------------------- - XRootDStatus FileSystem::Truncate( const std::string &path, - uint64_t size, - uint16_t timeout ) - { - SyncResponseHandler handler; - Status st = Truncate( path, size, &handler, timeout ); - if( !st.IsOK() ) - return st; - - return MessageUtils::WaitForStatus( &handler ); - } - - //---------------------------------------------------------------------------- - // Remove a file - async - //---------------------------------------------------------------------------- - XRootDStatus FileSystem::Rm( const std::string &path, - ResponseHandler *handler, - uint16_t timeout ) - { - if( pPlugIn ) - return pPlugIn->Rm( path, handler, timeout ); - - std::string fPath = FilterXrdClCgi( path ); - - Message *msg; - ClientRmRequest *req; - MessageUtils::CreateRequest( msg, req, fPath.length() ); - - req->requestid = kXR_rm; - req->dlen = fPath.length(); - msg->Append( fPath.c_str(), fPath.length(), 24 ); - MessageSendParams params; params.timeout = timeout; - MessageUtils::ProcessSendParams( params ); - XRootDTransport::SetDescription( msg ); - - return Send( msg, handler, params ); - } - - //---------------------------------------------------------------------------- - // Remove a file - sync - //---------------------------------------------------------------------------- - XRootDStatus FileSystem::Rm( const std::string &path, - uint16_t timeout ) - { - SyncResponseHandler handler; - Status st = Rm( path, &handler, timeout ); - if( !st.IsOK() ) - return st; - - return MessageUtils::WaitForStatus( &handler ); - } - - //---------------------------------------------------------------------------- - // Create a directory - async - //---------------------------------------------------------------------------- - XRootDStatus FileSystem::MkDir( const std::string &path, - MkDirFlags::Flags flags, - Access::Mode mode, - ResponseHandler *handler, - uint16_t timeout ) - { - if( pPlugIn ) - return pPlugIn->MkDir( path, flags, mode, handler, timeout ); - - std::string fPath = FilterXrdClCgi( path ); - - Message *msg; - ClientMkdirRequest *req; - MessageUtils::CreateRequest( msg, req, fPath.length() ); - - req->requestid = kXR_mkdir; - req->options[0] = flags; - req->mode = mode; - req->dlen = fPath.length(); - msg->Append( fPath.c_str(), fPath.length(), 24 ); - MessageSendParams params; params.timeout = timeout; - MessageUtils::ProcessSendParams( params ); - XRootDTransport::SetDescription( msg ); - - return Send( msg, handler, params ); - } - - //---------------------------------------------------------------------------- - // Create a directory - sync - //---------------------------------------------------------------------------- - XRootDStatus FileSystem::MkDir( const std::string &path, - MkDirFlags::Flags flags, - Access::Mode mode, - uint16_t timeout ) - { - SyncResponseHandler handler; - Status st = MkDir( path, flags, mode, &handler, timeout ); - if( !st.IsOK() ) - return st; - - return MessageUtils::WaitForStatus( &handler ); - } - - //---------------------------------------------------------------------------- - // Remove a directory - async - //---------------------------------------------------------------------------- - XRootDStatus FileSystem::RmDir( const std::string &path, - ResponseHandler *handler, - uint16_t timeout ) - { - if( pPlugIn ) - return pPlugIn->RmDir( path, handler, timeout ); - - std::string fPath = FilterXrdClCgi( path ); - - Message *msg; - ClientRmdirRequest *req; - MessageUtils::CreateRequest( msg, req, fPath.length() ); - - req->requestid = kXR_rmdir; - req->dlen = fPath.length(); - msg->Append( fPath.c_str(), fPath.length(), 24 ); - MessageSendParams params; params.timeout = timeout; - MessageUtils::ProcessSendParams( params ); - XRootDTransport::SetDescription( msg ); - - return Send( msg, handler, params ); - } - - //---------------------------------------------------------------------------- - // Remove a directory - sync - //---------------------------------------------------------------------------- - XRootDStatus FileSystem::RmDir( const std::string &path, - uint16_t timeout ) - { - SyncResponseHandler handler; - Status st = RmDir( path, &handler, timeout ); - if( !st.IsOK() ) - return st; - - return MessageUtils::WaitForStatus( &handler ); - } - - //---------------------------------------------------------------------------- - // Change access mode on a directory or a file - async - //---------------------------------------------------------------------------- - XRootDStatus FileSystem::ChMod( const std::string &path, - Access::Mode mode, - ResponseHandler *handler, - uint16_t timeout ) - { - if( pPlugIn ) - return pPlugIn->ChMod( path, mode, handler, timeout ); - - std::string fPath = FilterXrdClCgi( path ); - - Message *msg; - ClientChmodRequest *req; - MessageUtils::CreateRequest( msg, req, fPath.length() ); - - req->requestid = kXR_chmod; - req->mode = mode; - req->dlen = fPath.length(); - msg->Append( fPath.c_str(), fPath.length(), 24 ); - MessageSendParams params; params.timeout = timeout; - MessageUtils::ProcessSendParams( params ); - XRootDTransport::SetDescription( msg ); - - return Send( msg, handler, params ); - } - - //---------------------------------------------------------------------------- - // Change access mode on a directory or a file - async - //---------------------------------------------------------------------------- - XRootDStatus FileSystem::ChMod( const std::string &path, - Access::Mode mode, - uint16_t timeout ) - { - SyncResponseHandler handler; - Status st = ChMod( path, mode, &handler, timeout ); - if( !st.IsOK() ) - return st; - - return MessageUtils::WaitForStatus( &handler ); - } - - //---------------------------------------------------------------------------- - // Check if the server is alive - async - //---------------------------------------------------------------------------- - XRootDStatus FileSystem::Ping( ResponseHandler *handler, - uint16_t timeout ) - { - if( pPlugIn ) - return pPlugIn->Ping( handler, timeout ); - - Message *msg; - ClientPingRequest *req; - MessageUtils::CreateRequest( msg, req ); - - req->requestid = kXR_ping; - MessageSendParams params; params.timeout = timeout; - MessageUtils::ProcessSendParams( params ); - XRootDTransport::SetDescription( msg ); - - return Send( msg, handler, params ); - } - - //---------------------------------------------------------------------------- - // Check if the server is alive - sync - //---------------------------------------------------------------------------- - XRootDStatus FileSystem::Ping( uint16_t timeout ) - { - SyncResponseHandler handler; - Status st = Ping( &handler, timeout ); - if( !st.IsOK() ) - return st; - - return MessageUtils::WaitForStatus( &handler ); - } - - //---------------------------------------------------------------------------- - // Obtain status information for a path - async - //---------------------------------------------------------------------------- - XRootDStatus FileSystem::Stat( const std::string &path, - ResponseHandler *handler, - uint16_t timeout ) - { - if( pPlugIn ) - return pPlugIn->Stat( path, handler, timeout ); - - std::string fPath = FilterXrdClCgi( path ); - - Message *msg; - ClientStatRequest *req; - MessageUtils::CreateRequest( msg, req, fPath.length() ); - - req->requestid = kXR_stat; - req->options = 0; - req->dlen = fPath.length(); - msg->Append( fPath.c_str(), fPath.length(), 24 ); - MessageSendParams params; params.timeout = timeout; - MessageUtils::ProcessSendParams( params ); - XRootDTransport::SetDescription( msg ); - - return Send( msg, handler, params ); - } - - //---------------------------------------------------------------------------- - // Obtain status information for a path - sync - //---------------------------------------------------------------------------- - XRootDStatus FileSystem::Stat( const std::string &path, - StatInfo *&response, - uint16_t timeout ) - { - SyncResponseHandler handler; - Status st = Stat( path, &handler, timeout ); - if( !st.IsOK() ) - return st; - - return MessageUtils::WaitForResponse( &handler, response ); - } - - //---------------------------------------------------------------------------- - // Obtain status information for a path - async - //---------------------------------------------------------------------------- - XRootDStatus FileSystem::StatVFS( const std::string &path, - ResponseHandler *handler, - uint16_t timeout ) - { - if( pPlugIn ) - return pPlugIn->StatVFS( path, handler, timeout ); - - std::string fPath = FilterXrdClCgi( path ); - - Message *msg; - ClientStatRequest *req; - MessageUtils::CreateRequest( msg, req, fPath.length() ); - - req->requestid = kXR_stat; - req->options = kXR_vfs; - req->dlen = fPath.length(); - msg->Append( fPath.c_str(), fPath.length(), 24 ); - MessageSendParams params; params.timeout = timeout; - MessageUtils::ProcessSendParams( params ); - XRootDTransport::SetDescription( msg ); - - return Send( msg, handler, params ); - } - - //---------------------------------------------------------------------------- - // Obtain status information for a path - sync - //---------------------------------------------------------------------------- - XRootDStatus FileSystem::StatVFS( const std::string &path, - StatInfoVFS *&response, - uint16_t timeout ) - { - SyncResponseHandler handler; - Status st = StatVFS( path, &handler, timeout ); - if( !st.IsOK() ) - return st; - - return MessageUtils::WaitForResponse( &handler, response ); - } - - //---------------------------------------------------------------------------- - // Obtain server protocol information - async - //---------------------------------------------------------------------------- - XRootDStatus FileSystem::Protocol( ResponseHandler *handler, - uint16_t timeout ) - { - if( pPlugIn ) - return pPlugIn->Protocol( handler, timeout ); - - Message *msg; - ClientProtocolRequest *req; - MessageUtils::CreateRequest( msg, req ); - - req->requestid = kXR_protocol; - req->clientpv = kXR_PROTOCOLVERSION; - MessageSendParams params; params.timeout = timeout; - MessageUtils::ProcessSendParams( params ); - XRootDTransport::SetDescription( msg ); - - return Send( msg, handler, params ); - } - - //---------------------------------------------------------------------------- - // Obtain server protocol information - sync - //---------------------------------------------------------------------------- - XRootDStatus FileSystem::Protocol( ProtocolInfo *&response, - uint16_t timeout ) - { - SyncResponseHandler handler; - Status st = Protocol( &handler, timeout ); - if( !st.IsOK() ) - return st; - - return MessageUtils::WaitForResponse( &handler, response ); - } - - //---------------------------------------------------------------------------- - // List entries of a directory - async - //---------------------------------------------------------------------------- - XRootDStatus FileSystem::DirList( const std::string &path, - DirListFlags::Flags flags, - ResponseHandler *handler, - uint16_t timeout ) - { - if( pPlugIn ) - return pPlugIn->DirList( path, flags, handler, timeout ); - - URL url = URL( path ); - std::string fPath = FilterXrdClCgi( path ); - - Message *msg; - ClientDirlistRequest *req; - MessageUtils::CreateRequest( msg, req, fPath.length() ); - - req->requestid = kXR_dirlist; - req->dlen = fPath.length(); - - if( ( flags & DirListFlags::Stat ) || ( flags & DirListFlags::Recursive ) ) - req->options[0] = kXR_dstat; - - if( flags & DirListFlags::Recursive ) - handler = new RecursiveDirListHandler( *pUrl, url.GetPath(), flags, handler, timeout ); - - if( flags & DirListFlags::Merge ) - handler = new MergeDirListHandler( handler ); - - msg->Append( fPath.c_str(), fPath.length(), 24 ); - MessageSendParams params; params.timeout = timeout; - MessageUtils::ProcessSendParams( params ); - XRootDTransport::SetDescription( msg ); - - return Send( msg, handler, params ); - } - - //---------------------------------------------------------------------------- - // List entries of a directory - sync - //---------------------------------------------------------------------------- - XRootDStatus FileSystem::DirList( const std::string &path, - DirListFlags::Flags flags, - DirectoryList *&response, - uint16_t timeout ) - { - //-------------------------------------------------------------------------- - // We do the deep locate and ask all the returned servers for the list - //-------------------------------------------------------------------------- - if( flags & DirListFlags::Locate ) - { - //------------------------------------------------------------------------ - // Locate all the disk servers holding the directory - //------------------------------------------------------------------------ - LocationInfo *locations; - std::string locatePath = "*"; locatePath += path; - XRootDStatus st = DeepLocate( locatePath, OpenFlags::PrefName, locations ); - - if( !st.IsOK() ) - return st; - - if( locations->GetSize() == 0 ) - { - delete locations; - return XRootDStatus( stError, errNotFound ); - } - - //------------------------------------------------------------------------ - // Ask each server for a directory list - //------------------------------------------------------------------------ - flags &= ~DirListFlags::Locate; - FileSystem *fs; - DirectoryList *currentResp = 0; - uint32_t errors = 0; - uint32_t numLocations = locations->GetSize(); - bool partial = st.code == suPartial ? true : false; - - response = new DirectoryList(); - response->SetParentName( path ); - - for( uint32_t i = 0; i < locations->GetSize(); ++i ) - { - fs = new FileSystem( locations->At(i).GetAddress() ); - st = fs->DirList( path, flags, currentResp, timeout ); - if( !st.IsOK() ) - { - ++errors; - delete fs; - continue; - } - - if( st.code == suPartial ) - partial = true; - - DirectoryList::Iterator it; - - for( it = currentResp->Begin(); it != currentResp->End(); ++it ) - { - response->Add( *it ); - *it = 0; - } - - delete fs; - delete currentResp; - fs = 0; - currentResp = 0; - } - delete locations; - - if( flags & DirListFlags::Merge ) - MergeDirListHandler::Merge( response ); - - if( errors || partial ) - { - if( errors == numLocations ) - return st; - return XRootDStatus( stOK, suPartial ); - } - return XRootDStatus(); - }; - - //-------------------------------------------------------------------------- - // We just ask the current server - //-------------------------------------------------------------------------- - SyncResponseHandler handler; - XRootDStatus st = DirList( path, flags, &handler, timeout ); - if( !st.IsOK() ) - return st; - - st = MessageUtils::WaitForResponse( &handler, response ); - if( !st.IsOK() ) - return st; - - //-------------------------------------------------------------------------- - // Do the stats on all the entries if necessary. - // If we already have the stat objects it means that the bulk stat has - // succeeded. - //-------------------------------------------------------------------------- - if( !(flags & DirListFlags::Stat) ) - return st; - - if( response->GetSize() && response->At(0)->GetStatInfo() ) - return st; - - uint32_t quota = response->GetSize() <= 1024 ? response->GetSize() : 1024; - RequestSync sync( response->GetSize(), quota ); - for( uint32_t i = 0; i < response->GetSize(); ++i ) - { - std::string fullPath = response->GetParentName()+response->At(i)->GetName(); - ResponseHandler *handler = new DirListStatHandler( response, i, &sync ); - st = Stat( fullPath, handler, timeout ); - if( !st.IsOK() ) - { - sync.TaskDone( false ); - delete handler; - } - sync.WaitForQuota(); - } - sync.WaitForAll(); - - if( sync.FailureCount() ) - return XRootDStatus( stOK, suPartial ); - - return XRootDStatus(); - } - - //---------------------------------------------------------------------------- - // Send info to the server - async - //---------------------------------------------------------------------------- - XRootDStatus FileSystem::SendInfo( const std::string &info, - ResponseHandler *handler, - uint16_t timeout ) - { - if( pPlugIn ) - return pPlugIn->SendInfo( info, handler, timeout ); - - Message *msg; - ClientSetRequest *req; - const char *prefix = "monitor info "; - size_t prefixLen = strlen( prefix ); - MessageUtils::CreateRequest( msg, req, info.length()+prefixLen ); - - req->requestid = kXR_set; - req->dlen = info.length()+prefixLen; - msg->Append( prefix, prefixLen, 24 ); - msg->Append( info.c_str(), info.length(), 24+prefixLen ); - MessageSendParams params; params.timeout = timeout; - MessageUtils::ProcessSendParams( params ); - XRootDTransport::SetDescription( msg ); - - return Send( msg, handler, params ); - } - - //---------------------------------------------------------------------------- - //! Send info to the server - sync - //---------------------------------------------------------------------------- - XRootDStatus FileSystem::SendInfo( const std::string &info, - Buffer *&response, - uint16_t timeout ) - { - SyncResponseHandler handler; - Status st = SendInfo( info, &handler, timeout ); - if( !st.IsOK() ) - return st; - - return MessageUtils::WaitForResponse( &handler, response ); - } - - //---------------------------------------------------------------------------- - // Prepare one or more files for access - async - //---------------------------------------------------------------------------- - XRootDStatus FileSystem::Prepare( const std::vector &fileList, - PrepareFlags::Flags flags, - uint8_t priority, - ResponseHandler *handler, - uint16_t timeout ) - { - if( pPlugIn ) - return pPlugIn->Prepare( fileList, flags, priority, handler, timeout ); - - std::vector::const_iterator it; - std::string list; - for( it = fileList.begin(); it != fileList.end(); ++it ) - { - list += *it; - list += "\n"; - } - list.erase( list.length()-1, 1 ); - - Message *msg; - ClientPrepareRequest *req; - MessageUtils::CreateRequest( msg, req, list.length() ); - - req->requestid = kXR_prepare; - req->options = flags; - req->prty = priority; - req->dlen = list.length(); - - msg->Append( list.c_str(), list.length(), 24 ); - - MessageSendParams params; params.timeout = timeout; - MessageUtils::ProcessSendParams( params ); - XRootDTransport::SetDescription( msg ); - - return Send( msg, handler, params ); - } - - //------------------------------------------------------------------------ - //! Prepare one or more files for access - sync - //------------------------------------------------------------------------ - XRootDStatus FileSystem::Prepare( const std::vector &fileList, - PrepareFlags::Flags flags, - uint8_t priority, - Buffer *&response, - uint16_t timeout ) - { - SyncResponseHandler handler; - Status st = Prepare( fileList, flags, priority, &handler, timeout ); - if( !st.IsOK() ) - return st; - - return MessageUtils::WaitForResponse( &handler, response ); - } - - //---------------------------------------------------------------------------- - // Set file property - //---------------------------------------------------------------------------- - bool FileSystem::SetProperty( const std::string &name, - const std::string &value ) - { - if( pPlugIn ) - return pPlugIn->SetProperty( name, value ); - - if( name == "FollowRedirects" ) - { - if( value == "true" ) pFollowRedirects = true; - else pFollowRedirects = false; - return true; - } - return false; - } - - //---------------------------------------------------------------------------- - // Get file property - //---------------------------------------------------------------------------- - bool FileSystem::GetProperty( const std::string &name, - std::string &value ) const - { - if( pPlugIn ) - return pPlugIn->GetProperty( name, value ); - - if( name == "FollowRedirects" ) - { - if( pFollowRedirects ) value = "true"; - else value = "false"; - return true; - } - return false; - } - - //---------------------------------------------------------------------------- - // Assign a load balancer if it has not already been assigned - //---------------------------------------------------------------------------- - void FileSystem::AssignLoadBalancer( const URL &url ) - { - Log *log = DefaultEnv::GetLog(); - XrdSysMutexHelper scopedLock( pMutex ); - - if( pLoadBalancerLookupDone ) - return; - - log->Dump( FileSystemMsg, "[0x%x@%s] Assigning %s as load balancer", this, - pUrl->GetHostId().c_str(), url.GetHostId().c_str() ); - - delete pUrl; - pUrl = new URL( url ); - pLoadBalancerLookupDone = true; - } - - //---------------------------------------------------------------------------- - // Send a message in a locked environment - //---------------------------------------------------------------------------- - Status FileSystem::Send( Message *msg, - ResponseHandler *handler, - MessageSendParams ¶ms ) - { - Log *log = DefaultEnv::GetLog(); - XrdSysMutexHelper scopedLock( pMutex ); - - log->Dump( FileSystemMsg, "[0x%x@%s] Sending %s", this, - pUrl->GetHostId().c_str(), msg->GetDescription().c_str() ); - - if( !pLoadBalancerLookupDone && pFollowRedirects ) - handler = new AssignLBHandler( this, handler ); - - params.followRedirects = pFollowRedirects; - - return MessageUtils::SendMessage( *pUrl, msg, handler, params, 0 ); - } -} diff --git a/src/XrdCl/XrdClFileSystem.hh b/src/XrdCl/XrdClFileSystem.hh deleted file mode 100644 index 4ef82362906..00000000000 --- a/src/XrdCl/XrdClFileSystem.hh +++ /dev/null @@ -1,752 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2014 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_FILE_SYSTEM_HH__ -#define __XRD_CL_FILE_SYSTEM_HH__ - -#include "XrdCl/XrdClURL.hh" -#include "XrdCl/XrdClStatus.hh" -#include "XrdOuc/XrdOucEnum.hh" -#include "XrdOuc/XrdOucCompiler.hh" -#include "XrdCl/XrdClXRootDResponses.hh" -#include "XrdSys/XrdSysPthread.hh" -#include "XProtocol/XProtocol.hh" -#include -#include - -namespace XrdCl -{ - class PostMaster; - class Message; - class FileSystemPlugIn; - struct MessageSendParams; - - //---------------------------------------------------------------------------- - //! XRootD query request codes - //---------------------------------------------------------------------------- - struct QueryCode - { - //-------------------------------------------------------------------------- - //! XRootD query request codes - //-------------------------------------------------------------------------- - enum Code - { - Config = kXR_Qconfig, //!< Query server configuration - ChecksumCancel = kXR_Qckscan, //!< Query file checksum cancellation - Checksum = kXR_Qcksum, //!< Query file checksum - Opaque = kXR_Qopaque, //!< Implementation dependent - OpaqueFile = kXR_Qopaquf, //!< Implementation dependent - Prepare = kXR_QPrep, //!< Query prepare status - Space = kXR_Qspace, //!< Query logical space stats - Stats = kXR_QStats, //!< Query server stats - Visa = kXR_Qvisa, //!< Query file visa attributes - XAttr = kXR_Qxattr //!< Query file extended attributes - }; - }; - - //---------------------------------------------------------------------------- - //! Open flags, may be or'd when appropriate - //---------------------------------------------------------------------------- - struct OpenFlags - { - //-------------------------------------------------------------------------- - //! Open flags, may be or'd when appropriate - //-------------------------------------------------------------------------- - enum Flags - { - None = 0, //!< Nothing - Compress = kXR_compress, //!< Read compressed data for open (ignored), - //!< for kXR_locate return unique hosts - Delete = kXR_delete, //!< Open a new file, deleting any existing - //!< file - Force = kXR_force, //!< Ignore file usage rules, for kXR_locate - //!< it means ignoreing network dependencies - MakePath = kXR_mkpath, //!< Create directory path if it does not - //!< already exist - New = kXR_new, //!< Open the file only if it does not already - //!< exist - NoWait = kXR_nowait, //!< Open the file only if it does not cause - //!< a wait. For locate: provide a location as - //!< soon as one becomes known. This means - //!< that not all locations are necessarily - //!< returned. If the file does not exist a - //!< wait is still imposed. - Append = kXR_open_apnd, //!< Open only for appending - Read = kXR_open_read, //!< Open only for reading - Update = kXR_open_updt, //!< Open for reading and writing - Write = kXR_open_wrto, //!< Open only for writing - POSC = kXR_posc, //!< Enable Persist On Successful Close - //!< processing - Refresh = kXR_refresh, //!< Refresh the cached information on file's - //!< location. Voids NoWait. - Replica = kXR_replica, //!< The file is being opened for replica - //!< creation - SeqIO = kXR_seqio, //!< File will be read or written sequentially - PrefName = kXR_prefname //!< Hostname response is prefered, applies - //!< only to FileSystem::Locate - }; - }; - XRDOUC_ENUM_OPERATORS( OpenFlags::Flags ) - - //---------------------------------------------------------------------------- - //! Access mode - //---------------------------------------------------------------------------- - struct Access - { - //-------------------------------------------------------------------------- - //! Access mode - //-------------------------------------------------------------------------- - enum Mode - { - None = 0, - UR = kXR_ur, //!< owner readable - UW = kXR_uw, //!< owner writable - UX = kXR_ux, //!< owner executable/browsable - GR = kXR_gr, //!< group readable - GW = kXR_gw, //!< group writable - GX = kXR_gx, //!< group executable/browsable - OR = kXR_or, //!< world readable - OW = kXR_ow, //!< world writeable - OX = kXR_ox //!< world executable/browsable - }; - }; - XRDOUC_ENUM_OPERATORS( Access::Mode ) - - //---------------------------------------------------------------------------- - //! MkDir flags - //---------------------------------------------------------------------------- - struct MkDirFlags - { - enum Flags - { - None = 0, //!< Nothing special - MakePath = 1 //!< create the entire directory tree if it doesn't exist - }; - }; - XRDOUC_ENUM_OPERATORS( MkDirFlags::Flags ) - - //---------------------------------------------------------------------------- - //! DirList flags - //---------------------------------------------------------------------------- - struct DirListFlags - { - enum Flags - { - None = 0, //!< Nothing special - Stat = 1, //!< Stat each entry - Locate = 2, //!< Locate all servers hosting the directory and send - //!< the dirlist request to all of them - Recursive = 4, //!< Do a recursive listing - Merge = 8 - }; - }; - XRDOUC_ENUM_OPERATORS( DirListFlags::Flags ) - - //---------------------------------------------------------------------------- - //! Prepare flags - //---------------------------------------------------------------------------- - struct PrepareFlags - { - enum Flags - { - None = 0, //!< no flags - Colocate = kXR_coloc, //!< co-locate staged files, if possible - Fresh = kXR_fresh, //!< refresh file access time even if - //!< the location is known - Stage = kXR_stage, //!< stage the file to disk if it is not - //!< online - WriteMode = kXR_wmode //!< the file will be accessed for - //!< modification - }; - }; - XRDOUC_ENUM_OPERATORS( PrepareFlags::Flags ) - - //---------------------------------------------------------------------------- - //! Send file/filesystem queries to an XRootD cluster - //---------------------------------------------------------------------------- - class FileSystem - { - friend class AssignLBHandler; - friend class ForkHandler; - - public: - typedef std::vector LocationList; //!< Location list - - //------------------------------------------------------------------------ - //! Constructor - //! - //! @param url URL of the entry-point server to be contacted - //! @param enablePlugIns enable the plug-in mechanism for this object - //------------------------------------------------------------------------ - FileSystem( const URL &url, bool enablePlugIns = true ); - - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - ~FileSystem(); - - //------------------------------------------------------------------------ - //! Locate a file - async - //! - //! @param path path to the file to be located - //! @param flags some of the OpenFlags::Flags - //! @param handler handler to be notified when the response arrives, - //! the response parameter will hold a Buffer object - //! if the procedure is successful - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Locate( const std::string &path, - OpenFlags::Flags flags, - ResponseHandler *handler, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Locate a file - sync - //! - //! @param path path to the file to be located - //! @param flags some of the OpenFlags::Flags - //! @param response the response (to be deleted by the user) - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Locate( const std::string &path, - OpenFlags::Flags flags, - LocationInfo *&response, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Locate a file, recursively locate all disk servers - async - //! - //! @param path path to the file to be located - //! @param flags some of the OpenFlags::Flags - //! @param handler handler to be notified when the response arrives, - //! the response parameter will hold a Buffer object - //! if the procedure is successful - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus DeepLocate( const std::string &path, - OpenFlags::Flags flags, - ResponseHandler *handler, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Locate a file, recursively locate all disk servers - sync - //! - //! @param path path to the file to be located - //! @param flags some of the OpenFlags::Flags - //! @param response the response (to be deleted by the user) - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus DeepLocate( const std::string &path, - OpenFlags::Flags flags, - LocationInfo *&response, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Move a directory or a file - async - //! - //! @param source the file or directory to be moved - //! @param dest the new name - //! @param handler handler to be notified when the response arrives, - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Mv( const std::string &source, - const std::string &dest, - ResponseHandler *handler, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Move a directory or a file - sync - //! - //! @param source the file or directory to be moved - //! @param dest the new name - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Mv( const std::string &source, - const std::string &dest, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Obtain server information - async - //! - //! @param queryCode the query code as specified in the QueryCode struct - //! @param arg query argument - //! @param handler handler to be notified when the response arrives, - //! the response parameter will hold a Buffer object - //! if the procedure is successful - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Query( QueryCode::Code queryCode, - const Buffer &arg, - ResponseHandler *handler, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Obtain server information - sync - //! - //! @param queryCode the query code as specified in the QueryCode struct - //! @param arg query argument - //! @param response the response (to be deleted by the user) - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Query( QueryCode::Code queryCode, - const Buffer &arg, - Buffer *&response, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Truncate a file - async - //! - //! @param path path to the file to be truncated - //! @param size file size - //! @param handler handler to be notified when the response arrives - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Truncate( const std::string &path, - uint64_t size, - ResponseHandler *handler, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Truncate a file - sync - //! - //! @param path path to the file to be truncated - //! @param size file size - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Truncate( const std::string &path, - uint64_t size, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Remove a file - async - //! - //! @param path path to the file to be removed - //! @param handler handler to be notified when the response arrives - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Rm( const std::string &path, - ResponseHandler *handler, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Remove a file - sync - //! - //! @param path path to the file to be removed - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Rm( const std::string &path, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Create a directory - async - //! - //! @param path path to the directory - //! @param flags or'd MkDirFlags - //! @param mode access mode, or'd Access::Mode - //! @param handler handler to be notified when the response arrives - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus MkDir( const std::string &path, - MkDirFlags::Flags flags, - Access::Mode mode, - ResponseHandler *handler, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Create a directory - sync - //! - //! @param path path to the directory - //! @param flags or'd MkDirFlags - //! @param mode access mode, or'd Access::Mode - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus MkDir( const std::string &path, - MkDirFlags::Flags flags, - Access::Mode mode, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Remove a directory - async - //! - //! @param path path to the directory to be removed - //! @param handler handler to be notified when the response arrives - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus RmDir( const std::string &path, - ResponseHandler *handler, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Remove a directory - sync - //! - //! @param path path to the directory to be removed - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus RmDir( const std::string &path, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Change access mode on a directory or a file - async - //! - //! @param path file/directory path - //! @param mode access mode, or'd Access::Mode - //! @param handler handler to be notified when the response arrives - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus ChMod( const std::string &path, - Access::Mode mode, - ResponseHandler *handler, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Change access mode on a directory or a file - sync - //! - //! @param path file/directory path - //! @param mode access mode, or'd Access::Mode - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus ChMod( const std::string &path, - Access::Mode mode, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Check if the server is alive - async - //! - //! @param handler handler to be notified when the response arrives - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Ping( ResponseHandler *handler, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Check if the server is alive - sync - //! - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Ping( uint16_t timeout = 0 ) XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Obtain status information for a path - async - //! - //! @param path file/directory path - //! @param handler handler to be notified when the response arrives, - //! the response parameter will hold a StatInfo object - //! if the procedure is successful - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Stat( const std::string &path, - ResponseHandler *handler, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Obtain status information for a path - sync - //! - //! @param path file/directory path - //! @param response the response (to be deleted by the user only if the - //! procedure is successful) - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Stat( const std::string &path, - StatInfo *&response, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Obtain status information for a Virtual File System - async - //! - //! @param path file/directory path - //! @param handler handler to be notified when the response arrives, - //! the response parameter will hold a StatInfoVFS object - //! if the procedure is successful - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus StatVFS( const std::string &path, - ResponseHandler *handler, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Obtain status information for a Virtual File System - sync - //! - //! @param path file/directory path - //! @param response the response (to be deleted by the user) - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus StatVFS( const std::string &path, - StatInfoVFS *&response, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Obtain server protocol information - async - //! - //! @param handler handler to be notified when the response arrives, - //! the response parameter will hold a ProtocolInfo object - //! if the procedure is successful - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Protocol( ResponseHandler *handler, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Obtain server protocol information - sync - //! - //! @param response the response (to be deleted by the user) - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Protocol( ProtocolInfo *&response, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! List entries of a directory - async - //! - //! @param path directory path - //! @param flags currently unused - //! @param handler handler to be notified when the response arrives, - //! the response parameter will hold a DirectoryList - //! object if the procedure is successful - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus DirList( const std::string &path, - DirListFlags::Flags flags, - ResponseHandler *handler, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! List entries of a directory - sync - //! - //! @param path directory path - //! @param flags DirListFlags - //! @param response the response (to be deleted by the user) - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus DirList( const std::string &path, - DirListFlags::Flags flags, - DirectoryList *&response, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Send info to the server (up to 1024 characters)- async - //! - //! @param info the info string to be sent - //! @param handler handler to be notified when the response arrives, - //! the response parameter will hold a Buffer object - //! if the procedure is successful - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus SendInfo( const std::string &info, - ResponseHandler *handler, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Send info to the server (up to 1024 characters) - sync - //! - //! @param info the info string to be sent - //! @param response the response (to be deleted by the user) - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus SendInfo( const std::string &info, - Buffer *&response, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Prepare one or more files for access - async - //! - //! @param fileList list of files to be prepared - //! @param flags PrepareFlags::Flags - //! @param priority priority of the request 0 (lowest) - 3 (highest) - //! @param handler handler to be notified when the response arrives, - //! the response parameter will hold a Buffer object - //! if the procedure is successful - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Prepare( const std::vector &fileList, - PrepareFlags::Flags flags, - uint8_t priority, - ResponseHandler *handler, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Prepare one or more files for access - sync - //! - //! @param fileList list of files to be prepared - //! @param flags PrepareFlags::Flags - //! @param priority priority of the request 0 (lowest) - 3 (highest) - //! @param response the response (to be deleted by the user) - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Prepare( const std::vector &fileList, - PrepareFlags::Flags flags, - uint8_t priority, - Buffer *&response, - uint16_t timeout = 0 ) - XRD_WARN_UNUSED_RESULT; - - //------------------------------------------------------------------------ - //! Set filesystem property - //! - //! Filesystem properties: - //! FollowRedirects [true/false] - enable/disable following redirections - //------------------------------------------------------------------------ - bool SetProperty( const std::string &name, const std::string &value ); - - //------------------------------------------------------------------------ - //! Get filesystem property - //! - //! @see FileSystem::SetProperty for property list - //------------------------------------------------------------------------ - bool GetProperty( const std::string &name, std::string &value ) const; - - private: - FileSystem(const FileSystem &other); - FileSystem &operator = (const FileSystem &other); - - //------------------------------------------------------------------------ - // Send a message in a locked environment - //------------------------------------------------------------------------ - Status Send( Message *msg, - ResponseHandler *handler, - MessageSendParams ¶ms ); - - //------------------------------------------------------------------------ - // Assign a load balancer if it has not already been assigned - //------------------------------------------------------------------------ - void AssignLoadBalancer( const URL &url ); - - //------------------------------------------------------------------------ - // Lock the internal lock - //------------------------------------------------------------------------ - void Lock() - { - pMutex.Lock(); - } - - //------------------------------------------------------------------------ - // Unlock the internal lock - //------------------------------------------------------------------------ - void UnLock() - { - pMutex.UnLock(); - } - - XrdSysMutex pMutex; - bool pLoadBalancerLookupDone; - bool pFollowRedirects; - URL *pUrl; - FileSystemPlugIn *pPlugIn; - }; -} - -#endif // __XRD_CL_FILE_SYSTEM_HH__ diff --git a/src/XrdCl/XrdClFileSystemUtils.cc b/src/XrdCl/XrdClFileSystemUtils.cc deleted file mode 100644 index 8cc21078060..00000000000 --- a/src/XrdCl/XrdClFileSystemUtils.cc +++ /dev/null @@ -1,115 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2014 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#include "XrdCl/XrdClFileSystemUtils.hh" -#include "XrdCl/XrdClFileSystem.hh" -#include "XrdCl/XrdClFileSystem.hh" -#include "XrdCl/XrdClUglyHacks.hh" -#include "XrdCl/XrdClURL.hh" - -#include -#include -#include - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - // Recursively get space information for given path - //---------------------------------------------------------------------------- - XRootDStatus FileSystemUtils::GetSpaceInfo( SpaceInfo *&result, - FileSystem *fs, - const std::string &path ) - { - //-------------------------------------------------------------------------- - // Locate all the disk servers containing the space - //-------------------------------------------------------------------------- - LocationInfo *locationInfo = 0; - XRootDStatus st = fs->DeepLocate( path, OpenFlags::Compress, locationInfo ); - if( !st.IsOK() ) - return st; - - XRDCL_SMART_PTR_T locationInfoPtr( locationInfo ); - - bool partial = st.code == suPartial ? true : false; - - std::vector > resp; - resp.push_back( std::make_pair( std::string("oss.space"), (uint64_t)0 ) ); - resp.push_back( std::make_pair( std::string("oss.free"), (uint64_t)0 ) ); - resp.push_back( std::make_pair( std::string("oss.used"), (uint64_t)0 ) ); - resp.push_back( std::make_pair( std::string("oss.maxf"), (uint64_t)0 ) ); - - //-------------------------------------------------------------------------- - // Loop over the file servers and get the space info from each of them - //-------------------------------------------------------------------------- - LocationInfo::Iterator it; - Buffer pathArg; pathArg.FromString( path ); - for( it = locationInfo->Begin(); it != locationInfo->End(); ++it ) - { - //------------------------------------------------------------------------ - // Query the server - //------------------------------------------------------------------------ - Buffer *spaceInfo = 0; - FileSystem fs1( it->GetAddress() ); - st = fs1.Query( QueryCode::Space, pathArg, spaceInfo ); - if( !st.IsOK() ) - return st; - - XRDCL_SMART_PTR_T spaceInfoPtr( spaceInfo ); - - //------------------------------------------------------------------------ - // Parse the cgi - //------------------------------------------------------------------------ - std::string fakeUrl = "root://fake/fake?" + spaceInfo->ToString(); - URL url( fakeUrl ); - - if( !url.IsValid() ) - return XRootDStatus( stError, errInvalidResponse ); - - URL::ParamsMap params = url.GetParams(); - - //------------------------------------------------------------------------ - // Convert and add up the params - //------------------------------------------------------------------------ - st = XRootDStatus( stError, errInvalidResponse ); - for( size_t i = 0; i < resp.size(); ++i ) - { - URL::ParamsMap::iterator paramIt = params.find( resp[i].first ); - if( paramIt == params.end() ) return st; - char *res; - uint64_t num = ::strtoll( paramIt->second.c_str(), &res, 0 ); - if( *res != 0 ) return st; - if( resp[i].first == "oss.maxf" ) - { if( num > resp[i].second ) resp[i].second = num; } - else - resp[i].second += num; - } - } - - result = new SpaceInfo( resp[0].second, resp[1].second, resp[2].second, - resp[3].second ); - - st = XRootDStatus(); if( partial ) st.code = suPartial; - return st; - } -} diff --git a/src/XrdCl/XrdClFileSystemUtils.hh b/src/XrdCl/XrdClFileSystemUtils.hh deleted file mode 100644 index 3b5fe14bf5f..00000000000 --- a/src/XrdCl/XrdClFileSystemUtils.hh +++ /dev/null @@ -1,91 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2014 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_FILE_SYSTEM_UTILS_HH__ -#define __XRD_CL_FILE_SYSTEM_UTILS_HH__ - -#include "XrdCl/XrdClXRootDResponses.hh" - -#include -#include - -namespace XrdCl -{ - class FileSystem; - - //---------------------------------------------------------------------------- - //! A container for file system utility functions that do not belong in - //! FileSystem - //---------------------------------------------------------------------------- - class FileSystemUtils - { - public: - //------------------------------------------------------------------------ - //! Container for space information - //------------------------------------------------------------------------ - class SpaceInfo - { - public: - SpaceInfo( uint64_t total, uint64_t free, uint64_t used, - uint64_t largestChunk ): - pTotal( total ), pFree( free ), pUsed( used ), - pLargestChunk( largestChunk ) { } - - //-------------------------------------------------------------------- - //! Amount of total space in MB - //-------------------------------------------------------------------- - uint64_t GetTotal() const { return pTotal; } - - //-------------------------------------------------------------------- - //! Amount of free space in MB - //-------------------------------------------------------------------- - uint64_t GetFree() const { return pFree; } - - //-------------------------------------------------------------------- - //! Amount of used space in MB - //-------------------------------------------------------------------- - uint64_t GetUsed() const { return pUsed; } - - //-------------------------------------------------------------------- - //! Largest single chunk of free space - //-------------------------------------------------------------------- - uint64_t GetLargestFreeChunk() const { return pLargestChunk; } - - private: - uint64_t pTotal; - uint64_t pFree; - uint64_t pUsed; - uint64_t pLargestChunk; - }; - - //------------------------------------------------------------------------ - //! Recursively get space information for given path - //------------------------------------------------------------------------ - static XRootDStatus GetSpaceInfo( SpaceInfo *&result, - FileSystem *fs, - const std::string &path ); - }; -} - -#endif // __XRD_CL_FILE_SYSTEM_UTILS HH__ diff --git a/src/XrdCl/XrdClFileTimer.cc b/src/XrdCl/XrdClFileTimer.cc deleted file mode 100644 index d1341f59fb8..00000000000 --- a/src/XrdCl/XrdClFileTimer.cc +++ /dev/null @@ -1,41 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2013 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#include "XrdCl/XrdClFileTimer.hh" -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdCl/XrdClConstants.hh" -#include "XrdCl/XrdClFileStateHandler.hh" - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - // Perform the task's action - //---------------------------------------------------------------------------- - time_t FileTimer::Run( time_t now ) - { - pMutex.Lock(); - std::set::iterator it; - for( it = pFileObjects.begin(); it != pFileObjects.end(); ++it ) - (*it)->Tick(now); - pMutex.UnLock(); - Env *env = DefaultEnv::GetEnv(); - int timeoutResolution = DefaultTimeoutResolution; - env->GetInt( "TimeoutResolution", timeoutResolution ); - return now+timeoutResolution; - } -} diff --git a/src/XrdCl/XrdClFileTimer.hh b/src/XrdCl/XrdClFileTimer.hh deleted file mode 100644 index 59dc7cb0f61..00000000000 --- a/src/XrdCl/XrdClFileTimer.hh +++ /dev/null @@ -1,95 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2013 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_FILE_TIMER_HH__ -#define __XRD_CL_FILE_TIMER_HH__ - -#include "XrdSys/XrdSysPthread.hh" -#include "XrdCl/XrdClTaskManager.hh" - -namespace XrdCl -{ - class FileStateHandler; - - //---------------------------------------------------------------------------- - //! Task generating timeout events for FileStateHandlers in recovery mode - //---------------------------------------------------------------------------- - class FileTimer: public Task - { - public: - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - FileTimer() - { - SetName( "FileTimer task" ); - } - - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - virtual ~FileTimer() - { - } - - //------------------------------------------------------------------------ - //! Register a file state handler - //------------------------------------------------------------------------ - void RegisterFileObject( FileStateHandler *file ) - { - XrdSysMutexHelper scopedLock( pMutex ); - pFileObjects.insert( file ); - } - - //------------------------------------------------------------------------ - //! Un-register a file state handler - //------------------------------------------------------------------------ - void UnRegisterFileObject( FileStateHandler *file ) - { - XrdSysMutexHelper scopedLock( pMutex ); - pFileObjects.erase( file ); - } - - //------------------------------------------------------------------------ - //! Lock the task - //------------------------------------------------------------------------ - void Lock() - { - pMutex.Lock(); - } - - //------------------------------------------------------------------------ - //! Un-lock the task - //------------------------------------------------------------------------ - void UnLock() - { - pMutex.UnLock(); - } - - //------------------------------------------------------------------------ - //! Perform the task's action - //------------------------------------------------------------------------ - virtual time_t Run( time_t now ); - - private: - std::set pFileObjects; - XrdSysMutex pMutex; - }; -} - -#endif // __XRD_CL_FILE_TIMER_HH__ diff --git a/src/XrdCl/XrdClForkHandler.cc b/src/XrdCl/XrdClForkHandler.cc deleted file mode 100644 index 3ebcef169a8..00000000000 --- a/src/XrdCl/XrdClForkHandler.cc +++ /dev/null @@ -1,136 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#include "XrdCl/XrdClForkHandler.hh" -#include "XrdCl/XrdClConstants.hh" -#include "XrdCl/XrdClLog.hh" -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdCl/XrdClPostMaster.hh" -#include "XrdCl/XrdClFileTimer.hh" -#include "XrdCl/XrdClFileStateHandler.hh" - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - // Constructor - //---------------------------------------------------------------------------- - ForkHandler::ForkHandler(): - pPostMaster(0), pFileTimer(0) - { - } - - //---------------------------------------------------------------------------- - // Handle the preparation part of the forking process - //---------------------------------------------------------------------------- - void ForkHandler::Prepare() - { - Log *log = DefaultEnv::GetLog(); - pid_t pid = getpid(); - log->Debug( UtilityMsg, "Running the prepare fork handler for process %d", - pid ); - - pMutex.Lock(); - if( pPostMaster ) - pPostMaster->Stop(); - pFileTimer->Lock(); - - //-------------------------------------------------------------------------- - // Lock the user-level objects - //-------------------------------------------------------------------------- - log->Debug( UtilityMsg, "Locking File and FileSystem objects for process: " - "%d", pid ); - - std::set::iterator itFile; - for( itFile = pFileObjects.begin(); itFile != pFileObjects.end(); - ++itFile ) - (*itFile)->Lock(); - - std::set::iterator itFs; - for( itFs = pFileSystemObjects.begin(); itFs != pFileSystemObjects.end(); - ++itFs ) - (*itFs)->Lock(); - } - - //---------------------------------------------------------------------------- - // Handle the parent post-fork - //---------------------------------------------------------------------------- - void ForkHandler::Parent() - { - Log *log = DefaultEnv::GetLog(); - pid_t pid = getpid(); - log->Debug( UtilityMsg, "Running the parent fork handler for process %d", - pid ); - - log->Debug( UtilityMsg, "Unlocking File and FileSystem objects for " - "process: %d", pid ); - - std::set::iterator itFile; - for( itFile = pFileObjects.begin(); itFile != pFileObjects.end(); - ++itFile ) - (*itFile)->UnLock(); - - std::set::iterator itFs; - for( itFs = pFileSystemObjects.begin(); itFs != pFileSystemObjects.end(); - ++itFs ) - (*itFs)->UnLock(); - - pFileTimer->UnLock(); - if( pPostMaster ) - pPostMaster->Start(); - - pMutex.UnLock(); - } - - //------------------------------------------------------------------------ - //! Handler the child post-fork - //------------------------------------------------------------------------ - void ForkHandler::Child() - { - Log *log = DefaultEnv::GetLog(); - pid_t pid = getpid(); - log->Debug( UtilityMsg, "Running the child fork handler for process %d", - pid ); - - log->Debug( UtilityMsg, "Unlocking File and FileSystem objects for " - "process: %d", pid ); - - std::set::iterator itFile; - for( itFile = pFileObjects.begin(); itFile != pFileObjects.end(); - ++itFile ) - { - (*itFile)->AfterForkChild(); - (*itFile)->UnLock(); - } - - std::set::iterator itFs; - for( itFs = pFileSystemObjects.begin(); itFs != pFileSystemObjects.end(); - ++itFs ) - (*itFs)->UnLock(); - - pFileTimer->UnLock(); - if( pPostMaster ) - { - pPostMaster->Finalize(); - pPostMaster->Initialize(); - pPostMaster->Start(); - pPostMaster->GetTaskManager()->RegisterTask( pFileTimer, time(0), false ); - } - - pMutex.UnLock(); - } -} diff --git a/src/XrdCl/XrdClForkHandler.hh b/src/XrdCl/XrdClForkHandler.hh deleted file mode 100644 index c7f3a9e25c4..00000000000 --- a/src/XrdCl/XrdClForkHandler.hh +++ /dev/null @@ -1,115 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_FORK_HANDLER_HH__ -#define __XRD_CL_FORK_HANDLER_HH__ - -#include -#include - -namespace XrdCl -{ - class FileStateHandler; - class FileSystem; - class PostMaster; - class FileTimer; - - //---------------------------------------------------------------------------- - // Helper class for handling forking - //---------------------------------------------------------------------------- - class ForkHandler - { - public: - ForkHandler(); - - //------------------------------------------------------------------------ - //! Register a file object - //------------------------------------------------------------------------ - void RegisterFileObject( FileStateHandler *file ) - { - XrdSysMutexHelper scopedLock( pMutex ); - pFileObjects.insert( file ); - } - - //------------------------------------------------------------------------ - // Un-register a file object - //------------------------------------------------------------------------ - void UnRegisterFileObject( FileStateHandler *file ) - { - XrdSysMutexHelper scopedLock( pMutex ); - pFileObjects.erase( file ); - } - - //------------------------------------------------------------------------ - // Register a file system object - //------------------------------------------------------------------------ - void RegisterFileSystemObject( FileSystem *fs ) - { - XrdSysMutexHelper scopedLock( pMutex ); - pFileSystemObjects.insert( fs ); - } - - //------------------------------------------------------------------------ - //! Un-register a file system object - //------------------------------------------------------------------------ - void UnRegisterFileSystemObject( FileSystem *fs ) - { - XrdSysMutexHelper scopedLock( pMutex ); - pFileSystemObjects.erase( fs ); - } - - //------------------------------------------------------------------------ - //! Register a post master object - //------------------------------------------------------------------------ - void RegisterPostMaster( PostMaster *postMaster ) - { - XrdSysMutexHelper scopedLock( pMutex ); - pPostMaster = postMaster; - } - - void RegisterFileTimer( FileTimer *fileTimer ) - { - XrdSysMutexHelper scopedLock( pMutex ); - pFileTimer = fileTimer; - } - - //------------------------------------------------------------------------ - //! Handle the preparation part of the forking process - //------------------------------------------------------------------------ - void Prepare(); - - //------------------------------------------------------------------------ - //! Handle the parent post-fork - //------------------------------------------------------------------------ - void Parent(); - - //------------------------------------------------------------------------ - //! Handler the child post-fork - //------------------------------------------------------------------------ - void Child(); - - private: - std::set pFileObjects; - std::set pFileSystemObjects; - PostMaster *pPostMaster; - FileTimer *pFileTimer; - XrdSysMutex pMutex; - }; -} - -#endif // __XRD_CL_FORK_HANDLER_HH__ diff --git a/src/XrdCl/XrdClInQueue.cc b/src/XrdCl/XrdClInQueue.cc deleted file mode 100644 index 11fcc43f44d..00000000000 --- a/src/XrdCl/XrdClInQueue.cc +++ /dev/null @@ -1,230 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#include "XProtocol/XProtocol.hh" -#include "XrdCl/XrdClInQueue.hh" -#include "XrdCl/XrdClPostMasterInterfaces.hh" -#include "XrdCl/XrdClMessage.hh" - -#include // for network unmarshalling stuff - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - // Filter messages - //---------------------------------------------------------------------------- - bool InQueue::DiscardMessage(Message* msg, uint16_t& sid) const - { - if( msg->GetSize() < 8 ) - return true; - - ServerResponse *rsp = (ServerResponse *)msg->GetBuffer(); - - // We got an async message - if( rsp->hdr.status == kXR_attn ) - { - if( msg->GetSize() < 12 ) - return true; - - // We only care about async responses - if( rsp->body.attn.actnum != (int32_t)htonl(kXR_asynresp) ) - return true; - - if( msg->GetSize() < 24 ) - return true; - - ServerResponse *embRsp = (ServerResponse*)msg->GetBuffer(16); - sid = ((uint16_t)embRsp->hdr.streamid[1] << 8) | (uint16_t)embRsp->hdr.streamid[0]; - } - else - { - sid = ((uint16_t)rsp->hdr.streamid[1] << 8) | (uint16_t)rsp->hdr.streamid[0]; - } - - return false; - } - - //---------------------------------------------------------------------------- - // Add a message to the queue - //---------------------------------------------------------------------------- - bool InQueue::AddMessage( Message *msg ) - { - uint16_t action = 0; - IncomingMsgHandler* handler = 0; - uint16_t msgSid = 0; - - if (DiscardMessage(msg, msgSid)) - { - return true; - } - - // Lookup the sid in the map of handlers - pMutex.Lock(); - HandlerMap::iterator it = pHandlers.find(msgSid); - - if (it != pHandlers.end()) - { - handler = it->second.first; - action = handler->Examine( msg ); - - if( action & IncomingMsgHandler::RemoveHandler ) - pHandlers.erase( it ); - } - - if( !(action & IncomingMsgHandler::Take) ) - pMessages[msgSid] = msg; - - pMutex.UnLock(); - - if( handler && !(action & IncomingMsgHandler::NoProcess) ) - handler->Process( msg ); - - return true; - } - - //---------------------------------------------------------------------------- - // Add a listener that should be notified about incoming messages - //---------------------------------------------------------------------------- - void InQueue::AddMessageHandler( IncomingMsgHandler *handler, time_t expires ) - { - uint16_t action = 0; - uint16_t handlerSid = handler->GetSid(); - XrdSysMutexHelper scopedLock( pMutex ); - MessageMap::iterator it = pMessages.find(handlerSid); - - if (it != pMessages.end()) - { - action = handler->Examine( it->second ); - - if( action & IncomingMsgHandler::Take ) - { - if( !(action & IncomingMsgHandler::NoProcess ) ) - handler->Process( it->second ); - - pMessages.erase( it ); - } - } - - if( !(action & IncomingMsgHandler::RemoveHandler) ) - pHandlers[handlerSid] = HandlerAndExpire( handler, expires ); - } - - //---------------------------------------------------------------------------- - // Get a message handler interested in receiving message whose header - // is stored in msg - //---------------------------------------------------------------------------- - IncomingMsgHandler *InQueue::GetHandlerForMessage( Message *msg, - time_t &expires, - uint16_t &action ) - { - time_t exp = 0; - uint16_t act = 0; - uint16_t msgSid = 0; - IncomingMsgHandler* handler = 0; - - if (DiscardMessage(msg, msgSid)) - { - return handler; - } - - XrdSysMutexHelper scopedLock( pMutex ); - HandlerMap::iterator it = pHandlers.find(msgSid); - - if (it != pHandlers.end()) - { - handler = it->second.first; - act = handler->Examine( msg ); - exp = it->second.second; - - if( act & IncomingMsgHandler::Take ) - pHandlers.erase( it ); - } - - if( handler ) - { - expires = exp; - action = act; - } - - return handler; - } - - //---------------------------------------------------------------------------- - // Re-insert the handler without scanning the cached messages - //---------------------------------------------------------------------------- - void InQueue::ReAddMessageHandler( IncomingMsgHandler *handler, - time_t expires ) - { - uint16_t handlerSid = handler->GetSid(); - XrdSysMutexHelper scopedLock( pMutex ); - pHandlers[handlerSid] = HandlerAndExpire( handler, expires ); - } - - //---------------------------------------------------------------------------- - // Remove a listener - //---------------------------------------------------------------------------- - void InQueue::RemoveMessageHandler( IncomingMsgHandler *handler ) - { - uint16_t handlerSid = handler->GetSid(); - XrdSysMutexHelper scopedLock( pMutex ); - pHandlers.erase(handlerSid); - } - - //---------------------------------------------------------------------------- - // Report an event to the handlers - //---------------------------------------------------------------------------- - void InQueue::ReportStreamEvent( IncomingMsgHandler::StreamEvent event, - uint16_t streamNum, - Status status ) - { - uint8_t action = 0; - XrdSysMutexHelper scopedLock( pMutex ); - for( HandlerMap::iterator it = pHandlers.begin(); it != pHandlers.end(); ) - { - action = it->second.first->OnStreamEvent( event, streamNum, status ); - - if( action & IncomingMsgHandler::RemoveHandler ) - pHandlers.erase( it++ ); - else - ++it; - } - } - - //---------------------------------------------------------------------------- - // Timeout handlers - //---------------------------------------------------------------------------- - void InQueue::ReportTimeout( time_t now ) - { - if( !now ) - now = ::time(0); - - XrdSysMutexHelper scopedLock( pMutex ); - HandlerMap::iterator it = pHandlers.begin(); - while( it != pHandlers.end() ) - { - if( it->second.second <= now ) - { - it->second.first->OnStreamEvent( IncomingMsgHandler::Timeout, 0, - Status( stError, errOperationExpired ) ); - pHandlers.erase( it++ ); - } - else - ++it; - } - } -} diff --git a/src/XrdCl/XrdClInQueue.hh b/src/XrdCl/XrdClInQueue.hh deleted file mode 100644 index a3117f98a82..00000000000 --- a/src/XrdCl/XrdClInQueue.hh +++ /dev/null @@ -1,110 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_IN_QUEUE_HH__ -#define __XRD_CL_IN_QUEUE_HH__ - -#include -#include -#include -#include "XrdCl/XrdClStatus.hh" -#include "XrdCl/XrdClPostMasterInterfaces.hh" - -namespace XrdCl -{ - class Message; - - //---------------------------------------------------------------------------- - //! A synchronize queue for incoming data - //---------------------------------------------------------------------------- - class InQueue - { - public: - //------------------------------------------------------------------------ - //! Add a fully reconstructed message to the queue - //------------------------------------------------------------------------ - bool AddMessage( Message *msg ); - - //------------------------------------------------------------------------ - //! Add a listener that should be notified about incoming messages - //! - //! @param handler message handler - //! @param expires time when the message handler expires - //------------------------------------------------------------------------ - void AddMessageHandler( IncomingMsgHandler *handler, time_t expires ); - - //------------------------------------------------------------------------ - //! Get a message handler interested in receiving message whose header - //! is stored in msg - //! - //! @param msg message header - //! @param expires handle's expiration timestamp - //! @param action the action declared by the handler - //! - //! @return handler or 0 if none is interested - //------------------------------------------------------------------------ - IncomingMsgHandler *GetHandlerForMessage( Message *msg, - time_t &expires, - uint16_t &action ); - - //------------------------------------------------------------------------ - //! Re-insert the handler without scanning the cached messages - //------------------------------------------------------------------------ - void ReAddMessageHandler( IncomingMsgHandler *handler, time_t expires ); - - //------------------------------------------------------------------------ - //! Remove a listener - //------------------------------------------------------------------------ - void RemoveMessageHandler( IncomingMsgHandler *handler ); - - //------------------------------------------------------------------------ - //! Report an event to the handlers - //------------------------------------------------------------------------ - void ReportStreamEvent( IncomingMsgHandler::StreamEvent event, - uint16_t streamNum, - Status status ); - - //------------------------------------------------------------------------ - //! Timeout handlers - //------------------------------------------------------------------------ - void ReportTimeout( time_t now = 0 ); - - private: - - //------------------------------------------------------------------------ - //! Discard messages that don't meet basic criteria and extract the - //! message sid - //! - //! @param msg message object - //! @param sid extracted message sid used later for matching with the - //! handler - //! - //! @return true if message discarded, otherwise false - //------------------------------------------------------------------------ - bool DiscardMessage(Message* msg, uint16_t& sid) const; - - typedef std::pair HandlerAndExpire; - typedef std::map HandlerMap; - typedef std::map MessageMap; - MessageMap pMessages; - HandlerMap pHandlers; - XrdSysRecMutex pMutex; - }; -} - -#endif // __XRD_CL_IN_QUEUE_HH__ diff --git a/src/XrdCl/XrdClJobManager.cc b/src/XrdCl/XrdClJobManager.cc deleted file mode 100644 index 1514ea45322..00000000000 --- a/src/XrdCl/XrdClJobManager.cc +++ /dev/null @@ -1,152 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2013 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#include "XrdCl/XrdClJobManager.hh" -#include "XrdCl/XrdClLog.hh" -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdCl/XrdClConstants.hh" - -//------------------------------------------------------------------------------ -// The thread -//------------------------------------------------------------------------------ -extern "C" -{ - static void *RunRunnerThread( void *arg ) - { - using namespace XrdCl; - JobManager *mgr = (JobManager*)arg; - mgr->RunJobs(); - return 0; - } -} - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - // Initialize the job manager - //---------------------------------------------------------------------------- - bool JobManager::Initialize() - { - return true; - } - - //---------------------------------------------------------------------------- - // Finalize the job manager, clear the queues - //---------------------------------------------------------------------------- - bool JobManager::Finalize() - { - pJobs.Clear(); - return true; - } - - //---------------------------------------------------------------------------- - // Start the workers - //---------------------------------------------------------------------------- - bool JobManager::Start() - { - XrdSysMutexHelper scopedLock( pMutex ); - Log *log = DefaultEnv::GetLog(); - log->Debug( JobMgrMsg, "Starting the job manager..." ); - - if( pRunning ) - { - log->Error( JobMgrMsg, "The job manager is already running" ); - return false; - } - - for( uint32_t i = 0; i < pWorkers.size(); ++i ) - { - int ret = ::pthread_create( &pWorkers[i], 0, ::RunRunnerThread, this ); - if( ret != 0 ) - { - log->Error( JobMgrMsg, "Unable to spawn a job worker thread: %s", - strerror( errno ) ); - if( i > 0 ) - StopWorkers( i-1 ); - return false; - } - } - pRunning = true; - log->Debug( JobMgrMsg, "Job manager started, %d workers", pWorkers.size() ); - return true; - } - - //---------------------------------------------------------------------------- - // Stop the workers - //---------------------------------------------------------------------------- - bool JobManager::Stop() - { - XrdSysMutexHelper scopedLock( pMutex ); - Log *log = DefaultEnv::GetLog(); - log->Debug( JobMgrMsg, "Stopping the job manager..." ); - if( !pRunning ) - { - log->Error( JobMgrMsg, "The job manager is not running" ); - return false; - } - - StopWorkers( pWorkers.size()-1 ); - - pRunning = false; - log->Debug( JobMgrMsg, "Job manager stopped" ); - return true; - } - - //---------------------------------------------------------------------------- - // Stop all workers up to n'th - //---------------------------------------------------------------------------- - void JobManager::StopWorkers( uint32_t n ) - { - Log *log = DefaultEnv::GetLog(); - for( uint32_t i = 0; i <= n; ++i ) - { - void *threadRet; - log->Dump( JobMgrMsg, "Stopping worker #%d...", i ); - if( pthread_cancel( pWorkers[i] ) != 0 ) - { - log->Error( TaskMgrMsg, "Unable to cancel worker #%d: %s", i, - strerror( errno ) ); - abort(); - } - - if( pthread_join( pWorkers[i], (void**)&threadRet ) != 0 ) - { - log->Error( TaskMgrMsg, "Unable to join worker #%d: %s", i, - strerror( errno ) ); - abort(); - } - - log->Dump( JobMgrMsg, "Worker #%d stopped", i ); - } - } - - //---------------------------------------------------------------------------- - // Initialize the job manager - //---------------------------------------------------------------------------- - void JobManager::RunJobs() - { - pthread_setcanceltype( PTHREAD_CANCEL_DEFERRED, 0 ); - for( ;; ) - { - JobHelper h = pJobs.Get(); - pthread_setcancelstate( PTHREAD_CANCEL_DISABLE, 0 ); - h.job->Run( h.arg ); - pthread_setcancelstate( PTHREAD_CANCEL_ENABLE, 0 ); - } - } -} diff --git a/src/XrdCl/XrdClJobManager.hh b/src/XrdCl/XrdClJobManager.hh deleted file mode 100644 index 2a83e8ee152..00000000000 --- a/src/XrdCl/XrdClJobManager.hh +++ /dev/null @@ -1,130 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2013 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_JOB_MANAGER_HH__ -#define __XRD_CL_JOB_MANAGER_HH__ - -#include -#include -#include -#include -#include "XrdCl/XrdClSyncQueue.hh" - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - //! Interface for a job to be run by the job manager - //---------------------------------------------------------------------------- - class Job - { - public: - //------------------------------------------------------------------------ - //! Virtual destructor - //------------------------------------------------------------------------ - virtual ~Job() {}; - - //------------------------------------------------------------------------ - //! The job logic - //------------------------------------------------------------------------ - virtual void Run( void *arg ) = 0; - }; - - //---------------------------------------------------------------------------- - //! A synchronized queue - //---------------------------------------------------------------------------- - class JobManager - { - public: - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - JobManager( uint32_t workers ) - { - pRunning = false; - pWorkers.resize( workers ); - } - - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - ~JobManager() - { - } - - //------------------------------------------------------------------------ - //! Initialize the job manager - //------------------------------------------------------------------------ - bool Initialize(); - - //------------------------------------------------------------------------ - //! Finalize the job manager, clear the queues - //------------------------------------------------------------------------ - bool Finalize(); - - //------------------------------------------------------------------------ - //! Start the workers - //------------------------------------------------------------------------ - bool Start(); - - //------------------------------------------------------------------------ - //! Stop the workers - //------------------------------------------------------------------------ - bool Stop(); - - //------------------------------------------------------------------------ - //! Add a job to be run - //------------------------------------------------------------------------ - void QueueJob( Job *job, void *arg = 0 ) - { - pJobs.Put( JobHelper( job, arg ) ); - } - - //------------------------------------------------------------------------ - //! Run the jobs - //------------------------------------------------------------------------ - void RunJobs(); - - bool IsWorker() - { - pthread_t thread = pthread_self(); - std::vector::iterator itr = - std::find( pWorkers.begin(), pWorkers.end(), thread ); - return itr != pWorkers.end(); - } - - private: - //------------------------------------------------------------------------ - //! Stop all workers up to n'th - //------------------------------------------------------------------------ - void StopWorkers( uint32_t n ); - - struct JobHelper - { - JobHelper( Job *j = 0, void *a = 0 ): job(j), arg(a) {} - Job *job; - void *arg; - }; - - std::vector pWorkers; - SyncQueue pJobs; - XrdSysMutex pMutex; - bool pRunning; - }; -} - -#endif // __XRD_CL_ANY_OBJECT_HH__ diff --git a/src/XrdCl/XrdClLocalFileHandler.cc b/src/XrdCl/XrdClLocalFileHandler.cc deleted file mode 100644 index 0dae790e16e..00000000000 --- a/src/XrdCl/XrdClLocalFileHandler.cc +++ /dev/null @@ -1,701 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2017 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH -// Author: Paul-Niklas Kramp -// Michal Simon -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ -#include "XrdCl/XrdClLocalFileHandler.hh" -#include "XrdCl/XrdClConstants.hh" -#include "XrdCl/XrdClPostMaster.hh" -#include "XrdCl/XrdClURL.hh" -#include "XrdCl/XrdClMessageUtils.hh" -#include "XrdCl/XrdClFileSystem.hh" -#include "XProtocol/XProtocol.hh" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -namespace -{ - - class AioCtx - { - public: - - enum Opcode - { - None, - Read, - Write, - Sync - }; - - AioCtx( const XrdCl::HostList &hostList, XrdCl::ResponseHandler *handler ) : - opcode( None ), hosts( new XrdCl::HostList( hostList ) ), handler( handler ) - { - aiocb *ptr = new aiocb(); - memset( ptr, 0, sizeof( aiocb ) ); - - XrdCl::Env *env = XrdCl::DefaultEnv::GetEnv(); - int useSignals = XrdCl::DefaultAioSignal; - env->GetInt( "AioSignal", useSignals ); - - if( useSignals ) - { - static SignalHandlerRegistrator registrator; // registers the signal handler - - ptr->aio_sigevent.sigev_notify = SIGEV_SIGNAL; - ptr->aio_sigevent.sigev_signo = SIGUSR1; - } - else - { - ptr->aio_sigevent.sigev_notify = SIGEV_THREAD; - ptr->aio_sigevent.sigev_notify_function = ThreadHandler; - } - - ptr->aio_sigevent.sigev_value.sival_ptr = this; - - cb.reset( ptr ); - } - - - void SetWrite( int fd, size_t offset, size_t size, const void *buffer ) - { - cb->aio_fildes = fd; - cb->aio_offset = offset; - cb->aio_buf = const_cast( buffer ); - cb->aio_nbytes = size; - opcode = Opcode::Write; - } - - void SetRead( int fd, size_t offset, size_t size, void *buffer ) - { - cb->aio_fildes = fd; - cb->aio_offset = offset; - cb->aio_buf = buffer; - cb->aio_nbytes = size; - opcode = Opcode::Read; - } - - void SetFsync( int fd ) - { - cb->aio_fildes = fd; - opcode = Opcode::Sync; - } - - static void ThreadHandler( sigval arg ) - { - std::unique_ptr me( reinterpret_cast( arg.sival_ptr ) ); - Handler( std::move( me ) ); - } - - static void SignalHandler( int sig, siginfo_t *info, void *ucontext ) - { - std::unique_ptr me( reinterpret_cast( info->si_value.sival_ptr ) ); - Handler( std::move( me ) ); - } - - operator aiocb*() - { - return cb.get(); - } - - private: - - struct SignalHandlerRegistrator - { - SignalHandlerRegistrator() - { - struct sigaction newact, oldact; - newact.sa_sigaction = SignalHandler; - sigemptyset( &newact.sa_mask ); - newact.sa_flags = SA_SIGINFO; - int rc = sigaction( SIGUSR1, &newact, &oldact ); - if( rc < 0 ) - throw std::runtime_error( strerror( errno ) ); - } - }; - - static void Handler( std::unique_ptr me ) - { - if( me->opcode == Opcode::None ) - return; - - using namespace XrdCl; - - int rc = aio_return( me->cb.get() ); - if( rc < 0 ) - { - Log *log = DefaultEnv::GetLog(); - log->Error( FileMsg, GetErrMsg( me->opcode ), strerror( errno ) ); - XRootDStatus *error = new XRootDStatus( stError, errErrorResponse, - XProtocol::mapError( errno ), - strerror( errno ) ); - QueueTask( error, 0, me->hosts, me->handler ); - } - else - { - AnyObject *resp = 0; - - if( me->opcode == Opcode::Read ) - { - ChunkInfo *chunk = new ChunkInfo( me->cb->aio_offset, - rc, - const_cast( me->cb->aio_buf ) ); - resp = new AnyObject(); - resp->Set( chunk ); - } - - QueueTask( new XRootDStatus(), resp, me->hosts, me->handler ); - } - } - - static const char* GetErrMsg( Opcode opcode ) - { - static const char readmsg[] = "Read: failed %s"; - static const char writemsg[] = "Write: failed %s"; - static const char syncmsg[] = "Sync: failed %s"; - - switch( opcode ) - { - case Opcode::Read: return readmsg; - - case Opcode::Write: return writemsg; - - case Opcode::Sync: return syncmsg; - - default: return 0; - } - } - - static void QueueTask( XrdCl::XRootDStatus *status, XrdCl::AnyObject *resp, - XrdCl::HostList *hosts, XrdCl::ResponseHandler *handler ) - { - using namespace XrdCl; - - // if it is simply the sync handler we can release the semaphore - // and return there is no need to execute this in the thread-pool - SyncResponseHandler *syncHandler = - dynamic_cast( handler ); - if( syncHandler ) - { - syncHandler->HandleResponse( status, resp ); - } - else - { - JobManager *jmngr = DefaultEnv::GetPostMaster()->GetJobManager(); - LocalFileTask *task = new LocalFileTask( status, resp, hosts, handler ); - jmngr->QueueJob( task ); - } - } - - std::unique_ptr cb; - Opcode opcode; - XrdCl::HostList *hosts; - XrdCl::ResponseHandler *handler; - }; - -}; - -namespace XrdCl -{ - - //------------------------------------------------------------------------ - // Constructor - //------------------------------------------------------------------------ - LocalFileHandler::LocalFileHandler() : - fd( -1 ) - { - jmngr = DefaultEnv::GetPostMaster()->GetJobManager(); - } - - //------------------------------------------------------------------------ - // Destructor - //------------------------------------------------------------------------ - LocalFileHandler::~LocalFileHandler() - { - - } - - //------------------------------------------------------------------------ - // Open - //------------------------------------------------------------------------ - XRootDStatus LocalFileHandler::Open( const std::string& url, uint16_t flags, - uint16_t mode, ResponseHandler* handler, uint16_t timeout ) - { - AnyObject *resp = 0; - XRootDStatus st = OpenImpl( url, flags, mode, resp ); - if( !st.IsOK() && st.code != errErrorResponse ) - return st; - - return QueueTask( new XRootDStatus( st ), resp, handler ); - } - - XRootDStatus LocalFileHandler::Open( const URL *url, const Message *req, AnyObject *&resp ) - { - const ClientOpenRequest* request = - reinterpret_cast( req->GetBuffer() ); - uint16_t flags = ntohs( request->options ); - uint16_t mode = ntohs( request->mode ); - return OpenImpl( url->GetURL(), flags, mode, resp ); - } - - //------------------------------------------------------------------------ - // Close - //------------------------------------------------------------------------ - XRootDStatus LocalFileHandler::Close( ResponseHandler* handler, - uint16_t timeout ) - { - if( close( fd ) == -1 ) - { - Log *log = DefaultEnv::GetLog(); - log->Error( FileMsg, "Close: file fd: %i %s", fd, strerror( errno ) ); - XRootDStatus *error = new XRootDStatus( stError, errErrorResponse, - XProtocol::mapError( errno ), - strerror( errno ) ); - return QueueTask( error, 0, handler ); - } - - return QueueTask( new XRootDStatus(), 0, handler ); - } - - //------------------------------------------------------------------------ - // Stat - //------------------------------------------------------------------------ - XRootDStatus LocalFileHandler::Stat( ResponseHandler* handler, - uint16_t timeout ) - { - Log *log = DefaultEnv::GetLog(); - - struct stat ssp; - if( fstat( fd, &ssp ) == -1 ) - { - log->Error( FileMsg, "Stat: failed fd: %i %s", fd, strerror( errno ) ); - XRootDStatus *error = new XRootDStatus( stError, errErrorResponse, - XProtocol::mapError( errno ), - strerror( errno ) ); - return QueueTask( error, 0, handler ); - } - std::ostringstream data; - data << ssp.st_dev << " " << ssp.st_size << " " << ssp.st_mode << " " - << ssp.st_mtime; - log->Debug( FileMsg, data.str().c_str() ); - - StatInfo *statInfo = new StatInfo(); - if( !statInfo->ParseServerResponse( data.str().c_str() ) ) - { - log->Error( FileMsg, "Stat: ParseServerResponse failed." ); - delete statInfo; - return QueueTask( new XRootDStatus( stError, errErrorResponse, kXR_FSError ), - 0, handler ); - } - - AnyObject *resp = new AnyObject(); - resp->Set( statInfo ); - return QueueTask( new XRootDStatus(), resp, handler ); - } - - //------------------------------------------------------------------------ - // Read - //------------------------------------------------------------------------ - XRootDStatus LocalFileHandler::Read( uint64_t offset, uint32_t size, - void* buffer, ResponseHandler* handler, uint16_t timeout ) - { - AioCtx *ctx = new AioCtx( pHostList, handler ); - ctx->SetRead( fd, offset, size, buffer ); - - int rc = aio_read( *ctx ); - - if( rc < 0 ) - { - Log *log = DefaultEnv::GetLog(); - log->Error( FileMsg, "Read: failed %s", strerror( errno ) ); - return XRootDStatus( stError, errOSError, XProtocol::mapError( rc ), - strerror( errno ) ); - } - - return XRootDStatus(); - } - - //------------------------------------------------------------------------ - // Write - //------------------------------------------------------------------------ - XRootDStatus LocalFileHandler::Write( uint64_t offset, uint32_t size, - const void* buffer, ResponseHandler* handler, uint16_t timeout ) - { - AioCtx *ctx = new AioCtx( pHostList, handler ); - ctx->SetWrite( fd, offset, size, buffer ); - - int rc = aio_write( *ctx ); - - if( rc < 0 ) - { - Log *log = DefaultEnv::GetLog(); - log->Error( FileMsg, "Write: failed %s", strerror( errno ) ); - return XRootDStatus( stError, errOSError, XProtocol::mapError( rc ), - strerror( errno ) ); - } - - return XRootDStatus(); - } - - //------------------------------------------------------------------------ - // Sync - //------------------------------------------------------------------------ - XRootDStatus LocalFileHandler::Sync( ResponseHandler* handler, - uint16_t timeout ) - { - AioCtx *ctx = new AioCtx( pHostList, handler ); - ctx->SetFsync( fd ); - - int rc = aio_fsync( O_SYNC, *ctx ); - - if( rc < 0 ) - { - Log *log = DefaultEnv::GetLog(); - log->Error( FileMsg, "Sync: failed %s", strerror( errno ) ); - return XRootDStatus( stError, errOSError, XProtocol::mapError( rc ), - strerror( errno ) ); - } - - return XRootDStatus(); - } - - //------------------------------------------------------------------------ - // Truncate - //------------------------------------------------------------------------ - XRootDStatus LocalFileHandler::Truncate( uint64_t size, - ResponseHandler* handler, uint16_t timeout ) - { - if( ftruncate( fd, size ) ) - { - Log *log = DefaultEnv::GetLog(); - log->Error( FileMsg, "Truncate: failed, file descriptor: %i, %s", fd, - strerror( errno ) ); - XRootDStatus *error = new XRootDStatus( stError, errErrorResponse, - XProtocol::mapError( errno ), - strerror( errno ) ); - return QueueTask( error, 0, handler ); - } - - return QueueTask( new XRootDStatus( stOK ), 0, handler ); - } - - //------------------------------------------------------------------------ - // VectorRead - //------------------------------------------------------------------------ - XRootDStatus LocalFileHandler::VectorRead( const ChunkList& chunks, - void* buffer, ResponseHandler* handler, uint16_t timeout ) - { - std::unique_ptr info( new VectorReadInfo() ); - size_t totalSize = 0; - bool useBuffer( buffer ); - - for( auto itr = chunks.begin(); itr != chunks.end(); ++itr ) - { - auto &chunk = *itr; - if( !useBuffer ) - buffer = chunk.buffer; - ssize_t bytesRead = pread( fd, buffer, chunk.length, - chunk.offset ); - if( bytesRead < 0 ) - { - Log *log = DefaultEnv::GetLog(); - log->Error( FileMsg, "VectorRead: failed, file descriptor: %i, %s", - fd, strerror( errno ) ); - XRootDStatus *error = new XRootDStatus( stError, errErrorResponse, - XProtocol::mapError( errno ), - strerror( errno ) ); - return QueueTask( error, 0, handler ); - } - totalSize += bytesRead; - info->GetChunks().push_back( ChunkInfo( chunk.offset, bytesRead, buffer ) ); - if( useBuffer ) - buffer = reinterpret_cast( buffer ) + bytesRead; - } - - info->SetSize( totalSize ); - AnyObject *resp = new AnyObject(); - resp->Set( info.release() ); - return QueueTask( new XRootDStatus(), resp, handler ); - } - - //------------------------------------------------------------------------ - // VectorWrite - //------------------------------------------------------------------------ - XRootDStatus LocalFileHandler::VectorWrite( const ChunkList &chunks, - ResponseHandler *handler, uint16_t timeout ) - { - - for( auto itr = chunks.begin(); itr != chunks.end(); ++itr ) - { - auto &chunk = *itr; - ssize_t bytesWritten = pwrite( fd, chunk.buffer, chunk.length, - chunk.offset ); - if( bytesWritten < 0 ) - { - Log *log = DefaultEnv::GetLog(); - log->Error( FileMsg, "VectorWrite: failed, file descriptor: %i, %s", - fd, strerror( errno ) ); - XRootDStatus *error = new XRootDStatus( stError, errErrorResponse, - XProtocol::mapError( errno ), - strerror( errno ) ); - return QueueTask( error, 0, handler ); - } - } - - return QueueTask( new XRootDStatus(), 0, handler ); - } - - //------------------------------------------------------------------------ - // WriteV - //------------------------------------------------------------------------ - XRootDStatus LocalFileHandler::WriteV( uint64_t offset, - const struct iovec *iov, - int iovcnt, - ResponseHandler *handler, - uint16_t timeout ) - { - iovec iovcp[iovcnt]; - memcpy( iovcp, iov, sizeof( iovcp ) ); - iovec *iovptr = iovcp; - - ssize_t size = 0; - for( int i = 0; i < iovcnt; ++i ) - size += iovptr[i].iov_len; - - ssize_t bytesWritten = 0; - while( bytesWritten < size ) - { -#ifdef __APPLE__ - ssize_t ret = lseek( fd, offset, SEEK_SET ); - if( ret >= 0 ) - ret = writev( fd, iovptr, iovcnt ); -#else - ssize_t ret = pwritev( fd, iovptr, iovcnt, offset ); -#endif - if( ret < 0 ) - { - Log *log = DefaultEnv::GetLog(); - log->Error( FileMsg, "WriteV: failed %s", strerror( errno ) ); - XRootDStatus *error = new XRootDStatus( stError, errErrorResponse, - XProtocol::mapError( errno ), - strerror( errno ) ); - return QueueTask( error, 0, handler ); - } - - bytesWritten += ret; - while( ret ) - { - if( size_t( ret ) > iovptr[0].iov_len ) - { - ret -= iovptr[0].iov_len; - --iovcnt; - ++iovptr; - } - else - { - iovptr[0].iov_len -= ret; - iovptr[0].iov_base = reinterpret_cast( iovptr[0].iov_base ) + ret; - ret = 0; - } - } - } - - return QueueTask( new XRootDStatus(), 0, handler ); - } - - //------------------------------------------------------------------------ - // Fcntl - //------------------------------------------------------------------------ - XRootDStatus LocalFileHandler::Fcntl( const Buffer &arg, - ResponseHandler *handler, uint16_t timeout ) - { - return XRootDStatus( stError, errNotSupported ); - } - - //------------------------------------------------------------------------ - // Visa - //------------------------------------------------------------------------ - XRootDStatus LocalFileHandler::Visa( ResponseHandler *handler, - uint16_t timeout ) - { - return XRootDStatus( stError, errNotSupported ); - } - - //------------------------------------------------------------------------ - // QueueTask - queues error/success tasks for all operations. - // Must always return stOK. - // Is always creating the same HostList containing only localhost. - //------------------------------------------------------------------------ - XRootDStatus LocalFileHandler::QueueTask( XRootDStatus *st, AnyObject *resp, - ResponseHandler *handler ) - { - // if it is simply the sync handler we can release the semaphore - // and return there is no need to execute this in the thread-pool - SyncResponseHandler *syncHandler = - dynamic_cast( handler ); - if( syncHandler ) - { - syncHandler->HandleResponse( st, resp ); - return XRootDStatus(); - } - - HostList *hosts = pHostList.empty() ? 0 : new HostList( pHostList ); - LocalFileTask *task = new LocalFileTask( st, resp, hosts, handler ); - jmngr->QueueJob( task ); - return XRootDStatus(); - } - - //------------------------------------------------------------------------ - // MkdirPath - creates the folders specified in file_path - // called if kXR_mkdir flag is set - //------------------------------------------------------------------------ - XRootDStatus LocalFileHandler::MkdirPath( const std::string &path ) - { - // first find the most up-front component that exists - size_t pos = path.rfind( '/' ); - while( pos != std::string::npos && pos != 0 ) - { - std::string tmp = path.substr( 0, pos ); - struct stat st; - int rc = lstat( tmp.c_str(), &st ); - if( rc == 0 ) break; - if( errno != ENOENT ) - return XRootDStatus( stError, errErrorResponse, - XProtocol::mapError( errno ), - strerror( errno ) ); - pos = path.rfind( '/', pos - 1 ); - } - - pos = path.find( '/', pos + 1 ); - while( pos != std::string::npos && pos != 0 ) - { - std::string tmp = path.substr( 0, pos ); - if( mkdir( tmp.c_str(), 0755 ) ) - { - if( errno != EEXIST ) - return XRootDStatus( stError, errErrorResponse, - XProtocol::mapError( errno ), - strerror( errno ) ); - } - pos = path.find( '/', pos + 1 ); - } - return XRootDStatus(); - } - - XRootDStatus LocalFileHandler::OpenImpl( const std::string &url, uint16_t flags, - uint16_t mode, AnyObject *&resp) - { - Log *log = DefaultEnv::GetLog(); - - // safe the file URL for the HostList for later - pUrl = url; - - URL fileUrl( url ); - if( !fileUrl.IsValid() ) - return XRootDStatus( stError, errInvalidArgs ); - - if( fileUrl.GetHostName() != "localhost" ) - return XRootDStatus( stError, errNotSupported ); - - std::string path = fileUrl.GetPath(); - - //--------------------------------------------------------------------- - // Prepare Flags - //--------------------------------------------------------------------- - uint16_t openflags = 0; - if( flags & kXR_new ) - openflags |= O_CREAT | O_EXCL; - if( flags & kXR_open_wrto ) - openflags |= O_WRONLY; - else if( flags & kXR_open_updt ) - openflags |= O_RDWR; - else - openflags |= O_RDONLY; - if( flags & kXR_delete ) - openflags |= O_CREAT | O_TRUNC; - - if( flags & kXR_mkdir ) - { - XRootDStatus st = MkdirPath( path ); - if( !st.IsOK() ) - { - log->Error( FileMsg, "Open MkdirPath failed %s: %s", path.c_str(), - strerror( st.errNo ) ); - return st; - } - - } - //--------------------------------------------------------------------- - // Open File - //--------------------------------------------------------------------- - if( mode == Access::Mode::None) - mode = 0600; - fd = open( path.c_str(), openflags, mode ); - if( fd == -1 ) - { - log->Error( FileMsg, "Open: open failed: %s: %s", path.c_str(), - strerror( errno ) ); - - return XRootDStatus( stError, errErrorResponse, - XProtocol::mapError( errno ), - strerror( errno ) ); - } - //--------------------------------------------------------------------- - // Stat File and cache statInfo in openInfo - //--------------------------------------------------------------------- - struct stat ssp; - if( fstat( fd, &ssp ) == -1 ) - { - log->Error( FileMsg, "Open: stat failed." ); - return XRootDStatus( stError, errErrorResponse, - XProtocol::mapError( errno ), - strerror( errno ) ); - } - - std::ostringstream data; - data << ssp.st_dev << " " << ssp.st_size << " " << ssp.st_mode << " " - << ssp.st_mtime; - - StatInfo *statInfo = new StatInfo(); - if( !statInfo->ParseServerResponse( data.str().c_str() ) ) - { - log->Error( FileMsg, "Open: ParseServerResponse failed." ); - delete statInfo; - return XRootDStatus( stError, errErrorResponse, kXR_FSError ); - } - - // add the URL to hosts list - pHostList.push_back( HostInfo( pUrl, false ) ); - - //All went well - uint8_t ufd = fd; - OpenInfo *openInfo = new OpenInfo( &ufd, 1, statInfo ); - resp = new AnyObject(); - resp->Set( openInfo ); - return XRootDStatus(); - } -} diff --git a/src/XrdCl/XrdClLocalFileHandler.hh b/src/XrdCl/XrdClLocalFileHandler.hh deleted file mode 100644 index 5b87389d774..00000000000 --- a/src/XrdCl/XrdClLocalFileHandler.hh +++ /dev/null @@ -1,259 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2017 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH -// Author: Paul-Niklas Kramp -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ -#ifndef __XRD_CL_LOCAL_FILE_HANDLER_HH__ -#define __XRD_CL_LOCAL_FILE_HANDLER_HH__ -#include "XrdCl/XrdClJobManager.hh" -#include "XrdCl/XrdClLocalFileTask.hh" -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdCl/XrdClLog.hh" - -#include - -namespace XrdCl -{ - class Message; - - class LocalFileHandler - { - public: - - LocalFileHandler(); - - ~LocalFileHandler(); - - //------------------------------------------------------------------------ - //! Open the file pointed to by the given URL - //! - //! @param url url of the file to be opened - //! @param flags OpenFlags::Flags - //! @param mode Access::Mode for new files, 0 otherwise - //! @param handler handler to be notified about the status of the operation - //! @param timeout timeout value, if 0 the environment default will be - //! used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Open( const std::string &url, uint16_t flags, uint16_t mode, - ResponseHandler *handler, uint16_t timeout = 0 ); - - //------------------------------------------------------------------------ - //! Handle local redirect to given URL triggered by the given request - //------------------------------------------------------------------------ - XRootDStatus Open( const URL *url, const Message *req, AnyObject *&resp ); - - //------------------------------------------------------------------------ - //! Close the file object - //! - //! @param handler handler to be notified about the status of the operation - //! @param timeout timeout value, if 0 the environment default will be - //! used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Close( ResponseHandler *handler, uint16_t timeout = 0 ); - - //------------------------------------------------------------------------ - //! Obtain status information for this file - async - //! - //! @param handler handler to be notified when the response arrives, - //! the response parameter will hold a StatInfo object - //! if the procedure is successful - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Stat( ResponseHandler *handler, uint16_t timeout = 0 ); - - //------------------------------------------------------------------------ - //! Read a data chunk at a given offset - sync - //! - //! @param offset offset from the beginning of the file - //! @param size number of bytes to be read - //! @param buffer a pointer to a buffer big enough to hold the data - //! or 0 if the buffer should be allocated by the system - //! @param handler handler to be notified when the response arrives, - //! the response parameter will hold a buffer object if - //! the procedure was successful, if a preallocated - //! buffer was specified then the buffer object will - //! "wrap" this buffer - //! @param timeout timeout value, if 0 the environment default will be - //! used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Read( uint64_t offset, uint32_t size, void *buffer, - ResponseHandler *handler, uint16_t timeout = 0 ); - - //------------------------------------------------------------------------ - //! Write a data chunk at a given offset - async - //! - //! @param offset offset from the beginning of the file - //! @param size number of bytes to be written - //! @param buffer a pointer to the buffer holding the data to be written - //! @param handler handler to be notified when the response arrives - //! @param timeout timeout value, if 0 the environment default will be - //! used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Write( uint64_t offset, uint32_t size, const void *buffer, - ResponseHandler *handler, uint16_t timeout = 0 ); - - //------------------------------------------------------------------------ - //! Commit all pending disk writes - async - //! - //! @param handler handler to be notified when the response arrives - //! @param timeout timeout value, if 0 the environment default will be - //! used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Sync( ResponseHandler *handler, uint16_t timeout = 0 ); - - //------------------------------------------------------------------------ - //! Truncate the file to a particular size - async - //! - //! @param size desired size of the file - //! @param handler handler to be notified when the response arrives - //! @param timeout timeout value, if 0 the environment default will be - //! used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Truncate( uint64_t size, ResponseHandler *handler, - uint16_t timeout = 0 ); - - //------------------------------------------------------------------------ - //! Read scattered data chunks in one operation - async - //! - //! @param chunks list of the chunks to be read - //! @param buffer a pointer to a buffer big enough to hold the data - //! @param handler handler to be notified when the response arrives - //! @param timeout timeout value, if 0 then the environment default - //! will be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus VectorRead( const ChunkList &chunks, void *buffer, - ResponseHandler *handler, uint16_t timeout = 0 ); - - //------------------------------------------------------------------------ - //! Write scattered data chunks in one operation - async - //! - //! @param chunks list of the chunks to be read - //! @param handler handler to be notified when the response arrives - //! @param timeout timeout value, if 0 then the environment default - //! will be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus VectorWrite( const ChunkList &chunks, - ResponseHandler *handler, uint16_t timeout = 0 ); - - //------------------------------------------------------------------------ - //! Write scattered buffers in one operation - async - //! - //! @param offset offset from the beginning of the file - //! @param iov list of the buffers to be written - //! @param iovcnt number of buffers - //! @param handler handler to be notified when the response arrives - //! @param timeout timeout value, if 0 then the environment default - //! will be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus WriteV( uint64_t offset, - const struct iovec *iov, - int iovcnt, - ResponseHandler *handler, - uint16_t timeout = 0 ); - - //------------------------------------------------------------------------ - //! Queues a task to the jobmanager - //! - //! @param st the status of the file operation - //! @param obj the object holding data like open-, chunk- or vreadinfo - //! @param handler handler to be notified when the response arrives - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus QueueTask( XRootDStatus *st, AnyObject *obj, - ResponseHandler *handler ); - - //------------------------------------------------------------------------ - //! Performs a custom operation on an open file - async - //! - //! @param arg query argument - //! @param handler handler to be notified when the response arrives, - //! the response parameter will hold a Buffer object - //! if the procedure is successful - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Fcntl( const Buffer &arg, ResponseHandler *handler, - uint16_t timeout = 0 ); - - //------------------------------------------------------------------------ - //! Get access token to a file - async - //! - //! @param handler handler to be notified when the response arrives, - //! the response parameter will hold a Buffer object - //! if the procedure is successful - //! @param timeout timeout value, if 0 the environment default will - //! be used - //! @return status of the operation - //------------------------------------------------------------------------ - XRootDStatus Visa( ResponseHandler *handler, uint16_t timeout = 0 ); - //------------------------------------------------------------------------ - //! creates the directories specified in file_path - //! - //! @param file_path specifies which directories are to be created - //! @param mode same access modes as for the desired file operation - //! @return status of the mkdir system call - //------------------------------------------------------------------------ - static XRootDStatus MkdirPath( const std::string &path ); - - void SetHostList( const HostList &hostList ) - { - pHostList = hostList; - } - - const HostList& GetHostList() - { - return pHostList; - } - - private: - - XRootDStatus OpenImpl( const std::string &url, uint16_t flags, - uint16_t mode, AnyObject *&resp ); - - //--------------------------------------------------------------------- - // Receives LocalFileTasks to handle them async - //--------------------------------------------------------------------- - JobManager *jmngr; - - //--------------------------------------------------------------------- - // Internal filedescriptor, which is used by all operations after open - //--------------------------------------------------------------------- - int fd; - - //--------------------------------------------------------------------- - // The file URL - //--------------------------------------------------------------------- - std::string pUrl; - - //--------------------------------------------------------------------- - // The host list returned in the user callback - //--------------------------------------------------------------------- - HostList pHostList; - - }; -} -#endif diff --git a/src/XrdCl/XrdClLocalFileTask.cc b/src/XrdCl/XrdClLocalFileTask.cc deleted file mode 100644 index 1454ea61af8..00000000000 --- a/src/XrdCl/XrdClLocalFileTask.cc +++ /dev/null @@ -1,43 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2017 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH -// Author: Paul-Niklas Kramp -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ -#include "XrdCl/XrdClLocalFileTask.hh" - -namespace XrdCl -{ - LocalFileTask::LocalFileTask( XRootDStatus *st, AnyObject *obj, HostList *hosts, ResponseHandler *responsehandler ) - { - this->st = st; - this->obj = obj; - this->hosts = hosts; - this->responsehandler = responsehandler; - } - - LocalFileTask::~LocalFileTask(){} - - void LocalFileTask::Run( void *arg ) - { - if( responsehandler ) - responsehandler->HandleResponseWithHosts( st, obj, hosts ); - else{ - delete st; - delete obj; - delete hosts; - } - delete this; - } -} diff --git a/src/XrdCl/XrdClLocalFileTask.hh b/src/XrdCl/XrdClLocalFileTask.hh deleted file mode 100644 index 449303965f8..00000000000 --- a/src/XrdCl/XrdClLocalFileTask.hh +++ /dev/null @@ -1,42 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2017 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH -// Author: Paul-Niklas Kramp -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ -#ifndef __XRD_CL_LOCAL_FILE_TASK_HH__ -#define __XRD_CL_LOCAL_FILE_TASK_HH__ - -#include "XrdCl/XrdClStatus.hh" -#include "XrdCl/XrdClAnyObject.hh" -#include "XrdCl/XrdClJobManager.hh" -#include "XrdCl/XrdClXRootDResponses.hh" - -namespace XrdCl{ - class LocalFileTask : public Job{ - - public: - LocalFileTask( XRootDStatus *st, AnyObject *obj, HostList *hosts, ResponseHandler *responsehandler ); - ~LocalFileTask(); - virtual void Run( void *arg ); - - private: - XRootDStatus *st; - AnyObject *obj; - HostList *hosts; - ResponseHandler *responsehandler; - }; -} - -#endif diff --git a/src/XrdCl/XrdClLog.cc b/src/XrdCl/XrdClLog.cc deleted file mode 100644 index 53fa4da56b4..00000000000 --- a/src/XrdCl/XrdClLog.cc +++ /dev/null @@ -1,311 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "XrdCl/XrdClOptimizers.hh" -#include "XrdCl/XrdClLog.hh" - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - // Open a file - //---------------------------------------------------------------------------- - bool LogOutFile::Open( const std::string &filename ) - { - int fd = open( filename.c_str(), O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR ); - if( fd < 0 ) - { - std::cerr << "Unable to open " << filename << " " << strerror( errno ); - std::cerr << std::endl; - return false; - } - pFileDes = fd; - return true; - } - - //---------------------------------------------------------------------------- - // Destructor - //---------------------------------------------------------------------------- - void LogOutFile::Close() - { - if( pFileDes != -1 ) - { - close( pFileDes ); - pFileDes = -1; - } - } - - //---------------------------------------------------------------------------- - // Message handler - //---------------------------------------------------------------------------- - void LogOutFile::Write( const std::string &message ) - { - if( unlikely( pFileDes == -1 ) ) - { - std::cerr << "Log file not opened" << std::endl; - return; - } - int ret = write( pFileDes, message.c_str(), message.length() ); - if( ret < 0 ) - { - std::cerr << "Unable to write to the log file: " << strerror( errno ); - std::cerr << std::endl; - return; - } - } - - //---------------------------------------------------------------------------- - // Message handler - //---------------------------------------------------------------------------- - void LogOutCerr::Write( const std::string &message ) - { - pMutex.Lock(); - std::cerr << message; - pMutex.UnLock(); - } - - //---------------------------------------------------------------------------- - // Print an error message - //---------------------------------------------------------------------------- - void Log::Say( LogLevel level, - uint64_t topic, - const char *format, - va_list list ) - { - //-------------------------------------------------------------------------- - // Build the user message - //-------------------------------------------------------------------------- - int size = 1024; - int ret = 0; - char *buffer = 0; - while(1) - { - va_list cp; - va_copy( cp, list ); - buffer = new char[size]; - ret = vsnprintf( buffer, size, format, cp ); - va_end( cp ); - - if( ret < 0 ) - { - snprintf( buffer, size, "Error while processing a log message \"%s\" \n", format); - pOutput->Write(buffer); - delete [] buffer; - return; - } - else if( ret < size ) - break; - - size *= 2; - delete [] buffer; - } - - //-------------------------------------------------------------------------- - // Add time and error level - //-------------------------------------------------------------------------- - char now[48]; - char ts[32]; - char tz[8]; - tm tsNow; - timeval ttNow; - - gettimeofday( &ttNow, 0 ); - localtime_r( &ttNow.tv_sec, &tsNow ); - - strftime( ts, 32, "%Y-%m-%d %H:%M:%S", &tsNow ); - strftime( tz, 8, "%z", &tsNow ); - snprintf( now, 48, "%s.%06ld %s", ts, (long int)ttNow.tv_usec, tz ); - - XrdOucTokenizer tok( buffer ); - char *line = 0; - std::ostringstream out; - while( (line = tok.GetLine()) ) - { - out << "[" << now << "][" << LogLevelToString( level ) << "]"; - out << "[" << TopicToString( topic ) << "]"; - if(pPid) out << "[" << std::setw(5) << pPid << "]"; - out << " " << line << std::endl; - } - - pOutput->Write( out.str() ); - delete [] buffer; - } - - //---------------------------------------------------------------------------- - // Map a topic number to a string - //---------------------------------------------------------------------------- - void Log::SetTopicName( uint64_t topic, std::string name ) - { - uint32_t len = name.length(); - if( len > pTopicMaxLength ) - { - pTopicMaxLength = len; - TopicMap::iterator it; - for( it = pTopicMap.begin(); it != pTopicMap.end(); ++it ) - it->second.append( len-it->second.length(), ' ' ); - } - else - name.append( pTopicMaxLength-len, ' ' ); - pTopicMap[topic] = name; - } - - //---------------------------------------------------------------------------- - // Convert log level to string - //---------------------------------------------------------------------------- - std::string Log::LogLevelToString( LogLevel level ) - { - switch( level ) - { - case ErrorMsg: - return "Error "; - case WarningMsg: - return "Warning"; - case InfoMsg: - return "Info "; - case DebugMsg: - return "Debug "; - case DumpMsg: - return "Dump "; - default: - return "Unknown Level"; - } - } - - //---------------------------------------------------------------------------- - // Convert a string to LogLevel - //---------------------------------------------------------------------------- - bool Log::StringToLogLevel( const std::string &strLevel, LogLevel &level ) - { - if( strLevel == "Error" ) level = ErrorMsg; - else if( strLevel == "Warning" ) level = WarningMsg; - else if( strLevel == "Info" ) level = InfoMsg; - else if( strLevel == "Debug" ) level = DebugMsg; - else if( strLevel == "Dump" ) level = DumpMsg; - else return false; - return true; - } - - //---------------------------------------------------------------------------- - // Convert a topic number to a string - //---------------------------------------------------------------------------- - std::string Log::TopicToString( uint64_t topic ) - { - TopicMap::iterator it = pTopicMap.find( topic ); - if( it != pTopicMap.end() ) - return it->second; - std::ostringstream o; - o << "0x" << std::setw(pTopicMaxLength-2) << std::setfill( '0' ); - o << std::setbase(16) << topic; - return o.str(); - } - - //---------------------------------------------------------------------------- - // Report an error - //---------------------------------------------------------------------------- - void Log::Error( uint64_t topic, const char *format, ... ) - { - if( unlikely( GetLevel() < ErrorMsg ) ) - return; - - if( unlikely( (topic & pMask[ErrorMsg]) == 0 ) ) - return; - - va_list argList; - va_start( argList, format ); - Say( ErrorMsg, topic, format, argList ); - va_end( argList ); - } - - //---------------------------------------------------------------------------- - // Report a warning - //---------------------------------------------------------------------------- - void Log::Warning( uint64_t topic, const char *format, ... ) - { - if( unlikely( GetLevel() < WarningMsg ) ) - return; - - if( unlikely( (topic & pMask[WarningMsg]) == 0 ) ) - return; - - va_list argList; - va_start( argList, format ); - Say( WarningMsg, topic, format, argList ); - va_end( argList ); - } - - //---------------------------------------------------------------------------- - // Print an info - //---------------------------------------------------------------------------- - void Log::Info( uint64_t topic, const char *format, ... ) - { - if( likely( GetLevel() < InfoMsg ) ) - return; - - if( unlikely( (topic & pMask[InfoMsg]) == 0 ) ) - return; - - va_list argList; - va_start( argList, format ); - Say( InfoMsg, topic, format, argList ); - va_end( argList ); - } - - //---------------------------------------------------------------------------- - // Print a debug message - //---------------------------------------------------------------------------- - void Log::Debug( uint64_t topic, const char *format, ... ) - { - if( likely( GetLevel() < DebugMsg ) ) - return; - - if( unlikely( (topic & pMask[DebugMsg]) == 0 ) ) - return; - - va_list argList; - va_start( argList, format ); - Say( DebugMsg, topic, format, argList ); - va_end( argList ); - } - - //---------------------------------------------------------------------------- - // Print a dump message - //---------------------------------------------------------------------------- - void Log::Dump( uint64_t topic, const char *format, ... ) - { - if( likely( GetLevel() < DumpMsg ) ) - return; - - if( unlikely( (topic & pMask[DumpMsg]) == 0 ) ) - return; - - va_list argList; - va_start( argList, format ); - Say( DumpMsg, topic, format, argList ); - va_end( argList ); - } -} diff --git a/src/XrdCl/XrdClLog.hh b/src/XrdCl/XrdClLog.hh deleted file mode 100644 index bde0766ef1c..00000000000 --- a/src/XrdCl/XrdClLog.hh +++ /dev/null @@ -1,265 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2014 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_LOG_HH__ -#define __XRD_CL_LOG_HH__ - -#include -#include -#include -#include -#include "XrdSys/XrdSysPthread.hh" - -//------------------------------------------------------------------------------ -// C++11 atomics are used to avoid illegal behavior when setting/getting the -// log level. To minimize costs across all platforms, we use -// std::memory_order_relaxed; this means threads may reorder SetLogLevel writes -// and the visibility is relatively undefined. However, we know the stores are -// at least atomic. -//------------------------------------------------------------------------------ -#if __cplusplus >= 201103L -#include -#endif - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - //! Interface for logger outputs - //---------------------------------------------------------------------------- - class LogOut - { - public: - virtual ~LogOut() {} - - //------------------------------------------------------------------------ - //! Write a message to the destination - //! - //! @param message message to be written - //------------------------------------------------------------------------ - virtual void Write( const std::string &message ) = 0; - }; - - //---------------------------------------------------------------------------- - //! Write log messages to a file - //---------------------------------------------------------------------------- - class LogOutFile: public LogOut - { - public: - LogOutFile(): pFileDes(-1) {}; - virtual ~LogOutFile() { Close(); }; - - //------------------------------------------------------------------------ - //! Open the log file - //------------------------------------------------------------------------ - bool Open( const std::string &fileName ); - - //------------------------------------------------------------------------ - //! Close the log file - //------------------------------------------------------------------------ - void Close(); - virtual void Write( const std::string &message ); - private: - int pFileDes; - }; - - //---------------------------------------------------------------------------- - //! Write log messages to stderr - //---------------------------------------------------------------------------- - class LogOutCerr: public LogOut - { - public: - virtual void Write( const std::string &message ); - virtual ~LogOutCerr() {} - private: - XrdSysMutex pMutex; - }; - - //---------------------------------------------------------------------------- - //! Handle diagnostics - //---------------------------------------------------------------------------- - class Log - { - public: - //------------------------------------------------------------------------ - //! Log levels - //------------------------------------------------------------------------ - enum LogLevel - { - NoMsg = 0, //!< report nothing - ErrorMsg = 1, //!< report errors - WarningMsg = 2, //!< report warnings - InfoMsg = 3, //!< print info - DebugMsg = 4, //!< print debug info - DumpMsg = 5 //!< print details of the request and responses - }; - - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - Log(): pLevel( NoMsg ), pTopicMaxLength( 18 ), pPid(0) - { - pOutput = new LogOutCerr(); - int maxMask = (int)DumpMsg+1; - for( int i = 0; i < maxMask; ++i ) - pMask[i] = 0xffffffffffffffffULL; - } - - //------------------------------------------------------------------------ - // Destructor - //------------------------------------------------------------------------ - ~Log() - { - delete pOutput; - } - - //------------------------------------------------------------------------ - //! Report an error - //------------------------------------------------------------------------ - void Error( uint64_t topic, const char *format, ... ); - - //------------------------------------------------------------------------ - //! Report a warning - //------------------------------------------------------------------------ - void Warning( uint64_t topic, const char *format, ... ); - - //------------------------------------------------------------------------ - //! Print an info - //------------------------------------------------------------------------ - void Info( uint64_t topic, const char *format, ... ); - - //------------------------------------------------------------------------ - //! Print a debug message - //------------------------------------------------------------------------ - void Debug( uint64_t topic, const char *format, ... ); - - //------------------------------------------------------------------------ - //! Print a dump message - //------------------------------------------------------------------------ - void Dump( uint64_t topic, const char *format, ... ); - - //------------------------------------------------------------------------ - //! Always print the message - //! - //! @param level log level - //! @param type topic of the message - //! @param format format string - the same as in printf - //! @param list list of arguments - //------------------------------------------------------------------------ - void Say( LogLevel level, uint64_t topic, const char *format, va_list list ); - - //------------------------------------------------------------------------ - //! Set the level of the messages that should be sent to the destination - //------------------------------------------------------------------------ - void SetLevel( LogLevel level ) - { -#if __cplusplus >= 201103L - pLevel.store(level, std::memory_order_relaxed); -#else - pLevel = level; -#endif - } - - //------------------------------------------------------------------------ - //! Set the level of the messages that should be sent to the destination - //------------------------------------------------------------------------ - void SetLevel( const std::string &level ) - { - LogLevel lvl; - if( StringToLogLevel( level, lvl ) ) - SetLevel( lvl ); - } - - //------------------------------------------------------------------------ - //! Set the output that should be used. - //------------------------------------------------------------------------ - void SetOutput( LogOut *output ) - { - delete pOutput; - pOutput = output; - } - - //------------------------------------------------------------------------ - //! Sets the mask for the topics of messages that should be printed - //------------------------------------------------------------------------ - void SetMask( LogLevel level, uint64_t mask ) - { - pMask[level] = mask; - } - - //------------------------------------------------------------------------ - //! Sets the mask for the topics of messages that should be printed - //------------------------------------------------------------------------ - void SetMask( const std::string &level, uint64_t mask ) - { - LogLevel lvl; - if( StringToLogLevel( level, lvl ) ) - pMask[lvl] = mask; - } - - //------------------------------------------------------------------------ - //! Map a topic number to a string - //------------------------------------------------------------------------ - void SetTopicName( uint64_t topic, std::string name ); - - //------------------------------------------------------------------------ - //! Get the log level - //------------------------------------------------------------------------ - LogLevel GetLevel() const - { -#if __cplusplus >= 201103L - LogLevel lvl = pLevel.load(std::memory_order_relaxed); - return lvl; -#else - return pLevel; -#endif - } - - //------------------------------------------------------------------------ - //! Set pid - //------------------------------------------------------------------------ - void SetPid(pid_t pid) - { - pPid = pid; - } - - private: - typedef std::map TopicMap; - std::string LogLevelToString( LogLevel level ); - bool StringToLogLevel( const std::string &strLevel, LogLevel &level ); - std::string TopicToString( uint64_t topic ); - -#if __cplusplus >= 201103L - std::atomic pLevel; -#else - LogLevel pLevel; -#endif - uint64_t pMask[DumpMsg+1]; - LogOut *pOutput; - TopicMap pTopicMap; - uint32_t pTopicMaxLength; - pid_t pPid; - }; -} - -#endif // __XRD_CL_LOG_HH__ diff --git a/src/XrdCl/XrdClMessage.hh b/src/XrdCl/XrdClMessage.hh deleted file mode 100644 index 6529dcee58a..00000000000 --- a/src/XrdCl/XrdClMessage.hh +++ /dev/null @@ -1,102 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_MESSAGE_HH__ -#define __XRD_CL_MESSAGE_HH__ - -#include "XrdCl/XrdClBuffer.hh" - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - //! The message representation used throughout the system - //---------------------------------------------------------------------------- - class Message: public Buffer - { - public: - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - Message( uint32_t size = 0 ): - Buffer( size ), pIsMarshalled( false ), pSessionId(0) - { - if( size ) - Zero(); - } - - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - virtual ~Message() {} - - //------------------------------------------------------------------------ - //! Check if the message is marshalled - //------------------------------------------------------------------------ - bool IsMarshalled() const - { - return pIsMarshalled; - } - - //------------------------------------------------------------------------ - //! Set the marshalling status - //------------------------------------------------------------------------ - void SetIsMarshalled( bool isMarshalled ) - { - pIsMarshalled = isMarshalled; - } - - //------------------------------------------------------------------------ - //! Set the description of the message - //------------------------------------------------------------------------ - void SetDescription( const std::string &description ) - { - pDescription = description; - } - - //------------------------------------------------------------------------ - //! Get the description of the message - //------------------------------------------------------------------------ - const std::string &GetDescription() const - { - return pDescription; - } - - //------------------------------------------------------------------------ - //! Set the session ID which this message is meant for - //------------------------------------------------------------------------ - void SetSessionId( uint64_t sessionId ) - { - pSessionId = sessionId; - } - - //------------------------------------------------------------------------ - //! Get the session ID the message is meant for - //------------------------------------------------------------------------ - uint64_t GetSessionId() const - { - return pSessionId; - } - - private: - bool pIsMarshalled; - uint64_t pSessionId; - std::string pDescription; - }; -} - -#endif // __XRD_CL_MESSAGE_HH__ diff --git a/src/XrdCl/XrdClMessageUtils.cc b/src/XrdCl/XrdClMessageUtils.cc deleted file mode 100644 index 0ca417ec06a..00000000000 --- a/src/XrdCl/XrdClMessageUtils.cc +++ /dev/null @@ -1,323 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2014 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#include "XrdCl/XrdClMessageUtils.hh" -#include "XrdCl/XrdClLog.hh" -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdCl/XrdClConstants.hh" -#include "XrdCl/XrdClAnyObject.hh" -#include "XrdCl/XrdClSIDManager.hh" -#include "XrdCl/XrdClPostMaster.hh" -#include "XrdCl/XrdClXRootDTransport.hh" -#include "XrdCl/XrdClXRootDMsgHandler.hh" -#include "XrdClRedirectorRegistry.hh" - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - // Send a message - //---------------------------------------------------------------------------- - Status MessageUtils::SendMessage( const URL &url, - Message *msg, - ResponseHandler *handler, - const MessageSendParams &sendParams, - LocalFileHandler *lFileHandler ) - { - //-------------------------------------------------------------------------- - // Get the stuff needed to send the message - //-------------------------------------------------------------------------- - Log *log = DefaultEnv::GetLog(); - PostMaster *postMaster = DefaultEnv::GetPostMaster(); - Status st; - - if( !postMaster ) - return Status( stError, errUninitialized ); - - log->Dump( XRootDMsg, "[%s] Sending message %s", - url.GetHostId().c_str(), msg->GetDescription().c_str() ); - - - AnyObject sidMgrObj; - SIDManager *sidMgr = 0; - st = postMaster->QueryTransport( url, XRootDQuery::SIDManager, - sidMgrObj ); - - if( !st.IsOK() ) - { - log->Error( XRootDMsg, "[%s] Unable to get stream id manager", - url.GetHostId().c_str() ); - return st; - } - sidMgrObj.Get( sidMgr ); - - ClientRequestHdr *req = (ClientRequestHdr*)msg->GetBuffer(); - - - //-------------------------------------------------------------------------- - // Allocate the SID and marshall the message - //-------------------------------------------------------------------------- - st = sidMgr->AllocateSID( req->streamid ); - if( !st.IsOK() ) - { - log->Error( XRootDMsg, "[%s] Unable to allocate stream id", - url.GetHostId().c_str() ); - return st; - } - - XRootDTransport::MarshallRequest( msg ); - - //-------------------------------------------------------------------------- - // Create and set up the message handler - //-------------------------------------------------------------------------- - XRootDMsgHandler *msgHandler; - msgHandler = new XRootDMsgHandler( msg, handler, &url, sidMgr, lFileHandler ); - msgHandler->SetExpiration( sendParams.expires ); - msgHandler->SetRedirectAsAnswer( !sendParams.followRedirects ); - msgHandler->SetChunkList( sendParams.chunkList ); - msgHandler->SetRedirectCounter( sendParams.redirectLimit ); - - if( sendParams.loadBalancer.url.IsValid() ) - msgHandler->SetLoadBalancer( sendParams.loadBalancer ); - - HostList *list = 0; - list = new HostList(); - list->push_back( url ); - msgHandler->SetHostList( list ); - - //-------------------------------------------------------------------------- - // Send the message - //-------------------------------------------------------------------------- - st = postMaster->Send( url, msg, msgHandler, sendParams.stateful, - sendParams.expires ); - if( !st.IsOK() ) - { - XRootDTransport::UnMarshallRequest( msg ); - log->Error( XRootDMsg, "[%s] Unable to send the message %s: %s", - url.GetHostId().c_str(), msg->GetDescription().c_str(), - st.ToString().c_str() ); - delete msgHandler; - delete list; - return st; - } - return Status(); - } - - //---------------------------------------------------------------------------- - // Redirect a message - //---------------------------------------------------------------------------- - Status MessageUtils::RedirectMessage( const URL &url, - Message *msg, - ResponseHandler *handler, - MessageSendParams &sendParams, - LocalFileHandler *lFileHandler ) - { - //-------------------------------------------------------------------------- - // Register a new virtual redirector - //-------------------------------------------------------------------------- - RedirectorRegistry& registry = RedirectorRegistry::Instance(); - Status st = registry.Register( url ); - if( !st.IsOK() ) - return st; - - //-------------------------------------------------------------------------- - // Get the stuff needed to send the message - //-------------------------------------------------------------------------- - Log *log = DefaultEnv::GetLog(); - PostMaster *postMaster = DefaultEnv::GetPostMaster(); - - if( !postMaster ) - return Status( stError, errUninitialized ); - - log->Dump( XRootDMsg, "[%s] Redirecting message %s", - url.GetHostId().c_str(), msg->GetDescription().c_str() ); - - XRootDTransport::MarshallRequest( msg ); - - //-------------------------------------------------------------------------- - // Create and set up the message handler - //-------------------------------------------------------------------------- - XRootDMsgHandler *msgHandler; - msgHandler = new XRootDMsgHandler( msg, handler, &url, 0, lFileHandler ); - msgHandler->SetExpiration( sendParams.expires ); - msgHandler->SetRedirectAsAnswer( !sendParams.followRedirects ); - msgHandler->SetChunkList( sendParams.chunkList ); - msgHandler->SetRedirectCounter( sendParams.redirectLimit ); - msgHandler->SetFollowMetalink( true ); - - HostInfo info( url, true ); - sendParams.loadBalancer = info; - msgHandler->SetLoadBalancer( info ); - - HostList *list = 0; - list = new HostList(); - list->push_back( info ); - msgHandler->SetHostList( list ); - - //-------------------------------------------------------------------------- - // Redirect the message - //-------------------------------------------------------------------------- - st = postMaster->Redirect( url, msg, msgHandler, msgHandler ); - if( !st.IsOK() ) - { - XRootDTransport::UnMarshallRequest( msg ); - log->Error( XRootDMsg, "[%s] Unable to send the message %s: %s", - url.GetHostId().c_str(), msg->GetDescription().c_str(), - st.ToString().c_str() ); - delete msgHandler; - delete list; - return st; - } - return Status(); - } - - //---------------------------------------------------------------------------- - // Process sending params - //---------------------------------------------------------------------------- - void MessageUtils::ProcessSendParams( MessageSendParams &sendParams ) - { - //-------------------------------------------------------------------------- - // Timeout - //-------------------------------------------------------------------------- - Env *env = DefaultEnv::GetEnv(); - if( sendParams.timeout == 0 ) - { - int requestTimeout = DefaultRequestTimeout; - env->GetInt( "RequestTimeout", requestTimeout ); - sendParams.timeout = requestTimeout; - } - - if( sendParams.expires == 0 ) - sendParams.expires = ::time(0)+sendParams.timeout; - - //-------------------------------------------------------------------------- - // Redirect limit - //-------------------------------------------------------------------------- - if( sendParams.redirectLimit == 0 ) - { - int redirectLimit = DefaultRedirectLimit; - env->GetInt( "RedirectLimit", redirectLimit ); - sendParams.redirectLimit = redirectLimit; - } - } - - //---------------------------------------------------------------------------- - //! Append cgi to the one already present in the message - //---------------------------------------------------------------------------- - void MessageUtils::RewriteCGIAndPath( Message *msg, - const URL::ParamsMap &newCgi, - bool replace, - const std::string &newPath ) - { - ClientRequest *req = (ClientRequest *)msg->GetBuffer(); - switch( req->header.requestid ) - { - case kXR_chmod: - case kXR_mkdir: - case kXR_mv: - case kXR_open: - case kXR_rm: - case kXR_rmdir: - case kXR_stat: - case kXR_truncate: - { - //---------------------------------------------------------------------- - // Get the pointer to the appropriate path - //---------------------------------------------------------------------- - char *path = msg->GetBuffer( 24 ); - size_t length = req->header.dlen; - if( req->header.requestid == kXR_mv ) - { - for( int i = 0; i < req->header.dlen; ++i, ++path, --length ) - if( *path == ' ' ) - break; - ++path; - --length; - } - - //---------------------------------------------------------------------- - // Create a fake URL from an existing CGI - //---------------------------------------------------------------------- - char *pathWithNull = new char[length+1]; - memcpy( pathWithNull, path, length ); - pathWithNull[length] = 0; - std::ostringstream o; - o << "fake://fake:111/" << pathWithNull; - delete [] pathWithNull; - - URL currentPath( o.str() ); - URL::ParamsMap currentCgi = currentPath.GetParams(); - MergeCGI( currentCgi, newCgi, replace ); - currentPath.SetParams( currentCgi ); - if( !newPath.empty() ) - currentPath.SetPath( newPath ); - std::string newPathWitParams = currentPath.GetPathWithParams(); - - //---------------------------------------------------------------------- - // Write the path with the new cgi appended to the message - //---------------------------------------------------------------------- - uint32_t newDlen = req->header.dlen - length + newPathWitParams.size(); - msg->ReAllocate( 24+newDlen ); - req = (ClientRequest *)msg->GetBuffer(); - path = msg->GetBuffer( 24 ); - if( req->header.requestid == kXR_mv ) - { - for( int i = 0; i < req->header.dlen; ++i, ++path ) - if( *path == ' ' ) - break; - ++path; - } - memcpy( path, newPathWitParams.c_str(), newPathWitParams.size() ); - req->header.dlen = newDlen; - break; - } - } - XRootDTransport::SetDescription( msg ); - } - - //------------------------------------------------------------------------ - //! Merge cgi2 into cgi1 - //------------------------------------------------------------------------ - void MessageUtils::MergeCGI( URL::ParamsMap &cgi1, - const URL::ParamsMap &cgi2, - bool replace ) - { - URL::ParamsMap::const_iterator it; - for( it = cgi2.begin(); it != cgi2.end(); ++it ) - { - if( replace || cgi1.find( it->first ) == cgi1.end() ) - cgi1[it->first] = it->second; - else - { - std::string &v = cgi1[it->first]; - if( v.empty() ) - v = it->second; - else - { - v += ','; - v += it->second; - } - } - } - } -} diff --git a/src/XrdCl/XrdClMessageUtils.hh b/src/XrdCl/XrdClMessageUtils.hh deleted file mode 100644 index 7e93511990f..00000000000 --- a/src/XrdCl/XrdClMessageUtils.hh +++ /dev/null @@ -1,255 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2014 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_MESSAGE_UTILS_HH__ -#define __XRD_CL_MESSAGE_UTILS_HH__ - -#include "XrdCl/XrdClXRootDResponses.hh" -#include "XrdCl/XrdClURL.hh" -#include "XrdCl/XrdClMessage.hh" -#include "XrdCl/XrdClUglyHacks.hh" - -namespace XrdCl -{ - class LocalFileHandler; - - //---------------------------------------------------------------------------- - //! Synchronize the response - //---------------------------------------------------------------------------- - class SyncResponseHandler: public ResponseHandler - { - public: - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - SyncResponseHandler(): - pStatus(0), - pResponse(0), - pCondVar(0) {} - - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - virtual ~SyncResponseHandler() - { - } - - - //------------------------------------------------------------------------ - //! Handle the response - //------------------------------------------------------------------------ - virtual void HandleResponse( XRootDStatus *status, - AnyObject *response ) - { - XrdSysCondVarHelper scopedLock(pCondVar); - pStatus = status; - pResponse = response; - pCondVar.Broadcast(); - } - - //------------------------------------------------------------------------ - //! Get the status - //------------------------------------------------------------------------ - XRootDStatus *GetStatus() - { - return pStatus; - } - - //------------------------------------------------------------------------ - //! Get the response - //------------------------------------------------------------------------ - AnyObject *GetResponse() - { - return pResponse; - } - - //------------------------------------------------------------------------ - //! Wait for the arrival of the response - //------------------------------------------------------------------------ - void WaitForResponse() - { - XrdSysCondVarHelper scopedLock(pCondVar); - while (pStatus == 0) { - pCondVar.Wait(); - } - } - - private: - SyncResponseHandler(const SyncResponseHandler &other); - SyncResponseHandler &operator = (const SyncResponseHandler &other); - - XRootDStatus *pStatus; - AnyObject *pResponse; - XrdSysCondVar pCondVar; - }; - - - //---------------------------------------------------------------------------- - // We're not interested in the response just commit suicide - //---------------------------------------------------------------------------- - class NullResponseHandler: public XrdCl::ResponseHandler - { - public: - //------------------------------------------------------------------------ - // Handle the response - //------------------------------------------------------------------------ - virtual void HandleResponseWithHosts( XrdCl::XRootDStatus *status, - XrdCl::AnyObject *response, - XrdCl::HostList *hostList ) - { - delete this; - } - }; - - //---------------------------------------------------------------------------- - // Sending parameters - //---------------------------------------------------------------------------- - struct MessageSendParams - { - MessageSendParams(): - timeout(0), expires(0), followRedirects(true), stateful(true), - hostList(0), chunkList(0), redirectLimit(0) {} - uint16_t timeout; - time_t expires; - HostInfo loadBalancer; - bool followRedirects; - bool stateful; - HostList *hostList; - ChunkList *chunkList; - uint16_t redirectLimit; - }; - - class MessageUtils - { - public: - //------------------------------------------------------------------------ - //! Wait and return the status of the query - //------------------------------------------------------------------------ - static XRootDStatus WaitForStatus( SyncResponseHandler *handler ) - { - handler->WaitForResponse(); - XRootDStatus *status = handler->GetStatus(); - XRootDStatus ret( *status ); - delete status; - return ret; - } - - //------------------------------------------------------------------------ - //! Wait for the response - //------------------------------------------------------------------------ - template - static XrdCl::XRootDStatus WaitForResponse( - SyncResponseHandler *handler, - Type *&response ) - { - handler->WaitForResponse(); - - AnyObject *resp = handler->GetResponse(); - XRootDStatus *status = handler->GetStatus(); - XRootDStatus ret( *status ); - delete status; - - if( ret.IsOK() ) - { - if( !resp ) - return XRootDStatus( stError, errInternal ); - resp->Get( response ); - resp->Set( (int *)0 ); - delete resp; - - if( !response ) - return XRootDStatus( stError, errInternal ); - } - - return ret; - } - - //------------------------------------------------------------------------ - //! Create a message - //------------------------------------------------------------------------ - template - static void CreateRequest( Message *&msg, - Request *&req, - uint32_t payloadSize = 0 ) - { - msg = new Message( sizeof(Request) + payloadSize ); - req = (Request*)msg->GetBuffer(); - msg->Zero(); - } - - //------------------------------------------------------------------------ - //! Send message - //------------------------------------------------------------------------ - static Status SendMessage( const URL &url, - Message *msg, - ResponseHandler *handler, - const MessageSendParams &sendParams, - LocalFileHandler *lFileHandler ); - - //------------------------------------------------------------------------ - //! Redirect message - //------------------------------------------------------------------------ - static Status RedirectMessage( const URL &url, - Message *msg, - ResponseHandler *handler, - MessageSendParams &sendParams, - LocalFileHandler *lFileHandler ); - - //------------------------------------------------------------------------ - //! Process sending params - //------------------------------------------------------------------------ - static void ProcessSendParams( MessageSendParams &sendParams ); - - //------------------------------------------------------------------------ - //! Rewrite CGI and path if necessary - //! - //! @param msg message concerned - //! @param newCgi the new cgi - //! @param replace indicates whether, in case of a conflict, the new CGI - //! parameter should replace an existing one or be - //! appended to it using a comma - //! @param newPath will be used as the new destination path if it is - //! not empty - //------------------------------------------------------------------------ - static void RewriteCGIAndPath( Message *msg, - const URL::ParamsMap &newCgi, - bool replace, - const std::string &newPath ); - - //------------------------------------------------------------------------ - //! Merge cgi2 into cgi1 - //! - //! @param cgi1 cgi to be merged into - //! @param cgi2 cgi to be merged in - //! @param replace indicates whether, in case of a conflict, the new CGI - //! parameter should replace an existing one or be - //! appended to it using a comma - //------------------------------------------------------------------------ - static void MergeCGI( URL::ParamsMap &cgi1, - const URL::ParamsMap &cgi2, - bool replace ); - }; -} - -#endif // __XRD_CL_MESSAGE_UTILS_HH__ diff --git a/src/XrdCl/XrdClMetalinkRedirector.cc b/src/XrdCl/XrdClMetalinkRedirector.cc deleted file mode 100644 index a7cd571b7de..00000000000 --- a/src/XrdCl/XrdClMetalinkRedirector.cc +++ /dev/null @@ -1,482 +0,0 @@ -/* - * XrdClMetalinkRedirector.cc - * - * Created on: May 2, 2016 - * Author: simonm - */ - -#include "XrdClMetalinkRedirector.hh" - -#include "XrdCl/XrdClConstants.hh" -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdCl/XrdClLog.hh" -#include "XrdCl/XrdClMessageUtils.hh" -#include "XrdCl/XrdClFile.hh" -#include "XrdCl/XrdClFileStateHandler.hh" -#include "XrdCl/XrdClURL.hh" -#include "XrdCl/XrdClJobManager.hh" -#include "XrdCl/XrdClUtils.hh" -#include "XrdCl/XrdClPostMasterInterfaces.hh" -#include "XrdCl/XrdClPostMaster.hh" - -#include "XrdXml/XrdXmlMetaLink.hh" - -#include "XProtocol/XProtocol.hh" - -#include -#include - -namespace XrdCl -{ - - void DeallocArgs( XRootDStatus *status, AnyObject *response, - HostList *hostList ) - { - delete status; - delete response; - delete hostList; - } - - //---------------------------------------------------------------------------- - // Read metalink response handler. - // - // If the whole file has been read triggers parsing and - // initializes the Redirector, otherwise triggers another - // read. - //---------------------------------------------------------------------------- - class MetalinkReadHandler: public ResponseHandler - { - public: - //---------------------------------------------------------------------------- - // Constructor - //---------------------------------------------------------------------------- - MetalinkReadHandler( MetalinkRedirector *mr, ResponseHandler *userHandler, - const std::string &content = "" ) : - pRedirector( mr ), pUserHandler( userHandler ), pBuffer( - new char[DefaultCPChunkSize] ), pContent( content ) - { - } - - //---------------------------------------------------------------------------- - // Destructor - //---------------------------------------------------------------------------- - virtual ~MetalinkReadHandler() - { - delete[] pBuffer; - } - - //---------------------------------------------------------------------------- - // Handle the response - //---------------------------------------------------------------------------- - virtual void HandleResponseWithHosts( XRootDStatus *status, - AnyObject *response, HostList *hostList ) - { - try - { - // check the status - if( !status->IsOK() ) - throw status; - // we don't need it anymore - delete status; - // make sure we got a response - if( !response ) - throw new XRootDStatus( stError, errInternal ); - // make sure the response is not empty - ChunkInfo * info = 0; - response->Get( info ); - if( !info ) - throw new XRootDStatus( stError, errInternal ); - uint32_t bytesRead = info->length; - uint64_t offset = info->offset + bytesRead; - pContent += std::string( pBuffer, bytesRead ); - // are we done ? - if( bytesRead > 0 ) - { - // lets try to read another chunk - MetalinkReadHandler * mrh = new MetalinkReadHandler( pRedirector, - pUserHandler, pContent ); - XRootDStatus st = pRedirector->pFile->Read( offset, - DefaultCPChunkSize, mrh->GetBuffer(), mrh ); - if( !st.IsOK() ) - { - delete mrh; - throw new XRootDStatus( st ); - } - // clean up - DeallocArgs( 0, response, hostList ); - } else // we have the whole metalink file - { - // we don't need the File object anymore - delete pRedirector->pFile; - pRedirector->pFile = 0; - // now we can parse the metalink file - XRootDStatus st = pRedirector->Parse( pContent ); - // now with the redirector fully initialized we can handle pending requests - pRedirector->FinalizeInitialization(); - // we are done, pass the status to the user (whatever it is) - if( pUserHandler ) - pUserHandler->HandleResponseWithHosts( new XRootDStatus( st ), - response, hostList ); - else - DeallocArgs( 0, response, hostList ); - } - } catch( XRootDStatus *status ) - { - pRedirector->FinalizeInitialization( *status ); - // if we were not able to read from the metalink, - // propagate the error to the user handler - if( pUserHandler ) - pUserHandler->HandleResponseWithHosts( status, response, hostList ); - else - DeallocArgs( status, response, hostList ); - } - - delete this; - } - - //---------------------------------------------------------------------------- - // Get the receive-buffer - //---------------------------------------------------------------------------- - char * GetBuffer() - { - return pBuffer; - } - - private: - - MetalinkRedirector *pRedirector; - ResponseHandler *pUserHandler; - char *pBuffer; - std::string pContent; - }; - - //---------------------------------------------------------------------------- - // Open metalink response handler. - // - // After successful open trrigers a read. - //---------------------------------------------------------------------------- - class MetalinkOpenHandler: public ResponseHandler - { - public: - //---------------------------------------------------------------------------- - // Constructor - //---------------------------------------------------------------------------- - MetalinkOpenHandler( MetalinkRedirector *mr, - ResponseHandler *userHandler ) : - pRedirector( mr ), pUserHandler( userHandler ) - { - } - - //---------------------------------------------------------------------------- - // Handle the response - //---------------------------------------------------------------------------- - virtual void HandleResponseWithHosts( XRootDStatus *status, - AnyObject *response, HostList *hostList ) - { - try - { - if( status->IsOK() ) - { - delete status; - // download the content - MetalinkReadHandler *mrh = new MetalinkReadHandler( pRedirector, - pUserHandler ); - XRootDStatus st = pRedirector->pFile->Read( 0, DefaultCPChunkSize, - mrh->GetBuffer(), mrh ); - if( !st.IsOK() ) - { - delete mrh; - throw new XRootDStatus( stError, errInternal ); - } else - { - delete response; - delete hostList; - } - } else - throw status; - } catch( XRootDStatus *status ) - { - pRedirector->FinalizeInitialization( *status ); - // if we were not able to schedule a read - // pass an error to the user handler - if( pUserHandler ) - pUserHandler->HandleResponseWithHosts( status, response, hostList ); - else - DeallocArgs( status, response, hostList ); - } - - delete this; - } - - private: - - MetalinkRedirector *pRedirector; - ResponseHandler *pUserHandler; - }; - - //---------------------------------------------------------------------------- - // Constructor - //---------------------------------------------------------------------------- - MetalinkRedirector::MetalinkRedirector( const std::string & url ) : - pUrl( url ), pFile( new File( File::DisableVirtRedirect ) ), pReady( - false ), pFileSize( -1 ) - { - } - - //---------------------------------------------------------------------------- - // Destructor - //---------------------------------------------------------------------------- - MetalinkRedirector::~MetalinkRedirector() - { - delete pFile; - } - - //---------------------------------------------------------------------------- - // Initializes the object with the content of the metalink file - //---------------------------------------------------------------------------- - XRootDStatus MetalinkRedirector::Load( ResponseHandler *userHandler ) - { - MetalinkOpenHandler *handler = new MetalinkOpenHandler( this, userHandler ); - XRootDStatus st = pFile->Open( pUrl, OpenFlags::Read, Access::None, handler, - 0 ); - if( !st.IsOK() ) - delete handler; - - return st; - } - - //---------------------------------------------------------------------------- - // Parses the metalink file - //---------------------------------------------------------------------------- - XRootDStatus MetalinkRedirector::Parse( const std::string &metalink ) - { - Log *log = DefaultEnv::GetLog(); - Env *env = DefaultEnv::GetEnv(); - std::string glfnRedirector; - env->GetString( "GlfnRedirector", glfnRedirector ); - // parse the metalink - XrdXmlMetaLink parser( "root:xroot:file:", "xroot:", - glfnRedirector.empty() ? 0 : glfnRedirector.c_str() ); - int size = 0; - XrdOucFileInfo **fileInfos = parser.ConvertAll( metalink.c_str(), size, - metalink.size() ); - if( !fileInfos ) - { - int ecode; - const char * etxt = parser.GetStatus( ecode ); - log->Error( UtilityMsg, - "Failed to parse the metalink file: %s (error code: %d)", etxt, - ecode ); - return XRootDStatus( stError, errDataError, 0, - "Malformed or corrupted metalink file." ); - } - // we are expecting just one file per metalink (as agreed with RUCIO) - if( size != 1 ) - { - log->Error( UtilityMsg, "Expected only one file per metalink." ); - return XRootDStatus( stError, errDataError ); - } - - InitCksum( fileInfos ); - InitReplicas( fileInfos ); - pTarget = fileInfos[0]->GetTargetName(); - pFileSize = fileInfos[0]->GetSize(); - - XrdXmlMetaLink::DeleteAll( fileInfos, size ); - - return XRootDStatus(); - } - - //---------------------------------------------------------------------------- - // Finalize the initialization process: - // - mark as ready - // - setup the status - // - and handle pending redirects - //---------------------------------------------------------------------------- - void MetalinkRedirector::FinalizeInitialization( const XRootDStatus &status ) - { - XrdSysMutexHelper scopedLock( pMutex ); - pReady = true; - pStatus = status; - // Handle pending redirects (those that were - // submitted before the metalink has been loaded) - while( !pPendingRedirects.empty() ) - { - const Message *msg = pPendingRedirects.front().first; - IncomingMsgHandler *handler = pPendingRedirects.front().second; - pPendingRedirects.pop_front(); - if( !handler || !msg ) - continue; - HandleRequestImpl( msg, handler ); - } - } - - //---------------------------------------------------------------------------- - // Generates redirect response for the given request - //---------------------------------------------------------------------------- - Message* MetalinkRedirector::GetResponse( const Message *msg ) const - { - if( !pStatus.IsOK() ) - return GetErrorMsg( msg, "Could not load the Metalink file.", - static_cast( XProtocol::mapError( pStatus.errNo ) ) ); - const ClientRequestHdr *req = - reinterpret_cast( msg->GetBuffer() ); - // get the redirect location - std::string replica; - if( !GetReplica( msg, replica ).IsOK() ) - return GetErrorMsg( msg, "No more replicas to try.", kXR_NotFound ); - Message *resp = new Message( sizeof(ServerResponse) ); - ServerResponse* response = - reinterpret_cast( resp->GetBuffer() ); - response->hdr.status = kXR_redirect; - response->hdr.streamid[0] = req->streamid[0]; - response->hdr.streamid[1] = req->streamid[1]; - response->hdr.dlen = 4 + replica.size(); // 4 bytes are reserved for port number - response->body.redirect.port = -1; // this indicates that the full URL will be given in the 'host' field - memcpy( response->body.redirect.host, replica.c_str(), replica.size() ); - return resp; - } - - //---------------------------------------------------------------------------- - // Generates error response for the given request - //---------------------------------------------------------------------------- - Message* MetalinkRedirector::GetErrorMsg( const Message *msg, - const std::string &errMsg, XErrorCode code ) const - { - const ClientRequestHdr *req = - reinterpret_cast( msg->GetBuffer() ); - - Message* resp = new Message( sizeof(ServerResponse) ); - ServerResponse* response = - reinterpret_cast( resp->GetBuffer() ); - - response->hdr.status = kXR_error; - response->hdr.streamid[0] = req->streamid[0]; - response->hdr.streamid[1] = req->streamid[1]; - response->hdr.dlen = 4 + errMsg.size(); - response->body.error.errnum = htonl( code ); - memcpy( response->body.error.errmsg, errMsg.c_str(), errMsg.size() ); - - return resp; - } - - //---------------------------------------------------------------------------- - // Creates an instant redirect response for the given message - // or an error response if there are no more replicas to try. - // The virtual response is being handled by the given handler. - //---------------------------------------------------------------------------- - XRootDStatus MetalinkRedirector::HandleRequestImpl( const Message *msg, - IncomingMsgHandler *handler ) - { - Message *resp = GetResponse( msg ); - JobManager *jobMan = DefaultEnv::GetPostMaster()->GetJobManager(); - RedirectJob *job = new RedirectJob( handler ); - jobMan->QueueJob( job, resp ); - return XRootDStatus(); - } - - //---------------------------------------------------------------------------- - // If the MetalinkRedirector is initialized creates an instant - // redirect response, otherwise queues the request until initialization - // is done. - //---------------------------------------------------------------------------- - XRootDStatus MetalinkRedirector::HandleRequest( const Message *msg, - IncomingMsgHandler *handler ) - { - XrdSysMutexHelper scopedLck( pMutex ); - // if the metalink data haven't been loaded yet, make it pending - if( !pReady ) - { - pPendingRedirects.push_back( - std::make_pair( msg, handler ) );; - return XRootDStatus(); - } - // otherwise generate a virtual response - return HandleRequestImpl( msg, handler ); - } - - //---------------------------------------------------------------------------- - // Gets the file checksum if specified in the metalink - //---------------------------------------------------------------------------- - void MetalinkRedirector::InitCksum( XrdOucFileInfo **fileInfos ) - { - const char *chvalue = 0, *chtype = 0; - while( ( chtype = fileInfos[0]->GetDigest( chvalue ) ) ) - { - pChecksums[chtype] = chvalue; - } - } - - //---------------------------------------------------------------------------- - // Initializes replica list - //---------------------------------------------------------------------------- - void MetalinkRedirector::InitReplicas( XrdOucFileInfo **fileInfos ) - { - URL replica; - const char *url = 0; - while( ( url = fileInfos[0]->GetUrl() ) ) - { - replica = URL( url ); - if( replica.GetURL().size() > 4096 ) - continue; // this is the internal limit (defined in the protocol) - pReplicas.push_back( replica.GetURL() ); - } - } - - //---------------------------------------------------------------------------- - //! Get the next replica for the given message - //---------------------------------------------------------------------------- - XRootDStatus MetalinkRedirector::GetReplica( const Message *msg, - std::string &replica ) const - { - if( pReplicas.empty() ) - return XRootDStatus( stError, errNotFound ); - - std::string tried; - if( !GetCgiInfo( msg, "tried", tried ).IsOK() ) - { - replica = pReplicas.front(); - return XRootDStatus(); - } - ReplicaList triedList; - Utils::splitString( triedList, tried, "," ); - ReplicaList::const_iterator tItr = triedList.begin(), rItr = - pReplicas.begin(); - while( tItr != triedList.end() && rItr != pReplicas.end() ) - { - URL rUrl( *rItr ); - if( rUrl.GetHostName() == *tItr ) - ++rItr; - ++tItr; - } - if( rItr == pReplicas.end() ) - return XRootDStatus( stError, errNotFound ); // there are no more replicas to try - replica = *rItr; - return XRootDStatus(); - } - - //---------------------------------------------------------------------------- - //! Extracts an element from url cgi - //---------------------------------------------------------------------------- - XRootDStatus MetalinkRedirector::GetCgiInfo( const Message *msg, - const std::string &key, std::string &value ) const - { - const ClientRequestHdr *req = - reinterpret_cast( msg->GetBuffer() ); - kXR_int32 dlen = msg->IsMarshalled() ? ntohl( req->dlen ) : req->dlen; - std::string url( msg->GetBuffer( 24 ), dlen ); - size_t pos = url.find( '?' ); - if( pos == std::string::npos ) - return XRootDStatus( stError ); - size_t start = url.find( key, pos ); - if( start == std::string::npos ) - return XRootDStatus( stError ); - start += key.size() + 1; // the +1 stands for the '=' sign that is not part of the key - size_t end = url.find( '&', start ); - if( end == std::string::npos ) - end = url.size(); - value = url.substr( start, end - start ); - return XRootDStatus(); - } - -} /* namespace XrdCl */ diff --git a/src/XrdCl/XrdClMetalinkRedirector.hh b/src/XrdCl/XrdClMetalinkRedirector.hh deleted file mode 100644 index 84ffacd8102..00000000000 --- a/src/XrdCl/XrdClMetalinkRedirector.hh +++ /dev/null @@ -1,173 +0,0 @@ -/* - * XrdClMetalinkRedirector.hh - * - * Created on: May 2, 2016 - * Author: simonm - */ - -#ifndef SRC_XRDCL_XRDCLMETALINKREDIRECTOR_HH_ -#define SRC_XRDCL_XRDCLMETALINKREDIRECTOR_HH_ - -#include "XrdCl/XrdClMessageUtils.hh" -#include "XrdCl/XrdClRedirectorRegistry.hh" - -#include -#include -#include - - -class XrdOucFileInfo; - -namespace XrdCl -{ - -class File; -class Message; - -//---------------------------------------------------------------------------- -//! An abstraction representing a virtual -//! redirector based on a metalink file -//---------------------------------------------------------------------------- -class MetalinkRedirector : public VirtualRedirector -{ - friend class MetalinkOpenHandler; - friend class MetalinkReadHandler; - - public: - //---------------------------------------------------------------------------- - //! Constructor - //! @param url : URL to the metalink file - //! @param userHandler : the response handler provided by end user - //---------------------------------------------------------------------------- - MetalinkRedirector( const std::string &url ); - - //---------------------------------------------------------------------------- - //! Destructor - //---------------------------------------------------------------------------- - virtual ~MetalinkRedirector(); - - //---------------------------------------------------------------------------- - //! Initializes the object with the content of the metalink file - //---------------------------------------------------------------------------- - XRootDStatus Load( ResponseHandler *userHandler ); - - //---------------------------------------------------------------------------- - //! If the MetalinkRedirector is initialized creates an instant - //! redirect response, otherwise queues the request until initialization - //! is done. - //---------------------------------------------------------------------------- - XRootDStatus HandleRequest( const Message *msg, IncomingMsgHandler *handler ); - - //---------------------------------------------------------------------------- - //! Gets the file name as specified in the metalink - //---------------------------------------------------------------------------- - std::string GetTargetName() const - { - return pTarget; - } - - //---------------------------------------------------------------------------- - //! Returns the checksum of the given type if specified - //! in the metalink file, or an empty string otherwise - //---------------------------------------------------------------------------- - std::string GetCheckSum( const std::string &type ) const - { - CksumMap::const_iterator it = pChecksums.find( type ); - if( it == pChecksums.end() ) return std::string(); - return type + ":" + it->second; - } - - //---------------------------------------------------------------------------- - //! Returns the file size if specified in the metalink file, - //! otherwise a negative number - //---------------------------------------------------------------------------- - long long GetSize() const - { - return pFileSize; - } - - //---------------------------------------------------------------------------- - //! Returns a vector with replicas as given in the meatlink file - //---------------------------------------------------------------------------- - const std::vector& GetReplicas() - { - return pReplicas; - } - - private: - - //---------------------------------------------------------------------------- - //! Creates an instant redirect response for the given message - //! or an error response if there are no more replicas to try. - //! The virtual response is being handled by the given handler - //! in the thread-pool. - //---------------------------------------------------------------------------- - XRootDStatus HandleRequestImpl( const Message *msg, IncomingMsgHandler *handler ); - - //---------------------------------------------------------------------------- - //! Parses the metalink file - //! @param metalink : the content of the metalink file - //---------------------------------------------------------------------------- - XRootDStatus Parse( const std::string &metalink ); - - //---------------------------------------------------------------------------- - //! Finalize the initialization process: - //! - mark as ready - //! - setup the status - //! - and handle pending redirects - //---------------------------------------------------------------------------- - void FinalizeInitialization( const XRootDStatus &status = XRootDStatus() ); - - //---------------------------------------------------------------------------- - //! Generates redirect response for the given request - //---------------------------------------------------------------------------- - Message* GetResponse( const Message *msg ) const; - - //---------------------------------------------------------------------------- - //! Generates error response for the given request - //---------------------------------------------------------------------------- - Message* GetErrorMsg( const Message *msg, const std::string &errMsg, XErrorCode code ) const; - - //---------------------------------------------------------------------------- - //! Initializes checksum map - //---------------------------------------------------------------------------- - void InitCksum( XrdOucFileInfo **fileInfos ); - - //---------------------------------------------------------------------------- - //! Initializes replica list - //---------------------------------------------------------------------------- - void InitReplicas( XrdOucFileInfo **fileInfos ); - - //---------------------------------------------------------------------------- - //! Get the next replica for the given message - //---------------------------------------------------------------------------- - XRootDStatus GetReplica( const Message *msg, std::string &replica ) const; - - //---------------------------------------------------------------------------- - //! Extracts an element from URL cgi - //---------------------------------------------------------------------------- - XRootDStatus GetCgiInfo( const Message *msg, const std::string &key, std::string &out ) const; - - typedef std::list< std::pair > RedirectList; - typedef std::map CksumMap; - typedef std::vector ReplicaList; - - RedirectList pPendingRedirects; - std::string pUrl; - File *pFile; - CksumMap pChecksums; - ReplicaList pReplicas; - bool pReady; - XRootDStatus pStatus; - std::string pTarget; - long long pFileSize; - - XrdSysMutex pMutex; - - static const std::string LocalFile; - -}; - -} /* namespace XrdCl */ - -#endif /* SRC_XRDCL_XRDCLMETALINKREDIRECTOR_HH_ */ diff --git a/src/XrdCl/XrdClMonitor.hh b/src/XrdCl/XrdClMonitor.hh deleted file mode 100644 index 77635dd2846..00000000000 --- a/src/XrdCl/XrdClMonitor.hh +++ /dev/null @@ -1,237 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2012 by the Board of Trustees of the Leland Stanford, Jr., -// University -// Copyright (c) 2012 by European Organization for Nuclear Research (CERN) -// Author: Andrew Hanushevsky -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -//------------------------------------------------------------------------------ -//! When the envar XRD_CLIENTMONITOR is set to the libpath/libname.so that -//! holds the monitoring object, it is automatically loaded. The following -//! "C" external symbols must exist in the monitor plug-in shared library. -//! It is called to obtain an instance of the XrdCl::Monitor object. -//! -//! @param exec full path name to executable provided by XrdSysUtils::ExecName -//! -//! @param parms Value of XRD_CLIENTMONITOPARAM envar or null if it is not set. -//! -//! @return Pointer to an instance of XrdCl::Monitor or null which causes -//! monitoring to be disabled. -//! -//! extern "C" -//! { -//! XrdCl::Monitor *XrdClGetMonitor(const char *exec, const char *parms); -//! } -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_MONITOR_HH__ -#define __XRD_CL_MONITOR_HH__ - -#include "XrdCl/XrdClFileSystem.hh" - -namespace XrdCl -{ - class URL; - - //---------------------------------------------------------------------------- - //! An abstract class to describe the client-side monitoring plugin interface. - //---------------------------------------------------------------------------- - class Monitor - { - public: - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - Monitor() {} - - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - virtual ~Monitor() {} - - //------------------------------------------------------------------------ - //! Describe a server login event - //------------------------------------------------------------------------ - struct ConnectInfo - { - ConnectInfo(): streams( 0 ) - { - sTOD.tv_sec = 0; sTOD.tv_usec = 0; - eTOD.tv_sec = 0; eTOD.tv_usec = 0; - } - std::string server; //!< "user@host:port" - std::string auth; //!< authentication protocol used or empty if none - timeval sTOD; //!< gettimeofday() when login started - timeval eTOD; //!< gettimeofday() when login ended - uint16_t streams; //!< Number of streams - }; - - //------------------------------------------------------------------------ - //! Describe a server logout event - //------------------------------------------------------------------------ - struct DisconnectInfo - { - DisconnectInfo(): rBytes(0), sBytes(0), cTime(0) - {} - std::string server; //!< "user@host:port" - uint64_t rBytes; //!< Number of bytes received - uint64_t sBytes; //!< Number of bytes sent - time_t cTime; //!< Seconds connected to the server - Status status; //!< Disconnection status - }; - - //------------------------------------------------------------------------ - //! Describe a file open event to the monitor - //------------------------------------------------------------------------ - struct OpenInfo - { - OpenInfo(): file(0), fSize(0), oFlags(0) {} - const URL *file; //!< File in question - std::string dataServer; //!< Actual fata server - uint64_t fSize; //!< File size in bytes - uint16_t oFlags; //!< OpenFlags - }; - - //------------------------------------------------------------------------ - //! Describe a file close event - //------------------------------------------------------------------------ - struct CloseInfo - { - CloseInfo(): - file(0), rBytes(0), vBytes(0), wBytes(0), vSegs(0), rCount(0), - vCount(0), wCount(0), status(0) - { - oTOD.tv_sec = 0; oTOD.tv_usec = 0; - cTOD.tv_sec = 0; cTOD.tv_usec = 0; - } - const URL *file; //!< The file in question - timeval oTOD; //!< gettimeofday() when file was opened - timeval cTOD; //!< gettimeofday() when file was closed - uint64_t rBytes; //!< Total number of bytes read via read - uint64_t vBytes; //!< Total number of bytes read via readv - uint64_t wBytes; //!< Total number of bytes written -// uint64_t vwBytes; //!< Total number of bytes written vie writev - uint64_t vSegs; //!< Total count of readv segments - uint32_t rCount; //!< Total count of reads - uint32_t vCount; //!< Total count of readv - uint32_t wCount; //!< Total count of writes - const XRootDStatus *status; //!< Close status - }; - - //------------------------------------------------------------------------ - //! Describe an encountered file-based error - //------------------------------------------------------------------------ - struct ErrorInfo - { - enum Operation - { - ErrOpen = 0, //!< Open (ditto) - ErrRead, //!< Read - ErrReadV, //!< Readv - ErrWrite, //!< Write -// TODO -// ErrWriteV, //!< WriteV (we can uncomment only when we do a major -// release as this is an ABI change) - ErrUnc //!< Unclassified operation - }; - - ErrorInfo(): file(0), status(0), opCode( ErrUnc ) {} - const URL *file; //!< The file in question - const XRootDStatus *status; //!< Status code - Operation opCode; //!< The associated operation - }; - - //------------------------------------------------------------------------ - //! Describe the transfer - //------------------------------------------------------------------------ - struct TransferInfo - { - TransferInfo(): origin(0), target(0) {} - const URL *origin; //!< URL of the origin - const URL *target; //!< URL of the target - }; - - //------------------------------------------------------------------------ - //! Describe a start of copy event. Copy events are sequential by nature. - //! a copybeg event is followed by a number of open and close events. When - //! the copy finishes, all files are closed and a copyend event occurs. - //------------------------------------------------------------------------ - struct CopyBInfo - { - TransferInfo transfer; //!< The transfer in question - }; - - //------------------------------------------------------------------------ - //! Describe an end of copy event - //------------------------------------------------------------------------ - struct CopyEInfo - { - CopyEInfo(): sources(0), status(0) - { - bTOD.tv_sec = 0; bTOD.tv_usec = 0; - eTOD.tv_sec = 0; eTOD.tv_usec = 0; - } - TransferInfo transfer; //!< The transfer in question - int sources; //!< Number of sources used for the copy - timeval bTOD; //!< Copy start time - timeval eTOD; //!< Copy end time - const XRootDStatus *status; //!< Status of the copy - }; - - //------------------------------------------------------------------------ - //! Describe a checksum event - //------------------------------------------------------------------------ - struct CheckSumInfo - { - CheckSumInfo(): oTime(0), tTime(0), isOK(false) {} - TransferInfo transfer; //!< The transfer in question - std::string cksum; //!< Checksum as "type:value" - uint64_t oTime; //!< Microseconds to obtain cksum from origin - uint64_t tTime; //!< Microseconds to obtain cksum from target - bool isOK; //!< True if checksum matched, false otherwise - }; - - //------------------------------------------------------------------------ - //! Event codes passed to the Event() method. Event code values not - //! listed here, if encountered, should be ignored. - //------------------------------------------------------------------------ - enum EventCode - { - EvCopyBeg, //!< CopyBInfo: Copy operation started - EvCopyEnd, //!< CopyEInfo: Copy operation ended - EvCheckSum, //!< CheckSumInfo: File checksummed - EvOpen, //!< OpenInfo: File opened - EvClose, //!< CloseInfo: File closed - EvErrIO, //!< ErrorInfo: An I/O error occurred - EvConnect, //!< ConnectInfo: Login into a server - EvDisconnect //!< DisconnectInfo: Logout from a server - - }; - - //------------------------------------------------------------------------ - //! Inform the monitor of an event. - //! - //! @param evCode is the event that occurred (see enum evNum) - //! @param evData is the event information structure describing the event - //! it is cast to (void *) so that one method can be used - //! and should be recast to the correct corresponding struct - //------------------------------------------------------------------------ - virtual void Event( EventCode evCode, void *evData ) = 0; - }; -} - -#endif // __XRD_CL_MONITOR_HH diff --git a/src/XrdCl/XrdClOptimizers.hh b/src/XrdCl/XrdClOptimizers.hh deleted file mode 100644 index 36682d1b680..00000000000 --- a/src/XrdCl/XrdClOptimizers.hh +++ /dev/null @@ -1,30 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_OPTIMIZERS_HH__ -#define __XRD_CL_OPTIMIZERS_HH__ - -#ifdef __GNUC__ -#define likely(x) __builtin_expect(!!(x), 1) -#define unlikely(x) __builtin_expect(!!(x), 0) -#else -#define likely(x) x -#define unlikely(x) x -#endif - -#endif // __XRD_CL_OPTIMIZERS_HH__ diff --git a/src/XrdCl/XrdClOutQueue.cc b/src/XrdCl/XrdClOutQueue.cc deleted file mode 100644 index 24e77817626..00000000000 --- a/src/XrdCl/XrdClOutQueue.cc +++ /dev/null @@ -1,144 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#include "XrdCl/XrdClOutQueue.hh" -#include "XrdCl/XrdClPostMasterInterfaces.hh" - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - // Add a message to the back of the queue - //---------------------------------------------------------------------------- - void OutQueue::PushBack( Message *msg, - OutgoingMsgHandler *handler, - time_t expires, - bool stateful ) - { - pMessages.push_back( MsgHelper( msg, handler, expires, stateful ) ); - } - - //---------------------------------------------------------------------------- - // Add a message to the front the queue - //---------------------------------------------------------------------------- - void OutQueue::PushFront( Message *msg, - OutgoingMsgHandler *handler, - time_t expires, - bool stateful ) - { - pMessages.push_front( MsgHelper( msg, handler, expires, stateful ) ); - } - - //---------------------------------------------------------------------------- - //! Get a message from the front of the queue - //---------------------------------------------------------------------------- - Message *OutQueue::PopMessage( OutgoingMsgHandler *&handler, - time_t &expires, - bool &stateful ) - { - if( pMessages.empty() ) - return 0; - - MsgHelper m = pMessages.front(); - handler = m.handler; - expires = m.expires; - stateful = m.stateful; - pMessages.pop_front(); - return m.msg; - } - - //---------------------------------------------------------------------------- - // Remove a message from the front - //---------------------------------------------------------------------------- - void OutQueue::PopFront() - { - pMessages.pop_front(); - } - - //---------------------------------------------------------------------------- - // Report status to all handlers - //---------------------------------------------------------------------------- - void OutQueue::Report( Status status ) - { - MessageList::iterator it; - for( it = pMessages.begin(); it != pMessages.end(); ++it ) - it->handler->OnStatusReady( it->msg, status ); - } - - //------------------------------------------------------------------------ - // Return the size of the queue counting only the stateless messages - //------------------------------------------------------------------------ - uint64_t OutQueue::GetSizeStateless() const - { - uint64_t size = 0; - MessageList::const_iterator it; - for( it = pMessages.begin(); it != pMessages.end(); ++it ) - if( !it->stateful ) - ++size; - return size; - } - - //---------------------------------------------------------------------------- - // Remove all the expired messages from the queue and put them in - // this one - //---------------------------------------------------------------------------- - void OutQueue::GrabExpired( OutQueue &queue, time_t exp ) - { - MessageList::iterator it; - for( it = queue.pMessages.begin(); it != queue.pMessages.end(); ) - { - if( it->expires > exp ) - { - ++it; - continue; - } - pMessages.push_back( *it ); - it = queue.pMessages.erase( it ); - } - } - - //---------------------------------------------------------------------------- - // Remove all the stateful messages from the queue and put them in this - // one - //---------------------------------------------------------------------------- - void OutQueue::GrabStateful( OutQueue &queue ) - { - MessageList::iterator it; - for( it = queue.pMessages.begin(); it != queue.pMessages.end(); ) - { - if( !it->stateful ) - { - ++it; - continue; - } - pMessages.push_back( *it ); - it = queue.pMessages.erase( it ); - } - } - - //---------------------------------------------------------------------------- - // Take all the items from the queue and put them in this one - //---------------------------------------------------------------------------- - void OutQueue::GrabItems( OutQueue &queue ) - { - MessageList::iterator it; - for( it = queue.pMessages.begin(); it != queue.pMessages.end(); ++it ) - pMessages.push_back( *it ); - queue.pMessages.clear(); - } -} - diff --git a/src/XrdCl/XrdClOutQueue.hh b/src/XrdCl/XrdClOutQueue.hh deleted file mode 100644 index 01aaaee8e49..00000000000 --- a/src/XrdCl/XrdClOutQueue.hh +++ /dev/null @@ -1,153 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_OUT_QUEUE_HH__ -#define __XRD_CL_OUT_QUEUE_HH__ - -#include -#include -#include "XrdCl/XrdClStatus.hh" - -namespace XrdCl -{ - class Message; - class OutgoingMsgHandler; - - //---------------------------------------------------------------------------- - //! A synchronized queue for the outgoing data - //---------------------------------------------------------------------------- - class OutQueue - { - public: - //------------------------------------------------------------------------ - //! Add a message to the back the queue - //! - //! @param msg message to be sent - //! @param handler handler to be notified about the status of the - //! operation - //! @param expires timeout - //! @param stateful if true a disconnection will cause an error and - //! removing from the queue, otherwise sending - //! wil be re-attempted - //------------------------------------------------------------------------ - void PushBack( Message *msg, - OutgoingMsgHandler *handler, - time_t expires, - bool stateful ); - - //------------------------------------------------------------------------ - //! Add a message to the front the queue - //! - //! @param msg message to be sent - //! @param handler handler to be notified about the status of the - //! operation - //! @param expires timeout - //! @param stateful if true a disconnection will cause an error and - //! removing from the queue, otherwise sending - //! wil be re-attempted - //------------------------------------------------------------------------ - void PushFront( Message *msg, - OutgoingMsgHandler *handler, - time_t expires, - bool stateful ); - - //------------------------------------------------------------------------ - //! Pop a message from the front of the queue - //! - //! @return 0 if there is no message message - //------------------------------------------------------------------------ - Message *PopMessage( OutgoingMsgHandler *&handler, - time_t &expires, - bool &stateful ); - - //------------------------------------------------------------------------ - //! Remove a message from the front - //------------------------------------------------------------------------ - void PopFront(); - - //------------------------------------------------------------------------ - //! Report status to all the handlers - //------------------------------------------------------------------------ - void Report( Status status ); - - //------------------------------------------------------------------------ - //! Check if the queue is empty - //------------------------------------------------------------------------ - bool IsEmpty() const - { - return pMessages.empty(); - } - - //------------------------------------------------------------------------ - // Return the size of the queue - //------------------------------------------------------------------------ - uint64_t GetSize() const - { - return pMessages.size(); - } - - //------------------------------------------------------------------------ - //! Return the size of the queue counting only the stateless messages - //------------------------------------------------------------------------ - uint64_t GetSizeStateless() const; - - //------------------------------------------------------------------------ - //! Remove all the expired messages from the queue and put them in - //! this one - //! - //! @param queue queue to take the message from - //! @param exp expiration timestamp - //------------------------------------------------------------------------ - void GrabExpired( OutQueue &queue, time_t exp = 0 ); - - //------------------------------------------------------------------------ - //! Remove all the stateful messages from the queue and put them in this - //! one - //! - //! @param queue the queue to take the messages from - //------------------------------------------------------------------------ - void GrabStateful( OutQueue &queue ); - - //------------------------------------------------------------------------ - //! Take all the items from the queue and put them in this one - //! - //! @param queue queue to take the message - //------------------------------------------------------------------------ - void GrabItems( OutQueue &queue ); - - private: - //------------------------------------------------------------------------ - // Helper struct holding all the message data - //------------------------------------------------------------------------ - struct MsgHelper - { - MsgHelper( Message *m, OutgoingMsgHandler *h, time_t r, bool s ): - msg( m ), handler( h ), expires( r ), stateful( s ) {} - - Message *msg; - OutgoingMsgHandler *handler; - time_t expires; - bool stateful; - }; - - typedef std::list MessageList; - MessageList pMessages; - }; -} - -#endif // __XRD_CL_OUT_QUEUE_HH__ diff --git a/src/XrdCl/XrdClPlugInInterface.hh b/src/XrdCl/XrdClPlugInInterface.hh deleted file mode 100644 index bebcb2fc24e..00000000000 --- a/src/XrdCl/XrdClPlugInInterface.hh +++ /dev/null @@ -1,416 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2014 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_PLUGIN_INTERFACE__ -#define __XRD_CL_PLUGIN_INTERFACE__ - -#include "XrdCl/XrdClFile.hh" -#include "XrdCl/XrdClFileSystem.hh" - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - //! An interface for file plug-ins - //---------------------------------------------------------------------------- - class FilePlugIn - { - public: - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - virtual ~FilePlugIn() {} - //------------------------------------------------------------------------ - //! @see XrdCl::File::Open - //------------------------------------------------------------------------ - virtual XRootDStatus Open( const std::string &url, - OpenFlags::Flags flags, - Access::Mode mode, - ResponseHandler *handler, - uint16_t timeout ) - { - (void)url; (void)flags; (void)mode; (void)handler; (void)timeout; - return XRootDStatus( stError, errNotImplemented ); - } - - //------------------------------------------------------------------------ - //! @see XrdCl::File::Close - //------------------------------------------------------------------------ - virtual XRootDStatus Close( ResponseHandler *handler, - uint16_t timeout ) - { - (void)handler; (void)timeout; - return XRootDStatus( stError, errNotImplemented ); - } - - //------------------------------------------------------------------------ - //! @see XrdCl::File::Stat - //------------------------------------------------------------------------ - virtual XRootDStatus Stat( bool force, - ResponseHandler *handler, - uint16_t timeout ) - { - (void)force; (void)handler; (void)timeout; - return XRootDStatus( stError, errNotImplemented ); - } - - - //------------------------------------------------------------------------ - //! @see XrdCl::File::Read - //------------------------------------------------------------------------ - virtual XRootDStatus Read( uint64_t offset, - uint32_t size, - void *buffer, - ResponseHandler *handler, - uint16_t timeout ) - { - (void)offset; (void)size; (void)buffer; (void)handler; (void)timeout; - return XRootDStatus( stError, errNotImplemented ); - } - - //------------------------------------------------------------------------ - //! @see XrdCl::File::Write - //------------------------------------------------------------------------ - virtual XRootDStatus Write( uint64_t offset, - uint32_t size, - const void *buffer, - ResponseHandler *handler, - uint16_t timeout ) - { - (void)offset; (void)size; (void)buffer; (void)handler; (void)timeout; - return XRootDStatus( stError, errNotImplemented ); - } - - //------------------------------------------------------------------------ - //! @see XrdCl::File::Sync - //------------------------------------------------------------------------ - virtual XRootDStatus Sync( ResponseHandler *handler, - uint16_t timeout ) - { - (void)handler; (void)timeout; - return XRootDStatus( stError, errNotImplemented ); - } - - //------------------------------------------------------------------------ - //! @see XrdCl::File::Truncate - //------------------------------------------------------------------------ - virtual XRootDStatus Truncate( uint64_t size, - ResponseHandler *handler, - uint16_t timeout ) - { - (void)size; (void)handler; (void)timeout; - return XRootDStatus( stError, errNotImplemented ); - } - - //------------------------------------------------------------------------ - //! @see XrdCl::File::VectorRead - //------------------------------------------------------------------------ - virtual XRootDStatus VectorRead( const ChunkList &chunks, - void *buffer, - ResponseHandler *handler, - uint16_t timeout ) - { - (void)chunks; (void)buffer; (void)handler; (void)timeout; - return XRootDStatus( stError, errNotImplemented ); - } - - //------------------------------------------------------------------------ - //! @see XrdCl::File::Fcntl - //------------------------------------------------------------------------ - virtual XRootDStatus Fcntl( const Buffer &arg, - ResponseHandler *handler, - uint16_t timeout ) - { - (void)arg; (void)handler; (void)timeout; - return XRootDStatus( stError, errNotImplemented ); - } - - //------------------------------------------------------------------------ - //! @see XrdCl::File::Visa - //------------------------------------------------------------------------ - virtual XRootDStatus Visa( ResponseHandler *handler, - uint16_t timeout ) - { - (void)handler; (void)timeout; - return XRootDStatus( stError, errNotImplemented ); - } - - //------------------------------------------------------------------------ - //! @see XrdCl::File::IsOpen - //------------------------------------------------------------------------ - virtual bool IsOpen() const - { - return false; - } - - //------------------------------------------------------------------------ - //! @see XrdCl::File::SetProperty - //------------------------------------------------------------------------ - virtual bool SetProperty( const std::string &name, - const std::string &value ) - { - (void)name; (void)value; - return false; - } - - //------------------------------------------------------------------------ - //! @see XrdCl::File::GetProperty - //------------------------------------------------------------------------ - virtual bool GetProperty( const std::string &name, - std::string &value ) const - { - (void)name; (void)value; - return false; - } - }; - - //---------------------------------------------------------------------------- - //! An interface for file plug-ins - //---------------------------------------------------------------------------- - class FileSystemPlugIn - { - public: - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - virtual ~FileSystemPlugIn() {} - - //------------------------------------------------------------------------ - //! @see XrdCl::FileSystem::Locate - //------------------------------------------------------------------------ - virtual XRootDStatus Locate( const std::string &path, - OpenFlags::Flags flags, - ResponseHandler *handler, - uint16_t timeout ) - { - (void)path; (void)flags; (void)handler; (void)timeout; - return XRootDStatus( stError, errNotImplemented ); - } - - //------------------------------------------------------------------------ - //! @see XrdCl::FileSystem::Mv - //------------------------------------------------------------------------ - virtual XRootDStatus Mv( const std::string &source, - const std::string &dest, - ResponseHandler *handler, - uint16_t timeout ) - { - (void)source; (void)dest; (void)handler; (void)timeout; - return XRootDStatus( stError, errNotImplemented ); - } - - //------------------------------------------------------------------------ - //! @see XrdCl::FileSystem::Query - //------------------------------------------------------------------------ - virtual XRootDStatus Query( QueryCode::Code queryCode, - const Buffer &arg, - ResponseHandler *handler, - uint16_t timeout ) - { - (void)queryCode; (void)arg; (void)handler; (void)timeout; - return XRootDStatus( stError, errNotImplemented ); - } - - //------------------------------------------------------------------------ - //! @see XrdCl::FileSystem::Truncate - //------------------------------------------------------------------------ - virtual XRootDStatus Truncate( const std::string &path, - uint64_t size, - ResponseHandler *handler, - uint16_t timeout ) - { - (void)path; (void)size; (void)handler; (void)timeout; - return XRootDStatus( stError, errNotImplemented ); - } - - //------------------------------------------------------------------------ - //! @see XrdCl::FileSystem::Rm - //------------------------------------------------------------------------ - virtual XRootDStatus Rm( const std::string &path, - ResponseHandler *handler, - uint16_t timeout ) - { - (void)path; (void)handler; (void)timeout; - return XRootDStatus( stError, errNotImplemented ); - } - - //------------------------------------------------------------------------ - //! @see XrdCl::FileSystem::MkDir - //------------------------------------------------------------------------ - virtual XRootDStatus MkDir( const std::string &path, - MkDirFlags::Flags flags, - Access::Mode mode, - ResponseHandler *handler, - uint16_t timeout ) - { - (void)path; (void)flags; (void)mode; (void)handler; (void)timeout; - return XRootDStatus( stError, errNotImplemented ); - } - - //------------------------------------------------------------------------ - //! @see XrdCl::FileSystem::RmDir - //------------------------------------------------------------------------ - virtual XRootDStatus RmDir( const std::string &path, - ResponseHandler *handler, - uint16_t timeout ) - { - (void)path; (void)handler; (void)timeout; - return XRootDStatus( stError, errNotImplemented ); - } - - //------------------------------------------------------------------------ - //! @see XrdCl::FileSystem::ChMod - //------------------------------------------------------------------------ - virtual XRootDStatus ChMod( const std::string &path, - Access::Mode mode, - ResponseHandler *handler, - uint16_t timeout ) - { - (void)path; (void)mode; (void)handler; (void)timeout; - return XRootDStatus( stError, errNotImplemented ); - } - - //------------------------------------------------------------------------ - //! @see XrdCl::FileSystem::Ping - //------------------------------------------------------------------------ - virtual XRootDStatus Ping( ResponseHandler *handler, - uint16_t timeout ) - { - (void)handler; (void)timeout; - return XRootDStatus( stError, errNotImplemented ); - } - - //------------------------------------------------------------------------ - //! @see XrdCl::FileSystem::Stat - //------------------------------------------------------------------------ - virtual XRootDStatus Stat( const std::string &path, - ResponseHandler *handler, - uint16_t timeout ) - { - (void)path; (void)handler; (void)timeout; - return XRootDStatus( stError, errNotImplemented ); - } - - //------------------------------------------------------------------------ - //! @see XrdCl::FileSystem::StatVFS - //------------------------------------------------------------------------ - virtual XRootDStatus StatVFS( const std::string &path, - ResponseHandler *handler, - uint16_t timeout ) - { - (void)path; (void)handler; (void)timeout; - return XRootDStatus( stError, errNotImplemented ); - } - - //------------------------------------------------------------------------ - //! @see XrdCl::FileSystem::Protocol - //------------------------------------------------------------------------ - virtual XRootDStatus Protocol( ResponseHandler *handler, - uint16_t timeout = 0 ) - { - (void)handler; (void)timeout; - return XRootDStatus( stError, errNotImplemented ); - } - - //------------------------------------------------------------------------ - //! @see XrdCl::FileSystem::DirlList - //------------------------------------------------------------------------ - virtual XRootDStatus DirList( const std::string &path, - DirListFlags::Flags flags, - ResponseHandler *handler, - uint16_t timeout ) - { - (void)path; (void)flags; (void)handler; (void)timeout; - return XRootDStatus( stError, errNotImplemented ); - } - - //------------------------------------------------------------------------ - //! @see XrdCl::FileSystem::SendInfo - //------------------------------------------------------------------------ - virtual XRootDStatus SendInfo( const std::string &info, - ResponseHandler *handler, - uint16_t timeout ) - { - (void)info; (void)handler; (void)timeout; - return XRootDStatus( stError, errNotImplemented ); - } - - //------------------------------------------------------------------------ - //! @see XrdCl::FileSystem::Prepare - //------------------------------------------------------------------------ - virtual XRootDStatus Prepare( const std::vector &fileList, - PrepareFlags::Flags flags, - uint8_t priority, - ResponseHandler *handler, - uint16_t timeout ) - { - (void)fileList; (void)flags; (void)priority; (void)handler; - (void)timeout; - return XRootDStatus( stError, errNotImplemented ); - } - - //------------------------------------------------------------------------ - //! @see XrdCl::FileSystem::SetProperty - //------------------------------------------------------------------------ - virtual bool SetProperty( const std::string &name, - const std::string &value ) - { - (void)name; (void)value; - return false; - } - - //------------------------------------------------------------------------ - //! @see XrdCl::FileSystem::GetProperty - //------------------------------------------------------------------------ - virtual bool GetProperty( const std::string &name, - std::string &value ) const - { - (void)name; (void)value; - return false; - } - }; - - //---------------------------------------------------------------------------- - //! Plugin factory - //---------------------------------------------------------------------------- - class PlugInFactory - { - public: - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - virtual ~PlugInFactory() {} - - //------------------------------------------------------------------------ - //! Create a file plug-in for the given URL - //------------------------------------------------------------------------ - virtual FilePlugIn *CreateFile( const std::string &url ) = 0; - - //------------------------------------------------------------------------ - //! Create a file system plug-in for the given URL - //------------------------------------------------------------------------ - virtual FileSystemPlugIn *CreateFileSystem( const std::string &url ) = 0; - }; -} - -#endif // __XRD_CL_PLUGIN_INTERFACE__ diff --git a/src/XrdCl/XrdClPlugInManager.cc b/src/XrdCl/XrdClPlugInManager.cc deleted file mode 100644 index b651fab239c..00000000000 --- a/src/XrdCl/XrdClPlugInManager.cc +++ /dev/null @@ -1,481 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2014 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#include "XrdCl/XrdClPlugInManager.hh" -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdCl/XrdClLog.hh" -#include "XrdCl/XrdClConstants.hh" -#include "XrdCl/XrdClUtils.hh" -#include "XrdCl/XrdClURL.hh" -#include "XrdSys/XrdSysPwd.hh" -#include "XrdVersion.hh" - -#include -#include -#include -#include -#include - -XrdVERSIONINFOREF( XrdCl ); - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - // Constructor - //---------------------------------------------------------------------------- - PlugInManager::PlugInManager(): - pDefaultFactory(0) - { - } - - //---------------------------------------------------------------------------- - // Destructor - //---------------------------------------------------------------------------- - PlugInManager:: ~PlugInManager() - { - std::map::iterator it; - for( it = pFactoryMap.begin(); it != pFactoryMap.end(); ++it ) - { - it->second->counter--; - if( it->second->counter == 0 ) - delete it->second; - } - - delete pDefaultFactory; - } - - //---------------------------------------------------------------------------- - // Register a plug-in favtory for the given url - //---------------------------------------------------------------------------- - bool PlugInManager::RegisterFactory( const std::string &url, - PlugInFactory *factory ) - { - Log *log = DefaultEnv::GetLog(); - XrdSysMutexHelper scopedLock( pMutex ); - - std::string normUrl = NormalizeURL( url ); - if( normUrl == "" ) - return false; - - std::map::iterator it; - it = pFactoryMap.find( normUrl ); - if( it != pFactoryMap.end() ) - { - if( it->second->isEnv ) - return false; - - // we don't need to check the counter because it's valid only - // for environment plugins which cannot be replaced via - // this method - delete it->second; - } - - if( !factory ) - { - log->Debug( PlugInMgrMsg, "Removing the factory for %s", - normUrl.c_str() ); - pFactoryMap.erase( it ); - return true; - } - - log->Debug( PlugInMgrMsg, "Registering a factory for %s", - normUrl.c_str() ); - - FactoryHelper *h = new FactoryHelper(); - h->factory = factory; - h->counter = 1; - pFactoryMap[normUrl] = h; - return true; - } - - //------------------------------------------------------------------------ - //! Register a plug-in factory applying to all URLs - //------------------------------------------------------------------------ - bool PlugInManager::RegisterDefaultFactory( PlugInFactory *factory ) - { - Log *log = DefaultEnv::GetLog(); - XrdSysMutexHelper scopedLock( pMutex ); - - if( pDefaultFactory && pDefaultFactory->isEnv ) - return false; - - delete pDefaultFactory; - pDefaultFactory = 0; - - if( factory ) - { - log->Debug( PlugInMgrMsg, "Registering a default factory" ); - pDefaultFactory = new FactoryHelper; - pDefaultFactory->factory = factory; - } - else - log->Debug( PlugInMgrMsg, "Removing the default factory" ); - - return true; - } - - //---------------------------------------------------------------------------- - // Retrieve the plug-in factory for the given URL - //---------------------------------------------------------------------------- - PlugInFactory *PlugInManager::GetFactory( const std::string url ) - { - XrdSysMutexHelper scopedLock( pMutex ); - - if( pDefaultFactory && pDefaultFactory->isEnv ) - return pDefaultFactory->factory; - - std::string normUrl = NormalizeURL( url ); - if( normUrl.empty() ) - { - if( pDefaultFactory ) - return pDefaultFactory->factory; - return 0; - } - - std::map::iterator it; - it = pFactoryMap.find( normUrl ); - if( it != pFactoryMap.end() && it->second->isEnv ) - return it->second->factory; - - if( pDefaultFactory ) - return pDefaultFactory->factory; - - if( it != pFactoryMap.end() ) - return it->second->factory; - - return 0; - } - - //---------------------------------------------------------------------------- - // Process user environment to load plug-in settings. - //---------------------------------------------------------------------------- - void PlugInManager::ProcessEnvironmentSettings() - { - XrdSysMutexHelper scopedLock( pMutex ); - Log *log = DefaultEnv::GetLog(); - Env *env = DefaultEnv::GetEnv(); - - log->Debug( PlugInMgrMsg, "Initializing plug-in manager..." ); - - //-------------------------------------------------------------------------- - // Check if a default plug-in has been specified in the environment - //-------------------------------------------------------------------------- - bool loadConfigs = true; - std::string defaultPlugIn = DefaultPlugIn; - env->GetString( "PlugIn", defaultPlugIn ); - if( !defaultPlugIn.empty() ) - { - loadConfigs = false; - log->Debug( PlugInMgrMsg, "Loading default plug-in from %s...", - defaultPlugIn.c_str()); - - std::pair pg = LoadFactory( - defaultPlugIn, std::map() ); - - if( !pg.first ) - { - log->Debug( PlugInMgrMsg, "Failed to load default plug-in from %s", - defaultPlugIn.c_str()); - loadConfigs = false; - } - - pDefaultFactory = new FactoryHelper(); - pDefaultFactory->factory = pg.second; - pDefaultFactory->plugin = pg.first; - pDefaultFactory->isEnv = true; - } - - //-------------------------------------------------------------------------- - // If there is no default plug-in or it is invalid then load plug-in config - // files - //-------------------------------------------------------------------------- - if( loadConfigs ) - { - log->Debug( PlugInMgrMsg, - "No default plug-in, loading plug-in configs..." ); - - ProcessConfigDir( "/etc/xrootd/client.plugins.d" ); - - XrdSysPwd pwdHandler; - passwd *pwd = pwdHandler.Get( getuid() ); - if( !pwd ) return; - std::string userPlugIns = pwd->pw_dir; - userPlugIns += "/.xrootd/client.plugins.d"; - ProcessConfigDir( userPlugIns ); - - std::string customPlugIns = DefaultPlugInConfDir; - env->GetString( "PlugInConfDir", customPlugIns ); - if( !customPlugIns.empty() ) - ProcessConfigDir( customPlugIns ); - } - } - - //---------------------------------------------------------------------------- - // Process the configuration directory and load plug in definitions - //---------------------------------------------------------------------------- - void PlugInManager::ProcessConfigDir( const std::string &dir ) - { - Log *log = DefaultEnv::GetLog(); - log->Debug( PlugInMgrMsg, "Processing plug-in definitions in %s...", - dir.c_str()); - - std::vector entries; - std::vector::iterator it; - Status st = Utils::GetDirectoryEntries( entries, dir ); - if( !st.IsOK() ) - { - log->Debug( PlugInMgrMsg, "Unable to process directory %s: %s", - dir.c_str(), st.ToString().c_str() ); - return; - } - std::sort( entries.begin(), entries.end() ); - - for( it = entries.begin(); it != entries.end(); ++it ) - { - std::string confFile = dir + "/" + *it; - std::string suffix = ".conf"; - if( confFile.length() <= suffix.length() ) - continue; - if( !std::equal( suffix.rbegin(), suffix.rend(), confFile.rbegin() ) ) - continue; - - ProcessPlugInConfig( confFile ); - } - } - - //---------------------------------------------------------------------------- - // Process a plug-in config file and load the plug-in if possible - //---------------------------------------------------------------------------- - void PlugInManager::ProcessPlugInConfig( const std::string &confFile ) - { - Log *log = DefaultEnv::GetLog(); - log->Dump( PlugInMgrMsg, "Processing: %s", confFile.c_str() ); - - //-------------------------------------------------------------------------- - // Read the config - //-------------------------------------------------------------------------- - std::map config; - Status st = Utils::ProcessConfig( config, confFile ); - if( !st.IsOK() ) - { - log->Debug( PlugInMgrMsg, "Unable process config %s: %s", - confFile.c_str(), st.ToString().c_str() ); - return; - } - - const char *keys[] = { "url", "lib", "enable", 0 }; - for( int i = 0; keys[i]; ++i ) - { - if( config.find( keys[i] ) == config.end() ) - { - log->Debug( PlugInMgrMsg, "Unable to find '%s' key in the config file " - "%s, ignoring this config", keys[i], confFile.c_str() ); - return; - } - } - - //-------------------------------------------------------------------------- - // Attempt to load the plug in and place it in the map - //-------------------------------------------------------------------------- - std::string url = config["url"]; - std::string lib = config["lib"]; - std::string enable = config["enable"]; - - log->Dump( PlugInMgrMsg, "Settings from '%s': url='%s', lib='%s', " - "enable='%s'", confFile.c_str(), url.c_str(), lib.c_str(), - enable.c_str() ); - - std::pair pg; - pg.first = 0; pg.second = 0; - if( enable == "true" ) - { - log->Debug( PlugInMgrMsg, "Trying to load a plug-in for '%s' from '%s'", - url.c_str(), lib.c_str() ); - - pg = LoadFactory( lib, config ); - - if( !pg.first ) - return; - } - else - log->Debug( PlugInMgrMsg, "Trying to disable plug-in for '%s'", - url.c_str() ); - - if( !RegisterFactory( url, lib, pg.second, pg.first ) ) - { - delete pg.first; - delete pg.second; - } - } - - //---------------------------------------------------------------------------- - // Load the plug-in and create the factory - //---------------------------------------------------------------------------- - std::pair PlugInManager::LoadFactory( - const std::string &lib, const std::map &config ) - { - Log *log = DefaultEnv::GetLog(); - - char errorBuff[1024]; - XrdOucPinLoader *pgHandler = new XrdOucPinLoader( errorBuff, 1024, - &XrdVERSIONINFOVAR( XrdCl ), - "client", lib.c_str() ); - - PlugInFunc_t pgFunc = (PlugInFunc_t)pgHandler->Resolve("XrdClGetPlugIn", -1); - - if( !pgFunc ) - { - log->Debug( PlugInMgrMsg, "Error while loading %s: %s", lib.c_str(), - errorBuff ); - pgHandler->Unload(); - delete pgHandler; - return std::make_pair( 0, 0 ); - } - - PlugInFactory *f = (PlugInFactory*)pgFunc( &config ); - - if( !f ) - { - delete pgHandler; - return std::make_pair( 0, 0 ); - } - - return std::make_pair( pgHandler, f ); - } - - //---------------------------------------------------------------------------- - // Handle factory - register it or free all the memory - //---------------------------------------------------------------------------- - bool PlugInManager::RegisterFactory( const std::string &urlString, - const std::string &lib, - PlugInFactory *factory, - XrdOucPinLoader *plugin ) - { - //-------------------------------------------------------------------------- - // Process and normalize the URLs - //-------------------------------------------------------------------------- - Log *log = DefaultEnv::GetLog(); - std::vector urls; - std::vector normalizedURLs; - std::vector::iterator it; - - if (urlString == "*") { - if (pDefaultFactory) { - if (pDefaultFactory->isEnv) { - log->Debug(PlugInMgrMsg, "There is already an env default plugin " - "loaded, skiping %s", lib.c_str()); - return false; - } else { - log->Debug(PlugInMgrMsg, "There can be only one default plugin " - "loaded, skipping %s", lib.c_str()); - return false; - } - } else { - pDefaultFactory = new FactoryHelper(); - pDefaultFactory->factory = factory; - pDefaultFactory->plugin = plugin; - pDefaultFactory->isEnv = false; - return true; - } - } - - Utils::splitString( urls, urlString, ";" ); - - for( it = urls.begin(); it != urls.end(); ++it ) - { - std::string normURL = NormalizeURL( *it ); - if( normURL == "" ) - { - log->Debug( PlugInMgrMsg, "Url cannot be normalized: '%s', ignoring", - it->c_str() ); - continue; - } - normalizedURLs.push_back( normURL ); - } - - std::sort( normalizedURLs.begin(), normalizedURLs.end() ); - std::unique( normalizedURLs.begin(), normalizedURLs.end() ); - - if( normalizedURLs.empty() ) - return false; - - //-------------------------------------------------------------------------- - // Insert or remove from the map - //-------------------------------------------------------------------------- - FactoryHelper *h = 0; - - if( factory ) - { - h = new FactoryHelper(); - h->isEnv = true; - h->counter = normalizedURLs.size(); - h->plugin = plugin; - h->factory = factory; - } - - std::map::iterator mapIt; - for( it = normalizedURLs.begin(); it != normalizedURLs.end(); ++it ) - { - mapIt = pFactoryMap.find( *it ); - if( mapIt != pFactoryMap.end() ) - { - mapIt->second->counter--; - if( mapIt->second->counter == 0 ) - delete mapIt->second; - } - - if( h ) - { - log->Debug( PlugInMgrMsg, "Registering a factory for %s from %s", - it->c_str(), lib.c_str() ); - pFactoryMap[*it] = h; - } - else - { - if( mapIt != pFactoryMap.end() ) - { - log->Debug( PlugInMgrMsg, "Removing the factory for %s", - it->c_str() ); - pFactoryMap.erase( mapIt ); - } - } - } - - return true; - } - - //---------------------------------------------------------------------------- - // Normalize a URL - //---------------------------------------------------------------------------- - std::string PlugInManager::NormalizeURL( const std::string url ) - { - URL urlObj = url; - if( !urlObj.IsValid() ) - return ""; - std::ostringstream o; - o << urlObj.GetProtocol() << "://" << urlObj.GetHostName() << ":"; - o << urlObj.GetPort(); - return o.str(); - } -}; diff --git a/src/XrdCl/XrdClPlugInManager.hh b/src/XrdCl/XrdClPlugInManager.hh deleted file mode 100644 index a7a5439b118..00000000000 --- a/src/XrdCl/XrdClPlugInManager.hh +++ /dev/null @@ -1,174 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2014 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_PLUGIN_MANAGER__ -#define __XRD_CL_PLUGIN_MANAGER__ - -#include "XrdCl/XrdClPlugInInterface.hh" -#include "XrdOuc/XrdOucPinLoader.hh" -#include "XrdSys/XrdSysPthread.hh" - -#include -#include -#include - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - //! Manage client-side plug-ins and match them agains URLs - //---------------------------------------------------------------------------- - class PlugInManager - { - public: - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - PlugInManager(); - - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - ~PlugInManager(); - - //------------------------------------------------------------------------ - //! Register a plug-in factory for the given url, registering a 0 pointer - //! removes the factory for the url - //------------------------------------------------------------------------ - bool RegisterFactory( const std::string &url, - PlugInFactory *factory ); - - //------------------------------------------------------------------------ - //! Register a plug-in factory applying to all URLs, registering - //! a 0 pointer removes the factory - //------------------------------------------------------------------------ - bool RegisterDefaultFactory( PlugInFactory *factory ); - - //------------------------------------------------------------------------ - //! Retrieve the plug-in factory for the given URL - //! - //! @return you do not own the returned memory - //------------------------------------------------------------------------ - PlugInFactory *GetFactory( const std::string url ); - - //------------------------------------------------------------------------ - //! Process user environment to load plug-in settings. - //! - //! This will try to load a default plug-in from a library pointed to - //! by the XRD_PLUGIN envvar. If this fails it will scan the configuration - //! files located in: - //! - //! 1) system directory: /etc/xrootd/client.plugins.d/ - //! 2) user direvtory: ~/.xrootd/client.plugins.d/ - //! 3) directory pointed to by XRD_PLUGINCONFDIR envvar - //! - //! In that order. - //! - //! The configuration files contain lines with key-value pairs in the - //! form of 'key=value'. - //! - //! Mandatory keys are: - //! url - a semicolon separated list of URLs the plug-in applies to - //! lib - plugin library to be loaded - //! enabled - determines whether the plug-in should be enabled or not - //! - //! You may use any other keys for your own purposes. - //! - //! The config files are processed in alphabetic order, any satteing - //! found later superseeds the previous one. Any setting applied via - //! environment or config files superseeds any setting done - //! programatically. - //! - //! The plug-in library must implement the following C function: - //! - //! @code{.cpp} - //! extern "C" - //! { - //! void *XrdClGetPlugIn( const void *arg ) - //! { - //! return __your_plug_in_factory__; - //! } - //! } - //! @endcode - //! - //! where arg is a const pointer to std::map - //! containing the plug-in configuration. - //------------------------------------------------------------------------ - void ProcessEnvironmentSettings(); - - private: - typedef void *(*PlugInFunc_t)( const void *arg ); - - struct FactoryHelper - { - FactoryHelper(): plugin(0), factory(0), isEnv(false), counter(0) {} - ~FactoryHelper() - { - delete factory; - if(plugin) plugin->Unload(); - delete plugin; - } - XrdOucPinLoader *plugin; - PlugInFactory *factory; - bool isEnv; - uint32_t counter; - }; - - //------------------------------------------------------------------------ - //! Process the configuration directory and load plug in definitions - //------------------------------------------------------------------------ - void ProcessConfigDir( const std::string &dir ); - - //------------------------------------------------------------------------ - //! Process a plug-in config file and load the plug-in if possible - //------------------------------------------------------------------------ - void ProcessPlugInConfig( const std::string &confFile ); - - //------------------------------------------------------------------------ - //! Load the plug-in and create the factory - //------------------------------------------------------------------------ - std::pair LoadFactory( - const std::string &lib, - const std::map &config ); - - //------------------------------------------------------------------------ - //! Register factory, if successful it actuires ownership of the objects - //! @return true if successfully registered - //------------------------------------------------------------------------ - bool RegisterFactory( const std::string &urlString, - const std::string &lib, - PlugInFactory *factory, - XrdOucPinLoader *plugin ); - - //------------------------------------------------------------------------ - //! Normalize a URL - //------------------------------------------------------------------------ - std::string NormalizeURL( const std::string url ); - - std::map pFactoryMap; - FactoryHelper *pDefaultFactory; - XrdSysMutex pMutex; - }; -} - -#endif // __XRD_CL_PLUGIN_MANAGER__ diff --git a/src/XrdCl/XrdClPoller.hh b/src/XrdCl/XrdClPoller.hh deleted file mode 100644 index 928439f19ee..00000000000 --- a/src/XrdCl/XrdClPoller.hh +++ /dev/null @@ -1,163 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_POLLER_HH__ -#define __XRD_CL_POLLER_HH__ - -#include -#include - -namespace XrdCl -{ - class Socket; - class Poller; - - //---------------------------------------------------------------------------- - //! Interface - //---------------------------------------------------------------------------- - class SocketHandler - { - public: - //------------------------------------------------------------------------ - //! Event type - //------------------------------------------------------------------------ - enum EventType - { - ReadyToRead = 0x01, //!< New data has arrived - ReadTimeOut = 0x02, //!< Read timeout - ReadyToWrite = 0x04, //!< Writing won't block - WriteTimeOut = 0x08 //!< Write timeout - }; - - //------------------------------------------------------------------------ - // Destructor - //------------------------------------------------------------------------ - virtual ~SocketHandler() {} - - //------------------------------------------------------------------------ - //! Initializer - //------------------------------------------------------------------------ - virtual void Initialize( Poller * ) {} - - //------------------------------------------------------------------------ - //! Finalizer - //------------------------------------------------------------------------ - virtual void Finalize() {}; - - //------------------------------------------------------------------------ - //! Called when an event occurred on a given socket - //------------------------------------------------------------------------ - virtual void Event( uint8_t type, - Socket *socket ) = 0; - - //------------------------------------------------------------------------ - //! Translate the event type to a string - //------------------------------------------------------------------------ - static std::string EventTypeToString( uint8_t event ) - { - std::string ev; - if( event & ReadyToRead ) ev += "ReadyToRead|"; - if( event & ReadTimeOut ) ev += "ReadTimeOut|"; - if( event & ReadyToWrite ) ev += "ReadyToWrite|"; - if( event & WriteTimeOut ) ev += "WriteTimeOut|"; - ev.erase( ev.length()-1, 1) ; - return ev; - } - }; - - //---------------------------------------------------------------------------- - //! Interface for socket pollers - //---------------------------------------------------------------------------- - class Poller - { - public: - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - virtual ~Poller() {} - - //------------------------------------------------------------------------ - //! Initialize the poller - //------------------------------------------------------------------------ - virtual bool Initialize() = 0; - - //------------------------------------------------------------------------ - //! Finalize the poller - //------------------------------------------------------------------------ - virtual bool Finalize() = 0; - - //------------------------------------------------------------------------ - //! Start polling - //------------------------------------------------------------------------ - virtual bool Start() = 0; - - //------------------------------------------------------------------------ - //! Stop polling - //------------------------------------------------------------------------ - virtual bool Stop() = 0; - - //------------------------------------------------------------------------ - //! Add socket to the polling loop - //! - //! @param socket the socket - //! @param handler object handling the events - //------------------------------------------------------------------------ - virtual bool AddSocket( Socket *socket, - SocketHandler *handler ) = 0; - - //------------------------------------------------------------------------ - //! Remove the socket - //------------------------------------------------------------------------ - virtual bool RemoveSocket( Socket *socket ) = 0; - - //------------------------------------------------------------------------ - //! Notify the handler about read events - //! - //! @param socket the socket - //! @param notify specify if the handler should be notified - //! @param timeout if no read event occurred after this time a timeout - //! event will be generated - //------------------------------------------------------------------------ - virtual bool EnableReadNotification( Socket *socket, - bool notify, - uint16_t timeout = 60 ) = 0; - - //------------------------------------------------------------------------ - //! Notify the handler about write events - //! @param socket the socket - //! @param notify specify if the handler should be notified - //! @param timeout if no write event occurred after this time a timeout - //! event will be generated - //------------------------------------------------------------------------ - virtual bool EnableWriteNotification( Socket *socket, - bool notify, - uint16_t timeout = 60 ) = 0; - - //------------------------------------------------------------------------ - //! Check whether the socket is registered with the poller - //------------------------------------------------------------------------ - virtual bool IsRegistered( Socket *socket ) = 0; - - //------------------------------------------------------------------------ - //! Is the event loop running? - //------------------------------------------------------------------------ - virtual bool IsRunning() const = 0; - }; -} - -#endif // __XRD_CL_POLLER_HH__ diff --git a/src/XrdCl/XrdClPollerBuiltIn.cc b/src/XrdCl/XrdClPollerBuiltIn.cc deleted file mode 100644 index 71a458d8f73..00000000000 --- a/src/XrdCl/XrdClPollerBuiltIn.cc +++ /dev/null @@ -1,587 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2014 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#include "XrdCl/XrdClPollerBuiltIn.hh" -#include "XrdCl/XrdClLog.hh" -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdCl/XrdClConstants.hh" -#include "XrdCl/XrdClSocket.hh" -#include "XrdCl/XrdClOptimizers.hh" -#include "XrdSys/XrdSysIOEvents.hh" - -namespace -{ - //---------------------------------------------------------------------------- - // A helper struct passed to the callback as a custom arg - //---------------------------------------------------------------------------- - struct PollerHelper - { - PollerHelper(): - channel(0), callBack(0), readEnabled(false), writeEnabled(false), - readTimeout(0), writeTimeout(0) - {} - XrdSys::IOEvents::Channel *channel; - XrdSys::IOEvents::CallBack *callBack; - bool readEnabled; - bool writeEnabled; - uint16_t readTimeout; - uint16_t writeTimeout; - }; - - //---------------------------------------------------------------------------- - // Call back implementation - //---------------------------------------------------------------------------- - class SocketCallBack: public XrdSys::IOEvents::CallBack - { - public: - SocketCallBack( XrdCl::Socket *sock, XrdCl::SocketHandler *sh ): - pSocket( sock ), pHandler( sh ) {} - virtual ~SocketCallBack() {}; - - virtual bool Event( XrdSys::IOEvents::Channel *chP, - void *cbArg, - int evFlags ) - { - using namespace XrdCl; - uint8_t ev = 0; - - if( evFlags & ReadyToRead ) ev |= SocketHandler::ReadyToRead; - if( evFlags & ReadTimeOut ) ev |= SocketHandler::ReadTimeOut; - if( evFlags & ReadyToWrite ) ev |= SocketHandler::ReadyToWrite; - if( evFlags & WriteTimeOut ) ev |= SocketHandler::WriteTimeOut; - - Log *log = DefaultEnv::GetLog(); - if( unlikely(log->GetLevel() >= Log::DumpMsg) ) - { - log->Dump( PollerMsg, "%s Got an event: %s", - pSocket->GetName().c_str(), - SocketHandler::EventTypeToString( ev ).c_str() ); - } - - pHandler->Event( ev, pSocket ); - return true; - } - private: - XrdCl::Socket *pSocket; - XrdCl::SocketHandler *pHandler; - }; -} - - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - // Initialize the poller - //---------------------------------------------------------------------------- - bool PollerBuiltIn::Initialize() - { - return true; - } - - //---------------------------------------------------------------------------- - // Finalize the poller - //---------------------------------------------------------------------------- - bool PollerBuiltIn::Finalize() - { - //-------------------------------------------------------------------------- - // Clean up the channels - //-------------------------------------------------------------------------- - SocketMap::iterator it; - for( it = pSocketMap.begin(); it != pSocketMap.end(); ++it ) - { - PollerHelper *helper = (PollerHelper*)it->second; - helper->channel->Delete(); - delete helper->callBack; - delete helper; - } - pSocketMap.clear(); - - return true; - } - - //------------------------------------------------------------------------ - // Start polling - //------------------------------------------------------------------------ - bool PollerBuiltIn::Start() - { - //-------------------------------------------------------------------------- - // Start the poller - //-------------------------------------------------------------------------- - using namespace XrdSys; - - Log *log = DefaultEnv::GetLog(); - log->Debug( PollerMsg, "Creating and starting the built-in poller..." ); - XrdSysMutexHelper scopedLock( pMutex ); - int errNum = 0; - const char *errMsg = 0; - - for( int i = 0; i < pNbPoller; ++i ) - { - XrdSys::IOEvents::Poller* poller = IOEvents::Poller::Create( errNum, &errMsg ); - if( !poller ) - { - log->Error( PollerMsg, "Unable to create the internal poller object: ", - "%s (%s)", strerror( errno ), errMsg ); - return false; - } - pPollerPool.push_back( poller ); - } - - pNext = pPollerPool.begin(); - - log->Debug( PollerMsg, "Using %d poller threads", pNbPoller ); - - //-------------------------------------------------------------------------- - // Check if we have any descriptors to reinsert from the last time we - // were started - //-------------------------------------------------------------------------- - SocketMap::iterator it; - for( it = pSocketMap.begin(); it != pSocketMap.end(); ++it ) - { - PollerHelper *helper = (PollerHelper*)it->second; - Socket *socket = it->first; - helper->channel = new IOEvents::Channel( RegisterAndGetPoller( socket ), socket->GetFD(), - helper->callBack ); - if( helper->readEnabled ) - { - bool status = helper->channel->Enable( IOEvents::Channel::readEvents, - helper->readTimeout, &errMsg ); - if( !status ) - { - log->Error( PollerMsg, "Unable to enable read notifications ", - "while re-starting %s (%s)", strerror( errno ), errMsg ); - - return false; - } - } - - if( helper->writeEnabled ) - { - bool status = helper->channel->Enable( IOEvents::Channel::writeEvents, - helper->writeTimeout, &errMsg ); - if( !status ) - { - log->Error( PollerMsg, "Unable to enable write notifications ", - "while re-starting %s (%s)", strerror( errno ), errMsg ); - - return false; - } - } - } - return true; - } - - //------------------------------------------------------------------------ - // Stop polling - //------------------------------------------------------------------------ - bool PollerBuiltIn::Stop() - { - using namespace XrdSys::IOEvents; - - Log *log = DefaultEnv::GetLog(); - log->Debug( PollerMsg, "Stopping the poller..." ); - - XrdSysMutexHelper scopedLock( pMutex ); - - if( pPollerPool.empty() ) - { - log->Debug( PollerMsg, "Stopping a poller that has not been started" ); - return true; - } - - while( !pPollerPool.empty() ) - { - XrdSys::IOEvents::Poller *poller = pPollerPool.back(); - pPollerPool.pop_back(); - - if( !poller ) continue; - - scopedLock.UnLock(); - poller->Stop(); - delete poller; - scopedLock.Lock( &pMutex ); - } - pNext = pPollerPool.end(); - pPollerMap.clear(); - - SocketMap::iterator it; - const char *errMsg = 0; - - for( it = pSocketMap.begin(); it != pSocketMap.end(); ++it ) - { - PollerHelper *helper = (PollerHelper*)it->second; - Socket *socket = it->first; - bool status = helper->channel->Disable( Channel::allEvents, &errMsg ); - if( !status ) - { - log->Error( PollerMsg, "%s Unable to disable write notifications: %s", - socket->GetName().c_str(), errMsg ); - } - helper->channel->Delete(); - helper->channel = 0; - } - - return true; - } - - //------------------------------------------------------------------------ - // Add socket to the polling queue - //------------------------------------------------------------------------ - bool PollerBuiltIn::AddSocket( Socket *socket, - SocketHandler *handler ) - { - Log *log = DefaultEnv::GetLog(); - XrdSysMutexHelper scopedLock( pMutex ); - - if( !socket ) - { - log->Error( PollerMsg, "Invalid socket, impossible to poll" ); - return false; - } - - if( socket->GetStatus() != Socket::Connected && - socket->GetStatus() != Socket::Connecting ) - { - log->Error( PollerMsg, "Socket is not in a state valid for polling" ); - return false; - } - - log->Debug( PollerMsg, "Adding socket 0x%x to the poller", socket ); - - //-------------------------------------------------------------------------- - // Check if the socket is already registered - //-------------------------------------------------------------------------- - SocketMap::const_iterator it = pSocketMap.find( socket ); - if( it != pSocketMap.end() ) - { - log->Warning( PollerMsg, "%s Already registered with this poller", - socket->GetName().c_str() ); - return false; - } - - //-------------------------------------------------------------------------- - // Create the socket helper - //-------------------------------------------------------------------------- - XrdSys::IOEvents::Poller* poller = RegisterAndGetPoller( socket ); - - PollerHelper *helper = new PollerHelper(); - helper->callBack = new ::SocketCallBack( socket, handler ); - - if( poller ) - { - helper->channel = new XrdSys::IOEvents::Channel( poller, - socket->GetFD(), - helper->callBack ); - } - - handler->Initialize( this ); - pSocketMap[socket] = helper; - return true; - } - - //------------------------------------------------------------------------ - // Remove the socket - //------------------------------------------------------------------------ - bool PollerBuiltIn::RemoveSocket( Socket *socket ) - { - using namespace XrdSys::IOEvents; - Log *log = DefaultEnv::GetLog(); - - //-------------------------------------------------------------------------- - // Find the right socket - //-------------------------------------------------------------------------- - XrdSysMutexHelper scopedLock( pMutex ); - SocketMap::iterator it = pSocketMap.find( socket ); - if( it == pSocketMap.end() ) - return true; - - log->Debug( PollerMsg, "%s Removing socket from the poller", - socket->GetName().c_str() ); - - // unregister from the poller it's currently associated with - UnregisterFromPoller( socket ); - - //-------------------------------------------------------------------------- - // Remove the socket - //-------------------------------------------------------------------------- - PollerHelper *helper = (PollerHelper*)it->second; - - if( helper->channel ) - { - const char *errMsg; - bool status = helper->channel->Disable( Channel::allEvents, &errMsg ); - if( !status ) - { - log->Error( PollerMsg, "%s Unable to disable write notifications: %s", - socket->GetName().c_str(), errMsg ); - return false; - } - helper->channel->Delete(); - } - delete helper->callBack; - delete helper; - pSocketMap.erase( it ); - return true; - } - - //---------------------------------------------------------------------------- - // Notify the handler about read events - //---------------------------------------------------------------------------- - bool PollerBuiltIn::EnableReadNotification( Socket *socket, - bool notify, - uint16_t timeout ) - { - using namespace XrdSys::IOEvents; - Log *log = DefaultEnv::GetLog(); - - if( !socket ) - { - log->Error( PollerMsg, "Invalid socket, read events unavailable" ); - return false; - } - - //-------------------------------------------------------------------------- - // Check if the socket is registered - //-------------------------------------------------------------------------- - XrdSysMutexHelper scopedLock( pMutex ); - SocketMap::const_iterator it = pSocketMap.find( socket ); - if( it == pSocketMap.end() ) - { - log->Warning( PollerMsg, "%s Socket is not registered", - socket->GetName().c_str() ); - return false; - } - - PollerHelper *helper = (PollerHelper*)it->second; - XrdSys::IOEvents::Poller *poller = GetPoller( socket ); - - //-------------------------------------------------------------------------- - // Enable read notifications - //-------------------------------------------------------------------------- - if( notify ) - { - if( helper->readEnabled ) - return true; - helper->readTimeout = timeout; - - log->Dump( PollerMsg, "%s Enable read notifications, timeout: %d", - socket->GetName().c_str(), timeout ); - - if( poller ) - { - const char *errMsg; - bool status = helper->channel->Enable( Channel::readEvents, timeout, - &errMsg ); - if( !status ) - { - log->Error( PollerMsg, "%s Unable to enable read notifications: %s", - socket->GetName().c_str(), errMsg ); - return false; - } - } - helper->readEnabled = true; - } - - //-------------------------------------------------------------------------- - // Disable read notifications - //-------------------------------------------------------------------------- - else - { - if( !helper->readEnabled ) - return true; - - log->Dump( PollerMsg, "%s Disable read notifications", - socket->GetName().c_str() ); - - if( poller ) - { - const char *errMsg; - bool status = helper->channel->Disable( Channel::readEvents, &errMsg ); - if( !status ) - { - log->Error( PollerMsg, "%s Unable to disable read notifications: %s", - socket->GetName().c_str(), errMsg ); - return false; - } - } - helper->readEnabled = false; - } - return true; - } - - //---------------------------------------------------------------------------- - // Notify the handler about write events - //---------------------------------------------------------------------------- - bool PollerBuiltIn::EnableWriteNotification( Socket *socket, - bool notify, - uint16_t timeout ) - { - using namespace XrdSys::IOEvents; - Log *log = DefaultEnv::GetLog(); - - if( !socket ) - { - log->Error( PollerMsg, "Invalid socket, write events unavailable" ); - return false; - } - - //-------------------------------------------------------------------------- - // Check if the socket is registered - //-------------------------------------------------------------------------- - XrdSysMutexHelper scopedLock( pMutex ); - SocketMap::const_iterator it = pSocketMap.find( socket ); - if( it == pSocketMap.end() ) - { - log->Warning( PollerMsg, "%s Socket is not registered", - socket->GetName().c_str() ); - return false; - } - - PollerHelper *helper = (PollerHelper*)it->second; - XrdSys::IOEvents::Poller *poller = GetPoller( socket ); - - //-------------------------------------------------------------------------- - // Enable write notifications - //-------------------------------------------------------------------------- - if( notify ) - { - if( helper->writeEnabled ) - return true; - - helper->writeTimeout = timeout; - - log->Dump( PollerMsg, "%s Enable write notifications, timeout: %d", - socket->GetName().c_str(), timeout ); - - if( poller ) - { - const char *errMsg; - bool status = helper->channel->Enable( Channel::writeEvents, timeout, - &errMsg ); - if( !status ) - { - log->Error( PollerMsg, "%s Unable to enable write notifications: %s", - socket->GetName().c_str(), errMsg ); - return false; - } - } - helper->writeEnabled = true; - } - - //-------------------------------------------------------------------------- - // Disable read notifications - //-------------------------------------------------------------------------- - else - { - if( !helper->writeEnabled ) - return true; - - log->Dump( PollerMsg, "%s Disable write notifications", - socket->GetName().c_str() ); - if( poller ) - { - const char *errMsg; - bool status = helper->channel->Disable( Channel::writeEvents, &errMsg ); - if( !status ) - { - log->Error( PollerMsg, "%s Unable to disable write notifications: %s", - socket->GetName().c_str(), errMsg ); - return false; - } - } - helper->writeEnabled = false; - } - return true; - } - - //---------------------------------------------------------------------------- - // Check whether the socket is registered with the poller - //---------------------------------------------------------------------------- - bool PollerBuiltIn::IsRegistered( Socket *socket ) - { - XrdSysMutexHelper scopedLock( pMutex ); - SocketMap::iterator it = pSocketMap.find( socket ); - return it != pSocketMap.end(); - } - - //---------------------------------------------------------------------------- - // Return poller threads in round-robin fashion - //---------------------------------------------------------------------------- - XrdSys::IOEvents::Poller* PollerBuiltIn::GetNextPoller() - { - if( pPollerPool.empty() ) return 0; - - PollerPool::iterator ret = pNext; - ++pNext; - if( pNext == pPollerPool.end() ) - pNext = pPollerPool.begin(); - return *ret; - } - - //---------------------------------------------------------------------------- - // Return the poller associated with the respective channel - //---------------------------------------------------------------------------- - XrdSys::IOEvents::Poller* PollerBuiltIn::RegisterAndGetPoller(const Socket * socket) - { - PollerMap::iterator itr = pPollerMap.find( socket->GetChannelID() ); - if( itr == pPollerMap.end() ) - { - XrdSys::IOEvents::Poller* poller = GetNextPoller(); - if( poller ) - pPollerMap[socket->GetChannelID()] = std::make_pair( poller, size_t( 1 ) ); - return poller; - } - - ++( itr->second.second ); - return itr->second.first; - } - - void PollerBuiltIn::UnregisterFromPoller( const Socket *socket ) - { - PollerMap::iterator itr = pPollerMap.find( socket->GetChannelID() ); - if( itr == pPollerMap.end() ) return; - --itr->second.second; - if( itr->second.second == 0 ) - pPollerMap.erase( itr ); - - } - - XrdSys::IOEvents::Poller* PollerBuiltIn::GetPoller(const Socket * socket) - { - PollerMap::iterator itr = pPollerMap.find( socket->GetChannelID() ); - if( itr == pPollerMap.end() ) return 0; - return itr->second.first; - } - - //---------------------------------------------------------------------------- - // Get the initial value for pNbPoller - //---------------------------------------------------------------------------- - int PollerBuiltIn::GetNbPollerInit() - { - Env * env = DefaultEnv::GetEnv(); - int ret = XrdCl::DefaultParallelEvtLoop; - env->GetInt("ParallelEvtLoop", ret); - return ret; - } -} diff --git a/src/XrdCl/XrdClPollerBuiltIn.hh b/src/XrdCl/XrdClPollerBuiltIn.hh deleted file mode 100644 index 71c6e50df06..00000000000 --- a/src/XrdCl/XrdClPollerBuiltIn.hh +++ /dev/null @@ -1,164 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_POLLER_BUILT_IN_HH__ -#define __XRD_CL_POLLER_BUILT_IN_HH__ - -#include "XrdSys/XrdSysPthread.hh" -#include "XrdCl/XrdClPoller.hh" -#include -#include - - -namespace XrdSys { namespace IOEvents -{ - class Poller; -}; }; - -namespace XrdCl -{ - class AnyObject; - - //---------------------------------------------------------------------------- - //! A poller implementation using the build-in XRootD poller - //---------------------------------------------------------------------------- - class PollerBuiltIn: public Poller - { - public: - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - PollerBuiltIn() : pNbPoller( GetNbPollerInit() ){} - - ~PollerBuiltIn() {} - - //------------------------------------------------------------------------ - //! Initialize the poller - //------------------------------------------------------------------------ - virtual bool Initialize(); - - //------------------------------------------------------------------------ - //! Finalize the poller - //------------------------------------------------------------------------ - virtual bool Finalize(); - - //------------------------------------------------------------------------ - //! Start polling - //------------------------------------------------------------------------ - virtual bool Start(); - - //------------------------------------------------------------------------ - //! Stop polling - //------------------------------------------------------------------------ - virtual bool Stop(); - - //------------------------------------------------------------------------ - //! Add socket to the polling loop - //! - //! @param socket the socket - //! @param handler object handling the events - //------------------------------------------------------------------------ - virtual bool AddSocket( Socket *socket, - SocketHandler *handler ); - - - //------------------------------------------------------------------------ - //! Remove the socket - //------------------------------------------------------------------------ - virtual bool RemoveSocket( Socket *socket ); - - //------------------------------------------------------------------------ - //! Notify the handler about read events - //! - //! @param socket the socket - //! @param notify specify if the handler should be notified - //! @param timeout if no read event occurred after this time a timeout - //! event will be generated - //------------------------------------------------------------------------ - virtual bool EnableReadNotification( Socket *socket, - bool notify, - uint16_t timeout = 60 ); - - //------------------------------------------------------------------------ - //! Notify the handler about write events - //! - //! @param socket the socket - //! @param notify specify if the handler should be notified - //! @param timeout if no write event occurred after this time a timeout - //! event will be generated - //------------------------------------------------------------------------ - virtual bool EnableWriteNotification( Socket *socket, - bool notify, - uint16_t timeout = 60); - - //------------------------------------------------------------------------ - //! Check whether the socket is registered with the poller - //------------------------------------------------------------------------ - virtual bool IsRegistered( Socket *socket ); - - //------------------------------------------------------------------------ - //! Is the event loop running? - //------------------------------------------------------------------------ - virtual bool IsRunning() const - { - return !pPollerPool.empty(); - } - - private: - - //------------------------------------------------------------------------ - //! Goes over poller threads in round robin fashion - //------------------------------------------------------------------------ - XrdSys::IOEvents::Poller* GetNextPoller(); - - //------------------------------------------------------------------------ - //! Registers given socket as a poller user and returns the poller object - //------------------------------------------------------------------------ - XrdSys::IOEvents::Poller* RegisterAndGetPoller(const Socket *socket); - - //------------------------------------------------------------------------ - //! Unregisters given socket from poller object - //------------------------------------------------------------------------ - void UnregisterFromPoller( const Socket *socket); - - //------------------------------------------------------------------------ - //! Returns the poller object associated with the given socket - //------------------------------------------------------------------------ - XrdSys::IOEvents::Poller* GetPoller(const Socket *socket); - - //------------------------------------------------------------------------ - //! Gets the initial value for 'pNbPoller' - //------------------------------------------------------------------------ - static int GetNbPollerInit(); - - // associates channel ID to a pair: poller and count (how many sockets where mapped to this poller) - typedef std::map > PollerMap; - - typedef std::map SocketMap; - typedef std::vector PollerPool; - - SocketMap pSocketMap; - PollerMap pPollerMap; - PollerPool pPollerPool; - PollerPool::iterator pNext; - const int pNbPoller; - XrdSysMutex pMutex; - }; -} - -#endif // __XRD_CL_POLLER_BUILT_IN_HH__ diff --git a/src/XrdCl/XrdClPollerFactory.cc b/src/XrdCl/XrdClPollerFactory.cc deleted file mode 100644 index c6404659576..00000000000 --- a/src/XrdCl/XrdClPollerFactory.cc +++ /dev/null @@ -1,97 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#include "XrdCl/XrdClPollerFactory.hh" -#include "XrdCl/XrdClPollerBuiltIn.hh" -#include "XrdCl/XrdClConstants.hh" -#include "XrdCl/XrdClLog.hh" -#include "XrdCl/XrdClUtils.hh" -#include "XrdCl/XrdClDefaultEnv.hh" -#include -#include - -//------------------------------------------------------------------------------ -// Poller creators -//------------------------------------------------------------------------------ -namespace -{ - XrdCl::Poller *createBuiltIn() - { - return new XrdCl::PollerBuiltIn(); - } -}; - -namespace XrdCl -{ - //------------------------------------------------------------------------ - // Create a poller object, try in order of preference - //------------------------------------------------------------------------ - Poller *PollerFactory::CreatePoller( const std::string &preference ) - { - Log *log = DefaultEnv::GetLog(); - - //-------------------------------------------------------------------------- - // Create a list of known pollers - //-------------------------------------------------------------------------- - typedef std::map PollerMap; - PollerMap pollerMap; - pollerMap["built-in"] = createBuiltIn; - - //-------------------------------------------------------------------------- - // Print the list of available pollers - //-------------------------------------------------------------------------- - PollerMap::iterator it; - std::string available; - for( it = pollerMap.begin(); it != pollerMap.end(); ++it ) - { - available += it->first; available += ", "; - } - if( !available.empty() ) - available.erase( available.length()-2, 2 ); - log->Debug( PollerMsg, "Available pollers: %s", available.c_str() ); - - //-------------------------------------------------------------------------- - // Try to create a poller - //-------------------------------------------------------------------------- - if( preference.empty() ) - { - log->Error( PollerMsg, "Poller preference list is empty" ); - return 0; - } - log->Debug( PollerMsg, "Attempting to create a poller according to " - "preference: %s", preference.c_str() ); - - std::vector prefs; - std::vector::iterator itP; - Utils::splitString( prefs, preference, "," ); - for( itP = prefs.begin(); itP != prefs.end(); ++itP ) - { - it = pollerMap.find( *itP ); - if( it == pollerMap.end() ) - { - log->Debug( PollerMsg, "Unable to create poller: %s", - itP->c_str() ); - continue; - } - log->Debug( PollerMsg, "Creating poller: %s", itP->c_str() ); - return (*it->second)(); - } - - return 0; - } -} diff --git a/src/XrdCl/XrdClPollerFactory.hh b/src/XrdCl/XrdClPollerFactory.hh deleted file mode 100644 index 18758f786c3..00000000000 --- a/src/XrdCl/XrdClPollerFactory.hh +++ /dev/null @@ -1,46 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_POLLER_FACTORY_HH__ -#define __XRD_CL_POLLER_FACTORY_HH__ - -#include "XrdCl/XrdClPoller.hh" - -namespace XrdCl -{ - - //---------------------------------------------------------------------------- - //! Helper for creating poller objects - //---------------------------------------------------------------------------- - class PollerFactory - { - public: - //------------------------------------------------------------------------ - //! Create a poller object, try in order of preference, if none of the - //! poller types is known then return 0 - //! - //! @param preference comma separated list of poller types in order of - //! preference - //! @return poller object or 0 if non of the poller types - //! is known - //------------------------------------------------------------------------ - static Poller *CreatePoller( const std::string &preference ); - }; -} - -#endif // __XRD_CL_POLLER_FACTORY_HH__ diff --git a/src/XrdCl/XrdClPostMaster.cc b/src/XrdCl/XrdClPostMaster.cc deleted file mode 100644 index 3c07114e7bb..00000000000 --- a/src/XrdCl/XrdClPostMaster.cc +++ /dev/null @@ -1,312 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#include "XrdCl/XrdClPostMaster.hh" -#include "XrdCl/XrdClPollerFactory.hh" -#include "XrdCl/XrdClXRootDTransport.hh" -#include "XrdCl/XrdClMessage.hh" -#include "XrdCl/XrdClConstants.hh" -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdCl/XrdClPoller.hh" -#include "XrdCl/XrdClTaskManager.hh" -#include "XrdCl/XrdClJobManager.hh" -#include "XrdCl/XrdClTransportManager.hh" -#include "XrdCl/XrdClChannel.hh" -#include "XrdCl/XrdClConstants.hh" -#include "XrdCl/XrdClLog.hh" -#include "XrdCl/XrdClRedirectorRegistry.hh" - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - // Constructor - //---------------------------------------------------------------------------- - PostMaster::PostMaster(): - pPoller( 0 ), pInitialized( false ) - { - Env *env = DefaultEnv::GetEnv(); - int workerThreads = DefaultWorkerThreads; - env->GetInt( "WorkerThreads", workerThreads ); - - pTaskManager = new TaskManager(); - pJobManager = new JobManager(workerThreads); - } - - //---------------------------------------------------------------------------- - // Destructor - //---------------------------------------------------------------------------- - PostMaster::~PostMaster() - { - delete pPoller; - delete pTaskManager; - delete pJobManager; - } - - //---------------------------------------------------------------------------- - // Initializer - //---------------------------------------------------------------------------- - bool PostMaster::Initialize() - { - Env *env = DefaultEnv::GetEnv(); - std::string pollerPref = DefaultPollerPreference; - env->GetString( "PollerPreference", pollerPref ); - - pPoller = PollerFactory::CreatePoller( pollerPref ); - - if( !pPoller ) - return false; - - bool st = pPoller->Initialize(); - - if( !st ) - { - delete pPoller; - return false; - } - - pJobManager->Initialize(); - pInitialized = true; - return true; - } - - //---------------------------------------------------------------------------- - // Finalizer - //---------------------------------------------------------------------------- - bool PostMaster::Finalize() - { - //-------------------------------------------------------------------------- - // Clean up the channels - //-------------------------------------------------------------------------- - if( !pInitialized ) - return true; - - pInitialized = false; - pJobManager->Finalize(); - ChannelMap::iterator it; - - for( it = pChannelMap.begin(); it != pChannelMap.end(); ++it ) - delete it->second; - - pChannelMap.clear(); - return pPoller->Finalize(); - } - - //---------------------------------------------------------------------------- - // Start the post master - //---------------------------------------------------------------------------- - bool PostMaster::Start() - { - if( !pInitialized ) - return false; - - if( !pPoller->Start() ) - return false; - - if( !pTaskManager->Start() ) - { - pPoller->Stop(); - return false; - } - - if( !pJobManager->Start() ) - { - pPoller->Stop(); - pTaskManager->Stop(); - return false; - } - - return true; - } - - //---------------------------------------------------------------------------- - // Stop the postmaster - //---------------------------------------------------------------------------- - bool PostMaster::Stop() - { - if( !pInitialized ) - return true; - - if( !pJobManager->Stop() ) - return false; - if( !pTaskManager->Stop() ) - return false; - if( !pPoller->Stop() ) - return false; - return true; - } - - //---------------------------------------------------------------------------- - // Reinitialize after fork - //---------------------------------------------------------------------------- - bool PostMaster::Reinitialize() - { - return true; - } - - //---------------------------------------------------------------------------- - // Send a message synchronously - //---------------------------------------------------------------------------- - Status PostMaster::Send( const URL &url, - Message *msg, - bool stateful, - time_t expires ) - { - Channel *channel = GetChannel( url ); - - if( !channel ) - return Status( stError, errNotSupported ); - - return channel->Send( msg, stateful, expires ); - } - - //---------------------------------------------------------------------------- - // Send the message asynchronously - //---------------------------------------------------------------------------- - Status PostMaster::Send( const URL &url, - Message *msg, - OutgoingMsgHandler *handler, - bool stateful, - time_t expires ) - { - Channel *channel = GetChannel( url ); - - if( !channel ) - return Status( stError, errNotSupported ); - - return channel->Send( msg, handler, stateful, expires ); - } - - Status PostMaster::Redirect( const URL &url, - Message *msg, - OutgoingMsgHandler *outHandler, - IncomingMsgHandler *inHandler ) - { - RedirectorRegistry ®istry = RedirectorRegistry::Instance(); - VirtualRedirector *redirector = registry.Get( url ); - if( !redirector ) - return Status( stError, errInvalidOp ); - outHandler->OnStatusReady( msg, Status() ); - return redirector->HandleRequest( msg, inHandler ); - } - - //---------------------------------------------------------------------------- - // Synchronously receive a message - //---------------------------------------------------------------------------- - Status PostMaster::Receive( const URL &url, - Message *&msg, - MessageFilter *filter, - time_t expires ) - { - Channel *channel = GetChannel( url ); - - if( !channel ) - return Status( stError, errNotSupported ); - - return channel->Receive( msg, filter, expires ); - } - - //---------------------------------------------------------------------------- - // Listen to incoming messages - //---------------------------------------------------------------------------- - Status PostMaster::Receive( const URL &url, - IncomingMsgHandler *handler, - time_t expires ) - { - Channel *channel = GetChannel( url ); - - if( !channel ) - return Status( stError, errNotSupported ); - - return channel->Receive( handler, expires ); - } - - //---------------------------------------------------------------------------- - // Query the transport handler - //---------------------------------------------------------------------------- - Status PostMaster::QueryTransport( const URL &url, - uint16_t query, - AnyObject &result ) - { - Channel *channel = GetChannel( url ); - - if( !channel ) - return Status( stError, errNotSupported ); - - return channel->QueryTransport( query, result ); - } - - //---------------------------------------------------------------------------- - // Register channel event handler - //---------------------------------------------------------------------------- - Status PostMaster::RegisterEventHandler( const URL &url, - ChannelEventHandler *handler ) - { - Channel *channel = GetChannel( url ); - - if( !channel ) - return Status( stError, errNotSupported ); - - channel->RegisterEventHandler( handler ); - return Status(); - } - - //---------------------------------------------------------------------------- - // Remove a channel event handler - //---------------------------------------------------------------------------- - Status PostMaster::RemoveEventHandler( const URL &url, - ChannelEventHandler *handler ) - { - Channel *channel = GetChannel( url ); - - if( !channel ) - return Status( stError, errNotSupported ); - - channel->RemoveEventHandler( handler ); - return Status(); - } - - //---------------------------------------------------------------------------- - // Get the channel - //---------------------------------------------------------------------------- - Channel *PostMaster::GetChannel( const URL &url ) - { - XrdSysMutexHelper scopedLock( pChannelMapMutex ); - Channel *channel = 0; - ChannelMap::iterator it = pChannelMap.find( url.GetHostId() ); - - if( it == pChannelMap.end() ) - { - TransportManager *trManager = DefaultEnv::GetTransportManager(); - TransportHandler *trHandler = trManager->GetHandler( url.GetProtocol() ); - - if( !trHandler ) - { - Log *log = DefaultEnv::GetLog(); - log->Error( PostMasterMsg, "Unable to get transport handler for %s " - "protocol", url.GetProtocol().c_str() ); - return 0; - } - - channel = new Channel( url, pPoller, trHandler, pTaskManager, pJobManager ); - pChannelMap[url.GetHostId()] = channel; - } - else - channel = it->second; - return channel; - } -} diff --git a/src/XrdCl/XrdClPostMaster.hh b/src/XrdCl/XrdClPostMaster.hh deleted file mode 100644 index 6f5d0f1ee1e..00000000000 --- a/src/XrdCl/XrdClPostMaster.hh +++ /dev/null @@ -1,215 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_POST_MASTER_HH__ -#define __XRD_CL_POST_MASTER_HH__ - -#include -#include -#include - -#include "XrdCl/XrdClStatus.hh" -#include "XrdCl/XrdClURL.hh" -#include "XrdCl/XrdClPostMasterInterfaces.hh" - -#include "XrdSys/XrdSysPthread.hh" - -namespace XrdCl -{ - class Poller; - class TaskManager; - class Channel; - class JobManager; - - //---------------------------------------------------------------------------- - //! A hub for dispatching and receiving messages - //---------------------------------------------------------------------------- - class PostMaster - { - public: - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - PostMaster(); - - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - virtual ~PostMaster(); - - //------------------------------------------------------------------------ - //! Initializer - //------------------------------------------------------------------------ - bool Initialize(); - - //------------------------------------------------------------------------ - //! Finalizer - //------------------------------------------------------------------------ - bool Finalize(); - - //------------------------------------------------------------------------ - //! Start the post master - //------------------------------------------------------------------------ - bool Start(); - - //------------------------------------------------------------------------ - //! Stop the postmaster - //------------------------------------------------------------------------ - bool Stop(); - - //------------------------------------------------------------------------ - //! Reinitialize after fork - //------------------------------------------------------------------------ - bool Reinitialize(); - - //------------------------------------------------------------------------ - //! Send a message synchronously - synchronously means that - //! it will block until the message is written to a socket - //! - //! DEADLOCK WARNING: no lock should be taken while calling this method - //! that are used in the callback as well. - //! - //! @param url recipient of the message - //! @param msg message to be sent - //! @param stateful physical stream disconnection causes an error - //! @param expires unix timestamp after which a failure should be - //! reported if sending was unsuccessful - //! @return success if the message has been pushed through the wire, - //! failure otherwise - //------------------------------------------------------------------------ - Status Send( const URL &url, - Message *msg, - bool stateful, - time_t expires ); - - //------------------------------------------------------------------------ - //! Send the message asynchronously - the message is inserted into the - //! send queue and a listener is called when the message is succesfsully - //! pushed through the wire or when the timeout elapses - //! - //! DEADLOCK WARNING: no lock should be taken while calling this method - //! that are used in the callback as well. - //! - //! @param url recipient of the message - //! @param msg message to be sent - //! @param expires unix timestamp after which a failure is reported - //! to the handler - //! @param handler handler will be notified about the status - //! @param stateful physical stream disconnection causes an error - //! @return success if the message was successfully inserted - //! into the send queues, failure otherwise - //------------------------------------------------------------------------ - Status Send( const URL &url, - Message *msg, - OutgoingMsgHandler *handler, - bool stateful, - time_t expires ); - - //------------------------------------------------------------------------ - //! - //------------------------------------------------------------------------ - Status Redirect( const URL &url, - Message *msg, - OutgoingMsgHandler *outHandler, - IncomingMsgHandler *handler); - - //------------------------------------------------------------------------ - //! Synchronously receive a message - blocks until a message matching - //! a filter is found in the incoming queue or the timeout passes - //! - //! @param url sender of the message - //! @param msg reference to a message pointer, the pointer will - //! point to the received message - //! @param filter filter object defining what to look for - //! @param expires expiration timestamp - //! @return success when the message has been received - //! successfully, failure otherwise - //------------------------------------------------------------------------ - Status Receive( const URL &url, - Message *&msg, - MessageFilter *filter, - time_t expires ); - - //------------------------------------------------------------------------ - //! Listen to incoming messages, the listener is notified when a new - //! message arrives and when the timeout passes - //! - //! @param url sender of the message - //! @param handler handler to be notified about new messages - //! @param expires expiration timestamp - //! @return success when the listener has been inserted correctly - //------------------------------------------------------------------------ - Status Receive( const URL &url, - IncomingMsgHandler *handler, - time_t expires ); - - //------------------------------------------------------------------------ - //! Query the transport handler for a given URL - //! - //! @param url the channel to be queried - //! @param query the query as defined in the TransportQuery struct or - //! others that may be recognized by the protocol transport - //! @param result the result of the query - //! @return status of the query - //------------------------------------------------------------------------ - Status QueryTransport( const URL &url, - uint16_t query, - AnyObject &result ); - - //------------------------------------------------------------------------ - //! Register channel event handler - //------------------------------------------------------------------------ - Status RegisterEventHandler( const URL &url, - ChannelEventHandler *handler ); - - //------------------------------------------------------------------------ - //! Remove a channel event handler - //------------------------------------------------------------------------ - Status RemoveEventHandler( const URL &url, - ChannelEventHandler *handler ); - - //------------------------------------------------------------------------ - //! Get the task manager object user by the post master - //------------------------------------------------------------------------ - TaskManager *GetTaskManager() - { - return pTaskManager; - } - - //------------------------------------------------------------------------ - //! Get the job manager object user by the post master - //------------------------------------------------------------------------ - JobManager *GetJobManager() - { - return pJobManager; - } - - private: - Channel *GetChannel( const URL &url ); - - typedef std::map ChannelMap; - Poller *pPoller; - TaskManager *pTaskManager; - ChannelMap pChannelMap; - XrdSysMutex pChannelMapMutex; - bool pInitialized; - JobManager *pJobManager; - }; -} - -#endif // __XRD_CL_POST_MASTER_HH__ diff --git a/src/XrdCl/XrdClPostMasterInterfaces.hh b/src/XrdCl/XrdClPostMasterInterfaces.hh deleted file mode 100644 index 8bf9f0e2fde..00000000000 --- a/src/XrdCl/XrdClPostMasterInterfaces.hh +++ /dev/null @@ -1,451 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2014 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_POST_MASTER_INTERFACES_HH__ -#define __XRD_CL_POST_MASTER_INTERFACES_HH__ - -#include -#include - -#include "XrdCl/XrdClStatus.hh" -#include "XrdCl/XrdClAnyObject.hh" -#include "XrdCl/XrdClURL.hh" - -class XrdNetAddr; - -namespace XrdCl -{ - class Channel; - class Message; - class URL; - - //---------------------------------------------------------------------------- - //! Message filter - //---------------------------------------------------------------------------- - class MessageFilter - { - public: - virtual ~MessageFilter() {} - - //------------------------------------------------------------------------ - //! Examine the message and return true if the message should be picked - //! up (usually removed from the queue and to the caller) - //------------------------------------------------------------------------ - virtual bool Filter( const Message *msg ) = 0; - - //------------------------------------------------------------------------ - //! Get sid of the filter - //! - //! @return filter sid if exists, otherwise 0 - //------------------------------------------------------------------------ - virtual uint16_t GetSid() const = 0; - }; - - //---------------------------------------------------------------------------- - //! Message handler - //---------------------------------------------------------------------------- - class IncomingMsgHandler - { - public: - //------------------------------------------------------------------------ - //! Actions to be taken after a message is processed by the handler - //------------------------------------------------------------------------ - enum Action - { - Take = 0x0001, //!< Take ownership over the message - Ignore = 0x0002, //!< Ignore the message - RemoveHandler = 0x0004, //!< Remove the handler from the notification - //!< list - Raw = 0x0008, //!< the handler is interested in reading - //!< the message body directly from the - //!< socket - NoProcess = 0x0010 //!< don't call the processing callback - //!< even if the message belongs to this - //!< handler - }; - - //------------------------------------------------------------------------ - //! Events that may have occurred to the stream - //------------------------------------------------------------------------ - enum StreamEvent - { - Ready = 1, //!< The stream has become connected - Broken = 2, //!< The stream is broken - Timeout = 3, //!< The declared timeout has occurred - FatalError = 4 //!< Stream has been broken and won't be recovered - }; - - //------------------------------------------------------------------------ - //! Event types that the message handler may receive - //------------------------------------------------------------------------ - - virtual ~IncomingMsgHandler() {} - - //------------------------------------------------------------------------ - //! Examine an incoming message, and decide on the action to be taken - //! - //! @param msg the message, may be zero if receive failed - //! @return action type that needs to be take wrt the message and - //! the handler - //------------------------------------------------------------------------ - virtual uint16_t Examine( Message *msg ) = 0; - - //------------------------------------------------------------------------ - //! Get handler sid - //! - //! return sid of the corresponding request, otherwise 0 - //------------------------------------------------------------------------ - virtual uint16_t GetSid() const = 0; - - //------------------------------------------------------------------------ - //! Process the message if it was "taken" by the examine action - //! - //! @param msg the message to be processed - //------------------------------------------------------------------------ - virtual void Process( Message *msg ) { (void)msg; }; - - //------------------------------------------------------------------------ - //! Read message body directly from a socket - called if Examine returns - //! Raw flag - only socket related errors may be returned here - //! - //! @param msg the corresponding message header - //! @param socket the socket to read from - //! @param bytesRead number of bytes read by the method - //! @return stOK & suDone if the whole body has been processed - //! stOK & suRetry if more data is needed - //! stError on failure - //------------------------------------------------------------------------ - virtual Status ReadMessageBody( Message *msg, - int socket, - uint32_t &bytesRead ) - { - (void)msg; (void)socket; (void)bytesRead; - return Status( stOK, suDone ); - }; - - //------------------------------------------------------------------------ - //! Handle an event other that a message arrival - //! - //! @param event type of the event - //! @param streamNum stream concerned - //! @param status status info - //! @return Action::RemoveHandler or 0 - //------------------------------------------------------------------------ - virtual uint8_t OnStreamEvent( StreamEvent event, - uint16_t streamNum, - Status status ) - { - (void)event; (void)streamNum; (void)status; - return 0; - }; - }; - - //---------------------------------------------------------------------------- - //! Message status handler - //---------------------------------------------------------------------------- - class OutgoingMsgHandler - { - public: - virtual ~OutgoingMsgHandler() {} - - //------------------------------------------------------------------------ - //! The requested action has been performed and the status is available - //------------------------------------------------------------------------ - virtual void OnStatusReady( const Message *message, - Status status ) = 0; - - //------------------------------------------------------------------------ - //! Called just before the message is going to be sent through - //! a valid connection, so that the user can still make some - //! modifications that were impossible before (ie. protocol version - //! dependent adjustments) - //! - //! @param msg message concerned - //! @param streamNum number of the stream the message will go through - //------------------------------------------------------------------------ - virtual void OnReadyToSend( Message *msg, uint16_t streamNum ) - { - (void)msg; (void)streamNum; - }; - - //------------------------------------------------------------------------ - //! Determines whether the handler wants to write some data directly - //! to the socket after the message (or message header) has been sent, - //! WriteMessageBody will be called - //------------------------------------------------------------------------ - virtual bool IsRaw() const { return false; } - - //------------------------------------------------------------------------ - //! Write message body directly to a socket - called if IsRaw returns - //! true - only socket related errors may be returned here - //! - //! @param socket the socket to read from - //! @param bytesRead number of bytes read by the method - //! @return stOK & suDone if the whole body has been processed - //! stOK & suRetry if more data needs to be written - //! stError on failure - //------------------------------------------------------------------------ - virtual Status WriteMessageBody( int socket, - uint32_t &bytesRead ) - { - (void)socket; (void)bytesRead; - return Status(); - } - }; - - //---------------------------------------------------------------------------- - //! Channel event handler - //---------------------------------------------------------------------------- - class ChannelEventHandler - { - public: - //------------------------------------------------------------------------ - //! Events that may have occurred to the channel - //------------------------------------------------------------------------ - enum ChannelEvent - { - StreamReady = 1, //!< The stream has become connected - StreamBroken = 2, //!< The stream is broken - FatalError = 4 //!< Stream has been broken and won't be recovered - }; - - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - virtual ~ChannelEventHandler() {}; - - //------------------------------------------------------------------------ - //! Event callback - //! - //! @param event the event that has occurred - //! @param stream the stream concerned - //! @param status the status info - //! @return true if the handler should be kept - //! false if it should be removed from further consideration - //------------------------------------------------------------------------ - virtual bool OnChannelEvent( ChannelEvent event, - Status status, - uint16_t stream ) = 0; - }; - - //---------------------------------------------------------------------------- - //! Data structure that carries the handshake information - //---------------------------------------------------------------------------- - - struct HandShakeData - { - //-------------------------------------------------------------------------- - //! Constructor - //-------------------------------------------------------------------------- - HandShakeData( const URL *addr, uint16_t stream, uint16_t subStream ): - step(0), out(0), in(0), url(addr), streamId(stream), - subStreamId( subStream ), startTime( time(0) ), serverAddr(0) - {} - uint16_t step; //!< Handshake step - Message *out; //!< Message to be sent out - Message *in; //!< Message that has been received - const URL *url; //!< Destination URL - uint16_t streamId; //!< Stream number - uint16_t subStreamId; //!< Sub-stream id - time_t startTime; //!< Timestamp of when the handshake started - const - XrdNetAddr *serverAddr; //!< Server address - std::string clientName; //!< Client name (an IPv6 representation) - std::string streamName; //!< Name of the stream - }; - - //---------------------------------------------------------------------------- - //! Path ID - a pair of integers describing the up and down stream - //! for given interaction - //---------------------------------------------------------------------------- - struct PathID - { - PathID( uint16_t u = 0, uint16_t d = 0 ): up(u), down(d) {} - uint16_t up; - uint16_t down; - }; - - //---------------------------------------------------------------------------- - //! Transport query definitions - //! The transports may support other queries, with ids > 1000 - //---------------------------------------------------------------------------- - struct TransportQuery - { - static const uint16_t Name = 1; //!< Transport name, returns const char * - static const uint16_t Auth = 2; //!< Transport name, returns std::string * - }; - - //---------------------------------------------------------------------------- - //! Perform the handshake and the authentication for each physical stream - //---------------------------------------------------------------------------- - class TransportHandler - { - public: - - //------------------------------------------------------------------------ - //! Stream actions that may be triggered by incoming control messages - //------------------------------------------------------------------------ - enum StreamAction - { - NoAction = 0x0000, //!< No action - DigestMsg = 0x0001, //!< Digest the incoming message so that it won't - //!< be passed to the user handlers - AbortStream = 0x0002, //!< Disconnect, abort all the on-going - //!< operations and mark the stream as - //!< permanently broken [not yet implemented] - CloseStream = 0x0004, //!< Disconnect and attempt reconnection later - //!< [not yet implemented] - ResumeStream = 0x0008, //!< Resume sending requests - //!< [not yet implemented] - HoldStream = 0x0010, //!< Stop sending requests [not yet implemented] - RequestClose = 0x0020 //!< Send a close request - }; - - - virtual ~TransportHandler() {} - - //------------------------------------------------------------------------ - //! Read a message header from the socket, the socket is non-blocking, - //! so if there is not enough data the function should return errRetry - //! in which case it will be called again when more data arrives, with - //! the data previously read stored in the message buffer - //! - //! @param message the message buffer - //! @param socket the socket - //! @return stOK & suDone if the whole message has been processed - //! stOK & suRetry if more data is needed - //! stError on failure - //------------------------------------------------------------------------ - virtual Status GetHeader( Message *message, int socket ) = 0; - - //------------------------------------------------------------------------ - //! Read the message body from the socket, the socket is non-blocking, - //! the method may be called multiple times - see GetHeader for details - //! - //! @param message the message buffer containing the header - //! @param socket the socket - //! @return stOK & suDone if the whole message has been processed - //! stOK & suRetry if more data is needed - //! stError on failure - //------------------------------------------------------------------------ - virtual Status GetBody( Message *message, int socket ) = 0; - - //------------------------------------------------------------------------ - //! Initialize channel - //------------------------------------------------------------------------ - virtual void InitializeChannel( AnyObject &channelData ) = 0; - - //------------------------------------------------------------------------ - //! Finalize channel - //------------------------------------------------------------------------ - virtual void FinalizeChannel( AnyObject &channelData ) = 0; - - //------------------------------------------------------------------------ - //! HandHake - //------------------------------------------------------------------------ - virtual Status HandShake( HandShakeData *handShakeData, - AnyObject &channelData ) = 0; - - //------------------------------------------------------------------------ - //! Check if the stream should be disconnected - //------------------------------------------------------------------------ - virtual bool IsStreamTTLElapsed( time_t inactiveTime, - uint16_t streamId, - AnyObject &channelData ) = 0; - - //------------------------------------------------------------------------ - //! Check the stream is broken - ie. TCP connection got broken and - //! went undetected by the TCP stack - //------------------------------------------------------------------------ - virtual Status IsStreamBroken( time_t inactiveTime, - uint16_t streamId, - AnyObject &channelData ) = 0; - - //------------------------------------------------------------------------ - //! Return the ID for the up stream this message should be sent by - //! and the down stream which the answer should be expected at. - //! Modify the message itself if necessary. - //! If hint is non-zero then the message should be modified such that - //! the answer will be returned via the hinted stream. - //------------------------------------------------------------------------ - virtual PathID Multiplex( Message *msg, - AnyObject &channelData, - PathID *hint = 0 ) = 0; - - //------------------------------------------------------------------------ - //! Return the ID for the up substream this message should be sent by - //! and the down substream which the answer should be expected at. - //! Modify the message itself if necessary. - //! If hint is non-zero then the message should be modified such that - //! the answer will be returned via the hinted stream. - //------------------------------------------------------------------------ - virtual PathID MultiplexSubStream( Message *msg, - uint16_t streamId, - AnyObject &channelData, - PathID *hint = 0 ) = 0; - - //------------------------------------------------------------------------ - //! Return a number of streams that should be created - //------------------------------------------------------------------------ - virtual uint16_t StreamNumber( AnyObject &channelData ) = 0; - - //------------------------------------------------------------------------ - //! Return a number of substreams per stream that should be created - //------------------------------------------------------------------------ - virtual uint16_t SubStreamNumber( AnyObject &channelData ) = 0; - - //------------------------------------------------------------------------ - //! The stream has been disconnected, do the cleanups - //------------------------------------------------------------------------ - virtual void Disconnect( AnyObject &channelData, - uint16_t streamId, - uint16_t subStreamId ) = 0; - - //------------------------------------------------------------------------ - //! Query the channel - //------------------------------------------------------------------------ - virtual Status Query( uint16_t query, - AnyObject &result, - AnyObject &channelData ) = 0; - - //------------------------------------------------------------------------ - //! Check if the message invokes a stream action - //------------------------------------------------------------------------ - virtual uint32_t MessageReceived( Message *msg, - uint16_t streamId, - uint16_t subStream, - AnyObject &channelData ) = 0; - - //------------------------------------------------------------------------ - //! Notify the transport about a message having been sent - //------------------------------------------------------------------------ - virtual void MessageSent( Message *msg, - uint16_t streamId, - uint16_t subStream, - uint32_t bytesSent, - AnyObject &channelData ) = 0; - }; -} - -#endif // __XRD_CL_POST_MASTER_INTERFACES_HH__ diff --git a/src/XrdCl/XrdClPropertyList.hh b/src/XrdCl/XrdClPropertyList.hh deleted file mode 100644 index f9ce328629a..00000000000 --- a/src/XrdCl/XrdClPropertyList.hh +++ /dev/null @@ -1,317 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2014 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//----------------------------------------------------------------------------- -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_PROPERTY_LIST_HH__ -#define __XRD_CL_PROPERTY_LIST_HH__ - -#include -#include -#include -#include - -#include "XrdCl/XrdClXRootDResponses.hh" - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - //! A key-value pair map storing both keys and values as strings - //---------------------------------------------------------------------------- - class PropertyList - { - public: - typedef std::map PropertyMap; - - //------------------------------------------------------------------------ - //! Associate a value with a key - //! - //! @param name must not contain spaces - //! @param value needs to be convertible to std::string - //------------------------------------------------------------------------ - template - void Set( const std::string &name, const Item &value ) - { - std::ostringstream o; - o << value; - pProperties[name] = o.str(); - } - - //------------------------------------------------------------------------ - //! Get the value associated with a name - //! - //! @return true if the name was found, false otherwise - //------------------------------------------------------------------------ - template - bool Get( const std::string &name, Item &item ) const - { - PropertyMap::const_iterator it; - it = pProperties.find( name ); - if( it == pProperties.end() ) - return false; - std::istringstream i; i.str( it->second ); - i >> item; - if( i.bad() ) - return false; - return true; - } - - //------------------------------------------------------------------------ - //! Get the value associated with a name - //! - //! @return the value or Item() if the key does not exist - //------------------------------------------------------------------------ - template - Item Get( const std::string &name ) const - { - PropertyMap::const_iterator it; - it = pProperties.find( name ); - if( it == pProperties.end() ) - return Item(); - std::istringstream i; i.str( it->second ); - Item item; - i >> item; - if( i.bad() ) - return Item(); - return item; - } - - //------------------------------------------------------------------------ - //! Set a value with a name and an index - //! - //! @param name must not contain spaces - //! @param index - //! @param value must be convertible to std::string - //------------------------------------------------------------------------ - template - void Set( const std::string &name, uint32_t index, const Item &value ) - { - std::ostringstream o; - o << name << " " << index; - Set( o.str(), value ); - } - - //------------------------------------------------------------------------ - //! Get the value associated with a key and an index - //! - //! @return true if the key and index were found, false otherwise - //------------------------------------------------------------------------ - template - bool Get( const std::string &name, uint32_t index, Item &item ) const - { - std::ostringstream o; - o << name << " " << index; - return Get( o.str(), item ); - } - - //------------------------------------------------------------------------ - //! Get the value associated with a key and an index - //! - //! @return the value or Item() if the key does not exist - //------------------------------------------------------------------------ - template - Item Get( const std::string &name, uint32_t index ) const - { - std::ostringstream o; - o << name << " " << index; - return Get( o.str() ); - } - - //------------------------------------------------------------------------ - //! Check if we now about the given name - //------------------------------------------------------------------------ - bool HasProperty( const std::string &name ) const - { - return pProperties.find( name ) != pProperties.end(); - } - - //------------------------------------------------------------------------ - //! Check if we know about the given name and index - //------------------------------------------------------------------------ - bool HasProperty( const std::string &name, uint32_t index ) const - { - std::ostringstream o; - o << name << " " << index; - return HasProperty( o.str() ); - } - - //------------------------------------------------------------------------ - //! Get the begin iterator - //------------------------------------------------------------------------ - PropertyMap::const_iterator begin() const - { - return pProperties.begin(); - } - - //------------------------------------------------------------------------ - //! Get the end iterator - //------------------------------------------------------------------------ - PropertyMap::const_iterator end() const - { - return pProperties.end(); - } - - //------------------------------------------------------------------------ - //! Clear the property list - //------------------------------------------------------------------------ - void Clear() - { - pProperties.clear(); - } - - private: - PropertyMap pProperties; - }; - - //---------------------------------------------------------------------------- - // Specialize get for strings - //---------------------------------------------------------------------------- - template<> - inline bool PropertyList::Get( const std::string &name, - std::string &item ) const - { - PropertyMap::const_iterator it; - it = pProperties.find( name ); - if( it == pProperties.end() ) - return false; - item = it->second; - return true; - } - - template<> - inline std::string PropertyList::Get( const std::string &name ) const - { - PropertyMap::const_iterator it; - it = pProperties.find( name ); - if( it == pProperties.end() ) - return std::string(); - return it->second; - } - - //---------------------------------------------------------------------------- - // Specialize set for XRootDStatus - //---------------------------------------------------------------------------- - template<> - inline void PropertyList::Set( const std::string &name, - const XRootDStatus &item ) - { - std::ostringstream o; - o << item.status << ";" << item.code << ";" << item.errNo << "#"; - o << item.GetErrorMessage(); - Set( name, o.str() ); - } - - //---------------------------------------------------------------------------- - // Specialize get for XRootDStatus - //---------------------------------------------------------------------------- - template<> - inline bool PropertyList::Get( const std::string &name, - XRootDStatus &item ) const - { - std::string str, msg, tmp; - if( !Get( name, str ) ) - return false; - - std::string::size_type i; - i = str.find( '#' ); - if( i == std::string::npos ) - return false; - item.SetErrorMessage( str.substr( i+1, str.length()-i-1 ) ); - str.erase( i, str.length()-i ); - std::replace( str.begin(), str.end(), ';', ' ' ); - std::istringstream is; is.str( str ); - is >> item.status; if( is.bad() ) return false; - is >> item.code; if( is.bad() ) return false; - is >> item.errNo; if( is.bad() ) return false; - return true; - } - - template<> - inline XRootDStatus PropertyList::Get( - const std::string &name ) const - { - XRootDStatus st; - if( !Get( name, st ) ) - return XRootDStatus(); - return st; - } - - //---------------------------------------------------------------------------- - // Specialize set for URL - //---------------------------------------------------------------------------- - template<> - inline void PropertyList::Set( const std::string &name, - const URL &item ) - { - Set( name, item.GetURL() ); - } - - //---------------------------------------------------------------------------- - // Specialize get for URL - //---------------------------------------------------------------------------- - template<> - inline bool PropertyList::Get( const std::string &name, - URL &item ) const - { - std::string tmp; - if( !Get( name, tmp ) ) - return false; - - item = tmp; - return true; - } - - //---------------------------------------------------------------------------- - // Specialize set for vector - //---------------------------------------------------------------------------- - template<> - inline void PropertyList::Set >( - const std::string &name, - const std::vector &item ) - { - std::vector::const_iterator it; - int i = 0; - for( it = item.begin(); it != item.end(); ++it, ++i ) - Set( name, i, *it ); - } - - //---------------------------------------------------------------------------- - // Specialize get for XRootDStatus - //---------------------------------------------------------------------------- - template<> - inline bool PropertyList::Get >( - const std::string &name, - std::vector &item ) const - { - std::string tmp; - item.clear(); - for( int i = 0; HasProperty( name, i ); ++i ) - { - if( !Get( name, i, tmp ) ) - return false; - item.push_back( tmp ); - } - return true; - } -} - -#endif // __XRD_OUC_PROPERTY_LIST_HH__ diff --git a/src/XrdCl/XrdClRedirectorRegistry.cc b/src/XrdCl/XrdClRedirectorRegistry.cc deleted file mode 100644 index 2535b3f89e9..00000000000 --- a/src/XrdCl/XrdClRedirectorRegistry.cc +++ /dev/null @@ -1,155 +0,0 @@ -/* - * XrdClRedirectorRegister.cc - * - * Created on: May 23, 2016 - * Author: simonm - */ - -#include "XrdClRedirectorRegistry.hh" -#include "XrdCl/XrdClMetalinkRedirector.hh" -#include "XrdCl/XrdClPostMasterInterfaces.hh" -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdCl/XrdClConstants.hh" -#include "XrdCl/XrdClLog.hh" - -#include - -namespace XrdCl -{ - -void RedirectJob::Run( void *arg ) -{ - Message *msg = reinterpret_cast( arg ); - pHandler->Process( msg ); - delete msg; - delete this; -} - - -RedirectorRegistry& RedirectorRegistry::Instance() -{ - static RedirectorRegistry redirector; - return redirector; -} - -RedirectorRegistry::~RedirectorRegistry() -{ - RedirectorMap::iterator itr; - for( itr = pRegistry.begin(); itr != pRegistry.end(); ++itr ) - delete itr->second.first; -} - -XRootDStatus RedirectorRegistry::RegisterImpl( const URL &u, ResponseHandler *handler ) -{ - URL url = ConvertLocalfile( u ); - - // we can only create a virtual redirector if - // a path to a metadata file has been provided - if( url.GetPath().empty() ) return XRootDStatus( stError, errNotSupported ); - - // regarding file protocol we only support localhost - if( url.GetProtocol() == "file" && url.GetHostName() != "localhost" ) - return XRootDStatus( stError, errNotSupported ); - - XrdSysMutexHelper scopedLock( pMutex ); - // get the key and check if it is already in the registry - const std::string key = url.GetLocation(); - RedirectorMap::iterator itr = pRegistry.find( key ); - if( itr != pRegistry.end() ) - { - // increment user counter - ++itr->second.second; - if( handler ) handler->HandleResponseWithHosts( new XRootDStatus(), 0, 0 ); - return XRootDStatus( stOK, suAlreadyDone ); - } - // If it is a Metalink create a MetalinkRedirector - if( url.IsMetalink() ) - { - MetalinkRedirector *redirector = new MetalinkRedirector( key ); - XRootDStatus st = redirector->Load( handler ); - if( !st.IsOK() ) - delete redirector; - else - pRegistry[key] = std::pair( redirector, 1 ); - return st; - } - else - // so far we only support Metalink metadata format - return XRootDStatus( stError, errNotSupported ); -} - -URL RedirectorRegistry::ConvertLocalfile( const URL &url ) -{ - int localml = DefaultLocalMetalinkFile; - DefaultEnv::GetEnv()->GetInt( "LocalMetalinkFile", localml ); - - if( localml && url.GetProtocol() == "root" && url.GetHostName() == "localfile" ) - { - Log *log = DefaultEnv::GetLog(); - log->Warning( PostMasterMsg, - "Please note that the 'root://localfile//path/filename.meta4' " - "semantic is now deprecated, use 'file://localhost/path/filename.meta4'" - "instead!" ); - - URL copy( url ); - copy.SetHostName( "localhost" ); - copy.SetProtocol( "file" ); - return copy; - } - - return url; -} - -XRootDStatus RedirectorRegistry::Register( const URL &url ) -{ - return RegisterImpl( url, 0 ); -} - -XRootDStatus RedirectorRegistry::RegisterAndWait( const URL &url ) -{ - SyncResponseHandler handler; - Status st = RegisterImpl( url, &handler ); - if( !st.IsOK() ) return st; - return MessageUtils::WaitForStatus( &handler ); -} - -VirtualRedirector* RedirectorRegistry::Get( const URL &u ) const -{ - URL url = ConvertLocalfile( u ); - - XrdSysMutexHelper scopedLock( pMutex ); - // get the key and return the value if it is in the registry - // offset 24 is where the path has been stored - const std::string key = url.GetLocation(); - RedirectorMap::const_iterator itr = pRegistry.find( key ); - if( itr != pRegistry.end() ) - return itr->second.first; - // otherwise return null - return 0; -} - -//---------------------------------------------------------------------------- -// Release the virtual redirector associated with the given URL -//---------------------------------------------------------------------------- -void RedirectorRegistry::Release( const URL &u ) -{ - URL url = ConvertLocalfile( u ); - - XrdSysMutexHelper scopedLock( pMutex ); - // get the key and return the value if it is in the registry - // offset 24 is where the path has been stored - const std::string key = url.GetLocation(); - RedirectorMap::iterator itr = pRegistry.find( key ); - if( itr == pRegistry.end() ) return; - // decrement user counter - --itr->second.second; - // if nobody is using it delete the object - // and remove it from the registry - if( !itr->second.second ) - { - delete itr->second.first; - pRegistry.erase( itr ); - } -} - -} /* namespace XrdCl */ diff --git a/src/XrdCl/XrdClRedirectorRegistry.hh b/src/XrdCl/XrdClRedirectorRegistry.hh deleted file mode 100644 index 2547c890d8b..00000000000 --- a/src/XrdCl/XrdClRedirectorRegistry.hh +++ /dev/null @@ -1,180 +0,0 @@ -/* - * XrdClRedirectorRegister.hh - * - * Created on: May 23, 2016 - * Author: simonm - */ - -#ifndef SRC_XRDCL_XRDCLREDIRECTORREGISTRY_HH_ -#define SRC_XRDCL_XRDCLREDIRECTORREGISTRY_HH_ - -#include "XrdCl/XrdClXRootDResponses.hh" -#include "XrdCl/XrdClURL.hh" -#include "XrdCl/XrdClJobManager.hh" -#include "XrdSys/XrdSysPthread.hh" - -#include -#include - -namespace XrdCl -{ - -class Message; -class IncomingMsgHandler; -class OutgoingMsgHandler; - -//-------------------------------------------------------------------------------- -//! A job class for redirect handling in the thread-pool -//-------------------------------------------------------------------------------- -class RedirectJob: public Job -{ - public: - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - RedirectJob( IncomingMsgHandler *handler ) : pHandler( handler ) - { - } - - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - virtual ~RedirectJob() - { - } - - //------------------------------------------------------------------------ - //! Run the user handler - //------------------------------------------------------------------------ - virtual void Run( void *arg ); - - private: - IncomingMsgHandler *pHandler; -}; - -//-------------------------------------------------------------------------------- -//! An interface for metadata redirectors. -//-------------------------------------------------------------------------------- -class VirtualRedirector -{ - public: - //---------------------------------------------------------------------------- - //! Destructor. - //---------------------------------------------------------------------------- - virtual ~VirtualRedirector(){} - - //---------------------------------------------------------------------------- - //! Creates an instant redirect response for the given message - //! or an error response if there are no more replicas to try. - //! The virtual response is being handled by the given handler - //! in the thread-pool. - //---------------------------------------------------------------------------- - virtual XRootDStatus HandleRequest( const Message *msg, - IncomingMsgHandler *handler ) = 0; - - //---------------------------------------------------------------------------- - //! Initializes the object with the content of the metalink file - //---------------------------------------------------------------------------- - virtual XRootDStatus Load( ResponseHandler *userHandler ) = 0; - - //---------------------------------------------------------------------------- - //! Gets the file name as specified in the metalink - //---------------------------------------------------------------------------- - virtual std::string GetTargetName() const = 0; - - //---------------------------------------------------------------------------- - //! Returns the checksum of the given type if specified - //! in the metalink file, or an empty string otherwise - //---------------------------------------------------------------------------- - virtual std::string GetCheckSum( const std::string &type ) const = 0; - - //---------------------------------------------------------------------------- - //! Returns the file size as specified in the metalink, - //! or a negative number if size was not specified - //---------------------------------------------------------------------------- - virtual long long GetSize() const = 0; - - //---------------------------------------------------------------------------- - //! Returns a vector with replicas as given in the meatlink file - //---------------------------------------------------------------------------- - virtual const std::vector& GetReplicas() = 0; -}; - -//-------------------------------------------------------------------------------- -//! Singleton access to URL to virtual redirector mapping. -//-------------------------------------------------------------------------------- -class RedirectorRegistry -{ - - public: - - //---------------------------------------------------------------------------- - //! Returns reference to the single instance. - //---------------------------------------------------------------------------- - static RedirectorRegistry& Instance(); - - //---------------------------------------------------------------------------- - //! Destructor - //---------------------------------------------------------------------------- - ~RedirectorRegistry(); - - //---------------------------------------------------------------------------- - //! Creates a new virtual redirector and registers it (async). - //---------------------------------------------------------------------------- - XRootDStatus Register( const URL &url ); - - //---------------------------------------------------------------------------- - //! Creates a new virtual redirector and registers it (sync). - //---------------------------------------------------------------------------- - XRootDStatus RegisterAndWait( const URL &url ); - - //---------------------------------------------------------------------------- - //! Get a virtual redirector associated with the given URL. - //---------------------------------------------------------------------------- - VirtualRedirector* Get( const URL &url ) const; - - //---------------------------------------------------------------------------- - //! Release the virtual redirector associated with the given URL - //---------------------------------------------------------------------------- - void Release( const URL &url ); - - private: - - typedef std::map< std::string, std::pair > RedirectorMap; - - //---------------------------------------------------------------------------- - //! Register implementation. - //---------------------------------------------------------------------------- - XRootDStatus RegisterImpl( const URL &url, ResponseHandler *handler ); - - //---------------------------------------------------------------------------- - //! Convert the old convention for accessing local metalink files: - //! root://localfile//path/metalink.meta4 - //! into: - //! file://localhost/path/metalink.meta4 - //---------------------------------------------------------------------------- - static URL ConvertLocalfile( const URL &url ); - - //---------------------------------------------------------------------------- - // Constructor (private!). - //---------------------------------------------------------------------------- - RedirectorRegistry() {} - - //---------------------------------------------------------------------------- - // Copy constructor (private!). - //---------------------------------------------------------------------------- - RedirectorRegistry( const RedirectorRegistry & ); - - //---------------------------------------------------------------------------- - // Assignment operator (private!). - //---------------------------------------------------------------------------- - RedirectorRegistry& operator=( const RedirectorRegistry & ); - - RedirectorMap pRegistry; - - mutable XrdSysMutex pMutex; -}; - -} /* namespace XrdCl */ - -#endif /* SRC_XRDCL_XRDCLREDIRECTORREGISTRY_HH_ */ diff --git a/src/XrdCl/XrdClRequestSync.hh b/src/XrdCl/XrdClRequestSync.hh deleted file mode 100644 index 0b44204f632..00000000000 --- a/src/XrdCl/XrdClRequestSync.hh +++ /dev/null @@ -1,114 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2014 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_REQUEST_SYNC_HH__ -#define __XRD_CL_REQUEST_SYNC_HH__ - -#include "XrdSys/XrdSysPthread.hh" -#include "XrdCl/XrdClUglyHacks.hh" - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - //! A helper running a fixed number of requests at a given time - //---------------------------------------------------------------------------- - class RequestSync - { - public: - //------------------------------------------------------------------------ - //! Constructor - //! - //! @param reqTotal total number of requests - //! @param reqQuota number of requests to be run in parallel - //------------------------------------------------------------------------ - RequestSync( uint32_t reqTotal, uint32_t reqQuota ): - pQuotaSem( new Semaphore( reqQuota ) ), - pTotalSem( new Semaphore( 0 ) ), - pRequestsLeft( reqTotal ), - pFailureCounter( 0 ) - { - if( !reqTotal ) - pTotalSem->Post(); - } - - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - ~RequestSync() - { - delete pQuotaSem; - delete pTotalSem; - } - - //------------------------------------------------------------------------ - //! Wait for the request quota - //------------------------------------------------------------------------ - void WaitForQuota() - { - pQuotaSem->Wait(); - } - - //------------------------------------------------------------------------ - //! Wait for all the requests to be finished - //------------------------------------------------------------------------ - void WaitForAll() - { - pTotalSem->Wait(); - } - - //------------------------------------------------------------------------ - //! Report the request finish - //------------------------------------------------------------------------ - void TaskDone( bool success = true ) - { - XrdSysMutexHelper scopedLock( pMutex ); - if( !success ) - ++pFailureCounter; - --pRequestsLeft; - pQuotaSem->Post(); - if( !pRequestsLeft ) - pTotalSem->Post(); - } - - //------------------------------------------------------------------------ - //! Number of tasks finishing with an error - //------------------------------------------------------------------------ - uint32_t FailureCount() const - { - return pFailureCounter; - } - - private: - RequestSync(const RequestSync &other); - RequestSync &operator = (const RequestSync &other); - - XrdSysMutex pMutex; - Semaphore *pQuotaSem; - Semaphore *pTotalSem; - uint32_t pRequestsLeft; - uint32_t pFailureCounter; - }; -} - -#endif // __XRD_CL_REQUEST_SYNC_HH__ diff --git a/src/XrdCl/XrdClResponseJob.hh b/src/XrdCl/XrdClResponseJob.hh deleted file mode 100644 index 63adcc9dc1d..00000000000 --- a/src/XrdCl/XrdClResponseJob.hh +++ /dev/null @@ -1,70 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2013 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_RESPONSE_JOB_HH__ -#define __XRD_CL_RESPONSE_JOB_HH__ - -#include "XrdCl/XrdClJobManager.hh" -#include "XrdCl/XrdClXRootDResponses.hh" - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - //! Call the user callback - //---------------------------------------------------------------------------- - class ResponseJob: public Job - { - public: - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - ResponseJob( ResponseHandler *handler, - XRootDStatus *status, - AnyObject *response, - HostList *hostList ): - pHandler( handler ), pStatus( status ), pResponse( response ), - pHostList( hostList ) - { - } - - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - virtual ~ResponseJob() - { - } - - - //------------------------------------------------------------------------ - //! Run the user handler - //------------------------------------------------------------------------ - virtual void Run( void *arg ) - { - pHandler->HandleResponseWithHosts( pStatus, pResponse, pHostList ); - delete this; - } - - private: - ResponseHandler *pHandler; - XRootDStatus *pStatus; - AnyObject *pResponse; - HostList *pHostList; - }; -} - -#endif // __XRD_CL_RESPONSE_JOB_HH__ diff --git a/src/XrdCl/XrdClSIDManager.cc b/src/XrdCl/XrdClSIDManager.cc deleted file mode 100644 index dd6903d3344..00000000000 --- a/src/XrdCl/XrdClSIDManager.cc +++ /dev/null @@ -1,122 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#include "XrdCl/XrdClSIDManager.hh" - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - // Allocate a SID - //--------------------------------------------------------------------------- - Status SIDManager::AllocateSID( uint8_t sid[2] ) - { - XrdSysMutexHelper scopedLock( pMutex ); - - uint16_t allocSID = 1; - - //-------------------------------------------------------------------------- - // Get a SID from the list of free SIDs if it's not empty - //-------------------------------------------------------------------------- - if( !pFreeSIDs.empty() ) - { - allocSID = pFreeSIDs.front(); - pFreeSIDs.pop_front(); - } - //-------------------------------------------------------------------------- - // Allocate a new SID if possible - //-------------------------------------------------------------------------- - else - { - if( pSIDCeiling == 0xffff ) - return Status( stError, errNoMoreFreeSIDs ); - allocSID = pSIDCeiling++; - } - - memcpy( sid, &allocSID, 2 ); - return Status(); - } - - //---------------------------------------------------------------------------- - // Release the SID that is no longer needed - //---------------------------------------------------------------------------- - void SIDManager::ReleaseSID( uint8_t sid[2] ) - { - XrdSysMutexHelper scopedLock( pMutex ); - uint16_t relSID = 0; - memcpy( &relSID, sid, 2 ); - pFreeSIDs.push_back( relSID ); - } - - //---------------------------------------------------------------------------- - // Register a SID of a request that timed out - //---------------------------------------------------------------------------- - void SIDManager::TimeOutSID( uint8_t sid[2] ) - { - XrdSysMutexHelper scopedLock( pMutex ); - uint16_t tiSID = 0; - memcpy( &tiSID, sid, 2 ); - pTimeOutSIDs.insert( tiSID ); - } - - //---------------------------------------------------------------------------- - // Check if a SID is timed out - //---------------------------------------------------------------------------- - bool SIDManager::IsTimedOut( uint8_t sid[2] ) - { - XrdSysMutexHelper scopedLock( pMutex ); - uint16_t tiSID = 0; - memcpy( &tiSID, sid, 2 ); - std::set::iterator it = pTimeOutSIDs.find( tiSID ); - if( it != pTimeOutSIDs.end() ) - return true; - return false; - } - - //---------------------------------------------------------------------------- - // Release a timed out SID - //----------------------------------------------------------------------------- - void SIDManager::ReleaseTimedOut( uint8_t sid[2] ) - { - XrdSysMutexHelper scopedLock( pMutex ); - uint16_t tiSID = 0; - memcpy( &tiSID, sid, 2 ); - pTimeOutSIDs.erase( tiSID ); - pFreeSIDs.push_back( tiSID ); - } - - //------------------------------------------------------------------------ - // Release all timed out SIDs - //------------------------------------------------------------------------ - void SIDManager::ReleaseAllTimedOut() - { - XrdSysMutexHelper scopedLock( pMutex ); - std::set::iterator it; - for( it = pTimeOutSIDs.begin(); it != pTimeOutSIDs.end(); ++it ) - pFreeSIDs.push_back( *it ); - pTimeOutSIDs.clear(); - } - - //---------------------------------------------------------------------------- - // Get number of allocated SIDs - //---------------------------------------------------------------------------- - uint16_t SIDManager::GetNumberOfAllocatedSIDs() const - { - XrdSysMutexHelper scopedLock( pMutex ); - return pSIDCeiling - pFreeSIDs.size() - pTimeOutSIDs.size() - 1; - } -} diff --git a/src/XrdCl/XrdClSIDManager.hh b/src/XrdCl/XrdClSIDManager.hh deleted file mode 100644 index 59239345adf..00000000000 --- a/src/XrdCl/XrdClSIDManager.hh +++ /dev/null @@ -1,97 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_SID_MANAGER_HH__ -#define __XRD_CL_SID_MANAGER_HH__ - -#include -#include -#include -#include "XrdSys/XrdSysPthread.hh" -#include "XrdCl/XrdClStatus.hh" - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - //! Handle XRootD stream IDs - //---------------------------------------------------------------------------- - class SIDManager - { - public: - - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - SIDManager(): pSIDCeiling(1) {} - - //------------------------------------------------------------------------ - //! Allocate a SID - //! - //! @param sid a two byte array where the allocated SID should be stored - //! @return stOK on success, stError on error - //------------------------------------------------------------------------ - Status AllocateSID( uint8_t sid[2] ); - - //------------------------------------------------------------------------ - //! Release the SID that is no longer needed - //------------------------------------------------------------------------ - void ReleaseSID( uint8_t sid[2] ); - - //------------------------------------------------------------------------ - //! Register a SID of a request that timed out - //------------------------------------------------------------------------ - void TimeOutSID( uint8_t sid[2] ); - - //------------------------------------------------------------------------ - //! Check if a SID is timed out - //------------------------------------------------------------------------ - bool IsTimedOut( uint8_t sid[2] ); - - //------------------------------------------------------------------------ - //! Release a timed out SID - //------------------------------------------------------------------------ - void ReleaseTimedOut( uint8_t sid[2] ); - - //------------------------------------------------------------------------ - //! Release all timed out SIDs - //------------------------------------------------------------------------ - void ReleaseAllTimedOut(); - - //------------------------------------------------------------------------ - //! Number of timeout sids - //------------------------------------------------------------------------ - uint32_t NumberOfTimedOutSIDs() const - { - XrdSysMutexHelper scopedLock( pMutex ); - return pTimeOutSIDs.size(); - } - - //------------------------------------------------------------------------ - //! Number of allocated streams - //------------------------------------------------------------------------ - uint16_t GetNumberOfAllocatedSIDs() const; - - private: - std::list pFreeSIDs; - std::set pTimeOutSIDs; - uint16_t pSIDCeiling; - mutable XrdSysMutex pMutex; - }; -} - -#endif // __XRD_CL_SID_MANAGER_HH__ diff --git a/src/XrdCl/XrdClSocket.cc b/src/XrdCl/XrdClSocket.cc deleted file mode 100644 index 848c710044d..00000000000 --- a/src/XrdCl/XrdClSocket.cc +++ /dev/null @@ -1,604 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#include "XrdCl/XrdClSocket.hh" -#include "XrdCl/XrdClUtils.hh" -#include "XrdNet/XrdNetConnect.hh" -#include "XrdCl/XrdClConstants.hh" -#include "XrdCl/XrdClMessage.hh" -#include "XrdCl/XrdClDefaultEnv.hh" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - // Initialize - //---------------------------------------------------------------------------- - Status Socket::Initialize( int family ) - { - if( pSocket != -1 ) - return Status( stError, errInvalidOp ); - - pSocket = ::socket( family, SOCK_STREAM, 0 ); - if( pSocket < 0 ) - { - pSocket = -1; - return Status( stError, errSocketError ); - } - - pProtocolFamily = family; - - //-------------------------------------------------------------------------- - // Make the socket non blocking and disable the Nagle algorithm since - // we will be using this for transmitting messages not handling streams - //-------------------------------------------------------------------------- - int flags; - if( (flags = ::fcntl( pSocket, F_GETFL, 0 )) == -1 ) - flags = 0; - if( ::fcntl( pSocket, F_SETFL, flags | O_NONBLOCK | O_NDELAY ) == -1 ) - { - Close(); - return Status( stError, errFcntl, errno ); - } - - XrdCl::Env *env = XrdCl::DefaultEnv::GetEnv(); - flags = DefaultNoDelay; - env->GetInt( "NoDelay", flags ); - if( setsockopt( pSocket, IPPROTO_TCP, TCP_NODELAY, &flags, sizeof( int ) ) < 0 ) - { - Close(); - return Status( stError, errFcntl, errno ); - } - - //-------------------------------------------------------------------------- - // We use send with MSG_NOSIGNAL to avoid SIGPIPEs on Linux, on MacOSX - // we set SO_NOSIGPIPE option, on Solaris we ignore the SIGPIPE - //-------------------------------------------------------------------------- -#ifdef __APPLE__ - int set = 1; - Status st = SetSockOpt( SOL_SOCKET, SO_NOSIGPIPE, &set, sizeof(int) ); - if( !st.IsOK() ) - { - Close(); - return st; - } -#elif __solaris__ - struct sigaction act; - act.sa_handler = SIG_IGN; - sigaction( SIGPIPE, &act, NULL ); -#endif - - return Status(); - } - - //---------------------------------------------------------------------------- - // Set the socket flags - //---------------------------------------------------------------------------- - Status Socket::SetFlags( int flags ) - { - if( pSocket == -1 ) - return Status( stError, errInvalidOp ); - - int st = ::fcntl( pSocket, F_SETFL, flags ); - if( st == -1 ) - return Status( stError, errSocketError, errno ); - return Status(); - } - - //---------------------------------------------------------------------------- - // Get the socket flags - //---------------------------------------------------------------------------- - Status Socket::GetFlags( int &flags ) - { - if( pSocket == -1 ) - return Status( stError, errInvalidOp ); - - int st = ::fcntl( pSocket, F_GETFL, 0 ); - if( st == -1 ) - return Status( stError, errSocketError, errno ); - flags = st; - return Status(); - } - - //---------------------------------------------------------------------------- - // Get socket options - //---------------------------------------------------------------------------- - Status Socket::GetSockOpt( int level, int optname, void *optval, - socklen_t *optlen ) - { - if( pSocket == -1 ) - return Status( stError, errInvalidOp ); - - if( ::getsockopt( pSocket, level, optname, optval, optlen ) != 0 ) - return Status( stError, errSocketOptError, errno ); - - return Status(); - } - - //------------------------------------------------------------------------ - // Set socket options - //------------------------------------------------------------------------ - Status Socket::SetSockOpt( int level, int optname, const void *optval, - socklen_t optlen ) - { - if( pSocket == -1 ) - return Status( stError, errInvalidOp ); - - if( ::setsockopt( pSocket, level, optname, optval, optlen ) != 0 ) - return Status( stError, errSocketOptError, errno ); - - return Status(); - } - - - //---------------------------------------------------------------------------- - // Connect to the given host name - //---------------------------------------------------------------------------- - Status Socket::Connect( const std::string &host, - uint16_t port, - uint16_t timeout ) - { - if( pSocket == -1 || pStatus == Connected || pStatus == Connecting ) - return Status( stError, errInvalidOp ); - - std::vector addrs; - std::ostringstream o; o << host << ":" << port; - Status st; - - if( pProtocolFamily == AF_INET6 ) - st = Utils::GetHostAddresses( addrs, URL( o.str() ), Utils::IPAll ); - else - st = Utils::GetHostAddresses( addrs, URL( o.str() ), Utils::IPv4 ); - - if( !st.IsOK() ) - return st; - - Utils::LogHostAddresses( DefaultEnv::GetLog(), PostMasterMsg, o.str(), - addrs ); - - - return ConnectToAddress( addrs[0], timeout ); - } - - //---------------------------------------------------------------------------- - // Connect to the given host - //---------------------------------------------------------------------------- - Status Socket::ConnectToAddress( const XrdNetAddr &addr, - uint16_t timeout ) - { - if( pSocket == -1 || pStatus == Connected || pStatus == Connecting ) - return Status( stError, errInvalidOp ); - - pServerAddr = addr; - - //-------------------------------------------------------------------------- - // Connect - //-------------------------------------------------------------------------- - int status = XrdNetConnect::Connect( pSocket, pServerAddr.SockAddr(), - pServerAddr.SockSize(), timeout ); - if( status != 0 ) - { - Status st( stError ); - - //------------------------------------------------------------------------ - // If we connect asynchronously this is not really an error - //------------------------------------------------------------------------ - if( !timeout && status == EINPROGRESS ) - { - pStatus = Connecting; - return Status(); - } - - //------------------------------------------------------------------------ - // Errors - //------------------------------------------------------------------------ - else if( status == ETIMEDOUT ) - st.code = errSocketTimeout; - else - st.code = errSocketError; - st.errNo = status; - - Close(); - return st; - } - pStatus = Connected; - return Status(); - } - - //---------------------------------------------------------------------------- - // Disconnect - //---------------------------------------------------------------------------- - void Socket::Close() - { - if( pSocket != -1 ) - { - close( pSocket ); - pStatus = Disconnected; - pSocket = -1; - pSockName = ""; - pPeerName = ""; - pName = ""; - } - } - - //---------------------------------------------------------------------------- - //! Read raw bytes from the socket - //---------------------------------------------------------------------------- - Status Socket::ReadRaw( void *buffer, uint32_t size, int32_t timeout, - uint32_t &bytesRead ) - { - //-------------------------------------------------------------------------- - // Check if we're connected - //-------------------------------------------------------------------------- - if( pStatus != Connected ) - return Status( stError, errInvalidOp ); - - //-------------------------------------------------------------------------- - // Some useful variables - //-------------------------------------------------------------------------- - bytesRead = 0; - - char *current = (char *)buffer; - bool useTimeout = (timeout!=-1); - time_t now = 0; - time_t newNow = 0; - Status sc; - - if( useTimeout ) - now = ::time(0); - - //-------------------------------------------------------------------------- - // Repeat the following until we have read all the requested data - //-------------------------------------------------------------------------- - while ( bytesRead < size ) - { - //------------------------------------------------------------------------ - // Check if we can read something - //------------------------------------------------------------------------ - sc = Poll( true, false, useTimeout ? timeout : -1 ); - - //------------------------------------------------------------------------ - // It looks like we've got an event. Let's check if we can read something. - //------------------------------------------------------------------------ - if( sc.status == stOK ) - { - ssize_t n = ::read( pSocket, current, (size-bytesRead) ); - - if( n > 0 ) - { - bytesRead += n; - current += n; - } - - //---------------------------------------------------------------------- - // We got a close here - this means that there is no more data in - // the buffer so we disconnect - //---------------------------------------------------------------------- - if( n == 0 ) - { - Close(); - return Status( stError, errSocketDisconnected ); - } - - //---------------------------------------------------------------------- - // Error - //---------------------------------------------------------------------- - if( (n < 0) && (errno != EAGAIN) && (errno != EWOULDBLOCK) ) - { - Close(); - return Status( stError, errSocketError, errno ); - } - } - else - { - Close(); - return sc; - } - - //------------------------------------------------------------------------ - // Do we still have time to wait for data? - //------------------------------------------------------------------------ - if( useTimeout ) - { - newNow = ::time(0); - timeout -= (newNow-now); - now = newNow; - if( timeout < 0 ) - break; - } - } - - //-------------------------------------------------------------------------- - // Have we managed to read everything? - //-------------------------------------------------------------------------- - if( bytesRead < size ) - return Status( stError, errSocketTimeout ); - return Status( stOK ); - } - - //---------------------------------------------------------------------------- - // Write raw bytes to the socket - //---------------------------------------------------------------------------- - Status Socket::WriteRaw( void *buffer, uint32_t size, int32_t timeout, - uint32_t &bytesWritten ) - { - //-------------------------------------------------------------------------- - // Check if we're connected - //-------------------------------------------------------------------------- - if( pStatus != Connected ) - return Status( stError, errInvalidOp ); - - //-------------------------------------------------------------------------- - // Some useful variables - //-------------------------------------------------------------------------- - bytesWritten = 0; - - char *current = (char *)buffer; - bool useTimeout = (timeout!=-1); - time_t now = 0; - time_t newNow = 0; - Status sc; - - if( useTimeout ) - now = ::time(0); - - //-------------------------------------------------------------------------- - // Repeat the following until we have written everything - //-------------------------------------------------------------------------- - while ( bytesWritten < size ) - { - //------------------------------------------------------------------------ - // Check if we can read something - //------------------------------------------------------------------------ - sc = Poll( false, true, useTimeout ? timeout : -1 ); - - //------------------------------------------------------------------------ - // Let's write - //------------------------------------------------------------------------ - if( sc.status == stOK ) - { - ssize_t n = ::write( pSocket, current, (size-bytesWritten) ); - - if( n > 0 ) - { - bytesWritten += n; - current += n; - } - - //---------------------------------------------------------------------- - // Error - //---------------------------------------------------------------------- - if( (n <= 0) && (errno != EAGAIN) && (errno != EWOULDBLOCK) ) - { - Close(); - return Status( stError, errSocketError, errno ); - } - } - else - { - Close(); - return sc; - } - - //------------------------------------------------------------------------ - // Do we still have time to wait for data? - //------------------------------------------------------------------------ - if( useTimeout ) - { - newNow = ::time(0); - timeout -= (newNow-now); - now = newNow; - if( timeout < 0 ) - break; - } - } - - //-------------------------------------------------------------------------- - // Have we managed to read everything? - //-------------------------------------------------------------------------- - if( bytesWritten < size ) - return Status( stError, errSocketTimeout ); - - return Status( stOK ); - } - - //------------------------------------------------------------------------ - // Portable wrapper around SIGPIPE free send - //---------------------------------------------------------------------------- - ssize_t Socket::Send( void *buffer, uint32_t size ) - { - //-------------------------------------------------------------------------- - // We use send with MSG_NOSIGNAL to avoid SIGPIPEs on Linux - //-------------------------------------------------------------------------- -#ifdef __linux__ - return ::send( pSocket, buffer, size, MSG_NOSIGNAL ); -#else - return ::write( pSocket, buffer, size ); -#endif - } - - //------------------------------------------------------------------------ - // Wrapper around writev - //------------------------------------------------------------------------ - ssize_t Socket::WriteV( iovec *iov, int iovcnt ) - { - return ::writev( pSocket, iov, iovcnt ); - } - - //---------------------------------------------------------------------------- - // Poll the descriptor - //---------------------------------------------------------------------------- - Status Socket::Poll( bool readyForReading, bool readyForWriting, - int32_t timeout ) - { - //-------------------------------------------------------------------------- - // Check if we're connected - //-------------------------------------------------------------------------- - if( pStatus != Connected ) - return Status( stError, errInvalidOp ); - - //-------------------------------------------------------------------------- - // Prepare the stuff - //-------------------------------------------------------------------------- - pollfd pollDesc; - int pollRet; - bool useTimeout = (timeout!=-1); - time_t now = 0; - time_t newNow = 0; - short hupEvents = POLLHUP; - -#ifdef __linux__ - hupEvents |= POLLRDHUP; -#endif - - if( useTimeout ) - now = ::time(0); - - pollDesc.fd = pSocket; - pollDesc.events = POLLERR | POLLNVAL | hupEvents; - - if( readyForReading ) - pollDesc.events |= (POLLIN | POLLPRI); - - if( readyForWriting ) - pollDesc.events |= POLLOUT; - - //-------------------------------------------------------------------------- - // We loop on poll because it may return -1 even thought no fatal error - // has occurred, these may be: - // * a signal interrupting the execution (errno == EINTR) - // * a failure to initialize some internal structures (Solaris only) - // (errno == EAGAIN) - //-------------------------------------------------------------------------- - do - { - pollRet = poll( &pollDesc, 1, (useTimeout ? timeout*1000 : -1) ); - if( (pollRet < 0) && (errno != EINTR) && (errno != EAGAIN) ) - return Status( stError, errPoll, errno ); - - //------------------------------------------------------------------------ - // Check if we did not time out in the case where we are not supposed - // to wait indefinitely - //------------------------------------------------------------------------ - if( useTimeout ) - { - newNow = time(0); - timeout -= (newNow-now); - now = newNow; - if( timeout < 0 ) - return Status( stError, errSocketTimeout ); - } - } - while( pollRet == -1 ); - - //-------------------------------------------------------------------------- - // Check if we have timed out - //-------------------------------------------------------------------------- - if( pollRet == 0 ) - return Status( stError, errSocketTimeout ); - - //-------------------------------------------------------------------------- - // We have some events - //-------------------------------------------------------------------------- - if( pollDesc.revents & (POLLIN | POLLPRI | POLLOUT) ) - return Status( stOK ); - - //-------------------------------------------------------------------------- - // We've been hang up on - //-------------------------------------------------------------------------- - if( pollDesc.revents & hupEvents ) - return Status( stError, errSocketDisconnected ); - - //-------------------------------------------------------------------------- - // We're messed up, either because we messed up ourselves (POLLNVAL) or - // got messed up by the network (POLLERR) - //-------------------------------------------------------------------------- - return Status( stError, errSocketError ); - } - - //---------------------------------------------------------------------------- - // Get the name of the socket - //---------------------------------------------------------------------------- - std::string Socket::GetSockName() const - { - if( pStatus != Connected ) - return ""; - - if( pSockName.length() ) - return pSockName; - - char nameBuff[256]; - int len = XrdNetUtils::IPFormat( -pSocket, nameBuff, sizeof(nameBuff) ); - if( len == 0 ) - return ""; - - pSockName = nameBuff; - return pSockName; - } - - //---------------------------------------------------------------------------- - // Get the name of the remote peer - //---------------------------------------------------------------------------- - std::string Socket::GetPeerName() const - { - if( pStatus != Connected ) - return ""; - - if( pPeerName.length() ) - return pPeerName; - - char nameBuff[256]; - int len = XrdNetUtils::IPFormat( pSocket, nameBuff, sizeof(nameBuff) ); - if( len == 0 ) - return ""; - - pPeerName = nameBuff; - return pPeerName; - } - - //---------------------------------------------------------------------------- - // Get the string representation of the socket - //---------------------------------------------------------------------------- - std::string Socket::GetName() const - { - if( pStatus != Connected ) - return "<-->"; - - if( pName.length() ) - return pName; - - pName = "<"; - pName += GetSockName(); - pName += "><--><"; - pName += GetPeerName(); - pName += ">"; - return pName; - } -} diff --git a/src/XrdCl/XrdClSocket.hh b/src/XrdCl/XrdClSocket.hh deleted file mode 100644 index e79b66b9493..00000000000 --- a/src/XrdCl/XrdClSocket.hh +++ /dev/null @@ -1,257 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_SOCKET_HH__ -#define __XRD_CL_SOCKET_HH__ - -#include -#include -#include - -#include "XrdCl/XrdClStatus.hh" -#include "XrdNet/XrdNetAddr.hh" - -namespace XrdCl -{ - class AnyObject; - - //---------------------------------------------------------------------------- - //! A network socket - //---------------------------------------------------------------------------- - class Socket - { - public: - //------------------------------------------------------------------------ - //! Status of the socket - //------------------------------------------------------------------------ - enum SocketStatus - { - Disconnected = 1, //!< The socket is disconnected - Connected = 2, //!< The socket is connected - Connecting = 3 //!< The connection process is in progress - }; - - //------------------------------------------------------------------------ - //! Constructor - //! - //! @param socket already connected socket if available, -1 otherwise - //! @param status status of a socket if available - //------------------------------------------------------------------------ - Socket( int socket = -1, SocketStatus status = Disconnected ): - pSocket(socket), pStatus( status ), pServerAddr( 0 ), - pProtocolFamily( AF_INET ), - pChannelID( 0 ) - { - }; - - //------------------------------------------------------------------------ - //! Desctuctor - //------------------------------------------------------------------------ - virtual ~Socket() - { - Close(); - }; - - //------------------------------------------------------------------------ - //! Initialize the socket - //------------------------------------------------------------------------ - Status Initialize( int family = AF_INET ); - - //------------------------------------------------------------------------ - //! Set the socket flags (man fcntl) - //------------------------------------------------------------------------ - Status SetFlags( int flags ); - - //------------------------------------------------------------------------ - //! Get the socket flags (man fcntl) - //------------------------------------------------------------------------ - Status GetFlags( int &flags ); - - //------------------------------------------------------------------------ - //! Get socket options - //------------------------------------------------------------------------ - Status GetSockOpt( int level, int optname, void *optval, - socklen_t *optlen ); - - //------------------------------------------------------------------------ - //! Set socket options - //------------------------------------------------------------------------ - Status SetSockOpt( int level, int optname, const void *optval, - socklen_t optlen ); - - //------------------------------------------------------------------------ - //! Connect to the given host name - //! - //! @param host name of the host to connect to - //! @param port port to connect to - //! @param timout timeout in seconds, 0 for no timeout handling (may be - //! used for non blocking IO) - //------------------------------------------------------------------------ - Status Connect( const std::string &host, - uint16_t port, - uint16_t timout = 10 ); - - //------------------------------------------------------------------------ - //! Connect to the given host address - //! - //! @param addr address of the host to connect to - //! @param timout timeout in seconds, 0 for no timeout handling (may be - //! used for non blocking IO) - //------------------------------------------------------------------------ - Status ConnectToAddress( const XrdNetAddr &addr, - uint16_t timout = 10 ); - - //------------------------------------------------------------------------ - //! Disconnect - //------------------------------------------------------------------------ - void Close(); - - //------------------------------------------------------------------------ - //! Get the socket status - //------------------------------------------------------------------------ - SocketStatus GetStatus() const - { - return pStatus; - } - - //------------------------------------------------------------------------ - //! Set socket status - do not use unless you know what you're doing - //------------------------------------------------------------------------ - void SetStatus( SocketStatus status ) - { - pStatus = status; - } - - //------------------------------------------------------------------------ - //! Read raw bytes from the socket - //! - //! @param buffer data to be sent - //! @param size size of the data buffer - //! @param timeout timout value in seconds, -1 to wait indefinitely - //! @param bytesRead the amount of data actually read - //------------------------------------------------------------------------ - Status ReadRaw( void *buffer, uint32_t size, int32_t timeout, - uint32_t &bytesRead ); - - //------------------------------------------------------------------------ - //! Write raw bytes to the socket - //! - //! @param buffer data to be written - //! @param size size of the data buffer - //! @param timeout timeout value in seconds, -1 to wait indefinitely - //! @param bytesWritten the amount of data actually written - //------------------------------------------------------------------------ - Status WriteRaw( void *buffer, uint32_t size, int32_t timeout, - uint32_t &bytesWritten ); - - //------------------------------------------------------------------------ - //! Portable wrapper around SIGPIPE free send - //! - //! @param buffer : data to be written - //! @param size : size of the data buffer - //! @return : the amount of data actually written - //------------------------------------------------------------------------ - ssize_t Send( void *buffer, uint32_t size ); - - //------------------------------------------------------------------------ - //! Wrapper around writev - //! - //! @param iov : buffers to be written - //! @param iovcnt : number of buffers - //! @return : the amount of data actually written - //------------------------------------------------------------------------ - ssize_t WriteV( iovec *iov, int iovcnt ); - - //------------------------------------------------------------------------ - //! Get the file descriptor - //------------------------------------------------------------------------ - int GetFD() - { - return pSocket; - } - - //------------------------------------------------------------------------ - //! Get the name of the socket - //------------------------------------------------------------------------ - std::string GetSockName() const; - - //------------------------------------------------------------------------ - //! Get the name of the remote peer - //------------------------------------------------------------------------ - std::string GetPeerName() const; - - //------------------------------------------------------------------------ - //! Get the string representation of the socket - //------------------------------------------------------------------------ - std::string GetName() const; - - //------------------------------------------------------------------------ - //! Get the server address - //------------------------------------------------------------------------ - const XrdNetAddr &GetServerAddress() const - { - return pServerAddr; - } - - //------------------------------------------------------------------------ - //! Set Channel ID - //! (an object that allows to identify all sockets corresponding to the same channel) - //------------------------------------------------------------------------ - void SetChannelID( AnyObject *channelID ) - { - pChannelID = channelID; - } - - //------------------------------------------------------------------------ - //! Get Channel ID - //! (an object that allows to identify all sockets corresponding to the same channel) - //------------------------------------------------------------------------ - const AnyObject* GetChannelID() const - { - return pChannelID; - } - - private: - //------------------------------------------------------------------------ - //! Poll the socket to see whether it is ready for IO - //! - //! @param readyForReading poll for readiness to read - //! @param readyForWriting poll for readiness to write - //! @param timeout timeout in seconds, -1 to wait indefinitely - //! @return stOK - ready for IO - //! errSocketDisconnected - on disconnection - //! errSocketError - on socket error - //! errSocketTimeout - on socket timeout - //! errInvalidOp - when called on a non connected socket - //------------------------------------------------------------------------ - Status Poll( bool readyForReading, bool readyForWriting, - int32_t timeout ); - - int pSocket; - SocketStatus pStatus; - XrdNetAddr pServerAddr; - mutable std::string pSockName; // mutable because it's for caching - mutable std::string pPeerName; - mutable std::string pName; - int pProtocolFamily; - AnyObject *pChannelID; - }; -} - -#endif // __XRD_CL_SOCKET_HH__ - diff --git a/src/XrdCl/XrdClStatus.cc b/src/XrdCl/XrdClStatus.cc deleted file mode 100644 index 0b363ad8073..00000000000 --- a/src/XrdCl/XrdClStatus.cc +++ /dev/null @@ -1,132 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#include "XrdCl/XrdClStatus.hh" -#include "XProtocol/XProtocol.hh" -#include - -namespace -{ - using namespace XrdCl; - struct ErrorMap - { - uint16_t code; - const char *msg; - }; - - ErrorMap errors[] = { - { errUnknown, "Unknown error" }, - { errInvalidOp, "Invalid operation" }, - { errFcntl, "Fcntl error" }, - { errPoll, "Poll error" }, - { errConfig, "Configuration error" }, - { errInternal, "Internal error" }, - { errUnknownCommand, "Command not found" }, - { errInvalidArgs, "Invalid arguments" }, - { errInProgress, "Operation in progress" }, - { errUninitialized, "Initialization error" }, - { errOSError, "OS Error" }, - { errNotSupported, "Operation not supported" }, - { errDataError, "Received corrupted data" }, - { errNotImplemented, "Operation is not implemented" }, - { errNoMoreReplicas, "No more replicas to try" }, - { errInvalidAddr, "Invalid address" }, - { errSocketError, "Socket error" }, - { errSocketTimeout, "Socket timeout" }, - { errSocketDisconnected, "Socket disconnected" }, - { errPollerError, "Poller error" }, - { errSocketOptError, "Socket opt error" }, - { errStreamDisconnect, "Stream disconnect" }, - { errConnectionError, "Connection error" }, - { errInvalidSession, "Invalid session" }, - { errInvalidMessage, "Invalid message" }, - { errNotFound, "Resource not found" }, - { errCheckSumError, "CheckSum error" }, - { errRedirectLimit, "Redirect limit has been reached" }, - { errHandShakeFailed, "Hand shake failed" }, - { errLoginFailed, "Login failed" }, - { errAuthFailed, "Auth failed" }, - { errQueryNotSupported, "Query not supported" }, - { errOperationExpired, "Operation expired" }, - { errNoMoreFreeSIDs, "No more free SIDs" }, - { errInvalidRedirectURL, "Invalid redirect URL" }, - { errInvalidResponse, "Invalid response" }, - { errRedirect, "Unhandled redirect" }, - { errErrorResponse, "Error response" }, - { errResponseNegative, "Query response negative" }, - { 0, 0 } }; - - //---------------------------------------------------------------------------- - // Get error code - //---------------------------------------------------------------------------- - std::string GetErrorMessage( uint16_t code ) - { - for( int i = 0; errors[i].msg != 0; ++i ) - if( errors[i].code == code ) - return errors[i].msg; - return "Unknown error code"; - } -} - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - // Create a string representation - //---------------------------------------------------------------------------- - std::string Status::ToString() const - { - std::ostringstream o; - - //-------------------------------------------------------------------------- - // The status is OK - //-------------------------------------------------------------------------- - if( IsOK() ) - { - o << "[SUCCESS] "; - - if( code == suContinue ) - o << "Continue"; - else if( code == suRetry ) - o << "Retry"; - - return o.str(); - } - - //-------------------------------------------------------------------------- - // We have an error - //-------------------------------------------------------------------------- - if( IsFatal() ) - o << "[FATAL] "; - else - o << "[ERROR] "; - - o << GetErrorMessage( code ); - - //-------------------------------------------------------------------------- - // Add errno - //-------------------------------------------------------------------------- - if( errNo >= kXR_ArgInvalid ) // kXR_ArgInvalid is the first (lowest) xrootd error code - // it is used in an inconsistent way sometimes it is - // xrootd error code and sometimes it is a plain errno - o << ": " << strerror( XProtocol::toErrno( errNo ) ); - else if ( errNo ) - o << ": " << strerror( errNo ); - - return o.str(); - } -} diff --git a/src/XrdCl/XrdClStatus.hh b/src/XrdCl/XrdClStatus.hh deleted file mode 100644 index b821f19f377..00000000000 --- a/src/XrdCl/XrdClStatus.hh +++ /dev/null @@ -1,140 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_STATUS_HH__ -#define __XRD_CL_STATUS_HH__ - -#include -#include -#include - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - // Constants - //---------------------------------------------------------------------------- - const uint16_t stOK = 0x0000; //!< Everything went OK - const uint16_t stError = 0x0001; //!< An error occurred that could potentially be retried - const uint16_t stFatal = 0x0003; //!< Fatal error, it's still an error - - //---------------------------------------------------------------------------- - // Additional info for the stOK status - //---------------------------------------------------------------------------- - const uint16_t suDone = 0; - const uint16_t suContinue = 1; - const uint16_t suRetry = 2; - const uint16_t suPartial = 3; - const uint16_t suAlreadyDone = 4; - - //---------------------------------------------------------------------------- - // Generic errors - //---------------------------------------------------------------------------- - const uint16_t errNone = 0; //!< No error - const uint16_t errRetry = 1; //!< Try again for whatever reason - const uint16_t errUnknown = 2; //!< Unknown error - const uint16_t errInvalidOp = 3; //!< The operation cannot be performed in the - //!< given circumstances - const uint16_t errFcntl = 4; //!< failed manipulate file descriptor - const uint16_t errPoll = 5; //!< error while polling descriptors - const uint16_t errConfig = 6; //!< System misconfigured - const uint16_t errInternal = 7; //!< Internal error - const uint16_t errUnknownCommand = 8; - const uint16_t errInvalidArgs = 9; - const uint16_t errInProgress = 10; - const uint16_t errUninitialized = 11; - const uint16_t errOSError = 12; - const uint16_t errNotSupported = 13; - const uint16_t errDataError = 14; //!< data is corrupted - const uint16_t errNotImplemented = 15; //!< Operation is not implemented - const uint16_t errNoMoreReplicas = 16; //!< No more replicas to try - - //---------------------------------------------------------------------------- - // Socket related errors - //---------------------------------------------------------------------------- - const uint16_t errInvalidAddr = 101; - const uint16_t errSocketError = 102; - const uint16_t errSocketTimeout = 103; - const uint16_t errSocketDisconnected = 104; - const uint16_t errPollerError = 105; - const uint16_t errSocketOptError = 106; - const uint16_t errStreamDisconnect = 107; - const uint16_t errConnectionError = 108; - const uint16_t errInvalidSession = 109; - - //---------------------------------------------------------------------------- - // Post Master related errors - //---------------------------------------------------------------------------- - const uint16_t errInvalidMessage = 201; - const uint16_t errHandShakeFailed = 202; - const uint16_t errLoginFailed = 203; - const uint16_t errAuthFailed = 204; - const uint16_t errQueryNotSupported = 205; - const uint16_t errOperationExpired = 206; - - //---------------------------------------------------------------------------- - // XRootD related errors - //---------------------------------------------------------------------------- - const uint16_t errNoMoreFreeSIDs = 301; - const uint16_t errInvalidRedirectURL = 302; - const uint16_t errInvalidResponse = 303; - const uint16_t errNotFound = 304; - const uint16_t errCheckSumError = 305; - const uint16_t errRedirectLimit = 306; - - const uint16_t errErrorResponse = 400; - const uint16_t errRedirect = 401; - - const uint16_t errResponseNegative = 500; //!< Query response was negative - - //---------------------------------------------------------------------------- - //! Procedure execution status - //---------------------------------------------------------------------------- - struct Status - { - //-------------------------------------------------------------------------- - //! Constructor - //-------------------------------------------------------------------------- - Status( uint16_t st = stOK, uint16_t cod = errNone, uint32_t errN = 0 ): - status(st), code(cod), errNo( errN ) {} - - bool IsError() const { return status & stError; } //!< Error - bool IsFatal() const { return (status&0x0002) & stFatal; } //!< Fatal error - bool IsOK() const { return status == stOK; } //!< We're fine - - //-------------------------------------------------------------------------- - //! Get the status code that may be returned to the shell - //-------------------------------------------------------------------------- - int GetShellCode() const - { - if( IsOK() ) - return 0; - return (code/100)+50; - } - - //-------------------------------------------------------------------------- - //! Create a string representation - //-------------------------------------------------------------------------- - std::string ToString() const; - - uint16_t status; //!< Status of the execution - uint16_t code; //!< Error type, or additional hints on what to do - uint32_t errNo; //!< Errno, if any - }; -} - -#endif // __XRD_CL_STATUS_HH__ diff --git a/src/XrdCl/XrdClStream.cc b/src/XrdCl/XrdClStream.cc deleted file mode 100644 index 0f67643832f..00000000000 --- a/src/XrdCl/XrdClStream.cc +++ /dev/null @@ -1,996 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2014 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#include "XrdCl/XrdClStream.hh" -#include "XrdCl/XrdClSocket.hh" -#include "XrdCl/XrdClChannel.hh" -#include "XrdCl/XrdClConstants.hh" -#include "XrdCl/XrdClLog.hh" -#include "XrdCl/XrdClMessage.hh" -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdCl/XrdClUtils.hh" -#include "XrdCl/XrdClOutQueue.hh" -#include "XrdCl/XrdClMonitor.hh" -#include "XrdCl/XrdClAsyncSocketHandler.hh" -#include "XrdCl/XrdClMessageUtils.hh" -#include "XrdCl/XrdClXRootDTransport.hh" - -#include -#include -#include -#include - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - // Outgoing message helper - //---------------------------------------------------------------------------- - struct OutMessageHelper - { - OutMessageHelper( Message *message = 0, - OutgoingMsgHandler *hndlr = 0, - time_t expir = 0, - bool statefu = 0 ): - msg( message ), handler( hndlr ), expires( expir ), stateful( statefu ) {} - void Reset() - { - msg = 0; handler = 0; expires = 0; stateful = 0; - } - Message *msg; - OutgoingMsgHandler *handler; - time_t expires; - bool stateful; - }; - - //---------------------------------------------------------------------------- - // Incoming message helper - //---------------------------------------------------------------------------- - struct InMessageHelper - { - InMessageHelper( Message *message = 0, - IncomingMsgHandler *hndlr = 0, - time_t expir = 0, - uint16_t actio = 0 ): - msg( message ), handler( hndlr ), expires( expir ), action( actio ) {} - void Reset() - { - msg = 0; handler = 0; expires = 0; action = 0; - } - Message *msg; - IncomingMsgHandler *handler; - time_t expires; - uint16_t action; - }; - - //---------------------------------------------------------------------------- - // Sub stream helper - //---------------------------------------------------------------------------- - struct SubStreamData - { - SubStreamData(): socket( 0 ), status( Socket::Disconnected ) - { - outQueue = new OutQueue(); - } - ~SubStreamData() - { - delete socket; - delete outQueue; - } - AsyncSocketHandler *socket; - OutQueue *outQueue; - OutMessageHelper outMsgHelper; - InMessageHelper inMsgHelper; - Socket::SocketStatus status; - }; - - //---------------------------------------------------------------------------- - // Constructor - //---------------------------------------------------------------------------- - Stream::Stream( const URL *url, uint16_t streamNum ): - pUrl( url ), - pStreamNum( streamNum ), - pTransport( 0 ), - pPoller( 0 ), - pTaskManager( 0 ), - pJobManager( 0 ), - pIncomingQueue( 0 ), - pChannelData( 0 ), - pLastStreamError( 0 ), - pConnectionCount( 0 ), - pConnectionInitTime( 0 ), - pAddressType( Utils::IPAll ), - pSessionId( 0 ), - pQueueIncMsgJob(0), - pBytesSent( 0 ), - pBytesReceived( 0 ) - { - pConnectionStarted.tv_sec = 0; pConnectionStarted.tv_usec = 0; - pConnectionDone.tv_sec = 0; pConnectionDone.tv_usec = 0; - - std::ostringstream o; - o << pUrl->GetHostId() << " #" << pStreamNum; - pStreamName = o.str(); - - pConnectionWindow = Utils::GetIntParameter( *url, "ConnectionWindow", - DefaultConnectionWindow ); - pConnectionRetry = Utils::GetIntParameter( *url, "ConnectionRetry", - DefaultConnectionRetry ); - pStreamErrorWindow = Utils::GetIntParameter( *url, "StreamErrorWindow", - DefaultStreamErrorWindow ); - - std::string netStack = Utils::GetStringParameter( *url, "NetworkStack", - DefaultNetworkStack ); - - pAddressType = Utils::String2AddressType( netStack ); - - Log *log = DefaultEnv::GetLog(); - log->Debug( PostMasterMsg, "[%s] Stream parameters: Network Stack: %s, " - "Connection Window: %d, ConnectionRetry: %d, Stream Error " - "Widnow: %d", pStreamName.c_str(), netStack.c_str(), - pConnectionWindow, pConnectionRetry, pStreamErrorWindow ); - } - - //---------------------------------------------------------------------------- - // Destructor - //---------------------------------------------------------------------------- - Stream::~Stream() - { - Disconnect( true ); - - Log *log = DefaultEnv::GetLog(); - log->Debug( PostMasterMsg, "[%s] Destroying stream", - pStreamName.c_str() ); - - MonitorDisconnection( Status() ); - - SubStreamList::iterator it; - for( it = pSubStreams.begin(); it != pSubStreams.end(); ++it ) - delete *it; - - delete pQueueIncMsgJob; - } - - //---------------------------------------------------------------------------- - // Initializer - //---------------------------------------------------------------------------- - Status Stream::Initialize() - { - if( !pTransport || !pPoller || !pChannelData ) - return Status( stError, errUninitialized ); - - AsyncSocketHandler *s = new AsyncSocketHandler( pPoller, - pTransport, - pChannelData, - 0 ); - s->SetStream( this ); - - pSubStreams.push_back( new SubStreamData() ); - pSubStreams[0]->socket = s; - return Status(); - } - - //------------------------------------------------------------------------ - // Make sure that the underlying socket handler gets write readiness - // events - //------------------------------------------------------------------------ - Status Stream::EnableLink( PathID &path ) - { - XrdSysMutexHelper scopedLock( pMutex ); - - //-------------------------------------------------------------------------- - // We are in the process of connecting the main stream, so we do nothing - // because when the main stream connection is established it will connect - // all the other streams - //-------------------------------------------------------------------------- - if( pSubStreams[0]->status == Socket::Connecting ) - return Status(); - - //-------------------------------------------------------------------------- - // The main stream is connected, so we can verify whether we have - // the up and the down stream connected and ready to handle data. - // If anything is not right we fall back to stream 0. - //-------------------------------------------------------------------------- - if( pSubStreams[0]->status == Socket::Connected ) - { - if( pSubStreams[path.down]->status != Socket::Connected ) - path.down = 0; - - if( pSubStreams[path.up]->status == Socket::Disconnected ) - { - path.up = 0; - return pSubStreams[0]->socket->EnableUplink(); - } - - if( pSubStreams[path.up]->status == Socket::Connected ) - return pSubStreams[path.up]->socket->EnableUplink(); - - return Status(); - } - - //-------------------------------------------------------------------------- - // The main stream is not connected, we need to check whether enough time - // has passed since we last encountered an error (if any) so that we could - // re-attempt the connection - //-------------------------------------------------------------------------- - Log *log = DefaultEnv::GetLog(); - time_t now = ::time(0); - - if( now-pLastStreamError < pStreamErrorWindow ) - return pLastFatalError; - - gettimeofday( &pConnectionStarted, 0 ); - ++pConnectionCount; - - //-------------------------------------------------------------------------- - // Resolve all the addresses of the host we're supposed to connect to - //-------------------------------------------------------------------------- - Status st = Utils::GetHostAddresses( pAddresses, *pUrl, pAddressType ); - if( !st.IsOK() ) - { - log->Error( PostMasterMsg, "[%s] Unable to resolve IP address for " - "the host", pStreamName.c_str() ); - pLastStreamError = now; - st.status = stFatal; - pLastFatalError = st; - return st; - } - - Utils::LogHostAddresses( log, PostMasterMsg, pUrl->GetHostId(), - pAddresses ); - - //-------------------------------------------------------------------------- - // Initiate the connection process to the first one on the list. - // It's more efficient to remove addresses from the back of a vector - // so we reverse the it. - //-------------------------------------------------------------------------- - int preferIPv4 = DefaultPreferIPv4; - DefaultEnv::GetEnv()->GetInt( "PreferIPv4", preferIPv4 ); - if( !preferIPv4 ) - std::reverse( pAddresses.begin(), pAddresses.end() ); - - while( !pAddresses.empty() ) - { - pSubStreams[0]->socket->SetAddress( pAddresses.back() ); - pAddresses.pop_back(); - pConnectionInitTime = ::time( 0 ); - st = pSubStreams[0]->socket->Connect( pConnectionWindow ); - if( st.IsOK() ) - { - pSubStreams[0]->status = Socket::Connecting; - break; - } - } - return st; - } - - //---------------------------------------------------------------------------- - // Queue the message for sending - //---------------------------------------------------------------------------- - Status Stream::Send( Message *msg, - OutgoingMsgHandler *handler, - bool stateful, - time_t expires ) - { - XrdSysMutexHelper scopedLock( pMutex ); - Log *log = DefaultEnv::GetLog(); - - //-------------------------------------------------------------------------- - // Check the session ID and bounce if needed - //-------------------------------------------------------------------------- - if( msg->GetSessionId() && - (pSubStreams[0]->status != Socket::Connected || - pSessionId != msg->GetSessionId()) ) - return Status( stError, errInvalidSession ); - - //-------------------------------------------------------------------------- - // Decide on the path to send the message - //-------------------------------------------------------------------------- - PathID path = pTransport->MultiplexSubStream( msg, pStreamNum, - *pChannelData ); - if( pSubStreams.size() <= path.up ) - { - log->Warning( PostMasterMsg, "[%s] Unable to send message %s through " - "substream %d using 0 instead", pStreamName.c_str(), - msg->GetDescription().c_str(), path.up ); - path.up = 0; - } - - log->Dump( PostMasterMsg, "[%s] Sending message %s (0x%x) through " - "substream %d expecting answer at %d", pStreamName.c_str(), - msg->GetDescription().c_str(), msg, path.up, path.down ); - - //-------------------------------------------------------------------------- - // Enable *a* path and insert the message to the right queue - //-------------------------------------------------------------------------- - Status st = EnableLink( path ); - if( st.IsOK() ) - { - pTransport->MultiplexSubStream( msg, pStreamNum, *pChannelData, &path ); - pSubStreams[path.up]->outQueue->PushBack( msg, handler, - expires, stateful ); - } - else - st.status = stFatal; - return st; - } - - //---------------------------------------------------------------------------- - // Force connection - //---------------------------------------------------------------------------- - void Stream::ForceConnect() - { - XrdSysMutexHelper scopedLock( pMutex ); - pSubStreams[0]->status = Socket::Disconnected; - XrdCl::PathID path( 0, 0 ); - XrdCl::Status st = EnableLink( path ); - if( !st.IsOK() ) - OnConnectError( 0, st ); - } - - //---------------------------------------------------------------------------- - // Disconnect the stream - //---------------------------------------------------------------------------- - void Stream::Disconnect( bool /*force*/ ) - { - XrdSysMutexHelper scopedLock( pMutex ); - SubStreamList::iterator it; - for( it = pSubStreams.begin(); it != pSubStreams.end(); ++it ) - { - (*it)->socket->Close(); - (*it)->status = Socket::Disconnected; - } - } - - //---------------------------------------------------------------------------- - // Handle a clock event - //---------------------------------------------------------------------------- - void Stream::Tick( time_t now ) - { - //-------------------------------------------------------------------------- - // Check for timed-out requests and incoming handlers - //-------------------------------------------------------------------------- - pMutex.Lock(); - OutQueue q; - SubStreamList::iterator it; - for( it = pSubStreams.begin(); it != pSubStreams.end(); ++it ) - q.GrabExpired( *(*it)->outQueue, now ); - pMutex.UnLock(); - - q.Report( Status( stError, errOperationExpired ) ); - if( pStreamNum == 0 ) - pIncomingQueue->ReportTimeout( now ); - } -} - -//------------------------------------------------------------------------------ -// Handle message timeouts and reconnection in the future -//------------------------------------------------------------------------------ -namespace -{ - class StreamConnectorTask: public XrdCl::Task - { - public: - //------------------------------------------------------------------------ - // Constructor - //------------------------------------------------------------------------ - StreamConnectorTask( XrdCl::Stream *stream ): - pStream( stream ) - { - std::string name = "StreamConnectorTask for "; - name += stream->GetName(); - SetName( name ); - } - - //------------------------------------------------------------------------ - // Run the task - //------------------------------------------------------------------------ - time_t Run( time_t ) - { - pStream->ForceConnect(); - return 0; - } - - private: - XrdCl::Stream *pStream; - }; -} - -namespace XrdCl -{ - Status Stream::RequestClose( Message *response ) - { - ServerResponse *rsp = reinterpret_cast( response->GetBuffer() ); - if( rsp->hdr.dlen < 4 ) return Status( stError ); - Message *msg; - ClientCloseRequest *req; - MessageUtils::CreateRequest( msg, req ); - req->requestid = kXR_close; - memcpy( req->fhandle, reinterpret_cast( rsp->body.buffer.data ), 4 ); - XRootDTransport::SetDescription( msg ); - msg->SetSessionId( pSessionId ); - NullResponseHandler *handler = new NullResponseHandler(); - MessageSendParams params; - params.timeout = 0; - params.followRedirects = false; - params.stateful = true; - MessageUtils::ProcessSendParams( params ); - return MessageUtils::SendMessage( *pUrl, msg, handler, params, 0 ); - } - - //---------------------------------------------------------------------------- - // Call back when a message has been reconstructed - //---------------------------------------------------------------------------- - void Stream::OnIncoming( uint16_t subStream, - Message *msg, - uint32_t bytesReceived ) - { - msg->SetSessionId( pSessionId ); - pBytesReceived += bytesReceived; - - uint32_t streamAction = pTransport->MessageReceived( msg, pStreamNum, - subStream, - *pChannelData ); - if( streamAction & TransportHandler::DigestMsg ) - return; - - if( streamAction & TransportHandler::RequestClose ) - { - RequestClose( msg ); - delete msg; - return; - } - - //-------------------------------------------------------------------------- - // No handler, we cache and see what comes later - //-------------------------------------------------------------------------- - Log *log = DefaultEnv::GetLog(); - InMessageHelper &mh = pSubStreams[subStream]->inMsgHelper; - - if( !mh.handler ) - { - log->Dump( PostMasterMsg, "[%s] Queuing received message: 0x%x.", - pStreamName.c_str(), msg ); - - pJobManager->QueueJob( pQueueIncMsgJob, msg ); - return; - } - - //-------------------------------------------------------------------------- - // We have a handler, so we call the callback - //-------------------------------------------------------------------------- - log->Dump( PostMasterMsg, "[%s] Handling received message: 0x%x.", - pStreamName.c_str(), msg ); - - if( !(mh.action & IncomingMsgHandler::RemoveHandler) ) - pIncomingQueue->ReAddMessageHandler( mh.handler, mh.expires ); - - if( mh.action & (IncomingMsgHandler::NoProcess|IncomingMsgHandler::Ignore) ) - { - log->Dump( PostMasterMsg, "[%s] Ignoring the processing handler for: 0x%x.", - pStreamName.c_str(), msg->GetDescription().c_str() ); - bool delit = ( mh.action & IncomingMsgHandler::Ignore ); - mh.Reset(); - if (delit) delete msg; - return; - } - - Job *job = new HandleIncMsgJob( mh.handler ); - mh.Reset(); - pJobManager->QueueJob( job, msg ); - } - - //---------------------------------------------------------------------------- - // Call when one of the sockets is ready to accept a new message - //---------------------------------------------------------------------------- - std::pair - Stream::OnReadyToWrite( uint16_t subStream ) - { - XrdSysMutexHelper scopedLock( pMutex ); - Log *log = DefaultEnv::GetLog(); - if( pSubStreams[subStream]->outQueue->IsEmpty() ) - { - log->Dump( PostMasterMsg, "[%s] Nothing to write, disable uplink", - pSubStreams[subStream]->socket->GetStreamName().c_str() ); - - pSubStreams[subStream]->socket->DisableUplink(); - return std::make_pair( (Message *)0, (OutgoingMsgHandler *)0 ); - } - - OutMessageHelper &h = pSubStreams[subStream]->outMsgHelper; - h.msg = pSubStreams[subStream]->outQueue->PopMessage( h.handler, - h.expires, - h.stateful ); - scopedLock.UnLock(); - if( h.handler ) - h.handler->OnReadyToSend( h.msg, pStreamNum ); - return std::make_pair( h.msg, h.handler ); - } - - void Stream::DisableIfEmpty( uint16_t subStream ) - { - XrdSysMutexHelper scopedLock( pMutex ); - Log *log = DefaultEnv::GetLog(); - - if( pSubStreams[subStream]->outQueue->IsEmpty() ) - { - log->Dump( PostMasterMsg, "[%s] All messages consumed, disable uplink", - pSubStreams[subStream]->socket->GetStreamName().c_str() ); - pSubStreams[subStream]->socket->DisableUplink(); - } - } - - //---------------------------------------------------------------------------- - // Call when a message is written to the socket - //---------------------------------------------------------------------------- - void Stream::OnMessageSent( uint16_t subStream, - Message *msg, - uint32_t bytesSent ) - { - pTransport->MessageSent( msg, pStreamNum, subStream, bytesSent, - *pChannelData ); - OutMessageHelper &h = pSubStreams[subStream]->outMsgHelper; - pBytesSent += bytesSent; - if( h.handler ) - h.handler->OnStatusReady( msg, Status() ); - pSubStreams[subStream]->outMsgHelper.Reset(); - } - - //---------------------------------------------------------------------------- - // Call back when a message has been reconstructed - //---------------------------------------------------------------------------- - void Stream::OnConnect( uint16_t subStream ) - { - XrdSysMutexHelper scopedLock( pMutex ); - pSubStreams[subStream]->status = Socket::Connected; - Log *log = DefaultEnv::GetLog(); - log->Debug( PostMasterMsg, "[%s] Stream %d connected.", pStreamName.c_str(), - subStream ); - - if( subStream == 0 ) - { - pLastStreamError = 0; - pLastFatalError = Status(); - pConnectionCount = 0; - uint16_t numSub = pTransport->SubStreamNumber( *pChannelData ); - ++pSessionId; - - //------------------------------------------------------------------------ - // Create the streams if they don't exist yet - //------------------------------------------------------------------------ - if( pSubStreams.size() == 1 && numSub > 1 ) - { - for( uint16_t i = 1; i < numSub; ++i ) - { - AsyncSocketHandler *s = new AsyncSocketHandler( pPoller, pTransport, - pChannelData, i ); - s->SetStream( this ); - pSubStreams.push_back( new SubStreamData() ); - pSubStreams[i]->socket = s; - } - } - - //------------------------------------------------------------------------ - // Connect the extra streams, if we fail we move all the outgoing items - // to stream 0, we don't need to enable the uplink here, because it - // should be already enabled after the handshaking process is completed. - //------------------------------------------------------------------------ - if( pSubStreams.size() > 1 ) - { - log->Debug( PostMasterMsg, "[%s] Attempting to connect %d additional " - "streams.", pStreamName.c_str(), pSubStreams.size()-1 ); - for( size_t i = 1; i < pSubStreams.size(); ++i ) - { - pSubStreams[i]->socket->SetAddress( pSubStreams[0]->socket->GetAddress() ); - Status st = pSubStreams[i]->socket->Connect( pConnectionWindow ); - if( !st.IsOK() ) - { - pSubStreams[0]->outQueue->GrabItems( *pSubStreams[i]->outQueue ); - pSubStreams[i]->socket->Close(); - } - else - { - pSubStreams[i]->status = Socket::Connecting; - } - } - } - - //------------------------------------------------------------------------ - // Inform monitoring - //------------------------------------------------------------------------ - pBytesSent = 0; - pBytesReceived = 0; - gettimeofday( &pConnectionDone, 0 ); - Monitor *mon = DefaultEnv::GetMonitor(); - if( mon ) - { - Monitor::ConnectInfo i; - i.server = pUrl->GetHostId(); - i.sTOD = pConnectionStarted; - i.eTOD = pConnectionDone; - i.streams = pSubStreams.size(); - - AnyObject qryResult; - std::string *qryResponse = 0; - pTransport->Query( TransportQuery::Auth, qryResult, *pChannelData ); - qryResult.Get( qryResponse ); - i.auth = *qryResponse; - delete qryResponse; - mon->Event( Monitor::EvConnect, &i ); - } - } - } - - //---------------------------------------------------------------------------- - // On connect error - //---------------------------------------------------------------------------- - void Stream::OnConnectError( uint16_t subStream, Status status ) - { - XrdSysMutexHelper scopedLock( pMutex ); - Log *log = DefaultEnv::GetLog(); - pSubStreams[subStream]->socket->Close(); - time_t now = ::time(0); - - //-------------------------------------------------------------------------- - // If we connected subStream == 0 and cannot connect >0 then we just give - // up and move the outgoing messages to another queue - //-------------------------------------------------------------------------- - if( subStream > 0 ) - { - pSubStreams[subStream]->status = Socket::Disconnected; - pSubStreams[0]->outQueue->GrabItems( *pSubStreams[subStream]->outQueue ); - if( pSubStreams[0]->status == Socket::Connected ) - { - Status st = pSubStreams[0]->socket->EnableUplink(); - if( !st.IsOK() ) - OnFatalError( 0, st, scopedLock ); - return; - } - - if( pSubStreams[0]->status == Socket::Connecting ) - return; - - OnFatalError( subStream, status, scopedLock ); - return; - } - - //-------------------------------------------------------------------------- - // Check if we still have time to try and do something in the current window - //-------------------------------------------------------------------------- - time_t elapsed = now-pConnectionInitTime; - log->Error( PostMasterMsg, "[%s] elapsed = %d, pConnectionWindow = %d " - "seconds.", pStreamName.c_str(), elapsed, pConnectionWindow ); - - //------------------------------------------------------------------------ - // If we have some IP addresses left we try them - //------------------------------------------------------------------------ - if( !pAddresses.empty() ) - { - Status st; - do - { - pSubStreams[0]->socket->SetAddress( pAddresses.back() ); - pAddresses.pop_back(); - pConnectionInitTime = ::time( 0 ); - st = pSubStreams[0]->socket->Connect( pConnectionWindow ); - } - while( !pAddresses.empty() && !st.IsOK() ); - - if( !st.IsOK() ) - OnFatalError( subStream, st, scopedLock ); - - return; - } - //------------------------------------------------------------------------ - // If we still can retry with the same host name, we sleep until the end - // of the connection window and try - //------------------------------------------------------------------------ - else if( elapsed < pConnectionWindow && pConnectionCount < pConnectionRetry - && !status.IsFatal() ) - { - log->Info( PostMasterMsg, "[%s] Attempting reconnection in %d " - "seconds.", pStreamName.c_str(), pConnectionWindow-elapsed ); - - Task *task = new ::StreamConnectorTask( this ); - pTaskManager->RegisterTask( task, pConnectionInitTime+pConnectionWindow ); - return; - } - //-------------------------------------------------------------------------- - // We are out of the connection window, the only thing we can do here - // is re-resolving the host name and retrying if we still can - //-------------------------------------------------------------------------- - else if( pConnectionCount < pConnectionRetry && !status.IsFatal() ) - { - pAddresses.clear(); - pSubStreams[0]->status = Socket::Disconnected; - PathID path( 0, 0 ); - Status st = EnableLink( path ); - if( !st.IsOK() ) - OnFatalError( subStream, st, scopedLock ); - return; - } - - //-------------------------------------------------------------------------- - // Else, we fail - //-------------------------------------------------------------------------- - OnFatalError( subStream, status, scopedLock ); - } - - //---------------------------------------------------------------------------- - // Call back when an error has occurred - //---------------------------------------------------------------------------- - void Stream::OnError( uint16_t subStream, Status status ) - { - XrdSysMutexHelper scopedLock( pMutex ); - Log *log = DefaultEnv::GetLog(); - pSubStreams[subStream]->socket->Close(); - pSubStreams[subStream]->status = Socket::Disconnected; - - log->Debug( PostMasterMsg, "[%s] Recovering error for stream #%d: %s.", - pStreamName.c_str(), subStream, status.ToString().c_str() ); - - //-------------------------------------------------------------------------- - // Reinsert the stuff that we have failed to sent - //-------------------------------------------------------------------------- - if( pSubStreams[subStream]->outMsgHelper.msg ) - { - OutMessageHelper &h = pSubStreams[subStream]->outMsgHelper; - pSubStreams[subStream]->outQueue->PushFront( h.msg, h.handler, h.expires, - h.stateful ); - pSubStreams[subStream]->outMsgHelper.Reset(); - } - - //-------------------------------------------------------------------------- - // Reinsert the receiving handler - //-------------------------------------------------------------------------- - if( pSubStreams[subStream]->inMsgHelper.handler ) - { - InMessageHelper &h = pSubStreams[subStream]->inMsgHelper; - pIncomingQueue->ReAddMessageHandler( h.handler, h.expires ); - h.Reset(); - } - - //-------------------------------------------------------------------------- - // We are dealing with an error of a peripheral stream. If we don't have - // anything to send don't bother recovering. Otherwise move the requests - // to stream 0 if possible. - //-------------------------------------------------------------------------- - if( subStream > 0 ) - { - if( pSubStreams[subStream]->outQueue->IsEmpty() ) - return; - - if( pSubStreams[0]->status != Socket::Disconnected ) - { - pSubStreams[0]->outQueue->GrabItems( *pSubStreams[subStream]->outQueue ); - if( pSubStreams[0]->status == Socket::Connected ) - { - Status st = pSubStreams[0]->socket->EnableUplink(); - if( !st.IsOK() ) - OnFatalError( 0, st, scopedLock ); - return; - } - } - OnFatalError( subStream, status, scopedLock ); - return; - } - - //-------------------------------------------------------------------------- - // If we lost the stream 0 we have lost the session, we re-enable the - // stream if we still have things in one of the outgoing queues, otherwise - // there is not point to recover at this point. - //-------------------------------------------------------------------------- - if( subStream == 0 ) - { - MonitorDisconnection( status ); - - SubStreamList::iterator it; - size_t outstanding = 0; - for( it = pSubStreams.begin(); it != pSubStreams.end(); ++it ) - outstanding += (*it)->outQueue->GetSizeStateless(); - - if( outstanding ) - { - PathID path( 0, 0 ); - Status st = EnableLink( path ); - if( !st.IsOK() ) - { - OnFatalError( 0, st, scopedLock ); - return; - } - } - - //------------------------------------------------------------------------ - // We're done here, unlock the stream mutex to avoid deadlocks and - // report the disconnection event to the handlers - //------------------------------------------------------------------------ - log->Debug( PostMasterMsg, "[%s] Reporting disconnection to queued " - "message handlers.", pStreamName.c_str() ); - OutQueue q; - for( it = pSubStreams.begin(); it != pSubStreams.end(); ++it ) - q.GrabStateful( *(*it)->outQueue ); - scopedLock.UnLock(); - - q.Report( status ); - pIncomingQueue->ReportStreamEvent( IncomingMsgHandler::Broken, - pStreamNum, status ); - pChannelEvHandlers.ReportEvent( ChannelEventHandler::StreamBroken, status, - pStreamNum ); - return; - } - } - - //---------------------------------------------------------------------------- - // On fatal error - //---------------------------------------------------------------------------- - void Stream::OnFatalError( uint16_t subStream, - Status status, - XrdSysMutexHelper &lock ) - { - Log *log = DefaultEnv::GetLog(); - pSubStreams[subStream]->status = Socket::Disconnected; - log->Error( PostMasterMsg, "[%s] Unable to recover: %s.", - pStreamName.c_str(), status.ToString().c_str() ); - - pConnectionCount = 0; - pLastStreamError = ::time(0); - pLastFatalError = status; - - SubStreamList::iterator it; - OutQueue q; - for( it = pSubStreams.begin(); it != pSubStreams.end(); ++it ) - q.GrabItems( *(*it)->outQueue ); - lock.UnLock(); - - status.status = stFatal; - q.Report( status ); - pIncomingQueue->ReportStreamEvent( IncomingMsgHandler::FatalError, - pStreamNum, status ); - pChannelEvHandlers.ReportEvent( ChannelEventHandler::FatalError, status, - pStreamNum ); - - } - - //---------------------------------------------------------------------------- - // Inform monitoring about disconnection - //---------------------------------------------------------------------------- - void Stream::MonitorDisconnection( Status status ) - { - Monitor *mon = DefaultEnv::GetMonitor(); - if( mon ) - { - Monitor::DisconnectInfo i; - i.server = pUrl->GetHostId(); - i.rBytes = pBytesReceived; - i.sBytes = pBytesSent; - i.cTime = ::time(0) - pConnectionDone.tv_sec; - i.status = status; - mon->Event( Monitor::EvDisconnect, &i ); - } - } - - //---------------------------------------------------------------------------- - // Call back when a message has been reconstructed - //---------------------------------------------------------------------------- - void Stream::OnReadTimeout( uint16_t substream, bool &isBroken ) - { - isBroken = false; - - //-------------------------------------------------------------------------- - // We only take the main stream into account - //-------------------------------------------------------------------------- - if( substream != 0 ) - return; - - //-------------------------------------------------------------------------- - // Check if there is no outgoing messages and if the stream TTL is elapesed. - // It is assumed that the underlying transport makes sure that there is no - // pending requests that are not answered, ie. all possible virtual streams - // are de-allocated - //-------------------------------------------------------------------------- - Log *log = DefaultEnv::GetLog(); - SubStreamList::iterator it; - time_t now = time(0); - - XrdSysMutexHelper scopedLock( pMutex ); - uint32_t outgoingMessages = 0; - time_t lastActivity = 0; - for( it = pSubStreams.begin(); it != pSubStreams.end(); ++it ) - { - outgoingMessages += (*it)->outQueue->GetSize(); - time_t sockLastActivity = (*it)->socket->GetLastActivity(); - if( lastActivity < sockLastActivity ) - lastActivity = sockLastActivity; - } - - if( !outgoingMessages ) - { - bool disconnect = pTransport->IsStreamTTLElapsed( now-lastActivity, - pStreamNum, - *pChannelData ); - if( disconnect ) - { - log->Debug( PostMasterMsg, "[%s] Stream TTL elapsed, disconnecting...", - pStreamName.c_str() ); - Disconnect(); - } - } - - //-------------------------------------------------------------------------- - // Check if the stream is broken - //-------------------------------------------------------------------------- - Status st = pTransport->IsStreamBroken( now-lastActivity, - pStreamNum, - *pChannelData ); - if( !st.IsOK() ) - { - isBroken = true; - scopedLock.UnLock(); - OnError( substream, st ); - } - } - - //---------------------------------------------------------------------------- - // Call back when a message has been reconstru - //---------------------------------------------------------------------------- - void Stream::OnWriteTimeout( uint16_t /*substream*/ ) - { - } - - //---------------------------------------------------------------------------- - // Register channel event handler - //---------------------------------------------------------------------------- - void Stream::RegisterEventHandler( ChannelEventHandler *handler ) - { - pChannelEvHandlers.AddHandler( handler ); - } - - //---------------------------------------------------------------------------- - // Remove a channel event handler - //---------------------------------------------------------------------------- - void Stream::RemoveEventHandler( ChannelEventHandler *handler ) - { - pChannelEvHandlers.RemoveHandler( handler ); - } - - //---------------------------------------------------------------------------- - // Install a incoming message handler - //---------------------------------------------------------------------------- - std::pair - Stream::InstallIncHandler( Message *msg, uint16_t stream ) - { - InMessageHelper &mh = pSubStreams[stream]->inMsgHelper; - if( !mh.handler ) - mh.handler = pIncomingQueue->GetHandlerForMessage( msg, - mh.expires, - mh.action ); - - if( !mh.handler ) - return std::make_pair( (IncomingMsgHandler*)0, false ); - - bool ownership = mh.action & IncomingMsgHandler::Take; - if( mh.action & IncomingMsgHandler::Raw ) - return std::make_pair( mh.handler, ownership ); - return std::make_pair( (IncomingMsgHandler*)0, ownership ); - } -} diff --git a/src/XrdCl/XrdClStream.hh b/src/XrdCl/XrdClStream.hh deleted file mode 100644 index 94052171a35..00000000000 --- a/src/XrdCl/XrdClStream.hh +++ /dev/null @@ -1,351 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_STREAM_HH__ -#define __XRD_CL_STREAM_HH__ - -#include "XrdCl/XrdClPoller.hh" -#include "XrdCl/XrdClStatus.hh" -#include "XrdCl/XrdClURL.hh" -#include "XrdCl/XrdClPostMasterInterfaces.hh" -#include "XrdCl/XrdClChannelHandlerList.hh" -#include "XrdCl/XrdClJobManager.hh" -#include "XrdCl/XrdClInQueue.hh" -#include "XrdCl/XrdClUtils.hh" - -#include "XrdSys/XrdSysPthread.hh" -#include "XrdNet/XrdNetAddr.hh" -#include -#include - -namespace XrdCl -{ - class Message; - class Channel; - class TransportHandler; - class TaskManager; - struct SubStreamData; - - //---------------------------------------------------------------------------- - //! Stream - //---------------------------------------------------------------------------- - class Stream - { - public: - //------------------------------------------------------------------------ - //! Status of the stream - //------------------------------------------------------------------------ - enum StreamStatus - { - Disconnected = 0, //!< Not connected - Connected = 1, //!< Connected - Connecting = 2, //!< In the process of being connected - Error = 3 //!< Broken - }; - - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - Stream( const URL *url, uint16_t streamNum ); - - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - ~Stream(); - - //------------------------------------------------------------------------ - //! Initializer - //------------------------------------------------------------------------ - Status Initialize(); - - //------------------------------------------------------------------------ - //! Queue the message for sending - //------------------------------------------------------------------------ - Status Send( Message *msg, - OutgoingMsgHandler *handler, - bool stateful, - time_t expires ); - - //------------------------------------------------------------------------ - //! Set the transport - //------------------------------------------------------------------------ - void SetTransport( TransportHandler *transport ) - { - pTransport = transport; - } - - //------------------------------------------------------------------------ - //! Set the poller - //------------------------------------------------------------------------ - void SetPoller( Poller *poller ) - { - pPoller = poller; - } - - //------------------------------------------------------------------------ - //! Set the incoming queue - //------------------------------------------------------------------------ - void SetIncomingQueue( InQueue *incomingQueue ) - { - pIncomingQueue = incomingQueue; - delete pQueueIncMsgJob; - pQueueIncMsgJob = new QueueIncMsgJob( incomingQueue ); - } - - //------------------------------------------------------------------------ - //! Set the channel data - //------------------------------------------------------------------------ - void SetChannelData( AnyObject *channelData ) - { - pChannelData = channelData; - } - - //------------------------------------------------------------------------ - //! Set task manager - //------------------------------------------------------------------------ - void SetTaskManager( TaskManager *taskManager ) - { - pTaskManager = taskManager; - } - - //------------------------------------------------------------------------ - //! Set job manager - //------------------------------------------------------------------------ - void SetJobManager( JobManager *jobManager ) - { - pJobManager = jobManager; - } - - //------------------------------------------------------------------------ - //! Connect if needed, otherwise make sure that the underlying socket - //! handler gets write readiness events, it will update the path with - //! what it has actually enabled - //------------------------------------------------------------------------ - Status EnableLink( PathID &path ); - - //------------------------------------------------------------------------ - //! Disconnect the stream - //------------------------------------------------------------------------ - void Disconnect( bool force = false ); - - //------------------------------------------------------------------------ - //! Handle a clock event generated either by socket timeout, or by - //! the task manager event - //------------------------------------------------------------------------ - void Tick( time_t now ); - - //------------------------------------------------------------------------ - //! Get the URL - //------------------------------------------------------------------------ - const URL *GetURL() const - { - return pUrl; - } - - //------------------------------------------------------------------------ - //! Get the stream number - //------------------------------------------------------------------------ - uint16_t GetStreamNumber() const - { - return pStreamNum; - } - - //------------------------------------------------------------------------ - //! Force connection - //------------------------------------------------------------------------ - void ForceConnect(); - - //------------------------------------------------------------------------ - //! Return stream name - //------------------------------------------------------------------------ - const std::string &GetName() const - { - return pStreamName; - } - - //------------------------------------------------------------------------ - //! Disables respective uplink if empty - //------------------------------------------------------------------------ - void DisableIfEmpty( uint16_t subStream ); - - //------------------------------------------------------------------------ - //! Call back when a message has been reconstructed - //------------------------------------------------------------------------ - void OnIncoming( uint16_t subStream, - Message *msg, - uint32_t bytesReceived ); - - //------------------------------------------------------------------------ - // Call when one of the sockets is ready to accept a new message - //------------------------------------------------------------------------ - std::pair - OnReadyToWrite( uint16_t subStream ); - - //------------------------------------------------------------------------ - // Call when a message is written to the socket - //------------------------------------------------------------------------ - void OnMessageSent( uint16_t subStream, - Message *msg, - uint32_t bytesSent ); - - //------------------------------------------------------------------------ - //! Call back when a message has been reconstructed - //------------------------------------------------------------------------ - void OnConnect( uint16_t subStream ); - - //------------------------------------------------------------------------ - //! On connect error - //------------------------------------------------------------------------ - void OnConnectError( uint16_t subStream, Status status ); - - //------------------------------------------------------------------------ - //! On error - //------------------------------------------------------------------------ - void OnError( uint16_t subStream, Status status ); - - //------------------------------------------------------------------------ - //! On read timeout - //------------------------------------------------------------------------ - void OnReadTimeout( uint16_t subStream, bool &isBroken ); - - //------------------------------------------------------------------------ - //! On write timeout - //------------------------------------------------------------------------ - void OnWriteTimeout( uint16_t subStream ); - - //------------------------------------------------------------------------ - //! Register channel event handler - //------------------------------------------------------------------------ - void RegisterEventHandler( ChannelEventHandler *handler ); - - //------------------------------------------------------------------------ - //! Remove a channel event handler - //------------------------------------------------------------------------ - void RemoveEventHandler( ChannelEventHandler *handler ); - - //------------------------------------------------------------------------ - //! Install a message handler for the given message if there is one - //! available, if the handler want's to be called in the raw mode - //! it will be returned, the message ownership flag is returned - //! in any case - //! - //! @param msg message header - //! @param stream stream concerned - //! @return a pair containing the handler and ownership flag - //------------------------------------------------------------------------ - std::pair - InstallIncHandler( Message *msg, uint16_t stream ); - - private: - - //------------------------------------------------------------------------ - // Job queuing the incoming messages - //------------------------------------------------------------------------ - class QueueIncMsgJob: public Job - { - public: - QueueIncMsgJob( InQueue *queue ): pQueue( queue ) {}; - virtual ~QueueIncMsgJob() {}; - virtual void Run( void *arg ) - { - Message *msg = (Message *)arg; - pQueue->AddMessage( msg ); - } - private: - InQueue *pQueue; - }; - - //------------------------------------------------------------------------ - // Job handling the incoming messages - //------------------------------------------------------------------------ - class HandleIncMsgJob: public Job - { - public: - HandleIncMsgJob( IncomingMsgHandler *handler ): pHandler( handler ) {}; - virtual ~HandleIncMsgJob() {}; - virtual void Run( void *arg ) - { - Message *msg = (Message *)arg; - pHandler->Process( msg ); - delete this; - } - private: - IncomingMsgHandler *pHandler; - }; - - //------------------------------------------------------------------------ - //! On fatal error - unlocks the stream - //------------------------------------------------------------------------ - void OnFatalError( uint16_t subStream, - Status status, - XrdSysMutexHelper &lock ); - - //------------------------------------------------------------------------ - //! Inform the monitoring about disconnection - //------------------------------------------------------------------------ - void MonitorDisconnection( Status status ); - - //------------------------------------------------------------------------ - //! Send close after an open request timed out - //------------------------------------------------------------------------ - Status RequestClose( Message *resp ); - - typedef std::vector SubStreamList; - - //------------------------------------------------------------------------ - // Data members - //------------------------------------------------------------------------ - const URL *pUrl; - uint16_t pStreamNum; - std::string pStreamName; - TransportHandler *pTransport; - Poller *pPoller; - TaskManager *pTaskManager; - JobManager *pJobManager; - XrdSysRecMutex pMutex; - InQueue *pIncomingQueue; - AnyObject *pChannelData; - uint32_t pLastStreamError; - Status pLastFatalError; - uint16_t pStreamErrorWindow; - uint16_t pConnectionCount; - uint16_t pConnectionRetry; - time_t pConnectionInitTime; - uint16_t pConnectionWindow; - SubStreamList pSubStreams; - std::vector pAddresses; - Utils::AddressType pAddressType; - ChannelHandlerList pChannelEvHandlers; - uint64_t pSessionId; - - //------------------------------------------------------------------------ - // Jobs - //------------------------------------------------------------------------ - QueueIncMsgJob *pQueueIncMsgJob; - - //------------------------------------------------------------------------ - // Monitoring info - //------------------------------------------------------------------------ - timeval pConnectionStarted; - timeval pConnectionDone; - uint64_t pBytesSent; - uint64_t pBytesReceived; - }; -} - -#endif // __XRD_CL_STREAM_HH__ diff --git a/src/XrdCl/XrdClSyncQueue.hh b/src/XrdCl/XrdClSyncQueue.hh deleted file mode 100644 index 0e48f020fde..00000000000 --- a/src/XrdCl/XrdClSyncQueue.hh +++ /dev/null @@ -1,107 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2013 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_SYNC_QUEUE_HH__ -#define __XRD_CL_SYNC_QUEUE_HH__ - -#include - -#include "XrdSys/XrdSysPthread.hh" -#include "XrdCl/XrdClUglyHacks.hh" - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - //! A synchronized queue - //---------------------------------------------------------------------------- - template - class SyncQueue - { - public: - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - SyncQueue() - { - pSem = new Semaphore(0); - }; - - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - ~SyncQueue() - { - delete pSem; - } - - //------------------------------------------------------------------------ - //! Put the item in the queue - //------------------------------------------------------------------------ - void Put( const Item &item ) - { - XrdSysMutexHelper scopedLock( pMutex ); - pQueue.push( item ); - pSem->Post(); - } - - //------------------------------------------------------------------------ - //! Get the item from the front of the queue - //------------------------------------------------------------------------ - Item Get() - { - pSem->Wait(); - XrdSysMutexHelper scopedLock( pMutex ); - - // this is not possible, so when it happens we commit a suicide - if( pQueue.empty() ) - abort(); - - Item i = pQueue.front(); - pQueue.pop(); - return i; - } - - //------------------------------------------------------------------------ - //! Clear the queue - //------------------------------------------------------------------------ - void Clear() - { - XrdSysMutexHelper scopedLock( pMutex ); - while( !pQueue.empty() ) - pQueue.pop(); - delete pSem; - pSem = new Semaphore(0); - } - - //------------------------------------------------------------------------ - //! Check if the queue is empty - //------------------------------------------------------------------------ - bool IsEmpty() - { - XrdSysMutexHelper scopedLock( pMutex ); - return pQueue.empty(); - } - - protected: - std::queue pQueue; - XrdSysMutex pMutex; - Semaphore *pSem; - }; -} - -#endif // __XRD_CL_ANY_OBJECT_HH__ diff --git a/src/XrdCl/XrdClTPFallBackCopyJob.cc b/src/XrdCl/XrdClTPFallBackCopyJob.cc deleted file mode 100644 index 90620cada7d..00000000000 --- a/src/XrdCl/XrdClTPFallBackCopyJob.cc +++ /dev/null @@ -1,88 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2014 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#include "XrdCl/XrdClTPFallBackCopyJob.hh" -#include "XrdCl/XrdClThirdPartyCopyJob.hh" -#include "XrdCl/XrdClClassicCopyJob.hh" -#include "XrdCl/XrdClConstants.hh" -#include "XrdCl/XrdClLog.hh" -#include "XrdCl/XrdClDefaultEnv.hh" -#include - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - // Constructor - //---------------------------------------------------------------------------- - TPFallBackCopyJob::TPFallBackCopyJob( uint16_t jobId, - PropertyList *jobProperties, - PropertyList *jobResults ): - CopyJob( jobId, jobProperties, jobResults ), - pJob( 0 ) - { - Log *log = DefaultEnv::GetLog(); - log->Debug( UtilityMsg, "Creating a third party fall back copy job, " - "from %s to %s", GetSource().GetURL().c_str(), - GetTarget().GetURL().c_str() ); - } - - //------------------------------------------------------------------------ - // Destructor - //------------------------------------------------------------------------ - TPFallBackCopyJob::~TPFallBackCopyJob() - { - delete pJob; - } - - //---------------------------------------------------------------------------- - // Run the copy job - //---------------------------------------------------------------------------- - XRootDStatus TPFallBackCopyJob::Run( CopyProgressHandler *progress ) - { - //-------------------------------------------------------------------------- - // Set up the job - //-------------------------------------------------------------------------- - std::string tmp; - bool tpcFallBack = false; - - pProperties->Get( "thirdParty", tmp ); - if( tmp == "first" ) - tpcFallBack = true; - - XRootDStatus st = ThirdPartyCopyJob::CanDo( GetSource(), GetTarget(), - pProperties ); - - if( st.IsOK() ) - pJob = new ThirdPartyCopyJob( pJobId, pProperties, pResults ); - else if( tpcFallBack && !st.IsFatal() ) - pJob = new ClassicCopyJob( pJobId, pProperties, pResults ); - else - return st; - - //-------------------------------------------------------------------------- - // Run the job - //-------------------------------------------------------------------------- - return pJob->Run( progress ); - } -} diff --git a/src/XrdCl/XrdClTPFallBackCopyJob.hh b/src/XrdCl/XrdClTPFallBackCopyJob.hh deleted file mode 100644 index d711ee936e1..00000000000 --- a/src/XrdCl/XrdClTPFallBackCopyJob.hh +++ /dev/null @@ -1,61 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2014 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_TP_FALLBACK_COPY_JOB_HH__ -#define __XRD_CL_TP_FALLBACK_COPY_JOB_HH__ - -#include "XrdCl/XrdClCopyProcess.hh" -#include "XrdCl/XrdClCopyJob.hh" - -namespace XrdCl -{ - class TPFallBackCopyJob: public CopyJob - { - public: - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - TPFallBackCopyJob( uint16_t jobId, - PropertyList *jobProperties, - PropertyList *jobResults ); - - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - virtual ~TPFallBackCopyJob(); - - //------------------------------------------------------------------------ - //! Run the copy job - //! - //! @param progress the handler to be notified about the copy progress - //! @return status of the copy operation - //------------------------------------------------------------------------ - virtual XRootDStatus Run( CopyProgressHandler *progress = 0 ); - - private: - CopyJob *pJob; - }; -} - -#endif // __XRD_CL_TP_FALLBACK_COPY_JOB__ diff --git a/src/XrdCl/XrdClTaskManager.cc b/src/XrdCl/XrdClTaskManager.cc deleted file mode 100644 index 86664f66089..00000000000 --- a/src/XrdCl/XrdClTaskManager.cc +++ /dev/null @@ -1,247 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#include "XrdCl/XrdClTaskManager.hh" -#include "XrdCl/XrdClLog.hh" -#include "XrdCl/XrdClUtils.hh" -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdCl/XrdClConstants.hh" -#include "XrdSys/XrdSysTimer.hh" - -#include - -//------------------------------------------------------------------------------ -// The thread -//------------------------------------------------------------------------------ -extern "C" -{ - static void *RunRunnerThread( void *arg ) - { - using namespace XrdCl; - TaskManager *mgr = (TaskManager*)arg; - mgr->RunTasks(); - return 0; - } -} - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - // Constructor - //---------------------------------------------------------------------------- - TaskManager::TaskManager(): pResolution(1), pRunnerThread(0), pRunning(false) - {} - - //---------------------------------------------------------------------------- - // Destructor - //---------------------------------------------------------------------------- - TaskManager::~TaskManager() - { - TaskSet::iterator it, itE; - for( it = pTasks.begin(); it != pTasks.end(); ++it ) - if( it->own ) - delete it->task; - } - - //---------------------------------------------------------------------------- - // Start the manager - //---------------------------------------------------------------------------- - bool TaskManager::Start() - { - XrdSysMutexHelper scopedLock( pOpMutex ); - Log *log = DefaultEnv::GetLog(); - log->Debug( TaskMgrMsg, "Starting the task manager..." ); - - if( pRunning ) - { - log->Error( TaskMgrMsg, "The task manager is already running" ); - return false; - } - - int ret = ::pthread_create( &pRunnerThread, 0, ::RunRunnerThread, this ); - if( ret != 0 ) - { - log->Error( TaskMgrMsg, "Unable to spawn the task runner thread: %s", - strerror( errno ) ); - return false; - } - pRunning = true; - log->Debug( TaskMgrMsg, "Task manager started" ); - return true; - } - - //---------------------------------------------------------------------------- - // Stop the manager - //---------------------------------------------------------------------------- - bool TaskManager::Stop() - { - XrdSysMutexHelper scopedLock( pOpMutex ); - Log *log = DefaultEnv::GetLog(); - log->Debug( TaskMgrMsg, "Stopping the task manager..." ); - if( !pRunning ) - { - log->Error( TaskMgrMsg, "The task manager is not running" ); - return false; - } - - if( ::pthread_cancel( pRunnerThread ) != 0 ) - { - log->Error( TaskMgrMsg, "Unable to cancel the task runner thread: %s", - strerror( errno ) ); - return false; - } - - void *threadRet; - int ret = pthread_join( pRunnerThread, (void **)&threadRet ); - if( ret != 0 ) - { - log->Error( TaskMgrMsg, "Failed to join the task runner thread: %s", - strerror( errno ) ); - return false; - } - - pRunning = false; - log->Debug( TaskMgrMsg, "Task manager stopped" ); - return true; - } - - //---------------------------------------------------------------------------- - // Run the given task at the given time - //---------------------------------------------------------------------------- - void TaskManager::RegisterTask( Task *task, time_t time, bool own ) - { - Log *log = DefaultEnv::GetLog(); - - log->Debug( TaskMgrMsg, "Registering task: \"%s\" to be run at: [%s]", - task->GetName().c_str(), Utils::TimeToString(time).c_str() ); - - XrdSysMutexHelper scopedLock( pMutex ); - pTasks.insert( TaskHelper( task, time, own ) ); - } - - //-------------------------------------------------------------------------- - // Remove a task if it hasn't run yet - //-------------------------------------------------------------------------- - void TaskManager::UnregisterTask( Task *task ) - { - Log *log = DefaultEnv::GetLog(); - log->Debug( TaskMgrMsg, "Requesting unregistration of: \"%s\"", - task->GetName().c_str() ); - XrdSysMutexHelper scopedLock( pMutex ); - pToBeUnregistered.push_back( task ); - } - - //---------------------------------------------------------------------------- - // Run tasks - //---------------------------------------------------------------------------- - void TaskManager::RunTasks() - { - Log *log = DefaultEnv::GetLog(); - - //-------------------------------------------------------------------------- - // We want the thread to be cancelable only when we sleep between tasks - // execution - //-------------------------------------------------------------------------- - pthread_setcanceltype( PTHREAD_CANCEL_DEFERRED, 0 ); - - for(;;) - { - pthread_setcancelstate( PTHREAD_CANCEL_DISABLE, 0 ); - pMutex.Lock(); - - //------------------------------------------------------------------------ - // Remove the tasks from the active set - super inefficient, - // but, hopefully, never really necessary. We first need to build a list - // of iterators because it is impossible to remove elements from - // a multiset when iterating over it - //------------------------------------------------------------------------ - TaskList::iterator listIt = pToBeUnregistered.begin(); - TaskSet::iterator it, itE; - std::list iteratorList; - std::list::iterator itRem; - for( ; listIt != pToBeUnregistered.end(); ++listIt ) - { - for( it = pTasks.begin(); it != pTasks.end(); ++it ) - { - if( it->task == *listIt ) - iteratorList.push_back( it ); - } - } - - for( itRem = iteratorList.begin(); itRem != iteratorList.end(); ++itRem ) - { - Task *tsk = (*itRem)->task; - bool own = (*itRem)->own; - log->Debug( TaskMgrMsg, "Removing task: \"%s\"", tsk->GetName().c_str() ); - pTasks.erase( *itRem ); - if( own ) - delete tsk; - } - - pToBeUnregistered.clear(); - - //------------------------------------------------------------------------ - // Select the tasks to be run - //------------------------------------------------------------------------ - time_t now = time(0); - std::list toRun; - std::list::iterator trIt; - - it = pTasks.begin(); - itE = pTasks.upper_bound( TaskHelper( 0, now ) ); - - for( ; it != itE; ++it ) - toRun.push_back( TaskHelper( it->task, 0, it->own ) ); - - pTasks.erase( pTasks.begin(), itE ); - pMutex.UnLock(); - - //------------------------------------------------------------------------ - // Run the tasks and reinsert them if necessary - //------------------------------------------------------------------------ - for( trIt = toRun.begin(); trIt != toRun.end(); ++trIt ) - { - log->Dump( TaskMgrMsg, "Running task: \"%s\"", - trIt->task->GetName().c_str() ); - time_t schedule = trIt->task->Run( now ); - if( schedule ) - { - log->Dump( TaskMgrMsg, "Will rerun task \"%s\" at [%s]", - trIt->task->GetName().c_str(), - Utils::TimeToString(schedule).c_str() ); - pMutex.Lock(); - pTasks.insert( TaskHelper( trIt->task, schedule, trIt->own ) ); - pMutex.UnLock(); - } - else - { - log->Debug( TaskMgrMsg, "Done with task: \"%s\"", - trIt->task->GetName().c_str() ); - if( trIt->own ) - delete trIt->task; - } - } - - //------------------------------------------------------------------------ - // Enable the cancelation and go to sleep - //------------------------------------------------------------------------ - pthread_setcancelstate( PTHREAD_CANCEL_ENABLE, 0 ); - XrdSysTimer::Wait( pResolution*1000 ); - } - } -} diff --git a/src/XrdCl/XrdClTaskManager.hh b/src/XrdCl/XrdClTaskManager.hh deleted file mode 100644 index d0ad27b941e..00000000000 --- a/src/XrdCl/XrdClTaskManager.hh +++ /dev/null @@ -1,161 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_TASK_MANAGER_HH__ -#define __XRD_CL_TASK_MANAGER_HH__ - -#include -#include -#include -#include -#include -#include -#include "XrdSys/XrdSysPthread.hh" - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - //! Interface for a task to be run by the TaskManager - //---------------------------------------------------------------------------- - class Task - { - public: - virtual ~Task() {}; - - //------------------------------------------------------------------------ - //! Perform the task - //! - //! @param now current timestamp - //! @return 0 if the task is completed and should no longer be run or - //! the time at which it should be run again - //------------------------------------------------------------------------ - virtual time_t Run( time_t now ) = 0; - - //------------------------------------------------------------------------ - //! Name of the task - //------------------------------------------------------------------------ - const std::string &GetName() const - { - return pName; - } - - //------------------------------------------------------------------------ - //! Set name of the task - //------------------------------------------------------------------------ - void SetName( const std::string &name ) - { - pName = name; - } - - private: - std::string pName; - }; - - //---------------------------------------------------------------------------- - //! Run short tasks at a given time in the future - //! - //! The task manager just runs one extra thread so the execution of one tasks - //! may interfere with the execution of another - //---------------------------------------------------------------------------- - class TaskManager - { - public: - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - TaskManager(); - - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - ~TaskManager(); - - //------------------------------------------------------------------------ - //! Start the manager - //------------------------------------------------------------------------ - bool Start(); - - //------------------------------------------------------------------------ - //! Stop the manager - //! - //! Will wait until the currently running task completes - //------------------------------------------------------------------------ - bool Stop(); - - //------------------------------------------------------------------------ - //! Run the given task at the given time. - //! - //! @param task task to be run - //! @param time time at which the task should be run - //! @param own determines whether the task object should be destroyed - //! when no longer needed - //------------------------------------------------------------------------ - void RegisterTask( Task *task, time_t time, bool own = true ); - - //------------------------------------------------------------------------ - //! Remove a task, the unregistration process is asynchronous and may - //! be performed at any point in the future, the function just queues - //! the request. Unregistered task gets destroyed if it was owned by - //! the task manager. - //------------------------------------------------------------------------ - void UnregisterTask( Task *task ); - - //------------------------------------------------------------------------ - //! Run the tasks - this loops infinitely - //------------------------------------------------------------------------ - void RunTasks(); - - private: - - //------------------------------------------------------------------------ - // Task set helpers - //------------------------------------------------------------------------ - struct TaskHelper - { - TaskHelper( Task *tsk, time_t tme, bool ow = true ): - task(tsk), execTime(tme), own(ow) {} - Task *task; - time_t execTime; - bool own; - }; - - struct TaskHelperCmp - { - bool operator () ( const TaskHelper &th1, const TaskHelper &th2 ) const - { - return th1.execTime < th2.execTime; - } - }; - - typedef std::multiset TaskSet; - typedef std::list TaskList; - - //------------------------------------------------------------------------ - // Private variables - //------------------------------------------------------------------------ - uint16_t pResolution; - TaskSet pTasks; - TaskList pToBeUnregistered; - pthread_t pRunnerThread; - bool pRunning; - XrdSysMutex pMutex; - XrdSysMutex pOpMutex; - }; -} - -#endif // __XRD_CL_TASK_MANAGER_HH__ diff --git a/src/XrdCl/XrdClThirdPartyCopyJob.cc b/src/XrdCl/XrdClThirdPartyCopyJob.cc deleted file mode 100644 index 4157ed8d8a7..00000000000 --- a/src/XrdCl/XrdClThirdPartyCopyJob.cc +++ /dev/null @@ -1,618 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2014 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#include "XrdCl/XrdClThirdPartyCopyJob.hh" -#include "XrdCl/XrdClFile.hh" -#include "XrdCl/XrdClConstants.hh" -#include "XrdCl/XrdClLog.hh" -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdCl/XrdClUtils.hh" -#include "XrdCl/XrdClMessageUtils.hh" -#include "XrdCl/XrdClMonitor.hh" -#include "XrdCl/XrdClUglyHacks.hh" -#include "XrdCl/XrdClRedirectorRegistry.hh" -#include "XrdOuc/XrdOucTPC.hh" -#include "XrdSys/XrdSysTimer.hh" -#include -#include -#include -#include -#include -#include -#include -#include - -namespace -{ - //---------------------------------------------------------------------------- - //! Handle an async response - //---------------------------------------------------------------------------- - class TPCStatusHandler: public XrdCl::ResponseHandler - { - public: - //------------------------------------------------------------------------ - // Constructor - //------------------------------------------------------------------------ - TPCStatusHandler(): - pSem( new XrdCl::Semaphore(0) ), pStatus(0) - { - } - - //------------------------------------------------------------------------ - // Destructor - //------------------------------------------------------------------------ - virtual ~TPCStatusHandler() - { - delete pStatus; - delete pSem; - } - - //------------------------------------------------------------------------ - // Handle Response - //------------------------------------------------------------------------ - virtual void HandleResponse( XrdCl::XRootDStatus *status, - XrdCl::AnyObject *response ) - { - delete response; - pStatus = status; - pSem->Post(); - } - - //------------------------------------------------------------------------ - // Get Mutex - //------------------------------------------------------------------------ - XrdCl::Semaphore *GetSemaphore() - { - return pSem; - } - - //------------------------------------------------------------------------ - // Get status - //------------------------------------------------------------------------ - XrdCl::XRootDStatus *GetStatus() - { - return pStatus; - } - - private: - TPCStatusHandler(const TPCStatusHandler &other); - TPCStatusHandler &operator = (const TPCStatusHandler &other); - - XrdCl::Semaphore *pSem; - XrdCl::XRootDStatus *pStatus; - }; - -} - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - // Constructor - //---------------------------------------------------------------------------- - ThirdPartyCopyJob::ThirdPartyCopyJob( uint16_t jobId, - PropertyList *jobProperties, - PropertyList *jobResults ): - CopyJob( jobId, jobProperties, jobResults ) - { - Log *log = DefaultEnv::GetLog(); - log->Debug( UtilityMsg, "Creating a third party copy job, from %s to %s", - GetSource().GetURL().c_str(), GetTarget().GetURL().c_str() ); - } - - //---------------------------------------------------------------------------- - // Run the copy job - //---------------------------------------------------------------------------- - XRootDStatus ThirdPartyCopyJob::Run( CopyProgressHandler *progress ) - { - //-------------------------------------------------------------------------- - // Decode the parameters - //-------------------------------------------------------------------------- - std::string checkSumMode; - std::string checkSumType; - std::string checkSumPreset; - uint64_t sourceSize; - bool force, coerce; - - pProperties->Get( "checkSumMode", checkSumMode ); - pProperties->Get( "checkSumType", checkSumType ); - pProperties->Get( "checkSumPreset", checkSumPreset ); - pProperties->Get( "sourceSize", sourceSize ); - pProperties->Get( "force", force ); - pProperties->Get( "coerce", coerce ); - - //-------------------------------------------------------------------------- - // Generate the destination CGI - //-------------------------------------------------------------------------- - URL tpcSource; - pProperties->Get( "tpcSource", tpcSource ); - - Log *log = DefaultEnv::GetLog(); - log->Debug( UtilityMsg, "Generating the TPC URLs" ); - - std::string tpcKey = GenerateKey(); - char *cgiBuff = new char[2048]; - const char *cgiP = XrdOucTPC::cgiC2Dst( tpcKey.c_str(), - tpcSource.GetHostId().c_str(), - tpcSource.GetPath().c_str(), - 0, cgiBuff, 2048 ); - if( *cgiP == '!' ) - { - log->Error( UtilityMsg, "Unable to setup target url: %s", cgiP+1 ); - delete [] cgiBuff; - return XRootDStatus( stError, errInvalidArgs ); - } - - URL cgiURL; cgiURL.SetParams( cgiBuff ); - delete [] cgiBuff; - - URL realTarget = GetTarget(); - URL::ParamsMap params = realTarget.GetParams(); - MessageUtils::MergeCGI( params, cgiURL.GetParams(), true ); - - std::ostringstream o; o << sourceSize; - params["oss.asize"] = o.str(); - params["tpc.stage"] = "copy"; - realTarget.SetParams( params ); - - log->Debug( UtilityMsg, "Target url is: %s", realTarget.GetURL().c_str() ); - - //-------------------------------------------------------------------------- - // Timeouts - //-------------------------------------------------------------------------- - uint16_t timeLeft = 0; - pProperties->Get( "initTimeout", timeLeft ); - - time_t start = 0; - bool hasInitTimeout = false; - - if( timeLeft ) - { - hasInitTimeout = true; - start = time(0); - } - - uint16_t tpcTimeout = 0; - pProperties->Get( "tpcTimeout", tpcTimeout ); - - //-------------------------------------------------------------------------- - // Open the target file - //-------------------------------------------------------------------------- - File targetFile( File::DisableVirtRedirect ); - // set WriteRecovery property - std::string value; - DefaultEnv::GetEnv()->GetString( "WriteRecovery", value ); - targetFile.SetProperty( "WriteRecovery", value ); - - // Set the close timeout to the default value of the stream timeout - int closeTimeout = 0; - (void) DefaultEnv::GetEnv()->GetInt( "StreamTimeout", closeTimeout); - - OpenFlags::Flags targetFlags = OpenFlags::Update; - if( force ) - targetFlags |= OpenFlags::Delete; - else - targetFlags |= OpenFlags::New; - - if( coerce ) - targetFlags |= OpenFlags::Force; - - XRootDStatus st; - st = targetFile.Open( realTarget.GetURL(), targetFlags, Access::None, - timeLeft ); - - if( !st.IsOK() ) - { - log->Error( UtilityMsg, "Unable to open target %s: %s", - realTarget.GetURL().c_str(), st.ToStr().c_str() ); - return st; - } - std::string lastUrl; targetFile.GetProperty( "LastURL", lastUrl ); - realTarget = lastUrl; - - //-------------------------------------------------------------------------- - // Generate the source CGI - //-------------------------------------------------------------------------- - cgiBuff = new char[2048]; - cgiP = XrdOucTPC::cgiC2Src( tpcKey.c_str(), - realTarget.GetHostName().c_str(), -1, cgiBuff, - 2048 ); - if( *cgiP == '!' ) - { - log->Error( UtilityMsg, "Unable to setup source url: %s", cgiP+1 ); - delete [] cgiBuff; - return XRootDStatus( stError, errInvalidArgs ); - } - - cgiURL.SetParams( cgiBuff ); - delete [] cgiBuff; - params = tpcSource.GetParams(); - MessageUtils::MergeCGI( params, cgiURL.GetParams(), true ); - params["tpc.stage"] = "copy"; - tpcSource.SetParams( params ); - - log->Debug( UtilityMsg, "Source url is: %s", tpcSource.GetURL().c_str() ); - - //-------------------------------------------------------------------------- - // Calculate the time we hav left to perform source open - //-------------------------------------------------------------------------- - if( hasInitTimeout ) - { - time_t now = time(0); - if( now-start > timeLeft ) - { - XRootDStatus status = targetFile.Close( closeTimeout ); - return XRootDStatus( stError, errOperationExpired ); - } - else - timeLeft -= (now-start); - } - - //-------------------------------------------------------------------------- - // Set up the rendez-vous and open the source - //-------------------------------------------------------------------------- - st = targetFile.Sync( tpcTimeout ); - if( !st.IsOK() ) - { - log->Error( UtilityMsg, "Unable set up rendez-vous: %s", - st.ToStr().c_str() ); - XRootDStatus status = targetFile.Close( closeTimeout ); - return st; - } - - File sourceFile( File::DisableVirtRedirect ); - // set ReadRecovery property - DefaultEnv::GetEnv()->GetString( "ReadRecovery", value ); - sourceFile.SetProperty( "ReadRecovery", value ); - - st = sourceFile.Open( tpcSource.GetURL(), OpenFlags::Read, Access::None, - timeLeft ); - - if( !st.IsOK() ) - { - log->Error( UtilityMsg, "Unable to open source %s: %s", - tpcSource.GetURL().c_str(), st.ToStr().c_str() ); - XRootDStatus status = targetFile.Close( closeTimeout ); - return st; - } - - //-------------------------------------------------------------------------- - // Do the copy and follow progress - //-------------------------------------------------------------------------- - TPCStatusHandler statusHandler; - Semaphore *sem = statusHandler.GetSemaphore(); - StatInfo *info = 0; - - st = targetFile.Sync( &statusHandler ); - if( !st.IsOK() ) - { - log->Error( UtilityMsg, "Unable start the copy: %s", - st.ToStr().c_str() ); - XRootDStatus statusS = sourceFile.Close( closeTimeout ); - XRootDStatus statusT = targetFile.Close( closeTimeout ); - return st; - } - - //-------------------------------------------------------------------------- - // Stat the file every second until sync returns - //-------------------------------------------------------------------------- - bool canceled = false; - while( 1 ) - { - XrdSysTimer::Wait( 1000 ); - - if( progress ) - { - st = targetFile.Stat( true, info ); - if( st.IsOK() ) - { - progress->JobProgress( pJobId, info->GetSize(), sourceSize ); - delete info; - info = 0; - } - bool shouldCancel = progress->ShouldCancel( pJobId ); - if( shouldCancel ) - { - log->Debug( UtilityMsg, "Cancelation requested by progress handler" ); - Buffer arg, *response = 0; arg.FromString( "ofs.tpc cancel" ); - XRootDStatus st = targetFile.Fcntl( arg, response ); - if( !st.IsOK() ) - log->Debug( UtilityMsg, "Error while trying to cancel tpc: %s", - st.ToStr().c_str() ); - - delete response; - canceled = true; - break; - } - } - - if( sem->CondWait() ) - break; - } - - //-------------------------------------------------------------------------- - // Sync has returned so we can check if it was successful - //-------------------------------------------------------------------------- - if( canceled ) - sem->Wait(); - - st = *statusHandler.GetStatus(); - - if( !st.IsOK() ) - { - log->Error( UtilityMsg, "Third party copy from %s to %s failed: %s", - GetSource().GetURL().c_str(), GetTarget().GetURL().c_str(), - st.ToStr().c_str() ); - - // Ignore close response - XRootDStatus statusS = sourceFile.Close( closeTimeout ); - XRootDStatus statusT = targetFile.Close( closeTimeout ); - return st; - } - - XRootDStatus statusS = sourceFile.Close( closeTimeout ); - XRootDStatus statusT = targetFile.Close( closeTimeout ); - - if ( !statusS.IsOK() || !statusT.IsOK() ) - { - st = (statusS.IsOK() ? statusT : statusS); - log->Error( UtilityMsg, "Third party copy from %s to %s failed during " - "close of %s: %s", GetSource().GetURL().c_str(), - GetTarget().GetURL().c_str(), - (statusS.IsOK() ? "destination" : "source"), st.ToStr().c_str() ); - return st; - } - - log->Debug( UtilityMsg, "Third party copy from %s to %s successful", - GetSource().GetURL().c_str(), GetTarget().GetURL().c_str() ); - - pResults->Set( "size", sourceSize ); - - //-------------------------------------------------------------------------- - // Verify the checksums if needed - //-------------------------------------------------------------------------- - if( checkSumMode != "none" ) - { - log->Debug( UtilityMsg, "Attempting checksum calculation." ); - std::string sourceCheckSum; - std::string targetCheckSum; - - //------------------------------------------------------------------------ - // Get the check sum at source - //------------------------------------------------------------------------ - timeval oStart, oEnd; - XRootDStatus st; - if( checkSumMode == "end2end" || checkSumMode == "source" ) - { - gettimeofday( &oStart, 0 ); - if( !checkSumPreset.empty() ) - { - sourceCheckSum = checkSumType + ":"; - sourceCheckSum += checkSumPreset; - } - else - { - VirtualRedirector *redirector = 0; - std::string vrCheckSum; - if( GetSource().IsMetalink() && - ( redirector = RedirectorRegistry::Instance().Get( GetSource() ) ) && - !( vrCheckSum = redirector->GetCheckSum( checkSumType ) ).empty() ) - sourceCheckSum = vrCheckSum; - else - st = Utils::GetRemoteCheckSum( sourceCheckSum, checkSumType, - tpcSource.GetHostId(), - tpcSource.GetPath() ); - } - gettimeofday( &oEnd, 0 ); - if( !st.IsOK() ) - return st; - - pResults->Set( "sourceCheckSum", sourceCheckSum ); - } - - //------------------------------------------------------------------------ - // Get the check sum at destination - //------------------------------------------------------------------------ - timeval tStart, tEnd; - - if( checkSumMode == "end2end" || checkSumMode == "target" ) - { - gettimeofday( &tStart, 0 ); - st = Utils::GetRemoteCheckSum( targetCheckSum, checkSumType, - GetTarget().GetHostId(), - GetTarget().GetPath() ); - - gettimeofday( &tEnd, 0 ); - if( !st.IsOK() ) - return st; - pResults->Set( "targetCheckSum", targetCheckSum ); - } - - //------------------------------------------------------------------------ - // Compare and inform monitoring - //------------------------------------------------------------------------ - if( checkSumMode == "end2end" ) - { - bool match = false; - if( sourceCheckSum == targetCheckSum ) - match = true; - - Monitor *mon = DefaultEnv::GetMonitor(); - if( mon ) - { - Monitor::CheckSumInfo i; - i.transfer.origin = &GetSource(); - i.transfer.target = &GetTarget(); - i.cksum = sourceCheckSum; - i.oTime = Utils::GetElapsedMicroSecs( oStart, oEnd ); - i.tTime = Utils::GetElapsedMicroSecs( tStart, tEnd ); - i.isOK = match; - mon->Event( Monitor::EvCheckSum, &i ); - } - - if( !match ) - return XRootDStatus( stError, errCheckSumError, 0 ); - } - } - return XRootDStatus(); - } - - //---------------------------------------------------------------------------- - // Check whether doing a third party copy is feasible for given - // job descriptor - //---------------------------------------------------------------------------- - XRootDStatus ThirdPartyCopyJob::CanDo( const URL &source, const URL &target, - PropertyList *properties ) - { - - //-------------------------------------------------------------------------- - // Check the initial settings - //-------------------------------------------------------------------------- - Log *log = DefaultEnv::GetLog(); - log->Debug( UtilityMsg, "Check if third party copy between %s and %s " - "is possible", source.GetURL().c_str(), - target.GetURL().c_str() ); - - - if( source.GetProtocol() != "root" && - source.GetProtocol() != "xroot" ) - return XRootDStatus( stError, errNotSupported ); - - if( target.GetProtocol() != "root" && - target.GetProtocol() != "xroot" ) - return XRootDStatus( stError, errNotSupported ); - - uint16_t timeLeft = 0; - properties->Get( "initTimeout", timeLeft ); - - time_t start = 0; - bool hasInitTimeout = false; - - if( timeLeft ) - { - hasInitTimeout = true; - start = time(0); - } - - //-------------------------------------------------------------------------- - // Check if we can open the source file and whether the actual data server - // can support the third party copy - //-------------------------------------------------------------------------- - File sourceFile; - // set WriteRecovery property - std::string value; - DefaultEnv::GetEnv()->GetString( "ReadRecovery", value ); - sourceFile.SetProperty( "ReadRecovery", value ); - - XRootDStatus st; - URL sourceURL = source; - - URL::ParamsMap params = sourceURL.GetParams(); - params["tpc.stage"] = "placement"; - sourceURL.SetParams( params ); - log->Debug( UtilityMsg, "Trying to open %s for reading", - sourceURL.GetURL().c_str() ); - st = sourceFile.Open( sourceURL.GetURL(), OpenFlags::Read, Access::None, - timeLeft ); - if( !st.IsOK() ) - { - log->Error( UtilityMsg, "Cannot open source file %s: %s", - source.GetURL().c_str(), st.ToStr().c_str() ); - st.status = stFatal; - return st; - } - std::string sourceUrl; sourceFile.GetProperty( "LastURL", sourceUrl ); - URL sourceUrlU = sourceUrl; - properties->Set( "tpcSource", sourceUrl ); - - VirtualRedirector *redirector = 0; - long long size = -1; - if( source.IsMetalink() && - ( redirector = RedirectorRegistry::Instance().Get( sourceURL ) ) && - ( size = redirector->GetSize() ) >= 0 ) - properties->Set( "sourceSize", size ); - else - { - StatInfo *statInfo; - st = sourceFile.Stat( false, statInfo ); - if (st.IsOK()) properties->Set( "sourceSize", statInfo->GetSize() ); - delete statInfo; - } - - if( hasInitTimeout ) - { - time_t now = time(0); - if( now-start > timeLeft ) - timeLeft = 1; // we still want to send a close, but we time it out fast - else - timeLeft -= (now-start); - } - - st = sourceFile.Close( timeLeft ); - - if( hasInitTimeout ) - { - time_t now = time(0); - if( now-start > timeLeft ) - return XRootDStatus( stError, errOperationExpired ); - else - timeLeft -= (now-start); - } - - st = Utils::CheckTPC( sourceUrlU.GetHostId(), timeLeft ); - if( !st.IsOK() ) - return st; - - //-------------------------------------------------------------------------- - // Verify the destination - //-------------------------------------------------------------------------- -// st = Utils::CheckTPC( jd->target.GetHostId() ); -// if( !st.IsOK() ) -// return st; - - if( hasInitTimeout ) - { - if( timeLeft == 0 ) - properties->Set( "initTimeout", 1 ); - else - properties->Set( "initTimeout", timeLeft ); - } - return XRootDStatus(); - } - - //---------------------------------------------------------------------------- - // Generate a rendez-vous key - //---------------------------------------------------------------------------- - std::string ThirdPartyCopyJob::GenerateKey() - { - char tpcKey[25]; - struct timeval currentTime; - struct timezone tz; - gettimeofday( ¤tTime, &tz ); - int k1 = currentTime.tv_usec; - int k2 = getpid() | (getppid() << 16); - int k3 = currentTime.tv_sec; - snprintf( tpcKey, 25, "%08x%08x%08x", k1, k2, k3 ); - return std::string(tpcKey); - } -} diff --git a/src/XrdCl/XrdClThirdPartyCopyJob.hh b/src/XrdCl/XrdClThirdPartyCopyJob.hh deleted file mode 100644 index 04ee8e97525..00000000000 --- a/src/XrdCl/XrdClThirdPartyCopyJob.hh +++ /dev/null @@ -1,61 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_THIRD_PARTY_COPY_JOB_HH__ -#define __XRD_CL_THIRD_PARTY_COPY_JOB_HH__ - -#include "XrdCl/XrdClCopyProcess.hh" -#include "XrdCl/XrdClCopyJob.hh" - -namespace XrdCl -{ - class ThirdPartyCopyJob: public CopyJob - { - public: - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - ThirdPartyCopyJob( uint16_t jobId, - PropertyList *jobProperties, - PropertyList *jobResults ); - - //------------------------------------------------------------------------ - //! Run the copy job - //! - //! @param progress the handler to be notified about the copy progress - //! @return status of the copy operation - //------------------------------------------------------------------------ - virtual XRootDStatus Run( CopyProgressHandler *progress = 0 ); - - //------------------------------------------------------------------------ - //! Check whether doing a third party copy is feasible for given - //! job descriptor - //! - //! @param property list - may be extended by info needed for TPC - //! @return error when a third party copy cannot be performed and - //! fatal error when no copy can be performed - //------------------------------------------------------------------------ - static XRootDStatus CanDo( const URL &source, const URL &target, - PropertyList *properties ); - - private: - static std::string GenerateKey(); - }; -} - -#endif // __XRD_CL_THIRD_PARTY_COPY_JOB_HH__ diff --git a/src/XrdCl/XrdClTransportManager.cc b/src/XrdCl/XrdClTransportManager.cc deleted file mode 100644 index 75c6f2c9fbe..00000000000 --- a/src/XrdCl/XrdClTransportManager.cc +++ /dev/null @@ -1,73 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#include "XrdCl/XrdClTransportManager.hh" -#include "XrdCl/XrdClXRootDTransport.hh" - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - // Constructor - //---------------------------------------------------------------------------- - TransportManager::TransportManager() - { - pHandlers["root"] = new XRootDTransport(); - pHandlers["xroot"] = new XRootDTransport(); - } - - //---------------------------------------------------------------------------- - // Destructor - //---------------------------------------------------------------------------- - TransportManager::~TransportManager() - { - HandlerMap::iterator it; - for( it = pHandlers.begin(); it != pHandlers.end(); ++it ) - delete it->second; - } - - //---------------------------------------------------------------------------- - // Register a transport factory function for a given protocol - //---------------------------------------------------------------------------- - bool TransportManager::RegisterFactory( const std::string &protocol, - TransportFactory factory ) - { - FactoryMap::iterator it = pFactories.find( protocol ); - if( it == pFactories.end() ) - return false; - pFactories[protocol] = factory; - return true; - } - - //---------------------------------------------------------------------------- - // Get a transport handler object for a given protocol - //---------------------------------------------------------------------------- - TransportHandler *TransportManager::GetHandler( const std::string &protocol ) - { - HandlerMap::iterator itH = pHandlers.find( protocol ); - if( itH != pHandlers.end() ) - return itH->second; - - FactoryMap::iterator itF = pFactories.find( protocol ); - if( itF == pFactories.end() ) - return 0; - - TransportHandler *handler = (*itF->second)(); - pHandlers[protocol] = handler; - return handler; - } -} diff --git a/src/XrdCl/XrdClTransportManager.hh b/src/XrdCl/XrdClTransportManager.hh deleted file mode 100644 index 577cb12366d..00000000000 --- a/src/XrdCl/XrdClTransportManager.hh +++ /dev/null @@ -1,66 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_TRANSPORT_MANAGER_HH__ -#define __XRD_CL_TRANSPORT_MANAGER_HH__ - -#include -#include - -namespace XrdCl -{ - class TransportHandler; - - //---------------------------------------------------------------------------- - //! Manage transport handler objects - //---------------------------------------------------------------------------- - class TransportManager - { - public: - typedef TransportHandler *(*TransportFactory)(); - - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - TransportManager(); - - //------------------------------------------------------------------------ - // Destructor - //------------------------------------------------------------------------ - virtual ~TransportManager(); - - //------------------------------------------------------------------------ - //! Register a transport factory function for a given protocol - //------------------------------------------------------------------------ - bool RegisterFactory( const std::string &protocol, - TransportFactory factory ); - - //------------------------------------------------------------------------ - //! Get a transport handler object for a given protocol - //------------------------------------------------------------------------ - TransportHandler *GetHandler( const std::string &protocol ); - - private: - typedef std::map HandlerMap; - typedef std::map FactoryMap; - HandlerMap pHandlers; - FactoryMap pFactories; - }; -} - -#endif // __XRD_CL_TRANSPORT_MANAGER_HH__ diff --git a/src/XrdCl/XrdClURL.cc b/src/XrdCl/XrdClURL.cc deleted file mode 100644 index 0acebff6191..00000000000 --- a/src/XrdCl/XrdClURL.cc +++ /dev/null @@ -1,485 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#include "XrdCl/XrdClLog.hh" -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdCl/XrdClConstants.hh" -#include "XrdCl/XrdClURL.hh" -#include "XrdCl/XrdClUtils.hh" - -#include -#include -#include -#include - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - // Constructor - //---------------------------------------------------------------------------- - URL::URL(): - pPort( 1094 ) - { - } - - //---------------------------------------------------------------------------- - // Constructor - //---------------------------------------------------------------------------- - URL::URL( const std::string &url ): - pPort( 1094 ) - { - FromString( url ); - } - - //---------------------------------------------------------------------------- - // Parse URL - it is rather trivial and horribly slow but probably there - // is not need to have anything more fancy - //---------------------------------------------------------------------------- - bool URL::FromString( const std::string &url ) - { - Log *log = DefaultEnv::GetLog(); - - Clear(); - - if( url.length() == 0 ) - { - log->Error( UtilityMsg, "The given URL is empty" ); - return false; - } - - //-------------------------------------------------------------------------- - // Extract the protocol, assume file:// if none found - //-------------------------------------------------------------------------- - size_t pos = url.find( "://" ); - - std::string current; - if( pos != std::string::npos ) - { - pProtocol = url.substr( 0, pos ); - current = url.substr( pos+3 ); - } - else if( url[0] == '/' ) - { - pProtocol = "file"; - current = url; - } - else if( url[0] == '-' ) - { - pProtocol = "stdio"; - current = "-"; - pPort = 0; - } - else - { - pProtocol = "root"; - current = url; - } - - //-------------------------------------------------------------------------- - // Extract host info and path - //-------------------------------------------------------------------------- - std::string path; - std::string hostInfo; - - if( pProtocol == "stdio" ) - path = current; - else if( pProtocol == "file") - { - if( current[0] == '/' ) - current = "localhost" + current; - pos = current.find( '/' ); - if( pos == std::string::npos ) - hostInfo = current; - else - { - hostInfo = current.substr( 0, pos ); - path = current.substr( pos ); - } - } - else - { - pos = current.find( '/' ); - if( pos == std::string::npos ) - hostInfo = current; - else - { - hostInfo = current.substr( 0, pos ); - path = current.substr( pos+1 ); - } - } - - if( !ParseHostInfo( hostInfo ) ) - { - Clear(); - return false; - } - - if( !ParsePath( path ) ) - { - Clear(); - return false; - } - - ComputeURL(); - - //-------------------------------------------------------------------------- - // Dump the url - //-------------------------------------------------------------------------- - log->Dump( UtilityMsg, - "URL: %s\n" - "Protocol: %s\n" - "User Name: %s\n" - "Password: %s\n" - "Host Name: %s\n" - "Port: %d\n" - "Path: %s\n", - url.c_str(), pProtocol.c_str(), pUserName.c_str(), - pPassword.c_str(), pHostName.c_str(), pPort, pPath.c_str() ); - return true; - } - - //---------------------------------------------------------------------------- - // Parse host info - //---------------------------------------------------------------------------- - bool URL::ParseHostInfo( const std::string hostInfo ) - { - if( pProtocol == "stdio" ) - return true; - - if( pProtocol.empty() || hostInfo.empty() ) - return false; - - size_t pos = hostInfo.find( "@" ); - std::string hostPort; - - //-------------------------------------------------------------------------- - // We have found username-password - //-------------------------------------------------------------------------- - if( pos != std::string::npos ) - { - std::string userPass = hostInfo.substr( 0, pos ); - hostPort = hostInfo.substr( pos+1 ); - pos = userPass.find( ":" ); - - //------------------------------------------------------------------------ - // It's both username and password - //------------------------------------------------------------------------ - if( pos != std::string::npos ) - { - pUserName = userPass.substr( 0, pos ); - pPassword = userPass.substr( pos+1 ); - if( pPassword.empty() ) - return false; - } - //------------------------------------------------------------------------ - // It's just the user name - //------------------------------------------------------------------------ - else - pUserName = userPass; - if( pUserName.empty() ) - return false; - } - - //-------------------------------------------------------------------------- - // No username-password - //-------------------------------------------------------------------------- - else - hostPort = hostInfo; - - //-------------------------------------------------------------------------- - // Deal with hostname - IPv6 encoded address RFC 2732 - //-------------------------------------------------------------------------- - if( hostPort.length() >= 3 && hostPort[0] == '[' ) - { - pos = hostPort.find( "]" ); - if( pos != std::string::npos ) - { - pHostName = hostPort.substr( 0, pos+1 ); - hostPort.erase( 0, pos+2 ); - - //---------------------------------------------------------------------- - // Check if we're IPv6 encoded IPv4 - //---------------------------------------------------------------------- - pos = pHostName.find( "." ); - size_t pos2 = pHostName.find( "[::ffff" ); - size_t pos3 = pHostName.find( "[::" ); - if( pos != std::string::npos && pos3 != std::string::npos && - pos2 == std::string::npos ) - { - pHostName.erase( 0, 3 ); - pHostName.erase( pHostName.length()-1, 1 ); - } - } - } - else - { - pos = hostPort.find( ":" ); - if( pos != std::string::npos ) - { - pHostName = hostPort.substr( 0, pos ); - hostPort.erase( 0, pos+1 ); - } - else - { - pHostName = hostPort; - hostPort = ""; - } - if( pHostName.empty() ) - return false; - } - - //-------------------------------------------------------------------------- - // Deal with port number - //-------------------------------------------------------------------------- - if( !hostPort.empty() ) - { - char *result; - pPort = ::strtol( hostPort.c_str(), &result, 0 ); - if( *result != 0 ) - return false; - } - - ComputeHostId(); - return true; - } - - //---------------------------------------------------------------------------- - // Parse path - //---------------------------------------------------------------------------- - bool URL::ParsePath( const std::string &path ) - { - size_t pos = path.find( "?" ); - if( pos != std::string::npos ) - { - pPath = path.substr( 0, pos ); - SetParams( path.substr( pos+1, path.length() ) ); - } - else - pPath = path; - - std::string::iterator back = pPath.end() - 1; - if( pProtocol == "file" && *back == '/' ) - pPath.erase( back ); - - ComputeURL(); - return true; - } - - //---------------------------------------------------------------------------- - // Get path with params - //---------------------------------------------------------------------------- - std::string URL::GetPathWithParams() const - { - std::ostringstream o; - if( !pPath.empty() ) - o << pPath; - - o << GetParamsAsString(); - return o.str(); - } - - //------------------------------------------------------------------------ - //! Get the path with params, filteres out 'xrdcl.' - //------------------------------------------------------------------------ - std::string URL::GetPathWithFilteredParams() const - { - std::ostringstream o; - if( !pPath.empty() ) - o << pPath; - - o << GetParamsAsString( true ); - return o.str(); - } - - //------------------------------------------------------------------------ - //! Get protocol://host:port/path - //------------------------------------------------------------------------ - std::string URL::GetLocation() const - { - std::ostringstream o; - o << pProtocol << "://"; - if( pProtocol == "file" ) - o << pHostName; - else - o << pHostName << ":" << pPort << "/"; - o << pPath; - return o.str(); - } - - //------------------------------------------------------------------------ - // Get the URL params as string - //------------------------------------------------------------------------ - std::string URL::GetParamsAsString() const - { - return GetParamsAsString( false ); - } - - //------------------------------------------------------------------------ - //! Get the URL params as string - //------------------------------------------------------------------------ - std::string URL::GetParamsAsString( bool filter ) const - { - if( pParams.empty() ) - return ""; - - std::ostringstream o; - o << "?"; - ParamsMap::const_iterator it; - for( it = pParams.begin(); it != pParams.end(); ++it ) - { - // we filter out client specific parameters - if( filter && it->first.compare( 0, 6, "xrdcl." ) == 0 ) - continue; - if( it != pParams.begin() ) o << "&"; - o << it->first << "=" << it->second; - } - return o.str(); - } - - //------------------------------------------------------------------------ - // Set params - //------------------------------------------------------------------------ - void URL::SetParams( const std::string ¶ms ) - { - pParams.clear(); - std::string p = params; - - if( p.empty() ) - return; - - if( p[0] == '?' ) - p.erase( 0, 1 ); - - std::vector paramsVect; - std::vector::iterator it; - Utils::splitString( paramsVect, p, "&" ); - for( it = paramsVect.begin(); it != paramsVect.end(); ++it ) - { - size_t pos = it->find( "=" ); - if( pos == std::string::npos ) - pParams[*it] = ""; - else - pParams[it->substr(0, pos)] = it->substr( pos+1, it->length() ); - } - } - - //---------------------------------------------------------------------------- - // Clear the fields - //---------------------------------------------------------------------------- - void URL::Clear() - { - pHostId.clear(); - pProtocol.clear(); - pUserName.clear(); - pPassword.clear(); - pHostName.clear(); - pPort = 1094; - pPath.clear(); - pParams.clear(); - pURL.clear(); - } - - //---------------------------------------------------------------------------- - // Check validity - //---------------------------------------------------------------------------- - bool URL::IsValid() const - { - if( pProtocol.empty() ) - return false; - if( pProtocol == "file" && pPath.empty() ) - return false; - if( pProtocol == "stdio" && pPath != "-" ) - return false; - if( pProtocol != "file" && pProtocol != "stdio" && pHostName.empty() ) - return false; - return true; - } - - bool URL::IsMetalink() const - { - Env *env = DefaultEnv::GetEnv(); - int mlProcessing = DefaultMetalinkProcessing; - env->GetInt( "MetalinkProcessing", mlProcessing ); - if( !mlProcessing ) return false; - return PathEndsWith( ".meta4" ) || PathEndsWith( ".metalink" ); - } - - bool URL::IsLocalFile() const - { - return pProtocol == "file" && pHostName == "localhost"; - } - - bool URL::PathEndsWith(const std::string & sufix) const - { - if (sufix.size() > pPath.size()) return false; - return std::equal(sufix.rbegin(), sufix.rend(), pPath.rbegin() ); - } - - //---------------------------------------------------------------------------- - // Recompute the host id - //---------------------------------------------------------------------------- - void URL::ComputeHostId() - { - std::ostringstream o; - if( !pUserName.empty() ) - { - o << pUserName; - if( !pPassword.empty() ) - o << ":" << pPassword; - o << "@"; - } - if( pProtocol == "file" ) - o << pHostName; - else - o << pHostName << ":" << pPort; - pHostId = o.str(); - } - - //---------------------------------------------------------------------------- - // Recreate the url - //---------------------------------------------------------------------------- - void URL::ComputeURL() - { - if( !IsValid() ) - pURL = ""; - - std::ostringstream o; - if( !pProtocol.empty() ) - o << pProtocol << "://"; - - if( !pUserName.empty() ) - { - o << pUserName; - if( !pPassword.empty() ) - o << ":" << pPassword; - o << "@"; - } - - if( !pHostName.empty() ) - { - if( pProtocol == "file" ) - o << pHostName; - else - o << pHostName << ":" << pPort << "/"; - } - - o << GetPathWithParams(); - - pURL = o.str(); - } -} diff --git a/src/XrdCl/XrdClURL.hh b/src/XrdCl/XrdClURL.hh deleted file mode 100644 index 8ac70f65de7..00000000000 --- a/src/XrdCl/XrdClURL.hh +++ /dev/null @@ -1,275 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_URL_HH__ -#define __XRD_CL_URL_HH__ - -#include -#include - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - //! URL representation - //---------------------------------------------------------------------------- - class URL - { - public: - typedef std::map ParamsMap; //!< Map of get - //!< params - - //------------------------------------------------------------------------ - //! Default constructor - //------------------------------------------------------------------------ - URL(); - - //------------------------------------------------------------------------ - //! Constructor - //! - //! @param url an url in format: - //! protocol://user:password\@host:port/path?param1=x¶m2=y - //------------------------------------------------------------------------ - URL( const std::string &url ); - - //------------------------------------------------------------------------ - //! Is the url valid - //------------------------------------------------------------------------ - bool IsValid() const; - - //------------------------------------------------------------------------ - //! Is it a URL to a metalink - //------------------------------------------------------------------------ - bool IsMetalink() const; - - //------------------------------------------------------------------------ - //! Is it a URL to a local file - //! (file://localhost - //------------------------------------------------------------------------ - bool IsLocalFile() const; - - //------------------------------------------------------------------------ - //! Get the URL - //------------------------------------------------------------------------ - std::string GetURL() const - { - return pURL; - } - - //------------------------------------------------------------------------ - //! Get the host part of the URL (user:password\@host:port) - //------------------------------------------------------------------------ - std::string GetHostId() const - { - return pHostId; - } - - //------------------------------------------------------------------------ - //! Get location (protocol://host:port/path) - //------------------------------------------------------------------------ - std::string GetLocation() const; - - //------------------------------------------------------------------------ - //! Get the protocol - //------------------------------------------------------------------------ - const std::string &GetProtocol() const - { - return pProtocol; - } - - //------------------------------------------------------------------------ - //! Set protocol - //------------------------------------------------------------------------ - void SetProtocol( const std::string &protocol ) - { - pProtocol = protocol; - ComputeURL(); - } - - //------------------------------------------------------------------------ - //! Get the username - //------------------------------------------------------------------------ - const std::string &GetUserName() const - { - return pUserName; - } - - //------------------------------------------------------------------------ - //! Set the username - //------------------------------------------------------------------------ - void SetUserName( const std::string &userName ) - { - pUserName = userName; - ComputeHostId(); - ComputeURL(); - } - - //------------------------------------------------------------------------ - //! Get the password - //------------------------------------------------------------------------ - const std::string &GetPassword() const - { - return pPassword; - } - - //------------------------------------------------------------------------ - //! Set the password - //------------------------------------------------------------------------ - void SetPassword( const std::string &password ) - { - pPassword = password; - ComputeURL(); - } - - //------------------------------------------------------------------------ - //! Get the name of the target host - //------------------------------------------------------------------------ - const std::string &GetHostName() const - { - return pHostName; - } - - //------------------------------------------------------------------------ - //! Set the host name - //------------------------------------------------------------------------ - void SetHostName( const std::string &hostName ) - { - pHostName = hostName; - ComputeHostId(); - ComputeURL(); - } - - //------------------------------------------------------------------------ - //! Get the target port - //------------------------------------------------------------------------ - int GetPort() const - { - return pPort; - } - - //------------------------------------------------------------------------ - // Set port - //------------------------------------------------------------------------ - void SetPort( int port ) - { - pPort = port; - ComputeHostId(); - ComputeURL(); - } - - //------------------------------------------------------------------------ - // Set host and port - //------------------------------------------------------------------------ - void SetHostPort( const std::string &hostName, int port ) - { - pHostName = hostName; - pPort = port; - ComputeHostId(); - ComputeURL(); - } - - //------------------------------------------------------------------------ - //! Get the path - //------------------------------------------------------------------------ - const std::string &GetPath() const - { - return pPath; - } - - //------------------------------------------------------------------------ - //! Set the path - //------------------------------------------------------------------------ - void SetPath( const std::string &path ) - { - pPath = path; - ComputeURL(); - } - - //------------------------------------------------------------------------ - //! Get the path with params - //------------------------------------------------------------------------ - std::string GetPathWithParams() const; - - //------------------------------------------------------------------------ - //! Get the path with params, filteres out 'xrdcl.' - //------------------------------------------------------------------------ - std::string GetPathWithFilteredParams() const; - - //------------------------------------------------------------------------ - //! Get the URL params - //------------------------------------------------------------------------ - const ParamsMap &GetParams() const - { - return pParams; - } - - //------------------------------------------------------------------------ - //! Get the URL params as string - //------------------------------------------------------------------------ - std::string GetParamsAsString() const; - - //------------------------------------------------------------------------ - //! Get the URL params as string - //! - //! @param filter : if set to true filters out 'xrdcl.' - //------------------------------------------------------------------------ - std::string GetParamsAsString( bool filter ) const; - - //------------------------------------------------------------------------ - //! Set params - //------------------------------------------------------------------------ - void SetParams( const std::string ¶ms ); - - //------------------------------------------------------------------------ - //! Set params - //------------------------------------------------------------------------ - void SetParams( const ParamsMap ¶ms ) - { - pParams = params; - ComputeURL(); - } - - //------------------------------------------------------------------------ - //! Parse a string and fill the URL fields - //------------------------------------------------------------------------ - bool FromString( const std::string &url ); - - //------------------------------------------------------------------------ - //! Clear the url - //------------------------------------------------------------------------ - void Clear(); - - private: - bool ParseHostInfo( const std::string hhostInfo ); - bool ParsePath( const std::string &path ); - void ComputeHostId(); - void ComputeURL(); - bool PathEndsWith( const std::string & sufix ) const; - std::string pHostId; - std::string pProtocol; - std::string pUserName; - std::string pPassword; - std::string pHostName; - int pPort; - std::string pPath; - ParamsMap pParams; - std::string pURL; - - }; -} - -#endif // __XRD_CL_URL_HH__ diff --git a/src/XrdCl/XrdClUglyHacks.hh b/src/XrdCl/XrdClUglyHacks.hh deleted file mode 100644 index f99cb51f6e5..00000000000 --- a/src/XrdCl/XrdClUglyHacks.hh +++ /dev/null @@ -1,43 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//----------------------------------------------------------------------------- -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_UGLY_HACKS_HH__ -#define __XRD_CL_UGLY_HACKS_HH__ - -#include "XrdSys/XrdSysLinuxSemaphore.hh" -#include "XrdSys/XrdSysPthread.hh" - -namespace XrdCl -{ -#if defined(__linux__) && defined(HAVE_ATOMICS) && !USE_LIBC_SEMAPHORE - typedef XrdSys::LinuxSemaphore Semaphore; -#else - typedef XrdSysSemaphore Semaphore; -#endif - -#define XRDCL_SMART_PTR_T std::unique_ptr - -} - -#endif // __XRD_CL_UGLY_HACKS_HH__ diff --git a/src/XrdCl/XrdClUtils.cc b/src/XrdCl/XrdClUtils.cc deleted file mode 100644 index d52e9922cda..00000000000 --- a/src/XrdCl/XrdClUtils.cc +++ /dev/null @@ -1,492 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#include "XrdCl/XrdClUtils.hh" -#include "XrdCl/XrdClFileSystem.hh" -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdCl/XrdClConstants.hh" -#include "XrdCl/XrdClCheckSumManager.hh" -#include "XrdNet/XrdNetAddr.hh" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -namespace -{ - bool isNotSpace( char c ) - { - return c != ' '; - } - - //---------------------------------------------------------------------------- - // Ordering function for sorting IP addresses - //---------------------------------------------------------------------------- - struct PreferIPv6 - { - bool operator() ( const XrdNetAddr &l, const XrdNetAddr &r ) - { - bool rIsIPv4 = false; - if( r.isIPType( XrdNetAddrInfo::IPv4 ) || - (r.isIPType( XrdNetAddrInfo::IPv6 ) && r.isMapped()) ) - rIsIPv4 = true; - - if( l.isIPType( XrdNetAddrInfo::IPv6 ) && rIsIPv4 ) - return true; - return false; - } - }; -} - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - // Get a parameter either from the environment or URL - //---------------------------------------------------------------------------- - int Utils::GetIntParameter( const URL &url, - const std::string &name, - int defaultVal ) - { - Env *env = DefaultEnv::GetEnv(); - int value = defaultVal; - char *endPtr; - URL::ParamsMap::const_iterator it; - - env->GetInt( name, value ); - it = url.GetParams().find( std::string("XrdCl.") + name ); - if( it != url.GetParams().end() ) - { - int urlValue = (int)strtol( it->second.c_str(), &endPtr, 0 ); - if( !*endPtr ) - value = urlValue; - } - return value; - } - - //---------------------------------------------------------------------------- - // Get a parameter either from the environment or URL - //---------------------------------------------------------------------------- - std::string Utils::GetStringParameter( const URL &url, - const std::string &name, - const std::string &defaultVal ) - { - Env *env = DefaultEnv::GetEnv(); - std::string value = defaultVal; - URL::ParamsMap::const_iterator it; - - env->GetString( name, value ); - it = url.GetParams().find( std::string("XrdCl.") + name ); - if( it != url.GetParams().end() ) - value = it->second; - - return value; - } - - //---------------------------------------------------------------------------- - // Interpret a string as address type, default to IPAll - //---------------------------------------------------------------------------- - Utils::AddressType Utils::String2AddressType( const std::string &addressType ) - { - if( addressType == "IPv6" ) - return IPv6; - else if( addressType == "IPv4" ) - return IPv4; - else if( addressType == "IPv4Mapped6" ) - return IPv4Mapped6; - else if( addressType == "IPAll" ) - return IPAll; - else - return IPAuto; - } - - //---------------------------------------------------------------------------- - // Resolve IP addresses - //---------------------------------------------------------------------------- - Status Utils::GetHostAddresses( std::vector &addresses, - const URL &url, - Utils::AddressType type ) - { - Log *log = DefaultEnv::GetLog(); - XrdNetAddr *addrs; - int nAddrs = 0; - const char *err = 0; - - //-------------------------------------------------------------------------- - // Resolve all the addresses - //-------------------------------------------------------------------------- - std::ostringstream o; o << url.GetHostName() << ":" << url.GetPort(); - XrdNetUtils::AddrOpts opts; - - if( type == IPv6 ) opts = XrdNetUtils::onlyIPv6; - else if( type == IPv4 ) opts = XrdNetUtils::onlyIPv4; - else if( type == IPv4Mapped6 ) opts = XrdNetUtils::allV4Map; - else if( type == IPAll ) opts = XrdNetUtils::allIPMap; - else opts = XrdNetUtils::prefAuto; - - err = XrdNetUtils::GetAddrs( o.str().c_str(), &addrs, nAddrs, opts ); - - if( err ) - { - log->Error( UtilityMsg, "Unable to resolve %s: %s", o.str().c_str(), - err ); - return Status( stError, errInvalidAddr ); - } - - if( nAddrs == 0 ) - { - log->Error( UtilityMsg, "No addresses for %s were found", - o.str().c_str() ); - return Status( stError, errInvalidAddr ); - } - - addresses.clear(); - for( int i = 0; i < nAddrs; ++i ) - addresses.push_back( addrs[i] ); - delete [] addrs; - - //-------------------------------------------------------------------------- - // Sort and shuffle them - //-------------------------------------------------------------------------- - std::random_shuffle( addresses.begin(), addresses.end() ); - std::sort( addresses.begin(), addresses.end(), PreferIPv6() ); - return Status(); - } - - //---------------------------------------------------------------------------- - // Log all the addresses on the list - //---------------------------------------------------------------------------- - void Utils::LogHostAddresses( Log *log, - uint64_t type, - const std::string &hostId, - std::vector &addresses ) - { - std::string addrStr; - std::vector::iterator it; - for( it = addresses.begin(); it != addresses.end(); ++it ) - { - char nameBuff[256]; - it->Format( nameBuff, 256, XrdNetAddrInfo::fmtAdv6 ); - addrStr += nameBuff; - addrStr += ", "; - } - addrStr.erase( addrStr.length()-2, 2 ); - log->Debug( type, "[%s] Found %d address(es): %s", - hostId.c_str(), addresses.size(), addrStr.c_str() ); - } - - //---------------------------------------------------------------------------- - // Convert timestamp to a string - //---------------------------------------------------------------------------- - std::string Utils::TimeToString( time_t timestamp ) - { - char now[30]; - tm tsNow; - time_t ttNow = timestamp; - localtime_r( &ttNow, &tsNow ); - strftime( now, 30, "%Y-%m-%d %H:%M:%S %z", &tsNow ); - return now; - } - - //---------------------------------------------------------------------------- - // Get the elapsed microseconds between two timevals - //---------------------------------------------------------------------------- - uint64_t Utils::GetElapsedMicroSecs( timeval start, timeval end ) - { - uint64_t startUSec = start.tv_sec*1000000 + start.tv_usec; - uint64_t endUSec = end.tv_sec*1000000 + end.tv_usec; - return endUSec-startUSec; - } - - //---------------------------------------------------------------------------- - // Get remote checksum - //---------------------------------------------------------------------------- - XRootDStatus Utils::GetRemoteCheckSum( std::string &checkSum, - const std::string &checkSumType, - const std::string &server, - const std::string &path ) - { - FileSystem *fs = new FileSystem( URL( server ) ); - // add the 'cks.type' cgi tag in order to - // select the proper checksum type in case - // the server supports more than one checksum - size_t pos = path.find( '?' ); - std::string cksPath = path + ( pos == std::string::npos ? '?' : '&' ) + "cks.type=" + checkSumType; - Buffer arg; arg.FromString( cksPath ); - Buffer *cksResponse = 0; - XRootDStatus st; - Log *log = DefaultEnv::GetLog(); - - st = fs->Query( QueryCode::Checksum, arg, cksResponse ); - delete fs; - - if( !st.IsOK() ) - return st; - - if( !cksResponse ) - return XRootDStatus( stError, errInternal ); - - std::vector elems; - Utils::splitString( elems, cksResponse->ToString(), " " ); - delete cksResponse; - - if( elems.size() != 2 ) - return XRootDStatus( stError, errInvalidResponse ); - - if( elems[0] != checkSumType ) - return XRootDStatus( stError, errCheckSumError ); - - checkSum = elems[0] + ":"; - checkSum += NormalizeChecksum( elems[0], elems[1] ); - - log->Dump( UtilityMsg, "Checksum for %s checksum: %s", - path.c_str(), checkSum.c_str() ); - - return XRootDStatus(); - } - - //------------------------------------------------------------------------ - // Get a checksum from local file - //------------------------------------------------------------------------ - XRootDStatus Utils::GetLocalCheckSum( std::string &checkSum, - const std::string &checkSumType, - const std::string &path ) - { - Log *log = DefaultEnv::GetLog(); - CheckSumManager *cksMan = DefaultEnv::GetCheckSumManager(); - - if( !cksMan ) - { - log->Error( UtilityMsg, "Unable to get the checksum manager" ); - return XRootDStatus( stError, errInternal ); - } - - XrdCksData ckSum; ckSum.Set( checkSumType.c_str() ); - bool status = cksMan->Calculate( ckSum, checkSumType, path.c_str() ); - if( !status ) - { - log->Error( UtilityMsg, "Error while calculating checksum for %s", - path.c_str() ); - return XRootDStatus( stError, errCheckSumError ); - } - - char *cksBuffer = new char[265]; - ckSum.Get( cksBuffer, 256 ); - checkSum = checkSumType + ":"; - checkSum += NormalizeChecksum( checkSumType, cksBuffer ); - delete [] cksBuffer; - - log->Dump( UtilityMsg, "Checksum for %s is: %s", path.c_str(), - checkSum.c_str() ); - - return XRootDStatus(); - } - - //---------------------------------------------------------------------------- - // Convert bytes to a human readable string - //---------------------------------------------------------------------------- - std::string Utils::BytesToString( uint64_t bytes ) - { - double final = bytes; - int i = 0; - char suf[3] = { 'k', 'M', 'G' }; - for( i = 0; i < 3 && final > 1024; ++i, final /= 1024 ) {}; - std::ostringstream o; - o << std::setprecision(4) << final; - if( i > 0 ) o << suf[i-1]; - return o.str(); - } - - //---------------------------------------------------------------------------- - // Check if peer supports tpc - //---------------------------------------------------------------------------- - XRootDStatus Utils::CheckTPC( const std::string &server, uint16_t timeout ) - { - Log *log = DefaultEnv::GetLog(); - log->Debug( UtilityMsg, "Checking if the data server %s supports tpc", - server.c_str() ); - - FileSystem sourceDSFS( server ); - Buffer queryArg; queryArg.FromString( "tpc" ); - Buffer *queryResponse; - XRootDStatus st; - st = sourceDSFS.Query( QueryCode::Config, queryArg, queryResponse, - timeout ); - if( !st.IsOK() ) - { - log->Error( UtilityMsg, "Cannot query source data server %s: %s", - server.c_str(), st.ToStr().c_str() ); - st.status = stFatal; - return st; - } - - std::string answer = queryResponse->ToString(); - delete queryResponse; - if( answer.length() == 1 || !isdigit( answer[0] ) || atoi(answer.c_str()) == 0) - { - log->Debug( UtilityMsg, "Third party copy not supported at: %s", - server.c_str() ); - return XRootDStatus( stError, errNotSupported ); - } - log->Debug( UtilityMsg, "Third party copy supported at: %s", - server.c_str() ); - - return XRootDStatus(); - } - - //---------------------------------------------------------------------------- - // Convert the fully qualified host name to country code - //---------------------------------------------------------------------------- - std::string Utils::FQDNToCC( const std::string &fqdn ) - { - std::vector el; - Utils::splitString( el, fqdn, "." ); - if( el.size() < 2 ) - return "us"; - - std::string cc = *el.rbegin(); - if( cc.length() == 2 ) - return cc; - return "us"; - } - - //---------------------------------------------------------------------------- - // Get directory entries - //---------------------------------------------------------------------------- - Status Utils::GetDirectoryEntries( std::vector &entries, - const std::string &path ) - { - DIR *dp = opendir( path.c_str() ); - if( !dp ) - return Status( stError, errOSError, errno ); - - dirent *dirEntry; - - while( (dirEntry = readdir(dp)) != 0 ) - { - std::string entryName = dirEntry->d_name; - if( !entryName.compare( 0, 2, "..") ) - continue; - if( !entryName.compare( 0, 1, ".") ) - continue; - - entries.push_back( dirEntry->d_name ); - } - - closedir(dp); - - return Status(); - } - - //---------------------------------------------------------------------------- - // Process a config file and return key-value pairs - //---------------------------------------------------------------------------- - Status Utils::ProcessConfig( std::map &config, - const std::string &file ) - { - config.clear(); - std::ifstream inFile( file.c_str() ); - if( !inFile.good() ) - return Status( stError, errOSError, errno ); - - errno = 0; - std::string line; - while( std::getline( inFile, line ) ) - { - if( line.empty() || line[0] == '#' ) - continue; - - std::vector elems; - splitString( elems, line, "=" ); - if( elems.size() != 2 ) - return Status( stError, errConfig ); - std::string key = elems[0]; Trim( key ); - std::string value = elems[1]; Trim( value ); - config[key] = value; - } - - if( errno ) - return Status( stError, errOSError, errno ); - return Status(); - } - - //---------------------------------------------------------------------------- - // Trim a string - //---------------------------------------------------------------------------- - void Utils::Trim( std::string &str ) - { - str.erase( str.begin(), - std::find_if( str.begin(), str.end(), isNotSpace ) ); - str.erase( std::find_if( str.rbegin(), str.rend(), isNotSpace ).base(), - str.end() ); - } - - //---------------------------------------------------------------------------- - // Log property list - //---------------------------------------------------------------------------- - void Utils::LogPropertyList( Log *log, - uint64_t topic, - const char *format, - const PropertyList &list ) - { - PropertyList::PropertyMap::const_iterator it; - std::string keyVals; - for( it = list.begin(); it != list.end(); ++it ) - keyVals += "'" + it->first + "' = '" + it->second + "', "; - keyVals.erase( keyVals.length()-2, 2 ); - log->Dump( topic, format, keyVals.c_str() ); - } - - //---------------------------------------------------------------------------- - // Print a char array as hex - //---------------------------------------------------------------------------- - std::string Utils::Char2Hex( uint8_t *array, uint16_t size ) - { - char *hex = new char[2*size+1]; - for( uint16_t i = 0; i < size; ++i ) - snprintf( hex+(2*i), 3, "%02x", (int)array[i] ); - std::string result = hex; - delete [] hex; - return result; - } - - //---------------------------------------------------------------------------- - // Normalize checksum - //---------------------------------------------------------------------------- - std::string Utils::NormalizeChecksum( const std::string &name, - const std::string &checksum ) - { - if( name == "adler32" || name == "crc32" ) - { - size_t i; - for( i = 0; i < checksum.length(); ++i ) - if( checksum[i] != '0' ) - break; - return checksum.substr(i); - } - return checksum; - } -} diff --git a/src/XrdCl/XrdClUtils.hh b/src/XrdCl/XrdClUtils.hh deleted file mode 100644 index 2ae8c013ba5..00000000000 --- a/src/XrdCl/XrdClUtils.hh +++ /dev/null @@ -1,316 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_UTILS_HH__ -#define __XRD_CL_UTILS_HH__ - -#include -#include -#include "XrdCl/XrdClStatus.hh" -#include "XrdCl/XrdClLog.hh" -#include "XrdCl/XrdClURL.hh" -#include "XrdCl/XrdClXRootDResponses.hh" -#include "XrdCl/XrdClPropertyList.hh" -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdCl/XrdClConstants.hh" -#include "XrdNet/XrdNetUtils.hh" - -#include - -#ifdef __linux__ -#include -#endif - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - //! Random utilities - //---------------------------------------------------------------------------- - class Utils - { - public: - //------------------------------------------------------------------------ - //! Split a string - //------------------------------------------------------------------------ - template - static void splitString( Container &result, - const std::string &input, - const std::string &delimiter ) - { - size_t start = 0; - size_t end = 0; - size_t length = 0; - - do - { - end = input.find( delimiter, start ); - - if( end != std::string::npos ) - length = end - start; - else - length = input.length() - start; - - if( length ) - result.push_back( input.substr( start, length ) ); - - start = end + delimiter.size(); - } - while( end != std::string::npos ); - } - - //------------------------------------------------------------------------ - //! Get a parameter either from the environment or URL - //------------------------------------------------------------------------ - static int GetIntParameter( const URL &url, - const std::string &name, - int defaultVal ); - - //------------------------------------------------------------------------ - //! Get a parameter either from the environment or URL - //------------------------------------------------------------------------ - static std::string GetStringParameter( const URL &url, - const std::string &name, - const std::string &defaultVal ); - - //------------------------------------------------------------------------ - //! Address type - //------------------------------------------------------------------------ - enum AddressType - { - IPAuto = 0, - IPAll = 1, - IPv6 = 2, - IPv4 = 3, - IPv4Mapped6 = 4 - }; - - //------------------------------------------------------------------------ - //! Interpret a string as address type, default to IPAll - //------------------------------------------------------------------------ - static AddressType String2AddressType( const std::string &addressType ); - - //------------------------------------------------------------------------ - //! Resolve IP addresses - //------------------------------------------------------------------------ - static Status GetHostAddresses( std::vector &addresses, - const URL &url, - AddressType type ); - - //------------------------------------------------------------------------ - //! Log all the addresses on the list - //------------------------------------------------------------------------ - static void LogHostAddresses( Log *log, - uint64_t type, - const std::string &hostId, - std::vector &addresses ); - - //------------------------------------------------------------------------ - //! Convert timestamp to a string - //------------------------------------------------------------------------ - static std::string TimeToString( time_t timestamp ); - - //------------------------------------------------------------------------ - //! Get the elapsed microseconds between two timevals - //------------------------------------------------------------------------ - static uint64_t GetElapsedMicroSecs( timeval start, timeval end ); - - //------------------------------------------------------------------------ - //! Get a checksum from a remote xrootd server - //------------------------------------------------------------------------ - static XRootDStatus GetRemoteCheckSum( std::string &checkSum, - const std::string &checkSumType, - const std::string &server, - const std::string &path ); - - //------------------------------------------------------------------------ - //! Get a checksum from local file - //------------------------------------------------------------------------ - static XRootDStatus GetLocalCheckSum( std::string &checkSum, - const std::string &checkSumType, - const std::string &path ); - - //------------------------------------------------------------------------ - //! Convert bytes to a human readable string - //------------------------------------------------------------------------ - static std::string BytesToString( uint64_t bytes ); - - //------------------------------------------------------------------------ - //! Check if peer supports tpc - //------------------------------------------------------------------------ - static XRootDStatus CheckTPC( const std::string &server, - uint16_t timeout = 0 ); - - //------------------------------------------------------------------------ - //! Convert the fully qualified host name to country code - //------------------------------------------------------------------------ - static std::string FQDNToCC( const std::string &fqdn ); - - //------------------------------------------------------------------------ - //! Get directory entries - //------------------------------------------------------------------------ - static Status GetDirectoryEntries( std::vector &entries, - const std::string &path ); - - //------------------------------------------------------------------------ - //! Process a config file and return key-value pairs - //------------------------------------------------------------------------ - static Status ProcessConfig( std::map &config, - const std::string &file ); - - //------------------------------------------------------------------------ - //! Trim a string - //------------------------------------------------------------------------ - static void Trim( std::string &str ); - - //------------------------------------------------------------------------ - //! Log property list - //------------------------------------------------------------------------ - static void LogPropertyList( Log *log, - uint64_t topic, - const char *format, - const PropertyList &list ); - - //------------------------------------------------------------------------ - //! Print a char array as hex - //------------------------------------------------------------------------ - static std::string Char2Hex( uint8_t *array, uint16_t size ); - - //------------------------------------------------------------------------ - //! Normalize checksum - //------------------------------------------------------------------------ - static std::string NormalizeChecksum( const std::string &name, - const std::string &checksum ); - }; - - //---------------------------------------------------------------------------- - //! Smart descriptor - closes the descriptor on destruction - //---------------------------------------------------------------------------- - class ScopedDescriptor - { - public: - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - ScopedDescriptor( int descriptor ): pDescriptor( descriptor ) {} - - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - ~ScopedDescriptor() { if( pDescriptor >= 0 ) close( pDescriptor ); } - - //------------------------------------------------------------------------ - //! Release the descriptor being held - //------------------------------------------------------------------------ - int Release() - { - int desc = pDescriptor; - pDescriptor = -1; - return desc; - } - - //------------------------------------------------------------------------ - //! Get the descriptor - //------------------------------------------------------------------------ - int GetDescriptor() - { - return pDescriptor; - } - - private: - int pDescriptor; - }; - -#ifdef __linux__ - //---------------------------------------------------------------------------- - //! Scoped fsuid and fsgid setter, restoring original values on destruction - //---------------------------------------------------------------------------- - class ScopedFsUidSetter - { - public: - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - ScopedFsUidSetter(uid_t fsuid, gid_t fsgid, const std::string &streamName) - : pFsUid(fsuid), pFsGid(fsgid), pStreamName(streamName) - { - pOk = true; - pPrevFsUid = -1; - pPrevFsGid = -1; - - //---------------------------------------------------------------------- - //! Set fsuid - //---------------------------------------------------------------------- - if(pFsUid >= 0) { - pPrevFsUid = setfsuid(pFsUid); - - if(setfsuid(pFsUid) != pFsUid) { - pOk = false; - return; - } - } - - //---------------------------------------------------------------------- - //! Set fsgid - //---------------------------------------------------------------------- - if(pFsGid >= 0) { - pPrevFsGid = setfsgid(pFsGid); - - if(setfsgid(pFsGid) != pFsGid) { - pOk = false; - return; - } - } - } - - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - ~ScopedFsUidSetter() { - Log *log = DefaultEnv::GetLog(); - - if(pPrevFsUid >= 0) { - int retcode = setfsuid(pPrevFsUid); - log->Dump(XRootDTransportMsg, "[%s] Restored fsuid from %d to %d", pStreamName.c_str(), retcode, pPrevFsUid); - } - - if(pPrevFsGid >= 0) { - int retcode = setfsgid(pPrevFsGid); - log->Dump(XRootDTransportMsg, "[%s] Restored fsgid from %d to %d", pStreamName.c_str(), retcode, pPrevFsGid); - } - } - - bool IsOk() const { - return pOk; - } - - private: - int pFsUid; - int pFsGid; - - const std::string &pStreamName; - - int pPrevFsUid; - int pPrevFsGid; - - bool pOk; - }; -#endif - -} - -#endif // __XRD_CL_UTILS_HH__ diff --git a/src/XrdCl/XrdClXCpCtx.cc b/src/XrdCl/XrdClXCpCtx.cc deleted file mode 100644 index a44c0afdce6..00000000000 --- a/src/XrdCl/XrdClXCpCtx.cc +++ /dev/null @@ -1,199 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2017 by European Organization for Nuclear Research (CERN) -// Author: Michal Simon -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#include "XrdCl/XrdClXCpCtx.hh" -#include "XrdCl/XrdClXCpSrc.hh" -#include "XrdCl/XrdClLog.hh" -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdCl/XrdClConstants.hh" - -#include - -namespace XrdCl -{ - -XCpCtx::XCpCtx( const std::vector &urls, uint64_t blockSize, uint8_t parallelSrc, uint64_t chunkSize, uint64_t parallelChunks, int64_t fileSize ) : - pUrls( std::deque( urls.begin(), urls.end() ) ), pBlockSize( blockSize ), - pParallelSrc( parallelSrc ), pChunkSize( chunkSize ), pParallelChunks( parallelChunks ), - pOffset( 0 ), pFileSize( -1 ), pFileSizeCV( 0 ), pDataReceived( 0 ), pDone( false ), - pDoneCV( 0 ), pRefCount( 1 ) -{ - SetFileSize( fileSize ); -} - -XCpCtx::~XCpCtx() -{ - // at this point there's no concurrency - // this object dies as the last one - while( !pSink.IsEmpty() ) - { - ChunkInfo *chunk = pSink.Get(); - if( chunk ) - XCpSrc::DeleteChunk( chunk ); - } -} - -bool XCpCtx::GetNextUrl( std::string & url ) -{ - XrdSysMutexHelper lck( pMtx ); - if( pUrls.empty() ) return false; - url = pUrls.front(); - pUrls.pop(); - return true; -} - -XCpSrc* XCpCtx::WeakestLink( XCpSrc *exclude ) -{ - uint64_t transferRate = -1; // set transferRate to max uint64 value - XCpSrc *ret = 0; - - std::list::iterator itr; - for( itr = pSources.begin() ; itr != pSources.end() ; ++itr ) - { - XCpSrc *src = *itr; - if( src == exclude ) continue; - uint64_t tmp = src->TransferRate(); - if( src->HasData() && tmp < transferRate ) - { - ret = src; - transferRate = tmp; - } - } - - return ret; -} - -void XCpCtx::PutChunk( ChunkInfo* chunk ) -{ - pSink.Put( chunk ); -} - -std::pair XCpCtx::GetBlock() -{ - XrdSysMutexHelper lck( pMtx ); - - uint64_t blkSize = pBlockSize, offset = pOffset; - if( pOffset + blkSize > uint64_t( pFileSize ) ) - blkSize = pFileSize - pOffset; - pOffset += blkSize; - - return std::make_pair( offset, blkSize ); -} - -void XCpCtx::SetFileSize( int64_t size ) -{ - XrdSysMutexHelper lck( pMtx ); - if( pFileSize < 0 && size >= 0 ) - { - XrdSysCondVarHelper lck( pFileSizeCV ); - pFileSize = size; - pFileSizeCV.Broadcast(); - - if( pBlockSize > uint64_t( pFileSize ) / pParallelSrc ) - pBlockSize = pFileSize / pParallelSrc; - - if( pBlockSize < pChunkSize ) - pBlockSize = pChunkSize; - } -} - -XRootDStatus XCpCtx::Initialize() -{ - for( uint8_t i = 0; i < pParallelSrc; ++i ) - { - XCpSrc *src = new XCpSrc( pChunkSize, pParallelChunks, pFileSize, this ); - pSources.push_back( src ); - src->Start(); - } - - if( pSources.empty() ) - { - Log *log = DefaultEnv::GetLog(); - log->Error( UtilityMsg, "Failed to initialize (failed to create new threads)" ); - return XRootDStatus( stError, errInternal, EAGAIN, "XCpCtx: failed to create new threads." ); - } - - return XRootDStatus(); -} - -XRootDStatus XCpCtx::GetChunk( XrdCl::ChunkInfo &ci ) -{ - // if we received all the data we are done here - if( pDataReceived == uint64_t( pFileSize ) ) - { - XrdSysCondVarHelper lck( pDoneCV ); - pDone = true; - pDoneCV.Broadcast(); - return XRootDStatus( stOK, suDone ); - } - - // if we don't have active sources it means we failed - if( GetRunning() == 0 ) - { - XrdSysCondVarHelper lck( pDoneCV ); - pDone = true; - pDoneCV.Broadcast(); - return XRootDStatus( stError, errNoMoreReplicas ); - } - - ChunkInfo *chunk = pSink.Get(); - if( chunk ) - { - pDataReceived += chunk->length; - ci = *chunk; - delete chunk; - return XRootDStatus( stOK, suContinue ); - } - - return XRootDStatus( stOK, suRetry ); -} - -void XCpCtx::NotifyIdleSrc() -{ - pDoneCV.Broadcast(); -} - -bool XCpCtx::AllDone() -{ - XrdSysCondVarHelper lck( pDoneCV ); - - if( !pDone ) - pDoneCV.Wait( 60 ); - - return pDone; -} - -size_t XCpCtx::GetRunning() -{ - // count active sources - size_t nbRunning = 0; - std::list::iterator itr; - for( itr = pSources.begin() ; itr != pSources.end() ; ++ itr) - if( (*itr)->IsRunning() ) - ++nbRunning; - return nbRunning; -} - - -} /* namespace XrdCl */ diff --git a/src/XrdCl/XrdClXCpCtx.hh b/src/XrdCl/XrdClXCpCtx.hh deleted file mode 100644 index 3909c42d943..00000000000 --- a/src/XrdCl/XrdClXCpCtx.hh +++ /dev/null @@ -1,305 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2017 by European Organization for Nuclear Research (CERN) -// Author: Michal Simon -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#ifndef SRC_XRDCL_XRDCLXCPCTX_HH_ -#define SRC_XRDCL_XRDCLXCPCTX_HH_ - -#include "XrdCl/XrdClSyncQueue.hh" -#include "XrdCl/XrdClXRootDResponses.hh" -#include "XrdSys/XrdSysPthread.hh" - -#include -#include - -namespace XrdCl -{ - -class XCpSrc; - -class XCpCtx -{ - public: - - /** - * Constructor - * - * @param urls : list of replica urls - * @param blockSize : the default block size - * @param parallelSrc : maximum number of parallel sources - * @param chunkSize : the default chunk size - * @param parallelChunks : the default number of parallel chunks per source - * @param fileSize : the file size if specified in the metalink file - * (-1 indicates that the file size is not known and - * a stat should be done) - */ - XCpCtx( const std::vector &urls, uint64_t blockSize, uint8_t parallelSrc, uint64_t chunkSize, uint64_t parallelChunks, int64_t fileSize ); - - /** - * Deletes the instance if the reference counter reached 0. - */ - void Delete() - { - XrdSysMutexHelper lck( pMtx ); - --pRefCount; - if( !pRefCount ) - { - lck.UnLock(); - delete this; - } - } - - /** - * Increments the reference counter. - * - * @return : myself. - */ - XCpCtx* Self() - { - XrdSysMutexHelper lck( pMtx ); - ++pRefCount; - return this; - } - - /** - * Gets the next URL from the list of file replicas - * - * @param url : the output parameter - * @return : true if a url has been written to the - * url parameter, false otherwise - */ - bool GetNextUrl( std::string & url ); - - /** - * Get the 'weakest' sources - * - * @param exclude : the source that is excluded from the - * search - * @return : the weakest source - */ - XCpSrc* WeakestLink( XCpSrc *exclude ); - - /** - * Put a chunk into the sink - * - * @param chunk : the chunk - */ - void PutChunk( ChunkInfo* chunk ); - - /** - * Get next block that has to be transfered - * - * @return : pair of offset and block size - */ - std::pair GetBlock(); - - /** - * Set the file size (GetSize will block until - * SetFileSize will be called). - * Also calculates the block size. - * - * @param size : file size - */ - void SetFileSize( int64_t size ); - - /** - * Get file size. The call blocks until the file - * size is being set using SetFileSize. - */ - int64_t GetSize() - { - XrdSysCondVarHelper lck( pFileSizeCV ); - while( pFileSize < 0 && GetRunning() > 0 ) pFileSizeCV.Wait(); - return pFileSize; - } - - /** - * Starts one thread per source, each thread - * tries to open a file, stat the file if necessary, - * and then starts reading the file, all chunks read - * go to the sink. - * - * @return Error if we were not able to create any threads - */ - XRootDStatus Initialize(); - - /** - * Gets the next chunk from the sink, if the sink is empty blocks. - * - * @param ci : the chunk retrieved from sink (output parameter) - * @retrun : stError if we failed to transfer the file, - * stOK otherwise, with one of the following codes: - * - suDone : the whole file has been transfered, - * we are done - * - suContinue : a chunk has been written into ci, - * continue calling GetChunk in order - * to retrieve remaining chunks - * - suRetry : a chunk has not been written into ci, - * try again. - */ - XRootDStatus GetChunk( XrdCl::ChunkInfo &ci ); - - /** - * Remove given source - * - * @param src : the source to be removed - */ - void RemoveSrc( XCpSrc *src ) - { - XrdSysMutexHelper lck( pMtx ); - pSources.remove( src ); - } - - /** - * Notify idle sources, used in two case: - * - if one of the sources failed and an - * idle source needs to take over - * - or if we are done and all idle source - * should be stopped - */ - void NotifyIdleSrc(); - - /** - * Returns true if all chunks have been transfered, - * otherwise blocks until NotifyIdleSrc is called, - * or a 1 minute timeout occurs. - * - * @return : true is all chunks have been transfered, - * false otherwise. - */ - bool AllDone(); - - /** - * Notify those who are waiting for initialization. - * In particular the GetSize() caller will be waiting - * on the result of initialization. - */ - void NotifyInitExpectant() - { - pFileSizeCV.Broadcast(); - } - - - private: - - /** - * Returns the number of active sources - * - * @return : number of active sources - */ - size_t GetRunning(); - - /** - * Destructor (private). - * - * Use Delelte to destroy the object. - */ - virtual ~XCpCtx(); - - /** - * The URLs of all the replicas that were provided - * to us. - */ - std::queue pUrls; - - /** - * The size of the block allocated to a single source. - */ - uint64_t pBlockSize; - - /** - * Number of parallel sources. - */ - uint8_t pParallelSrc; - - /** - * Chunk size. - */ - uint32_t pChunkSize; - - /** - * Number of parallel chunks per source. - */ - uint8_t pParallelChunks; - - /** - * Offset in the file (everything before the offset - * has been allocated, everything after the offset - * needs to be allocated) - */ - uint64_t pOffset; - - /** - * File size. - */ - int64_t pFileSize; - - /** - * File Size conditional variable. - * (notifies waiters if the file size has been set) - */ - XrdSysCondVar pFileSizeCV; - - /** - * List of sources. Those pointers are not owned by - * this object. - */ - std::list pSources; - - /** - * A queue shared between all the sources (producers), - * and the extreme copy context (consumer). - */ - SyncQueue pSink; - - /** - * Total amount of data received - */ - uint64_t pDataReceived; - - /** - * A flag, true if all chunks have been received and we are done, - * false otherwise - */ - bool pDone; - - /** - * A condition variable, idle sources wait on this cond var until - * we are done, or until one of the active sources fails. - */ - XrdSysCondVar pDoneCV; - - /** - * A mutex guarding the object - */ - XrdSysMutex pMtx; - - /** - * Reference counter - */ - size_t pRefCount; -}; - -} /* namespace XrdCl */ - -#endif /* SRC_XRDCL_XRDCLXCPCTX_HH_ */ diff --git a/src/XrdCl/XrdClXCpSrc.cc b/src/XrdCl/XrdClXCpSrc.cc deleted file mode 100644 index 9ce9bb667f5..00000000000 --- a/src/XrdCl/XrdClXCpSrc.cc +++ /dev/null @@ -1,543 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2017 by European Organization for Nuclear Research (CERN) -// Author: Michal Simon -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#include "XrdCl/XrdClXCpSrc.hh" -#include "XrdCl/XrdClXCpCtx.hh" -#include "XrdCl/XrdClLog.hh" -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdCl/XrdClConstants.hh" - -#include -#include - -namespace XrdCl -{ - -class ChunkHandler: public ResponseHandler -{ - public: - - ChunkHandler( XCpSrc *src, uint64_t offset, uint64_t size, char *buffer, File *handle ) : - pSrc( src->Self() ), pOffset( offset ), pSize( size ), pBuffer( buffer ), pHandle( handle ) - { - - } - - virtual ~ChunkHandler() - { - pSrc->Delete(); - } - - virtual void HandleResponse( XRootDStatus *status, AnyObject *response ) - { - ChunkInfo *chunk = 0; - if( response ) // get the response - { - response->Get( chunk ); - response->Set( ( int* )0 ); - delete response; - } - - if( !chunk && status->IsOK() ) // if the response is not there make sure the status is error - { - *status = XRootDStatus( stError, errInternal ); - } - - if( status->IsOK() && chunk->length != pSize ) // the file size on the server is different - { // than the one specified in metalink file - *status = XRootDStatus( stError, errDataError ); - } - - if( !status->IsOK() ) - { - delete[] pBuffer; - delete chunk; - chunk = 0; - } - - pSrc->ReportResponse( status, chunk, pHandle ); - - delete this; - } - - private: - - XCpSrc *pSrc; - uint64_t pOffset; - uint64_t pSize; - char *pBuffer; - File *pHandle; -}; - - -XCpSrc::XCpSrc( uint32_t chunkSize, uint8_t parallel, int64_t fileSize, XCpCtx *ctx ) : - pChunkSize( chunkSize ), pParallel( parallel ), pFileSize( fileSize ), pThread(), - pCtx( ctx->Self() ), pFile( 0 ), pCurrentOffset( 0 ), pBlkEnd( 0 ), pDataTransfered( 0 ), pRefCount( 1 ), - pRunning( false ), pStartTime( 0 ), pTransferTime( 0 ) -{ - -} - -XCpSrc::~XCpSrc() -{ - pCtx->RemoveSrc( this ); - pCtx->Delete(); -} - -void XCpSrc::Start() -{ - pRunning = true; - int rc = pthread_create( &pThread, 0, Run, this ); - if( rc ) - { - pRunning = false; - pCtx->RemoveSrc( this ); - pCtx->Delete(); - } -} - -void* XCpSrc::Run( void* arg ) -{ - XCpSrc *me = static_cast( arg ); - me->StartDownloading(); - me->Delete(); - return 0; -} - -void XCpSrc::StartDownloading() -{ - XRootDStatus st = Initialize(); - if( !st.IsOK() ) - { - pRunning = false; - // notify those who wait for the file - // size, they wont get it from this - // source - pCtx->NotifyInitExpectant(); - // put a null chunk so we are sure - // the main thread doesn't get stuck - // at the sync queue - pCtx->PutChunk( 0 ); - return; - } - - // start counting transfer time - pStartTime = time( 0 ); - - while( pRunning ) - { - st = ReadChunks(); - if( st.IsOK() && st.code == suPartial ) - { - // we have only ongoing transfers - // so we can already ask for new block - if( GetWork().IsOK() ) continue; - } - else if( st.IsOK() && st.code == suDone ) - { - // if we are done, try to get more work, - // if successful continue - if( GetWork().IsOK() ) continue; - // keep track of the time before we go idle - pTransferTime += time( 0 ) - pStartTime; - // check if the overall download process is - // done, this makes the thread wait until - // either the download is done, or a source - // went to error, or a 60s timeout has been - // reached (the timeout is there so we can - // check if a source degraded in the meanwhile - // and now we can steal from it) - if( !pCtx->AllDone() ) - { - // reset start time after pause - pStartTime = time( 0 ); - continue; - } - // stop counting - // otherwise we are done here - pRunning = false; - return; - } - - XRootDStatus *status = pReports.Get(); - if( !status->IsOK() ) - { - Log *log = DefaultEnv::GetLog(); - std::string myHost = URL( pUrl ).GetHostName(); - log->Error( UtilityMsg, "Failed to read chunk from %s: %s", myHost.c_str(), status->GetErrorMessage().c_str() ); - - if( !Recover().IsOK() ) - { - delete status; - pRunning = false; - // notify idle sources, they might be - // interested in taking over my workload - pCtx->NotifyIdleSrc(); - // put a null chunk so we are sure - // the main thread doesn't get stuck - // at the sync queue - pCtx->PutChunk( 0 ); - // if we have data we need to wait for someone to take over - // unless the extreme copy is over, in this case we don't care - while( HasData() && !pCtx->AllDone() ); - - return; - } - } - delete status; - } -} - -XRootDStatus XCpSrc::Initialize() -{ - Log *log = DefaultEnv::GetLog(); - XRootDStatus st; - - do - { - if( !pCtx->GetNextUrl( pUrl ) ) - { - log->Error( UtilityMsg, "Failed to initialize XCp source, no more replicas to try" ); - return XRootDStatus( stError ); - } - - log->Debug( UtilityMsg, "Opening %s for reading", pUrl.c_str() ); - - std::string value; - DefaultEnv::GetEnv()->GetString( "ReadRecovery", value ); - - pFile = new File(); - pFile->SetProperty( "ReadRecovery", value ); - - st = pFile->Open( pUrl, OpenFlags::Read ); - if( !st.IsOK() ) - { - log->Warning( UtilityMsg, "Failed to open %s for reading: %s", pUrl.c_str(), st.GetErrorMessage().c_str() ); - DeletePtr( pFile ); - continue; - } - - if( pFileSize < 0 ) - { - StatInfo *statInfo = 0; - st = pFile->Stat( false, statInfo ); - if( !st.IsOK() ) - { - log->Warning( UtilityMsg, "Failed to stat %s: %s", pUrl.c_str(), st.GetErrorMessage().c_str() ); - DeletePtr( pFile ); - continue; - } - pFileSize = statInfo->GetSize(); - pCtx->SetFileSize( pFileSize ); - delete statInfo; - } - } - while( !st.IsOK() ); - - std::pair p = pCtx->GetBlock(); - pCurrentOffset = p.first; - pBlkEnd = p.second + p.first; - - return st; -} - -XRootDStatus XCpSrc::Recover() -{ - Log *log = DefaultEnv::GetLog(); - XRootDStatus st; - - do - { - if( !pCtx->GetNextUrl( pUrl ) ) - { - log->Error( UtilityMsg, "Failed to initialize XCp source, no more replicas to try" ); - return XRootDStatus( stError ); - } - - log->Debug( UtilityMsg, "Opening %s for reading", pUrl.c_str() ); - - std::string value; - DefaultEnv::GetEnv()->GetString( "ReadRecovery", value ); - - pFile = new File(); - pFile->SetProperty( "ReadRecovery", value ); - - st = pFile->Open( pUrl, OpenFlags::Read ); - if( !st.IsOK() ) - { - DeletePtr( pFile ); - log->Warning( UtilityMsg, "Failed to open %s for reading: %s", pUrl.c_str(), st.GetErrorMessage().c_str() ); - } - } - while( !st.IsOK() ); - - pRecovered.insert( pOngoing.begin(), pOngoing.end() ); - pOngoing.clear(); - - // since we have a brand new source, we need - // to restart transfer rate statistics - pTransferTime = 0; - pStartTime = time( 0 ); - pDataTransfered = 0; - - return st; -} - -XRootDStatus XCpSrc::ReadChunks() -{ - XrdSysMutexHelper lck( pMtx ); - - while( pOngoing.size() < pParallel && !pRecovered.empty() ) - { - std::pair p; - std::map::iterator itr = pRecovered.begin(); - p = *itr; - pOngoing.insert( p ); - pRecovered.erase( itr ); - - char *buffer = new char[p.second]; - ChunkHandler *handler = new ChunkHandler( this, p.first, p.second, buffer, pFile ); - XRootDStatus st = pFile->Read( p.first, p.second, buffer, handler ); - if( !st.IsOK() ) - { - delete[] buffer; - delete handler; - ReportResponse( new XRootDStatus( st ), 0, pFile ); - return st; - } - } - - while( pOngoing.size() < pParallel && pCurrentOffset < pBlkEnd ) - { - uint64_t chunkSize = pChunkSize; - if( pCurrentOffset + chunkSize > pBlkEnd ) - chunkSize = pBlkEnd - pCurrentOffset; - pOngoing[pCurrentOffset] = chunkSize; - char *buffer = new char[chunkSize]; - ChunkHandler *handler = new ChunkHandler( this, pCurrentOffset, chunkSize, buffer, pFile ); - XRootDStatus st = pFile->Read( pCurrentOffset, chunkSize, buffer, handler ); - pCurrentOffset += chunkSize; - if( !st.IsOK() ) - { - delete[] buffer; - delete handler; - ReportResponse( new XRootDStatus( st ), 0, pFile ); - return st; - } - } - - if( pOngoing.empty() ) return XRootDStatus( stOK, suDone ); - - if( pRecovered.empty() && pCurrentOffset >= pBlkEnd ) return XRootDStatus( stOK, suPartial ); - - return XRootDStatus( stOK, suContinue ); -} - -void XCpSrc::ReportResponse( XRootDStatus *status, ChunkInfo *chunk, File *handle ) -{ - XrdSysMutexHelper lck( pMtx ); - bool ignore = false; - - if( status->IsOK() ) - { - // if the status is OK remove it from - // the list of ongoing transfers, if it - // was not on the list we ignore the - // response (this could happen due to - // source change or stealing) - ignore = !pOngoing.erase( chunk->offset ); - } - else if( FilesEqual( pFile, handle ) ) - { - // if the status is NOT OK, and pFile - // match the handle it means that we see - // an error for the first time, map the - // broken file to the number of outstanding - // asynchronous operations and reset the pointer - pFailed[pFile] = pOngoing.size(); - pFile = 0; - } - else - DeletePtr( status ); - - if( !FilesEqual( pFile, handle ) ) - { - // if the pFile does not match the handle, - // it means that this response came from - // a broken source, decrement the count of - // outstanding async operations for this src, - --pFailed[handle]; - if( pFailed[handle] == 0 ) - { - // if this was the last outstanding operation - // close the file and delete it - pFailed.erase( handle ); - XRootDStatus st = handle->Close(); - delete handle; - } - } - - lck.UnLock(); - - if( status ) pReports.Put( status ); - - if( ignore ) - { - DeleteChunk( chunk ); - return; - } - - if( chunk ) - { - pDataTransfered += chunk->length; - pCtx->PutChunk( chunk ); - } -} - -void XCpSrc::Steal( XCpSrc *src ) -{ - if( !src ) return; - - XrdSysMutexHelper lck1( pMtx ), lck2( src->pMtx ); - - Log *log = DefaultEnv::GetLog(); - std::string myHost = URL( pUrl ).GetHostName(), srcHost = URL( src->pUrl ).GetHostName(); - - if( !src->pRunning ) - { - // the source we are stealing from is in error state, we can have everything - - pRecovered.insert( src->pOngoing.begin(), src->pOngoing.end() ); - pRecovered.insert( src->pRecovered.begin(), src->pRecovered.end() ); - pCurrentOffset = src->pCurrentOffset; - pBlkEnd = src->pBlkEnd; - - src->pOngoing.clear(); - src->pRecovered.clear(); - src->pCurrentOffset = 0; - src->pBlkEnd = 0; - - // a broken source might be waiting for - // someone to take over his data, so we - // need to notify - pCtx->NotifyIdleSrc(); - - log->Debug( UtilityMsg, "s%: Stealing everything from %s", myHost.c_str(), srcHost.c_str() ); - - return; - } - - // the source we are stealing from is just slower, only take part of its work - // so we want a fraction of its work we want for ourself - uint64_t myTransferRate = TransferRate(), srcTransferRate = src->TransferRate(); - if( myTransferRate == 0 ) return; - double fraction = double( myTransferRate ) / double( myTransferRate + srcTransferRate ); - - if( src->pCurrentOffset < src->pBlkEnd ) - { - // the source still has a block of data - uint64_t blkSize = src->pBlkEnd - src->pCurrentOffset; - uint64_t steal = static_cast( round( fraction * blkSize ) ); - // if after stealing there will be less than one chunk - // take everything - if( blkSize - steal <= pChunkSize ) - steal = blkSize; - - pCurrentOffset = src->pBlkEnd - steal; - pBlkEnd = src->pBlkEnd; - src->pBlkEnd -= steal; - - log->Debug( UtilityMsg, "s%: Stealing fraction (%f) of block from %s", myHost.c_str(), fraction, srcHost.c_str() ); - - return; - } - - if( !src->pRecovered.empty() ) - { - size_t count = static_cast( round( fraction * src->pRecovered.size() ) ); - while( count-- ) - { - std::map::iterator itr = src->pRecovered.begin(); - pRecovered.insert( *itr ); - src->pRecovered.erase( itr ); - } - - log->Debug( UtilityMsg, "s%: Stealing fraction (%f) of recovered chunks from %s", myHost.c_str(), fraction, srcHost.c_str() ); - - return; - } - - // * a fraction < 0.5 means that we are actually slower (so it does - // not make sense to steal ongoing's from someone who's faster) - // * a fraction ~ 0.5 means that we have more or less the same transfer - // rate (similarly, it doesn't make sense to steal) - // * the source needs to be really faster (though, this is an arbitrary - // choice) to actually steal something - if( !src->pOngoing.empty() && fraction > 0.7 ) - { - size_t count = static_cast( round( fraction * src->pOngoing.size() ) ); - while( count-- ) - { - std::map::iterator itr = src->pOngoing.begin(); - pRecovered.insert( *itr ); - src->pOngoing.erase( itr ); - } - - log->Debug( UtilityMsg, "s%: Stealing fraction (%f) of ongoing chunks from %s", myHost.c_str(), fraction, srcHost.c_str() ); - } -} - -XRootDStatus XCpSrc::GetWork() -{ - std::pair p = pCtx->GetBlock(); - - if( p.second > 0 ) - { - XrdSysMutexHelper lck( pMtx ); - pCurrentOffset = p.first; - pBlkEnd = p.first + p.second; - - Log *log = DefaultEnv::GetLog(); - std::string myHost = URL( pUrl ).GetHostName(); - log->Debug( UtilityMsg, "s% got next block", myHost.c_str() ); - - return XRootDStatus(); - } - - XCpSrc *wLink = pCtx->WeakestLink( this ); - Steal( wLink ); - - // if we managed to steal something declare success - if( pCurrentOffset < pBlkEnd || !pRecovered.empty() ) return XRootDStatus(); - // otherwise return an error - return XRootDStatus( stError, errInvalidOp ); -} - -uint64_t XCpSrc::TransferRate() -{ - time_t duration = pTransferTime + time( 0 ) - pStartTime; - return pDataTransfered / ( duration + 1 ); // add one to avoid floating point exception -} - -} /* namespace XrdCl */ diff --git a/src/XrdCl/XrdClXCpSrc.hh b/src/XrdCl/XrdClXCpSrc.hh deleted file mode 100644 index 23799fe5d73..00000000000 --- a/src/XrdCl/XrdClXCpSrc.hh +++ /dev/null @@ -1,367 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2017 by European Organization for Nuclear Research (CERN) -// Author: Michal Simon -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#ifndef SRC_XRDCL_XRDCLXCPSRC_HH_ -#define SRC_XRDCL_XRDCLXCPSRC_HH_ - -#include "XrdCl/XrdClFile.hh" -#include "XrdCl/XrdClSyncQueue.hh" -#include "XrdSys/XrdSysPthread.hh" - -namespace XrdCl -{ - -class XCpCtx; - -class XCpSrc -{ - friend class ChunkHandler; - - public: - - /** - * Constructor. - * - * @param chunkSize : default chunk size - * @param parallel : number of parallel chunks - * @param fileSize : file size if available (e.g. in metalink file), - * should be set to -1 if not available, in this case - * a stat will be performed during initialization - * @param ctx : Extreme Copy context - */ - XCpSrc( uint32_t chunkSize, uint8_t parallel, int64_t fileSize, XCpCtx *ctx ); - - /** - * Creates new thread with XCpSrc::Run as the start routine. - */ - void Start(); - - /** - * Stops the thread. - */ - void Stop() - { - pRunning = false; - } - - /** - * Deletes the instance if the reference counter reached 0. - */ - void Delete() - { - XrdSysMutexHelper lck( pMtx ); - --pRefCount; - if( !pRefCount ) - { - lck.UnLock(); - delete this; - } - } - - /** - * Increments the reference counter. - * - * @return : myself. - */ - XCpSrc* Self() - { - XrdSysMutexHelper lck( pMtx ); - ++pRefCount; - return this; - } - - /** - * @return : true if the thread is running, false otherwise - */ - bool IsRunning() - { - return pRunning; - } - - /** - * @return true if the source has a block of non zero - * size / some chunks allocated, false otherwise - */ - bool HasData() - { - XrdSysMutexHelper lck( pMtx ); - return pCurrentOffset < pBlkEnd || !pRecovered.empty() || !pOngoing.empty(); - } - - - - /** - * Get the transfer rate for current source - * - * @return : transfer rate for current source [B/s] - */ - uint64_t TransferRate(); - - /** - * Delete ChunkInfo object, and set the pointer to null. - * - * @param chunk : the chunk to be deleted - */ - static void DeleteChunk( ChunkInfo *&chunk ) - { - if( chunk ) - { - delete[] static_cast( chunk->buffer ); - delete chunk; - chunk = 0; - } - } - - private: - - /** - * Destructor (private). - * - * Use Delelte() method to destroy the object. - */ - virtual ~XCpSrc(); - - /** - * The start routine. - */ - static void* Run( void* arg ); - - /** - * Initializes the object first. - * Afterwards, starts the download. - */ - void StartDownloading(); - - /** - * Initializes the object: - * - Opens a file (retries with another - * URL, in case of failure) - * - Stats the file if necessary - * - Gets the first block (offset and size) - * for download - * - * @return : error in case the object could not be initialized - */ - XRootDStatus Initialize(); - - /** - * Tries to open the file at the next available URL. - * Moves all ongoing chunk to recovered. - * - * @return : error if run out of URLs to try, - * success otherwise - */ - XRootDStatus Recover(); - - /** - * Asynchronously reads consecutive chunks. - * - * @return : operation status: - * - suContinue : I still have work to do - * - suPartial : I only have ongoing transfers, - * but the block has been consumed - * - suDone : We are done, the block has been - * consumed, there are no ongoing - * transfers, and there are no new - * data - */ - XRootDStatus ReadChunks(); - - /** - * Steal work from given source. - * - * - if it is a failed source we can have everything - * - otherwise, if the source has a block of size - * greater than 0, steal respective fraction of - * the block - * - otherwise, if the source has recovered chunks, - * steal respective fraction of those chunks - * - otherwise, steal respective fraction of ongoing - * chunks, if we are a faster source - * - * @param src : the source from whom we are stealing - */ - void Steal( XCpSrc *src ); - - /** - * Get more work. - * First try to get a new block. - * If there are no blocks remaining, - * try stealing from others. - * - * @return : error if didn't got any data to transfer - */ - XRootDStatus GetWork(); - - /** - * This method is used by ChunkHandler to report the result of a write, - * to the source object. - * - * @param stats : operation status - * @param chunk : the read chunk (if operation failed, should be null) - * @param handle : the file object used to read the chunk - */ - void ReportResponse( XRootDStatus *status, ChunkInfo *chunk, File *handle ); - - /** - * Delets a pointer and sets it to null. - */ - template - static void DeletePtr( T *&obj ) - { - delete obj; - obj = 0; - } - - /** - * Check if two file object point to the same URL. - * - * @return : true if both files point to the same URL, - * false otherwise - */ - static bool FilesEqual( File *f1, File *f2 ) - { - if( !f1 || !f2 ) return false; - - const std::string lastURL = "LastURL"; - std::string url1, url2; - - f1->GetProperty( lastURL, url1 ); - f2->GetProperty( lastURL, url2 ); - - // remove cgi information - size_t pos = url1.find( '?' ); - if( pos != std::string::npos ) - url1 = url1.substr( 0 , pos ); - pos = url2.find( '?' ); - if( pos != std::string::npos ) - url2 = url2.substr( 0 , pos ); - - return url1 == url2; - } - - /** - * Default chunk size - */ - uint32_t pChunkSize; - - /** - * Number of parallel chunks - */ - uint8_t pParallel; - - /** - * The file size - */ - int64_t pFileSize; - - /** - * Thread id - */ - pthread_t pThread; - - /** - * Extreme Copy context - */ - XCpCtx *pCtx; - - /** - * Source URL. - */ - std::string pUrl; - - /** - * Handle to the file. - */ - File *pFile; - - std::map pFailed; - - /** - * The offset of the next chunk to be transfered. - */ - uint64_t pCurrentOffset; - - /** - * End of the our block. - */ - uint64_t pBlkEnd; - - /** - * Total number of data transfered from this source. - */ - uint64_t pDataTransfered; - - /** - * A map of ongoing transfers (the offset is the key, - * the chunk size is the value). - */ - std::map pOngoing; - - /** - * A map of stolen chunks (again the offset is the key, - * the chunk size is the value). - */ - std::map pRecovered; - - /** - * Sync queue with reports (statuses) from async reads - * that have been issued. An error appears only once - * per URL (independently of how many concurrent async - * reads are allowed). - */ - SyncQueue pReports; - - /** - * A mutex guarding the object. - */ - XrdSysRecMutex pMtx; - - /** - * Reference counter - */ - size_t pRefCount; - - /** - * A flag, true means the source is running, - * false means the source has been stopped, - * or failed. - */ - bool pRunning; - - /** - * The time when we started / restarted chunks - */ - time_t pStartTime; - - /** - * The total time we were transferring data, before - * the restart - */ - time_t pTransferTime; -}; - -} /* namespace XrdCl */ - -#endif /* SRC_XRDCL_XRDCLXCPSRC_HH_ */ diff --git a/src/XrdCl/XrdClXRootDMsgHandler.cc b/src/XrdCl/XrdClXRootDMsgHandler.cc deleted file mode 100644 index a0f274724ae..00000000000 --- a/src/XrdCl/XrdClXRootDMsgHandler.cc +++ /dev/null @@ -1,2083 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2014 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#include "XrdCl/XrdClXRootDMsgHandler.hh" -#include "XrdCl/XrdClLog.hh" -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdCl/XrdClConstants.hh" -#include "XrdCl/XrdClXRootDTransport.hh" -#include "XrdCl/XrdClMessage.hh" -#include "XrdCl/XrdClURL.hh" -#include "XrdCl/XrdClUglyHacks.hh" -#include "XrdCl/XrdClUtils.hh" -#include "XrdCl/XrdClTaskManager.hh" -#include "XrdCl/XrdClJobManager.hh" -#include "XrdCl/XrdClSIDManager.hh" -#include "XrdCl/XrdClMessageUtils.hh" -#include "XrdCl/XrdClLocalFileHandler.hh" - -#include // for network unmarshalling stuff -#include "XrdSys/XrdSysPlatform.hh" // same as above -#include -#include - -namespace -{ - //---------------------------------------------------------------------------- - // We need an extra task what will run the handler in the future, because - // tasks get deleted and we need the handler - //---------------------------------------------------------------------------- - class WaitTask: public XrdCl::Task - { - public: - WaitTask( XrdCl::XRootDMsgHandler *handler ): pHandler( handler ) - { - std::ostringstream o; - o << "WaitTask for: 0x" << handler->GetRequest(); - SetName( o.str() ); - } - - virtual time_t Run( time_t now ) - { - pHandler->WaitDone( now ); - return 0; - } - private: - XrdCl::XRootDMsgHandler *pHandler; - }; - -}; - -namespace XrdCl -{ - - //---------------------------------------------------------------------------- - // Delegate the response handling to the thread-pool - //---------------------------------------------------------------------------- - class HandleRspJob: public XrdCl::Job - { - public: - HandleRspJob( XrdCl::XRootDMsgHandler *handler ): pHandler( handler ) - { - - } - - virtual ~HandleRspJob() - { - - } - - virtual void Run( void *arg ) - { - pHandler->HandleResponse(); - delete this; - } - private: - XrdCl::XRootDMsgHandler *pHandler; - }; - - //---------------------------------------------------------------------------- - // Examine an incoming message, and decide on the action to be taken - //---------------------------------------------------------------------------- - uint16_t XRootDMsgHandler::Examine( Message *msg ) - { - if( msg->GetSize() < 8 ) - return Ignore; - - ServerResponse *rsp = (ServerResponse *)msg->GetBuffer(); - ClientRequest *req = (ClientRequest *)pRequest->GetBuffer(); - uint16_t status = 0; - uint32_t dlen = 0; - - //-------------------------------------------------------------------------- - // We got an async message - //-------------------------------------------------------------------------- - if( rsp->hdr.status == kXR_attn ) - { - if( msg->GetSize() < 12 ) - return Ignore; - - //------------------------------------------------------------------------ - // We only care about async responses - //------------------------------------------------------------------------ - if( rsp->body.attn.actnum != (int32_t)htonl(kXR_asynresp) ) - return Ignore; - - if( msg->GetSize() < 24 ) - return Ignore; - - //------------------------------------------------------------------------ - // Check if the message has the stream ID that we're interested in - //------------------------------------------------------------------------ - ServerResponse *embRsp = (ServerResponse*)msg->GetBuffer(16); - if( embRsp->hdr.streamid[0] != req->header.streamid[0] || - embRsp->hdr.streamid[1] != req->header.streamid[1] ) - return Ignore; - - status = ntohs( embRsp->hdr.status ); - dlen = ntohl( embRsp->hdr.dlen ); - } - //-------------------------------------------------------------------------- - // We got a sync message - check if it belongs to us - //-------------------------------------------------------------------------- - else - { - if( rsp->hdr.streamid[0] != req->header.streamid[0] || - rsp->hdr.streamid[1] != req->header.streamid[1] ) - return Ignore; - - status = rsp->hdr.status; - dlen = rsp->hdr.dlen; - } - - //-------------------------------------------------------------------------- - // We take the ownership of the message and decide what we will do - // with the handler itself, the options are: - // 1) we want to either read in raw mode (the Raw flag) or have the message - // body reconstructed for us by the TransportHandler by the time - // Process() is called (default, no extra flag) - // 2) we either got a full response in which case we don't want to be - // notified about anything anymore (RemoveHandler) or we got a partial - // answer and we need to wait for more (default, no extra flag) - //-------------------------------------------------------------------------- - pResponse = msg; - - Log *log = DefaultEnv::GetLog(); - switch( status ) - { - //------------------------------------------------------------------------ - // Handle the cached cases - //------------------------------------------------------------------------ - case kXR_error: - case kXR_redirect: - case kXR_wait: - return Take | RemoveHandler; - - case kXR_waitresp: - pResponse = 0; - return Take | Ignore; // This must be handled synchronously! - - //------------------------------------------------------------------------ - // Handle the potential raw cases - //------------------------------------------------------------------------ - case kXR_ok: - { - //---------------------------------------------------------------------- - // For kXR_read we read in raw mode if we haven't got the full message - // already (handler installed to late and the message has been cached) - //---------------------------------------------------------------------- - uint16_t reqId = ntohs( req->header.requestid ); - if( reqId == kXR_read && msg->GetSize() == 8 ) - { - pReadRawStarted = false; - pAsyncMsgSize = dlen; - return Take | Raw | RemoveHandler; - } - - //---------------------------------------------------------------------- - // kXR_readv is the same as kXR_read - //---------------------------------------------------------------------- - if( reqId == kXR_readv && msg->GetSize() == 8 ) - { - pAsyncMsgSize = dlen; - pReadVRawMsgOffset = 0; - return Take | Raw | RemoveHandler; - } - - //---------------------------------------------------------------------- - // For everything else we just take what we got - //---------------------------------------------------------------------- - return Take | RemoveHandler; - } - - //------------------------------------------------------------------------ - // kXR_oksofars are special, they are not full responses, so we reset - // the response pointer to 0 and add the message to the partial list - //------------------------------------------------------------------------ - case kXR_oksofar: - { - log->Dump( XRootDMsg, "[%s] Got a kXR_oksofar response to request " - "%s", pUrl.GetHostId().c_str(), - pRequest->GetDescription().c_str() ); - pResponse = 0; - pPartialResps.push_back( msg ); - - //---------------------------------------------------------------------- - // For kXR_read we either read in raw mode if the message has not - // been fully reconstructed already, if it has, we adjust - // the buffer offset to prepare for the next one - //---------------------------------------------------------------------- - uint16_t reqId = ntohs( req->header.requestid ); - if( reqId == kXR_read ) - { - if( msg->GetSize() == 8 ) - { - pReadRawStarted = false; - pAsyncMsgSize = dlen; - return Take | Raw | NoProcess; - } - else - { - pReadRawCurrentOffset += dlen; - return Take | NoProcess; - } - } - - //---------------------------------------------------------------------- - // kXR_readv is similar to read, except that the payload is different - //---------------------------------------------------------------------- - if( reqId == kXR_readv ) - { - if( msg->GetSize() == 8 ) - { - pAsyncMsgSize = dlen; - pReadVRawMsgOffset = 0; - return Take | Raw | NoProcess; - } - else - return Take | NoProcess; - } - - return Take | NoProcess; - } - - //------------------------------------------------------------------------ - // Default - //------------------------------------------------------------------------ - default: - return Take | RemoveHandler; - } - return Take | RemoveHandler; - } - - //---------------------------------------------------------------------------- - // Get handler sid - //---------------------------------------------------------------------------- - uint16_t XRootDMsgHandler::GetSid() const - { - ClientRequest* req = (ClientRequest*) pRequest->GetBuffer(); - return ((uint16_t)req->header.streamid[1] << 8) | (uint16_t)req->header.streamid[0]; - } - - //---------------------------------------------------------------------------- - //! Process the message if it was "taken" by the examine action - //---------------------------------------------------------------------------- - void XRootDMsgHandler::Process( Message *msg ) - { - Log *log = DefaultEnv::GetLog(); - - ServerResponse *rsp = (ServerResponse *)msg->GetBuffer(); - ClientRequest *req = (ClientRequest *)pRequest->GetBuffer(); - - //-------------------------------------------------------------------------- - // We got an async message - //-------------------------------------------------------------------------- - if( rsp->hdr.status == kXR_attn ) - { - log->Dump( XRootDMsg, "[%s] Got an async response to message %s, " - "processing it", pUrl.GetHostId().c_str(), - pRequest->GetDescription().c_str() ); - Message *embededMsg = new Message( rsp->hdr.dlen-8 ); - embededMsg->Append( msg->GetBuffer( 16 ), rsp->hdr.dlen-8 ); - XRDCL_SMART_PTR_T msgPtr( msg ); - pResponse = embededMsg; // this can never happen for oksofars - - // we need to unmarshall the header by hand - XRootDTransport::UnMarshallHeader( embededMsg ); - - //------------------------------------------------------------------------ - // Check if the dlen field of the embedded message is consistent with - // the dlen value of the original message - //------------------------------------------------------------------------ - ServerResponse *embRsp = (ServerResponse *)embededMsg->GetBuffer(); - if( embRsp->hdr.dlen != rsp->hdr.dlen-16 ) - { - log->Error( XRootDMsg, "[%s] Sizes of the async response to %s and the " - "embedded message are inconsistent. Expected %d, got %d.", - pUrl.GetHostId().c_str(), pRequest->GetDescription().c_str(), - rsp->hdr.dlen-16, embRsp->hdr.dlen); - - pStatus = Status( stFatal, errInvalidMessage ); - HandleResponse(); - return; - } - - Process( embededMsg ); - return; - } - - //-------------------------------------------------------------------------- - // If it is a local file, it can be only a metalink redirector - //-------------------------------------------------------------------------- - if( pUrl.IsLocalFile() && pUrl.IsMetalink() ) - { - pHosts->back().flags = kXR_isManager; - pHosts->back().protocol = kXR_PROTOCOLVERSION; - } - //-------------------------------------------------------------------------- - // We got an answer, check who we were talking to - //-------------------------------------------------------------------------- - else - { - AnyObject qryResult; - int *qryResponse = 0; - pPostMaster->QueryTransport( pUrl, XRootDQuery::ServerFlags, qryResult ); - qryResult.Get( qryResponse ); - pHosts->back().flags = *qryResponse; delete qryResponse; qryResponse = 0; - pPostMaster->QueryTransport( pUrl, XRootDQuery::ProtocolVersion, qryResult ); - qryResult.Get( qryResponse ); - pHosts->back().protocol = *qryResponse; delete qryResponse; - } - - //-------------------------------------------------------------------------- - // Process the message - //-------------------------------------------------------------------------- - Status st = XRootDTransport::UnMarshallBody( msg, req->header.requestid ); - if( !st.IsOK() ) - { - pStatus = Status( stFatal, errInvalidMessage ); - HandleResponse(); - return; - } - - switch( rsp->hdr.status ) - { - //------------------------------------------------------------------------ - // kXR_ok - we're done here - //------------------------------------------------------------------------ - case kXR_ok: - { - log->Dump( XRootDMsg, "[%s] Got a kXR_ok response to request %s", - pUrl.GetHostId().c_str(), - pRequest->GetDescription().c_str() ); - pStatus = Status(); - HandleResponse(); - return; - } - - //------------------------------------------------------------------------ - // kXR_error - we've got a problem - //------------------------------------------------------------------------ - case kXR_error: - { - char *errmsg = new char[rsp->hdr.dlen-3]; errmsg[rsp->hdr.dlen-4] = 0; - memcpy( errmsg, rsp->body.error.errmsg, rsp->hdr.dlen-4 ); - log->Dump( XRootDMsg, "[%s] Got a kXR_error response to request %s " - "[%d] %s", pUrl.GetHostId().c_str(), - pRequest->GetDescription().c_str(), rsp->body.error.errnum, - errmsg ); - delete [] errmsg; - - HandleError( Status(stError, errErrorResponse, rsp->body.error.errnum), - pResponse ); - return; - } - - //------------------------------------------------------------------------ - // kXR_redirect - they tell us to go elsewhere - //------------------------------------------------------------------------ - case kXR_redirect: - { - XRDCL_SMART_PTR_T msgPtr( pResponse ); - pResponse = 0; - - if( rsp->hdr.dlen < 4 ) - { - log->Error( XRootDMsg, "[%s] Got invalid redirect response.", - pUrl.GetHostId().c_str() ); - pStatus = Status( stError, errInvalidResponse ); - HandleResponse(); - return; - } - - char *urlInfoBuff = new char[rsp->hdr.dlen-3]; - urlInfoBuff[rsp->hdr.dlen-4] = 0; - memcpy( urlInfoBuff, rsp->body.redirect.host, rsp->hdr.dlen-4 ); - std::string urlInfo = urlInfoBuff; - delete [] urlInfoBuff; - log->Dump( XRootDMsg, "[%s] Got kXR_redirect response to " - "message %s: %s, port %d", pUrl.GetHostId().c_str(), - pRequest->GetDescription().c_str(), urlInfo.c_str(), - rsp->body.redirect.port ); - - //---------------------------------------------------------------------- - // Check if we can proceed - //---------------------------------------------------------------------- - if( !pRedirectCounter ) - { - log->Warning( XRootDMsg, "[%s] Redirect limit has been reached for " - "message %s, the last known error is: %s", - pUrl.GetHostId().c_str(), - pRequest->GetDescription().c_str(), - pLastError.ToString().c_str() ); - - - pStatus = Status( stFatal, errRedirectLimit ); - HandleResponse(); - return; - } - --pRedirectCounter; - - //---------------------------------------------------------------------- - // Keep the info about this server if we still need to find a load - // balancer - //---------------------------------------------------------------------- - if( !pHasLoadBalancer ) - { - uint32_t flags = pHosts->back().flags; - if( flags & kXR_isManager ) - { - //------------------------------------------------------------------ - // If the current server is a meta manager then it supersedes - // any existing load balancer, otherwise we assign a load-balancer - // only if it has not been already assigned - //------------------------------------------------------------------ - if( ( flags & kXR_attrMeta ) || !pLoadBalancer.url.IsValid() ) - { - pLoadBalancer = pHosts->back(); - log->Dump( XRootDMsg, "[%s] Current server has been assigned " - "as a load-balancer for message %s", - pUrl.GetHostId().c_str(), - pRequest->GetDescription().c_str() ); - HostList::iterator it; - for( it = pHosts->begin(); it != pHosts->end(); ++it ) - it->loadBalancer = false; - pHosts->back().loadBalancer = true; - } - } - } - - //---------------------------------------------------------------------- - // Build the URL and check it's validity - //---------------------------------------------------------------------- - std::vector urlComponents; - std::string newCgi; - Utils::splitString( urlComponents, urlInfo, "?" ); - - std::ostringstream o; - - o << urlComponents[0]; - if( rsp->body.redirect.port != -1 ) - o << ":" << rsp->body.redirect.port << "/"; - - URL newUrl = URL( o.str() ); - if( !newUrl.IsValid() ) - { - pStatus = Status( stError, errInvalidRedirectURL ); - log->Error( XRootDMsg, "[%s] Got invalid redirection URL: %s", - pUrl.GetHostId().c_str(), urlInfo.c_str() ); - HandleResponse(); - return; - } - - if( pUrl.GetUserName() != "" && newUrl.GetUserName() == "" ) - newUrl.SetUserName( pUrl.GetUserName() ); - - if( pUrl.GetPassword() != "" && newUrl.GetPassword() == "" ) - newUrl.SetPassword( pUrl.GetPassword() ); - - //---------------------------------------------------------------------- - // Forward any "xrd.*" params from the original client request also to - // the new redirection url - //---------------------------------------------------------------------- - std::ostringstream ossXrd; - const URL::ParamsMap &urlParams = pUrl.GetParams(); - - for(URL::ParamsMap::const_iterator it = urlParams.begin(); - it != urlParams.end(); ++it ) - { - if( it->first.compare( 0, 4, "xrd." ) ) - continue; - - ossXrd << it->first << '=' << it->second << '&'; - } - - std::string xrdCgi = ossXrd.str(); - pUrl = newUrl; - pRedirectUrl = newUrl.GetURL(); - - URL cgiURL; - if( urlComponents.size() > 1 ) - { - pRedirectUrl += "?"; - pRedirectUrl += urlComponents[1]; - std::ostringstream o; - o << "fake://fake:111//fake?"; - o << urlComponents[1]; - - if (!xrdCgi.empty()) - { - o << '&' << xrdCgi; - pRedirectUrl += '&'; - pRedirectUrl += xrdCgi; - } - - cgiURL = URL( o.str() ); - } - else { - if (!xrdCgi.empty()) - { - std::ostringstream o; - o << "fake://fake:111//fake?"; - o << xrdCgi; - cgiURL = URL( o.str() ); - pRedirectUrl += '?'; - pRedirectUrl += xrdCgi; - } - } - - //---------------------------------------------------------------------- - // Check if we need to return the URL as a response - //---------------------------------------------------------------------- - if( pUrl.GetProtocol() != "root" && pUrl.GetProtocol() != "xroot" && - !pUrl.IsLocalFile() ) - pRedirectAsAnswer = true; - - if( pRedirectAsAnswer ) - { - pStatus = Status( stError, errRedirect ); - pResponse = msgPtr.release(); - HandleResponse(); - return; - } - - //---------------------------------------------------------------------- - // Rewrite the message in a way required to send it to another server - //---------------------------------------------------------------------- - Status st = RewriteRequestRedirect( cgiURL.GetParams(), - pUrl.GetPath() ); - if( !st.IsOK() ) - { - pStatus = st; - HandleResponse(); - return; - } - - //---------------------------------------------------------------------- - // Send the request to the new location - //---------------------------------------------------------------------- - pHosts->push_back( pUrl ); - pHosts->back().url.SetParams( cgiURL.GetParams() ); - HandleError( RetryAtServer(pUrl) ); - return; - } - - //------------------------------------------------------------------------ - // kXR_wait - we wait, and re-issue the request later - //------------------------------------------------------------------------ - case kXR_wait: - { - XRDCL_SMART_PTR_T msgPtr( pResponse ); - pResponse = 0; - uint32_t waitSeconds = 0; - - if( rsp->hdr.dlen >= 4 ) - { - char *infoMsg = new char[rsp->hdr.dlen-3]; - infoMsg[rsp->hdr.dlen-4] = 0; - memcpy( infoMsg, rsp->body.wait.infomsg, rsp->hdr.dlen-4 ); - log->Dump( XRootDMsg, "[%s] Got kXR_wait response of %d seconds to " - "message %s: %s", pUrl.GetHostId().c_str(), - rsp->body.wait.seconds, pRequest->GetDescription().c_str(), - infoMsg ); - delete [] infoMsg; - waitSeconds = rsp->body.wait.seconds; - } - else - { - log->Dump( XRootDMsg, "[%s] Got kXR_wait response of 0 seconds to " - "message %s", pUrl.GetHostId().c_str(), - pRequest->GetDescription().c_str() ); - } - - //---------------------------------------------------------------------- - // Some messages require rewriting before they can be sent again - // after wait - //---------------------------------------------------------------------- - Status st = RewriteRequestWait(); - if( !st.IsOK() ) - { - pStatus = st; - HandleResponse(); - return; - } - - //---------------------------------------------------------------------- - // Register a task to resend the message in some seconds, if we still - // have time to do that, and report a timeout otherwise - //---------------------------------------------------------------------- - time_t resendTime = ::time(0)+waitSeconds; - - if( resendTime < pExpiration ) - { - TaskManager *taskMgr = pPostMaster->GetTaskManager(); - taskMgr->RegisterTask( new WaitTask( this ), resendTime ); - } - else - { - log->Debug( XRootDMsg, "[%s] Wait time is too long, timing out %s", - pUrl.GetHostId().c_str(), - pRequest->GetDescription().c_str() ); - pStatus = Status( stError, errOperationExpired ); - HandleResponse(); - } - return; - } - - //------------------------------------------------------------------------ - // kXR_waitresp - the response will be returned in some seconds as an - // unsolicited message. Currently all messages of this type are handled - // one step before in the XrdClStream::OnIncoming as they need to be - // processed synchronously. - //------------------------------------------------------------------------ - case kXR_waitresp: - { - XRDCL_SMART_PTR_T msgPtr( pResponse ); - pResponse = 0; - - if( rsp->hdr.dlen < 4 ) - { - log->Error( XRootDMsg, "[%s] Got invalid waitresp response.", - pUrl.GetHostId().c_str() ); - pStatus = Status( stError, errInvalidResponse ); - HandleResponse(); - return; - } - - log->Dump( XRootDMsg, "[%s] Got kXR_waitresp response of %d seconds to " - "message %s", pUrl.GetHostId().c_str(), - rsp->body.waitresp.seconds, - pRequest->GetDescription().c_str() ); - return; - } - - //------------------------------------------------------------------------ - // Default - unrecognized/unsupported response, declare an error - //------------------------------------------------------------------------ - default: - { - XRDCL_SMART_PTR_T msgPtr( pResponse ); - pResponse = 0; - log->Dump( XRootDMsg, "[%s] Got unrecognized response %d to " - "message %s", pUrl.GetHostId().c_str(), - rsp->hdr.status, pRequest->GetDescription().c_str() ); - pStatus = Status( stError, errInvalidResponse ); - HandleResponse(); - return; - } - } - - return; - } - - //---------------------------------------------------------------------------- - // Handle an event other that a message arrival - may be timeout - //---------------------------------------------------------------------------- - uint8_t XRootDMsgHandler::OnStreamEvent( StreamEvent event, - uint16_t streamNum, - Status status ) - { - Log *log = DefaultEnv::GetLog(); - log->Dump( XRootDMsg, "[%s] Stream event reported for msg %s", - pUrl.GetHostId().c_str(), pRequest->GetDescription().c_str() ); - - if( event == Ready ) - return 0; - - if( streamNum != 0 ) - return 0; - - HandleError( status, 0 ); - return RemoveHandler; - } - - //---------------------------------------------------------------------------- - // Read message body directly from a socket - //---------------------------------------------------------------------------- - Status XRootDMsgHandler::ReadMessageBody( Message *msg, - int socket, - uint32_t &bytesRead ) - { - ClientRequest *req = (ClientRequest *)pRequest->GetBuffer(); - uint16_t reqId = ntohs( req->header.requestid ); - if( reqId == kXR_read ) - return ReadRawRead( msg, socket, bytesRead ); - - if( reqId == kXR_readv ) - return ReadRawReadV( msg, socket, bytesRead ); - - return ReadRawOther( msg, socket, bytesRead ); - } - - //---------------------------------------------------------------------------- - // Handle a kXR_read in raw mode - //---------------------------------------------------------------------------- - Status XRootDMsgHandler::ReadRawRead( Message *msg, - int socket, - uint32_t &bytesRead ) - { - Log *log = DefaultEnv::GetLog(); - - //-------------------------------------------------------------------------- - // We need to check if we have and overflow, before we start reading - // anything - //-------------------------------------------------------------------------- - if( !pReadRawStarted ) - { - ChunkInfo chunk = pChunkList->front(); - pAsyncOffset = 0; - pAsyncReadSize = pAsyncMsgSize; - pAsyncReadBuffer = ((char*)chunk.buffer)+pReadRawCurrentOffset; - if( pReadRawCurrentOffset + pAsyncMsgSize > chunk.length ) - { - log->Error( XRootDMsg, "[%s] Overflow data while reading response to %s" - ": expected: %d, got %d bytes", - pUrl.GetHostId().c_str(), pRequest->GetDescription().c_str(), - chunk.length, pReadRawCurrentOffset + pAsyncMsgSize ); - - pChunkStatus.front().sizeError = true; - pOtherRawStarted = false; - } - else - pReadRawCurrentOffset += pAsyncMsgSize; - pReadRawStarted = true; - } - - //-------------------------------------------------------------------------- - // If we have an overflow we discard all the incoming data. We do this - // instead of just quitting in order to keep the stream sane. - //-------------------------------------------------------------------------- - if( pChunkStatus.front().sizeError ) - return ReadRawOther( msg, socket, bytesRead ); - - //-------------------------------------------------------------------------- - // Read the data - //-------------------------------------------------------------------------- - return ReadAsync( socket, bytesRead ); - } - - //---------------------------------------------------------------------------- - // Handle a kXR_readv in raw mode - //---------------------------------------------------------------------------- - Status XRootDMsgHandler::ReadRawReadV( Message *msg, - int socket, - uint32_t &bytesRead ) - { - if( pReadVRawMsgOffset == pAsyncMsgSize ) - return Status( stOK, suDone ); - - Log *log = DefaultEnv::GetLog(); - - //-------------------------------------------------------------------------- - // We've had an error and we are in the discarding mode - //-------------------------------------------------------------------------- - if( pReadVRawMsgDiscard ) - { - Status st = ReadAsync( socket, bytesRead ); - - if( st.IsOK() && st.code == suDone ) - { - pReadVRawMsgOffset += pAsyncReadSize; - pReadVRawChunkHeaderDone = false; - pReadVRawChunkHeaderStarted = false; - pReadVRawMsgDiscard = false; - delete [] pAsyncReadBuffer; - - if( pReadVRawMsgOffset != pAsyncMsgSize ) - st.code = suRetry; - - log->Dump( XRootDMsg, "[%s] ReadRawReadV: Discarded %d bytes, " - "current offset: %d/%d", pUrl.GetHostId().c_str(), - pAsyncReadSize, pReadVRawMsgOffset, pAsyncMsgSize ); - } - return st; - } - - //-------------------------------------------------------------------------- - // Handle chunk header - //-------------------------------------------------------------------------- - if( !pReadVRawChunkHeaderDone ) - { - //------------------------------------------------------------------------ - // Set up the header reading - //------------------------------------------------------------------------ - if( !pReadVRawChunkHeaderStarted ) - { - pReadVRawChunkHeaderStarted = true; - - //---------------------------------------------------------------------- - // We cannot afford to read the next header from the stream because - // we will cross the message boundary - //---------------------------------------------------------------------- - if( pReadVRawMsgOffset + 16 > pAsyncMsgSize ) - { - uint32_t discardSize = pAsyncMsgSize - pReadVRawMsgOffset; - log->Error( XRootDMsg, "[%s] ReadRawReadV: No enough data to read " - "another chunk header. Discarding %d bytes.", - pUrl.GetHostId().c_str(), discardSize ); - - pReadVRawMsgDiscard = true; - pAsyncOffset = 0; - pAsyncReadSize = discardSize; - pAsyncReadBuffer = new char[discardSize]; - return Status( stOK, suRetry ); - } - - //---------------------------------------------------------------------- - // We set up reading of the next header - //---------------------------------------------------------------------- - pAsyncOffset = 0; - pAsyncReadSize = 16; - pAsyncReadBuffer = (char*)&pReadVRawChunkHeader; - } - - //------------------------------------------------------------------------ - // Do the reading - //------------------------------------------------------------------------ - Status st = ReadAsync( socket, bytesRead ); - - //------------------------------------------------------------------------ - // Finalize the header and set everything up for the actual buffer - //------------------------------------------------------------------------ - if( st.IsOK() && st.code == suDone ) - { - pReadVRawChunkHeaderDone = true; - pReadVRawMsgOffset += 16; - - pReadVRawChunkHeader.rlen = ntohl( pReadVRawChunkHeader.rlen ); - pReadVRawChunkHeader.offset = ntohll( pReadVRawChunkHeader.offset ); - - //---------------------------------------------------------------------- - // Find the buffer corresponding to the chunk - //---------------------------------------------------------------------- - bool chunkFound = false; - for( int i = pReadVRawChunkIndex; i < (int)pChunkList->size(); ++i ) - { - if( (*pChunkList)[i].offset == (uint64_t)pReadVRawChunkHeader.offset && - (*pChunkList)[i].length == (uint32_t)pReadVRawChunkHeader.rlen ) - { - chunkFound = true; - pReadVRawChunkIndex = i; - break; - } - } - - //---------------------------------------------------------------------- - // If the chunk was no found we discard the chunk - //---------------------------------------------------------------------- - if( !chunkFound ) - { - log->Error( XRootDMsg, "[%s] ReadRawReadV: Impossible to find chunk " - "buffer corresponding to %d bytes at %ld", - pUrl.GetHostId().c_str(), pReadVRawChunkHeader.rlen, - pReadVRawChunkHeader.offset ); - - uint32_t discardSize = pReadVRawChunkHeader.rlen; - if( pReadVRawMsgOffset + discardSize > pAsyncMsgSize ) - discardSize = pAsyncMsgSize - pReadVRawMsgOffset; - pReadVRawMsgDiscard = true; - pAsyncOffset = 0; - pAsyncReadSize = discardSize; - pAsyncReadBuffer = new char[discardSize]; - - log->Dump( XRootDMsg, "[%s] ReadRawReadV: Discarding %d bytes", - pUrl.GetHostId().c_str(), discardSize ); - return Status( stOK, suRetry ); - } - - //---------------------------------------------------------------------- - // The chunk was found, but reading all the data will cross the message - // boundary - //---------------------------------------------------------------------- - if( pReadVRawMsgOffset + pReadVRawChunkHeader.rlen > pAsyncMsgSize ) - { - uint32_t discardSize = pAsyncMsgSize - pReadVRawMsgOffset; - - log->Error( XRootDMsg, "[%s] ReadRawReadV: Malformed chunk header: " - "reading %d bytes from message would cross the message " - "boundary, discarding %d bytes.", pUrl.GetHostId().c_str(), - pReadVRawChunkHeader.rlen, discardSize ); - - pReadVRawMsgDiscard = true; - pAsyncOffset = 0; - pAsyncReadSize = discardSize; - pAsyncReadBuffer = new char[discardSize]; - pChunkStatus[pReadVRawChunkIndex].sizeError = true; - return Status( stOK, suRetry ); - } - - //---------------------------------------------------------------------- - // We're good - //---------------------------------------------------------------------- - pAsyncOffset = 0; - pAsyncReadSize = pReadVRawChunkHeader.rlen; - pAsyncReadBuffer = (char*)(*pChunkList)[pReadVRawChunkIndex].buffer; - } - - //------------------------------------------------------------------------ - // We've seen a reading error - //------------------------------------------------------------------------ - if( !st.IsOK() ) - return st; - - //------------------------------------------------------------------------ - // If we are not done reading the header, return back to the event loop. - //------------------------------------------------------------------------ - if( st.IsOK() && st.code != suDone ) - return st; - } - - //-------------------------------------------------------------------------- - // Read the body - //-------------------------------------------------------------------------- - Status st = ReadAsync( socket, bytesRead ); - - if( st.IsOK() && st.code == suDone ) - { - - pReadVRawMsgOffset += pAsyncReadSize; - pReadVRawChunkHeaderDone = false; - pReadVRawChunkHeaderStarted = false; - pChunkStatus[pReadVRawChunkIndex].done = true; - - log->Dump( XRootDMsg, "[%s] ReadRawReadV: read buffer for chunk %d@%ld", - pUrl.GetHostId().c_str(), - pReadVRawChunkHeader.rlen, pReadVRawChunkHeader.offset, - pReadVRawMsgOffset, pAsyncMsgSize ); - - if( pReadVRawMsgOffset < pAsyncMsgSize ) - st.code = suRetry; - } - return st; - } - - //---------------------------------------------------------------------------- - // Handle anything other than kXR_read and kXR_readv in raw mode - //---------------------------------------------------------------------------- - Status XRootDMsgHandler::ReadRawOther( Message *msg, - int socket, - uint32_t &bytesRead ) - { - if( !pOtherRawStarted ) - { - pAsyncOffset = 0; - pAsyncReadSize = pAsyncMsgSize; - pAsyncReadBuffer = new char[pAsyncMsgSize]; - pOtherRawStarted = true; - } - - Status st = ReadAsync( socket, bytesRead ); - - if( st.IsOK() && st.code == suRetry ) - return st; - - delete [] pAsyncReadBuffer; - pAsyncReadBuffer = 0; - pAsyncOffset = pAsyncReadSize = 0; - - return st; - } - - //-------------------------------------------------------------------------- - // Read a buffer asynchronously - depends on pAsyncBuffer, pAsyncSize - // and pAsyncOffset - //-------------------------------------------------------------------------- - Status XRootDMsgHandler::ReadAsync( int socket, uint32_t &bytesRead ) - { - char *buffer = pAsyncReadBuffer; - buffer += pAsyncOffset; - while( pAsyncOffset < pAsyncReadSize ) - { - uint32_t toBeRead = pAsyncReadSize - pAsyncOffset; - int status = ::read( socket, buffer, toBeRead ); - if( status < 0 && (errno == EAGAIN || errno == EWOULDBLOCK) ) - return Status( stOK, suRetry ); - - if( status <= 0 ) - return Status( stError, errSocketError, errno ); - - pAsyncOffset += status; - buffer += status; - bytesRead += status; - } - return Status( stOK, suDone ); - } - - //---------------------------------------------------------------------------- - // We're here when we requested sending something over the wire - // and there has been a status update on this action - //---------------------------------------------------------------------------- - void XRootDMsgHandler::OnStatusReady( const Message *message, - Status status ) - { - Log *log = DefaultEnv::GetLog(); - - //-------------------------------------------------------------------------- - // We were successful, so we now need to listen for a response - //-------------------------------------------------------------------------- - if( status.IsOK() ) - { - log->Dump( XRootDMsg, "[%s] Message %s has been successfully sent.", - pUrl.GetHostId().c_str(), message->GetDescription().c_str() ); - Status st = pPostMaster->Receive( pUrl, this, pExpiration ); - if( st.IsOK() ) - return; - } - - //-------------------------------------------------------------------------- - // We have failed, recover if possible - //-------------------------------------------------------------------------- - log->Error( XRootDMsg, "[%s] Impossible to send message %s. Trying to " - "recover.", pUrl.GetHostId().c_str(), - message->GetDescription().c_str() ); - HandleError( status, 0 ); - } - - //---------------------------------------------------------------------------- - // Are we a raw writer or not? - //---------------------------------------------------------------------------- - bool XRootDMsgHandler::IsRaw() const - { - ClientRequest *req = (ClientRequest *)pRequest->GetBuffer(); - uint16_t reqId = ntohs( req->header.requestid ); - if( reqId == kXR_write || reqId == kXR_writev ) - return true; - return false; - } - - //---------------------------------------------------------------------------- - // Write the message body - //---------------------------------------------------------------------------- - Status XRootDMsgHandler::WriteMessageBody( int socket, - uint32_t &bytesRead ) - { - (void)socket; - (void)bytesRead; - //-------------------------------------------------------------------------- - // We no longer support this type of body writing in XRootDMsgHandler - //-------------------------------------------------------------------------- - return Status( stError, errNotSupported ); - } - - //---------------------------------------------------------------------------- - // We're here when we got a time event. We needed to re-issue the request - // in some time in the future, and that moment has arrived - //---------------------------------------------------------------------------- - void XRootDMsgHandler::WaitDone( time_t ) - { - HandleError( RetryAtServer(pUrl) ); - } - - //---------------------------------------------------------------------------- - // Unpack the message and call the response handler - //---------------------------------------------------------------------------- - void XRootDMsgHandler::HandleResponse() - { - //-------------------------------------------------------------------------- - // Process the response and notify the listener - //-------------------------------------------------------------------------- - XRootDTransport::UnMarshallRequest( pRequest ); - XRootDStatus *status = ProcessStatus(); - AnyObject *response = 0; - - if( status->IsOK() ) - { - Status st = ParseResponse( response ); - if( !st.IsOK() ) - { - delete status; - delete response; - status = new XRootDStatus( st ); - response = 0; - } - } - - //-------------------------------------------------------------------------- - // Release the stream id - //-------------------------------------------------------------------------- - if( pSidMgr ) - { - ClientRequest *req = (ClientRequest *)pRequest->GetBuffer(); - if( !status->IsOK() && status->code == errOperationExpired ) - pSidMgr->TimeOutSID( req->header.streamid ); - else - pSidMgr->ReleaseSID( req->header.streamid ); - } - - pResponseHandler->HandleResponseWithHosts( status, response, pHosts ); - - //-------------------------------------------------------------------------- - // As much as I hate to say this, we cannot do more, so we commit - // a suicide... just make sure that this is the last stateful thing - // we'll ever do - //-------------------------------------------------------------------------- - delete this; - } - - - //---------------------------------------------------------------------------- - // Extract the status information from the stuff that we got - //---------------------------------------------------------------------------- - XRootDStatus *XRootDMsgHandler::ProcessStatus() - { - XRootDStatus *st = new XRootDStatus( pStatus ); - ServerResponse *rsp = 0; - if( pResponse ) - rsp = (ServerResponse *)pResponse->GetBuffer(); - - if( !pStatus.IsOK() && rsp ) - { - if( pStatus.code == errErrorResponse ) - { - st->errNo = rsp->body.error.errnum; - char *errmsg = new char[rsp->hdr.dlen-3]; - errmsg[rsp->hdr.dlen-4] = 0; - memcpy( errmsg, rsp->body.error.errmsg, rsp->hdr.dlen-4 ); - st->SetErrorMessage( errmsg ); - delete [] errmsg; - } - else if( pStatus.code == errRedirect ) - st->SetErrorMessage( pRedirectUrl ); - } - return st; - } - - //------------------------------------------------------------------------ - // Parse the response and put it in an object that could be passed to - // the user - //------------------------------------------------------------------------ - Status XRootDMsgHandler::ParseResponse( AnyObject *&response ) - { - if( !pResponse ) - return Status(); - - ServerResponse *rsp = (ServerResponse *)pResponse->GetBuffer(); - ClientRequest *req = (ClientRequest *)pRequest->GetBuffer(); - Log *log = DefaultEnv::GetLog(); - - //-------------------------------------------------------------------------- - // Handle redirect as an answer - //-------------------------------------------------------------------------- - if( rsp->hdr.status == kXR_redirect ) - { - log->Error( XRootDMsg, "Internal Error: unable to process redirect" ); - return 0; - } - - //-------------------------------------------------------------------------- - // We only handle the kXR_ok responses further down - //-------------------------------------------------------------------------- - if( rsp->hdr.status != kXR_ok ) - return 0; - - Buffer buff; - uint32_t length = 0; - char *buffer = 0; - - //-------------------------------------------------------------------------- - // We don't have any partial answers so pass what we have - //-------------------------------------------------------------------------- - if( pPartialResps.empty() ) - { - buffer = rsp->body.buffer.data; - length = rsp->hdr.dlen; - } - //-------------------------------------------------------------------------- - // Partial answers, we need to glue them together before parsing - //-------------------------------------------------------------------------- - else if( req->header.requestid != kXR_read && - req->header.requestid != kXR_readv ) - { - for( uint32_t i = 0; i < pPartialResps.size(); ++i ) - { - ServerResponse *part = (ServerResponse*)pPartialResps[i]->GetBuffer(); - length += part->hdr.dlen; - } - length += rsp->hdr.dlen; - - buff.Allocate( length ); - uint32_t offset = 0; - for( uint32_t i = 0; i < pPartialResps.size(); ++i ) - { - ServerResponse *part = (ServerResponse*)pPartialResps[i]->GetBuffer(); - buff.Append( part->body.buffer.data, part->hdr.dlen, offset ); - offset += part->hdr.dlen; - } - buff.Append( rsp->body.buffer.data, rsp->hdr.dlen, offset ); - buffer = buff.GetBuffer(); - } - - //-------------------------------------------------------------------------- - // Right, but what was the question? - //-------------------------------------------------------------------------- - switch( req->header.requestid ) - { - //------------------------------------------------------------------------ - // kXR_mv, kXR_truncate, kXR_rm, kXR_mkdir, kXR_rmdir, kXR_chmod, - // kXR_ping, kXR_close, kXR_write, kXR_sync - //------------------------------------------------------------------------ - case kXR_mv: - case kXR_truncate: - case kXR_rm: - case kXR_mkdir: - case kXR_rmdir: - case kXR_chmod: - case kXR_ping: - case kXR_close: - case kXR_write: - case kXR_writev: - case kXR_sync: - return Status(); - - //------------------------------------------------------------------------ - // kXR_locate - //------------------------------------------------------------------------ - case kXR_locate: - { - AnyObject *obj = new AnyObject(); - - char *nullBuffer = new char[length+1]; - nullBuffer[length] = 0; - memcpy( nullBuffer, buffer, length ); - - log->Dump( XRootDMsg, "[%s] Parsing the response to %s as " - "LocateInfo: %s", pUrl.GetHostId().c_str(), - pRequest->GetDescription().c_str(), nullBuffer ); - LocationInfo *data = new LocationInfo(); - - if( data->ParseServerResponse( nullBuffer ) == false ) - { - delete obj; - delete data; - delete [] nullBuffer; - return Status( stError, errInvalidResponse ); - } - delete [] nullBuffer; - - obj->Set( data ); - response = obj; - return Status(); - } - - //------------------------------------------------------------------------ - // kXR_stat - //------------------------------------------------------------------------ - case kXR_stat: - { - AnyObject *obj = new AnyObject(); - - //---------------------------------------------------------------------- - // Virtual File System stat (kXR_vfs) - //---------------------------------------------------------------------- - if( req->stat.options & kXR_vfs ) - { - StatInfoVFS *data = new StatInfoVFS(); - - char *nullBuffer = new char[length+1]; - nullBuffer[length] = 0; - memcpy( nullBuffer, buffer, length ); - - log->Dump( XRootDMsg, "[%s] Parsing the response to %s as " - "StatInfoVFS: %s", pUrl.GetHostId().c_str(), - pRequest->GetDescription().c_str(), nullBuffer ); - - if( data->ParseServerResponse( nullBuffer ) == false ) - { - delete obj; - delete data; - delete [] nullBuffer; - return Status( stError, errInvalidResponse ); - } - delete [] nullBuffer; - - obj->Set( data ); - } - //---------------------------------------------------------------------- - // Normal stat - //---------------------------------------------------------------------- - else - { - StatInfo *data = new StatInfo(); - - char *nullBuffer = new char[length+1]; - nullBuffer[length] = 0; - memcpy( nullBuffer, buffer, length ); - - log->Dump( XRootDMsg, "[%s] Parsing the response to %s as StatInfo: " - "%s", pUrl.GetHostId().c_str(), - pRequest->GetDescription().c_str(), nullBuffer ); - - if( data->ParseServerResponse( nullBuffer ) == false ) - { - delete obj; - delete data; - delete [] nullBuffer; - return Status( stError, errInvalidResponse ); - } - delete [] nullBuffer; - obj->Set( data ); - } - - response = obj; - return Status(); - } - - //------------------------------------------------------------------------ - // kXR_protocol - //------------------------------------------------------------------------ - case kXR_protocol: - { - log->Dump( XRootDMsg, "[%s] Parsing the response to %s as ProtocolInfo", - pUrl.GetHostId().c_str(), - pRequest->GetDescription().c_str() ); - - if( rsp->hdr.dlen < 8 ) - { - log->Error( XRootDMsg, "[%s] Got invalid redirect response.", - pUrl.GetHostId().c_str() ); - return Status( stError, errInvalidResponse ); - } - - AnyObject *obj = new AnyObject(); - ProtocolInfo *data = new ProtocolInfo( rsp->body.protocol.pval, - rsp->body.protocol.flags ); - obj->Set( data ); - response = obj; - return Status(); - } - - //------------------------------------------------------------------------ - // kXR_dirlist - //------------------------------------------------------------------------ - case kXR_dirlist: - { - AnyObject *obj = new AnyObject(); - log->Dump( XRootDMsg, "[%s] Parsing the response to %s as " - "DirectoryList", pUrl.GetHostId().c_str(), - pRequest->GetDescription().c_str() ); - - char *path = new char[req->dirlist.dlen+1]; - path[req->dirlist.dlen] = 0; - memcpy( path, pRequest->GetBuffer(24), req->dirlist.dlen ); - - DirectoryList *data = new DirectoryList(); - data->SetParentName( path ); - delete [] path; - - char *nullBuffer = new char[length+1]; - nullBuffer[length] = 0; - memcpy( nullBuffer, buffer, length ); - - if( data->ParseServerResponse( pUrl.GetHostId(), nullBuffer ) == false ) - { - delete data; - delete obj; - delete [] nullBuffer; - return Status( stError, errInvalidResponse ); - } - - delete [] nullBuffer; - obj->Set( data ); - response = obj; - return Status(); - } - - //------------------------------------------------------------------------ - // kXR_open - if we got the statistics, otherwise return 0 - //------------------------------------------------------------------------ - case kXR_open: - { - log->Dump( XRootDMsg, "[%s] Parsing the response to %s as OpenInfo", - pUrl.GetHostId().c_str(), - pRequest->GetDescription().c_str() ); - - if( rsp->hdr.dlen < 4 ) - { - log->Error( XRootDMsg, "[%s] Got invalid open response.", - pUrl.GetHostId().c_str() ); - return Status( stError, errInvalidResponse ); - } - - AnyObject *obj = new AnyObject(); - StatInfo *statInfo = 0; - - //---------------------------------------------------------------------- - // Handle StatInfo if requested - //---------------------------------------------------------------------- - if( req->open.options & kXR_retstat ) - { - log->Dump( XRootDMsg, "[%s] Parsing StatInfo in response to %s", - pUrl.GetHostId().c_str(), - pRequest->GetDescription().c_str() ); - - if( rsp->hdr.dlen >= 12 ) - { - char *nullBuffer = new char[rsp->hdr.dlen-11]; - nullBuffer[rsp->hdr.dlen-12] = 0; - memcpy( nullBuffer, buffer+12, rsp->hdr.dlen-12 ); - - statInfo = new StatInfo(); - if( statInfo->ParseServerResponse( nullBuffer ) == false ) - { - delete statInfo; - statInfo = 0; - } - delete [] nullBuffer; - } - - if( rsp->hdr.dlen < 12 || !statInfo ) - { - log->Error( XRootDMsg, "[%s] Unable to parse StatInfo in response " - "to %s", pUrl.GetHostId().c_str(), - pRequest->GetDescription().c_str() ); - delete obj; - return Status( stError, errInvalidResponse ); - } - } - - OpenInfo *data = new OpenInfo( (uint8_t*)buffer, - pResponse->GetSessionId(), - statInfo ); - obj->Set( data ); - response = obj; - return Status(); - } - - //------------------------------------------------------------------------ - // kXR_read - //------------------------------------------------------------------------ - case kXR_read: - { - log->Dump( XRootDMsg, "[%s] Parsing the response to %s as ChunkInfo", - pUrl.GetHostId().c_str(), - pRequest->GetDescription().c_str() ); - - //---------------------------------------------------------------------- - // Glue in the cached responses if necessary - //---------------------------------------------------------------------- - ChunkInfo chunk = pChunkList->front(); - bool sizeMismatch = false; - uint32_t currentOffset = 0; - char *cursor = (char*)chunk.buffer; - for( uint32_t i = 0; i < pPartialResps.size(); ++i ) - { - ServerResponse *part = (ServerResponse*)pPartialResps[i]->GetBuffer(); - - if( currentOffset + part->hdr.dlen > chunk.length ) - { - sizeMismatch = true; - break; - } - - if( pPartialResps[i]->GetSize() > 8 ) - memcpy( cursor, part->body.buffer.data, part->hdr.dlen ); - currentOffset += part->hdr.dlen; - cursor += part->hdr.dlen; - } - - if( currentOffset + rsp->hdr.dlen <= chunk.length ) - { - if( pResponse->GetSize() > 8 ) - memcpy( cursor, rsp->body.buffer.data, rsp->hdr.dlen ); - currentOffset += rsp->hdr.dlen; - } - else - sizeMismatch = true; - - //---------------------------------------------------------------------- - // Overflow - //---------------------------------------------------------------------- - if( pChunkStatus.front().sizeError || sizeMismatch ) - { - log->Error( XRootDMsg, "[%s] Handling response to %s: user supplied " - "buffer is too small for the received data.", - pUrl.GetHostId().c_str(), - pRequest->GetDescription().c_str() ); - return Status( stError, errInvalidResponse ); - } - - AnyObject *obj = new AnyObject(); - ChunkInfo *retChunk = new ChunkInfo( chunk.offset, currentOffset, - chunk.buffer ); - obj->Set( retChunk ); - response = obj; - return Status(); - } - - //------------------------------------------------------------------------ - // kXR_readv - we need to pass the length of the buffer to the user code - //------------------------------------------------------------------------ - case kXR_readv: - { - log->Dump( XRootDMsg, "[%s] Parsing the response to 0x%x as " - "VectorReadInfo", pUrl.GetHostId().c_str(), - pRequest->GetDescription().c_str() ); - - VectorReadInfo *info = new VectorReadInfo(); - Status st = PostProcessReadV( info ); - if( !st.IsOK() ) - { - delete info; - return st; - } - - AnyObject *obj = new AnyObject(); - obj->Set( info ); - response = obj; - return Status(); - } - - //------------------------------------------------------------------------ - // kXR_query - //------------------------------------------------------------------------ - case kXR_query: - case kXR_set: - case kXR_prepare: - default: - { - AnyObject *obj = new AnyObject(); - log->Dump( XRootDMsg, "[%s] Parsing the response to %s as BinaryData", - pUrl.GetHostId().c_str(), - pRequest->GetDescription().c_str() ); - - BinaryDataInfo *data = new BinaryDataInfo(); - data->Allocate( length ); - data->Append( buffer, length ); - obj->Set( data ); - response = obj; - return Status(); - } - }; - return Status( stError, errInvalidMessage ); - } - - //---------------------------------------------------------------------------- - // Perform the changes to the original request needed by the redirect - // procedure - allocate new streamid, append redirection data and such - //---------------------------------------------------------------------------- - Status XRootDMsgHandler::RewriteRequestRedirect( - const URL::ParamsMap &newCgi, - const std::string &newPath ) - { - Log *log = DefaultEnv::GetLog(); - ClientRequest *req = (ClientRequest *)pRequest->GetBuffer(); - - //-------------------------------------------------------------------------- - // Assign a new stream id to the message - //-------------------------------------------------------------------------- - Status st; - // it could be a redirect from a local metalink, - // in this case the pSidMgr is null - if( pSidMgr ) - { - pSidMgr->ReleaseSID( req->header.streamid ); - pSidMgr = 0; - } - AnyObject sidMgrObj; - // Append any "xrd.*" parameters present in newCgi so that any authentication - // requirements are properly enforced - std::string xrdCgi = ""; - std::ostringstream ossXrd; - for(URL::ParamsMap::const_iterator it = newCgi.begin(); it != newCgi.end(); ++it ) - { - if( it->first.compare( 0, 4, "xrd." ) ) - continue; - ossXrd << it->first << '=' << it->second << '&'; - } - - xrdCgi = ossXrd.str(); - // Redirection URL containing also any original xrd.* opaque parameters - XrdCl::URL authUrl; - - if (xrdCgi.empty()) - { - authUrl = pUrl; - } - else - { - std::string surl = pUrl.GetURL(); - (surl.find('?') == std::string::npos) ? (surl += '?') : - ((*surl.rbegin() != '&') ? (surl += '&') : (surl += "")); - surl += xrdCgi; - - if (!authUrl.FromString(surl)) - { - log->Error( XRootDMsg, "[%s] Failed to build redirection url from data:" - "%s", surl.c_str()); - return Status(stError, errInvalidRedirectURL); - } - } - - // it could be a redirect to a local file, in this case there is no SID - if( !authUrl.IsLocalFile() ) - { - st = pPostMaster->QueryTransport( authUrl, XRootDQuery::SIDManager, - sidMgrObj ); - - if( !st.IsOK() ) - { - log->Error( XRootDMsg, "[%s] Impossible to send message %s.", - pUrl.GetHostId().c_str(), - pRequest->GetDescription().c_str() ); - return st; - } - - sidMgrObj.Get( pSidMgr ); - st = pSidMgr->AllocateSID( req->header.streamid ); - if( !st.IsOK() ) - { - log->Error( XRootDMsg, "[%s] Impossible to send message %s.", - pUrl.GetHostId().c_str(), - pRequest->GetDescription().c_str() ); - return st; - } - } - - //-------------------------------------------------------------------------- - // Rewrite particular requests - //-------------------------------------------------------------------------- - XRootDTransport::UnMarshallRequest( pRequest ); - MessageUtils::RewriteCGIAndPath( pRequest, newCgi, true, newPath ); - XRootDTransport::MarshallRequest( pRequest ); - return Status(); - } - - //---------------------------------------------------------------------------- - // Some requests need to be rewritten also after getting kXR_wait - //---------------------------------------------------------------------------- - Status XRootDMsgHandler::RewriteRequestWait() - { - ClientRequest *req = (ClientRequest *)pRequest->GetBuffer(); - - XRootDTransport::UnMarshallRequest( pRequest ); - - //------------------------------------------------------------------------ - // For kXR_locate and kXR_open request the kXR_refresh bit needs to be - // turned off after wait - //------------------------------------------------------------------------ - switch( req->header.requestid ) - { - case kXR_locate: - { - uint16_t refresh = kXR_refresh; - req->locate.options &= (~refresh); - break; - } - - case kXR_open: - { - uint16_t refresh = kXR_refresh; - req->locate.options &= (~refresh); - break; - } - } - - XRootDTransport::SetDescription( pRequest ); - XRootDTransport::MarshallRequest( pRequest ); - return Status(); - } - - //---------------------------------------------------------------------------- - // Post process vector read - //---------------------------------------------------------------------------- - Status XRootDMsgHandler::PostProcessReadV( VectorReadInfo *vReadInfo ) - { - //-------------------------------------------------------------------------- - // Unpack the stuff that needs to be unpacked - //-------------------------------------------------------------------------- - for( uint32_t i = 0; i < pPartialResps.size(); ++i ) - if( pPartialResps[i]->GetSize() != 8 ) - UnPackReadVResponse( pPartialResps[i] ); - - if( pResponse->GetSize() != 8 ) - UnPackReadVResponse( pResponse ); - - //-------------------------------------------------------------------------- - // See if all the chunks are OK and put them in the response - //-------------------------------------------------------------------------- - uint32_t size = 0; - for( uint32_t i = 0; i < pChunkList->size(); ++i ) - { - if( !pChunkStatus[i].done ) - return Status( stFatal, errInvalidResponse ); - - vReadInfo->GetChunks().push_back( - ChunkInfo( (*pChunkList)[i].offset, - (*pChunkList)[i].length, - (*pChunkList)[i].buffer ) ); - size += (*pChunkList)[i].length; - } - vReadInfo->SetSize( size ); - return Status(); - } - - //---------------------------------------------------------------------------- - //! Unpack a single readv response - //---------------------------------------------------------------------------- - Status XRootDMsgHandler::UnPackReadVResponse( Message *msg ) - { - Log *log = DefaultEnv::GetLog(); - log->Dump( XRootDMsg, "[%s] Handling response to %s: unpacking " - "data from a cached message", pUrl.GetHostId().c_str(), - pRequest->GetDescription().c_str() ); - - uint32_t offset = 0; - uint32_t len = msg->GetSize()-8; - uint32_t currentChunk = 0; - char *cursor = msg->GetBuffer(8); - - while( 1 ) - { - //------------------------------------------------------------------------ - // Check whether we should stop - //------------------------------------------------------------------------ - if( offset+16 > len ) - break; - - //------------------------------------------------------------------------ - // Extract and check the validity of the chunk - //------------------------------------------------------------------------ - readahead_list *chunk = (readahead_list*)(cursor); - chunk->rlen = ntohl( chunk->rlen ); - chunk->offset = ntohll( chunk->offset ); - - bool chunkFound = false; - for( uint32_t i = currentChunk; i < pChunkList->size(); ++i ) - { - if( (*pChunkList)[i].offset == (uint64_t)chunk->offset && - (*pChunkList)[i].length == (uint32_t)chunk->rlen ) - { - chunkFound = true; - currentChunk = i; - break; - } - } - - if( !chunkFound ) - { - log->Error( XRootDMsg, "[%s] Handling response to %s: the response " - "no corresponding chunk buffer found to store %d bytes " - "at %ld", pUrl.GetHostId().c_str(), - pRequest->GetDescription().c_str(), chunk->rlen, - chunk->offset ); - return Status( stFatal, errInvalidResponse ); - } - - //------------------------------------------------------------------------ - // Extract the data - //------------------------------------------------------------------------ - if( !(*pChunkList)[currentChunk].buffer ) - { - log->Error( XRootDMsg, "[%s] Handling response to %s: the user " - "supplied buffer is 0, discarding the data", - pUrl.GetHostId().c_str(), - pRequest->GetDescription().c_str() ); - } - else - { - if( offset+chunk->rlen+16 > len ) - { - log->Error( XRootDMsg, "[%s] Handling response to %s: copying " - "requested data would cross message boundary", - pUrl.GetHostId().c_str(), - pRequest->GetDescription().c_str() ); - return Status( stFatal, errInvalidResponse ); - } - memcpy( (*pChunkList)[currentChunk].buffer, cursor+16, chunk->rlen ); - } - - pChunkStatus[currentChunk].done = true; - - offset += (16 + chunk->rlen); - cursor += (16 + chunk->rlen); - ++currentChunk; - } - return Status(); - } - - //---------------------------------------------------------------------------- - // Recover error - //---------------------------------------------------------------------------- - void XRootDMsgHandler::HandleError( Status status, Message *msg ) - { - //-------------------------------------------------------------------------- - // If there was no error then do nothing - //-------------------------------------------------------------------------- - if( status.IsOK() ) - return; - - pLastError = status; - - Log *log = DefaultEnv::GetLog(); - log->Debug( XRootDMsg, "[%s] Handling error while processing %s: %s.", - pUrl.GetHostId().c_str(), pRequest->GetDescription().c_str(), - status.ToString().c_str() ); - - //-------------------------------------------------------------------------- - // We have got an error message, we can recover it at the load balancer if: - // 1) we haven't got it from the load balancer - // 2) we have a load balancer assigned - // 3) the error is either one of: kXR_FSError, kXR_IOError, kXR_ServerError, - // kXR_NotFound - // 4) in the case of kXR_NotFound a kXR_refresh flags needs to be set - //-------------------------------------------------------------------------- - if( status.code == errErrorResponse ) - { - if( pLoadBalancer.url.IsValid() && - pUrl.GetLocation() != pLoadBalancer.url.GetLocation() && - (status.errNo == kXR_FSError || status.errNo == kXR_IOError || - status.errNo == kXR_ServerError || status.errNo == kXR_NotFound) ) - { - UpdateTriedCGI(status.errNo); - if( status.errNo == kXR_NotFound ) - SwitchOnRefreshFlag(); - HandleError( RetryAtServer( pLoadBalancer.url ) ); - delete pResponse; - pResponse = 0; - return; - } - else - { - pStatus = status; - HandleRspOrQueue(); - return; - } - } - - //-------------------------------------------------------------------------- - // Nothing can be done if: - // 1) a user timeout has occurred - // 2) has a non-zero session id - // 3) if another error occurred and the validity of the message expired - //-------------------------------------------------------------------------- - if( status.code == errOperationExpired || pRequest->GetSessionId() || - time(0) >= pExpiration ) - { - log->Error( XRootDMsg, "[%s] Unable to get the response to request %s", - pUrl.GetHostId().c_str(), - pRequest->GetDescription().c_str() ); - pStatus = status; - HandleRspOrQueue(); - return; - } - - //-------------------------------------------------------------------------- - // At this point we're left with connection errors, we recover them - // at a load balancer if we have one and if not on the current server - // until we get a response, an unrecoverable error or a timeout - //-------------------------------------------------------------------------- - if( pLoadBalancer.url.IsValid() && - pLoadBalancer.url.GetLocation() != pUrl.GetLocation() ) - { - UpdateTriedCGI(); - HandleError( RetryAtServer( pLoadBalancer.url ) ); - return; - } - else - { - if( !status.IsFatal() ) - { - HandleError( RetryAtServer( pUrl ) ); - return; - } - pStatus = status; - HandleRspOrQueue(); - return; - } - } - - //---------------------------------------------------------------------------- - // Retry the message at another server - //---------------------------------------------------------------------------- - Status XRootDMsgHandler::RetryAtServer( const URL &url ) - { - Log *log = DefaultEnv::GetLog(); - - if( pUrl.GetLocation() != url.GetLocation() ) - { - pHosts->push_back( url ); - - //------------------------------------------------------------------------ - // Assign a new stream id to the message - //------------------------------------------------------------------------ - - // first release the old stream id - // (though it could be a redirect from a local - // metalink file, in this case there's no SID) - ClientRequestHdr *req = (ClientRequestHdr*)pRequest->GetBuffer(); - if( pSidMgr ) - { - pSidMgr->ReleaseSID( req->streamid ); - pSidMgr = 0; - } - - // then get the new SIDManager - // (again this could be a redirect to a local - // file and in this case there is no SID) - if( !url.IsLocalFile() ) - { - AnyObject sidMgrObj; - Status st = pPostMaster->QueryTransport( url, XRootDQuery::SIDManager, - sidMgrObj ); - if( !st.IsOK() ) - { - log->Error( XRootDMsg, "[%s] Impossible to send message %s.", - pUrl.GetHostId().c_str(), - pRequest->GetDescription().c_str() ); - return st; - } - sidMgrObj.Get( pSidMgr ); - - // and finally allocate new stream id - st = pSidMgr->AllocateSID( req->streamid ); - if( !st.IsOK() ) - { - log->Error( XRootDMsg, "[%s] Impossible to send message %s.", - pUrl.GetHostId().c_str(), - pRequest->GetDescription().c_str() ); - return st; - } - } - } - pUrl = url; - if( pUrl.IsMetalink() && pFollowMetalink ) - return pPostMaster->Redirect( pUrl, pRequest, this, this ); - else if( pUrl.IsLocalFile() ) - { - HandleLocalRedirect( &pUrl ); - return Status(); - } - else - return pPostMaster->Send( pUrl, pRequest, this, true, pExpiration ); - } - - //---------------------------------------------------------------------------- - // Update the "tried=" part of the CGI of the current message - //---------------------------------------------------------------------------- - void XRootDMsgHandler::UpdateTriedCGI(uint32_t errNo) - { - URL::ParamsMap cgi; - std::string tried = pUrl.GetHostName(); - - // Report the reason for the failure to the next location - // - if (errNo) - { if (errNo == kXR_NotFound) cgi["triedrc"] = "enoent"; - else if (errNo == kXR_IOError) cgi["triedrc"] = "ioerr"; - else if (errNo == kXR_FSError) cgi["triedrc"] = "fserr"; - else if (errNo == kXR_ServerError) cgi["triedrc"] = "srverr"; - } - - //-------------------------------------------------------------------------- - // If our current load balancer is a metamanager and we failed either - // at a diskserver or at an unidentified node we also exclude the last - // known manager - //-------------------------------------------------------------------------- - if( pLoadBalancer.url.IsValid() && (pLoadBalancer.flags & kXR_attrMeta) ) - { - HostList::reverse_iterator it; - for( it = pHosts->rbegin()+1; it != pHosts->rend(); ++it ) - { - if( it->loadBalancer ) - break; - - tried += "," + it->url.GetHostName(); - - if( it->flags & kXR_isManager ) - break; - } - } - - cgi["tried"] = tried; - XRootDTransport::UnMarshallRequest( pRequest ); - MessageUtils::RewriteCGIAndPath( pRequest, cgi, false, "" ); - XRootDTransport::MarshallRequest( pRequest ); - } - - //---------------------------------------------------------------------------- - // Switch on the refresh flag for some requests - //---------------------------------------------------------------------------- - void XRootDMsgHandler::SwitchOnRefreshFlag() - { - XRootDTransport::UnMarshallRequest( pRequest ); - ClientRequest *req = (ClientRequest *)pRequest->GetBuffer(); - switch( req->header.requestid ) - { - case kXR_locate: - { - req->locate.options |= kXR_refresh; - break; - } - - case kXR_open: - { - req->locate.options |= kXR_refresh; - break; - } - } - XRootDTransport::SetDescription( pRequest ); - XRootDTransport::MarshallRequest( pRequest ); - } - - //------------------------------------------------------------------------ - // If the current thread is a worker thread from our thread-pool - // handle the response, otherwise submit a new task to the thread-pool - //------------------------------------------------------------------------ - void XRootDMsgHandler::HandleRspOrQueue() - { - JobManager *jobMgr = pPostMaster->GetJobManager(); - if( jobMgr->IsWorker() ) - HandleResponse(); - else - jobMgr->QueueJob( new HandleRspJob( this ), 0 ); - } - - //------------------------------------------------------------------------ - //! Notify the filestatehandler to retry Open() with new URL - //------------------------------------------------------------------------ - void XRootDMsgHandler::HandleLocalRedirect( URL *url ) - { - if( !pLFileHandler ) - { - HandleError( XRootDStatus( stError, errNotSupported ) ); - return; - } - - AnyObject *resp = 0; - pLFileHandler->SetHostList( *pHosts ); - XRootDStatus st = pLFileHandler->Open( url, pRequest, resp ); - if( !st.IsOK() ) - { - HandleError( st ); - return; - } - - pResponseHandler->HandleResponseWithHosts( new XRootDStatus(), - resp, - pHosts ); - delete this; - - return; - } -} diff --git a/src/XrdCl/XrdClXRootDMsgHandler.hh b/src/XrdCl/XrdClXRootDMsgHandler.hh deleted file mode 100644 index 688f8b86334..00000000000 --- a/src/XrdCl/XrdClXRootDMsgHandler.hh +++ /dev/null @@ -1,431 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2014 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_XROOTD_MSG_HANDLER_HH__ -#define __XRD_CL_XROOTD_MSG_HANDLER_HH__ - -#include "XrdCl/XrdClPostMasterInterfaces.hh" -#include "XrdCl/XrdClXRootDResponses.hh" -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdCl/XrdClMessage.hh" -#include "XProtocol/XProtocol.hh" - -#include - -namespace XrdCl -{ - class PostMaster; - class SIDManager; - class URL; - class LocalFileHandler; - - //---------------------------------------------------------------------------- - //! Handle/Process/Forward XRootD messages - //---------------------------------------------------------------------------- - class XRootDMsgHandler: public IncomingMsgHandler, - public OutgoingMsgHandler - { - friend class HandleRspJob; - - public: - //------------------------------------------------------------------------ - //! Constructor - //! - //! @param msg message that has been sent out - //! @param respHandler response handler to be called then the final - //! final response arrives - //! @param url the url the message has been sent to - //! @param sidMgr the sid manager used to allocate SID for the initial - //! message - //------------------------------------------------------------------------ - XRootDMsgHandler( Message *msg, - ResponseHandler *respHandler, - const URL *url, - SIDManager *sidMgr, - LocalFileHandler *lFileHandler ): - pRequest( msg ), - pResponse( 0 ), - pResponseHandler( respHandler ), - pUrl( *url ), - pSidMgr( sidMgr ), - pLFileHandler( lFileHandler ), - pExpiration( 0 ), - pRedirectAsAnswer( false ), - pHosts( 0 ), - pHasLoadBalancer( false ), - pHasSessionId( false ), - pChunkList( 0 ), - pRedirectCounter( 0 ), - - pAsyncOffset( 0 ), - pAsyncReadSize( 0 ), - pAsyncReadBuffer( 0 ), - pAsyncMsgSize( 0 ), - - pReadRawStarted( false ), - pReadRawCurrentOffset( 0 ), - - pReadVRawMsgOffset( 0 ), - pReadVRawChunkHeaderDone( false ), - pReadVRawChunkHeaderStarted( false ), - pReadVRawSizeError( false ), - pReadVRawChunkIndex( 0 ), - pReadVRawMsgDiscard( false ), - - pOtherRawStarted( false ), - - pFollowMetalink( false ) - { - pPostMaster = DefaultEnv::GetPostMaster(); - if( msg->GetSessionId() ) - pHasSessionId = true; - memset( &pReadVRawChunkHeader, 0, sizeof( readahead_list ) ); - } - - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - ~XRootDMsgHandler() - { - if( !pHasSessionId ) - delete pRequest; - delete pResponse; - std::vector::iterator it; - for( it = pPartialResps.begin(); it != pPartialResps.end(); ++it ) - delete *it; - } - - //------------------------------------------------------------------------ - //! Examine an incoming message, and decide on the action to be taken - //! - //! @param msg the message, may be zero if receive failed - //! @return action type that needs to be take wrt the message and - //! the handler - //------------------------------------------------------------------------ - virtual uint16_t Examine( Message *msg ); - - //------------------------------------------------------------------------ - //! Get handler sid - //! - //! return sid of the corresponding request, otherwise 0 - //------------------------------------------------------------------------ - virtual uint16_t GetSid() const; - - //------------------------------------------------------------------------ - //! Process the message if it was "taken" by the examine action - //! - //! @param msg the message to be processed - //------------------------------------------------------------------------ - virtual void Process( Message *msg ); - - //------------------------------------------------------------------------ - //! Read message body directly from a socket - called if Examine returns - //! Raw flag - only socket related errors may be returned here - //! - //! @param msg the corresponding message header - //! @param socket the socket to read from - //! @param bytesRead number of bytes read by the method - //! @return stOK & suDone if the whole body has been processed - //! stOK & suRetry if more data is needed - //! stError on failure - //------------------------------------------------------------------------ - virtual Status ReadMessageBody( Message *msg, - int socket, - uint32_t &bytesRead ); - - //------------------------------------------------------------------------ - //! Handle an event other that a message arrival - //! - //! @param event type of the event - //! @param streamNum stream concerned - //! @param status status info - //------------------------------------------------------------------------ - virtual uint8_t OnStreamEvent( StreamEvent event, - uint16_t streamNum, - Status status ); - - //------------------------------------------------------------------------ - //! The requested action has been performed and the status is available - //------------------------------------------------------------------------ - virtual void OnStatusReady( const Message *message, - Status status ); - - //------------------------------------------------------------------------ - //! Are we a raw writer or not? - //------------------------------------------------------------------------ - virtual bool IsRaw() const; - - //------------------------------------------------------------------------ - //! Write message body directly to a socket - called if IsRaw returns - //! true - only socket related errors may be returned here - //! - //! @param socket the socket to read from - //! @param bytesRead number of bytes read by the method - //! @return stOK & suDone if the whole body has been processed - //! stOK & suRetry if more data needs to be written - //! stError on failure - //------------------------------------------------------------------------ - Status WriteMessageBody( int socket, - uint32_t &bytesRead ); - - //------------------------------------------------------------------------ - //! Get message body - called if IsRaw returns true - //! - //! @param asyncOffset : the current async offset - //! @return : the list of chunks - //------------------------------------------------------------------------ - ChunkList* GetMessageBody( uint32_t *&asyncOffset ) - { - asyncOffset = &pAsyncOffset; - return pChunkList; - } - - //------------------------------------------------------------------------ - //! Called after the wait time for kXR_wait has elapsed - //! - //! @param now current timestamp - //------------------------------------------------------------------------ - void WaitDone( time_t now ); - - //------------------------------------------------------------------------ - //! Set a timestamp after which we give up - //------------------------------------------------------------------------ - void SetExpiration( time_t expiration ) - { - pExpiration = expiration; - } - - //------------------------------------------------------------------------ - //! Treat the kXR_redirect response as a valid answer to the message - //! and notify the handler with the URL as a response - //------------------------------------------------------------------------ - void SetRedirectAsAnswer( bool redirectAsAnswer ) - { - pRedirectAsAnswer = redirectAsAnswer; - } - - //------------------------------------------------------------------------ - //! Get the request pointer - //------------------------------------------------------------------------ - const Message *GetRequest() const - { - return pRequest; - } - - //------------------------------------------------------------------------ - //! Set the load balancer - //------------------------------------------------------------------------ - void SetLoadBalancer( const HostInfo &loadBalancer ) - { - if( !loadBalancer.url.IsValid() ) - return; - pLoadBalancer = loadBalancer; - pHasLoadBalancer = true; - } - - //------------------------------------------------------------------------ - //! Set host list - //------------------------------------------------------------------------ - void SetHostList( HostList *hostList ) - { - delete pHosts; - pHosts = hostList; - } - - //------------------------------------------------------------------------ - //! Set the chunk list - //------------------------------------------------------------------------ - void SetChunkList( ChunkList *chunkList ) - { - pChunkList = chunkList; - if( chunkList ) - pChunkStatus.resize( chunkList->size() ); - else - pChunkStatus.clear(); - } - - //------------------------------------------------------------------------ - //! Set the redirect counter - //------------------------------------------------------------------------ - void SetRedirectCounter( uint16_t redirectCounter ) - { - pRedirectCounter = redirectCounter; - } - - void SetFollowMetalink( bool followMetalink ) - { - pFollowMetalink = followMetalink; - } - - private: - //------------------------------------------------------------------------ - //! Handle a kXR_read in raw mode - //------------------------------------------------------------------------ - Status ReadRawRead( Message *msg, - int socket, - uint32_t &bytesRead ); - - //------------------------------------------------------------------------ - //! Handle a kXR_readv in raw mode - //------------------------------------------------------------------------ - Status ReadRawReadV( Message *msg, - int socket, - uint32_t &bytesRead ); - - //------------------------------------------------------------------------ - //! Handle anything other than kXR_read and kXR_readv in raw mode - //------------------------------------------------------------------------ - Status ReadRawOther( Message *msg, - int socket, - uint32_t &bytesRead ); - - //------------------------------------------------------------------------ - //! Read a buffer asynchronously - depends on pAsyncBuffer, pAsyncSize - //! and pAsyncOffset - //------------------------------------------------------------------------ - Status ReadAsync( int socket, uint32_t &btesRead ); - - //------------------------------------------------------------------------ - //! Recover error - //------------------------------------------------------------------------ - void HandleError( Status status, Message *msg = 0 ); - - //------------------------------------------------------------------------ - //! Retry the request at another server - //------------------------------------------------------------------------ - Status RetryAtServer( const URL &url ); - - //------------------------------------------------------------------------ - //! Unpack the message and call the response handler - //------------------------------------------------------------------------ - void HandleResponse(); - - //------------------------------------------------------------------------ - //! Extract the status information from the stuff that we got - //------------------------------------------------------------------------ - XRootDStatus *ProcessStatus(); - - //------------------------------------------------------------------------ - //! Parse the response and put it in an object that could be passed to - //! the user - //------------------------------------------------------------------------ - Status ParseResponse( AnyObject *&response ); - - //------------------------------------------------------------------------ - //! Perform the changes to the original request needed by the redirect - //! procedure - allocate new streamid, append redirection data and such - //------------------------------------------------------------------------ - Status RewriteRequestRedirect( const URL::ParamsMap &newCgi, - const std::string &newPath ); - - //------------------------------------------------------------------------ - //! Some requests need to be rewritten also after getting kXR_wait - sigh - //------------------------------------------------------------------------ - Status RewriteRequestWait(); - - //------------------------------------------------------------------------ - //! Post process vector read - //------------------------------------------------------------------------ - Status PostProcessReadV( VectorReadInfo *vReadInfo ); - - //------------------------------------------------------------------------ - //! Unpack a single readv response - //------------------------------------------------------------------------ - Status UnPackReadVResponse( Message *msg ); - - //------------------------------------------------------------------------ - //! Update the "tried=" part of the CGI of the current message - //------------------------------------------------------------------------ - void UpdateTriedCGI(uint32_t errNo=0); - - //------------------------------------------------------------------------ - //! Switch on the refresh flag for some requests - //------------------------------------------------------------------------ - void SwitchOnRefreshFlag(); - - //------------------------------------------------------------------------ - //! If the current thread is a worker thread from our thread-pool - //! handle the response, otherwise submit a new task to the thread-pool - //------------------------------------------------------------------------ - void HandleRspOrQueue(); - - //------------------------------------------------------------------------ - //! Handle a redirect to a local file - //------------------------------------------------------------------------ - void HandleLocalRedirect( URL *url ); - - //------------------------------------------------------------------------ - // Helper struct for async reading of chunks - //------------------------------------------------------------------------ - struct ChunkStatus - { - ChunkStatus(): sizeError( false ), done( false ) {} - bool sizeError; - bool done; - }; - - Message *pRequest; - Message *pResponse; - std::vector pPartialResps; - ResponseHandler *pResponseHandler; - URL pUrl; - PostMaster *pPostMaster; - SIDManager *pSidMgr; - LocalFileHandler *pLFileHandler; - Status pStatus; - Status pLastError; - time_t pExpiration; - bool pRedirectAsAnswer; - HostList *pHosts; - bool pHasLoadBalancer; - HostInfo pLoadBalancer; - bool pHasSessionId; - std::string pRedirectUrl; - ChunkList *pChunkList; - std::vector pChunkStatus; - uint16_t pRedirectCounter; - - uint32_t pAsyncOffset; - uint32_t pAsyncReadSize; - char* pAsyncReadBuffer; - uint32_t pAsyncMsgSize; - - bool pReadRawStarted; - uint32_t pReadRawCurrentOffset; - - uint32_t pReadVRawMsgOffset; - bool pReadVRawChunkHeaderDone; - bool pReadVRawChunkHeaderStarted; - bool pReadVRawSizeError; - int32_t pReadVRawChunkIndex; - readahead_list pReadVRawChunkHeader; - bool pReadVRawMsgDiscard; - - bool pOtherRawStarted; - - bool pFollowMetalink; - }; -} - -#endif // __XRD_CL_XROOTD_MSG_HANDLER_HH__ diff --git a/src/XrdCl/XrdClXRootDResponses.cc b/src/XrdCl/XrdClXRootDResponses.cc deleted file mode 100644 index 4b59f20c715..00000000000 --- a/src/XrdCl/XrdClXRootDResponses.cc +++ /dev/null @@ -1,299 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#include "XrdCl/XrdClXRootDResponses.hh" -#include "XrdCl/XrdClLog.hh" -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdCl/XrdClConstants.hh" -#include "XrdCl/XrdClUtils.hh" -#include - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - // LocationInfo constructor - //---------------------------------------------------------------------------- - LocationInfo::LocationInfo() - { - } - - //---------------------------------------------------------------------------- - // Parse the server location response - //---------------------------------------------------------------------------- - bool LocationInfo::ParseServerResponse( const char *data ) - { - if( !data || strlen( data ) == 0 ) - return false; - - std::vector locations; - std::vector::iterator it; - Utils::splitString( locations, data, " " ); - for( it = locations.begin(); it != locations.end(); ++it ) - if( ProcessLocation( *it ) == false ) - return false; - return true; - } - - //---------------------------------------------------------------------------- - // Process location - //---------------------------------------------------------------------------- - bool LocationInfo::ProcessLocation( std::string &location ) - { - if( location.length() < 5 ) - return false; - - //-------------------------------------------------------------------------- - // Decode location type - //-------------------------------------------------------------------------- - LocationInfo::LocationType type; - switch( location[0] ) - { - case 'M': - type = LocationInfo::ManagerOnline; - break; - case 'm': - type = LocationInfo::ManagerPending; - break; - case 'S': - type = LocationInfo::ServerOnline; - break; - case 's': - type = LocationInfo::ServerPending; - break; - default: - return false; - } - - //-------------------------------------------------------------------------- - // Decode access type - //-------------------------------------------------------------------------- - LocationInfo::AccessType access; - switch( location[1] ) - { - case 'r': - access = LocationInfo::Read; - break; - case 'w': - access = LocationInfo::ReadWrite; - break; - default: - return false; - } - - //-------------------------------------------------------------------------- - // Push the location info - //-------------------------------------------------------------------------- - pLocations.push_back( Location( location.substr(2), type, access ) ); - - return true; - } - - //---------------------------------------------------------------------------- - // StatInfo constructor - //---------------------------------------------------------------------------- - StatInfo::StatInfo(): - pSize( 0 ), - pFlags( 0 ), - pModTime( 0 ) - { - } - - //---------------------------------------------------------------------------- - // Parse the stat info returned by the server - //---------------------------------------------------------------------------- - bool StatInfo::ParseServerResponse( const char *data ) - { - if( !data || strlen( data ) == 0 ) - return false; - - std::vector chunks; - Utils::splitString( chunks, data, " " ); - - if( chunks.size() < 4 ) - return false; - - pId = chunks[0]; - - char *result; - pSize = ::strtoll( chunks[1].c_str(), &result, 0 ); - if( *result != 0 ) - { - pSize = 0; - return false; - } - - pFlags = ::strtol( chunks[2].c_str(), &result, 0 ); - if( *result != 0 ) - { - pFlags = 0; - return false; - } - - pModTime = ::strtoll( chunks[3].c_str(), &result, 0 ); - if( *result != 0 ) - { - pModTime = 0; - return false; - } - - return true; - } - - //---------------------------------------------------------------------------- - // StatInfo constructor - //---------------------------------------------------------------------------- - StatInfoVFS::StatInfoVFS(): - pNodesRW( 0 ), - pFreeRW( 0 ), - pUtilizationRW( 0 ), - pNodesStaging( 0 ), - pFreeStaging( 0 ), - pUtilizationStaging( 0 ) - { - } - - //---------------------------------------------------------------------------- - // Parse the stat info returned by the server - //---------------------------------------------------------------------------- - bool StatInfoVFS::ParseServerResponse( const char *data ) - { - if( !data || strlen( data ) == 0 ) - return false; - - std::vector chunks; - Utils::splitString( chunks, data, " " ); - - if( chunks.size() < 6 ) - return false; - - char *result; - pNodesRW = ::strtoll( chunks[0].c_str(), &result, 0 ); - if( *result != 0 ) - { - pNodesRW = 0; - return false; - } - - pFreeRW = ::strtoll( chunks[1].c_str(), &result, 0 ); - if( *result != 0 ) - { - pFreeRW = 0; - return false; - } - - pUtilizationRW = ::strtol( chunks[2].c_str(), &result, 0 ); - if( *result != 0 ) - { - pUtilizationRW = 0; - return false; - } - - pNodesStaging = ::strtoll( chunks[3].c_str(), &result, 0 ); - if( *result != 0 ) - { - pNodesStaging = 0; - return false; - } - - pFreeStaging = ::strtoll( chunks[4].c_str(), &result, 0 ); - if( *result != 0 ) - { - pFreeStaging = 0; - return false; - } - - pUtilizationStaging = ::strtol( chunks[5].c_str(), &result, 0 ); - if( *result != 0 ) - { - pUtilizationStaging = 0; - return false; - } - - return true; - } - - //---------------------------------------------------------------------------- - // DirectoryList constructor - //---------------------------------------------------------------------------- - DirectoryList::DirectoryList() - { - } - - //---------------------------------------------------------------------------- - // Destructor - //---------------------------------------------------------------------------- - DirectoryList::~DirectoryList() - { - for( Iterator it = pDirList.begin(); it != pDirList.end(); ++it ) - delete *it; - } - - //---------------------------------------------------------------------------- - // Parse the directory list - //---------------------------------------------------------------------------- - bool DirectoryList::ParseServerResponse( const std::string &hostId, - const char *data ) - { - if( !data ) - return false; - - //-------------------------------------------------------------------------- - // Check what kind of response we're dealing with - //-------------------------------------------------------------------------- - std::string dat = data; - std::string dStatPrefix = ".\n0 0 0 0"; - bool isDStat = false; - - if( !dat.compare( 0, dStatPrefix.size(), dStatPrefix ) ) - isDStat = true; - - std::vector entries; - std::vector::iterator it; - Utils::splitString( entries, dat, "\n" ); - - //-------------------------------------------------------------------------- - // Normal response - //-------------------------------------------------------------------------- - if( !isDStat ) - { - for( it = entries.begin(); it != entries.end(); ++it ) - Add( new ListEntry( hostId, *it ) ); - return true; - } - - //-------------------------------------------------------------------------- - // kXR_dstat - //-------------------------------------------------------------------------- - if( (entries.size() < 2) || (entries.size() % 2) ) - return false; - - it = entries.begin(); ++it; ++it; - for( ; it != entries.end(); ++it ) - { - ListEntry *entry = new ListEntry( hostId, *it ); - Add( entry ); - ++it; - StatInfo *i = new StatInfo(); - entry->SetStatInfo( i ); - bool ok = i->ParseServerResponse( it->c_str() ); - if( !ok ) - return false; - } - return true; - } -} diff --git a/src/XrdCl/XrdClXRootDResponses.hh b/src/XrdCl/XrdClXRootDResponses.hh deleted file mode 100644 index 57b3c042d5a..00000000000 --- a/src/XrdCl/XrdClXRootDResponses.hh +++ /dev/null @@ -1,871 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_XROOTD_RESPONSES_HH__ -#define __XRD_CL_XROOTD_RESPONSES_HH__ - -#include "XrdCl/XrdClBuffer.hh" -#include "XrdCl/XrdClStatus.hh" -#include "XrdCl/XrdClURL.hh" -#include "XrdCl/XrdClAnyObject.hh" -#include "XProtocol/XProtocol.hh" -#include -#include -#include -#include - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - //! Path location info - //---------------------------------------------------------------------------- - class LocationInfo - { - public: - //------------------------------------------------------------------------ - //! Describes the node type and file status for a given location - //------------------------------------------------------------------------ - enum LocationType - { - ManagerOnline, //!< manager node where the file is online - ManagerPending, //!< manager node where the file is pending to be online - ServerOnline, //!< server node where the file is online - ServerPending //!< server node where the file is pending to be online - }; - - //------------------------------------------------------------------------ - //! Describes the allowed access type for the file at given location - //------------------------------------------------------------------------ - enum AccessType - { - Read, //!< read access is allowed - ReadWrite //!< write access is allowed - }; - - //------------------------------------------------------------------------ - //! Location - //------------------------------------------------------------------------ - class Location - { - public: - - //-------------------------------------------------------------------- - //! Constructor - //-------------------------------------------------------------------- - Location( const std::string &address, - LocationType type, - AccessType access ): - pAddress( address ), - pType( type ), - pAccess( access ) {} - - //-------------------------------------------------------------------- - //! Get address - //-------------------------------------------------------------------- - const std::string &GetAddress() const - { - return pAddress; - } - - //-------------------------------------------------------------------- - //! Get location type - //-------------------------------------------------------------------- - LocationType GetType() const - { - return pType; - } - - //-------------------------------------------------------------------- - //! Get access type - //-------------------------------------------------------------------- - AccessType GetAccessType() const - { - return pAccess; - } - - //-------------------------------------------------------------------- - //! Check whether the location is a server - //-------------------------------------------------------------------- - bool IsServer() const - { - return pType == ServerOnline || pType == ServerPending; - } - - //-------------------------------------------------------------------- - //! Check whether the location is a manager - //-------------------------------------------------------------------- - bool IsManager() const - { - return pType == ManagerOnline || pType == ManagerPending; - } - - private: - std::string pAddress; - LocationType pType; - AccessType pAccess; - }; - - //------------------------------------------------------------------------ - //! List of locations - //------------------------------------------------------------------------ - typedef std::vector LocationList; - - //------------------------------------------------------------------------ - //! Iterator over locations - //------------------------------------------------------------------------ - typedef LocationList::iterator Iterator; - - //------------------------------------------------------------------------ - //! Iterator over locations - //------------------------------------------------------------------------ - typedef LocationList::const_iterator ConstIterator; - - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - LocationInfo(); - - //------------------------------------------------------------------------ - //! Get number of locations - //------------------------------------------------------------------------ - uint32_t GetSize() const - { - return pLocations.size(); - } - - //------------------------------------------------------------------------ - //! Get the location at index - //------------------------------------------------------------------------ - Location &At( uint32_t index ) - { - return pLocations[index]; - } - - //------------------------------------------------------------------------ - //! Get the location begin iterator - //------------------------------------------------------------------------ - Iterator Begin() - { - return pLocations.begin(); - } - - //------------------------------------------------------------------------ - //! Get the location begin iterator - //------------------------------------------------------------------------ - ConstIterator Begin() const - { - return pLocations.begin(); - } - - //------------------------------------------------------------------------ - //! Get the location end iterator - //------------------------------------------------------------------------ - Iterator End() - { - return pLocations.end(); - } - - //------------------------------------------------------------------------ - //! Get the location end iterator - //------------------------------------------------------------------------ - ConstIterator End() const - { - return pLocations.end(); - } - - //------------------------------------------------------------------------ - //! Add a location - //------------------------------------------------------------------------ - void Add( const Location &location ) - { - pLocations.push_back( location ); - } - - //------------------------------------------------------------------------ - //! Parse server response and fill up the object - //------------------------------------------------------------------------ - bool ParseServerResponse( const char *data ); - - private: - bool ProcessLocation( std::string &location ); - LocationList pLocations; - }; - - //---------------------------------------------------------------------------- - //! Request status - //---------------------------------------------------------------------------- - class XRootDStatus: public Status - { - public: - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - XRootDStatus( uint16_t st = 0, - uint16_t code = 0, - uint32_t errN = 0, - const std::string &message = "" ): - Status( st, code, errN ), - pMessage( message ) {} - - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - XRootDStatus( const Status &st, - const std::string &message = "" ): - Status( st ), - pMessage( message ) {} - - //------------------------------------------------------------------------ - //! Get error message - //------------------------------------------------------------------------ - const std::string &GetErrorMessage() const - { - return pMessage; - } - - //------------------------------------------------------------------------ - //! Set the error message - //------------------------------------------------------------------------ - void SetErrorMessage( const std::string &message ) - { - pMessage = message; - } - - //------------------------------------------------------------------------ - //! Convert to string - //------------------------------------------------------------------------ - std::string ToStr() const - { - if( code == errErrorResponse ) - { - std::ostringstream o; - o << "[ERROR] Server responded with an error: [" << errNo << "] "; - o << pMessage << std::endl; - return o.str(); - } - std::string str = ToString(); - if( !pMessage.empty() ) - str += ": " + pMessage; - return str; - } - - private: - std::string pMessage; - }; - - //---------------------------------------------------------------------------- - //! Binary buffer - //---------------------------------------------------------------------------- - typedef Buffer BinaryDataInfo; - - //---------------------------------------------------------------------------- - //! Protocol response - //---------------------------------------------------------------------------- - class ProtocolInfo - { - public: - //------------------------------------------------------------------------ - //! Types of XRootD servers - //------------------------------------------------------------------------ - enum HostTypes - { - IsManager = kXR_isManager, //!< Manager - IsServer = kXR_isServer, //!< Data server - AttrMeta = kXR_attrMeta, //!< Meta attribute - AttrProxy = kXR_attrProxy, //!< Proxy attribute - AttrSuper = kXR_attrSuper //!< Supervisor attribute - }; - - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - ProtocolInfo( uint32_t version, uint32_t hostInfo ): - pVersion( version ), pHostInfo( hostInfo ) {} - - //------------------------------------------------------------------------ - //! Get version info - //------------------------------------------------------------------------ - uint32_t GetVersion() const - { - return pVersion; - } - - //------------------------------------------------------------------------ - //! Get host info - //------------------------------------------------------------------------ - uint32_t GetHostInfo() const - { - return pHostInfo; - } - - //------------------------------------------------------------------------ - //! Test host info flags - //------------------------------------------------------------------------ - bool TestHostInfo( uint32_t flags ) - { - return pHostInfo & flags; - } - - private: - uint32_t pVersion; - uint32_t pHostInfo; - }; - - //---------------------------------------------------------------------------- - //! Object stat info - //---------------------------------------------------------------------------- - class StatInfo - { - public: - //------------------------------------------------------------------------ - //! Flags - //------------------------------------------------------------------------ - enum Flags - { - XBitSet = kXR_xset, //!< Executable/searchable bit set - IsDir = kXR_isDir, //!< This is a directory - Other = kXR_other, //!< Neither a file nor a directory - Offline = kXR_offline, //!< File is not online (ie. on disk) - POSCPending = kXR_poscpend, //!< File opened with POST flag, not yet - //!< successfully closed - IsReadable = kXR_readable, //!< Read access is allowed - IsWritable = kXR_writable, //!< Write access is allowed - BackUpExists = kXR_bkpexist //!< Back up copy exists - }; - - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - StatInfo(); - - //------------------------------------------------------------------------ - //! Get id - //------------------------------------------------------------------------ - const std::string GetId() const - { - return pId; - } - - //------------------------------------------------------------------------ - //! Get size (in bytes) - //------------------------------------------------------------------------ - uint64_t GetSize() const - { - return pSize; - } - - //------------------------------------------------------------------------ - //! Get flags - //------------------------------------------------------------------------ - uint32_t GetFlags() const - { - return pFlags; - } - - //------------------------------------------------------------------------ - //! Test flags - //------------------------------------------------------------------------ - bool TestFlags( uint32_t flags ) const - { - return pFlags & flags; - } - - //------------------------------------------------------------------------ - //! Get modification time (in seconds since epoch) - //------------------------------------------------------------------------ - uint64_t GetModTime() const - { - return pModTime; - } - - //------------------------------------------------------------------------ - //! Get modification time - //------------------------------------------------------------------------ - std::string GetModTimeAsString() const - { - char ts[256]; - time_t modTime = pModTime; - tm *t = gmtime( &modTime ); - strftime( ts, 255, "%F %T", t ); - return ts; - } - - //------------------------------------------------------------------------ - //! Parse server response and fill up the object - //------------------------------------------------------------------------ - bool ParseServerResponse( const char *data ); - - private: - - //------------------------------------------------------------------------ - // Normal stat - //------------------------------------------------------------------------ - std::string pId; - uint64_t pSize; - uint32_t pFlags; - uint64_t pModTime; - }; - - //---------------------------------------------------------------------------- - //! VFS stat info - //---------------------------------------------------------------------------- - class StatInfoVFS - { - public: - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - StatInfoVFS(); - - //------------------------------------------------------------------------ - //! Get number of nodes that can provide read/write space - //------------------------------------------------------------------------ - uint64_t GetNodesRW() const - { - return pNodesRW; - } - - //------------------------------------------------------------------------ - //! Get size of the largest contiguous area of free r/w space (in MB) - //------------------------------------------------------------------------ - uint64_t GetFreeRW() const - { - return pFreeRW; - } - - //------------------------------------------------------------------------ - //! Get percentage of the partition utilization represented by FreeRW - //------------------------------------------------------------------------ - uint8_t GetUtilizationRW() const - { - return pUtilizationRW; - } - - //------------------------------------------------------------------------ - //! Get number of nodes that can provide staging space - //------------------------------------------------------------------------ - uint64_t GetNodesStaging() const - { - return pNodesStaging; - } - - //------------------------------------------------------------------------ - //! Get size of the largest contiguous area of free staging space (in MB) - //------------------------------------------------------------------------ - uint64_t GetFreeStaging() const - { - return pFreeStaging; - } - - //------------------------------------------------------------------------ - //! Get percentage of the partition utilization represented by FreeStaging - //------------------------------------------------------------------------ - uint8_t GetUtilizationStaging() const - { - return pUtilizationStaging; - } - - //------------------------------------------------------------------------ - //! Parse server response and fill up the object - //------------------------------------------------------------------------ - bool ParseServerResponse( const char *data ); - - private: - - //------------------------------------------------------------------------ - // kXR_vfs stat - //------------------------------------------------------------------------ - uint64_t pNodesRW; - uint64_t pFreeRW; - uint32_t pUtilizationRW; - uint64_t pNodesStaging; - uint64_t pFreeStaging; - uint32_t pUtilizationStaging; - }; - - //---------------------------------------------------------------------------- - //! Directory list - //---------------------------------------------------------------------------- - class DirectoryList - { - public: - //------------------------------------------------------------------------ - //! Directory entry - //------------------------------------------------------------------------ - class ListEntry - { - public: - //-------------------------------------------------------------------- - //! Constructor - //-------------------------------------------------------------------- - ListEntry( const std::string &hostAddress, - const std::string &name, - StatInfo *statInfo = 0): - pHostAddress( hostAddress ), - pName( name ), - pStatInfo( statInfo ) - {} - - //-------------------------------------------------------------------- - //! Destructor - //-------------------------------------------------------------------- - ~ListEntry() - { - delete pStatInfo; - } - - //-------------------------------------------------------------------- - //! Get host address - //-------------------------------------------------------------------- - const std::string &GetHostAddress() const - { - return pHostAddress; - } - - //-------------------------------------------------------------------- - //! Get file name - //-------------------------------------------------------------------- - const std::string &GetName() const - { - return pName; - } - - //-------------------------------------------------------------------- - //! Get the stat info object - //-------------------------------------------------------------------- - StatInfo *GetStatInfo() - { - return pStatInfo; - } - - //-------------------------------------------------------------------- - //! Get the stat info object - //-------------------------------------------------------------------- - const StatInfo *GetStatInfo() const - { - return pStatInfo; - } - - //-------------------------------------------------------------------- - //! Set the stat info object (and transfer the ownership) - //-------------------------------------------------------------------- - void SetStatInfo( StatInfo *info ) - { - pStatInfo = info; - } - - private: - std::string pHostAddress; - std::string pName; - StatInfo *pStatInfo; - }; - - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - DirectoryList(); - - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - ~DirectoryList(); - - //------------------------------------------------------------------------ - //! Directory listing - //------------------------------------------------------------------------ - typedef std::vector DirList; - - //------------------------------------------------------------------------ - //! Directory listing iterator - //------------------------------------------------------------------------ - typedef DirList::iterator Iterator; - - //------------------------------------------------------------------------ - //! Directory listing const iterator - //------------------------------------------------------------------------ - typedef DirList::const_iterator ConstIterator; - - //------------------------------------------------------------------------ - //! Add an entry to the list - takes ownership - //------------------------------------------------------------------------ - void Add( ListEntry *entry ) - { - pDirList.push_back( entry ); - } - - //------------------------------------------------------------------------ - //! Get an entry at given index - //------------------------------------------------------------------------ - ListEntry *At( uint32_t index ) - { - return pDirList[index]; - } - - //------------------------------------------------------------------------ - //! Get the begin iterator - //------------------------------------------------------------------------ - Iterator Begin() - { - return pDirList.begin(); - } - - //------------------------------------------------------------------------ - //! Get the begin iterator - //------------------------------------------------------------------------ - ConstIterator Begin() const - { - return pDirList.begin(); - } - - //------------------------------------------------------------------------ - //! Get the end iterator - //------------------------------------------------------------------------ - Iterator End() - { - return pDirList.end(); - } - - //------------------------------------------------------------------------ - //! Get the end iterator - //------------------------------------------------------------------------ - ConstIterator End() const - { - return pDirList.end(); - } - - //------------------------------------------------------------------------ - //! Get the size of the listing - //------------------------------------------------------------------------ - uint32_t GetSize() const - { - return pDirList.size(); - } - - //------------------------------------------------------------------------ - //! Get parent directory name - //------------------------------------------------------------------------ - const std::string &GetParentName() const - { - return pParent; - } - - //------------------------------------------------------------------------ - //! Set name of the parent directory - //------------------------------------------------------------------------ - void SetParentName( const std::string &parent ) - { - size_t pos = parent.find( '?' ); - pParent = pos == std::string::npos ? parent : parent.substr( 0, pos ); - if( !pParent.empty() && pParent[pParent.length()-1] != '/' ) - pParent += "/"; - } - - //------------------------------------------------------------------------ - //! Parse server response and fill up the object - //------------------------------------------------------------------------ - bool ParseServerResponse( const std::string &hostId, - const char *data ); - - private: - DirList pDirList; - std::string pParent; - }; - - //---------------------------------------------------------------------------- - //! Information returned by file open operation - //---------------------------------------------------------------------------- - class OpenInfo - { - public: - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - OpenInfo( const uint8_t *fileHandle, - uint64_t sessionId, - StatInfo *statInfo = 0 ): - pSessionId(sessionId), pStatInfo( statInfo ) - { - memcpy( pFileHandle, fileHandle, 4 ); - } - - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - ~OpenInfo() - { - delete pStatInfo; - } - - //------------------------------------------------------------------------ - //! Get the file handle (4bytes) - //------------------------------------------------------------------------ - void GetFileHandle( uint8_t *fileHandle ) const - { - memcpy( fileHandle, pFileHandle, 4 ); - } - - //------------------------------------------------------------------------ - //! Get the stat info - //------------------------------------------------------------------------ - const StatInfo *GetStatInfo() const - { - return pStatInfo; - } - - //------------------------------------------------------------------------ - // Get session ID - //------------------------------------------------------------------------ - uint64_t GetSessionId() const - { - return pSessionId; - } - - private: - uint8_t pFileHandle[4]; - uint64_t pSessionId; - StatInfo *pStatInfo; - }; - - //---------------------------------------------------------------------------- - //! Describe a data chunk for vector read - //---------------------------------------------------------------------------- - struct ChunkInfo - { - //-------------------------------------------------------------------------- - //! Constructor - //-------------------------------------------------------------------------- - ChunkInfo( uint64_t off = 0, uint32_t len = 0, void *buff = 0 ): - offset( off ), length( len ), buffer(buff) {} - - uint64_t offset; //! offset in the file - uint32_t length; //! length of the chunk - void *buffer; //! optional buffer pointer - }; - - //---------------------------------------------------------------------------- - //! List of chunks - //---------------------------------------------------------------------------- - typedef std::vector ChunkList; - - //---------------------------------------------------------------------------- - //! Vector read info - //---------------------------------------------------------------------------- - class VectorReadInfo - { - public: - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - VectorReadInfo(): pSize( 0 ) {} - - //------------------------------------------------------------------------ - //! Get Size - //------------------------------------------------------------------------ - uint32_t GetSize() const - { - return pSize; - } - - //------------------------------------------------------------------------ - //! Set size - //------------------------------------------------------------------------ - void SetSize( uint32_t size ) - { - pSize = size; - } - - //------------------------------------------------------------------------ - //! Get chunks - //------------------------------------------------------------------------ - ChunkList &GetChunks() - { - return pChunks; - } - - //------------------------------------------------------------------------ - //! Get chunks - //------------------------------------------------------------------------ - const ChunkList &GetChunks() const - { - return pChunks; - } - - private: - ChunkList pChunks; - uint32_t pSize; - }; - - //---------------------------------------------------------------------------- - // List of URLs - //---------------------------------------------------------------------------- - struct HostInfo - { - HostInfo(): - flags(0), protocol(0), loadBalancer(false) {} - HostInfo( const URL &u, bool lb = false ): - flags(0), protocol(0), loadBalancer(lb), url(u) {} - uint32_t flags; //!< Host type - uint32_t protocol; //!< Version of the protocol the host is speaking - bool loadBalancer; //!< Was the host used as a load balancer - URL url; //!< URL of the host - }; - - typedef std::vector HostList; - - //---------------------------------------------------------------------------- - //! Handle an async response - //---------------------------------------------------------------------------- - class ResponseHandler - { - public: - virtual ~ResponseHandler() {} - - //------------------------------------------------------------------------ - //! Called when a response to associated request arrives or an error - //! occurs - //! - //! @param status status of the request - //! @param response an object associated with the response - //! (request dependent) - //! @param hostList list of hosts the request was redirected to - //------------------------------------------------------------------------ - virtual void HandleResponseWithHosts( XRootDStatus *status, - AnyObject *response, - HostList *hostList ) - { - delete hostList; - HandleResponse( status, response ); - } - - //------------------------------------------------------------------------ - //! Called when a response to associated request arrives or an error - //! occurs - //! - //! @param status status of the request - //! @param response an object associated with the response - //! (request dependent) - //------------------------------------------------------------------------ - virtual void HandleResponse( XRootDStatus *status, - AnyObject *response ) - { - (void)status; (void)response; - } - }; -} - -#endif // __XRD_CL_XROOTD_RESPONSES_HH__ diff --git a/src/XrdCl/XrdClXRootDTransport.cc b/src/XrdCl/XrdClXRootDTransport.cc deleted file mode 100644 index 979e24698e0..00000000000 --- a/src/XrdCl/XrdClXRootDTransport.cc +++ /dev/null @@ -1,2564 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2014 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#include "XrdCl/XrdClXRootDTransport.hh" -#include "XrdCl/XrdClConstants.hh" -#include "XrdCl/XrdClLog.hh" -#include "XrdCl/XrdClSocket.hh" -#include "XrdCl/XrdClMessage.hh" -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdCl/XrdClSIDManager.hh" -#include "XrdCl/XrdClUtils.hh" -#include "XrdCl/XrdClTransportManager.hh" -#include "XrdNet/XrdNetAddr.hh" -#include "XrdNet/XrdNetUtils.hh" -#include "XrdSys/XrdSysPlatform.hh" -#include "XrdOuc/XrdOucErrInfo.hh" -#include "XrdOuc/XrdOucUtils.hh" -#include "XrdSys/XrdSysTimer.hh" -#include "XrdSys/XrdSysPlugin.hh" -#include "XrdSec/XrdSecLoadSecurity.hh" -#include "XrdSec/XrdSecProtect.hh" -#include "XrdVersion.hh" - -#include -#include -#include -#include -#include -#include -#include - -XrdVERSIONINFOREF( XrdCl ); - -namespace XrdCl -{ - struct PluginUnloadHandler - { - PluginUnloadHandler() : unloaded( false ) { } - - static void UnloadHandler() - { - UnloadHandler( "root" ); - UnloadHandler( "xroot" ); - } - - static void UnloadHandler( const std::string &trProt ) - { - TransportManager *trManager = DefaultEnv::GetTransportManager(); - TransportHandler *trHandler = trManager->GetHandler( trProt ); - XRootDTransport *xrdTransport = dynamic_cast( trHandler ); - if( !xrdTransport ) return; - - PluginUnloadHandler *me = xrdTransport->pSecUnloadHandler; - XrdSysRWLockHelper scope( me->lock, false ); // obtain write lock - me->unloaded = true; - } - - void Register( const std::string &protocol ) - { - XrdSysRWLockHelper scope( lock, false ); // obtain write lock - std::pair< std::set::iterator, bool > ret = protocols.insert( protocol ); - // if that's the first time we are using the protocol, the sec lib - // was just loaded so now's the time to register the atexit handler - if( ret.second ) - { - atexit( UnloadHandler ); - } - } - - XrdSysRWLock lock; - bool unloaded; - std::set protocols; - }; - - //---------------------------------------------------------------------------- - //! Information holder for XRootDStreams - //---------------------------------------------------------------------------- - struct XRootDStreamInfo - { - //-------------------------------------------------------------------------- - // Define the stream status for the link negotiation purposes - //-------------------------------------------------------------------------- - enum StreamStatus - { - Disconnected, - Broken, - HandShakeSent, - HandShakeReceived, - LoginSent, - AuthSent, - BindSent, - EndSessionSent, - Connected - }; - - //-------------------------------------------------------------------------- - // Constructor - //-------------------------------------------------------------------------- - XRootDStreamInfo(): status( Disconnected ), pathId( 0 ) - { - } - - StreamStatus status; - uint8_t pathId; - }; - - //---------------------------------------------------------------------------- - //! Information holder for xrootd channels - //---------------------------------------------------------------------------- - struct XRootDChannelInfo - { - //-------------------------------------------------------------------------- - // Constructor - //-------------------------------------------------------------------------- - XRootDChannelInfo(): - serverFlags(0), - protocolVersion(0), - firstLogIn(true), - sidManager(0), - authBuffer(0), - authProtocol(0), - authParams(0), - authEnv(0), - openFiles(0), - waitBarrier(0), - protection(0), - protRespBody(0), - protRespSize(0) - { - sidManager = new SIDManager(); - memset( sessionId, 0, 16 ); - memset( oldSessionId, 0, 16 ); - } - - //-------------------------------------------------------------------------- - // Destructor - //-------------------------------------------------------------------------- - ~XRootDChannelInfo() - { - delete sidManager; - delete [] authBuffer; - } - - typedef std::vector StreamInfoVector; - - //-------------------------------------------------------------------------- - // Data - //-------------------------------------------------------------------------- - uint32_t serverFlags; - uint32_t protocolVersion; - uint8_t sessionId[16]; - uint8_t oldSessionId[16]; - bool firstLogIn; - SIDManager *sidManager; - char *authBuffer; - XrdSecProtocol *authProtocol; - XrdSecParameters *authParams; - XrdOucEnv *authEnv; - StreamInfoVector stream; - std::string streamName; - std::string authProtocolName; - std::set sentOpens; - std::set sentCloses; - uint32_t openFiles; - time_t waitBarrier; - XrdSecProtect *protection; - ServerResponseBody_Protocol *protRespBody; - unsigned int protRespSize; - XrdSysMutex mutex; - }; - - //---------------------------------------------------------------------------- - // Constructor - //---------------------------------------------------------------------------- - XRootDTransport::XRootDTransport(): - pAuthHandler(0), - pSecUnloadHandler( new PluginUnloadHandler() ) - { - } - - //---------------------------------------------------------------------------- - // Destructor - //---------------------------------------------------------------------------- - XRootDTransport::~XRootDTransport() - { - delete pSecUnloadHandler; pSecUnloadHandler = 0; - } - - //---------------------------------------------------------------------------- - // Read message header - //---------------------------------------------------------------------------- - Status XRootDTransport::GetHeader( Message *message, int socket ) - { - //-------------------------------------------------------------------------- - // A new message - allocate the space needed for the header - //-------------------------------------------------------------------------- - if( message->GetCursor() == 0 && message->GetSize() < 8 ) - message->Allocate( 8 ); - - //-------------------------------------------------------------------------- - // Read the message header - //-------------------------------------------------------------------------- - if( message->GetCursor() < 8 ) - { - uint32_t leftToBeRead = 8-message->GetCursor(); - while( leftToBeRead ) - { - int status = ::read( socket, message->GetBufferAtCursor(), leftToBeRead ); - if( status < 0 && (errno == EAGAIN || errno == EWOULDBLOCK) ) - return Status( stOK, suRetry ); - - if( status <= 0 ) - return Status( stError, errSocketError, errno ); - - leftToBeRead -= status; - message->AdvanceCursor( status ); - } - UnMarshallHeader( message ); - - uint32_t bodySize = *(uint32_t*)(message->GetBuffer(4)); - Log *log = DefaultEnv::GetLog(); - log->Dump( XRootDTransportMsg, "[msg: 0x%x] Expecting %d bytes of message " - "body", message, bodySize ); - - return Status( stOK, suDone ); - } - return Status( stError, errInternal ); - } - - //---------------------------------------------------------------------------- - // Read message body - //---------------------------------------------------------------------------- - Status XRootDTransport::GetBody( Message *message, int socket ) - { - //-------------------------------------------------------------------------- - // Retrieve the body - //-------------------------------------------------------------------------- - uint32_t leftToBeRead = 0; - uint32_t bodySize = *(uint32_t*)(message->GetBuffer(4)); - - if( message->GetCursor() == 8 ) - message->ReAllocate( bodySize + 8 ); - - leftToBeRead = bodySize-(message->GetCursor()-8); - while( leftToBeRead ) - { - int status = ::read( socket, message->GetBufferAtCursor(), leftToBeRead ); - if( status < 0 && (errno == EAGAIN || errno == EWOULDBLOCK) ) - return Status( stOK, suRetry ); - - if( status <= 0 ) - return Status( stError, errSocketError, errno ); - - leftToBeRead -= status; - message->AdvanceCursor( status ); - } - return Status( stOK, suDone ); - } - - //---------------------------------------------------------------------------- - // Initialize channel - //---------------------------------------------------------------------------- - void XRootDTransport::InitializeChannel( AnyObject &channelData ) - { - XRootDChannelInfo *info = new XRootDChannelInfo(); - XrdSysMutexHelper scopedLock( info->mutex ); - channelData.Set( info ); - - Env *env = DefaultEnv::GetEnv(); - int streams = DefaultSubStreamsPerChannel; - env->GetInt( "SubStreamsPerChannel", streams ); - if( streams < 1 ) streams = 1; - info->stream.resize( streams ); - } - - //---------------------------------------------------------------------------- - // Finalize channel - //---------------------------------------------------------------------------- - void XRootDTransport::FinalizeChannel( AnyObject & ) - { - } - - //---------------------------------------------------------------------------- - // HandShake - //---------------------------------------------------------------------------- - Status XRootDTransport::HandShake( HandShakeData *handShakeData, - AnyObject &channelData ) - { - XRootDChannelInfo *info = 0; - channelData.Get( info ); - XrdSysMutexHelper scopedLock( info->mutex ); - - if( info->stream.size() <= handShakeData->subStreamId ) - { - Log *log = DefaultEnv::GetLog(); - log->Error( XRootDTransportMsg, - "[%s] Internal error: not enough substreams", - handShakeData->streamName.c_str() ); - return Status( stFatal, errInternal ); - } - - if( handShakeData->subStreamId == 0 ) - { - info->streamName = handShakeData->streamName; - return HandShakeMain( handShakeData, channelData ); - } - return HandShakeParallel( handShakeData, channelData ); - } - - //---------------------------------------------------------------------------- - // Hand shake the main stream - //---------------------------------------------------------------------------- - Status XRootDTransport::HandShakeMain( HandShakeData *handShakeData, - AnyObject &channelData ) - { - XRootDChannelInfo *info = 0; - channelData.Get( info ); - XRootDStreamInfo &sInfo = info->stream[handShakeData->subStreamId]; - - //-------------------------------------------------------------------------- - // First step - we need to create and initial handshake and send it out - //-------------------------------------------------------------------------- - if( sInfo.status == XRootDStreamInfo::Disconnected || - sInfo.status == XRootDStreamInfo::Broken ) - { - handShakeData->out = GenerateInitialHSProtocol( handShakeData, info ); - sInfo.status = XRootDStreamInfo::HandShakeSent; - return Status( stOK, suContinue ); - } - - //-------------------------------------------------------------------------- - // Second step - we got the reply message to the initial handshake - //-------------------------------------------------------------------------- - if( sInfo.status == XRootDStreamInfo::HandShakeSent ) - { - Status st = ProcessServerHS( handShakeData, info ); - if( st.IsOK() ) - sInfo.status = XRootDStreamInfo::HandShakeReceived; - else - sInfo.status = XRootDStreamInfo::Broken; - return st; - } - - //-------------------------------------------------------------------------- - // Third step - we got the response to the protocol request, we need - // to process it and send out a login request - //-------------------------------------------------------------------------- - if( sInfo.status == XRootDStreamInfo::HandShakeReceived ) - { - Status st = ProcessProtocolResp( handShakeData, info ); - - if( !st.IsOK() ) - { - sInfo.status = XRootDStreamInfo::Broken; - return st; - } - - handShakeData->out = GenerateLogIn( handShakeData, info ); - sInfo.status = XRootDStreamInfo::LoginSent; - return Status( stOK, suContinue ); - } - - //-------------------------------------------------------------------------- - // Fourth step - handle the log in response and proceed with the - // authentication if required by the server - //-------------------------------------------------------------------------- - if( sInfo.status == XRootDStreamInfo::LoginSent ) - { - Status st = ProcessLogInResp( handShakeData, info ); - - if( !st.IsOK() ) - { - sInfo.status = XRootDStreamInfo::Broken; - return st; - } - - if( st.IsOK() && st.code == suDone ) - { - //---------------------------------------------------------------------- - // If it's not our first log in we need to end the previous session - // to make sure that the server noticed our disconnection and closed - // all the writable handles that we owned - //---------------------------------------------------------------------- - if( !info->firstLogIn ) - { - handShakeData->out = GenerateEndSession( handShakeData, info ); - sInfo.status = XRootDStreamInfo::EndSessionSent; - return Status( stOK, suContinue ); - } - - sInfo.status = XRootDStreamInfo::Connected; - info->firstLogIn = false; - return st; - } - - st = DoAuthentication( handShakeData, info ); - if( !st.IsOK() ) - sInfo.status = XRootDStreamInfo::Broken; - else - sInfo.status = XRootDStreamInfo::AuthSent; - return st; - } - - //-------------------------------------------------------------------------- - // Fifth step and later - proceed with the authentication - //-------------------------------------------------------------------------- - if( sInfo.status == XRootDStreamInfo::AuthSent ) - { - Status st = DoAuthentication( handShakeData, info ); - - if( !st.IsOK() ) - { - sInfo.status = XRootDStreamInfo::Broken; - return st; - } - - if( st.IsOK() && st.code == suDone ) - { - //---------------------------------------------------------------------- - // If it's not our first log in we need to end the previous session - //---------------------------------------------------------------------- - if( !info->firstLogIn ) - { - handShakeData->out = GenerateEndSession( handShakeData, info ); - sInfo.status = XRootDStreamInfo::EndSessionSent; - return Status( stOK, suContinue ); - } - - sInfo.status = XRootDStreamInfo::Connected; - info->firstLogIn = false; - return st; - } - - return st; - } - - //-------------------------------------------------------------------------- - // The last step - kXR_endsess returned - //-------------------------------------------------------------------------- - if( sInfo.status == XRootDStreamInfo::EndSessionSent ) - { - Status st = ProcessEndSessionResp( handShakeData, info ); - - if( !st.IsOK() ) - { - sInfo.status = XRootDStreamInfo::Broken; - return st; - } - - if( st.IsOK() && st.code == suDone ) - { - sInfo.status = XRootDStreamInfo::Connected; - return st; - } - } - - return Status( stOK, suDone ); - } - - //---------------------------------------------------------------------------- - // Hand shake parallel stream - //---------------------------------------------------------------------------- - Status XRootDTransport::HandShakeParallel( HandShakeData *handShakeData, - AnyObject &channelData ) - { - XRootDChannelInfo *info = 0; - channelData.Get( info ); - - XRootDStreamInfo &sInfo = info->stream[handShakeData->subStreamId]; - - //-------------------------------------------------------------------------- - // First step - we need to create and initial handshake and send it out - //-------------------------------------------------------------------------- - if( sInfo.status == XRootDStreamInfo::Disconnected || - sInfo.status == XRootDStreamInfo::Broken ) - { - handShakeData->out = GenerateInitialHS( handShakeData, info ); - sInfo.status = XRootDStreamInfo::HandShakeSent; - return Status( stOK, suContinue ); - } - - //-------------------------------------------------------------------------- - // Second step - we got the reply message to the initial handshake, - // if successful we need to send bind - //-------------------------------------------------------------------------- - if( sInfo.status == XRootDStreamInfo::HandShakeSent ) - { - Status st = ProcessServerHS( handShakeData, info ); - if( st.IsOK() ) - { - sInfo.status = XRootDStreamInfo::BindSent; - handShakeData->out = GenerateBind( handShakeData, info ); - return Status( stOK, suContinue ); - } - sInfo.status = XRootDStreamInfo::Broken; - return st; - } - - //-------------------------------------------------------------------------- - // Third step - we got the response to the kXR_bind - //-------------------------------------------------------------------------- - if( sInfo.status == XRootDStreamInfo::BindSent ) - { - Status st = ProcessBindResp( handShakeData, info ); - - if( !st.IsOK() ) - { - sInfo.status = XRootDStreamInfo::Broken; - return st; - } - sInfo.status = XRootDStreamInfo::Connected; - return Status(); - } - return Status(); - } - - //---------------------------------------------------------------------------- - // Check if the stream should be disconnected - //---------------------------------------------------------------------------- - bool XRootDTransport::IsStreamTTLElapsed( time_t inactiveTime, - uint16_t streamId, - AnyObject &channelData ) - { - XRootDChannelInfo *info = 0; - channelData.Get( info ); - Env *env = DefaultEnv::GetEnv(); - Log *log = DefaultEnv::GetLog(); - - //-------------------------------------------------------------------------- - // Check the TTL settings for the current server - //-------------------------------------------------------------------------- - int ttl; - if( info->serverFlags & kXR_isServer ) - { - ttl = DefaultDataServerTTL; - env->GetInt( "DataServerTTL", ttl ); - } - else - { - ttl = DefaultLoadBalancerTTL; - env->GetInt( "LoadBalancerTTL", ttl ); - } - - //-------------------------------------------------------------------------- - // See whether we can give a go-ahead for the disconnection - //-------------------------------------------------------------------------- - XrdSysMutexHelper scopedLock( info->mutex ); - uint16_t allocatedSIDs = info->sidManager->GetNumberOfAllocatedSIDs(); - log->Dump( XRootDTransportMsg, "[%s] Stream inactive since %d seconds, " - "TTL: %d, allocated SIDs: %d, open files: %d", - info->streamName.c_str(), inactiveTime, ttl, allocatedSIDs, - info->openFiles ); - - if( info->openFiles ) - return false; - - if( !allocatedSIDs && inactiveTime > ttl ) - return true; - - return false; - } - - //---------------------------------------------------------------------------- - // Check the stream is broken - ie. TCP connection got broken and - // went undetected by the TCP stack - //---------------------------------------------------------------------------- - Status XRootDTransport::IsStreamBroken( time_t inactiveTime, - uint16_t streamId, - AnyObject &channelData ) - { - XRootDChannelInfo *info = 0; - channelData.Get( info ); - Env *env = DefaultEnv::GetEnv(); - Log *log = DefaultEnv::GetLog(); - - int streamTimeout = DefaultStreamTimeout; - env->GetInt( "StreamTimeout", streamTimeout ); - - XrdSysMutexHelper scopedLock( info->mutex ); - - uint16_t allocatedSIDs = info->sidManager->GetNumberOfAllocatedSIDs(); - - log->Dump( XRootDTransportMsg, "[%s] Stream inactive since %d seconds, " - "stream timeout: %d, allocated SIDs: %d, wait barrier: %s", - info->streamName.c_str(), inactiveTime, streamTimeout, - allocatedSIDs, Utils::TimeToString(info->waitBarrier).c_str() ); - - if( inactiveTime < streamTimeout ) - return Status(); - - if( time(0) < info->waitBarrier ) - return Status(); - - if( !allocatedSIDs ) - return Status(); - - return Status( stError, errSocketError ); - } - - //---------------------------------------------------------------------------- - // Multiplex - //---------------------------------------------------------------------------- - PathID XRootDTransport::Multiplex( Message *, AnyObject &, PathID * ) - { - return PathID( 0, 0 ); - } - - //---------------------------------------------------------------------------- - // Multiplex - //---------------------------------------------------------------------------- - PathID XRootDTransport::MultiplexSubStream( Message *msg, - uint16_t streamId, - AnyObject &channelData, - PathID *hint ) - { - XRootDChannelInfo *info = 0; - channelData.Get( info ); - XrdSysMutexHelper scopedLock( info->mutex ); - - //-------------------------------------------------------------------------- - // If we're not connected to a data server or we don't know that yet - // we stream through 0 - //-------------------------------------------------------------------------- - if( !(info->serverFlags & kXR_isServer) || info->stream.size() == 0 ) - return PathID( 0, 0 ); - - //-------------------------------------------------------------------------- - // Select the streams - //-------------------------------------------------------------------------- - Log *log = DefaultEnv::GetLog(); - uint16_t upStream = 0; - uint16_t downStream = 0; - - if( hint ) - { - upStream = hint->up; - downStream = hint->down; - } - else - { - upStream = 0; - std::vector connected; - for( size_t i = 1; i < info->stream.size(); ++i ) - if( info->stream[i].status == XRootDStreamInfo::Connected ) - connected.push_back( i ); - - if( connected.empty() ) - downStream = 0; - else - downStream = connected[random()%connected.size()]; - } - - if( upStream >= info->stream.size() ) - { - log->Debug( XRootDTransportMsg, - "[%s] Up link stream %d does not exist, using 0", - info->streamName.c_str(), upStream ); - upStream = 0; - } - - if( downStream >= info->stream.size() ) - { - log->Debug( XRootDTransportMsg, - "[%s] Down link stream %d does not exist, using 0", - info->streamName.c_str(), downStream ); - downStream = 0; - } - - //-------------------------------------------------------------------------- - // Modify the message - //-------------------------------------------------------------------------- - UnMarshallRequest( msg ); - ClientRequestHdr *hdr = (ClientRequestHdr*)msg->GetBuffer(); - switch( hdr->requestid ) - { - //------------------------------------------------------------------------ - // Read - we update the path id to tell the server where we want to - // get the response, but we still send the request through stream 0 - // We need to allocate space for read_args if we don't have it - // included yet - //------------------------------------------------------------------------ - case kXR_read: - { - if( msg->GetSize() < sizeof(ClientReadRequest) + 8 ) - { - msg->ReAllocate( sizeof(ClientReadRequest) + 8 ); - void *newBuf = msg->GetBuffer(sizeof(ClientReadRequest)); - memset( newBuf, 0, 8 ); - ClientReadRequest *req = (ClientReadRequest*)msg->GetBuffer(); - req->dlen += 8; - } - read_args *args = (read_args*)msg->GetBuffer(sizeof(ClientReadRequest)); - args->pathid = info->stream[downStream].pathId; - break; - } - - //------------------------------------------------------------------------ - // ReadV - the situation is identical to read but we don't need any - // additional structures to specify the return path - //------------------------------------------------------------------------ - case kXR_readv: - { - ClientReadVRequest *req = (ClientReadVRequest*)msg->GetBuffer(); - req->pathid = info->stream[downStream].pathId; - break; - } - - //------------------------------------------------------------------------ - // Write - multiplexing writes doesn't work properly in the server - //------------------------------------------------------------------------ - case kXR_write: - { -// ClientWriteRequest *req = (ClientWriteRequest*)msg->GetBuffer(); -// req->pathid = info->stream[downStream].pathId; - break; - } - - //------------------------------------------------------------------------ - // WriteV - multiplexing writes doesn't work properly in the server - //------------------------------------------------------------------------ - case kXR_writev: - { -// ClientWriteVRequest *req = (ClientWriteVRequest*)msg->GetBuffer(); -// req->pathid = info->stream[downStream].pathId; - break; - } - - }; - MarshallRequest( msg ); - return PathID( upStream, downStream ); - } - - //---------------------------------------------------------------------------- - // Return a number of streams that should be created - we always have - // one primary stream - //---------------------------------------------------------------------------- - uint16_t XRootDTransport::StreamNumber( AnyObject &/*channelData*/ ) - { - return 1; - } - - //---------------------------------------------------------------------------- - // Return a number of substreams per stream that should be created - // This depends on the environment and whether we are connected to - // a data server or not - //---------------------------------------------------------------------------- - uint16_t XRootDTransport::SubStreamNumber( AnyObject &channelData ) - { - XRootDChannelInfo *info = 0; - channelData.Get( info ); - XrdSysMutexHelper scopedLock( info->mutex ); - - if( info->serverFlags & kXR_isServer ) - return info->stream.size(); - - return 1; - } - - //---------------------------------------------------------------------------- - // Marshall - //---------------------------------------------------------------------------- - Status XRootDTransport::MarshallRequest( Message *msg ) - { - ClientRequest *req = (ClientRequest*)msg->GetBuffer(); - switch( req->header.requestid ) - { - //------------------------------------------------------------------------ - // kXR_protocol - //------------------------------------------------------------------------ - case kXR_protocol: - req->protocol.clientpv = htonl( req->protocol.clientpv ); - break; - - //------------------------------------------------------------------------ - // kXR_login - //------------------------------------------------------------------------ - case kXR_login: - req->login.pid = htonl( req->login.pid ); - break; - - //------------------------------------------------------------------------ - // kXR_locate - //------------------------------------------------------------------------ - case kXR_locate: - req->locate.options = htons( req->locate.options ); - break; - - //------------------------------------------------------------------------ - // kXR_query - //------------------------------------------------------------------------ - case kXR_query: - req->query.infotype = htons( req->query.infotype ); - break; - - //------------------------------------------------------------------------ - // kXR_truncate - //------------------------------------------------------------------------ - case kXR_truncate: - req->truncate.offset = htonll( req->truncate.offset ); - break; - - //------------------------------------------------------------------------ - // kXR_mkdir - //------------------------------------------------------------------------ - case kXR_mkdir: - req->mkdir.mode = htons( req->mkdir.mode ); - break; - - //------------------------------------------------------------------------ - // kXR_chmod - //------------------------------------------------------------------------ - case kXR_chmod: - req->chmod.mode = htons( req->chmod.mode ); - break; - - //------------------------------------------------------------------------ - // kXR_open - //------------------------------------------------------------------------ - case kXR_open: - req->open.mode = htons( req->open.mode ); - req->open.options = htons( req->open.options ); - break; - - //------------------------------------------------------------------------ - // kXR_read - //------------------------------------------------------------------------ - case kXR_read: - req->read.offset = htonll( req->read.offset ); - req->read.rlen = htonl( req->read.rlen ); - break; - - //------------------------------------------------------------------------ - // kXR_write - //------------------------------------------------------------------------ - case kXR_write: - req->write.offset = htonll( req->write.offset ); - break; - - //------------------------------------------------------------------------ - // kXR_mv - //------------------------------------------------------------------------ - case kXR_mv: - req->mv.arg1len = htons( req->mv.arg1len ); - break; - - //------------------------------------------------------------------------ - // kXR_readv - //------------------------------------------------------------------------ - case kXR_readv: - { - uint16_t numChunks = (req->readv.dlen)/16; - readahead_list *dataChunk = (readahead_list*)msg->GetBuffer( 24 ); - for( size_t i = 0; i < numChunks; ++i ) - { - dataChunk[i].rlen = htonl( dataChunk[i].rlen ); - dataChunk[i].offset = htonll( dataChunk[i].offset ); - } - break; - } - - //------------------------------------------------------------------------ - // kXR_writev - //------------------------------------------------------------------------ - case kXR_writev: - { - uint16_t numChunks = (req->writev.dlen)/16; - XrdProto::write_list *wrtList = - reinterpret_cast( msg->GetBuffer( 24 ) ); - for( size_t i = 0; i < numChunks; ++i ) - { - wrtList[i].wlen = htonl( wrtList[i].wlen ); - wrtList[i].offset = htonll( wrtList[i].offset ); - } - } - }; - - req->header.requestid = htons( req->header.requestid ); - req->header.dlen = htonl( req->header.dlen ); - msg->SetIsMarshalled( true ); - return Status(); - } - - //---------------------------------------------------------------------------- - // Unmarshall the request - sometimes the requests need to be rewritten, - // so we need to unmarshall them - //---------------------------------------------------------------------------- - Status XRootDTransport::UnMarshallRequest( Message *msg ) - { - // We rely on the marshaling process to be symmetric! - // First we unmarshall the request ID and the length because - // MarshallRequest() relies on these, and then we need to unmarshall these - // two again, because they get marshalled in MarshallRequest(). - // All this is pretty damn ugly and should be rewritten. - ClientRequest *req = (ClientRequest*)msg->GetBuffer(); - req->header.requestid = htons( req->header.requestid ); - req->header.dlen = htonl( req->header.dlen ); - Status st = MarshallRequest( msg ); - req->header.requestid = htons( req->header.requestid ); - req->header.dlen = htonl( req->header.dlen ); - msg->SetIsMarshalled( false ); - return st; - } - - //---------------------------------------------------------------------------- - // Unmarshall the body of the incoming message - //---------------------------------------------------------------------------- - Status XRootDTransport::UnMarshallBody( Message *msg, uint16_t reqType ) - { - ServerResponse *m = (ServerResponse *)msg->GetBuffer(); - - //-------------------------------------------------------------------------- - // kXR_ok - //-------------------------------------------------------------------------- - if( m->hdr.status == kXR_ok ) - { - switch( reqType ) - { - //---------------------------------------------------------------------- - // kXR_protocol - //---------------------------------------------------------------------- - case kXR_protocol: - if( m->hdr.dlen < 8 ) - return Status( stError, errInvalidMessage ); - m->body.protocol.pval = ntohl( m->body.protocol.pval ); - m->body.protocol.flags = ntohl( m->body.protocol.flags ); - break; - } - } - //-------------------------------------------------------------------------- - // kXR_error - //-------------------------------------------------------------------------- - else if( m->hdr.status == kXR_error ) - { - if( m->hdr.dlen < 4 ) - return Status( stError, errInvalidMessage ); - m->body.error.errnum = ntohl( m->body.error.errnum ); - } - - //-------------------------------------------------------------------------- - // kXR_wait - //-------------------------------------------------------------------------- - else if( m->hdr.status == kXR_wait ) - { - if( m->hdr.dlen < 4 ) - return Status( stError, errInvalidMessage ); - m->body.wait.seconds = htonl( m->body.wait.seconds ); - } - - //-------------------------------------------------------------------------- - // kXR_redirect - //-------------------------------------------------------------------------- - else if( m->hdr.status == kXR_redirect ) - { - if( m->hdr.dlen < 4 ) - return Status( stError, errInvalidMessage ); - m->body.redirect.port = htonl( m->body.redirect.port ); - } - - //-------------------------------------------------------------------------- - // kXR_waitresp - //-------------------------------------------------------------------------- - else if( m->hdr.status == kXR_waitresp ) - { - if( m->hdr.dlen < 4 ) - return Status( stError, errInvalidMessage ); - m->body.waitresp.seconds = htonl( m->body.waitresp.seconds ); - } - - //-------------------------------------------------------------------------- - // kXR_attn - //-------------------------------------------------------------------------- - else if( m->hdr.status == kXR_attn ) - { - if( m->hdr.dlen < 4 ) - return Status( stError, errInvalidMessage ); - m->body.attn.actnum = htonl( m->body.attn.actnum ); - } - - return Status(); - } - - //------------------------------------------------------------------------ - // Unmarshall the header of the incoming message - //------------------------------------------------------------------------ - void XRootDTransport::UnMarshallHeader( Message *msg ) - { - ServerResponseHeader *header = (ServerResponseHeader *)msg->GetBuffer(); - header->status = ntohs( header->status ); - header->dlen = ntohl( header->dlen ); - } - - //---------------------------------------------------------------------------- - // Log server error response - //---------------------------------------------------------------------------- - void XRootDTransport::LogErrorResponse( const Message &msg ) - { - Log *log = DefaultEnv::GetLog(); - ServerResponse *rsp = (ServerResponse *)msg.GetBuffer(); - char *errmsg = new char[rsp->hdr.dlen-3]; errmsg[rsp->hdr.dlen-4] = 0; - memcpy( errmsg, rsp->body.error.errmsg, rsp->hdr.dlen-4 ); - log->Error( XRootDTransportMsg, "Server responded with an error [%d]: %s", - rsp->body.error.errnum, errmsg ); - delete [] errmsg; - } - - //---------------------------------------------------------------------------- - // The stream has been disconnected, do the cleanups - //---------------------------------------------------------------------------- - void XRootDTransport::Disconnect( AnyObject &channelData, - uint16_t /*streamId*/, - uint16_t subStreamId ) - { - XRootDChannelInfo *info = 0; - channelData.Get( info ); - XrdSysMutexHelper scopedLock( info->mutex ); - - CleanUpProtection( info ); - - if( !info->stream.empty() ) - { - XRootDStreamInfo &sInfo = info->stream[subStreamId]; - sInfo.status = XRootDStreamInfo::Disconnected; - } - - if( subStreamId == 0 ) - { - info->sidManager->ReleaseAllTimedOut(); - info->sentOpens.clear(); - info->sentCloses.clear(); - info->openFiles = 0; - info->waitBarrier = 0; - } - } - - //------------------------------------------------------------------------ - // Query the channel - //------------------------------------------------------------------------ - Status XRootDTransport::Query( uint16_t query, - AnyObject &result, - AnyObject &channelData ) - { - XRootDChannelInfo *info = 0; - channelData.Get( info ); - XrdSysMutexHelper scopedLock( info->mutex ); - - switch( query ) - { - //------------------------------------------------------------------------ - // Protocol name - //------------------------------------------------------------------------ - case TransportQuery::Name: - result.Set( (const char*)"XRootD", false ); - return Status(); - - //------------------------------------------------------------------------ - // Authentication - //------------------------------------------------------------------------ - case TransportQuery::Auth: - result.Set( new std::string( info->authProtocolName ), false ); - return Status(); - - //------------------------------------------------------------------------ - // SID Manager object - //------------------------------------------------------------------------ - case XRootDQuery::SIDManager: - result.Set( info->sidManager, false ); - return Status(); - - //------------------------------------------------------------------------ - // Server flags - //------------------------------------------------------------------------ - case XRootDQuery::ServerFlags: - result.Set( new int( info->serverFlags ), false ); - return Status(); - - //------------------------------------------------------------------------ - // Protocol version - //------------------------------------------------------------------------ - case XRootDQuery::ProtocolVersion: - result.Set( new int( info->protocolVersion ), false ); - return Status(); - }; - return Status( stError, errQueryNotSupported ); - } - - //---------------------------------------------------------------------------- - // Check whether the transport can hijack the message - //---------------------------------------------------------------------------- - uint32_t XRootDTransport::MessageReceived( Message *msg, - uint16_t streamId, - uint16_t subStream, - AnyObject &channelData ) - { - XRootDChannelInfo *info = 0; - channelData.Get( info ); - XrdSysMutexHelper scopedLock( info->mutex ); - Log *log = DefaultEnv::GetLog(); - - //-------------------------------------------------------------------------- - // Check whether this message is a response to a request that has - // timed out, and if so, drop it - //-------------------------------------------------------------------------- - ServerResponse *rsp = (ServerResponse*)msg->GetBuffer(); - if( rsp->hdr.status == kXR_attn ) - { - if( rsp->body.attn.actnum != (int32_t)htonl(kXR_asynresp) ) - return NoAction; - rsp = (ServerResponse*)msg->GetBuffer(16); - } - - if( info->sidManager->IsTimedOut( rsp->hdr.streamid ) ) - { - log->Error( XRootDTransportMsg, "Message 0x%x, stream [%d, %d] is a " - "response that we're no longer interested in (timed out)", - msg, rsp->hdr.streamid[0], rsp->hdr.streamid[1] ); - //------------------------------------------------------------------------ - // If it is kXR_waitresp there will be another one, - // so we don't release the sid yet - //------------------------------------------------------------------------ - if( rsp->hdr.status != kXR_waitresp ) - info->sidManager->ReleaseTimedOut( rsp->hdr.streamid ); - //------------------------------------------------------------------------ - // If it is a successful response to an open request - // that timed out, we need to send a close - //------------------------------------------------------------------------ - uint16_t sid; memcpy( &sid, rsp->hdr.streamid, 2 ); - std::set::iterator sidIt = info->sentOpens.find( sid ); - if( sidIt != info->sentOpens.end() ) - { - info->sentOpens.erase( sidIt ); - if( rsp->hdr.status == kXR_ok ) return RequestClose; - } - delete msg; - return DigestMsg; - } - - //-------------------------------------------------------------------------- - // If we have a wait or waitresp - //-------------------------------------------------------------------------- - uint32_t seconds = 0; - if( rsp->hdr.status == kXR_wait ) - seconds = ntohl( rsp->body.wait.seconds ) + 5; // we need extra time - // to re-send the request - else if( rsp->hdr.status == kXR_waitresp ) - seconds = ntohl( rsp->body.waitresp.seconds ); - - time_t barrier = time(0) + seconds; - if( info->waitBarrier < barrier ) - info->waitBarrier = barrier; - - //-------------------------------------------------------------------------- - // If we got a response to an open request, we may need to bump the counter - // of open files - //-------------------------------------------------------------------------- - uint16_t sid; memcpy( &sid, rsp->hdr.streamid, 2 ); - std::set::iterator sidIt = info->sentOpens.find( sid ); - if( sidIt != info->sentOpens.end() ) - { - if( rsp->hdr.status == kXR_waitresp ) - return NoAction; - info->sentOpens.erase( sidIt ); - if( rsp->hdr.status == kXR_ok ) - ++info->openFiles; - return NoAction; - } - - //-------------------------------------------------------------------------- - // If we got a response to a close, we may need to decrement the counter of - // open files - //-------------------------------------------------------------------------- - sidIt = info->sentCloses.find( sid ); - if( sidIt != info->sentCloses.end() ) - { - if( rsp->hdr.status == kXR_waitresp ) - return NoAction; - info->sentCloses.erase( sidIt ); - --info->openFiles; - return NoAction; - } - return NoAction; - } - - //---------------------------------------------------------------------------- - // Notify the transport about a message having been sent - //---------------------------------------------------------------------------- - void XRootDTransport::MessageSent( Message *msg, - uint16_t streamId, - uint16_t subStream, - uint32_t bytesSent, - AnyObject &channelData ) - { - XRootDChannelInfo *info = 0; - channelData.Get( info ); - XrdSysMutexHelper scopedLock( info->mutex ); - ClientRequest *req = (ClientRequest*)msg->GetBuffer(); - uint16_t reqid = ntohs( req->header.requestid ); - - - //-------------------------------------------------------------------------- - // We need to track opens to know if we can close streams due to idleness - //-------------------------------------------------------------------------- - uint16_t sid; - memcpy( &sid, req->header.streamid, 2 ); - - if( reqid == kXR_open ) - info->sentOpens.insert( sid ); - else if( reqid == kXR_close ) - info->sentCloses.insert( sid ); - } - - - //------------------------------------------------------------------------ - // Get signature for given message - //------------------------------------------------------------------------ - Status XRootDTransport::GetSignature( Message *toSign, Message *&sign, AnyObject &channelData ) - { - XrdSysRWLockHelper scope( pSecUnloadHandler->lock ); - if( pSecUnloadHandler->unloaded ) return Status( stError, errInvalidOp ); - - ClientRequest *thereq = reinterpret_cast( toSign->GetBuffer() ); - XRootDChannelInfo *info = 0; - channelData.Get( info ); - if( !info ) return Status( stError, errInternal ); - if( info->protection ) - { - SecurityRequest *newreq = 0; - // check if we have to secure the request in the first place - if( !NEED2SECURE ( info->protection )( *thereq ) ) return Status(); - // secure (sign/encrypt) the request - int rc = info->protection->Secure( newreq, *thereq, 0 ); - // there was an error - if( rc < 0 ) - return Status( stError, errInternal, -rc ); - - sign = new Message(); - sign->Grab( reinterpret_cast( newreq ), rc ); - } - - return Status(); - } - - //---------------------------------------------------------------------------- - // Generate the message to be sent as an initial handshake - // (handshake+kXR_protocol) - //---------------------------------------------------------------------------- - Message *XRootDTransport::GenerateInitialHSProtocol( HandShakeData *hsData, - XRootDChannelInfo * ) - { - Log *log = DefaultEnv::GetLog(); - log->Debug( XRootDTransportMsg, - "[%s] Sending out the initial hand shake + kXR_protocol", - hsData->streamName.c_str() ); - - Message *msg = new Message(); - - msg->Allocate( 20+sizeof(ClientProtocolRequest) ); - msg->Zero(); - - ClientInitHandShake *init = (ClientInitHandShake *)msg->GetBuffer(); - ClientProtocolRequest *proto = (ClientProtocolRequest *)msg->GetBuffer(20); - init->fourth = htonl(4); - init->fifth = htonl(2012); - - proto->requestid = htons(kXR_protocol); - proto->clientpv = htonl(kXR_PROTOCOLVERSION); - proto->flags = kXR_secreqs; - return msg; - } - - //---------------------------------------------------------------------------- - // Generate the message to be sent as an initial handshake - //---------------------------------------------------------------------------- - Message *XRootDTransport::GenerateInitialHS( HandShakeData *hsData, - XRootDChannelInfo * ) - { - Log *log = DefaultEnv::GetLog(); - log->Debug( XRootDTransportMsg, - "[%s] Sending out the initial hand shake", - hsData->streamName.c_str() ); - - Message *msg = new Message(); - - msg->Allocate( 20 ); - msg->Zero(); - - ClientInitHandShake *init = (ClientInitHandShake *)msg->GetBuffer(); - init->fourth = htonl(4); - init->fifth = htonl(2012); - return msg; - } - - //---------------------------------------------------------------------------- - // Process the server initial handshake response - //---------------------------------------------------------------------------- - Status XRootDTransport::ProcessServerHS( HandShakeData *hsData, - XRootDChannelInfo *info ) - { - Log *log = DefaultEnv::GetLog(); - - Message *msg = hsData->in; - ServerResponseHeader *respHdr = (ServerResponseHeader *)msg->GetBuffer(); - ServerInitHandShake *hs = (ServerInitHandShake *)msg->GetBuffer(4); - - if( respHdr->status != kXR_ok ) - { - log->Error( XRootDTransportMsg, "[%s] Invalid hand shake response", - hsData->streamName.c_str() ); - - return Status( stFatal, errHandShakeFailed ); - } - - info->protocolVersion = ntohl(hs->protover); - info->serverFlags = ntohl(hs->msgval) == kXR_DataServer ? - kXR_isServer: - kXR_isManager; - - log->Debug( XRootDTransportMsg, - "[%s] Got the server hand shake response (%s, protocol " - "version %x)", - hsData->streamName.c_str(), - ServerFlagsToStr( info->serverFlags ).c_str(), - info->protocolVersion ); - - return Status( stOK, suContinue ); - } - - //---------------------------------------------------------------------------- - // Process the protocol response - //---------------------------------------------------------------------------- - Status XRootDTransport::ProcessProtocolResp( HandShakeData *hsData, - XRootDChannelInfo *info ) - { - Log *log = DefaultEnv::GetLog(); - - Status st = UnMarshallBody( hsData->in, kXR_protocol ); - if( !st.IsOK() ) - return st; - - ServerResponse *rsp = (ServerResponse*)hsData->in->GetBuffer(); - - - if( rsp->hdr.status != kXR_ok ) - { - log->Error( XRootDTransportMsg, "[%s] kXR_protocol request failed", - hsData->streamName.c_str() ); - - return Status( stFatal, errHandShakeFailed ); - } - - if( rsp->body.protocol.pval >= 0x297 ) - info->serverFlags = rsp->body.protocol.flags; - - if( rsp->hdr.dlen > 8 ) - { - info->protRespBody = new ServerResponseBody_Protocol( rsp->body.protocol ); - info->protRespSize = rsp->hdr.dlen; - } - - log->Debug( XRootDTransportMsg, - "[%s] kXR_protocol successful (%s, protocol version %x)", - hsData->streamName.c_str(), - ServerFlagsToStr( info->serverFlags ).c_str(), - info->protocolVersion ); - - return Status( stOK, suContinue ); - } - - //---------------------------------------------------------------------------- - // Generate the bind message - //---------------------------------------------------------------------------- - Message *XRootDTransport::GenerateBind( HandShakeData *hsData, - XRootDChannelInfo *info ) - { - Log *log = DefaultEnv::GetLog(); - - log->Debug( XRootDTransportMsg, - "[%s] Sending out the bind request", - hsData->streamName.c_str() ); - - - Message *msg = new Message( sizeof( ClientBindRequest ) ); - ClientBindRequest *bindReq = (ClientBindRequest *)msg->GetBuffer(); - - bindReq->requestid = kXR_bind; - memcpy( bindReq->sessid, info->sessionId, 16 ); - bindReq->dlen = 0; - MarshallRequest( msg ); - return msg; - } - - //---------------------------------------------------------------------------- - // Generate the bind message - //---------------------------------------------------------------------------- - Status XRootDTransport::ProcessBindResp( HandShakeData *hsData, - XRootDChannelInfo *info ) - { - Log *log = DefaultEnv::GetLog(); - - Status st = UnMarshallBody( hsData->in, kXR_bind ); - if( !st.IsOK() ) - return st; - - ServerResponse *rsp = (ServerResponse*)hsData->in->GetBuffer(); - - if( rsp->hdr.status != kXR_ok ) - { - log->Error( XRootDTransportMsg, "[%s] kXR_bind request failed", - hsData->streamName.c_str() ); - return Status( stFatal, errHandShakeFailed ); - } - - info->stream[hsData->subStreamId].pathId = rsp->body.bind.substreamid; - - log->Debug( XRootDTransportMsg, "[%s] kXR_bind successful", - hsData->streamName.c_str() ); - - return Status(); - } - - //---------------------------------------------------------------------------- - // Generate the login message - //---------------------------------------------------------------------------- - Message *XRootDTransport::GenerateLogIn( HandShakeData *hsData, - XRootDChannelInfo * ) - { - Log *log = DefaultEnv::GetLog(); - Env *env = DefaultEnv::GetEnv(); - - //-------------------------------------------------------------------------- - // Compute the login cgi - //-------------------------------------------------------------------------- - int timeZone = XrdSysTimer::TimeZone(); - char *hostName = XrdNetUtils::MyHostName(); - std::string countryCode = Utils::FQDNToCC( hostName ); - char *cgiBuffer = new char[1024]; - std::string appName; - std::string monInfo; - env->GetString( "AppName", appName ); - env->GetString( "MonInfo", monInfo ); - snprintf( cgiBuffer, 1024, - "?xrd.cc=%s&xrd.tz=%d&xrd.appname=%s&xrd.info=%s&" - "xrd.hostname=%s&xrd.rn=%s", countryCode.c_str(), timeZone, - appName.c_str(), monInfo.c_str(), hostName, XrdVERSION ); - uint16_t cgiLen = strlen( cgiBuffer ); - free( hostName ); - - //-------------------------------------------------------------------------- - // Generate the message - //-------------------------------------------------------------------------- - Message *msg = new Message( sizeof(ClientLoginRequest) + cgiLen ); - ClientLoginRequest *loginReq = (ClientLoginRequest *)msg->GetBuffer(); - - loginReq->requestid = kXR_login; - loginReq->pid = ::getpid(); - loginReq->capver[0] = kXR_asyncap | kXR_ver004; - loginReq->role[0] = kXR_useruser; - loginReq->dlen = cgiLen; - loginReq->ability = kXR_fullurl | kXR_readrdok; - - int multiProtocol = 0; - env->GetInt( "MultiProtocol", multiProtocol ); - if(multiProtocol) - loginReq->ability |= kXR_multipr; - - //-------------------------------------------------------------------------- - // Check the IP stacks - //-------------------------------------------------------------------------- - XrdNetUtils::NetProt stacks = XrdNetUtils::NetConfig(); - bool dualStack = false; - bool privateIPv6 = false; - bool privateIPv4 = false; - - if( (stacks & XrdNetUtils::hasIP64) == XrdNetUtils::hasIP64 ) - { - dualStack = true; - loginReq->ability |= kXR_hasipv64; - } - - if( (stacks & XrdNetUtils::hasIPv6) && !(stacks & XrdNetUtils::hasPub6) ) - { - privateIPv6 = true; - loginReq->ability |= kXR_onlyprv6; - } - - if( (stacks & XrdNetUtils::hasIPv4) && !(stacks & XrdNetUtils::hasPub4) ) - { - privateIPv4 = true; - loginReq->ability |= kXR_onlyprv4; - } - - // The following code snippet tries to overcome the problem that this host - // may still be dual-stacked but we don't know it because one of the - // interfaces was not registered in DNS. - // - if( !dualStack && hsData->serverAddr ) - {if ( ( ( stacks & XrdNetUtils::hasIPv4 ) - && hsData->serverAddr->isIPType(XrdNetAddrInfo::IPv6)) - || ( ( stacks & XrdNetUtils::hasIPv6 ) - && hsData->serverAddr->isIPType(XrdNetAddrInfo::IPv4))) - {dualStack = true; - loginReq->ability |= kXR_hasipv64; - } - } - - //-------------------------------------------------------------------------- - // Check the username - //-------------------------------------------------------------------------- - std::string buffer( 8, 0 ); - if( hsData->url->GetUserName().length() ) - buffer = hsData->url->GetUserName(); - else - { - char *name = new char[1024]; - if( !XrdOucUtils::UserName( geteuid(), name, 1024 ) ) - buffer = name; - else - buffer = "????"; - delete [] name; - } - buffer.resize( 8, 0 ); - std::copy( buffer.begin(), buffer.end(), (char*)loginReq->username ); - - msg->Append( cgiBuffer, cgiLen, 24 ); - - log->Debug( XRootDTransportMsg, "[%s] Sending out kXR_login request, " - "username: %s, cgi: %s, dual-stack: %s, private IPv4: %s, " - "private IPv6: %s", hsData->streamName.c_str(), - loginReq->username, cgiBuffer, dualStack ? "true" : "false", - privateIPv4 ? "true" : "false", - privateIPv6 ? "true" : "false" ); - - delete [] cgiBuffer; - MarshallRequest( msg ); - return msg; - } - - //---------------------------------------------------------------------------- - // Process the protocol response - //---------------------------------------------------------------------------- - Status XRootDTransport::ProcessLogInResp( HandShakeData *hsData, - XRootDChannelInfo *info ) - { - Log *log = DefaultEnv::GetLog(); - - Status st = UnMarshallBody( hsData->in, kXR_login ); - if( !st.IsOK() ) - return st; - - ServerResponse *rsp = (ServerResponse*)hsData->in->GetBuffer(); - - if( rsp->hdr.status != kXR_ok ) - { - log->Error( XRootDTransportMsg, "[%s] Got invalid login response", - hsData->streamName.c_str() ); - return Status( stFatal, errLoginFailed ); - } - - if( !info->firstLogIn ) - memcpy( info->oldSessionId, info->sessionId, 16 ); - - if( rsp->hdr.dlen == 0 && info->protocolVersion <= 0x289 ) - { - //-------------------------------------------------------------------------- - // This if statement is there only to support dCache inaccurate - // implementation of XRoot protocol, that in some cases returns - // an empty login response for protocol version <= 2.8.9. - //-------------------------------------------------------------------------- - memset( info->sessionId, 0, 16 ); - log->Warning( XRootDTransportMsg, - "[%s] Logged in, accepting empty login response.", - hsData->streamName.c_str() ); - return Status(); - } - - if( rsp->hdr.dlen < 16 ) - return Status( stError, errDataError ); - - memcpy( info->sessionId, rsp->body.login.sessid, 16 ); - - std::string sessId = Utils::Char2Hex( rsp->body.login.sessid, 16 ); - - log->Debug( XRootDTransportMsg, "[%s] Logged in, session: %s", - hsData->streamName.c_str(), sessId.c_str() ); - - //-------------------------------------------------------------------------- - // We have an authentication info to process - //-------------------------------------------------------------------------- - if( rsp->hdr.dlen > 16 ) - { - size_t len = rsp->hdr.dlen-16; - info->authBuffer = new char[len+1]; - info->authBuffer[len] = 0; - memcpy( info->authBuffer, rsp->body.login.sec, len ); - log->Debug( XRootDTransportMsg, "[%s] Authentication is required: %s", - hsData->streamName.c_str(), info->authBuffer ); - - return Status( stOK, suContinue ); - } - - return Status(); - } - - //---------------------------------------------------------------------------- - // Do the authentication - //---------------------------------------------------------------------------- - Status XRootDTransport::DoAuthentication( HandShakeData *hsData, - XRootDChannelInfo *info ) - { - //-------------------------------------------------------------------------- - // Prepare - //-------------------------------------------------------------------------- - Log *log = DefaultEnv::GetLog(); - XRootDStreamInfo &sInfo = info->stream[hsData->streamId]; - XrdSecCredentials *credentials = 0; - std::string protocolName; - - //-------------------------------------------------------------------------- - // We're doing this for the first time - //-------------------------------------------------------------------------- - if( sInfo.status == XRootDStreamInfo::LoginSent ) - { - log->Debug( XRootDTransportMsg, "[%s] Sending authentication data", - hsData->streamName.c_str() ); - - //------------------------------------------------------------------------ - // Set up the authentication environment - //------------------------------------------------------------------------ - info->authEnv = new XrdOucEnv(); - info->authEnv->Put( "sockname", hsData->clientName.c_str() ); - info->authEnv->Put( "username", hsData->url->GetUserName().c_str() ); - info->authEnv->Put( "password", hsData->url->GetPassword().c_str() ); - - const URL::ParamsMap &urlParams = hsData->url->GetParams(); - URL::ParamsMap::const_iterator it; - for( it = urlParams.begin(); it != urlParams.end(); ++it ) - { - if( it->first.compare( 0, 4, "xrd." ) == 0 || - it->first.compare( 0, 6, "xrdcl." ) == 0 ) - info->authEnv->Put( it->first.c_str(), it->second.c_str() ); - } - - //------------------------------------------------------------------------ - // Initialize some other structs - //------------------------------------------------------------------------ - size_t authBuffLen = strlen( info->authBuffer ); - char *pars = (char *)malloc( authBuffLen ); - memcpy( pars, info->authBuffer, authBuffLen ); - info->authParams = new XrdSecParameters( pars, authBuffLen ); - sInfo.status = XRootDStreamInfo::AuthSent; - delete [] info->authBuffer; - info->authBuffer = 0; - - //------------------------------------------------------------------------ - // Find a protocol that gives us valid credentials - //------------------------------------------------------------------------ - Status st = GetCredentials( credentials, hsData, info ); - if( !st.IsOK() ) - { - CleanUpAuthentication( info ); - return st; - } - protocolName = info->authProtocol->Entity.prot; - } - - //-------------------------------------------------------------------------- - // We've been here already - //-------------------------------------------------------------------------- - else - { - ServerResponse *rsp = (ServerResponse*)hsData->in->GetBuffer(); - protocolName = info->authProtocol->Entity.prot; - - //------------------------------------------------------------------------ - // We're required to send out more authentication data - //------------------------------------------------------------------------ - if( rsp->hdr.status == kXR_authmore ) - { - log->Debug( XRootDTransportMsg, - "[%s] Sending more authentication data for %s", - hsData->streamName.c_str(), protocolName.c_str() ); - - uint32_t len = rsp->hdr.dlen; - char *secTokenData = (char*)malloc( len ); - memcpy( secTokenData, rsp->body.authmore.data, len ); - XrdSecParameters *secToken = new XrdSecParameters( secTokenData, len ); - XrdOucErrInfo ei( "", info->authEnv); - credentials = info->authProtocol->getCredentials( secToken, &ei ); - delete secToken; - - //---------------------------------------------------------------------- - // The protocol handler refuses to give us the data - //---------------------------------------------------------------------- - if( !credentials ) - { - log->Debug( XRootDTransportMsg, - "[%s] Auth protocol handler for %s refuses to give " - "us more credentials %s", - hsData->streamName.c_str(), protocolName.c_str(), - ei.getErrText() ); - CleanUpAuthentication( info ); - return Status( stFatal, errAuthFailed ); - } - } - - //------------------------------------------------------------------------ - // We have succeeded - //------------------------------------------------------------------------ - else if( rsp->hdr.status == kXR_ok ) - { - info->authProtocolName = info->authProtocol->Entity.prot; - - //---------------------------------------------------------------------- - // Do we need protection? - //---------------------------------------------------------------------- - if( info->protRespBody ) - { - int rc = XrdSecGetProtection( info->protection, *info->authProtocol, *info->protRespBody, info->protRespSize ); - if( rc > 0 ) - { - log->Debug( XRootDTransportMsg, - "[%s] XrdSecProtect loaded.", hsData->streamName.c_str() ); - } - else if( rc == 0 ) - { - log->Debug( XRootDTransportMsg, - "[%s] XrdSecProtect: no protection needed.", - hsData->streamName.c_str() ); - } - else - { - log->Debug( XRootDTransportMsg, - "[%s] Failed to load XrdSecProtect: %s", - hsData->streamName.c_str(), strerror( -rc ) ); - CleanUpAuthentication( info ); - - return Status( stError, errAuthFailed, -rc ); - } - } - - if( !info->protection ) - CleanUpAuthentication( info ); - else - pSecUnloadHandler->Register( info->authProtocolName ); - - log->Debug( XRootDTransportMsg, - "[%s] Authenticated with %s.", hsData->streamName.c_str(), - protocolName.c_str() ); - return Status(); - } - //------------------------------------------------------------------------ - // Failure - //------------------------------------------------------------------------ - else if( rsp->hdr.status == kXR_error ) - { - char *errmsg = new char[rsp->hdr.dlen-3]; errmsg[rsp->hdr.dlen-4] = 0; - memcpy( errmsg, rsp->body.error.errmsg, rsp->hdr.dlen-4 ); - log->Error( XRootDTransportMsg, - "[%s] Authentication with %s failed: %s", - hsData->streamName.c_str(), protocolName.c_str(), - errmsg ); - delete [] errmsg; - - info->authProtocol->Delete(); - info->authProtocol = 0; - - //---------------------------------------------------------------------- - // Find another protocol that gives us valid credentials - //---------------------------------------------------------------------- - Status st = GetCredentials( credentials, hsData, info ); - if( !st.IsOK() ) - { - CleanUpAuthentication( info ); - return st; - } - protocolName = info->authProtocol->Entity.prot; - } - //------------------------------------------------------------------------ - // God knows what - //------------------------------------------------------------------------ - else - { - info->authProtocolName = info->authProtocol->Entity.prot; - CleanUpAuthentication( info ); - - log->Error( XRootDTransportMsg, - "[%s] Authentication with %s failed: unexpected answer", - hsData->streamName.c_str(), protocolName.c_str() ); - return Status( stFatal, errAuthFailed ); - } - } - - //-------------------------------------------------------------------------- - // Generate the client request - //-------------------------------------------------------------------------- - Message *msg = new Message( sizeof(ClientAuthRequest)+credentials->size ); - msg->Zero(); - ClientRequest *req = (ClientRequest*)msg->GetBuffer(); - char *reqBuffer = msg->GetBuffer(sizeof(ClientAuthRequest)); - - req->header.requestid = kXR_auth; - req->auth.dlen = credentials->size; - memcpy( req->auth.credtype, protocolName.c_str(), - protocolName.length() > 4 ? 4 : protocolName.length() ); - - memcpy( reqBuffer, credentials->buffer, credentials->size ); - hsData->out = msg; - MarshallRequest( msg ); - delete credentials; - return Status( stOK, suContinue ); - } - - //------------------------------------------------------------------------ - // Get the initial credentials using one of the protocols - //------------------------------------------------------------------------ - Status XRootDTransport::GetCredentials( XrdSecCredentials *&credentials, - HandShakeData *hsData, - XRootDChannelInfo *info ) - { - //-------------------------------------------------------------------------- - // Set up the auth handler - //-------------------------------------------------------------------------- - Log *log = DefaultEnv::GetLog(); - XrdOucErrInfo ei( "", info->authEnv); - XrdSecGetProt_t authHandler = GetAuthHandler(); - if( !authHandler ) - return Status( stFatal, errAuthFailed ); - - //-------------------------------------------------------------------------- - // Retrieve secuid and secgid, if available. These will override the fsuid - // and fsgid of the current thread reading the credentials to prevent - // security holes in case this process is running with elevated permissions. - //-------------------------------------------------------------------------- - char *secuidc = (ei.getEnv()) ? ei.getEnv()->Get("xrdcl.secuid") : 0; - char *secgidc = (ei.getEnv()) ? ei.getEnv()->Get("xrdcl.secgid") : 0; - - int secuid = -1; - int secgid = -1; - - if(secuidc) secuid = atoi(secuidc); - if(secgidc) secgid = atoi(secgidc); - -#ifdef __linux__ - ScopedFsUidSetter uidSetter(secuid, secgid, hsData->streamName); - if(!uidSetter.IsOk()) { - log->Error( XRootDTransportMsg, "[%s] Error while setting (fsuid, fsgid) to (%d, %d)", - hsData->streamName.c_str(), secuid, secgid ); - return Status( stFatal, errAuthFailed ); - } -#else - if(secuid >= 0 || secgid >= 0) { - log->Error( XRootDTransportMsg, "[%s] xrdcl.secuid and xrdcl.secgid only supported on Linux.", - hsData->streamName.c_str() ); - return Status( stFatal, errAuthFailed ); - } -#endif - - //-------------------------------------------------------------------------- - // Loop over the possible protocols to find one that gives us valid - // credentials - //-------------------------------------------------------------------------- - XrdNetAddrInfo &srvAddrInfo = *const_cast(hsData->serverAddr); - while(1) - { - //------------------------------------------------------------------------ - // Get the protocol - //------------------------------------------------------------------------ - info->authProtocol = (*authHandler)( hsData->url->GetHostName().c_str(), - srvAddrInfo, - *info->authParams, - &ei ); - if( !info->authProtocol ) - { - log->Error( XRootDTransportMsg, "[%s] No protocols left to try", - hsData->streamName.c_str() ); - return Status( stFatal, errAuthFailed ); - } - - std::string protocolName = info->authProtocol->Entity.prot; - log->Debug( XRootDTransportMsg, "[%s] Trying to authenticate using %s", - hsData->streamName.c_str(), protocolName.c_str() ); - - //------------------------------------------------------------------------ - // Get the credentials from the current protocol - //------------------------------------------------------------------------ - credentials = info->authProtocol->getCredentials( 0, &ei ); - if( !credentials ) - { - log->Debug( XRootDTransportMsg, - "[%s] Cannot get credentials for protocol %s: %s", - hsData->streamName.c_str(), protocolName.c_str(), - ei.getErrText() ); - info->authProtocol->Delete(); - continue; - } - return Status( stOK, suContinue ); - } - } - - //------------------------------------------------------------------------ - // Clean up the data structures created for the authentication process - //------------------------------------------------------------------------ - Status XRootDTransport::CleanUpAuthentication( XRootDChannelInfo *info ) - { - if( info->authProtocol ) - info->authProtocol->Delete(); - delete info->authParams; - delete info->authEnv; - info->authProtocol = 0; - info->authParams = 0; - info->authEnv = 0; - return Status(); - } - - //------------------------------------------------------------------------ - // Clean up the data structures created for the protection purposes - //------------------------------------------------------------------------ - Status XRootDTransport::CleanUpProtection( XRootDChannelInfo *info ) - { - XrdSysRWLockHelper scope( pSecUnloadHandler->lock ); - if( pSecUnloadHandler->unloaded ) return Status( stError, errInvalidOp ); - - if( info->protection ) - { - info->protection->Delete(); - info->protection = 0; - - CleanUpAuthentication( info ); - } - - if( info->protRespBody ) - { - delete info->protRespBody; - info->protRespBody = 0; - info->protRespSize = 0; - } - - return Status(); - } - - //---------------------------------------------------------------------------- - // Get the authentication function handle - //---------------------------------------------------------------------------- - XrdSecGetProt_t XRootDTransport::GetAuthHandler() - { - Log *log = DefaultEnv::GetLog(); - - if( pAuthHandler ) - return pAuthHandler; - - char errorBuff[1024]; - - pAuthHandler = XrdSecLoadSecFactory( errorBuff, 1024 ); - - if( !pAuthHandler ) - { - log->Error( XRootDTransportMsg, - "Unable to get the security framework: %s", errorBuff ); - return 0; - } - return pAuthHandler; - } - - //---------------------------------------------------------------------------- - // Generate the end session message - //---------------------------------------------------------------------------- - Message *XRootDTransport::GenerateEndSession( HandShakeData *hsData, - XRootDChannelInfo *info ) - { - Log *log = DefaultEnv::GetLog(); - - //-------------------------------------------------------------------------- - // Generate the message - //-------------------------------------------------------------------------- - Message *msg = new Message( sizeof(ClientEndsessRequest) ); - ClientEndsessRequest *endsessReq = (ClientEndsessRequest *)msg->GetBuffer(); - - endsessReq->requestid = kXR_endsess; - memcpy( endsessReq->sessid, info->oldSessionId, 16 ); - std::string sessId = Utils::Char2Hex( endsessReq->sessid, 16 ); - - log->Debug( XRootDTransportMsg, "[%s] Sending out kXR_endsess for session:" - " %s", hsData->streamName.c_str(), sessId.c_str() ); - - MarshallRequest( msg ); - return msg; - } - - //---------------------------------------------------------------------------- - // Process the protocol response - //---------------------------------------------------------------------------- - Status XRootDTransport::ProcessEndSessionResp( HandShakeData *hsData, - XRootDChannelInfo *info ) - { - Log *log = DefaultEnv::GetLog(); - - Status st = UnMarshallBody( hsData->in, kXR_endsess ); - if( !st.IsOK() ) - return st; - - ServerResponse *rsp = (ServerResponse*)hsData->in->GetBuffer(); - - if( rsp->hdr.status == kXR_error ) - { - char *errorMsg = new char[rsp->hdr.dlen-3]; errorMsg[rsp->hdr.dlen-4] = 0; - memcpy( errorMsg, rsp->body.error.errmsg, rsp->hdr.dlen-4 ); - log->Debug( XRootDTransportMsg, "[%s] Got error response to " - "kXR_endsess: %s", hsData->streamName.c_str(), - errorMsg ); - delete [] errorMsg; - // we don't really care if it failed - // return Status( stFatal, errLoginFailed ); - } - - return Status(); - } - - //---------------------------------------------------------------------------- - // Get a string representation of the server flags - //---------------------------------------------------------------------------- - std::string XRootDTransport::ServerFlagsToStr( uint32_t flags ) - { - std::string repr = "type: "; - if( flags & kXR_isManager ) - repr += "manager "; - - else if( flags & kXR_isServer ) - repr += "server "; - - repr += "["; - - if( flags & kXR_attrMeta ) - repr += "meta "; - - else if( flags & kXR_attrProxy ) - repr += "proxy "; - - else if( flags & kXR_attrSuper ) - repr += "super "; - - else - repr += " "; - - repr.erase( repr.length()-1, 1 ); - - repr += "]"; - return repr; - } -} - -namespace -{ - //---------------------------------------------------------------------------- - // Extract file name from a request - //---------------------------------------------------------------------------- - char *GetDataAsString( XrdCl::Message *msg ) - { - ClientRequestHdr *req = (ClientRequestHdr*)msg->GetBuffer(); - char *fn = new char[req->dlen+1]; - memcpy( fn, msg->GetBuffer(24), req->dlen ); - fn[req->dlen] = 0; - return fn; - } -} - -namespace XrdCl -{ - //---------------------------------------------------------------------------- - // Get the description of a message - //---------------------------------------------------------------------------- - void XRootDTransport::SetDescription( Message *msg ) - { - Log *log = DefaultEnv::GetLog(); - if( log->GetLevel() < Log::ErrorMsg ) - return; - - ClientRequestHdr *req = (ClientRequestHdr *)msg->GetBuffer(); - std::ostringstream o; - switch( req->requestid ) - { - //------------------------------------------------------------------------ - // kXR_open - //------------------------------------------------------------------------ - case kXR_open: - { - ClientOpenRequest *sreq = (ClientOpenRequest *)msg->GetBuffer(); - o << "kXR_open ("; - char *fn = GetDataAsString( msg ); - o << "file: " << fn << ", "; - delete [] fn; - o << "mode: 0" << std::setbase(8) << sreq->mode << ", "; - o << std::setbase(10); - o << "flags: "; - if( sreq->options == 0 ) - o << "none"; - else - { - if( sreq->options & kXR_delete ) - o << "kXR_delete "; - if( sreq->options & kXR_force ) - o << "kXR_force "; - if( sreq->options & kXR_mkpath ) - o << "kXR_mkpath "; - if( sreq->options & kXR_new ) - o << "kXR_new "; - if( sreq->options & kXR_nowait ) - o << "kXR_delete "; - if( sreq->options & kXR_open_apnd ) - o << "kXR_open_apnd "; - if( sreq->options & kXR_open_read ) - o << "kXR_open_read "; - if( sreq->options & kXR_open_updt ) - o << "kXR_open_updt "; - if( sreq->options & kXR_posc ) - o << "kXR_posc "; - if( sreq->options & kXR_refresh ) - o << "kXR_refresh "; - if( sreq->options & kXR_replica ) - o << "kXR_replica "; - if( sreq->options & kXR_seqio ) - o << "kXR_seqio "; - if( sreq->options & kXR_async ) - o << "kXR_async "; - if( sreq->options & kXR_retstat ) - o << "kXR_retstat "; - } - o << ")"; - break; - } - - //------------------------------------------------------------------------ - // kXR_close - //------------------------------------------------------------------------ - case kXR_close: - { - ClientCloseRequest *sreq = (ClientCloseRequest *)msg->GetBuffer(); - o << "kXR_close ("; - o << "handle: " << FileHandleToStr( sreq->fhandle ); - o << ")"; - break; - } - - //------------------------------------------------------------------------ - // kXR_stat - //------------------------------------------------------------------------ - case kXR_stat: - { - ClientStatRequest *sreq = (ClientStatRequest *)msg->GetBuffer(); - o << "kXR_stat ("; - if( sreq->dlen ) - { - char *fn = GetDataAsString( msg );; - o << "path: " << fn << ", "; - delete [] fn; - } - else - { - o << "handle: " << FileHandleToStr( sreq->fhandle ); - o << ", "; - } - o << "flags: "; - if( sreq->options == 0 ) - o << "none"; - else - { - if( sreq->options & kXR_vfs ) - o << "kXR_vfs"; - } - o << ")"; - break; - } - - //------------------------------------------------------------------------ - // kXR_read - //------------------------------------------------------------------------ - case kXR_read: - { - ClientReadRequest *sreq = (ClientReadRequest *)msg->GetBuffer(); - o << "kXR_read ("; - o << "handle: " << FileHandleToStr( sreq->fhandle ); - o << std::setbase(10); - o << ", "; - o << "offset: " << sreq->offset << ", "; - o << "size: " << sreq->rlen << ")"; - break; - } - - //------------------------------------------------------------------------ - // kXR_write - //------------------------------------------------------------------------ - case kXR_write: - { - ClientWriteRequest *sreq = (ClientWriteRequest *)msg->GetBuffer(); - o << "kXR_write ("; - o << "handle: " << FileHandleToStr( sreq->fhandle ); - o << std::setbase(10); - o << ", "; - o << "offset: " << sreq->offset << ", "; - o << "size: " << sreq->dlen << ")"; - break; - } - - //------------------------------------------------------------------------ - // kXR_sync - //------------------------------------------------------------------------ - case kXR_sync: - { - ClientSyncRequest *sreq = (ClientSyncRequest *)msg->GetBuffer(); - o << "kXR_sync ("; - o << "handle: " << FileHandleToStr( sreq->fhandle ); - o << ")"; - break; - } - - //------------------------------------------------------------------------ - // kXR_truncate - //------------------------------------------------------------------------ - case kXR_truncate: - { - ClientTruncateRequest *sreq = (ClientTruncateRequest *)msg->GetBuffer(); - o << "kXR_truncate ("; - if( !sreq->dlen ) - o << "handle: " << FileHandleToStr( sreq->fhandle ); - else - { - char *fn = GetDataAsString( msg );; - o << "file: " << fn; - delete [] fn; - } - o << std::setbase(10); - o << ", "; - o << "offset: " << sreq->offset; - o << ")"; - break; - } - - //------------------------------------------------------------------------ - // kXR_readv - //------------------------------------------------------------------------ - case kXR_readv: - { - unsigned char *fhandle = 0; - o << "kXR_readv ("; - - readahead_list *dataChunk = (readahead_list*)msg->GetBuffer( 24 ); - uint64_t size = 0; - uint32_t numChunks = 0; - for( size_t i = 0; i < req->dlen/sizeof(readahead_list); ++i ) - { - fhandle = dataChunk[i].fhandle; - size += dataChunk[i].rlen; - ++numChunks; - } - o << "handle: "; - if( fhandle ) - o << FileHandleToStr( fhandle ); - else - o << "unknown"; - o << ", "; - o << std::setbase(10); - o << "chunks: " << numChunks << ", "; - o << "total size: " << size << ")"; - break; - } - - //------------------------------------------------------------------------ - // kXR_writev - //------------------------------------------------------------------------ - case kXR_writev: - { - unsigned char *fhandle = 0; - o << "kXR_writev ("; - - XrdProto::write_list *wrtList = - reinterpret_cast( msg->GetBuffer( 24 ) ); - uint64_t size = 0; - uint32_t numChunks = 0; - for( size_t i = 0; i < req->dlen/sizeof(XrdProto::write_list); ++i ) - { - fhandle = wrtList[i].fhandle; - size += wrtList[i].wlen; - ++numChunks; - } - o << "handle: "; - if( fhandle ) - o << FileHandleToStr( fhandle ); - else - o << "unknown"; - o << ", "; - o << std::setbase(10); - o << "chunks: " << numChunks << ", "; - o << "total size: " << size << ")"; - break; - } - - //------------------------------------------------------------------------ - // kXR_locate - //------------------------------------------------------------------------ - case kXR_locate: - { - ClientLocateRequest *sreq = (ClientLocateRequest *)msg->GetBuffer(); - char *fn = GetDataAsString( msg );; - o << "kXR_locate ("; - o << "path: " << fn << ", "; - delete [] fn; - o << "flags: "; - if( sreq->options == 0 ) - o << "none"; - else - { - if( sreq->options & kXR_refresh ) - o << "kXR_refresh "; - if( sreq->options & kXR_prefname ) - o << "kXR_prefname "; - if( sreq->options & kXR_nowait ) - o << "kXR_nowait "; - if( sreq->options & kXR_force ) - o << "kXR_force "; - if( sreq->options & kXR_compress ) - o << "kXR_compress "; - } - o << ")"; - break; - } - - //------------------------------------------------------------------------ - // kXR_mv - //------------------------------------------------------------------------ - case kXR_mv: - { - ClientMvRequest *sreq = (ClientMvRequest *)msg->GetBuffer(); - o << "kXR_mv ("; - o << "source: "; - o.write( msg->GetBuffer( sizeof( ClientMvRequest ) ), sreq->arg1len ); - o << ", "; - o << "destination: "; - o.write( msg->GetBuffer( sizeof( ClientMvRequest ) + sreq->arg1len + 1 ), sreq->dlen - sreq->arg1len - 1 ); - o << ")"; - break; - } - - //------------------------------------------------------------------------ - // kXR_query - //------------------------------------------------------------------------ - case kXR_query: - { - ClientQueryRequest *sreq = (ClientQueryRequest *)msg->GetBuffer(); - o << "kXR_query ("; - o << "code: "; - switch( sreq->infotype ) - { - case kXR_Qconfig: o << "kXR_Qconfig"; break; - case kXR_Qckscan: o << "kXR_Qckscan"; break; - case kXR_Qcksum: o << "kXR_Qcksum"; break; - case kXR_Qopaque: o << "kXR_Qopaque"; break; - case kXR_Qopaquf: o << "kXR_Qopaquf"; break; - case kXR_Qopaqug: o << "kXR_Qopaqug"; break; - case kXR_QPrep: o << "kXR_QPrep"; break; - case kXR_Qspace: o << "kXR_Qspace"; break; - case kXR_QStats: o << "kXR_QStats"; break; - case kXR_Qvisa: o << "kXR_Qvisa"; break; - case kXR_Qxattr: o << "kXR_Qxattr"; break; - default: o << sreq->infotype; break; - } - o << ", "; - - if( sreq->infotype == kXR_Qopaqug || sreq->infotype == kXR_Qvisa ) - { - o << "handle: " << FileHandleToStr( sreq->fhandle ); - o << ", "; - } - - o << "arg length: " << sreq->dlen << ")"; - break; - } - - //------------------------------------------------------------------------ - // kXR_rm - //------------------------------------------------------------------------ - case kXR_rm: - { - o << "kXR_rm ("; - char *fn = GetDataAsString( msg );; - o << "path: " << fn << ")"; - delete [] fn; - break; - } - - //------------------------------------------------------------------------ - // kXR_mkdir - //------------------------------------------------------------------------ - case kXR_mkdir: - { - ClientMkdirRequest *sreq = (ClientMkdirRequest *)msg->GetBuffer(); - o << "kXR_mkdir ("; - char *fn = GetDataAsString( msg );; - o << "path: " << fn << ", "; - delete [] fn; - o << "mode: 0" << std::setbase(8) << sreq->mode << ", "; - o << std::setbase(10); - o << "flags: "; - if( sreq->options[0] == 0 ) - o << "none"; - else - { - if( sreq->options[0] & kXR_mkdirpath ) - o << "kXR_mkdirpath"; - } - o << ")"; - break; - } - - //------------------------------------------------------------------------ - // kXR_rmdir - //------------------------------------------------------------------------ - case kXR_rmdir: - { - o << "kXR_rmdir ("; - char *fn = GetDataAsString( msg );; - o << "path: " << fn << ")"; - delete [] fn; - break; - } - - //------------------------------------------------------------------------ - // kXR_chmod - //------------------------------------------------------------------------ - case kXR_chmod: - { - ClientChmodRequest *sreq = (ClientChmodRequest *)msg->GetBuffer(); - o << "kXR_chmod ("; - char *fn = GetDataAsString( msg );; - o << "path: " << fn << ", "; - delete [] fn; - o << "mode: 0" << std::setbase(8) << sreq->mode << ")"; - break; - } - - //------------------------------------------------------------------------ - // kXR_ping - //------------------------------------------------------------------------ - case kXR_ping: - { - o << "kXR_ping ()"; - break; - } - - //------------------------------------------------------------------------ - // kXR_protocol - //------------------------------------------------------------------------ - case kXR_protocol: - { - ClientProtocolRequest *sreq = (ClientProtocolRequest *)msg->GetBuffer(); - o << "kXR_protocol ("; - o << "clientpv: 0x" << std::setbase(16) << sreq->clientpv << ")"; - break; - } - - //------------------------------------------------------------------------ - // kXR_dirlist - //------------------------------------------------------------------------ - case kXR_dirlist: - { - o << "kXR_dirlist ("; - char *fn = GetDataAsString( msg );; - o << "path: " << fn << ")"; - delete [] fn; - break; - } - - //------------------------------------------------------------------------ - // kXR_set - //------------------------------------------------------------------------ - case kXR_set: - { - o << "kXR_set ("; - char *fn = GetDataAsString( msg );; - o << "data: " << fn << ")"; - delete [] fn; - break; - } - - //------------------------------------------------------------------------ - // kXR_prepare - //------------------------------------------------------------------------ - case kXR_prepare: - { - ClientPrepareRequest *sreq = (ClientPrepareRequest *)msg->GetBuffer(); - o << "kXR_prepare ("; - o << "flags: "; - - if( sreq->options == 0 ) - o << "none"; - else - { - if( sreq->options & kXR_stage ) - o << "kXR_stage "; - if( sreq->options & kXR_wmode ) - o << "kXR_wmode "; - if( sreq->options & kXR_coloc ) - o << "kXR_coloc "; - if( sreq->options & kXR_fresh ) - o << "kXR_fresh "; - } - - o << ", priority: " << (int) sreq->prty << ", "; - - char *fn = GetDataAsString( msg ); - char *cursor; - for( cursor = fn; *cursor; ++cursor ) - if( *cursor == '\n' ) *cursor = ' '; - - o << "paths: " << fn << ")"; - delete [] fn; - break; - } - - //------------------------------------------------------------------------ - // Default - //------------------------------------------------------------------------ - default: - { - o << "kXR_unknown (length: " << req->dlen << ")"; - break; - } - }; - msg->SetDescription( o.str() ); - } - - //---------------------------------------------------------------------------- - // Get a string representation of file handle - //---------------------------------------------------------------------------- - std::string XRootDTransport::FileHandleToStr( const unsigned char handle[4] ) - { - std::ostringstream o; - o << "0x"; - for( uint8_t i = 0; i < 4; ++i ) - { - o << std::setbase(16) << std::setfill('0') << std::setw(2); - o << (int)handle[i]; - } - return o.str(); - } -} diff --git a/src/XrdCl/XrdClXRootDTransport.hh b/src/XrdCl/XrdClXRootDTransport.hh deleted file mode 100644 index fc160ffb2d5..00000000000 --- a/src/XrdCl/XrdClXRootDTransport.hh +++ /dev/null @@ -1,354 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2014 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#ifndef __XRD_CL_XROOTD_TRANSPORT_HH__ -#define __XRD_CL_XROOTD_TRANSPORT_HH__ - -#include "XrdCl/XrdClPostMaster.hh" -#include "XProtocol/XProtocol.hh" -#include "XrdSec/XrdSecInterface.hh" -#include "XrdOuc/XrdOucEnv.hh" - -class XrdSysPlugin; -class XrdSecProtect; - -namespace XrdCl -{ - struct XRootDChannelInfo; - struct PluginUnloadHandler; - - //---------------------------------------------------------------------------- - //! XRootD related protocol queries - //---------------------------------------------------------------------------- - struct XRootDQuery - { - static const uint16_t SIDManager = 1001; //!< returns the SIDManager object - static const uint16_t ServerFlags = 1002; //!< returns server flags - static const uint16_t ProtocolVersion = 1003; //!< returns the protocol version - }; - - //---------------------------------------------------------------------------- - //! XRootD transport handler - //---------------------------------------------------------------------------- - class XRootDTransport: public TransportHandler - { - public: - //------------------------------------------------------------------------ - //! Constructor - //------------------------------------------------------------------------ - XRootDTransport(); - - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - ~XRootDTransport(); - - //------------------------------------------------------------------------ - //! Read a message header from the socket, the socket is non-blocking, - //! so if there is not enough data the function should return errRetry - //! in which case it will be called again when more data arrives, with - //! the data previously read stored in the message buffer - //! - //! @param message the message buffer - //! @param socket the socket - //! @return stOK & suDone if the whole message has been processed - //! stOK & suRetry if more data is needed - //! stError on failure - //------------------------------------------------------------------------ - virtual Status GetHeader( Message *message, int socket ); - - //------------------------------------------------------------------------ - //! Read the message body from the socket, the socket is non-blocking, - //! the method may be called multiple times - see GetHeader for details - //! - //! @param message the message buffer containing the header - //! @param socket the socket - //! @return stOK & suDone if the whole message has been processed - //! stOK & suRetry if more data is needed - //! stError on failure - //------------------------------------------------------------------------ - virtual Status GetBody( Message *message, int socket ); - - //------------------------------------------------------------------------ - //! Initialize channel - //------------------------------------------------------------------------ - virtual void InitializeChannel( AnyObject &channelData ); - - //------------------------------------------------------------------------ - //! Finalize channel - //------------------------------------------------------------------------ - virtual void FinalizeChannel( AnyObject &channelData ); - - //------------------------------------------------------------------------ - //! HandShake - //------------------------------------------------------------------------ - virtual Status HandShake( HandShakeData *handShakeData, - AnyObject &channelData ); - - //------------------------------------------------------------------------ - //! Check if the stream should be disconnected - //------------------------------------------------------------------------ - virtual bool IsStreamTTLElapsed( time_t time, - uint16_t streamId, - AnyObject &channelData ); - - //------------------------------------------------------------------------ - //! Check the stream is broken - ie. TCP connection got broken and - //! went undetected by the TCP stack - //------------------------------------------------------------------------ - virtual Status IsStreamBroken( time_t inactiveTime, - uint16_t streamId, - AnyObject &channelData ); - - //------------------------------------------------------------------------ - //! Return the ID for the up stream this message should be sent by - //! and the down stream which the answer should be expected at. - //! Modify the message itself if necessary. - //! If hint is non-zero then the message should be modified such that - //! the answer will be returned via the hinted stream. - //------------------------------------------------------------------------ - virtual PathID Multiplex( Message *msg, - AnyObject &channelData, - PathID *hint = 0 ); - - //------------------------------------------------------------------------ - //! Return the ID for the up substream this message should be sent by - //! and the down substream which the answer should be expected at. - //! Modify the message itself if necessary. - //! If hint is non-zero then the message should be modified such that - //! the answer will be returned via the hinted stream. - //------------------------------------------------------------------------ - virtual PathID MultiplexSubStream( Message *msg, - uint16_t streamId, - AnyObject &channelData, - PathID *hint = 0 ); - - //------------------------------------------------------------------------ - //! Return a number of streams that should be created - //------------------------------------------------------------------------ - virtual uint16_t StreamNumber( AnyObject &channelData ); - - //------------------------------------------------------------------------ - //! Return a number of substreams per stream that should be created - //------------------------------------------------------------------------ - virtual uint16_t SubStreamNumber( AnyObject &channelData ); - - //------------------------------------------------------------------------ - //! Return the information whether a control connection needs to be - //! valid before establishing other connections - //------------------------------------------------------------------------ - virtual bool NeedControlConnection() - { - return true; - } - - //------------------------------------------------------------------------ - //! Marshal the outgoing message - //------------------------------------------------------------------------ - static Status MarshallRequest( Message *msg ); - - //------------------------------------------------------------------------ - //! Unmarshall the request - sometimes the requests need to be rewritten, - //! so we need to unmarshall them - //------------------------------------------------------------------------ - static Status UnMarshallRequest( Message *msg ); - - //------------------------------------------------------------------------ - //! Unmarshall the body of the incoming message - //------------------------------------------------------------------------ - static Status UnMarshallBody( Message *msg, uint16_t reqType ); - - //------------------------------------------------------------------------ - //! Unmarshall the header incoming message - //------------------------------------------------------------------------ - static void UnMarshallHeader( Message *msg ); - - //------------------------------------------------------------------------ - //! Log server error response - //------------------------------------------------------------------------ - static void LogErrorResponse( const Message &msg ); - - //------------------------------------------------------------------------ - //! The stream has been disconnected, do the cleanups - //------------------------------------------------------------------------ - virtual void Disconnect( AnyObject &channelData, - uint16_t streamId, - uint16_t subStreamId ); - - //------------------------------------------------------------------------ - //! Query the channel - //------------------------------------------------------------------------ - virtual Status Query( uint16_t query, - AnyObject &result, - AnyObject &channelData ); - - //------------------------------------------------------------------------ - //! Get the description of a message - //------------------------------------------------------------------------ - static void SetDescription( Message *msg ); - - //------------------------------------------------------------------------ - //! Check if the message invokes a stream action - //------------------------------------------------------------------------ - virtual uint32_t MessageReceived( Message *msg, - uint16_t streamId, - uint16_t subStream, - AnyObject &channelData ); - - //------------------------------------------------------------------------ - //! Notify the transport about a message having been sent - //------------------------------------------------------------------------ - virtual void MessageSent( Message *msg, - uint16_t streamId, - uint16_t subStream, - uint32_t bytesSent, - AnyObject &channelData ); - - //------------------------------------------------------------------------ - //! Get signature for given message - //------------------------------------------------------------------------ - virtual Status GetSignature( Message *toSign, Message *&sign, - AnyObject &channelData ); - - private: - - //------------------------------------------------------------------------ - // Hand shake the main stream - //------------------------------------------------------------------------ - Status HandShakeMain( HandShakeData *handShakeData, - AnyObject &channelData ); - - //------------------------------------------------------------------------ - // Hand shake a parallel stream - //------------------------------------------------------------------------ - Status HandShakeParallel( HandShakeData *handShakeData, - AnyObject &channelData ); - - //------------------------------------------------------------------------ - // Generate the message to be sent as an initial handshake - //------------------------------------------------------------------------ - Message *GenerateInitialHS( HandShakeData *hsData, - XRootDChannelInfo *info ); - - //------------------------------------------------------------------------ - // Generate the message to be sent as an initial handshake - // (handshake + kXR_protocol) - //------------------------------------------------------------------------ - Message *GenerateInitialHSProtocol( HandShakeData *hsData, - XRootDChannelInfo *info ); - - //------------------------------------------------------------------------ - // Process the server initial handshake response - //------------------------------------------------------------------------ - Status ProcessServerHS( HandShakeData *hsData, - XRootDChannelInfo *info ); - - //----------------------------------------------------------------------- - // Process the protocol response - //------------------------------------------------------------------------ - Status ProcessProtocolResp( HandShakeData *hsData, - XRootDChannelInfo *info ); - - //------------------------------------------------------------------------ - // Generate the bind message - //------------------------------------------------------------------------ - Message *GenerateBind( HandShakeData *hsData, - XRootDChannelInfo *info ); - - //------------------------------------------------------------------------ - // Generate the bind message - //------------------------------------------------------------------------ - Status ProcessBindResp( HandShakeData *hsData, - XRootDChannelInfo *info ); - - //------------------------------------------------------------------------ - // Generate the login message - //------------------------------------------------------------------------ - Message *GenerateLogIn( HandShakeData *hsData, - XRootDChannelInfo *info ); - - //------------------------------------------------------------------------ - // Process the login response - //------------------------------------------------------------------------ - Status ProcessLogInResp( HandShakeData *hsData, - XRootDChannelInfo *info ); - - //------------------------------------------------------------------------ - // Do the authentication - //------------------------------------------------------------------------ - Status DoAuthentication( HandShakeData *hsData, - XRootDChannelInfo *info ); - - //------------------------------------------------------------------------ - // Get the initial credentials using one of the protocols - //------------------------------------------------------------------------ - Status GetCredentials( XrdSecCredentials *&credentials, - HandShakeData *hsData, - XRootDChannelInfo *info ); - - //------------------------------------------------------------------------ - // Clean up the data structures created for the authentication process - //------------------------------------------------------------------------ - Status CleanUpAuthentication( XRootDChannelInfo *info ); - - //------------------------------------------------------------------------ - // Clean up the data structures created for the protection purposes - //------------------------------------------------------------------------ - Status CleanUpProtection( XRootDChannelInfo *info ); - - //------------------------------------------------------------------------ - // Get the authentication function handle - //------------------------------------------------------------------------ - XrdSecGetProt_t GetAuthHandler(); - - //------------------------------------------------------------------------ - // Generate the end session message - //------------------------------------------------------------------------ - Message *GenerateEndSession( HandShakeData *hsData, - XRootDChannelInfo *info ); - - //------------------------------------------------------------------------ - // Process the end session response - //------------------------------------------------------------------------ - Status ProcessEndSessionResp( HandShakeData *hsData, - XRootDChannelInfo *info ); - - //------------------------------------------------------------------------ - // Get a string representation of the server flags - //------------------------------------------------------------------------ - static std::string ServerFlagsToStr( uint32_t flags ); - - //------------------------------------------------------------------------ - // Get a string representation of file handle - //------------------------------------------------------------------------ - static std::string FileHandleToStr( const unsigned char handle[4] ); - - XrdSecGetProt_t pAuthHandler; - - friend struct PluginUnloadHandler; - PluginUnloadHandler *pSecUnloadHandler; - }; -} - -#endif // __XRD_CL_XROOTD_TRANSPORT_HANDLER_HH__ diff --git a/src/XrdCl/XrdClZipArchiveReader.cc b/src/XrdCl/XrdClZipArchiveReader.cc deleted file mode 100644 index e18958d5827..00000000000 --- a/src/XrdCl/XrdClZipArchiveReader.cc +++ /dev/null @@ -1,716 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2014 by European Organization for Nuclear Research (CERN) -// Author: Michal Simon -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#include "XrdCl/XrdClZipArchiveReader.hh" -#include "XrdCl/XrdClFile.hh" -#include "XrdCl/XrdClMessageUtils.hh" -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdCl/XrdClLog.hh" -#include "XrdCl/XrdClConstants.hh" - -#include "XrdSys/XrdSysPthread.hh" - -#include -#include - -namespace XrdCl -{ - -struct EOCD -{ - EOCD( char *buffer ) - { - pNbDisk = *reinterpret_cast( buffer + 4 ); - pDisk = *reinterpret_cast( buffer + 6 ); - pNbCdRecD = *reinterpret_cast( buffer + 8 ); - pNbCdRec = *reinterpret_cast( buffer + 10 ); - pCdSize = *reinterpret_cast( buffer + 12 ); - pCdOffset = *reinterpret_cast( buffer + 16 ); - pCommSize = *reinterpret_cast( buffer + 20 ); - pComment = std::string( buffer + 22, pCommSize ); - } - - uint16_t pNbDisk; - uint16_t pDisk; - uint16_t pNbCdRecD; - uint16_t pNbCdRec; - uint32_t pCdSize; - uint32_t pCdOffset; - uint16_t pCommSize; - std::string pComment; - - static const uint16_t kEocdBaseSize = 22; - static const uint32_t kEocdSign = 0x06054b50; - static const uint16_t kMaxCommentSize = 65535; -}; - - -struct CDFH -{ - CDFH( char *buffer ) - { - pZipVersion = *reinterpret_cast( buffer + 4 ); - pMinZipVersion = *reinterpret_cast( buffer + 6 ); - pCompressionMethod = *reinterpret_cast( buffer + 10 ); - pCrc32 = *reinterpret_cast( buffer + 16 ); - pCompressedSize = *reinterpret_cast( buffer + 20 ); - pUncompressedSize = *reinterpret_cast( buffer + 24 ); - pDiskNb = *reinterpret_cast( buffer + 34 ); - pOffset = *reinterpret_cast( buffer + 42 ); - - uint16_t filenameLength = *reinterpret_cast( buffer + 28 ); - uint16_t extraLength = *reinterpret_cast( buffer + 30 ); - uint16_t commentLength = *reinterpret_cast( buffer + 32 ); - - pFilename = std::string( buffer + 46, filenameLength ); - - pCdfhSize = kCdfhBaseSize + filenameLength +extraLength + commentLength; - } - - uint16_t pZipVersion; - uint16_t pMinZipVersion; - uint16_t pCompressionMethod; - uint32_t pCrc32; - uint32_t pCompressedSize; - uint32_t pUncompressedSize; - uint16_t pDiskNb; - uint32_t pOffset; - std::string pFilename; - uint16_t pCdfhSize; - - static const uint16_t kCdfhBaseSize = 46; - static const uint32_t kCdfhSign = 0x02014b50; -}; - - -class ZipArchiveReaderImpl -{ - public: - - ZipArchiveReaderImpl() : pArchiveSize( 0 ), pBuffer( 0 ), pEocd( 0 ), pRefCount( 1 ), pOpen( false ) { } - - ZipArchiveReaderImpl* Self() - { - XrdSysMutexHelper scopedLock( pMutex ); - ++pRefCount; - return this; - } - - void Delete() - { - XrdSysMutexHelper scopedLock( pMutex ); - --pRefCount; - if( !pRefCount ) - { - scopedLock.UnLock(); - delete this; - } - } - - XRootDStatus Open( const std::string &url, ResponseHandler *userHandler, uint16_t timeout = 0 ); - - XRootDStatus StatArchive( ResponseHandler *userHandler ); - - XRootDStatus ReadArchive( ResponseHandler *userHandler ); - - XRootDStatus ReadEocd( ResponseHandler *userHandler ); - - XRootDStatus ReadCdfh( uint64_t bytesRead, ResponseHandler *userHandler ); - - XRootDStatus Read( const std::string &filename, uint64_t relativeOffset, uint32_t size, void *buffer, ResponseHandler *userHandler, uint16_t timeout = 0 ); - - XRootDStatus Close( ResponseHandler *handler, uint16_t timeout ) - { - XRootDStatus st = pArchive.Close( handler, timeout ); - if( st.IsOK() ) - { - delete[] pBuffer; - pBuffer = 0; - ClearRecords(); - } - return st; - } - - bool SetProperty( const std::string &name, const std::string &value ) - { - return pArchive.SetProperty( name, value ); - } - - XRootDStatus GetSize( const std::string & filename, uint32_t &size ) const - { - std::map::const_iterator it = pFileToCdfh.find( filename ); - if( it == pFileToCdfh.end() ) return XRootDStatus( stError, errNotFound ); - CDFH *cdfh = pCdRecords[it->second]; - size = cdfh->pCompressionMethod ? cdfh->pCompressedSize : cdfh->pUncompressedSize; - return XRootDStatus(); - } - - bool IsOpen() const - { - return pOpen; - } - - void SetArchiveSize( uint64_t size ) - { - pArchiveSize = size; - } - - char* LookForEocd( uint64_t size ) - { - for( ssize_t offset = size - EOCD::kEocdBaseSize; offset >= 0; --offset ) - { - uint32_t *signature = reinterpret_cast( pBuffer + offset ); - if( *signature == EOCD::kEocdSign ) return pBuffer + offset; - } - return 0; - } - - XRootDStatus ParseCdRecords( char *buffer, uint16_t nbCdRecords, uint32_t bufferSize ) - { - uint32_t offset = 0; - pCdRecords.reserve( nbCdRecords ); - - for( size_t i = 0; i < nbCdRecords; ++i ) - { - if( bufferSize < CDFH::kCdfhBaseSize ) break; - // check the signature - uint32_t *signature = (uint32_t*)( buffer + offset ); - if( *signature != CDFH::kCdfhSign ) return XRootDStatus( stError, errErrorResponse, errDataError, "Central-directory-file-header signature not found." ); - // parse the record - CDFH *cdfh = new CDFH( buffer + offset ); - offset += cdfh->pCdfhSize; - bufferSize -= cdfh->pCdfhSize; - pCdRecords.push_back( cdfh ); - pFileToCdfh[cdfh->pFilename] = i; - } - - pOpen = true; - return XRootDStatus(); - } - - XRootDStatus HandleWholeArchive() - { - // create the End-of-Central-Directory record - char *eocdBlock = LookForEocd( pArchiveSize ); - if( !eocdBlock ) return XRootDStatus( stError, errErrorResponse, errDataError, "End-of-central-directory signature not found." ); - pEocd = new EOCD( eocdBlock ); - - // parse Central-Directory-File-Header records - XRootDStatus st = ParseCdRecords( pBuffer + pEocd->pCdOffset, pEocd->pNbCdRec, pEocd->pCdSize ); - - return st; - } - - XRootDStatus HandleCdfh( uint16_t nbCdRecords, uint32_t bufferSize ) - { - // parse Central-Directory-File-Header records - XRootDStatus st = ParseCdRecords( pBuffer, nbCdRecords, bufferSize ); - // successful or not we don't need it anymore - delete[] pBuffer; - pBuffer = 0; - return st; - } - - private: - - void ClearRecords() - { - delete pEocd; - pEocd = 0; - - for( std::vector::iterator it = pCdRecords.begin(); it != pCdRecords.end(); ++it ) - delete *it; - pCdRecords.clear(); - pFileToCdfh.clear(); - } - - ~ZipArchiveReaderImpl() - { - delete[] pBuffer; - ClearRecords(); - if( pArchive.IsOpen() ) - { - XRootDStatus st = pArchive.Close(); - if( !st.IsOK() ) - { - Log *log = DefaultEnv::GetLog(); - log->Warning( FileMsg, "ZipArchiveReader failed to close file upon destruction: %s.", st.ToString().c_str() ); - } - } - } - - File pArchive; - std::string pFilename; - uint64_t pArchiveSize; - char* pBuffer; - EOCD *pEocd; - std::vector pCdRecords; - std::map pFileToCdfh; - mutable XrdSysMutex pMutex; - size_t pRefCount; - bool pOpen; -}; - -template -struct ZipHandlerException -{ - ZipHandlerException( XRootDStatus *status, RESP *response ) : status( status ), response( response ) { } - - XRootDStatus *status; - RESP *response; -}; - - -class ZipHandlerCommon : public ResponseHandler -{ - public: - - ZipHandlerCommon( ZipArchiveReaderImpl *impl, ResponseHandler *userHandler ) : pImpl( impl->Self() ), pUserHandler( userHandler ) { } - - virtual ~ZipHandlerCommon() - { - pImpl->Delete(); - } - - template - void DeleteArgs( XRootDStatus *status, RESP *response ) - { - delete status; - delete response; - } - - template - AnyObject* PkgResp( RESP *resp ) - { - AnyObject *response = new AnyObject(); - response->Set( resp ); - return response; - } - - protected: - - ZipArchiveReaderImpl *pImpl; - ResponseHandler *pUserHandler; -}; - - -template -class ZipHandlerBase : public ZipHandlerCommon -{ - public: - - ZipHandlerBase( ZipArchiveReaderImpl *impl, ResponseHandler *userHandler ) : ZipHandlerCommon( impl, userHandler ) { } - - virtual void HandleResponseImpl( XRootDStatus *status, RESP *response ) = 0; - - virtual void HandleResponse( XRootDStatus *status, AnyObject *response ) - { - try - { - if( !status->IsOK() ) throw ZipHandlerException( status, response ); - - if( !response ) - { - *status = XRootDStatus( stError, errInternal ); - throw ZipHandlerException( status, response ); - } - - RESP *resp = 0; - response->Get( resp ); - if( !resp ) - { - *status = XRootDStatus( stError, errInternal ); - throw ZipHandlerException( status, response ); - } - response->Set( (int *)0 ); - delete response; - - HandleResponseImpl( status, resp ); - } - catch( ZipHandlerException& ex) - { - if( pUserHandler ) pUserHandler->HandleResponse( ex.status, ex.response ); - else DeleteArgs( ex.status, ex.response ); - } - catch( ZipHandlerException& ex ) - { - if( pUserHandler ) pUserHandler->HandleResponse( ex.status, PkgResp( ex.response ) ); - else DeleteArgs( ex.status, ex.response ); - } - - delete this; - } -}; - - -template<> -class ZipHandlerBase : public ZipHandlerCommon -{ - public: - - ZipHandlerBase( ZipArchiveReaderImpl *impl, ResponseHandler *userHandler ) : ZipHandlerCommon( impl, userHandler ) { } - - virtual void HandleResponseImpl( XRootDStatus *status, AnyObject *response ) = 0; - - virtual void HandleResponse( XRootDStatus *status, AnyObject *response ) - { - try - { - if( !status->IsOK() ) throw ZipHandlerException( status, response ); - HandleResponseImpl( status, response ); - } - catch( ZipHandlerException& ex) - { - if( pUserHandler ) pUserHandler->HandleResponse( ex.status, ex.response ); - else DeleteArgs( ex.status, ex.response ); - } - - delete this; - } -}; - - -class ZipOpenHandler : public ZipHandlerBase -{ - public: - - ZipOpenHandler( ZipArchiveReaderImpl *impl, ResponseHandler *userHandler ): ZipHandlerBase( impl, userHandler ) { } - - virtual void HandleResponseImpl( XRootDStatus *status, AnyObject *response ) - { - XRootDStatus st = pImpl->StatArchive( pUserHandler ); - if( !st.IsOK() ) - { - *status = st; - throw ZipHandlerException( status, response ); - } - - DeleteArgs( status, response ); - } -}; - - -class StatArchiveHandler : public ZipHandlerBase -{ - public: - - StatArchiveHandler( ZipArchiveReaderImpl *impl, ResponseHandler *userHandler ): ZipHandlerBase( impl, userHandler ) { } - - virtual void HandleResponseImpl( XRootDStatus *status, StatInfo *response ) - { - uint64_t size = response->GetSize(); - pImpl->SetArchiveSize( size ); - - // if the size of the file is smaller than the maximum comment size + - // EOCD size simply download the whole file, otherwise download the EOCD - XRootDStatus st = ( size <= EOCD::kMaxCommentSize + EOCD::kEocdBaseSize ) ? pImpl->ReadArchive( pUserHandler ) : pImpl->ReadEocd( pUserHandler ); - if( !st.IsOK() ) - { - *status = st; - throw ZipHandlerException( status, response ); - } - - DeleteArgs( status, response ); - } -}; - - -class ReadArchiveHandler : public ZipHandlerBase -{ - public: - - ReadArchiveHandler( ZipArchiveReaderImpl *impl, ResponseHandler *userHandler ) : ZipHandlerBase( impl, userHandler ) { } - - virtual void HandleResponseImpl( XRootDStatus *status, ChunkInfo *response ) - { - pImpl->SetArchiveSize( response->length ); - // we have the whole archive locally in the buffer - XRootDStatus st = pImpl->HandleWholeArchive(); - if( pUserHandler ) - { - *status = st; - pUserHandler->HandleResponse( status, PkgResp( response ) ); - } - else - DeleteArgs( status, response ); - } -}; - - -class ReadCdfhHandler : public ZipHandlerBase -{ - public: - - ReadCdfhHandler( ZipArchiveReaderImpl *impl, ResponseHandler *userHandler, uint16_t nbCdRec ) : ZipHandlerBase( impl, userHandler ), pNbCdRec( nbCdRec ) { } - - virtual void HandleResponseImpl( XRootDStatus *status, ChunkInfo *response ) - { - XRootDStatus st = pImpl->HandleCdfh( pNbCdRec, response->length ); - if( pUserHandler ) - { - *status = st; - pUserHandler->HandleResponse( status, PkgResp( response ) ); - } - else - DeleteArgs( status, response ); - } - - private: - - uint16_t pNbCdRec; -}; - - -class ReadEocdHandler : public ZipHandlerBase -{ - public: - - ReadEocdHandler( ZipArchiveReaderImpl *impl, ResponseHandler *userHandler ) : ZipHandlerBase( impl, userHandler ) { } - - virtual void HandleResponseImpl( XRootDStatus *status, ChunkInfo *response ) - { - XRootDStatus st = pImpl->ReadCdfh( response->length, pUserHandler ); - if( !st.IsOK() ) - { - *status = st; - throw ZipHandlerException( status, response ); - } - else - DeleteArgs( status, response ); - } - -}; - - -class ZipReadHandler : public ZipHandlerBase -{ - public: - - ZipReadHandler( uint64_t relativeOffset, ZipArchiveReaderImpl *impl, ResponseHandler *userHandler ) : ZipHandlerBase( impl, userHandler ), pRelativeOffset( relativeOffset ) { } - - virtual void HandleResponseImpl( XRootDStatus *status, ChunkInfo *response ) - { - response->offset = pRelativeOffset; - if( pUserHandler ) pUserHandler->HandleResponse( status, PkgResp( response ) ); - else - DeleteArgs( status, response ); - } - - private: - - uint64_t pRelativeOffset; -}; - - -ZipArchiveReader::ZipArchiveReader() : pImpl( new ZipArchiveReaderImpl() ) -{ - -} - - -ZipArchiveReader::~ZipArchiveReader() -{ - pImpl->Delete(); -} - - -XRootDStatus ZipArchiveReaderImpl::Open( const std::string &url, ResponseHandler *userHandler, uint16_t timeout ) -{ - ZipOpenHandler *handler = new ZipOpenHandler( this, userHandler ); - XRootDStatus st = pArchive.Open( url, OpenFlags::Read, Access::None, handler, timeout ); - if( !st.IsOK() ) delete handler; - return st; -} - - -XRootDStatus ZipArchiveReader::Open( const std::string &url, ResponseHandler *handler, uint16_t timeout ) -{ - return pImpl->Open( url, handler, timeout ); -} - - -XRootDStatus ZipArchiveReader::Open( const std::string &url, uint16_t timeout ) -{ - SyncResponseHandler handler; - Status st = Open( url, &handler, timeout ); - if( !st.IsOK() ) - return st; - - return MessageUtils::WaitForStatus( &handler ); -} - - -XRootDStatus ZipArchiveReaderImpl::StatArchive( ResponseHandler *userHandler ) -{ - // we are doing the stat only while - // opening an archive so we clear - // just to be on the safe side - ClearRecords(); - - StatArchiveHandler *handler = new StatArchiveHandler( this, userHandler ); - XRootDStatus st = pArchive.Stat( true, handler ); - if( !st.IsOK() ) delete handler; - return st; -} - -XRootDStatus ZipArchiveReaderImpl::ReadArchive( ResponseHandler *userHandler ) -{ - uint64_t offset = 0; - uint32_t size = pArchiveSize; - pBuffer = new char[size]; - ReadArchiveHandler *handler = new ReadArchiveHandler( this, userHandler ); - XRootDStatus st = pArchive.Read( offset, size, pBuffer, handler ); - if( !st.IsOK() ) delete handler; - return st; -} - -XRootDStatus ZipArchiveReaderImpl::ReadEocd( ResponseHandler *userHandler ) -{ - uint32_t size = EOCD::kMaxCommentSize + EOCD::kEocdBaseSize; - uint64_t offset = pArchiveSize - size; - pBuffer = new char[size]; - ReadEocdHandler *handler = new ReadEocdHandler( this, userHandler ); - XRootDStatus st = pArchive.Read( offset, size, pBuffer, handler ); - if( !st.IsOK() ) delete handler; - return st; -} - -XRootDStatus ZipArchiveReaderImpl::ReadCdfh( uint64_t bytesRead, ResponseHandler *userHandler ) -{ - char *eocdBlock = LookForEocd( bytesRead ); - if( !eocdBlock ) throw ZipHandlerException( new XRootDStatus( stError, errErrorResponse, errDataError, "End-of-central-directory signature not found." ), 0 ); - pEocd = new EOCD( eocdBlock ); - uint64_t offset = pEocd->pCdOffset; - uint32_t size = pEocd->pCdSize; - delete[] pBuffer; - pBuffer = new char[size]; - ReadCdfhHandler *handler = new ReadCdfhHandler( this, userHandler, pEocd->pNbCdRec ); - XRootDStatus st = pArchive.Read( offset, size, pBuffer, handler ); - if( !st.IsOK() ) delete handler; - return st; -} - -XRootDStatus ZipArchiveReader::Read( const std::string &filename, uint64_t offset, uint32_t size, void *buffer, ResponseHandler *handler, uint16_t timeout ) -{ - return pImpl->Read( filename, offset, size, buffer, handler, timeout ); -} - -XRootDStatus ZipArchiveReader::Read( const std::string &filename, uint64_t offset, uint32_t size, void *buffer, uint32_t &bytesRead, uint16_t timeout ) -{ - SyncResponseHandler handler; - Status st = Read( filename, offset, size, buffer, &handler, timeout ); - if( !st.IsOK() ) - return st; - - ChunkInfo *chunkInfo = 0; - XRootDStatus status = MessageUtils::WaitForResponse( &handler, chunkInfo ); - if( status.IsOK() ) - { - bytesRead = chunkInfo->length; - delete chunkInfo; - } - return status; -} - -XRootDStatus ZipArchiveReaderImpl::Read( const std::string &filename, uint64_t relativeOffset, uint32_t size, void *buffer, ResponseHandler *userHandler, uint16_t timeout ) -{ - if( !pArchive.IsOpen() ) return XRootDStatus( stError, errInvalidOp, errInvalidOp, "Archive not opened." ); - - std::map::iterator cditr = pFileToCdfh.find( filename ); - if( cditr == pFileToCdfh.end() ) return XRootDStatus( stError, errNotFound, errNotFound, "File not found." ); - CDFH *cdfh = pCdRecords[cditr->second]; - - // Now the problem is that at the beginning of our - // file there is the Local-file-header, which size - // is not known because of the variable size 'extra' - // field, so we need to know the offset of the next - // record and shift it by the file size. - // The next record is either the next LFH (next file) - // or the start of the Central-directory. - uint64_t nextRecordOffset = ( cditr->second + 1 < pCdRecords.size() ) ? pCdRecords[cditr->second + 1]->pOffset : pEocd->pCdOffset; - uint32_t fileSize = cdfh->pCompressionMethod ? cdfh->pCompressedSize : cdfh->pUncompressedSize; - uint64_t offset = nextRecordOffset - fileSize + relativeOffset; - uint32_t sizeTillEnd = fileSize - relativeOffset; - if( size > sizeTillEnd ) size = sizeTillEnd; - - // check if we have the whole file in our local buffer - if( pBuffer ) - { - if( offset + size > pArchiveSize ) - { - if( userHandler ) userHandler->HandleResponse( new XRootDStatus( stError, errDataError ), 0 ); - return XRootDStatus( stError, errDataError ); - } - - memcpy( buffer, pBuffer + offset, size ); - - if( userHandler ) - { - XRootDStatus *st = new XRootDStatus(); - AnyObject *resp = new AnyObject(); - ChunkInfo *info = new ChunkInfo( relativeOffset, size, buffer ); - resp->Set( info ); - userHandler->HandleResponse( st, resp ); - } - return XRootDStatus(); - } - - ZipReadHandler *handler = new ZipReadHandler( relativeOffset, this, userHandler ); - XRootDStatus st = pArchive.Read( offset, size, buffer, handler, timeout ); - if( !st.IsOK() ) delete handler; - - return st; -} - -XRootDStatus ZipArchiveReader::Close( ResponseHandler *handler, uint16_t timeout ) -{ - return pImpl->Close( handler, timeout ); -} - -XRootDStatus ZipArchiveReader::Close( uint16_t timeout ) -{ - SyncResponseHandler handler; - Status st = Close( &handler, timeout ); - if( !st.IsOK() ) - return st; - - return MessageUtils::WaitForStatus( &handler ); -} - -bool ZipArchiveReader::SetProperty( const std::string &name, const std::string &value ) -{ - return pImpl->SetProperty( name, value ); -} - -XRootDStatus ZipArchiveReader::GetSize( const std::string &filename, uint32_t &size ) const -{ - return pImpl->GetSize( filename, size ); -} - -bool ZipArchiveReader::IsOpen() const -{ - return pImpl->IsOpen(); -} - -} /* namespace XrdCl */ diff --git a/src/XrdCl/XrdClZipArchiveReader.hh b/src/XrdCl/XrdClZipArchiveReader.hh deleted file mode 100644 index e4e899fb29c..00000000000 --- a/src/XrdCl/XrdClZipArchiveReader.hh +++ /dev/null @@ -1,151 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2014 by European Organization for Nuclear Research (CERN) -// Author: Michal Simon -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#ifndef SRC_XRDCL_XRDCLZIPARCHIVEREADER_HH_ -#define SRC_XRDCL_XRDCLZIPARCHIVEREADER_HH_ - -#include "XrdClXRootDResponses.hh" - -namespace XrdCl -{ - -class ZipArchiveReaderImpl; - -//---------------------------------------------------------------------------- -//! A wrapper class for the XrdCl::File. -//! -//! It is an abstraction for a ZIP file containing multiple sub-files. -//! The class does not provide any unzip utilities, it just readjusts -//! the offset so a respective file inside of the archive can be read. -//! It is meant for ZIP archives containing uncompressed root files, -//! so a single file can be accessed without downloading the whole -//! archive. -//---------------------------------------------------------------------------- -class ZipArchiveReader -{ - public: - - //------------------------------------------------------------------------ - //! Constructor. - //------------------------------------------------------------------------ - ZipArchiveReader(); - - //------------------------------------------------------------------------ - //! Destructor. - //------------------------------------------------------------------------ - virtual ~ZipArchiveReader(); - - //------------------------------------------------------------------------ - //! Asynchronous open of a given ZIP archive for reading. - //! - //! During the open, the End-of-central-directory record - //! and the Central-directory-file-headers records are - //! being read and parsed. - //! - //! If the ZIP archive is smaller than the maximum size - //! of the EOCD record the whole archive is being down- - //! loaded and kept in local memory. - //! - //! @param url : URL of the archive - //! @param handler : the handler for the async operation - //! @param timeout : the timeout of the async operation - //! - //! @return : OK on success, error otherwise - //------------------------------------------------------------------------ - XRootDStatus Open( const std::string &url, ResponseHandler *handler, uint16_t timeout = 0 ); - - //------------------------------------------------------------------------ - //! Synchronous open of a given ZIP archive for reading. - //------------------------------------------------------------------------ - XRootDStatus Open( const std::string &url, uint16_t timeout = 0 ); - - //------------------------------------------------------------------------ - //! Async read. - //! - //! @param filename : name of the file that will the readout - //! @param offset : offset (relative for the given file) - //! @param size : size of the buffer - //! @param buffer : the readout buffer - //! @param handler : the handler for the async operation - //! @param timeout : the timeout of the async operation - //! - //! @return : OK on success, error otherwise - //------------------------------------------------------------------------ - XRootDStatus Read( const std::string &filename, uint64_t offset, uint32_t size, void *buffer, ResponseHandler *handler, uint16_t timeout = 0 ); - - //------------------------------------------------------------------------ - // Sync read. - //------------------------------------------------------------------------ - XRootDStatus Read( const std::string &filename, uint64_t offset, uint32_t size, void *buffer, uint32_t &bytesRead, uint16_t timeout = 0 ); - - //------------------------------------------------------------------------ - //! Async close. - //! - //! @param handler : the handler for the async operation - //! @param timeout : the timeout of the async operation - //! - //! @return : OK on success, error otherwise - //------------------------------------------------------------------------ - XRootDStatus Close( ResponseHandler *handler, uint16_t timeout = 0 ); - - //------------------------------------------------------------------------ - //! Sync close. - //------------------------------------------------------------------------ - XRootDStatus Close( uint16_t timeout = 0 ); - - //------------------------------------------------------------------------ - //! Set file property - //! - //! File properties: - //! ReadRecovery [true/false] - enable/disable read recovery - //! WriteRecovery [true/false] - enable/disable write recovery - //! FollowRedirects [true/false] - enable/disable following redirections - //------------------------------------------------------------------------ - bool SetProperty( const std::string &name, const std::string &value ); - - //------------------------------------------------------------------------ - //! Gets the size of the given file - //! - //! @param filename : the name of the file - //! - //! @return : the size of the file as in CDFH record - //------------------------------------------------------------------------ - XRootDStatus GetSize( const std::string &filename, uint32_t &size ) const; - - //------------------------------------------------------------------------ - //! Check if the archive is open - //------------------------------------------------------------------------ - bool IsOpen() const; - - private: - - //------------------------------------------------------------------------ - //! Pointer to the implementation. - //------------------------------------------------------------------------ - ZipArchiveReaderImpl *pImpl; -}; - -} /* namespace XrdCl */ - -#endif /* SRC_XRDCL_XRDCLZIPARCHIVEREADER_HH_ */ diff --git a/src/XrdClient.cmake b/src/XrdClient.cmake deleted file mode 100644 index 57c592ff3f5..00000000000 --- a/src/XrdClient.cmake +++ /dev/null @@ -1,117 +0,0 @@ - -include( XRootDCommon ) - -#------------------------------------------------------------------------------- -# Shared library version -#------------------------------------------------------------------------------- -set( XRD_CLIENT_VERSION 2.0.0 ) -set( XRD_CLIENT_SOVERSION 2 ) - -#------------------------------------------------------------------------------- -# The XrdClient lib -#------------------------------------------------------------------------------- -add_library( - XrdClient - SHARED - XrdClient/XrdClientAbs.cc XrdClient/XrdClientAbs.hh - XrdClient/XrdClient.cc XrdClient/XrdClient.hh - XrdClient/XrdClientSock.cc XrdClient/XrdClientSock.hh - XrdClient/XrdClientPSock.cc XrdClient/XrdClientPSock.hh - XrdClient/XrdClientConn.cc XrdClient/XrdClientConn.hh - XrdClient/XrdClientConnMgr.cc XrdClient/XrdClientConnMgr.hh - XrdClient/XrdClientUnsolMsg.hh - XrdClient/XrdClientDebug.cc XrdClient/XrdClientDebug.hh - XrdClient/XrdClientInputBuffer.cc XrdClient/XrdClientInputBuffer.hh - XrdClient/XrdClientLogConnection.cc XrdClient/XrdClientLogConnection.hh - XrdClient/XrdClientPhyConnection.cc XrdClient/XrdClientPhyConnection.hh - XrdClient/XrdClientMessage.cc XrdClient/XrdClientMessage.hh - XrdClient/XrdClientProtocol.cc XrdClient/XrdClientProtocol.hh - XrdClient/XrdClientReadCache.cc XrdClient/XrdClientReadCache.hh - XrdClient/XrdClientUrlInfo.cc XrdClient/XrdClientUrlInfo.hh - XrdClient/XrdClientUrlSet.cc XrdClient/XrdClientUrlSet.hh - XrdClient/XrdClientThread.cc XrdClient/XrdClientThread.hh - XrdClient/XrdClientAdmin.cc XrdClient/XrdClientAdmin.hh - XrdClient/XrdClientVector.hh - XrdClient/XrdClientEnv.cc XrdClient/XrdClientEnv.hh - XrdClient/XrdClientConst.hh - XrdClient/XrdClientSid.cc XrdClient/XrdClientSid.hh - XrdClient/XrdClientMStream.cc XrdClient/XrdClientMStream.hh - XrdClient/XrdClientReadV.cc XrdClient/XrdClientReadV.hh - XrdClient/XrdClientReadAhead.cc XrdClient/XrdClientReadAhead.hh ) - -target_link_libraries( - XrdClient - XrdUtils - dl - pthread ) - -set_target_properties( - XrdClient - PROPERTIES - VERSION ${XRD_CLIENT_VERSION} - SOVERSION ${XRD_CLIENT_SOVERSION} - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) - -#------------------------------------------------------------------------------- -# xrd -#------------------------------------------------------------------------------- -add_executable( - xrd - XrdClient/XrdCommandLine.cc ) - -target_link_libraries( - xrd - XrdClient - XrdUtils - pthread - ${READLINE_LIBRARY} - ${NCURSES_LIBRARY} ) - -#------------------------------------------------------------------------------- -# xprep -#------------------------------------------------------------------------------- -add_executable( - xprep - XrdClient/XrdClientPrep.cc ) - -target_link_libraries( - xprep - XrdClient - XrdUtils - pthread ) - -#------------------------------------------------------------------------------- -# xrdstagetool -#------------------------------------------------------------------------------- -add_executable( - xrdstagetool - XrdClient/XrdStageTool.cc ) - -target_link_libraries( - xrdstagetool - XrdClient - XrdUtils - pthread ) - -#------------------------------------------------------------------------------- -# Install -#------------------------------------------------------------------------------- -install( - TARGETS XrdClient xrd xprep xrdstagetool - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ) - -install( - FILES - ${PROJECT_SOURCE_DIR}/docs/man/xrd.1 - ${PROJECT_SOURCE_DIR}/docs/man/xprep.1 - ${PROJECT_SOURCE_DIR}/docs/man/xrdstagetool.1 - DESTINATION ${CMAKE_INSTALL_MANDIR}/man1 ) - -# FIXME: files -#-rw-r--r-- 1 ljanyst ljanyst 1643 2011-03-21 16:13 TestXrdClient.cc -#-rw-r--r-- 1 ljanyst ljanyst 20576 2011-03-21 16:13 TestXrdClient_read.cc -#-rwxr-xr-x 1 ljanyst ljanyst 15314 2011-03-21 16:13 xrdadmin -#-rw-r--r-- 1 ljanyst ljanyst 1516 2011-03-21 16:13 XrdClientAbsMonIntf.hh -#-rw-r--r-- 1 ljanyst ljanyst 1026 2011-03-21 16:13 XrdClientCallback.hh diff --git a/src/XrdClient/README_Xrdcp b/src/XrdClient/README_Xrdcp deleted file mode 100644 index ac2a6576d66..00000000000 --- a/src/XrdClient/README_Xrdcp +++ /dev/null @@ -1,21 +0,0 @@ - - $Id$ - -Xrdcp is a cp-like tool to copy files: -- FROM an xrootd system TO a local file system -- FROM ax xrootd system TO an xrootd system (even the same) -- FROM a local file system TO an xrootd system - -Usage: - - xrdcp [-ddebuglevel] [-DSparmname stringvalue] ... [-DIparmname intvalue] - - where: - 0 <= debuglevel <= 4 - parmname is the name of an internal parameter - stringvalue is a string to be assigned to an internal parameter - intvalue is an int to be assigned to an internal parameter - -The values of the internal env parameters usually do not need to be modified. -For a complete list and their meaning, please refer to README_params - diff --git a/src/XrdClient/README_params b/src/XrdClient/README_params deleted file mode 100644 index c370bea2451..00000000000 --- a/src/XrdClient/README_params +++ /dev/null @@ -1,98 +0,0 @@ - -$Id$ - -Here is the list of the internal parameters of XrdClient, with their meaning. -The parameters names are case sensitive. - - -Name: ConnectTimeout -Default value: 60 -Desc: The timeout value for connection attempts at the init of an instance. - Any timeout will be internally treated as a recoverable error, and handled - in some way, typically retrying. Note that a connection attempt may - fail before this time. - -Name: RequestTimeout -Default value: 60 -Desc: The timeout value for send/receive attempts. Any timeout - will be internally treated as a recoverable error, and handled - in some way, typically auto-redirecting to the first redirector encountered. - -Name: MaxRedirectcount -Default value: 255 -Desc: If a client gets redirected or auto-redirected more times than this - number in the last RedirCntTimeout seconds, the pending operation will be abandoned. - -Name: RedirCntTimeout -Default value: 3600 -Desc: see up. - -Name: DebugLevel -Default value: 0 -Desc: Verbosity of the printed output. - -1 = no printing at all - 0 = print only errors, and a few more. - 1 = really basic trace of what's happening - 2 = trace of what is the client doing - 3 = Ultra-verbose: dump every data exchange - 4 = Hyper-verbose: dump everything flowing through sockets - -Name: ReconnectTimeout -Default value: 10 -Desc: The timeout value for re-connection attempts. Any timeout - will be internally treated as a recoverable error, and handled - in some way, typically retrying. Note that a connection attempt may - fail before this time. - - -Name: FirstConnectMaxCnt -Default value: 150 -Desc: How many times the connection on initialization will be retried, - in the case of error. - -Name: StartGarbageCollectorThread -Default value: 1 -Desc: A really paranoid parameter. If set to 0, the physical/logical - connections are no more closed if inactive. - -Name: GoAsync -Default value: 1 -Desc: Choose between the sync/async internal architecture. The sync one - is unable to handle unsolicited responses, and will never be. - -Name: ReadCacheSize -Default value: 4000000 -Desc: The max size of the read cache. One cache per instance. Set to 0 to - disable caching. - -Name: ReadAheadSize -Default value: 800000 -Desc: The size of the blocks requested and populating the cache. - -Name: RedirDomainAllowRE -Default value: Internally computed to allow only operations - on the local domain. -Desc: Given a list of |-separated regexps for the domains to ALLOW REDIR to, - match every entry with domain. If NO match is found, deny access. Only * is - allowed in the regexps, and only at the beginning or the end of the single exprs - -Name: RedirDomainDenyRE -Default value: Internally computed to allow only operations - on the local domain. -Desc: Given a list of |-separated regexps for the domains to DENY REDIR to, - match every entry with domain. If ANY match is found, deny access. Only * is - allowed in the regexps, and only at the beginning or the end of the single exprs - -Name: ConnectDomainAllowRE -Default value: Internally computed to allow only operations - on the local domain. -Desc: Given a list of |-separated regexps for the domains to ALLOW CONNECT to, - match every entry with domain. If NO match is found, deny access. Only * is - allowed in the regexps, and only at the beginning or the end of the single exprs - -Name: ConnectDomainDenyRE -Default value: Internally computed to allow only operations - on the local domain. -Desc: Given a list of |-separated regexps for the domains to DENY CONNECT to, - match every entry with domain. If ANY match is found, deny access. Only * is - allowed in the regexps, and only at the beginning or the end of the single exprs \ No newline at end of file diff --git a/src/XrdClient/TestXrdClient.cc b/src/XrdClient/TestXrdClient.cc deleted file mode 100644 index 3fa930b74f8..00000000000 --- a/src/XrdClient/TestXrdClient.cc +++ /dev/null @@ -1,117 +0,0 @@ -/******************************************************************************/ -/* */ -/* T e s t X r d C l i e n t . c c */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -// Simple keleton for simple tests of Xrdclient and XrdClientAdmin -// - -#include "XrdClient/XrdClient.hh" -#include "XrdClient/XrdClientAdmin.hh" -#include "XrdClient/XrdClientEnv.hh" -#include "XrdSys/XrdSysHeaders.hh" - -int main(int argc, char **argv) { - -// EnvPutInt(NAME_DEBUG, 3); - //EnvPutInt(NAME_READCACHESIZE, 100000000); - - -// XrdClient *x = new XrdClient(argv[1]); -// XrdClient *y = new XrdClient(argv[2]); -// x->Open(0, 0); - -// for (int i = 0; i < 1000; i++) -// x->Copy("/tmp/testcopy"); - -// x->Close(); - -// delete x; -// x = 0; - -// y->Open(0, 0); - -// for (int i = 0; i < 1000; i++) -// x->Copy("/tmp/testcopy"); - -// y->Close(); - -// delete y; - - XrdClientUrlInfo u; - XrdClientAdmin *adm = new XrdClientAdmin(argv[1]); - - adm->Connect(); - - string s; - int i = 0; - XrdClientLocate_Info loc; - while (!cin.eof()) { - cin >> s; - - if (!s.size()) continue; - - if (!adm->Locate((kXR_char*)s.c_str(), loc)) { - cout << endl << - " The server complained for file:" << endl << - s.c_str() << endl << endl; - } - - if (!(i % 100)) cout << i << "..."; - i++; -// if (i == 9000) break; - } - -// vecString vs; -// XrdOucString os; -// string s; -// int i = 0; -// while (!cin.eof()) { -// cin >> s; - -// if (!s.size()) continue; - -// os = s.c_str(); -// vs.Push_back(os); - -// if (!(i % 200)) { -// cout << i << "..."; -// adm->Prepare(vs, kXR_stage, 0); -// vs.Clear(); -// } - -// i++; - -// } - - - -// adm->Prepare(vs, 0, 0); -// cout << endl << endl; - - delete adm; - - - - -} diff --git a/src/XrdClient/TestXrdClient_read.cc b/src/XrdClient/TestXrdClient_read.cc deleted file mode 100644 index 5f86e0d7f6a..00000000000 --- a/src/XrdClient/TestXrdClient_read.cc +++ /dev/null @@ -1,778 +0,0 @@ -/******************************************************************************/ -/* */ -/* T e s t X r d C l i e n t _ r e a d . c c */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdClient/XrdClient.hh" -#include "XrdClient/XrdClientEnv.hh" -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdClient/XrdClientCallback.hh" -#include -#include -#include -#include -#include - -class MyXrdClientCallback: public XrdClientCallback { - - virtual void OpenComplete(XrdClientAbs *clientP, void *cbArg, bool res) { - cout << "OpenComplete! res:" << res << endl; - } - -}; - -kXR_unt16 open_mode = (kXR_ur | kXR_uw); -kXR_unt16 open_opts = (0); - -int ReadSome(kXR_int64 *offs, kXR_int32 *lens, int maxnread, long long &totalbytes) { - - for (int i = 0; i < maxnread;) { - - lens[i] = -1; - offs[i] = -1; - - if (cin.eof()) return i; - - cin >> lens[i] >> offs[i]; - - if ((lens[i] > 0) && (offs[i] >= 0)) { - totalbytes += lens[i]; - i++; - } - - } - - return maxnread; - -} - -// Waste cpu cycles for msdelay milliseconds -void Think(long msdelay) { - - timeval tv; - long long tlimit, t; - - if (msdelay <= 0) return; - - gettimeofday(&tv, 0); - tlimit = (long long)tv.tv_sec * 1000 + tv.tv_usec / 1000 + msdelay; - t = 0; - - while ( t < tlimit ) { - - double numb[1000]; - for (int i = 0; i < 100; i++) - numb[i] = random(); - - for (int i = 0; i < 100; i++) - numb[i] = sqrt(numb[i]); - - for (int i = 0; i < 100; i++) - memmove(numb+10, numb, 90*sizeof(double)); - - gettimeofday(&tv, 0); - t = (long long)tv.tv_sec * 1000 + tv.tv_usec / 1000; - } - - -} - - -int main(int argc, char **argv) { - void *buf; - int vectored_style = 0; - long read_delay = 0; - timeval tv; - double starttime = 0, openphasetime = 0, endtime = 0, closetime = 0; - long long totalbytesread = 0, prevtotalbytesread = 0; - long totalreadscount = 0; - int filezcount = 0; - string summarypref = "$$$"; - bool iserror = false; - bool dobytecheck = false; - - gettimeofday(&tv, 0); - starttime = tv.tv_sec + tv.tv_usec / 1000000.0; - closetime = openphasetime = starttime; - - - if (argc < 2) { - cout << endl << endl << - "This program gets from the standard input a sequence of" << endl << - " (one for each line, with less than 16M)" << endl << - " and performs the corresponding read requests towards the given xrootd URL or to ALL" << endl << - " the xrootd URLS contained in the given file." << endl << - endl << - "Usage: TestXrdClient_read [--check] [-DSparmname stringvalue]... [-DIparmname intvalue]..." << - endl << endl << - " Where:" << endl << - " is the xrootd URL of a remote file " << endl << - " is the read ahead size. Can be 0." << endl << - " is the size of the internal cache, in bytes. Can be 0." << endl << - " means 0: no vectored reads (default)," << endl << - " 1: sync vectored reads," << endl << - " 2: async vectored reads, do not access the buffer," << endl << - " 3: async vectored reads, copy the buffers" << endl << - " (makes it sync through async calls!)" << endl << - " 4: no vectored reads. Async reads followed by sync reads." << endl << - " (exploits the multistreaming for single reads)" << endl << - " 5: don't read, but write data which is compatible with the --check option." << endl << - " is the optional think time between reads." << endl << - " note: the think time will comsume cpu cycles, not sleep." << endl << - " --check verify if the value of the byte at offet i is i%256. Valid only for the single url mode." << endl << - " -DSparmname stringvalue" << endl << - " set the internal parm with the string value " << endl << - " See XrdClientConst.hh for a list of parameters." << endl << - " -DIparmname intvalue" << endl << - " set the internal parm with the integer value " << endl << - " See XrdClientConst.hh for a list of parameters." << endl << - " Examples: -DSSocks4Server 123.345.567.8 -DISocks4Port 8080 -DIDebugLevel 1" << endl; - exit(1); - } - - if (argc > 2) - EnvPutInt( NAME_READAHEADSIZE, atol(argv[2])); - - if (argc >= 3) - EnvPutInt( NAME_READCACHESIZE, atol(argv[3])); - - if (argc > 4) - vectored_style = atol(argv[4]); - - cout << "Read style: "; - switch (vectored_style) { - case 0: - cout << "Synchronous reads, ev. with read ahead." << endl; - break; - case 1: - cout << "Synchronous readv" << endl; - break; - case 2: - cout << "Asynchronous readv, data is not processed." << endl; - break; - case 3: - cout << "Asynchronous readv." << endl; - break; - case 4: - cout << "Asynchronous reads." << endl; - break; - case 5: - cout << "Write test file." << endl; - open_opts |= kXR_open_updt; - break; - default: - cout << "Unknown." << endl; - break; - } - - - if (argc > 5) - read_delay = atol(argv[5]); - - // The other args, they have to be an even number. Odd only if --check is there - if (argc > 6) - for (int i=6; i < argc; i++) { - - if (strstr(argv[i], "--check") == argv[i]) { - cerr << "Enabling file content check." << endl; - dobytecheck = true; - continue; - } - - if ( (strstr(argv[i], "-DS") == argv[i]) && - (argc >= i+2) ) { - cerr << "Overriding " << argv[i]+3 << " with value " << argv[i+1] << ". "; - EnvPutString( argv[i]+3, argv[i+1] ); - cerr << " Final value: " << EnvGetString(argv[i]+3) << endl; - i++; - continue; - } - - if ( (strstr(argv[i], "-DI") == argv[i]) && - (argc >= i+2) ) { - cerr << "Overriding '" << argv[i]+3 << "' with value " << argv[i+1] << ". "; - EnvPutInt( argv[i]+3, atoi(argv[i+1]) ); - cerr << " Final value: " << EnvGetLong(argv[i]+3) << endl; - i++; - continue; - } - - } - - buf = malloc(200*1024*1024); - - // Check if we have a file or a root:// url - bool isrooturl = (strstr(argv[1], "root://")); - int retval = 0; - int ntoread = 0; - int maxtoread = 20480; - kXR_int64 v_offsets[20480]; - kXR_int32 v_lens[20480]; - - if (isrooturl) { - MyXrdClientCallback mycb; - XrdClient *cli = new XrdClient(argv[1], &mycb, (void *)1234); - - cli->Open(open_mode, open_opts | ( (vectored_style > 4) ? kXR_delete : 0 ) ); - filezcount = 1; - - gettimeofday(&tv, 0); - openphasetime = tv.tv_sec + tv.tv_usec / 1000000.0; - - while ( (ntoread = ReadSome(v_offsets, v_lens, maxtoread, totalbytesread)) ) { - cout << "."; - - totalreadscount += ntoread; - - switch (vectored_style) { - case 0: // no readv - for (int iii = 0; iii < ntoread; iii++) { - retval = cli->Read(buf, v_offsets[iii], v_lens[iii]); - - if (retval <= 0) { - cout << endl << "---Read (" << iii << " of " << ntoread << " " << - v_lens[iii] << "@" << v_offsets[iii] << - " returned " << retval << endl; - - iserror = true; - break; - } - else { - - if (dobytecheck) - for ( unsigned int jj = 0; jj < (unsigned)v_lens[iii]; jj++) { - - if ( ((jj+v_offsets[iii]) % 256) != ((unsigned char *)buf)[jj] ) { - cout << "errore nel file all'offset= " << jj+v_offsets[iii] << - " letto: " << (int)((unsigned char *)buf)[jj] << " atteso: " << (jj+v_offsets[iii]) % 256 << endl; - iserror = true; - break; - } - } - if (!((iii+1) % 100)) Think(read_delay); - } - - } - break; - case 1: // sync - retval = cli->ReadV((char *)buf, v_offsets, v_lens, ntoread); - cout << endl << "---ReadV returned " << retval << endl; - - if (retval > 0) - for (int iii = 0; iii < ntoread; iii++){ - if (!((iii+1) % 100)) Think(read_delay); - - } - - else { - iserror = true; - break; - } - - break; - - case 2: // async - retval = cli->ReadV(0, v_offsets, v_lens, ntoread); - cout << endl << "---ReadV returned " << retval << endl; - break; - - case 3: // async and immediate read, optimized! - cli->RemoveAllDataFromCache(); - for (int ii = 0; ii < ntoread+512; ii+=512) { - - if (ii < ntoread) { - // Read a chunk of data - retval = cli->ReadV(0, v_offsets+ii, v_lens+ii, xrdmin(ntoread - ii, 512) ); - cout << endl << "---ReadV returned " << retval << endl; - - if (retval <= 0) { - iserror = true; - break; - } - } - - - // Process the preceeding chunk while the last is coming - for (int iii = ii-512; (iii >= 0) && (iii < ii) && (iii < ntoread); iii++) { - retval = cli->Read(buf, v_offsets[iii], v_lens[iii]); - - if (retval <= 0) - cout << endl << "---Read (" << iii << " of " << ntoread << " " << - v_lens[iii] << "@" << v_offsets[iii] << - " returned " << retval << endl; - - if (dobytecheck) - for ( unsigned int jj = 0; jj < (unsigned)v_lens[iii]; jj++) { - if ( ((jj+v_offsets[iii]) % 256) != ((unsigned char *)buf)[jj] ) { - cout << "errore nel file all'offset= " << jj+v_offsets[iii] << - " letto: " << (int)((unsigned char *)buf)[jj] << " atteso: " << (jj+v_offsets[iii]) % 256 << endl; - iserror = true; - break; - } - } - - if (!((iii+1) % 100)) Think(read_delay); - } - - } - - retval = 1; - - break; - - - case 4: // read async and then read - cli->RemoveAllDataFromCache(); - for (int iii = -512; iii < ntoread; iii++) { - if (iii + 512 < ntoread) - retval = cli->Read_Async(v_offsets[iii+512], v_lens[iii+512]); - - if (retval <= 0) { - cout << endl << "---Read_Async (" << iii+512 << " of " << ntoread << " " << - v_lens[iii+512] << "@" << v_offsets[iii+512] << - " returned " << retval << endl; - iserror = true; - break; - } - - if (iii >= 0) { - retval = cli->Read(buf, v_offsets[iii], v_lens[iii]); - - if (retval <= 0) { - cout << endl << "---Read (" << iii << " of " << ntoread << " " << - v_lens[iii] << "@" << v_offsets[iii] << - " returned " << retval << endl; - - iserror = true; - break; - } - - if (!((iii+1) % 100)) Think(read_delay); - } - - } - - break; - - case 5: // don't read... write - for (int iii = 0; iii < ntoread; iii++) { - - for (int kkk = 0; kkk < v_lens[iii]; kkk++) - ((unsigned char *)buf)[kkk] = (v_offsets[iii]+kkk) % 256; - - retval = cli->Write(buf, v_offsets[iii], v_lens[iii]); - - if (retval <= 0) { - cout << endl << "---Write (" << iii << " of " << ntoread << ") " << - v_lens[iii] << "@" << v_offsets[iii] << - " returned " << retval << endl; - iserror = true; - break; - } - - if (retval > 0) { - if (!((iii+1) % 100)) Think(read_delay); - } - } - - break; - - - - } // switch - - if (!cli->IsOpen_wait()) { - iserror = true; - break; - } - - } // while - - gettimeofday(&tv, 0); - closetime = tv.tv_sec + tv.tv_usec / 1000000.0; - - cli->Close(); - delete cli; - cli = 0; - - - } - else { - // Same test on multiple filez - - vector xrdcvec; - ifstream filez(argv[1]); - int i = 0, fnamecount = 0;; - XrdClientUrlInfo u; - - // Open all the files (in parallel man!) - while (!filez.eof()) { - string s; - XrdClient * cli; - - filez >> s; - if (s != "") { - fnamecount++; - cli = new XrdClient(s.c_str()); - u.TakeUrl(s.c_str()); - - if (cli->Open( open_mode, open_opts | ((vectored_style > 4) ? kXR_delete : 0) )) { - cout << "--- Open of " << s << " in progress." << endl; - xrdcvec.push_back(cli); - } - else delete cli; - } - - i++; - } - - filez.close(); - - filezcount = xrdcvec.size(); - cout << "--- All the open requests have been submitted" << endl; - - if (fnamecount == filezcount) { - - i = 0; - - - gettimeofday(&tv, 0); - openphasetime = tv.tv_sec + tv.tv_usec / 1000000.0; - - while ( (ntoread = ReadSome(v_offsets, v_lens, 10240, totalbytesread)) ) { - - - switch (vectored_style) { - case 0: // no readv - for (int iii = 0; iii < ntoread; iii++) { - - - for(int i = 0; i < (int) xrdcvec.size(); i++) { - - retval = xrdcvec[i]->Read(buf, v_offsets[iii], v_lens[iii]); - - - if (retval <= 0) { - cout << endl << "---Read (" << iii << " of " << ntoread << ") " << - v_lens[iii] << "@" << v_offsets[iii] << - " returned " << retval << endl; - iserror = true; - break; - } - - - if (retval > 0) { - - if (dobytecheck) - for ( unsigned int jj = 0; jj < (unsigned)v_lens[iii]; jj++) { - - if ( ((jj+v_offsets[iii]) % 256) != ((unsigned char *)buf)[jj] ) { - cout << "errore nel file all'offset= " << jj+v_offsets[iii] << - " letto: " << (int)((unsigned char *)buf)[jj] << " atteso: " << (jj+v_offsets[iii]) % 256 << endl; - iserror = true; - break; - } - } - - Think(read_delay); - } - - } - - } - - break; - case 1: // sync - - for(int i = 0; i < (int) xrdcvec.size(); i++) { - - retval = xrdcvec[i]->ReadV((char *)buf, v_offsets, v_lens, ntoread); - - - cout << endl << "---ReadV " << xrdcvec[i]->GetCurrentUrl().GetUrl() << - " of " << ntoread << " chunks " << - " returned " << retval << endl; - - if (retval) { - cout << "start think " << time(0) << endl; - for (int kkk = 0; kkk < ntoread; kkk++) Think(read_delay); - cout << time(0) << endl; - } - else { - iserror = true; - break; - } - - } - - break; - - case 2: // async - - for(int i = 0; i < (int) xrdcvec.size(); i++) { - - retval = xrdcvec[i]->ReadV((char *)0, v_offsets, v_lens, ntoread); - cout << endl << "---ReadV " << xrdcvec[i]->GetCurrentUrl().GetUrl() << - " returned " << retval << endl; - } - - break; - - case 3: // async readv and immediate read, optimized! - - for (int ii = 0; ii < ntoread; ii+=4096) { - - // Read a chunk of data - for(int i = 0; i < (int) xrdcvec.size(); i++) { - - retval = xrdcvec[i]->ReadV((char *)0, v_offsets+ii, v_lens+ii, xrdmin(4096, ntoread-ii)); - cout << endl << "---ReadV " << xrdcvec[i]->GetCurrentUrl().GetUrl() << - " of " << xrdmin(4096, ntoread-ii) << " chunks " << - " returned " << retval << endl; - - if (retval <= 0) { - iserror = true; - break; - } - - } - - // Process the preceeding chunk while the last is coming - for (int iii = ii-4096; (iii >= 0) && (iii < ii); iii++) { - - for(int i = 0; i < (int) xrdcvec.size(); i++) { - - retval = xrdcvec[i]->Read(buf, v_offsets[iii], v_lens[iii]); - - if (retval <= 0) - cout << endl << "---Read " << xrdcvec[i]->GetCurrentUrl().GetUrl() << - "(" << iii << " of " << ntoread << ") " << - v_lens[iii] << "@" << v_offsets[iii] << - " returned " << retval << endl; - - if (retval > 0) { - - - if (dobytecheck) - for ( unsigned int jj = 0; jj < (unsigned)v_lens[iii]; jj++) { - - if ( ((jj+v_offsets[iii]) % 256) != ((unsigned char *)buf)[jj] ) { - cout << "errore nel file all'offset= " << jj+v_offsets[iii] << - " letto: " << (int)((unsigned char *)buf)[jj] << " atteso: " << (jj+v_offsets[iii]) % 256 << endl; - iserror = true; - break; - } - } - - Think(read_delay); - } - - } - - } - - } - - retval = 1; - - break; - - case 4: // read async and then read - - // Start being in advance of 512 reads per file - for(int i = 0; i < (int) xrdcvec.size(); i++) { - - for (int jj = 0; jj < xrdmin(512, ntoread); jj++) { - retval = xrdcvec[i]->Read_Async(v_offsets[jj], v_lens[jj]); - - if (retval != kOK) { - cout << endl << "---Read_Async " << xrdcvec[i]->GetCurrentUrl().GetUrl() << - "(" << jj << " of " << ntoread << ") " << - v_lens[jj] << "@" << v_offsets[jj] << - " returned " << retval << endl; - break; - } - - } - - } - - // Then read everything - for (int ii = 0; ii < ntoread; ii++) { - - // Read_async a chunk of data per file - for(int i = 0; i < (int) xrdcvec.size(); i++) { - - if (ii + 512 < ntoread) - retval = xrdcvec[i]->Read_Async(v_offsets[ii+512], v_lens[ii+512]); - - if (retval != kOK) { - cout << endl << "---Read_Async " << xrdcvec[i]->GetCurrentUrl().GetUrl() << - "(" << ii+512 << " of " << ntoread << ") " << - v_lens[ii+512] << "@" << v_offsets[ii+512] << - " returned " << retval << endl; - } - - } - - // Now process one chunk per file - for(int i = 0; i < (int) xrdcvec.size(); i++) { - - retval = xrdcvec[i]->Read(buf, v_offsets[ii], v_lens[ii]); - - - if (retval > 0) { - - if (dobytecheck) - for ( unsigned int jj = 0; jj < (unsigned)v_lens[ii]; jj++) { - - if ( ((jj+v_offsets[ii]) % 256) != ((unsigned char *)buf)[jj] ) { - cout << "errore nel file all'offset= " << jj+v_offsets[ii] << - " letto: " << (int)((unsigned char *)buf)[jj] << " atteso: " << (jj+v_offsets[ii]) % 256 << endl; - iserror = true; - break; - } - } - - Think(read_delay); - } - else { - cout << endl << "---Read (" << ii << " of " << ntoread << ") " << - v_lens[ii] << "@" << v_offsets[ii] << - " returned " << retval << endl; - iserror = true; - break; - } - - - if (iserror) break; - - } // for i - - if (iserror) break; - } // for ii - - retval = 1; - - - break; - - case 5: // don't read... write - for (int iii = 0; iii < ntoread; iii++) { - - - for(int i = 0; i < (int) xrdcvec.size(); i++) { - - for (int kkk = 0; kkk < v_lens[iii]; kkk++) - ((unsigned char *)buf)[kkk] = (v_offsets[iii]+kkk) % 256; - - retval = xrdcvec[i]->Write(buf, v_offsets[iii], v_lens[iii]); - - if (retval <= 0) { - cout << endl << "---Write (" << iii << " of " << ntoread << ") " << - v_lens[iii] << "@" << v_offsets[iii] << - " returned " << retval << endl; - iserror = true; - break; - } - - if (retval > 0) { - Think(read_delay); - } - - } - - } - - break; - - - - - } // switch - - if (iserror && prevtotalbytesread) { - totalbytesread = prevtotalbytesread; - break; - } - - prevtotalbytesread = totalbytesread; - totalreadscount += ntoread; - } // while readsome - - gettimeofday(&tv, 0); - closetime = tv.tv_sec + tv.tv_usec / 1000000.0; - - cout << endl << endl << "--- Closing all instances" << endl; - for(int i = 0; i < (int) xrdcvec.size(); i++) { - if (xrdcvec[i]->IsOpen()) xrdcvec[i]->Close(); - else cout << "WARNING: file '" << - xrdcvec[i]->GetCurrentUrl().GetUrl() << " was not opened." << endl; - } - - cout << "--- Deleting all instances" << endl; - for(int i = 0; i < (int) xrdcvec.size(); i++) delete xrdcvec[i]; - - cout << "--- Clearing pointer vector" << endl; - xrdcvec.clear(); - - - } //if fnamecount == filezcount - - - - } // Case of multiple urls - - - cout << "--- Freeing buffer" << endl; - free(buf); - - gettimeofday(&tv, 0); - endtime = tv.tv_sec + tv.tv_usec / 1000000.0; - - if (iserror) summarypref = "%%%"; - - - cout << "Summary ----------------------------" << endl; - cout << summarypref << " starttime: " << starttime << endl; - cout << summarypref << " lastopentime: " << openphasetime << endl; - cout << summarypref << " closetime: " << closetime << endl; - cout << summarypref << " endtime: " << endtime << endl; - cout << summarypref << " open_elapsed: " << openphasetime - starttime << endl; - cout << summarypref << " data_xfer_elapsed: " << closetime - openphasetime << endl; - cout << summarypref << " close_elapsed: " << endtime - closetime << endl; - cout << summarypref << " total_elapsed: " << endtime - starttime << endl; - cout << summarypref << " totalbytesreadperfile: " << totalbytesread << endl; - cout << summarypref << " maxbytesreadpersecperfile: " << totalbytesread / (closetime - openphasetime) << endl; - cout << summarypref << " effbytesreadpersecperfile: " << totalbytesread / (endtime - starttime) << endl; - cout << summarypref << " readscountperfile: " << totalreadscount << endl; - cout << summarypref << " openedkofilescount: " << filezcount << endl; - cout << endl; - - - return 0; - - - - -} diff --git a/src/XrdClient/XrdClient.cc b/src/XrdClient/XrdClient.cc deleted file mode 100644 index a641cceff2c..00000000000 --- a/src/XrdClient/XrdClient.cc +++ /dev/null @@ -1,2050 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C l i e n t . c c */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2004) */ -/* Adapted from TXNetFile (root.cern.ch) originally done by */ -/* Alvise Dorigo, Fabrizio Furano */ -/* INFN Padova, 2003 */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// A UNIX reference client for xrootd. // -// // -////////////////////////////////////////////////////////////////////////// - -#include "XrdClient/XrdClient.hh" -#include "XrdClient/XrdClientDebug.hh" -#include "XrdClient/XrdClientThread.hh" -#include "XrdClient/XrdClientUrlSet.hh" -#include "XrdClient/XrdClientConn.hh" -#include "XrdClient/XrdClientEnv.hh" -#include "XrdClient/XrdClientConnMgr.hh" -#include "XrdClient/XrdClientSid.hh" -#include "XrdClient/XrdClientMStream.hh" -#include "XrdClient/XrdClientReadV.hh" -#include "XrdOuc/XrdOucCRC.hh" -#include "XrdClient/XrdClientReadAhead.hh" -#include "XrdClient/XrdClientCallback.hh" - -#include -#ifndef WIN32 -#include -#endif - -#include -#include -#include -#include - - -XrdSysSemWait XrdClient::fConcOpenSem(DFLT_MAXCONCURRENTOPENS); - -//_____________________________________________________________________________ -// Calls the Open func in order to parallelize the Open requests -// -void *FileOpenerThread(void *arg, XrdClientThread *thr) { - // Function executed in the garbage collector thread - XrdClient *thisObj = (XrdClient *)arg; - - thr->SetCancelDeferred(); - thr->SetCancelOn(); - - - bool res = thisObj->TryOpen(thisObj->fOpenPars.mode, thisObj->fOpenPars.options, false); - if (thisObj->fXrdCcb) thisObj->fXrdCcb->OpenComplete(thisObj, thisObj->fXrdCcbArg, res); - - return 0; -} - - -//_____________________________________________________________________________ -XrdClient::XrdClient(const char *url, - XrdClientCallback *XrdCcb, - void *XrdCcbArg) : XrdClientAbs(XrdCcb, XrdCcbArg) { - - fReadAheadMgr = 0; - fReadTrimBlockSize = 0; - fOpenerTh = 0; - fOpenProgCnd = new XrdSysCondVar(0); - fReadWaitData = new XrdSysCondVar(0); - - memset(&fStatInfo, 0, sizeof(fStatInfo)); - memset(&fOpenPars, 0, sizeof(fOpenPars)); - memset(&fCounters, 0, sizeof(fCounters)); - - // Pick-up the latest setting of the debug level - DebugSetLevel(EnvGetLong(NAME_DEBUG)); - - if (!ConnectionManager) - Info(XrdClientDebug::kUSERDEBUG, - "Create", - "(C) 2004-2010 by the Xrootd group. XrdClient $Revision$ - Xrootd version: " << XrdVSTRING); - -#ifndef WIN32 - signal(SIGPIPE, SIG_IGN); -#endif - - fInitialUrl = url; - - fConnModule = new XrdClientConn(); - - - if (!fConnModule) { - Error("Create","Object creation failed."); - abort(); - } - - fConnModule->SetRedirHandler(this); - - int CacheSize = EnvGetLong(NAME_READCACHESIZE); - int RaSize = EnvGetLong(NAME_READAHEADSIZE); - int RmPolicy = EnvGetLong(NAME_READCACHEBLKREMPOLICY); - int ReadAheadStrategy = EnvGetLong(NAME_READAHEADSTRATEGY); - - SetReadAheadStrategy(ReadAheadStrategy); - SetBlockReadTrimming(EnvGetLong(NAME_READTRIMBLKSZ)); - - fUseCache = (CacheSize > 0); - SetCacheParameters(CacheSize, RaSize, RmPolicy); -} - -//_____________________________________________________________________________ -XrdClient::~XrdClient() -{ - - if (IsOpen_wait()) Close(); - - // Terminate the opener thread - fOpenProgCnd->Lock(); - - if (fOpenerTh) { - fOpenerTh->Cancel(); - fOpenerTh->Join(); - delete fOpenerTh; - fOpenerTh = 0; - } - - fOpenProgCnd->UnLock(); - - if (fConnModule) - delete fConnModule; - - if (fReadAheadMgr) delete fReadAheadMgr; - fReadAheadMgr = 0; - - delete fReadWaitData; - delete fOpenProgCnd; - - PrintCounters(); -} - -//_____________________________________________________________________________ -bool XrdClient::IsOpen_inprogress() -{ - // Non blocking access to the 'inprogress' flag - bool res; - - if (!fOpenProgCnd) return false; - - fOpenProgCnd->Lock(); - res = fOpenPars.inprogress; - fOpenProgCnd->UnLock(); - - return res; -}; - -//_____________________________________________________________________________ -bool XrdClient::IsOpen_wait() { - bool res; - - if (!fOpenProgCnd) return false; - - fOpenProgCnd->Lock(); - - if (fOpenPars.inprogress) { - fOpenProgCnd->Wait(); - - if (fOpenerTh) { - // To prevent deadlocks in the case of - // accesses from the Open() callback - fOpenProgCnd->UnLock(); - - fOpenerTh->Join(); - delete fOpenerTh; - fOpenerTh = 0; - - // We need the lock again... sigh - fOpenProgCnd->Lock(); - } - } - res = fOpenPars.opened; - fOpenProgCnd->UnLock(); - - return res; -}; - -//_____________________________________________________________________________ -void XrdClient::TerminateOpenAttempt() { - fOpenProgCnd->Lock(); - - fOpenPars.inprogress = false; - fOpenProgCnd->Broadcast(); - fOpenProgCnd->UnLock(); - - fConcOpenSem.Post(); - - //cout << "Mytest " << time(0) << " File: " << fUrl.File << " - Open finished." << endl; -} - -#define OpenErr(a,b) fConnModule->LastServerError.errnum=a;\ - strcpy(fConnModule->LastServerError.errmsg,b);\ - Error("Open", b) - -//_____________________________________________________________________________ -bool XrdClient::Open(kXR_unt16 mode, kXR_unt16 options, bool doitparallel) { - class urlHelper - {public: - void Erase(XrdClientUrlInfo *url){urlSet->EraseUrl(url);} - - XrdClientUrlInfo *Get() {return urlSet->GetARandomUrl(seed);} - - int Init(XrdOucString urls) - {if (urlSet) delete urlSet; - urlSet = new XrdClientUrlSet(urls); - iSize = (urlSet->IsValid() ? urlSet->Size():0); - return iSize; - } - - int Size() {return iSize;} - - urlHelper() : urlSet(0), iSize(0) - {seed = static_cast - (getpid() ^ getppid()); - } - ~urlHelper() {if (urlSet) delete urlSet;} - private: - XrdClientUrlSet *urlSet; - int iSize; - unsigned int seed; - }; - urlHelper urlList; - - - // But we initialize the internal params... - fOpenPars.opened = FALSE; - fOpenPars.options = options; - fOpenPars.mode = mode; - - // Now we try to set up the first connection - // We cycle through the list of urls given in fInitialUrl - - - // Max number of tries - int connectMaxTry = EnvGetLong(NAME_FIRSTCONNECTMAXCNT); - - fConnModule->SetOpTimeLimit(EnvGetLong(NAME_TRANSACTIONTIMEOUT)); - - // - // Now start the connection phase, picking randomly from UrlList - // - int urlstried = 0; - fConnModule->LastServerError.errnum = kXR_noErrorYet; - for (int connectTry = 0; - (connectTry < connectMaxTry) && (!fConnModule->IsConnected()); - connectTry++) { - - int urlCount; - if ((urlCount = urlList.Init(fInitialUrl)) < 1) { - OpenErr(kXR_ArgInvalid, "The URL provided is incorrect."); - return FALSE; - } - - XrdClientUrlInfo *thisUrl = 0; - urlstried = (urlstried == urlCount) ? 0 : urlstried; - - if ( fConnModule->IsOpTimeLimitElapsed(time(0)) ) { - // We have been so unlucky and wasted too much time in connecting and being redirected - fConnModule->Disconnect(TRUE); - Error("Open", "Access to server failed: Too much time elapsed without success."); - break; - } - - bool nogoodurl = TRUE; - while (urlCount--) { - - // Get an url from the available set - if ((thisUrl = urlList.Get())) { - - if (fConnModule->CheckHostDomain(thisUrl->Host)) { - nogoodurl = FALSE; - - Info(XrdClientDebug::kHIDEBUG, "Open", "Trying to connect to " << - thisUrl->Host << ":" << thisUrl->Port << ". Connect try " << - connectTry+1); - fConnModule->Connect(*thisUrl, this); - // To find out if we have tried the whole URLs set - urlstried++; - if (!(fConnModule->IsConnected())) continue; - break; - } else { - // Invalid domain: drop the url and move to next, if any - urlList.Erase(thisUrl); - continue; - } - } - } - if (nogoodurl) { - OpenErr(kXR_NotAuthorized, "Access denied to all URL domains requested"); - break; - } - - // We are connected to a host. Let's handshake with it. - if (fConnModule->IsConnected()) { - - // Now the have the logical Connection ID, that we can use as streamid for - // communications with the server - - Info(XrdClientDebug::kHIDEBUG, "Open", - "The logical connection id is " << fConnModule->GetLogConnID() << - "."); - - fConnModule->SetUrl(*thisUrl); - fUrl = *thisUrl; - - Info(XrdClientDebug::kHIDEBUG, "Open", "Working url is " << thisUrl->GetUrl()); - - // after connection deal with server - if (!fConnModule->GetAccessToSrv()) { - - if (fConnModule->GetRedirCnt() >= fConnModule->GetMaxRedirCnt()) { - // We have been so unlucky. - // The max number of redirections was exceeded while logging in - fConnModule->Disconnect(TRUE); - OpenErr(kXR_ServerError, "Unable to connect; too many redirections."); - break; - } - - if (fConnModule->LastServerError.errnum == kXR_NotAuthorized) { - if (urlstried == urlList.Size()) { - // Authentication error: we tried all the indicated URLs: - // does not make much sense to retry - fConnModule->Disconnect(TRUE); - XrdOucString msg(fConnModule->LastServerError.errmsg); - msg.erasefromend(1); - Error("Open", "Authentication failure: " << msg); - connectTry = connectMaxTry; - } else { - XrdOucString msg(fConnModule->LastServerError.errmsg); - msg.erasefromend(1); - Info(XrdClientDebug::kHIDEBUG, "Open", - "Authentication failure: " << msg); - } - } else { - fConnModule->Disconnect(TRUE); - Error("Open", "Access to server failed: error: " << - fConnModule->LastServerError.errnum << " (" << - fConnModule->LastServerError.errmsg << ") - retrying."); - } - } - else { - Info(XrdClientDebug::kUSERDEBUG, "Open", "Access to server granted."); - break; - } - } - - // The server denied access. We have to disconnect. - Info(XrdClientDebug::kHIDEBUG, "Open", "Disconnecting."); - - fConnModule->Disconnect(FALSE); - - if (connectTry < connectMaxTry-1) { - - if (DebugLevel() >= XrdClientDebug::kUSERDEBUG) - Info(XrdClientDebug::kUSERDEBUG, "Open", - "Connection attempt failed. Sleeping " << - EnvGetLong(NAME_RECONNECTWAIT) << " seconds."); - - sleep(EnvGetLong(NAME_RECONNECTWAIT)); - - } - - } //for connect try - - if (!fConnModule->IsConnected()) { - if (fConnModule->LastServerError.errnum == kXR_noErrorYet) - {OpenErr(kXR_noserver, "Server is unreachable.");} - return FALSE; - } - - // - // Variable initialization - // If the server is a new xrootd ( load balancer or data server) - // - if ((fConnModule->GetServerType() != kSTRootd) && - (fConnModule->GetServerType() != kSTNone)) { - // Now we are connected to a server that didn't redirect us after the - // login/auth phase - // let's continue with the openfile sequence - - Info(XrdClientDebug::kUSERDEBUG, - "Open", "Opening the remote file " << fUrl.File); - - if (!TryOpen(mode, options, doitparallel)) { - Error("Open", "Error opening the file " << - fUrl.File << " on host " << fUrl.Host << ":" << - fUrl.Port); - - if (fXrdCcb && !doitparallel) - fXrdCcb->OpenComplete(this, fXrdCcbArg, false); - OpenErr(kXR_Cancelled, "Open failed for unknown reason."); - - return FALSE; - - } else { - - if (doitparallel) { - Info(XrdClientDebug::kUSERDEBUG, "Open", "File open in progress."); - } - else { - Info(XrdClientDebug::kUSERDEBUG, "Open", "File opened succesfully."); - if (fXrdCcb) - fXrdCcb->OpenComplete(this, fXrdCcbArg, true); - } - - } - - } else { - // the server is an old rootd - if (fConnModule->GetServerType() == kSTRootd) { - OpenErr(kXR_ArgInvalid, "Server is not an xrootd server."); - return FALSE; - } - if (fConnModule->GetServerType() == kSTNone) { - OpenErr(kXR_ArgInvalid, "Server is not an xrootd server."); - return FALSE; - } - } - - - fConnModule->LastServerError.errnum = kXR_noErrorYet; - return TRUE; - -} - -//_____________________________________________________________________________ -int XrdClient::Read(void *buf, long long offset, int len) { - XrdClientIntvList cacheholes; - long blkstowait; - char *tmpbuf = (char *)buf; - - Info( XrdClientDebug::kHIDEBUG, "Read", - "Read(offs=" << offset << - ", len=" << len << ")" ); - - if (!IsOpen_wait()) { - Error("Read", "File not opened."); - return 0; - } - - // Set the max transaction duration - fConnModule->SetOpTimeLimit(EnvGetLong(NAME_TRANSACTIONTIMEOUT)); - - fCounters.ReadRequests++; - - int cachesize = 0; - long long cachebytessubmitted = 0; - long long cachebyteshit = 0; - long long cachemisscount = 0; - float cachemissrate = 0.0; - long long cachereadreqcnt = 0; - float cachebytesusefulness = 0.0; - bool cachegood = fConnModule->GetCacheInfo(cachesize, cachebytessubmitted, - cachebyteshit, cachemisscount, - cachemissrate, cachereadreqcnt, - cachebytesusefulness); - - - // Note: old servers do not support unsolicited responses for reads - // We also use the plain sync reading if the size of the block is excessive - // or no cache at all is used - if (!fUseCache || !cachegood || - (cachesize < len) || - (fConnModule->GetServerProtocol() < 0x00000270) ) { - // Without caching - - // Prepare a request header - ClientRequest readFileRequest; - memset( &readFileRequest, 0, sizeof(readFileRequest) ); - fConnModule->SetSID(readFileRequest.header.streamid); - readFileRequest.read.requestid = kXR_read; - memcpy( readFileRequest.read.fhandle, fHandle, sizeof(fHandle) ); - readFileRequest.read.offset = offset; - readFileRequest.read.rlen = len; - readFileRequest.read.dlen = 0; - - if (!fConnModule->SendGenCommand(&readFileRequest, 0, 0, (void *)buf, - FALSE, (char *)"ReadBuffer")) return 0; - - fCounters.ReadBytes += fConnModule->LastServerResp.dlen; - return fConnModule->LastServerResp.dlen; - } - - - // Ok, from now on we are sure that we have to deal with the cache - - // Do the read ahead - long long araoffset; - long aralen; - if (fReadAheadMgr && fUseCache && - !fReadAheadMgr->GetReadAheadHint(offset, len, araoffset, aralen, fReadTrimBlockSize) && - fConnModule->CacheWillFit(aralen)) { - - long long o = araoffset; - long l = aralen; - - while (l > 0) { - long ll = xrdmin(4*1024*1024, l); - Read_Async(o, ll, true); - l -= ll; - o += ll; - } - - } - - struct XrdClientStatInfo stinfo; - Stat(&stinfo); - len = xrdmax(0, xrdmin(len, stinfo.size - offset)); - - bool retrysync = false; - long totbytes = 0; - bool cachehit = true; - - // we cycle until we get all the needed data - do { - fReadWaitData->Lock(); - - cacheholes.Clear(); - blkstowait = 0; - long bytesgot = 0; - - - - - - if (!retrysync) { - - - - - - bytesgot = fConnModule->GetDataFromCache(tmpbuf+totbytes, offset + totbytes, - len + offset - 1, - true, - cacheholes, blkstowait); - - totbytes += bytesgot; - - Info(XrdClientDebug::kHIDEBUG, "Read", - "Cache response: got " << bytesgot << "@" << offset + totbytes << " bytes. Holes= " << - cacheholes.GetSize() << " Outstanding= " << blkstowait); - - // If the cache gives the data to us - // we don't need to ask the server for them... in principle! - if( bytesgot >= len ) { - - // The cache gave us all the requested data - - Info(XrdClientDebug::kHIDEBUG, "Read", - "Found data in cache. len=" << len << - " offset=" << offset); - - fReadWaitData->UnLock(); - - if (cachehit) fCounters.ReadHits++; - fCounters.ReadBytes += len; - return len; - } - - - // We are here if the cache did not give all the data to us - // We should have a list of blocks to request - for (int i = 0; i < cacheholes.GetSize(); i++) { - long long o; - long l; - - o = cacheholes[i].beginoffs; - l = cacheholes[i].endoffs - o + 1; - - - Info( XrdClientDebug::kUSERDEBUG, "Read", - "Hole in the cache: offs=" << o << - ", len=" << l ); - - - XrdClientReadAheadMgr::TrimReadRequest(o, l, 0, fReadTrimBlockSize); - - Read_Async(o, l, false); - - cachehit = false; - } - - - } - - // If we got nothing from the cache let's do it sync and exit! - // Note that this part has the side effect of triggering the recovery actions - // if we get here after an error (or timeout) - // Hence it's not a good idea to make async also this read - // Remember also that a sync read request must not be modified if it's going to be - // written into the application-given buffer - if (retrysync || (!bytesgot && !blkstowait && !cacheholes.GetSize())) { - - cachehit = false; - - fReadWaitData->UnLock(); - - memset(&fConnModule->LastServerError, 0, sizeof(fConnModule->LastServerError)); - fConnModule->LastServerError.errnum = kXR_noErrorYet; - - Info( XrdClientDebug::kHIDEBUG, "Read", - "Read(offs=" << offset << - ", len=" << len << "). Going sync." ); - - if ((fReadTrimBlockSize > 0) && !retrysync) { - long long offs = offset; - long l = len; - - XrdClientReadAheadMgr::TrimReadRequest(offs, l, 0, fReadTrimBlockSize); - Read_Async(offs, l, false); - blkstowait++; - } else { - - // Prepare a request header - ClientRequest readFileRequest; - memset( &readFileRequest, 0, sizeof(readFileRequest) ); - fConnModule->SetSID(readFileRequest.header.streamid); - readFileRequest.read.requestid = kXR_read; - memcpy( readFileRequest.read.fhandle, fHandle, sizeof(fHandle) ); - readFileRequest.read.offset = offset; - readFileRequest.read.rlen = len; - readFileRequest.read.dlen = 0; - - if (!fConnModule->SendGenCommand(&readFileRequest, 0, 0, (void *)buf, - FALSE, (char *)"ReadBuffer")) - return 0; - - fCounters.ReadBytes += len; - return len; - } - - retrysync = false; - } - - // Now it's time to sleep - // This thread will be awakened when new data will arrive - if ( (blkstowait > 0) || cacheholes.GetSize() ) { - Info( XrdClientDebug::kHIDEBUG, "Read", - "Waiting " << blkstowait+cacheholes.GetSize() << "outstanding blocks." ); - - if (!fConnModule->IsPhyConnConnected() || - fReadWaitData->Wait( EnvGetLong(NAME_REQUESTTIMEOUT) ) || - (fConnModule->LastServerError.errnum != kXR_noErrorYet) ) { - - fConnModule->LastServerError.errnum = kXR_noErrorYet; - - if (DebugLevel() >= XrdClientDebug::kUSERDEBUG) { - fConnModule->PrintCache(); - - Error( "Read", - "Timeout or error waiting outstanding blocks. " - "Retrying sync! " - "List of outstanding reqs follows." ); - ConnectionManager->SidManager()->PrintoutOutstandingRequests(); - } - - retrysync = true; - } - - - - } - - fReadWaitData->UnLock(); - - } while ((blkstowait > 0) || cacheholes.GetSize()); - - // To lower caching overhead in copy-like applications - if (EnvGetLong(NAME_REMUSEDCACHEBLKS)) { - Info(XrdClientDebug::kHIDEBUG, "Read", - "Removing used blocks " << 0 << "->" << offset ); - fConnModule->RemoveDataFromCache(0, offset); - } - - - if (cachehit) fCounters.ReadHits++; - fCounters.ReadBytes += len; - return len; -} - -//_____________________________________________________________________________ -kXR_int64 XrdClient::ReadV(char *buf, kXR_int64 *offsets, int *lens, int nbuf) -{ - // If buf==0 then the request is considered as asynchronous - - if (!nbuf) return 0; - - if (!IsOpen_wait()) { - Error("ReadV", "File not opened."); - return 0; - } - - // This means problems in getting the protocol version - if ( fConnModule->GetServerProtocol() < 0 ) { - Info(XrdClientDebug::kHIDEBUG, "ReadV", - "Problems retrieving protocol version run by the server" ); - return -1; - } - - // This means the server won't support it - if ( fConnModule->GetServerProtocol() < 0x00000247 ) { - Info(XrdClientDebug::kHIDEBUG, "ReadV", - "The server is an old version " << fConnModule->GetServerProtocol() << - " and doesn't support vectored reading" ); - return -1; - } - - Stat(0); - - - // Set the max transaction duration - fConnModule->SetOpTimeLimit(EnvGetLong(NAME_TRANSACTIONTIMEOUT)); - - // We pre-process the request list in order to make it compliant - // with the restrictions imposed by the server - XrdClientVector reqvect(nbuf); - - // First we want to know how much data we expect - kXR_int64 maxbytes = 0; - for (int ii = 0; ii < nbuf; ii++) - maxbytes += lens[ii]; - - // Then we get a suggestion about the splitsize to use - int spltsize = 0; - int reqsperstream = 0; - XrdClientMStream::GetGoodSplitParameters(fConnModule, spltsize, reqsperstream, maxbytes); - - // To optimize the splitting for the readv, we need the greater multiple - // of READV_MAXCHUNKSIZE... maybe yes, maybe not... - // if (spltsize > 2*READV_MAXCHUNKSIZE) blah blah - - // Each subchunk must not exceed spltsize bytes - for (int ii = 0; ii < nbuf; ii++) - XrdClientReadV::PreProcessChunkRequest(reqvect, offsets[ii], lens[ii], - fStatInfo.size, - spltsize ); - - - int i = 0, startitem = 0; - kXR_int64 res = 0, bytesread = 0; - - if (buf) - fCounters.ReadVRequests++; - else - fCounters.ReadVAsyncRequests++; - - - while ( i < reqvect.GetSize() ) { - - // Here we have the sequence of fixed blocks to request - // We want to request single readv reqs which - // - are compliant with the max number of blocks the server supports - // - do not request more than maxbytes bytes each - kXR_int64 tmpbytes = 0; - - int maxchunkcnt = READV_MAXCHUNKS; - if (EnvGetLong(NAME_MULTISTREAMCNT) > 0) - maxchunkcnt = reqvect.GetSize() / EnvGetLong(NAME_MULTISTREAMCNT)+1; - - if (maxchunkcnt < 2) maxchunkcnt = 2; - if (maxchunkcnt > READV_MAXCHUNKS) maxchunkcnt = READV_MAXCHUNKS; - - int chunkcnt = 0; - while ( i < reqvect.GetSize() ) { - if (chunkcnt >= maxchunkcnt) break; - if (tmpbytes + reqvect[i].len > spltsize) break; - tmpbytes += reqvect[i].len; - chunkcnt++; - i++; - } - - - if (i-startitem == 1) { - if (buf) { - // Synchronous - fCounters.ReadVSubRequests++; - fCounters.ReadVSubChunks++; - fCounters.ReadVBytes += reqvect[startitem].len; - res = Read(buf+bytesread, reqvect[startitem].offset, reqvect[startitem].len); - - } else { - // Asynchronous, res stays the same - fCounters.ReadVAsyncSubRequests++; - fCounters.ReadVAsyncSubChunks++; - fCounters.ReadVAsyncBytes += reqvect[startitem].len; - Read_Async(reqvect[startitem].offset, reqvect[startitem].len, false); - res = reqvect[startitem].len; - } - } else { - if (buf) { - - res = XrdClientReadV::ReqReadV(fConnModule, fHandle, buf+bytesread, - reqvect, startitem, i-startitem, - fConnModule->GetParallelStreamToUse(reqsperstream) ); - fCounters.ReadVSubRequests++; - fCounters.ReadVSubChunks += i-startitem; - fCounters.ReadVBytes += res; - } - else { - res = XrdClientReadV::ReqReadV(fConnModule, fHandle, 0, - reqvect, startitem, i-startitem, - fConnModule->GetParallelStreamToUse(reqsperstream) ); - fCounters.ReadVAsyncSubRequests++; - fCounters.ReadVAsyncSubChunks += i-startitem; - fCounters.ReadVAsyncBytes += res; - } - } - - // The next bunch of chunks to request starts from here - startitem = i; - - if ( res < 0 ) - break; - - bytesread += res; - - } - - if (!buf && !fConnModule->CacheWillFit(bytesread+bytesread/4)) { - Info(XrdClientDebug::kUSERDEBUG, "ReadV", - "Excessive async readv size " << bytesread+bytesread/4 << ". Fixing cache size." ); - SetCacheParameters(bytesread, -1, -1); - } - - // pos will indicate the size of the data read - // Even if we were able to read only a part of the buffer !!! - return bytesread; -} - - -//_____________________________________________________________________________ -bool XrdClient::Write(const void *buf, long long offset, int len) { - - if (!IsOpen_wait()) { - Error("WriteBuffer", "File not opened."); - return FALSE; - } - - - // Set the max transaction duration - fConnModule->SetOpTimeLimit(EnvGetLong(NAME_TRANSACTIONTIMEOUT)); - - fCounters.WrittenBytes += len; - fCounters.WriteRequests++; - - // Prepare request - ClientRequest writeFileRequest; - memset( &writeFileRequest, 0, sizeof(writeFileRequest) ); - fConnModule->SetSID(writeFileRequest.header.streamid); - writeFileRequest.write.requestid = kXR_write; - memcpy( writeFileRequest.write.fhandle, fHandle, sizeof(fHandle) ); - - bool ret = false; - - if (!fUseCache) { - // Silly situation but worth handling - writeFileRequest.write.pathid = 0; - writeFileRequest.write.dlen = len; - writeFileRequest.write.offset = offset; - ret = fConnModule->SendGenCommand(&writeFileRequest, (void *)buf, 0, 0, - FALSE, (char *)"Write"); - - if (ret && fStatInfo.stated) - fStatInfo.size = xrdmax(fStatInfo.size, offset + len); - - return ret; - } - - // Soft checkpoint, we check just for timeouts in old outstanding write requests - // An unrecoverable error in an old request gives no sense to continue here. - // Rather unfortunate but happens. One more weird metaphor of life?!?!? - if (!fConnModule->DoWriteSoftCheckPoint()) return false; - - fConnModule->RemoveDataFromCache(offset, offset+len-1, true); - - XrdClientVector rl; - XrdClientMStream::SplitReadRequest(fConnModule, offset, len, rl); - kXR_char *cbuf = (kXR_char *)buf; - int writtenok = 0; - - for (int i = 0; i < rl.GetSize(); i++) { - - writeFileRequest.write.offset = rl[i].offset; - writeFileRequest.write.dlen = rl[i].len; - writeFileRequest.write.pathid = rl[i].streamtosend; - - // The req is sent only asynchronously. So, the only bottleneck here is the kernel - // and its tcp buffer sizes... and the network of course. But beware of the crappy - // default tcp settings of the various SLCs - XReqErrorType b; - int cnt = 0; - do { - b = fConnModule->WriteToServer_Async(&writeFileRequest, (kXR_char *)buf+(rl[i].offset-offset), rl[i].streamtosend); - ret = (b == kOK); - if (b != kNOMORESTREAMS) break; - - // There are no more slots for outstanding requests - // Asking for a hard checkpoint is a good way to waste some time - // and to wait for some slots to be free - // The only drawback is that the mechanism needs enough memory to fill - // the pipeline given by the network+server latency, or the max number of available slots - if (!fConnModule->DoWriteHardCheckPoint()) break; - } while (cnt < 10); - - if (b != kOK) { - // We need to deal with errors while sending the request - // So, if there is an error or timeout while sending the req, it has to be retried sync - // in order to trigger immediately the normal retry mechanism, if needed - // Try again the write op, but in sync mode - writeFileRequest.write.pathid = 0; - ret = fConnModule->SendGenCommand(&writeFileRequest, (kXR_char *)buf+(rl[i].offset-offset), 0, 0, - FALSE, (char *)"Write"); - if (!ret) break; - } - writtenok += rl[i].len; - cbuf += rl[i].len; - } - - if (ret && fStatInfo.stated) - fStatInfo.size = xrdmax(fStatInfo.size, offset + writtenok); - - return ret; -} - - -//_____________________________________________________________________________ -bool XrdClient::Sync() -{ - // Flushes un-written data - - - if (!IsOpen_wait()) { - Error("Sync", "File not opened."); - return FALSE; - } - - if (!fConnModule->DoWriteHardCheckPoint()) return false; - - - // Set the max transaction duration - fConnModule->SetOpTimeLimit(EnvGetLong(NAME_TRANSACTIONTIMEOUT)); - - // Prepare request - ClientRequest flushFileRequest; - memset( &flushFileRequest, 0, sizeof(flushFileRequest) ); - - fConnModule->SetSID(flushFileRequest.header.streamid); - - flushFileRequest.sync.requestid = kXR_sync; - - memcpy(flushFileRequest.sync.fhandle, fHandle, sizeof(fHandle)); - - flushFileRequest.sync.dlen = 0; - - return fConnModule->SendGenCommand(&flushFileRequest, 0, 0, 0, - FALSE, (char *)"Sync"); - -} - -//_____________________________________________________________________________ -bool XrdClient::TryOpen(kXR_unt16 mode, kXR_unt16 options, bool doitparallel) { - - int thrst = 0; - - fOpenPars.inprogress = true; - - if (doitparallel) { - - for (int i = 0; i < DFLT_MAXCONCURRENTOPENS; i++) { - - fConcOpenSem.Wait(); - fOpenerTh = new XrdClientThread(FileOpenerThread); - - thrst = fOpenerTh->Run(this); - if (!thrst) { - // The thread start seems OK. This open will go in parallel - - //if (fOpenerTh->Detach()) - // Error("XrdClient", "Thread detach failed. Low system resources?"); - - return true; - } - - // Note: the Post() here is intentionally missing. - - Error("XrdClient", "Parallel open thread start failed. Low system" - " resources? Res=" << thrst << " Count=" << i); - delete fOpenerTh; - fOpenerTh = 0; - - } - - // If we are here it seems that this machine cannot start open threads at all - // In this desperate situation we try to go sync anyway. - for (int i = 0; i < DFLT_MAXCONCURRENTOPENS; i++) fConcOpenSem.Post(); - - Error("XrdClient", "All the parallel open thread start attempts failed." - " Desperate situation. Going sync."); - - doitparallel = false; - } - - // First attempt to open a remote file - bool lowopenRes = LowOpen(fUrl.File.c_str(), mode, options); - if (lowopenRes) { - - // And here we fire up the needed parallel streams - XrdClientMStream::EstablishParallelStreams(fConnModule); - int retc; - - if (!fConnModule->IsConnected()) { - fOpenPars.opened = false; - retc = false; - } else retc = true; - - TerminateOpenAttempt(); - return retc; - - } - - //-------------------------------------------------------------------------- - // We have failed during authentication, normally this is fatal, but - // we can check if we have seen a meta manager so that we could ask it to - // send us to another cluster - //-------------------------------------------------------------------------- - if( fConnModule->GetMetaUrl() && - ((fConnModule->GetCurrentUrl().Host != fConnModule->GetMetaUrl()->Host) || - (fConnModule->GetCurrentUrl().Port != fConnModule->GetMetaUrl()->Port)) ) - { - while( fConnModule->LastServerError.errnum == kXR_NotAuthorized ) - { - - if( fConnModule->GetRedirCnt() >= fConnModule->GetMaxRedirCnt() ) - break; - - //---------------------------------------------------------------------- - // We have seen a manager that is not the same as the meta manager, - // so we need to exclude it - //---------------------------------------------------------------------- - if( fConnModule->GetLBSUrl() && - ((fConnModule->GetLBSUrl()->Host != fConnModule->GetMetaUrl()->Host) || - (fConnModule->GetLBSUrl()->Port != fConnModule->GetMetaUrl()->Port) ) ) - { - fExcludedHosts.push_back( fConnModule->GetLBSUrl()->Host.c_str() ); - } - - //---------------------------------------------------------------------- - // We also exclude the current host if different from the manager, - // just in case - //---------------------------------------------------------------------- - if( fConnModule->GetLBSUrl() && - ((fConnModule->GetCurrentUrl().Host != fConnModule->GetLBSUrl()->Host) || - (fConnModule->GetCurrentUrl().Port != fConnModule->GetLBSUrl()->Port) ) ) - { - fExcludedHosts.push_back( fConnModule->GetCurrentUrl().Host.c_str() ); - } - - //---------------------------------------------------------------------- - // Ask the metamanager to redirect us to another host - //---------------------------------------------------------------------- - std::string excludedHosts = "&tried="; - std::vector::iterator it; - for( it = fExcludedHosts.begin(); it != fExcludedHosts.end(); ++it ) - { - excludedHosts += *it; - excludedHosts += ","; - } - - Info( XrdClientDebug::kUSERDEBUG, - "Open", - "Back to " << fConnModule->GetMetaUrl()->Host << " " << - fConnModule->GetMetaUrl()->Port << - ". Refreshing cache. Opaque info: " << excludedHosts ); - - fConnModule->Disconnect( true ); - - if( (fConnModule->GoToMetaManager() == kOK) && - LowOpen( fUrl.File.c_str(), mode, options | kXR_refresh, - (char *)excludedHosts.c_str() ) ) - { - XrdClientMStream::EstablishParallelStreams(fConnModule); - TerminateOpenAttempt(); - return true; - } - } - } - - // If the open request failed for the error "file not found" proceed, - // otherwise return FALSE - if ( (fConnModule->LastServerResp.status != kXR_error) || - ((fConnModule->LastServerResp.status == kXR_error) && - (fConnModule->LastServerError.errnum != kXR_NotFound)) ){ - - TerminateOpenAttempt(); - - return FALSE; - } - - // If connected to a host saying "File not Found" or similar then... - - // If we are currently connected to a host which is different - // from the one we formerly connected, then we resend the request - // specifyng the supposed failing server as opaque info - if (fConnModule->GetLBSUrl() && - ( (fConnModule->GetCurrentUrl().Host != fConnModule->GetLBSUrl()->Host) || - (fConnModule->GetCurrentUrl().Port != fConnModule->GetLBSUrl()->Port) ) ) { - XrdOucString opinfo; - - opinfo = "&tried=" + fConnModule->GetCurrentUrl().Host; - - Info(XrdClientDebug::kUSERDEBUG, - "Open", "Back to " << fConnModule->GetLBSUrl()->Host << - ". Refreshing cache. Opaque info: " << opinfo); - - // First disconnect the current logical connection (otherwise spurious - // connection will stay around and create problems with processing of - // unsolicited messages) - fConnModule->Disconnect(FALSE); - - if ( (fConnModule->GoToAnotherServer(*fConnModule->GetLBSUrl()) == kOK) && - LowOpen(fUrl.File.c_str(), mode, options | kXR_refresh, - (char *)opinfo.c_str() ) ) { - - // And here we fire up the needed parallel streams - XrdClientMStream::EstablishParallelStreams(fConnModule); - - TerminateOpenAttempt(); - - return TRUE; - } - else { - - Error("Open", "Error opening the file."); - TerminateOpenAttempt(); - - return FALSE; - } - - } - - TerminateOpenAttempt(); - return FALSE; - -} - -//_____________________________________________________________________________ -bool XrdClient::LowOpen(const char *file, kXR_unt16 mode, kXR_unt16 options, - char *additionalquery) { - - // Low level Open method - XrdOucString finalfilename(file); - - if ((fConnModule->fRedirOpaque.length() > 0) || additionalquery) { - finalfilename += "?"; - - if (fConnModule->fRedirOpaque.length() > 0) - finalfilename += fConnModule->fRedirOpaque; - - if (additionalquery) - finalfilename += additionalquery; - } - - - - - - // Send a kXR_open request in order to open the remote file - ClientRequest openFileRequest; - - char buf[1024]; - struct ServerResponseBody_Open *openresp = (struct ServerResponseBody_Open *)buf; - - memset(&openFileRequest, 0, sizeof(openFileRequest)); - - fConnModule->SetSID(openFileRequest.header.streamid); - - openFileRequest.header.requestid = kXR_open; - - openFileRequest.open.options = options | kXR_retstat; - - // Set the open mode field - openFileRequest.open.mode = mode; - - // Set the length of the data (in this case data describes the path and - // file name) - openFileRequest.open.dlen = finalfilename.length(); - - // Send request to server and receive response - bool resp = fConnModule->SendGenCommand(&openFileRequest, - (const void *)finalfilename.c_str(), - 0, openresp, false, (char *)"Open"); - - if (resp && (fConnModule->LastServerResp.status == 0)) { - // Get the file handle to use for future read/write... - if (fConnModule->LastServerResp.dlen >= (kXR_int32)sizeof(fHandle)) { - - memcpy( fHandle, openresp->fhandle, sizeof(fHandle) ); - - fOpenPars.opened = TRUE; - fOpenPars.options = options; - fOpenPars.mode = mode; - } - else - Error("Open", - "Server did not return a filehandle. Protocol error."); - - if (fConnModule->LastServerResp.dlen > 12) { - // Get the stats - Info(XrdClientDebug::kHIDEBUG, - "Open", "Returned stats=" << ((char *)openresp + sizeof(struct ServerResponseBody_Open))); - - sscanf((char *)openresp + sizeof(struct ServerResponseBody_Open), "%ld %lld %ld %ld", - &fStatInfo.id, - &fStatInfo.size, - &fStatInfo.flags, - &fStatInfo.modtime); - - fStatInfo.stated = true; - } - - } - - - return fOpenPars.opened; -} - -//_____________________________________________________________________________ -bool XrdClient::Stat(struct XrdClientStatInfo *stinfo, bool force) { - - if (!force && fStatInfo.stated) { - if (stinfo) - memcpy(stinfo, &fStatInfo, sizeof(fStatInfo)); - return TRUE; - } - - if (!IsOpen_wait()) { - Error("Stat", "File not opened."); - return FALSE; - } - - if (force && !Sync()) return false; - - // asks the server for stat file informations - ClientRequest statFileRequest; - - memset(&statFileRequest, 0, sizeof(ClientRequest)); - - fConnModule->SetSID(statFileRequest.header.streamid); - - statFileRequest.stat.requestid = kXR_stat; - memset(statFileRequest.stat.reserved, 0, - sizeof(statFileRequest.stat.reserved)); - - statFileRequest.stat.dlen = fUrl.File.length(); - - char fStats[2048]; - memset(fStats, 0, 2048); - - bool ok = fConnModule->SendGenCommand(&statFileRequest, - (const char*)fUrl.File.c_str(), - 0, fStats , FALSE, (char *)"Stat"); - - if (ok && (fConnModule->LastServerResp.status == 0) ) { - - Info(XrdClientDebug::kHIDEBUG, - "Stat", "Returned stats=" << fStats); - - sscanf(fStats, "%ld %lld %ld %ld", - &fStatInfo.id, - &fStatInfo.size, - &fStatInfo.flags, - &fStatInfo.modtime); - - if (stinfo) - memcpy(stinfo, &fStatInfo, sizeof(fStatInfo)); - - fStatInfo.stated = true; - } - - return ok; -} - -//_____________________________________________________________________________ -bool XrdClient::Close() { - - if (!IsOpen_wait()) { - Info(XrdClientDebug::kUSERDEBUG, "Close", "File not opened."); - return TRUE; - } - - ClientRequest closeFileRequest; - - // Set the max transaction duration - fConnModule->SetOpTimeLimit(EnvGetLong(NAME_TRANSACTIONTIMEOUT)); - - memset(&closeFileRequest, 0, sizeof(closeFileRequest) ); - - fConnModule->SetSID(closeFileRequest.header.streamid); - - closeFileRequest.close.requestid = kXR_close; - memcpy(closeFileRequest.close.fhandle, fHandle, sizeof(fHandle) ); - closeFileRequest.close.dlen = 0; - - // Use the sync one only if the file was opened for writing - // To enforce the server side correct data flushing - bool status1 = true; - if (IsOpenedForWrite()) - if( !fConnModule->DoWriteHardCheckPoint() ) - status1 = false; - - bool status2 = fConnModule->SendGenCommand(&closeFileRequest, - 0, - 0, 0 , FALSE, (char *)"Close"); - - // No file is opened for now - fOpenPars.opened = FALSE; - fConnModule->Disconnect( false ); - - return status1 && status2; -} - - -//_____________________________________________________________________________ -bool XrdClient::OpenFileWhenRedirected(char *newfhandle, bool &wasopen) -{ - // Called by the comm module when it needs to reopen a file - // after a redir - - wasopen = fOpenPars.opened; - - if (!fOpenPars.opened) - return TRUE; - - fOpenPars.opened = FALSE; - - Info(XrdClientDebug::kHIDEBUG, - "OpenFileWhenRedirected", "Trying to reopen the same file." ); - - kXR_unt16 options = fOpenPars.options; - - if (fOpenPars.options & kXR_delete) { - Info(XrdClientDebug::kHIDEBUG, - "OpenFileWhenRedirected", "Stripping off the 'delete' option." ); - - options &= ~kXR_delete; - options |= kXR_open_updt; - } - - if (fOpenPars.options & kXR_new) { - Info(XrdClientDebug::kHIDEBUG, - "OpenFileWhenRedirected", "Stripping off the 'new' option." ); - - options &= ~kXR_new; - options |= kXR_open_updt; - } - - if ( TryOpen(fOpenPars.mode, options, false) ) { - - fOpenPars.opened = TRUE; - - Info(XrdClientDebug::kHIDEBUG, - "OpenFileWhenRedirected", - "Open successful." ); - - memcpy(newfhandle, fHandle, sizeof(fHandle)); - - return TRUE; - } else { - Error("OpenFileWhenRedirected", - "File open failed."); - - return FALSE; - } -} - -//_____________________________________________________________________________ -bool XrdClient::Copy(const char *localpath) { - - if (!IsOpen_wait()) { - Error("Copy", "File not opened."); - return FALSE; - } - - Stat(0); - int f = open(localpath, O_CREAT | O_RDWR, 0); - if (f < 0) { - Error("Copy", "Error opening local file."); - return FALSE; - } - - void *buf = malloc(100000); - long long offs = 0; - int nr = 1; - - while ((nr > 0) && (offs < fStatInfo.size)) - if ( (nr = Read(buf, offs, 100000)) ) - offs += write(f, buf, nr); - - close(f); - free(buf); - - return TRUE; -} - -//_____________________________________________________________________________ -UnsolRespProcResult XrdClient::ProcessUnsolicitedMsg(XrdClientUnsolMsgSender *sender, - XrdClientMessage *unsolmsg) { - // We are here if an unsolicited response comes from a logical conn - // The response comes in the form of a TXMessage *, that must NOT be - // destroyed after processing. It is destroyed by the first sender. - // Remember that we are in a separate thread, since unsolicited - // responses are asynchronous by nature. - - if ( unsolmsg->GetStatusCode() != XrdClientMessage::kXrdMSC_ok ) { - Info(XrdClientDebug::kHIDEBUG, - "ProcessUnsolicitedMsg", "Incoming unsolicited communication error message." ); - } - else { - Info(XrdClientDebug::kHIDEBUG, - "ProcessUnsolicitedMsg", "Incoming unsolicited response from streamid " << - unsolmsg->HeaderSID() ); - } - - // Local processing .... - - if (unsolmsg->IsAttn()) { - struct ServerResponseBody_Attn *attnbody; - - attnbody = (struct ServerResponseBody_Attn *)unsolmsg->GetData(); - - int actnum = (attnbody) ? (attnbody->actnum) : 0; - - // "True" async resp is processed here - switch (actnum) { - - case kXR_asyncdi: - // Disconnection + delayed reconnection request - - struct ServerResponseBody_Attn_asyncdi *di; - di = (struct ServerResponseBody_Attn_asyncdi *)unsolmsg->GetData(); - - // Explicit redirection request - if (di) { - Info(XrdClientDebug::kUSERDEBUG, - "ProcessUnsolicitedMsg", "Requested Disconnection + Reconnect in " << - ntohl(di->wsec) << " seconds."); - - fConnModule->SetRequestedDestHost((char *)fUrl.Host.c_str(), fUrl.Port); - fConnModule->SetREQDelayedConnectState(ntohl(di->wsec)); - } - - // Other objects may be interested in this async resp - return kUNSOL_CONTINUE; - break; - - case kXR_asyncrd: - // Redirection request - - struct ServerResponseBody_Attn_asyncrd *rd; - rd = (struct ServerResponseBody_Attn_asyncrd *)unsolmsg->GetData(); - - // Explicit redirection request - if (rd && (strlen(rd->host) > 0)) { - Info(XrdClientDebug::kUSERDEBUG, - "ProcessUnsolicitedMsg", "Requested redir to " << rd->host << - ":" << ntohl(rd->port)); - - fConnModule->SetRequestedDestHost(rd->host, ntohl(rd->port)); - } - - // Other objects may be interested in this async resp - return kUNSOL_CONTINUE; - break; - - case kXR_asyncwt: - // Puts the client in wait state - - struct ServerResponseBody_Attn_asyncwt *wt; - wt = (struct ServerResponseBody_Attn_asyncwt *)unsolmsg->GetData(); - - if (wt) { - Info(XrdClientDebug::kUSERDEBUG, - "ProcessUnsolicitedMsg", "Pausing client for " << ntohl(wt->wsec) << - " seconds."); - - fConnModule->SetREQPauseState(ntohl(wt->wsec)); - } - - // Other objects may be interested in this async resp - return kUNSOL_CONTINUE; - break; - - case kXR_asyncgo: - // Resumes from pause state - - Info(XrdClientDebug::kUSERDEBUG, - "ProcessUnsolicitedMsg", "Resuming from pause."); - - fConnModule->SetREQPauseState(0); - - // Other objects may be interested in this async resp - return kUNSOL_CONTINUE; - break; - - case kXR_asynresp: - // A response to a request which got a kXR_waitresp as a response - - // We pass it direcly to the connmodule for processing - // The processing will tell if the streamid matched or not, - // in order to stop further processing - return fConnModule->ProcessAsynResp(unsolmsg); - break; - - default: - - Info(XrdClientDebug::kUSERDEBUG, - "ProcessUnsolicitedMsg", "Empty message"); - - // Propagate the message - return kUNSOL_CONTINUE; - - } // switch - - - } - else - // Let's see if the message is a communication error message - if (unsolmsg->GetStatusCode() != XrdClientMessage::kXrdMSC_ok){ - // This is a low level error. The outstanding things have to be terminated - // Awaken all the waiting threads, some of them may be interested - fReadWaitData->Broadcast(); - TerminateOpenAttempt(); - - return fConnModule->ProcessAsynResp(unsolmsg); - } - else - // Let's see if we are receiving the response to an async read/write request - if ( ConnectionManager->SidManager()->JoinedSids(fConnModule->GetStreamID(), - unsolmsg->HeaderSID()) ) { - struct SidInfo *si = - ConnectionManager->SidManager()->GetSidInfo(unsolmsg->HeaderSID()); - - if (!si) { - Error("ProcessUnsolicitedMsg", - "Orphaned streamid detected: " << unsolmsg->HeaderSID()); - return kUNSOL_DISPOSE; - } - - - ClientRequest *req = &(si->outstandingreq); - - Info(XrdClientDebug::kHIDEBUG, - "ProcessUnsolicitedMsg", - "Processing async response from streamid " << - unsolmsg->HeaderSID() << " father=" << - si->fathersid ); - - // We are interested in data, not errors here... - if ( (unsolmsg->HeaderStatus() == kXR_oksofar) || - (unsolmsg->HeaderStatus() == kXR_ok) ) { - - switch (req->header.requestid) { - - case kXR_read: { - long long offs = req->read.offset + si->reqbyteprogress; - - Info(XrdClientDebug::kHIDEBUG, "ProcessUnsolicitedMsg", - "Putting kXR_read data into cache. Offset=" << - offs << - " len " << - unsolmsg->fHdr.dlen); - - { - // Keep in sync with the cache lookup - XrdSysCondVarHelper cndh(fReadWaitData); - - // To compute the end offset of the block we have to take 1 from the size! - fConnModule->SubmitDataToCache(unsolmsg, offs, - offs + unsolmsg->fHdr.dlen - 1); - - } - si->reqbyteprogress += unsolmsg->fHdr.dlen; - - // Awaken all the waiting threads, some of them may be interested - fReadWaitData->Broadcast(); - - if (unsolmsg->HeaderStatus() == kXR_ok) return kUNSOL_DISPOSE; - else return kUNSOL_KEEP; - - break; - } - - case kXR_readv: { - - Info(XrdClientDebug::kHIDEBUG, "ProcessUnsolicitedMsg", - "Putting kXR_readV data into cache. " << - " len " << - unsolmsg->fHdr.dlen); - { - // Keep in sync with the cache lookup - XrdSysCondVarHelper cndh(fReadWaitData); - - XrdClientReadV::SubmitToCacheReadVResp(fConnModule, (char *)unsolmsg->DonateData(), - unsolmsg->fHdr.dlen); - } - // Awaken all the sleepers. Some of them may be interested - fReadWaitData->Broadcast(); - - if (unsolmsg->HeaderStatus() == kXR_ok) return kUNSOL_DISPOSE; - else return kUNSOL_KEEP; - - break; - } - - - case kXR_write: { - Info(XrdClientDebug::kHIDEBUG, "ProcessUnsolicitedMsg", - "Got positive ack for write req " << req->header.dlen << - "@" << req->write.offset); - - // the corresponding cache blk has to be unpinned, and eventually - // purged - fConnModule->UnPinCacheBlk(req->write.offset, req->write.offset+req->header.dlen); - - // A bit cpu consuming... need to optimize this - if (EnvGetLong(NAME_PURGEWRITTENBLOCKS)) - fConnModule->RemoveDataFromCache(req->write.offset, req->write.offset+req->header.dlen-1, true); - - // This streamid will be released - return kUNSOL_DISPOSE; - } - } - - } // if oksofar or ok - else { - // And here we treat the errors which can be fatal or just ugly to ignore - // even if the strategy should be completely async - switch (req->header.requestid) { - - case kXR_read: { - // We invalidate the whole request in the cache - - Error("ProcessUnsolicitedMsg", - "Got a kxr_read error. Req offset=" << - req->read.offset << - " len=" << - req->read.rlen); - - { - // Keep in sync with the cache lookup - XrdSysCondVarHelper cndh(fReadWaitData); - - // To compute the end offset of the block we have to take 1 from the size! - // Note that this is an error, we try to invalidate everythign which - // can be related to this chunk - fConnModule->RemoveDataFromCache(req->read.offset, - req->read.offset + req->read.rlen - 1, true); - - } - - - // Print out the error information, as received by the server - struct ServerResponseBody_Error *body_err; - body_err = (struct ServerResponseBody_Error *)(unsolmsg->GetData()); - if (body_err) - Info(XrdClientDebug::kNODEBUG, "ProcessUnsolicitedMsg", "Server declared: " << - (const char*)body_err->errmsg << "(error code: " << ntohl(body_err->errnum) << ")"); - - // Save the last error received - memset(&fConnModule->LastServerError, 0, sizeof(fConnModule->LastServerError)); - memcpy(&fConnModule->LastServerError, body_err, - xrdmin(sizeof(fConnModule->LastServerError), (unsigned)unsolmsg->DataLen()) ); - fConnModule->LastServerError.errnum = ntohl(body_err->errnum); - - - // Awaken all the waiting threads, some of them may be interested - fReadWaitData->Broadcast(); - - // Other clients might be interested - return kUNSOL_CONTINUE; - - break; - } - case kXR_write: { - Error("ProcessUnsolicitedMsg", - "Got a kxr_write error. Req offset=" << - req->write.offset << - " len=" << - req->write.dlen); - - - // Print out the error information, as received by the server - struct ServerResponseBody_Error *body_err; - body_err = (struct ServerResponseBody_Error *)(unsolmsg->GetData()); - if (body_err) { - Info(XrdClientDebug::kNODEBUG, "ProcessUnsolicitedMsg", "Server declared: " << - (const char*)body_err->errmsg << "(error code: " << ntohl(body_err->errnum) << ") writing " << - req->write.dlen << "@" << req->write.offset); - - // Save the last error received - memset(&fConnModule->LastServerError, 0, sizeof(fConnModule->LastServerError)); - memcpy(&fConnModule->LastServerError, body_err, - xrdmin(sizeof(fConnModule->LastServerError), (unsigned)unsolmsg->DataLen()) ); - fConnModule->LastServerError.errnum = ntohl(body_err->errnum); - - // Mark the request as an error. It will be catched by the next write soft checkpoint - ConnectionManager->SidManager()->ReportSidResp(unsolmsg->HeaderSID(), - unsolmsg->GetStatusCode(), - ntohl(body_err->errnum), - body_err->errmsg); - } - else - ConnectionManager->SidManager()->ReportSidResp(unsolmsg->HeaderSID(), - unsolmsg->GetStatusCode(), - kXR_noErrorYet, - 0); - - // Awaken all the waiting threads, some of them may be interested - fReadWaitData->Broadcast(); - - // This streamid must be kept as pending. It will be handled by the subsequent - // write checkpoint - return kUNSOL_KEEP; - - break; - } - - } // switch - } // else - - - - - - - } - - - return kUNSOL_CONTINUE; -} - -XReqErrorType XrdClient::Read_Async(long long offset, int len, bool updatecounters) { - - if (!IsOpen_wait()) { - Error("Read", "File not opened."); - return kGENERICERR; - } - - Stat(0); - len = xrdmin(fStatInfo.size - offset, len); - - if (len <= 0) return kOK; - - if (fUseCache) - fConnModule->SubmitPlaceholderToCache(offset, offset+len-1); - else return kOK; - - if (updatecounters) { - fCounters.ReadAsyncRequests++; - fCounters.ReadAsyncBytes += len; - } - - // Prepare request - ClientRequest readFileRequest; - memset( &readFileRequest, 0, sizeof(readFileRequest) ); - - // No need to initialize the streamid, it will be filled by XrdClientConn - readFileRequest.read.requestid = kXR_read; - memcpy( readFileRequest.read.fhandle, fHandle, sizeof(fHandle) ); - readFileRequest.read.offset = offset; - readFileRequest.read.rlen = len; - readFileRequest.read.dlen = 0; - - Info(XrdClientDebug::kHIDEBUG, "Read_Async", - "Requesting to read " << - readFileRequest.read.rlen << - " bytes of data at offset " << - readFileRequest.read.offset); - - // This request might be splitted and distributed through multiple streams - XrdClientVector chunks; - XReqErrorType ok = kOK; - - if (XrdClientMStream::SplitReadRequest(fConnModule, offset, len, - chunks) ) { - - for (int i = 0; i < chunks.GetSize(); i++) { - XrdClientMStream::ReadChunk *c; - - read_args args; - memset(&args, 0, sizeof(args)); - - c = &chunks[i]; - args.pathid = c->streamtosend; - - Info(XrdClientDebug::kHIDEBUG, "Read_Async", - "Requesting pathid " << c->streamtosend); - - readFileRequest.read.offset = c->offset; - readFileRequest.read.rlen = c->len; - - if (args.pathid != 0) { - readFileRequest.read.dlen = sizeof(read_args); - ok = fConnModule->WriteToServer_Async(&readFileRequest, &args, - 0); - } - else { - readFileRequest.read.dlen = 0; - ok = fConnModule->WriteToServer_Async(&readFileRequest, 0, - 0); - } - - - if (ok != kOK) break; - } - } - else - return (fConnModule->WriteToServer_Async(&readFileRequest, 0)); - - return ok; - -} - -//_____________________________________________________________________________ -// Truncates the open file at a specified length -bool XrdClient::Truncate(long long len) { - - if (!IsOpen_wait()) { - Info(XrdClientDebug::kUSERDEBUG, "Truncate", "File not opened."); - return true; - } - - ClientRequest truncFileRequest; - - memset(&truncFileRequest, 0, sizeof(truncFileRequest) ); - - fConnModule->SetSID(truncFileRequest.header.streamid); - - truncFileRequest.truncate.requestid = kXR_truncate; - memcpy(truncFileRequest.truncate.fhandle, fHandle, sizeof(fHandle) ); - truncFileRequest.truncate.offset = len; - - bool ok = fConnModule->SendGenCommand(&truncFileRequest, - 0, - 0, 0 , FALSE, (char *)"Truncate"); - - if (ok && fStatInfo.stated) fStatInfo.size = len; - - return ok; -} - - - -//_____________________________________________________________________________ -// Sleeps on a condvar which is signalled when a new async block arrives -void XrdClient::WaitForNewAsyncData() { - XrdSysCondVarHelper cndh(fReadWaitData); - - fReadWaitData->Wait(); - -} - -//_____________________________________________________________________________ -bool XrdClient::UseCache(bool u) -{ - // Set use of cache flag after checking if the requested value make sense. - // Returns the previous value to allow quick toggling of the flag. - - bool r = fUseCache; - - if (!u) { - fUseCache = false; - } else { - int size; - long long bytessubmitted, byteshit, misscount, readreqcnt; - float missrate, bytesusefulness; - - - if ( fConnModule && - fConnModule->GetCacheInfo(size, bytessubmitted, byteshit, misscount, missrate, readreqcnt, bytesusefulness) && - size ) - fUseCache = true; - } - - // Return the previous setting - return r; -} - - -// To set at run time the cache/readahead parameters for this instance only -// If a parameter is < 0 then it's left untouched. -// To simply enable/disable the caching, just use UseCache(), not this function -void XrdClient::SetCacheParameters(int CacheSize, int ReadAheadSize, int RmPolicy) { - if (fConnModule) { - if (CacheSize >= 0) fConnModule->SetCacheSize(CacheSize); - if (RmPolicy >= 0) fConnModule->SetCacheRmPolicy(RmPolicy); - } - - if ((ReadAheadSize >= 0) && fReadAheadMgr) fReadAheadMgr->SetRASize(ReadAheadSize); -} - -// To enable/disable different read ahead strategies. Defined in XrdClientReadAhead.hh -void XrdClient::SetReadAheadStrategy(int strategy) { - if (!fConnModule) return; - - if (fReadAheadMgr && fReadAheadMgr->GetCurrentStrategy() != (XrdClientReadAheadMgr::XrdClient_RAStrategy)strategy) { - - delete fReadAheadMgr; - fReadAheadMgr = 0; - } - - if (!fReadAheadMgr) - fReadAheadMgr = XrdClientReadAheadMgr::CreateReadAheadMgr((XrdClientReadAheadMgr::XrdClient_RAStrategy)strategy); -} - -// To enable the trimming of the blocks to read. Blocksize will be rounded to a multiple of 512. -// Each read request will have the offset and length aligned with a multiple of blocksize -// This strategy is similar to a read ahead, but not quite. It anyway needs to have the cache enabled to work. -// Here we see it as a transformation of the stream of the read accesses to request. -void XrdClient::SetBlockReadTrimming(int blocksize) { - blocksize = blocksize >> 9; - blocksize = blocksize << 9; - if (blocksize < 512) blocksize = 512; - - fReadTrimBlockSize = blocksize; -} - - -bool XrdClient::GetCacheInfo( - // The actual cache size - int &size, - - // The number of bytes submitted since the beginning - long long &bytessubmitted, - - // The number of bytes found in the cache (estimate) - long long &byteshit, - - // The number of reads which did not find their data - // (estimate) - long long &misscount, - - // miss/totalreads ratio (estimate) - float &missrate, - - // number of read requests towards the cache - long long &readreqcnt, - - // ratio between bytes found / bytes submitted - float &bytesusefulness - ) { - if (!fConnModule) return false; - - - if (!fConnModule->GetCacheInfo(size, - bytessubmitted, - byteshit, - misscount, - missrate, - readreqcnt, - bytesusefulness)) - return false; - - return true; -} - -// Returns client-level information about the activity performed up to now -bool XrdClient::GetCounters( XrdClientCounters *cnt ) { - - fCounters.ReadMisses = fCounters.ReadRequests-fCounters.ReadHits; - fCounters.ReadMissRate = ( fCounters.ReadRequests ? (float)fCounters.ReadMisses / fCounters.ReadRequests : 0 ); - - memcpy( cnt, &fCounters, sizeof(fCounters)); - return true; -} - - -void XrdClient:: RemoveAllDataFromCache() -{ if (fConnModule) fConnModule->RemoveAllDataFromCache();} - -void XrdClient::RemoveDataFromCache(long long begin_offs, - long long end_offs, - bool remove_overlapped) -{ if (fConnModule) - fConnModule->RemoveDataFromCache(begin_offs, end_offs, remove_overlapped); -} - -void XrdClient::PrintCounters() { - - if (DebugLevel() < XrdClientDebug::kUSERDEBUG) return; - - XrdClientCounters cnt; - GetCounters(&cnt); - - printf("XrdClient counters:\n");; - printf(" ReadBytes: %lld\n", cnt.ReadBytes ); - printf(" WrittenBytes: %lld\n", cnt.WrittenBytes ); - printf(" WriteRequests: %lld\n", cnt.WriteRequests ); - - printf(" ReadRequests: %lld\n", cnt.ReadRequests ); - printf(" ReadMisses: %lld\n", cnt.ReadMisses ); - printf(" ReadHits: %lld\n", cnt.ReadHits ); - printf(" ReadMissRate: %f\n", cnt.ReadMissRate ); - - printf(" ReadVRequests: %lld\n", cnt.ReadVRequests ); - printf(" ReadVSubRequests: %lld\n", cnt.ReadVSubRequests ); - printf(" ReadVSubChunks: %lld\n", cnt.ReadVSubChunks ); - printf(" ReadVBytes: %lld\n", cnt.ReadVBytes ); - - printf(" ReadVAsyncRequests: %lld\n", cnt.ReadVAsyncRequests ); - printf(" ReadVAsyncSubRequests: %lld\n", cnt.ReadVAsyncSubRequests ); - printf(" ReadVAsyncSubChunks: %lld\n", cnt.ReadVAsyncSubChunks ); - printf(" ReadVAsyncBytes: %lld\n", cnt.ReadVAsyncBytes ); - - printf(" ReadAsyncRequests: %lld\n", cnt.ReadAsyncRequests ); - printf(" ReadAsyncBytes: %lld\n\n", cnt.ReadAsyncBytes ); - -} - - diff --git a/src/XrdClient/XrdClient.hh b/src/XrdClient/XrdClient.hh deleted file mode 100644 index c98b2b6c01a..00000000000 --- a/src/XrdClient/XrdClient.hh +++ /dev/null @@ -1,315 +0,0 @@ -#ifndef XRD_CLIENT_H -#define XRD_CLIENT_H -/******************************************************************************/ -/* */ -/* X r d C l i e n t . h h */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2004) */ -/* Adapted from TXNetFile (root.cern.ch) originally done by */ -/* Alvise Dorigo, Fabrizio Furano */ -/* INFN Padova, 2003 */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// A UNIX reference client for xrootd. // -// // -////////////////////////////////////////////////////////////////////////// - -////////////////////////////////////////////////////////////////////////// -// // -// // -// Some features: // -// - Automatic server kind recognition (xrootd load balancer, xrootd // -// data server, old rootd) // -// - Fault tolerance for read/write operations (read/write timeouts // -// and retry) // -// - Internal connection timeout (tunable indipendently from the OS // -// one) // -// - Full implementation of the xrootd protocol // -// - handling of redirections from server // -// - Connection multiplexing // -// - Asynchronous operation mode // -// - High performance read caching with read-ahead // -// - Thread safe // -// - Tunable log verbosity level (0 = nothing, 3 = dump read/write // -// buffers too!) // -// - Many parameters configurable. But the default are OK for nearly // -// all the situations. // -// // -////////////////////////////////////////////////////////////////////////// - -#include "XProtocol/XProtocol.hh" - -#include "XrdClient/XrdClientAbs.hh" -#include "XrdClient/XrdClientConst.hh" -#include "XrdOuc/XrdOucString.hh" -#include "XrdSys/XrdSysSemWait.hh" -#include "XrdVersion.hh" -#include -#include - -class XrdClientReadAheadMgr; -class XrdClientThread; - -struct XrdClientOpenInfo { - bool inprogress; - bool opened; - kXR_unt16 mode; - kXR_unt16 options; -}; - -struct XrdClientStatInfo { - int stated; - long long size; - long id; - long flags; - long modtime; -}; - -struct XrdClientCounters { - int CacheSize; - - // This does not take into account the 'suggestions' - // like async read or async readV - // We count only for functions which return data, eventually - // taken from the cache - long long ReadBytes; - long long WrittenBytes; - long long WriteRequests; - - long long ReadRequests; - long long ReadMisses; - long long ReadHits; - float ReadMissRate; - - long long ReadVRequests; // How many readVs (sync) were requested - long long ReadVSubRequests; // In how many sub-readVs they were split - long long ReadVSubChunks; // How many subchunks in total - long long ReadVBytes; // How many bytes were requested (sync) - - long long ReadVAsyncRequests; // How many readVs (async) were requested - long long ReadVAsyncSubRequests; // In how many sub-readVs they were split - long long ReadVAsyncSubChunks; // How many subchunks in total - long long ReadVAsyncBytes; // How many bytes were requested (async) - - long long ReadAsyncRequests; - long long ReadAsyncBytes; -}; - - -class XrdClient : public XrdClientAbs { - friend void *FileOpenerThread(void*, XrdClientThread*); - - -private: - - struct XrdClientOpenInfo fOpenPars; // Just a container for the last parameters - // passed to a Open method - - // The open request can be in progress. Further requests must be delayed until - // finished. - XrdSysCondVar *fOpenProgCnd; - - // Used to open a file in parallel - XrdClientThread *fOpenerTh; - - // Used to limit the maximum number of concurrent opens - static XrdSysSemWait fConcOpenSem; - - bool fOpenWithRefresh; - - XrdSysCondVar *fReadWaitData; // Used to wait for outstanding data - - struct XrdClientStatInfo fStatInfo; - - long fReadTrimBlockSize; - - bool fUseCache; - - XrdOucString fInitialUrl; - XrdClientUrlInfo fUrl; - - bool TryOpen(kXR_unt16 mode, - kXR_unt16 options, - bool doitparallel); - - bool LowOpen(const char *file, - kXR_unt16 mode, - kXR_unt16 options, - char *additionalquery = 0); - - void TerminateOpenAttempt(); - - void WaitForNewAsyncData(); - - // Real implementation for ReadV - // To call it we need to be aware of the restrictions so the public - // interface should be ReadV() - kXR_int64 ReadVEach(char *buf, kXR_int64 *offsets, int *lens, int &nbuf); - - bool IsOpenedForWrite() { - // This supposes that no options means read only - if (!fOpenPars.options) return false; - - if (fOpenPars.options & kXR_open_read) return false; - - return true; - } - - XrdClientReadAheadMgr *fReadAheadMgr; - - void PrintCounters(); -protected: - - XrdClientCounters fCounters; - - virtual bool OpenFileWhenRedirected(char *newfhandle, - bool &wasopen); - - virtual bool CanRedirOnError() { - // Can redir away on error if no file is opened - // or the file is opened in read mode - - if ( !fOpenPars.opened ) return true; - - return !IsOpenedForWrite(); - - } - - -public: - - XrdClient(const char *url, XrdClientCallback *XrdCcb = 0, void *XrdCcbArg = 0); - virtual ~XrdClient(); - - UnsolRespProcResult ProcessUnsolicitedMsg(XrdClientUnsolMsgSender *sender, - XrdClientMessage *unsolmsg); - - bool Close(); - - // Ask the server to flush its cache - bool Sync(); - - // Copy the whole file to the local filesystem. Not very efficient. - bool Copy(const char *localpath); - - // Returns low level information about the cache - bool GetCacheInfo( - // The actual cache size - int &size, - - // The number of bytes submitted since the beginning - long long &bytessubmitted, - - // The number of bytes found in the cache (estimate) - long long &byteshit, - - // The number of reads which did not find their data - // (estimate) - long long &misscount, - - // miss/totalreads ratio (estimate) - float &missrate, - - // number of read requests towards the cache - long long &readreqcnt, - - // ratio between bytes found / bytes submitted - float &bytesusefulness - ); - - - - // Returns client-level information about the activity performed up to now - bool GetCounters( XrdClientCounters *cnt ); - - // Quickly tells if the file is open - inline bool IsOpen() { return fOpenPars.opened; } - - // Tells if the file opening is in progress - bool IsOpen_inprogress(); - - // Tells if the file is open, waiting for the completion of the parallel open - bool IsOpen_wait(); - - // Open the file. See the xrootd documentation for mode and options - // If parallel, then the open is done by a separate thread, and - // all the operations are delayed until the open has finished - bool Open(kXR_unt16 mode, kXR_unt16 options, bool doitparallel=true); - - // Read a block of data. If no error occurs, it returns all the requested bytes. - int Read(void *buf, long long offset, int len); - - // Read multiple blocks of data compressed into a sinle one. It's up - // to the application to do the logistic (having the offset and len to find - // the position of the required buffer given the big one). If no error - // occurs, it returns all the requested bytes. - // NOTE: if buf == 0 then the req will be carried out asynchronously, i.e. - // the result of the request will only populate the internal cache. A subsequent read() - // of that chunk will get the data from the cache - kXR_int64 ReadV(char *buf, long long *offsets, int *lens, int nbuf); - - // Submit an asynchronous read request. Its result will only populate the cache - // (if any!!) - XReqErrorType Read_Async(long long offset, int len, bool updatecounters=true); - - // Get stat info about the file. Normally it tries to guess the file size variations - // unless force==true - bool Stat(struct XrdClientStatInfo *stinfo, bool force = false); - - // On-the-fly enabling/disabling of the cache - bool UseCache(bool u = true); - - // To instantly remove all the chunks in the cache - void RemoveAllDataFromCache(); - - // To remove pieces of data from the cache - void RemoveDataFromCache(long long begin_offs, - long long end_offs, - bool remove_overlapped = false); - - // To set at run time the cache/readahead parameters for this instance only - // If a parameter is < 0 then it's left untouched. - // To simply enable/disable the caching, just use UseCache(), not this function - void SetCacheParameters(int CacheSize, int ReadAheadSize, int RmPolicy); - - // To enable/disable different read ahead strategies. Defined in XrdClientReadAhead.hh - void SetReadAheadStrategy(int strategy); - - // To enable the trimming of the blocks to read. Blocksize will be rounded to a multiple of 512. - // Each read request will have the offset and length aligned with a multiple of blocksize - // This strategy is similar to a read ahead, but not quite. Here we see it as a transformation - // of the stream of the read accesses to request - void SetBlockReadTrimming(int blocksize); - - // Truncates the open file at a specified length - bool Truncate(long long len); - - // Write data to the file - bool Write(const void *buf, long long offset, int len); - - std::vector fExcludedHosts; - -}; -#endif diff --git a/src/XrdClient/XrdClientAbs.cc b/src/XrdClient/XrdClientAbs.cc deleted file mode 100644 index 165ab252626..00000000000 --- a/src/XrdClient/XrdClientAbs.cc +++ /dev/null @@ -1,268 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C l i e n t A b s . c c */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2004) */ -/* Adapted from TXNetFile (root.cern.ch) originally done by */ -/* Alvise Dorigo, Fabrizio Furano */ -/* INFN Padova, 2003 */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// Base class for objects who has to handle redirections with open files// -// // -////////////////////////////////////////////////////////////////////////// - -#include "XrdClient/XrdClientAbs.hh" -#include "XrdClient/XrdClientConn.hh" -#include "XrdClient/XrdClientDebug.hh" -#include "XrdClient/XrdClientEnv.hh" - -#include "XProtocol/XProtocol.hh" - -//_____________________________________________________________________________ -XrdClientUrlInfo XrdClientAbs::GetCurrentUrl() -{ - if (fConnModule) return fConnModule->GetCurrentUrl(); - else {XrdClientUrlInfo empty; - return empty; - } -} - -//_____________________________________________________________________________ -struct ServerResponseBody_Error *XrdClientAbs::LastServerError() -{ - if (fConnModule) return &fConnModule->LastServerError; - else return 0; -} - -//_____________________________________________________________________________ -struct ServerResponseHeader *XrdClientAbs::LastServerResp() -{ - IsOpen_wait(); - if (fConnModule) return &fConnModule->LastServerResp; - else return 0; -} - -//_____________________________________________________________________________ -void XrdClientAbs::SetParm(const char *parm, int val) -{ - // This method configure TXNetFile's behaviour settings through the - // setting of special ROOT env vars via the TEnv facility. - // A ROOT env var is not a environment variable (that you can get using - // getenv() syscall). It's an internal ROOT one (see TEnv documentation - // for more details). - // At the moment the following env vars are handled by TXNetFile - // XNet.ConnectTimeout - maximum time to wait before server's - // response on a connect - // XNet.RequestTimeout - maximum time to wait before considering - // a read/write failure - // XNet.ConnectDomainAllowRE - // - sequence of TRegexp regular expressions - // separated by a |. - // A domain (or w.x.y.z addr) is granted - // access to for the - // first connection if it matches one of these - // regexps. Example: - // slac.stanford.edu|pd.infn.it|fe.infn.it - // XNet.ConnectDomainDenyRE - // - sequence of TRegexp regular expressions - // separated by a |. - // A domain (or w.x.y.z addr) is denied - // access to for the - // first connection if it matches one of these - // regexps. Example: - // slac.stanford.edu|pd.infn.it|fe.infn.it - // XNet.RedirDomainAllowRE - // - sequence of TRegexp regular expressions - // separated by a |. - // A domain (or w.x.y.z addr) is granted - // access to for a - // redirection if it matches one of these - // regexps. Example: - // slac.stanford.edu|pd.infn.it|fe.infn.it - // XNet.RedirDomainDenyRE - // - sequence of TRegexp regular expressions - // separated by a |. - // A domain (or w.x.y.z addr) is denied - // access to for a - // redirection if it matches one of these - // regexps. Example: - // slac.stanford.edu|pd.infn.it|fe.infn.it - // - // XNet.MaxRedirectCount - maximum number of redirections from - // server - // XNet.Debug - log verbosity level - // (0=nothing, - // 1=messages of interest to the user, - // 2=messages of interest to the developers - // (includes also user messages), - // 3=dump of all sent/received data buffers - // (includes also user and developers - // messages). - // XNet.ReconnectTimeout - sleep-time before going back to the - // load balancer (or rebouncing to the same - // failing host) after a read/write error - // XNet.StartGarbageCollectorThread - - // for test/development purposes. Normally - // nonzero (True), but as workaround for - // external causes someone could be - // interested in not having the garbage - // collector thread around. - // XNet.TryConnect - Number of tries connect to a single - // server before giving up - // XNet.TryConnectServersList - // - Number of connect retries to the whole - // server list given - // XNet.PrintTAG - Print a particular string the developers - // can choose to quickly recognize the - // version at run time - // XNet.ReadCacheSize - The size of the cache. One cache per instance! - // 0 for no cache. The cache gets all the - // kxr_read positive responses received - // XNet.ReadAheadSize - The size of the read-ahead blocks. - // 0 for no read-ahead. - - if (DebugLevel() >= XrdClientDebug::kUSERDEBUG) - Info(XrdClientDebug::kUSERDEBUG, - "AbsNetCommon::SetParm", - "Setting " << parm << " to " << val); - - EnvPutInt((char *)parm, val); -} - -//_____________________________________________________________________________ -void XrdClientAbs::SetParm(const char *parm, double val) -{ - // Setting TXNetFile specific ROOT-env variables (see previous method - // for details - - if (DebugLevel() >= XrdClientDebug::kUSERDEBUG) - Info(XrdClientDebug::kUSERDEBUG, - "TXAbsNetCommon::SetParm", - "Setting " << parm << " to " << val); - - - //EnvPutString(parm, val); -} - - - -//_____________________________________________________________________________ -// Returns query information - -bool XrdClientAbs::Query(kXR_int16 ReqCode, const kXR_char *Args, kXR_char *Resp, kXR_int32 MaxResplen) -{ - return Query( ReqCode, Args, &Resp, MaxResplen ); -} - -bool XrdClientAbs::Query(kXR_int16 ReqCode, const kXR_char *Args, kXR_char **Resp, kXR_int32 MaxResplen) -{ - if (!fConnModule) return false; - if (!fConnModule->IsConnected()) return false; - if (!Resp) return false; - if (!*Resp && MaxResplen) return false; - - // Set the max transaction duration - fConnModule->SetOpTimeLimit(EnvGetLong(NAME_TRANSACTIONTIMEOUT)); - - ClientRequest qryRequest; - - memset( &qryRequest, 0, sizeof(qryRequest) ); - - fConnModule->SetSID(qryRequest.header.streamid); - - qryRequest.query.requestid = kXR_query; - qryRequest.query.infotype = ReqCode; - - if (Args) - qryRequest.query.dlen = strlen((char *)Args); - - if (ReqCode == kXR_Qvisa) - memcpy( qryRequest.query.fhandle, fHandle, sizeof(fHandle) ); - - kXR_char *rsp = 0; - bool ret = fConnModule->SendGenCommand(&qryRequest, (const char*)Args, - (void **)&rsp, 0, true, - (char *)"Query"); - - if (ret) { - - if (Args) { - - if (rsp) { - Info(XrdClientDebug::kHIDEBUG, - "XrdClientAdmin::Query", - "Query(" << ReqCode << ", '" << Args << "') returned '" << rsp << "'" ); - } - else { - Info(XrdClientDebug::kHIDEBUG, - "XrdClientAdmin::Query", - "Query(" << ReqCode << ", '" << Args << "') returned a null string" ); - } - - } - else { - Info(XrdClientDebug::kHIDEBUG, - "XrdClientAdmin::Query", - "Query(" << ReqCode << ", NULL') returned '" << rsp << "'" ); - } - - //------------------------------------------------------------------------ - // We have got an answer - //------------------------------------------------------------------------ - if ( rsp && (LastServerResp()->status == kXR_ok) ) - { - //---------------------------------------------------------------------- - // We are dealing with a preallocated buffer - //---------------------------------------------------------------------- - if( MaxResplen ) - { - int l = xrdmin(MaxResplen, LastServerResp()->dlen); - strncpy((char *)*Resp, (char *)rsp, l); - if (l >= 0) (*Resp)[l-1] = '\0'; - } - //---------------------------------------------------------------------- - // We need to allocate the buffer - //---------------------------------------------------------------------- - else - { - int l = LastServerResp()->dlen+1; - *Resp = (kXR_char*)realloc( *Resp, l ); - if( !*Resp ) - { - free( rsp ); - return false; - } - strncpy((char *)*Resp, (char *)rsp, l-1); - (*Resp)[l-1] = 0; - } - free(rsp); - rsp = 0; - } - } - - return ret; -} - diff --git a/src/XrdClient/XrdClientAbs.hh b/src/XrdClient/XrdClientAbs.hh deleted file mode 100644 index 67a0b63b08f..00000000000 --- a/src/XrdClient/XrdClientAbs.hh +++ /dev/null @@ -1,120 +0,0 @@ -#ifndef XRD_ABSCLIENTBASE_H -#define XRD_ABSCLIENTBASE_H -/******************************************************************************/ -/* */ -/* X r d C l i e n t A b s . h h */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2004) */ -/* Adapted from TXNetFile (root.cern.ch) originally done by */ -/* Alvise Dorigo, Fabrizio Furano */ -/* INFN Padova, 2003 */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// Base class for objects handling redirections keeping open files // -// // -////////////////////////////////////////////////////////////////////////// - -#include - -#include "XrdClient/XrdClientUnsolMsg.hh" -#include "XrdClient/XrdClientUrlInfo.hh" - -#include "XProtocol/XPtypes.hh" - -class XrdClientCallback; -class XrdClientConn; - -class XrdClientAbs: public XrdClientAbsUnsolMsgHandler { - - // Do NOT abuse of this - friend class XrdClientConn; - - -protected: - XrdClientConn* fConnModule; - - char fHandle[4]; // The file handle returned by the server, - // to be used for successive requests - - - XrdClientCallback* fXrdCcb; - void * fXrdCcbArg; - - // After a redirection the file must be reopened. - virtual bool OpenFileWhenRedirected(char *newfhandle, - bool &wasopen) = 0; - - // In some error circumstances (e.g. when writing) - // a redirection on error must be denied - virtual bool CanRedirOnError() = 0; - -public: - - XrdClientAbs(XrdClientCallback *XrdCcb = 0, void *XrdCcbArg = 0) { - memset( fHandle, 0, sizeof(fHandle) ); - - // Set the callback object, if any - fXrdCcb = XrdCcb; - fXrdCcbArg = XrdCcbArg; - } - - virtual bool IsOpen_wait() { - return true; - }; - - void SetParm(const char *parm, int val); - void SetParm(const char *parm, double val); - - // Hook to the open connection (needed by TXNetFile) - XrdClientConn *GetClientConn() const { return fConnModule; } - - XrdClientUrlInfo GetCurrentUrl(); - - // The last response got from a non-async request - struct ServerResponseHeader *LastServerResp(); - - struct ServerResponseBody_Error *LastServerError(); - - // Asks for the value of some parameter - //--------------------------------------------------------------------------- - //! @param ReqCode request code - //! @param Args arguments - //! @param Resp a prealocated buffer - //! @param MaxResplen size of the buffer - //--------------------------------------------------------------------------- - bool Query(kXR_int16 ReqCode, const kXR_char *Args, kXR_char *Resp, kXR_int32 MaxResplen); - - //--------------------------------------------------------------------------- - //! @param ReqCode request code - //! @param Args arguments - //! @param Resp pointer to a preallocated buffer or a pointer to 0 if a - //! sufficiently large buffer should be allocated automagically, - //! in which case the buffer needs to be freed with free() - //! @param MaxResplen size of the buffer or 0 for automatic allocation - //--------------------------------------------------------------------------- - bool Query( kXR_int16 ReqCode, const kXR_char *Args, kXR_char **Resp, kXR_int32 MaxResplen ); - -}; -#endif diff --git a/src/XrdClient/XrdClientAbsMonIntf.hh b/src/XrdClient/XrdClientAbsMonIntf.hh deleted file mode 100644 index 8c7373f9895..00000000000 --- a/src/XrdClient/XrdClientAbsMonIntf.hh +++ /dev/null @@ -1,74 +0,0 @@ -#ifndef __XRDCLIABSMONINTF_H__ -#define __XRDCLIABSMONINTF_H__ -/******************************************************************************/ -/* */ -/* X r d C l i e n t A b s M o n I n t f . h h */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -// XrdClientAbsMonIntf -// Public interface to generic monitoring systems -// - -class XrdClientAbsMonIntf { -public: - - - // Initialization of the external library - virtual int Init(const char *src, const char *dest, int debug=0, void *parm=0) = 0; - virtual int DeInit() = 0; - - // To get the name of the library and other info - virtual int GetMonLibInfo(char **name, char **version, char **remarks) = 0; - - - // To submit a set of info about the progress of something - // Set force to true to be sure that the info is sent and not eventually skipped - virtual int PutProgressInfo(long long bytecount=0, - long long size=0, - float percentage=0.0, - bool force=false) = 0; - - - XrdClientAbsMonIntf() {}; - virtual ~XrdClientAbsMonIntf() {}; -}; - - - - -/******************************************************************************/ -/* X r d C l i e n t A b s M o n I n t f . h h */ -/******************************************************************************/ - -// The XrdClientMonIntf() function is called when the shared library containing -// implementation of this class is loaded. It must exist in the library as an -// 'extern "C"' defined function. - - -#define XrdClientMonIntfArgs const char *src, const char *dst - -extern "C" { - typedef XrdClientAbsMonIntf *(*XrdClientMonIntfHook)(XrdClientMonIntfArgs); -XrdClientAbsMonIntf *XrdClientgetMonIntf(XrdClientMonIntfArgs); -} -#endif diff --git a/src/XrdClient/XrdClientAdmin.cc b/src/XrdClient/XrdClientAdmin.cc deleted file mode 100644 index 769dc5e90e4..00000000000 --- a/src/XrdClient/XrdClientAdmin.cc +++ /dev/null @@ -1,1613 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C l i e n t A d m i n . c c */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2004) */ -/* Adapted from XTNetAdmin (root.cern.ch) originally done by */ -/* Alvise Dorigo, Fabrizio Furano */ -/* INFN Padova, 2003 */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// A UNIX reference admin client for xrootd. // -// // -////////////////////////////////////////////////////////////////////////// - -#include "XrdClient/XrdClientAdmin.hh" -#include "XrdClient/XrdClientDebug.hh" -#include "XrdClient/XrdClientUrlSet.hh" -#include "XrdClient/XrdClientConn.hh" -#include "XrdClient/XrdClientEnv.hh" -#include "XrdClient/XrdClientConnMgr.hh" -#include "XrdOuc/XrdOucTokenizer.hh" -#include "XrdVersion.hh" - -#include -#include -#include -#include -#ifndef WIN32 -#include -#include -#include -#endif - -//_____________________________________________________________________________ -void joinStrings(XrdOucString &buf, vecString &vs, - int startidx, int endidx) -{ - - if (endidx < 0) endidx = vs.GetSize()-1; - - if (!vs.GetSize() || (vs.GetSize() <= startidx) || - (endidx < startidx) ){ - buf = ""; - return; - } - - int lastidx = xrdmin(vs.GetSize()-1, endidx); - - for(int j=startidx; j <= lastidx; j++) { - buf += vs[j]; - if (j < lastidx) buf += "\n"; - } - -} - -//_____________________________________________________________________________ -XrdClientAdmin::XrdClientAdmin(const char *url) { - - - // Pick-up the latest setting of the debug level - DebugSetLevel(EnvGetLong(NAME_DEBUG)); - - if (!ConnectionManager) - Info(XrdClientDebug::kUSERDEBUG, - "", - "(C) 2004-2010 by the Xrootd group. XrdClientAdmin " << XrdVSTRING); - - fInitialUrl = url; - - fConnModule = new XrdClientConn(); - - if (!fConnModule) { - Error("XrdClientAdmin", - "Object creation failed."); - abort(); - } - - // Set this instance as a handler for handling the consequences of a redirection - fConnModule->SetRedirHandler(this); - -} - -//_____________________________________________________________________________ -XrdClientAdmin::~XrdClientAdmin() -{ - delete fConnModule; -} - - - -//_____________________________________________________________________________ -bool XrdClientAdmin::Connect() -{ - // Connect to the server - - // Nothing to do if already connected - if (fConnModule && fConnModule->IsConnected()) { - return TRUE; - } - - - // Now we try to set up the first connection - // We cycle through the list of urls given in fInitialUrl - - // Max number of tries - int connectMaxTry = EnvGetLong(NAME_FIRSTCONNECTMAXCNT); - - // Construction of the url set coming from the resolution of the hosts given - XrdClientUrlSet urlArray(fInitialUrl); - if (!urlArray.IsValid()) { - Error("Connect", "The URL provided is incorrect."); - return FALSE; - } - - // Set the max transaction duration - fConnModule->SetOpTimeLimit(EnvGetLong(NAME_TRANSACTIONTIMEOUT)); - - // - // Now start the connection phase, picking randomly from UrlArray - // - urlArray.Rewind(); - int urlstried = 0; - for (int connectTry = 0; - (connectTry < connectMaxTry) && (!fConnModule->IsConnected()); - connectTry++) { - - XrdClientUrlSet urlArray(fInitialUrl); - urlArray.Rewind(); - - XrdClientUrlInfo *thisUrl = 0; - urlstried = (urlstried == urlArray.Size()) ? 0 : urlstried; - - if ( fConnModule->IsOpTimeLimitElapsed(time(0)) ) { - // We have been so unlucky and wasted too much time in connecting and being redirected - fConnModule->Disconnect(TRUE); - Error("Connect", "Access to server failed: Too much time elapsed without success."); - break; - } - - bool nogoodurl = TRUE; - while (urlArray.Size() > 0) { - - // Get an url from the available set - if ((thisUrl = urlArray.GetARandomUrl())) { - - if (fConnModule->CheckHostDomain(thisUrl->Host)) { - nogoodurl = FALSE; - Info(XrdClientDebug::kHIDEBUG, "Connect", "Trying to connect to " << - thisUrl->Host << ":" << thisUrl->Port << - ". Connect try " << connectTry+1); - fConnModule->Connect(*thisUrl, this); - // To find out if we have tried the whole URLs set - urlstried++; - break; - } else { - // Invalid domain: drop the url and move to next, if any - urlArray.EraseUrl(thisUrl); - continue; - } - - } - } - if (nogoodurl) { - Error("Connect", "Access denied to all URL domains requested"); - break; - } - - // We are connected to a host. Let's handshake with it. - if (fConnModule->IsConnected()) { - - // Now the have the logical Connection ID, that we can use as streamid for - // communications with the server - - Info(XrdClientDebug::kHIDEBUG, "Connect", - "The logical connection id is " << fConnModule->GetLogConnID() << - ". This will be the streamid for this client"); - - fConnModule->SetUrl(*thisUrl); - - Info(XrdClientDebug::kHIDEBUG, "Connect", - "Working url is " << thisUrl->GetUrl()); - - // after connection deal with server - if (!fConnModule->GetAccessToSrv()) { - if (fConnModule->LastServerError.errnum == kXR_NotAuthorized) { - if (urlstried == urlArray.Size()) { - // Authentication error: we tried all the indicated URLs: - // does not make much sense to retry - fConnModule->Disconnect(TRUE); - XrdOucString msg(fConnModule->LastServerError.errmsg); - msg.erasefromend(1); - Error("Connect", "Authentication failure: " << msg); - connectTry = connectMaxTry; - } else { - XrdOucString msg(fConnModule->LastServerError.errmsg); - msg.erasefromend(1); - Info(XrdClientDebug::kHIDEBUG, "Connect", - "Authentication failure: " << msg); - } - } else { - Error("Connect", "Access to server failed: error: " << - fConnModule->LastServerError.errnum << " (" << - fConnModule->LastServerError.errmsg << ") - retrying."); - } - } else { - Info(XrdClientDebug::kUSERDEBUG, "Connect", "Access to server granted."); - break; - } - } - - // The server denied access. We have to disconnect. - Info(XrdClientDebug::kHIDEBUG, "Connect", "Disconnecting."); - - fConnModule->Disconnect(FALSE); - - if (connectTry < connectMaxTry-1) { - - if (DebugLevel() >= XrdClientDebug::kUSERDEBUG) - Info(XrdClientDebug::kUSERDEBUG, "Connect", - "Connection attempt failed. Sleeping " << - EnvGetLong(NAME_RECONNECTWAIT) << " seconds."); - - sleep(EnvGetLong(NAME_RECONNECTWAIT)); - - } - - } //for connect try - - - if (!fConnModule->IsConnected()) { - return FALSE; - } - - - // - // Variable initialization - // If the server is a new xrootd ( load balancer or data server) - // - if ((fConnModule->GetServerType() != kSTRootd) && - (fConnModule->GetServerType() != kSTNone)) { - // Now we are connected to a server that didn't redirect us after the - // login/auth phase - - Info(XrdClientDebug::kUSERDEBUG, "Connect", "Connected."); - - - } else { - // We close the connection only if we do not know the server type. - // In the rootd case the connection may be re-used later. - if (fConnModule->GetServerType() == kSTNone) - fConnModule->Disconnect(TRUE); - - return FALSE; - } - - return TRUE; - -} - - - -//_____________________________________________________________________________ -bool XrdClientAdmin::Stat(const char *fname, long &id, long long &size, long &flags, long &modtime) -{ - // Return file stat information. The interface and return value is - // identical to TSystem::GetPathInfo(). - - bool ok; - - // Set the max transaction duration - fConnModule->SetOpTimeLimit(EnvGetLong(NAME_TRANSACTIONTIMEOUT)); - - // asks the server for stat file informations - ClientRequest statFileRequest; - - memset( &statFileRequest, 0, sizeof(ClientRequest) ); - - fConnModule->SetSID(statFileRequest.header.streamid); - - statFileRequest.stat.requestid = kXR_stat; - - memset(statFileRequest.stat.reserved, 0, - sizeof(statFileRequest.stat.reserved)); - - statFileRequest.header.dlen = strlen(fname); - - char fStats[2048]; - id = 0; - size = 0; - flags = 0; - modtime = 0; - - ok = fConnModule->SendGenCommand(&statFileRequest, (const char*)fname, - NULL, fStats , FALSE, (char *)"Stat"); - - - if (ok && (fConnModule->LastServerResp.status == 0)) { - if (fConnModule->LastServerResp.dlen >= 0) - fStats[fConnModule->LastServerResp.dlen] = 0; - else - fStats[0] = 0; - Info(XrdClientDebug::kHIDEBUG, - "Stat", "Returned stats=" << fStats); - sscanf(fStats, "%ld %lld %ld %ld", &id, &size, &flags, &modtime); - } - - return ok; -} - - - -//_____________________________________________________________________________ -bool XrdClientAdmin::Stat_vfs(const char *fname, - int &rwservers, - long long &rwfree, - int &rwutil, - int &stagingservers, - long long &stagingfree, - int &stagingutil) -{ - // Return information for a virtual file system - - bool ok; - - // asks the server for stat file informations - ClientRequest statFileRequest; - - // Set the max transaction duration - fConnModule->SetOpTimeLimit(EnvGetLong(NAME_TRANSACTIONTIMEOUT)); - - memset( &statFileRequest, 0, sizeof(ClientRequest) ); - - fConnModule->SetSID(statFileRequest.header.streamid); - - statFileRequest.stat.requestid = kXR_stat; - - memset(statFileRequest.stat.reserved, 0, - sizeof(statFileRequest.stat.reserved)); - - statFileRequest.stat.options = kXR_vfs; - - statFileRequest.header.dlen = strlen(fname); - - char fStats[2048]; - rwservers = 0; - rwfree = 0; - rwutil = 0; - stagingservers = 0; - stagingfree = 0; - stagingutil = 0; - - - ok = fConnModule->SendGenCommand(&statFileRequest, (const char*)fname, - NULL, fStats , FALSE, (char *)"Stat_vfs"); - - - if (ok && (fConnModule->LastServerResp.status == 0)) { - if (fConnModule->LastServerResp.dlen >= 0) - fStats[fConnModule->LastServerResp.dlen] = 0; - else - fStats[0] = 0; - Info(XrdClientDebug::kHIDEBUG, - "Stat_vfs", "Returned stats=" << fStats); - - sscanf(fStats, "%d %lld %d %d %lld %d", &rwservers, &rwfree, &rwutil, - &stagingservers, &stagingfree, &stagingutil); - - } - - return ok; -} - - -//_____________________________________________________________________________ -bool XrdClientAdmin::SysStatX(const char *paths_list, kXR_char *binInfo) -{ - XrdOucString pl(paths_list); - bool ret; - // asks the server for stat file informations - ClientRequest statXFileRequest; - - // Set the max transaction duration - fConnModule->SetOpTimeLimit(EnvGetLong(NAME_TRANSACTIONTIMEOUT)); - - memset( &statXFileRequest, 0, sizeof(ClientRequest) ); - fConnModule->SetSID(statXFileRequest.header.streamid); - statXFileRequest.header.requestid = kXR_statx; - - statXFileRequest.stat.dlen = pl.length(); - - ret = fConnModule->SendGenCommand(&statXFileRequest, pl.c_str(), - NULL, binInfo , FALSE, (char *)"SysStatX"); - - return(ret); -} - -//_____________________________________________________________________________ -bool XrdClientAdmin::ExistFiles(vecString &vs, vecBool &vb) -{ - bool ret; - XrdOucString buf; - joinStrings(buf, vs); - - kXR_char *Info; - Info = (kXR_char*) malloc(vs.GetSize()+10); - memset((void *)Info, 0, vs.GetSize()+10); - - ret = this->SysStatX(buf.c_str(), Info); - - if (ret) for(int j=0; j <= vs.GetSize()-1; j++) { - bool tmp = TRUE; - - if ( (*(Info+j) & kXR_isDir) || (*(Info+j) & kXR_other) || - (*(Info+j) & kXR_offline) ) - tmp = FALSE; - - vb.Push_back(tmp); - } - - - free(Info); - - return ret; -} - -//_____________________________________________________________________________ -bool XrdClientAdmin::ExistDirs(vecString &vs, vecBool &vb) -{ - bool ret; - XrdOucString buf; - joinStrings(buf, vs); - - kXR_char *Info; - Info = (kXR_char*) malloc(vs.GetSize()+10); - memset((void *)Info, 0, vs.GetSize()+10); - - ret = this->SysStatX(buf.c_str(), Info); - - if (ret) for(int j=0; j <= vs.GetSize()-1; j++) { - bool tmp; - - if( (*(Info+j) & kXR_isDir) ) { - tmp = TRUE; - vb.Push_back(tmp); - } else { - tmp = FALSE; - vb.Push_back(tmp); - } - - } - - free(Info); - - return ret; -} - -//_____________________________________________________________________________ -bool XrdClientAdmin::IsFileOnline(vecString &vs, vecBool &vb) -{ - bool ret; - XrdOucString buf; - joinStrings(buf, vs); - - kXR_char *Info; - Info = (kXR_char*) malloc(vs.GetSize()+10); - memset((void *)Info, 0, vs.GetSize()+10); - - ret = this->SysStatX(buf.c_str(), Info); - - if (ret) for(int j=0; j <= vs.GetSize()-1; j++) { - bool tmp; - - if( !(*(Info+j) & kXR_offline) ) { - tmp = TRUE; - vb.Push_back(tmp); - } else { - tmp = FALSE; - vb.Push_back(tmp); - } - - } - - free(Info); - - return ret; -} - - -// Called by the conn module after a redirection has been succesfully handled -//_____________________________________________________________________________ -bool XrdClientAdmin::OpenFileWhenRedirected(char *newfhandle, bool &wasopen) { - // We simply do nothing... - wasopen = FALSE; - return TRUE; -} - -//_____________________________________________________________________________ -bool XrdClientAdmin::Rmdir(const char *path) -{ - // Remove an empty remote directory - ClientRequest rmdirFileRequest; - - // Set the max transaction duration - fConnModule->SetOpTimeLimit(EnvGetLong(NAME_TRANSACTIONTIMEOUT)); - - memset( &rmdirFileRequest, 0, sizeof(rmdirFileRequest) ); - fConnModule->SetSID(rmdirFileRequest.header.streamid); - rmdirFileRequest.header.requestid = kXR_rmdir; - rmdirFileRequest.header.dlen = strlen(path); - - return (fConnModule->SendGenCommand(&rmdirFileRequest, path, - NULL, NULL, FALSE, (char *)"Rmdir")); - -} - -//_____________________________________________________________________________ -bool XrdClientAdmin::Rm(const char *file) -{ - // Remove a remote file - ClientRequest rmFileRequest; - - // Set the max transaction duration - fConnModule->SetOpTimeLimit(EnvGetLong(NAME_TRANSACTIONTIMEOUT)); - - memset( &rmFileRequest, 0, sizeof(rmFileRequest) ); - fConnModule->SetSID(rmFileRequest.header.streamid); - rmFileRequest.header.requestid = kXR_rm; - rmFileRequest.header.dlen = strlen(file); - - return (fConnModule->SendGenCommand(&rmFileRequest, file, - NULL, NULL, FALSE, (char *)"Rm")); -} - -//_____________________________________________________________________________ -bool XrdClientAdmin::Chmod(const char *file, int user, int group, int other) -{ - // Change the permission of a remote file - ClientRequest chmodFileRequest; - - // Set the max transaction duration - fConnModule->SetOpTimeLimit(EnvGetLong(NAME_TRANSACTIONTIMEOUT)); - - memset( &chmodFileRequest, 0, sizeof(chmodFileRequest) ); - - fConnModule->SetSID(chmodFileRequest.header.streamid); - chmodFileRequest.header.requestid = kXR_chmod; - - if(user & 4) - chmodFileRequest.chmod.mode |= kXR_ur; - if(user & 2) - chmodFileRequest.chmod.mode |= kXR_uw; - if(user & 1) - chmodFileRequest.chmod.mode |= kXR_ux; - - if(group & 4) - chmodFileRequest.chmod.mode |= kXR_gr; - if(group & 2) - chmodFileRequest.chmod.mode |= kXR_gw; - if(group & 1) - chmodFileRequest.chmod.mode |= kXR_gx; - - if(other & 4) - chmodFileRequest.chmod.mode |= kXR_or; - if(other & 2) - chmodFileRequest.chmod.mode |= kXR_ow; - if(other & 1) - chmodFileRequest.chmod.mode |= kXR_ox; - - chmodFileRequest.header.dlen = strlen(file); - - - return (fConnModule->SendGenCommand(&chmodFileRequest, file, - NULL, NULL, FALSE, (char *)"Chmod")); - -} - -//_____________________________________________________________________________ -bool XrdClientAdmin::Mkdir(const char *dir, int user, int group, int other) -{ - // Create a remote directory - ClientRequest mkdirRequest; - - // Set the max transaction duration - fConnModule->SetOpTimeLimit(EnvGetLong(NAME_TRANSACTIONTIMEOUT)); - - memset( &mkdirRequest, 0, sizeof(mkdirRequest) ); - - fConnModule->SetSID(mkdirRequest.header.streamid); - - mkdirRequest.header.requestid = kXR_mkdir; - - memset(mkdirRequest.mkdir.reserved, 0, - sizeof(mkdirRequest.mkdir.reserved)); - - if(user & 4) - mkdirRequest.mkdir.mode |= kXR_ur; - if(user & 2) - mkdirRequest.mkdir.mode |= kXR_uw; - if(user & 1) - mkdirRequest.mkdir.mode |= kXR_ux; - - if(group & 4) - mkdirRequest.mkdir.mode |= kXR_gr; - if(group & 2) - mkdirRequest.mkdir.mode |= kXR_gw; - if(group & 1) - mkdirRequest.mkdir.mode |= kXR_gx; - - if(other & 4) - mkdirRequest.mkdir.mode |= kXR_or; - if(other & 2) - mkdirRequest.mkdir.mode |= kXR_ow; - if(other & 1) - mkdirRequest.mkdir.mode |= kXR_ox; - - mkdirRequest.mkdir.options[0] = kXR_mkdirpath; - - mkdirRequest.header.dlen = strlen(dir); - - return (fConnModule->SendGenCommand(&mkdirRequest, dir, - NULL, NULL, FALSE, (char *)"Mkdir")); - -} - -//_____________________________________________________________________________ -bool XrdClientAdmin::Mv(const char *fileSrc, const char *fileDest) -{ - bool ret; - - // Rename a remote file - ClientRequest mvFileRequest; - - // Set the max transaction duration - fConnModule->SetOpTimeLimit(EnvGetLong(NAME_TRANSACTIONTIMEOUT)); - - - memset( &mvFileRequest, 0, sizeof(mvFileRequest) ); - - fConnModule->SetSID(mvFileRequest.header.streamid); - mvFileRequest.header.requestid = kXR_mv; - - mvFileRequest.header.dlen = strlen( fileDest ) + strlen( fileSrc ) + 1; // len + len + string terminator \0 - - char *data = new char[mvFileRequest.header.dlen+2]; // + 1 for space separator + 1 for \0 - memset(data, 0, mvFileRequest.header.dlen+2); - strcpy( data, fileSrc ); - strcat( data, " " ); - strcat( data, fileDest ); - - ret = fConnModule->SendGenCommand(&mvFileRequest, data, - NULL, NULL, FALSE, (char *)"Mv"); - - delete [] data; - - return ret; -} - -//_____________________________________________________________________________ -UnsolRespProcResult XrdClientAdmin::ProcessUnsolicitedMsg(XrdClientUnsolMsgSender *sender, - XrdClientMessage *unsolmsg) -{ - // We are here if an unsolicited response comes from a logical conn - // The response comes in the form of an TXMessage *, that must NOT be - // destroyed after processing. It is destroyed by the first sender. - // Remember that we are in a separate thread, since unsolicited - // responses are asynchronous by nature. - - if (unsolmsg->GetStatusCode() != XrdClientMessage::kXrdMSC_ok) { - Info(XrdClientDebug::kHIDEBUG, - "ProcessUnsolicitedMsg", "Incoming unsolicited communication error message." ); - } - else { - Info(XrdClientDebug::kHIDEBUG, - "ProcessUnsolicitedMsg", "Incoming unsolicited response from streamid " << - unsolmsg->HeaderSID() ); - } - - // Local processing .... - if (unsolmsg->IsAttn()) { - struct ServerResponseBody_Attn *attnbody; - - attnbody = (struct ServerResponseBody_Attn *)unsolmsg->GetData(); - - int actnum = (attnbody) ? (attnbody->actnum) : 0; - - // "True" async resp is processed here - switch (actnum) { - - case kXR_asyncdi: - // Disconnection + delayed reconnection request - - struct ServerResponseBody_Attn_asyncdi *di; - di = (struct ServerResponseBody_Attn_asyncdi *)unsolmsg->GetData(); - - // Explicit redirection request - if (di) { - Info(XrdClientDebug::kUSERDEBUG, - "ProcessUnsolicitedMsg", "Requested Disconnection + Reconnect in " << - ntohl(di->wsec) << " seconds."); - - fConnModule->SetRequestedDestHost((char *)(fConnModule->GetCurrentUrl().Host.c_str()), - fConnModule->GetCurrentUrl().Port); - fConnModule->SetREQDelayedConnectState(ntohl(di->wsec)); - } - - // Other objects may be interested in this async resp - return kUNSOL_CONTINUE; - break; - - case kXR_asyncrd: - // Redirection request - - struct ServerResponseBody_Attn_asyncrd *rd; - rd = (struct ServerResponseBody_Attn_asyncrd *)unsolmsg->GetData(); - - // Explicit redirection request - if (rd && (strlen(rd->host) > 0)) { - Info(XrdClientDebug::kUSERDEBUG, - "ProcessUnsolicitedMsg", "Requested redir to " << rd->host << - ":" << ntohl(rd->port)); - - fConnModule->SetRequestedDestHost(rd->host, ntohl(rd->port)); - } - - // Other objects may be interested in this async resp - return kUNSOL_CONTINUE; - break; - - case kXR_asyncwt: - // Puts the client in wait state - - struct ServerResponseBody_Attn_asyncwt *wt; - wt = (struct ServerResponseBody_Attn_asyncwt *)unsolmsg->GetData(); - - if (wt) { - Info(XrdClientDebug::kUSERDEBUG, - "ProcessUnsolicitedMsg", "Pausing client for " << ntohl(wt->wsec) << - " seconds."); - - fConnModule->SetREQPauseState(ntohl(wt->wsec)); - } - - // Other objects may be interested in this async resp - return kUNSOL_CONTINUE; - break; - - case kXR_asyncgo: - // Resumes from pause state - - Info(XrdClientDebug::kUSERDEBUG, - "ProcessUnsolicitedMsg", "Resuming from pause."); - - fConnModule->SetREQPauseState(0); - - // Other objects may be interested in this async resp - return kUNSOL_CONTINUE; - break; - - case kXR_asynresp: - // A response to a request which got a kXR_waitresp as a response - - // We pass it direcly to the connmodule for processing - // The processing will tell if the streamid matched or not, - // in order to stop further processing - return fConnModule->ProcessAsynResp(unsolmsg); - break; - - default: - - Info(XrdClientDebug::kUSERDEBUG, - "ProcessUnsolicitedMsg", "Empty message"); - - // Propagate the message - return kUNSOL_CONTINUE; - - } // switch - - - } - else - // Let's see if the message is a communication error message - if (unsolmsg->GetStatusCode() != XrdClientMessage::kXrdMSC_ok) - return fConnModule->ProcessAsynResp(unsolmsg); - - - return kUNSOL_CONTINUE; -} - - - -//_____________________________________________________________________________ -bool XrdClientAdmin::Protocol(kXR_int32 &proto, kXR_int32 &kind) -{ - ClientRequest protoRequest; - - // Set the max transaction duration - fConnModule->SetOpTimeLimit(EnvGetLong(NAME_TRANSACTIONTIMEOUT)); - - memset( &protoRequest, 0, sizeof(protoRequest) ); - - fConnModule->SetSID(protoRequest.header.streamid); - - protoRequest.header.requestid = kXR_protocol; - - char buf[8]; // For now 8 bytes are returned... in future could increase with more infos - bool ret = fConnModule->SendGenCommand(&protoRequest, NULL, - NULL, buf, FALSE, (char *)"Protocol"); - - memcpy(&proto, buf, sizeof(proto)); - memcpy(&kind, buf + sizeof(proto), sizeof(kind)); - - proto = ntohl(proto); - kind = ntohl(kind); - - return ret; -} - -//_____________________________________________________________________________ -bool XrdClientAdmin::Prepare(vecString vs, kXR_char option, kXR_char prty) -{ - // Send a bulk prepare request for a vector of paths - // Split a huge prepare list into smaller chunks - - XrdOucString buf; - - // Set the max transaction duration - fConnModule->SetOpTimeLimit(EnvGetLong(NAME_TRANSACTIONTIMEOUT)); - - if (vs.GetSize() < 75) { - joinStrings(buf, vs); - return Prepare(buf.c_str(), option, prty); - } - - - for (int i = 0; i < vs.GetSize()+50; i+=50) { - joinStrings(buf, vs, i, i+49); - - if (!Prepare(buf.c_str(), option, prty)) return false; - buf = ""; - } - - return true; -} - -//_____________________________________________________________________________ -bool XrdClientAdmin::Prepare(const char *buf, kXR_char option, kXR_char prty) -{ - // Send a bulk prepare request for a '\n' separated list in buf - - ClientRequest prepareRequest; - - // Set the max transaction duration - fConnModule->SetOpTimeLimit(EnvGetLong(NAME_TRANSACTIONTIMEOUT)); - - memset( &prepareRequest, 0, sizeof(prepareRequest) ); - - fConnModule->SetSID(prepareRequest.header.streamid); - - prepareRequest.header.requestid = kXR_prepare; - prepareRequest.prepare.options = option; - prepareRequest.prepare.prty = prty; - - prepareRequest.header.dlen = strlen(buf); - - bool ret = fConnModule->SendGenCommand(&prepareRequest, buf, - NULL, NULL , FALSE, (char *)"Prepare"); - - return ret; -} - -//_____________________________________________________________________________ -bool XrdClientAdmin::DirList(const char *dir, vecString &entries, bool askallservers) { - // Get an ls-like output with respect to the specified dir - - // If this is a redirector, we will be given the list of the servers - // which host this directory - // If askallservers is true then we will just ask for the whole list of servers. - // the query will always be the same, and this will likely skip the 5s delay after the first shot - // The danger is to be forced to contact a huge number of servers in very big clusters - // - bool ret = true; - XrdClientVector hosts; - if (askallservers && (fConnModule->GetServerProtocol() >= 0x291)) { - char str[1024]; - strcpy(str, "*"); - strncat(str, dir, 1023); - if (!Locate((kXR_char *)str, hosts)) return false; - } - else { - XrdClientLocate_Info nfo; - memset(&nfo, 0, sizeof(nfo)); - strcpy((char *)nfo.Location, GetCurrentUrl().HostWPort.c_str()); - hosts.Push_back(nfo); - } - - - // Then we cycle among them asking everyone - bool foundsomething = false; - for (int i = 0; i < hosts.GetSize(); i++) { - - fConnModule->Disconnect(false); - XrdClientUrlInfo url((const char *)hosts[i].Location); - - url.Proto = "root"; - - if (fConnModule->GoToAnotherServer(url) != kOK) { - ret = false; - break; - } - - fConnModule->ClearLastServerError(); - if (!DirList_low(dir, entries)) { - if (fConnModule->LastServerError.errnum != kXR_NotFound) { - ret = false; - break; - } - } - else foundsomething = true; - - - } - - // At the end we want to rewind to the main redirector in any case - GoBackToRedirector(); - - if (!foundsomething) ret = false; - return ret; -} - -//_____________________________________________________________________________ -bool XrdClientAdmin::DirList(const char *dir, - XrdClientVector &dirlistinfo, - bool askallservers) { - // Get an ls-like output with respect to the specified dir - // Here we are also interested in the stat information for each file - - // If this is a redirector, we will be given the list of the servers - // which host this directory - // If askallservers is true then we will just ask for the whole list of servers. - // the query will always be the same, and this will likely skip the 5s delay after the first shot - // The danger is to be forced to contact a huge number of servers in very big clusters - // If this is a concern, one should set askallservers to false - // - bool ret = true; - vecString entries; - XrdClientVector hosts; - XrdOucString fullpath; - - if (askallservers && (fConnModule->GetServerProtocol() >= 0x291)) { - char str[1024]; - strcpy(str, "*"); - strncat(str, dir, 1023); - if (!Locate((kXR_char *)str, hosts)) return false; - } - else { - XrdClientLocate_Info nfo; - memset(&nfo, 0, sizeof(nfo)); - strcpy((char *)nfo.Location, GetCurrentUrl().HostWPort.c_str()); - hosts.Push_back(nfo); - } - - - // Then we cycle among them asking everyone - bool foundsomething = false; - for (int i = 0; i < hosts.GetSize(); i++) { - - fConnModule->Disconnect(false); - XrdClientUrlInfo url((const char *)hosts[i].Location); - url.Proto = "root"; - - if (fConnModule->GoToAnotherServer(url) != kOK) { - ret = false; - break; - } - - fConnModule->ClearLastServerError(); - - int precentries = entries.GetSize(); - if (!DirList_low(dir, entries)) { - if ((fConnModule->LastServerError.errnum != kXR_NotFound) && (fConnModule->LastServerError.errnum != kXR_noErrorYet)) { - ret = false; - break; - } - } - else foundsomething = true; - - int newentries = entries.GetSize(); - - DirListInfo info; - dirlistinfo.Resize(newentries); - - // Here we have the entries. We want to accumulate the stat information for each of them - // We are still connected to the same server which gave the last dirlist response - info.host = GetCurrentUrl().HostWPort; - for (int k = precentries; k < newentries; k++) { - info.fullpath = dir; - if (info.fullpath[info.fullpath.length()-1] != '/') info.fullpath += "/"; - info.fullpath += entries[k]; - info.size = 0; - info.id = 0; - info.flags = 0; - info.modtime = 0; - - if (!Stat(info.fullpath.c_str(), - info.id, - info.size, - info.flags, - info.modtime)) { - ret = false; - //break; - } - - dirlistinfo[k] = info; - - } - - } - - // At the end we want to rewind to the main redirector in any case - GoBackToRedirector(); - - if (!foundsomething) ret = false; - return ret; - } - - - - - -//_____________________________________________________________________________ -bool XrdClientAdmin::DirList_low(const char *dir, vecString &entries) { - bool ret; - // asks the server for the content of a directory - ClientRequest DirListFileRequest; - kXR_char *dl; - - // Set the max transaction duration - fConnModule->SetOpTimeLimit(EnvGetLong(NAME_TRANSACTIONTIMEOUT)); - - memset( &DirListFileRequest, 0, sizeof(ClientRequest) ); - fConnModule->SetSID(DirListFileRequest.header.streamid); - DirListFileRequest.header.requestid = kXR_dirlist; - - DirListFileRequest.dirlist.dlen = strlen(dir); - - // Note that the connmodule has to dynamically alloc the space for the answer - ret = fConnModule->SendGenCommand(&DirListFileRequest, dir, - reinterpret_cast(&dl), 0, TRUE, (char *)"DirList"); - - // Now parse the answer building the entries vector - if (ret) { - - kXR_char *startp = dl, *endp = dl; - char entry[1024]; - XrdOucString e; - - while (startp) { - - if ( (endp = (kXR_char *)strchr((const char*)startp, '\n')) ) { - strncpy(entry, (char *)startp, endp-startp); - entry[endp-startp] = 0; - endp++; - } - else - strcpy(entry, (char *)startp); - - - if (strlen(entry) && strcmp((char *)entry, ".") && strcmp((char *)entry, "..")) { - e = entry; - entries.Push_back(e); - } - - - startp = endp; - } - - - - } - - if (dl) free(dl); - return(ret); - -} - -//_____________________________________________________________________________ -long XrdClientAdmin::GetChecksum(kXR_char *path, kXR_char **chksum) -{ - ClientRequest chksumRequest; - - // Set the max transaction duration - fConnModule->SetOpTimeLimit(EnvGetLong(NAME_TRANSACTIONTIMEOUT)); - - - memset( &chksumRequest, 0, sizeof(chksumRequest) ); - - fConnModule->SetSID(chksumRequest.header.streamid); - - chksumRequest.query.requestid = kXR_query; - chksumRequest.query.infotype = kXR_Qcksum; - chksumRequest.query.dlen = strlen((char *) path); - - bool ret = fConnModule->SendGenCommand(&chksumRequest, (const char*) path, - (void **)chksum, NULL, TRUE, - (char *)"GetChecksum"); - - if (ret) return (fConnModule->LastServerResp.dlen); - else return 0; -} - -int XrdClientAdmin::LocalLocate(kXR_char *path, XrdClientVector &res, - bool writable, int opts, bool all) { - // Fires a locate req towards the currently connected server, and pushes the - // results into the res vector - // - // If 'all' is false, returns the position in the vector of the found info (-1 if - // not found); else returns the number of non-data servers. - - ClientRequest locateRequest; - - char *resp = 0; - int retval = (all) ? 0 : -1; - - memset( &locateRequest, 0, sizeof(locateRequest) ); - - fConnModule->SetSID(locateRequest.header.streamid); - - locateRequest.locate.requestid = kXR_locate; - locateRequest.locate.options = opts; - locateRequest.locate.dlen = strlen((char *) path); - - // Resp is allocated inside the call - bool ret = fConnModule->SendGenCommand(&locateRequest, (const char*) path, - (void **)&resp, 0, true, - (char *)"LocalLocate"); - - if (!ret) return -2; - if (!resp) return -1; - if (!strlen(resp)) { - free(resp); - return -1; - } - - - - // Parse the output - XrdOucString rs(resp), s; - free(resp); - int from = 0; - while ((from = rs.tokenize(s,from,' ')) != -1) { - - // If a token is bad, we keep the ones processed previously - if (s.length() < 8 || (s[2] != '[') || (s[4] != ':')) { - Error("LocalLocate", "Invalid server response. Resp: '" << s << "'"); - continue; - } - - XrdClientLocate_Info nfo; - - // Info type - switch (s[0]) { - case 'S': - nfo.Infotype = XrdClientLocate_Info::kXrdcLocDataServer; - break; - case 's': - nfo.Infotype = XrdClientLocate_Info::kXrdcLocDataServerPending; - break; - case 'M': - nfo.Infotype = XrdClientLocate_Info::kXrdcLocManager; - break; - case 'm': - nfo.Infotype = XrdClientLocate_Info::kXrdcLocManagerPending; - break; - default: - Info(XrdClientDebug::kNODEBUG, "LocalLocate", - "Unknown info type: '" << s << "'"); - } - - // Write capabilities - nfo.CanWrite = (s[1] == 'w') ? 1 : 0; - - // Endpoint url - s.erase(0, s.find("[::")+3); - s.replace("]",""); - strcpy((char *)nfo.Location, s.c_str()); - - res.Push_back(nfo); - - if (nfo.Infotype == XrdClientLocate_Info::kXrdcLocDataServer) { - if (!all) { - if (!writable || nfo.CanWrite) { - retval = res.GetSize() - 1; - break; - } - } - } else { - if (all) - // Count non-dataservers - retval++; - } - } - - return retval; -} - - -//_____________________________________________________________________________ -bool XrdClientAdmin::Locate(kXR_char *path, XrdClientLocate_Info &resp, bool writable) -{ - // Find out any exact location of file 'path' and save the corresponding - // URL in resp. - // Returns true if found - // If the retval is false and writable==true , resp contains a non writable url - // if there is one - - bool found = false; - memset(&resp, 0, sizeof(resp)); - - if (!fConnModule) return 0; - if (!fConnModule->IsConnected()) return 0; - - // Set the max transaction duration - fConnModule->SetOpTimeLimit(EnvGetLong(NAME_TRANSACTIONTIMEOUT)); - - - - // Old servers will use what's there - if (fConnModule->GetServerProtocol() < 0x290) { - long id, flags, modtime; - long long size; - - bool ok = Stat((const char *)path, id, size, flags, modtime); - if (ok && (fConnModule->LastServerResp.status == 0)) { - resp.Infotype = XrdClientLocate_Info::kXrdcLocDataServer; - resp.CanWrite = 1; - strcpy((char *)resp.Location, fConnModule->GetCurrentUrl().HostWPort.c_str()); - } - GoBackToRedirector(); - return ok; - } - - - XrdClientUrlInfo currurl(fConnModule->GetCurrentUrl().GetUrl()); - if (!currurl.HostWPort.length()) return 0; - - // Set up the starting point in the vectored queue - XrdClientVector hosts; - XrdClientLocate_Info nfo; - nfo.Infotype = XrdClientLocate_Info::kXrdcLocManager; - nfo.CanWrite = true; - strcpy((char *)nfo.Location, currurl.HostWPort.c_str()); - hosts.Push_back(nfo); - bool firsthost = true; - XrdClientLocate_Info currnfo; - - int pos = 0; - - // Expand a level, i.e. ask to all the masters and remove items from the list - while (pos < hosts.GetSize()) { - - // Take the first item to process - currnfo = hosts[pos]; - - // If it's a master, we have to contact it, otherwise take the next - if ((currnfo.Infotype == XrdClientLocate_Info::kXrdcLocDataServer) || - (currnfo.Infotype == XrdClientLocate_Info::kXrdcLocDataServerPending)) { - pos++; - continue; - } - - // Here, currnfo is pointing to a master we have to contact - currurl.TakeUrl((char *)currnfo.Location); - if (currurl.HostAddr == "") currurl.HostAddr = currurl.Host; - - // Connect to the given host. At the beginning we are connected to the starting point - // A failed connection is just ignored. Only one attempt is performed. Timeouts are - // honored. - if (!firsthost) { - fConnModule->Disconnect(false); - if (fConnModule->GoToAnotherServer(currurl) != kOK) { - hosts.Erase(pos); - continue; - } - } - - if (firsthost) firsthost = false; - - // We are connected, do the locate - int posds = LocalLocate(path, hosts, writable, kXR_nowait); - - found = (posds > -1) ? 1 : 0; - - if (found) { - resp = hosts[posds]; - break; - } - - // We did not finish, take the next - hosts.Erase(pos); - } - - if (!found && hosts.GetSize()) { - // If not found, we check anyway in the remaining list - // to pick a pending one if present - for (int ii = 0; ii < hosts.GetSize(); ii++) { - currnfo = hosts[ii]; - if ( (currnfo.Infotype == XrdClientLocate_Info::kXrdcLocDataServer) || - (currnfo.Infotype == XrdClientLocate_Info::kXrdcLocDataServerPending) ) { - resp = currnfo; - - if (!writable || resp.CanWrite) { - found = true; - break; - } - - } - - - } - - } - - // At the end we want to rewind to the main redirector in any case - GoBackToRedirector(); - - return found; -} - - -//_____________________________________________________________________________ -bool XrdClientAdmin::Locate( kXR_char *path, - XrdClientVector &hosts, - int opts ) -{ - // Find out any exact location of file 'path' and save the corresponding - // URL in resp. - // Returns true if found at least one - - hosts.Clear(); - - if (!fConnModule) return 0; - if (!fConnModule->IsConnected()) return 0; - - - // Set the max transaction duration - fConnModule->SetOpTimeLimit(EnvGetLong(NAME_TRANSACTIONTIMEOUT)); - - - - // Old servers will use what's there - if (fConnModule->GetServerProtocol() < 0x290) { - long id, flags, modtime; - long long size; - XrdClientLocate_Info resp; - - bool ok = Stat((const char *)path, id, size, flags, modtime); - if (ok && (fConnModule->LastServerResp.status == 0)) { - resp.Infotype = XrdClientLocate_Info::kXrdcLocDataServer; - resp.CanWrite = 1; - strcpy((char *)resp.Location, fConnModule->GetCurrentUrl().HostWPort.c_str()); - hosts.Push_back(resp); - } - GoBackToRedirector(); - - return ok; - } - - XrdClientUrlInfo currurl(fConnModule->GetCurrentUrl().GetUrl()); - if (!currurl.HostWPort.length()) return 0; - - // Set up the starting point in the vectored queue - XrdClientLocate_Info nfo; - nfo.Infotype = XrdClientLocate_Info::kXrdcLocManager; - nfo.CanWrite = true; - strcpy((char *)nfo.Location, currurl.HostWPort.c_str()); - hosts.Push_back(nfo); - bool firsthost = true; - XrdClientLocate_Info currnfo; - - int pos = 0; - - // Expand a level, i.e. ask to all the masters and remove items from the list - while (pos < hosts.GetSize()) { - - // Take the first item to process - currnfo = hosts[pos]; - - // If it's a master, we have to contact it, otherwise take the next - if ((currnfo.Infotype == XrdClientLocate_Info::kXrdcLocDataServer) || - (currnfo.Infotype == XrdClientLocate_Info::kXrdcLocDataServerPending)) { - pos++; - continue; - } - - // Here, currnfo is pointing to a master we have to contact - currurl.TakeUrl((char *)currnfo.Location); - if (currurl.HostAddr == "") currurl.HostAddr = currurl.Host; - - // Connect to the given host. At the beginning we are connected to the starting point - // A failed connection is just ignored. Only one attempt is performed. Timeouts are - // honored. - if (!firsthost) { - - fConnModule->Disconnect(false); - if (fConnModule->GoToAnotherServer(currurl) != kOK) { - hosts.Erase(pos); - continue; - } - } - - if (firsthost) firsthost = false; - - // We are connected, do the locate - LocalLocate(path, hosts, true, opts, true); - - // We did not finish, take the next - hosts.Erase(pos); - } - - // At the end we want to rewind to the main redirector in any case - GoBackToRedirector(); - - return (hosts.GetSize() > 0); -} - -bool XrdClientAdmin::Truncate(const char *path, long long newsize) { - - ClientRequest truncateRequest; - int l = strlen(path); - if (!l) return false; - - // Set the max transaction duration - fConnModule->SetOpTimeLimit(EnvGetLong(NAME_TRANSACTIONTIMEOUT)); - - - memset( &truncateRequest, 0, sizeof(truncateRequest) ); - - fConnModule->SetSID(truncateRequest.header.streamid); - - truncateRequest.header.requestid = kXR_truncate; - truncateRequest.truncate.offset = newsize; - - truncateRequest.header.dlen = l; - - bool ret = fConnModule->SendGenCommand(&truncateRequest, path, - NULL, NULL , FALSE, (char *)"Truncate"); - - return ret; - - -} - - - -// Quickly jump to the former redirector. Useful after having been redirected. -void XrdClientAdmin::GoBackToRedirector() { - - if (fConnModule) { - fConnModule->GoBackToRedirector(); - - if (!fConnModule->IsConnected()) { - XrdClientUrlInfo u(fInitialUrl); - fConnModule->GoToAnotherServer(u); - } - - } - - - -} - - - -// Compute an estimation of the available free space in the given cachefs partition -// The estimation can be fooled if multiple servers mount the same network storage -bool XrdClientAdmin::GetSpaceInfo(const char *logicalname, - long long &totspace, - long long &totfree, - long long &totused, - long long &largestchunk) { - - bool ret = true; - XrdClientVector hosts; - - totspace = 0; - totfree = 0; - totused = 0; - largestchunk = 0; - - if (fConnModule->GetServerProtocol() >= 0x291) { - if (!Locate((kXR_char *)"*", hosts)) return false; - } - else { - XrdClientLocate_Info nfo; - memset(&nfo, 0, sizeof(nfo)); - strcpy((char *)nfo.Location, GetCurrentUrl().HostWPort.c_str()); - hosts.Push_back(nfo); - } - - - // Then we cycle among them asking everyone - for (int i = 0; i < hosts.GetSize(); i++) { - - fConnModule->Disconnect(false); - XrdClientUrlInfo url((const char *)hosts[i].Location); - - url.Proto = "root"; - - if (fConnModule->GoToAnotherServer(url) != kOK) { - ret = false; - break; - } - - - - // Fire the query request and update the results - ClientRequest qspacereq; - - // Set the max transaction duration - fConnModule->SetOpTimeLimit(EnvGetLong(NAME_TRANSACTIONTIMEOUT)); - - - memset( &qspacereq, 0, sizeof(qspacereq) ); - - fConnModule->SetSID(qspacereq.header.streamid); - - qspacereq.query.requestid = kXR_query; - qspacereq.query.infotype = kXR_Qspace; - qspacereq.query.dlen = ( logicalname ? strlen(logicalname) : 0); - - char *resp = 0; - if (fConnModule->SendGenCommand(&qspacereq, logicalname, - (void **)&resp, 0, TRUE, - (char *)"GetSpaceInfo")) { - - XrdOucString rs(resp), s; - free(resp); - - // Here we have the response relative to a server - // Now we are going to have fun in parsing it - - int from = 0; - while ((from = rs.tokenize(s,from,'&')) != -1) { - if (s.length() < 4) continue; - - int pos = s.find("="); - XrdOucString tk, val; - if (pos != STR_NPOS) { - tk.assign(s, 0, pos-1); - val.assign(s, pos+1); -#ifndef WIN32 - if ( (tk == "oss.space") && (val.length() > 1) ) { - totspace += atoll(val.c_str()); - } else - if ( (tk == "oss.free") && (val.length() > 1) ) { - totfree += atoll(val.c_str()); - } else - if ( (tk == "oss.maxf") && (val.length() > 1) ) { - largestchunk = xrdmax(largestchunk, atoll(val.c_str())); - } else - if ( (tk == "oss.used") && (val.length() > 1) ) { - totused += atoll(val.c_str()); - } -#else - if ( (tk == "oss.space") && (val.length() > 1) ) { - totspace += _atoi64(val.c_str()); - } else - if ( (tk == "oss.free") && (val.length() > 1) ) { - totfree += _atoi64(val.c_str()); - } else - if ( (tk == "oss.maxf") && (val.length() > 1) ) { - largestchunk = xrdmax(largestchunk, _atoi64(val.c_str())); - } else - if ( (tk == "oss.used") && (val.length() > 1) ) { - totused += _atoi64(val.c_str()); - } -#endif - } - } - - - } - - } - - // At the end we want to rewind to the main redirector in any case - GoBackToRedirector(); - return ret; - - -} diff --git a/src/XrdClient/XrdClientAdmin.hh b/src/XrdClient/XrdClientAdmin.hh deleted file mode 100644 index b4a41cb58e2..00000000000 --- a/src/XrdClient/XrdClientAdmin.hh +++ /dev/null @@ -1,195 +0,0 @@ -#ifndef XRD_CLIENT_ADMIN_H -#define XRD_CLIENT_ADMIN_H -/******************************************************************************/ -/* */ -/* X r d C l i e n t A d m i n . h h */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2004) */ -/* Adapted from TXNetFile (root.cern.ch) originally done by */ -/* Alvise Dorigo, Fabrizio Furano */ -/* INFN Padova, 2003 */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// A UNIX reference admin client for xrootd. // -// // -////////////////////////////////////////////////////////////////////////// - -#include "XrdClient/XrdClientAbs.hh" -#include "XrdClient/XrdClientVector.hh" -#include "XrdOuc/XrdOucHash.hh" -#include "XrdOuc/XrdOucString.hh" - -typedef XrdClientVector vecString; -typedef XrdClientVector vecBool; - -void joinStrings(XrdOucString &buf, vecString &vs, int startidx = 0, int endidx=-1); - -struct XrdClientLocate_Info { - enum { - kXrdcLocNone, - kXrdcLocDataServer, - kXrdcLocDataServerPending, - kXrdcLocManager, - kXrdcLocManagerPending - } Infotype; - - bool CanWrite; - - kXR_char Location[256]; -}; - -class XrdClientAdmin : public XrdClientAbs { - - XrdOucString fInitialUrl; - bool DirList_low(const char *dir, vecString &entries); - int LocalLocate(kXR_char *path, - XrdClientVector &res, - bool writable, int opts, bool all = false); - protected: - - bool CanRedirOnError() { - // We deny any redir on error - return false; - } - - // To be called after a redirection - bool OpenFileWhenRedirected(char *, bool &); - - public: - XrdClientAdmin(const char *url); - virtual ~XrdClientAdmin(); - - bool Connect(); - - // Some administration functions, see the protocol specs for details - bool SysStatX(const char *paths_list, - kXR_char *binInfo); - - bool Stat(const char *fname, - long &id, - long long &size, - long &flags, - long &modtime); - - - bool Stat_vfs(const char *fname, - int &rwservers, - long long &rwfree, - int &rwutil, - int &stagingservers, - long long &stagingfree, - int &stagingutil); - - bool DirList(const char *dir, - vecString &entries, bool askallservers=false); - - struct DirListInfo { - XrdOucString fullpath; - XrdOucString host; - long long size; - long id; - long flags; - long modtime; - }; - bool DirList(const char *dir, - XrdClientVector &dirlistinfo, - bool askallservers=false); - - bool ExistFiles(vecString&, - vecBool&); - - bool ExistDirs(vecString&, - vecBool&); - - // Compute an estimation of the available free space in the given cachefs partition - // The estimation can be fooled if multiple servers mount the same network storage - bool GetSpaceInfo(const char *logicalname, - long long &totspace, - long long &totfree, - long long &totused, - long long &largestchunk); - - long GetChecksum(kXR_char *path, - kXR_char **chksum); - - // Quickly jump to the former redirector. Useful after having been redirected. - void GoBackToRedirector(); - - bool IsFileOnline(vecString&, - vecBool&); - - bool Mv(const char *fileSrc, - const char *fileDest); - - bool Mkdir(const char *dir, - int user, - int group, - int other); - - bool Chmod(const char *file, - int user, - int group, - int other); - - bool Rm(const char *file); - - bool Rmdir(const char *path); - - bool Protocol(kXR_int32 &proto, - kXR_int32 &kind); - - bool Prepare(vecString vs, - kXR_char opts, - kXR_char prty); - bool Prepare(const char *paths, - kXR_char opts, - kXR_char prty); - - // Gives ONE location of a particular file... if present - // if writable is true only a writable location is searched - // but, if no writable locations are found, the result is negative but may - // propose a non writable one as a bonus - bool Locate(kXR_char *path, XrdClientLocate_Info &resp, - bool writable=false); - - // Gives ALL the locations of a particular file... if present - bool Locate(kXR_char *path, - XrdClientVector &hosts) - { - return Locate( path, hosts, 0 ); - } - - bool Locate(kXR_char *path, - XrdClientVector &hosts, - int opts ); - - - bool Truncate(const char *path, long long newsize); - - UnsolRespProcResult ProcessUnsolicitedMsg(XrdClientUnsolMsgSender *sender, - XrdClientMessage *unsolmsg); - -}; -#endif diff --git a/src/XrdClient/XrdClientCallback.hh b/src/XrdClient/XrdClientCallback.hh deleted file mode 100644 index eb859d4ddd1..00000000000 --- a/src/XrdClient/XrdClientCallback.hh +++ /dev/null @@ -1,49 +0,0 @@ -#ifndef XRD_CLIENTCALLBACK_H -#define XRD_CLIENTCALLBACK_H -/******************************************************************************/ -/* */ -/* X r d C l i e n t C a l l b a c k . h h */ -/* */ -/* Author: Fabrizio Furano (CERN IT-DSS, 2009) */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// Base class for objects receiving events from XrdClient // -// // -////////////////////////////////////////////////////////////////////////// - -class XrdClientAbs; - -class XrdClientCallback -{ - -public: - - // Invoked when an Open request completes with some result. - virtual void OpenComplete(XrdClientAbs *clientP, void *cbArg, bool res) = 0; - - XrdClientCallback() {} - virtual ~XrdClientCallback() {} -}; -#endif diff --git a/src/XrdClient/XrdClientConn.cc b/src/XrdClient/XrdClientConn.cc deleted file mode 100644 index 62fa46e2376..00000000000 --- a/src/XrdClient/XrdClientConn.cc +++ /dev/null @@ -1,2764 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C l i e n t C o n n . c c */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2004) */ -/* Adapted from TXNetFile (root.cern.ch) originally done by */ -/* Alvise Dorigo, Fabrizio Furano */ -/* INFN Padova, 2003 */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// High level handler of connections to xrootd. // -// // -////////////////////////////////////////////////////////////////////////// - -#include "XrdClient/XrdClientDebug.hh" - -#include "XrdClient/XrdClientConnMgr.hh" -#include "XrdClient/XrdClientConn.hh" -#include "XrdClient/XrdClientLogConnection.hh" -#include "XrdClient/XrdClientPhyConnection.hh" -#include "XrdClient/XrdClientProtocol.hh" - -#include "XrdNet/XrdNetAddr.hh" -#include "XrdOuc/XrdOucEnv.hh" -#include "XrdOuc/XrdOucErrInfo.hh" -#include "XrdOuc/XrdOucUtils.hh" -#include "XrdSec/XrdSecInterface.hh" -#include "XrdSec/XrdSecLoadSecurity.hh" -#include "XrdSys/XrdSysDNS.hh" -#include "XrdClient/XrdClientUrlInfo.hh" -#include "XrdClient/XrdClientEnv.hh" -#include "XrdClient/XrdClientAbs.hh" - -#include "XrdClient/XrdClientSid.hh" - -#include "XrdSys/XrdSysPriv.hh" -#include "XrdSys/XrdSysPlatform.hh" -#include "XrdSec/XrdSecLoadSecurity.hh" - -// Dynamic libs -// Bypass Solaris ELF madness -// -#if defined(__solaris__) -#include -#if defined(_ILP32) && (_FILE_OFFSET_BITS != 32) -#undef _FILE_OFFSET_BITS -#define _FILE_OFFSET_BITS 32 -#undef _LARGEFILE_SOURCE -#endif -#endif - -#include // needed by printf -#include // needed by getenv() -#ifndef WIN32 -#include // needed by getpid() -#include // needed by getpid() and getuid() -#else -#include -#include "XrdSys/XrdWin32.hh" -#endif -#include // needed by memcpy() and strcspn() -#include // needed by getservbyname() -#include - -#define SafeDelete(x) { if (x) { delete x; x = 0; } } - - -XrdSysMutex XrdClientConn::fSessionIDRMutex; -XrdOucHash XrdClientConn::fSessionIDRepo; - -// Instance of the Connection Manager -XrdClientConnectionMgr *XrdClientConn::fgConnectionMgr = 0; -XrdOucString XrdClientConn::fgClientHostDomain; - -//_____________________________________________________________________________ -void ParseRedirHost(XrdOucString &host, XrdOucString &opaque, XrdOucString &token) -{ - // Small utility function... we want to parse a hostname which - // can contain opaque or token info - - int pos; - - token = ""; - opaque = ""; - - if ( (pos = host.find('?')) != STR_NPOS ) { - opaque.assign(host,pos+1); - host.erasefromend(host.length()-pos); - - if ( (pos = opaque.find('?')) != STR_NPOS ) { - token.assign(host,pos+1); - opaque.erasefromend(opaque.length()-pos); - } - - } - -} - -//_____________________________________________________________________________ -void ParseRedir(XrdClientMessage* xmsg, int &port, XrdOucString &host, XrdOucString &opaque, XrdOucString &token) -{ - // Small utility function... we want to parse the content - // of a redir response from the server. - - // Remember... an instance of XrdClientMessage automatically 0-terminates the - // data if present - struct ServerResponseBody_Redirect* redirdata = - (struct ServerResponseBody_Redirect*)xmsg->GetData(); - - port = 0; - - if (redirdata) { - XrdOucString h(redirdata->host); - ParseRedirHost(h, opaque, token); - host = h; - port = ntohl(redirdata->port); - } -} - - - - -//_____________________________________________________________________________ -XrdClientConn::XrdClientConn(): fOpenError((XErrorCode)0), fUrl(""), - fLBSUrl(0), - fConnected(false), - fGettingAccessToSrv(false), - fMainReadCache(0), - fREQWaitRespData(0), - fREQWaitTimeLimit(0), - fREQConnectWaitTimeLimit(0), - fMetaUrl( 0 ), - fLBSIsMeta( false ) -{ - // Constructor - ClearLastServerError(); - memset(&LastServerResp, 0, sizeof(LastServerResp)); - LastServerResp.status = kXR_noResponsesYet; - - fREQUrl.Clear(); - fREQWait = new XrdSysCondVar(0); - fREQConnectWait = new XrdSysCondVar(0); - fREQWaitResp = new XrdSysCondVar(0); - fWriteWaitAck = new XrdSysCondVar(0); - - fRedirHandler = 0; - fUnsolMsgHandler = 0; - - // Init the redirection counter parameters - fGlobalRedirLastUpdateTimestamp = time(0); - fGlobalRedirCnt = 0; - fMaxGlobalRedirCnt = EnvGetLong(NAME_MAXREDIRECTCOUNT); - - fOpenSockFD = -1; - - // Init connection manager (only once) - if (!fgConnectionMgr) { - if (!(fgConnectionMgr = new XrdClientConnectionMgr())) { - Error("XrdClientConn::XrdClientConn", "initializing connection manager"); - } - - char buf[255]; - gethostname(buf, sizeof(buf)); - fgClientHostDomain = GetDomainToMatch(buf); - - if (fgClientHostDomain == "") - Error("XrdClientConn", - "Error resolving this host's domain name." ); - - XrdOucString goodDomainsRE = fgClientHostDomain; - goodDomainsRE += "|*"; - - if (EnvGetString(NAME_REDIRDOMAINALLOW_RE) == 0) - EnvPutString(NAME_REDIRDOMAINALLOW_RE, goodDomainsRE.c_str()); - if (EnvGetString(NAME_REDIRDOMAINDENY_RE) == 0) - EnvPutString(NAME_REDIRDOMAINDENY_RE, ""); - if (EnvGetString(NAME_CONNECTDOMAINALLOW_RE) == 0) - EnvPutString(NAME_CONNECTDOMAINALLOW_RE, goodDomainsRE.c_str()); - if (EnvGetString(NAME_CONNECTDOMAINDENY_RE) == 0) - EnvPutString(NAME_CONNECTDOMAINDENY_RE, ""); - } - - // Server type unknown at initialization - fServerType = kSTNone; -} - -//_____________________________________________________________________________ -XrdClientConn::~XrdClientConn() -{ - - - // Disconnect underlying logical connection - Disconnect(FALSE); - - // Destructor - if (fMainReadCache && (DebugLevel() >= XrdClientDebug::kUSERDEBUG)) - fMainReadCache->PrintPerfCounters(); - - if (fLBSUrl) delete fLBSUrl; - - if (fMainReadCache) delete fMainReadCache; - fMainReadCache = 0; - - delete fREQWait; - fREQWait = 0; - - delete fREQConnectWait; - fREQConnectWait = 0; - - delete fREQWaitResp; - fREQWaitResp = 0; - - delete fWriteWaitAck; - fWriteWaitAck = 0; - -} - -//_____________________________________________________________________________ -short XrdClientConn::Connect(XrdClientUrlInfo Host2Conn, - XrdClientAbsUnsolMsgHandler *unsolhandler) -{ - // Connect method (called the first time when XrdClient is first created, - // and used for each redirection). The global static connection manager - // object is firstly created here. If another XrdClient object is created - // inside the same application this connection manager will be used and - // no new one will be created. - // No login/authentication are performed at this stage. - - // We try to connect to the host. What we get is the logical conn id - short logid; - logid = -1; - fPrimaryStreamid = 0; - fLogConnID = 0; - - CheckREQConnectWaitState(); - - Info(XrdClientDebug::kHIDEBUG, - "XrdClientConn", "Trying to connect to " << - Host2Conn.HostAddr << ":" << Host2Conn.Port); - - logid = ConnectionManager->Connect(Host2Conn); - - Info(XrdClientDebug::kHIDEBUG, - "Connect", "Connect(" << Host2Conn.Host << ", " << - Host2Conn.Port << ") returned " << - logid ); - - if (logid < 0) { - Error("XrdNetFile", - "Error creating logical connection to " << - Host2Conn.Host << ":" << Host2Conn.Port ); - - fLogConnID = logid; - fConnected = FALSE; - return -1; - } - - fConnected = TRUE; - - fLogConnID = logid; - fPrimaryStreamid = ConnectionManager->GetConnection(fLogConnID)->Streamid(); - - ConnectionManager->GetConnection(fLogConnID)->UnsolicitedMsgHandler = unsolhandler; - fUnsolMsgHandler = unsolhandler; - - return logid; -} - -//_____________________________________________________________________________ -void XrdClientConn::Disconnect(bool ForcePhysicalDisc) -{ - // Disconnect... is it so difficult? Yes! - if( ConnectionManager->SidManager() ) - ConnectionManager->SidManager()->GetAllOutstandingWriteRequests(fPrimaryStreamid, fWriteReqsToRetry); - - if (fMainReadCache && (DebugLevel() >= XrdClientDebug::kDUMPDEBUG) ) fMainReadCache->PrintCache(); - - if (fConnected) - ConnectionManager->Disconnect(fLogConnID, ForcePhysicalDisc); - - fConnected = FALSE; -} - -//_____________________________________________________________________________ -XrdClientMessage *XrdClientConn::ClientServerCmd(ClientRequest *req, const void *reqMoreData, - void **answMoreDataAllocated, - void *answMoreData, bool HasToAlloc, - int substreamid) -{ - // ClientServerCmd tries to send a command to the server and to get a response. - // Here the kXR_redirect is handled, as well as other things. - // - // If the calling function requests the memory allocation (HasToAlloc is true) - // then: - // o answMoreDataAllocated is filled with a pointer to the new block. - // o The caller MUST free it when it's no longer used if - // answMoreDataAllocated is 0 - // then the caller is not interested in getting the data. - // o We must anyway read it from the stream and throw it away. - // - // If the calling function does NOT request the memory allocation - // (HasToAlloc is false) then: - // o answMoreData is filled with the data read - // o the caller MUST be sure that the arriving data will fit into the - // o passed memory block - // - // We need to do this here because the calling func *may* not know the size - // to allocate for the request to be submitted. For instance, for the kXR_read - // cmd the size is known, while for the kXR_getfile cmd is not. - - bool addOpaque = false; - - size_t TotalBlkSize = 0; - - void *tmpMoreData; - XReqErrorType errorType = kOK; - - XrdClientMessage *xmsg = 0; - - // Check for a redrive of an open from a previous redirect - // - if (req->header.requestid == kXR_open && fRedirOpaque.length()) - addOpaque = true; - - // In the case of an abort due to errors, better to return - // a blank struct. Also checks the validity of the pointer. - // memset(answhdr, 0, sizeof(answhdr)); - - // Cycle for redirections... - do { - // Send to the server the request - - // We have to unconditionally set the streamid inside the - // header, because, in case of 'rebouncing here', the Logical Connection - // ID might have changed, while in the header to write it remained the - // same as before, not valid anymore - SetSID(req->header.streamid); - -// Check for redirections of a filename -// - if (addOpaque) - {string new_fname; - int oldlen = req->header.dlen; - new_fname.assign((const char *)reqMoreData); - if (new_fname.find('?') == string::npos) new_fname += "?"; - else new_fname += "&"; - new_fname += string(fRedirOpaque.c_str()); - req->open.dlen = new_fname.length(); - fRedirOpaque.erase(); addOpaque = false; - errorType = WriteToServer(req, new_fname.c_str(), fLogConnID, substreamid); - req->header.dlen = oldlen; - } else { - errorType = WriteToServer(req, reqMoreData, fLogConnID, substreamid); - } - - // Read from server the answer - // Note that the answer can be composed by many reads, in the case that - // the status field of the responses is kXR_oksofar - - TotalBlkSize = 0; - - // A temp pointer to the mem block growing across the multiple kXR_oksofar - tmpMoreData = 0; - if ((answMoreData != 0) && !HasToAlloc) - tmpMoreData = answMoreData; - - // Cycle for the kXR_oksofar i.e. partial answers to be collected - do { - - XrdClientConn::EThreeStateReadHandler whatToDo; - - delete xmsg; - - xmsg = ReadPartialAnswer(errorType, TotalBlkSize, req, HasToAlloc, - &tmpMoreData, whatToDo); - - // If the cmd went ok and was a read request, we use it to populate - // the cache - if (xmsg && fMainReadCache && (req->header.requestid == kXR_read) && - ((xmsg->HeaderStatus() == kXR_oksofar) || - (xmsg->HeaderStatus() == kXR_ok))) - // To compute the end offset of the block we have to take 1 from the size! - fMainReadCache->SubmitXMessage(xmsg, req->read.offset + TotalBlkSize - xmsg->fHdr.dlen, - req->read.offset + TotalBlkSize - 1); - - if (whatToDo == kTSRHReturnNullMex) { - delete xmsg; - return 0; - } - - if (whatToDo == kTSRHReturnMex) - return xmsg; - - if (xmsg && (xmsg->HeaderStatus() == kXR_oksofar) && - (xmsg->DataLen() == 0)) - return xmsg; - - } while (xmsg && (xmsg->HeaderStatus() == kXR_oksofar)); - - if (xmsg && (xmsg->HeaderStatus() == kXR_redirect) && fRedirOpaque.length() - && ( req->header.requestid == kXR_open - || (req->header.requestid == kXR_stat && req->header.dlen) - || req->header.requestid == kXR_dirlist - || req->header.requestid == kXR_locate - || req->header.requestid == kXR_mkdir - || req->header.requestid == kXR_rm - || req->header.requestid == kXR_rmdir - || (req->header.requestid == kXR_truncate && req->header.dlen) - || req->header.requestid == kXR_mv - || req->header.requestid == kXR_chmod)) - addOpaque = true; - - } while ((fGlobalRedirCnt < fMaxGlobalRedirCnt) && - !IsOpTimeLimitElapsed(time(0)) && - xmsg && (xmsg->HeaderStatus() == kXR_redirect)); - - // We collected all the partial responses into a single memory block. - // If the block has been allocated here then we must pass its address - if (HasToAlloc && (answMoreDataAllocated)) { - *answMoreDataAllocated = tmpMoreData; - } - - // We might have collected multiple partial response also in a given mem block - if (xmsg && (xmsg->HeaderStatus() == kXR_ok) && TotalBlkSize) - xmsg->fHdr.dlen = TotalBlkSize; - - return xmsg; -} - -//_____________________________________________________________________________ -bool XrdClientConn::SendGenCommand(ClientRequest *req, const void *reqMoreData, - void **answMoreDataAllocated, - void *answMoreData, bool HasToAlloc, - char *CmdName, - int substreamid) { - - // SendGenCommand tries to send a single command for a number of times - - short retry = 0; - bool resp = FALSE, abortcmd = FALSE; - - // if we're going to open a file for the 2nd time we should reset fOpenError, - // just in case... - if (req->header.requestid == kXR_open) - fOpenError = (XErrorCode)0; - - while (!abortcmd && !resp) { - abortcmd = FALSE; - - // This client might have been paused - CheckREQPauseState(); - - // Send the cmd, dealing automatically with redirections and - // redirections on error - Info(XrdClientDebug::kHIDEBUG, - "SendGenCommand","Sending command " << CmdName); - - // Note: some older server versions expose a bug associated to kXR_retstat - if ( (req->header.requestid == kXR_open) && - (GetServerProtocol() < 0x00000270) ) { - if (req->open.options & kXR_retstat) - req->open.options ^= kXR_retstat; - - Info(XrdClientDebug::kHIDEBUG, "SendGenCommand", - "Old server proto version(" << GetServerProtocol() << - ". kXR_retstat is now disabled. Current open options: " << req->open.options); - } - - XrdClientMessage *cmdrespMex = ClientServerCmd(req, reqMoreData, - answMoreDataAllocated, - answMoreData, HasToAlloc, - substreamid); - // Save server response header if requested - if (cmdrespMex) - memcpy(&LastServerResp, &cmdrespMex->fHdr,sizeof(struct ServerResponseHeader)); - - // Check for the max time allowed for this request - if (IsOpTimeLimitElapsed(time(0))) { - Error("SendGenCommand", - "Max time limit elapsed for request " << - convertRequestIdToChar(req->header.requestid) << - ". Aborting command."); - abortcmd = TRUE; - - } else - // Check for the redir count limit - if (fGlobalRedirCnt >= fMaxGlobalRedirCnt) { - Error("SendGenCommand", - "Too many redirections for request " << - convertRequestIdToChar(req->header.requestid) << - ". Aborting command."); - - abortcmd = TRUE; - } - else { - - // On serious communication error we retry for a number of times, - // waiting for the server to come back - if (!cmdrespMex || cmdrespMex->IsError()) { - - Info(XrdClientDebug::kHIDEBUG, - "SendGenCommand", "Got (and maybe recovered) an error from " << - fUrl.Host << ":" << fUrl.Port); - - - // For the kxr_open request we don't rely on the count limit of other - // reqs. The open request is bounded only by the redir count limit - if (req->header.requestid != kXR_open) - retry++; - - if (retry > kXR_maxReqRetry) { - Error("SendGenCommand", - "Too many errors communication errors with server" - ". Aborting command."); - - abortcmd = TRUE; - } - - else - if (req->header.requestid == kXR_bind) { - Info(XrdClientDebug::kHIDEBUG, - "SendGenCommand", "Parallel stream bind failure. Aborting request." << - fUrl.Host << ":" << fUrl.Port); - - abortcmd = TRUE; - } - - - else { - - // Here we are connected, but we could not have a filehandle for - // various reasons. The server may have denied it to us. - // So, if we were requesting things that needed a filehandle, - // and the file seems not open, then abort the request - if ( (LastServerResp.status != kXR_ok) && - ( (req->header.requestid == kXR_read) || - (req->header.requestid == kXR_write) || - (req->header.requestid == kXR_sync) || - (req->header.requestid == kXR_close) ) ) { - - Info(XrdClientDebug::kHIDEBUG, - "SendGenCommand", "Recovery failure detected. Aborting request." << - fUrl.Host << ":" << fUrl.Port); - - abortcmd = TRUE; - - } - else - abortcmd = FALSE; - - } - } else { - - // We are here if we got an answer for the command, so - // the server (original or redirected) is alive - resp = CheckResp(&cmdrespMex->fHdr, CmdName); - retry++; - - - // If the answer was not (or not totally) positive, we must - // investigate on the result - if (!resp) { - - - // this could be a delayed response. A Strange hybrid. Not a quark. - if (cmdrespMex->fHdr.status == kXR_waitresp) { - // Let's sleep! - kXR_int32 *maxwait = (kXR_int32 *)cmdrespMex->GetData(); - kXR_int32 mw; - - if (maxwait) - mw = ntohl(*maxwait); - else mw = 30; - - if (!WaitResp(mw)) { - // we did not time out, so the response is here - - memcpy(&LastServerResp, &fREQWaitRespData->resphdr, - sizeof(struct ServerResponseHeader)); - - // Let's fake a regular answer - - // Note: kXR_wait can be a fake response used to make the client retry! - if (fREQWaitRespData->resphdr.status == kXR_wait) { - cmdrespMex->fHdr.status = kXR_wait; - if (fREQWaitRespData->resphdr.dlen) - memcpy(cmdrespMex->GetData(), fREQWaitRespData->respdata, sizeof(kXR_int32)); - else memset(cmdrespMex->GetData(), 0, sizeof(kXR_int32)); - - CheckErrorStatus(cmdrespMex, retry, CmdName); - // This causes a retry - resp = false; - } - else { - - if (HasToAlloc) { - *answMoreDataAllocated = malloc(LastServerResp.dlen); - memcpy(*answMoreDataAllocated, - &fREQWaitRespData->respdata, - LastServerResp.dlen); - } - else { - - if (answMoreData) memcpy(answMoreData, - &fREQWaitRespData->respdata, - LastServerResp.dlen); - - } - - // This makes the request exit with the new answer - resp = true; - } - - free( fREQWaitRespData); - fREQWaitRespData = 0; - - // abortcmd = false; Don't set abort categorically false, check for error! - abortcmd = (LastServerResp.status == kXR_error); - - } - - - } - else { - - abortcmd = CheckErrorStatus(cmdrespMex, retry, CmdName); - - // An open request which fails for an application reason like kxr_wait - // must have its kXR_Refresh bit cleared. - if (req->header.requestid == kXR_open) - req->open.options &= ((kXR_unt16)~kXR_refresh); - } - } - - if (retry > kXR_maxReqRetry) { - Error("SendGenCommand", - "Too many errors messages from server." - " Aborting command."); - - abortcmd = TRUE; - } - } // else... the case of a correct server response but declaring an error - } - - delete cmdrespMex; - } // while - - return (!abortcmd); -} - -//_____________________________________________________________________________ -bool XrdClientConn::CheckHostDomain(XrdOucString hostToCheck) -{ - // Checks domain matching - - static XrdOucHash knownHosts; - static XrdOucString alloweddomains = EnvGetString(NAME_REDIRDOMAINALLOW_RE); - static XrdOucString denieddomains = EnvGetString(NAME_REDIRDOMAINDENY_RE); - static XrdSysMutex knownHostsMutex; - - XrdSysMutexHelper scopedLock(knownHostsMutex); - - // Check cached info - int *he = knownHosts.Find(hostToCheck.c_str()); - if (he) - return (*he == 1) ? TRUE : FALSE; - - // Get the domain for the url to check - XrdOucString domain = GetDomainToMatch(hostToCheck); - - // If we are unable to get the domain for the url to check --> access denied to it - if (domain.length() <= 0) { - Error("CheckHostDomain", "Error resolving domain name for " << - hostToCheck << ". Denying access."); - return FALSE; - } - Info(XrdClientDebug::kHIDEBUG, "CheckHostDomain", "Resolved [" << hostToCheck << - "]'s domain name into [" << domain << "]" ); - - // Given a list of |-separated regexps for the hosts to DENY, - // match every entry with domain. If any match is found, deny access. - if (DomainMatcher(domain, denieddomains) ) { - knownHosts.Add(hostToCheck.c_str(), new int(0)); - Error("CheckHostDomain", "Access denied to the domain of [" << hostToCheck << "]."); - return FALSE; - } - - // Given a list of |-separated regexps for the hosts to ALLOW, - // match every entry with domain. If any match is found, grant access. - if (DomainMatcher(domain, alloweddomains) ) { - knownHosts.Add(hostToCheck.c_str(), new int(1)); - Info(XrdClientDebug::kHIDEBUG, "CheckHostDomain", - "Access granted to the domain of [" << hostToCheck << "]."); - return TRUE; - } - - Error("CheckHostDomain", "Access to domain " << domain << - " is not allowed nor denied: deny."); - - return FALSE; - -} - -//___________________________________________________________________________ -bool XrdClientConn::DomainMatcher(XrdOucString dom, XrdOucString domlist) -{ - // Check matching of domain 'dom' in list 'domlist'. - // Items in list are separated by '|' and can contain the wild - // cards '*', e.g. - // - // domlist.c_str() = "cern.ch|*.stanford.edu|slac.*.edu" - // - // The domain to match is a FQDN host or domain name, e.g. - // - // dom.c_str() = "flora02.slac.stanford.edu" - // - - Info(XrdClientDebug::kHIDEBUG, "DomainMatcher", - "search for '"< 0) { - XrdOucString domain; - int nm = 0, from = 0; - while ((from = domlist.tokenize(domain, from, '|')) != STR_NPOS) { - Info(XrdClientDebug::kDUMPDEBUG, "DomainMatcher", - "checking domain: "< 0) { - Info(XrdClientDebug::kHIDEBUG, "DomainMatcher", - "domain: "<status == kXR_redirect) { - // too many redirections. Exit! - Error(method, "Error in handling a redirection."); - return FALSE; - } - - if ((resp->status != kXR_ok) && (resp->status != kXR_authmore)) - // Error message is notified in CheckErrorStatus - return FALSE; - - return TRUE; - - } else { - Error(method, "The return message doesn't belong to this client."); - return FALSE; - } -} - -//_____________________________________________________________________________ -bool XrdClientConn::MatchStreamid(struct ServerResponseHeader *ServerResponse) -{ - // Check stream ID matching between the given response and - // the one contained in the current logical conn - - - return ( memcmp(ServerResponse->streamid, - &fPrimaryStreamid, - sizeof(ServerResponse->streamid)) == 0 ); -} - -//_____________________________________________________________________________ -void XrdClientConn::SetSID(kXR_char *sid) { - // Set our stream id, to match against that one in the server's response. - - memcpy((void *)sid, (const void*)&fPrimaryStreamid, 2); -} - - -//_____________________________________________________________________________ -XReqErrorType XrdClientConn::WriteToServer(ClientRequest *req, - const void* reqMoreData, - short LogConnID, - int substreamid) { - - // Send message to server - ClientRequest req_netfmt = *req; - XrdClientLogConnection *lgc = 0; - XrdClientPhyConnection *phyc = 0; - - if (DebugLevel() >= XrdClientDebug::kDUMPDEBUG) - smartPrintClientHeader(req); - - lgc = ConnectionManager->GetConnection(LogConnID); - if (!lgc) { - Error("WriteToServer", - "Unknown logical conn " << LogConnID); - - return kWRITE; - } - - phyc = lgc->GetPhyConnection(); - if (!phyc) { - Error("WriteToServer", - "Cannot find physical conn for logid " << LogConnID); - - return kWRITE; - } - - clientMarshall(&req_netfmt); - - // Strong mutual exclusion over the physical channel - { - XrdClientPhyConnLocker pcl(phyc); - - // Now we write the request to the logical connection through the - // connection manager - - short len = sizeof(req->header); - - // A request header is always sent through the main stream, except for kxr_bind! - int writeres; - if ( req->header.requestid == kXR_bind ) - writeres = ConnectionManager->WriteRaw(LogConnID, &req_netfmt, len, substreamid); - else - writeres = ConnectionManager->WriteRaw(LogConnID, &req_netfmt, len, 0); - - fLastDataBytesSent = req->header.dlen; - - // A complete communication failure has to be handled later, but we - // don't have to abort what we are doing - if (writeres < 0) { - Error("WriteToServer", - "Error sending " << len << " bytes in the header part" - " to server [" << - fUrl.Host << ":" << fUrl.Port << "]."); - - return kWRITE; - } - - // Send to the server the data. - // If we got an error we can safely skip this... no need to get more - if (req->header.dlen > 0) { - - // Now we write the data associated to the request. Through the - // connection manager - // the data chunk can be sent through a parallel stream - writeres = ConnectionManager->WriteRaw(LogConnID, reqMoreData, - req->header.dlen, substreamid); - - // A complete communication failure has to be handled later, but we - // don't have to abort what we are doing - if (writeres < 0) { - Error("WriteToServer", - "Error sending " << req->header.dlen << " bytes in the data part" - " to server [" << - fUrl.Host << ":" << fUrl.Port << "]."); - - return kWRITE; - } - } - - fLastDataBytesSent = req->header.dlen; - return kOK; - } -} - -//_____________________________________________________________________________ -bool XrdClientConn::CheckErrorStatus(XrdClientMessage *mex, short &Retry, char *CmdName) -{ - // Check error status, returns true if the retrials have to be aborted - - if (mex->HeaderStatus() == kXR_redirect) { - // Too many redirections - Error("CheckErrorStatus", - "Error while being redirected for request " << CmdName ); - return TRUE; - } - - if (mex->HeaderStatus() == kXR_error) { - // The server declared an error. - // In this case it's better to exit, unhandled error - - struct ServerResponseBody_Error *body_err; - - body_err = (struct ServerResponseBody_Error *)(mex->GetData()); - - if (body_err) { - // Print out the error information, as received by the server - - fOpenError = (XErrorCode)ntohl(body_err->errnum); - - Info(XrdClientDebug::kNODEBUG, "CheckErrorStatus", "Server [" << GetCurrentUrl().HostWPort << - "] declared: " << - (const char*)body_err->errmsg << "(error code: " << fOpenError << ")"); - - // Save the last error received - memset(&LastServerError, 0, sizeof(LastServerError)); - memcpy(&LastServerError, body_err, mex->DataLen()); - LastServerError.errnum = fOpenError; - - } - return TRUE; - } - - if (mex->HeaderStatus() == kXR_wait) { - // We have to wait for a specified number of seconds and then - // retry the same cmd - - struct ServerResponseBody_Wait *body_wait; - - body_wait = (struct ServerResponseBody_Wait *)mex->GetData(); - - if (body_wait) { - - if (mex->DataLen() > 4) - Info(XrdClientDebug::kUSERDEBUG, "CheckErrorStatus", "Server [" << - fUrl.Host << ":" << fUrl.Port << - "] requested " << ntohl(body_wait->seconds) << " seconds" - " of wait. Server message is " << body_wait->infomsg) - else - Info(XrdClientDebug::kUSERDEBUG, "CheckErrorStatus", "Server [" << - fUrl.Host << ":" << fUrl.Port << - "] requested " << ntohl(body_wait->seconds) << " seconds" - " of wait") - - // Check if we have to sleep - int cmw = (getenv("XRDCLIENTMAXWAIT")) ? atoi(getenv("XRDCLIENTMAXWAIT")) : -1; - int bws = (int)ntohl(body_wait->seconds); - if ((cmw > -1) && cmw < bws) { - Error("CheckErrorStatus", "XROOTD MaxWait forced - file is offline" - ". Aborting command. " << cmw << " : " << bws); - Retry= kXR_maxReqRetry; - return TRUE; - } - - - // Look for too stupid a delay. In this case, set a reasonable value. - int newbws = bws; - if (bws <= 0) newbws = 1; - if (bws > 1800) newbws = 10; - if (bws != newbws) - Error("CheckErrorStatus", "Sleep time fixed from " << bws << " to " << newbws); - - // Sleep now, and hope that the sandman does not enter here. - sleep(newbws); - } - - // We don't want kxr_wait to count as an error - Retry--; - return FALSE; - } - - // We don't understand what the server said. Better investigate on it... - Error("CheckErrorStatus", - "Answer from server [" << - fUrl.Host << ":" << fUrl.Port << - "] not recognized after executing " << CmdName); - - return TRUE; -} - -//_____________________________________________________________________________ -XrdClientMessage *XrdClientConn::ReadPartialAnswer(XReqErrorType &errorType, - size_t &TotalBlkSize, - ClientRequest *req, - bool HasToAlloc, void** tmpMoreData, - EThreeStateReadHandler &what_to_do) -{ - // Read server answer - - XrdClientMessage *Xmsg = 0; - void *tmp2MoreData; - - // No need to actually read if we are in error... - if (errorType == kOK) { - - - Info(XrdClientDebug::kHIDEBUG, "ReadPartialAnswer", - "Reading a XrdClientMessage from the server [" << - fUrl.Host << ":" << fUrl.Port << "]..."); - - // A complete communication failure has to be handled later, but we - // don't have to abort what we are doing - - // Beware! Now Xmsg contains ALSO the information about the esit of - // the communication at low level. - Xmsg = ConnectionManager->ReadMsg(fLogConnID); - - fLastDataBytesRecv = Xmsg ? Xmsg->DataLen() : 0; - - if ( !Xmsg || (Xmsg->IsError()) ) { - Info(XrdClientDebug::kNODEBUG, "ReadPartialAnswer", "Failed to read msg from connmgr" - " (server [" << fUrl.Host << ":" << fUrl.Port << "]). Retrying ..."); - - if (HasToAlloc) { - if (*tmpMoreData) - free(*tmpMoreData); - *tmpMoreData = 0; - } - errorType = kREAD; - } - else - // is not necessary because the Connection Manager unmarshalls the mex - Xmsg->Unmarshall(); - } - - if (Xmsg != 0) - if (DebugLevel() >= XrdClientDebug::kDUMPDEBUG) - smartPrintServerHeader(&Xmsg->fHdr); - - // Now we have all the data. We must copy it back to the buffer where - // they are needed, only if we are not in troubles with errorType - if ((errorType == kOK) && (Xmsg->DataLen() > 0)) { - - // If this is a redirection answer, its data MUST NOT overwrite - // the given buffer - if ( (Xmsg->HeaderStatus() == kXR_ok) || - (Xmsg->HeaderStatus() == kXR_oksofar) || - (Xmsg->HeaderStatus() == kXR_authmore) ) - { - // Now we allocate a sufficient memory block, if needed - // If the calling function passed a null pointer, then we - // fill it with the new pointer, otherwise the func knew - // about the size of the expected answer, and we use - // the given pointer. - // We need to do this here because the calling func *may* not - // know the size to allocate - // For instance, for the ReadBuffer cmd the size is known, while - // for the ReadFile cmd is not - if (HasToAlloc) { - tmp2MoreData = realloc(*tmpMoreData, TotalBlkSize + Xmsg->DataLen()); - if (!tmp2MoreData) { - - Error("ReadPartialAnswer", "Error reallocating " << - TotalBlkSize << " bytes."); - - free(*tmpMoreData); - *tmpMoreData = 0; - what_to_do = kTSRHReturnNullMex; - - delete Xmsg; - - return 0; - } - *tmpMoreData = tmp2MoreData; - } - - // Now we copy the content of the Xmsg to the buffer where - // the data are needed - if (*tmpMoreData) - memcpy(((kXR_char *)(*tmpMoreData)) + TotalBlkSize, - Xmsg->GetData(), Xmsg->DataLen()); - - // Dump the buffer tmpMoreData -// if (DebugLevel() >= XrdClientDebug::kDUMPDEBUG) { - -// Info (XrdClientDebug::kDUMPDEBUG, "ReadPartialAnswer","Dumping read data..."); -// for(int jj = 0; jj < Xmsg->DataLen(); jj++) { -// printf("0x%.2x ", *( ((kXR_char *)Xmsg->GetData()) + jj ) ); -// if ( !((jj+1) % 10) ) printf("\n"); -// } -// printf("\n\n"); -// } - - TotalBlkSize += Xmsg->DataLen(); - - } else { - - Info(XrdClientDebug::kHIDEBUG, "ReadPartialAnswer", - "Server [" << - fUrl.Host << ":" << fUrl.Port << "] answered [" << - convertRespStatusToChar(Xmsg->fHdr.status) << - "] (" << Xmsg->fHdr.status << ")"); - } - } // End of DATA reading - - // Now answhdr contains the server response. We pass it as is to the - // calling function. - // The only exception is that we must deal here with redirections. - // If the server redirects us, then we - // add 1 to redircnt - // close the logical connection - // try to connect to the new destination. - // login/auth to the new destination (this can generate other calls - // to this method if it has been invoked by DoLogin!) - // Reopen the file if the current fhandle value is not null (this - // can generate other calls to this method, not for the dologin - // phase) - // resend the command - // - // Also a READ/WRITE error requires a redirection - // - if ( (errorType == kREAD) || - (errorType == kWRITE) || - isRedir(&Xmsg->fHdr) ) - { - // this procedure can decide if return to caller or - // continue with processing - - ESrvErrorHandlerRetval Return = HandleServerError(errorType, Xmsg, req); - - if (Return == kSEHRReturnMsgToCaller) { - // The caller is allowed to continue its processing - // with the current Xmsg - // Note that this can be a way to stop retrying - // e.g. if the resp in Xmsg is kxr_redirect, it means - // that the redir limit has been reached - if (HasToAlloc) { - free(*tmpMoreData); - *tmpMoreData = 0; - } - - // Return the message to the client (SendGenCommand) - what_to_do = kTSRHReturnMex; - return Xmsg; - } - - if (Return == kSEHRReturnNoMsgToCaller) { - // There was no Xmsg to return, or the previous one - // has no meaning anymore - - // The caller will retry the cmd for some times, - // If we are connected the attempt will go OK, - // otherwise the next retry will fail, causing a - // redir to the lb or a rebounce. - if (HasToAlloc) { - free(*tmpMoreData); - *tmpMoreData = 0; - } - - delete Xmsg; - Xmsg = 0; - - what_to_do = kTSRHReturnMex; - return Xmsg; - } - } - - what_to_do = kTSRHContinue; - return Xmsg; -} - - -//_____________________________________________________________________________ -bool XrdClientConn::GetAccessToSrv() -{ - // Gets access to the connected server. The login and authorization steps - // are performed here (calling method DoLogin() that performs logging-in - // and calls DoAuthentication() ). - // If the server redirects us, this is gently handled by the general - // functions devoted to the handling of the server's responses. - // Nothing is visible here, and nothing is visible from the other high - // level functions. - - XrdClientLogConnection *logconn = ConnectionManager->GetConnection(fLogConnID); - - // This is to prevent recursion in this delicate phase - if (fGettingAccessToSrv) { - logconn->GetPhyConnection()->StartReader(); - return true; - } - - fGettingAccessToSrv = true; - - switch ((fServerType = DoHandShake(fLogConnID))) { - case kSTError: - Info(XrdClientDebug::kNODEBUG, - "GetAccessToSrv", - "HandShake failed with server [" << - fUrl.Host << ":" << fUrl.Port << "]"); - - Disconnect(TRUE); - - fGettingAccessToSrv = false; - return FALSE; - - case kSTNone: - Info(XrdClientDebug::kNODEBUG, - "GetAccessToSrv", "The server on [" << - fUrl.Host << ":" << fUrl.Port << "] is unknown"); - - Disconnect(TRUE); - - fGettingAccessToSrv = false; - return FALSE; - - case kSTRootd: - - if (EnvGetLong(NAME_KEEPSOCKOPENIFNOTXRD) == 1) { - Info(XrdClientDebug::kHIDEBUG, - "GetAccessToSrv","Ok: the server on [" << - fUrl.Host << ":" << fUrl.Port << - "] is a rootd. Saving socket for later use."); - // Get socket descriptor - fOpenSockFD = logconn->GetPhyConnection()->SaveSocket(); - Disconnect(TRUE); - ConnectionManager->GarbageCollect(); - break; - - } else { - - Info(XrdClientDebug::kHIDEBUG, - "GetAccessToSrv","Ok: the server on [" << - fUrl.Host << ":" << fUrl.Port << "] is a rootd." - " Not supported."); - - Disconnect(TRUE); - - fGettingAccessToSrv = false; - return FALSE; - } - - case kSTBaseXrootd: - - Info(XrdClientDebug::kHIDEBUG, - "GetAccessToSrv", - "Ok: the server on [" << - fUrl.Host << ":" << fUrl.Port << "] is an xrootd redirector."); - - logconn->GetPhyConnection()->SetTTL(EnvGetLong(NAME_LBSERVERCONN_TTL)); - - break; - - case kSTMetaXrootd: - - Info(XrdClientDebug::kHIDEBUG, - "GetAccessToSrv", - "Ok: the server on [" << - fUrl.Host << ":" << fUrl.Port << "] is an xrootd meta manager."); - - logconn->GetPhyConnection()->SetTTL(EnvGetLong(NAME_LBSERVERCONN_TTL)); - - break; - - case kSTDataXrootd: - - Info( XrdClientDebug::kHIDEBUG, - "GetAccessToSrv", - "Ok, the server on [" << - fUrl.Host << ":" << fUrl.Port << "] is an xrootd data server."); - - logconn->GetPhyConnection()->SetTTL(EnvGetLong(NAME_DATASERVERCONN_TTL)); - - break; - } - - bool retval = false; - - - XrdClientPhyConnection *phyc = logconn->GetPhyConnection(); - if (!phyc) { - fGettingAccessToSrv = false; - return false; - } - - XrdClientPhyConnLocker pl(phyc); - - // Execute a login if connected to a xrootd server - if (fServerType != kSTRootd) { - - phyc = logconn->GetPhyConnection(); - if (!phyc || !phyc->IsValid()) { - Error( "GetAccessToSrv", "Physical connection disappeared."); - fGettingAccessToSrv = false; - return false; - } - - // Start the reader thread in the phyconn, if needed - phyc->StartReader(); - - if (phyc->IsLogged() == kNo) - retval = DoLogin(); - else { - - Info( XrdClientDebug::kHIDEBUG, - "GetAccessToSrv", "Reusing physical connection to server [" << - fUrl.Host << ":" << fUrl.Port << "])."); - - retval = true; - } - } - else - retval = true; - - fGettingAccessToSrv = false; - return retval; -} - -//_____________________________________________________________________________ -ERemoteServerType XrdClientConn::DoHandShake(short int log) { - - struct ServerInitHandShake xbody; - ERemoteServerType type; - - // Get the physical connection - XrdClientLogConnection *lcn = ConnectionManager->GetConnection(log); - - if (!lcn) return kSTError; - - XrdClientPhyConnection *phyconn = lcn->GetPhyConnection(); - - if (!phyconn || !phyconn->IsValid()) return kSTError; - - { - XrdClientPhyConnLocker pl(phyconn); - - if( phyconn->fServerType != kSTNone ) - type = phyconn->fServerType; - else - type = phyconn->DoHandShake( xbody ); - - if (type == kSTError) return type; - - // Check if the server is the eXtended rootd or not, checking the value - // of type - fServerProto = phyconn->fServerProto; - - //------------------------------------------------------------------------ - // Handle a redirector - we always remember the first manager (redirector) - // encountered - //------------------------------------------------------------------------ - if( type == kSTBaseXrootd ) - { - if( !fLBSUrl || fLBSIsMeta ) - { - delete fLBSUrl; - fLBSIsMeta = false; - - Info( XrdClientDebug::kHIDEBUG, "DoHandShake", - "Setting Load Balancer Server Url = " << fUrl.GetUrl() ); - - fLBSUrl = new XrdClientUrlInfo( fUrl.GetUrl() ); - } - } - - //------------------------------------------------------------------------ - // Handle a meta manager - we always remember the last meta manager - // encountered - //------------------------------------------------------------------------ - if( type == kSTMetaXrootd ) - { - delete fMetaUrl; - Info( XrdClientDebug::kHIDEBUG, "DoHandShake", - "Setting Meta Manager Server Url = " << fUrl.GetUrl() ); - fMetaUrl = new XrdClientUrlInfo( fUrl.GetUrl() ); - - //---------------------------------------------------------------------- - // We always remember the first manager in the chain, unless there is - // none available in which case we use the last meta manager for this - // purpose - //---------------------------------------------------------------------- - if( !fLBSUrl || fLBSIsMeta ) - { - delete fLBSUrl; - fLBSIsMeta = true; - - Info( XrdClientDebug::kHIDEBUG, "DoHandShake", - "Setting Meta Load Balancer Server Url = " << fUrl.GetUrl() ); - - fLBSUrl = new XrdClientUrlInfo( fUrl.GetUrl() ); - } - } - - return type; - } -} - -//_____________________________________________________________________________ -bool XrdClientConn::DoLogin() -{ - // This method perform the loggin-in into the server just after the - // hand-shake. It also calls the DoAuthentication() method - - ClientRequest reqhdr; - bool resp; - - // We fill the header struct containing the request for login - memset( &reqhdr, 0, sizeof(reqhdr)); - - SetSID(reqhdr.header.streamid); - reqhdr.header.requestid = kXR_login; - reqhdr.login.capver[0] = XRD_CLIENT_CAPVER; - reqhdr.login.pid = getpid(); - - // Get username from Url - XrdOucString User = fUrl.User; - if (User.length() <= 0) { - // Use local username, if not specified - char name[256]; -#ifndef WIN32 -if (!XrdOucUtils::UserName(geteuid(), name, sizeof(name))) User = name; -#else - DWORD length = sizeof (name); - GetUserName(name, &length); - User = name; -#endif - } - if (User.length() > 0) - { - strncpy( (char *)reqhdr.login.username, User.c_str(), 7 ); - reqhdr.login.username[7] = 0; - } - else - strcpy( (char *)reqhdr.login.username, "????" ); - - // If we run with root as effective user we need to temporary change - // effective ID to User - XrdOucString effUser = User; -#ifndef WIN32 - if (!getuid()) { - if (getenv("XrdClientEUSER")) effUser = getenv("XrdClientEUSER"); - } - XrdSysPrivGuard guard(effUser.c_str()); - if (!guard.Valid() && !getuid()) { - // Set error, in case of need - fOpenError = kXR_NotAuthorized; - LastServerError.errnum = fOpenError; - XrdOucString emsg("Cannot set effective uid for user: "); - emsg += effUser; - strcpy(LastServerError.errmsg, emsg.c_str()); - Error("DoLogin", emsg << ". Exiting."); - return false; - } -#endif - - // set the token with the value provided by a previous - // redirection (if any) - reqhdr.header.dlen = fRedirInternalToken.length(); - - // We call SendGenCommand, the function devoted to sending commands. - Info(XrdClientDebug::kHIDEBUG, - "DoLogin", - "Logging into the server [" << fUrl.Host << ":" << fUrl.Port << - "]. pid=" << reqhdr.login.pid << " uid=" << reqhdr.login.username); - - { - XrdClientLogConnection *l = ConnectionManager->GetConnection(fLogConnID); - XrdClientPhyConnection *p = 0; - if (l) p = l->GetPhyConnection(); - if (p) {p->SetLogged(kNo); fOpenSockFD = p->GetSocket();} - else { - Error("DoLogin", - "Logical connection disappeared before request?!? Srv: [" << fUrl.Host << ":" << fUrl.Port << - "]. Exiting."); - return false; - } - } - - - char *plist = 0; - resp = SendGenCommand(&reqhdr, fRedirInternalToken.c_str(), - (void **)&plist, 0, - TRUE, (char *)"XrdClientConn::DoLogin"); - - // plist is the plain response from the server. We need a way to 0-term it. - XrdSecProtocol *secp = 0; - SessionIDInfo prevSessionID, *prevsessidP = 0; - XrdOucString sessname; - XrdOucString sessdump; - if (resp && LastServerResp.dlen && plist) { - - plist = (char *)realloc(plist, LastServerResp.dlen+1); - // Terminate server reply - plist[LastServerResp.dlen]=0; - - char *pauth = 0; - int lenauth = 0; - if ((fServerProto >= 0x240) && (LastServerResp.dlen >= 16)) { - - if (XrdClientDebug::kHIDEBUG <= DebugLevel()) { - char b[20]; - for (unsigned int i = 0; i < 16; i++) { - snprintf(b, 20, "%.2x", plist[i]); - sessdump += b; - } - Info(XrdClientDebug::kHIDEBUG, - "DoLogin","Got session ID: " << sessdump); - } - - // Get the previous session id, in order to kill it later - - char buf[20]; - snprintf(buf, 20, ":%d.", fUrl.Port); - - sessname+= fUrl.HostAddr; - if (sessname.length() <= 0) - sessname = fUrl.Host; - - sessname += buf; - sessname += fUrl.User; - - memcpy(&mySessionID, plist, sizeof(mySessionID)); - fSessionIDRMutex.Lock(); - prevsessidP = fSessionIDRepo.Find(sessname.c_str()); - if (prevsessidP) - {prevSessionID = *prevsessidP; - memcpy(prevsessidP, &mySessionID, sizeof(mySessionID)); - } - fSessionIDRMutex.UnLock(); - - // Check if we need to authenticate - if (LastServerResp.dlen > 16) { - Info(XrdClientDebug::kHIDEBUG, "DoLogin","server requires authentication"); - pauth = plist+16; - lenauth = LastServerResp.dlen-15; - } - - - } else { - // We need to authenticate - Info(XrdClientDebug::kHIDEBUG, "DoLogin","server requires authentication"); - pauth = plist; - lenauth = LastServerResp.dlen+1; - } - - // Run authentication, if needed - if (pauth) { - - char *cenv = 0; - // - // Set trace level - if (EnvGetLong(NAME_DEBUG) > 0) { - cenv = new char[18]; - sprintf(cenv, "XrdSecDEBUG=%ld",EnvGetLong(NAME_DEBUG)); - putenv(cenv); - } - // - // Set username - cenv = new char[User.length()+12]; - sprintf(cenv, "XrdSecUSER=%s",User.c_str()); - putenv(cenv); - // - // Set remote hostname - cenv = new char[fUrl.Host.length()+12]; - sprintf(cenv, "XrdSecHOST=%s",fUrl.Host.c_str()); - putenv(cenv); - - secp = DoAuthentication(pauth, lenauth); - resp = (secp != 0) ? 1 : 0; - } - - if( resp ) { - if (prevsessidP) { - // - // We have to kill the previous session, if any - // By sending a kXR_endsess - - if (XrdClientDebug::kHIDEBUG <= DebugLevel()) { - XrdOucString sessdump; - char b[20]; - for (unsigned int i = 0; i < sizeof(prevsessidP->id); i++) { - snprintf(b, 20, "%.2x", prevsessidP->id[i]); - sessdump += b; - } - Info(XrdClientDebug::kHIDEBUG, - "DoLogin","Found prev session info for " << sessname << - ": " << sessdump); - } - - memset( &reqhdr, 0, sizeof(reqhdr)); - SetSID(reqhdr.header.streamid); - reqhdr.header.requestid = kXR_endsess; - - memcpy(reqhdr.endsess.sessid, &prevSessionID, sizeof(prevSessionID)); - - // terminate session - Info(XrdClientDebug::kHIDEBUG, - "DoLogin","Trying to terminate previous session."); - - SendGenCommand(&reqhdr, 0, 0, 0, - FALSE, (char *)"XrdClientConn::Endsess"); - - } else { - Info(XrdClientDebug::kHIDEBUG, - "DoLogin","No prev session info for " << sessname); - - // No session info? Let's create one. - SessionIDInfo *newsessid = new SessionIDInfo; - *newsessid = mySessionID; - - fSessionIDRMutex.Lock(); - fSessionIDRepo.Rep(sessname.c_str(), newsessid); - fSessionIDRMutex.UnLock(); - } - - } } //resp - - // Flag success if everything went ok - { - XrdClientLogConnection *l = ConnectionManager->GetConnection(fLogConnID); - XrdClientPhyConnection *p = 0; - if (l) p = l->GetPhyConnection(); - if (!p) { - Error("DoLogin", - "Logical connection disappeared after request?!? Srv: [" << fUrl.Host << ":" << fUrl.Port << - "]. Exiting."); - return false; - } - - if (resp) { - p->SetLogged(kYes); - p->SetSecProtocol(secp); - } - else Disconnect(true); - } - - if (plist) - free(plist); - - return resp; - -} - -//_____________________________________________________________________________ -XrdSecProtocol *XrdClientConn::DoAuthentication(char *plist, int plsiz) -{ - // Negotiate authentication with the remote server. Tries in turn - // all available protocols proposed by the server (in plist), - // starting from the first. - static XrdSecGetProt_t getp = 0; - XrdSecProtocol *protocol = (XrdSecProtocol *)0; - XrdOucEnv authEnv; - struct sockaddr myAddr; - socklen_t myALen = sizeof(myAddr); - - if (!plist || plsiz <= 0) - return protocol; - - Info(XrdClientDebug::kHIDEBUG, "DoAuthentication", - "host " << fUrl.Host << " sent a list of " << plsiz << " bytes"); - // - // Prepare host/IP information of the remote xrootd. This is required - // for the authentication. - XrdNetAddr theAddr; - const char *hosterrmsg = 0; - if ((hosterrmsg = theAddr.Set(fOpenSockFD))) - {Info(XrdClientDebug::kUSERDEBUG, "DoAuthentication", - "getHostAddr said '" << *hosterrmsg << "'"); - return protocol; - } - - // Establish our local connection details (used by sss protocol) - // - if (!getsockname(fOpenSockFD, &myAddr, &myALen)) - {char ipBuff[64]; - if (XrdSysDNS::IPFormat(&myAddr, ipBuff, sizeof(ipBuff))) - authEnv.Put("sockname", ipBuff); - } - - // - // Variables for negotiation - XrdSecParameters *secToken = 0; - XrdSecCredentials *credentials = 0; - - // - // Prepare the parms object - char *bpar = (char *)malloc(plsiz + 1); - if (bpar) memcpy(bpar, plist, plsiz); - bpar[plsiz] = 0; - XrdSecParameters Parms(bpar, plsiz + 1); - - // We need to load the protocol getter the first time we are here - if (!getp) { - LastServerError.errmsg[0] = 0; - if (!(getp = XrdSecLoadSecFactory(LastServerError.errmsg, - sizeof(LastServerError.errmsg)))) - {Info(XrdClientDebug::kHIDEBUG, "DoAuthentication", - "unable to load XrdSecGetProtocol()"); - // Set error, in case of need - fOpenError = kXR_NotAuthorized; - LastServerError.errnum = fOpenError; - if (!LastServerError.errmsg[0]) - strcpy(LastServerError.errmsg, "Unable to load XrdSecGetProtocol()"); - return protocol; - } - } - // - // Get a instance of XrdSecProtocol; the order of preference is the one - // specified by the server; the env XRDSECPROTOCOL can be used to force - // the choice. - while ((protocol = (*getp)((char *)fUrl.Host.c_str(), theAddr, Parms, 0))) - // - // Protocol name - {XrdOucString protname = protocol->Entity.prot; - // - // Once we have the protocol, get the credentials - XrdOucErrInfo ei("", &authEnv); - credentials = protocol->getCredentials(0, &ei); - if (!credentials) { - Info(XrdClientDebug::kHIDEBUG, "DoAuthentication", - "cannot obtain credentials (protocol: "<< - protname<<")"); - // Set error, in case of need - fOpenError = kXR_NotAuthorized; - LastServerError.errnum = fOpenError; - strcpy(LastServerError.errmsg, "cannot obtain credentials for protocol: "); - strcat(LastServerError.errmsg, ei.getErrText()); - protocol->Delete(); - protocol = 0; - continue; - } else { - Info(XrdClientDebug::kHIDEBUG, "DoAuthentication", - "credentials size: "<< credentials->size); - } - // - // We fill the header struct containing the request for login - ClientRequest reqhdr; - memset(reqhdr.auth.reserved, 0, 12); - memset(reqhdr.auth.credtype, 0, 4 ); - memcpy(reqhdr.auth.credtype, protname.c_str(), protname.length() > 4 ? 4 : protname.length() ); - - LastServerResp.status = kXR_authmore; - char *srvans = 0; - while (LastServerResp.status == kXR_authmore) { - bool resp = false; - - // - // Length of the credentials buffer - reqhdr.header.dlen = credentials->size; - SetSID(reqhdr.header.streamid); - reqhdr.header.requestid = kXR_auth; - resp = SendGenCommand(&reqhdr, credentials->buffer, (void **)&srvans, 0, TRUE, - (char *)"XrdClientConn::DoAuthentication"); - SafeDelete(credentials); - Info(XrdClientDebug::kHIDEBUG, "DoAuthentication", - "server reply: status: "<< - LastServerResp.status << - " dlen: "<< LastServerResp.dlen); - if (resp && (LastServerResp.status == kXR_authmore)) { - // - // We are required to send additional information - // First assign the security token that we have received - // at the login request - secToken = new XrdSecParameters(srvans,LastServerResp.dlen); - // - // then get next part of the credentials - credentials = protocol->getCredentials(secToken, &ei); - SafeDelete(secToken); // nb: srvans is released here - srvans = 0; - if (!credentials) { - Info(XrdClientDebug::kUSERDEBUG, "DoAuthentication", - "cannot obtain credentials"); - // Set error, in case of need - fOpenError = kXR_NotAuthorized; - LastServerError.errnum = fOpenError; - strcpy(LastServerError.errmsg, "cannot obtain credentials: "); - strcat(LastServerError.errmsg, ei.getErrText()); - protocol->Delete(); - protocol = 0; - break; - } else { - Info(XrdClientDebug::kHIDEBUG, "DoAuthentication", - "credentials size " << credentials->size); - } - } else { - // Something happened, it could be an error or a good thing as well - - if (LastServerResp.status == kXR_error) { - // Unexpected reply: stop handshake and print error msg, if any - - if (LastServerError.errmsg[0]) - Error("DoAuthentication", LastServerError.errmsg); - - protocol->Delete(); - protocol = 0; - // This is a fatal auth error - break; - } - - if (!resp) { - // Communication error - - protocol->Delete(); - protocol = 0; - // This is a fatal auth error - break; - } - - } - } - - // If we are done - if (protocol) break; - } - - if (!protocol) { - Info(XrdClientDebug::kHIDEBUG, "DoAuthentication", - "unable to get protocol object."); - // Set error, in case of need - fOpenError = kXR_NotAuthorized; - LastServerError.errnum = fOpenError; - strcat(LastServerError.errmsg, ": unable to get protocol object."); - } - - // Return the result of the negotiation - // - return protocol; -} - -//_____________________________________________________________________________ -XrdClientConn::ESrvErrorHandlerRetval -XrdClientConn::HandleServerError(XReqErrorType &errorType, XrdClientMessage *xmsg, - ClientRequest *req) -{ - // Handle errors from server - - int newport; - XrdOucString newhost; - - bool noRedirError = (fMaxGlobalRedirCnt == 1 && xmsg && isRedir(&xmsg->fHdr)); - - // Close the log connection at this point the fLogConnID is no longer valid. - // On read/write error the physical channel may be not OK, so it's a good - // idea to shutdown it. - // If there are other logical conns pointing to it, they will get an error, - // which will be handled - if (!noRedirError) { - if ((errorType == kREAD) || (errorType == kWRITE)) { - Disconnect(TRUE); - - if (fMainReadCache) - fMainReadCache->RemovePlaceholders(); - } else - Disconnect(FALSE); - } - - // We cycle repeatedly trying to ask the dlb for a working redir destination - do { - - // Anyway, let's update the counter, we have just been redirected - fGlobalRedirCnt++; - - Info(XrdClientDebug::kHIDEBUG, - "HandleServerError", - "Redir count=" << fGlobalRedirCnt); - - if ( fGlobalRedirCnt >= fMaxGlobalRedirCnt ) { - if (noRedirError) { - // The caller just wants the redir info: - // extract it (new host:port) from the response and return - newhost = ""; - newport = 0; - // An explicit redir overwrites token and opaque info - ParseRedir(xmsg, newport, newhost, fRedirOpaque, fRedirInternalToken); - fRedirCGI = fRedirOpaque; - - // Save it in fREQUrl - // fREQUrl = fUrl; - // fREQUrl.Host = newhost; - // fREQUrl.Port = newport; - - // Reset counter - fGlobalRedirCnt = 0; - return kSEHRReturnMsgToCaller; - } else { - return kSEHRContinue; - } - } - - // If the time limit has expired... exit - if (IsOpTimeLimitElapsed(time(0))) return kSEHRContinue; - - newhost = ""; - newport = 0; - - if ((errorType == kREAD) || - (errorType == kWRITE) || - (errorType == kREDIRCONNECT)) { - - bool cangoaway = ( fRedirHandler && - fRedirHandler->CanRedirOnError() ); - - // We got some errors in the communication phase - // the physical connection has been closed; - // then we must go back to the load balancer - // if there is any - - // The exception here is that if the file was open in - // write mode, we must rebounce and not go away to other hosts - // To state this, we just asked to our redir handler - - if ( (fREQUrl.Host.length() > 0) ) { - // If this client was explicitly told to redirect somewhere... - //ClearSessyionID(); - - // Note, this could contain opaque information - newhost = fREQUrl.Host; - newport = fREQUrl.Port; - - ParseRedirHost(newhost, fRedirOpaque, fRedirInternalToken); - fRedirCGI = fRedirOpaque; - - // An unsuccessful connection to the dest host will make the - // client go to the LB - fREQUrl.Clear(); - } - else - if ( cangoaway && fLBSUrl && (fLBSUrl->GetUrl().length() > 0) ) { - // "Normal" error... we go to the LB if any - // Clear the current session info. Rather simplicistic. - //ClearSessionID(); - - newhost = fLBSUrl->Host; - newport = fLBSUrl->Port; - } - else { - - Error("HandleServerError", - "Communication error" - " with server [" << fUrl.Host << ":" << fUrl.Port << - "]. Rebouncing here."); - - if (fUrl.Host.length()) newhost = fUrl.Host; - else - newhost = fUrl.HostAddr; - fRedirOpaque = fRedirCGI; - newport = fUrl.Port; - } - - } else if (isRedir(&xmsg->fHdr)) { - - // Extract the info (new host:port) from the response - newhost = ""; - newport = 0; - - // An explicit redir overwrites token and opaque info - ParseRedir(xmsg, newport, newhost, fRedirOpaque, fRedirInternalToken); - fRedirCGI = fRedirOpaque; - - // Clear the current session info. Rather simplicistic. - //ClearSessionID(); - } - - // Now we should have the parameters needed for the redir - - CheckPort(newport); - - if ((newhost.length() > 0) && newport) { - XrdClientUrlInfo NewUrl(fUrl.GetUrl()); - - if (DebugLevel() >= XrdClientDebug::kUSERDEBUG) - Info(XrdClientDebug::kUSERDEBUG, - "HandleServerError", - "Received redirection to [" << newhost << ":" << newport << - "]. Token=[" << fRedirInternalToken << "]" << - "]. Opaque=[" << fRedirOpaque << "]."); - - errorType = kOK; - - NewUrl.Host = NewUrl.HostAddr = newhost; - NewUrl.Port = newport; - NewUrl.SetAddrFromHost(); - - - if ( !CheckHostDomain(newhost) ) { - Error("HandleServerError", - "Redirection to a server out-of-domain disallowed. Abort."); - abort(); - } - - errorType = GoToAnotherServer(NewUrl); - } - else { - // Host or port are not valid or empty - Error("HandleServerError", - "Received redirection to [" << newhost << ":" << newport << - "]. Token=[" << fRedirInternalToken << "]" << - "]. Opaque=[" << fRedirOpaque << "]. No server to go..."); - - errorType = kREDIRCONNECT; - } - - // We don't want to flood servers... - if (errorType == kREDIRCONNECT) { - if (LastServerError.errnum == kXR_NotAuthorized) - return kSEHRReturnMsgToCaller; - - sleep(EnvGetLong(NAME_RECONNECTWAIT)); - } - - // We keep trying the connection to the same host (we have only one) - // until we are connected, or the max count for - // redirections is reached - - // The attempts must be stopped if we are not authorized - } while ((errorType == kREDIRCONNECT) && (LastServerError.errnum != kXR_NotAuthorized)); - - - // We are here if correctly connected and handshaked and logged - if (!IsConnected()) { - Error("HandleServerError", - "Not connected. Internal error. Abort."); - abort(); - } - - // If the former request was a kxr_open, - // there is no need to reissue it, since it will be the next attempt - // to rerun the cmd. - // We simply return to the caller, which will retry - // The same applies to kxr_login. No need to reopen a file if we are - // just logging into another server. - // The open request will surely follow if needed. - if ((req->header.requestid == kXR_open) || - (req->header.requestid == kXR_login)) return kSEHRReturnNoMsgToCaller; - - // Here we are. If we had a filehandle then we must - // request a new one. - char localfhandle[4]; - bool wasopen, newopenok; - - if (fRedirHandler) { - newopenok = fRedirHandler->OpenFileWhenRedirected(localfhandle, wasopen); - if (newopenok && wasopen) { - // We are here if the file has been opened succesfully - // or if it was not open - // Tricky thing: now we have a new filehandle, perhaps in - // a different server. Then we must correct the filehandle in - // the msg we were sending and that we must repeat... - PutFilehandleInRequest(req, localfhandle); - - // Everything should be ok here. - // If we have been redirected,then we are connected, logged and reopened - // the file. If we had a r/w error (xmsg==0 or xmsg->IsError) we are - // OK too. Since we may come from a comm error, then xmsg can be null. - if (xmsg && !xmsg->IsError()) - return kSEHRContinue; // the case of explicit redir - else - return kSEHRReturnNoMsgToCaller; // the case of recovered error - } - - if (!newopenok) return kSEHRContinue; // the case of explicit redir - - } - - // We are here if we had no fRedirHandler or the reopen failed - // If we have no fRedirHandler then treat it like an OK - if (!fRedirHandler) { - // Since we may come from a comm error, then xmsg can be null. - //if (xmsg) xmsg->SetHeaderStatus( kXR_ok ); - if (xmsg && !xmsg->IsError()) - return kSEHRContinue; // the case of explicit redir - else - return kSEHRReturnNoMsgToCaller; // the case of recovered error - } - - // We are here if we have been unable to connect somewhere to handle the - // troubled situation - return kSEHRContinue; -} - -//_____________________________________________________________________________ -XReqErrorType XrdClientConn::GoToAnotherServer(XrdClientUrlInfo &newdest) -{ - // Re-directs to another server - if( EnvGetLong( NAME_PRINT_REDIRECTS ) ) - Info( XrdClientDebug::kNODEBUG, "GoToAnotherServer", - "Going to: " << newdest.Host << ":" << newdest.Port ); - - fGettingAccessToSrv = false; - - if (!newdest.Port) newdest.Port = 1094; - if (newdest.HostAddr == "") newdest.HostAddr = newdest.Host; - - if ( (fLogConnID = Connect( newdest, fUnsolMsgHandler)) == -1) { - - // Note: if Connect is unable to work then we are in trouble. - // It seems that we have been redirected to a non working server - Error("GoToAnotherServer", "Error connecting to [" << - newdest.Host << ":" << newdest.Port); - - // If no conn is possible then we return to the load balancer - return kREDIRCONNECT; - } - - // - // Set fUrl to the new data/lb server if the - // connection has been succesfull - // - fUrl = newdest; - - if (IsConnected() && !GetAccessToSrv()) { - Error("GoToAnotherServer", "Error handshaking to [" << - newdest.Host.c_str() << ":" << newdest.Port << "]"); - return kREDIRCONNECT; - } - - fPrimaryStreamid = ConnectionManager->GetConnection(fLogConnID)->Streamid(); - - return kOK; -} - -//_____________________________________________________________________________ -XReqErrorType XrdClientConn::GoToMetaManager() -{ - if( !fMetaUrl ) - return kGENERICERR; - - //---------------------------------------------------------------------------- - // We go back to the meta manager so we need to forget the manager that - // we have encountered - //---------------------------------------------------------------------------- - delete fLBSUrl; - fLBSUrl = new XrdClientUrlInfo( fMetaUrl->GetUrl() ); - fLBSIsMeta = true; - return GoToAnotherServer( *fMetaUrl ); -} - - -//_____________________________________________________________________________ -XReqErrorType XrdClientConn::GoBackToRedirector() { - // This is a primitive used to force a client to consider again - // the root node as the default connection, even after requests that involve - // redirections. Used typically for stat and similar functions - Disconnect(false); - if (fGlobalRedirCnt) fGlobalRedirCnt--; - return (fLBSUrl ? GoToAnotherServer(*fLBSUrl) : kOK); -} - -//_____________________________________________________________________________ -XrdOucString XrdClientConn::GetDomainToMatch(XrdOucString hostname) { - // Return net-domain of host hostname in 's'. - // If the host is unknown in the DNS world but it's a - // valid inet address, then that address is returned, in order - // to be matched later for access granting - - char *fullname, *err; - - // The name may be already a FQDN: try extracting the domain - XrdOucString res = ParseDomainFromHostname(hostname); - if (res.length() > 0) - return res; - - // Let's look up the hostname - // It may also be a w.x.y.z type address. - err = - fullname = XrdSysDNS::getHostName((char *)hostname.c_str(), &err); - - if ( strcmp(fullname, (char *)"0.0.0.0") ) { - // The looked up name seems valid - // The hostname domain can still be unknown - - Info(XrdClientDebug::kHIDEBUG, - "GetDomainToMatch", "GetHostName(" << hostname << - ") returned name=" << fullname); - - res = ParseDomainFromHostname(fullname); - - if (res == "") { - Info(XrdClientDebug::kHIDEBUG, - "GetDomainToMatch", "No domain contained in " << fullname); - - res = ParseDomainFromHostname(hostname); - } - if (res == "") { - Info(XrdClientDebug::kHIDEBUG, - "GetDomainToMatch", "No domain contained in " << hostname); - - res = hostname; - } - - } else { - - Info(XrdClientDebug::kHIDEBUG, - "GetDomainToMatch", "GetHostName(" << hostname << ") returned a non valid address. errtxt=" << err); - - res = ParseDomainFromHostname(hostname); - } - - Info(XrdClientDebug::kHIDEBUG, - "GetDomainToMatch", "GetDomain(" << hostname << ") --> " << res); - - - if (fullname) free(fullname); - - return res; -} - -//_____________________________________________________________________________ -XrdOucString XrdClientConn::ParseDomainFromHostname(XrdOucString hostname) -{ - // Extract the domain - - XrdOucString res; - int idot = hostname.find('.'); - if (idot != STR_NPOS) - res.assign(hostname, idot+1); - // Done - return res; -} - -//_____________________________________________________________________________ -void XrdClientConn::CheckPort(int &port) { - - if(port <= 0) { - - Info(XrdClientDebug::kHIDEBUG, - "checkPort", - "TCP port not specified. Trying to get it from /etc/services..."); - - struct servent *S = getservbyname("rootd", "tcp"); - if(!S) { - - Info(XrdClientDebug::kHIDEBUG, - "checkPort", "Service rootd not specified in /etc/services. " - "Using default IANA tcp port 1094"); - port = 1094; - } else { - Info(XrdClientDebug::kNODEBUG, - "checkPort", "Found tcp port " << ntohs(S->s_port) << - " in /etc/service"); - - port = (int)ntohs(S->s_port); - } - - } -} - - -//___________________________________________________________________________ -long XrdClientConn::GetDataFromCache(const void *buffer, long long begin_offs, - long long end_offs, bool PerfCalc, - XrdClientIntvList &missingblks, long &outstandingblks) { - - // Copies the requested data from the cache. Returns the number of bytes got - // Perfcalc = kFALSE forces the call not to impact the perf counters - - if (!fMainReadCache) - return FALSE; - - return ( fMainReadCache->GetDataIfPresent(buffer, - begin_offs, - end_offs, - PerfCalc, - missingblks, outstandingblks) ); -} - -//___________________________________________________________________________ -bool XrdClientConn::SubmitDataToCache(XrdClientMessage *xmsg, long long begin_offs, - long long end_offs) { - // Inserts the data part of this message into the cache - if (xmsg && fMainReadCache && - ((xmsg->HeaderStatus() == kXR_oksofar) || - (xmsg->HeaderStatus() == kXR_ok))) - - fMainReadCache->SubmitXMessage(xmsg, begin_offs, end_offs); - - return true; -} - -//___________________________________________________________________________ -bool XrdClientConn::SubmitRawDataToCache(const void *buffer, - long long begin_offs, - long long end_offs) { - - - if (fMainReadCache) { - if (!fMainReadCache->SubmitRawData(buffer, begin_offs, end_offs)) - free(const_cast(buffer)); - } - - return true; - -} - - -//___________________________________________________________________________ -XReqErrorType XrdClientConn::WriteToServer_Async(ClientRequest *req, - const void* reqMoreData, - int substreamid) { - - - // We allocate a new child streamid, linked to this req - // Note that the content of the req will be copied. This allows us - // to send N times the same req without destroying it - // if an answer comes before we finish - // req is automatically updated with the new streamid - if (!ConnectionManager->SidManager()->GetNewSid(fPrimaryStreamid, req)) - return kNOMORESTREAMS; - - // If this is a write request, its buffer has to be inserted into the cache - // This will be used for reference if the request has to be retried later - // or to give coherency to the read/write semantic - // From this point on, we consider the request as outstanding - // Note that his kind of blocks has to be pinned inside the cache until the write is successful - if (fMainReadCache && (req->header.requestid == kXR_write)) { - // We have to dup the mem blk - // It will be destroyed when purged by the cache, only after it has been - // acknowledged and pinned - void *locbuf = malloc(req->header.dlen); - if (!locbuf) { - Error("WriteToServer_Async", "Error allocating " << - req->header.dlen << " bytes."); - return kGENERICERR; - } - - memcpy(locbuf, reqMoreData, req->header.dlen); - - if (!fMainReadCache->SubmitRawData(locbuf, req->write.offset, req->write.offset+req->header.dlen-1, true)) - free(locbuf); - } - // Send the req to the server - return WriteToServer(req, reqMoreData, fLogConnID, substreamid); - -} - - -//_____________________________________________________________________________ -bool XrdClientConn::PanicClose() { - ClientRequest closeFileRequest; - - memset(&closeFileRequest, 0, sizeof(closeFileRequest) ); - - SetSID(closeFileRequest.header.streamid); - - closeFileRequest.close.requestid = kXR_close; - - //memcpy(closeFileRequest.close.fhandle, fHandle, sizeof(fHandle) ); - - closeFileRequest.close.dlen = 0; - - WriteToServer(&closeFileRequest, 0, fLogConnID); - - return TRUE; -} - - - -void XrdClientConn::CheckREQPauseState() { - // This client might have been paused. In this case the calling thread - // is put to sleep into a condvar until the desired time arrives. - // The caller can be awakened by signalling the condvar. But, if the - // requested wake up time did not elapse, the caller has to sleep again. - - time_t timenow; - - // Lock mutex - fREQWait->Lock(); - - // Check condition - while (1) { - timenow = time(0); - - if ((timenow < fREQWaitTimeLimit) && !IsOpTimeLimitElapsed(timenow)) { - // If still to wait... wait in relatively small steps - time_t tt = xrdmin(fREQWaitTimeLimit - timenow, 10); - - fREQWait->Wait(tt); - } - else break; - } - - // Unlock mutex - fREQWait->UnLock(); -} - - -void XrdClientConn::CheckREQConnectWaitState() { - // This client might have been paused. In this case the calling thread - // is put to sleep into a condvar until the desired time arrives. - // The caller can be awakened by signalling the condvar. But, if the - // requested wake up time did not elapse, the caller has to sleep again. - - time_t timenow; - - // Lock mutex - fREQConnectWait->Lock(); - - // Check condition - while (1) { - timenow = time(0); - - if ((timenow < fREQConnectWaitTimeLimit) && !IsOpTimeLimitElapsed(timenow)) { - // If still to wait... wait in relatively small steps - time_t tt = xrdmin(fREQWaitTimeLimit - timenow, 10); - // If still to wait... wait - fREQConnectWait->Wait(tt); - } - else break; - } - - // Unlock mutex - fREQConnectWait->UnLock(); -} - - -bool XrdClientConn::WaitResp(int secsmax) { - // This client might have been paused to wait for a delayed response. - // In this case the calling thread - // is put to sleep into a condvar until the timeout or the response arrives. - - // Returns true on timeout, false if a signal was caught - - int rc = false; - - Info(XrdClientDebug::kHIDEBUG, - "WaitResp", "Waiting response for " << secsmax << " secs." ); - - // Lock condvar - fREQWaitResp->Lock(); - - time_t timelimit = time(0)+secsmax; - - while (!fREQWaitRespData) { - rc = true; - time_t timenow = time(0); - - if ((timenow < timelimit) && !IsOpTimeLimitElapsed(timenow)) { - // If still to wait... wait in relatively small steps - time_t tt = xrdmin(timelimit - timenow, 10); - fREQWaitResp->Wait(tt); - - // Let's see if there's something - // If not.. continue waiting - if (fREQWaitRespData) { - rc = false; - break; - } - - } - else break; - - - } - - // Unlock condvar - fREQWaitResp->UnLock(); - - if (rc) { - Info(XrdClientDebug::kHIDEBUG, - "WaitResp", "Timeout elapsed."); - } - else { - Info(XrdClientDebug::kHIDEBUG, - "WaitResp", "Got an unsolicited response. Data=" << fREQWaitRespData); - } - - return rc; -} - - - -UnsolRespProcResult XrdClientConn::ProcessAsynResp(XrdClientMessage *unsolmsg) { - // A client on the current physical conn might be in a "wait for response" state - // Here we process a potential response - - - // If this is a comm error, let's awake the sleeping thread and continue - if (unsolmsg->GetStatusCode() != XrdClientMessage::kXrdMSC_ok) { - fREQWaitResp->Lock(); - - // We also have to fake a regular answer. kxr_wait is ok! - fREQWaitRespData = (ServerResponseBody_Attn_asynresp *)malloc( sizeof(struct ServerResponseBody_Attn_asynresp) ); - memset( fREQWaitRespData, 0, sizeof(struct ServerResponseBody_Attn_asynresp) ); - - fREQWaitRespData->resphdr.status = kXR_wait; - fREQWaitRespData->resphdr.dlen = sizeof(kXR_int32); - - kXR_int32 i = htonl(1); - memcpy(&fREQWaitRespData->respdata, &i, sizeof(i)); - - fREQWaitResp->Signal(); - fREQWaitResp->UnLock(); - return kUNSOL_CONTINUE; - } - - - ServerResponseBody_Attn_asynresp *ar; - ar = (ServerResponseBody_Attn_asynresp *)unsolmsg->GetData(); - - - - // If the msg streamid matched ours then continue - if ( !MatchStreamid(&ar->resphdr) ) return kUNSOL_CONTINUE; - - Info(XrdClientDebug::kHIDEBUG, - "ProcessAsynResp", "Streamid matched." ); - - fREQWaitResp->Lock(); - - // Strip the data from the message and save it. It's the response we are waiting for. - // Note that it will contain also the data! - fREQWaitRespData = ar; - - - clientUnmarshall(&fREQWaitRespData->resphdr); - - if (DebugLevel() >= XrdClientDebug::kDUMPDEBUG) - smartPrintServerHeader(&fREQWaitRespData->resphdr); - - // After all, this is the last resp we received - memcpy(&LastServerResp, &fREQWaitRespData->resphdr, sizeof(struct ServerResponseHeader)); - - - switch (fREQWaitRespData->resphdr.status) { - case kXR_error: { - // The server declared an error. - // We want to save its content - - struct ServerResponseBody_Error *body_err; - - body_err = (struct ServerResponseBody_Error *)(&fREQWaitRespData->respdata); - - if (body_err) { - // Print out the error information, as received by the server - - kXR_int32 fErr = (XErrorCode)ntohl(body_err->errnum); - - Info(XrdClientDebug::kNODEBUG, "ProcessAsynResp", "Server declared: " << - (const char*)body_err->errmsg << "(error code: " << fErr << ")"); - - // Save the last error received - memset(&LastServerError, 0, sizeof(LastServerError)); - memcpy(&LastServerError, body_err, xrdmin(fREQWaitRespData->resphdr.dlen, (kXR_int32)(sizeof(LastServerError)-1) )); - LastServerError.errnum = fErr; - - } - - break; - } - - case kXR_redirect: { - - // Hybrid case. A sync redirect request which comes out the async way. - // We handle it by simulating an async one - - // Get the encapsulated data - struct ServerResponseBody_Redirect *rd; - rd = (struct ServerResponseBody_Redirect *)fREQWaitRespData->respdata; - - // Explicit redirection request - if (rd && (strlen(rd->host) > 0)) { - Info(XrdClientDebug::kUSERDEBUG, - "ProcessAsynResp", "Requested sync redir (via async response) to " << rd->host << - ":" << ntohl(rd->port)); - - SetRequestedDestHost(rd->host, ntohl(rd->port)); - - // And then we disconnect only this logical conn - // The subsequent retry will bounce to the requested host - Disconnect(FALSE); - } - - - // We also have to fake a regular answer. kxr_wait is ok to make the thing retry! - fREQWaitRespData = (ServerResponseBody_Attn_asynresp *)malloc( sizeof(struct ServerResponseBody_Attn_asynresp) ); - memset( fREQWaitRespData, 0, sizeof(struct ServerResponseBody_Attn_asynresp) ); - - fREQWaitRespData->resphdr.status = kXR_wait; - fREQWaitRespData->resphdr.dlen = sizeof(kXR_int32); - - kXR_int32 i = htonl(1); - memcpy(&fREQWaitRespData->respdata, &i, sizeof(i)); - - free(unsolmsg->DonateData()); - break; - } - } - - unsolmsg->DonateData(); // The data blk is released from the orig message - - // Signal the waiting condvar. Waiting is no more needed - // Note: the message's data will be freed by the waiting process! - fREQWaitResp->Signal(); - - fREQWaitResp->UnLock(); - - // The message is to be destroyed, its data has been saved - return kUNSOL_DISPOSE; -} - - - - - - -//_____________________________________________________________________________ -int XrdClientConn::GetParallelStreamToUse(int reqsperstream) { - // Gets a parallel stream id to use to set the return path for a req - XrdClientLogConnection *lgc = 0; - XrdClientPhyConnection *phyc = 0; - - lgc = ConnectionManager->GetConnection(fLogConnID); - if (!lgc) { - Error("GetParallelStreamToUse", - "Unknown logical conn " << fLogConnID); - - return kWRITE; - } - - phyc = lgc->GetPhyConnection(); - if (!phyc) { - Error("GetParallelStreamToUse", - "Cannot find physical conn for logid " << fLogConnID); - - return kWRITE; - } - - return phyc->GetSockIdHint(reqsperstream); -} - -//_____________________________________________________________________________ -bool XrdClientConn::IsPhyConnConnected() { - // Tells if this instance seems correctly connected to a server - - XrdClientLogConnection *lgc = 0; - XrdClientPhyConnection *phyc = 0; - - lgc = ConnectionManager->GetConnection(fLogConnID); - if (!lgc) return false; - - phyc = lgc->GetPhyConnection(); - if (!phyc) return false; - - return phyc->IsValid(); -} -//_____________________________________________________________________________ -int XrdClientConn:: GetParallelStreamCount() { - - XrdClientLogConnection *lgc = 0; - XrdClientPhyConnection *phyc = 0; - - lgc = ConnectionManager->GetConnection(fLogConnID); - if (!lgc) { - Error("GetParallelStreamCount", - "Unknown logical conn " << fLogConnID); - - return 0; - } - - phyc = lgc->GetPhyConnection(); - if (!phyc) { - Error("GetParallelStreamCount", - "Cannot find physical conn for logid " << fLogConnID); - - return 0; - } - - return phyc->GetSockIdCount(); - -} - - -//_____________________________________________________________________________ -XrdClientPhyConnection *XrdClientConn::GetPhyConn(int LogConnID) { - // Protected way to get the underlying physical connection - - XrdClientLogConnection *log; - - log = ConnectionManager->GetConnection(LogConnID); - if (log) return log->GetPhyConnection(); - return 0; - -} - - -bool XrdClientConn::DoWriteSoftCheckPoint() { - // Cycle trough the outstanding write requests, - // If some of them are expired, cancel them - // and retry in the sync way, one by one - // If some of them got a negative response of some kind... the same - - // Exit at the first sync error - - // Get the failed write reqs, and put them in a safe place. - // This call has to be done also just before disconnecting a logical conn, - // in order to collect all the outstanding write requests before they are forgotten - ConnectionManager->SidManager()->GetFailedOutstandingWriteRequests(fPrimaryStreamid, fWriteReqsToRetry); - - for (int it = 0; it < fWriteReqsToRetry.GetSize(); it++) { - - ClientRequest req; - req = fWriteReqsToRetry[it]; - - // Get the mem blk to write, directly from the cache, where it should be - // a unique blk. If it's not there, then this is an internal error. - void *data = fMainReadCache->FindBlk(req.write.offset, req.write.offset+req.write.dlen-1); - - // Now we have the req and the data, we let the things go almost normally - // No big troubles, this func is called by the main requesting thread - if (data) { - // The recoveries go always through the main stream - req.write.pathid = 0; - bool ok = SendGenCommand(&req, data, 0, 0, - false, (char *)"Write_checkpoint"); - - UnPinCacheBlk(req.write.offset, req.write.offset+req.write.dlen-1); - // A total sync failure means that there is no more hope and that the destination file is - // surely incomplete. - if (!ok) return false; - - } - else { - Error("DoWriteSoftCheckPoint", "Checkpoint data disappeared."); - return false; - } - - } - - // If we are here, all the requests were successful - fWriteReqsToRetry.Clear(); - return true; -} - -bool XrdClientConn::DoWriteHardCheckPoint() { - // Do almost the same as the soft checkpoint, - // But don't exit if there are still pending write reqs - // This has to guarantee that either a fatal error or a full success has happened - // Acts like a client-and-network-side flush - - while(1) { - if (ConnectionManager->SidManager()->GetOutstandingWriteRequestCnt(fPrimaryStreamid) == 0) return true; - if (!DoWriteSoftCheckPoint()) return false; - if (ConnectionManager->SidManager()->GetOutstandingWriteRequestCnt(fPrimaryStreamid) == 0) return true; - - //ConnectionManager->SidManager()->PrintoutOutstandingRequests(); - fWriteWaitAck->Wait(1); - } - -} - - -//_____________________________________________________________________________ - -void XrdClientConn::SetOpTimeLimit(int delta_secs) { - fOpTimeLimit = time(0)+delta_secs; -} - -bool XrdClientConn::IsOpTimeLimitElapsed(time_t timenow) { - return (timenow > fOpTimeLimit); -} diff --git a/src/XrdClient/XrdClientConn.hh b/src/XrdClient/XrdClientConn.hh deleted file mode 100644 index 48124d8fc95..00000000000 --- a/src/XrdClient/XrdClientConn.hh +++ /dev/null @@ -1,454 +0,0 @@ -#ifndef XRD_CONN_H -#define XRD_CONN_H -/******************************************************************************/ -/* */ -/* X r d C l i e n t C o n n . h h */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2004) */ -/* Adapted from TXNetFile (root.cern.ch) originally done by */ -/* Alvise Dorigo, Fabrizio Furano */ -/* INFN Padova, 2003 */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// High level handler of connections to xrootd. // -// // -////////////////////////////////////////////////////////////////////////// - -#include "XrdClient/XrdClientConst.hh" - -#include "time.h" -#include "XrdClient/XrdClientConnMgr.hh" -#include "XrdClient/XrdClientMessage.hh" -#include "XrdClient/XrdClientUrlInfo.hh" -#include "XrdClient/XrdClientReadCache.hh" -#include "XrdOuc/XrdOucHash.hh" -#include "XrdSys/XrdSysPthread.hh" - -#define ConnectionManager XrdClientConn::GetConnectionMgr() - -class XrdClientAbs; -class XrdSecProtocol; - -class XrdClientConn { - -public: - - enum ESrvErrorHandlerRetval { - kSEHRReturnMsgToCaller = 0, - kSEHRBreakLoop = 1, - kSEHRContinue = 2, - kSEHRReturnNoMsgToCaller = 3, - kSEHRRedirLimitReached = 4 - }; - enum EThreeStateReadHandler { - kTSRHReturnMex = 0, - kTSRHReturnNullMex = 1, - kTSRHContinue = 2 - }; - - // To keep info about an open session - struct SessionIDInfo { - char id[16]; - }; - - int fLastDataBytesRecv; - int fLastDataBytesSent; - XErrorCode fOpenError; - - XrdOucString fRedirOpaque; // Opaque info returned by the server when - - // redirecting. To be used in the next opens - XrdClientConn(); - virtual ~XrdClientConn(); - - inline bool CacheWillFit(long long bytes) { - if (!fMainReadCache) - return FALSE; - return fMainReadCache->WillFit(bytes); - } - - bool CheckHostDomain(XrdOucString hostToCheck); - short Connect(XrdClientUrlInfo Host2Conn, - XrdClientAbsUnsolMsgHandler *unsolhandler); - void Disconnect(bool ForcePhysicalDisc); - virtual bool GetAccessToSrv(); - XReqErrorType GoBackToRedirector(); - - XrdOucString GetClientHostDomain() { return fgClientHostDomain; } - - - static XrdClientPhyConnection *GetPhyConn(int LogConnID); - - - // --------- Cache related stuff - - long GetDataFromCache(const void *buffer, - long long begin_offs, - long long end_offs, - bool PerfCalc, - XrdClientIntvList &missingblks, - long &outstandingblks ); - - bool SubmitDataToCache(XrdClientMessage *xmsg, - long long begin_offs, - long long end_offs); - - bool SubmitRawDataToCache(const void *buffer, - long long begin_offs, - long long end_offs); - - void SubmitPlaceholderToCache(long long begin_offs, - long long end_offs) { - if (fMainReadCache) - fMainReadCache->PutPlaceholder(begin_offs, end_offs); - } - - - void RemoveAllDataFromCache(bool keepwriteblocks=true) { - if (fMainReadCache) - fMainReadCache->RemoveItems(keepwriteblocks); - } - - void RemoveDataFromCache(long long begin_offs, - long long end_offs, bool remove_overlapped = false) { - if (fMainReadCache) - fMainReadCache->RemoveItems(begin_offs, end_offs, remove_overlapped); - } - - void RemovePlaceholdersFromCache() { - if (fMainReadCache) - fMainReadCache->RemovePlaceholders(); - } - - void PrintCache() { - if (fMainReadCache) - fMainReadCache->PrintCache(); - } - - - bool GetCacheInfo( - // The actual cache size - int &size, - - // The number of bytes submitted since the beginning - long long &bytessubmitted, - - // The number of bytes found in the cache (estimate) - long long &byteshit, - - // The number of reads which did not find their data - // (estimate) - long long &misscount, - - // miss/totalreads ratio (estimate) - float &missrate, - - // number of read requests towards the cache - long long &readreqcnt, - - // ratio between bytes found / bytes submitted - float &bytesusefulness - ) { - if (!fMainReadCache) return false; - - fMainReadCache->GetInfo(size, - bytessubmitted, - byteshit, - misscount, - missrate, - readreqcnt, - bytesusefulness); - return true; - } - - - void SetCacheSize(int CacheSize) { - if (!fMainReadCache && CacheSize) - fMainReadCache = new XrdClientReadCache(); - - if (fMainReadCache) - fMainReadCache->SetSize(CacheSize); - } - - void SetCacheRmPolicy(int RmPolicy) { - if (fMainReadCache) - fMainReadCache->SetBlkRemovalPolicy(RmPolicy); - } - - void UnPinCacheBlk(long long begin_offs, long long end_offs) { - fMainReadCache->UnPinCacheBlk(begin_offs, end_offs); - // Also use this to signal the possibility to proceed for a hard checkpoint - fWriteWaitAck->Broadcast(); - } - - - // ------------------- - - - int GetLogConnID() const { return fLogConnID; } - - ERemoteServerType GetServerType() const { return fServerType; } - - kXR_unt16 GetStreamID() const { return fPrimaryStreamid; } - - inline XrdClientUrlInfo *GetLBSUrl() { return fLBSUrl; } - inline XrdClientUrlInfo *GetMetaUrl() { return fMetaUrl; } - inline XrdClientUrlInfo GetCurrentUrl() { return fUrl; } - inline XrdClientUrlInfo GetRedirUrl() { return fREQUrl; } - - XErrorCode GetOpenError() const { return fOpenError; } - virtual XReqErrorType GoToAnotherServer(XrdClientUrlInfo &newdest); - virtual XReqErrorType GoToMetaManager(); - bool IsConnected() const { return fConnected; } - bool IsPhyConnConnected(); - - struct ServerResponseHeader - LastServerResp; - - struct ServerResponseBody_Error - LastServerError; - - void ClearLastServerError() { - memset(&LastServerError, 0, sizeof(LastServerError)); - LastServerError.errnum = kXR_noErrorYet; - } - - UnsolRespProcResult ProcessAsynResp(XrdClientMessage *unsolmsg); - - virtual bool SendGenCommand(ClientRequest *req, - const void *reqMoreData, - void **answMoreDataAllocated, - void *answMoreData, bool HasToAlloc, - char *CmdName, int substreamid = 0); - - int GetOpenSockFD() const { return fOpenSockFD; } - - void SetClientHostDomain(const char *src) { fgClientHostDomain = src; } - void SetConnected(bool conn) { fConnected = conn; } - - void SetOpenError(XErrorCode err) { fOpenError = err; } - - // Gets a parallel stream id to use to set the return path for a re - int GetParallelStreamToUse(int reqsperstream); - int GetParallelStreamCount(); // Returns the total number of connected streams - - void SetRedirHandler(XrdClientAbs *rh) { fRedirHandler = rh; } - - void SetRequestedDestHost(char *newh, kXR_int32 port) { - fREQUrl = fUrl; - fREQUrl.Host = newh; - fREQUrl.Port = port; - fREQUrl.SetAddrFromHost(); - } - - // Puts this instance in pause state for wsec seconds. - // A value <= 0 revokes immediately the pause state - void SetREQPauseState(kXR_int32 wsec) { - // Lock mutex - fREQWait->Lock(); - - if (wsec > 0) - fREQWaitTimeLimit = time(0) + wsec; - else { - fREQWaitTimeLimit = 0; - fREQWait->Broadcast(); - } - - // UnLock mutex - fREQWait->UnLock(); - } - - // Puts this instance in connect-pause state for wsec seconds. - // Any future connection attempt will not happen before wsec - // and the first one will be towards the given host - void SetREQDelayedConnectState(kXR_int32 wsec) { - // Lock mutex - fREQConnectWait->Lock(); - - if (wsec > 0) - fREQConnectWaitTimeLimit = time(0) + wsec; - else { - fREQConnectWaitTimeLimit = 0; - fREQConnectWait->Broadcast(); - } - - // UnLock mutex - fREQConnectWait->UnLock(); - } - - void SetSID(kXR_char *sid); - inline void SetUrl(XrdClientUrlInfo thisUrl) { fUrl = thisUrl; } - - // Sends the request to the server, through logconn with ID LogConnID - // The request is sent with a streamid 'child' of the current one, then marked as pending - // Its answer will be caught asynchronously - XReqErrorType WriteToServer_Async(ClientRequest *req, - const void* reqMoreData, - int substreamid = 0); - - static XrdClientConnectionMgr *GetConnectionMgr() - { return fgConnectionMgr;} //Instance of the conn manager - - static void DelSessionIDRepo() {fSessionIDRMutex.Lock(); - fSessionIDRepo.Purge(); - fSessionIDRMutex.UnLock(); - } - - void GetSessionID(SessionIDInfo &sess) {sess = mySessionID;} - - long GetServerProtocol() { return fServerProto; } - - short GetMaxRedirCnt() const { return fMaxGlobalRedirCnt; } - void SetMaxRedirCnt(short mx) {fMaxGlobalRedirCnt = mx; } - short GetRedirCnt() const { return fGlobalRedirCnt; } - - bool DoWriteSoftCheckPoint(); - bool DoWriteHardCheckPoint(); - void UnPinCacheBlk(); - - - // To give a max number of seconds for an operation to complete, no matter what happens inside - // e.g. redirections, sleeps, failed connection attempts etc. - void SetOpTimeLimit(int delta_secs); - bool IsOpTimeLimitElapsed(time_t timenow); - - -protected: - void SetLogConnID(int cid) { fLogConnID = cid; } - void SetStreamID(kXR_unt16 sid) { fPrimaryStreamid = sid; } - - - - // The handler which first tried to connect somewhere - XrdClientAbsUnsolMsgHandler *fUnsolMsgHandler; - - XrdClientUrlInfo fUrl; // The current URL - XrdClientUrlInfo *fLBSUrl; // Needed to save the load balancer url - XrdClientUrlInfo fREQUrl; // For explicitly requested redirs - - short fGlobalRedirCnt; // Number of redirections - -private: - - static XrdOucString fgClientHostDomain; // Save the client's domain name - bool fConnected; - bool fGettingAccessToSrv; // To avoid recursion in desperate situations - time_t fGlobalRedirLastUpdateTimestamp; // Timestamp of last redirection - - int fLogConnID; // Logical connection ID used - kXR_unt16 fPrimaryStreamid; // Streamid used for normal communication - // NB it's a copy of the one contained in - // the logconn - - short fMaxGlobalRedirCnt; - XrdClientReadCache *fMainReadCache; - - // The time limit for a transaction - time_t fOpTimeLimit; - - XrdClientAbs *fRedirHandler; // Pointer to a class inheriting from - // XrdClientAbs providing methods - // to handle a redir at higher level - - XrdOucString fRedirInternalToken; // Token returned by the server when - // redirecting. To be used in the next logins - - XrdSysCondVar *fREQWaitResp; // For explicitly requested delayed async responses - ServerResponseBody_Attn_asynresp * - fREQWaitRespData; // For explicitly requested delayed async responses - - time_t fREQWaitTimeLimit; // For explicitly requested pause state - XrdSysCondVar *fREQWait; // For explicitly requested pause state - time_t fREQConnectWaitTimeLimit; // For explicitly requested delayed reconnect - XrdSysCondVar *fREQConnectWait; // For explicitly requested delayed reconnect - - long fServerProto; // The server protocol - ERemoteServerType fServerType; // Server type as returned by doHandShake() - SessionIDInfo mySessionID; // Login session ID - - - static XrdSysMutex fSessionIDRMutex; // Mutex for the Repo - static XrdOucHash - fSessionIDRepo; // The repository of session IDs, shared. - // Association between - // :. and a SessionIDInfo struct - - int fOpenSockFD; // Descriptor of the underlying socket - static XrdClientConnectionMgr *fgConnectionMgr; //Instance of the Connection Manager - - XrdSysCondVar *fWriteWaitAck; - XrdClientVector fWriteReqsToRetry; // To store the write reqs to retry in case of a disconnection - - bool CheckErrorStatus(XrdClientMessage *, short &, char *); - void CheckPort(int &port); - void CheckREQPauseState(); - void CheckREQConnectWaitState(); - bool CheckResp(struct ServerResponseHeader *resp, const char *method); - XrdClientMessage *ClientServerCmd(ClientRequest *req, - const void *reqMoreData, - void **answMoreDataAllocated, - void *answMoreData, - bool HasToAlloc, - int substreamid = 0); - XrdSecProtocol *DoAuthentication(char *plist, int plsiz); - - ERemoteServerType DoHandShake(short log); - - bool DoLogin(); - bool DomainMatcher(XrdOucString dom, XrdOucString domlist); - - XrdOucString GetDomainToMatch(XrdOucString hostname); - - ESrvErrorHandlerRetval HandleServerError(XReqErrorType &, XrdClientMessage *, - ClientRequest *); - bool MatchStreamid(struct ServerResponseHeader *ServerResponse); - - // Sends a close request, without waiting for an answer - // useful (?) to be sent just before closing a badly working stream - bool PanicClose(); - - XrdOucString ParseDomainFromHostname(XrdOucString hostname); - - XrdClientMessage *ReadPartialAnswer(XReqErrorType &, size_t &, - ClientRequest *, bool, void**, - EThreeStateReadHandler &); - -// void ClearSessionID(); - - XReqErrorType WriteToServer(ClientRequest *req, - const void* reqMoreData, - short LogConnID, - int substreamid = 0); - - bool WaitResp(int secsmax); - - XrdClientUrlInfo *fMetaUrl; // Meta manager url - bool fLBSIsMeta; // Is current redirector a meta manager? - -public: - XrdOucString fRedirCGI; // Same as fRedirOpaque but persistent - -}; -#endif diff --git a/src/XrdClient/XrdClientConnMgr.cc b/src/XrdClient/XrdClientConnMgr.cc deleted file mode 100644 index b438e665605..00000000000 --- a/src/XrdClient/XrdClientConnMgr.cc +++ /dev/null @@ -1,734 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C l i e n t C o n n M g r . c c */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2004) */ -/* Adapted from TXNetFile (root.cern.ch) originally done by */ -/* Alvise Dorigo, Fabrizio Furano */ -/* INFN Padova, 2003 */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// The connection manager maps multiple logical connections on a single // -// physical connection. // -// There is one and only one logical connection per client // -// and one and only one physical connection per server:port. // -// Thus multiple objects withing a given application share // -// the same physical TCP channel to communicate with a server. // -// This reduces the time overhead for socket creation and reduces also // -// the server load due to handling many sockets. // -// // -////////////////////////////////////////////////////////////////////////// - -#include "XrdClient/XrdClientConnMgr.hh" -#include "XrdClient/XrdClientDebug.hh" -#include "XrdClient/XrdClientMessage.hh" -#include "XrdClient/XrdClientLogConnection.hh" -#include "XrdClient/XrdClientThread.hh" -#include "XrdClient/XrdClientEnv.hh" -#include "XrdClient/XrdClientSid.hh" -#include "XrdOuc/XrdOucUtils.hh" -#ifdef WIN32 -#include "XrdSys/XrdWin32.hh" -#endif - -#include - -#ifdef AIX -#include -#else -#include -#endif - -// For user info -#ifndef WIN32 -#include -#endif - -// Max number allowed of logical connections ( 2**15 - 1, short int) -#define XRC_MAXVECTSIZE 32767 - -//_____________________________________________________________________________ -void * GarbageCollectorThread(void *arg, XrdClientThread *thr) -{ - // Function executed in the garbage collector thread - - // Mask all allowed signals - if (thr->MaskSignal(0) != 0) - Error("GarbageCollectorThread", "Warning: problems masking signals"); - - XrdClientConnectionMgr *thisObj = (XrdClientConnectionMgr *)arg; - - thr->SetCancelDeferred(); - thr->SetCancelOn(); - - while (1) { - thr->CancelPoint(); - - thisObj->GarbageCollect(); - - thr->CancelPoint(); - - sleep(30); - - } - - return 0; -} - -//_____________________________________________________________________________ -int DisconnectElapsedPhyConn(const char *key, - XrdClientPhyConnection *p, void *voidcmgr) -{ - // Function applied to the hash table to disconnect the unused elapsed - // physical connections - - XrdClientConnectionMgr *cmgr = (XrdClientConnectionMgr *)voidcmgr; - assert(cmgr != 0); - - if (p) { - if ((p->GetLogConnCnt() <= 0) && - p->ExpiredTTL() && p->IsValid()) { - p->Touch(); - p->Disconnect(); - } - - if (!p->IsValid()) { - - // Make sure that we kill the socket in the case of conns killed by the server - p->Touch(); - p->Disconnect(); - - // And then add the instance to the trashed list - cmgr->fPhyTrash.Push_back(p); - - // And remove the current from here - return -1; - } - } - - // Process next - return 0; -} - - - -//_____________________________________________________________________________ -int DumpPhyConn(const char *key, - XrdClientPhyConnection *p, void *voidcmgr) -{ - - if (!p) { - Info(XrdClientDebug::kDUMPDEBUG, "DumpPhyConn", "Phyconn entry, key=NULL"); - return 0; - } - - Info(XrdClientDebug::kDUMPDEBUG, "DumpPhyConn", "Phyconn entry, key='" << - (key ? key : "***def***") << - "', LogCnt=" << p->GetLogConnCnt() << (p->IsValid() ? " Valid" : " NotValid") ) - - // Process next - return 0; -} - -//_____________________________________________________________________________ -int DestroyPhyConn(const char *key, - XrdClientPhyConnection *p, void *voidcmgr) -{ - // Function applied to the hash table to destroy all the phyconns - - if (p) { - p->Touch(); - p->Disconnect(); - p->UnsolicitedMsgHandler = 0; - delete(p); - } - - // Process next, remove current item - return -1; -} - - -//_____________________________________________________________________________ -XrdClientConnectionMgr::XrdClientConnectionMgr() : fSidManager(0), - fGarbageColl(0) -{ - // XrdClientConnectionMgr constructor. - // Creates a Connection Manager object. - // Starts the garbage collector thread. - BootUp(); - -} - -//_____________________________________________________________________________ -XrdClientConnectionMgr::~XrdClientConnectionMgr() -{ - // Deletes mutex locks, stops garbage collector thread. - ShutDown(); -} - -//------------------------------------------------------------------------------ -// Initializer -//------------------------------------------------------------------------------ -bool XrdClientConnectionMgr::BootUp() -{ - fLastLogIdUsed = 0; - - fGarbageColl = new XrdClientThread(GarbageCollectorThread); - - if (!fGarbageColl) - Error("ConnectionMgr", - "Can't create garbage collector thread: out of system resources"); - - fGarbageColl->Run(this); - - fSidManager = new XrdClientSid(); - if (!fSidManager) { - Error("ConnectionMgr", - "Can't create sid manager: out of system resources"); - abort(); - } - - return true; -} - -bool XrdClientConnectionMgr::ShutDown() -{ - fPhyHash.Apply(DumpPhyConn, this); - - { - XrdSysMutexHelper mtx(fMutex); - - for( int i = 0; i < fLogVec.GetSize(); i++) - if (fLogVec[i]) Disconnect(i, TRUE); - } - - if (fGarbageColl) - { - void *ret; - fGarbageColl->Cancel(); - fGarbageColl->Join(&ret); - delete fGarbageColl; - } - - GarbageCollect(); - - fPhyHash.Apply(DestroyPhyConn, this); - - for(int i = fPhyTrash.GetSize()-1; i >= 0; i--) - DestroyPhyConn( "Trashed connection", fPhyTrash[i], this); - - fPhyTrash.Clear(); - fPhyHash.Purge(); - fLogVec.Clear(); - - delete fSidManager; - - fSidManager = 0; - fGarbageColl = 0; - - return true; -} - - -//_____________________________________________________________________________ -void XrdClientConnectionMgr::GarbageCollect() -{ - // Get rid of unused physical connections. 'Unused' means not used for a - // TTL time from any logical one. The TTL depends on the kind of remote - // server. For a load balancer the TTL is very high, while for a data server - // is quite small. - - // Mutual exclusion on the vectors and other vars - XrdSysMutexHelper mtx(fMutex); - - if (fPhyHash.Num() > 0) { - - if(DebugLevel() >= XrdClientDebug::kUSERDEBUG) - fPhyHash.Apply(DumpPhyConn, this); - - // Cycle all the physical connections to disconnect the elapsed ones - fPhyHash.Apply(DisconnectElapsedPhyConn, this); - - } - - // Cycle all the trashed physical connections to destroy the elapsed once more - // after a disconnection. Doing this way, a phyconn in async mode has - // all the time it needs to terminate its reader thread pool - for (int i = fPhyTrash.GetSize()-1; i >= 0; i--) { - - DumpPhyConn("Trashed connection", - fPhyTrash[i], this); - - if ( !fPhyTrash[i] || - ((fPhyTrash[i]->GetLogConnCnt() <= 0) && (fPhyTrash[i]->ExpiredTTL())) ) { - - if (fPhyTrash[i] && (fPhyTrash[i]->GetReaderThreadsCnt() <= 0)) { - delete fPhyTrash[i]; - - - } - - - fPhyTrash.Erase(i); - } - } - -} - -//_____________________________________________________________________________ -int XrdClientConnectionMgr::Connect(XrdClientUrlInfo RemoteServ) -{ - // Connects to the remote server: - // - Looks first for an existing physical connection already bound to - // User@RemoteAddress:TcpPort; - // - If needed, creates a TCP channel to User@RemoteAddress:TcpPort - // (this is a physical connection); - // - Creates a logical connection and binds it to the previous - // created physical connection; - // - Returns the logical connection ID. Every client will use this - // ID to deal with the server. - - XrdClientLogConnection *logconn = 0; - XrdClientPhyConnection *phyconn = 0; - CndVarInfo *cnd = 0; - - int newid = -1; - bool phyfound = FALSE; - - // First we get a new logical connection object - Info(XrdClientDebug::kHIDEBUG, - "Connect", "Creating a logical connection..."); - - logconn = new XrdClientLogConnection(fSidManager); - if (!logconn) { - Error("Connect", "Object creation failed. Aborting."); - abort(); - } - - // If empty, fill the user name with the default to avoid fake mismatches - if (RemoteServ.User.length() <= 0) { - char name[256]; -#ifndef WIN32 - RemoteServ.User = (XrdOucUtils::UserName(getuid(), name, sizeof(name)) - ? "" : name); -#else - DWORD length = sizeof (name); - ::GetUserName(name, &length); - RemoteServ.User = name; -#endif - } - - // Keys - XrdOucString key; - XrdOucString key1(RemoteServ.User.c_str(), 256); key1 += '@'; - key1 += RemoteServ.Host; key1 += ':'; key1 += RemoteServ.Port; - XrdOucString key2(RemoteServ.User.c_str(), 256); key2 += '@'; - key2 += RemoteServ.HostAddr; key2 += ':'; key2 += RemoteServ.Port; - - do { - - - fMutex.Lock(); - cnd = 0; - - cnd = fConnectingCondVars.Find(key1.c_str()); - if (!cnd) cnd = fConnectingCondVars.Find(key2.c_str()); - - // If there are no connection attempts in progress... - if (!cnd) { - - // If we already have a physical connection to that host:port, - // then we use that - if (fPhyHash.Num() > 0) { - XrdClientPhyConnection *p = 0; - - // We must avoid the unfortunate pick of a disconnected phyconn - GarbageCollect(); - - if (((p = fPhyHash.Find(key1.c_str())) || - (p = fPhyHash.Find(key2.c_str()))) && p->IsValid()) { - // We link that physical connection to the new logical connection - phyconn = p; - phyconn->CountLogConn(); - phyconn->Touch(); - logconn->SetPhyConnection(phyconn); - - phyfound = TRUE; - } - else { - // no connection attempts in progress and no already established connections - // Mark this as an ongoing attempt - // Now we have a pending conn attempt - fConnectingCondVars.Rep(key1.c_str(), new CndVarInfo(), 0, Hash_keepdata); - } - } - - fMutex.UnLock(); - } - else { - // this is an attempt which must wait for the result of a previous one - // In any case after the wait we loop and recheck - cnd->cv.Lock(); - cnd->cnt++; - fMutex.UnLock(); - cnd->cv.Wait(); - cnd->cnt--; - cnd->cv.UnLock(); - } - - } while (cnd); // here cnd means "if there is a condvar to wait on" - - // We are here if cnd == 0 - - if (!phyfound) { - - Info(XrdClientDebug::kHIDEBUG, - "Connect", - "Physical connection not found. Creating a new one..."); - - if (DebugLevel() >= XrdClientDebug::kHIDEBUG) - fPhyHash.Apply(DumpPhyConn, this); - - - // If not already present, then we must build a new physical connection, - // and try to connect it - // While we are trying to connect, the mutex must be unlocked - // Note that at this point logconn is a pure local instance, so it - // does not need to be protected by mutex - if (!(phyconn = new XrdClientPhyConnection(this, fSidManager))) { - Error("Connect", "Object creation failed. Aborting."); - abort(); - } - if ( phyconn && phyconn->Connect(RemoteServ) ) { - - phyconn->CountLogConn(); - logconn->SetPhyConnection(phyconn); - - if (DebugLevel() >= XrdClientDebug::kHIDEBUG) - Info(XrdClientDebug::kHIDEBUG, - "Connect", - "New physical connection to server " << - RemoteServ.Host << ":" << RemoteServ.Port << - " succesfully created."); - - } else { - - // The connection attempt failed, so we signal all the threads waiting for a result - { - XrdSysMutexHelper mtx(fMutex); - int cnt; - - key = key1; - cnd = fConnectingCondVars.Find(key.c_str()); - if (!cnd) { key = key2; cnd = fConnectingCondVars.Find(key.c_str()); } - if (cnd) { - cnd->cv.Lock(); - cnd->cv.Broadcast(); - fConnectingCondVars.Del(key.c_str()); - cnt = cnd->cnt; - cnd->cv.UnLock(); - - if (!cnt) { - Info(XrdClientDebug::kHIDEBUG, "Connect", - "Destroying connection condvar for " << key ); - delete cnd; - } - } - } - delete logconn; - // And then add the instance to the trashed list - phyconn->Touch(); - fPhyTrash.Push_back(phyconn); - //delete phyconn; - - return -1; - } - - } - - - // Now, we are connected to the host desired. - // The physical connection can be old or newly created - { - XrdSysMutexHelper mtx(fMutex); - phyconn->WipeStreamid(logconn->Streamid()); - - // Then, if needed, we push the physical connection into its vector - if (!phyfound) { - if (!phyconn) - Error("Connect"," problems connecting to " << key1); - fPhyHash.Rep(key1.c_str(), phyconn, 0, Hash_keepdata); - } - - if (fLogVec.GetSize() < XRC_MAXVECTSIZE) { - // Then we push the logical connection into its vector, up to a max size - fLogVec.Push_back(logconn); - // and the new position is the ID - newid = fLogVec.GetSize()-1; - } - else { - // The array is too big, get a free slot, if any - newid = -1; - for (int i = 0; i < fLogVec.GetSize(); i++) { - int idx = (fLastLogIdUsed + i) % fLogVec.GetSize(); - if (!fLogVec[idx]) { - fLogVec[idx] = logconn; - newid = idx; - fLastLogIdUsed = idx; - break; - } - } - if (newid == -1) { - delete logconn; - Error("Connect", "Critical error - Out of allocated resources:" - " max number allowed of logical connections reached ("<= XrdClientDebug::kHIDEBUG) { - - int logCnt = 0; - for (int i=0; i < fLogVec.GetSize(); i++) - if (fLogVec[i]) - logCnt++; - - Info(XrdClientDebug::kHIDEBUG, "Connect", - "LogConn: size:" << fLogVec.GetSize() << " count: " << logCnt << - "PhyConn: size:" << fPhyHash.Num()); - } - - - - // The connection attempt went ok, so we signal all the threads waiting for a result, if we can still find the corresponding condvar - int cnt; - // if (!phyfound) { - key = key1; - cnd = fConnectingCondVars.Find(key.c_str()); - if (!cnd) { key = key2; cnd = fConnectingCondVars.Find(key.c_str()); } - if (cnd) { - cnd->cv.Lock(); - cnd->cv.Broadcast(); - fConnectingCondVars.Del(key.c_str()); - cnt = cnd->cnt; - cnd->cv.UnLock(); - - if (!cnt) { - Info(XrdClientDebug::kHIDEBUG, "Connect", - "Destroying connection condvar for " << key ); - delete cnd; - } - } - // } - - } // mutex - - return newid; -} - -//_____________________________________________________________________________ -void XrdClientConnectionMgr::Disconnect(int LogConnectionID, - bool ForcePhysicalDisc) -{ - // Deletes a logical connection. - // Also deletes the related physical one if ForcePhysicalDisc=TRUE. - if (LogConnectionID < 0) return; - - { - XrdSysMutexHelper mtx(fMutex); - - if ((LogConnectionID < 0) || - (LogConnectionID >= fLogVec.GetSize()) || (!fLogVec[LogConnectionID])) { - Error("Disconnect", "Destroying nonexistent logconn " << LogConnectionID); - return; - } - - fLogVec[LogConnectionID]->GetPhyConnection()->WipeStreamid(fLogVec[LogConnectionID]->Streamid()); - if (ForcePhysicalDisc) { - // We disconnect the phyconn - // But it will be removed by the garbagecollector as soon as possible - // Note that here we cannot destroy the phyconn, since there can be other - // logconns pointing to it the phyconn will be removed when there are no - // more logconns pointing to it - fLogVec[LogConnectionID]->GetPhyConnection()->UnsolicitedMsgHandler = 0; - fLogVec[LogConnectionID]->GetPhyConnection()->Disconnect(); - GarbageCollect(); - } - - - fLogVec[LogConnectionID]->GetPhyConnection()->Touch(); - delete fLogVec[LogConnectionID]; - fLogVec[LogConnectionID] = 0; - - Info(XrdClientDebug::kHIDEBUG, "Disconnect", - " LogConnID: " << LogConnectionID <<" destroyed"); - } - -} - -//_____________________________________________________________________________ -int XrdClientConnectionMgr::ReadRaw(int LogConnectionID, void *buffer, - int BufferLength) -{ - // Read BufferLength bytes from the logical connection LogConnectionID - - XrdClientLogConnection *logconn; - - logconn = GetConnection(LogConnectionID); - - if (logconn) { - return logconn->ReadRaw(buffer, BufferLength); - } - else { - Error("ReadRaw", "There's not a logical connection with id " << - LogConnectionID); - - return(TXSOCK_ERR); - } -} - -//_____________________________________________________________________________ -XrdClientMessage *XrdClientConnectionMgr::ReadMsg(int LogConnectionID) -{ - XrdClientLogConnection *logconn; - XrdClientMessage *mex; - - logconn = GetConnection(LogConnectionID); - - // Now we get the message from the queue, with the timeouts needed - // Note that the physical connection know about streamids, NOT logconnids !! - mex = logconn->GetPhyConnection()->ReadMessage(logconn->Streamid()); - - // Return the message unmarshalled to ClientServerCmd - return mex; -} - -//_____________________________________________________________________________ -int XrdClientConnectionMgr::WriteRaw(int LogConnectionID, const void *buffer, - int BufferLength, int substreamid) { - // Write BufferLength bytes into the logical connection LogConnectionID - - XrdClientLogConnection *logconn; - - logconn = GetConnection(LogConnectionID); - - if (logconn) { - return logconn->WriteRaw(buffer, BufferLength, substreamid); - } - else { - Error("WriteRaw", "There's not a logical connection with id " << - LogConnectionID); - - return(TXSOCK_ERR); - } -} - -//_____________________________________________________________________________ -XrdClientLogConnection *XrdClientConnectionMgr::GetConnection( int LogConnectionID) -{ - // Return a logical connection object that has LogConnectionID as its ID. - XrdSysMutexHelper mtx(fMutex); - - return (LogConnectionID > -1) ? fLogVec[LogConnectionID] : (XrdClientLogConnection *)0; -} - -//____________________________________________________________________________ -XrdClientPhyConnection *XrdClientConnectionMgr::GetPhyConnection(XrdClientUrlInfo server) -{ - // Gets pointer to the physical connection to 'server', if any. - // Return 0 if none is found. - - XrdClientPhyConnection *p = 0; - - // If empty, fill the user name with the default to avoid fake mismatches - if (server.User.length() <= 0) { - char name[256]; -#ifndef WIN32 - server.User = (XrdOucUtils::UserName(getuid(), name, sizeof(name)) - ? "" : name); -#else - DWORD length = sizeof (name); - ::GetUserName(name, &length); - server.User = name; -#endif - } - - // Keys - XrdOucString key; - XrdOucString key1(server.User.c_str(), 256); key1 += '@'; - key1 += server.Host; key1 += ':'; key1 += server.Port; - XrdOucString key2(server.User.c_str(), 256); key2 += '@'; - key2 += server.HostAddr; key2 += ':'; key2 += server.Port; - - if (fPhyHash.Num() > 0) { - if (((p = fPhyHash.Find(key1.c_str())) || - (p = fPhyHash.Find(key2.c_str()))) && !(p->IsValid())) { - // We found an invalid connection: do not use it - p = 0; - } - } - - // Done - return p; -} - -//_____________________________________________________________________________ -UnsolRespProcResult XrdClientConnectionMgr::ProcessUnsolicitedMsg(XrdClientUnsolMsgSender *sender, - XrdClientMessage *unsolmsg) -{ - // We are here if an unsolicited response comes from a physical connection - // The response comes in the form of an TXMessage *, that must NOT be - // destroyed after processing. It is destroyed by the first sender. - // Remember that we are in a separate thread, since unsolicited responses - // are asynchronous by nature. - - //Info(XrdClientDebug::kDUMPDEBUG, "ConnectionMgr", - // "Processing unsolicited response (ID:"<HeaderSID()<<")"); - UnsolRespProcResult res = kUNSOL_CONTINUE; - // Local processing .... - - // Now we propagate the message to the interested objects. - // In our architecture, the interested objects are the objects which - // self-registered in the logical connections belonging to the Phyconn - // which threw the event - // So we throw the evt towards each logical connection - { - // Find an interested logid - XrdSysMutexHelper mtx(fMutex); - - for (int i = 0; i < fLogVec.GetSize(); i++) { - - if ( fLogVec[i] && (fLogVec[i]->GetPhyConnection() == sender) ) { - fMutex.UnLock(); - res = fLogVec[i]->ProcessUnsolicitedMsg(sender, unsolmsg); - fMutex.Lock(); - - if (res != kUNSOL_CONTINUE) break; - } - } - } - return res; -} diff --git a/src/XrdClient/XrdClientConnMgr.hh b/src/XrdClient/XrdClientConnMgr.hh deleted file mode 100644 index 680c5823628..00000000000 --- a/src/XrdClient/XrdClientConnMgr.hh +++ /dev/null @@ -1,130 +0,0 @@ -#ifndef XRC_CONNMGR_H -#define XRC_CONNMGR_H -/******************************************************************************/ -/* */ -/* X r d C l i e n t C o n n M g r . h h */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2004) */ -/* Adapted from TXNetFile (root.cern.ch) originally done by */ -/* Alvise Dorigo, Fabrizio Furano */ -/* INFN Padova, 2003 */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// The connection manager maps multiple logical connections on a single // -// physical connection. // -// There is one and only one logical connection per client // -// and one and only one physical connection per server:port. // -// Thus multiple objects withing a given application share // -// the same physical TCP channel to communicate with a server. // -// This reduces the time overhead for socket creation and reduces also // -// the server load due to handling many sockets. // -// // -////////////////////////////////////////////////////////////////////////// - -#include "XrdOuc/XrdOucHash.hh" -#include "XrdSys/XrdSysPthread.hh" -#include "XrdClient/XrdClientUnsolMsg.hh" -#include "XrdClient/XrdClientPhyConnection.hh" -#include "XrdClient/XrdClientVector.hh" - -class XrdClientSid; -class XrdClientLogConnection; -class XrdClientMessage; -class XrdClientThread; - -// Ugly prototype to avoid warnings under solaris -//void * GarbageCollectorThread(void * arg, XrdClientThread *thr); - -class XrdClientConnectionMgr: public XrdClientAbsUnsolMsgHandler, - XrdClientUnsolMsgSender { - -private: - XrdClientSid *fSidManager; - - XrdClientVector fLogVec; - XrdOucHash fPhyHash; - - // To try not to reuse too much the same array ids - int fLastLogIdUsed; - // Phyconns are inserted here when they have to be destroyed later - // All the phyconns here are disconnected. - XrdClientVector fPhyTrash; - - // To arbitrate between multiple threads trying to connect to the same server. - // The first has to connect, all the others have to wait for the completion - // The meaning of this is: if there is a condvar associated to the hostname key, - // then wait for it to be signalled before deciding what to do - class CndVarInfo { - public: - XrdSysCondVar cv; - int cnt; - CndVarInfo(): cv(0), cnt(0) {}; - }; - - XrdOucHash fConnectingCondVars; - - XrdSysRecMutex fMutex; // mutex used to protect local variables - // of this and TXLogConnection, TXPhyConnection - // classes; not used to protect i/o streams - - XrdClientThread *fGarbageColl; - - friend void * GarbageCollectorThread(void *, XrdClientThread *thr); - UnsolRespProcResult - ProcessUnsolicitedMsg(XrdClientUnsolMsgSender *sender, - XrdClientMessage *unsolmsg); -public: - XrdClientConnectionMgr(); - - virtual ~XrdClientConnectionMgr(); - - bool BootUp(); - bool ShutDown(); - - - int Connect(XrdClientUrlInfo RemoteAddress); - void Disconnect(int LogConnectionID, bool ForcePhysicalDisc); - - void GarbageCollect(); - - XrdClientLogConnection - *GetConnection(int LogConnectionID); - XrdClientPhyConnection *GetPhyConnection(XrdClientUrlInfo server); - - XrdClientMessage* - ReadMsg(int LogConnectionID); - - int ReadRaw(int LogConnectionID, void *buffer, int BufferLength); - int WriteRaw(int LogConnectionID, const void *buffer, - int BufferLength, int substreamid); - - XrdClientSid *SidManager() { return fSidManager; } - - friend int DisconnectElapsedPhyConn(const char *, - XrdClientPhyConnection *, void *); - friend int DestroyPhyConn(const char *, - XrdClientPhyConnection *, void *); -}; -#endif diff --git a/src/XrdClient/XrdClientConst.hh b/src/XrdClient/XrdClientConst.hh deleted file mode 100644 index df583a9d21e..00000000000 --- a/src/XrdClient/XrdClientConst.hh +++ /dev/null @@ -1,192 +0,0 @@ -#ifndef _XRC_CONST_H -#define _XRC_CONST_H -/******************************************************************************/ -/* */ -/* X r d C l i e n t C o n s t . h h */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2004) */ -/* Adapted from TXNetFile (root.cern.ch) originally done by */ -/* Alvise Dorigo, Fabrizio Furano */ -/* INFN Padova, 2003 */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// Constants for Xrd // -// // -////////////////////////////////////////////////////////////////////////// - -#define DFLT_CONNECTTIMEOUT 120 -#define NAME_CONNECTTIMEOUT (char *)"ConnectTimeout" - -#define DFLT_REQUESTTIMEOUT 300 -#define NAME_REQUESTTIMEOUT (char *)"RequestTimeout" - -#define DFLT_MAXREDIRECTCOUNT 16 -#define NAME_MAXREDIRECTCOUNT (char *)"MaxRedirectcount" - -#define DFLT_DEBUG 0 -#define NAME_DEBUG (char *)"DebugLevel" - -#define DFLT_RECONNECTWAIT 5 -#define NAME_RECONNECTWAIT (char *)"ReconnectWait" - -#define DFLT_REDIRCNTTIMEOUT 36000 -#define NAME_REDIRCNTTIMEOUT (char *)"RedirCntTimeout" - -#define DFLT_FIRSTCONNECTMAXCNT 8 -#define NAME_FIRSTCONNECTMAXCNT (char *)"FirstConnectMaxCnt" - -#define DFLT_TRANSACTIONTIMEOUT 28800 -#define NAME_TRANSACTIONTIMEOUT (char *)"TransactionTimeout" - - -#define TXSOCK_ERR_TIMEOUT -1 -#define TXSOCK_ERR -2 -#define TXSOCK_ERR_INTERRUPT -3 - -// the default number of parallel streams PER physical connection -// 0 means that the multistream support is disabled -#define DFLT_MULTISTREAMCNT 0 -#define NAME_MULTISTREAMCNT (char *)"ParStreamsPerPhyConn" - -// The minimum size to use to split big single requests -// through multiple streams -#define DFLT_MULTISTREAMSPLITSIZE (4*1024*1024) - -// keep/dont-keep the socket open (required by optimized rootd fallback) -#define DFLT_KEEPSOCKOPENIFNOTXRD 0 -#define NAME_KEEPSOCKOPENIFNOTXRD (char *)"KeepSockOpenIfNotXrd" - -// Printable version -#define XRD_CLIENT_VERSION (char *)"kXR_ver002+kXR_asyncap" - -// Version and capabilities sent to the server -#define XRD_CLIENT_CURRENTVER (kXR_ver002) -#define XRD_CLIENT_CAPVER ((kXR_char)kXR_asyncap | XRD_CLIENT_CURRENTVER) - -// Defaults for ReadAhead and Cache -#define DFLT_READCACHESIZE 0 -#define NAME_READCACHESIZE (char *)"ReadCacheSize" - -// 0 = LRU -// 1 = Remove least offest -#define DFLT_READCACHEBLKREMPOLICY 0 -#define NAME_READCACHEBLKREMPOLICY (char *)"ReadCacheBlkRemPolicy" - -#define DFLT_READAHEADSIZE (0) -#define NAME_READAHEADSIZE (char *)"ReadAheadSize" - -// Align all the read requests to multiples of a number -#define DFLT_READTRIMBLKSZ (0) -#define NAME_READTRIMBLKSZ (char *)"ReadTrimBlockSize" - -// The default read ahead strategy to use -#define DFLT_READAHEADSTRATEGY (1) // This is the sequential readahead -#define NAME_READAHEADSTRATEGY (char *)"ReadAheadStrategy" - - -// To be used in copy-like apps when the data is to be accessed only once -// ... to reduce additional cache overhead -#define DFLT_REMUSEDCACHEBLKS 0 -#define NAME_REMUSEDCACHEBLKS (char *)"RemoveUsedCacheBlocks" - -// When writing async, purge immediately the written blocks from the cache -#define DFLT_PURGEWRITTENBLOCKS 0 -#define NAME_PURGEWRITTENBLOCKS (char *)"PurgeWrittenBlocks" - -#define NAME_REDIRDOMAINALLOW_RE (char *)"RedirDomainAllowRE" -#define NAME_REDIRDOMAINDENY_RE (char *)"RedirDomainDenyRE" -#define NAME_CONNECTDOMAINALLOW_RE (char *)"ConnectDomainAllowRE" -#define NAME_CONNECTDOMAINDENY_RE (char *)"ConnectDomainDenyRE" - -#define PROTO (char *)"root" - -// The max number of threads spawned to do parallel opens -// Note for dummies: this is not the max number of parallel opens -#define DFLT_MAXCONCURRENTOPENS 100 - -#define READV_MAXCHUNKS 512 -#define READV_MAXCHUNKSIZE (1024*192) - -// SOCKS4 support -#define NAME_SOCKS4HOST (char *)"Socks4Server" -#define NAME_SOCKS4PORT (char *)"Socks4Port" - -// Default TCP windows size -// A value of '0' implies "use the default OS settings" -// which enables window scaling on some platforms (linux, MacOsX) -// but may be to small on others (solaris); the preprocessor macro -// is set based on the platform information found in configure -#if defined(__linux__) || defined(__APPLE__) -#define DFLT_DFLTTCPWINDOWSIZE (0) -#else -#define DFLT_DFLTTCPWINDOWSIZE (262144) -#endif -#define NAME_DFLTTCPWINDOWSIZE (char *)"DfltTcpWindowSize" - -// A connection towards a data server timeouts quickly -#define DFLT_DATASERVERCONN_TTL 300 -#define NAME_DATASERVERCONN_TTL (char *)"DataServerConn_ttl" - -// A connection towards a Load Balancer timeouts after many seconds of no use -#define DFLT_LBSERVERCONN_TTL 1200 -#define NAME_LBSERVERCONN_TTL (char *)"LBServerConn_ttl" - -// Switch on/off the fork handlers -#define DFLT_ENABLE_FORK_HANDLERS 0 -#define NAME_ENABLE_FORK_HANDLERS (char *)"EnableForkHandlers" - -// Use TCP keepalive -#define DFLT_ENABLE_TCP_KEEPALIVE 0 -#define NAME_ENABLE_TCP_KEEPALIVE (char *)"EnableTCPKeepAlive" - -// Tweak the TCP keepalive - these are only meaningful on Linux - -// Interval (in seconds) between the last data packet and the first probe -#define DFLT_TCP_KEEPALIVE_TIME 7200 -#define NAME_TCP_KEEPALIVE_TIME (char *)"TCPKeepAliveTime" - -// Interval (in seconds) between the probes -#define DFLT_TCP_KEEPALIVE_INTERVAL 75 -#define NAME_TCP_KEEPALIVE_INTERVAL (char *)"TCPKeepAliveInterval" - -// Number of probes lost to consider the connection broken -#define DFLT_TCP_KEEPALIVE_PROBES 9 -#define NAME_TCP_KEEPALIVE_PROBES (char *)"TCPKeepAliveProbes" - -// Enable/disable the file size hint in xrdcp -#define DFLT_XRDCP_SIZE_HINT 1 -#define NAME_XRDCP_SIZE_HINT (char *)"XrdCpSizeHint" - -// Enable/disable redirection printing -#define DFLT_PRINT_REDIRECTS 0 -#define NAME_PRINT_REDIRECTS (char *)"PrintRedirects" - -#define TRUE 1 -#define FALSE 0 - -#define xrdmin(a, b) (a < b ? a : b) -#define xrdmax(a, b) (a > b ? a : b) - -#endif diff --git a/src/XrdClient/XrdClientDebug.cc b/src/XrdClient/XrdClientDebug.cc deleted file mode 100644 index a4b1fd074c7..00000000000 --- a/src/XrdClient/XrdClientDebug.cc +++ /dev/null @@ -1,67 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C l i e n t D e b u g . c c */ -/* */ -/* 2003 Produced by Alvise Dorigo & Fabrizio Furano for INFN padova */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdClient/XrdClientDebug.hh" - -#include "XrdSys/XrdSysPthread.hh" -XrdClientDebug *XrdClientDebug::fgInstance = 0; - -//_____________________________________________________________________________ -XrdClientDebug* XrdClientDebug::Instance() { - // Create unique instance - - if (!fgInstance) { - fgInstance = new XrdClientDebug; - if (!fgInstance) { - abort(); - } - } - return fgInstance; -} - -//_____________________________________________________________________________ -XrdClientDebug::XrdClientDebug() { - // Constructor - - fOucLog = new XrdSysLogger(); - fOucErr = new XrdSysError(fOucLog, "Xrd"); - - fDbgLevel = EnvGetLong(NAME_DEBUG); -} - -//_____________________________________________________________________________ -XrdClientDebug::~XrdClientDebug() { - // Destructor - delete fOucErr; - delete fOucLog; - - fOucErr = 0; - fOucLog = 0; - - delete fgInstance; - fgInstance = 0; -} diff --git a/src/XrdClient/XrdClientDebug.hh b/src/XrdClient/XrdClientDebug.hh deleted file mode 100644 index 4e3a776d41f..00000000000 --- a/src/XrdClient/XrdClientDebug.hh +++ /dev/null @@ -1,126 +0,0 @@ -#ifndef XRC_DEBUG_H -#define XRC_DEBUG_H -/******************************************************************************/ -/* */ -/* X r d C l i e n t D e b u g . h h */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2004) */ -/* Adapted from TXNetFile (root.cern.ch) originally done by */ -/* Alvise Dorigo, Fabrizio Furano */ -/* INFN Padova, 2003 */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// Singleton used to handle the debug level and the log output // -// // -////////////////////////////////////////////////////////////////////////// - -#include -#include "XrdClient/XrdClientConst.hh" -#include "XrdSys/XrdSysPthread.hh" -#include "XrdClient/XrdClientEnv.hh" -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdSys/XrdSysLogger.hh" -#include "XrdSys/XrdSysError.hh" - -using namespace std; - -#define DebugLevel() XrdClientDebug::Instance()->GetDebugLevel() -#define DebugSetLevel(l) XrdClientDebug::Instance()->SetLevel(l) - -#define Info(lvl, where, what) { \ -XrdClientDebug::Instance()->Lock();\ -if (XrdClientDebug::Instance()->GetDebugLevel() >= lvl) {\ -ostringstream outs;\ -outs << where << ": " << what; \ -XrdClientDebug::Instance()->TraceStream((short)lvl, outs);\ -}\ -XrdClientDebug::Instance()->Unlock();\ -} - -#define Error(where, what) { \ -ostringstream outs;\ -outs << where << ": " << what; \ -XrdClientDebug::Instance()->TraceStream((short)XrdClientDebug::kNODEBUG, outs);\ -} - - -class XrdClientDebug { - private: - short fDbgLevel; - - XrdSysLogger *fOucLog; - XrdSysError *fOucErr; - - static XrdClientDebug *fgInstance; - - XrdSysRecMutex fMutex; - - protected: - XrdClientDebug(); - ~XrdClientDebug(); - - public: - - enum { - kNODEBUG = 0, - kUSERDEBUG = 1, - kHIDEBUG = 2, - kDUMPDEBUG = 3 - }; - - short GetDebugLevel() { - XrdSysMutexHelper m(fMutex); - return fDbgLevel; - } - - static XrdClientDebug *Instance(); - - inline void SetLevel(int l) { - XrdSysMutexHelper m(fMutex); - fDbgLevel = l; - } - - inline void TraceStream(short DbgLvl, ostringstream &s) { - XrdSysMutexHelper m(fMutex); - - if (DbgLvl <= GetDebugLevel()) - fOucErr->Emsg("", s.str().c_str() ); - - s.str(""); - } - - // ostringstream outs; // Declare an output string stream. - - inline void TraceString(short DbgLvl, char * s) { - XrdSysMutexHelper m(fMutex); - if (DbgLvl <= GetDebugLevel()) - fOucErr->Emsg("", s); - } - - inline void Lock() { fMutex.Lock(); } - inline void Unlock() { fMutex.UnLock(); } - -}; -#endif diff --git a/src/XrdClient/XrdClientEnv.cc b/src/XrdClient/XrdClientEnv.cc deleted file mode 100644 index a7b721b1c5f..00000000000 --- a/src/XrdClient/XrdClientEnv.cc +++ /dev/null @@ -1,261 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C l i e n t E n v . c c */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2004) */ -/* Adapted from TXNetFile (root.cern.ch) originally done by */ -/* Alvise Dorigo, Fabrizio Furano */ -/* INFN Padova, 2003 */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// Singleton used to handle the default parameter values // -// // -////////////////////////////////////////////////////////////////////////// - -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdClient/XrdClientConst.hh" -#include "XrdClient/XrdClientEnv.hh" -#include "XrdClient/XrdClientConn.hh" -#include "XrdClient/XrdClientConnMgr.hh" -#include -#include -#include - -XrdClientEnv *XrdClientEnv::fgInstance = 0; - -XrdClientEnv *XrdClientEnv::Instance() { - // Create unique instance - - if (!fgInstance) { - fgInstance = new XrdClientEnv; - if (!fgInstance) { - std::cerr << "XrdClientEnv::Instance: fatal - couldn't create XrdClientEnv" << std::endl; - abort(); - } - } - return fgInstance; -} - -//_____________________________________________________________________________ -XrdClientEnv::XrdClientEnv() { - // Constructor - fOucEnv = new XrdOucEnv(); - fShellEnv = new XrdOucEnv(); - - PutInt(NAME_CONNECTTIMEOUT, DFLT_CONNECTTIMEOUT); - PutInt(NAME_REQUESTTIMEOUT, DFLT_REQUESTTIMEOUT); - PutInt(NAME_MAXREDIRECTCOUNT, DFLT_MAXREDIRECTCOUNT); - PutInt(NAME_DEBUG, DFLT_DEBUG); - PutInt(NAME_RECONNECTWAIT, DFLT_RECONNECTWAIT); - PutInt(NAME_REDIRCNTTIMEOUT, DFLT_REDIRCNTTIMEOUT); - PutInt(NAME_FIRSTCONNECTMAXCNT, DFLT_FIRSTCONNECTMAXCNT); - PutInt(NAME_READCACHESIZE, DFLT_READCACHESIZE); - PutInt(NAME_READCACHEBLKREMPOLICY, DFLT_READCACHEBLKREMPOLICY); - PutInt(NAME_READAHEADSIZE, DFLT_READAHEADSIZE); - PutInt(NAME_MULTISTREAMCNT, DFLT_MULTISTREAMCNT); - PutInt(NAME_DFLTTCPWINDOWSIZE, DFLT_DFLTTCPWINDOWSIZE); - PutInt(NAME_DATASERVERCONN_TTL, DFLT_DATASERVERCONN_TTL); - PutInt(NAME_LBSERVERCONN_TTL, DFLT_LBSERVERCONN_TTL); - PutInt(NAME_PURGEWRITTENBLOCKS, DFLT_PURGEWRITTENBLOCKS); - PutInt(NAME_READAHEADSTRATEGY, DFLT_READAHEADSTRATEGY); - PutInt(NAME_READTRIMBLKSZ, DFLT_READTRIMBLKSZ); - PutInt(NAME_TRANSACTIONTIMEOUT, DFLT_TRANSACTIONTIMEOUT); - PutInt(NAME_REMUSEDCACHEBLKS, DFLT_REMUSEDCACHEBLKS); - PutInt(NAME_ENABLE_FORK_HANDLERS, DFLT_ENABLE_FORK_HANDLERS); - PutInt(NAME_ENABLE_TCP_KEEPALIVE, DFLT_ENABLE_TCP_KEEPALIVE); - PutInt(NAME_TCP_KEEPALIVE_TIME, DFLT_TCP_KEEPALIVE_TIME); - PutInt(NAME_TCP_KEEPALIVE_INTERVAL, DFLT_TCP_KEEPALIVE_INTERVAL); - PutInt(NAME_TCP_KEEPALIVE_PROBES, DFLT_TCP_KEEPALIVE_PROBES); - PutInt(NAME_XRDCP_SIZE_HINT, DFLT_XRDCP_SIZE_HINT); - PutInt(NAME_PRINT_REDIRECTS, DFLT_PRINT_REDIRECTS); - - ImportInt( NAME_CONNECTTIMEOUT ); - ImportInt( NAME_REQUESTTIMEOUT ); - ImportInt( NAME_MAXREDIRECTCOUNT ); - ImportInt( NAME_DEBUG ); - ImportInt( NAME_RECONNECTWAIT ); - ImportInt( NAME_REDIRCNTTIMEOUT ); - ImportInt( NAME_FIRSTCONNECTMAXCNT ); - ImportInt( NAME_READCACHESIZE ); - ImportInt( NAME_READCACHEBLKREMPOLICY ); - ImportInt( NAME_READAHEADSIZE ); - ImportInt( NAME_MULTISTREAMCNT ); - ImportInt( NAME_DFLTTCPWINDOWSIZE ); - ImportInt( NAME_DATASERVERCONN_TTL ); - ImportInt( NAME_LBSERVERCONN_TTL ); - ImportInt( NAME_PURGEWRITTENBLOCKS ); - ImportInt( NAME_READAHEADSTRATEGY ); - ImportInt( NAME_READTRIMBLKSZ ); - ImportInt( NAME_TRANSACTIONTIMEOUT ); - ImportInt( NAME_REMUSEDCACHEBLKS ); - ImportInt( NAME_ENABLE_FORK_HANDLERS ); - ImportInt( NAME_ENABLE_TCP_KEEPALIVE ); - ImportInt( NAME_TCP_KEEPALIVE_TIME ); - ImportInt( NAME_TCP_KEEPALIVE_INTERVAL ); - ImportInt( NAME_TCP_KEEPALIVE_PROBES ); - ImportInt( NAME_XRDCP_SIZE_HINT ); - ImportInt( NAME_PRINT_REDIRECTS ); -} - -//------------------------------------------------------------------------------ -// Import a string variable from the shell environment -//------------------------------------------------------------------------------ -bool XrdClientEnv::ImportStr( const char *varname ) -{ - std::string name = "XRD_"; - name += varname; - std::transform( name.begin(), name.end(), name.begin(), ::toupper ); - - char *value; - if( !XrdOucEnv::Import( name.c_str(), value ) ) - return false; - - fShellEnv->Put( varname, value ); - return true; -} - -//------------------------------------------------------------------------------ -// Import an int variable from the shell environment -//------------------------------------------------------------------------------ -bool XrdClientEnv::ImportInt( const char *varname ) -{ - std::string name = "XRD_"; - name += varname; - std::transform( name.begin(), name.end(), name.begin(), ::toupper ); - - long value; - if( !XrdOucEnv::Import( name.c_str(), value ) ) - return false; - - fShellEnv->PutInt( varname, value ); - return true; -} - -//------------------------------------------------------------------------------ -// Get a string from the shell environment -//------------------------------------------------------------------------------ -const char *XrdClientEnv::ShellGet(const char *varname) -{ - XrdSysMutexHelper m( fMutex ); - const char *res = fShellEnv->Get( varname ); - if( res ) - return res; - - res = fOucEnv->Get( varname ); - return res; -} - -//------------------------------------------------------------------------------ -// Get an integer from the shell environment -//------------------------------------------------------------------------------ -long XrdClientEnv::ShellGetInt(const char *varname) -{ - XrdSysMutexHelper m( fMutex ); - const char *res = fShellEnv->Get( varname ); - - if( res ) - return fShellEnv->GetInt( varname ); - - return fOucEnv->GetInt( varname ); -} - - -//_____________________________________________________________________________ -XrdClientEnv::~XrdClientEnv() { - // Destructor - delete fOucEnv; - delete fShellEnv; - delete fgInstance; - - fgInstance = 0; -} - -//------------------------------------------------------------------------------ -// The fork handlers need to have C linkage (no symbol name mangling) -//------------------------------------------------------------------------------ -extern "C" -{ - -//------------------------------------------------------------------------------ -// To be called prior to forking -//------------------------------------------------------------------------------ -static void prepare() -{ - if( EnvGetLong( NAME_ENABLE_FORK_HANDLERS ) && ConnectionManager ) - { - ConnectionManager->ShutDown(); - XrdClientConn::DelSessionIDRepo(); - } - XrdClientEnv::Instance()->Lock(); -} - -//------------------------------------------------------------------------------ -// To be called in the parent just after forking -//------------------------------------------------------------------------------ -static void parent() -{ - XrdClientEnv::Instance()->UnLock(); - if( EnvGetLong( NAME_ENABLE_FORK_HANDLERS ) && ConnectionManager ) - { - ConnectionManager->BootUp(); - } -} - -//------------------------------------------------------------------------------ -// To be called in the child just after forking -//------------------------------------------------------------------------------ -static void child() -{ - XrdClientEnv::Instance()->ReInitLock(); - if( EnvGetLong( NAME_ENABLE_FORK_HANDLERS ) && ConnectionManager ) - { - ConnectionManager->BootUp(); - } -} - -} // extern "C" - -//------------------------------------------------------------------------------ -// Install the fork handlers on application startup and set IPV4 mode -//------------------------------------------------------------------------------ -namespace -{ - static struct Initializer - { - Initializer() - { - //------------------------------------------------------------------------ - // Install the fork handlers - //------------------------------------------------------------------------ -#ifndef WIN32 - if( pthread_atfork( prepare, parent, child ) != 0 ) - { - std::cerr << "Unable to install the fork handlers - safe forking not "; - std::cerr << "possible" << std::endl; - } -#endif - } - } initializer; -} diff --git a/src/XrdClient/XrdClientEnv.hh b/src/XrdClient/XrdClientEnv.hh deleted file mode 100644 index 9eb7d654bfa..00000000000 --- a/src/XrdClient/XrdClientEnv.hh +++ /dev/null @@ -1,127 +0,0 @@ -#ifndef XRD_CENV_H -#define XRD_CENV_H -/******************************************************************************/ -/* */ -/* X r d C l i e n t E n v . h h */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2004) */ -/* Adapted from TXNetFile (root.cern.ch) originally done by */ -/* Alvise Dorigo, Fabrizio Furano */ -/* INFN Padova, 2003 */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// Singleton used to handle the default parameter values // -// // -////////////////////////////////////////////////////////////////////////// - -#include "XrdOuc/XrdOucEnv.hh" -#include "XrdSys/XrdSysPthread.hh" - -#include - -#define EnvGetLong(x) XrdClientEnv::Instance()->ShellGetInt(x) -#define EnvGetString(x) XrdClientEnv::Instance()->ShellGet(x) -#define EnvPutString(name, val) XrdClientEnv::Instance()->Put(name, val) -#define EnvPutInt(name, val) XrdClientEnv::Instance()->PutInt(name, val) - -class XrdClientEnv { - private: - - XrdOucEnv *fOucEnv; - XrdSysRecMutex fMutex; - static XrdClientEnv *fgInstance; - XrdOucEnv *fShellEnv; - - protected: - XrdClientEnv(); - ~XrdClientEnv(); - - //--------------------------------------------------------------------------- - //! Import the variables from the shell environment, the variable names - //! are capitalized and prefixed with "XRD_" - //--------------------------------------------------------------------------- - bool ImportStr( const char *varname ); - bool ImportInt( const char *varname ); - - public: - - const char * Get(const char *varname) { - const char *res; - XrdSysMutexHelper m(fMutex); - - res = fOucEnv->Get(varname); - return res; - } - - long GetInt(const char *varname) { - long res; - XrdSysMutexHelper m(fMutex); - - res = fOucEnv->GetInt(varname); - return res; - } - - //--------------------------------------------------------------------------- - //! Get a string variable from the environment, the same as Get, but - //! checks the shell environment first - //--------------------------------------------------------------------------- - const char *ShellGet( const char *varname ); - - //--------------------------------------------------------------------------- - //! Get an integet variable from the environment, the same as GetInt, but - //! checks the shell environment first - //--------------------------------------------------------------------------- - long ShellGetInt( const char *varname ); - - - void Put(const char *varname, const char *value) { - XrdSysMutexHelper m(fMutex); - - fOucEnv->Put(varname, value); - } - - void PutInt(const char *varname, long value) { - XrdSysMutexHelper m(fMutex); - - fOucEnv->PutInt(varname, value); - } - void Lock() - { - fMutex.Lock(); - } - - void UnLock() - { - fMutex.UnLock(); - } - - int ReInitLock() - { - return fMutex.ReInitRecMutex(); - } - - static XrdClientEnv *Instance(); -}; -#endif diff --git a/src/XrdClient/XrdClientInputBuffer.cc b/src/XrdClient/XrdClientInputBuffer.cc deleted file mode 100644 index 48f4cb2b7cb..00000000000 --- a/src/XrdClient/XrdClientInputBuffer.cc +++ /dev/null @@ -1,230 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C l i e n t I n p u t B u f f e r . c c */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2004) */ -/* Adapted from TXNetFile (root.cern.ch) originally done by */ -/* Alvise Dorigo, Fabrizio Furano */ -/* INFN Padova, 2003 */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// Buffer for incoming messages (responses) // -// Handles the waiting (with timeout) for a message to come // -// belonging to a logical streamid // -// Multithread friendly // -// // -////////////////////////////////////////////////////////////////////////// - -#include "XrdClient/XrdClientInputBuffer.hh" -#include "XrdSys/XrdSysPthread.hh" -#include "XrdClient/XrdClientDebug.hh" -#ifndef WIN32 -#include -#endif -#include - -using namespace std; - -//________________________________________________________________________ -int XrdClientInputBuffer::MsgForStreamidCnt(int streamid) -{ - // Counts the number of messages belonging to the given streamid - - int cnt = 0; - XrdClientMessage *m = 0; - - for (fMsgIter = 0; fMsgIter < fMsgQue.GetSize(); ++fMsgIter) { - m = fMsgQue[fMsgIter]; - if (m->MatchStreamid(streamid)) - cnt++; - } - - return cnt; -} - - - -//________________________________________________________________________ -int XrdClientInputBuffer::WipeStreamid(int streamid) -{ - // Remove all the pending messages for the given streamid - // Healthy after connection shutdowns - - int cnt = 0; - XrdClientMessage *m = 0; - { - XrdSysMutexHelper mtx(fMutex); - - for (fMsgIter = fMsgQue.GetSize()-1; fMsgIter >= 0; --fMsgIter) { - m = fMsgQue[fMsgIter]; - if (m->MatchStreamid(streamid)) { - delete m; - fMsgQue.Erase(fMsgIter); - cnt++; - } - - } - } - - return cnt; -} - -//________________________________________________________________________ -XrdSysSemWait *XrdClientInputBuffer::GetSyncObjOrMakeOne(int streamid) { - // Gets the right sync obj to wait for messages for a given streamid - // If the semaphore is not available, it creates one. - - XrdSysSemWait *sem; - - { - XrdSysMutexHelper mtx(fMutex); - char buf[20]; - - snprintf(buf, 20, "%d", streamid); - - sem = fSyncobjRepo.Find(buf); - - if (!sem) { - sem = new XrdSysSemWait(0); - - fSyncobjRepo.Rep(buf, sem); - return sem; - - } else - return sem; - } - -} - - - -//_______________________________________________________________________ -XrdClientInputBuffer::XrdClientInputBuffer() { - // Constructor - - fMsgQue.Clear(); -} - - - -//_______________________________________________________________________ -int DeleteHashItem(const char *key, XrdSysSemWait *sem, void *Arg) { - - // This makes the Apply method delete the entry - return -1; -} - -XrdClientInputBuffer::~XrdClientInputBuffer() { - // Destructor - - // Delete all the syncobjs - { - XrdSysMutexHelper mtx(fMutex); - - - // Delete the content of the queue - for (fMsgIter = 0; fMsgIter < fMsgQue.GetSize(); ++fMsgIter) { - if (fMsgQue[fMsgIter]) delete fMsgQue[fMsgIter]; - fMsgQue[fMsgIter] = 0; - } - - fMsgQue.Clear(); - - // Delete all the syncobjs - fSyncobjRepo.Apply(DeleteHashItem, 0); - - } - - -} - -//_______________________________________________________________________ -int XrdClientInputBuffer::PutMsg(XrdClientMessage* m) -{ - // Put message in the list - int sz; - XrdSysSemWait *sem = 0; - - { - XrdSysMutexHelper mtx(fMutex); - - fMsgQue.Push_back(m); - sz = MexSize(); - - // Is anybody sleeping ? - if (m) - sem = GetSyncObjOrMakeOne( m->HeaderSID() ); - - } - - if (sem) { - sem->Post(); - } - - return sz; -} - - -//_______________________________________________________________________ -XrdClientMessage *XrdClientInputBuffer::GetMsg(int streamid, int secstimeout) -{ - // Gets the first XrdClientMessage from the queue, given a matching streamid. - // If there are no XrdClientMessages for the streamid, it waits for a number - // of seconds for something to come - - XrdSysSemWait *sem = 0; - XrdClientMessage *res = 0, *m = 0; - - // Find the sem where to wait for a msg - sem = GetSyncObjOrMakeOne(streamid); - - int to = secstimeout; - int dt = (to > 2) ? 2 : to; // 2 secs steps - while (to > 0) { - int rc = sem->Wait(dt); - if (!rc) { - // make sure is not a spurious signal ... - XrdSysMutexHelper mtx(fMutex); - if (fMsgQue.GetSize() > 0) { - - // We were awakened. Or the timeout elapsed. The mtx is again locked. - // If there are messages to dequeue, we pick the oldest one - for (fMsgIter = 0; fMsgIter < fMsgQue.GetSize(); ++fMsgIter) { - m = fMsgQue[fMsgIter]; - if ((!m) || m->IsError() || m->MatchStreamid(streamid)) { - res = fMsgQue[fMsgIter]; - fMsgQue.Erase(fMsgIter); - if (!m) return 0; - break; - } - } - break; - } - } else - to -= dt; - } - - return res; -} diff --git a/src/XrdClient/XrdClientInputBuffer.hh b/src/XrdClient/XrdClientInputBuffer.hh deleted file mode 100644 index 971fe2eba26..00000000000 --- a/src/XrdClient/XrdClientInputBuffer.hh +++ /dev/null @@ -1,89 +0,0 @@ -#ifndef XRC_INPUTBUFFER_H -#define XRC_INPUTBUFFER_H -/******************************************************************************/ -/* */ -/* X r d C l i e n t I n p u t B u f f e r . h h */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2004) */ -/* Adapted from TXNetFile (root.cern.ch) originally done by */ -/* Alvise Dorigo, Fabrizio Furano */ -/* INFN Padova, 2003 */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// Buffer for incoming messages (responses) // -// Handles the waiting (with timeout) for a message to come // -// belonging to a logical streamid // -// Multithread friendly // -// // -////////////////////////////////////////////////////////////////////////// - -#include "XrdClient/XrdClientMessage.hh" -#include "XrdSys/XrdSysPthread.hh" -#include "XrdSys/XrdSysSemWait.hh" -#include "XrdOuc/XrdOucHash.hh" -#include "XrdClient/XrdClientVector.hh" - -using namespace std; - -class XrdClientInputBuffer { - -private: - - XrdClientVector fMsgQue; // queue for incoming messages - int fMsgIter; // an iterator on it - - XrdSysRecMutex fMutex; // mutex to protect data structures - - XrdOucHash fSyncobjRepo; - // each streamid counts on a condition - // variable to make the caller wait - // until some data is available - - - XrdSysSemWait *GetSyncObjOrMakeOne(int streamid); - - int MsgForStreamidCnt(int streamid); - -public: - XrdClientInputBuffer(); - ~XrdClientInputBuffer(); - - inline bool IsMexEmpty() { return (MexSize() == 0); } - inline bool IsSemEmpty() { return (SemSize() == 0); } - inline int MexSize() { - XrdSysMutexHelper mtx(fMutex); - return fMsgQue.GetSize(); - } - int PutMsg(XrdClientMessage *msg); - inline int SemSize() { - XrdSysMutexHelper mtx(fMutex); - return fSyncobjRepo.Num(); - } - - int WipeStreamid(int streamid); - - XrdClientMessage *GetMsg(int streamid, int secstimeout); -}; -#endif diff --git a/src/XrdClient/XrdClientLogConnection.cc b/src/XrdClient/XrdClientLogConnection.cc deleted file mode 100644 index 4f7de2a1c73..00000000000 --- a/src/XrdClient/XrdClientLogConnection.cc +++ /dev/null @@ -1,112 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C l i e n t L o g C o n n e c t i o n . c c */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2004) */ -/* Adapted from TXNetFile (root.cern.ch) originally done by */ -/* Alvise Dorigo, Fabrizio Furano */ -/* INFN Padova, 2003 */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// Class implementing logical connections // -// // -////////////////////////////////////////////////////////////////////////// - -#include "XrdClient/XrdClientLogConnection.hh" -#include "XrdClient/XrdClientPhyConnection.hh" -#include "XrdClient/XrdClientDebug.hh" -#include "XrdClient/XrdClientSid.hh" - -//_____________________________________________________________________________ -XrdClientLogConnection::XrdClientLogConnection(XrdClientSid *sidmgr): - fSidManager(sidmgr) { - - // Constructor - - fPhyConnection = 0; - fStreamid = fSidManager ? fSidManager->GetNewSid() : 0; -} - -//_____________________________________________________________________________ -XrdClientLogConnection::~XrdClientLogConnection() { - // Destructor - - // Decrement counter in the reference phy conn - if (fPhyConnection) - fPhyConnection->CountLogConn(-1); - if (fSidManager) - fSidManager->ReleaseSidTree(fStreamid); -} - -//_____________________________________________________________________________ -int XrdClientLogConnection::WriteRaw(const void *buffer, int bufferlength, - int substreamid) -{ - // Send over the open physical connection 'bufferlength' bytes located - // at buffer. - // Return number of bytes sent. - - Info(XrdClientDebug::kDUMPDEBUG, - "WriteRaw", - "Writing " << bufferlength << " bytes to physical connection"); - - return fPhyConnection->WriteRaw(buffer, bufferlength, substreamid); -} - -//_____________________________________________________________________________ -int XrdClientLogConnection::ReadRaw(void *buffer, int bufferlength) -{ - // Receive from the open physical connection 'bufferlength' bytes and - // save in buffer. - // Return number of bytes received. - - Info(XrdClientDebug::kDUMPDEBUG, - "ReadRaw", - "Reading " << bufferlength << " bytes from physical connection"); - - return fPhyConnection->ReadRaw(buffer, bufferlength); - - -} - -//_____________________________________________________________________________ -UnsolRespProcResult XrdClientLogConnection::ProcessUnsolicitedMsg(XrdClientUnsolMsgSender *sender, - XrdClientMessage *unsolmsg) -{ - // We are here if an unsolicited response comes from the connmgr - // The response comes in the form of an XrdClientMessage *, that must NOT be - // destroyed after processing. It is destroyed by the first sender. - // Remember that we are in a separate thread, since unsolicited responses - // are asynchronous by nature. - - //Info(XrdClientDebug::kDUMPDEBUG, "LogConnection", - // "Processing unsolicited response (ID:"<HeaderSID()<<")"); - - // Local processing .... - - // We propagate the event to the obj which registered itself here - return SendUnsolicitedMsg(sender, unsolmsg); - -} diff --git a/src/XrdClient/XrdClientLogConnection.hh b/src/XrdClient/XrdClientLogConnection.hh deleted file mode 100644 index ea735029f0a..00000000000 --- a/src/XrdClient/XrdClientLogConnection.hh +++ /dev/null @@ -1,78 +0,0 @@ -#ifndef XRD_CLOGCONNECTION_H -#define XRD_CLOGCONNECTION_H -/******************************************************************************/ -/* */ -/* X r d C l i e n t L o g C o n n e c t i o n . h h */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2004) */ -/* Adapted from TXNetFile (root.cern.ch) originally done by */ -/* Alvise Dorigo, Fabrizio Furano */ -/* INFN Padova, 2003 */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// Class implementing logical connections // -// // -////////////////////////////////////////////////////////////////////////// - -#include "XProtocol/XPtypes.hh" -#include "XrdClient/XrdClientUnsolMsg.hh" - -class XrdClientSid; -class XrdClientPhyConnection; - -class XrdClientLogConnection: public XrdClientAbsUnsolMsgHandler, - public XrdClientUnsolMsgSender { -private: - XrdClientPhyConnection *fPhyConnection; - - // A logical connection has a private streamid - kXR_unt16 fStreamid; - - XrdClientSid *fSidManager; - -public: - XrdClientLogConnection(XrdClientSid *sidmgr); - virtual ~XrdClientLogConnection(); - - inline XrdClientPhyConnection *GetPhyConnection() { - return fPhyConnection; - } - - UnsolRespProcResult ProcessUnsolicitedMsg(XrdClientUnsolMsgSender *sender, - XrdClientMessage *unsolmsg); - - int ReadRaw(void *buffer, int BufferLength); - - inline void SetPhyConnection(XrdClientPhyConnection *PhyConn) { - fPhyConnection = PhyConn; - } - - int WriteRaw(const void *buffer, int BufferLength, int substreamid); - - inline kXR_unt16 Streamid() { - return fStreamid; - }; -}; -#endif diff --git a/src/XrdClient/XrdClientMStream.cc b/src/XrdClient/XrdClientMStream.cc deleted file mode 100644 index af7925b9121..00000000000 --- a/src/XrdClient/XrdClientMStream.cc +++ /dev/null @@ -1,359 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C l i e n t M S t r e a m . c c */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2006) */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// Helper code for XrdClient to handle multistream behavior // -// Functionalities dealing with // -// mstream creation on init // -// decisions to add/remove one // -// // -////////////////////////////////////////////////////////////////////////// - -#include "XrdClient/XrdClientProtocol.hh" -#include "XrdClient/XrdClientMStream.hh" -#include "XrdClient/XrdClientLogConnection.hh" -#include "XrdClient/XrdClientEnv.hh" -#include "XrdClient/XrdClientDebug.hh" -#include "XrdClient/XrdClientThread.hh" - -// This has to be a socket id pool which the server will never assign by itself -// Moreover, socketids are local to an instance of XrdClientPSock -#define XRDCLI_PSOCKTEMP -1000 - -struct ParStreamOpenerArgs { - XrdClientThread *thr; - XrdClientConn *cliconn; - int wan_port, wan_window; - int tmpid; -}; - -//_____________________________________________________________________________ -void *ParStreamOpenerThread(void *arg, XrdClientThread *thr) -{ - // This one just opens a new stream - - // Mask all allowed signals - if (thr->MaskSignal(0) != 0) - Error("ParStreamOpenerThread", "Warning: problems masking signals"); - - ParStreamOpenerArgs *parms = (ParStreamOpenerArgs *)arg; - - XrdClientMStream::AddParallelStream(parms->cliconn, parms->wan_port, parms->wan_window, parms->tmpid); - - return 0; -} - - -int XrdClientMStream::EstablishParallelStreams(XrdClientConn *cliconn) { - int mx = EnvGetLong(NAME_MULTISTREAMCNT); - int i, res; - int wan_port = 0, wan_window = 0; - - if (mx <= 1) return 1; - if (cliconn->GetServerType() == kSTBaseXrootd || - cliconn->GetServerType() == kSTMetaXrootd) return 1; - - // Get the XrdClientPhyconn to be used - XrdClientPhyConnection *phyconn = XrdClientConn::GetPhyConn(cliconn->GetLogConnID()); - if (!phyconn) return 0; - - // For a given phyconn we allow only one single attempt to establish multiple streams - // Any other thread or subsequent attempt will exit - if (phyconn->TestAndSetMStreamsGoing()) return 1; - - // Query the server config, for the WAN port and the windowsize - char *qryitems = (char *)"wan_port wan_window"; - ClientRequest qryRequest; - char qryResp[1024]; - memset( &qryRequest, 0, sizeof(qryRequest) ); - memset( qryResp, 0, 1024 ); - - cliconn->SetSID(qryRequest.header.streamid); - qryRequest.header.requestid = kXR_query; - qryRequest.query.infotype = kXR_Qconfig; - qryRequest.header.dlen = strlen(qryitems); - - res = cliconn->SendGenCommand(&qryRequest, qryitems, 0, qryResp, - false, (char *)"QueryConfig"); - - if (res && (cliconn->LastServerResp.status == kXR_ok) && - cliconn->LastServerResp.dlen) { - - sscanf(qryResp, "%d\n%d", - &wan_port, - &wan_window); - - Info(XrdClientDebug::kUSERDEBUG, - "XrdClientMStream::EstablishParallelStreams", "Server WAN parameters: port=" << wan_port << " windowsize=" << wan_window ); - } - - // Start the whole bunch of asynchronous connection requests - // By starting one thread for each, calling AddParallelStream once - // If no more threads are available, wait and retry - - ParStreamOpenerArgs paropeners[16]; - for (i = 0; i < mx; i++) { - paropeners[i].thr = 0; - paropeners[i].cliconn = cliconn; - paropeners[i].wan_port = wan_port; - paropeners[i].wan_window = wan_window; - paropeners[i].tmpid = 0; - } - - for (i = 0; i < mx; i++) { - Info(XrdClientDebug::kHIDEBUG, - "XrdClientMStream::EstablishParallelStreams", "Trying to establish " << i+1 << "th substream." ); - - paropeners[i].thr = new XrdClientThread(ParStreamOpenerThread); - if (paropeners[i].thr) { - paropeners[i].tmpid = XRDCLI_PSOCKTEMP - i; - if (paropeners[i].thr->Run(&paropeners[i])) { - Error("XrdClientMStream::EstablishParallelStreams", "Error establishing " << i+1 << "th substream. Thread start failed."); - delete paropeners[i].thr; - paropeners[i].thr = 0; - break; - } - } - - } - - for (i = 0; i < mx; i++) - if (paropeners[i].thr) { - Info(XrdClientDebug::kHIDEBUG, - "XrdClientMStream::EstablishParallelStreams", "Waiting for substream " << i+1 << "." ); - paropeners[i].thr->Join(0); - delete paropeners[i].thr; - } - - // If something goes wrong, stop adding new streams - //if (AddParallelStream(cliconn, wan_port, wan_window, XRDCLI_PSOCKTEMP - i)) - // break; - - - Info(XrdClientDebug::kHIDEBUG, - "XrdClientMStream::EstablishParallelStreams", "Parallel streams establishment finished." ); - - return i; -} - -// Add a parallel stream to the pool used by the given client inst -// Returns 0 if ok -int XrdClientMStream::AddParallelStream(XrdClientConn *cliconn, int port, int windowsz, int tempid) { - // Get the XrdClientPhyconn to be used - XrdClientPhyConnection *phyconn = XrdClientConn::GetPhyConn(cliconn->GetLogConnID()); - - - // If the phyconn already has all the needed streams... exit - if (phyconn->GetSockIdCount() > EnvGetLong(NAME_MULTISTREAMCNT)) return 0; - - // Connect a new connection, set the temp socket id and get the descriptor - // Temporary means that we need one to communicate, but its final id - // will be given by the server - int sockdescr = phyconn->TryConnectParallelStream(port, windowsz, tempid); - - if (sockdescr < 0) return -1; - - // The connection now is here but has not yet to be considered by the reader threads - // before having handshaked it, and this has to be sync man - // Do the handshake - ServerInitHandShake xbody; - if (phyconn->DoHandShake(xbody, tempid) == kSTError) return -1; - - // Send the kxr_bind req to get a new substream id, going to be the final one - int newid = -1; - int res = -1; - if (BindPendingStream(cliconn, tempid, newid) && - phyconn->IsValid() ) { - - // Everything ok, Establish the new connection with the new id - res = phyconn->EstablishPendingParallelStream(tempid, newid); - - if (res) { - // If the establish failed we have to remove the pending stream - RemoveParallelStream(cliconn, tempid); - return res; - } - - // After everything make the reader thread aware of the new stream - phyconn->UnBanSockDescr(sockdescr); - phyconn->ReinitFDTable(); - - } - else { - // If the bind failed we have to remove the pending stream - RemoveParallelStream(cliconn, tempid); - return -1; - } - - Info(XrdClientDebug::kHIDEBUG, - "XrdClientMStream::EstablishParallelStreams", "Substream added." ); - return 0; - -} - -// Remove a parallel stream to the pool used by the given client inst -int XrdClientMStream::RemoveParallelStream(XrdClientConn *cliconn, int substream) { - - // Get the XrdClientPhyconn to be used - XrdClientLogConnection *log = ConnectionManager->GetConnection(cliconn->GetLogConnID()); - if (!log) return 0; - - XrdClientPhyConnection *phyconn = log->GetPhyConnection(); - - if (phyconn) - phyconn->RemoveParallelStream(substream); - - return 0; - -} - - - -// Binds the pending temporary parallel stream to the current session -// Returns the substreamid assigned by the server into newid -bool XrdClientMStream::BindPendingStream(XrdClientConn *cliconn, int substreamid, int &newid) { - bool res = false; - - // Prepare request - ClientRequest bindFileRequest; - XrdClientConn::SessionIDInfo sess; - ServerResponseBody_Bind bndresp; - - // Get the XrdClientPhyconn to be used - XrdClientPhyConnection *phyconn = - ConnectionManager->GetConnection(cliconn->GetLogConnID())->GetPhyConnection(); - - cliconn->GetSessionID(sess); - - memset( &bindFileRequest, 0, sizeof(bindFileRequest) ); - cliconn->SetSID(bindFileRequest.header.streamid); - bindFileRequest.bind.requestid = kXR_bind; - memcpy( bindFileRequest.bind.sessid, sess.id, sizeof(sess.id) ); - - // The request has to be sent through the stream which has to be bound! - clientMarshall(&bindFileRequest); - res = phyconn->WriteRaw(&bindFileRequest, sizeof(bindFileRequest), substreamid); - - if (!res) return false; - - ServerResponseHeader hdr; - int rdres = 0; - - // Now wait for the header, on the same substream - rdres = phyconn->ReadRaw(&hdr, sizeof(ServerResponseHeader), substreamid); - - if (rdres < (int)sizeof(ServerResponseHeader)) { - Error("BindPendingStream", "Error reading bind response header for substream " << substreamid << "."); - return false; - } - - clientUnmarshall(&hdr); - - // Now wait for the response data, if any - // This code is specialized. - // If the answer is not what we were expecting, just return false, - // expecting that this connection will be shut down - if (hdr.status != kXR_ok) { - Error("BindPendingStream", "Server denied binding for substream " << substreamid << "."); - return false; - } - - if (hdr.dlen != sizeof(bndresp)) { - Error("BindPendingStream", "Unrecognized response datalen binding substream " << substreamid << "."); - return false; - } - - rdres = phyconn->ReadRaw(&bndresp, sizeof(bndresp), substreamid); - if (rdres != sizeof(bndresp)) { - Error("BindPendingStream", "Error reading response binding substream " << substreamid << "."); - return false; - } - - newid = bndresp.substreamid; - - return res; - -} - - -void XrdClientMStream::GetGoodSplitParameters(XrdClientConn *cliconn, - int &spltsize, int &reqsperstream, - kXR_int32 len) { - spltsize = DFLT_MULTISTREAMSPLITSIZE; - reqsperstream = 4; - - - // Let's try to distribute the load into maximum sized chunks - if (cliconn->GetParallelStreamCount() > 1) { - - // We start seeing which length we get trying to fill all the - // available slots ( per stream) - int candlen = xrdmax(DFLT_MULTISTREAMSPLITSIZE, - len / (reqsperstream * (cliconn->GetParallelStreamCount()-1)) + 1 ); - - // We don't want blocks smaller than a min value - // If this is the case we consider only one slot per stream - if (candlen < DFLT_MULTISTREAMSPLITSIZE) { - spltsize = xrdmax(DFLT_MULTISTREAMSPLITSIZE, - len / (cliconn->GetParallelStreamCount()-1) + 1 ); - reqsperstream = 1; - } - else spltsize = candlen; - - } - else spltsize = len; - - //cout << "parstreams: " << cliconn->GetParallelStreamCount() << - // " len: " << len << " splitsize: " << spltsize << " reqsperstream: " << - // reqsperstream << endl << endl; -} - - -// This splits a long requests into many smaller requests, to be sent in parallel -// through multiple streams -// Returns false if the chunk is not worth splitting -bool XrdClientMStream::SplitReadRequest(XrdClientConn *cliconn, kXR_int64 offset, kXR_int32 len, - XrdClientVector &reqlists) { - - int spltsize = 0; - int reqsperstream = 0; - - GetGoodSplitParameters(cliconn, spltsize, reqsperstream, len); - for (kXR_int32 pp = 0; pp < len; pp += spltsize) { - ReadChunk ck; - - ck.offset = pp+offset; - ck.len = xrdmin(len - pp, spltsize); - ck.streamtosend = cliconn->GetParallelStreamToUse(reqsperstream); - - reqlists.Push_back(ck); - - } - - return true; -} diff --git a/src/XrdClient/XrdClientMStream.hh b/src/XrdClient/XrdClientMStream.hh deleted file mode 100644 index 74f62ae4388..00000000000 --- a/src/XrdClient/XrdClientMStream.hh +++ /dev/null @@ -1,75 +0,0 @@ -#ifndef XRD_CLI_MSTREAM -#define XRD_CLI_MSTREAM -/******************************************************************************/ -/* */ -/* X r d C l i e n t M S t r e a m . h h */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2006) */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// Helper code for XrdClient to handle multistream behavior // -// // -////////////////////////////////////////////////////////////////////////// - -#include "XrdClient/XrdClientConn.hh" - -class XrdClientMStream { -public: - - // Compute the parameters to split blocks - static void GetGoodSplitParameters(XrdClientConn *cliconn, - int &spltsize, int &reqsperstream, - kXR_int32 len); - - // Establish all the parallel streams, stop - // adding streams at the first creation refusal/failure - static int EstablishParallelStreams(XrdClientConn *cliconn); - - // Add a parallel stream to the pool used by the given client inst - static int AddParallelStream(XrdClientConn *cliconn, int port, int windowsz, int tempid); - - // Remove a parallel stream to the pool used by the given client inst - static int RemoveParallelStream(XrdClientConn *cliconn, int substream); - - // Binds the pending temporary parallel stream to the current session - // Returns into newid the substreamid assigned by the server - static bool BindPendingStream(XrdClientConn *cliconn, int substreamid, int &newid); - - struct ReadChunk { - kXR_int64 offset; - kXR_int32 len; - int streamtosend; - }; - - - // This splits a long requests into many smaller requests, to be sent in parallel - // through multiple streams - // Returns false if the chunk is not worth splitting - static bool SplitReadRequest(XrdClientConn *cliconn, kXR_int64 offset, kXR_int32 len, - XrdClientVector &reqlists); - - -}; -#endif diff --git a/src/XrdClient/XrdClientMessage.cc b/src/XrdClient/XrdClientMessage.cc deleted file mode 100644 index 8eb396d8c48..00000000000 --- a/src/XrdClient/XrdClientMessage.cc +++ /dev/null @@ -1,254 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C l i e n t M e s s a g e . c c */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2004) */ -/* Adapted from TXNetFile (root.cern.ch) originally done by */ -/* Alvise Dorigo, Fabrizio Furano */ -/* INFN Padova, 2003 */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// A message coming from a physical connection. I.e. a server response // -// or some kind of error // -// // -////////////////////////////////////////////////////////////////////////// - -#include "XrdClient/XrdClientMessage.hh" -#include "XrdClient/XrdClientProtocol.hh" -#include "XrdClient/XrdClientDebug.hh" -#include "XrdClient/XrdClientPhyConnection.hh" - -#include "XrdSys/XrdSysPlatform.hh" - -#include // for malloc -#include // for memcpy - -//__________________________________________________________________________ -XrdClientMessage::XrdClientMessage(struct ServerResponseHeader header) -{ - // Constructor - - fStatusCode = kXrdMSC_ok; - memcpy((void *)&fHdr, (const void*)&header, sizeof(ServerResponseHeader)); - fData = 0; - fMarshalled = false; - if (!CreateData()) { - Error("XrdClientMessage", - "Error allocating " << fHdr.dlen << " bytes."); - fAllocated = false; - } else - fAllocated = true; -} - -//__________________________________________________________________________ -XrdClientMessage::XrdClientMessage() -{ - // Default constructor - - memset(&fHdr, 0, sizeof(fHdr)); - fStatusCode = kXrdMSC_ok; - fData = 0; - fMarshalled = false; - fAllocated = false; -} - -//__________________________________________________________________________ -XrdClientMessage::~XrdClientMessage() -{ - // Destructor - - if (fData) - free(fData); -} - -//__________________________________________________________________________ -void *XrdClientMessage::DonateData() -{ - // Unlink the owned data in order to pass them elsewhere - - void *res = fData; - fData = 0; - fAllocated = false; - - return (res); -} - -//__________________________________________________________________________ -bool XrdClientMessage::CreateData() -{ - // Allocate data - - if (!fAllocated) { - if (fHdr.dlen > 0) { - - long pgsz = sysconf(_SC_PAGESIZE); - int memtrbl = 0; - if ((pgsz > 0) && (fHdr.dlen+1 > pgsz)) - memtrbl = posix_memalign(&fData, pgsz, fHdr.dlen+1); - else - fData = malloc(fHdr.dlen+1); - - if (!fData || memtrbl) { - Error("XrdClientMessage::CreateData", - "Fatal ERROR *** memory allocation alloc of " << - fHdr.dlen+1 << " bytes failed." - " Probable system resources exhausted."); - return FALSE; - } - char *tmpPtr = (char *)fData; - - // Useful to get always 0-terminated strings - tmpPtr[fHdr.dlen] = '\0'; - } - if (!fData) - return FALSE; - else - return TRUE; - } else - return TRUE; -} - -//__________________________________________________________________________ -void XrdClientMessage::Marshall() -{ - // Marshall, i.e. put in network byte order - - if (!fMarshalled) { - ServerResponseHeader2NetFmt(&fHdr); - fMarshalled = TRUE; - } -} - -//__________________________________________________________________________ -void XrdClientMessage::Unmarshall() -{ - // Unmarshall, i.e. from network byte to normal order - - if (fMarshalled) { - clientUnmarshall(&fHdr); - fMarshalled = FALSE; - } -} - -//__________________________________________________________________________ -int XrdClientMessage::ReadRaw(XrdClientPhyConnection *phy) -{ - // Given a physical connection, we completely build the content - // of the message, reading it from the socket of a phyconn - - int readres; - int readLen = sizeof(ServerResponseHeader); - int usedsubstreamid = 0; - - phy->ReadLock(); - - Info(XrdClientDebug::kDUMPDEBUG, - "XrdClientMessage::ReadRaw", - "Reading header (" << readLen << " bytes)."); - - // Read a header from any substream and report it - readres = phy->ReadRaw((void *)&fHdr, readLen, -1, &usedsubstreamid); - if (readres == readLen) phy->PauseSelectOnSubstream(usedsubstreamid); - - phy->ReadUnLock(); - - if (readres != readLen) { - - if (readres == TXSOCK_ERR_TIMEOUT) - SetStatusCode(kXrdMSC_timeout); - else { - Info(XrdClientDebug::kNODEBUG,"XrdClientMessage::ReadRaw", - "Failed to read header (" << readLen << " bytes)."); - SetStatusCode(kXrdMSC_readerr); - - } - memset(&fHdr, 0, sizeof(fHdr)); - } - - // the data arrive marshalled from the server (i.e. network byte order) - SetMarshalled(TRUE); - Unmarshall(); - - Info(XrdClientDebug::kDUMPDEBUG, - "XrdClientMessage::ReadRaw"," sid: "< 0) { - - Info(XrdClientDebug::kDUMPDEBUG, - "XrdClientMessage::ReadRaw", - "Reading data (" << fHdr.dlen << " bytes) from substream " << usedsubstreamid); - - if (!CreateData()) { - - Info(XrdClientDebug::kNODEBUG,"XrdClientMessage::ReadRaw", - "Failed to create data (" << fHdr.dlen << " bytes) from substream " << - usedsubstreamid << "."); - - SetStatusCode(kXrdMSC_timeout); - - memset(&fHdr, 0, sizeof(fHdr)); - - } else if (phy->ReadRaw(fData, fHdr.dlen, usedsubstreamid) != fHdr.dlen) { - - Info(XrdClientDebug::kNODEBUG,"XrdClientMessage::ReadRaw", - "Failed to read data (" << fHdr.dlen << " bytes) from substream " << - usedsubstreamid << "."); - - free( DonateData() ); - - if (readres == TXSOCK_ERR_TIMEOUT) - SetStatusCode(kXrdMSC_timeout); - else - SetStatusCode(kXrdMSC_readerr); - - memset(&fHdr, 0, sizeof(fHdr)); - } - } - phy->RestartSelectOnSubstream(usedsubstreamid); - // phy->ReadUnLock(); - return 1; -} - -//___________________________________________________________________________ -void XrdClientMessage::Int2CharStreamid(kXR_char *charstreamid, short intstreamid) -{ - // Converts a streamid given as an integer to its representation - // suitable for the streamid inside the messages (i.e. ascii) - - memcpy(charstreamid, &intstreamid, sizeof(intstreamid)); -} - -//___________________________________________________________________________ -kXR_unt16 XrdClientMessage::CharStreamid2Int(kXR_char *charstreamid) -{ - // Converts a streamid given as an integer to its representation - // suitable for the streamid inside the messages (i.e. ascii) - - kXR_unt16 res = *((short *)charstreamid); - - return res; -} diff --git a/src/XrdClient/XrdClientMessage.hh b/src/XrdClient/XrdClientMessage.hh deleted file mode 100644 index 38107e0dc9f..00000000000 --- a/src/XrdClient/XrdClientMessage.hh +++ /dev/null @@ -1,103 +0,0 @@ -#ifndef XRC_MESSAGE_H -#define XRC_MESSAGE_H -/******************************************************************************/ -/* */ -/* X r d C l i e n t M e s s a g e . h h */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2004) */ -/* Adapted from TXNetFile (root.cern.ch) originally done by */ -/* Alvise Dorigo, Fabrizio Furano */ -/* INFN Padova, 2003 */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// A message coming from a physical connection. I.e. a server response // -// or some kind of error // -// // -////////////////////////////////////////////////////////////////////////// - -#include "XProtocol/XProtocol.hh" -#include "XrdSys/XrdSysPthread.hh" - -#ifndef WIN32 -#include -#endif - -class XrdClientPhyConnection; - -class XrdClientMessage { - -private: - bool fAllocated; - void *fData; - bool fMarshalled; - short fStatusCode; - XrdSysRecMutex fMultireadMutex; - -public: - - static kXR_unt16 CharStreamid2Int(kXR_char *charstreamid); - static void Int2CharStreamid(kXR_char *charstreamid, short intstreamid); - - enum EXrdMSCStatus { // Some status codes useful - kXrdMSC_ok = 0, - kXrdMSC_readerr = 1, - kXrdMSC_writeerr = 2, - kXrdMSC_timeout = 3 - }; - - ServerResponseHeader fHdr; - - XrdClientMessage(ServerResponseHeader header); - XrdClientMessage(); - - ~XrdClientMessage(); - - bool CreateData(); - - inline int DataLen() { return fHdr.dlen; } - - void *DonateData(); - inline void *GetData() {return fData;} - inline int GetStatusCode() { return fStatusCode; } - - inline int HeaderStatus() { return fHdr.status; } - - inline kXR_unt16 HeaderSID() { return CharStreamid2Int(fHdr.streamid); } - - bool IsAttn() { return (HeaderStatus() == kXR_attn); } - - inline bool IsError() { return (fStatusCode != kXrdMSC_ok); }; - - inline bool IsMarshalled() { return fMarshalled; } - void Marshall(); - inline bool MatchStreamid(short sid) { return (HeaderSID() == sid);} - int ReadRaw(XrdClientPhyConnection *phy); - inline void SetHeaderStatus(kXR_unt16 sts) { fHdr.status = sts; } - inline void SetMarshalled(bool m) { fMarshalled = m; } - inline void SetStatusCode(kXR_unt16 status) { fStatusCode = status; } - void Unmarshall(); - -}; -#endif diff --git a/src/XrdClient/XrdClientPSock.cc b/src/XrdClient/XrdClientPSock.cc deleted file mode 100644 index 641b771c1e5..00000000000 --- a/src/XrdClient/XrdClientPSock.cc +++ /dev/null @@ -1,457 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C l i e n t P S o c k . c c */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2006) */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// Client Socket with parallel streams and timeout features using XrdNet// -// // -////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include -#include -#include "XrdClient/XrdClientPSock.hh" -#include "XrdSys/XrdSysLogger.hh" -#include "XrdNet/XrdNetSocket.hh" -#include "XrdClient/XrdClientDebug.hh" -#include "XrdClient/XrdClientEnv.hh" - -#ifdef __solaris__ -#include -#endif - -#ifndef WIN32 -#include -#include -#else -#include "XrdSys/XrdWin32.hh" -#endif - -//_____________________________________________________________________________ -XrdClientPSock::XrdClientPSock(XrdClientUrlInfo Host, int windowsize): - XrdClientSock(Host, windowsize) { - - lastsidhint = 0; - fReinit_fd = true; - -} - -//_____________________________________________________________________________ -XrdClientPSock::~XrdClientPSock() -{ - // Destructor - Disconnect(); -} - -//_____________________________________________________________________________ -int CloseSockFunc(int K, int V, void *arg) { - ::close(V); - - // And also we delete this item by returning < 0 - return -1; -} -//_____________________________________________________________________________ -void XrdClientPSock::Disconnect() -{ - // Close the connection - XrdSysMutexHelper mtx(fMutex); - - fConnected = FALSE; - - // Make the SocketPool invoke the closing of all sockets - fSocketPool.Apply( CloseSockFunc, 0 ); - - fSocketIdPool.Purge(); - fSocketIdRepo.Clear(); - -} - - - -//_____________________________________________________________________________ - -struct FdSetSockFuncPars { - struct fdinfo *fdnfo; - XrdOucRash *banned; -}; - -int FdSetSockFunc(int sockid, int sockdescr, void *arg) { - struct FdSetSockFuncPars *pars = (struct FdSetSockFuncPars *)arg; - struct fdinfo *fds = pars->fdnfo; - - - // There could some sockets in the "banned" state - // I.e. still in the process of being handshaked, but present in the tables - // Those sockets must not be taken into acct by the global selecting mechanism - if ( (sockdescr >= 0) && (!pars->banned->Find(sockdescr)) ) { - FD_SET(sockdescr, &fds->fdset); - fds->maxfd = xrdmax(fds->maxfd, sockdescr); - } - - - // And we continue - return 0; -} - -//_____________________________________________________________________________ -int XrdClientPSock::RecvRaw(void* buffer, int length, int substreamid, - int *usedsubstreamid) -{ - // Read bytes following carefully the timeout rules - time_t starttime; - int bytesread = 0; - int selRet; - // The local set of interesting sock descriptors - struct fdinfo locfdinfo; - - // We cycle reading data. - // An exit occurs if: - // We have all the data we are waiting for - // Or a timeout occurs - // The connection is closed by the other peer - - if (!fConnected) { - Error("XrdClientPSock::RecvRaw", "Not connected."); - return TXSOCK_ERR; - } - if (GetMainSock() < 0) { - Error("XrdClientPSock::RecvRaw", "cannot find main socket."); - return TXSOCK_ERR; - } - - - starttime = time(0); - - while (bytesread < length) { - - // We cycle on the select, ignoring the possible interruptions - // We are waiting for something to come from the socket(s) - do { - - if (fReinit_fd) { - // we want to reconstruct the global fd_set - Info(XrdClientDebug::kDUMPDEBUG, "XrdClientPSock::RecvRaw", "Reconstructing global fd table."); - - XrdSysMutexHelper mtx(fMutex); - - FD_ZERO(&globalfdinfo.fdset); - globalfdinfo.maxfd = 0; - - // We are interested in any sock, except for the banned ones - struct FdSetSockFuncPars fdpars; - fdpars.fdnfo = &globalfdinfo; - fdpars.banned = &fSocketNYHandshakedIdPool; - - fSocketPool.Apply( FdSetSockFunc, (void *)&fdpars ); - fReinit_fd = false; - } - - // If we already read something, then we are stuck to a single socket - // waiting for the completion of its read - // This is reflected in the local fdset hence we don't have to touch it - // if ((!bytesread) || (substreamid == -1)) { - - if (substreamid == -1) { - // We are interested in any sock and we are not stuck - // to any in particular so we take the global fdset - locfdinfo = globalfdinfo; - - } else { - // we are using a single specified sock - XrdSysMutexHelper mtx(fMutex); - - FD_ZERO(&locfdinfo.fdset); - locfdinfo.maxfd = 0; - - int sock = GetSock(substreamid); - if (sock >= 0) { - FD_SET(sock, &locfdinfo.fdset); - locfdinfo.maxfd = sock; - - } - else { - Error("XrdClientPSock::RecvRaw", "since we entered RecvRaw, the substreamid " << - substreamid << " has been removed."); - - // A dropped parallel stream is not considered - // as an error - if (substreamid == 0) - return TXSOCK_ERR; - else { - XrdSysMutexHelper mtx(fMutex); - if (sock >= 0) - FD_CLR(sock, &globalfdinfo.fdset); - - RemoveParallelSock(substreamid); - //ReinitFDTable(); - return TXSOCK_ERR_TIMEOUT; - } - } - } - - // } - - - // If too much time has elapsed, then we return an error - if ((time(0) - starttime) > EnvGetLong(NAME_REQUESTTIMEOUT)) { - - return TXSOCK_ERR_TIMEOUT; - } - - struct timeval tv = { 0, 100000 }; // .1 second as timeout step - - // Wait for some events from the socket pool - errno = 0; - selRet = select(locfdinfo.maxfd+1, &locfdinfo.fdset, NULL, NULL, &tv); - - if ( (selRet < 0) && (errno != EINTR) && (errno != EAGAIN) ) { - Error("XrdClientPSock::RecvRaw", "Error in select() : " << - ::strerror(errno)); - - ReinitFDTable(); - return TXSOCK_ERR; - } - - } while (selRet <= 0 && !fRDInterrupt); - - // If we are here, selRet is > 0 why? - // Because the timeout and the select error are handled inside the previous loop - // But we could have been requested to interrupt - - if (GetMainSock() < 0) { - Error("XrdClientPSock::RecvRaw", "since we entered RecvRaw, the main socket " - "file descriptor has been removed."); - return TXSOCK_ERR; - } - - // If we have been interrupt, reset the interrupt and exit - if (fRDInterrupt) { - fRDInterrupt = 0; - Error("XrdClientPSock::RecvRaw", "got interrupt"); - return TXSOCK_ERR_INTERRUPT; - } - - // First of all, we check if there is something to read from any sock. - // the first we find is ok for now - for (int ii = 0; ii <= locfdinfo.maxfd; ii++) { - - if (FD_ISSET(ii, &locfdinfo.fdset)) { - int n = 0; - - do { - errno = 0; - n = ::recv(ii, static_cast(buffer) + bytesread, - length - bytesread, 0); - } while (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)); - - // If we read nothing, the connection has been closed by the other side - if ((n <= 0) && (errno != EINTR)) { - Error("XrdClientPSock::RecvRaw", "Error reading from socket " << ii << ". n=" << n << - " Error:'" << - ::strerror(errno) << "'"); - - // A dropped parallel stream is not considered - // as an error - if (( GetSockId(ii) == 0 ) || ( GetSockId(ii) == -1 )) - return TXSOCK_ERR; - else { - XrdSysMutexHelper mtx(fMutex); - FD_CLR(ii, &globalfdinfo.fdset); - RemoveParallelSock(GetSockId(ii)); - //ReinitFDTable(); - return TXSOCK_ERR_TIMEOUT; - } - - } - - if (n > 0) bytesread += n; - - // If we need to loop more than once to get the whole amount - // of requested bytes, then we have to select only on this fd which - // started providing a chunk of data - FD_ZERO(&locfdinfo.fdset); - FD_SET(ii, &locfdinfo.fdset); - locfdinfo.maxfd = ii; - substreamid = GetSockId(ii); - - if (usedsubstreamid) *usedsubstreamid = GetSockId(ii); - - // We got some data, hence we stop scanning the fd list, - // but we remain stuck to the socket which started providing data - break; - } - } - - } // while - - // Return number of bytes received - // And also usedparsockid has been initialized with the sockid we got something from - - return bytesread; -} - -int XrdClientPSock::SendRaw(const void* buffer, int length, int substreamid) { - - int sfd = GetSock(substreamid); - - Info(XrdClientDebug::kDUMPDEBUG, - "SendRaw", - "Writing to substreamid " << - substreamid << " mapped to socket fd " << sfd); - - return XrdClientSock::SendRaw(buffer, length, sfd); - -} - -//_____________________________________________________________________________ -void XrdClientPSock::TryConnect(bool isUnix) { - // Already connected - we are done. - // - if (fConnected) { - assert(GetMainSock() >= 0); - return; - } - - int s = TryConnect_low(isUnix); - - if (s >= 0) { - XrdSysMutexHelper mtx(fMutex); - - int z = 0; - fSocketPool.Rep(0, s); - fSocketIdPool.Rep(s, z); - // fSocketIdRepo.Push_back(z); - } - -} - -XrdClientSock::Sockdescr XrdClientPSock::TryConnectParallelSock(int port, int windowsz, Sockid &newid) { - - int s = TryConnect_low(false, port, windowsz); - - if (s >= 0) { - - XrdSysMutexHelper mtx(fMutex); - - // Now we have a good connection, valid from the TCP point of view - - // But we prevent the socket from appearing in the global fd table for now - BanSockDescr(s, newid); - - // We put the descriptor and the id in the tables - fSocketPool.Rep(newid, s); - fSocketIdPool.Rep(s, newid); - - } - - return s; -} - -int XrdClientPSock::RemoveParallelSock(int sockid) { - - XrdSysMutexHelper mtx(fMutex); - - int s = GetSock(sockid); - - if (s >= 0) ::close(s); - - fSocketIdPool.Del(s); - fSocketPool.Del(sockid); - - for (int i = 0; i < fSocketIdRepo.GetSize(); i++) - if (fSocketIdRepo[i] == sockid) { - fSocketIdRepo.Erase(i); - break; - } - - return 0; -} - -int XrdClientPSock::EstablishParallelSock(Sockid tmpsockid, Sockid newsockid) { - XrdSysMutexHelper mtx(fMutex); - - Sockdescr s = GetSock(tmpsockid); - if (s >= 0) { - - fSocketPool.Del(tmpsockid); - fSocketIdPool.Del(s); - - fSocketPool.Rep(newsockid, s); - fSocketIdPool.Rep(s, newsockid); - fSocketIdRepo.Push_back(newsockid); - - Info(XrdClientDebug::kUSERDEBUG, - "XrdClientSock::EstablishParallelSock", "Sockid " << newsockid << " established."); - - return 0; - } - - return -1; - -} - -int XrdClientPSock::GetSockIdHint(int reqsperstream) { - - XrdSysMutexHelper mtx(fMutex); - - // A round robin through the secondary streams. We avoid - // requesting data through the main one because it can become a bottleneck - if (fSocketIdRepo.GetSize() > 0) { - int tmp = lastsidhint+1; - lastsidhint = ( ( tmp % (fSocketIdRepo.GetSize()*reqsperstream) ) ); - } - else lastsidhint = 0; - - return fSocketIdRepo[lastsidhint / reqsperstream]; - //return (random() % (fSocketIdRepo.GetSize()+1)); - -} - - - -void XrdClientPSock::PauseSelectOnSubstream(int substreamid) { - XrdSysMutexHelper mtx(fMutex); - - int sock = GetSock(substreamid); - - if (sock >= 0) - FD_CLR(sock, &globalfdinfo.fdset); - -} - - -void XrdClientPSock::RestartSelectOnSubstream(int substreamid) { - XrdSysMutexHelper mtx(fMutex); - - int sock = GetSock(substreamid); - - if (sock >= 0) - FD_SET(sock, &globalfdinfo.fdset); - -} diff --git a/src/XrdClient/XrdClientPSock.hh b/src/XrdClient/XrdClientPSock.hh deleted file mode 100644 index 4c2628c35a9..00000000000 --- a/src/XrdClient/XrdClientPSock.hh +++ /dev/null @@ -1,157 +0,0 @@ -#ifndef XRC_PSOCK_H -#define XRC_PSOCK_H -/******************************************************************************/ -/* */ -/* X r d C l i e n t P S o c k . h h */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2006) */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// Client Socket with multiple streams and timeout features // -// // -////////////////////////////////////////////////////////////////////////// - -#include "XrdClient/XrdClientSock.hh" -#include "XrdClient/XrdClientVector.hh" -#include "XrdOuc/XrdOucRash.hh" -#include "XrdSys/XrdSysPthread.hh" - -struct fdinfo { - fd_set fdset; - int maxfd; -}; - -class XrdClientPSock: public XrdClientSock { - -friend class XrdClientPhyConnection; - -private: - - - XrdSysRecMutex fMutex; - - // The set of interesting sock descriptors - fdinfo globalfdinfo; - - Sockid lastsidhint; - - // To have a pool of the ids in use, - // e.g. to select a random stream from the set of possible streams - XrdClientVector fSocketIdRepo; - - // To translate from socket id to socket descriptor - XrdOucRash fSocketPool; - - // To keep track of the sockets which have to be - // temporarily ignored in the global fd - // because they have not yet been handshaked - XrdOucRash fSocketNYHandshakedIdPool; - - Sockdescr GetSock(Sockid id) { - XrdSysMutexHelper mtx(fMutex); - - Sockdescr *fd = fSocketPool.Find(id); - if (fd) return *fd; - else return -1; - } - - Sockdescr GetMainSock() { - return GetSock(0); - } - - // To translate from socket descriptor to socket id - XrdOucRash fSocketIdPool; - - // From a socket descriptor, we get its id - Sockid GetSockId(Sockdescr sock) { - XrdSysMutexHelper mtx(fMutex); - - Sockid *id = fSocketIdPool.Find(sock); - if (id) return *id; - else return -1; - } - -protected: - - virtual int SaveSocket() { - XrdSysMutexHelper mtx(fMutex); - - // this overwrites the main stream - Sockdescr *fd = fSocketPool.Find(0); - - fSocketIdPool.Del(*fd); - fSocketPool.Del(0); - - fConnected = 0; - fRDInterrupt = 0; - fWRInterrupt = 0; - - if (fd) return *fd; - else return 0; - } - -public: - XrdClientPSock(XrdClientUrlInfo host, int windowsize = 0); - virtual ~XrdClientPSock(); - - void BanSockDescr(Sockdescr s, Sockid newid) { XrdSysMutexHelper mtx(fMutex); fSocketNYHandshakedIdPool.Rep(s, newid); } - void UnBanSockDescr(Sockdescr s) { XrdSysMutexHelper mtx(fMutex); fSocketNYHandshakedIdPool.Del(s); } - - // Gets length bytes from the parsockid socket - // If substreamid = -1 then - // gets length bytes from any par socket, and returns the usedsubstreamid - // where it got the bytes from - virtual int RecvRaw(void* buffer, int length, Sockid substreamid = -1, - Sockid *usedsubstreamid = 0); - - // Send the buffer to the specified substream - // if substreamid == 0 then use the main socket - virtual int SendRaw(const void* buffer, int length, Sockid substreamid = 0); - - virtual void TryConnect(bool isUnix = 0); - - virtual Sockdescr TryConnectParallelSock(int port, int windowsz, Sockid &tmpid); - - virtual int EstablishParallelSock(Sockid tmpsockid, Sockid newsockid); - - virtual void Disconnect(); - - virtual int RemoveParallelSock(Sockid sockid); - - // Suggests a sockid to be used for a req - virtual Sockid GetSockIdHint(int reqsperstream); - - // And this is the total stream count - virtual int GetSockIdCount() { - XrdSysMutexHelper mtx(fMutex); - - return fSocketPool.Num(); - } - - virtual void PauseSelectOnSubstream(Sockid substreamid); - virtual void RestartSelectOnSubstream(Sockid substreamid); - -}; -#endif diff --git a/src/XrdClient/XrdClientPhyConnection.cc b/src/XrdClient/XrdClientPhyConnection.cc deleted file mode 100644 index dc1f8724c02..00000000000 --- a/src/XrdClient/XrdClientPhyConnection.cc +++ /dev/null @@ -1,908 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C l i e n t P h y C o n n e c t i o n . c c */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2004) */ -/* Adapted from TXNetFile (root.cern.ch) originally done by */ -/* Alvise Dorigo, Fabrizio Furano */ -/* INFN Padova, 2003 */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// Class handling physical connections to xrootd servers // -// // -////////////////////////////////////////////////////////////////////////// - -#include -#include -#include "XrdClient/XrdClientPhyConnection.hh" -#include "XrdClient/XrdClientDebug.hh" -#include "XrdClient/XrdClientMessage.hh" -#include "XrdClient/XrdClientEnv.hh" -#include "XrdClient/XrdClientSid.hh" -#include "XrdClient/XrdClientPSock.hh" -#include "XrdClient/XrdClientThread.hh" -#include "XrdSec/XrdSecInterface.hh" -#ifndef WIN32 -#include -#else -#include -#endif - - -#define READERCOUNT (xrdmin(50, EnvGetLong(NAME_MULTISTREAMCNT)+1)) - -//____________________________________________________________________________ -void *SocketReaderThread(void * arg, XrdClientThread *thr) -{ - // This thread is the base for the async capabilities of XrdClientPhyConnection - // It repeatedly keeps reading from the socket, while feeding the - // MsqQ with a stream of XrdClientMessages containing what's happening - // at the socket level - - // Mask all allowed signals - if (thr->MaskSignal(0) != 0) - Error("SocketReaderThread", "Warning: problems masking signals"); - - XrdClientPhyConnection *thisObj; - - - Info(XrdClientDebug::kHIDEBUG, - "SocketReaderThread", - "Reader Thread starting."); - - thisObj = (XrdClientPhyConnection *)arg; - thisObj->StartedReader(); - - while (1) { - thisObj->BuildMessage(TRUE, TRUE); - - if (thisObj->CheckAutoTerm()) - break; - } - - Info(XrdClientDebug::kHIDEBUG, - "SocketReaderThread", - "Reader Thread exiting."); - - return 0; -} - -//____________________________________________________________________________ -XrdClientPhyConnection::XrdClientPhyConnection(XrdClientAbsUnsolMsgHandler *h, - XrdClientSid *sid): - fMStreamsGoing(false), fReaderCV(0), fLogConnCnt(0), fSidManager(sid), - fServerProto(0) { - - // Constructor - fServerType = kSTNone; - - // Immediate destruction of this object is always a bad idea - fTTLsec = 30; - - Touch(); - - fServer.Clear(); - - SetLogged(kNo); - - fRequestTimeout = EnvGetLong(NAME_REQUESTTIMEOUT); - - UnsolicitedMsgHandler = h; - - for (int i = 0; i < READERCOUNT; i++) - fReaderthreadhandler[i] = 0; - fReaderthreadrunning = 0; - - fSecProtocol = 0; -} - -//____________________________________________________________________________ -XrdClientPhyConnection::~XrdClientPhyConnection() -{ - // Destructor - Info(XrdClientDebug::kUSERDEBUG, - "XrdClientPhyConnection", - "Destroying. [" << fServer.Host << ":" << fServer.Port << "]"); - - Disconnect(); - for (int i = 0; i < READERCOUNT; i++) - { - if(fReaderthreadhandler[i]) - { - fReaderthreadhandler[i]->Join(); - delete fReaderthreadhandler[i]; - } - } - - - - if (fSocket) { - delete fSocket; - fSocket = 0; - } - - UnlockChannel(); - - - if (fSecProtocol) { - // This insures that the right destructor is called - // (Do not do C++ delete). - fSecProtocol->Delete(); - fSecProtocol = 0; - } -} - -//____________________________________________________________________________ -bool XrdClientPhyConnection::Connect(XrdClientUrlInfo RemoteHost, bool isUnix) -{ - return Connect( RemoteHost, isUnix, -1 ); -} - -bool XrdClientPhyConnection::Connect(XrdClientUrlInfo RemoteHost, bool isUnix, int fd) -{ - // Connect to remote server - XrdSysMutexHelper l(fMutex); - - - if (isUnix) { - Info(XrdClientDebug::kHIDEBUG, "Connect", "Connecting to " << RemoteHost.File); - } else { - Info(XrdClientDebug::kHIDEBUG, - "Connect", "Connecting to [" << RemoteHost.Host << ":" << RemoteHost.Port << "]"); - } - - if (EnvGetLong(NAME_MULTISTREAMCNT)) - fSocket = new XrdClientPSock(RemoteHost); - else - fSocket = new XrdClientSock(RemoteHost, 0, fd ); - - if(!fSocket) { - Error("Connect","Unable to create a client socket. Aborting."); - abort(); - } - - fSocket->TryConnect(isUnix); - - if (!fSocket->IsConnected()) { - if (isUnix) { - Error("Connect", "can't open UNIX connection to " << RemoteHost.File); - } else { - Error("Connect", "can't open connection to [" << - RemoteHost.Host << ":" << RemoteHost.Port << "]"); - } - Disconnect(); - - return FALSE; - } - - Touch(); - - fTTLsec = EnvGetLong(NAME_DATASERVERCONN_TTL); - - if (isUnix) { - Info(XrdClientDebug::kHIDEBUG, "Connect", "Connected to " << RemoteHost.File); - } else { - Info(XrdClientDebug::kHIDEBUG, "Connect", "Connected to [" << - RemoteHost.Host << ":" << RemoteHost.Port << "]"); - } - - fServer = RemoteHost; - - { - XrdSysMutexHelper l(fMutex); - fReaderthreadrunning = 0; - } - - return TRUE; -} - -//____________________________________________________________________________ -void XrdClientPhyConnection::StartReader() { - bool running; - - { - XrdSysMutexHelper l(fMutex); - running = fReaderthreadrunning; - } - // Start reader thread - - // Parametric asynchronous stuff. - // If we are going Sync, then nothing has to be done, - // otherwise the reader thread must be started - if ( !running ) { - - Info(XrdClientDebug::kHIDEBUG, - "StartReader", "Starting reader thread..."); - - int rdcnt = READERCOUNT; - if (fServerType == kSTBaseXrootd) rdcnt = 1; - - for (int i = 0; i < rdcnt; i++) { - - // Now we launch the reader thread - fReaderthreadhandler[i] = new XrdClientThread(SocketReaderThread); - if (!fReaderthreadhandler[i]) { - Error("PhyConnection", - "Can't create reader thread: out of system resources"); -// HELP: what do we do here - exit(-1); - } - - if (fReaderthreadhandler[i]->Run(this)) { - Error("PhyConnection", - "Can't run reader thread: out of system resources. Critical error."); -// HELP: what do we do here - exit(-1); - } - - } - // sleep until at least one thread starts running, which hopefully - // is not forever. - int maxRetries = 10; - while (--maxRetries >= 0) { - { XrdSysMutexHelper l(fMutex); - if (fReaderthreadrunning) - break; - } - fReaderCV.Wait(100); - } - } -} - - -//____________________________________________________________________________ -void XrdClientPhyConnection::StartedReader() { - XrdSysMutexHelper l(fMutex); - fReaderthreadrunning++; - fReaderCV.Post(); -} - -//____________________________________________________________________________ -bool XrdClientPhyConnection::ReConnect(XrdClientUrlInfo RemoteHost) -{ - // Re-connection attempt - - Disconnect(); - return Connect(RemoteHost); -} - -//____________________________________________________________________________ -void XrdClientPhyConnection::Disconnect() -{ - XrdSysMutexHelper l(fMutex); - - // Disconnect from remote server - - if (fSocket) { - Info(XrdClientDebug::kHIDEBUG, - "PhyConnection", "Disconnecting socket..."); - fSocket->Disconnect(); - - } - - // We do not destroy the socket here. The socket will be destroyed - // in CheckAutoTerm or in the ConnMgr -} - -//____________________________________________________________________________ -bool XrdClientPhyConnection::CheckAutoTerm() { - XrdSysMutexHelper l(fMutex); - - // Parametric asynchronous stuff - // If we are going async, we might be willing to term ourself - if ( !IsValid() ) { - - Info(XrdClientDebug::kHIDEBUG, - "CheckAutoTerm", "Self-Cancelling reader thread."); - - fReaderthreadrunning--; - return true; - } - return false; -} - - -//____________________________________________________________________________ -void XrdClientPhyConnection::Touch() -{ - // Set last-use-time to present time - XrdSysMutexHelper l(fMutex); - - time_t t = time(0); - - //Info(XrdClientDebug::kDUMPDEBUG, - // "Touch", - // "Setting last use to current time" << t); - - fLastUseTimestamp = t; -} - -//____________________________________________________________________________ -int XrdClientPhyConnection::ReadRaw(void *buf, int len, int substreamid, - int *usedsubstreamid) { - // Receive 'len' bytes from the connected server and store them in 'buf'. - // Return 0 if OK. - // If substreamid = -1 then - // gets length bytes from any par socket, and returns the usedsubstreamid - // where it got the bytes from - // Otherwise read bytes from the specified substream. 0 is the main one. - - int res; - - - if (IsValid()) { - - Info(XrdClientDebug::kDUMPDEBUG, - "ReadRaw", - "Reading from " << - fServer.Host << ":" << fServer.Port); - - res = fSocket->RecvRaw(buf, len, substreamid, usedsubstreamid); - - if ((res < 0) && (res != TXSOCK_ERR_TIMEOUT) && errno ) { - //strerror_r(errno, errbuf, sizeof(buf)); - - Info(XrdClientDebug::kHIDEBUG, - "ReadRaw", "Read error on " << - fServer.Host << ":" << fServer.Port << ". errno=" << errno ); - } - - // If a socket error comes, then we disconnect - // but we have not to disconnect in the case of a timeout - if (((res < 0) && (res == TXSOCK_ERR)) || - (!fSocket->IsConnected())) { - - Info(XrdClientDebug::kHIDEBUG, - "ReadRaw", - "Disconnection reported on" << - fServer.Host << ":" << fServer.Port); - - Disconnect(); - } - - - // Let's dump the received bytes - if ((res > 0) && (DebugLevel() > XrdClientDebug::kDUMPDEBUG)) { - XrdOucString s = " "; - char b[256]; - - for (int i = 0; i < xrdmin(res, 256); i++) { - sprintf(b, "%.2x ", *((unsigned char *)buf + i)); - s += b; - if (!((i + 1) % 16)) s += "\n "; - } - - Info(XrdClientDebug::kHIDEBUG, - "ReadRaw", "Read " << res << "bytes. Dump:" << endl << s << endl); - - } - - return res; - } - else { - // Socket already destroyed or disconnected - Info(XrdClientDebug::kUSERDEBUG, - "ReadRaw", "Socket is disconnected."); - - return TXSOCK_ERR; - } - -} - -//____________________________________________________________________________ -XrdClientMessage *XrdClientPhyConnection::ReadMessage(int streamid) { - // Gets a full loaded XrdClientMessage from this phyconn. - // May be a pure msg pick from a queue - - Touch(); - return fMsgQ.GetMsg(streamid, fRequestTimeout ); - - } - -//____________________________________________________________________________ -XrdClientMessage *XrdClientPhyConnection::BuildMessage(bool IgnoreTimeouts, bool Enqueue) -{ - // Builds an XrdClientMessage, and makes it read its header/data from the socket - // Also put automatically the msg into the queue - - XrdClientMessage *m; - struct SidInfo *parallelsid = 0; - UnsolRespProcResult res = kUNSOL_KEEP; - - m = new XrdClientMessage(); - if (!m) { - Error("BuildMessage", - "Cannot create a new Message. Aborting."); - abort(); - } - - { -// fMultireadMutex.Lock(); - m->ReadRaw(this); -// fMultireadMutex.UnLock(); - } - - parallelsid = (fSidManager) ? fSidManager->GetSidInfo(m->HeaderSID()) : 0; - - if ( parallelsid || (m->IsAttn()) || (m->GetStatusCode() == XrdClientMessage::kXrdMSC_readerr)) { - - - // Here we insert the PhyConn-level support for unsolicited responses - // Some of them will be propagated in some way to the upper levels - // The path should be - // here -> XrdClientConnMgr -> all the involved XrdClientLogConnections -> - // -> all the corresponding XrdClient - - if (m->GetStatusCode() == XrdClientMessage::kXrdMSC_readerr) { - Info(XrdClientDebug::kDUMPDEBUG, - "BuildMessage"," propagating a communication error message."); - } - else { - Info(XrdClientDebug::kDUMPDEBUG, - "BuildMessage"," propagating unsol id " << m->HeaderSID()); - } - - Touch(); - res = HandleUnsolicited(m); - - - - } - - if (Enqueue && !parallelsid && !m->IsAttn() && (m->GetStatusCode() != XrdClientMessage::kXrdMSC_readerr)) { - // If we have to ignore the socket timeouts, then we have not to - // feed the queue with them. In this case, the newly created XrdClientMessage - // has to be freed. - //if ( !IgnoreTimeouts || !m->IsError() ) - - //bool waserror; - - if (IgnoreTimeouts) { - - if (m->GetStatusCode() != XrdClientMessage::kXrdMSC_timeout) { - //waserror = m->IsError(); - - Info(XrdClientDebug::kDUMPDEBUG, - "BuildMessage"," posting id "<HeaderSID()); - - fMsgQ.PutMsg(m); - - //if (waserror) - // for (int kk=0; kk < 10; kk++) fMsgQ.PutMsg(0); - } - else { - - Info(XrdClientDebug::kDUMPDEBUG, - "BuildMessage"," deleting id "<HeaderSID()); - - delete m; - m = 0; - } - - } else - fMsgQ.PutMsg(m); - } - else { - - - // The purpose of this message ends here - if ( (parallelsid) && (res != kUNSOL_KEEP) && - (m->GetStatusCode() != XrdClientMessage::kXrdMSC_readerr) ) - if (fSidManager && (m->HeaderStatus() != kXR_oksofar)) - fSidManager->ReleaseSid(m->HeaderSID()); - - // if (m->GetStatusCode() != XrdClientMessage::kXrdMSC_readerr) { - delete m; - m = 0; - // } - - } - - return m; -} - -//____________________________________________________________________________ -UnsolRespProcResult XrdClientPhyConnection::HandleUnsolicited(XrdClientMessage *m) -{ - // Local processing of unsolicited responses is done here - - bool ProcessingToGo = TRUE; - struct ServerResponseBody_Attn *attnbody; - - Touch(); - - // Local pre-processing of the unsolicited XrdClientMessage - attnbody = (struct ServerResponseBody_Attn *)m->GetData(); - - if (attnbody && (m->IsAttn())) { - attnbody->actnum = ntohl(attnbody->actnum); - - switch (attnbody->actnum) { - case kXR_asyncms: - // A message arrived from the server. Let's print it. - Info(XrdClientDebug::kNODEBUG, - "HandleUnsolicited", - "Message from " << - fServer.Host << ":" << fServer.Port << ". '" << - attnbody->parms << "'"); - - ProcessingToGo = FALSE; - break; - - case kXR_asyncab: - // The server requested to abort the execution!!!! - Info(XrdClientDebug::kNODEBUG, - "HandleUnsolicited", - "******* Abort request received ******* Server: " << - fServer.Host << ":" << fServer.Port << ". Msg: '" << - attnbody->parms << "'"); - - exit(255); - - ProcessingToGo = FALSE; - break; - } - } - - // Now we propagate the message to the interested object, if any - // It could be some sort of upper layer of the architecture - if (ProcessingToGo) { - UnsolRespProcResult retval; - - retval = SendUnsolicitedMsg(this, m); - - // Request post-processing - if (attnbody && (m->IsAttn())) { - switch (attnbody->actnum) { - - case kXR_asyncrd: - // After having set all the belonging object, we disconnect. - // The next commands will redirect-on-error where we want - - Disconnect(); - break; - - case kXR_asyncdi: - // After having set all the belonging object, we disconnect. - // The next connection attempt will behave as requested, - // i.e. waiting some time before reconnecting - - Disconnect(); - break; - - } // switch - } - return retval; - - } - else - return kUNSOL_CONTINUE; -} - -//____________________________________________________________________________ -int XrdClientPhyConnection::WriteRaw(const void *buf, int len, int substreamid) { - // Send 'len' bytes located at 'buf' to the connected server. - // Return number of bytes sent. - // usesubstreams tells if we have to select a substream to send the data through or - // the main stream is to be used - // substreamid == 0 means to use the main stream - - int res; - - Touch(); - - if (IsValid()) { - - Info(XrdClientDebug::kDUMPDEBUG, - "WriteRaw", - "Writing to substreamid " << - substreamid); - - res = fSocket->SendRaw(buf, len, substreamid); - - if ((res < 0) && (res != TXSOCK_ERR_TIMEOUT) && errno) { - //strerror_r(errno, errbuf, sizeof(buf)); - - Info(XrdClientDebug::kHIDEBUG, - "WriteRaw", "Write error on " << - fServer.Host << ":" << fServer.Port << ". errno=" << errno ); - - } - - // If a socket error comes, then we disconnect (and destroy the fSocket) - if ((res < 0) || (!fSocket) || (!fSocket->IsConnected())) { - - Info(XrdClientDebug::kHIDEBUG, - "WriteRaw", - "Disconnection reported on" << - fServer.Host << ":" << fServer.Port); - - Disconnect(); - } - - Touch(); - return( res ); - } - else { - // Socket already destroyed or disconnected - Info(XrdClientDebug::kUSERDEBUG, - "WriteRaw", - "Socket is disconnected."); - return TXSOCK_ERR; - } -} - - -//____________________________________________________________________________ -bool XrdClientPhyConnection::ExpiredTTL() -{ - // Check expiration time - return( (time(0) - fLastUseTimestamp) > fTTLsec ); -} - -//____________________________________________________________________________ -void XrdClientPhyConnection::LockChannel() -{ - // Lock - fRwMutex.Lock(); -} - -//____________________________________________________________________________ -void XrdClientPhyConnection::UnlockChannel() -{ - // Unlock - fRwMutex.UnLock(); -} - -//_____________________________________________________________________________ -ERemoteServerType XrdClientPhyConnection::DoHandShake(ServerInitHandShake &xbody, - int substreamid) -{ - // Performs initial hand-shake with the server in order to understand which - // kind of server is there at the other side and to make the server know who - // we are. Note that if the substreamid is negative, this is a handshake for - // a parallel stream and we can do a short handshake. - struct ClientInitHandShake initHS; - ServerResponseType type; - ERemoteServerType typeres = kSTNone; - int isPS = (substreamid < 0); - - int writeres, readres, len = 0; - - // Set field in network byte order - memset(&initHS, 0, sizeof(initHS)); - initHS.fourth = (kXR_int32)htonl(4); - initHS.fifth = (kXR_int32)htonl(2012); - - //--------------------------------------------------------------------------- - // Create protocol request - //--------------------------------------------------------------------------- - ClientRequest req; - memset( &req, 0, sizeof( req ) ); - req.header.requestid = kXR_protocol; - req.protocol.clientpv = kXR_PROTOCOLVERSION; - - //--------------------------------------------------------------------------- - // Send the handshake and the kXR_protocol request - //--------------------------------------------------------------------------- - - if( DebugLevel() >= XrdClientDebug::kDUMPDEBUG ) - smartPrintClientHeader( &req ); - // For parallel streams we only send the handshake. For normal streams we - // piggy-back a protocol request (i.e., extended handshake). This is for - // historical reasons to keep backward compatability. - if (isPS) - {Info( XrdClientDebug::kHIDEBUG, "DoHandShake", - "HandShake step 1: Sending handshake for a parallel stream" ); - writeres = WriteRaw( &initHS, sizeof(initHS), substreamid ); - } else { - Info( XrdClientDebug::kHIDEBUG, "DoHandShake", - "HandShake step 1: Sending handshake with a piggy-backed protocol request" ); - clientMarshall( &req ); - len = sizeof( req ) + sizeof( initHS ); - char buffer[sizeof(req)+sizeof(initHS)]; - memcpy( buffer, &initHS, sizeof( initHS ) ); - memcpy( buffer+sizeof( initHS ), &req, sizeof( req ) ); - writeres = WriteRaw( buffer, len, substreamid ); - } - - if( writeres < 0 ) - { - Info( XrdClientDebug::kNODEBUG,"DoHandShake", "Failed to send " << len << - " bytes of protocol info request. Retrying ..."); - return kSTError; - } - - // Read from server the first 4 bytes - len = sizeof(type); - - Info(XrdClientDebug::kHIDEBUG, - "DoHandShake", - "HandShake step 2: Reading " << len << - " bytes."); - - // - // Read returns the return value of TSocket->RecvRaw... that returns the - // return value of recv (unix low level syscall) - // - readres = ReadRaw(&type, - len, substreamid); // Reads 4(2+2) bytes - - if (readres < 0) { - Info(XrdClientDebug::kNODEBUG, "DoHandShake", "Failed to read " << len << - " bytes. Retrying ..."); - - return kSTError; - } - - // to host byte order - type = ntohl(type); - - // Check if the server is the eXtended rootd or not, checking the value - // of type - if (type == 0) { // ok, eXtended! - - len = sizeof(xbody); - - Info(XrdClientDebug::kHIDEBUG, - "DoHandShake", - "HandShake step 3: Reading " << len << - " bytes."); - - readres = ReadRaw(&xbody, len, substreamid); // Read 12(4+4+4) bytes - - if (readres < 0) { - Error("DoHandShake", "Error reading " << len << - " bytes."); - - return kSTError; - } - - ServerInitHandShake2HostFmt(&xbody); - - Info(XrdClientDebug::kHIDEBUG, - "DoHandShake", - "Server protocol: " << xbody.protover << " type: " << xbody.msgval); - - // For parallel streams we never sent a protocol request. Otherwise, we - // need to get the protocol request response. Ideally, we would continue - // doing this using the unlocked ReadRaw() as the handshake is technically - // atomic. The added code went through the message queue making it not - // atomic which made it impossible to setup a parallel stream. the ideal - // fix would cause too much code to change, so we do a quick and dirty. - // - if (isPS) - {typeres = kSTDataXrootd; - if (xbody.msgval & kXR_DataServer) fServerType = kSTDataXrootd; - else fServerType = kSTBaseXrootd; - fServerProto = xbody.protover; - return fServerType; - } - - //------------------------------------------------------------------------ - // Get the response to the protocol message - //------------------------------------------------------------------------ - XrdClientMessage *msg = new XrdClientMessage(); - msg->ReadRaw( this ); - - if( DebugLevel() >= XrdClientDebug::kDUMPDEBUG ) - smartPrintServerHeader( &msg->fHdr ); - - //------------------------------------------------------------------------ - // Got correct response from the server - //------------------------------------------------------------------------ - if( !msg->IsError() && msg->HeaderStatus() == kXR_ok ) - { - ServerResponseBody_Protocol *resp = (ServerResponseBody_Protocol*)msg->GetData(); - resp->pval = ntohl( resp->pval ); - resp->flags = ntohl( resp->flags ); - Info( XrdClientDebug::kHIDEBUG, "DoHandShake", - "Server protocol (kXR_protocol): " << resp->pval << " flags: " << resp->flags ); - - //---------------------------------------------------------------------- - // Get the server type - //---------------------------------------------------------------------- - if( resp->pval >= 0x297 ) - { - if( resp->flags & kXR_isManager ) - { - if( resp->flags & kXR_attrMeta ) typeres = kSTMetaXrootd; - else typeres = kSTBaseXrootd; - } - else if( resp->flags & kXR_isServer ) - typeres = kSTDataXrootd; - - fServerType = typeres; - fServerProto = resp->pval; - delete msg; - return typeres; - } - } - //------------------------------------------------------------------------ - // Protocol not supported - //------------------------------------------------------------------------ - else - { - Info( XrdClientDebug::kHIDEBUG, "DoHandShake", - "No valid response to the protocol request" ); - } - delete msg; - msg = 0; - - // check if the eXtended rootd is a data server - switch (xbody.msgval) { - - case kXR_DataServer: - // This is a data server - typeres = kSTDataXrootd; - break; - - case kXR_LBalServer: - typeres = kSTBaseXrootd; - break; - } - - } else { - - // We are here if it wasn't an XRootd - // and we need to complete the reading - if (type == 8) - typeres = kSTRootd; - else - // We dunno the server type - typeres = kSTNone; - } - - fServerType = typeres; - fServerProto = xbody.protover; - return typeres; -} - -//____________________________________________________________________________ -void XrdClientPhyConnection::CountLogConn(int d) -{ - // Modify countre of logical connections using this phyconn - fMutex.Lock(); - fLogConnCnt += d; - fMutex.UnLock(); -} - - -bool XrdClientPhyConnection::TestAndSetMStreamsGoing() { - XrdSysMutexHelper mtx(fMutex); - bool retval = fMStreamsGoing; - fMStreamsGoing = true; - return retval; -} - -bool XrdClientPhyConnection::IsValid() { - XrdSysMutexHelper l(fMutex); - return ( (fSocket != 0) && fSocket->IsConnected()); -} - -ELoginState XrdClientPhyConnection::IsLogged() { - const XrdSysMutexHelper l(fMutex); - return fLogged; -} diff --git a/src/XrdClient/XrdClientPhyConnection.hh b/src/XrdClient/XrdClientPhyConnection.hh deleted file mode 100644 index 9419a1d9fcf..00000000000 --- a/src/XrdClient/XrdClientPhyConnection.hh +++ /dev/null @@ -1,226 +0,0 @@ -#ifndef _XrdClientPhyConnection -#define _XrdClientPhyConnection -/******************************************************************************/ -/* */ -/* X r d C l i e n t P h y C o n n e c t i o n . h h */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2004) */ -/* Adapted from TXNetFile (root.cern.ch) originally done by */ -/* Alvise Dorigo, Fabrizio Furano */ -/* INFN Padova, 2003 */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// Class handling physical connections to xrootd servers // -// // -////////////////////////////////////////////////////////////////////////// - - -#include "XrdClient/XrdClientSock.hh" -#include "XrdClient/XrdClientMessage.hh" -#include "XrdClient/XrdClientUnsolMsg.hh" -#include "XrdClient/XrdClientInputBuffer.hh" -#include "XrdClient/XrdClientUrlInfo.hh" -#include "XrdSys/XrdSysPthread.hh" -#include "XrdSys/XrdSysSemWait.hh" - -#include // for time_t data type - -enum ELoginState { - kNo = 0, - kYes = 1, - kPending = 2 -}; - -enum ERemoteServerType { - kSTError = -1, // Some error occurred: server type undetermined - kSTNone = 0, // Remote server type un-recognized - kSTRootd = 1, // Remote server type: old rootd server - kSTBaseXrootd = 2, // Remote server type: xrootd dynamic load balancer - kSTDataXrootd = 3, // Remote server type: xrootd data server - kSTMetaXrootd = 4 // Remote server type: xrootd meta manager -}; - -class XrdClientSid; -class XrdClientThread; -class XrdSecProtocol; - -class XrdClientPhyConnection: public XrdClientUnsolMsgSender { - -private: - time_t fLastUseTimestamp; - enum ELoginState fLogged; // only 1 login/auth is needed for physical - XrdSecProtocol *fSecProtocol; // authentication protocol - - XrdClientInputBuffer - fMsgQ; // The queue used to hold incoming messages - - int fRequestTimeout; - bool fMStreamsGoing; - XrdSysRecMutex fRwMutex; // Lock before using the physical channel - // (for reading and/or writing) - - XrdSysRecMutex fMutex; - XrdSysRecMutex fMultireadMutex; // Used to arbitrate between multiple - // threads reading msgs from the same conn - - XrdClientThread *fReaderthreadhandler[64]; // The thread which is going to pump - // out the data from the socket - - int fReaderthreadrunning; - - XrdClientUrlInfo fServer; - - XrdClientSock *fSocket; - - UnsolRespProcResult HandleUnsolicited(XrdClientMessage *m); - - XrdSysSemWait fReaderCV; - - short fLogConnCnt; // Number of logical connections using this phyconn - - XrdClientSid *fSidManager; - -public: - long fServerProto; // The server protocol - ERemoteServerType fServerType; - long fTTLsec; - - XrdClientPhyConnection(XrdClientAbsUnsolMsgHandler *h, XrdClientSid *sid); - ~XrdClientPhyConnection(); - - XrdClientMessage *BuildMessage(bool IgnoreTimeouts, bool Enqueue); - bool CheckAutoTerm(); - - bool Connect(XrdClientUrlInfo RemoteHost, bool isUnix = 0); - - //-------------------------------------------------------------------------- - //! Connect to a remote location - //! - //! @param RemoteHost address descriptor - //! @param isUnix true if the address points to a Unix socket - //! @param fd a descriptor pointing to a connected socket - //! if the subroutine is supposed to reuse an existing - //! connection, -1 otherwise - //-------------------------------------------------------------------------- - bool Connect( XrdClientUrlInfo RemoteHost, bool isUnix , int fd ); - - void CountLogConn(int d = 1); - void Disconnect(); - - ERemoteServerType - DoHandShake(ServerInitHandShake &xbody, - int substreamid = 0); - - bool ExpiredTTL(); - short GetLogConnCnt() const { return fLogConnCnt; } - int GetReaderThreadsCnt() { XrdSysMutexHelper l(fMutex); return fReaderthreadrunning; } - - long GetTTL() { return fTTLsec; } - - XrdSecProtocol *GetSecProtocol() const { return fSecProtocol; } - int GetSocket() { return fSocket ? fSocket->fSocket : -1; } - - // Tells to the sock to rebuild the list of interesting selectors - void ReinitFDTable() { if (fSocket) fSocket->ReinitFDTable(); } - - int SaveSocket() { fTTLsec = 0; return fSocket ? (fSocket->SaveSocket()) : -1; } - void SetInterrupt() { if (fSocket) fSocket->SetInterrupt(); } - void SetSecProtocol(XrdSecProtocol *sp) { fSecProtocol = sp; } - - void StartedReader(); - - bool IsAddress(const XrdOucString &addr) { - return ( (fServer.Host == addr) || - (fServer.HostAddr == addr) ); - } - - ELoginState IsLogged(); - - bool IsPort(int port) { return (fServer.Port == port); }; - bool IsUser(const XrdOucString &usr) { return (fServer.User == usr); }; - bool IsValid(); - - - void LockChannel(); - - // see XrdClientSock for the meaning of the parameters - int ReadRaw(void *buffer, int BufferLength, int substreamid = -1, - int *usedsubstreamid = 0); - - XrdClientMessage *ReadMessage(int streamid); - bool ReConnect(XrdClientUrlInfo RemoteHost); - void SetLogged(ELoginState status) { fLogged = status; } - inline void SetTTL(long ttl) { fTTLsec = ttl; } - void StartReader(); - void Touch(); - void UnlockChannel(); - int WriteRaw(const void *buffer, int BufferLength, int substreamid = 0); - - int TryConnectParallelStream(int port, int windowsz, int sockid) { return ( fSocket ? fSocket->TryConnectParallelSock(port, windowsz, sockid) : -1); } - int EstablishPendingParallelStream(int tmpid, int newid) { return ( fSocket ? fSocket->EstablishParallelSock(tmpid, newid) : -1); } - void RemoveParallelStream(int substreamid) { if (fSocket) fSocket->RemoveParallelSock(substreamid); } - // Tells if the attempt to establish the parallel streams is ongoing or was done - // and mark it as ongoing or done - bool TestAndSetMStreamsGoing(); - - int GetSockIdHint(int reqsperstream) { return ( fSocket ? fSocket->GetSockIdHint(reqsperstream) : 0); } - int GetSockIdCount() {return ( fSocket ? fSocket->GetSockIdCount() : 0); } - void PauseSelectOnSubstream(int substreamid) { if (fSocket) fSocket->PauseSelectOnSubstream(substreamid); } - void RestartSelectOnSubstream(int substreamid) { if (fSocket) fSocket->RestartSelectOnSubstream(substreamid); } - - // To prohibit/re-enable a socket descriptor from being looked at by the reader threads - virtual void BanSockDescr(int sockdescr, int sockid) { if (fSocket) fSocket->BanSockDescr(sockdescr, sockid); } - virtual void UnBanSockDescr(int sockdescr) { if (fSocket) fSocket->UnBanSockDescr(sockdescr); } - - void ReadLock() { fMultireadMutex.Lock(); } - void ReadUnLock() { fMultireadMutex.UnLock(); } - - int WipeStreamid(int streamid) { return fMsgQ.WipeStreamid(streamid); } -}; - - - - -// -// Class implementing a trick to automatically unlock an XrdClientPhyConnection -// -class XrdClientPhyConnLocker { -private: - XrdClientPhyConnection *phyconn; - -public: - XrdClientPhyConnLocker(XrdClientPhyConnection *phyc) { - // Constructor - phyconn = phyc; - phyconn->LockChannel(); - } - - ~XrdClientPhyConnLocker(){ - // Destructor. - phyconn->UnlockChannel(); - } - -}; -#endif diff --git a/src/XrdClient/XrdClientPrep.cc b/src/XrdClient/XrdClientPrep.cc deleted file mode 100644 index e85d89b520e..00000000000 --- a/src/XrdClient/XrdClientPrep.cc +++ /dev/null @@ -1,207 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C l i e n t P r e p . c c */ -/* */ -/* (c) 2012 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include - -#include "XProtocol/XProtocol.hh" -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdClient/XrdClientAdmin.hh" -#include "XrdClient/XrdClientConst.hh" -#include "XrdClient/XrdClientEnv.hh" - -/******************************************************************************/ -/* m a i n */ -/******************************************************************************/ - -int main(int argc, char **argv) -{ - extern char *optarg; - extern int optind, opterr; - extern void Fatal(const char *, XrdClientAdmin *); - extern void Usage(int); - static const int MaxPathLen = MAXPATHLEN+1; - XrdClientAdmin *Admin; - FILE *Stream = 0; - char c, Target[512], buff[16384], *bp, *sp, *theBuff = buff; - char *inFile = 0; - kXR_char Prty = 0, Opts = 0; - long Debug = 0; - int setDebug = 0, didPrep = 0, theBsz = sizeof(buff)-2, bsz, slen, rc; - -// Process the options -// - opterr = 0; - if (argc > 1 && '-' == *argv[1]) - while ((c = getopt(argc,argv,"d:f:p:sStw")) && ((unsigned char)c != 0xff)) - { switch(c) - { - case 'd': Debug = atol(optarg); setDebug = 1; - break; - case 'f': inFile = optarg; - break; - case 'p': Prty = kXR_char(atoi(optarg)); - break; - case 's': Opts |= kXR_stage; - break; - case 'S': Opts |=(kXR_stage|kXR_coloc); - break; - case 't': Opts |= kXR_fresh; - break; - case 'w': Opts |= kXR_wmode; - break; - default: cerr <<"xprep: Invalid option '-" <= argc || !isalnum(*argv[optind])) - {cerr <<"xprep: target host name not specified" <Connect()) Fatal("Connect", Admin); - -// If an infile was specified, make sure we can open it -// - if (inFile && !(Stream = fopen(inFile, "r"))) - {cerr <<"xprep: " <Prepare(buff+1, Opts, Prty)) Fatal("Prepare", Admin); - didPrep = 1; - } while(optind < argc); - -// If colocating, make sure we have the anchor file -// - if (Opts & kXR_coloc && theBuff == buff) - {if (!Stream || !(sp = fgets(buff+1, MaxPathLen, Stream))) inFile = 0; - else {slen = strlen(sp); theBsz -= (slen+1); theBuff += slen+1;} - } else theBuff++; - -// Process the file -// - if (inFile) - {do {bp = theBuff; bsz = theBsz; - while(bsz >= MaxPathLen) - {if (!(sp = fgets(bp, MaxPathLen, Stream))) break; - {slen = strlen(sp); bsz -= slen; bp += slen;} - } - if (bp == theBuff) break; - if (!Admin->Prepare(buff+1, Opts, Prty)) Fatal("Prepare", Admin); - didPrep = 1; - } while(!feof(Stream) && !ferror(Stream)); - if ((rc = ferror(Stream))) - {cerr <<"xprep: Error " < buff+1) - {*theBuff = '\0'; - if (!Admin->Prepare(buff+1, Opts, Prty)) Fatal("Prepare", Admin); - } else {cerr <<"xprep: No files to prepare were specified" <LastServerError()->errmsg; - -// Print a message and exit -// - if (etext && *etext) cerr <<"xprep: " <. */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// utility functions to deal with the protocol // -// // -////////////////////////////////////////////////////////////////////////// - -#include "XProtocol/XProtocol.hh" -#include "XrdSys/XrdSysPlatform.hh" -#include -#ifndef WIN32 -#include -#include // needed to use htonl/htons byte swap functions -#endif -#include // proto for memcpy (wanted by Solaris compiler) -#include - -#define _htonll(x) htonll(x) - -// //____________________________________________________________________________ -// kXR_int64 _htonll(kXR_int64 n) -// { -// // custom client routine to convert long long (64 bit integers) from -// // host to network byte order -// return (kXR_int64)host2net(n); -// } - -//___________________________________________________________________________ -void clientMarshall(ClientRequest* str) -{ - // This function applies the network byte order on those - // parts of the 16-bytes buffer, only if it is composed - // by some binary part - - kXR_int64 tmpl; - - switch(str->header.requestid) { - case kXR_auth: - // no swap on ASCII fields - break; - case kXR_chmod: - str->chmod.mode = htons(str->chmod.mode); - break; - case kXR_close: - // no swap on ASCII fields - break; - case kXR_dirlist: - // no swap on ASCII fields - break; - case kXR_getfile: - str->getfile.options = htonl(str->getfile.options); - str->getfile.buffsz = htonl(str->getfile.buffsz); - break; - case kXR_locate: - str->locate.options = htons(str->getfile.options); - break; - case kXR_login: - str->login.pid = htonl(str->login.pid); - break; - case kXR_mkdir: - // no swap on ASCII fields - str->mkdir.mode = htons(str->mkdir.mode); - break; - case kXR_mv: - // no swap on ASCII fields - break; - case kXR_open: - str->open.mode = htons(str->open.mode); - str->open.options = htons(str->open.options); - break; - case kXR_ping: - // no swap on ASCII fields - break; - case kXR_protocol: - str->protocol.clientpv = htons( str->protocol.clientpv ); - break; - case kXR_putfile: - str->putfile.options = htonl(str->putfile.options); - str->putfile.buffsz = htonl(str->putfile.buffsz); - break; - case kXR_query: - str->query.infotype = htons(str->query.infotype); - break; - case kXR_read: - memcpy(&tmpl, &str->read.offset, sizeof(kXR_int64) ); - tmpl = _htonll(tmpl); - memcpy(&str->read.offset, &tmpl, sizeof(kXR_int64) ); - str->read.rlen = htonl(str->read.rlen); - break; - case kXR_readv: - // no swap on ASCII fields - // and the swap of the list is done in - // clientMarshallReadAheadList - break; - case kXR_rm: - // no swap on ASCII fields - break; - case kXR_rmdir: - // no swap on ASCII fields - break; - case kXR_set: - // no swap on ASCII fields - break; - case kXR_stat: - // no swap on ASCII fields - break; - case kXR_sync: - // no swap on ASCII fields - break; - case kXR_write: - memcpy(&tmpl, &str->write.offset, sizeof(kXR_int64) ); - tmpl = _htonll(tmpl); - memcpy(&str->write.offset, &tmpl, sizeof(kXR_int64) ); - break; - case kXR_truncate: - memcpy(&tmpl, &str->truncate.offset, sizeof(kXR_int64) ); - tmpl = _htonll(tmpl); - memcpy(&str->truncate.offset, &tmpl, sizeof(kXR_int64) ); - break; - } - - str->header.requestid = htons(str->header.requestid); - str->header.dlen = htonl(str->header.dlen); -} - -//___________________________________________________________________________ -void clientMarshallReadAheadList(readahead_list *buf_list, kXR_int32 dlen) -{ - // This function applies the network byte order on the - // vector of read-ahead information - kXR_int64 tmpl; - - int n = dlen / (sizeof(struct readahead_list)); - for( int i = 0; i < n; i++ ) { - memcpy(&tmpl, &(buf_list[i].offset), sizeof(kXR_int64) ); - tmpl = htonll(tmpl); - memcpy(&(buf_list[i].offset), &tmpl, sizeof(kXR_int64) ); - buf_list[i].rlen = htonl(buf_list[i].rlen); - } -} -//___________________________________________________________________________ -void clientUnMarshallReadAheadList(readahead_list *buf_list, kXR_int32 dlen) -{ - // This function applies the network byte order on the - // vector of read-ahead information - kXR_int64 tmpl; - - int n = dlen / (sizeof(struct readahead_list)); - for( int i = 0; i < n; i++ ) { - memcpy(&tmpl, &(buf_list[i].offset), sizeof(kXR_int64) ); - tmpl = ntohll(tmpl); - memcpy(&(buf_list[i].offset), &tmpl, sizeof(kXR_int64) ); - buf_list[i].rlen = ntohl(buf_list[i].rlen); - } -} - -//_________________________________________________________________________ -void clientUnmarshall(struct ServerResponseHeader* str) -{ - str->status = ntohs(str->status); - str->dlen = ntohl(str->dlen); -} - -//_________________________________________________________________________ -void ServerResponseHeader2NetFmt(struct ServerResponseHeader *srh) -{ - srh->status = htons(srh->status); - srh->dlen = htonl(srh->dlen); -} - -//_________________________________________________________________________ -void ServerInitHandShake2HostFmt(struct ServerInitHandShake *srh) -{ - srh->msglen = ntohl(srh->msglen); - srh->protover = ntohl(srh->protover); - srh->msgval = ntohl(srh->msgval); -} - -//_________________________________________________________________________ -bool isRedir(struct ServerResponseHeader *ServerResponse) -{ - // Recognizes if the response contains a redirection - - return ( (ServerResponse->status == kXR_redirect) ? true : false); -} - -//_________________________________________________________________________ -char *convertRequestIdToChar(kXR_unt16 requestid) -{ - // This procedure convert the request code id (an integer defined in - // XProtocol.hhh) in the ascii label (human readable) - - switch(requestid) { - case kXR_auth: - return (char *)"kXR_auth"; - break; - case kXR_chmod: - return (char *)"kXR_chmod"; - break; - case kXR_close: - return (char *)"kXR_close"; - break; - case kXR_dirlist: - return (char *)"kXR_dirlist"; - break; - case kXR_getfile: - return (char *)"kXR_getfile"; - break; - case kXR_locate: - return (char *)"kXR_locate"; - break; - case kXR_login: - return (char *)"kXR_login"; - break; - case kXR_mkdir: - return (char *)"kXR_mkdir"; - break; - case kXR_mv: - return (char *)"kXR_mv"; - break; - case kXR_open: - return (char *)"kXR_open"; - break; - case kXR_ping: - return (char *)"kXR_ping"; - break; - case kXR_protocol: - return (char *)"kXR_protocol"; - break; - case kXR_putfile: - return (char *)"kXR_putfile"; - break; - case kXR_query: - return (char *)"kXR_query"; - break; - case kXR_read: - return (char *)"kXR_read"; - break; - case kXR_readv: - return (char *)"kXR_readv"; - break; - case kXR_rm: - return (char *)"kXR_rm"; - break; - case kXR_rmdir: - return (char *)"kXR_rmdir"; - break; - case kXR_set: - return (char *)"kXR_set"; - break; - case kXR_stat: - return (char *)"kXR_stat"; - break; - case kXR_sync: - return (char *)"kXR_sync"; - break; - case kXR_write: - return (char *)"kXR_write"; - break; - case kXR_prepare: - return (char *)"kXR_prepare"; - break; - case kXR_admin: - return (char *)"kXR_admin"; - break; - case kXR_statx: - return (char *)"kXR_statx"; - break; - case kXR_endsess: - return (char *)"kXR_endsess"; - break; - case kXR_bind: - return (char *)"kXR_bind"; - break; - case kXR_truncate: - return (char *)"kXR_truncate"; - break; - default: - return (char *)"kXR_UNKNOWN"; - break; - } - - return (char *)"kXR_UNKNOWN"; -} - -//___________________________________________________________________________ -void PutFilehandleInRequest(ClientRequest* str, char *fHandle) -{ - // this function inserts a filehandle in a generic request header - // already composed - - switch(str->header.requestid) { - case kXR_close: - memcpy( str->close.fhandle, fHandle, sizeof(str->close.fhandle) ); - break; - case kXR_read: - memcpy( str->read.fhandle, fHandle, sizeof(str->read.fhandle) ); - break; - case kXR_sync: - memcpy( str->sync.fhandle, fHandle, sizeof(str->sync.fhandle) ); - break; - case kXR_write: - memcpy( str->write.fhandle, fHandle, sizeof(str->write.fhandle) ); - break; - } -} - -//___________________________________________________________________________ -char *convertRespStatusToChar(kXR_unt16 status) -{ - switch( status) { - case kXR_ok: - return (char *)"kXR_ok"; - break; - case kXR_oksofar: - return (char *)"kXR_oksofar"; - break; - case kXR_attn: - return (char *)"kXR_attn"; - break; - case kXR_authmore: - return (char *)"kXR_authmore"; - break; - case kXR_error: - return (char *)"kXR_error"; - break; - case kXR_redirect: - return (char *)"kXR_redirect"; - break; - case kXR_wait: - return (char *)"kXR_wait"; - break; - case kXR_waitresp: - return (char *)"kXR_waitresp"; - break; - default: - return (char *)"kXR_UNKNOWN"; - break; - } -} - - -//___________________________________________________________________________ -void smartPrintClientHeader(ClientRequest* hdr) -{ - kXR_int64 tmpl; - - fprintf(stderr, "\n\n================= DUMPING CLIENT REQUEST HEADER =================\n"); - - fprintf(stderr, "%40s0x%.2x 0x%.2x\n", "ClientHeader.streamid = ", - hdr->header.streamid[0], - hdr->header.streamid[1]); - - fprintf(stderr, "%40s%s (%d)\n", - "ClientHeader.requestid = ", - convertRequestIdToChar(hdr->header.requestid), hdr->header.requestid); - - switch(hdr->header.requestid) { - case kXR_admin: - fprintf(stderr, "%40s0 repeated %d times\n", - "ClientHeader.admin.reserved = ", - (kXR_int32)sizeof(hdr->admin.reserved)); - break; - - case kXR_auth: - fprintf(stderr, "%40s0 repeated %d times\n", - "ClientHeader.auth.reserved = ", - (kXR_int32)sizeof(hdr->auth.reserved)); - - fprintf(stderr, " ClientHeader.auth.credtype= 0x%.2x 0x%.2x 0x%.2x 0x%.2x \n", - hdr->auth.credtype[0], - hdr->auth.credtype[1], - hdr->auth.credtype[2], - hdr->auth.credtype[3]); - break; - - case kXR_chmod: - fprintf(stderr, "%40s0 repeated %d times\n", - "ClientHeader.chmod.reserved = ", - (kXR_int32)sizeof(hdr->chmod.reserved)); - - fprintf(stderr, " ClientHeader.chmod.mode= 0x%.2x 0x%.2x \n", - *((kXR_char *)&hdr->chmod.mode), - *(((kXR_char *)&hdr->chmod.mode)+1) - ); - break; - - case kXR_close: - fprintf(stderr, "%40s0x%.2x 0x%.2x 0x%.2x 0x%.2x \n", - "ClientHeader.close.fhandle = ", - hdr->close.fhandle[0], - hdr->close.fhandle[1], - hdr->close.fhandle[2], - hdr->close.fhandle[3]); - - fprintf(stderr, "%40s0 repeated %d times\n", - "ClientHeader.close.reserved = ", - (kXR_int32)sizeof(hdr->close.reserved)); - break; - - case kXR_dirlist: - fprintf(stderr, "%40s0 repeated %d times\n", - "ClientHeader.dirlist.reserved = ", - (kXR_int32)sizeof(hdr->dirlist.reserved)); - break; - case kXR_locate: - fprintf(stderr, " ClientHeader.locate.options= 0x%.2x 0x%.2x \n", - *((kXR_char *)&hdr->locate.options), - *(((kXR_char *)&hdr->locate.options)+1) - ); - - fprintf(stderr, "%40s0 repeated %d times\n", - "ClientHeader.locate.reserved = ", - (kXR_int32)sizeof(hdr->locate.reserved)); - break; - case kXR_login: - fprintf(stderr, "%40s%d \n", - "ClientHeader.login.pid = ", - hdr->login.pid); - - fprintf(stderr, "%40s%s\n", - "ClientHeader.login_body.username = ", - hdr->login.username); - - fprintf(stderr, "%40s0 repeated %d times\n", - "ClientHeader.login.reserved = ", - (kXR_int32)sizeof(hdr->login.reserved)); - - fprintf(stderr, "%40s%d\n", - "ClientHeader.login.capver = ", - hdr->login.capver[0]); - - fprintf(stderr, "%40s%d\n", - "ClientHeader.login.role = ", - hdr->login.role[0]); - break; - - case kXR_mkdir: - fprintf(stderr, "%40s0 repeated %d times\n", - "ClientHeader.mkdir.reserved = ", - (kXR_int32)sizeof(hdr->mkdir.reserved)); - - fprintf(stderr, "%40s0x%.2x 0x%.2x\n", - "ClientHeader.mkdir.mode = ", - *((kXR_char*)&hdr->mkdir.mode), - *(((kXR_char*)&hdr->mkdir.mode)+1) - ); - break; - - case kXR_mv: - fprintf(stderr, "%40s0 repeated %d times\n", - "ClientHeader.mv.reserved = ", - (kXR_int32)sizeof(hdr->mv.reserved)); - break; - - case kXR_open: - fprintf(stderr, "%40s0x%.2x 0x%.2x\n", - "ClientHeader.open.mode = ", - *((kXR_char*)&hdr->open.mode), - *(((kXR_char*)&hdr->open.mode)+1) - ); - - fprintf(stderr, "%40s0x%.2x 0x%.2x\n", - "ClientHeader.open.options = ", - *((kXR_char*)&hdr->open.options), - *(((kXR_char*)&hdr->open.options)+1)); - - fprintf(stderr, "%40s0 repeated %d times\n", - "ClientHeader.open.reserved = ", - (kXR_int32)sizeof(hdr->open.reserved)); - break; - - case kXR_ping: - fprintf(stderr, "%40s0 repeated %d times\n", - "ClientHeader.ping.reserved = ", - (kXR_int32)sizeof(hdr->ping.reserved)); - break; - - case kXR_protocol: - fprintf(stderr, "%40s0x%.2x\n", - "ClientHeader.protocol.clientpv = ", - hdr->protocol.clientpv ); - - fprintf(stderr, "%40s0 repeated %d times\n", - "ClientHeader.protocol.reserved = ", - (kXR_int32)sizeof(hdr->protocol.reserved)); - break; - - case kXR_prepare: - fprintf(stderr, "%40s0x%.2x\n", - "ClientHeader.prepare.options = ", - hdr->prepare.options); - fprintf(stderr, "%40s0x%.2x\n", - "ClientHeader.prepare.prty = ", - hdr->prepare.prty); - fprintf(stderr, "%40s0 repeated %d times\n", - "ClientHeader.prepare.reserved = ", - (kXR_int32)sizeof(hdr->prepare.reserved)); - break; - - case kXR_read: - fprintf(stderr, "%40s0x%.2x 0x%.2x 0x%.2x 0x%.2x \n", - "ClientHeader.read.fhandle = ", - hdr->read.fhandle[0], - hdr->read.fhandle[1], - hdr->read.fhandle[2], - hdr->read.fhandle[3]); - - memcpy(&tmpl, &hdr->read.offset, sizeof(kXR_int64) ); - - fprintf(stderr, "%40s%lld\n", - "ClientHeader.read.offset = ", - tmpl); - - fprintf(stderr, "%40s%d\n", - "ClientHeader.read.rlen = ", - hdr->read.rlen); - break; - - case kXR_readv: - fprintf(stderr, "%40s0 repeated %d times\n", - "ClientHeader.readv.reserved = ", - (kXR_int32)sizeof(hdr->readv.reserved)); - - break; - - case kXR_rm: - fprintf(stderr, "%40s0 repeated %d times\n", - "ClientHeader.rm.reserved = ", - (kXR_int32)sizeof(hdr->rm.reserved)); - - break; - - case kXR_rmdir: - fprintf(stderr, "%40s0 repeated %d times\n", - "ClientHeader.rmdir.reserved = ", - (kXR_int32)sizeof(hdr->rmdir.reserved)); - break; - - case kXR_set: - fprintf(stderr, "%40s0 repeated %d times\n", - "ClientHeader.set.reserved = ", - (kXR_int32)sizeof(hdr->set.reserved)); - break; - - case kXR_stat: - fprintf(stderr, "%40s0 repeated %d times\n", - "ClientHeader.stat.reserved = ", - (kXR_int32)sizeof(hdr->stat.reserved)); - break; - - case kXR_sync: - fprintf(stderr, "%40s0x%.2x 0x%.2x 0x%.2x 0x%.2x \n", - "ClientHeader.sync.fhandle = ", - hdr->sync.fhandle[0], - hdr->sync.fhandle[1], - hdr->sync.fhandle[2], - hdr->sync.fhandle[3]); - - fprintf(stderr, "%40s0 repeated %d times\n", - "ClientHeader.sync.reserved = ", - (kXR_int32)sizeof(hdr->sync.reserved)); - break; - - case kXR_write: - fprintf(stderr, "%40s0x%.2x 0x%.2x 0x%.2x 0x%.2x \n", - "ClientHeader.write.fhandle = ", - hdr->write.fhandle[0], - hdr->write.fhandle[1], - hdr->write.fhandle[2], - hdr->write.fhandle[3]); - - memcpy(&tmpl, &hdr->write.offset, sizeof(kXR_int64) ); - - fprintf(stderr, "%40s%lld\n", - "ClientHeader.write.offset = ", - tmpl); - - fprintf(stderr, "%40s%d\n", - "ClientHeader.write.pathid = ", - hdr->write.pathid); - - fprintf(stderr, "%40s0 repeated %d times\n", - "ClientHeader.write.reserved = ", - (kXR_int32)sizeof(hdr->write.reserved)); - break; - } - - fprintf(stderr, "%40s%d", - "ClientHeader.header.dlen = ", - hdr->header.dlen); - fprintf(stderr, "\n=================== END CLIENT HEADER DUMPING ===================\n\n"); -} - -//___________________________________________________________________________ -void smartPrintServerHeader(struct ServerResponseHeader* hdr) -{ - fprintf(stderr, "\n\n======== DUMPING SERVER RESPONSE HEADER ========\n"); - fprintf(stderr, "%30s0x%.2x 0x%.2x\n", - "ServerHeader.streamid = ", - hdr->streamid[0], - hdr->streamid[1]); - switch(hdr->status) { - case kXR_ok: - fprintf(stderr, "%30skXR_ok", - "ServerHeader.status = "); - break; - case kXR_attn: - fprintf(stderr, "%30skXR_attn", - "ServerHeader.status = "); - break; - case kXR_authmore: - fprintf(stderr, "%30skXR_authmore", - "ServerHeader.status = "); - break; - case kXR_error: - fprintf(stderr, "%30skXR_error", - "ServerHeader.status = "); - break; - case kXR_oksofar: - fprintf(stderr, "%30skXR_oksofar", - "ServerHeader.status = "); - break; - case kXR_redirect: - fprintf(stderr, "%30skXR_redirect", - "ServerHeader.status = "); - break; - case kXR_wait: - fprintf(stderr, "%30skXR_wait", - "ServerHeader.status = "); - break; - } - fprintf(stderr, " (%d)\n", hdr->status); - fprintf(stderr, "%30s%d", - "ServerHeader.dlen = ", hdr->dlen); - fprintf(stderr, "\n========== END DUMPING SERVER HEADER ===========\n\n"); -} - diff --git a/src/XrdClient/XrdClientProtocol.hh b/src/XrdClient/XrdClientProtocol.hh deleted file mode 100644 index dd5926c0a6a..00000000000 --- a/src/XrdClient/XrdClientProtocol.hh +++ /dev/null @@ -1,60 +0,0 @@ -#ifndef XRD_CPROTOCOL_H -#define XRD_CPROTOCOL_H -/******************************************************************************/ -/* */ -/* X r d C l i e n t P r o t o c o l . h h */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2004) */ -/* Adapted from TXNetFile (root.cern.ch) originally done by */ -/* Alvise Dorigo, Fabrizio Furano */ -/* INFN Padova, 2003 */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// utility functions to deal with the protocol // -// // -////////////////////////////////////////////////////////////////////////// - -#include "XProtocol/XProtocol.hh" - -void clientMarshall(ClientRequest* str); -void clientMarshallReadAheadList(readahead_list *buf_list, kXR_int32 dlen); -void clientUnMarshallReadAheadList(readahead_list *buf_list, kXR_int32 dlen); -void clientUnmarshall(struct ServerResponseHeader* str); - -void ServerResponseHeader2NetFmt(struct ServerResponseHeader *srh); -void ServerInitHandShake2HostFmt(struct ServerInitHandShake *srh); - -bool isRedir(struct ServerResponseHeader *ServerResponse); - -char *convertRequestIdToChar(kXR_unt16 requestid); - -void PutFilehandleInRequest(ClientRequest* str, char *fHandle); - -char *convertRespStatusToChar(kXR_unt16 status); - -void smartPrintClientHeader(ClientRequest* hdr); -void smartPrintServerHeader(struct ServerResponseHeader* hdr); - -#endif diff --git a/src/XrdClient/XrdClientReadAhead.cc b/src/XrdClient/XrdClientReadAhead.cc deleted file mode 100644 index 4152ad567c6..00000000000 --- a/src/XrdClient/XrdClientReadAhead.cc +++ /dev/null @@ -1,293 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C l i e n t R e a d A h e a d . c c */ -/* */ -/* Author: Fabrizio Furano (CERN IT-DM, 2009) */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -///////////////////////////////////////////////////////////////////////// -// // -// Classes to implement a selectable read ahead decision maker // -// // -////////////////////////////////////////////////////////////////////////// - -#include "XrdClientReadAhead.hh" -#include "XrdClientConst.hh" -#include "XrdClientVector.hh" - -bool XrdClientReadAheadMgr::TrimReadRequest(long long &offs, long &len, long rasize, long blksz) { - - if (!blksz) return true; - - long long newoffs; - long newlen; - - long long lastbyte; - - newoffs = (long long)(offs / blksz); - newoffs *= blksz; - - lastbyte = offs+len+blksz-1; - lastbyte = (long long)(lastbyte / blksz); - lastbyte *= blksz; - - newlen = lastbyte-newoffs; - -// std::cerr << "Trim: " << offs << "," << len << " --> " << newoffs << "," << newlen << std::endl; - offs = newoffs; - len = newlen; - return true; - -} - - - - - -// ----------------------------------------------------------------------- - - - - - - -// A basic implementation. Purely sequential read ahead -class XrdClientReadAhead_pureseq : public XrdClientReadAheadMgr { - -protected: - long long RALast; - -public: - - XrdClientReadAhead_pureseq() { - RALast = 0; - } - - virtual int GetReadAheadHint(long long offset, long len, long long &raoffset, long &ralen, long blksz); - - virtual int Reset() { - RALast = 0; - return 0; - } - -}; - - - - - -int XrdClientReadAhead_pureseq::GetReadAheadHint(long long offset, long len, long long &raoffset, long &ralen, long blksz) { - - if (!blksz) blksz = 128*1024; - - // We read ahead only if (offs+len) lies in an interval of RALast not bigger than the readahead size - if ( (RALast - (offset+len) < RASize) && - (RALast - (offset+len) > -RASize) && - (RASize > 0) ) { - - // This is a HIT case. Async readahead will try to put some data - // in advance into the cache. The higher the araoffset will be, - // the best chances we have not to cause overhead - raoffset = xrdmax(RALast, offset + len); - ralen = xrdmin(RASize, - offset + len + RASize - raoffset); - - if (ralen > 0) { - TrimReadRequest(raoffset, ralen, RASize, blksz); - RALast = raoffset + ralen; - return 0; - } - } - - return 1; - -}; - - -// ----------------------------------------------------------------------- - - - - - - -// Another read ahead schema. A window centered on the recent average slides through the file -// following the stream of the requests -class XrdClientReadAhead_slidingavg : public XrdClientReadAheadMgr { - -protected: - long long RALast; - - long long LastOffsSum, LastOffsSum2; - long long LastOffsSumsq, LastOffsSumsq2; - XrdClientVector LastOffs; - XrdClientVector LastAvgApprox, LastAvgApprox2; -public: - - XrdClientReadAhead_slidingavg() { - RALast = 0; - LastOffsSum = LastOffsSum2 = 0; - LastOffsSumsq = LastOffsSumsq2 = 0; - - - } - - virtual int GetReadAheadHint(long long offset, long len, long long &raoffset, long &ralen, long blksz); - - virtual int Reset() { - RALast = 0; - LastOffsSum = LastOffsSum2 = 0; - LastOffsSumsq = LastOffsSumsq2 = 0; - return 0; - } - -}; - - - - - -int XrdClientReadAhead_slidingavg::GetReadAheadHint(long long offset, long len, long long &raoffset, long &ralen, long blksz) { - - if (!blksz) blksz = 128*1024; - - // Keep the sums up to date, together with the max array size and the sumsqs - LastOffsSum += offset; - LastOffsSum2 += offset; - LastOffs.Push_back(offset); - - int loSZ = LastOffs.GetSize(), loSZ50 = loSZ-50; - if (loSZ >= 50) { - LastOffsSum2 -= LastOffs[loSZ50]; - } - if (LastOffs.GetSize() >= 1000) { - LastOffsSum -= LastOffs[0]; - } - - long long lastavg = LastOffsSum / LastOffs.GetSize(); - long long lastavg2 = LastOffsSum2 / xrdmin(LastOffs.GetSize(), 50); - - - // Now the approximations of the std deviation, shifted right by some positions to avoid overflows - long long sqerr = (((offset >> 20) - (lastavg >> 20))*((offset >> 20) - (lastavg >> 20))); - LastOffsSumsq += sqerr; - long long sqerr2 = ( ((offset - lastavg2) >> 20) * ((offset - lastavg2) >> 20) ); - LastOffsSumsq2 += sqerr2; - - LastAvgApprox.Push_back(sqerr); - LastAvgApprox2.Push_back(sqerr2); - - if (LastAvgApprox2.GetSize() >= 50) { - LastOffsSumsq2 -= LastAvgApprox2[0]; - LastAvgApprox2.Erase(0); - } - - if (LastAvgApprox.GetSize() >= 1000) { - LastOffsSumsq -= LastAvgApprox[0]; - LastAvgApprox.Erase(0); - } - - if (LastOffs.GetSize() >= 1000) { - LastOffs.Erase(0); - } - - long long stddevi = LastOffsSumsq / LastOffs.GetSize(); - long long stddevi2 = LastOffsSumsq2 / LastAvgApprox2.GetSize(); - - //std::cerr << "offs:" << offset << " avg:" << lastavg << " avg2:" << lastavg2 << " devi:" << stddevi << " devi2:" << stddevi2; - - // To read ahead, we want at least a few samples - //if ( LastOffs.GetSize() < 10 ) return 1; - - // If the more stable avg is usable, use it - if ((stddevi << 20) < 3*RASize) { - raoffset = xrdmax(RALast, lastavg - RASize/2); - - ralen = xrdmin(RASize, - lastavg + RASize/2 - raoffset); - - if (ralen > (1024*1024)) { - TrimReadRequest(raoffset, ralen, RASize, blksz); - RALast = raoffset + ralen; - //std::cerr << " raoffs:" << raoffset << " ralen:" << ralen << " Got avg" << std::endl; - return 0; - } - //std::cerr << std::endl; - } else - // If the less stable avg is usable, use it - if ((stddevi2 << 20) < 3*RASize) { - raoffset = xrdmax(RALast, lastavg2 - RASize/2); - - ralen = xrdmin(RASize, - lastavg2 + RASize/2 - raoffset); - - - if (ralen > (1024*1024)) { - TrimReadRequest(raoffset, ralen, RASize, blksz); - RALast = raoffset + ralen; - //std::cerr << " raoffs:" << raoffset << " ralen:" << ralen << " Got avg2" << std::endl; - return 0; - } - //std::cerr << std::endl; - } - - //std::cerr << std::endl; - return 1; - - -}; - - - - - - - - - - - -// ------------------------------------------------------ - -XrdClientReadAheadMgr *XrdClientReadAheadMgr::CreateReadAheadMgr(XrdClient_RAStrategy strategy) { - XrdClientReadAheadMgr *ramgr = 0; - - switch (strategy) { - - case RAStr_none: - break; - - case RAStr_pureseq: { - ramgr = new XrdClientReadAhead_pureseq(); - break; - } - case RAStr_SlidingAvg: { - ramgr = new XrdClientReadAhead_slidingavg(); - break; - } - - } - - if (ramgr) ramgr->currstrategy = strategy; - return ramgr; -} diff --git a/src/XrdClient/XrdClientReadAhead.hh b/src/XrdClient/XrdClientReadAhead.hh deleted file mode 100644 index f9b8bfa2756..00000000000 --- a/src/XrdClient/XrdClientReadAhead.hh +++ /dev/null @@ -1,64 +0,0 @@ -#ifndef XRD_CLI_READAHEAD -#define XRD_CLI_READAHEAD -/******************************************************************************/ -/* */ -/* X r d C l i e n t R e a d A h e a d . h h */ -/* */ -/* Author: Fabrizio Furano (CERN IT-DM, 2009) */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// Classes to implement a selectable read ahead decision maker // -// // -////////////////////////////////////////////////////////////////////////// - -class XrdClientReadAheadMgr { -public: - enum XrdClient_RAStrategy { - RAStr_none, - RAStr_pureseq, - RAStr_SlidingAvg - }; - -protected: - long RASize; - XrdClient_RAStrategy currstrategy; - -public: - - static XrdClientReadAheadMgr *CreateReadAheadMgr(XrdClient_RAStrategy strategy); - - - XrdClientReadAheadMgr() { RASize = 0; }; - virtual ~XrdClientReadAheadMgr() {}; - - virtual int GetReadAheadHint(long long offset, long len, long long &raoffset, long &ralen, long blksize) = 0; - virtual int Reset() = 0; - virtual void SetRASize(long bytes) { RASize = bytes; }; - - static bool TrimReadRequest(long long &offs, long &len, long rasize, long blksize); - - XrdClient_RAStrategy GetCurrentStrategy() { return currstrategy; } -}; -#endif diff --git a/src/XrdClient/XrdClientReadCache.cc b/src/XrdClient/XrdClientReadCache.cc deleted file mode 100644 index e42588d980c..00000000000 --- a/src/XrdClient/XrdClientReadCache.cc +++ /dev/null @@ -1,912 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C l i e n t R e a d C a c h e . c c */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2006) */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// Classes to handle cache reading and cache placeholders // -// // -////////////////////////////////////////////////////////////////////////// - -#include "XrdClient/XrdClientReadCache.hh" -#include "XrdSys/XrdSysPthread.hh" -#include "XrdClient/XrdClientDebug.hh" -#include "XrdClient/XrdClientEnv.hh" - -//________________________________________________________________________ -XrdClientReadCacheItem::XrdClientReadCacheItem(const void *buffer, long long begin_offs, - long long end_offs, long long ticksnow, bool placeholder) -{ - // Constructor - fIsPlaceholder = placeholder; - - fData = (void *)0; - if (!fIsPlaceholder) - fData = (void *)buffer; - - Touch(ticksnow); - fBeginOffset = begin_offs; - fEndOffset = end_offs; - Pinned = false; -} - -//________________________________________________________________________ -XrdClientReadCacheItem::~XrdClientReadCacheItem() -{ - // Destructor - - if (fData) - free(fData); -} - -// -// XrdClientReadCache -// - -//________________________________________________________________________ -long long XrdClientReadCache::GetTimestampTick() -{ - // Return timestamp - - // Mutual exclusion man! - XrdSysMutexHelper mtx(fMutex); - return ++fTimestampTickCounter; -} - -//________________________________________________________________________ -XrdClientReadCache::XrdClientReadCache() : fItems(4096) -{ - // Constructor - - fTimestampTickCounter = 0; - fTotalByteCount = 0; - - fMissRate = 0.0; - fMissCount = 0; - fReadsCounter = 0; - - fBytesSubmitted = 0; - fBytesHit = 0; - fBytesUsefulness = 0.0; - - fMaxCacheSize = EnvGetLong(NAME_READCACHESIZE); - fBlkRemPolicy = EnvGetLong(NAME_READCACHEBLKREMPOLICY); -} - -//________________________________________________________________________ -XrdClientReadCache::~XrdClientReadCache() -{ - // Destructor - RemoveItems(false); - -} - - - -//________________________________________________________________________ -bool XrdClientReadCache::SubmitRawData(const void *buffer, long long begin_offs, - long long end_offs, bool pinned) -{ - if (!buffer) return true; - XrdClientReadCacheItem *itm; - - Info(XrdClientDebug::kHIDEBUG, "Cache", - "Submitting " << begin_offs << "->" << end_offs << " to cache" << (pinned ? " as pinned data." : ".") ); - - // Mutual exclusion man! - XrdSysMutexHelper mtx(fMutex); - - - // PrintCache(); - - // We remove all the blocks contained in the one we are going to put - RemoveItems(begin_offs, end_offs); - bool spaceok = MakeFreeSpace(end_offs - begin_offs + 1); - - if (pinned || spaceok) { - - - - // We find the correct insert position to keep the list sorted by - // BeginOffset - // A data block will always be inserted BEFORE a true block with - // equal beginoffset - int pos = FindInsertionApprox(begin_offs); - if (fItems.GetSize()) - for (; pos >= 0; pos--) - if ((pos < fItems.GetSize()) && - fItems[pos] && (fItems[pos]->EndOffset() < begin_offs)) break; - if (pos < 0) pos = 0; - - for (; pos < fItems.GetSize(); pos++) { - // Don't add this block if it is contained in a bigger one - if (!fItems[pos]->IsPlaceholder() && fItems[pos]->ContainsInterval(begin_offs, end_offs)) { - pos = -1; - break; - } - if (fItems[pos]->BeginOffset() >= begin_offs) - break; - } - - if (pos >= 0) { - itm = new XrdClientReadCacheItem(buffer, begin_offs, end_offs, - GetTimestampTick()); - itm->Pinned = pinned; - - fItems.Insert(itm, pos); - - if (!pinned) { - fTotalByteCount += itm->Size(); - fBytesSubmitted += itm->Size(); - } - - return true; - } - - return false; - } // if - - - return false; -} - - -//________________________________________________________________________ -void XrdClientReadCache::SubmitXMessage(XrdClientMessage *xmsg, long long begin_offs, - long long end_offs) -{ - // To populate the cache of items, newly received - - const void *buffer = xmsg->DonateData(); - - if (!SubmitRawData(buffer, begin_offs, end_offs)) - free(const_cast(buffer)); -} - - - -//________________________________________________________________________ -int XrdClientReadCache::FindInsertionApprox(long long begin_offs) { - - // quickly finds the correct insertion point for a placeholder or for a data block - // Remember that placeholders are inserted before data blks with - // identical beginoffs - - if (!fItems.GetSize()) return 0; - - int pos, i; - pos = FindInsertionApprox_rec(0, fItems.GetSize()-1, begin_offs); - - for (i = pos-1; i >= 0; i--) { - if (fItems[i] && (fItems[i]->BeginOffset() >= begin_offs)) pos = i; - else break; - } - - return pos; -} - - -//________________________________________________________________________ -int XrdClientReadCache::FindInsertionApprox_rec(int startidx, int endidx, - long long begin_offs) { - - // Dicotomic search to quickly find a place where to start scanning - // for the final destination of a blk - - if (endidx - startidx <= 1) { - - - if (fItems[startidx]->BeginOffset() >= begin_offs) { - // The item is to be inserted before the startidx pos - return startidx; - } - if (fItems[endidx]->BeginOffset() < begin_offs) { - // The item is to be inserted after the endidx pos - return endidx+1; - } - - return endidx; - - } - - int pos2 = (endidx + startidx) / 2; - - if (fItems[startidx]->BeginOffset() >= begin_offs) { - // The item is not here! - return startidx; - } - if (fItems[endidx]->BeginOffset() < begin_offs) { - // The item is not here! - return endidx+1; - } - - if (fItems[pos2]->BeginOffset() >= begin_offs) { - // The item is between startidx and pos2! - return FindInsertionApprox_rec(startidx, pos2, begin_offs); - } - - if (fItems[pos2]->BeginOffset() < begin_offs) { - // The item is between pos2 and endidx! - return FindInsertionApprox_rec(pos2, endidx, begin_offs); - } - - return endidx; -} - -//________________________________________________________________________ -void XrdClientReadCache::PutPlaceholder(long long begin_offs, - long long end_offs) -{ - // To put a placeholder into the cache - - XrdClientReadCacheItem *itm = 0; - - { - // Mutual exclusion man! - XrdSysMutexHelper mtx(fMutex); - - // We find the correct insert position to keep the list sorted by - // BeginOffset - int pos = FindInsertionApprox(begin_offs); - int p = pos - 1; - - if (fItems.GetSize()) - for (; p >= 0; p--) - if ((p < fItems.GetSize()) && - fItems[p] && (fItems[p]->EndOffset() < begin_offs)) break; - if (p < 0) p = 0; - - for (; p < fItems.GetSize(); p++) { - if (fItems[p]->ContainsInterval(begin_offs, end_offs)) { - return; - } - - if (fItems[p]->BeginOffset() > end_offs) - break; - - // We found an item which is overlapping the new candidate. - // Here we shrink the candidate at the left - if ( (fItems[p]->BeginOffset() >= begin_offs) && - (fItems[p]->BeginOffset() <= end_offs) ) { - - itm = 0; - if (begin_offs < fItems[p]->BeginOffset()-1) - itm = new XrdClientReadCacheItem(0, begin_offs, fItems[p]->BeginOffset()-1, - GetTimestampTick(), true); - begin_offs = fItems[p]->EndOffset()+1; - if (itm) { - fItems.Insert(itm, p); - - // Optimization: we avoid to check the same block twice - p++; - } - - } - - if ( (fItems[p]->BeginOffset() <= begin_offs) && - (fItems[p]->EndOffset() >= begin_offs) ) { - - begin_offs = fItems[p]->EndOffset()+1; - - } - - - pos = p+1; - - if (begin_offs >= end_offs) return; - - - } - - itm = new XrdClientReadCacheItem(0, begin_offs, end_offs, - GetTimestampTick(), true); - fItems.Insert(itm, pos); - - } - - // PrintCache(); -} - -//________________________________________________________________________ -long XrdClientReadCache::GetDataIfPresent(const void *buffer, - long long begin_offs, - long long end_offs, - bool PerfCalc, - XrdClientIntvList &missingblks, - long &outstandingblks) -{ - // Copies the requested data from the cache. False if not possible - // Also, this function figures out if: - // - there are data blocks marked as outstanding - // - there are sub blocks which should be requested - - int it; - long bytesgot = 0; - - long long lastseenbyte = begin_offs-1; - - outstandingblks = 0; - missingblks.Clear(); - - XrdSysMutexHelper mtx(fMutex); - - //PrintCache(); - - if (PerfCalc) - fReadsCounter++; - - // We try to compose the requested data block by concatenating smaller - // blocks. - - - // Find a block helping us to go forward - // The blocks are sorted - // By scanning the list we also look for: - // - the useful blocks which are outstanding - // - the useful blocks which are missing, and not outstanding - - // First scan: we get the useful data - // and remember where we arrived - it = FindInsertionApprox(begin_offs); - - if (fItems.GetSize()) - for (; it >= 0; it--) - if ((it < fItems.GetSize()) && - fItems[it] && (fItems[it]->EndOffset() < begin_offs)) break; - if (it < 0) it = 0; - - for (; it < fItems.GetSize(); it++) { - long l = 0; - - if (!fItems[it]) continue; - - if (fItems[it]->BeginOffset() > lastseenbyte+1) break; - - if (!fItems[it]->IsPlaceholder()) - // If it's not a placeholder then we take useful bytes from it - l = fItems[it]->GetPartialInterval(((char *)buffer)+bytesgot, - begin_offs+bytesgot, end_offs); - else { - // If it's a placeholder and it has useful bytes, - // we increment the outstanding blks counter - if (fItems[it]->GetPartialInterval(0, begin_offs+bytesgot, end_offs) > 0) { - - if (fBlkRemPolicy != kRmBlk_FIFO) - fItems[it]->Touch(GetTimestampTick()); - - outstandingblks++; - - } - - } - - lastseenbyte = xrdmax(lastseenbyte, fItems[it]->EndOffset()); - - if (l > 0) { - bytesgot += l; - - if (fBlkRemPolicy != kRmBlk_FIFO) - fItems[it]->Touch(GetTimestampTick()); - - if (PerfCalc) { - fBytesHit += l; - UpdatePerfCounters(); - } - - if (bytesgot >= end_offs - begin_offs + 1) { - return bytesgot; - } - - } - - } - - - // We are here if something is missing to get all the data we need - // Hence we build a list of what is missing - // right now what is missing is the interval - // [lastseenbyte+1, end_offs] - - XrdClientCacheInterval intv; - - - for (; it < fItems.GetSize(); it++) { - long l; - - if (fItems[it]->BeginOffset() > end_offs) break; - - if (fItems[it]->BeginOffset() > lastseenbyte+1) { - // We found that the interval - // [lastbyteseen+1, fItems[it]->BeginOffset-1] - // is a hole, which should be requested explicitly - - intv.beginoffs = lastseenbyte+1; - intv.endoffs = fItems[it]->BeginOffset()-1; - missingblks.Push_back( intv ); - - lastseenbyte = fItems[it]->EndOffset(); - if (lastseenbyte >= end_offs) break; - continue; - } - - // Let's see if we can get something from this blk, even if it's a placeholder - l = fItems[it]->GetPartialInterval(0, lastseenbyte+1, end_offs); - - if (l > 0) { - // We found a placeholder to wait for - // or a data block - - if (fItems[it]->IsPlaceholder()) { - // Add this interval to the number of blocks to wait for - outstandingblks++; - - } - - - lastseenbyte += l; - } - - - } - - if (lastseenbyte+1 <= end_offs) { - intv.beginoffs = lastseenbyte+1; - intv.endoffs = end_offs; - missingblks.Push_back( intv ); - } - - - if (PerfCalc) { - fMissCount++; - UpdatePerfCounters(); - } - - return bytesgot; -} - - -//________________________________________________________________________ -void XrdClientReadCache::PrintCache() { - - XrdSysMutexHelper mtx(fMutex); - int it; - - Info(XrdClientDebug::kUSERDEBUG, "Cache", - "Cache Status --------------------------"); - - for (it = 0; it < fItems.GetSize(); it++) { - - if (fItems[it]) { - - if (fItems[it]->IsPlaceholder()) { - - Info(XrdClientDebug::kUSERDEBUG, - "Cache blk", it << "Placeholder " << - fItems[it]->BeginOffset() << "->" << fItems[it]->EndOffset() ); - - } - else - Info(XrdClientDebug::kUSERDEBUG, - "Cache blk", it << "Data block " << - fItems[it]->BeginOffset() << "->" << fItems[it]->EndOffset() << - (fItems[it]->Pinned ? " (pinned) " : "" ) ); - - } - } - - Info(XrdClientDebug::kUSERDEBUG, "Cache", - "-------------------------------------- fTotalByteCount = " << fTotalByteCount ); - -} - -void *XrdClientReadCache::FindBlk(long long begin_offs, long long end_offs) { - - int it; - XrdSysMutexHelper mtx(fMutex); - - it = FindInsertionApprox(begin_offs); - - if (fItems.GetSize()) - for (; it >= 0; it--) - if ((it < fItems.GetSize()) && - fItems[it] && (fItems[it]->EndOffset() < begin_offs)) break; - if (it < 0) it = 0; - - while (it < fItems.GetSize()) { - if (fItems[it]) { - - if (fItems[it]->BeginOffset() > end_offs) break; - - if ((fItems[it]->BeginOffset() == begin_offs) && - (fItems[it]->EndOffset() == end_offs)) { - return fItems[it]->GetData(); - } - else it++; - - } - else it++; - - } - - return 0; - -} - - - -void XrdClientReadCache::UnPinCacheBlk(long long begin_offs, long long end_offs) { - - int it; - XrdSysMutexHelper mtx(fMutex); - - it = FindInsertionApprox(begin_offs); - - if (fItems.GetSize()) - for (; it >= 0; it--) - if ((it < fItems.GetSize()) && - fItems[it] && (fItems[it]->EndOffset() < begin_offs)) break; - if (it < 0) it = 0; - - // We make sure that exactly tat block gets unpinned - while (it < fItems.GetSize()) { - if (fItems[it]) { - - if (fItems[it]->BeginOffset() > end_offs) break; - - if (fItems[it]->Pinned && fItems[it]->ContainedInInterval(begin_offs, end_offs)) { - fItems[it]->Pinned = false; - fTotalByteCount += fItems[it]->Size(); - break; - } - else it++; - - } - else it++; - - } - -} - - -//________________________________________________________________________ -void XrdClientReadCache::RemoveItems(long long begin_offs, long long end_offs, bool remove_overlapped) -{ - // To remove all the items contained in the given interval - // if remove_overlapping, then remove also the ones just overlapping the given interval - - int it; - XrdSysMutexHelper mtx(fMutex); - - it = FindInsertionApprox(begin_offs); - - // To spot the overlapped this is potentially not perfect - if (it < fItems.GetSize()) - for (; it >= 0; it--) - if ((it < fItems.GetSize()) && - fItems[it] && (fItems[it]->EndOffset() < begin_offs)) break; - if (it < 0) it = 0; - - // We remove all the blocks contained in the given interval - while (it < fItems.GetSize()) { - if (fItems[it]) { - - if (!remove_overlapped) { - if (fItems[it]->BeginOffset() > end_offs) break; - - if (!fItems[it]->Pinned && fItems[it]->ContainedInInterval(begin_offs, end_offs)) { - - if (!fItems[it]->IsPlaceholder()) - fTotalByteCount -= fItems[it]->Size(); - - delete fItems[it]; - fItems.Erase(it); - } - else it++; - } - else { - // Remove a data chunk just if it overlaps - if (fItems[it]->BeginOffset() > end_offs) break; - if (!fItems[it]->Pinned && !fItems[it]->IsPlaceholder() && - fItems[it]->IntersectInterval(begin_offs, end_offs)) { - - fTotalByteCount -= fItems[it]->Size(); - - delete fItems[it]; - fItems.Erase(it); - } - else it++; - - } - - } - else it++; - - } - // Then we resize or split the placeholders overlapping the given interval - bool changed; - it = FindInsertionApprox(begin_offs); - if (fItems.GetSize()) - for (; it >= 0; it--) - if ((it < fItems.GetSize()) && - fItems[it] && (fItems[it]->EndOffset() < begin_offs)) break; - if (it < 0) it = 0; - - - do { - changed = false; - for (; it < fItems.GetSize(); it++) { - - - if (fItems[it]) { - - if (fItems[it]->BeginOffset() > end_offs) break; - - if ( fItems[it]->IsPlaceholder() ) { - long long plc1_beg = 0; - long long plc1_end = 0; - - long long plc2_beg = 0; - long long plc2_end = 0; - - // We have a placeholder which contains the arrived block - plc1_beg = fItems[it]->BeginOffset(); - plc1_end = begin_offs-1; - - plc2_beg = end_offs+1; - plc2_end = fItems[it]->EndOffset(); - - if ( ( (begin_offs >= fItems[it]->BeginOffset()) && - (begin_offs <= fItems[it]->EndOffset()) ) || - ( (end_offs >= fItems[it]->BeginOffset()) && - (end_offs <= fItems[it]->EndOffset()) ) ) { - - delete fItems[it]; - fItems.Erase(it); - changed = true; - it--; - - if (plc1_end - plc1_beg > 32) { - PutPlaceholder(plc1_beg, plc1_end); - } - - if (plc2_end - plc2_beg > 32) { - PutPlaceholder(plc2_beg, plc2_end); - } - - break; - - } - - - - - } - - } - - } - - it = xrdmax(0, it-2); - } while (changed); - - - -} - -//________________________________________________________________________ -void XrdClientReadCache::RemoveItems(bool leavepinned) -{ - // To remove all the items which were not pinned - // The typical reason to pin a block is because there is an outstanding write on it - - // if leavepinned == false then it removes everything - XrdSysMutexHelper mtx(fMutex); - int it = fItems.GetSize()-1; - - for (; it >= 0; it--) { - if (!fItems[it]->Pinned) { - fTotalByteCount -= fItems[it]->Size(); - delete fItems[it]; - fItems.Erase(it, true); - continue; - } - - if (fItems[it]->Pinned && !leavepinned) { - delete fItems[it]; - fItems.Erase(it, true); - continue; - } - } - - if (!leavepinned) fTotalByteCount = 0; - -} - - -//________________________________________________________________________ -void XrdClientReadCache::RemovePlaceholders() { - - // Finds the LRU item and removes it - // We remove placeholders - - int it = 0; - - XrdSysMutexHelper mtx(fMutex); - - if (!fItems.GetSize()) return; - - while (1) { - - if (fItems[it] && fItems[it]->IsPlaceholder()) { - delete fItems[it]; - fItems.Erase(it); - } - else - it++; - - if (it == fItems.GetSize()) break; - } - -} - - -//________________________________________________________________________ -bool XrdClientReadCache::RemoveFirstItem() -{ - // Finds the first item (lower offset) and removes it - // We don't remove placeholders or pinned items - - int it, lruit; - XrdClientReadCacheItem *item; - - XrdSysMutexHelper mtx(fMutex); - - lruit = -1; - - // Kill the first not placeholder if we have too many blks - lruit = -1; - for (it = 0; it < fItems.GetSize(); it++) { - // We don't remove placeholders - if (!fItems[it]->IsPlaceholder() && !fItems[it]->Pinned) { - lruit = it; - break; - } - } - - - if (lruit >= 0) - item = fItems[lruit]; - else return false; - - fTotalByteCount -= item->Size(); - delete item; - fItems.Erase(lruit); - - - return true; -} - - -//________________________________________________________________________ -bool XrdClientReadCache::RemoveLRUItem() -{ - // Finds the LRU item and removes it - // We don't remove placeholders or pinned items - - int it, lruit; - long long minticks = -1; - XrdClientReadCacheItem *item = 0; - - XrdSysMutexHelper mtx(fMutex); - - lruit = -1; - - if (fItems.GetSize() < 1000000) - for (it = 0; it < fItems.GetSize(); it++) { - // We don't remove placeholders - if (fItems[it] && !fItems[it]->IsPlaceholder() && !fItems[it]->Pinned) { - if ((minticks < 0) || (fItems[it]->GetTimestampTicks() < minticks)) { - minticks = fItems[it]->GetTimestampTicks(); - lruit = it; - } - } - } - else { - - // Kill the first not placeholder if we have tooooo many blks - lruit = -1; - for (it = 0; it < fItems.GetSize(); it++) { - // We don't remove placeholders - if (!fItems[it]->IsPlaceholder() && !fItems[it]->Pinned) { - lruit = it; - minticks = 0; - break; - } - } - } - - if (lruit >= 0) - item = fItems[lruit]; - else return false; - - if (item) { - fTotalByteCount -= item->Size(); - delete item; - fItems.Erase(lruit); - } - - return true; -} - -//________________________________________________________________________ -bool XrdClientReadCache::RemoveItem() { - - switch (fBlkRemPolicy) { - - case kRmBlk_LRU: - case kRmBlk_FIFO: - return RemoveLRUItem(); - - case kRmBlk_LeastOffs: - return RemoveFirstItem(); - - } - - return RemoveLRUItem(); -} - -//________________________________________________________________________ -bool XrdClientReadCache::MakeFreeSpace(long long bytes) -{ - // False if not possible (requested space exceeds max size!) - - if (!WillFit(bytes)) - return false; - - XrdSysMutexHelper mtx(fMutex); - - while (fMaxCacheSize - fTotalByteCount < bytes) - if (!RemoveItem()) - return false; - - return true; -} - - -void XrdClientReadCache::GetInfo(int &size, long long &bytessubmitted, - long long &byteshit, - long long &misscount, - float &missrate, - long long &readreqcnt, - float &bytesusefulness ) { - size = fMaxCacheSize; - bytessubmitted = fBytesSubmitted; - byteshit = fBytesHit; - misscount = fMissCount; - missrate = fMissRate; - readreqcnt = fReadsCounter; - bytesusefulness = fBytesUsefulness; -} diff --git a/src/XrdClient/XrdClientReadCache.hh b/src/XrdClient/XrdClientReadCache.hh deleted file mode 100644 index dd6c49b107c..00000000000 --- a/src/XrdClient/XrdClientReadCache.hh +++ /dev/null @@ -1,285 +0,0 @@ -#ifndef XRD_READCACHE_H -#define XRD_READCACHE_H -/******************************************************************************/ -/* */ -/* X r d C l i e n t R e a d C a c h e . h h */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2006) */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// Classes to handle cache reading and cache placeholders // -// // -////////////////////////////////////////////////////////////////////////// - -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdClient/XrdClientInputBuffer.hh" -#include "XrdClient/XrdClientMessage.hh" -#include "XrdClient/XrdClientVector.hh" -#include "XrdClient/XrdClientConst.hh" - -// -// XrdClientReadCacheItem -// -// An item is nothing more than an interval of bytes taken from a file. -// Extremes are included. -// Since a cache object is to be associated to a single instance -// of TXNetFile, we do not have to keep here any filehandle -// - -class XrdClientReadCacheItem { -private: - // A placeholder block is a "fake block" used to mark outstanding data - bool fIsPlaceholder; - - long long fBeginOffset; // Offset of the first byte of data - void *fData; - long long fEndOffset; // Offset of the last byte of data - long fTimestampTicks; // timestamp updated each time it's referenced - -public: - XrdClientReadCacheItem(const void *buffer, long long begin_offs, - long long end_offs, long long ticksnow, - bool placeholder=false); - ~XrdClientReadCacheItem(); - - inline long long BeginOffset() { return fBeginOffset; } - inline long long EndOffset() { return fEndOffset; } - - // Is this obj contained in the given interval (which is going to be inserted) ? - inline bool ContainedInInterval(long long begin_offs, long long end_offs) { - return ( (end_offs >= begin_offs) && - (fBeginOffset >= begin_offs) && - (fEndOffset <= end_offs) ); - } - - // Does this obj contain the given interval (which is going to be requested) ? - inline bool ContainsInterval(long long begin_offs, long long end_offs) { - return ( (end_offs > begin_offs) && - (fBeginOffset <= begin_offs) && (fEndOffset >= end_offs) ); - } - - // Are the two intervals intersecting in some way? - inline bool IntersectInterval(long long begin_offs, long long end_offs) { - if ( ContainsOffset( begin_offs ) || ContainsOffset( end_offs ) ) return true; - if ( (fBeginOffset >= begin_offs) && (fBeginOffset <= end_offs) ) return true; - return false; - } - - - inline bool ContainsOffset(long long offs) { - return (fBeginOffset <= offs) && (fEndOffset >= offs); - } - - void *GetData() { return fData; } - - // Get the requested interval, if possible - inline bool GetInterval(const void *buffer, long long begin_offs, - long long end_offs) { - if (!ContainsInterval(begin_offs, end_offs)) - return FALSE; - memcpy((void *)buffer, ((char *)fData)+(begin_offs - fBeginOffset), - end_offs - begin_offs + 1); - return TRUE; - } - - // Get as many bytes as possible, starting from the beginning of the given - // interval - inline long GetPartialInterval(const void *buffer, long long begin_offs, - long long end_offs) { - - long long b = -1, e, l; - - if (begin_offs > end_offs) return 0; - - // Try to set the starting point, if contained in the given interval - if ( (begin_offs >= fBeginOffset) && - (begin_offs <= fEndOffset) ) - b = begin_offs; - - if (b < 0) return 0; - - // The starting point is in the interval. Let's get the minimum endpoint - e = xrdmin(end_offs, fEndOffset); - - l = e - b + 1; - - if (buffer && fData) - memcpy((void *)buffer, ((char *)fData)+(b - fBeginOffset), l); - - return l; - } - - inline long long GetTimestampTicks() { return(fTimestampTicks); } - - inline bool IsPlaceholder() { return fIsPlaceholder; } - - long Size() { return (fEndOffset - fBeginOffset + 1); } - - inline void Touch(long long ticksnow) { fTimestampTicks = ticksnow; } - - bool Pinned; -}; - -// -// XrdClientReadCache -// -// The content of the cache. Not cache blocks, but -// variable length Items -// -typedef XrdClientVector ItemVect; - -// A cache interval, extremes included -struct XrdClientCacheInterval { - long long beginoffs; - long long endoffs; -}; - -typedef XrdClientVector XrdClientIntvList; - -class XrdClientReadCache { -private: - - long long fBytesHit; // Total number of bytes read with a cache hit - long long fBytesSubmitted; // Total number of bytes inserted - float fBytesUsefulness; - ItemVect fItems; - long long fMaxCacheSize; - long long fMissCount; // Counter of the cache misses - float fMissRate; // Miss rate - XrdSysRecMutex fMutex; - long long fReadsCounter; // Counter of all the attempted reads (hit or miss) - int fBlkRemPolicy; // The algorithm used to remove "old" chunks - long long fTimestampTickCounter; // Aging mechanism yuk! - long long fTotalByteCount; - - long long GetTimestampTick(); - bool MakeFreeSpace(long long bytes); - - bool RemoveItem(); - bool RemoveLRUItem(); - bool RemoveFirstItem(); - - inline void UpdatePerfCounters() { - if (fReadsCounter > 0) - fMissRate = (float)fMissCount / fReadsCounter; - if (fBytesSubmitted > 0) - fBytesUsefulness = (float)fBytesHit / fBytesSubmitted; - } - - int FindInsertionApprox(long long begin_offs); - int FindInsertionApprox_rec(int startidx, int endidx, - long long begin_offs); -public: - - // The algos available for the removal of "old" blocks - enum { - kRmBlk_LRU = 0, - kRmBlk_LeastOffs, - kRmBlk_FIFO - }; - - XrdClientReadCache(); - ~XrdClientReadCache(); - - long GetDataIfPresent(const void *buffer, long long begin_offs, - long long end_offs, bool PerfCalc, - XrdClientIntvList &missingblks, long &outstandingblks); - - void GetInfo( - // The actual cache size - int &size, - - // The number of bytes submitted since the beginning - long long &bytessubmitted, - - // The number of bytes found in the cache (estimate) - long long &byteshit, - - // The number of reads which did not find their data - // (estimate) - long long &misscount, - - // miss/totalreads ratio (estimate) - float &missrate, - - // number of read requests towards the cache - long long &readreqcnt, - - // ratio between bytes found / bytes submitted - float &bytesusefulness - ); - - inline long long GetTotalByteCount() { - XrdSysMutexHelper m(fMutex); - return fTotalByteCount; - } - - void PutPlaceholder(long long begin_offs, long long end_offs); - - inline void PrintPerfCounters() { - XrdSysMutexHelper m(fMutex); - - cout << "Low level caching info:" << endl; - cout << " StallsRate=" << fMissRate << endl; - cout << " StallsCount=" << fMissCount << endl; - cout << " ReadsCounter=" << fReadsCounter << endl; - cout << " BytesUsefulness=" << fBytesUsefulness << endl; - cout << " BytesSubmitted=" << fBytesSubmitted << " BytesHit=" << - fBytesHit << endl << endl; - } - - - void PrintCache(); - - void SubmitXMessage(XrdClientMessage *xmsg, long long begin_offs, - long long end_offs); - - bool SubmitRawData(const void *buffer, long long begin_offs, - long long end_offs, bool pinned=false); - - void RemoveItems(bool leavepinned=true); - void RemoveItems(long long begin_offs, long long end_offs, bool remove_overlapped = false); - void RemovePlaceholders(); - - - void SetSize(int sz) { - fMaxCacheSize = sz; - } - - void SetBlkRemovalPolicy(int p) { - fBlkRemPolicy = p; - } - - void UnPinCacheBlk(long long begin_offs, long long end_offs); - void *FindBlk(long long begin_offs, long long end_offs); - - // To check if a block dimension will fit into the cache - inline bool WillFit(long long bc) { - XrdSysMutexHelper m(fMutex); - return (bc < fMaxCacheSize); - } - -}; -#endif diff --git a/src/XrdClient/XrdClientReadV.cc b/src/XrdClient/XrdClientReadV.cc deleted file mode 100644 index b51f1d947f8..00000000000 --- a/src/XrdClient/XrdClientReadV.cc +++ /dev/null @@ -1,273 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C l i e n t R e a d V . c c */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2006) */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// Helper functions for the vectored read functionality // -// // -////////////////////////////////////////////////////////////////////////// - -#include "XrdClient/XrdClientProtocol.hh" -#include "XrdClient/XrdClientReadV.hh" -#include "XrdClient/XrdClientConn.hh" -#include "XrdClient/XrdClientDebug.hh" - -#include "XrdSys/XrdSysPlatform.hh" - -#include - -// Builds a request and sends it to the server -// If destbuf == 0 the request is sent asynchronously -// nbuf returns the number of processed buffers -kXR_int64 XrdClientReadV::ReqReadV(XrdClientConn *xrdc, char *handle, char *destbuf, - XrdClientVector &reqvect, - int firstreq, int nreq, int streamtosend) { - - readahead_list buflis[READV_MAXCHUNKS]; - - Info(XrdClientDebug::kUSERDEBUG, "ReqReadV", - "Requesting to read " << nreq << - " chunks."); - - kXR_int64 total_len = 0; - - // Now we build the protocol-ready read ahead list - // and also put the correct placeholders inside the cache - for (int i = 0; i < nreq; i++) { - - memcpy( &(buflis[i].fhandle), handle, 4 ); - - - if (!destbuf) - xrdc->SubmitPlaceholderToCache(reqvect[firstreq+i].offset, - reqvect[firstreq+i].offset + - reqvect[firstreq+i].len-1); - - buflis[i].offset = reqvect[firstreq+i].offset; - buflis[i].rlen = reqvect[firstreq+i].len; - total_len += buflis[i].rlen; - } - - if (nreq > 0) { - - // Prepare a request header - ClientRequest readvFileRequest; - memset( &readvFileRequest, 0, sizeof(readvFileRequest) ); - xrdc->SetSID(readvFileRequest.header.streamid); - readvFileRequest.header.requestid = kXR_readv; - readvFileRequest.readv.dlen = nreq * sizeof(struct readahead_list); - - if (destbuf) { - // A buffer able to hold the data and the info about the chunks - char *res_buf = new char[total_len + (nreq * sizeof(struct readahead_list))]; - - clientMarshallReadAheadList(buflis, readvFileRequest.readv.dlen); - bool r = xrdc->SendGenCommand(&readvFileRequest, buflis, 0, - (void *)res_buf, FALSE, (char *)"ReadV"); - clientUnMarshallReadAheadList(buflis, readvFileRequest.readv.dlen); - - if ( r ) { - - total_len = UnpackReadVResp(destbuf, res_buf, - xrdc->LastServerResp.dlen, - buflis, - nreq); - } - else - total_len = -1; - - delete [] res_buf; - } - else { - clientMarshallReadAheadList(buflis, readvFileRequest.readv.dlen); - if (xrdc->WriteToServer_Async(&readvFileRequest, - buflis) != kOK ) - total_len = 0; - - } - - } - - Info(XrdClientDebug::kHIDEBUG, "ReqReadV", - "Returning: total_len " << total_len); - return total_len; -} - - -// Picks a readv response and puts the individual chunks into the dest buffer -kXR_int32 XrdClientReadV::UnpackReadVResp(char *destbuf, char *respdata, kXR_int32 respdatalen, - readahead_list *buflis, int nbuf) { - - int res = respdatalen; - - // I just rebuild the readahead_list element - struct readahead_list header; - kXR_int32 pos_from = 0, pos_to = 0; - int i = 0; - kXR_int64 cur_buf_offset = -1; - int cur_buf_len = 0, cur_buf = 0; - - while ( (pos_from < respdatalen) && (i < nbuf) ) { - memcpy(&header, respdata + pos_from, sizeof(struct readahead_list)); - - kXR_int64 tmpl; - memcpy(&tmpl, &header.offset, sizeof(kXR_int64) ); - tmpl = ntohll(tmpl); - memcpy(&header.offset, &tmpl, sizeof(kXR_int64) ); - - header.rlen = ntohl(header.rlen); - - // Do some consistency checks - if (cur_buf_len == 0) { - cur_buf_offset = header.offset; - if (cur_buf_offset != buflis[cur_buf].offset) { - res = -1; - break; - } - cur_buf_len += header.rlen; - if (cur_buf_len > buflis[cur_buf].rlen) { - res = -1; - break; - } - if (cur_buf_len == buflis[cur_buf].rlen) { - cur_buf++; - cur_buf_len = 0; - } - } - - pos_from += sizeof(struct readahead_list); - memcpy( &destbuf[pos_to], &respdata[pos_from], header.rlen); - pos_from += header.rlen; - pos_to += header.rlen; - i++; - } - - if (pos_from != respdatalen || i != nbuf) - Error("UnpackReadVResp","Inconsistency: pos_from " << pos_from << - " respdatalen " << respdatalen << - " i " << i << - " nbuf " << nbuf ); - - if (res > 0) - res = pos_to; - - return res; -} - -// Picks a readv response and puts the individual chunks into the cache -int XrdClientReadV::SubmitToCacheReadVResp(XrdClientConn *xrdc, char *respdata, - kXR_int32 respdatalen) { - - // This probably means that the server doesnt support ReadV - // ( old version of the server ) - int res = -1; - - - res = respdatalen; - - // I just rebuild the readahead_list element - - struct readahead_list header; - kXR_int32 pos_from = 0; - kXR_int32 rlen = 0; - kXR_int64 offs=0; - -// // Just to log the entries -// while ( pos_from < respdatalen ) { -// header = ( readahead_list * )(respdata + pos_from); - -// memcpy(&offs, &header->offset, sizeof(kXR_int64) ); -// offs = ntohll(offs); -// rlen = ntohl(header->rlen); - -// pos_from += sizeof(struct readahead_list); - -// Info(XrdClientDebug::kHIDEBUG, "ReadV", -// "Received chunk " << rlen << " @ " << offs ); - -// pos_from += rlen; -// } - - pos_from = 0; - - - while ( pos_from < respdatalen ) { - memcpy(&header, respdata + pos_from, sizeof(struct readahead_list)); - - offs = ntohll(header.offset); - rlen = ntohl(header.rlen); - - pos_from += sizeof(struct readahead_list); - - // NOTE: we must duplicate the buffer to be submitted, since a cache block has to be - // contained in one single memblock, while here we have one for multiple chunks. - void *newbuf = malloc(rlen); - memcpy(newbuf, &respdata[pos_from], rlen); - - xrdc->SubmitRawDataToCache(newbuf, offs, offs + rlen - 1); - - pos_from += rlen; - - } - res = pos_from; - - free( respdata ); - - return res; - - - -} - - - -void XrdClientReadV::PreProcessChunkRequest(XrdClientVector &reqvect, - kXR_int64 offs, kXR_int32 len, - kXR_int64 filelen, - kXR_int32 spltsize) { - // Process a single subchunk request, eventually splitting it into more than one - - kXR_int32 len_ok = 0; - kXR_int32 newlen = xrdmin(filelen - offs, len); - - // We want blocks whose len does not exceed READV_MAXCHUNKSIZE - spltsize = xrdmin(spltsize, READV_MAXCHUNKSIZE); - - while (len_ok < newlen) { - XrdClientReadVinfo nfo; - - nfo.offset = offs+len_ok; - nfo.len = xrdmin(newlen-len_ok, spltsize); - - reqvect.Push_back(nfo); - - len_ok += nfo.len; - } - -} - - diff --git a/src/XrdClient/XrdClientReadV.hh b/src/XrdClient/XrdClientReadV.hh deleted file mode 100644 index f993ee103e5..00000000000 --- a/src/XrdClient/XrdClientReadV.hh +++ /dev/null @@ -1,72 +0,0 @@ -#ifndef XRD_CLIENT_READV -#define XRD_CLIENT_READV -/******************************************************************************/ -/* */ -/* X r d C l i e n t R e a d V . h h */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2006) */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// Helper functions for the vectored read functionality // -// // -////////////////////////////////////////////////////////////////////////// - -class XrdClientConn; -#include "XProtocol/XPtypes.hh" -#include "XProtocol/XProtocol.hh" -#include "XrdClient/XrdClientVector.hh" - -struct XrdClientReadVinfo { - kXR_int64 offset; - kXR_int32 len; -}; - -class XrdClientReadV { -public: - - // Builds a request and sends it to the server - // If destbuf == 0 the request is sent asynchronously - static kXR_int64 ReqReadV(XrdClientConn *xrdc, char *handle, char *destbuf, - XrdClientVector &reqvect, - int firstreq, int nreq, int streamtosend); - - // Picks a readv response and puts the individual chunks into the dest buffer - static kXR_int32 UnpackReadVResp(char *destbuf, char *respdata, kXR_int32 respdatalen, - readahead_list *buflis, int nbuf); - - // Picks a readv response and puts the individual chunks into the cache - static kXR_int32 SubmitToCacheReadVResp(XrdClientConn *xrdc, char *respdata, - kXR_int32 respdatalen); - - static void PreProcessChunkRequest(XrdClientVector &reqvect, - kXR_int64 offs, kXR_int32 len, - kXR_int64 filelen); - - static void PreProcessChunkRequest(XrdClientVector &reqvect, - kXR_int64 offs, kXR_int32 len, - kXR_int64 filelen, - kXR_int32 spltsize); -}; -#endif diff --git a/src/XrdClient/XrdClientSid.cc b/src/XrdClient/XrdClientSid.cc deleted file mode 100644 index 43e962379f1..00000000000 --- a/src/XrdClient/XrdClientSid.cc +++ /dev/null @@ -1,282 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C l i e n t S i d . c c */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2005) */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// Utility classes to handle the mapping between xrootd streamids. // -// A single streamid can have multiple "parallel" streamids. // -// Their use is typically to support the client to submit multiple // -// parallel requests (belonging to the same LogConnectionID), // -// which are to be processed asynchronously when the answers arrive. // -// // -////////////////////////////////////////////////////////////////////////// - -#include "XrdClient/XrdClientSid.hh" -#include "XrdClient/XrdClientEnv.hh" -#include "XrdClient/XrdClientConst.hh" - -XrdClientSid::XrdClientSid() { - - freesids.Resize(65536); - - // We populate the free sids queue - for (kXR_unt16 i = 65535; i >= 1; i--) - freesids.Push_back(i); -} - -XrdClientSid::~XrdClientSid() { - freesids.Clear(); - childsidnfo.Purge(); - -} - - -// Gets an available sid -// From now on it will be no more available. -// A retval of 0 means that there are no more available sids -kXR_unt16 XrdClientSid::GetNewSid() { - XrdSysMutexHelper l(fMutex); - - if (!freesids.GetSize()) return 0; - - return (freesids.Pop_back()); - -}; - -// Gets an available sid for a request which is to be outstanding -// This means that this sid will be inserted into the Rash -// The request gets inserted the new sid in the right place -// Also the one passed as parameter gets the new sid, as should be expected -kXR_unt16 XrdClientSid::GetNewSid(kXR_unt16 sid, ClientRequest *req) { - XrdSysMutexHelper l(fMutex); - - if (!freesids.GetSize()) return 0; - - kXR_unt16 nsid = freesids.Pop_back(); - - if (nsid) { - struct SidInfo si; - - memcpy(req->header.streamid, &nsid, sizeof(req->header.streamid)); - - si.fathersid = sid; - si.outstandingreq = *req; - si.reqbyteprogress = 0; - si.sendtime = time(0); - - si.rspstatuscode = 0; - si.rsperrno = kXR_noErrorYet; - si.rsperrmsg = 0; - - childsidnfo.Add(nsid, si); - } - - - - return nsid; - -}; - -// Report the response for an outstanding request -// Typically this is used to keep track of the received errors, expecially -// for async writes -void XrdClientSid::ReportSidResp(kXR_unt16 sid, kXR_unt16 statuscode, kXR_unt32 errcode, char *errmsg) { - XrdSysMutexHelper l(fMutex); - struct SidInfo *si = childsidnfo.Find(sid); - - if (si) { - si->rspstatuscode = statuscode; - si->rsperrno = errcode; - if (si->rsperrmsg) free(si->rsperrmsg); - - if (errmsg) si->rsperrmsg = strdup(errmsg); - else si->rsperrmsg = 0; - } - -}; - -// Releases a sid. -// It is re-inserted into the available set -// Its info is rmeoved from the tree -void XrdClientSid::ReleaseSid(kXR_unt16 sid) { - XrdSysMutexHelper l(fMutex); - - childsidnfo.Del(sid); - freesids.Push_back(sid); -}; - - - -//_____________________________________________________________________________ -struct ReleaseSidTreeItem_data { - kXR_unt16 fathersid; - XrdClientVector *freesids; -}; -int ReleaseSidTreeItem(kXR_unt16 key, - struct SidInfo si, void *arg) { - - ReleaseSidTreeItem_data *data = (ReleaseSidTreeItem_data *)arg; - - // If the sid we have is a son of the given father then delete it - if (si.fathersid == data->fathersid) { - free(si.rsperrmsg); - data->freesids->Push_back(key); - return -1; - } - - return 0; -} - -// Releases a sid and all its childs -void XrdClientSid::ReleaseSidTree(kXR_unt16 fathersid) { - XrdSysMutexHelper l(fMutex); - - ReleaseSidTreeItem_data data; - data.fathersid = fathersid; - data.freesids = &freesids; - - childsidnfo.Apply(ReleaseSidTreeItem, static_cast(&data)); - freesids.Push_back(fathersid); -} - - - - - -static int printoutreq(kXR_unt16, - struct SidInfo p, void *) { - - smartPrintClientHeader(&p.outstandingreq); - return 0; -} - -void XrdClientSid::PrintoutOutstandingRequests() { - cerr << "-------------------------------------------------- start outstanding reqs dump. freesids: " << freesids.GetSize() << endl; - XrdSysMutexHelper l(fMutex); - childsidnfo.Apply(printoutreq, this); - cerr << "++++++++++++++++++++++++++++++++++++++++++++++++++++ end outstanding reqs dump." << endl; -} - - -struct sniffOutstandingFailedWriteReq_data { - XrdClientVector *reqs; - kXR_unt16 fathersid; - XrdClientVector *freesids; -}; -static int sniffOutstandingFailedWriteReq(kXR_unt16 sid, - struct SidInfo p, void *d) { - - sniffOutstandingFailedWriteReq_data *data = (sniffOutstandingFailedWriteReq_data *)d; - if ((p.fathersid == data->fathersid) && - (p.outstandingreq.header.requestid == kXR_write)) { - - // If it went into timeout or got a negative response - // we add this req to the vector - if ( (time(0) - p.sendtime > EnvGetLong(NAME_REQUESTTIMEOUT)) || - (p.rspstatuscode != kXR_ok) || (p.rsperrno != kXR_noErrorYet) ) { - data->reqs->Push_back(p.outstandingreq); - - // And we release the failed sid - free(p.rsperrmsg); - data->freesids->Push_back(sid); - return -1; - } - - } - - // smartPrintClientHeader(&p.outstandingreq); - return 0; -} - -static int sniffOutstandingAllWriteReq(kXR_unt16 sid, - struct SidInfo p, void *d) { - - sniffOutstandingFailedWriteReq_data *data = (sniffOutstandingFailedWriteReq_data *)d; - if ((p.fathersid == data->fathersid) && - (p.outstandingreq.header.requestid == kXR_write)) { - - // we add this req to the vector - data->reqs->Push_back(p.outstandingreq); - - // And we release the failed sid - free(p.rsperrmsg); - data->freesids->Push_back(sid); - return -1; - } - - // smartPrintClientHeader(&p.outstandingreq); - return 0; -} - -struct countOutstandingWriteReq_data { - int cnt; - kXR_unt16 fathersid; -}; -static int countOutstandingWriteReq(kXR_unt16 sid, - struct SidInfo p, void *c) { - - countOutstandingWriteReq_data *data = (countOutstandingWriteReq_data *)c; - - if ((p.fathersid == data->fathersid) && (p.outstandingreq.header.requestid == kXR_write)) - data->cnt++; - - // smartPrintClientHeader(&p.outstandingreq); - return 0; -} - -int XrdClientSid::GetFailedOutstandingWriteRequests(kXR_unt16 fathersid, XrdClientVector &reqvect) { - XrdSysMutexHelper l(fMutex); - sniffOutstandingFailedWriteReq_data data; - data.reqs = &reqvect; - data.fathersid = fathersid; - data.freesids = &freesids; - - childsidnfo.Apply(sniffOutstandingFailedWriteReq, (void *)&data); - return reqvect.GetSize(); -} - -int XrdClientSid::GetOutstandingWriteRequestCnt(kXR_unt16 fathersid) { - XrdSysMutexHelper l(fMutex); - countOutstandingWriteReq_data data; - data.fathersid = fathersid; - data.cnt = 0; - childsidnfo.Apply(countOutstandingWriteReq, (void *)&data); - return data.cnt; -} - - - -int XrdClientSid::GetAllOutstandingWriteRequests(kXR_unt16 fathersid, XrdClientVector &reqvect) { - XrdSysMutexHelper l(fMutex); - sniffOutstandingFailedWriteReq_data data; - data.reqs = &reqvect; - data.fathersid = fathersid; - data.freesids = &freesids; - - childsidnfo.Apply(sniffOutstandingAllWriteReq, (void *)&data); - return reqvect.GetSize(); -} diff --git a/src/XrdClient/XrdClientSid.hh b/src/XrdClient/XrdClientSid.hh deleted file mode 100644 index a5d612afa36..00000000000 --- a/src/XrdClient/XrdClientSid.hh +++ /dev/null @@ -1,130 +0,0 @@ -#ifndef XRC_SID_H -#define XRC_SID_H -/******************************************************************************/ -/* */ -/* X r d C l i e n t S i d . h h */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2005) */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// Utility classes to handle the mapping between xrootd streamids. // -// A single streamid can have multiple "parallel" streamids. // -// Their use is typically to support the client to submit multiple // -// parallel requests (belonging to the same LogConnectionID), // -// whose answers are to be processed asynchronously when they arrive. // -// // -////////////////////////////////////////////////////////////////////////// - -#include "XrdOuc/XrdOucRash.hh" -#include "XProtocol/XProtocol.hh" -#include "XrdClient/XrdClientProtocol.hh" -#include "XrdClient/XrdClientVector.hh" -#include "XrdSys/XrdSysPthread.hh" - -struct SidInfo { - kXR_unt16 fathersid; - ClientRequest outstandingreq; - long long reqbyteprogress; - time_t sendtime; - - kXR_unt16 rspstatuscode; - kXR_unt32 rsperrno; - char *rsperrmsg; -}; - -class XrdClientSid { - - private: - // Used to quickly get info about a sid being used - // as a child of another sid. A child sid is used to parallely - // interact with a server for the same logical connection using its father sid - // Only child sids are inserted here. If a sid is not here but is not free, - // then it's a father sid, i.e. normally a sid used for the non async xrootd traffic - // Remember: for any child sid, it's mandatory to keep the request - // which is outstanding for that stream. This struct can be used to - // read data, but also for preparing many files in advance. - XrdOucRash childsidnfo; - - // To quickly get a sid which is not being used - // This one has constant time operations if the ops - // are performed at the back of the vector - // Remember: 0 is NOT a valid sid - XrdClientVector freesids; - - XrdSysMutex fMutex; - - public: - XrdClientSid(); - virtual ~XrdClientSid(); - - // Gets an available sid - // From now on it will be no more available. - // A retval of 0 means that there are no more available sids - kXR_unt16 GetNewSid(); - - // Gets an available sid for a request which is to be outstanding - // This means that this sid will be inserted into the Rash - // The request gets inserted the new sid in the right place - // Also the one passed as parameter gets the new sid, as should be expected - kXR_unt16 GetNewSid(kXR_unt16 sid, ClientRequest *req); - - - // Releases a sid. - // It is re-inserted into the available set - // Its info is rmeoved from the tree - void ReleaseSid(kXR_unt16 sid); - - // Releases a sid and all its childs - void ReleaseSidTree(kXR_unt16 fathersid); - - // Report the response for an outstanding request - // Typically this is used to keep track of the received errors, expecially - // for async writes - void ReportSidResp(kXR_unt16 sid, kXR_unt16 statuscode, kXR_unt32 errcode, char *errmsg); - - int GetFailedOutstandingWriteRequests(kXR_unt16 fathersid, XrdClientVector &reqvect); - int GetAllOutstandingWriteRequests(kXR_unt16 fathersid, XrdClientVector &reqvect); - int GetOutstandingWriteRequestCnt(kXR_unt16 fathersid); - - // 0 if non existent as a child sid - inline struct SidInfo *GetSidInfo(kXR_unt16 sid) { - XrdSysMutexHelper l(fMutex); - return (childsidnfo.Find(sid)); - }; - - inline bool JoinedSids(kXR_unt16 father, kXR_unt16 child) { - XrdSysMutexHelper l(fMutex); - - struct SidInfo *si = childsidnfo.Find(child); - - if (!si) return false; - return (si->fathersid == father); - } - - - // Useful for debugging - void PrintoutOutstandingRequests(); -}; -#endif diff --git a/src/XrdClient/XrdClientSock.cc b/src/XrdClient/XrdClientSock.cc deleted file mode 100644 index 8d8b29003c5..00000000000 --- a/src/XrdClient/XrdClientSock.cc +++ /dev/null @@ -1,515 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C l i e n t S o c k . c c */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2004) */ -/* Adapted from TXNetFile (root.cern.ch) originally done by */ -/* Alvise Dorigo, Fabrizio Furano */ -/* INFN Padova, 2003 */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// Client Socket with timeout features using XrdNet // -// // -////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include -#include -#include "XrdClient/XrdClientSock.hh" -#include "XrdSys/XrdSysLogger.hh" -#include "XrdNet/XrdNetSocket.hh" -#include "XrdNet/XrdNetOpts.hh" -#include "XrdOuc/XrdOucUtils.hh" -#include "XrdSys/XrdSysPlatform.hh" -#include "XrdClient/XrdClientDebug.hh" -#include "XrdClient/XrdClientEnv.hh" - -#ifndef WIN32 -#include -#include -#include -#include -#else -#include "XrdSys/XrdWin32.hh" -#endif - -#ifdef __FreeBSD__ -#include -#endif - -#if __cplusplus >= 201103L -#define XRDCL_SMART_PTR_T std::unique_ptr -#else -#define XRDCL_SMART_PTR_T std::auto_ptr -#endif - -//_____________________________________________________________________________ -XrdClientSock::XrdClientSock(XrdClientUrlInfo Host, int windowsize, int fd) -{ - // Constructor - - fHost.TcpHost = Host; - fHost.TcpWindowSize = windowsize; - fRDInterrupt = 0; - fWRInterrupt = 0; - fSocket = fd; - - if( fSocket >= 0 ) - fConnected = TRUE; - else - fConnected = FALSE; - - fRequestTimeout = EnvGetLong(NAME_REQUESTTIMEOUT); -} - -//_____________________________________________________________________________ -XrdClientSock::~XrdClientSock() -{ - // Destructor - Disconnect(); -} - -//_____________________________________________________________________________ -void XrdClientSock::SetRequestTimeout(int timeout) -{ - // Set request timeout. If timeout is non-positive reset the request - // timeout to the default value - - fRequestTimeout = (timeout > 0) ? timeout : EnvGetLong(NAME_REQUESTTIMEOUT); -} - -//_____________________________________________________________________________ -void XrdClientSock::Disconnect() -{ - // Close the connection -// if (fConnected && fSocket >= 0) { -// ::close(fSocket); -// fConnected = FALSE; -// fSocket = -1; -// } - if (fSocket >= 0) { - ::close(fSocket); - } - - fConnected = false; - fSocket = -1; - -} - -//_____________________________________________________________________________ -int XrdClientSock::RecvRaw(void* buffer, int length, int substreamid, - int *usedsubstreamid) -{ - // Read bytes following carefully the timeout rules - struct pollfd fds_r; - int bytesread = 0; - int pollRet; - - // We cycle reading data. - // An exit occurs if: - // We have all the data we are waiting for - // Or a timeout occurs - // The connection is closed by the other peer - - // Init of the pollfd struct - if (fSocket < 0) { - Error("XrdClientSock::RecvRaw", "socket fd is " << fSocket); - return TXSOCK_ERR; - } - - fds_r.fd = fSocket; - // fds_r.events = POLLIN | POLLPRI | POLLERR | POLLHUP | POLLNVAL; - fds_r.events = POLLIN; - - while (bytesread < length) { - - // We cycle on the poll, ignoring the possible interruptions - // We are waiting for something to come from the socket - - // We cycle on the poll, ignoring the possible interruptions - // We are waiting for something to come from the socket, - // but we will not wait forever - int timeleft = fRequestTimeout; - do { - // Wait for some event from the socket - pollRet = poll(&fds_r, - 1, - 1000 // 1 second as a step - ); - - if ((pollRet < 0) && (errno != EINTR) && (errno != EAGAIN) ) - return TXSOCK_ERR; - - } while (--timeleft && pollRet <= 0 && !fRDInterrupt); - - - // If we are here, pollRet is > 0 why? - // Because the timeout and the poll error are handled inside the previous loop - - if (fSocket < 0) { - if (fConnected) { - Error("XrdClientSock::RecvRaw", "since we entered RecvRaw, socket " - "file descriptor has changed to " << fSocket); - fConnected = FALSE; - } - return TXSOCK_ERR; - } - - // If we have been timed-out, return a specific error code - if (timeleft <= 0) { - if ((DebugLevel() >= XrdClientDebug::kDUMPDEBUG)) - Info(XrdClientDebug::kNODEBUG, - "ClientSock::RecvRaw", - "Request timed out "<< fRequestTimeout << - "seconds reading " << length << " bytes" << - " from server " << fHost.TcpHost.Host << - ":" << fHost.TcpHost.Port); - return TXSOCK_ERR_TIMEOUT; - } - - // If we have been interrupt, reset the inetrrupt and exit - if (fRDInterrupt) { - fRDInterrupt = 0; - Error("XrdClientSock::RecvRaw", "got interrupt"); - return TXSOCK_ERR_INTERRUPT; - } - - - // First of all, we check if there is something to read - if (fds_r.revents & (POLLIN | POLLPRI)) { - int n = 0; - - do { - n = ::recv(fSocket, static_cast(buffer) + bytesread, - length - bytesread, 0); - } while(n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)); - - // If we read nothing, the connection has been closed by the other side - if (n <= 0) { - if (errno > 0) { - Error("XrdClientSock::RecvRaw", "Error reading from socket: " << - ::strerror(errno)); - } - return TXSOCK_ERR; - } - bytesread += n; - } - - // Then we check if poll reports a complaint from the socket like disconnections - if (fds_r.revents & (POLLERR | POLLHUP | POLLNVAL)) { - Error("ClientSock::RecvRaw", - "Disconnection detected reading " << length << - " bytes from socket " << fds_r.fd << - " (server[" << fHost.TcpHost.Host << - ":" << fHost.TcpHost.Port << - "]). Revents=" << fds_r.revents ); - return TXSOCK_ERR; - } - - } // while - - // Return number of bytes received - return bytesread; -} - -//_____________________________________________________________________________ -int XrdClientSock::SendRaw_sock(const void* buffer, int length, int sock) -{ - // Write bytes following carefully the timeout rules - // (writes will not hang) - - struct pollfd fds_w; - int byteswritten = 0; - int pollRet; - - // Init of the pollfd structs. If sock is not valid... we can do this anyway - fds_w.fd = sock; - - fds_w.events = POLLOUT | POLLERR | POLLHUP | POLLNVAL; - - // We cycle until we write all we have to write - // Or until a timeout occurs - - while (byteswritten < length) { - - // We will not wait forever - int timeleft = fRequestTimeout; - do { - // Wait for some event from the socket - pollRet = poll(&fds_w, - 1, - 1000 // 1 second as a step - ); - if (((pollRet < 0) && (errno != EINTR)) || !fConnected) - return TXSOCK_ERR; - - } while (--timeleft && pollRet <= 0 && !fWRInterrupt); - - // If we have been timed-out, return a specific error code - if (timeleft <= 0) { //gEnv - Error("ClientSock::SendRaw", - "Request timed out "<< fRequestTimeout << //gEnv - "seconds writing " << length << " bytes" << - " to server " << fHost.TcpHost.Host << - ":" << fHost.TcpHost.Port); - - return TXSOCK_ERR_TIMEOUT; - } - - // If we have been interrupt, reset the interrupt and exit - if (fWRInterrupt) { - fWRInterrupt = 0; - Error("XrdClientSock::SendRaw", "got interrupt"); - return TXSOCK_ERR_INTERRUPT; - } - - // First of all, we check if we are allowed to write - if (fds_w.revents & POLLOUT) { - - // We will be retrying on errors like EAGAIN or EWOULDBLOCK, - // but not forever - timeleft = fRequestTimeout; - int n = -1; - while (n <= 0 && timeleft--) { - if ((n = send(sock, static_cast(buffer) + byteswritten, - length - byteswritten, 0)) <= 0) { - if (timeleft <= 0 || (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)) { - // Real error: nothing more to do! - // If we wrote nothing, the connection has been closed by the other - Error("ClientSock::SendRaw", "Error writing to a socket: " << - ::strerror(errno)); - return (TXSOCK_ERR); - } else { - // Sleep one second - sleep(1); - } - } - } - byteswritten += n; - } - - // Then we check if poll reports a complaint from the socket like disconnections - if (fds_w.revents & (POLLERR | POLLHUP | POLLNVAL)) { - - Error("ClientSock::SendRaw", - "Disconnection detected writing " << length << - " bytes to socket " << fds_w.fd << - " (server[" << fHost.TcpHost.Host << - ":" << fHost.TcpHost.Port << - "]). Revents=" << fds_w.revents ); - return TXSOCK_ERR; - } - - } // while - - // Return number of bytes sent - return byteswritten; -} - -//_____________________________________________________________________________ -int XrdClientSock::SendRaw(const void* buffer, int length, int substreamid) -{ - // Note: here substreamid is used as "alternative socket" instead of fSocket - - if (substreamid > 0) - return SendRaw_sock(buffer, length, substreamid); - else - return SendRaw_sock(buffer, length, fSocket); -} - -//_____________________________________________________________________________ -void XrdClientSock::TryConnect(bool isUnix) -{ - // Already connected - we are done. - // - if (fConnected) { - assert(fSocket >= 0); - return; - } - - - fSocket = TryConnect_low(isUnix); - - if (fSocket >= 0) { - - // If we are using a SOCKS4 host... - if ( EnvGetString(NAME_SOCKS4HOST) ) { - - Info(XrdClientDebug::kHIDEBUG, "ClientSock::TryConnect", "Handshaking with SOCKS4 host"); - - switch (Socks4Handshake(fSocket)) { - - case 90: - // Everything OK! - Info(XrdClientDebug::kHIDEBUG, "ClientSock::TryConnect", "SOCKS4 connection OK"); - break; - case 91: - case 92: - case 93: - // Failed - Info(XrdClientDebug::kHIDEBUG, "ClientSock::TryConnect", - "SOCKS host refused the connection."); - Disconnect(); - break; - } - - - - } - - } -} - -//_____________________________________________________________________________ -int XrdClientSock::TryConnect_low(bool isUnix, int altport, int windowsz) -{ - int sock = -1; - XrdOucString host; - int port; - if (!windowsz) windowsz = EnvGetLong(NAME_DFLTTCPWINDOWSIZE); - - - host = EnvGetString(NAME_SOCKS4HOST); - port = EnvGetLong(NAME_SOCKS4PORT); - - if (host.length() == 0) { - host = fHost.TcpHost.HostAddr; - port = fHost.TcpHost.Port; - - if (altport) port = altport; - } - else - Info(XrdClientDebug::kHIDEBUG, "ClientSock::TryConnect_low", "Trying SOCKS4 host " << - host << ":" << port); - - XRDCL_SMART_PTR_T s(new XrdNetSocket()); - - // Log the attempt - // - if (!isUnix) { - Info(XrdClientDebug::kHIDEBUG, "ClientSock::TryConnect_low", - "Trying to connect to " << - fHost.TcpHost.Host << "(" << host << "):" << - port << " Windowsize=" << windowsz << " Timeout=" << EnvGetLong(NAME_CONNECTTIMEOUT)); - - // Connect to a remote host - // - if (port) - sock = s->Open(host.c_str(), - port, EnvGetLong(NAME_CONNECTTIMEOUT), - windowsz ); - } else { - Info(XrdClientDebug::kHIDEBUG, "ClientSock::TryConnect_low", - "Trying to UNIX connect to" << fHost.TcpHost.File << - "; timeout=" << EnvGetLong(NAME_CONNECTTIMEOUT)); - - // Connect to a remote host - // - sock = s->Open(fHost.TcpHost.File.c_str(), -1, EnvGetLong(NAME_CONNECTTIMEOUT)); - } - - // Check if we really got a connection and the remote host is available - // - if (sock < 0) { - if (isUnix) { - Info(XrdClientDebug::kHIDEBUG, "ClientSock::TryConnect_low", "Connection to" << - fHost.TcpHost.File << " failed. (" << sock << ")"); - } else { - Info(XrdClientDebug::kHIDEBUG, "ClientSock::TryConnect_low", "Connection to" << - fHost.TcpHost.Host << ":" << fHost.TcpHost.Port << " failed. (" << sock << ")"); - } - - } else { - fConnected = TRUE; - int detachedFD = s->Detach(); - if (sock != detachedFD) { - Error("ClientSock::TryConnect_low", - "Socket detach returned " << detachedFD << " but expected " << sock); - } - } - - //-------------------------------------------------------------------------- - // Set the keepalive options - //-------------------------------------------------------------------------- - if( !isUnix && EnvGetLong( NAME_ENABLE_TCP_KEEPALIVE ) ) - { - if( XrdNetSocket::setOpts( sock, XRDNET_KEEPALIVE, 0 ) != 0 ) - Error( "ClientSock::TryConnect_low", "Unable to set the TCP Keep Alive option" ); - -#if defined(__linux__) && defined( TCP_KEEPIDLE ) && defined( TCP_KEEPINTVL ) && defined( TCP_KEEPCNT ) - - int val = EnvGetLong( NAME_TCP_KEEPALIVE_TIME ); - if( setsockopt( sock, SOL_TCP, TCP_KEEPIDLE, (char *)&val, sizeof( val ) ) < 0 ) - Error( "ClientSock::TryConnect_low", "Unable to set the TCP Keep Alive Time" ); - - val = EnvGetLong( NAME_TCP_KEEPALIVE_INTERVAL ); - if( setsockopt( sock, SOL_TCP, TCP_KEEPINTVL, (char *)&val, sizeof( val ) ) < 0 ) - Error( "ClientSock::TryConnect_low", "Unable to set the TCP Keep Alive Interval" ); - - val = EnvGetLong( NAME_TCP_KEEPALIVE_PROBES ); - if( setsockopt( sock, SOL_TCP, TCP_KEEPCNT, (char *)&val, sizeof( val ) ) < 0 ) - Error( "ClientSock::TryConnect_low", "Unable to set the TCP Keep Alive Probes" ); - -#endif - - } - - return sock; -} - -int XrdClientSock::Socks4Handshake(int sockid) { - - char buf[4096], userid[256]; - uint16_t port; - char a, b, c, d; - - // Issue a Connect req - buf[0] = 4; // Socks version - buf[1] = 1; // Connect request - - port = htons(fHost.TcpHost.Port); - memcpy(buf+2, &port, sizeof(port)); // The port - - sscanf(fHost.TcpHost.HostAddr.c_str(), "%hhd.%hhd.%hhd.%hhd", &a, &b, &c, &d ); - buf[4] = a; - buf[5] = b; - buf[6] = c; - buf[7] = d; - - if (XrdOucUtils::UserName(geteuid(), userid, sizeof(userid))) *userid = 0; - strcpy(buf+8, userid); - - // send the buffer to the server! - SendRaw(buf, 8+strlen(userid)+1, sockid); - - // Now wait the answer on the same sock - RecvRaw(buf, 8, sockid); - - return buf[1]; - - -} diff --git a/src/XrdClient/XrdClientSock.hh b/src/XrdClient/XrdClientSock.hh deleted file mode 100644 index 84dce51bf20..00000000000 --- a/src/XrdClient/XrdClientSock.hh +++ /dev/null @@ -1,151 +0,0 @@ -#ifndef XRC_SOCK_H -#define XRC_SOCK_H -/******************************************************************************/ -/* */ -/* X r d C l i e n t S o c k . h h */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2004) */ -/* Adapted from TXNetFile (root.cern.ch) originally done by */ -/* Alvise Dorigo, Fabrizio Furano */ -/* INFN Padova, 2003 */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// Client Socket with timeout features // -// // -// June 06 - Fabrizio Furano // -// The function prototypes allow specializations for multistream xfer // -// purposes. In this class only monostream xfers are allowed. // -// // -////////////////////////////////////////////////////////////////////////// - -#include - -struct XrdClientSockConnectParms { - XrdClientUrlInfo TcpHost; - int TcpWindowSize; -}; - -class XrdClientSock { -public: - typedef int Sockid; - typedef int Sockdescr; - - friend class XrdClientPhyConnection; - -private: - - int fSocket; - -protected: - - - int fRequestTimeout; - XrdClientSockConnectParms fHost; - - bool fConnected; - bool fRDInterrupt; - bool fWRInterrupt; - - // Tells if we have to reinit the table of the fd selectors - // after adding or removing one of them - bool fReinit_fd; - - virtual int SaveSocket() { int fd = fSocket; fSocket = -1; - fConnected = 0; fRDInterrupt = 0; fWRInterrupt = 0; return fd; } - - void SetInterrupt(int which = 0) { if (which == 0 || which == 1) fRDInterrupt = 1; - if (which == 0 || which == 2) fWRInterrupt = 1; } - - // returns the socket descriptor or -1 - int TryConnect_low(bool isUnix = 0, int altport = 0, int windowsz = 0); - - // Send the buffer to the specified socket - virtual int SendRaw_sock(const void* buffer, int length, Sockdescr sock); -public: - - //-------------------------------------------------------------------------- - //! Construct a socket helper - //! - //! @param host Remote location to connect to - //! @param windowSize TCP window size: 0 for OS defaults or the ENV setting - //! @param fd A descriptor pointing to an already connected socket, - //! -1 if not available - //-------------------------------------------------------------------------- - XrdClientSock(XrdClientUrlInfo host, int windowsize = 0, int fd = -1 ); - virtual ~XrdClientSock(); - - virtual void BanSockDescr(Sockdescr, Sockid) {} - virtual void UnBanSockDescr(Sockdescr) { } - - // Makes a pending recvraw to rebuild the list of interesting selectors - void ReinitFDTable() { fReinit_fd = true; } - - // Gets length bytes from the specified substreamid - // If substreamid = 0 then use the main stream - // If substreamid = -1 then - // use any stream which has something pending - // and return its id in usedsubstreamid - // Note that in this base class only the multistream intf is provided - // but the implementation is monostream - virtual int RecvRaw(void* buffer, int length, Sockid substreamid = -1, - Sockid *usedsubstreamid = 0); - - // Send the buffer to the specified substream - // if substreamid <= 0 then use the main one - virtual int SendRaw(const void* buffer, int length, Sockid substreamid = 0); - - void SetRequestTimeout(int timeout = -1); - - // Performs a SOCKS4 handshake in a given stream - // Returns the handshake result - // If successful, we are connected through a socks4 proxy - virtual int Socks4Handshake(Sockid sockid); - - virtual void TryConnect(bool isUnix = 0); - - // Returns a temporary socket id or -1 - // The temporary given sock id is to be used to send the kxr_bind_request - // If all this goes ok, then the caller must call EstablishParallelSock, otherwise the - // creation of parallel streams should be aborted (but the already created streams are OK) - virtual Sockdescr TryConnectParallelSock(int /*port*/, int /*windowsz*/, Sockid &/*tmpid*/) { return -1; } - - // Attach the pending (and hidden) sock - // to the given substreamid - // the given substreamid could be an integer suggested by the server - virtual int EstablishParallelSock(Sockid /*tmpsockid*/, Sockid /*newsockid*/) { return -1; } - - virtual int RemoveParallelSock(Sockid /* sockid */) { return -1; }; - - // Suggests a sockid to be used for a req - virtual Sockid GetSockIdHint(int /* reqsperstream */ ) { return 0; } - - virtual void Disconnect(); - - bool IsConnected() {return fConnected;} - virtual int GetSockIdCount() { return 1; } - virtual void PauseSelectOnSubstream(Sockid /* substreamid */) { } - virtual void RestartSelectOnSubstream(Sockid /*substreamid */) { } -}; -#endif diff --git a/src/XrdClient/XrdClientThread.cc b/src/XrdClient/XrdClientThread.cc deleted file mode 100644 index edcea207fe4..00000000000 --- a/src/XrdClient/XrdClientThread.cc +++ /dev/null @@ -1,76 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C l i e n t T h r e a d . c c */ -/* */ -/* Author: F.Furano (INFN, 2005) */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// An user friendly thread wrapper // -// // -////////////////////////////////////////////////////////////////////////// - -#include -#include - -#include "XrdClient/XrdClientThread.hh" - -//_____________________________________________________________________________ -void * XrdClientThreadDispatcher(void * arg) -{ - // This function is launched by the thread implementation. Its purpose - // is to call the actual thread body, passing to it the original arg and - // a pointer to the thread object which launched it. - - XrdClientThread::XrdClientThreadArgs *args = (XrdClientThread::XrdClientThreadArgs *)arg; - - args->threadobj->SetCancelDeferred(); - args->threadobj->SetCancelOn(); - - if (args->threadobj->ThreadFunc) - return args->threadobj->ThreadFunc(args->arg, args->threadobj); - - return 0; - -} - -//_____________________________________________________________________________ -int XrdClientThread::MaskSignal(int snum, bool block) -{ - // Modify masking for signal snum: if block is true the signal is blocked, - // else is unblocked. If snum <= 0 (default) all the allowed signals are - // blocked / unblocked. -#ifndef WIN32 - sigset_t mask; - int how = block ? SIG_BLOCK : SIG_UNBLOCK; - if (snum <= 0) - sigfillset(&mask); - else sigaddset(&mask, snum); - return pthread_sigmask(how, &mask, 0); -#else - return 0; -#endif -} - - diff --git a/src/XrdClient/XrdClientThread.hh b/src/XrdClient/XrdClientThread.hh deleted file mode 100644 index 8d277d98036..00000000000 --- a/src/XrdClient/XrdClientThread.hh +++ /dev/null @@ -1,105 +0,0 @@ -#ifndef XRC_THREAD_H -#define XRC_THREAD_H -/******************************************************************************/ -/* */ -/* X r d C l i e n t T h r e a d . h h */ -/* */ -/* Author: F.Furano (INFN, 2005) */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// An user friendly thread wrapper // -// // -////////////////////////////////////////////////////////////////////////// - -#include "XrdSys/XrdSysPthread.hh" - -void * XrdClientThreadDispatcher(void * arg); - -class XrdClientThread { -private: - pthread_t fThr; - - typedef void *(*VoidRtnFunc_t)(void *, XrdClientThread *); - VoidRtnFunc_t ThreadFunc; - friend void *XrdClientThreadDispatcher(void *); - - public: - struct XrdClientThreadArgs { - void *arg; - XrdClientThread *threadobj; - } fArg; - - - XrdClientThread(VoidRtnFunc_t fn) { -#ifndef WIN32 - fThr = 0; -#endif - ThreadFunc = fn; - }; - - virtual ~XrdClientThread() { - -// Cancel(); - }; - - int Cancel() { - return XrdSysThread::Cancel(fThr); - }; - - int Run(void *arg = 0) { - fArg.arg = arg; - fArg.threadobj = this; - return XrdSysThread::Run(&fThr, XrdClientThreadDispatcher, (void *)&fArg, - XRDSYSTHREAD_HOLD, ""); - }; - - int Detach() { - return XrdSysThread::Detach(fThr); - }; - - int Join(void **ret = 0) { - return XrdSysThread::Join(fThr, ret); - }; - - // these funcs are to be called only from INSIDE the thread loop - int SetCancelOn() { - return XrdSysThread::SetCancelOn(); - }; - int SetCancelOff() { - return XrdSysThread::SetCancelOff(); - }; - int SetCancelAsynchronous() { - return XrdSysThread::SetCancelAsynchronous(); - }; - int SetCancelDeferred() { - return XrdSysThread::SetCancelDeferred(); - }; - void CancelPoint() { - XrdSysThread::CancelPoint(); - }; - - int MaskSignal(int snum = 0, bool block = 1); -}; -#endif diff --git a/src/XrdClient/XrdClientUnsolMsg.hh b/src/XrdClient/XrdClientUnsolMsg.hh deleted file mode 100644 index 0fdd8770f15..00000000000 --- a/src/XrdClient/XrdClientUnsolMsg.hh +++ /dev/null @@ -1,82 +0,0 @@ -#ifndef XRC_UNSOLMSG_H -#define XRC_UNSOLMSG_H -/******************************************************************************/ -/* */ -/* X r d C l i e n t U n s o l M s g . h h */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2004) */ -/* Adapted from TXNetFile (root.cern.ch) originally done by */ -/* Alvise Dorigo, Fabrizio Furano */ -/* INFN Padova, 2003 */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// Base classes for unsolicited msg senders/receivers // -// // -////////////////////////////////////////////////////////////////////////// - -class XrdClientMessage; -class XrdClientUnsolMsgSender; - -// The processing result for an unsolicited response -enum UnsolRespProcResult { - kUNSOL_CONTINUE = 0, // Dispatching must continue to other interested handlers - kUNSOL_KEEP, // Dispatching ended, but stream still alive (must keep the SID) - kUNSOL_DISPOSE // Dispatching ended, stream no more to be used -}; - -// Handler - -class XrdClientAbsUnsolMsgHandler { - public: - - virtual ~XrdClientAbsUnsolMsgHandler() { } - // To be called when an unsolicited response arrives from the lower layers - virtual UnsolRespProcResult ProcessUnsolicitedMsg(XrdClientUnsolMsgSender *sender, - XrdClientMessage *unsolmsg) = 0; - -}; - -// Sender - -class XrdClientUnsolMsgSender { - - public: - - virtual ~XrdClientUnsolMsgSender() { } - - // The upper level handler for unsolicited responses - XrdClientAbsUnsolMsgHandler *UnsolicitedMsgHandler; - - inline UnsolRespProcResult SendUnsolicitedMsg(XrdClientUnsolMsgSender *sender, XrdClientMessage *unsolmsg) { - // We simply send the event - if (UnsolicitedMsgHandler) - return (UnsolicitedMsgHandler->ProcessUnsolicitedMsg(sender, unsolmsg)); - - return kUNSOL_CONTINUE; - } - - inline XrdClientUnsolMsgSender() { UnsolicitedMsgHandler = 0; } -}; -#endif diff --git a/src/XrdClient/XrdClientUrlInfo.cc b/src/XrdClient/XrdClientUrlInfo.cc deleted file mode 100644 index 35d8d8a2667..00000000000 --- a/src/XrdClient/XrdClientUrlInfo.cc +++ /dev/null @@ -1,272 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C l i e n t U r l I n f o . c c */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2004) */ -/* Adapted from TXNetFile (root.cern.ch) originally done by */ -/* Alvise Dorigo, Fabrizio Furano, INFN Padova, 2003 */ -/* Revised by G. Ganis, CERN, June 2005 */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// Class handling information about an url // -// // -////////////////////////////////////////////////////////////////////////// - -#include "XrdClient/XrdClientDebug.hh" -#include "XrdClient/XrdClientUrlInfo.hh" -#include "XrdSys/XrdSysDNS.hh" -#ifndef WIN32 -#include -#include -#else -#include "XrdSys/XrdWin32.hh" -#endif -#include -#include - -//_____________________________________________________________________________ -XrdClientUrlInfo::XrdClientUrlInfo() -{ - // Default constructor - - Clear(); -} - -//_____________________________________________________________________________ -XrdClientUrlInfo::XrdClientUrlInfo(const char *url) -{ - // Constructor from a string specifying an url or multiple urls in the - // form: - // [proto://][user1@]host1:port1[,[user2@]host2:port2, ... , - // [userN@]hostN:portN]]/pathfile - - Clear(); - TakeUrl(XrdOucString(url)); -} - -//_____________________________________________________________________________ -XrdClientUrlInfo::XrdClientUrlInfo(const XrdOucString &url) -{ - // Constructor from a string specifying an url or multiple urls in the - // form: - // [proto://][user1@]host1:port1[,[user2@]host2:port2, ... , - // [userN@]hostN:portN]]/pathfile - - Clear(); - TakeUrl(url); -} - -//______________________________________________________________________________ -XrdClientUrlInfo::XrdClientUrlInfo(const XrdClientUrlInfo &url) -{ - // Copy constructor - - Proto = url.Proto; - User = url.User; - Passwd = url.Passwd; - Host = url.Host; - HostWPort= url.HostWPort; - HostAddr = url.HostAddr; - Port = url.Port; - File = url.File; - -} - -//_____________________________________________________________________________ -void XrdClientUrlInfo::Clear() { - // Set defaults - - Proto = ""; - User = ""; - Passwd = ""; - Host = ""; - HostWPort= ""; - HostAddr = ""; - Port = -1; - File = "/"; - -} - -//______________________________________________________________________________ -XrdClientUrlInfo& XrdClientUrlInfo::operator=(const XrdOucString &url) -{ - // Assign url to local url. - - this->TakeUrl(url); - - return (*this); -} - -//______________________________________________________________________________ -XrdClientUrlInfo& XrdClientUrlInfo::operator=(const XrdClientUrlInfo &url) -{ - // Assign url to local url. - - Proto = url.Proto; - User = url.User; - Passwd = url.Passwd; - Host = url.Host; - HostWPort= url.HostWPort; - HostAddr = url.HostAddr; - Port = url.Port; - File = url.File; - - return (*this); -} - -//_____________________________________________________________________________ -void XrdClientUrlInfo::TakeUrl(XrdOucString u) -{ - // Parse url character string and split in its different subcomponents. - // Use IsValid() to check if URL is legal. - // Url format: - // [proto://][user[:passwd]@]host:port/pathfile - // - int p1 = 0, p2 = STR_NPOS, p3 = STR_NPOS, left = u.length(); - - Clear(); - - Info(XrdClientDebug::kHIDEBUG,"TakeUrl", "parsing url: " << u); - - if (u.length() <= 0) return; - - // Get protocol - if ((p2 = u.find("://")) != STR_NPOS) { - Proto.assign(u, p1, p2-1); - Info(XrdClientDebug::kHIDEBUG,"TakeUrl", " Proto: " << Proto); - // Update start of search range and remaining length - p1 = p2 + 3; - left = u.length() - p1; - } - if (left <= 0) { - Clear(); - return; - } - - // Store the whole "[user[:passwd]@]host:port" thing in HostWPort - if ((p2 = u.find('/', p1)) != STR_NPOS) { - if (p2 > p1) { - HostWPort.assign(u, p1, p2-1); - // Update start of search range and remaining length - p1 = p2+1; - left = u.length() - p1; - } - } else { - HostWPort.assign(u, p1); - left = 0; - } - Info(XrdClientDebug::kHIDEBUG,"TakeUrl", " HostWPort: " << HostWPort); - - // Get pathfile - if (left > 0) - File.assign(u, p1); - Info(XrdClientDebug::kHIDEBUG,"TakeUrl", " File: " << File); - - // - // Resolve username, passwd, host and port. - p1 = 0; - left = HostWPort.length(); - // Get user/pwd - if ((p2 = HostWPort.find('@', p1)) != STR_NPOS) { - p3 = HostWPort.find(':', p1); - if (p3 != STR_NPOS && p3 < p2) { - User.assign(HostWPort, p1, p3-1); - Passwd.assign(HostWPort, p3+1, p2-1); - Info(XrdClientDebug::kHIDEBUG,"TakeUrl", " Passwd: " << Passwd); - } else { - User.assign(HostWPort, p1, p2-1); - } - Info(XrdClientDebug::kHIDEBUG,"TakeUrl", " User: " << User); - // Update start of search range and remaining length - p1 = p2 + 1; - left -= p1; - } - - // Get host:port - if ((p2 = HostWPort.find(':', p1)) != STR_NPOS ) { - Host.assign(HostWPort, p1, p2-1); - Port = strtol((HostWPort.c_str())+p2+1, (char **)0, 10); - } else { - Host.assign(HostWPort, p1); - Port = 0; - } - Info(XrdClientDebug::kHIDEBUG,"TakeUrl", " Host: " << Host); - Info(XrdClientDebug::kHIDEBUG,"TakeUrl", " Port: " << Port); -} - -//_____________________________________________________________________________ -XrdOucString XrdClientUrlInfo::GetUrl() -{ - // Get full url - // The fields might have been modified, so the full url - // must be reconstructed - - XrdOucString s; - - if (Proto != "") { - s = Proto; - s += "://"; - } - - if (User != "") { - s += User; - - if (Passwd != "") { - s += ":"; - s += Passwd; - } - - s += "@"; - } - - s += Host; - - if ( (Host != "") && (Port > 0) ) { - char buf[256]; - sprintf(buf, "%d", Port); - s += ":"; - s += buf; - } - - if (File != "") { - s += "/"; - s += File; - } - - return s; - -} - -//_____________________________________________________________________________ -void XrdClientUrlInfo::SetAddrFromHost() -{ - struct sockaddr_in ip[2]; - char buf[255], **errmsg = 0; - - int numaddr = XrdSysDNS::getHostAddr((char *)Host.c_str(), - (struct sockaddr *)ip, 1, errmsg); - if (numaddr > 0) - HostAddr = inet_ntop(ip[0].sin_family, &ip[0].sin_addr, buf, sizeof(buf)); -} diff --git a/src/XrdClient/XrdClientUrlInfo.hh b/src/XrdClient/XrdClientUrlInfo.hh deleted file mode 100644 index b607101cead..00000000000 --- a/src/XrdClient/XrdClientUrlInfo.hh +++ /dev/null @@ -1,77 +0,0 @@ -#ifndef _XRC_URLINFO_H -#define _XRC_URLINFO_H -/******************************************************************************/ -/* */ -/* X r d C l i e n t U r l I n f o . h h */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2004) */ -/* Adapted from TXNetFile (root.cern.ch) originally done by */ -/* Alvise Dorigo, Fabrizio Furano, INFN Padova, 2003 */ -/* Revised by G. Ganis, CERN, June 2005 */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// Class handling information about an url // -// The purpose of this class is to allow: // -// - parsing a string url into its components // -// - reading/writing the single components // -// - reading the modified full url // -// // -////////////////////////////////////////////////////////////////////////// - -#include "XrdOuc/XrdOucString.hh" - -// -// The information an url may contain -// Plus utilities for parsing and rebuilding an url -// - -class XrdClientUrlInfo { - public: - XrdOucString Proto; - XrdOucString Passwd; - XrdOucString User; - XrdOucString Host; - int Port; - XrdOucString HostAddr; - XrdOucString HostWPort; - XrdOucString File; - - void Clear(); - void TakeUrl(XrdOucString url); - XrdOucString GetUrl(); - - XrdClientUrlInfo(const char *url); - XrdClientUrlInfo(const XrdOucString &url); - XrdClientUrlInfo(const XrdClientUrlInfo &url); - XrdClientUrlInfo(); - - void SetAddrFromHost(); - - inline bool IsValid() { return (Port >= 0); } - - XrdClientUrlInfo &operator=(const XrdOucString &url); - XrdClientUrlInfo &operator=(const XrdClientUrlInfo &url); -}; -#endif diff --git a/src/XrdClient/XrdClientUrlSet.cc b/src/XrdClient/XrdClientUrlSet.cc deleted file mode 100644 index 50e0a332c4c..00000000000 --- a/src/XrdClient/XrdClientUrlSet.cc +++ /dev/null @@ -1,417 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C l i e n t U r l S e t . c c */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2004) */ -/* Adapted from TXNetFile (root.cern.ch) originally done by */ -/* Alvise Dorigo, Fabrizio Furano, INFN Padova, 2003 */ -/* Revised by G. Ganis, CERN, June 2005 */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// A container for multiple urls to be resolved through DNS aliases // -// // -////////////////////////////////////////////////////////////////////////// - -#include "XrdClient/XrdClientUrlSet.hh" -#include "XrdClient/XrdClientUrlInfo.hh" -#include "XrdSys/XrdSysDNS.hh" -#include "XrdSys/XrdSysHeaders.hh" - -#include -#include -#include // needed by isdigit() -#ifndef WIN32 -#include // needed by getservbyname() -#include // needed by ntohs() - -#include -#include -#include -#include -#else -#include -#include "XrdSys/XrdWin32.hh" -#include -#endif - -#include "XrdClient/XrdClientDebug.hh" - -#ifdef __solaris__ -#include -#endif - - -using namespace std; - - -//_____________________________________________________________________________ -XrdOucString XrdClientUrlSet::GetServers() -{ - // Returns the final resolved list of servers - XrdOucString s; - - for ( int i = 0; i < fUrlArray.GetSize(); i++ ) { - s += fUrlArray[i]->Host; - s += "\n"; - } - - return s; -} - -//_____________________________________________________________________________ -double XrdClientUrlSet::GetRandom(int i) -{ -// Machine independent random number generator. -// Produces uniformly-distributed floating points between 0 and 1. -// Identical sequence on all machines of >= 32 bits. -// Periodicity = 10**8 -// Universal version (Fred James 1985). -// generates a number in ]0,1] - - const double kCONS = 4.6566128730774E-10; - const int kMASK24 = 2147483392; - - fSeed *= 69069; - unsigned int jy = (fSeed&kMASK24); // Set lower 8 bits to zero to assure exact float - if (jy) return kCONS*jy; - return GetRandom(); -} - -//_____________________________________________________________________________ -XrdClientUrlSet::XrdClientUrlSet(XrdOucString urls) : fIsValid(TRUE) -{ - // A container for multiple urls. - // It creates an array of multiple urls parsing the argument 'urls' and - // resolving the DNS aliases - // - // 'urls' MUST be in the form: - // - // [proto://][user1@]host1:port1[,[user2@]host2:port2, ... , - // [userN@]hostN:portN]]/pathfile - // - // Using the method GetNextUrl() the user can obtain the next - // XrdClientUrlInfo object pointer in the array (the array is cyclic). - // Using the method GetARandomUrl() the user can obtain a random - // XrdClientUrlInfo from the array. - // - UrlArray urlArray; - XrdOucString listOfMachines; - XrdOucString proto; - XrdOucString file; - Info(XrdClientDebug::kHIDEBUG, "XrdClientUrlSet", "parsing: "< 0) - file.assign(urls, p1); - Info(XrdClientDebug::kHIDEBUG,"XrdClientUrlSet", "file: "<GetDebugLevel() >= - XrdClientDebug::kUSERDEBUG) - ShowUrls(); - } - -} - -//_____________________________________________________________________________ -XrdClientUrlSet::~XrdClientUrlSet() -{ - fTmpUrlArray.Clear(); - - for( int i=0; i < fUrlArray.GetSize(); i++) - delete fUrlArray[i]; - - fUrlArray.Clear(); -} - -//_____________________________________________________________________________ -XrdClientUrlInfo *XrdClientUrlSet::GetNextUrl() -{ - // Returns the next url object pointer in the array. - // After the last object is returned, the array is rewind-ed. - // Now implemented as a pick from the tmpUrlArray queue - - XrdClientUrlInfo *retval; - - if ( !fTmpUrlArray.GetSize() ) Rewind(); - - retval = fTmpUrlArray.Pop_back(); - - return retval; -} - -//_____________________________________________________________________________ -void XrdClientUrlSet::Rewind() -{ - // Rebuilds tmpUrlArray, i..e the urls that have to be picked - fTmpUrlArray.Clear(); - - for(int i=0; i <= fUrlArray.GetSize()-1; i++) - fTmpUrlArray.Push_back( fUrlArray[i] ); -} - -//_____________________________________________________________________________ -XrdClientUrlInfo *XrdClientUrlSet::GetARandomUrl() -{ - XrdClientUrlInfo *retval; - int rnd = 0; - - if (!fTmpUrlArray.GetSize()) Rewind(); - - // If the urlarray is still empty, just exits - if (!fTmpUrlArray.GetSize()) return 0; - - for (int i=0; i < 10; i++) - rnd = static_cast(GetRandom() * fTmpUrlArray.GetSize()) % fTmpUrlArray.GetSize(); - - // Returns a random url from the ones that have to be picked - // When all the urls have been picked, we restart from the full url set - - retval = fTmpUrlArray[rnd]; - fTmpUrlArray.Erase(rnd); - - return retval; -} - - -//_____________________________________________________________________________ -XrdClientUrlInfo *XrdClientUrlSet::GetARandomUrl(unsigned int seed) -{ - XrdClientUrlInfo *retval; - - if (!fTmpUrlArray.GetSize()) Rewind(); - - // If the urlarray is still empty, just exits - if (!fTmpUrlArray.GetSize()) return 0; - - // When all the urls have been picked, we restart from the full url set - - int rnd = seed % fTmpUrlArray.GetSize(); - retval = fTmpUrlArray[rnd]; - fTmpUrlArray.Erase(rnd); - - return retval; -} - -//_____________________________________________________________________________ -void XrdClientUrlSet::EraseUrl(XrdClientUrlInfo *url) -{ - // Eliminates url from the list - - for(int i=0; i < fUrlArray.GetSize(); i++) { - if (url == fUrlArray[i]) { - fUrlArray.Erase(i); - Info(XrdClientDebug::kHIDEBUG, "EraseUrl", - " url found and dropped from the list"); - return; - } - } - Info(XrdClientDebug::kHIDEBUG, "EraseUrl", " url NOT found in the list"); -} - -//_____________________________________________________________________________ -void XrdClientUrlSet::ShowUrls() -{ - // Prints the list of urls - - Info(XrdClientDebug::kUSERDEBUG, "ShowUrls", - "The converted URLs count is " << fUrlArray.GetSize() ); - - for(int i=0; i < fUrlArray.GetSize(); i++) - Info(XrdClientDebug::kUSERDEBUG, "ShowUrls", - "URL n." << i+1 << ": "<< fUrlArray[i]->GetUrl() << "."); - -} - -//_____________________________________________________________________________ -void XrdClientUrlSet::CheckPort(int &port) -{ - // Checks the validity of port in the given host[:port] - // Eventually completes the port if specified in the services file - - if (port <= 0) { - - // Port not specified - Info(XrdClientDebug::kHIDEBUG, "CheckPort", - "TCP port not specified: trying /etc/services ..."); - - struct servent *svc = getservbyname("rootd", "tcp"); - - if (!svc) { - Info(XrdClientDebug::kHIDEBUG, "CheckPort", - "service rootd not specified in /etc/services;" << - "using default IANA tcp port 1094"); - port= 1094; - - } else { - port = ntohs(svc->s_port); - Info(XrdClientDebug::kHIDEBUG, "CheckPort", - "found tcp port " << port << "."); - } - - } else - // Port is potentially valid - Info(XrdClientDebug::kHIDEBUG, "CheckPort", - "specified port (" << port << ") potentially valid."); -} - -//_____________________________________________________________________________ -void XrdClientUrlSet::ConvertDNSAlias(UrlArray& urls, XrdOucString proto, - XrdOucString host, XrdOucString file) -{ - // Create an XrdClientUrlInfo from protocol 'proto', remote host 'host', - // file 'file' and add it to the array, after having resolved the DNS - // information. - bool hasPort; - XrdOucString tmpaddr; - - XrdClientUrlInfo *newurl = new XrdClientUrlInfo(host); - hasPort = (newurl->Port > 0); - - if (hasPort) { - Info(XrdClientDebug::kHIDEBUG, "ConvertDNSAlias", - "resolving " << newurl->Host << ":" << newurl->Port); - } else - Info(XrdClientDebug::kHIDEBUG, "ConvertDNSAlias", - "resolving " << newurl->Host); - - // Make sure port is a reasonable number - CheckPort(newurl->Port); - - // Resolv the DNS information - char *haddr[10] = {0}, *hname[10] = {0}; - int naddr = XrdSysDNS::getAddrName(newurl->Host.c_str(), 10, haddr, hname); - - // Fill the list - int i = 0; - for (; i < naddr; i++ ) { - - // Address - newurl->HostAddr = (const char *) haddr[i]; - - // Name - newurl->Host = (const char *) hname[i]; - - // Protocol - newurl->Proto = proto; - - // File - newurl->File = file; - - // Add to the list - urls.Push_back(newurl); - - // Notify - Info(XrdClientDebug::kHIDEBUG, "ConvertDNSAlias", - "found host " << newurl->Host << " with addr " << newurl->HostAddr); - - // Get a copy, if we need to store another - if (i < (naddr-1)) - newurl = new XrdClientUrlInfo(*newurl); - - // Cleanup - if (haddr[i]) free(haddr[i]); - if (hname[i]) free(hname[i]); - } -} diff --git a/src/XrdClient/XrdClientUrlSet.hh b/src/XrdClient/XrdClientUrlSet.hh deleted file mode 100644 index 9378ff05aa4..00000000000 --- a/src/XrdClient/XrdClientUrlSet.hh +++ /dev/null @@ -1,99 +0,0 @@ -#ifndef _XRC_URLSET_H -#define _XRC_URLSET_H -/******************************************************************************/ -/* */ -/* X r d C l i e n t U r l S e t . h h */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2004) */ -/* Adapted from TXNetFile (root.cern.ch) originally done by */ -/* Alvise Dorigo, Fabrizio Furano, INFN Padova, 2003 */ -/* Revised by G. Ganis, CERN, June 2005 */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// A container for multiple urls to be resolved through DNS aliases // -// // -////////////////////////////////////////////////////////////////////////// - -#include "XrdClient/XrdClientConst.hh" -#include "XrdClient/XrdClientVector.hh" -#include "XrdOuc/XrdOucString.hh" - -using namespace std; - -class XrdClientUrlInfo; - -typedef XrdClientVector UrlArray; - -// -// Manages a set of XrdClientUrlInfo objects and provides a set -// of utilities to resolve multiple addresses from the dns -// and to pick urls sequentially and randomly an url -// - -class XrdClientUrlSet { -private: - UrlArray fUrlArray; - UrlArray fTmpUrlArray; - XrdOucString fPathName; - - bool fIsValid; - unsigned int fSeed; - - void CheckPort(int &port); - void ConvertDNSAlias(UrlArray& urls, XrdOucString proto, - XrdOucString host, XrdOucString file); - double GetRandom(int seed = 0); - -public: - XrdClientUrlSet(XrdOucString urls); - ~XrdClientUrlSet(); - - // Returns the final resolved list of servers - XrdOucString GetServers(); - - // Gets the subsequent Url, the one after the last given - XrdClientUrlInfo *GetNextUrl(); - - // From the remaining urls we pick a random one. Without reinsert. - // i.e. while there are not considered urls, never pick an already seen one - XrdClientUrlInfo *GetARandomUrl(); - // Given a seed, use that to pick an url - // the effect will be that, given the same list, the same seed will pick the same url - XrdClientUrlInfo *GetARandomUrl(unsigned int seed); - - void Rewind(); - void ShowUrls(); - void EraseUrl(XrdClientUrlInfo *url); - - // Returns the number of urls - int Size() { return fUrlArray.GetSize(); } - - // Returns the pathfile extracted from the CTOR's argument - XrdOucString GetFile() { return fPathName; } - - bool IsValid() { return fIsValid; } // Spot malformations - -}; -#endif diff --git a/src/XrdClient/XrdClientVector.hh b/src/XrdClient/XrdClientVector.hh deleted file mode 100644 index adea811eacf..00000000000 --- a/src/XrdClient/XrdClientVector.hh +++ /dev/null @@ -1,388 +0,0 @@ -#ifndef XRD_CLIIDXVEC_H -#define XRD_CLIIDXVEC_H -/******************************************************************************/ -/* */ -/* X r d C l i e n t V e c t o r . h h */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2006) */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// A vector class optimized for insertions and deletions // -// indexed access takes O(1) // -// insertion takes O(1) plus a very small fraction of O(n) // -// deletion takes O(1) plus a very small fraction of O(n) // -// // -// Better suited than XrdClientVector to hold complex objects // -// // -////////////////////////////////////////////////////////////////////////// - -#include -#include - -#include "XrdSys/XrdSysHeaders.hh" - -#define IDXVEC_MINCAPACITY 128 - -template -class XrdClientVector { - -private: - - // We keep the corrected size of T - int sizeof_t; - - char *rawdata; // A raw mem block to hold (casted) T instances - - struct myindex { - long offs; // Offset to a T inside rawdata - bool notempty; - } *index; - - // the number of holes inside rawdata - // each hole is sizeof_t bytes - int holecount; - - long size, mincap; - long capacity, maxsize; - - // Completely packs rawdata - // Eventually adjusts the sizes in order to contain at least - // newsize elements - int BufRealloc(int newsize); - - inline void Init(int cap = -1) { - if (rawdata) free(rawdata); - if (index) free(index); - - mincap = (cap > 0) ? cap : IDXVEC_MINCAPACITY; - - rawdata = static_cast(malloc(mincap * sizeof_t)); - - index = static_cast(malloc(mincap * sizeof(myindex))); - - if (!rawdata || !index) { - std::cerr << "XrdClientIdxVector::Init .... out of memory. sizeof_t=" << sizeof_t << - " sizeof(myindex)=" << sizeof(myindex) << " capacity=" << mincap << std::endl; - abort(); - } - - // and we make every item as empty, i.e. not pointing to anything - memset(index, 0, mincap * sizeof(myindex)); - - holecount = 0; - - size = 0; - maxsize = capacity = mincap; - } - - // Makes a null position... not to be exposed - // Because after this the element pointed to by el becomes invalid - // Typically el will be moved at the end, at the size+holecount position - void DestroyElem(myindex *el) { - reinterpret_cast(rawdata+el->offs)->~T(); - // el->notempty = false; - } - - void put(T& item, long pos) { - // Puts an element in position pos - // Hence write at pos in the index array - // Use a new chunk of rawdata if the item does not point to a chunk - if (size+holecount >= capacity) { - std::cerr << "XrdClientIdxVector::put .... internal error." << std::endl; - abort(); - } - - T *p; - long offs = (size+holecount)*sizeof_t; - - if (index[pos].notempty) { - offs = index[pos].offs; - - // did we fill a hole? - holecount--; - } - - p = new(rawdata + offs) T(item); - - if (p) { - index[pos].offs = offs; - index[pos].notempty = true; - } - else { - std::cerr << "XrdClientIdxVector::put .... out of memory." << std::endl; - abort(); - } - - } - -public: - - inline int GetSize() const { return size; } - - void Clear() { - for (long i = 0; i < size; i++) - if (index[i].notempty) DestroyElem(&index[i]); - - Init(mincap); - } - - XrdClientVector(int cap = -1): - sizeof_t(0), rawdata(0), index(0) - { - // We calculate a size which is aligned on 4-bytes - sizeof_t = (sizeof(T) + 3) >> 2 << 2; - Init(cap); - } - - XrdClientVector(XrdClientVector &v): - rawdata(0), index(0) { - - sizeof_t = (sizeof(T) + 3) >> 2 << 2; - - Init(v.capacity); - BufRealloc(v.size); - - for (int i = 0; i < v.size; i++) - Push_back( v[i] ); - } - - ~XrdClientVector() { - for (long i = 0; i < size; i++) - if (index[i].notempty) DestroyElem(&index[i]); - - if (rawdata) free(rawdata); - if (index) free(index); - } - - void Resize(int newsize) { - long oldsize = size; - - if (newsize > oldsize) { - BufRealloc(newsize); - T *item = new T; - // Add new elements if needed - for (long i = oldsize; i < newsize; i++) { - put(*item, size++); - } - delete item; - } - else { - for (long i = oldsize; i > newsize; i--) - Erase(i-1, false); - } - } - - void Push_back(T& item) { - - if ( BufRealloc(size+1) ) - put(item, size++); - - } - -// // Inserts an item in the given position -// void Insert(T& item, int pos) { - -// if (pos >= size) { -// Push_back(item); -// return; -// } - -// if ( BufRealloc(size+1) ) { - -// memmove(&index[pos+1], &index[pos], (size+holecount-pos) * sizeof(myindex)); -// index[pos].notempty = false; -// size++; -// put(item, pos); -// } - -// } - - - // Inserts an item in the given position - void Insert(T& item, int pos) { - - if (pos >= size) { - Push_back(item); - return; - } - - if ( BufRealloc(size+1) ) { - - if (holecount > 0) { - struct myindex tmpi = index[size]; - memmove(&index[pos+1], &index[pos], (size-pos) * sizeof(myindex)); - index[pos] = tmpi; - } else { - memmove(&index[pos+1], &index[pos], (size-pos) * sizeof(myindex)); - index[pos].notempty = false; - } - - size++; - put(item, pos); - } - - } - -// // Removes a single element in position pos -// void Erase(unsigned int pos, bool dontrealloc=true) { -// // We make the position empty, then move the free index to the end -// DestroyElem(index + pos); - -// index[size+holecount] = index[pos]; -// holecount++; - -// memmove(&index[pos], &index[pos+1], (size+holecount-pos) * sizeof(myindex)); - -// size--; - -// if (!dontrealloc) -// BufRealloc(size); - -// } - - // Removes a single element in position pos - void Erase(unsigned int pos, bool dontrealloc=true) { - // We make the position empty, then move the free index to the end of the full items - DestroyElem(index + pos); - - struct myindex tmpi = index[pos]; - holecount++; - - memmove(&index[pos], &index[pos+1], (size-pos-1) * sizeof(myindex)); - - size--; - index[size] = tmpi; - if (!dontrealloc) - BufRealloc(size); - - } - - T Pop_back() { - T r( At(size-1) ); - - DestroyElem(index+size-1); - - holecount++; - size--; - //BufRealloc(size); - - return (r); - } - - T Pop_front() { - T res; - - res = At(0); - - Erase(0); - return (res); - } - - // Bounded array like access - inline T &At(int pos) { - if ((pos < 0) || (static_cast(pos) >= - static_cast(size))) abort(); - - return *( reinterpret_cast(rawdata + index[pos].offs)); - } - - inline T &operator[] (int pos) { - return At(pos); - } - -}; - - -// Completely packs rawdata if needed -// Eventually adjusts the sizes in order to fit newsize elements -template -int XrdClientVector::BufRealloc(int newsize) { - - // If for some reason we have too many holes, we repack everything - // this is very heavy!! - if ((size+holecount >= capacity-2) && (holecount > 4*size)) - while (size+holecount >= capacity-2) { - long lastempty = size+holecount-1; // The first hole to fill - - // Pack everything in rawdata - // Keep the pointers updated - - // Do the trick - - // Move the last filled to the first encountered hole - memmove(rawdata + index[lastempty].offs, rawdata + index[lastempty].offs + sizeof_t, - (size+holecount)*sizeof_t - index[lastempty].offs ); - - // Drop the index - index[lastempty].notempty = false; - holecount--; - - // Adjust all the pointers to the subsequent chunks - for (long i = 0; i < size+holecount; i++) - if (index[i].notempty && (index[i].offs > index[lastempty].offs)) - index[i].offs -= sizeof_t; - - } - - if (newsize > maxsize) maxsize = newsize; - - while (newsize+holecount > capacity*2/3) { - // Too near to the end? - // double the capacity - - capacity *= 2; - - rawdata = static_cast(realloc(rawdata, capacity*sizeof_t)); - if (!rawdata) { - std::cerr << "XrdClientIdxVector::BufRealloc .... out of memory." << std::endl; - abort(); - } - - index = static_cast(realloc(index, capacity*sizeof(myindex))); - memset(index+capacity/2, 0, capacity*sizeof(myindex)/2); - - } - - while ((newsize+holecount < capacity/3) && (capacity > 2*mincap)) { - // Too near to the beginning? - // half the capacity - - - capacity /= 2; - - rawdata = static_cast(realloc(rawdata, capacity*sizeof_t)); - if (!rawdata) { - std::cerr << "XrdClientIdxVector::BufRealloc .... out of memory." << std::endl; - abort(); - } - - index = static_cast(realloc(index, capacity*sizeof(myindex))); - - } - - return 1; - -} -#endif diff --git a/src/XrdClient/XrdCommandLine.cc b/src/XrdClient/XrdCommandLine.cc deleted file mode 100644 index 6dd555626e1..00000000000 --- a/src/XrdClient/XrdCommandLine.cc +++ /dev/null @@ -1,1957 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C o m m a n d L i n e . c c */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2005) */ -/* Rewritten by Elvin Sindrilaru */ -/* with mods from Lukasz Janyst (CERN, 2010) */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -//------------------------------------------------------------------------------ -// A command line tool for xrootd environments. The executable normally -// is named xrd. -//------------------------------------------------------------------------------ - -#include "XrdClient/XrdClientUrlInfo.hh" -#include "XrdClient/XrdClient.hh" -#include "XrdClient/XrdClientAdmin.hh" -#include "XrdClient/XrdClientDebug.hh" -#include "XrdClient/XrdClientEnv.hh" -#include "XrdOuc/XrdOucTokenizer.hh" -#include "XrdSys/XrdSysHeaders.hh" - -#include -#include -#include -#include -#include -#include -#include - -#ifdef HAVE_READLINE -#include -#include -#endif - -#define XRDCLI_VERSION "(C) 2004-2010 by the Xrootd group. Xrootd version: " XrdVSTRING - -//------------------------------------------------------------------------------ -// Some globals -//------------------------------------------------------------------------------ -char *opaqueinfo = 0; // opaque info to be added to urls -kXR_unt16 xrd_wr_flags = kXR_async | kXR_mkpath | kXR_open_updt | kXR_new; -char *initialhost = 0; -XrdClient *genclient = 0; -XrdClientAdmin *genadmin = 0; -XrdOucString currentpath = "/"; -XrdOucString cmdline_cmd; - -//------------------------------------------------------------------------------ -// Interrupt signal handler -//------------------------------------------------------------------------------ -void CtrlCHandler(int sig) -{ - std::cerr << std::endl; - std::cerr << "Please use 'exit' to terminate this program." << std::endl; -} - -//------------------------------------------------------------------------------ -// Commandline help message -//------------------------------------------------------------------------------ -void PrintUsage() -{ - std::cerr << "usage: xrd [host]"; - std::cerr << "[-DSparmname stringvalue] ... [-DIparmname intvalue] "; - std::cerr << "[-O] [command]" << std::endl; - - std::cerr << " -DSparmname stringvalue : "; - std::cerr << "override the default value of an internal XrdClient setting "; - std::cerr << "(of string type)" << std::endl; - - std::cerr << " -DIparmname intvalue : "; - std::cerr << "override the default value of an internal XrdClient setting "; - std::cerr << "(of int type)" << std::endl; - - std::cerr << " -O : adds some opaque information to any used "; - std::cerr << "xrootd url" << std::endl; - - std::cerr << " -h : this help screen" << std::endl; - - std::cerr <GetCurrentUrl().Host; - s << ":" << genadmin->GetCurrentUrl().Port; - s << "/" << currentpath; - } - s << "> "; -} - -//------------------------------------------------------------------------------ -// Out own primitive implementation of GNU readline -//------------------------------------------------------------------------------ -#ifndef HAVE_READLINE -char *readline(const char *prompt) -{ - std::cout << prompt << std::flush; - std::string input; - std::getline( std::cin, input ); - - if( !cin.good() ) - return 0; - - char *linebuf = (char *)malloc( input.size()+1 ); - strncpy( linebuf, input.c_str(), input.size()+1 ); - - return linebuf; -} -#endif - -//------------------------------------------------------------------------------ -// Print help -//------------------------------------------------------------------------------ -void PrintHelp() -{ - std::cout << std::endl << XRDCLI_VERSION << std::endl << std::endl; - std::cout << "Usage: xrd [-O] [-DS stringvalue] "; - std::cout << "[-DI integervalue] [host[:port]] [batchcommand]"; - std::cout << std::endl << std::endl; - - std::cout << "List of available commands:" << std::endl << std::endl; - std::cout << std::left; - std::cout << std::setw(55) << "cat [xrdcp parameters]"; - std::cout << "Output a file on standard output using xrdcp. "; - std::cout << "can be a root:// URL." << std::endl; - - std::cout << std::setw(55) << "cd "; - std::cout << "Change the current directory. Note: no existence check is "; - std::cout << "performed." << std::endl; - - std::cout << std::setw(55) << "chmod "; - std::cout << "Modify file permissions." << std::endl; - - std::cout << std::setw(55) << "connect [hostname[:port]]"; - std::cout << "Connect to the specified host." << std::endl; - - std::cout << std::setw(55) << "cp [xrdcp parameters]"; - std::cout << "Copies a file using xrdcp. are always "; - std::cout << "relative to the" << std::endl; - std::cout << std::setw(55) << " " << "current remote path. Also, they "; - std::cout << "can be root:// URLs specifying any other host." << std::endl; - - std::cout << std::setw(55) << "dirlist [dirname]"; - std::cout << "Get the requested directory listing." << std::endl; - - std::cout << std::setw(55) << "dirlistrec [dirname]"; - std::cout << "Get the requested recursive directory listing."; - std::cout << std::endl; - - std::cout << std::setw(55) << "envputint "; - std::cout << "Put an integer in the internal environment." << std::endl; - - std::cout << std::setw(55) << "envputstring "; - std::cout << "Put a string in the internal environment." << std::endl; - - std::cout << std::setw(55) << "exit"; - std::cout << "Exits from the program." << std::endl; - - std::cout << std::setw(55) << "help"; - std::cout << "This help screen." << std::endl; - - std::cout << std::setw(55) << "stat [fileordirname]"; - std::cout << "Get info about the given file or directory path."; - std::cout << std::endl; - - std::cout << std::setw(55) << "statvfs [vfilesystempath]"; - std::cout << "Get info about a virtual file system." << std::endl; - - std::cout << std::setw(55) << "existfile "; - std::cout << "Test if the specified file exists." << std::endl; - - std::cout << std::setw(55) << "existdir "; - std::cout << "Test if the specified directory exists." << std::endl; - - std::cout << std::setw(55) << "getchecksum "; - std::cout << "Get the checksum for the specified file." << std::endl; - - std::cout << std::setw(55) << "isfileonline "; - std::cout << "Test if the specified file is online." << std::endl; - - std::cout << std::setw(55) << "locatesingle "; - std::cout << "Give a location of the given file in the currently "; - std::cout << "connected cluster." << std::endl; - std::cout << std::setw(55) << " " << "if writable is true only a "; - std::cout << "writable location is searched" << std::endl; - std::cout << std::setw(55) << " " << "but, if no writable locations "; - std::cout << "are found, the result is negative but may" << std::endl; - std::cout << std::setw(55) << " " << "propose a non writable one."; - std::cout << std::endl; - - std::cout << std::setw(55) << "locateall "; - std::cout << "Give all the locations of the given file in the currently"; - std::cout << "connected cluster." << std::endl; - - std::cout << std::setw(55) << "mv "; - std::cout << "Move filename1 to filename2 locally to the same server."; - std::cout << std::endl; - - std::cout << std::setw(55) << "mkdir [user] [group] [other]"; - std::cout << "Creates a directory." << std::endl; - - std::cout << std::setw(55) << "rm "; - std::cout << "Remove a file." << std::endl; - - std::cout << std::setw(55) << "rmdir "; - std::cout << "Removes a directory." << std::endl; - - std::cout << std::setw(55) << "prepare "; - std::cout << "Stage a file in." << std::endl; - - std::cout << std::setw(55) << "query "; - std::cout << "Obtain server information." << std::endl; - - std::cout << std::setw(55) << "queryspace "; - std::cout << "Obtain space information." << std::endl; - - std::cout << std::setw(55) << "truncate "; - std::cout << "Truncate a file." << std::endl; - - std::cout << std::endl << "For further information, please read the "; - std::cout << "xrootd protocol documentation." << std::endl; - - std::cout << std::endl; -} - -//------------------------------------------------------------------------------ -// Check the answer of the server -//------------------------------------------------------------------------------ -bool CheckAnswer(XrdClientAbs *gencli) -{ - if (!gencli->LastServerResp()) return false; - - switch (gencli->LastServerResp()->status) - { - case kXR_ok: - return true; - - case kXR_error: - std::cout << "Error " << gencli->LastServerError()->errnum; - std::cout << ": " << gencli->LastServerError()->errmsg << std::endl; - std::cout << std::endl; - return false; - - default: - std::cout << "Server response: " << gencli->LastServerResp()->status; - std::cout << std::endl; - return true; - - } -} - -//------------------------------------------------------------------------------ -// Print the output from locate -//------------------------------------------------------------------------------ -void PrintLocateInfo(XrdClientLocate_Info &loc) -{ - std::cout << "InfoType: "; - switch (loc.Infotype) - { - case XrdClientLocate_Info::kXrdcLocNone: - std::cout << "none" << std::endl; - break; - case XrdClientLocate_Info::kXrdcLocDataServer: - std::cout << "kXrdcLocDataServer" << std::endl; - break; - case XrdClientLocate_Info::kXrdcLocDataServerPending: - std::cout << "kXrdcLocDataServerPending" << std::endl; - break; - case XrdClientLocate_Info::kXrdcLocManager: - std::cout << "kXrdcLocManager" << std::endl; - break; - case XrdClientLocate_Info::kXrdcLocManagerPending: - std::cout << "kXrdcLocManagerPending" << std::endl; - break; - default: - std::cout << "Invalid Infotype" << std::endl; - } - std::cout << "CanWrite: "; - if (loc.CanWrite) std::cout << "true" << std::endl; - else std::cout << "false" << std::endl; - std::cout << "Location: '" << loc.Location << "'" << std::endl; -} - -//------------------------------------------------------------------------------ -// process the "EXISTDIR" command -//------------------------------------------------------------------------------ -void executeExistDir(XrdOucTokenizer &tkzer) -{ - int retval = 0; - - if (!genadmin) - { - std::cout << "Not connected to any server." << std::endl << std::endl; - retval = 1; - } - - if (!retval) - { - char *fname = tkzer.GetToken(0, 0); - XrdOucString pathname; - - if (fname) - { - if (fname[0] == '/') - pathname = fname; - else - pathname = currentpath + "/" + fname; - } - else pathname = currentpath; - - // Try to issue the request - vecBool vb; - vecString vs; - vs.Push_back(pathname); - genadmin->ExistDirs(vs, vb); - - // Check the answer - if (vb[0] && (vb.GetSize() >= 1)) - std::cout << "The directory exists." << std::endl; - else - std::cout << "Directory not found." << std::endl; - std::cout << std::endl; - return; - } -} - -//------------------------------------------------------------------------------ -// process the "CD" command -//------------------------------------------------------------------------------ -void executeCd(XrdOucTokenizer &tkzer) -{ - char *parmname = tkzer.GetToken(0, 0); - XrdOucString pathname; - - if (!genadmin) - { - std::cout << "Not connected to any server." << std::endl << std::endl; - return; - } - - if (!parmname || !strlen(parmname)) - { - std::cout << "A directory name is needed." << std::endl << std::endl; - } - else - { - - // Quite trivial directory processing - if (!strcmp(parmname, "..")) - { - if (currentpath == "/") return; - - int pos = currentpath.rfind('/'); - - if (pos != STR_NPOS) - currentpath.erase(pos); - - if (!currentpath.length()) - currentpath = "/"; - - return; - } - else if (!strcmp(parmname, ".")) - { - return; - } - - if (!currentpath.length() || (currentpath[currentpath.length()-1] != '/')) - currentpath += "/"; - - XrdOucString tmpPath; - if (parmname[0] == '/') - tmpPath = parmname; - else - tmpPath = currentpath + parmname; - - // Verify if tmpPath really exists - vecBool vb; - vecString vs; - vs.Push_back(tmpPath); - genadmin->ExistDirs(vs, vb); - - // Now check the answer - if (CheckAnswer(genadmin)) - currentpath = tmpPath; - else - std::cout << "The directory does not exist." << std::endl << std::endl; - return; - } -} - -//------------------------------------------------------------------------------ -// process the "ENVPUTINT" command -//------------------------------------------------------------------------------ -void executeEnvPutInt(XrdOucTokenizer &tkzer) -{ - char *parmname = tkzer.GetToken(0, 0), - *val = tkzer.GetToken(0, 1); - - if (!parmname || !val) - { - std::cout << "Please provide command parameters:envputint "; - std::cout << "" << std::endl << std::endl; - } - else - { - EnvPutInt(parmname, atoi(val)); - DebugSetLevel(EnvGetLong(NAME_DEBUG)); - } - return; -} - -//------------------------------------------------------------------------------ -// process the "ENVPUTSTRING" command -//------------------------------------------------------------------------------ -void executeEnvPutString(XrdOucTokenizer &tkzer) -{ - char *parmname = tkzer.GetToken(0, 0), - *val = tkzer.GetToken(0, 1); - - if (!parmname || !val) - { - std::cout << "Please provide command parameters:envputstring "; - std::cout << "" << std::endl << std::endl; - } - else - EnvPutString(parmname, val); - return; -} - -//------------------------------------------------------------------------------ -// process the "HELP" command -//------------------------------------------------------------------------------ -void executeHelp(XrdOucTokenizer &) -{ - PrintHelp(); -} - -//------------------------------------------------------------------------------ -// process the "CONNECT" command -//------------------------------------------------------------------------------ -void executeConnect(XrdOucTokenizer &tkzer) -{ - int retval = 0; - char *host = initialhost; - - // If no host was given, then pretend one - if (!host) - { - host = tkzer.GetToken(0, 1); - if (!host || !strlen(host)) - { - std::cout << "A hostname is needed to connect somewhere."; - std::cout << std::endl << std::endl; - retval = 1; - } - } - - if (!retval) - { - // Init the instance - if (genadmin) delete genadmin; - XrdOucString h(host); - h = "root://" + h; - h += "//dummy"; - - genadmin = new XrdClientAdmin(h.c_str()); - - // Then connect - if (!genadmin->Connect()) - { - delete genadmin; - genadmin = 0; - } - } -} - -//------------------------------------------------------------------------------ -// process the "DIRLISTREC" command -//------------------------------------------------------------------------------ -void executeDirListRec(XrdOucTokenizer &tkzer) -{ - XrdClientVector pathq; - int retval = 0; - - if (!genadmin) - { - std::cout << "Not connected to any server." << std::endl << std::endl; - retval = 1; - } - - if (!retval) - { - char *dirname = tkzer.GetToken(0, 0); - XrdOucString path; - - if (dirname) - { - if (dirname[0] == '/') - path = dirname; - else - { - if ((currentpath.length() > 0) && - (currentpath[currentpath.length()-1] != '/')) - path = currentpath + "/" + dirname; - else - path = currentpath + dirname; - - } - } - else path = currentpath; - - if (!path.length()) - { - std::cout << "The current path is an empty string. Assuming '/'."; - std::cout << std::endl << std::endl; - path = '/'; - } - - // Initialize the queue with this path - pathq.Push_back(path); - - while (pathq.GetSize() > 0) - { - XrdOucString pathtodo = pathq.Pop_back(); - - // Now try to issue the request - XrdClientVector nfo; - genadmin->DirList(pathtodo.c_str(), nfo, true); - - // Now check the answer - if (!CheckAnswer(genadmin)) - { - retval = 1; - std::cout << "In server "; - std::cout << genadmin->GetCurrentUrl().HostWPort; - std::cout << " or in some of its child nodes." << std::endl; - break; - } - - for (int i = 0; i < nfo.GetSize(); i++) - { - - if ((nfo[i].flags & kXR_isDir) && - (nfo[i].flags & kXR_readable) && - (nfo[i].flags & kXR_xset)) - { - - // The path has not to be pushed if it's already present - // This may happen if several servers have the same path - bool foundpath = false; - for (int ii = 0; ii < pathq.GetSize(); ii++) - { - if (nfo[i].fullpath == pathq[ii]) - { - foundpath = true; - break; - } - } - - if (!foundpath) - pathq.Push_back(nfo[i].fullpath); - else - // If the path is already present in the queue - // then it was already printed as well. - continue; - - } - - char ts[256]; - strcpy(ts, "n/a"); - - struct tm *t = gmtime(&nfo[i].modtime); - strftime(ts, 255, "%F %T", t); - - char cflgs[16]; - memset(cflgs, 0, 16); - - if (nfo[i].flags & kXR_isDir) - strcat(cflgs, "d"); - else strcat(cflgs, "-"); - - if (nfo[i].flags & kXR_readable) - strcat(cflgs, "r"); - else strcat(cflgs, "-"); - - if (nfo[i].flags & kXR_writable) - strcat(cflgs, "w"); - else strcat(cflgs, "-"); - - if (nfo[i].flags & kXR_xset) - strcat(cflgs, "x"); - else strcat(cflgs, "-"); - - printf( "%s(%03ld) %12lld %s %s\n", - cflgs, nfo[i].flags, nfo[i].size, - ts, nfo[i].fullpath.c_str()); - } - } - } - std::cout << std::endl; - return; -} - -//------------------------------------------------------------------------------ -// process the "DIRLIST" command -//------------------------------------------------------------------------------ -void executeDirList(XrdOucTokenizer &tkzer) -{ - int retval = 0; - - if (!genadmin) - { - std::cout << "Not connected to any server." << std::endl << std::endl; - retval = 1; - } - - if (!retval) - { - char *dirname = tkzer.GetToken(0, 0); - XrdOucString path; - - if (dirname) - { - if (dirname[0] == '/') - path = dirname; - else - { - if ((currentpath.length() > 0) && - (currentpath[currentpath.length()-1] != '/')) - path = currentpath + "/" + dirname; - else - path = currentpath + dirname; - - } - } - else path = currentpath; - - if (!path.length()) - { - std::cout << "The current path is an empty string. Assuming '/'."; - std::cout << std::endl << std::endl; - path = '/'; - } - - // Now try to issue the request - XrdClientVector nfo; - genadmin->DirList(path.c_str(), nfo, true); - - // Now check the answer - if (!CheckAnswer(genadmin)) - { - retval = 1; - std::cout << "In server " << genadmin->GetCurrentUrl().HostWPort; - std::cout << " or in some of its child nodes." << std::endl; - //nfo.Clear(); - } - - for (int i = 0; i < nfo.GetSize(); i++) - { - char ts[256]; - strcpy(ts, "n/a"); - - struct tm *t = gmtime(&nfo[i].modtime); - strftime(ts, 255, "%F %T", t); - - char cflgs[16]; - memset(cflgs, 0, 16); - - if (nfo[i].flags & kXR_isDir) - strcat(cflgs, "d"); - else strcat(cflgs, "-"); - - if (nfo[i].flags & kXR_readable) - strcat(cflgs, "r"); - else strcat(cflgs, "-"); - - if (nfo[i].flags & kXR_writable) - strcat(cflgs, "w"); - else strcat(cflgs, "-"); - - if (nfo[i].flags & kXR_xset) - strcat(cflgs, "x"); - else strcat(cflgs, "-"); - - printf( "%s(%03ld) %12lld %s %s\n", - cflgs, nfo[i].flags, nfo[i].size, ts, - nfo[i].fullpath.c_str()); - } - std::cout << std::endl; - } - return; -} - -//------------------------------------------------------------------------------ -// process the "LOCATESINGLE" command -//------------------------------------------------------------------------------ -void executeLocateSingle(XrdOucTokenizer &tkzer) -{ - int retval = 0; - - if (!genadmin) - { - std::cout << "Not connected to any server." <Locate((kXR_char *)pathname.c_str(), loc, wrt); - if (!r) - std::cout << "No matching files were found." < loc; - bool r; - r = genadmin->Locate((kXR_char *)pathname.c_str(), loc); - if (!r) - std::cout << "No matching files were found." <Stat(pathname.c_str(), id, size, flags, modtime); - - // Now check the answer - if (!CheckAnswer(genadmin)) - { - std::cout << "The command returned an error."; - std::cout << std::endl << std::endl; - } - else - { - std::cout << "Id: " << id << " Size: " << size << " Flags: "; - std::cout << flags << " Modtime: " << modtime << std::endl; - std::cout <Stat_vfs(pathname.c_str(), rwservers, rwfree, rwutil, - stagingservers, stagingfree, stagingutil); - - // Now check the answer - if (!CheckAnswer(genadmin)) - { - std::cout << "The command returned an error."; - std::cout << std::endl <ExistFiles(vs, vb); - - // Now check the answer - if (!CheckAnswer(genadmin)) - retval = 1; - - if (vb[0] && (vb.GetSize() >= 1)) - std::cout << "The file exists." << std::endl; - else - std::cout << "File not found." << std::endl; - std::cout <GetChecksum((kXR_char *)pathname.c_str(), &ans); - - // Now check the answer - if (!CheckAnswer(genadmin)) - { - std::cout << "The command returned an error."; - std::cout << std::endl << std::endl; - } - else - { - std::cout << "Checksum: " << ans << std::endl; - free(ans); - std::cout << std::endl; - } - } - else - { - std::cout << "Please provide command parameter: getchecksum "; - std::cout << "" << std::endl << std::endl; - } - } -} - -//------------------------------------------------------------------------------ -// process the "ISFILEONLINE" command -//------------------------------------------------------------------------------ -void executeIsFileOnline(XrdOucTokenizer &tkzer) -{ - int retval = 0; - - if (!genadmin) - { - std::cout << "Not connected to any server." << std::endl << std::endl; - retval = 1; - } - - if (!retval) - { - char *fname = tkzer.GetToken(0, 0); - XrdOucString pathname; - - if (fname) - { - if (fname[0] == '/') - pathname = fname; - else - pathname = currentpath + "/" + fname; - - // Now try to issue the request - vecBool vb; - vecString vs; - vs.Push_back(pathname); - genadmin->IsFileOnline(vs, vb); - - // Now check the answer - if (!CheckAnswer(genadmin)) - { - std::cout << "The command returned an error."; - std::cout << std::endl << std::endl; - } - else - { - if (vb[0] && (vb.GetSize() >= 1)) - std::cout << "The file is online." << std::endl; - else - std::cout << "The file is not online." << std::endl; - std::cout <" << std::endl << std::endl; - } - } -} - -//------------------------------------------------------------------------------ -// process the "MV" command -//------------------------------------------------------------------------------ -void executeMv(XrdOucTokenizer &tkzer) -{ - int retval = 0; - - if (!genadmin) - { - std::cout << "Not connected to any server." << std::endl << std::endl; - retval = 1; - } - - if (!retval) - { - char *fname1 = tkzer.GetToken(0, 0); - char *fname2 = tkzer.GetToken(0, 0); - XrdOucString pathname1, pathname2; - - if (!fname1 || !fname2) - { - std::cout << "Please provide command parameteres: mv "; - std::cout << "" <Mv(pathname1.c_str(), pathname2.c_str()); - - // Now check the answer - if (!CheckAnswer(genadmin)) - std::cout << "The command returned an error." < "; - std::cout << "[ ]" << std::endl << std::endl; - } - else - { - int user = 0, group = 0, other = 0; - if (userc) user = atoi(userc); - if (groupc) group = atoi(groupc); - if (otherc) other = atoi(otherc); - - XrdOucString pathname1; - - if (fname1[0] == '/') - pathname1 = fname1; - else - pathname1 = currentpath + "/" + fname1; - - // Now try to issue the request - genadmin->Mkdir(pathname1.c_str(), user, group, other); - - // Now check the answer - if (!CheckAnswer(genadmin)) - { - std::cout << "The command returned an error."; - std::cout << std::endl << std::endl; - } - } - } -} - -//------------------------------------------------------------------------------ -// process the "CHMOD" command -//------------------------------------------------------------------------------ -void executeChmod(XrdOucTokenizer &tkzer) -{ - - int retval = 0; - - if (!genadmin) - { - std::cout << "Not connected to any server." < "; - std::cout << " " << std::endl << std::endl; - } - else - { - int user = 0, group = 0, other = 0; - if (userc) user = atoi(userc); - if (groupc) group = atoi(groupc); - if (otherc) other = atoi(otherc); - - XrdOucString pathname1; - - if (fname1[0] == '/') - pathname1 = fname1; - else - pathname1 = currentpath + "/" + fname1; - - // Now try to issue the request - genadmin->Chmod(pathname1.c_str(), user, group, other); - - // Now check the answer - if (!CheckAnswer(genadmin)) - std::cout << "The command returned an error." <Truncate(pathname1.c_str(), len); - - // Now check the answer - if (!CheckAnswer(genadmin)) - std::cout << "The command returned an error." < "; - std::cout << "" << std::endl << std::endl; - return; - } - } -} - -//------------------------------------------------------------------------------ -// process the "RM" command -//------------------------------------------------------------------------------ -void executeRm(XrdOucTokenizer &tkzer) -{ - int retval = 0; - - if (!genadmin) - { - std::cout << "Not connected to any server." <Rm(pathname.c_str()); - - // Now check the answer - if (!CheckAnswer(genadmin)) - { - std::cout << "The command returned an error."; - std::cout << std::endl << std::endl; - } - } - else - { - std::cout << "Please provide command parameter: rm "; - std::cout << std::endl << std::endl; - } - } -} - -//------------------------------------------------------------------------------ -// process the "RMDIR" command -//------------------------------------------------------------------------------ -void executeRmDir(XrdOucTokenizer &tkzer) -{ - int retval = 0; - - if (!genadmin) - { - std::cout << "Not connected to any server." <Rmdir(pathname.c_str()); - - // Now check the answer - if (!CheckAnswer(genadmin)) - { - std::cout << "The command returned an error."; - std::cout << std::endl << std::endl; - } - } - else - { - std::cout << "Please provide command parameter: rmdir "; - std::cout << std::endl << std::endl; - } - } -} - -//------------------------------------------------------------------------------ -// process the "PREPARE" command -//------------------------------------------------------------------------------ -void executePrepare(XrdOucTokenizer &tkzer) -{ - int retval = 0; - - if (!genadmin) - { - std::cout << "Not connected to any server." << std::endl << std::endl; - retval = 1; - } - - if (!retval) - { - char *fname1 = tkzer.GetToken(0, 0); - char *optsc = tkzer.GetToken(0, 0); - char *prioc = tkzer.GetToken(0, 0); - - if (!fname1) - { - std::cout << "Please provide command parameters: prepare "; - std::cout << " "; - std::cout << std::endl << std::endl; - } - else - { - int opts = 0, prio = 0; - if (optsc) opts = atoi(optsc); - if (prioc) prio = atoi(prioc); - - XrdOucString pathname1; - - if (fname1[0] == '/') - pathname1 = fname1; - else - pathname1 = currentpath + "/" + fname1; - - // Now try to issue the request - vecString vs; - vs.Push_back(pathname1); - genadmin->Prepare(vs, (kXR_char)opts, (kXR_char)prio); - - // Now check the answer - if (!CheckAnswer(genadmin)) - { - std::cout << "The command returned an error."; - std::cout << std::endl << std::endl; - } - } - } -} - -//------------------------------------------------------------------------------ -// process the "CAT" command -//------------------------------------------------------------------------------ -void executeCat(XrdOucTokenizer &tkzer) -{ - int retval = 0; - - if (!genadmin) - { - std::cout << "Not connected to any server." << std::endl << std::endl; - retval = 1; - } - - if (!retval) - { - char *fname1 = tkzer.GetToken(0, 0); - char *tk; - XrdOucString pars; - - while ((tk = tkzer.GetToken(0, 0))) - { - pars += " "; - pars += tk; - } - - XrdOucString pathname1; - - if (fname1) - { - if ( (strstr(fname1, "root://") == fname1) || - (strstr(fname1, "xroot://") == fname1) ) - pathname1 = fname1; - else if (fname1[0] == '/') - { - pathname1 = "root://" + genadmin->GetCurrentUrl().HostWPort; - pathname1 += "/"; - pathname1 += fname1; - } - else - { - pathname1 = "root://" + genadmin->GetCurrentUrl().HostWPort; - pathname1 += "/"; - pathname1 += currentpath; - pathname1 += "/"; - pathname1 += fname1; - } - } - else - std::cout << "Missing parameter." <GetCurrentUrl().HostWPort; - pathname1 += "/"; - pathname1 += fname1; - } - else - { - pathname1 = "root://" + genadmin->GetCurrentUrl().HostWPort; - pathname1 += "/"; - pathname1 += currentpath; - pathname1 += "/"; - pathname1 += fname1; - } - - if (fname2) - { - - if ( (strstr(fname2, "root://") == fname2) || - (strstr(fname2, "xroot://") == fname2) ) - pathname2 = fname2; - else if (fname2[0] == '/') - { - pathname2 = "root://" + genadmin->GetCurrentUrl().HostWPort; - pathname2 += "/"; - pathname2 += fname2; - } - else - { - pathname2 = "root://" + genadmin->GetCurrentUrl().HostWPort; - pathname2 += "/"; - pathname2 += currentpath; - pathname2 += "/"; - pathname2 += fname2; - } - - } - else - { - std::cout << "Please provide command parameters: cp "; - std::cout << " []" < "; - std::cout << " []" < " << std::endl << std::endl; - } - else - { - - const kXR_char *args = (const kXR_char *)tkzer.GetToken(0, 0); - kXR_char *Resp = 0; - - genadmin->Query(atoi(reqcode), args, &Resp, 0); - - // Now check the answer - if (!CheckAnswer(genadmin)) - retval = 1; - - std::cout << Resp << std::endl << std::endl; - free( Resp ); - } - } -} - -//------------------------------------------------------------------------------ -// process the "QUERYSPACE" command -//------------------------------------------------------------------------------ -void executeQuerySpace(XrdOucTokenizer &tkzer) -{ - int retval = 0; - - if (!genadmin) - { - std::cout << "Not connected to any server." <" << std::endl << std::endl; - } - else - { - - long long totspace; - long long totfree; - long long totused; - long long largestchunk; - - genadmin->GetSpaceInfo(ns, totspace, totfree, totused, largestchunk); - - // Now check the answer - if (!CheckAnswer(genadmin)) - retval = 1; - - std::cout << "Disk space approximations (MB):" << std::endl; - std::cout << "Total : " << totspace/(1024*1024) << std::endl; - std::cout << "Free : " << totfree/(1024*1024) << std::endl; - std::cout << "Used : " << totused/(1024*1024) << std::endl; - std::cout << "Largest chunk : " << largestchunk/(1024*1024); - std::cout << std::endl << std::endl; - } - } -} - -//------------------------------------------------------------------------------ -// process the "DEGBUG" command -//------------------------------------------------------------------------------ -void executeDebug(XrdOucTokenizer &tkzer) -{ - - int level = -2, retval = 0; - string delim = "="; - if (!genadmin) - { - std::cout << "Not connected to any server." <" << std::endl << std::endl; -} - -//------------------------------------------------------------------------------ -// Function lookup -//------------------------------------------------------------------------------ -typedef void (*CommandCallback)(XrdOucTokenizer &); - -struct LookupItem -{ - const char *name; - CommandCallback callback; -}; - -LookupItem lookupTable[] = -{ - {"cd", executeCd }, - {"envputint", executeEnvPutInt }, - {"envputstring", executeEnvPutString}, - {"help", executeHelp }, - {"connect", executeConnect }, - {"dirlistrec", executeDirListRec }, - {"dirlist", executeDirList }, - {"ls", executeDirList }, - {"locatesingle", executeLocateSingle}, - {"locateall", executeLocateAll }, - {"stat", executeStat }, - {"statvfs", executeStatvfs }, - {"existfile", executeExistFile }, - {"existdir", executeExistDir }, - {"getchecksum", executeGetCheckSum }, - {"isfileonline", executeIsFileOnline}, - {"mv", executeMv }, - {"mkdir", executeMkDir }, - {"chmod", executeChmod }, - {"truncate", executeTruncate }, - {"rm", executeRm }, - {"rmdir", executeRmDir }, - {"prepare", executePrepare }, - {"cat", executeCat }, - {"cp", executeCp }, - {"query", executeQuery }, - {"queryspace", executeQuerySpace }, - {"debug", executeDebug }, - {0, 0 } -}; - -CommandCallback lookup( char *command ) -{ - LookupItem *it = lookupTable; - while( it->name != 0 ) - { - if( strcmp( command, it->name ) == 0 ) - return it->callback; - ++it; - } - return 0; -} - -//------------------------------------------------------------------------------ -// Main program -//------------------------------------------------------------------------------ -int main(int argc, char**argv) -{ - - int retval = 0; - - DebugSetLevel(0); - - // We want this tool to be able to connect everywhere - // Note that the side effect of these calls here is to initialize the - // XrdClient environment. - // This is crucial if we want to later override its default values - EnvPutString( NAME_REDIRDOMAINALLOW_RE, "*" ); - EnvPutString( NAME_CONNECTDOMAINALLOW_RE, "*" ); - EnvPutString( NAME_REDIRDOMAINDENY_RE, "" ); - EnvPutString( NAME_CONNECTDOMAINDENY_RE, "" ); - - EnvPutInt( NAME_DEBUG, -1); - - //-------------------------------------------------------------------------- - // Parse the commandline - //-------------------------------------------------------------------------- - for (int i=1; i < argc; i++) - { - if ( (strstr(argv[i], "-O") == argv[i])) - { - opaqueinfo=argv[i]+2; - continue; - } - - if ( (strstr(argv[i], "-h") == argv[i]) || - (strstr(argv[i], "--help") == argv[i]) ) - { - PrintUsage(); - exit(0); - continue; - } - - if ( (strstr(argv[i], "-DS") == argv[i]) && - (argc >= i+2) ) - { - std::cerr << "Overriding " << argv[i]+3 << " with value "; - std::cerr << argv[i+1] << ". "; - EnvPutString( argv[i]+3, argv[i+1] ); - std::cerr << " Final value: " << EnvGetString(argv[i]+3); - std::cerr << std::endl; - i++; - continue; - } - - if ( (strstr(argv[i], "-DI") == argv[i]) && - (argc >= i+2) ) - { - std::cerr << "Overriding '" << argv[i]+3 << "' with value "; - std::cerr << argv[i+1] << ". "; - EnvPutInt( argv[i]+3, atoi(argv[i+1]) ); - std::cerr << " Final value: " << EnvGetLong(argv[i]+3); - std::cerr << std::endl; - i++; - continue; - } - - // Any other par is ignored - if ( (strstr(argv[i], "-") == argv[i]) && (strlen(argv[i]) > 1) ) - { - std::cerr << "Unknown parameter " << argv[i] << std::endl; - continue; - } - - if (!initialhost) initialhost = argv[i]; - else - { - cmdline_cmd += argv[i]; - cmdline_cmd += " "; - } - } - - - //-------------------------------------------------------------------------- - // Initialize the client - //-------------------------------------------------------------------------- - DebugSetLevel(EnvGetLong(NAME_DEBUG)); - - // if there's no command to execute from the cmdline... - if (cmdline_cmd.length() == 0) - { - std::cout << XRDCLI_VERSION << std::endl; - std::cout << "Welcome to the xrootd command line interface."; - std::cout << std::endl; - std::cout << "Type 'help' for a list of available commands."; - std::cout << std::endl; - } - - if (initialhost) - { - XrdOucString s = "root://"; - s += initialhost; - s += "//dummy"; - genadmin = new XrdClientAdmin(s.c_str()); - - // Then connect - if (!genadmin->Connect()) - { - delete genadmin; - genadmin = 0; - } - } - - //-------------------------------------------------------------------------- - // Get the command from the std input - //-------------------------------------------------------------------------- - while( true ) - { - //---------------------------------------------------------------------- - // Parse the string - //---------------------------------------------------------------------- - stringstream prompt; - char *linebuf=0; - - if (cmdline_cmd.length() == 0) - { - BuildPrompt(prompt); - linebuf = readline(prompt.str().c_str()); - if( !linebuf ) - { - std::cout << "Goodbye." << std::endl << std::endl; - break; - } - if( ! *linebuf) - { - free(linebuf); - continue; - } -#ifdef HAVE_READLINE - add_history(linebuf); -#endif - } - else linebuf = strdup(cmdline_cmd.c_str()); - - XrdOucTokenizer tkzer(linebuf); - if (!tkzer.GetLine()) continue; - - char *cmd = tkzer.GetToken(0, 1); - - if (!cmd) continue; - - //---------------------------------------------------------------------- - // Execute the command - //---------------------------------------------------------------------- - CommandCallback callback = lookup( cmd ); - if( !callback ) - { - if( strcmp( cmd, "exit" ) == 0 ) - { - std::cout << "Goodbye." << std::endl << std::endl; - free( linebuf ); - break; - } - std::cout << "Command not recognized." << std::endl; - std::cout << "Type \"help\" for a list of commands."; - std::cout < 0) break; - - } - - if (genadmin) - delete genadmin; - - return retval; -} diff --git a/src/XrdClient/XrdCpMthrQueue.cc b/src/XrdClient/XrdCpMthrQueue.cc deleted file mode 100644 index 278074c520a..00000000000 --- a/src/XrdClient/XrdCpMthrQueue.cc +++ /dev/null @@ -1,114 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C p M t h r Q u e u e . c c */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2004) */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// A thread safe queue to be used for multithreaded producers-consumers // -// // -////////////////////////////////////////////////////////////////////////// - -#include "XrdClient/XrdCpMthrQueue.hh" -#include "XrdSys/XrdSysPthread.hh" -#include "XrdClient/XrdClientDebug.hh" - -XrdCpMthrQueue::XrdCpMthrQueue(): fWrWait(0), fReadSem(0), fWriteSem(0) { - // Constructor - - fMsgQue.Clear(); - fTotSize = 0; -} - -int XrdCpMthrQueue::PutBuffer(void *buf, long long offs, int len) { - XrdCpMessage *m; - - fMutex.Lock(); - if (len && fTotSize > CPMTQ_BUFFSIZE) - {fWrWait++; - fMutex.UnLock(); - fWriteSem.Wait(); - } - - m = new XrdCpMessage; - m->offs = offs; - m->buf = buf; - m->len = len; - - // Put message in the list - // - fMsgQue.Push_back(m); - fTotSize += len; - - fMutex.UnLock(); - fReadSem.Post(); - - return 0; -} - -int XrdCpMthrQueue::GetBuffer(void **buf, long long &offs, int &len) { - XrdCpMessage *res; - - res = 0; - - // If there is no data for one hour, then give up with an error - if (!fReadSem.Wait(3600)) - {fMutex.Lock(); - // If there are messages to dequeue, we pick the oldest one - if (fMsgQue.GetSize() > 0) - {res = fMsgQue.Pop_front(); - if (res) {fTotSize -= res->len; - if (fWrWait) {fWrWait--; fWriteSem.Post();} - } - } - fMutex.UnLock(); - } - - - if (res) { - *buf = res->buf; - len = res->len; - offs = res->offs; - delete res; - } - - return (res != 0); -} - - -void XrdCpMthrQueue::Clear() { - void *buf; - int len; - long long offs; - - while (fMsgQue.GetSize() && GetBuffer(&buf, offs, len)) { - free(buf); - } - - fTotSize = 0; - -} - - diff --git a/src/XrdClient/XrdCpMthrQueue.hh b/src/XrdClient/XrdCpMthrQueue.hh deleted file mode 100644 index f3dcb210464..00000000000 --- a/src/XrdClient/XrdCpMthrQueue.hh +++ /dev/null @@ -1,76 +0,0 @@ -#ifndef XRDCPMTHRQ__HH -#define XRDCPMTHRQ__HH -/******************************************************************************/ -/* */ -/* X r d C p M t h r Q u e u e . h h */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2004) */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// A thread safe queue to be used for multithreaded producers-consumers // -// // -////////////////////////////////////////////////////////////////////////// - -#include "XrdSys/XrdSysPthread.hh" -#include "XrdClient/XrdClientVector.hh" -#include "XrdSys/XrdSysSemWait.hh" -#include "XrdSys/XrdSysHeaders.hh" - -using namespace std; - -struct XrdCpMessage { - void *buf; - long long offs; - int len; -}; - -// The max allowed size for this queue -// If this value is reached, then the writer has to wait... -#define CPMTQ_BUFFSIZE 50000000 - -class XrdCpMthrQueue { - private: - long fTotSize; - XrdClientVector fMsgQue; // queue for incoming messages - int fMsgIter; // an iterator on it - int fWrWait; // Write waiters - - XrdSysRecMutex fMutex; // mutex to protect data structures - - XrdSysSemWait fReadSem; // variable to make the reader wait - // until some data is available - XrdSysSemaphore fWriteSem; // variable to make the writer wait - // if the queue is full - public: - - XrdCpMthrQueue(); - ~XrdCpMthrQueue() {} - - int PutBuffer(void *buf, long long offs, int len); - int GetBuffer(void **buf, long long &offs, int &len); - int GetLength() { return fMsgQue.GetSize(); } - void Clear(); -}; -#endif diff --git a/src/XrdClient/XrdCpWorkLst.cc b/src/XrdClient/XrdCpWorkLst.cc deleted file mode 100644 index 852fb142f41..00000000000 --- a/src/XrdClient/XrdCpWorkLst.cc +++ /dev/null @@ -1,464 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C p W o r k L s t . c c */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2004) */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// A class implementing a list of cps to do for XrdCp // -// // -////////////////////////////////////////////////////////////////////////// - -#include "XrdClient/XrdClientEnv.hh" -#include "XrdClient/XrdCpWorkLst.hh" -#include "XrdOuc/XrdOucErrInfo.hh" -#include "XrdSys/XrdSysDir.hh" -#include "XrdSys/XrdSysHeaders.hh" -#include -#include -#include -#ifndef WIN32 -#include -#else -#include "XrdSys/XrdWin32.hh" -#endif - -using namespace std; - -// To print out the last server error info -//____________________________________________________________________________ -void PrintLastServerError(XrdClient *cli) { - struct ServerResponseBody_Error *e; - - if ( cli && (e = cli->LastServerError()) ) - cerr << "Last server error " << e->errnum << " ('" << e->errmsg << "')" << - endl; - -} - -const char *ServerError(XrdClient *cli) { - struct ServerResponseBody_Error *e; - - if ( cli && (e = cli->LastServerError()) ) return e->errmsg; - return "Copy failed for unknown reasons!"; -} - -bool PedanticOpen4Write(XrdClient *cli, kXR_unt16 mode, kXR_unt16 options) { - // To be really pedantic we must disable the parallel open - bool paropen = !(options & kXR_delete); - - if (!cli) return false; - - if ( !cli->Open(mode, options, paropen) ) { - - if ( (cli->LastServerError()->errnum == kXR_NotFound) - && (options & kXR_delete) ) { - // We silently try to remove the dest file, ignoring the result - XrdClientAdmin adm(cli->GetCurrentUrl().GetUrl().c_str()); - if (adm.Connect()) { - adm.Rm( cli->GetCurrentUrl().File.c_str() ); - } - - // And then we try again - if ( !cli->Open(mode, options, paropen) ) - return false; - - } - else return false; - } - - return true; -} - -//------------------------------------------------------------------------------ -// Check if the opaque data provides the file size information and add it -// if needed -//------------------------------------------------------------------------------ -XrdOucString AddSizeHint( const char *dst, off_t size ) -{ - // to be removed when we have no more <3.0.4 servers - // needed because of a bug fixed by 787446f38296698d2881ed45d3009336bde0834d - if( !EnvGetLong( NAME_XRDCP_SIZE_HINT ) ) - return dst; - - XrdOucString dest = dst; - std::stringstream s; - if( dest.find( "?oss.asize=" ) == STR_NPOS && - dest.find( "&oss.asize=" ) == STR_NPOS ) - { - s << dst; - if( dest.find( "?" ) == STR_NPOS ) - s << "?"; - else - s << "&"; - s << "oss.asize=" << size; - dest = s.str().c_str(); - } - return dest; -} - - -XrdCpWorkLst::XrdCpWorkLst() { - fWorkList.Clear(); - xrda_src = 0; - xrda_dst = 0; - pSourceSize = 0; - srcPathLen = 0; -} - -XrdCpWorkLst::~XrdCpWorkLst() { - fWorkList.Clear(); -} - -// Sets the source path for the file copy -// i.e. expand the given url to the list of the files it involves -int XrdCpWorkLst::SetSrc(XrdClient **srccli, XrdOucString url, - XrdOucString urlopaquedata, bool do_recurse, int newCP) { - - XrdOucString fullurl(url); - - if (urlopaquedata.length()) - fullurl = url + "?" + urlopaquedata; - - fSrcIsDir = FALSE; - - if (fullurl.beginswith("root://") || fullurl.beginswith("xroot://") ) { - // It's an xrd url - - fSrc = url; - - // We must see if it's a dir - if (!*srccli) - (*srccli) = new XrdClient(fullurl.c_str()); - - //------------------------------------------------------------------------ - // We're opening a remote file as a source, let's push it to the - // working list and remember the size so that it could be hinted to - // the destination server later - //------------------------------------------------------------------------ - if ((*srccli)->Open(0, kXR_async) && - ((*srccli)->LastServerResp()->status == kXR_ok)) { - // If the file has been succesfully opened, we use this url - fWorkList.Push_back(fSrc); - XrdClientStatInfo stat; - (*srccli)->Stat(&stat); - pSourceSize = stat.size; - } - else - if ( do_recurse && - ((*srccli)->LastServerError()->errnum == kXR_isDirectory) ){ - - - delete (*srccli); - *srccli = 0; - - // So, it's a dir for sure - // Let's process it recursively - - fSrcIsDir = TRUE; - - xrda_src = new XrdClientAdmin(fullurl.c_str()); - - if (xrda_src->Connect()) { - - BuildWorkList_xrd(fSrc, urlopaquedata); - } - - delete xrda_src; - xrda_src = 0; - - } - else { - // It was not opened, nor it was a dir. - if (!newCP) PrintLastServerError(*srccli); - return 1; - //fWorkList.Push_back(fSrc); - } - - - - - } - else { - // It's a local file or path - fSrc = url; - fSrcIsDir = FALSE; - - // We must see if it's a dir - XrdSysDir d(url.c_str()); - - if (!d.isValid()) { - if (d.lastError() == ENOTDIR) - { - fWorkList.Push_back(fSrc); - struct stat statStruct; - if( stat( fSrc.c_str(), &statStruct ) ) - return errno; - pSourceSize = statStruct.st_size; - } - else - return d.lastError(); - } else { - fSrcIsDir = TRUE; - srcPathLen = strlen(url.c_str())+1; - BuildWorkList_loc(&d, url); - } - } - - fWorkIt = 0; - return 0; -} - -// Sets the destination of the file copy -// i.e. decides if it's a directory or file name -// It will delete and set to 0 xrddest if it's not a file -int XrdCpWorkLst::SetDest(XrdClient **xrddest, const char *url, - const char *urlopaquedata, - kXR_unt16 xrdopenflags, int newCP) { - int retval = 0; - - // Special case: if url terminates with "/" then it's a dir - if (url[strlen(url)-1] == '/') { - fDest = url; - fDestIsDir = TRUE; - return 0; - } - - if ( (strstr(url, "root://") == url) || - (strstr(url, "xroot://") == url) ) { - // It's an xrd url - - fDest = url; - - if (fSrcIsDir) { - fDestIsDir = TRUE; - // Make sure fDest ends with a '/' to avoid problems in - // path formation later on - if (!fDest.endswith('/')) - fDest += '/'; - return 0; - } - else { - - // The source is a single file - fDestIsDir = FALSE; - XrdOucString fullurl(url); - - if (urlopaquedata) { - if ((*urlopaquedata) != '?') fullurl += "?"; - fullurl += urlopaquedata; - } - - fullurl = AddSizeHint( fullurl.c_str(), pSourceSize ); - - // let's see if url can be opened as a file (file-to-file copy) - *xrddest = new XrdClient(fullurl.c_str()); - - if ( PedanticOpen4Write(*xrddest, kXR_ur | kXR_uw | kXR_gw | kXR_gr | kXR_or, - xrdopenflags) && - ((*xrddest)->LastServerResp()->status == kXR_ok) ) { - - return 0; - - //XrdClientUrlInfo u(url); - -// // If a file open succeeded, then it's a file good for writing to! -// fDestIsDir = FALSE; - -// // In any case we might have been assigned a destination data server -// // Better to take this into account instead of the former one -// if ((*xrddest)->GetCurrentUrl().IsValid()) { -// XrdClientUrlInfo uu; -// uu = (*xrddest)->GetCurrentUrl(); -// u.Host = uu.Host; -// u.Port = uu.Port; -// fDest = u.GetUrl(); -// } - - } else { - - // The file open was not successful. Let's see why. - - if ((*xrddest)->LastServerError()->errnum == kXR_isDirectory) { - - // It may be only a dir - fDestIsDir = TRUE; - - // Make sure fDest ends with a '/' to avoid problems in - // path formation later on - if (!fDest.endswith('/')) - fDest += '/'; - - // Anyway, it's ok - retval = 0; - } - else { - if (!newCP) PrintLastServerError(*xrddest); - retval = 1; - } - - // If the file has not been opened for writing, - // there is no need to keep this instance alive. - if (!newCP) {delete *xrddest; *xrddest = 0;} - - return retval; - } - - } - - } - else { - // It's a local file or path - - if (strcmp(url, "-")) { - - fDestIsDir = TRUE; - // We must see if it's a dir - struct stat st; - if (lstat(url, &st) == 0) { - if (!S_ISDIR(st.st_mode)) - fDestIsDir = FALSE; - } else { - if (errno == ENOENT) - fDestIsDir = FALSE; - else - return errno; - } - fDest = url; - // Make sure fDest ends with a '/' to avoid problems in - // path formation later on - if (fDestIsDir && !fDest.endswith('/')) - fDest += '/'; - return 0; - } - else { - // dest is stdout - fDest = url; - fDestIsDir = FALSE; - } - - } - - fWorkIt = 0; - return 0; -} - -// Actually builds the worklist expanding the source of the files -int XrdCpWorkLst::BuildWorkList_xrd(XrdOucString url, XrdOucString opaquedata) { - vecString entries; - int it; - long id, flags, modtime; - long long size; - XrdOucString fullpath; - XrdClientUrlInfo u(url); - - // Invoke the DirList cmd to get the content of the dir - if (!xrda_src->DirList(u.File.c_str(), entries)) return -1; - - // Cycle on the content and spot all the files - for (it = 0; it < entries.GetSize(); it++) { - fullpath = url + "/" + entries[it]; - - XrdClientUrlInfo u(fullpath); - - // We must see if it's a dir - // If a dir is found, do it recursively - if ( xrda_src->Stat((char *)u.File.c_str(), id, size, flags, modtime) && - (flags & kXR_isDir) ) { - - BuildWorkList_xrd(fullpath, opaquedata); - } - else - fWorkList.Push_back(fullpath); - - - } - - return 0; -} - - -int XrdCpWorkLst::BuildWorkList_loc(XrdSysDir *dir, XrdOucString path) -{ - - char *ent = 0; - XrdOucString fullpath; - - // Here we already have an usable dir handle - // Cycle on the content and spot all the files - while (dir && (ent = dir->nextEntry())) { - - if (!strcmp(ent, ".") || !strcmp(ent, "..")) - continue; - - // Assemble full path name. - fullpath = path + "/" + ent; - - // Get info for the entry - struct stat ftype; - if ( lstat(fullpath.c_str(), &ftype) < 0 ) - continue; - - // If it's a dir, then proceed recursively - if (S_ISDIR(ftype.st_mode)) { - XrdSysDir d(fullpath.c_str()); - - if (d.isValid()) - BuildWorkList_loc(&d, fullpath); - } else - // If it's a file, then add it to the worklist - if (S_ISREG(ftype.st_mode)) - fWorkList.Push_back(fullpath); - } - - return 0; -} - - -// Get the next cp job to do -bool XrdCpWorkLst::GetCpJob(XrdOucString &src, XrdOucString &dest) { - - if (fWorkIt >= fWorkList.GetSize()) return FALSE; - - src = fWorkList[fWorkIt]; - dest = fDest; - - if (fDestIsDir) { - - // If the dest is a directory name, we must concatenate - // the actual filename, i.e. the token in src following the last / - // when the source is not local recursive. Otherwise, we need to - // concatenate the relative path after the specified path. - int slpos = (srcPathLen ? srcPathLen : src.rfind('/')); - - if (slpos != STR_NPOS) - dest += XrdOucString(src, slpos); - } - - fWorkIt++; - - return TRUE; -} - diff --git a/src/XrdClient/XrdCpWorkLst.hh b/src/XrdClient/XrdCpWorkLst.hh deleted file mode 100644 index a974f039855..00000000000 --- a/src/XrdClient/XrdCpWorkLst.hh +++ /dev/null @@ -1,97 +0,0 @@ -#ifndef XRDCPWORKLST_HH -#define XRDCPWORKLST_HH -/******************************************************************************/ -/* */ -/* X r d C p W o r k L s t . h h */ -/* */ -/* Author: Fabrizio Furano (INFN Padova, 2004) */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// A class implementing a list of cp to do for XrdCp // -// // -////////////////////////////////////////////////////////////////////////// - -#include -#include "XrdClient/XrdClientAdmin.hh" -#include "XrdClient/XrdClient.hh" -#include - -class XrdSysDir; -const char *ServerError(XrdClient *cli); -void PrintLastServerError(XrdClient *cli); -bool PedanticOpen4Write(XrdClient *cli, kXR_unt16 mode, kXR_unt16 options); - -//------------------------------------------------------------------------------ -// Check if the opaque data provides the file size information and add it -// if needed -//------------------------------------------------------------------------------ -XrdOucString AddSizeHint( const char *dst, off_t size ); - -class XrdCpWorkLst { - - vecString fWorkList; - uint64_t pSourceSize; // set if the source URL refers to a file - int srcPathLen; - int fWorkIt; - - XrdClientAdmin *xrda_src, *xrda_dst; - - XrdOucString fSrc, fDest; - bool fDestIsDir, fSrcIsDir; - - public: - - XrdCpWorkLst(); - ~XrdCpWorkLst(); - - - // Sets the source path for the file copy - int SetSrc(XrdClient **srccli, XrdOucString url, - XrdOucString urlopaquedata, bool do_recurse, int newCP=0); - - // Sets the destination of the file copy - int SetDest(XrdClient **xrddest, const char *url, - const char *urlopaquedata, - kXR_unt16 xrdopenflags, int newCP=0); - - inline void GetDest(XrdOucString &dest, bool& isdir) { - dest = fDest; - isdir = fDestIsDir; - } - - inline void GetSrc(XrdOucString &src, bool& isdir) { - src = fSrc; - isdir = fSrcIsDir; - } - - - // Actually builds the worklist - int BuildWorkList_xrd(XrdOucString url, XrdOucString opaquedata); - int BuildWorkList_loc(XrdSysDir *dir, XrdOucString pat); - - bool GetCpJob(XrdOucString &src, XrdOucString &dest); - -}; -#endif diff --git a/src/XrdClient/XrdStageTool.cc b/src/XrdClient/XrdStageTool.cc deleted file mode 100644 index 7efcba04435..00000000000 --- a/src/XrdClient/XrdStageTool.cc +++ /dev/null @@ -1,357 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S t a g e T o o l . c c */ -/* */ -/* Author: Fabrizio Furano (CERN, 2007) */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// A command line tool for xrootd environments, to trigger sync or async// -// staging of files // -// // -////////////////////////////////////////////////////////////////////////// - -#include "XrdClient/XrdClientUrlInfo.hh" -#include "XrdClient/XrdClient.hh" -#include "XrdClient/XrdClientAdmin.hh" -#include "XrdClient/XrdClientDebug.hh" -#include "XrdClient/XrdClientEnv.hh" -#include "XrdOuc/XrdOucTokenizer.hh" -#include "XrdSys/XrdSysHeaders.hh" - -#include -#include -#include -#include - -///////////////////////////////////////////////////////////////////// -// function + macro to allow formatted print via cout,cerr -///////////////////////////////////////////////////////////////////// -extern "C" { - - void cout_print(const char *format, ...) { - char cout_buff[4096]; - va_list args; - va_start(args, format); - vsprintf(cout_buff, format, args); - va_end(args); - cout << cout_buff; - } - - void cerr_print(const char *format, ...) { - char cerr_buff[4096]; - va_list args; - va_start(args, format); - vsprintf(cerr_buff, format, args); - va_end(args); - cerr << cerr_buff; - } - -#define COUT(s) do { \ - cout_print s; \ - } while (0) - -#define CERR(s) do { \ - cerr_print s; \ - } while (0) - -} - -////////////////////////////////////////////////////////////////////// - - -#define XRDSTAGETOOL_VERSION "(C) 2004-2010 by the Xrootd group. $Revision$ - Xrootd version: " XrdVSTRING - - -/////////////////////////////////////////////////////////////////////// -// Coming from parameters on the cmd line - -XrdOucString opaqueinfo; - -// Default open flags for opening a file (xrd) -kXR_unt16 xrd_open_flags = kXR_retstat; - -XrdOucString srcurl; -bool useprepare = false; -int maxopens = 2; -int dbglvl = 0; -int verboselvl = 0; - -/////////////////////// - - - - -/////////////////////////////////////////////////////////////////////// -// Generic instances used throughout the code - -XrdClient *genclient; -XrdClientAdmin *genadmin = 0; -XrdClientVector urls; - -struct OpenInfo { - XrdClient *cli; - XrdClientUrlInfo *origurl; -}; - -XrdClientVector opening; - -/////////////////////// - -void PrintUsage() { - cerr << - "usage1: xrdstagetool [-d dbglevel] [-p] [-s] [-O info] xrootd_url1 ... xrootd_urlN " << endl << - " Requests xrootd MSS staging for the listed complete xrootd URLs" << endl << - " in the form root://host//absolute_path_of_a_file" << endl << - " Please note that in the xrootd world a MSS is not necessarily a tape system. " << endl << - " Some form of staging system must be set up in each contacted host." << endl << endl << - "usage2: xrdstagetool [-d dbglevel] [-p] xrootd_url_dest -S xrootd_url_src" << endl << - " Contacts the dest host and requests to stage the file xrootd_url_dest" << endl << - " by fetching it from xrootd_url_src." << - " This feature must be set up in the dest host, and the src host must be reachable by dest host."<< endl << - " and by all its data servers." << endl << endl << - " Parameters:" << endl << - " -d dbglevel : set the XrdClient debug level (0..4)" << endl << - " -p : asynchronous staging through Prepare request" << endl << - " (must be set up at the involved server side)" << endl << - " -O info : add some opaque info to the issued requests" << endl; -} - - -bool CheckAnswer(XrdClientAbs *gencli) { - if (!gencli->LastServerResp()) return false; - - switch (gencli->LastServerResp()->status) { - case kXR_ok: - return true; - - case kXR_error: - - cout << "Error " << gencli->LastServerError()->errnum << - ": " << gencli->LastServerError()->errmsg << endl << endl; - return false; - - default: - cout << "Server response: " << gencli->LastServerResp()->status << endl; - return true; - - } -} - - -// Main program -int main(int argc, char**argv) { - - dbglvl = -1; - - // We want this tool to be able to connect everywhere - // Note that the side effect of these calls here is to initialize the - // XrdClient environment. - // This is crucial if we want to later override its default values - EnvPutString( NAME_REDIRDOMAINALLOW_RE, "*" ); - EnvPutString( NAME_CONNECTDOMAINALLOW_RE, "*" ); - EnvPutString( NAME_REDIRDOMAINDENY_RE, "" ); - EnvPutString( NAME_CONNECTDOMAINDENY_RE, "" ); - - if (argc <= 1) { - PrintUsage(); - exit(0); - } - - for (int i=1; i < argc; i++) { - - - if ( (strstr(argv[i], "-O") == argv[i]) && (argc >= i+2)) { - opaqueinfo=argv[i+1]; - ++i; - continue; - } - - if ( (strstr(argv[i], "-h") == argv[i])) { - PrintUsage(); - exit(0); - } - - if ( (strstr(argv[i], "-p") == argv[i])) { - // Use prepare instead of Open - useprepare = true; - continue; - } - - if ( (strstr(argv[i], "-v") == argv[i])) { - // Increase verbosity level - verboselvl++; - cout << "Verbosity level is now " << verboselvl << endl; - continue; - } - - if ( (strstr(argv[i], "-d") == argv[i])) { - // Debug level - dbglvl = atoi(argv[i+1]); - i++; - continue; - } - - if ( (strstr(argv[i], "-S") == argv[i])) { - // The url to fetch the file from - srcurl = argv[i+1]; - i++; - continue; - } - - if ( (strstr(argv[i], "-m") == argv[i])) { - // Max number of concurrent open reqs - maxopens = atoi(argv[i+1]); - i++; - continue; - } - - // Any other par is considered as an url and queued - if ( (strstr(argv[i], "-") != argv[i]) && (strlen(argv[i]) > 1) ) { - // Enqueue - if (verboselvl > 0) - cout << "Enqueueing file " << argv[i] << endl; - XrdClientUrlInfo u(argv[i]); - urls.Push_back(u); - - if (verboselvl > 1) - cout << "Enqueued URL " << u.GetUrl() << endl; - - continue; - } - - - } - - EnvPutInt(NAME_DEBUG, dbglvl); - EnvPutInt(NAME_TRANSACTIONTIMEOUT, 3600); - - if (useprepare) { - // Fire all the prepare requests at max speed - - for (int i = 0; i < urls.GetSize(); i++) { - XrdClientUrlInfo u; - - if (srcurl.length() > 5) { - // If -S is specified and has a non trivial content, - // we must connect to the dest host anyway - // but add the source url as opaque info to the filename - u.TakeUrl(urls[i].GetUrl().c_str()); - u.File += "?fetchfrom="; - u.File += srcurl; - } - else u.TakeUrl(urls[i].GetUrl().c_str()); - - if (opaqueinfo.length() > 0) { - // Take care of the heading "?" - if (opaqueinfo[0] != '?') { - u.File += "?"; - } - - u.File += opaqueinfo; - } - - XrdClientAdmin adm(u.GetUrl().c_str()); - - if (verboselvl > 1) - cout << "Connecting to: " << u.GetUrl() << endl; - - if (!adm.Connect()) { - cout << "Unable to connect to " << u.GetUrl() << endl; - continue; - } - - if (verboselvl > 0) - cout << "Requesting prepare for: " << u.GetUrl() << endl; - - if (!adm.Prepare(u.File.c_str(), (kXR_char)kXR_stage, 0)) { - cout << "Unable to send Prepare request for " << u.GetUrl() << endl; - continue; - } - - } - } - else - while((urls.GetSize() > 0) || (opening.GetSize() > 0)) { - // Open all the files in sequence, asynchronously - // Keep a max of maxopens as outstanding - - // See if there are open instances to clean up - for (int i = opening.GetSize()-1; (i >= 0) && (opening.GetSize() > 0); i--) { - struct OpenInfo oi = opening[i]; - - if ( !oi.cli->IsOpen_inprogress() ) { - struct XrdClientStatInfo sti; - - if (oi.cli->IsOpen_wait() && oi.cli->Stat(&sti)) { - cout << "OK " << oi.origurl->GetUrl() << - " Size: " << sti.size << endl; - } - else { - cout << "FAIL " << oi.origurl->GetUrl() << endl; - } - - // In any case this element has to be removed. - delete oi.cli; - delete oi.origurl; - opening.Erase(i); - } - } - - // See how many attempts to start now - int todonow = maxopens - opening.GetSize(); - todonow = xrdmin(todonow, urls.GetSize()); - - if (verboselvl > 1) - cout << "Sync staging attempts to start: " << todonow << endl; - - for (int i = 0; i < todonow; i++) { - XrdClient *cli = new XrdClient(urls[0].GetUrl().c_str()); - XrdClientUrlInfo u(urls[0]); - urls.Erase(0); - - if (!cli || !cli->Open(0, xrd_open_flags)) - cerr << "Error opening '" << endl << u.GetUrl() << endl; - else { - struct OpenInfo oi; - oi.cli = cli; - oi.origurl = new XrdClientUrlInfo(u); - opening.Push_back(oi); - } - } - - - - sleep(1); - - } // while - - - - - - cout << endl; - return 0; - -} diff --git a/src/XrdClient/XrdcpXtremeRead.cc b/src/XrdClient/XrdcpXtremeRead.cc deleted file mode 100644 index 176b3452d04..00000000000 --- a/src/XrdClient/XrdcpXtremeRead.cc +++ /dev/null @@ -1,209 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d c p X t r e m e R e a d . c c */ -/* */ -/* Author: Fabrizio Furano (CERN, 2009) */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// Utility classes handling coordinated parallel reads from multiple // -// XrdClient instances // -// // -////////////////////////////////////////////////////////////////////////// - -#include "XrdClient/XrdcpXtremeRead.hh" -#include "XrdClient/XrdClientAdmin.hh" - -XrdXtRdFile::XrdXtRdFile(int blksize, long long filesize) { - blocks = 0; - clientidxcnt = 0; - freeblks = 0; - doneblks = 0; - - freeblks = nblks = (filesize + blksize - 1) / blksize; - - blocks = new XrdXtRdBlkInfo[nblks]; - - // Init the list of blocks - long long ofs = 0; - for (int i = 0; i < nblks; i++) { - blocks[i].offs = ofs; - blocks[i].len = xrdmax(0, xrdmin(filesize, ofs+blksize) - ofs); - ofs += blocks[i].len; - } - -} - -XrdXtRdFile::~XrdXtRdFile() { - delete []blocks; -} - -int XrdXtRdFile::GimmeANewClientIdx() { - XrdSysMutexHelper m(mtx); - return ++clientidxcnt; -} - -int XrdXtRdFile::GetBlkToPrefetch(int fromidx, int clientidx, XrdXtRdBlkInfo *&blkreadonly) { - // Considering fromidx as a starting point in the blocks array, - // finds a block which is worth prefetching - // If there are free blocks it's trivial - // Otherwise it will be stolen from other readers which are clearly late - - XrdSysMutexHelper m(mtx); - - - // Find a non assigned blk - for (int i = 0; i < nblks; i++) { - int pos = (fromidx + i) % nblks; - - // Find a non assigned blk - if (blocks[pos].requests.GetSize() == 0) { - blocks[pos].requests.Push_back(clientidx); - blocks[pos].lastrequested = time(0); - blkreadonly = &blocks[pos]; - return pos; - } - } - - // Steal an outstanding missing block, even if in progress - // The outcome of this is that, at the end, all thethe fastest free clients will - // ask for the missing blks - // The only thing to avoid is that a client asks twice the same blk for itself - - for (int i = nblks; i > 0; i--) { - int pos = (fromidx + i) % nblks; - - // Find a non finished blk to steal - if (!blocks[pos].done && !blocks[pos].AlreadyRequested(clientidx) && - (blocks[pos].requests.GetSize() < 3) ) { - - blocks[pos].requests.Push_back(clientidx); - blkreadonly = &blocks[pos]; - blocks[pos].lastrequested = time(0); - return pos; - } - } - - // No blocks to request or steal... probably everything's finished - return -1; - -} - -int XrdXtRdFile::GetBlkToRead(int fromidx, int clientidx, XrdXtRdBlkInfo *&blkreadonly) { - // Get the next already prefetched block, now we want to get its content - - XrdSysMutexHelper m(mtx); - - for (int i = 0; i < nblks; i++) { - int pos = (fromidx + i) % nblks; - if (!blocks[pos].done && - blocks[pos].AlreadyRequested(clientidx)) { - - blocks[pos].lastrequested = time(0); - blkreadonly = &blocks[pos]; - return pos; - } - } - - return -1; -} - -int XrdXtRdFile::MarkBlkAsRead(int blkidx) { - XrdSysMutexHelper m(mtx); - - int reward = 0; - - // If the block was stolen by somebody else then the reward is negative - if (blocks[blkidx].done) reward = -1; - if (!blocks[blkidx].done) { - doneblks++; - if (blocks[blkidx].requests.GetSize() > 1) reward = 1; - } - - - blocks[blkidx].done = true; - return reward; -} - - -int XrdXtRdFile::GetListOfSources(XrdClient *ref, XrdOucString xtrememgr, - XrdClientVector &clients, - int maxSources) -{ - // Exploit Locate in order to find as many sources as possible. - // Make sure that ref appears once and only once - // Instantiate and open the relative client instances - - XrdClientVector hosts; - if (xtrememgr == "") return 0; - - // In the simple case the xtrememgr is just the host of the original url. - if (!xtrememgr.beginswith("root://") && !xtrememgr.beginswith("xroot://")) { - - // Create an acceptable xrootd url - XrdOucString loc2; - loc2 = "root://"; - loc2 += xtrememgr; - loc2 += "/xyz"; - xtrememgr = loc2; - } - - XrdClientAdmin adm(xtrememgr.c_str()); - if (!adm.Connect()) return 0; - - int locateok = adm.Locate((kXR_char *)ref->GetCurrentUrl().File.c_str(), hosts, kXR_nowait); - if (!locateok || !hosts.GetSize()) return 0; - if (maxSources > hosts.GetSize()) maxSources = hosts.GetSize(); - - // Here we have at least a result... hopefully - bool found = false; - for (int i = 0; i < maxSources; i++) - if (ref->GetCurrentUrl().HostWPort == (const char *)(hosts[i].Location)) { - found = true; - break; - } - - // Now initialize the clients and start the parallel opens - for (int i = 0; i < maxSources; i++) { - XrdOucString loc; - - loc = "root://"; - loc += (const char *)hosts[i].Location; - loc += "/"; - loc += ref->GetCurrentUrl().File; - cout << "Source #" << i+1 << " " << loc << endl; - - XrdClient *cli = new XrdClient(loc.c_str()); - if (cli) { - clients.Push_back(cli); - - } - - } - - // Eventually add the ref client to the vector - if (!found && ref) clients.Push_back(ref); - - return clients.GetSize(); -} diff --git a/src/XrdClient/XrdcpXtremeRead.hh b/src/XrdClient/XrdcpXtremeRead.hh deleted file mode 100644 index e267a950fa9..00000000000 --- a/src/XrdClient/XrdcpXtremeRead.hh +++ /dev/null @@ -1,102 +0,0 @@ -#ifndef XRDCPXTREMEREAD_HH -#define XRDCPXTREMEREAD_HH -/******************************************************************************/ -/* */ -/* X r d c p X t r e m e R e a d . h h */ -/* */ -/* Author: Fabrizio Furano (CERN, 2009) */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// Utility classes handling Extreme readers, i.e. coordinated parallel // -// reads from multiple XrdClient instances // -// // -////////////////////////////////////////////////////////////////////////// - -#include "XrdSys/XrdSysPthread.hh" -#include "XrdClient/XrdClient.hh" -#include "XrdClient/XrdClientVector.hh" - -class XrdXtRdBlkInfo { -public: - long long offs; - int len; - time_t lastrequested; - - // Nothing more to do, block acquired - bool done; - - // The seq of the clientidxs which requested this blk - XrdClientVector requests; - - bool AlreadyRequested(int clientIdx) { - for (int i = 0; i < requests.GetSize(); i++) - if (requests[i] == clientIdx) return true; - return false; - } - - XrdXtRdBlkInfo() {offs = 0; len = 0; done = false; requests.Clear(); lastrequested = 0; } -}; - -class XrdXtRdFile { -private: - int clientidxcnt; // counter to assign client idxs - XrdSysRecMutex mtx; // mutex to protect data structures - - int freeblks; // Blocks not yet assigned to readers - int nblks; // Total number of blocks - int doneblks; // Xferred blocks - - XrdXtRdBlkInfo *blocks; - -public: - - // Models a file as a sequence of blocks, which can be attrbuted to - // different readers - XrdXtRdFile(int blksize, long long filesize); - ~XrdXtRdFile(); - - bool AllDone() { XrdSysMutexHelper m(mtx); return (doneblks >= nblks); } - - // Gives a unique ID which can identify a reader client in the game - int GimmeANewClientIdx(); - - int GetNBlks() { return nblks; } - - // Finds a block to prefetch and then read - // Atomically associates it to a client idx - // Returns the blk index - int GetBlkToPrefetch(int fromidx, int clientIdx, XrdXtRdBlkInfo *&blkreadonly); - int GetBlkToRead(int fromidx, int clientidx, XrdXtRdBlkInfo *&blkreadonly); - - void MarkBlkAsRequested(int blkidx); - int MarkBlkAsRead(int blkidx); - - static int GetListOfSources(XrdClient *ref, XrdOucString xtrememgr, - XrdClientVector &clients, - int maxSources=12); - - -}; -#endif diff --git a/src/XrdClient/tinytestXTNetAdmin.pl b/src/XrdClient/tinytestXTNetAdmin.pl deleted file mode 100755 index e139cc5493d..00000000000 --- a/src/XrdClient/tinytestXTNetAdmin.pl +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/perl - -# $Id$ - -use XrdClientAdmin; -XrdClientAdmin::XrdInitialize("root://localhost/dummy", "DebugLevel 4\nConnectTimeout 60\nRequestTimeout 60"); - - -$par = "/tmp/vmware.zip"; -@ans = XrdClientAdmin::XrdStat($par); -print "\nThe answer of XrdClientAdmin::Stat($par) is: \"$ans[1]\"-\"$ans[2]\"-\"$ans[3]\"-\"$ans[4]\" \n\n\n"; - -$par = "/prod\n/store/PR/R14/AllEvents/0004/35/14.2.0b/AllEvents_00043511_14.2.0bV00.02E.root\n/tmp"; -$ans = XrdClientAdmin::XrdSysStatX($par); -print "\nThe answer of XrdClientAdmin::SysStatX($par) is: \"$ans\" \n\n\n"; - -$par = "/store/PR/R14/AllEvents/0004/35/14.2.0b/AllEvents_00043511_14.2.0bV00.02E.root"; -$ans = XrdClientAdmin::XrdExistFiles($par); -print "\nThe answer of XrdClientAdmin::ExistFiles($par) is: \"$ans\" \n\n\n"; - -#$par = "/prod\n"; -#$ans = XrdClientAdmin::XrdExistDirs($par); -#print "\nThe answer of XrdClientAdmin::ExistDirs($par) is: \"$ans\" \n\n\n"; - -$par = "/prod\n/store/PR/R14/AllEvents/0004/35/14.2.0b/AllEvents_00043511_14.2.0bV00.02E.root\n/tmp"; -$ans = XrdClientAdmin::XrdExistFiles($par); -print "\nThe answer of XrdClientAdmin::ExistFiles($par) is: \"$ans\" \n\n\n"; - -$par = "/prod\n/store\n/store/PR"; -$ans = XrdClientAdmin::XrdExistDirs($par); -print "\nThe answer of XrdClientAdmin::ExistDirs($par) is: \"$ans\" \n\n\n"; - -$par = "/store/PR/R14/AllEvents/0004/35/14.2.0b/AllEvents_00043511_14.2.0bV00.02E.root"; -$ans = XrdClientAdmin::XrdIsFileOnline("$par"); -print "\nThe answer of XrdClientAdmin::IsFileOnline($par) is: \"$ans\" \n\n\n"; - -$par = "/tmp/grossofile.dat"; -$ans = XrdClientAdmin::XrdGetChecksum("$par"); -print "\nThe answer of XrdClientAdmin::GetChecksum($par) is: \"$ans\" \n\n\n"; - -$ans = XrdClientAdmin::XrdGetCurrentHost(); -print "\nWe are here. Good or bad, after all the current host we are connected to is: \"$ans\" \n\n\n"; - -XrdClientAdmin::XrdTerminate(); - diff --git a/src/XrdClient/xrdadmin b/src/XrdClient/xrdadmin deleted file mode 100755 index 2c74379a36b..00000000000 --- a/src/XrdClient/xrdadmin +++ /dev/null @@ -1,547 +0,0 @@ -#!/usr/bin/perl - -#************************************************************************** -# xrdadmin -# $Id$ -# -# Administration utility for an xrootd server. -# See xrdadmin -h for help. -# -#************************************************************************** - -use strict 'vars'; -use Getopt::Long; -use Socket; -use Term::ReadLine; - -# Default options -my $iname = ''; # instance name of xrootd server -my $adminDir = ''; # direcory of this script on remote machine - -my %opts = (); -GetOptions(\%opts, - 'inst|i=s' => \$iname, - 'path|p=s' => \$adminDir, - 'debug|d', - 'help|h' - ); - -&usage(1) if ($Getopt::Long::error); -&usage(0) if ($opts{'help'}); -&usage(0) if ($#ARGV>0); - -# Possible requests to xrootd -# -my %reqs = ("abort" => ["abort target [msg]", - "Send abort and message to target"], - "close" => ["close target", - "Send close to target"], - "cont" => ["cont target", - "Invalidates a previous pause command"], - "disc" => ["disc target", - "Send disc to target"], - "lsc" => ["lsc target", - "List the client connections"], - "lsd" => ["lsd target", - "List detailed client connections"], - "msg" => ["msg target [msg]", - "Send a message to target"], - "pause" => ["pause target wsec", - "Target should wait for wsec seconds"], - "redirect" => ["redirect target host[?token]:port[?token]", - "Redirect target to host:port and transmit token"] - ); - -# Possible command to xrdadmin -# -my %cmds = ("help" => ["help [request]", - "Display help (for request)"], - "exit" => ["exit", - "Exit xrdadmin (also ^D)"], - "listterm" => ["listterm", - "List the features of Term::Readline"] - ); - - - -#(see "M A I N" for the main program at the end)# - -#****************************************************************************** -#* u s a g e * -#****************************************************************************** -sub usage { - my($exit, $message) = @_; - - print STDERR $message if defined $message; - print STDERR <This is the value -# -# Input: $token name of token -# $msg XML message -# $hash reference to a hash -# $end reference to a scalar -# -# Ouput: $hash hash with XML attributes of token -# $end position in $msg where the token ended -# -1 if the token was not found -# -# Return: value of token ("" if the token was not found) -# -sub getToken { - my ($token, $msg, $hash, $end) = @_; - - (my $attr, my $value) = ($msg =~ m#<$token(.*?)>(.*?)#); - if ($#- < 0) { # no match - $$end = -1; - return ""; - } - else { # match - $$end = $+[0]; - } - - $attr =~ s/^\s+|\s+$//g; # remove leading/trailing whitespaces - while (not $attr eq "") { - $attr =~ /\s*(\S+?)\s*=\s*(\S+)\s*/; # match one 'id = value' entity - $attr = substr($attr, $+[2]); # cut off the matched part - $hash->{$1} = $2; # store in hash - $hash->{$1} =~ s/\"//g; # remove quotes - } - - return $value; -} - - - -sub stripQuotes { - my ($s) = @_; - - $s =~ s/[\"\']//g; - return $s; -} - -#****************************************************************************** -#* h a n d l e R e s p * -#****************************************************************************** -# This routine handles the XML response from the xrootd server -# -# Input: $msg XML message from xrootd -# -sub handleResp { - my ($msg) = @_; - - chomp ($msg); - print "Received: $msg\n" if $opts{debug}; - - # Get request iD - my ($id) = ($msg =~ m##); - - # Get error code - my $error = getToken('rc',$msg); - - # If error, print error message and return - if ($error != 0) { - my $errorMsg = getToken('msg',$msg); - print "$id: Error $error: $errorMsg\n"; - return; - } - - # Now handle the known request ID's - if ($id eq "login") { - my $version = getToken("v", $msg); - print "$id: Successfull. Protocol version $version\n"; - } - - elsif ($id eq "abort") { - my $num = getToken("num", $msg); - print "$id: $num abort requests sent.\n"; - } - - elsif ($id eq "close") { - my $num = getToken("num", $msg); - print "$id: $num connections closed.\n"; - } - - elsif ($id eq "cont" || $id eq "disc" || $id eq "msg" || - $id eq "pause" || $id eq "redirect") { - my $num = getToken("num", $msg); - print "$id: $num '$id' requests sent to clients.\n"; - } - - elsif ($id eq "lsc") { - my $conn = getToken("conn", $msg); - if ($conn eq "") { print "No connections.\n"; } - else { - my @list = split(" ",$conn); - print join("\n",@list)."\n"; - } - } - - elsif ($id eq "lsd") { - handle_lsd($msg); - } - - else { - print "Unkown response ID '$id'. Server response was:\n$msg\n" - } -} - -#****************************************************************************** -#* h a n d l e _ l s d * -#****************************************************************************** -# This routine handles the server response to the 'lsd' command -# -# Input: $msg XML message from xrootd -# -sub handle_lsd() { - (my $msg) = @_; - - while (not $msg eq "") { - my %hash = (); - my $end = 0; - - my $tok = getToken("c", $msg, \%hash, \$end); - last if $tok eq ""; - - # Cut off this part of the message - $msg = substr($msg, $end); - - my ($cname) = ($tok =~ m#(.*?)<#); # everything before next '<' - - # IO token - my %iohash = (); - my $iotok = getToken("io", $tok, \%iohash); - my $nfiles = getToken("nf", $iotok); - - my $ptok = getToken("p", $iotok); - my ($pbytes) = ($ptok =~ m#(.*?)<#); - my $pcnt = getToken("n", $ptok); - - my $itok = getToken("i", $iotok); - my ($ibytes) = ($itok =~ m#(.*?)<#); - my $wcnt = getToken("n", $itok); - - my $otok = getToken("o", $iotok); - my ($obytes) = ($otok =~ m#(.*?)<#); - my $rcnt = getToken("n", $otok); - - my $stalls = getToken("s", $iotok); - my $tardies = getToken("t", $iotok); - - my $role = "not available"; - if (exists $hash{r}) { - if ($hash{r} eq "a") { $role = "administrative"; } - elsif ($hash{r} eq "u") { $role = "user"; } - else { $role = "unkown role ($hash{r})"; } - } - - my $mon = "none"; - if (exists $hash{m} && not $hash{m} eq "") { - if ($hash{m} eq "f") { $mon = "file-level"; } - elsif ($hash{m} eq "i") { $mon = "I/O-level"; } - else { $mon = "unkown ($hash{m})"; } - } - - # AUTH token - my %ahash = (); - my $atok = getToken("auth", $tok, \%ahash); - my ($name, $host, $org, $arole); - if (not $atok eq "") { - $name = getToken("n", $atok); - $host = getToken("h", $atok); - $org = getToken("o", $atok); - $arole = getToken("r", $atok); - } - - # Print information - print "$cname\n"; - print " Role: $role\n"; - print " Connect time: ",scalar(localtime($hash{t})),"\n"; - print " Client capabilities: ",($hash{v} & 192)>>6,"\n"; - print " Protocol: ",($hash{v} & 63),"\n"; - print " Monitoring: $mon\n"; - - print " I/O statistics:\n"; - print " References: $iohash{u}\n"; - print " Open files: $nfiles\n"; - print " Pre-read: $pbytes bytes, $pcnt requests\n"; - print " Read: $ibytes bytes, $wcnt requests\n"; - print " Write: $obytes bytes, $rcnt request\n"; - print " stalls: $stalls tardies: $tardies\n"; - - if (not $atok eq "") { - print " Authentication:\n"; - print " Protocol: $ahash{p}\n"; - print " Name: $name\n"; - print " Host: $host\n"; - print " Organization: $org\n"; - print " Role: $arole\n"; - } - print "\n"; - } -} - - - -#****************************************************************************** -#* X R D A D M I N * -#****************************************************************************** -# Main subroutine -# -sub xrdadmin() { - - # Set backspace as erase character - # - system ('stty erase \^?'); - - # Setup the ReadLine module - # - my $term = new Term::ReadLine 'PerlSQ'; - my $OUT = $term->OUT || *STDOUT{IO}; - - $term->newTTY(*STDIN, $OUT); - - # Allocate a socket if we do not have one - # - my $path; - exit 8 if !fileno(XRDSOCK) && !getSock($iname, $path); - - # Send the login sequence - # - my @pwd = getpwuid($>); - my $adminID = $pwd[0].'.'.$$; - exit 16 if !sendMsg("login",$adminID); - my $Resp = ; - handleResp($Resp); - - # Continue getting commands here - # - while (defined(my $line = $term->readline("xrdadmin> ")) ) { - - $line =~ s/^\s+|\s+$//g; # remove whitespaces - next if $line eq ""; # ignore empty lines - - my ($cmd,$args) = ("",""); - ($cmd,$args) = split(/ /,$line,2); # split into cmd and args - if (length($cmd)>15) { - print "The command $cmd is too long (>15 characters).\n"; - next; - } - - # Handle xrdadmin command - # - if ($cmd eq "help") { printHelp($args); } - elsif ($cmd eq "exit" || $cmd eq "bye") { last; } - elsif ($cmd eq "listterm") { - foreach (keys %{$term->Features}) { print; print "\n";} - } - - # Handle requests to xrootd - # - else { - sendMsg($cmd, $args); - $Resp = ; - handleResp($Resp); - } - } - return 0; -} - - -#****************************************************************************** -#* M A I N * -#****************************************************************************** - -if ($#ARGV==0) { - - # This will start xrdadmin on the server via an ssh-tunnel - - my $host = $ARGV[0]; - my $options = ''; - $options = " -d" if $opts{debug}; - $options .= " -i $iname" if ($iname); - $adminDir = '$XRDADMIN' if ($adminDir eq ''); - my $cmd = "ssh -xt $ARGV[0] 'eval $adminDir/xrdadmin $options'"; - print "Executing $cmd\n" if $opts{debug}; - exit system ($cmd); -} -else { - - # Server runs on the local machine - exit xrdadmin(); -} diff --git a/src/XrdCms/XrdCmsAdmin.cc b/src/XrdCms/XrdCmsAdmin.cc deleted file mode 100644 index 277af4c0851..00000000000 --- a/src/XrdCms/XrdCmsAdmin.cc +++ /dev/null @@ -1,810 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C m s A d m i n . c c */ -/* */ -/* (c) 2007 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include - -#include "XProtocol/XProtocol.hh" -#include "XProtocol/YProtocol.hh" - -#include "XrdCms/XrdCmsAdmin.hh" -#include "XrdCms/XrdCmsConfig.hh" -#include "XrdCms/XrdCmsManager.hh" -#include "XrdCms/XrdCmsPrepare.hh" -#include "XrdCms/XrdCmsState.hh" -#include "XrdCms/XrdCmsTrace.hh" -#include "XrdNet/XrdNetSocket.hh" -#include "XrdOuc/XrdOuca2x.hh" -#include "XrdOuc/XrdOucName2Name.hh" -#include "XrdOuc/XrdOucTList.hh" -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysPlatform.hh" -#include "XrdSys/XrdSysTimer.hh" - -using namespace XrdCms; - -/******************************************************************************/ -/* L o c a l C l a s s e s */ -/******************************************************************************/ - -namespace XrdCms -{ -class AdminReq -{ -public: - - AdminReq *Next; -const char *Req; -const char *Path; - CmsRRHdr Hdr; - char *Data; - int Dlen; -static int numinQ; -static const int maxinQ = 1024; - -static AdminReq *getReq() {AdminReq *arP; - do {QPresent.Wait(); - QMutex.Lock(); - if ((arP = First)) - {if (!(First = arP->Next)) Last = 0; - numinQ--; - } - QMutex.UnLock(); - } while (!arP); - return arP; - } - - void Requeue() {QMutex.Lock(); - Next=First; First=this; QPresent.Post(); numinQ++; - QMutex.UnLock(); - } - - AdminReq(const char *req, XrdCmsRRData &RRD) - : Next(0), Req(req), Path(RRD.Path ? RRD.Path : ""), - Hdr(RRD.Request), Data(RRD.Buff), Dlen(RRD.Dlen) - {RRD.Buff = 0; - QMutex.Lock(); - if (Last) {Last->Next = this; Last = this;} - else First=Last = this; - QPresent.Post(); - numinQ++; - QMutex.UnLock(); - } - - ~AdminReq() {if (Data) free(Data);} - -private: - -static XrdSysSemaphore QPresent; -static XrdSysMutex QMutex; -static AdminReq *First; -static AdminReq *Last; -}; -}; - -/******************************************************************************/ -/* G l o b a l s & S t a t i c s */ -/******************************************************************************/ - - - XrdSysSemaphore AdminReq::QPresent(0); - XrdSysMutex AdminReq::QMutex; - AdminReq *AdminReq::First = 0; - AdminReq *AdminReq::Last = 0; - int AdminReq::numinQ= 0; - - XrdOssStatInfo2_t XrdCmsAdmin::areFunc = 0; - XrdOucTList *XrdCmsAdmin::areFirst = 0; - XrdOucTList *XrdCmsAdmin::areLast = 0; - XrdSysMutex XrdCmsAdmin::areMutex; - XrdSysSemaphore XrdCmsAdmin::areSem(0); - bool XrdCmsAdmin::arePost = false; - - XrdSysMutex XrdCmsAdmin::myMutex; - XrdSysSemaphore *XrdCmsAdmin::SyncUp = 0; - int XrdCmsAdmin::POnline= 0; - -/******************************************************************************/ -/* E x t e r n a l T h r e a d I n t e r f a c e s */ -/******************************************************************************/ - -void *XrdCmsAdminLogin(void *carg) - {XrdCmsAdmin *Admin = new XrdCmsAdmin(); - Admin->Login(*(int *)carg); - delete Admin; - return (void *)0; - } - -void *XrdCmsAdminMonAds(void *carg) - {XrdCmsAdmin *Admin = (XrdCmsAdmin *)carg; - Admin->MonAds(); - return (void *)0; - } - -void *XrdCmsAdminMonARE(void *carg) - {XrdCmsAdmin::RelayAREvent(); - return (void *)0; - } - -void *XrdCmsAdminSend(void *carg) - {XrdCmsAdmin::Relay(0,0); - return (void *)0; - } - -/******************************************************************************/ -/* I n i t A R E v e n t s */ -/******************************************************************************/ - -bool XrdCmsAdmin::InitAREvents(void *arFunc) -{ - pthread_t tid; - -// Record the function we will be using -// - areFunc = (XrdOssStatInfo2_t)arFunc; - -// Start the event relay -// - if (XrdSysThread::Run(&tid,XrdCmsAdminMonARE,(void *)0)) - {Say.Emsg("InitAREvents", errno, "start arevent relay"); - return false; - } - -// All done -// - return true; -} - -/******************************************************************************/ -/* L o g i n */ -/******************************************************************************/ - -void XrdCmsAdmin::Login(int socknum) -{ - const char *epname = "Admin_Login"; - const char *sMsg[2] = {"temporary suspend requested by", - "long-term suspend requested by"}; - char *request, *tp; - int sPerm; - -// Attach the socket FD to a stream -// - Stream.Attach(socknum); - -// The first request better be "login" -// - if ((request = Stream.GetLine())) - {DEBUG("initial request: '" < 0); - } while(rc < 0 && errno == EINTR); - - if (rc < 0) Say.Emsg(epname, errno, "maintain contact with", pname); - else Say.Emsg(epname,"Lost contact with", pname); - - CmsState.Update(XrdCmsState::FrontEnd, 0, -1); - close(sFD); - XrdSysTimer::Snooze(15); - - } while(1); -} - -/******************************************************************************/ -/* N o t e s */ -/******************************************************************************/ - -void *XrdCmsAdmin::Notes(XrdNetSocket *AnoteSock) -{ - const char *epname = "Notes"; - char *request, *tp; - int rc; - -// Bind the udp socket to a stream -// - Stream.Attach(AnoteSock->Detach()); - Sname = strdup("anon"); - -// Accept notifications in an endless loop -// - do {while((request = Stream.GetLine())) - {DEBUG("received notification: '" <= 0) close(curSock); - else if (newSock >= 0) SReady.Post(); - if (newSock < 0) curSock = -1; - else {curSock = dup(newSock); XrdNetSocket::setOpts(curSock, 0);} - SMutex.UnLock(); - return; - } - -// This is just an endless loop -// - do {while(mySock < 0) - {SMutex.Lock(); - if (curSock < 0) {SMutex.UnLock(); SReady.Wait(); SMutex.Lock();} - mySock = curSock; curSock = -1; - SMutex.UnLock(); - } - - do {arP = AdminReq::getReq(); - - if ((retc = write(mySock, &arP->Hdr, HdrSz)) != HdrSz - || (retc = write(mySock, arP->Data, arP->Dlen)) != arP->Dlen) - retc = (retc < 0 ? errno : ECANCELED); - else {DEBUG("sent " <Req <<' ' <Path); - delete arP; retc = 0; - } - } while(retc == 0); - - if (retc) Say.Emsg("AdminRelay", retc, "relay", arP->Req); - arP->Requeue(); - close(mySock); - mySock = -1; - } while(1); -} - -/******************************************************************************/ -/* R e l a y A R E v e n t */ -/******************************************************************************/ - -void XrdCmsAdmin::RelayAREvent() -{ - EPNAME("RelayAREvent"); - const char *evWhat; - XrdOucTList *evP; - int evType, mod; - -// Endless loop relaying events -// -do{areMutex.Lock(); - while((evP = areFirst)) - {if (evP == areLast) areFirst = areLast = 0; - else areFirst = evP->next; - areMutex.UnLock(); - XrdCms::CmsReqCode reqCode = static_cast(evP->ival[0]); - mod = evP->ival[1]; - if (reqCode == kYR_have) - {if (mod & CmsHaveRequest::Pending) - {evType = XrdOssStatEvent::PendAdded; - evWhat = "pend "; - } else { - evType = XrdOssStatEvent::FileAdded; - evWhat = "have "; - } - } else { - evType = XrdOssStatEvent::FileRemoved; - evWhat = "gone "; - } - (*areFunc)(evP->text, 0, evType, 0, evP->text); - DEBUG("sending managers " <text); - XrdCmsManager::Inform(reqCode, mod, evP->text, strlen(evP->text)+1); - delete evP; - areMutex.Lock(); - } - arePost = true; - areMutex.UnLock(); - areSem.Wait(); - } while(true); -} - -/******************************************************************************/ -/* S e n d */ -/******************************************************************************/ - -void XrdCmsAdmin::Send(const char *Req, XrdCmsRRData &Data) -{ -// AdminReq *arP; - - if (AdminReq::numinQ < AdminReq::maxinQ) new AdminReq(Req, Data); - else Say.Emsg("Send", "Queue full; ignoring", Req, Data.Path); -} - -/******************************************************************************/ -/* S t a r t */ -/******************************************************************************/ - -void *XrdCmsAdmin::Start(XrdNetSocket *AdminSock) -{ - const char *epname = "Start"; - int InSock; - pthread_t tid; - -// Start the relay thread -// - if (XrdSysThread::Run(&tid,XrdCmsAdminSend,(void *)0)) - Say.Emsg(epname, errno, "start admin relay"); - -// If we are in independent mode then let the caller continue -// - if (Config.doWait) - {if (Config.adsPort) BegAds(); - else Say.Emsg(epname, "Waiting for primary server to login."); - } - else if (SyncUp) {SyncUp->Post(); SyncUp = 0;} - -// Accept connections in an endless loop -// - while(1) if ((InSock = AdminSock->Accept()) >= 0) - {XrdNetSocket::setOpts(InSock, 0); - if (XrdSysThread::Run(&tid,XrdCmsAdminLogin,(void *)&InSock)) - {Say.Emsg(epname, errno, "start admin"); - close(InSock); - } - } else Say.Emsg(epname, errno, "accept connection"); - return (void *)0; -} - -/******************************************************************************/ -/* P r i v a t e M e t h o d s */ -/******************************************************************************/ -/******************************************************************************/ -/* A d d E v e n t */ -/******************************************************************************/ - -void XrdCmsAdmin::AddEvent(const char *path, XrdCms::CmsReqCode req, int mods) -{ - int info[2] = {(int)req, mods}; - XrdOucTList *evP = new XrdOucTList(path, info); - -// Add the event to he queue -// - areMutex.Lock(); - if (areLast) areLast->next = evP; - else areFirst = evP; - areLast = evP; - if (arePost) {areSem.Post(); arePost = false;} - areMutex.UnLock(); -} - -/******************************************************************************/ -/* B e g A d s */ -/******************************************************************************/ - -void XrdCmsAdmin::BegAds() -{ - const char *epname = "BegAds"; - pthread_t tid; - -// If we don't need to monitor he alternate data server then we are all set -// - if (!Config.adsMon) - {Say.Emsg(epname, "Assuming alternate data server is functional."); - CmsState.Update(XrdCmsState::FrontEnd, 1, Config.adsPort); - if (SyncUp) {SyncUp->Post(); SyncUp = 0;} - return; - } - -// Start the connection/ping thread for the alternate data server -// - if (XrdSysThread::Run(&tid,XrdCmsAdminMonAds,(void *)this)) - Say.Emsg(epname, errno, "start alternate data server monitor"); -} - -/******************************************************************************/ -/* C h e c k V N i d */ -/******************************************************************************/ - -bool XrdCmsAdmin::CheckVNid(const char *xNid) -{ - -// Check if we have a vnid but the server is supplying one or is not the same -// - if (Config.myVNID) - {if (!xNid) - {Say.Emsg("do_Login", "Warning! No xrootd vnid specified; " - "proceeding only with cmsd vnid."); - return true; - } - if (!strcmp(xNid, Config.myVNID)) return true; - std::string msg("xrootd vnid '"); - msg += xNid; msg += "' does not match cmsd vnid '"; - msg += Config.myVNID; msg += "'."; - Say.Emsg("do_Login", msg.c_str()); - return false; - } - -// We don't have a vnid, check if one is present -// - if (xNid) Say.Emsg("do_Login", "Warning! xrootd has a vnid but cmsd does " - "not; proceeding without a vnid!"); - return true; -} - -/******************************************************************************/ -/* C o n 2 A d s */ -/******************************************************************************/ - -int XrdCmsAdmin::Con2Ads(const char *pname) -{ - const char *epname = "Con2Ads"; - static ClientInitHandShake hsVal = {0, 0, 0, (int)htonl(4), (int)htonl(2012)}; - static ClientLoginRequest loginReq = {{0, 0}, - (kXR_unt16)htons(kXR_login), - (kXR_int32)htonl(getpid()), - {'c', 'm', 's', 'd', 0, 0, 0, 0}, - 0, 0, {0}, {0}, 0}; - struct {kXR_int32 siHS[4];} hsRsp; - XrdNetSocket adsSocket; - int ecode, snum; - char ecnt = 10; - -// Create a socket and to connect to the alternate data server -// -do{while((snum = adsSocket.Open("localhost", Config.adsPort)) < 0) - {if (ecnt >= 10) - {ecode = adsSocket.LastError(); - Say.Emsg(epname, ecode, "connect to", pname); - ecnt = 1; - } else ecnt++; - XrdSysTimer::Snooze(3); - } - -// Write the handshake to make sure the connection went fine -// - if (write(snum, &hsVal, sizeof(hsVal)) < 0) - {Say.Emsg(epname, errno, "send handshake to", pname); - close(snum); continue; - } - -// Read the mandatory response -// - if (recv(snum, &hsRsp, sizeof(hsRsp), MSG_WAITALL) < 0) - {Say.Emsg(epname, errno, "recv handshake from", pname); - close(snum); continue; - } - -// Now we need to send the login request -// - if (write(snum, &loginReq, sizeof(loginReq)) < 0) - {Say.Emsg(epname, errno, "send login to", pname); - close(snum); continue; - } else break; - - } while(1); - -// Indicate what we just did -// - Say.Emsg(epname, "Logged into", pname); - -// We connected, so we indicate that the alternate is ok -// - myMutex.Lock(); - CmsState.Update(XrdCmsState::FrontEnd, 1, Config.adsPort); - if (SyncUp) {SyncUp->Post(); SyncUp = 0;} - myMutex.UnLock(); - -// All done -// - return adsSocket.Detach(); -} - -/******************************************************************************/ -/* d o _ L o g i n */ -/******************************************************************************/ - -int XrdCmsAdmin::do_Login() -{ - std::string vnidVal; - const char *emsg; - char buff[64], *tp, Ltype = 0; - int Port = 0; - -// Process: login {p | P | s | u} [port ] [nid ] -// - if (!(tp = Stream.GetToken())) - {Say.Emsg("do_Login", "login type not specified"); - return 0; - } - - Ltype = *tp; - if (*(tp+1) == '\0') - switch (*tp) - {case 'p': Stype = "Primary server"; break; - case 'P': Stype = "Proxy server"; break; - case 's': Stype = "Server"; break; - case 'u': Stype = "Admin"; break; - default: Ltype = 0; break; - } - - if (!Ltype) - {Say.Emsg("do_Login", "Invalid login type,", tp); - return 0; - } else Ltype = *tp; - - if (Config.adsPort && Ltype != 'u') - {Say.Emsg("do_login", Stype, " login rejected; configured for an " - "alternate data server."); - return 0; - } - - if (!(tp = Stream.GetToken())) - {Say.Emsg("do_Login", "login name not specified"); - return 0; - } else Sname = strdup(tp); - -// Get any additional options -// - while((tp = Stream.GetToken())) - { if (!strcmp(tp, "port")) - {if (!(tp = Stream.GetToken())) - {Say.Emsg("do_Login", "login port not specified"); - return 0; - } - if (XrdOuca2x::a2i(Say,"login port",tp,&Port,0)) - return 0; - } - else if (!strcmp(tp, "vnid")) - {if (!(tp = Stream.GetToken())) - {Say.Emsg("do_Login", "vnid value not specified"); - return 0; - } - vnidVal = tp; - } - else {Say.Emsg("do_Login", "invalid login option -", tp); - return 0; - } - } - -// If this is not a primary, we are done. Otherwise there is much more. We -// must make sure we are compatible with the login. Note that for alternate -// data servers we already screened out primary logins, so we will return. -// - if (Ltype != 'p' && Ltype != 'P') return 1; - if (Ltype == 'p' && Config.asProxy()) emsg = "only accepts proxies"; - else if (Ltype == 'P' && !Config.asProxy()) emsg = "does not accept proxies"; - else emsg = 0; - if (emsg) - {Say.Emsg("do_login", "Server login rejected; configured role", emsg); - return 0; - } - -// Verify virtual networking -// - if ((vnidVal.length() || Config.myVNID) - && !CheckVNid(vnidVal.length() ? vnidVal.c_str() : 0)) - {Say.Emsg("do_login", "Server login rejected; virtual networking error."); - return 0; - } - -// Discard login if this is a duplicate primary server -// - myMutex.Lock(); - if (POnline) - {myMutex.UnLock(); - Say.Emsg("do_Login", "Primary server already logged in; login of", - tp, "rejected."); - return 0; - } - -// Indicate we have a primary -// - Primary = 1; - POnline = 1; - Relay(1, Stream.FDNum()); - CmsState.Update(XrdCmsState::FrontEnd, 1, Port); - -// Check if this is the first primary login and resume if we must -// - if (SyncUp) {SyncUp->Post(); SyncUp = 0;} - myMutex.UnLock(); - -// Document the login -// - sprintf(buff, "logged in; data port is %d", Port); - Say.Emsg("do_Login:", Stype, Sname, buff); - return 1; -} - -/******************************************************************************/ -/* d o _ R m D i d */ -/******************************************************************************/ - -void XrdCmsAdmin::do_RmDid(int isPfn) -{ - const char *epname = "do_RmDid"; - char *tp, *thePath, apath[XrdCmsMAX_PATH_LEN]; - int rc; - - if (!(tp = Stream.GetToken())) - {Say.Emsg(epname,"removed path not specified by",Stype,Sname); - return; - } - -// Handle prepare queue removal -// - if (PrepQ.isOK()) - {if (!isPfn && Config.lcl_N2N) - if ((rc = Config.lcl_N2N->lfn2pfn(tp, apath, sizeof(apath)))) - {Say.Emsg(epname, rc, "determine pfn for removed path", tp); - thePath = 0; - } else thePath = apath; - else thePath = tp; - if (thePath) PrepQ.Gone(thePath); - } - -// If we have a pfn then we must get the lfn to inform our manager about the file -// - if (isPfn && Config.lcl_N2N) - {if ((rc = Config.lcl_N2N->pfn2lfn(tp, apath, sizeof(apath)))) - {Say.Emsg(epname, rc, "determine lfn for removed path", tp); - return; - } else tp = apath; - } - -// Check if we are relaying remove events and, if so, vector through that. -// - if (areFunc) AddEvent(tp, kYR_gone, kYR_raw); - else {DEBUG("sending managers gone " <pfn2lfn(tp, apath, sizeof(apath)))) - {Say.Emsg(epname, rc, "determine lfn for added path", tp); - return; - } else tp = apath; - } - -// Check if we are relaying remove events and, if so, vector through that. -// - if (areFunc) AddEvent(tp, kYR_have, Mods); - else {DEBUG("sending managers have online " <. */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -#include "XrdCms/XrdCmsProtocol.hh" -#include "XrdCms/XrdCmsRRData.hh" -#include "XrdOss/XrdOssStatInfo.hh" -#include "XrdOuc/XrdOucStream.hh" -#include "XrdSys/XrdSysPthread.hh" - -class XrdNetSocket; -class XrdOucTList; - -class XrdCmsAdmin -{ -public: - -static bool InitAREvents(void *arFunc); - - void Login(int socknum); - - void MonAds(); - -static void setSync(XrdSysSemaphore *sync) {SyncUp = sync;} - - void *Notes(XrdNetSocket *AdminSock); - -static void Relay(int setSock, int newSock); - -static void RelayAREvent(); - - void Send(const char *Req, XrdCmsRRData &Data); - - void *Start(XrdNetSocket *AdminSock); - - XrdCmsAdmin() {Sname = 0; Stype = "Server"; Primary = 0;} - ~XrdCmsAdmin() {if (Sname) free(Sname);} - -private: - -static -void AddEvent(const char *path, XrdCms::CmsReqCode req, int mods); -void BegAds(); -bool CheckVNid(const char *xNid); -int Con2Ads(const char *pname); -int do_Login(); -void do_RmDid(int dotrim=0); -void do_RmDud(int dotrim=0); - -static XrdOssStatInfo2_t areFunc; -static XrdOucTList *areFirst; -static XrdOucTList *areLast; -static XrdSysMutex areMutex; -static XrdSysSemaphore areSem; -static bool arePost; - -static XrdSysMutex myMutex; -static XrdSysSemaphore *SyncUp; -static int POnline; - XrdOucStream Stream; - const char *Stype; - char *Sname; - int Primary; -}; -#endif diff --git a/src/XrdCms/XrdCmsBaseFS.cc b/src/XrdCms/XrdCmsBaseFS.cc deleted file mode 100644 index bf5ac0b571d..00000000000 --- a/src/XrdCms/XrdCmsBaseFS.cc +++ /dev/null @@ -1,420 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C m s B a s e F S . c c */ -/* */ -/* (c) 2011 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include - -#include "XProtocol/YProtocol.hh" -#include "XProtocol/XPtypes.hh" - -#include "XrdCms/XrdCmsBaseFS.hh" -#include "XrdCms/XrdCmsConfig.hh" -#include "XrdCms/XrdCmsPrepare.hh" -#include "XrdCms/XrdCmsTrace.hh" - -#include "XrdOss/XrdOss.hh" - -#include "XrdSfs/XrdSfsFlags.hh" - -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysTimer.hh" - -using namespace XrdCms; - -/******************************************************************************/ -/* E x t e r n a l T h r e a d I n t e r f a c e s */ -/******************************************************************************/ - -void *XrdCmsBasePacer(void *carg) - {((XrdCmsBaseFS *)carg)->Pacer(); - return (void *)0; - } - -void *XrdCmsBaseRunner(void *carg) - {((XrdCmsBaseFS *)carg)->Runner(); - return (void *)0; - } - -/******************************************************************************/ -/* Private: B y p a s s */ -/******************************************************************************/ - -int XrdCmsBaseFS::Bypass() -{ - static XrdSysTimer Window; - -// If we are not timing requests, we can bypass (typically checked beforehand) -// - if (!theQ.rLimit) return 1; - -// If this is a fixed rate queue then we cannot bypass -// - if (Fixed) return 0; - -// Check if we can reset the number of requests that can be issued inline. We -// do this to bypass the queue unless until we get flooded by requests. -// - theQ.Mutex.Lock(); - if (!theQ.rLeft && !theQ.pqFirst) - {unsigned long Interval = 0; - Window.Report(Interval); - if (Interval >= 450) - {theQ.rLeft = theQ.rAgain; - Window.Reset(); - cerr <<"BYPASS " <= 0 && Path[fnPos] != '/'; fnPos--) {} - if (fnPos > 0 && !hasDir(Path, fnPos)) return -1; - } - -// Issue stat() via oss plugin. If it succeeds, return result. -// - if (!Config.ossFS->Stat(Path, &buf, Opts)) - {if ((buf.st_mode & S_IFMT) == S_IFREG) - return (buf.st_mode & XRDSFS_POSCPEND ? CmsHaveRequest::Pending - : CmsHaveRequest::Online); - - return (buf.st_mode & S_IFMT) == S_IFDIR ? CmsHaveRequest::Online : -1; - } - -// The entry does not exist but if we are a staging server then it may be in -// the prepare queue in which case we must say that it is pending arrival. -// - if (Config.DiskSS && PrepQ.Exists(Path)) return CmsHaveRequest::Pending; - -// The entry does not exist. Check if the directory exists and if not, put it -// in our directory missing table so we don't keep hitting this directory. -// This is disabled by default and enabled by the cms.dfs directive. -// - if (fnPos > 0 && dmLife) - {struct dMoP *xVal = &dirMiss; - int xLife = dmLife; - Path[fnPos] = '\0'; - if (!Config.ossFS->Stat(Path, &buf, XRDOSS_resonly)) - {xLife = dpLife; xVal = &dirPres;} - fsMutex.Lock(); - fsDirMP.Rep(Path, xVal, xLife, Hash_keepdata); - fsMutex.UnLock(); - DEBUG("add " <Present ? " okdir " : " nodir ") <Present : 1); - fsMutex.UnLock(); - Path[fnPos] = '/'; - return Have; -} - -/******************************************************************************/ -/* I n i t */ -/******************************************************************************/ - -void XrdCmsBaseFS::Init(int Opts, int DMLife, int DPLife) -{ - -// Set values. -// - dmLife = DMLife; - dpLife = DPLife ? DPLife : DMLife * 10; - Server = (Opts & Servr) != 0; - lclStat = (Opts & Cntrl) != 0 || Server; - preSel = (Opts & Immed) == 0; - dfsSys = (Opts & DFSys) != 0; -} - -/******************************************************************************/ -/* L i m i t */ -/******************************************************************************/ - -void XrdCmsBaseFS::Limit(int rLim, int Qmax) -{ - -// Establish the limits -// - if (rLim < 0) {theQ.rAgain=theQ.rLeft = -1; rLim = -rLim; Fixed = 1;} - else {theQ.rAgain = theQ.rLeft = (rLim > 1 ? rLim/2 : 1); Fixed = 0;} - theQ.rLimit = (rLim <= 1000 ? rLim : 0); - if (Qmax > 0) theQ.qMax = Qmax; - else if (!(theQ.qMax = theQ.rLimit*2 + theQ.rLimit/2)) theQ.qMax = 1; -} - -/******************************************************************************/ -/* P a c e r */ -/******************************************************************************/ - -void XrdCmsBaseFS::Pacer() -{ - XrdCmsBaseFR *rP; - int inQ, rqRate = 1000/theQ.rLimit; - -// Process requests at the given rate -// -do{theQ.pqAvail.Wait(); - theQ.Mutex.Lock(); inQ = 1; - while((rP = theQ.pqFirst)) - {if (!(theQ.pqFirst = rP->Next)) {theQ.pqLast = 0; inQ = 0;} - theQ.Mutex.UnLock(); - if (rP->PDirLen > 0 && !hasDir(rP->Path, rP->PDirLen)) - {delete rP; continue;} - theQ.Mutex.Lock(); - if (theQ.rqFirst) {theQ.rqLast->Next = rP; theQ.rqLast = rP;} - else {theQ.rqFirst = theQ.rqLast = rP; theQ.rqAvail.Post();} - theQ.Mutex.UnLock(); - XrdSysTimer::Wait(rqRate); - if (!inQ) break; - theQ.Mutex.Lock(); - } - if (inQ) theQ.Mutex.UnLock(); - } while(1); -} - -/******************************************************************************/ -/* Q u e u e */ -/******************************************************************************/ - -void XrdCmsBaseFS::Queue(XrdCmsRRData &Arg, XrdCmsPInfo &Who, - int fnpos, int Force) -{ - EPNAME("Queue"); - static int noMsg = 1; - XrdCmsBaseFR *rP; - int Msg, n, prevHWM; - -// If we can bypass the queue and execute this now. Avoid the grabbing the buff. -// - if (!Force) - {XrdCmsBaseFR myReq(&Arg, Who, fnpos); - Xeq(&myReq); - return; - } - -// Queue this request for callback after an appropriate time. -// We will also steal the underlying data buffer from the Arg. -// - DEBUG("inq " < prevHWM))) theQ.qHWM = n; - if (theQ.pqFirst) {theQ.pqLast->Next = rP; theQ.pqLast = rP;} - else {theQ.pqFirst = theQ.pqLast = rP; theQ.pqAvail.Post();} - theQ.Mutex.UnLock(); - -// Issue a warning message if we have an excessive number of requests queued -// - if (n > theQ.qMax && Msg && (n-prevHWM > 3 || noMsg)) - {int Pct = n/theQ.qMax; - char Buff[80]; - noMsg = 0; - sprintf(Buff, "Queue overrun %d%%; %d requests now queued.", Pct, n); - Say.Emsg("Pacer", Buff); - } -} - -/******************************************************************************/ -/* R u n n e r */ -/******************************************************************************/ - -void XrdCmsBaseFS::Runner() -{ - XrdCmsBaseFR *rP; - int inQ; - -// Process requests at the given rate -// -do{theQ.rqAvail.Wait(); - theQ.Mutex.Lock(); inQ = 1; - while((rP = theQ.rqFirst)) - {if (!(theQ.rqFirst = rP->Next)) {theQ.rqLast = 0; inQ = 0;} - theQ.qNum--; - theQ.Mutex.UnLock(); - Xeq(rP); delete rP; - if (!inQ) break; - theQ.Mutex.Lock(); - } - if (inQ) theQ.Mutex.UnLock(); - } while(1); -} - -/******************************************************************************/ -/* S t a r t */ -/******************************************************************************/ - -void XrdCmsBaseFS::Start() -{ - EPNAME("Start"); - void *Me = (void *)this; - pthread_t tid; - -// Issue some debugging here so we know how we are starting up -// - DEBUG("Srv=" <PDirLen > 0 && !hasDir(rP->Path, rP->PDirLen)) - {if (cBack) (*cBack)(rP, -1); - return; - } - -// If we have exceeded the queue limit and this is a meta-manager request -// then just deep-six it. Local requests must complete -// - if (theQ.qNum > theQ.qMax) - {Say.Emsg("Xeq", "Queue limit exceeded; ignoring lkup for", rP->Path); - return; - } - -// Perform a local stat() and if we don't have the file -// - rc = Exists(rP->Path, rP->PDirLen); - if (cBack) (*cBack)(rP, rc); -} diff --git a/src/XrdCms/XrdCmsBaseFS.hh b/src/XrdCms/XrdCmsBaseFS.hh deleted file mode 100644 index 5b904da433f..00000000000 --- a/src/XrdCms/XrdCmsBaseFS.hh +++ /dev/null @@ -1,207 +0,0 @@ -#ifndef __CMS_BASEFS_H__ -#define __CMS_BASEFS_H__ -/******************************************************************************/ -/* */ -/* X r d C m s B a s e F S . h h */ -/* */ -/* (c) 2011 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -#include "XrdCms/XrdCmsPList.hh" -#include "XrdCms/XrdCmsRRData.hh" -#include "XrdCms/XrdCmsTypes.hh" -#include "XrdOuc/XrdOucHash.hh" -#include "XrdSys/XrdSysPthread.hh" - -/******************************************************************************/ -/* C l a s s X r d C m s B a s e F R */ -/******************************************************************************/ - -class XrdCmsPInfo; - -class XrdCmsBaseFR -{ -public: - -SMask_t Route; -SMask_t RouteW; -XrdCmsBaseFR *Next; -char *Buff; -char *Path; -short PathLen; -short PDirLen; -kXR_unt32 Sid; -kXR_char Mod; - - XrdCmsBaseFR(XrdCmsRRData &Arg, XrdCmsPInfo &Who, int Dln) - : Route(Who.rovec), RouteW(Who.rwvec), Next(0), - PathLen(Arg.PathLen), PDirLen(Dln), - Sid(Arg.Request.streamid), - Mod(Arg.Request.modifier) - {if (Arg.Buff) - {Path=Arg.Path; Buff=Arg.Buff; Arg.Buff=0;} - else Buff = Path = strdup(Arg.Path); - } - - XrdCmsBaseFR(XrdCmsRRData *aP, XrdCmsPInfo &Who, int Dln) - : Route(Who.rovec), RouteW(Who.rwvec), - Next(0), Buff(0), Path(aP->Path), - PathLen(aP->PathLen), PDirLen(Dln), - Sid(aP->Request.streamid), - Mod(aP->Request.modifier) - {} - - ~XrdCmsBaseFR() {if (Buff) free(Buff); Buff = 0;} -}; - -/******************************************************************************/ -/* C l a s s X r d C m s B a s e F S */ -/******************************************************************************/ - -class XrdCmsBaseFS -{ -public: - - int dfsTries() {return dfsMaxTries;} - -// Exists() returns a tri-logic state: -// CmsHaveRequest::Online -> File is known to exist and is available -// CmsHaveRequest::Pending -> File is known to exist but is not available -// 0 -> File state unknown, result will be provided later -// -1 -> File is known not to exist -// - int Exists(XrdCmsRRData &Arg,XrdCmsPInfo &Who,int noLim=0); - -// The following exists works as above but limits are never enforced and it -// never returns 0. Additionally, the fnpos parameter works as follows: -// -// > 0 -> Offset into path to the last slash before the filename. -// = 0 -> A valid offset has not been calculated nor should it be calculated. -// < 0 -> A valid offset has not been calculated, fnpos = -(length of Path). - - int Exists(char *Path, int fnPos, int UpAT=0); - -// Valid Opts for Init() -// -static const int Cntrl = 0x0001; // Centralize stat() o/w distribute it -static const int DFSys = 0x0002; // Distributed filesystem o/w jbods -static const int Immed = 0x0004; // Redirect immediately o/w preselect -static const int Servr = 0x0100; // This is a pure server node - - void Init(int Opts, int DMlife, int DPLife); - -inline int isDFS() {return dfsSys;} - -inline int Limit() {return theQ.rLimit;} - - void Limit(int rLim, int qMax); - -inline int Local() {return lclStat;} - - void Pacer(); - - void Runner(); - -static const int dfltDfsTries = 2; -static const int dfltStgTries = 3; - - void SetTries(bool xdfs, int tcnt) - {if (xdfs) dfsMaxTries = - (tcnt < 1 ? dfltDfsTries : tcnt); - else stgMaxTries = - (tcnt < 1 ? dfltStgTries : tcnt); - } - - void Start(); - - int stgTries() {return stgMaxTries;} - -inline int Trim() {return preSel;} - -inline int Traverse() {return Punt;} - - XrdCmsBaseFS(void (*theCB)(XrdCmsBaseFR *, int)) - : cBack(theCB), dfsMaxTries(dfltDfsTries), - stgMaxTries(dfltStgTries), - dmLife(0), dpLife(0), lclStat(0), preSel(1), - dfsSys(0), Server(0), Fixed(0), Punt(0) {} - ~XrdCmsBaseFS() {} - -private: - -struct dMoP {int Present;}; - - int Bypass(); - int FStat( char *Path, int fnPos, int upat=0); - int hasDir(char *Path, int fnPos); - void Queue(XrdCmsRRData &Arg, XrdCmsPInfo &Who, - int dln, int Frc=0); - void Xeq(XrdCmsBaseFR *rP); - - XrdSysMutex fsMutex; - XrdOucHash fsDirMP; - void (*cBack)(XrdCmsBaseFR *, int); - -struct RequestQ - {XrdSysMutex Mutex; - XrdSysSemaphore pqAvail; - XrdSysSemaphore rqAvail; - XrdCmsBaseFR *pqFirst; - XrdCmsBaseFR *pqLast; - XrdCmsBaseFR *rqFirst; - XrdCmsBaseFR *rqLast; - int rLimit; // Maximum number of requests per second - int qHWM; // Queue high watermark - int qMax; // Maximum elements to be queued - int qNum; // Total number of queued elements (pq + rq) - int rLeft; // Number of non-queue requests allowed - int rAgain; // Value to reinitialize rLeft - RequestQ() : pqAvail(0), rqAvail(0), - pqFirst(0), pqLast(0), rqFirst(0), rqLast(0), - rLimit(0), qHWM(0), qMax(1), qNum(0), - rLeft(0), rAgain(0) {} - ~RequestQ() {} - } theQ; - - int dfsMaxTries; - int stgMaxTries; - int dmLife; - int dpLife; - char lclStat; // 1-> Local stat() calls wanted - char preSel; // 1-> Preselect before redirect - char dfsSys; // 1-> Distributed Filesystem - char Server; // 1-> This is a data server - char Fixed; // 1-> Use fixed rate processing - char Punt; // 1-> Pass through any forwarding -}; -namespace XrdCms -{ -extern XrdCmsBaseFS baseFS; -} -#endif diff --git a/src/XrdCms/XrdCmsBlackList.cc b/src/XrdCms/XrdCmsBlackList.cc deleted file mode 100644 index 7248427a2df..00000000000 --- a/src/XrdCms/XrdCmsBlackList.cc +++ /dev/null @@ -1,611 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C m s B l a c k L i s t . c c */ -/* */ -/* (c) 2014 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include - -#include "Xrd/XrdScheduler.hh" - -#include "XrdCms/XrdCmsBlackList.hh" -#include "XrdCms/XrdCmsCluster.hh" -#include "XrdCms/XrdCmsTrace.hh" -#include "XrdCms/XrdCmsUtils.hh" - -#include "XrdNet/XrdNetAddr.hh" - -#include "XrdOuc/XrdOucEnv.hh" -#include "XrdOuc/XrdOucTList.hh" -#include "XrdOuc/XrdOucStream.hh" -#include "XrdOuc/XrdOucTokenizer.hh" - -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysLogger.hh" -#include "XrdSys/XrdSysPthread.hh" - -/******************************************************************************/ -/* L o c a l C l a s s e s */ -/******************************************************************************/ - -class BL_Grip - {public: - void Add(XrdOucTList *tP) - {if (last) last->next = tP; - else first = tP; - last = tP; - } - - XrdOucTList **Array(int &anum) - {XrdOucTList *tP = first; - anum = Count(); - if (!anum) return 0; - XrdOucTList **vec = new XrdOucTList *[anum]; - for (int i = 0; i < anum; i++) {vec[i] = tP; tP = tP->next;} - first = last = 0; - return vec; - } - - int Count() - {XrdOucTList *tP = first; - int n = 0; - while(tP) {tP=tP->next; n++;} - return n; - } - - XrdOucTList *Export() - {XrdOucTList *tP = first; - first = last = 0; - return tP; - } - - bool Include(const char *item, int &i) - {XrdOucTList *tP = first; - i = 0; - while(tP && strcmp(item,tP->text)) {tP=tP->next; i++;} - if (!tP) {Add(new XrdOucTList(item)); return false;} - return true; - } - - BL_Grip() : first(0), last(0) {} - - ~BL_Grip() {XrdOucTList *tP; - while((tP = first)) {first = tP->next; delete tP;} - last = 0; - } - - private: - XrdOucTList *first; - XrdOucTList *last; - }; - -union BL_Info - {long long info; - struct {short flags; - short pfxLen; - short sfxLen; - short totLen; - } v; - enum {exact = 0x8000, - redir = 0x4000, - rmask = 0x00ff - }; - }; - -/******************************************************************************/ -/* G l o b a l O b j e c t s */ -/******************************************************************************/ - -namespace XrdCms -{ - XrdScheduler *blSched = 0; - - XrdCmsCluster *blCluster; - - class MidNightTask : public XrdSysLogger::Task - {public: - void Ring(); - MidNightTask() {} - ~MidNightTask() {} - }; - MidNightTask blMN; - - XrdCmsBlackList BlackList; - - XrdSysMutex blMutex; - - XrdOucTList *blReal = 0; - XrdOucTList **blRedr = 0; - - char *blFN; - - time_t blTime = 0; - - int blChk; - int blRcnt = 0; - bool isWList=false; - - extern XrdSysError Say; -}; - -using namespace XrdCms; - -/******************************************************************************/ -/* Private: A d d B L */ -/******************************************************************************/ - -bool XrdCmsBlackList::AddBL(BL_Grip &bAnchor, char *hSpec, - BL_Grip *rAnchor, char *rSpec) -{ - const char *bwTag = (isWList ? "whitelist '" : "blacklist '"); - XrdNetAddr blAddr; - XrdOucTList *bP; - BL_Info Hdr; - const char *eText; - char *Ast, blBuff[512]; - -// Initialize the header -// - Hdr.info = 0; - -// Check if we are processing a redirect -// - if (rSpec) - {int i = AddRD(rAnchor, rSpec, hSpec); - if (i < 0) return false; - Hdr.v.flags |= BL_Info::redir | i; - } - -// Get the full name if this is not generic -// - if (!(Ast = index(hSpec, '*'))) - {if ((eText = blAddr.Set(hSpec,0))) - {snprintf(blBuff, sizeof(blBuff), "'; %s", eText); - Say.Say("Config ", "Unable to ", bwTag, hSpec, blBuff); - return false; - } - blAddr.Format(blBuff, sizeof(blBuff), XrdNetAddrInfo::fmtName, - XrdNetAddrInfo::noPort); - hSpec = blBuff; - Hdr.v.flags |= BL_Info::exact; - } else { - Hdr.v.pfxLen = Ast - hSpec; - Hdr.v.sfxLen = strlen(hSpec + Hdr.v.pfxLen + 1); - Hdr.v.totLen = Hdr.v.pfxLen + Hdr.v.sfxLen; - } - -// Add specification to the list -// - bP = new XrdOucTList(hSpec, &Hdr.info); - bAnchor.Add(bP); - return true; -} - -/******************************************************************************/ -/* A d d R D */ -/******************************************************************************/ - -int XrdCmsBlackList::AddRD(BL_Grip *rAnchor, char *rSpec, char *hSpec) -{ - XrdOucTList *rP, *rList = 0; - char *rTarg; - int ival; - bool aOK = true; - -// First see if we have this entry already -// - if (rAnchor[0].Include(rSpec, ival)) return ival; - -// Make sure we did not exceed the maximum number of redirect entries -// - if (ival > BL_Info::rmask) - {Say.Say("Config ", "Too many different redirects at ", hSpec, - "redirect", rSpec); - return -1; - } - -// We now ned to tokenize the specification -// - XrdOucTokenizer rToks(rSpec); - rToks.GetLine(); - -// Process each item -// - while((rTarg = rToks.GetToken()) && *rTarg) aOK &= AddRD(&rList,rTarg,hSpec); - if (!aOK) return -1; - -// Flatten the list and add it to out list of redirect targets -// - rP = Flatten(rList, rList->val); - rAnchor[1].Add(rP); - -// Delete the rlist -// - while((rP = rList)) {rList = rList->next; delete rP;} - -// All done -// - return ival; -} - -/******************************************************************************/ - -bool XrdCmsBlackList::AddRD(XrdOucTList **rList, char *rSpec, char *hSpec) -{ - char *rPort; - -// Screen out IPV6 specifications -// - if (*rSpec == '[') - {if (!(rPort = index(rSpec, ']'))) - {Say.Say("Config ","Invalid ",hSpec," redirect specification - ",rSpec); - return -1; - } - } else rPort = rSpec; - -// Grab the port number -// - if ((rPort = index(rPort, ':'))) - {if (!(*(rPort+1))) rPort = 0; - else *rPort++ = '\0'; - } - -// We should have a port specification now -// - if (!rPort) {Say.Say("Config ", "redirect port not specified for ", hSpec); - return -1; - } - -// Convert this to a list of redirect targets -// - return XrdCmsUtils::ParseMan(&Say, rList, rSpec, rPort, 0, true); -} - -/******************************************************************************/ -/* D o I t */ -/******************************************************************************/ - -void XrdCmsBlackList::DoIt() -{ - struct stat Stat; - XrdOucTList **blOldRedr = 0, **blNewRedr = 0, *blNewReal = 0, *tP = 0, *nP; - int rc, blOldRcnt = 0, blNewRcnt; - bool doUpdt = false, doPrt = false; - -// Check if the black list file was modified -// - rc = stat(blFN, &Stat); - if ((!rc && blTime != Stat.st_mtime) || (rc && blTime && errno == ENOENT)) - {blTime = (rc ? 0 : Stat.st_mtime); - if (GetBL(blNewReal, blNewRedr, blNewRcnt)) - {blMutex.Lock(); - tP = blReal; blReal = blNewReal; - blOldRedr = blRedr; blRedr = blNewRedr; - blOldRcnt = blRcnt; blRcnt = blNewRcnt; - blMutex.UnLock(); - if (!blReal && tP) doPrt = !isWList; - else blMN.Ring(); - doUpdt = true; - } - } - -// Delete any list we need to release -// - while(tP) - {if (doPrt) Say.Say("Config ", tP->text, " removed from blacklist."); - nP = tP->next; delete tP; tP = nP; - } - -// Delete the old redirect array -// - if (blOldRedr) - {for (int i = 0; i < blOldRcnt; i++) delete blOldRedr[i]; - delete [] blOldRedr; - } - -// Do real-time update if need be -// - if (doUpdt) blCluster->BlackList(blReal); - -// Reschedule this to check any modifications -// - blSched->Schedule((XrdJob *)&BlackList, time(0) + blChk); -} - -/******************************************************************************/ -/* Private: F l a t t e n */ -/******************************************************************************/ - -XrdOucTList *XrdCmsBlackList::Flatten(XrdOucTList *tList, int tPort) -{ - XrdOucTList *tP = tList; - char buff[4096], bPort[8], *bP = buff; - int n, pLen, bleft = sizeof(buff); - short xdata[4] = {0}; - -// Convert port to a suffix -// - pLen = sprintf(bPort, ":%d", tPort); - *buff = 0; - -// Fill the buffer and truncate as necessary -// - while(tP) - {n = strlen(tP->text)+pLen+2; - if (n >= bleft) break; - n = sprintf(bP, " %s%s", tP->text, bPort); - bP += n; bleft -= n; - tP = tP->next; - } - -// Get actual length including null byte -// - xdata[0] = strlen(buff+1) + 1; - xdata[1] = xdata[0] + sizeof(short); - xdata[2] = htons(xdata[0]); - -// Create a new tlist item -// - tP = new XrdOucTList(buff+1, xdata); - return tP; -} - -/******************************************************************************/ -/* Private: G e t B L */ -/******************************************************************************/ - -bool XrdCmsBlackList::GetBL(XrdOucTList *&bList, - XrdOucTList **&rList, int &rcnt) -{ - static int msgCnt = 0; - XrdOucEnv myEnv; - XrdOucStream blFile(&Say, getenv("XRDINSTANCE"), &myEnv, "=====> "); - BL_Grip bAnchor, rAnchor[2]; - const char *fType, *oEmsg, *rEmsg; - char *hsp, *rsp, hspBuff[512], rSpec[4096]; - int blFD, retc; - bool aOK = true; - -// Setup message plugins -// - if (isWList) - {oEmsg = "open whitelist file"; - rEmsg = "read whitelist file"; - fType = "whitelist"; - } else { - oEmsg = "open blacklist file"; - rEmsg = "read blacklist file"; - fType = "blacklist"; - } - -// Open the config file -// - if ( (blFD = open(blFN, O_RDONLY, 0)) < 0) - {if (errno == ENOENT) return true; - if (!(msgCnt & 0x03)) Say.Emsg("GetBL", errno, oEmsg, blFN); - return false; - } - blFile.Attach(blFD, 4096); - -// Trace this now -// - Say.Say("Config processing ", fType, " file ", blFN); - -// Start reading the black list -// - while((hsp = blFile.GetMyFirstWord())) - {if (strlen(hsp) >= sizeof(hspBuff)) - {Say.Say("Config ", hsp, " is too long."); aOK = false; continue;} - strcpy(hspBuff, hsp); hsp = hspBuff; - if ( (rsp = blFile.GetWord()) && *rsp) - {if (strcmp("redirect", rsp)) - {Say.Say("Config ", rsp, " is an invalid modifier for ", hsp); - aOK = false; - continue; - } - *rSpec = 0; rsp = rSpec; - if (!blFile.GetRest(rSpec, sizeof(rSpec))) - {Say.Say("Config ", "redirect target too long ", hsp); - aOK = false; - continue; - } - if (!(*rSpec)) - {Say.Say("Config ", "redirect target missing for ", hsp); - aOK = false; - continue; - } - } else rsp = 0; - blFile.noEcho(); - if (!AddBL(bAnchor, hsp, rAnchor, rsp)) aOK = false; - } - -// Now check if any errors occured during file i/o -// - if ((retc = blFile.LastError())) - {Say.Emsg("GetBL", retc, rEmsg, blFN); aOK = false;} - else if (!aOK) Say.Emsg("GetBL", "Error(s) encountered in",fType,"file!"); - -// Return ending status -// - blFile.Close(); - bList = (aOK ? bAnchor.Export() : 0); - rList = rAnchor[1].Array(rcnt); - return aOK; -} - -/******************************************************************************/ -/* I n i t */ -/******************************************************************************/ - -void XrdCmsBlackList::Init(XrdScheduler *sP, XrdCmsCluster *cP, - const char *blfn, int chkt) -{ - struct stat Stat; - const char *cfn; - -// Copy out the scheduler and cluster pointers -// - blSched = sP; - blCluster = cP; - -// Determine if this is a black or white list -// - if (chkt < 0) {isWList = true; chkt = -chkt;} - -// Copy the file path (this is a one time call during config) -// - if (blfn) blFN = strdup(blfn); - else if (!(cfn = getenv("XRDCONFIGFN"))) return; - else {char pBuff[2048], *Slash; - strcpy(pBuff, cfn); - if (!(Slash = rindex(pBuff, '/'))) return; - strcpy(Slash+1, (isWList ? "cms.whitelist" : "cms.blacklist")); - blFN = strdup(pBuff); - } - -// Check if the black list file exists, it might not. If it does, process it -// - if (!stat(blFN, &Stat)) - {blTime = Stat.st_mtime; - GetBL(blReal, blRedr, blRcnt); - if (blReal) blMN.Ring(); - } - -// Schedule this to recheck any modifications -// - blChk = chkt; - blSched->Schedule((XrdJob *)&BlackList, time(0) + chkt); - -// Add ourselves to the midnight run list -// - Say.logger()->AtMidnight(&blMN); -} - -/******************************************************************************/ -/* P r e s e n t */ -/******************************************************************************/ - -int XrdCmsBlackList::Present(const char *hName, XrdOucTList *bList, - char *rBuff, int rBLen) -{ - BL_Info Hdr; - int hLen, retval; - bool doUnLk; - -// Check if we really have a name here -// - if (!hName || !blSched) return 0; - -// Check if we need to supply our list -// - if (bList) doUnLk = false; - else {doUnLk = true; - blMutex.Lock(); - bList = blReal; - } - -// By definition, if there is no list at all then everybody is allowed -// - if (!bList) - {if (doUnLk) blMutex.UnLock(); - return 0; - } - -// Run through the list and try to compare -// - hLen = strlen(hName); - while(bList) - {Hdr.info = bList->dval; - if (Hdr.v.flags & BL_Info::exact) - {if (!strcmp(hName, bList->text)) break;} - else if (hLen >= Hdr.v.totLen) - {if (!Hdr.v.pfxLen - || !strncmp(bList->text, hName, Hdr.v.pfxLen)) - {if (!Hdr.v.sfxLen - || !strncmp(bList->text+Hdr.v.pfxLen+1, - hName + (hLen - Hdr.v.sfxLen), - Hdr.v.sfxLen)) break; - } - } - bList = bList->next; - } - -// If we have a black list check if we should redirect -// - if (bList) - {if (!(Hdr.v.flags & BL_Info::redir)) retval = (isWList ? 0 : -1); - else {XrdOucTList *rP = blRedr[Hdr.v.flags & BL_Info::rmask]; - if (rP) - {retval = rP->sval[1]; - if (!rBuff || retval > rBLen) retval = -retval; - else {memcpy(rBuff, &(rP->sval[2]), sizeof(short)); - memcpy(rBuff+sizeof(short), rP->text, rP->sval[0]); - } - } else retval = -1; - } - } else retval = (isWList ? -1 : 0); - -// Unlock ourselves if need be and return result -// - if (doUnLk) blMutex.UnLock(); - return retval; -} - -/******************************************************************************/ -/* R i n g */ -/******************************************************************************/ - -void MidNightTask::Ring() -{ - BL_Info Hdr; - XrdOucTList *tP; - const char *bwTag = (isWList ? "Whitelisting " : "Blacklisting "); - -// Get the list lock -// - blMutex.Lock(); - tP = blReal; - -// Print the list -// - while(tP) - {Hdr.info = tP->dval; - if (!(Hdr.v.flags & BL_Info::redir)) - Say.Say("Config ", bwTag, tP->text); - else {XrdOucTList *rP = blRedr[Hdr.v.flags & BL_Info::rmask]; - Say.Say("Config Blacklisting ",tP->text," redirect ",rP->text); - } - tP = tP->next; - } - -// All done -// - blMutex.UnLock(); -} diff --git a/src/XrdCms/XrdCmsBlackList.hh b/src/XrdCms/XrdCmsBlackList.hh deleted file mode 100644 index 036e533019e..00000000000 --- a/src/XrdCms/XrdCmsBlackList.hh +++ /dev/null @@ -1,102 +0,0 @@ -#ifndef __XRDCMSBLACKLIST_HH__ -#define __XRDCMSBLACKLIST_HH__ -/******************************************************************************/ -/* */ -/* X r d C m s B l a c k L i s t . h h */ -/* */ -/* (c) 2014 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "Xrd/XrdJob.hh" - -class XrdCmsCluster; -class XrdOucTList; -class XrdScheduler; - -class BL_Grip; - -class XrdCmsBlackList : public XrdJob -{ -public: - -//------------------------------------------------------------------------------ -//! Time driven method for checking black list file. -//------------------------------------------------------------------------------ - - void DoIt(); - -//------------------------------------------------------------------------------ -//! Initialize the black list -//! -//! @param sP Pointer to the scheduler object. -//! @param cP Pointer to the cluster object. -//! @param blfn The path to the black list file or null. -//! @param chkt Seconds between checks for blacklist changes. If the value -//! is negative, the blacklist is treated as a whitelist. -//------------------------------------------------------------------------------ - -static void Init(XrdScheduler *sP, XrdCmsCluster *cP, - const char *blfn, int chkt=600); - -//------------------------------------------------------------------------------ -//! Check if host is in the black list and how it should be managed. -//! -//! @param hName Pointer to the host name or address. -//! @param bList Optional pointer to a private black list. -//! @param rbuff Pointer to the buffer to contain the redirect response. If -//! nil, the host is not redirected. -//! @param rblen The size of rbuff. If zero or insufficiently large the host -//! is not redirected. -//! -//! @return < -1 Host is in the black list and would be redirected; -//! but either rbuff was nil or the buffer was too small. The -//! abs(returned value) is the size the buffer should have been. -//! @return = -1 Host is in the black list and should not be redirected. -//! @return = 0 Host not in the black list. -//! @return > 0 Host is in the black list and should be redirected. -//! The return value is the size of the redirect response placed -//! in the supplied buffer. -//------------------------------------------------------------------------------ - -static int Present(const char *hName, XrdOucTList *bList=0, - char *rbuff=0, int rblen=0); - -//------------------------------------------------------------------------------ -//! Constructor and Destructor -//------------------------------------------------------------------------------ - - XrdCmsBlackList() : XrdJob("Black List Check") {} - ~XrdCmsBlackList() {} -private: -static bool AddBL(BL_Grip &bAnchor, char *hSpec, - BL_Grip *rAnchor, char *rSpec); -static int AddRD(BL_Grip *rAnchor, char *rSpec, char *hSpec); -static bool AddRD(XrdOucTList **rList, char *rSpec, char *hSpec); -static -XrdOucTList *Flatten(XrdOucTList *tList, int tPort); -static bool GetBL(XrdOucTList *&bList, XrdOucTList **&rList, int &rcnt); -}; -#endif diff --git a/src/XrdCms/XrdCmsCache.cc b/src/XrdCms/XrdCmsCache.cc deleted file mode 100644 index db1f186dc4a..00000000000 --- a/src/XrdCms/XrdCmsCache.cc +++ /dev/null @@ -1,570 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C m s C a c h e . c c */ -/* */ -/* (c) 2007 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -#include "XrdCms/XrdCmsCache.hh" -#include "XrdCms/XrdCmsRRQ.hh" -#include "XrdCms/XrdCmsTrace.hh" - -#include "XrdSys/XrdSysTimer.hh" - -#include "Xrd/XrdJob.hh" -#include "Xrd/XrdScheduler.hh" - -namespace XrdCms -{ -extern XrdScheduler *Sched; -} - -using namespace XrdCms; - -/******************************************************************************/ -/* G l o b a l s */ -/******************************************************************************/ - -XrdCmsCache XrdCms::Cache; - -/******************************************************************************/ -/* L o c a l C l a s s e s */ -/******************************************************************************/ - -class XrdCmsCacheJob : XrdJob -{ -public: - -void DoIt() {Cache.Recycle(myList); delete this;} - - XrdCmsCacheJob(XrdCmsKeyItem *List) - : XrdJob("cache scrubber"), myList(List) {} - ~XrdCmsCacheJob() {} - -private: - -XrdCmsKeyItem *myList; -}; - -/******************************************************************************/ -/* E x t e r n a l T h r e a d I n t e r f a c e s */ -/******************************************************************************/ - -void *XrdCmsStartTickTock(void *carg) - {XrdCmsCache *myCache = (XrdCmsCache *)carg; - return myCache->TickTock(); - } - -/******************************************************************************/ -/* P u b l i c C a c h e M a n i p u l a t i o n M e t h o d s */ -/******************************************************************************/ -/******************************************************************************/ -/* Public A d d F i l e */ -/******************************************************************************/ - -// This method insert or updates information about a path in the cache. - -// Key was found: Location information is updated depending on mask -// mask == 0 Indicates that the information is being refreshed. -// Location information is nullified. The update deadline is set -// QDelay seconds in the future. The entry window is set to the -// current window to be held for a full fxhold period. -// mask != 0 Indicates that some location information is now known. -// Location information is updated according to the mask. -// For a r/w location, the deadline is satisfied and all -// callbacks are dispatched. For an r/o location the deadline -// is satisfied if no r/w callback is pending. Any r/o -// callback is dispatched. The Info object is ignored. - -// Key not found: A selective addition occurs, depending on Sel.Opts -// Opts !Advisory: The entry is added to the cache with location information -// set as passed (usually 0). The update deadline is us set to -// DLTtime seconds in the future. The entry window is set -// to the current window. -// Opts Advisory: The call is ignored since we do not keep information about -// paths that were never asked for. - -// Returns True If this is the first time location information was added -// to the entry. -// Returns False Otherwise. - -int XrdCmsCache::AddFile(XrdCmsSelect &Sel, SMask_t mask) -{ - XrdCmsKeyItem *iP; - SMask_t xmask; - int isrw = (Sel.Opts & XrdCmsSelect::Write), isnew = 0; - -// Serialize processing -// - myMutex.Lock(); - -// Check for fast path processing -// - if ( !(iP = Sel.Path.TODRef) || !(iP->Key.Equiv(Sel.Path))) - if ((iP = Sel.Path.TODRef = CTable.Find(Sel.Path))) - Sel.Path.Ref = iP->Key.Ref; - -// Add/Modify the entry -// - if (iP) - {if (!mask) - {iP->Loc.deadline = QDelay + time(0); - iP->Loc.lifeline = nilTMO + iP->Loc.deadline; - iP->Loc.hfvec = 0; iP->Loc.pfvec = 0; iP->Loc.qfvec = 0; - iP->Loc.TOD_B = BClock; - iP->Key.TOD = Tock; - } else { - xmask = iP->Loc.pfvec; - if (Sel.Opts & XrdCmsSelect::Pending) iP->Loc.pfvec |= mask; - else iP->Loc.pfvec &= ~mask; - isnew = (iP->Loc.hfvec == 0) || (iP->Loc.pfvec != xmask); - iP->Loc.hfvec |= mask; - iP->Loc.qfvec &= ~mask; - if (isrw) {iP->Loc.deadline = 0; - if (iP->Loc.roPend || iP->Loc.rwPend) - Dispatch(Sel, iP, iP->Loc.roPend, iP->Loc.rwPend); - } - else {if (!iP->Loc.rwPend) iP->Loc.deadline = 0; - if (iP->Loc.roPend) Dispatch(Sel, iP, iP->Loc.roPend, 0); - } - } - } else if (!(Sel.Opts & XrdCmsSelect::Advisory)) - {Sel.Path.TOD = Tock; - if ((iP = CTable.Add(Sel.Path))) - {iP->Loc.pfvec = (Sel.Opts&XrdCmsSelect::Pending?mask:0); - iP->Loc.hfvec = mask; - iP->Loc.TOD_B = BClock; - iP->Loc.qfvec = 0; - iP->Loc.deadline = QDelay + time(0); - iP->Loc.lifeline = nilTMO + iP->Loc.deadline; - Sel.Path.Ref = iP->Key.Ref; - Sel.Path.TODRef = iP; isnew = 1; - } - } - -// All done -// - myMutex.UnLock(); - return isnew; -} - -/******************************************************************************/ -/* Public D e l F i l e */ -/******************************************************************************/ - -// This method removes location information from existing valid entries. If an -// existing valid entry is found, based on Sel.Opts the following occurs: - -// Opts Advisory only locate information is removed. -// Opts !Advisory if the entry has no location information it is removed from -// the cache, if possible. - -// TRUE is returned if the entry was valid but location information was cleared. -// Otherwise, FALSE is returned. - -int XrdCmsCache::DelFile(XrdCmsSelect &Sel, SMask_t mask) -{ - XrdCmsKeyItem *iP; - int gone4good; - -// Lock the hash table -// - myMutex.Lock(); - -// Look up the entry and remove server -// - if ((iP = CTable.Find(Sel.Path))) - {iP->Loc.hfvec &= ~mask; - iP->Loc.pfvec &= ~mask; - if ((gone4good = (iP->Loc.hfvec == 0))) - {if (nilTMO) iP->Loc.lifeline = nilTMO + time(0); - if (!(Sel.Opts & XrdCmsSelect::Advisory) - && XrdCmsKeyItem::Unload(iP) && !CTable.Recycle(iP)) - Say.Emsg("DelFile", "Delete failed for", iP->Key.Val); - } - } else gone4good = 0; - -// All done -// - myMutex.UnLock(); - return gone4good; -} - -/******************************************************************************/ -/* Public G e t F i l e */ -/******************************************************************************/ - -// This method looks up entries in the cache. An "entry not found" condition -// holds is the entry was found but is marked as deleted. - -// Entry was found: Location information is passed bask. If the update deadline -// has passed, it is nullified and 1 is returned. Otherwise, -// -1 is returned indicating a query is in progress. - -// Entry not found: FALSE is returned. - -int XrdCmsCache::GetFile(XrdCmsSelect &Sel, SMask_t mask) -{ - XrdCmsKeyItem *iP; - SMask_t bVec; - int retc; - -// Lock the hash table -// - myMutex.Lock(); - -// Look up the entry and return location information -// - if ((iP = CTable.Find(Sel.Path))) - {if ((bVec = (iP->Loc.TOD_B < BClock - ? getBVec(iP->Key.TOD, iP->Loc.TOD_B) & mask : 0))) - {iP->Loc.hfvec &= ~bVec; - iP->Loc.pfvec &= ~bVec; - iP->Loc.qfvec &= ~mask; - iP->Loc.deadline = QDelay + time(0); - iP->Loc.lifeline = nilTMO + iP->Loc.deadline; - retc = -1; - } else if (iP->Loc.deadline) - if (iP->Loc.deadline > time(0)) retc = -1; - else {iP->Loc.deadline = 0; retc = 1;} - else retc = 1; - - if (nilTMO && retc == 1 && iP->Loc.hfvec == 0 - && iP->Loc.lifeline <= time(0)) retc = 0; - - Sel.Vec.hf = okVec & iP->Loc.hfvec; - Sel.Vec.pf = okVec & iP->Loc.pfvec; - Sel.Vec.bf = okVec & (bVec | iP->Loc.qfvec); iP->Loc.qfvec = 0; - Sel.Path.Ref = iP->Key.Ref; - } else retc = 0; - -// All done -// - myMutex.UnLock(); - Sel.Path.TODRef = iP; - return retc; -} - -/******************************************************************************/ -/* Public U n k F i l e */ -/******************************************************************************/ - -int XrdCmsCache::UnkFile(XrdCmsSelect &Sel, SMask_t mask) -{ - EPNAME("UnkFile"); - XrdCmsKeyItem *iP; - -// Make sure we have the proper information. If so, lock the hash table -// - myMutex.Lock(); - -// Look up the entry and if valid update the unqueried vector. Note that -// this method may only be called after GetFile() or AddFile() for a new entry -// - if ((iP = Sel.Path.TODRef)) - {if (iP->Key.Equiv(Sel.Path)) iP->Loc.qfvec = mask; - else iP = 0; - } - -// Return result -// - myMutex.UnLock(); - DEBUG("rc=" <<(iP ? 1 : 0) <<" path=" <Key.Equiv(Sel.Path))) retc = DLTime; - else if (iP->Loc.hfvec != mask) retc = 1; - else {Now = time(0); retc = 0; - if (iP->Loc.deadline && iP->Loc.deadline <= Now) - iP->Loc.deadline = DLTime + Now; - Add2Q(Sel.InfoP, iP, Sel.Opts); - } - -// Return result -// - myMutex.UnLock(); - DEBUG("rc=" <Recycle(); - -// All done -// - return 1; -} - -/******************************************************************************/ -/* public T i c k T o c k */ -/******************************************************************************/ - -void *XrdCmsCache::TickTock() -{ - XrdCmsKeyItem *iP; - -// Simply adjust the clock and trim old entries -// - do {XrdSysTimer::Snooze(Tick); - myMutex.Lock(); - Tock = (Tock+1) & XrdCmsKeyItem::TickMask; - Bhistory[Tock].Start = Bhistory[Tock].End = 0; - iP = XrdCmsKeyItem::Unload(Tock); - myMutex.UnLock(); - if (iP) Sched->Schedule((XrdJob *)new XrdCmsCacheJob(iP)); - } while(1); - -// Keep compiler happy -// - return (void *)0; -} - -/******************************************************************************/ -/* P r i v a t e M e t h o d s */ -/******************************************************************************/ -/******************************************************************************/ -/* A d d 2 Q */ -/******************************************************************************/ - -void XrdCmsCache::Add2Q(XrdCmsRRQInfo *Info, XrdCmsKeyItem *iP, int selOpts) -{ - bool isrw = (selOpts & XrdCmsSelect::Write) != 0; - short Slot = (isrw ? iP->Loc.rwPend : iP->Loc.roPend); - -// Add the request to the appropriate pending queue -// - Info->Key = iP; - Info->isRW= isrw; - Info->ifOP= (selOpts & XrdCmsSelect::ifWant); - if (!(Slot = RRQ.Add(Slot, Info))) Info->Key = 0; - else if (isrw) iP->Loc.rwPend = Slot; - else iP->Loc.roPend = Slot; -} - -/******************************************************************************/ -/* D i s p a t c h */ -/******************************************************************************/ - -void XrdCmsCache::Dispatch(XrdCmsSelect &Sel, XrdCmsKeyItem *iP, - short roQ, short rwQ) -{ - -// Dispatching shared-everything nodes is very different from shared-nothing -// since one ready node means all are ready and we can use any one of them. -// The current list of nodes must is provided by the caller adding the entry. -// Note that the minimum number of nodes will always be set to 0 via config. -// - if (isDFS) - {if (roQ && RRQ.Ready(roQ, iP, Sel.Vec.hf, Sel.Vec.pf & Sel.Vec.hf)) - iP->Loc.roPend = 0; - if((rwQ && Sel.Vec.wf) - && RRQ.Ready(rwQ, iP, Sel.Vec.wf, Sel.Vec.pf & Sel.Vec.wf)) - iP->Loc.rwPend = 0; - return; - } - -// Disptaching shared-nothing nodes is a one-shot affair. Only one node becomes -// ready at a time and we can immediately disptach that node unless we need to -// wait for more nodes to respond. -// - if (roQ && RRQ.Ready(roQ, iP, iP->Loc.hfvec, iP->Loc.pfvec)) - iP->Loc.roPend = 0; - if (rwQ && RRQ.Ready(rwQ, iP, iP->Loc.hfvec, iP->Loc.pfvec)) - iP->Loc.rwPend = 0; -} - -/******************************************************************************/ -/* g e t B V e c */ -/******************************************************************************/ - -SMask_t XrdCmsCache::getBVec(unsigned int TODa, unsigned int &TODb) -{ - EPNAME("getBVec"); - SMask_t BVec(0); - long long i; - -// See if we can use a previously calculated bVec -// - if (Bhistory[TODa].End == BClock && Bhistory[TODa].Start <= TODb) - {Bhits++; TODb = BClock; return Bhistory[TODa].Vec;} - -// Calculate the new vector -// - for (i = 0; i <= vecHi; i++) - if (TODb < Bounced[i]) BVec |= 1ULL << i; - - Bhistory[TODa].Vec = BVec; - Bhistory[TODa].Start = TODb; - Bhistory[TODa].End = BClock; - TODb = BClock; - Bmiss++; - if (!(Bmiss & 0xff)) DEBUG("hits=" <. */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -#include "Xrd/XrdJob.hh" -#include "Xrd/XrdScheduler.hh" -#include "XrdCms/XrdCmsKey.hh" -#include "XrdCms/XrdCmsNash.hh" -#include "XrdCms/XrdCmsPList.hh" -#include "XrdSys/XrdSysPthread.hh" -#include "XrdCms/XrdCmsSelect.hh" -#include "XrdCms/XrdCmsTypes.hh" - -class XrdCmsCache -{ -public: -friend class XrdCmsCacheJob; - -XrdCmsPList_Anchor Paths; - -// AddFile() returns true if this is the first addition, false otherwise. See -// method for detailed information on processing. -// -int AddFile(XrdCmsSelect &Sel, SMask_t mask); - -// DelFile() returns true if this is the last deletion, false otherwise -// -int DelFile(XrdCmsSelect &Sel, SMask_t mask); - -// GetFile() returns true if we actually found the file -// -int GetFile(XrdCmsSelect &Sel, SMask_t mask); - -// UnkFile() updates the unqueried vector and returns 1 upon success, 0 o/w. -// -int UnkFile(XrdCmsSelect &Sel, SMask_t mask); - -// WT4File() adds a request to the callback queue and returns a 0 if added -// of a wait time to be returned to the client. -// -int WT4File(XrdCmsSelect &Sel, SMask_t mask); - -void Bounce(SMask_t smask, int SNum); - -void Drop(SMask_t mask, int SNum, int xHi); - -int Init(int fxHold, int fxDelay, int fxQuery, int seFS, int nxHold); - -void *TickTock(); - -static const int min_nxTime = 60; - - XrdCmsCache() : okVec(0), Tick(8*60*60), Tock(0), BClock(0), - nilTMO(0), - DLTime(5), QDelay(5), Bhits(0), Bmiss(0), vecHi(-1), - isDFS(0) - {memset(Bounced, 0, sizeof(Bounced)); - memset(Bhistory, 0, sizeof(Bhistory)); - } - ~XrdCmsCache() {} // Never gets deleted - -private: - -void Add2Q(XrdCmsRRQInfo *Info, XrdCmsKeyItem *cp, int selOpts); -void Dispatch(XrdCmsSelect &Sel, XrdCmsKeyItem *cinfo, - short roQ, short rwQ); -SMask_t getBVec(unsigned int todA, unsigned int &todB); -void Recycle(XrdCmsKeyItem *theList); - -struct {SMask_t Vec; - unsigned int Start; - unsigned int End; - } Bhistory[XrdCmsKeyItem::TickRate]; - -XrdSysMutex myMutex; -XrdCmsNash CTable; -unsigned int Bounced[STMax]; -SMask_t okVec; -unsigned int Tick; -unsigned int Tock; -unsigned int BClock; - int nilTMO; - int DLTime; - int QDelay; - int Bhits; - int Bmiss; - int vecHi; - int isDFS; -}; - -namespace XrdCms -{ -extern XrdCmsCache Cache; -} -#endif diff --git a/src/XrdCms/XrdCmsClient.cc b/src/XrdCms/XrdCmsClient.cc deleted file mode 100644 index a09f0a61de6..00000000000 --- a/src/XrdCms/XrdCmsClient.cc +++ /dev/null @@ -1,54 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C m s C l i e n t . c c */ -/* */ -/* (c) 2012 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdCms/XrdCmsClient.hh" -#include "XrdCms/XrdCmsFinder.hh" - -/******************************************************************************/ -/* G e t D e f a u l t C l i e n t */ -/******************************************************************************/ - -namespace XrdCms -{ -XrdCmsClient *GetDefaultClient(XrdSysLogger *Logger, - int opMode, - int myPort - ) -{ -// Determine which client to generate for the caller. This function is -// provided as an ABI-compatible interface to obtaining a default client. -// - if (opMode & IsRedir) - return (XrdCmsClient *)new XrdCmsFinderRMT(Logger, opMode, myPort); - if (opMode & IsTarget) - return (XrdCmsClient *)new XrdCmsFinderTRG(Logger, opMode, myPort); - return 0; -} -}; diff --git a/src/XrdCms/XrdCmsClient.hh b/src/XrdCms/XrdCmsClient.hh deleted file mode 100644 index 2c7c2090086..00000000000 --- a/src/XrdCms/XrdCmsClient.hh +++ /dev/null @@ -1,455 +0,0 @@ -#ifndef __CMS_CLIENT__ -#define __CMS_CLIENT__ -/******************************************************************************/ -/* */ -/* X r d C m s C l i e n t . h h */ -/* */ -/* (c) 2007 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -class XrdOucEnv; -class XrdOucErrInfo; -class XrdOucLogger; -class XrdOucTList; -struct XrdSfsPrep; -class XrdSysLogger; - -/******************************************************************************/ -/* R e t u r n C o n v e n t i o n s */ -/******************************************************************************/ - -/* The following return conventions are use by Forward(), Locate(), & Prepare() - Return Val Resp.errcode Resp.errtext - --------- ------------------- -------- - SFS_DATA Length of data. Data to be returned to caller. - Action: Caller is provided data as successful response. - - SFS_ERROR errno Error message text. - Action: Caller given error response. - - SFS_REDIRECT port (0 for default) Host name - Action: Caller is redirected to : - - SFS_STARTED Expected seconds n/a - Action: Caller is told to wait for the "expected seconds" for a - callback with the result. A callback must follow. - See how to do callbacks below. - - > 0 Wait time (= retval) Reason for wait - Action: Caller told to wait retval seconds and retry request. - - < 0 Error number Error message - Action: Same as SFS_ERROR. You should *always* use SFS_ERROR. - - = 0 Not applicable Not applicable (see below) - Action: Forward() -> Return success; request forwarded. - Locate() -> Redirection does not apply, operation - should be done against local file system. - Prepare() -> Return success, request submitted. -*/ - -/******************************************************************************/ -/* C a l l b a c k C o n v e n t i o n s */ -/******************************************************************************/ - -/* Most operations allow you to return SFS_STARTED to setup a callback. - Callback information is contained in the XrdOucErrInfo object passed to - Forward(), Locate() and Prepare(); the only methods that can apply callbacks. - Use a callback when the operation will take at least several seconds so as - to not occupy the calling thread for an excessive amount of time. - - The actual mechanics of a callback are rather complicated because callbacks - are subject to non-causaility if not correctly handled. In order to avoid - such issues, you should use the XrdOucCallBack object (see XrdOucCallBack.hh) - to test for applicability, setup, and effect a callback. - - When calling back, you return the same information you would have returned - had the execution path been synchronous. From that standpoint callbacks are - relatively easy to understand. All you are doing is defering the return of - information without occupying a thread while waiting to do so. - - A typical scenario, using Resp and the original ErrInfo object, would be.... - - XrdOucCallBack cbObject; // Must be persistent for the callback duration - - if (XrdOucCallBack::Allowed(Resp)) - {cbObject.Init(Resp); - - Resp.setErrCode(); - return SFS_STARTED; // Effect callback response! - } - - Once the thread doing the work has a result, send it via a callback as if - the work was done in a synchronous fashion. - - cbObject->Reply(retValue, ErrCodeValue, ErrTextValue); -*/ - -/******************************************************************************/ -/* C l a s s X r d C m s C l i e n t */ -/******************************************************************************/ - -class XrdCmsClient -{ -public: - -//------------------------------------------------------------------------------ -//! Notify the cms of a newly added file or a file whose state has changed on -//! a data server node. -//! -//! @param path The logical file name. -//! @param Pend When true, the file is scheduled to be present in the future -//! (e.g. copied in). -//------------------------------------------------------------------------------ - -virtual void Added(const char *path, int Pend=0) { (void)path; (void)Pend; } - -//------------------------------------------------------------------------------ -//! Configure the client object. -//! -//! @param cfn The configuration file name. -//! @param Parms Any parameters specified in the cmslib directive. If none, -//! the pointer may be null. -//! @param EnvInfo Environmental information of the caller. -//! -//! @return Success !0 -//! Failure =0 -//------------------------------------------------------------------------------ - -virtual int Configure(const char *cfn, char *Parms, XrdOucEnv *EnvInfo) = 0; - -//------------------------------------------------------------------------------ -//! Relay a meta-operation to all nodes in the cluster. -//! -//! This method is only used on manager nodes and is enabled by the ofs.forward -//! directive. -//! -//! @param Resp Object where messages are to be returned. -//! @param cmd The operation being performed (see table below). -//! If it starts with a '+' then a response (2way) is needed. -//! Otherwise, a best-effort is all that is all that is required -//! and success can always be returned. -//! @param arg1 1st argument to cmd. -//! @param arg2 2nd argument to cmd, which may be null if none exists. -//! @param Env1 Associated environmental information for arg1 (e.g., cgi info -//! which can be retrieved by Env1->Env()). -//! @param Env2 Associated environmental information for arg2 (e.g., cgi info -//! which can be retrieved by Env1->Env()). -//! -//! -//! cmd arg1 arg2 cmd arg1 arg2 -//! -------- ------ ------ -------- ------ ------ -//! [+]chmod [+]rmdir 0 -//! [+]mkdir [+]mv -//! [+]mkpath [+]trunc -//! [+]rm 0 -//! -//! @return As explained under "return conventions". -//------------------------------------------------------------------------------ - -virtual int Forward(XrdOucErrInfo &Resp, const char *cmd, - const char *arg1=0, const char *arg2=0, - XrdOucEnv *Env1=0, XrdOucEnv *Env2=0) -{ - (void)Resp; (void)cmd; (void)arg1; (void)arg2; (void)Env1; (void)Env2; - return 0; -} - -//------------------------------------------------------------------------------ -//! Check if this client is configured for a manager node. -//! -//! @return !0 Yes, configured as a manager. -//! =0 No. -//------------------------------------------------------------------------------ - -virtual int isRemote() {return myPersona == XrdCmsClient::amRemote;} - -//------------------------------------------------------------------------------ -//! Retrieve file location information. -//! -//! @param Resp Object where message or response is to be returned. -//! @param path The logical path whise location is wanted. -//! @param flags One or more of the following: -//! -//! SFS_O_LOCATE - return the list of servers that have the file. -//! Otherwise, redirect to the best server for the file. -//! SFS_O_NOWAIT - w/ SFS_O_LOCATE return readily available info. -//! Otherwise, select online files only. -//! SFS_O_CREAT - file will be created. -//! SFS_O_NOWAIT - select server if file is online. -//! SFS_O_REPLICA - a replica of the file will be made. -//! SFS_O_STAT - only stat() information wanted. -//! SFS_O_TRUNC - file will be truncated. -//! -//! For any the the above, additional flags are passed: -//! SFS_O_META - data will not change (inode operation only) -//! SFS_O_RESET - reset cached info and recaculate the location(s). -//! SFS_O_WRONLY - file will be only written (o/w RDWR or RDONLY). -//! SFS_O_RDWR - file may be read and written (o/w WRONLY or RDONLY). -//! -//! @param Info Associated environmental information for arg2 (e.g., cgi info -//! which can be retrieved by Env1->Env()). -//! -//! @return As explained under "return conventions". -//------------------------------------------------------------------------------ - -virtual int Locate(XrdOucErrInfo &Resp, const char *path, int flags, - XrdOucEnv *Info=0) = 0; - -//------------------------------------------------------------------------------ -//! Obtain the list of cmsd's being used by a manager node along with their -//! associated index numbers, origin 1. -//! -//! @return The list of cmsd's being used. The list is considered permanent -//! and is not deleted. -// Return: A list of managers or null if none exist. -//------------------------------------------------------------------------------ - -virtual -XrdOucTList *Managers() {return 0;} - -//------------------------------------------------------------------------------ -//! Start the preparation of a file for future processing. -//! -//! @param Resp Object where message or response is to be returned. -//! @param pargs Information on which and how to prepare the file. -//! @param Info Associated environmental information. -//! -//! @return As explained under "return conventions". -//------------------------------------------------------------------------------ - -virtual int Prepare(XrdOucErrInfo &Resp, XrdSfsPrep &pargs, - XrdOucEnv *Info=0) -{ - (void)Resp; (void)pargs; (void)Info; - return 0; -} - -//------------------------------------------------------------------------------ -//! Notify the cmsd that a file or directory has been deleted. It is only called -//! called on a data server node. -//! -//! @param path The logical file name that was removed. -//------------------------------------------------------------------------------ - -virtual void Removed(const char *path) { (void)path; } - -//------------------------------------------------------------------------------ -//! Resume service after a suspension. -//! -//! @param Perm When true the resume persist across server restarts. Otherwise, -//! it is treated as a temporary request. -//------------------------------------------------------------------------------ - -virtual void Resume (int Perm=1) { (void)Perm; } - -//------------------------------------------------------------------------------ -//! Suspend service. -//! -//! @param Perm When true the suspend persist across server restarts. -//! Otherwise, it is treated as a temporary request. -//------------------------------------------------------------------------------ - -virtual void Suspend(int Perm=1) { (void)Perm; } - -// The following set of functions can be used to control whether or not clients -// are dispatched to this data server based on a virtual resource. The default -// implementations do nothing. -// -//------------------------------------------------------------------------------ -//! Enables the Reserve() & Release() methods. -//! -//! @param n a positive integer that specifies the amount of resource units -//! that are available. It may be reset at any time. -//! -//! @return The previous resource value. This first call returns 0. -//------------------------------------------------------------------------------ - -virtual int Resource(int n) { (void)n; return 0;} - -//------------------------------------------------------------------------------ -//! Decreases the amount of resources available. When the available resources -//! becomes non-positive, perform a temporary suspend to prevent additional -//! clients from being dispatched to this data server. -//! -//! @param n The value by which resources are decreased (default 1). -//! -//! @return The amount of resource left. -//------------------------------------------------------------------------------ - -virtual int Reserve (int n=1) { (void)n; return 0;} - -//------------------------------------------------------------------------------ -//! Increases the amount of resource available. When transitioning from a -//! a non-positive to a positive resource amount, perform a resume so that -//! additional clients may be dispatched to this data server. -//! -//! @param n The value to add to the resources available (default 1). The -//! total amount is capped by the amount specified by Resource(). -//! -//! @return The amount of resource left. -//------------------------------------------------------------------------------ - -virtual int Release (int n=1) { (void)n; return 0;} - -//------------------------------------------------------------------------------ -//! Obtain the overall space usage of a cluster. Called only on manager nodes. -//! -//! @param Resp Object to hold response or error message. -//! @param path Associated logical path for the space request. -//! @param Info Associated cgi information for path. -//! -//! @return Space information as defined by the response to kYR_statfs. For a -//! typical implementation see XrdCmsNode::do_StatFS(). -//------------------------------------------------------------------------------ - -virtual int Space(XrdOucErrInfo &Resp, const char *path, - XrdOucEnv *Info=0) = 0; - -//------------------------------------------------------------------------------ -//! Constructor -//! -//! @param acting The type of function this object is performing. -//------------------------------------------------------------------------------ - - enum Persona {amLocal, //!< Not affiliated with a cluster - amRemote, //!< Am a manager an issue redirects - amTarget //!< Am a server an field redirects - }; - - XrdCmsClient(Persona acting) : myPersona(acting) {} - -//------------------------------------------------------------------------------ -//! Destructor -//------------------------------------------------------------------------------ - -virtual ~XrdCmsClient() {} - -protected: - -Persona myPersona; -}; - -/******************************************************************************/ -/* I n s t a n t i a t i o n M o d e F l a g s */ -/******************************************************************************/ - -/*! The following instantiation mode flags are passed to the instantiator (see - comments that follow). They may be or'd together, depending on which mode - the cms client should operate. They are defined as follows: -*/ -namespace XrdCms -{ -enum {IsProxy = 1, //!< The role is proxy {plus one or more of the below} - IsRedir = 2, //!< The role is manager and will redirect users - IsTarget = 4, //!< The role is server and will be a redirection target - IsMeta = 8 //!< The role is meta {plus one or more of the above} - }; -} - -/******************************************************************************/ -/* C M S C l i e n t I n s t a n t i a t o r */ -/******************************************************************************/ - -//------------------------------------------------------------------------------ -//! Obtain an instance of a configured XrdCmsClient. -//! -//! The following extern "C" function is called to obtain an instance of the -//! XrdCmsClient object. This is only used if the client is an actual plug-in -//! as identified by the ofs.cmslib directive. Once the XrdCmsClient object -//! is obtained, its Configure() method is called to initialize the object. -//! -//! @param logger -> XrdSysLogger to be tied to an XrdSysError object for -//! any messages. -//! @param opMode -> The operational mode as defined by the enum above. There -//! are two general types of clients, IsRedir and IsTarget. -//! The IsProxy and IsMeta modes are specialization of these -//! two basic types. The plug-in must provide an instance of -//! the one asked for whether or not they actually do anything. -//! -//! IsRedir clients are anything other than a data provider -//! (i.e., data servers). These clients are expected -//! to locate files and redirect a requestor to an -//! actual data server. -//! -//! IsTarget clients are typically data providers (i.e., data -//! servers) but may actually do other functions are -//! are allowed to redirect as well. -//! -//! @param myPort -> The server's port number. -//! @param theSS -> The object that implements he underlying storage system. -//! This object may be passed for historic reasons. -//! -//! @return Success: a pointer to the appropriate object (IsRedir or IsTarget). -//! -//! Failure: a null pointer which causes initialization to fail. -//------------------------------------------------------------------------------ - -class XrdOss; - -typedef XrdCmsClient *(*XrdCmsClient_t)(XrdSysLogger *, int, int, XrdOss *); - -/*! extern "C" XrdCmsClient *XrdCmsGetClient(XrdSysLogger *Logger, - int opMode, - int myPort - XrdOss *theSS); -*/ - -//------------------------------------------------------------------------------ -//! Obtain an instance of a default unconfigured XrdCmsClient. -//! -//! The following function may be called to obtain an instance of the default -//! XrdCmsClient object. The Configure() method is *not* called before the -//! object is returned. The parameters are the same as those for the function -//! XrdCmsGetClient(), above. Note that you need not supply a pointer to the -//! underlying storage system, as this is historic in nature. -//! -//! @return Success: a pointer to the appropriate object (IsRedir or IsTarget). -//! -//! Failure: a null pointer, neither ISRedir nor IsTarget has been -//! specified or there is insufficient memory. -//------------------------------------------------------------------------------ - -namespace XrdCms -{ - XrdCmsClient *GetDefaultClient(XrdSysLogger *Logger, - int opMode, - int myPort - ); -} - -//------------------------------------------------------------------------------ -//! Declare compilation version. -//! -//! Additionally, you *should* declare the xrootd version you used to compile -//! your plug-in. -//------------------------------------------------------------------------------ - -/*! #include "XrdVersion.hh" - XrdVERSIONINFO(XrdCmsGetClient,); - -*/ -#endif diff --git a/src/XrdCms/XrdCmsClientConfig.cc b/src/XrdCms/XrdCmsClientConfig.cc deleted file mode 100644 index 30e7d3c634d..00000000000 --- a/src/XrdCms/XrdCmsClientConfig.cc +++ /dev/null @@ -1,634 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C m s C l i e n t C o n f i g . c c */ -/* */ -/* (c) 2011 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include - -#include "XrdCms/XrdCmsClientConfig.hh" -#include "XrdCms/XrdCmsClientMsg.hh" -#include "XrdCms/XrdCmsSecurity.hh" -#include "XrdCms/XrdCmsTrace.hh" -#include "XrdCms/XrdCmsUtils.hh" - -#include "XrdOuc/XrdOuca2x.hh" -#include "XrdOuc/XrdOucEnv.hh" -#include "XrdOuc/XrdOucStream.hh" -#include "XrdOuc/XrdOucTList.hh" -#include "XrdOuc/XrdOucUtils.hh" -#include "XrdSys/XrdSysHeaders.hh" - -using namespace XrdCms; - -/******************************************************************************/ -/* d e f i n e s */ -/******************************************************************************/ - -#define TS_Xeq(x,m) if (!strcmp(x,var)) return m(Config); - -/******************************************************************************/ -/* D e s t r u c t o r */ -/******************************************************************************/ - -XrdCmsClientConfig::~XrdCmsClientConfig() -{ - XrdOucTList *tp, *tpp; - - tpp = ManList; - while((tp = tpp)) {tpp = tp->next; delete tp;} - tpp = PanList; - while((tp = tpp)) {tpp = tp->next; delete tp;} -} - -/******************************************************************************/ -/* C o n f i g u r e */ -/******************************************************************************/ - -int XrdCmsClientConfig::Configure(const char *cfn, configWhat What, - configHow How) -{ -/* - Function: Establish configuration at start up time. - - Input: None. - - Output: 0 upon success or !0 otherwise. -*/ - XrdOucTList *tpe, *tpl; - int i, NoGo = 0; - const char *eText = 0; - char buff[256], *slash, *temp, *bP, sfx; - -// Preset some values -// - myHost = getenv("XRDHOST"); - if (!myHost) myHost = "localhost"; - myName = XrdOucUtils::InstName(1); - CMSPath= strdup("/tmp/"); - isMeta = How & configMeta; - isMan = What& configMan; - -// Process the configuration file -// - if (!(NoGo = ConfigProc(cfn)) && isMan) - {if (How & configProxy) eText = (PanList ? 0 : "Proxy manager"); - else if (!ManList) - eText = (How & configMeta ? "Meta manager" : "Manager"); - if (eText) {Say.Emsg("Config", eText, "not specified."); NoGo=1;} - } - -// Reset tracing options -// - if (getenv("XRDDEBUG")) Trace.What = TRACE_ALL; - -// Set proper local socket path -// - temp=XrdOucUtils::genPath(CMSPath, XrdOucUtils::InstName(-1), ".olb"); - free(CMSPath); CMSPath = temp; - XrdOucEnv::Export("XRDCMSPATH", temp); - XrdOucEnv::Export("XRDOLBPATH", temp); //Compatability - -// Determine what type of role we are playing -// - if (What & configServer) sfx = 's'; - else if (What & configSuper) sfx = 'u'; - else sfx = 'm'; - -// Determine which manager list we will be using -// - if (How & configProxy) - {sfx = toupper(sfx); - tpl = PanList; - } else tpl = ManList; - -// Generate the system ID for this configuration. -// - if (!ConfigSID(cfn, tpl, sfx)) NoGo = 1; - -// Export the manager list -// - if (tpl) - {i = 0; tpe = tpl; - while(tpe) {i += strlen(tpe->text) + 9; tpe = tpe->next;} - bP = temp = (char *)malloc(i); - while(tpl) - {bP += sprintf(bP, "%s:%d ", tpl->text, tpl->val); - tpl = tpl->next; - } - *(bP-1) = '\0'; - XrdOucEnv::Export("XRDCMSMAN", temp); free(temp); - } - -// Construct proper communications path for a supervisor node -// - i = strlen(CMSPath); - if (What & configSuper) - {while((tpl = ManList)) {ManList = tpl->next; delete tpl;} - slash = (CMSPath[i-1] == '/' ? (char *)"" : (char *)"/"); - sprintf(buff, "%s%solbd.super", CMSPath, slash); - ManList = new XrdOucTList(buff, -1, 0); - SMode = SModeP = FailOver; - } - -// Construct proper old communication path for a target node -// - temp = (What & (configMan|configSuper) ? (char *)"nimda" : (char *)"admin"); - slash = (CMSPath[i-1] == '/' ? (char *)"" : (char *)"/"); - sprintf(buff, "%s%solbd.%s", CMSPath, slash, temp); - free(CMSPath); CMSPath = strdup(buff); - - RepWaitMS = RepWait * 1000; - -// Initialize the msg queue -// - if (XrdCmsClientMsg::Init()) - {Say.Emsg("Config", ENOMEM, "allocate initial msg objects"); - NoGo = 1; - } - - return NoGo; -} - -/******************************************************************************/ -/* P r i v a t e F u n c t i o n s */ -/******************************************************************************/ -/******************************************************************************/ -/* C o n f i g P r o c */ -/******************************************************************************/ - -int XrdCmsClientConfig::ConfigProc(const char *ConfigFN) -{ - static int DoneOnce = 0; - char *var; - int cfgFD, retc, NoGo = 0; - XrdOucEnv myEnv; - XrdOucStream Config((DoneOnce ? 0 : &Say), getenv("XRDINSTANCE"), - &myEnv, "=====> "); - -// Make sure we have a config file -// - if (!ConfigFN || !*ConfigFN) - {Say.Emsg("Config", "cms configuration file not specified."); - return 1; - } - -// Try to open the configuration file. -// - if ( (cfgFD = open(ConfigFN, O_RDONLY, 0)) < 0) - {Say.Emsg("Config", errno, "open config file", ConfigFN); - return 1; - } - Config.Attach(cfgFD); - -// Now start reading records until eof. -// - while((var = Config.GetMyFirstWord())) - {if (!strncmp(var, "cms.", 4) - || !strncmp(var, "odc.", 4) // Compatability - || !strcmp(var, "all.manager") - || !strcmp(var, "all.adminpath") - || !strcmp(var, "olb.adminpath")) - if (ConfigXeq(var+4, Config)) {Config.Echo(); NoGo = 1;} - } - -// Now check if any errors occured during file i/o -// - if ((retc = Config.LastError())) - NoGo = Say.Emsg("Config", retc, "read config file", ConfigFN); - Config.Close(); - -// Return final return code -// - DoneOnce = 1; - return NoGo; -} - -/******************************************************************************/ -/* C o n f i g S i d */ -/******************************************************************************/ - -bool XrdCmsClientConfig::ConfigSID(const char *cFN, XrdOucTList *tpl, char sfx) -{ - char *sidVal; - -// Get the node ID if we need to -// - if (VNID_Lib) - {myVNID = XrdCmsSecurity::getVnId(Say, cFN, VNID_Lib, VNID_Parms, sfx); - if (!myVNID) return false; - } - -// Generate the system ID and set the cluster ID -// - sidVal = XrdCmsSecurity::setSystemID(tpl, myVNID, cidTag, sfx); - if (!sidVal || *sidVal == '!') - {const char *msg; - if (!sidVal) msg = "too many managers."; - else msg = sidVal+1; - Say.Emsg("Config ","Unable to generate system ID; ", msg); - return false; - } - else if (QTRACE(Debug)) - Say.Say("Config ", "Global System Identification: ", sidVal); - return true; -} - -/******************************************************************************/ -/* C o n f i g X e q */ -/******************************************************************************/ - -int XrdCmsClientConfig::ConfigXeq(char *var, XrdOucStream &Config) -{ - - // Process items. for either a local or a remote configuration - // - TS_Xeq("cidtag", xcidt); - TS_Xeq("conwait", xconw); - TS_Xeq("manager", xmang); - TS_Xeq("adminpath", xapath); - TS_Xeq("request", xreqs); - TS_Xeq("trace", xtrac); - TS_Xeq("vnid", xvnid); - return 0; -} - -/******************************************************************************/ -/* x a p a t h */ -/******************************************************************************/ - -/* Function: xapath - - Purpose: To parse the directive: adminpath [ group ] - - the path of the named socket to use for admin requests. - Only the path may be specified, not the filename. - group allow group access to the path. - - Type: Manager only, non-dynamic. - - Output: 0 upon success or !0 upon failure. -*/ - -int XrdCmsClientConfig::xapath(XrdOucStream &Config) -{ - char *pval; - -// Get the path -// - pval = Config.GetWord(); - if (!pval || !pval[0]) - {Say.Emsg("Config", "cms admin path not specified"); return 1;} - -// Make sure it's an absolute path -// - if (*pval != '/') - {Say.Emsg("Config", "cms admin path not absolute"); return 1;} - -// Record the path -// - if (CMSPath) free(CMSPath); - CMSPath = strdup(pval); - return 0; -} - -/******************************************************************************/ -/* x c i d t */ -/******************************************************************************/ - -/* Function: xcidt - - Purpose: To parse the directive: cidtag - - a 1- to 16-character cluster ID tag. - - Output: 0 upon success or !0 upon failure. -*/ - -int XrdCmsClientConfig::xcidt(XrdOucStream &Config) -{ - char *val; - -// Get the path -// - if (!(val = Config.GetWord()) || !val[0]) - {Say.Emsg("Config", "tag not specified"); return 1;} - -// Make sure it is not too long -// - if ((int)strlen(val) > 16) - {Say.Emsg("Config", "tag is > 16 characters"); return 1;} - -// Record the tag -// - if (cidTag) free(cidTag); - cidTag = strdup(val); - return 0; -} - -/******************************************************************************/ -/* x c o n w */ -/******************************************************************************/ - -/* Function: xconw - - Purpose: To parse the directive: conwait - - number of seconds to wait for a manager connection - - Type: Remote server only, dynamic. - - Output: 0 upon success or !0 upon failure. -*/ - -int XrdCmsClientConfig::xconw(XrdOucStream &Config) -{ - char *val; - int cw; - - if (!(val = Config.GetWord())) - {Say.Emsg("Config", "conwait value not specified."); return 1;} - - if (XrdOuca2x::a2tm(Say,"conwait value",val,&cw,1)) return 1; - - ConWait = cw; - return 0; -} - -/******************************************************************************/ -/* x m a n g */ -/******************************************************************************/ - -/* Function: xmang - - Purpose: Parse: manager [meta | peer | proxy] [all|any] - [+][:|] [if ...] - - meta For cmsd: Specifies the manager when running as a manager - For xrootd: Specifies the manager when running as a meta - peer For cmsd: Specifies the manager when running as a peer - For xrootd: The directive is ignored. - proxy For cmsd: This directive is ignored. - For xrootd: Specifies the cms-proxy service manager - all Distribute requests across all managers. - any Choose different manager only when necessary (default). - The dns name of the host that is the cache manager. - If the host name ends with a plus, all addresses that are - associated with the host are treated as managers. - The port number to use for this host. - if Apply the manager directive if "if" is true. See - XrdOucUtils:doIf() for "if" syntax. - - Notes: Any number of manager directives can be given. When niether peer nor - proxy is specified, then regardless of role the following occurs: - cmsd: Subscribes to each manager whens role is not peer. - xrootd: Logins in as a redirector to each manager when role is not - proxy or server. - - Type: Remote server only, non-dynamic. - - Output: 0 upon success or !0 upon failure. -*/ - -int XrdCmsClientConfig::xmang(XrdOucStream &Config) -{ - class StorageHelper - {public: - StorageHelper(char **v1, char **v2) : val1(v1), val2(v2) {} - ~StorageHelper() {if (*val1) free(*val1); - if (*val2) free(*val2); - } - char **val1, **val2; - }; - - XrdOucTList **theList; - char *val, *hSpec = 0, *hPort = 0; - StorageHelper SHelp(&hSpec, &hPort); - int rc, xMeta = 0, xProxy = 0, smode = FailOver; - -// Process the optional "peer" or "proxy" -// - if ((val = Config.GetWord())) - {if (!strcmp("peer", val)) return Config.noEcho(); - if ((xProxy = !strcmp("proxy", val))) val = Config.GetWord(); - else if ((xMeta = !strcmp("meta", val))) - if (isMeta || isMan) val = Config.GetWord(); - else return Config.noEcho(); - else if (isMeta) return Config.noEcho(); - } - -// We can accept this manager. Skip the optional "all" or "any" -// - if (val) - { if (!strcmp("any", val)) smode = FailOver; - else if (!strcmp("all", val)) smode = RoundRob; - else smode = 0; - if (smode) - {if (xProxy) SModeP = smode; - else SMode = smode; - val = Config.GetWord(); - } - } - -// Get the actual manager -// - if (!val) - {Say.Emsg("Config","manager host name not specified"); return 1;} - else hSpec = strdup(val); - -// Grab the port number (either in hostname or following token) -// - if (!(hPort = XrdCmsUtils::ParseManPort(&Say, Config, hSpec))) return 1; - -// Process any "if" clause now -// - if ((val = Config.GetWord()) && !strcmp("if", val)) - if ((rc = XrdOucUtils::doIf(&Say,Config,"manager directive", - myHost, myName, getenv("XRDPROG"))) <= 0) - {if (!rc) Config.noEcho(); return (rc < 0);} - -// If we are a manager and found a meta-manager indidicate it and bail. -// - if (xMeta && !isMeta) {haveMeta = 1; return 0;} - theList = (xProxy ? &PanList : &ManList); - -// Parse the manager list and return the result -// - return (XrdCmsUtils::ParseMan(&Say, theList, hSpec, hPort, 0) ? 0 : 1); -} - -/******************************************************************************/ -/* x r e q s */ -/******************************************************************************/ - -/* Function: xreqs - - Purpose: To parse the directive: request [repwait ] [delay ] - [noresp ] [prep ] - [fwd ] - - max number of seconds to wait for a cmsd reply - number of seconds to delay a retry upon failure - number of no-responses before cms fault declared. - milliseconds between prepare/forward requests - - Type: Remote server only, dynamic. - - Output: 0 upon success or !0 upon failure. -*/ - -int XrdCmsClientConfig::xreqs(XrdOucStream &Config) -{ - char *val; - static struct reqsopts {const char *opname; int istime; int *oploc;} - rqopts[] = - { - {"delay", 1, &RepDelay}, - {"fwd", 0, &FwdWait}, - {"noresp", 0, &RepNone}, - {"prep", 0, &PrepWait}, - {"repwait", 1, &RepWait} - }; - int i, ppp, numopts = sizeof(rqopts)/sizeof(struct reqsopts); - - if (!(val = Config.GetWord())) - {Say.Emsg("Config", "request arguments not specified"); return 1;} - - while (val) - do {for (i = 0; i < numopts; i++) - if (!strcmp(val, rqopts[i].opname)) - { if (!(val = Config.GetWord())) - {Say.Emsg("Config","request argument value not specified"); - return 1;} - if (rqopts[i].istime ? - XrdOuca2x::a2tm(Say,"request value",val,&ppp,1) : - XrdOuca2x::a2i( Say,"request value",val,&ppp,1)) - return 1; - else *rqopts[i].oploc = ppp; - break; - } - if (i >= numopts) Say.Say("Config warning: ignoring invalid request option '",val,"'."); - } while((val = Config.GetWord())); - return 0; -} - -/******************************************************************************/ -/* x t r a c e */ -/******************************************************************************/ - -/* Function: xtrace - - Purpose: To parse the directive: trace - - the blank separated list of events to trace. Trace - directives are cummalative. - - Output: retc upon success or -EINVAL upon failure. -*/ - -int XrdCmsClientConfig::xtrac(XrdOucStream &Config) -{ - char *val; - static struct traceopts {const char *opname; int opval;} tropts[] = - { - {"all", TRACE_ALL}, - {"debug", TRACE_Debug}, - {"files", TRACE_Files}, - {"forward", TRACE_Forward}, - {"redirect", TRACE_Redirect}, - {"defer", TRACE_Defer}, - {"stage", TRACE_Stage} - }; - int i, neg, trval = 0, numopts = sizeof(tropts)/sizeof(struct traceopts); - - if (!(val = Config.GetWord())) - {Say.Emsg("config", "trace option not specified"); return 1;} - while (val) - {if (!strcmp(val, "off")) trval = 0; - else {if ((neg = (val[0] == '-' && val[1]))) val++; - for (i = 0; i < numopts; i++) - {if (!strcmp(val, tropts[i].opname)) - {if (neg) trval &= ~tropts[i].opval; - else trval |= tropts[i].opval; - break; - } - } - if (i >= numopts) - Say.Say("Config warning: ignoring invalid trace option '",val,"'."); - } - val = Config.GetWord(); - } - Trace.What = trval; - return 0; -} - -/******************************************************************************/ -/* x v n i d */ -/******************************************************************************/ - -/* Function: xvnid - - Purpose: To parse the directive: vnid {=|<|@} [] - - = - the actual vnid value - < - the path of the file to be read for the vnid. - @ - the path of the plugin library to be used. - optional parms to be passed - - Output: 0 upon success or !0 upon failure. -*/ - -int XrdCmsClientConfig::xvnid(XrdOucStream &Config) -{ - char *val, parms[1024]; - -// Get the argument -// - if (!(val = Config.GetWord()) || !val[0]) - {Say.Emsg("Config", "vnid not specified"); return 1;} - -// Record the path -// - if (VNID_Lib) free(VNID_Lib); - VNID_Lib = strdup(val); - -// Record any parms (only if it starts with an @) -// - if (VNID_Parms) {free(VNID_Parms); VNID_Parms = 0;} - if (*VNID_Lib == '@') - {if (!Config.GetRest(parms, sizeof(parms))) - {Say.Emsg("Config", "vnid plug-in parameters too long"); return 1;} - if (*parms) VNID_Parms = strdup(parms); - } - return 0; -} diff --git a/src/XrdCms/XrdCmsClientConfig.hh b/src/XrdCms/XrdCmsClientConfig.hh deleted file mode 100644 index adc793f4be3..00000000000 --- a/src/XrdCms/XrdCmsClientConfig.hh +++ /dev/null @@ -1,101 +0,0 @@ -#ifndef _CMS_CLIENTCONFIG_H -#define _CMS_CLIENTCONFIG_H -/******************************************************************************/ -/* */ -/* X r d C m s C l i e n t C o n f i g . h h */ -/* */ -/* (c) 2007 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdOuc/XrdOucTList.hh" -#include "XrdOuc/XrdOuca2x.hh" - -class XrdOucStream; -class XrdSysError; - -#define ODC_FAILOVER 'f' -#define ODC_ROUNDROB 'r' - -class XrdCmsClientConfig -{ -public: - -enum configHow {configMeta = 1, configNorm = 2, configProxy = 4}; -enum configWhat {configMan = 1, configSuper = 2, configServer = 4}; - -int Configure(const char *cfn, configWhat What, configHow How); - -int ConWait; // Seconds to wait for a manager connection -int RepWait; // Seconds to wait for manager replies -int RepWaitMS; // RepWait*1000 for poll() -int RepDelay; // Seconds to delay before retrying manager -int RepNone; // Max number of consecutive non-responses -int PrepWait; // Millisecond wait between prepare requests -int FwdWait; // Millisecond wait between foward requests -int haveMeta; // Have a meta manager (only if we are a manager) - -char *CMSPath; // Path to the local cmsd for target nodes -const char *myHost; -const char *myName; - char *myVNID; - char *cidTag; - -XrdOucTList *ManList; // List of managers for remote redirection -XrdOucTList *PanList; // List of managers for proxy redirection -unsigned char SMode; // Manager selection mode -unsigned char SModeP; // Manager selection mode (proxy) - -enum {FailOver = 'f', RoundRob = 'r'}; - - XrdCmsClientConfig() : ConWait(10), RepWait(3), RepWaitMS(3000), - RepDelay(5), RepNone(8), PrepWait(33), - FwdWait(0), haveMeta(0), CMSPath(0), - myHost(0), myName(0), myVNID(0), - cidTag(0), ManList(0), PanList(0), - SMode(FailOver), SModeP(FailOver), - VNID_Lib(0), VNID_Parms(0), - isMeta(0), isMan(0) {} - ~XrdCmsClientConfig(); - -private: -char *VNID_Lib; -char *VNID_Parms; - -int isMeta; // We are a meta manager -int isMan; // We are a manager - -int ConfigProc(const char *cfn); -bool ConfigSID(const char *cFile, XrdOucTList *tpl, char sfx); -int ConfigXeq(char *var, XrdOucStream &Config); -int xapath(XrdOucStream &Config); -int xcidt(XrdOucStream &Config); -int xconw(XrdOucStream &Config); -int xmang(XrdOucStream &Config); -int xreqs(XrdOucStream &Config); -int xtrac(XrdOucStream &Config); -int xvnid(XrdOucStream &Config); -}; -#endif diff --git a/src/XrdCms/XrdCmsClientMan.cc b/src/XrdCms/XrdCmsClientMan.cc deleted file mode 100644 index 0ee2c8725f2..00000000000 --- a/src/XrdCms/XrdCmsClientMan.cc +++ /dev/null @@ -1,491 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C m s C l i e n t M a n . c c */ -/* */ -/* (c) 2007 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -#include "XrdCms/XrdCmsClientMan.hh" -#include "XrdCms/XrdCmsClientMsg.hh" -#include "XrdCms/XrdCmsLogin.hh" -#include "XrdCms/XrdCmsTrace.hh" - -#include "XrdSfs/XrdSfsInterface.hh" - -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysTimer.hh" - -#include "Xrd/XrdInet.hh" -#include "Xrd/XrdLink.hh" - -using namespace XrdCms; - -/******************************************************************************/ -/* G l o b a l s */ -/******************************************************************************/ - -XrdOucBuffPool XrdCmsClientMan::BuffPool(XrdOucEI::Max_Error_Len, 65536, 1, 16); - -XrdInet *XrdCmsClientMan::Network = 0; - -char XrdCmsClientMan::doDebug = 0; - -const char *XrdCmsClientMan::ConfigFN = 0; - -XrdSysMutex XrdCmsClientMan::manMutex; - -/******************************************************************************/ -/* C o n s t r u c t o r */ -/******************************************************************************/ - -XrdCmsClientMan::XrdCmsClientMan(char *host, int port, - int cw, int nr, int rw, int rd) - : syncResp(0) -{ - static XrdSysMutex initMutex; - static int Instance = 0; - char *dot; - - Host = strdup(host); - if ((dot = index(Host, '.'))) - {*dot = '\0'; HPfx = strdup(Host); *dot = '.';} - else HPfx = strdup(Host); - Port = port; - Link = 0; - Active = 0; - Silent = 0; - Suspend = 1; - RecvCnt = 0; - nrMax = nr; - NetBuff = BuffPool.Alloc(XrdOucEI::Max_Error_Len); - repWMax = rw; - repWait = 0; - minDelay= rd; - maxDelay= rd*3; - chkCount= chkVal; - lastUpdt= lastTOut = time(0); - Next = 0; - manInst = 1; - -// Compute dally value -// - dally = cw / 2 - 1; - if (dally < 3) dally = 3; - else if (dally > 10) dally = 10; - -// Provide a unique mask number for this manager -// - initMutex.Lock(); - manMask = 1<Close(); - if (Host) free(Host); - if (HPfx) free(HPfx); - if (NetBuff) NetBuff->Recycle(); -} - -/******************************************************************************/ -/* d e l a y R e s p */ -/******************************************************************************/ - -int XrdCmsClientMan::delayResp(XrdOucErrInfo &Resp) -{ - XrdCmsResp *rp; - int msgid; - -// Obtain the message ID -// - if (!(msgid = Resp.getErrInfo())) - {Say.Emsg("Manager", Host, "supplied invalid waitr msgid"); - Resp.setErrInfo(EILSEQ, "redirector protocol error"); - syncResp.Post(); - return SFS_ERROR; - } - -// Allocate a delayed response object -// - if (!(rp = XrdCmsResp::Alloc(&Resp, msgid))) - {Say.Emsg("Manager",ENOMEM,"allocate resp object for",Resp.getErrUser()); - Resp.setErrInfo(0, "0"); - syncResp.Post(); - return SFS_STALL; - } - -// Add this object to our delayed response queue. If the manager bounced then -// purge all of the pending repsonses to avoid sending wrong ones. -// - if (msgid < maxMsgID) RespQ.Purge(); - maxMsgID = msgid; - RespQ.Add(rp); - -// Tell client to wait for response. The semaphore post allows the manager -// to get the next message from the cmsd. This prevents us from getting the -// delayed response before the response object is added to the queue. -// - Resp.setErrInfo(0, ""); - syncResp.Post(); - return SFS_STARTED; -} - -/******************************************************************************/ -/* S e n d */ -/******************************************************************************/ - -int XrdCmsClientMan::Send(unsigned int &iMan, char *msg, int mlen) -{ - int allok = 0; - -// Determine message length -// - if (!mlen) mlen = strlen(msg); - -// Send the request -// - myData.Lock(); - iMan = manInst; - if (Active) - {if (Link) - {if (!(allok = Link->Send(msg, mlen) > 0)) - {Active = 0; - Link->Close(1); - manInst++; - } else SendCnt++; - } - } - myData.UnLock(); - -// All done -// - return allok; -} - -/******************************************************************************/ - -int XrdCmsClientMan::Send(unsigned int &iMan, const struct iovec *iov, int iovcnt, int iotot) -{ - int allok = 0; - -// Send the request -// - myData.Lock(); - iMan = manInst; - if (Active) - {if (Link) - {if (!(allok = Link->Send(iov, iovcnt, iotot) > 0)) - {Active = 0; - Link->Close(1); - manInst++; - } else SendCnt++; - } - } - myData.UnLock(); - -// All done -// - return allok; -} - -/******************************************************************************/ -/* S t a r t */ -/******************************************************************************/ - -void *XrdCmsClientMan::Start() -{ - -// First step is to connect to the manager -// - do {Hookup(); - // Now simply start receiving messages on the stream. When we get a - // respwait reply then we must be assured that the object representing - // the request is added to the queue before the actual reply arrives. - // We do this by waiting on syncResp which is posted once the request - // object is fully processed. The actual response associated with the - // respwait is synchronized during the callback phase since the client - // must receive the respwait before the subsequent response. - // - while(Receive()) - if (Response.modifier & CmsResponse::kYR_async) relayResp(); - else if (Response.rrCode == kYR_status) setStatus(); - else if (XrdCmsClientMsg::Reply(HPfx, Response, NetBuff)) - {if (Response.rrCode == kYR_waitresp) syncResp.Wait();} - - // Tear down the connection - // - myData.Lock(); - if (Link) {Link->Close(); Link = 0;} - Active = 0; Suspend = 1; - myData.UnLock(); - - // Indicate the problem - // - Say.Emsg("ClientMan", "Disconnected from", Host); - XrdSysTimer::Snooze(dally); - } while(1); - -// We should never get here -// - return (void *)0; -} - -/******************************************************************************/ -/* w h a t s U p */ -/******************************************************************************/ - -int XrdCmsClientMan::whatsUp(const char *user, const char *path, - unsigned int iMan) -{ - EPNAME("whatsUp"); - unsigned int xMan; - int theDelay, inQ; - bool lClose = false; - -// The cmsd did not respond. Increase silent count and see if restart is needed -// Otherwise, increase the wait interval just in case things are just slow. -// - myData.Lock(); - if (Active) - {if (Active == RecvCnt) - {if ((time(0)-lastTOut) >= repWait) - {Silent++; - if (Silent > nrMax) - {Active = 0; Silent = 0; Suspend = 1; - if (Link && iMan == manInst) - {Link->Close(1); - manInst++; lClose = true; - } - } else if (Silent & 0x02 && repWait < repWMax) repWait++; - } - } else {Active = RecvCnt; Silent = 0; lastTOut = time(0);} - } - -// Calclulate how long to delay the client. This will be based on the number -// of outstanding requests bounded by the config delay value. -// - inQ = XrdCmsClientMsg::inQ(); - theDelay = inQ * qTime; - xMan = manInst; - myData.UnLock(); - theDelay = theDelay/1000 + (theDelay % 1000 ? 1 : 0); - if (theDelay < minDelay) theDelay = minDelay; - if (theDelay > maxDelay) theDelay = maxDelay; - -// Do Some tracing here -// - TRACE(Redirect, user <<" no resp from inst " <Connect(Host, Port, opts))) - {XrdSysTimer::Snooze(dally); - if (tries--) opts = XRDNET_NOEMSG; - else {opts = 0; tries = 12;} - continue; - } -// lp->Bind(XrdSysThread::ID()); - memset(&Data, 0, sizeof(Data)); - Data.Mode = CmsLoginData::kYR_director; - Data.HoldTime = static_cast(getpid()); - if (!(rc = XrdCmsLogin::Login(lp, Data))) break; - lp->Close(); - XrdSysTimer::Snooze(dally); - } while(1); - -// Establish global state -// - manMutex.Lock(); - doDebug |= (Data.Mode & CmsLoginData::kYR_debug ? manMask : 0); - manMutex.UnLock(); - -// All went well, finally -// - myData.Lock(); - Link = lp; - Active = 1; - Silent = 0; - RecvCnt = 1; - SendCnt = 1; - Suspend = (Data.Mode & CmsLoginData::kYR_suspend); - -// Calculate how long we will wait for replies before delaying the client. -// This is computed dynamically based on the expected response window. -// - if ((oldWait = (repWait*20/100)) < 2) oldWait = 2; - if (Data.HoldTime > repWMax*1000) repWait = repWMax; - else if (Data.HoldTime <= 0) repWait = repWMax; - else {repWait = Data.HoldTime*3; - repWait = (repWait/1000) + (repWait % 1000 ? 1 : 0); - if (repWait > repWMax) repWait = repWMax; - else if (repWait < oldWait) repWait = oldWait; - } - qTime = (Data.HoldTime < 100 ? 100 : Data.HoldTime); - lastTOut = time(0); - myData.UnLock(); - -// Tell the world -// - sprintf(buff, "v %d", Data.Version); - Say.Emsg("ClientMan", (Suspend ? "Connected to suspended" : "Connected to"), - Host, buff); - DEBUG(Host <<" qt=" <RecvAll((char *)&Response, sizeof(Response)) > 0) - {int dlen = static_cast(ntohs(Response.datalen)); - RecvCnt++; - DEBUG(Link->Name() <<' ' < NetBuff->BuffSize()) - && (Response.rrCode != kYR_data || !NetBuff->Resize(dlen))) - Say.Emsg("ClientMan", "Excessive msg length from", Host); - else {NetBuff->SetLen(dlen); - return Link->RecvAll(NetBuff->Buffer(), dlen); - } - } - return 0; -} - -/******************************************************************************/ -/* r e l a y R e s p */ -/******************************************************************************/ - -void XrdCmsClientMan::relayResp() -{ - EPNAME("relayResp"); - XrdCmsResp *rp; - -// Remove the response object from our queue. -// - if (!(rp = RespQ.Rem(Response.streamid))) - {DEBUG(Host <<" replied to non-existent request; id=" <Reply(HPfx, Response, NetBuff); - -// Obtain a new network buffer -// - NetBuff = BuffPool.Alloc(XrdOucEI::Max_Error_Len); -} - -/******************************************************************************/ -/* Private: c h k S t a t u s */ -/******************************************************************************/ - -int XrdCmsClientMan::chkStatus() -{ - static CmsUpdateRequest Updt = {{0, kYR_update, 0, 0}}; - XrdSysMutexHelper mdMon(myData); - time_t nowTime; - -// Count down the query count and ask again every 30 seconds -// - if (!chkCount--) - {chkCount = chkVal; - nowTime = time(0); - if ((nowTime - lastUpdt) >= 30) - {lastUpdt = nowTime; - if (Active) Link->Send((char *)&Updt, sizeof(Updt)); - } - } - return Suspend; -} - -/******************************************************************************/ -/* s e t S t a t u s */ -/******************************************************************************/ - -void XrdCmsClientMan::setStatus() -{ - EPNAME("setStatus"); - const char *State = 0, *Event = "?"; - - - myData.Lock(); - if (Response.modifier & CmsStatusRequest::kYR_Suspend) - {Event = "suspend"; - if (!Suspend) {Suspend = 1; State = "suspended";} - } - else if (Response.modifier & CmsStatusRequest::kYR_Resume) - {Event = "resume"; - if (Suspend) {Suspend = 0; State = "resumed";} - } - myData.UnLock(); - - DEBUG(Host <<" sent " <. */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -#include "XProtocol/YProtocol.hh" - -#include "XrdCms/XrdCmsResp.hh" -#include "XrdOuc/XrdOucBuffer.hh" -#include "XrdOuc/XrdOucErrInfo.hh" -#include "XrdSys/XrdSysAtomics.hh" -#include "XrdSys/XrdSysPthread.hh" - -class XrdInet; -class XrdLink; - -class XrdCmsClientMan -{ -public: - -static char doDebug; - -int delayResp(XrdOucErrInfo &Resp); - -inline int isActive() {AtomicRet(myData, Active);} - -XrdCmsClientMan *nextManager() {return Next;} - -char *Name() {return Host;} -char *NPfx() {return HPfx;} - -int manPort() {return Port;} - -int Send(unsigned int &iMan, char *msg, int mlen=0); -int Send(unsigned int &iMan, const struct iovec *iov, - int iovcnt, int iotot=0); - -void *Start(); - -inline int Suspended() {AtomicBeg(myData); - int sVal = AtomicGet(Suspend); - AtomicEnd(myData); - if (!sVal) return sVal; - return chkStatus(); - } - -void setNext(XrdCmsClientMan *np) {Next = np;} - -static void setNetwork(XrdInet *nP) {Network = nP;} - -static void setConfig(const char *cfn) {ConfigFN = cfn;} - -int whatsUp(const char *user, const char *path, - unsigned int iMan); - -inline int waitTime() {AtomicRet(myData, repWait);} - - XrdCmsClientMan(char *host,int port,int cw,int nr,int rw,int rd); - ~XrdCmsClientMan(); - -private: -int Hookup(); -int Receive(); -void relayResp(); -int chkStatus(); -void setStatus(); - -static XrdSysMutex manMutex; -static XrdOucBuffPool BuffPool; -static XrdInet *Network; -static const char *ConfigFN; -static const int chkVal = 256; - -XrdSysSemaphore syncResp; -XrdCmsRespQ RespQ; - -XrdCmsClientMan *Next; -XrdSysMutex myData; -XrdLink *Link; -char *Host; -char *HPfx; -int Port; -unsigned int manInst; -int manMask; -int dally; -int Active; -int Silent; -int Suspend; -int RecvCnt; -int SendCnt; -int nrMax; -int maxMsgID; -int repWait; -int repWMax; -int minDelay; -int maxDelay; -int qTime; -int chkCount; -time_t lastUpdt; -time_t lastTOut; -XrdCms::CmsRRHdr Response; -XrdOucBuffer *NetBuff; -}; -#endif diff --git a/src/XrdCms/XrdCmsClientMsg.cc b/src/XrdCms/XrdCmsClientMsg.cc deleted file mode 100644 index c602856a143..00000000000 --- a/src/XrdCms/XrdCmsClientMsg.cc +++ /dev/null @@ -1,187 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C m s C l i e n t M s g . c c */ -/* */ -/* (c) 2007 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -#include "XProtocol/YProtocol.hh" -#include "XrdCms/XrdCmsClientMsg.hh" -#include "XrdCms/XrdCmsParser.hh" -#include "XrdCms/XrdCmsTrace.hh" -#include "XrdOuc/XrdOucBuffer.hh" -#include "XrdOuc/XrdOucErrInfo.hh" - -using namespace XrdCms; - -/******************************************************************************/ -/* G l o b a l s */ -/******************************************************************************/ - -int XrdCmsClientMsg::nextid = 0; -int XrdCmsClientMsg::numinQ = 0; - -XrdCmsClientMsg *XrdCmsClientMsg::msgTab = 0; -XrdCmsClientMsg *XrdCmsClientMsg::nextfree = 0; - -XrdSysMutex XrdCmsClientMsg::FreeMsgQ; - -/******************************************************************************/ -/* A l l o c */ -/******************************************************************************/ - -// Returns the message object locked! - -XrdCmsClientMsg *XrdCmsClientMsg::Alloc(XrdOucErrInfo *erp) -{ - XrdCmsClientMsg *mp; - int lclid; - -// Allocate a message object -// - FreeMsgQ.Lock(); - if (nextfree) {mp = nextfree; nextfree = mp->next;} - else {FreeMsgQ.UnLock(); return (XrdCmsClientMsg *)0;} - lclid = nextid = (nextid + MidIncr) & IncMask; - numinQ++; - FreeMsgQ.UnLock(); - -// Initialize it -// - mp->Hold.Lock(); - mp->id = (mp->id & MidMask) | lclid; - mp->Resp = erp; - mp->next = 0; - mp->inwaitq = 1; - -// Return the message object -// - return mp; -} - -/******************************************************************************/ -/* I n i t */ -/******************************************************************************/ - -int XrdCmsClientMsg::Init() -{ - int i; - XrdCmsClientMsg *msgp; - -// Allocate the fixed number of msg blocks. These will never be freed! -// - if (!(msgp = new XrdCmsClientMsg[MaxMsgs]())) return 1; - msgTab = &msgp[0]; - nextid = MaxMsgs; - -// Place all of the msg blocks on the free list -// - for (i = 0; i < MaxMsgs; i++) - {msgp->next = nextfree; nextfree = msgp; msgp->id = i; msgp++;} - -// All done -// - return 0; -} - -/******************************************************************************/ -/* R e c y c l e */ -/******************************************************************************/ - -// Message object lock *must* be held by the caller upon entry! - -void XrdCmsClientMsg::Recycle() -{ - static XrdOucErrInfo dummyResp; - -// Remove this from he wait queue and substitute a safe resp object. We do -// this because a reply may be pending and will post when we release the lock -// - inwaitq = 0; - Resp = &dummyResp; - Hold.UnLock(); - -// Place message object on re-usable queue -// - FreeMsgQ.Lock(); - next = nextfree; - nextfree = this; - if (numinQ >= 0) numinQ--; - FreeMsgQ.UnLock(); -} - -/******************************************************************************/ -/* R e p l y */ -/******************************************************************************/ - -int XrdCmsClientMsg::Reply(const char *Man, CmsRRHdr &hdr, XrdOucBuffer *buff) -{ - EPNAME("Reply") - XrdCmsClientMsg *mp; - -// Find the appropriate message -// - if (!(mp = XrdCmsClientMsg::RemFromWaitQ(hdr.streamid))) - {DEBUG("to non-existent message; id=" <Result = XrdCmsParser::Decode(Man,hdr,buff,(XrdOucErrInfo *)(mp->Resp)); - -// Signal a reply and return -// - mp->Hold.Signal(); - mp->Hold.UnLock(); - return 1; -} - -/******************************************************************************/ -/* P r i v a t e M e t h o d s */ -/******************************************************************************/ -/******************************************************************************/ -/* R e m F r o m W a i t Q */ -/******************************************************************************/ - -// RemFromWaitQ() returns the msg object with the object locked! The caller -// must unlock the object. - -XrdCmsClientMsg *XrdCmsClientMsg::RemFromWaitQ(int msgid) -{ - int msgnum; - -// Locate the message object (the low order bits index it) -// - msgnum = msgid & MidMask; - msgTab[msgnum].Hold.Lock(); - if (!msgTab[msgnum].inwaitq || msgTab[msgnum].id != msgid) - {msgTab[msgnum].Hold.UnLock(); return (XrdCmsClientMsg *)0;} - msgTab[msgnum].inwaitq = 0; - return &msgTab[msgnum]; -} diff --git a/src/XrdCms/XrdCmsClientMsg.hh b/src/XrdCms/XrdCmsClientMsg.hh deleted file mode 100644 index 796f673d39f..00000000000 --- a/src/XrdCms/XrdCmsClientMsg.hh +++ /dev/null @@ -1,88 +0,0 @@ -#ifndef __CMS_CLIENTMSG__ -#define __CMS_CLIENTMSG__ -/******************************************************************************/ -/* */ -/* X r d C m s C l i e n t M s g . h h */ -/* */ -/* (c) 2007 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XProtocol/YProtocol.hh" - -#include "XrdSys/XrdSysPthread.hh" - -class XrdOucErrInfo; -class XrdOucBuffer; - -class XrdCmsClientMsg -{ -public: - -static XrdCmsClientMsg *Alloc(XrdOucErrInfo *erp); - -inline int getResult() {return Result;} - -inline int ID() {return id;} - -static int Init(); - -static int inQ() {return numinQ;} - - void Lock() {Hold.Lock();} - - void Recycle(); - -static int Reply(const char *Man,XrdCms::CmsRRHdr &hdr,XrdOucBuffer *buff); - - void UnLock() {Hold.UnLock();} - - int Wait4Reply(int wtime) {return Hold.Wait(wtime);} - - XrdCmsClientMsg() : Hold(0) {next = 0; inwaitq = 0; Resp = 0; Result = 0;} - ~XrdCmsClientMsg() {} - -private: -static const int MidMask = 1023; -static const int MaxMsgs = 1024; -static const int MidIncr = 1024; -static const int IncMask = 0x3ffffc00; -static XrdCmsClientMsg *RemFromWaitQ(int msgid); - -static int nextid; -static int numinQ; - -static XrdCmsClientMsg *msgTab; -static XrdCmsClientMsg *nextfree; -static XrdSysMutex FreeMsgQ; - -XrdCmsClientMsg *next; -XrdSysCondVar Hold; -int inwaitq; -int id; -XrdOucErrInfo *Resp; -int Result; -}; -#endif diff --git a/src/XrdCms/XrdCmsClustID.cc b/src/XrdCms/XrdCmsClustID.cc deleted file mode 100644 index 080570941c1..00000000000 --- a/src/XrdCms/XrdCmsClustID.cc +++ /dev/null @@ -1,255 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C m s C l u s t I D . c c */ -/* */ -/* (c) 2014 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Deprtment of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -#include "XrdCms/XrdCmsClustID.hh" -#include "XrdCms/XrdCmsNode.hh" -#include "XrdCms/XrdCmsTrace.hh" -#include "XrdOuc/XrdOucHash.hh" -#include "XrdSys/XrdSysPthread.hh" - -using namespace XrdCms; - -/******************************************************************************/ -/* L o c a l S t a t i c O b j e c t s */ -/******************************************************************************/ - -namespace -{ -XrdSysMutex cidMtx; - -XrdOucHash cidTab; - -XrdCmsClustID *cidFree = new XrdCmsClustID(); -}; - -/******************************************************************************/ -/* Static: A d d I D */ -/******************************************************************************/ - -XrdCmsClustID *XrdCmsClustID::AddID(const char *cID) -{ - XrdCmsClustID *cidObj; - const char *cHN; - char *clustID; - -// Massage the clusterid (it's in bi-compatible format) -// - if ((cHN = rindex(cID, ' ')) && *(cHN+1)) cID = cHN+1; - clustID = strdup(cID); - -// Lock ourselves -// - cidMtx.Lock(); - -// Allocate a new cluster ID object if we don't have one ready -// - if (!cidFree) cidFree = new XrdCmsClustID(); - -// Attempt to add this object to our cid table -// - if (!(cidObj = cidTab.Add(clustID, cidFree, 0, Hash_keep))) - {cidObj = cidFree; - cidObj->cidName = clustID; - cidFree = new XrdCmsClustID(); - } else free(clustID); - -// We can unlock now -// - cidMtx.UnLock(); - -// Return the entry -// - return cidObj; -} - -/******************************************************************************/ -/* A d d N o d e */ -/******************************************************************************/ - -bool XrdCmsClustID::AddNode(XrdCmsNode *nP, bool isMan) -{ - EPNAME("AddNode"); - XrdSysMutexHelper cidHelper(cidMtx); - int iNum, sNum; - -// For servers we only add the identification mask -// - if (!isMan) - {cidMask |= nP->Mask(); - DEBUG("srv " <Ident <<" cluster " <Name()); - return false; - } - -// Make sure the slot numbers match for this node -// - sNum = nP->ID(iNum); - if (npNum > 0 && ntSlot != sNum) - {char buff[256]; - sprintf(buff,"cluster slot mismatch: %d != %d; rejecting",sNum,ntSlot); - Say.Emsg("ClustID", cidName, buff, nP->Name()); - return false; - } - -// Add the entry to the table -// - ntSlot = sNum; - cidMask |= nP->Mask(); - nodeP[npNum++] = nP; - DEBUG("man " <Ident <<" cluster " <isMan | nP->isPeer)) - {cidMask &= ~(nP->Mask()); - DEBUG("srv " <Ident <<" cluster " <Ident <<" cluster " <. */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -class XrdLink; -class XrdCmsNode; - -#include "XrdCms/XrdCmsTypes.hh" - -class XrdCmsClustID -{ -public: - -static XrdCmsClustID *AddID(const char *cID); - - bool AddNode(XrdCmsNode *nP, bool isMan); - -inline bool Avail() {return npNum < altMax;} - - bool Exists(XrdLink *lp, const char *nid, int port); - -static XrdCmsClustID *Find(const char *cID); - -static SMask_t Mask(const char *cID); - -inline bool IsEmpty() {return npNum < 1;} - -inline bool IsSingle() {return npNum == 1;} - - XrdCmsNode *RemNode(XrdCmsNode *nP); - -inline int Slot() {return ntSlot;} - - XrdCmsClustID() : cidMask(0), cidName(0), ntSlot(-1), npNum(0) - {memset(nodeP, 0, sizeof(nodeP));} - - ~XrdCmsClustID() {if (cidName) free(cidName);} - -private: - -static const int altMax = 8; - - SMask_t cidMask; - char *cidName; - int ntSlot; - int npNum; - XrdCmsNode *nodeP[altMax]; -}; -#endif diff --git a/src/XrdCms/XrdCmsCluster.cc b/src/XrdCms/XrdCmsCluster.cc deleted file mode 100644 index 8efb4dd471c..00000000000 --- a/src/XrdCms/XrdCmsCluster.cc +++ /dev/null @@ -1,1984 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C m s C l u s t e r . c c */ -/* */ -/* (c) 2007 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include - -#include "XProtocol/YProtocol.hh" - -#include "Xrd/XrdJob.hh" -#include "Xrd/XrdLink.hh" -#include "Xrd/XrdScheduler.hh" - -#include "XrdCms/XrdCmsBaseFS.hh" -#include "XrdCms/XrdCmsBlackList.hh" -#include "XrdCms/XrdCmsCache.hh" -#include "XrdCms/XrdCmsConfig.hh" -#include "XrdCms/XrdCmsCluster.hh" -#include "XrdCms/XrdCmsClustID.hh" -#include "XrdCms/XrdCmsNode.hh" -#include "XrdCms/XrdCmsRole.hh" -#include "XrdCms/XrdCmsRRQ.hh" -#include "XrdCms/XrdCmsState.hh" -#include "XrdCms/XrdCmsSelect.hh" -#include "XrdCms/XrdCmsTrace.hh" -#include "XrdCms/XrdCmsTypes.hh" - -#include "XrdOuc/XrdOucPup.hh" - -#include "XrdSys/XrdSysPlatform.hh" -#include "XrdSys/XrdSysPthread.hh" -#include "XrdSys/XrdSysTimer.hh" - -using namespace XrdCms; - -/******************************************************************************/ -/* G l o b a l O b j e c t s */ -/******************************************************************************/ - - XrdCmsCluster XrdCms::Cluster; - -/******************************************************************************/ -/* L o c a l S t r u c t u r e s */ -/******************************************************************************/ - -class XrdCmsDrop : XrdJob -{ -public: - - void DoIt() {if (nodeP) - {nodeP->Delete(Cluster.STMutex); - delete this; - } else { - if (!Cluster.Drop(nodeEnt, nodeInst, this)) delete this; - } - } - - XrdCmsDrop(XrdCmsNode *nP) : XrdJob("delete node"), nodeP(nP), - nodeEnt(0), nodeInst(0) - {Sched->Schedule((XrdJob *)this);} - - XrdCmsDrop(int nid, int inst) : XrdJob("drop node"), nodeP(0), - nodeEnt(nid), nodeInst(inst) - {Sched->Schedule((XrdJob *)this, time(0)+Config.DRPDelay);} - - ~XrdCmsDrop() {} - -XrdCmsNode *nodeP; -int nodeEnt; -int nodeInst; -}; - -/******************************************************************************/ -/* C o n s t r u c t o r */ -/******************************************************************************/ - -XrdCmsCluster::XrdCmsCluster() -{ - memset((void *)NodeTab, 0, sizeof(NodeTab)); - memset((void *)AltMans, (int)' ', sizeof(AltMans)); - AltMend = AltMans; - AltMent = -1; - NodeCnt = 0; - STHi = -1; - SelWcnt = 0; - SelWtot = 0; - SelRcnt = 0; - SelRtot = 0; - SelTcnt = 0; - doReset = 0; - resetMask = 0; - peerHost = 0; - peerMask = ~peerHost; -} - -/******************************************************************************/ -/* A d d */ -/******************************************************************************/ - -XrdCmsNode *XrdCmsCluster::Add(XrdLink *lp, int port, int Status, int sport, - const char *theNID, const char *theIF) - -{ - EPNAME("Add") - const char *act = ""; - XrdCmsNode *nP = 0; - XrdCmsClustID *cidP = 0; - XrdSysMutexHelper STMHelper(STMutex); - int tmp, Slot, Free = -1, Bump1 = -1, Bump2 = -1, Bump3 = -1, aSet = 0; - bool Special = (Status & (CMS_isMan|CMS_isPeer)); - bool SpecAlt = (Special && !(Status & CMS_isSuper)); - bool Hidden = false; - -// Find available slot for this node. Here are the priorities: -// Slot = Reconnecting node -// Free = Available slot ( 1st in table) -// Bump1 = Disconnected server (last in table) -// Bump2 = Connected server (last in table) if new one is managr/peer -// Bump3 = Disconnected managr/peer ( 1st in table) if new one is managr/peer -// - for (Slot = 0; Slot < STMax; Slot++) - if (NodeTab[Slot]) - {if (NodeTab[Slot]->isNode(lp, theNID, port)) break; -/*Conn*/ if (NodeTab[Slot]->isConn) - {if (!NodeTab[Slot]->isPerm && Special) - Bump2 = Slot; // Last conn Server -/*Disc*/ } else { - if ( NodeTab[Slot]->isPerm) - {if (Bump3 < 0 && Special) Bump3 = Slot;}// 1st disc Man/Pr - else Bump1 = Slot; // Last disc Server - } - } else if (Free < 0) Free = Slot; // 1st free slot - -// Check if node is already logged in or is a relogin -// - if (Slot < STMax) - {if (NodeTab[Slot] && NodeTab[Slot]->isBound) - {Say.Emsg("Cluster", lp->ID, "already logged in."); - return 0; - } else { // Rehook node to previous unconnected entry - nP = NodeTab[Slot]; - nP->Link = lp; - nP->isOffline = 0; - nP->isBad &= ~XrdCmsNode::isSuspend; - nP->isConn = 1; - nP->Instance++; - nP->setName(lp, theIF, port); // Just in case it changed - act = "Reconnect "; - } - } - -// First see if this node may be an alternate -// - if (!nP && SpecAlt) - {if ((cidP = XrdCmsClustID::Find(theNID)) && !(cidP->IsEmpty())) - {if (!(nP = AddAlt(cidP, lp, port, Status, sport, theNID, theIF))) - return 0; - aSet = 1; Slot = nP->NodeID; - if (nP != NodeTab[Slot]) {Hidden = true; act = "Alternate ";} - } - } - -// Reuse an old ID if we must or redirect the incomming node -// - if (!nP) - {if (Free >= 0) Slot = Free; - else {if (Bump1 >= 0) Slot = Bump1; - else Slot = (Bump2 >= 0 ? Bump2 : Bump3); - if (Slot < 0) - {if (Status & CMS_isPeer) Say.Emsg("Cluster", "Add peer", lp->ID, - "failed; too many subscribers."); - else {sendAList(lp); - DEBUG(lp->ID <<" redirected; too many subscribers."); - } - return 0; - } - - if (Status & CMS_isMan) {setAltMan(Slot, lp, sport); aSet=1;} - if (NodeTab[Slot] && !(Status & CMS_isPeer)) - sendAList(NodeTab[Slot]->Link); - - DEBUG(lp->ID << " bumps " << NodeTab[Slot]->Ident <<" #" <Lock(true); - Remove("redirected", NodeTab[Slot], -1); - act = "Shoved "; - } - NodeTab[Slot] = nP = new XrdCmsNode(lp, theIF, theNID, port, 0, Slot); - if (!cidP) cidP = XrdCmsClustID::AddID(theNID); - if ((cidP->AddNode(nP, SpecAlt))) nP->cidP = cidP; - else {delete nP; NodeTab[Slot] = 0; return 0;} // OK to do delete! - } - -// Indicate whether this snode can be redirected -// - nP->isPerm = (Status & (CMS_isMan | CMS_isPeer)) ? 1 : 0; - -// Assign new server -// - if (!aSet && (Status & CMS_isSuper)) setAltMan(Slot, lp, sport); - if (Slot > STHi) STHi = Slot; - nP->isBound = 1; - nP->isConn = 1; - nP->isNoStage = 0 != (Status & CMS_noStage); - nP->isBad |= (Status & CMS_Suspend ? XrdCmsNode::isSuspend : 0); - nP->isMan = 0 != (Status & CMS_isMan); - nP->isPeer = 0 != (Status & CMS_isPeer); - nP->isBad |= XrdCmsNode::isDisabled; - nP->subsPort = sport; - -// If this is an actual non-hidden node, count it -// - if (!Hidden) - {NodeCnt++; - if (Config.SUPLevel - && (tmp = NodeCnt*Config.SUPLevel/100) > Config.SUPCount) - {Config.SUPCount=tmp; CmsState.Set(tmp);} - } else nP->isMan |= 0x02; - -// Compute new peer mask, as needed -// - if (nP->isPeer) peerHost |= nP->NodeMask; - else peerHost &= ~nP->NodeMask; - peerMask = ~peerHost; - -// Document login -// - if (QTRACE(Debug)) - {DEBUG(act <Ident <<" to cluster " <myNID <<" slot " - <Instance <<" (nodecnt=" <isBad & XrdCmsNode::isSuspend ? 0 : 1, - nP->isNoStage ? 0 : 1); - -// All done. Return the node locked. -// - nP->Lock(false); - return nP; -} - -/******************************************************************************/ -/* Private: A d d A l t */ -/******************************************************************************/ - -// Warning STMutex must be held by the caller! - -XrdCmsNode *XrdCmsCluster::AddAlt(XrdCmsClustID *cidP, XrdLink *lp, - int port, int Status, int sport, - const char *theNID, const char *theIF) - -{ - EPNAME("AddAlt") - XrdCmsNode *pP, *nP = 0; - int slot = cidP->Slot(); - -// Check if this node is already in the alternate table -// - if (cidP->Exists(lp, theNID, port)) - {Say.Emsg(epname, lp->ID, "already logged in."); - return 0; - } - -// Add this node if there is room -// - if (cidP->Avail()) - {nP = new XrdCmsNode(lp, theIF, theNID, port, 0, slot); - if (!(cidP->AddNode(nP, true))) {delete nP; nP = 0;} // OK to do delete! - } - -// Check if we were actually able to add this node -// - if (!nP) - {Say.Emsg(epname, "Add alternate manager", lp->ID, - "failed; too many subscribers."); - return 0; - } - -// Check if the existing lead dead and we can substiture this one -// - if ((pP = NodeTab[slot]) && !(pP->isBound)) - {setAltMan(nP->NodeID, nP->Link, sport); - Say.Emsg("AddAlt", nP->Ident, "replacing dropped", pP->Ident); - NodeTab[slot] = nP; - pP->DropJob = new XrdCmsDrop(pP); // Schedule deletion - } - -// Hook the node to the cluster table and return -// - nP->cidP = cidP; - return nP; -} - -/******************************************************************************/ -/* B l a c k L i s t */ -/******************************************************************************/ - -void XrdCmsCluster::BlackList(XrdOucTList *blP) -{ - static CmsDiscRequest discRequest = {{0, kYR_disc, 0, 0}}; - XrdCmsNode *nP; - const char *etxt = "blacklisted."; - int i, blRD = 0; - bool inBL; - -// Obtain a lock on the table -// - STMutex.Lock(); - -// Run through the table looking to put or out of the blacklist -// - for (i = 0; i <= STHi; i++) - {if ((nP = NodeTab[i])) - {inBL = (blP && (blRD = XrdCmsBlackList::Present(nP->Name(), blP))); - if ((!inBL && !(nP->isBad & XrdCmsNode::isBlisted)) - || ( inBL && (nP->isBad & XrdCmsNode::isBlisted))) continue; - nP->g2nLock(STMutex); // Downgrade to only node lock - if (inBL) - {nP->isBad |= XrdCmsNode::isBlisted | XrdCmsNode::isDoomed; - if (blRD < -1) - {if (kYR_Version > nP->myVersion) - etxt = "blacklisted; redirect unsupported."; - else etxt = "blacklisted with redirect."; - nP->isBad |= XrdCmsNode::isDoomed; - nP->Send((char *)&discRequest, sizeof(discRequest)); - } - Say.Emsg("Manager", nP->Name(), etxt); - } else { - nP->isBad &= ~(XrdCmsNode::isBlisted | XrdCmsNode::isDoomed); - Say.Emsg("Manager", nP->Name(), "removed from blacklist."); - } - nP->n2gLock(STMutex); - } - } - STMutex.UnLock(); -} - -/******************************************************************************/ -/* B r o a d c a s t */ -/******************************************************************************/ - -SMask_t XrdCmsCluster::Broadcast(SMask_t smask, const struct iovec *iod, - int iovcnt, int iotot) -{ - EPNAME("Broadcast") - int i; - XrdCmsNode *nP; - SMask_t bmask, unQueried(0); - -// Obtain a lock on the table and screen out peer nodes -// - STMutex.Lock(); - bmask = smask & peerMask; - -// Run through the table looking for nodes to send messages to. We don't need -// the node lock for this but we do need to up the reference count to keep the -// node pointer valid for the duration of the send() (may or may not block). -// - for (i = 0; i <= STHi; i++) - {if ((nP = NodeTab[i]) && nP->isNode(bmask)) - {if (nP->isOffline) unQueried |= nP->Mask(); - else {nP->g2Ref(STMutex); - if (nP->Send(iod, iovcnt, iotot) < 0) - {unQueried |= nP->Mask(); - DEBUG(nP->Ident <<" is unreachable"); - } - nP->Ref2g(STMutex); - } - } - } - STMutex.UnLock(); - return unQueried; -} - -/******************************************************************************/ - -SMask_t XrdCmsCluster::Broadcast(SMask_t smask, XrdCms::CmsRRHdr &Hdr, - char *Data, int Dlen) -{ - struct iovec ioV[3], *iovP = &ioV[1]; - unsigned short Temp; - int Blen; - -// Construct packed data for the character argument. If data is a string then -// Dlen must include the null byte if it is specified at all. -// - Blen = XrdOucPup::Pack(&iovP, Data, Temp, (Dlen ? strlen(Data)+1 : Dlen)); - Hdr.datalen = htons(static_cast(Blen)); - -// Complete the iovec and send off the data -// - ioV[0].iov_base = (char *)&Hdr; ioV[0].iov_len = sizeof(Hdr); - return Broadcast(smask, ioV, 3, Blen+sizeof(Hdr)); -} - -/******************************************************************************/ - -SMask_t XrdCmsCluster::Broadcast(SMask_t smask, XrdCms::CmsRRHdr &Hdr, - void *Data, int Dlen) -{ - struct iovec ioV[2] = {{(char *)&Hdr, sizeof(Hdr)}, - {(char *)Data, (size_t)Dlen}}; - -// Send of the data as eveything was constructed properly -// - Hdr.datalen = htons(static_cast(Dlen)); - return Broadcast(smask, ioV, 2, Dlen+sizeof(Hdr)); -} - -/******************************************************************************/ -/* B r o a d s e n d */ -/******************************************************************************/ - -int XrdCmsCluster::Broadsend(SMask_t Who, XrdCms::CmsRRHdr &Hdr, - void *Data, int Dlen) -{ - EPNAME("Broadsend"); - static int Start = 0; - XrdCmsNode *nP; - struct iovec ioV[2] = {{(char *)&Hdr, sizeof(Hdr)}, - {(char *)Data, (size_t)Dlen}}; - int i, Beg, Fin, ioTot = Dlen+sizeof(Hdr); - -// Send of the data as eveything was constructed properly -// - Hdr.datalen = htons(static_cast(Dlen)); - -// Obtain a lock on the table and get the starting and ending position. Note -// that the mechnism we use will necessarily skip newly added nodes. -// - STMutex.Lock(); - Beg = Start = (Start <= STHi ? Start+1 : 0); - Fin = STHi; - -// Run through the table looking for nodes to send messages to. We don't need -// the node lock for this but we do need to up the reference count to keep the -// node pointer valid for the duration of the send() (may or may not block). -// -do{for (i = Beg; i <= Fin; i++) - {if ((nP = NodeTab[i]) && nP->isNode(Who)) - {if (nP->isOffline) continue; - nP->g2Ref(STMutex); - if (nP->Send(ioV, 2, ioTot) >= 0) {nP->UnLock(); return 1;} - DEBUG(nP->Ident <<" is unreachable"); - nP->Ref2g(STMutex); - } - } - if (!Beg) break; - Fin = Beg-1; Beg = 0; - } while(1); - -// Did not send to anyone -// - STMutex.UnLock(); - return 0; -} - -/******************************************************************************/ -/* g e t M a s k */ -/******************************************************************************/ - -SMask_t XrdCmsCluster::getMask(const XrdNetAddr *addr) -{ - int i; - XrdCmsNode *nP; - SMask_t smask(0); - -// Obtain a lock on the table -// - STMutex.Lock(); - -// Run through the table looking for a node with matching IP address -// - for (i = 0; i <= STHi; i++) - if ((nP = NodeTab[i]) && nP->isNode(addr)) - {smask = nP->NodeMask; break;} - -// All done -// - STMutex.UnLock(); - return smask; -} - -/******************************************************************************/ - -SMask_t XrdCmsCluster::getMask(const char *Cid) -{ - return XrdCmsClustID::Mask(Cid); -} - -/******************************************************************************/ -/* L i s t */ -/******************************************************************************/ - -XrdCmsSelected *XrdCmsCluster::List(SMask_t mask, CmsLSOpts opts, bool &oksel) -{ - static const int iSize = XrdCmsSelected::IdentSize; - XrdCmsNode *nP; - XrdCmsSelected *sipp = 0, *sip; - XrdNetIF::ifType ifType = (XrdNetIF::ifType)(opts & LS_IFMASK); - XrdNetIF::ifType ifGet = ifType; - int i, destLen; - bool retName = (opts & LS_IDNT) != 0; - bool retAny = (opts & LS_ANY ) != 0; - bool retDest = retName || (opts & LS_IPO); - -// If only one wanted, the select appropriately -// - oksel = false; - STMutex.Lock(); - for (i = 0; i <= STHi; i++) - if ((nP=NodeTab[i]) && (nP->NodeMask & mask)) - {oksel = true; - if (retDest) - { if (nP->netIF.HasDest(ifType)) ifGet = ifType; - else if (!retAny) continue; - else {ifGet = (XrdNetIF::ifType)(ifType ^ XrdNetIF::PrivateIF); - if (!nP->netIF.HasDest(ifGet)) continue; - } - } - sip = new XrdCmsSelected(sipp); - if (retDest) destLen = nP->netIF.GetDest(sip->Ident, iSize, - ifGet, retName); - else if (nP->myNlen >= XrdCmsSelected::IdentSize) destLen = 0; - else {strcpy(sip->Ident, nP->myName); destLen = nP->myNlen;} - if (!destLen) {delete sip; continue;} - - sip->IdentLen = destLen; - sip->Mask = nP->NodeMask; - sip->Id = nP->NodeID; - sip->Port = nP->netIF.Port(); - sip->RefTotW = nP->RefTotW; - sip->RefTotR = nP->RefTotR; - sip->Shrin = nP->Shrin; - sip->Share = nP->Share; - sip->RoleID = nP->RoleID; - sip->Status = (nP->isOffline ? XrdCmsSelected::Offline : 0); - if (nP->isBad & (XrdCmsNode::isDisabled | XrdCmsNode::isBlisted)) - sip->Status |= XrdCmsSelected::Disable; - if (nP->isNoStage) sip->Status |= XrdCmsSelected::NoStage; - if (nP->isBad & XrdCmsNode::isSuspend) - sip->Status |= XrdCmsSelected::Suspend; - if (nP->isRW ) sip->Status |= XrdCmsSelected::isRW; - if (nP->isMan ) sip->Status |= XrdCmsSelected::isMangr; - sipp = sip; - } - STMutex.UnLock(); - -// Return result -// - return sipp; -} - -/******************************************************************************/ -/* L o c a t e */ -/******************************************************************************/ - -int XrdCmsCluster::Locate(XrdCmsSelect &Sel) -{ - EPNAME("Locate"); - XrdCmsPInfo pinfo; - SMask_t qfVec(0); - char *Path; - int retc = 0; - -// Check if this is a locate for all current servers -// - if (*Sel.Path.Val != '*') Path = Sel.Path.Val; - else {if (*(Sel.Path.Val+1) == '\0') - {Sel.Vec.hf = ~0LL; Sel.Vec.pf = Sel.Vec.wf = 0; - return 0; - } - Path = Sel.Path.Val+1; - } - -// Find out who serves this path -// - if (!Cache.Paths.Find(Path, pinfo) || !pinfo.rovec) - {Sel.Vec.hf = Sel.Vec.pf = Sel.Vec.wf = 0; - return -1; - } else Sel.Vec.wf = pinfo.rwvec; - -// Check if this was a non-lookup request -// - if (*Sel.Path.Val == '*') - {Sel.Vec.hf = pinfo.rovec; Sel.Vec.pf = 0; - Sel.Vec.wf = pinfo.rwvec; - return 0; - } - -// Complete the request info object if we have one -// - if (Sel.InfoP) - {Sel.InfoP->rwVec = pinfo.rwvec; - Sel.InfoP->isLU = 1; - } - -// If we are running a shared file system preform an optional restricted -// pre-selection and then do a standard selection. -// - if (baseFS.isDFS()) - {SMask_t amask, smask, pmask; - amask = pmask = pinfo.rovec; - smask = (Sel.Opts & XrdCmsSelect::Online ? 0 : pinfo.ssvec & amask); - Sel.Resp.DLen = 0; - if (!(retc = SelDFS(Sel, amask, pmask, smask, 1))) - return (Sel.Opts & XrdCmsSelect::Asap && Sel.InfoP - ? Cache.WT4File(Sel,Sel.Vec.hf) : Config.LUPDelay); - if (retc < 0) return -1; - return 0; - } - -// First check if we have seen this file before. If so, get nodes that have it. -// A Refresh request kills this because it's as if we hadn't seen it before. -// If the file was found but either a query is in progress or we have a server -// bounce; the client must wait. -// - if (Sel.Opts & XrdCmsSelect::Refresh - || !(retc = Cache.GetFile(Sel, pinfo.rovec))) - {Cache.AddFile(Sel, 0); - qfVec = pinfo.rovec; Sel.Vec.hf = 0; - } else qfVec = Sel.Vec.bf; - -// Compute the delay, if any -// - if ((!qfVec && retc >= 0) || (Sel.Vec.hf && Sel.InfoP)) retc = 0; - else if (!(retc = Cache.WT4File(Sel, Sel.Vec.hf))) retc = -2; - -// Check if we have to ask any nodes if they have the file -// - if (qfVec) - {CmsStateRequest QReq = {{Sel.Path.Hash, kYR_state, kYR_raw, 0}}; - if (Sel.Opts & XrdCmsSelect::Refresh) - QReq.Hdr.modifier |= CmsStateRequest::kYR_refresh; - TRACE(Files, "seeking " <= Config.RefTurn); - resetR = (SelRcnt >= Config.RefTurn); - resetWR = (loopmax && loopcnt >= loopmax && (resetW || resetR)); - if (doReset || resetWR) - {for (i = 0; i <= STHi; i++) - if ((nP = NodeTab[i]) - && (resetWR || (doReset && nP->isNode(resetMask))) ) - {if (resetW || doReset) nP->RefW=0; - if (resetR || doReset) nP->RefR=0; - nP->Shrem = nP->Share; - } - if (resetWR) - {if (resetW) {SelWtot += SelWcnt; SelWcnt = 0;} - if (resetR) {SelRtot += SelRcnt; SelRcnt = 0;} - loopcnt = 0; - } - if (doReset) {doReset = 0; resetMask = 0;} - } - STMutex.UnLock(); - } while(1); - return (void *)0; -} - -/******************************************************************************/ -/* R e m o v e */ -/******************************************************************************/ - -// Warning! The node object must be locked upon entry. The lock is released -// upon deletion of the object. - -void XrdCmsCluster::Remove(XrdCmsNode *theNode) -{ - theNode->DropJob = new XrdCmsDrop(theNode); -} - -// Warning! The node object must be locked upon entry. The lock is released -// prior to returning to the caller. This entry obtains the node -// table lock. When immed != 0 then the node is immediately dropped. -// When immed if < 0 then the caller already holds the STMutex and it -// is not released upon exit. - -void XrdCmsCluster::Remove(const char *reason, XrdCmsNode *theNode, int immed) -{ - EPNAME("Remove_Node") - struct theLocks - {XrdSysMutex *myMutex; - XrdCmsNode *myNode; - int myNID; - int myInst; - bool hasLK; - bool doDrop; - char myIdent[510]; - - theLocks(XrdSysMutex *mtx, XrdCmsNode *node, int immed) - : myMutex(mtx), myNode(node), hasLK(immed < 0), - doDrop(false) - {strlcpy(myIdent, node->Ident, sizeof(myIdent)); - myNID = node->ID(myInst); - if (!hasLK) - {myNode->UnLock(); - myMutex->Lock(); // Get global lock - myNode->Lock(true); - } - } - ~theLocks() - {if (myNode) - {if (doDrop) - {myNode->isBound = 0; - myNode->DropTime = 0; - myNode->DropJob = new XrdCmsDrop(myNode); - myNode->UnLock(); - } else myNode->UnLock(); - } - if (!hasLK) myMutex->UnLock(); - } - } LockHandler(&STMutex, theNode, immed); - - XrdCmsNode *altNode = 0; - int Inst, NodeID = theNode->ID(Inst); - -// The LockHandler makes sure that the proper locks are obtained in a deadlock -// free order. However, this may require that the node lock be released and -// then re-aquired. We check if we are still dealing with same node at entry. -// If not, issue message and high-tail it out. -// - if (LockHandler.myNID != NodeID || LockHandler.myInst != Inst) - {Say.Emsg("Manager", LockHandler.myIdent, "removal aborted."); - DEBUG(LockHandler.myIdent <<" node " <isOffline = 1; // STMutex is held here - -// If the node is connected we simply close the connection. This will cause -// the connection handler to re-initiate the node removal. This condition -// exists only if one node is being displaced by another node. The Disc() -// may take a long time, but it's done async by default on the WAN and sync -// on the LAN (local connections are fast enough and error-free for this). -// - if (theNode->isConn) - {theNode->Disc(reason, 0); - theNode->isGone = 1; // Disc() sets the isOffline flag - return; - } - -// If we are not the primary node, then get rid of this node post-haste -// - if (!(NodeTab[NodeID] == theNode)) - {const char *why = (theNode->isMan ? "dropped as alternate." - : "dropped and redirected."); - Say.Emsg("Remove_Node", theNode->Ident, why); - LockHandler.doDrop = true; - return; - } - - -// If the node is part of the cluster, do not count it anymore and -// indicate new state of this nodes if we are a reporting manager -// - if (theNode->isBound) - {theNode->isBound = 0; - NodeCnt--; - if (Config.asManager()) - CmsState.Update(XrdCmsState::Counts, - theNode->isBad & XrdCmsNode::isSuspend ? 0 : -1, - theNode->isNoStage ? 0 : -1); - } - -// If we have a working alternate, substitute it here and immediately drop -// the former primary. This allows the cache to remain warm. -// - if (theNode->isMan && theNode->cidP && !(theNode->cidP->IsSingle()) - && (altNode = theNode->cidP->RemNode(theNode))) - {if (altNode->isBound) NodeCnt++; - NodeTab[NodeID] = altNode; - if (Config.asManager()) - CmsState.Update(XrdCmsState::Counts, - altNode->isBad & XrdCmsNode::isSuspend ? 0 : 1, - altNode->isNoStage ? 0 : 1); - setAltMan(altNode->NodeID, altNode->Link, altNode->subsPort); - Say.Emsg("Manager",altNode->Ident,"replacing dropped",theNode->Ident); - LockHandler.doDrop = true; - return; - } - -// If this is an immediate drop request, do so now. Drop() will delete -// the node object, so remove the node lock and tell LockHandler that. -// - if (immed || !Config.DRPDelay || theNode->isBad & XrdCmsNode::isDoomed) - {theNode->UnLock(); - LockHandler.myNode = 0; - Drop(NodeID, Inst); - return; - } - -// If a drop job is already scheduled, update the instance field. Otherwise, -// Schedule a node drop at a future time. -// - theNode->DropTime = time(0)+Config.DRPDelay; - if (theNode->DropJob) theNode->DropJob->nodeInst = Inst; - else theNode->DropJob = new XrdCmsDrop(NodeID, Inst); - -// Document removal -// - if (reason) - Say.Emsg("Manager", theNode->Ident, "scheduled for removal;", reason); - else DEBUG(theNode->Ident <<" node " <(ifWant); - -// If there is nothing to select from, return failure -// - if (!pmask) return 0; - -// Obtain the network we need for the client -// - selR.needNet = XrdNetIF::Mask(nType); - -// Packed selection can never occur in this code path so we turn it off -// - selR.selPack = false; - -// If we are exporting a shared-everything system then the incomming mask -// may have more than one server indicated. So, we need to do a full select. -// This is forced when isMulti is true, indicating a choice may exist. Note -// that the node, if any, is returned unlocked but we have the global mutex. -// - if (isMulti || baseFS.isDFS()) - {STMutex.Lock(); - nP = (Config.sched_RR ? SelbyRef(pmask,selR) : SelbyLoad(pmask,selR)); - if (nP) hlen = nP->netIF.GetName(hbuff, port, nType) + 1; - else hlen = 0; - STMutex.UnLock(); - return hlen != 1; - } - -// In shared-nothing systems the incomming mask will only have a single node. -// Compute the a single node number that is contained in the mask. -// - do {if (!(tmask = pmask & smLow)) Snum += 8; - else {while((tmask = tmask>>1)) Snum++; break;} - } while((pmask = pmask >> 8)); - -// See if the node passes muster -// - STMutex.Lock(); - if ((nP = NodeTab[Snum])) - { if (nP->isBad) nP = 0; - else if (!Config.sched_RR && (nP->myLoad > Config.MaxLoad)) nP = 0; - else if (!(selR.needNet & nP->hasNet)) nP = 0; - if (nP) - {if (isrw) - if (nP->isNoStage || nP->DiskFree < nP->DiskMinF) nP = 0; - else {SelWcnt++; nP->RefTotW++; nP->RefW++;} - else {SelRcnt++; nP->RefTotR++; nP->RefR++;} - } - } - -// At this point either we have a node or we do not -// - if (nP) - {hlen = nP->netIF.GetName(hbuff, port, nType) + 1; - nP->RefR++; - STMutex.UnLock(); - return hlen != 1; - } - STMutex.UnLock(); - return 0; -} - -/******************************************************************************/ -/* S e l F a i l */ -/******************************************************************************/ - -int XrdCmsCluster::SelFail(XrdCmsSelect &Sel, int rc) -{ -// - const char *etext; - - switch(rc) - {case eExists: etext = "Unable to create new file; file already exists."; - break; - case eROfs: etext = "Unable to write file; r/o file already exists."; - break; - case eDups: etext = "Unable to write file; multiple files exist."; - break; - case eNoRep: etext = "Unable to replicate file; no new sites available."; - break; - case eNoSel: etext = (Sel.Vec.hf & Sel.nmask - ? "Unable to write file; eligible servers shunned." - : "Unable to write file; r/w exports not found."); - break; - default: etext = "Unable to access file; file does not exist."; - break; - }; - - Sel.Resp.DLen = strlcpy(Sel.Resp.Data, etext, sizeof(Sel.Resp.Data))+1; - return -1; -} - -/******************************************************************************/ -/* S p a c e */ -/******************************************************************************/ - -void XrdCmsCluster::Space(SpaceData &sData, SMask_t smask) -{ - XrdCmsNode *nP; - SMask_t bmask; - int i; - bool doAll = !baseFS.isDFS(); - -// Obtain a lock on the table and screen out peer nodes -// - STMutex.Lock(); - bmask = smask & peerMask; - -// Run through the table getting space information -// - for (i = 0; i <= STHi; i++) - if ((nP = NodeTab[i]) && nP->isNode(bmask) && !(nP->isOffline)) - {if (doAll || !sData.Total) - {sData.Total += nP->DiskTotal; - sData.TotFr += nP->DiskFree; - } - if (nP->isRW & XrdCmsNode::allowsSS) - {sData.sNum++; - if (sData.sFree < nP->DiskFree) - {sData.sFree = nP->DiskFree; sData.sUtil = nP->DiskUtil;} - } - if (nP->isRW & XrdCmsNode::allowsRW) - {sData.wNum++; - if (sData.wFree < nP->DiskFree) - {sData.wFree = nP->DiskFree; sData.wUtil = nP->DiskUtil; - sData.wMinF = nP->DiskMinF; - } - } - } - STMutex.UnLock(); -} - -/******************************************************************************/ -/* S t a t s */ -/******************************************************************************/ - -int XrdCmsCluster::Stats(char *bfr, int bln) -{ - static const char statfmt1[] = "" - "%s"; - int mlen; - -// Check if actual length wanted -// - if (!bfr) return sizeof(statfmt1) + 8; - -// Format the statistics (not much here for now) -// - mlen = snprintf(bfr, bln, statfmt1, Config.myRType); - - if ((bln -= mlen) <= 0) return 0; - return mlen; -} - -/******************************************************************************/ -/* S t a t t */ -/******************************************************************************/ - -int XrdCmsCluster::Statt(char *bfr, int bln) -{ - static const char statfmt0[] = ""; - static const char statfmt1[] = "" - "%s%lld%lld%lld" - "%d"; - static const char statfmt2[] = "" - "%s%s" - "%s%d%d%s"; - static const char statfmt3[] = "%d%d"; - static const char statfmt4[] = ""; - static const char statfmt5[] = - "%lld%lld%lld%lld" - "%lld%lld%lld%lld"; - - static int AddFrq = (Config.RepStats & XrdCmsConfig::RepStat_frq); - static int AddShr = (Config.RepStats & XrdCmsConfig::RepStat_shr) - && Config.asMetaMan(); - - XrdCmsRRQ::Info Frq; - XrdCmsSelected *sp; - long long SelRnum, SelWnum; - int mlen, tlen, n = 0; - char shrBuff[80], stat[6], *stp; - bool oksel; - - class spmngr { - public: XrdCmsSelected *sp; - - spmngr() {sp = 0;} - ~spmngr() {XrdCmsSelected *xsp; - while((xsp = sp)) {sp = sp->next; delete xsp;} - } - } mngrsp; - -// Check if actual length wanted -// - if (!bfr) - {n = sizeof(statfmt0) + - sizeof(statfmt1) + 12*3 + 3 + 3 + - (sizeof(statfmt2) + 10*2 + 256 + 16) * STMax + sizeof(statfmt4); - if (AddShr) n += sizeof(statfmt3) + 12; - if (AddFrq) n += sizeof(statfmt4) + (10*8); - return n; - } - -// Get the statistics -// - if (AddFrq) RRQ.Statistics(Frq); - mngrsp.sp = sp = List(FULLMASK, LS_NULL, oksel); - -// Count number of nodes we have -// - while(sp) {n++; sp = sp->next;} - sp = mngrsp.sp; - -// Gather totals from the running total and the current value -// - STMutex.Lock(); - SelRnum = SelRtot + SelRcnt; - SelWnum = SelWtot + SelWcnt; - STMutex.UnLock(); - -// Format the statistics -// - mlen = snprintf(bfr, bln, statfmt1, - Config.myRType, SelTcnt, SelRnum, SelWnum, n); - - if ((bln -= mlen) <= 0) return 0; - tlen = mlen; bfr += mlen; n = 0; *shrBuff = 0; - - while(sp && bln > 0) - {stp = stat; - if (sp->Status & XrdCmsSelected::Offline) *stp++ = 'o'; - else if (sp->Status & XrdCmsSelected::Suspend) *stp++ = 's'; - else if (sp->Status & XrdCmsSelected::Disable) *stp++ = 'd'; - else *stp++ = 'a'; - if (sp->Status & XrdCmsSelected::isRW) *stp++ = 'w'; - if (sp->Status & XrdCmsSelected::NoStage) *stp++ = 'n'; - *stp = 0; - if (AddShr) snprintf(shrBuff, sizeof(shrBuff), statfmt3, - (sp->Share ? sp->Share : 100), sp->Shrin); - mlen = snprintf(bfr, bln, statfmt2, n, sp->Ident, - XrdCmsRole::Type(static_cast(sp->RoleID)), - stat, sp->RefTotR, sp->RefTotW, shrBuff); - bfr += mlen; bln -= mlen; tlen += mlen; - sp = sp->next; n++; - } - - if (bln <= (int)sizeof(statfmt4)) return 0; - strcpy(bfr, statfmt4); mlen = sizeof(statfmt4) - 1; - bfr += mlen; bln -= mlen; tlen += mlen; - - if (AddFrq && bln > 0) - {mlen = snprintf(bfr, bln, statfmt5, Frq.Add2Q, Frq.PBack, Frq.Resp, - Frq.Multi, Frq.luFast, Frq.luSlow, Frq.rdFast, Frq.rdSlow); - bfr += mlen; bln -= mlen; tlen += mlen; - } - -// See if we overflowed. otherwise finish up -// - if (sp || bln < (int)sizeof(statfmt0)) return 0; - strcpy(bfr, statfmt0); - return tlen + sizeof(statfmt0) - 1; -} - -/******************************************************************************/ -/* P r i v a t e M e t h o d s */ -/******************************************************************************/ -/******************************************************************************/ -/* c a l c D e l a y */ -/******************************************************************************/ - -XrdCmsNode *XrdCmsCluster::calcDelay(XrdCmsSelector &selR) -{ - if (!selR.nPick) {selR.delay = 0; - selR.reason = (selR.xNoNet - ? "no eligible servers reachable for" - : "no eligible servers for"); - } - else if (selR.xFull) {selR.delay = Config.DiskWT; - selR.reason = "no eligible servers have space for"; - } - else if (selR.xOvld) {selR.delay = Config.MaxDelay; - selR.reason = "eligible servers overloaded for"; - } - else if (selR.xSusp) {selR.delay = Config.SUSDelay; - selR.reason = "eligible servers suspended for"; - } - else if (selR.xOff) {selR.delay = Config.SUPDelay; - selR.reason = "eligible servers offline for"; - } - else {selR.delay = Config.SUPDelay; - selR.reason = "server selection error for"; - } - return (XrdCmsNode *)0; -} - -/******************************************************************************/ -/* D r o p */ -/******************************************************************************/ - -// Warning: STMutex must be locked upon entry and the caller must release it -// if this method is called directily. Otherwise, the mutex will be -// obtained and released. Also, this method may only be called via -// Remove() either directly or via a defered job scheduled by that -// method. This method actually deletes the node object. - -int XrdCmsCluster::Drop(int sent, int sinst, XrdCmsDrop *djp) -{ - EPNAME("Drop_Node") - XrdCmsNode *nP; - char hname[512]; - -// If we are being called outside of a scheduled job, obtain the mutex -// - if (djp) STMutex.Lock(); - -// Make sure this node is the right one -// - if (!(nP = NodeTab[sent]) || nP->Inst() != sinst) - {if (nP && djp == nP->DropJob) {nP->DropJob = 0; nP->DropTime = 0;} - if (djp) STMutex.UnLock(); - DEBUG(sent <<'.' <DropTime) - {Sched->Schedule((XrdJob *)djp, nP->DropTime); - if (djp) STMutex.UnLock(); - return 1; - } - -// Save the node name (don't want to hold a lock across a message) -// - strlcpy(hname, nP->Ident, sizeof(hname)); - -// Cleanup status -// - NodeTab[sent] = 0; - nP->isOffline = 1; // STMutex is locked - nP->DropTime = 0; - nP->DropJob = 0; - nP->isBound = 0; - -// Remove node from the peer list (if it is one) -// - if (nP->isPeer) {peerHost &= nP->NodeMask; peerMask = ~peerHost;} - -// Remove node entry from the alternate list and readjust the end pointer. -// - if (nP->isMan) - {memset((void *)&AltMans[sent*AltSize], (int)' ', AltSize); - if (sent == AltMent) - {AltMent--; - while(AltMent >= 0 && NodeTab[AltMent] - && !NodeTab[AltMent]->isMan) AltMent--; - if (AltMent < 0) AltMend = AltMans; - else AltMend = AltMans + ((AltMent+1)*AltSize); - } - } - -// Readjust STHi -// - if (sent == STHi) while(STHi >= 0 && !NodeTab[STHi]) STHi--; - -// Invalidate any cached entries for this node -// - if (nP->NodeMask) Cache.Drop(nP->NodeMask, sent, STHi); - -// We can now delete the node object if we were called via a job as we are on -// a different thread. Direct calls require that we schedule the deletion as -// it may take a long time if there are oustanding references to this node. -// - if (djp) {STMutex.UnLock(); nP->Delete(STMutex);} - else nP->DropJob = new XrdCmsDrop(nP); - -// Document the drop -// - Say.Emsg("Drop_Node", hname, "dropped."); - return 0; -} - -/******************************************************************************/ -/* M u l t i p l e */ -/******************************************************************************/ - -int XrdCmsCluster::Multiple(SMask_t mVec) -{ - static const unsigned long long Left32 = 0xffffffff00000000LL; - static const unsigned long long Right32 = 0x00000000ffffffffLL; - static const unsigned long long Left16 = 0x00000000ffff0000LL; - static const unsigned long long Right16 = 0x000000000000ffffLL; - static const unsigned long long Left08 = 0x000000000000ff00LL; - static const unsigned long long Right08 = 0x00000000000000ffLL; - static const unsigned long long Left04 = 0x00000000000000f0LL; - static const unsigned long long Right04 = 0x000000000000000fLL; -// 0 1 2 3 4 5 6 7 8 9 A B C D E F - static const int isMult[16] = {0,0,0,1,0,1,1,1,0,1,1,1,1,1,1,1}; - - if (mVec & Left32) {if (mVec & Right32) return 1; - else mVec = mVec >> 32LL; - } - if (mVec & Left16) {if (mVec & Right16) return 1; - else mVec = mVec >> 16LL; - } - if (mVec & Left08) {if (mVec & Right08) return 1; - else mVec = mVec >> 8LL; - } - if (mVec & Left04) {if (mVec & Right04) return 1; - else mVec = mVec >> 4LL; - } - return isMult[mVec]; -} - -/******************************************************************************/ -/* m a x B i t s */ -/******************************************************************************/ - -bool XrdCmsCluster::maxBits(SMask_t mVec, int mbits) -{ - int count = 0; - -// Count bits. This is the fastest way assuming few bits are set -// - while(mVec) - {mVec &= (mVec - 1); - count++; - if (count >= mbits) return true; - } - -// Indicate we have not reached the maximum bits set -// - return false; -} - -/******************************************************************************/ -/* R e c o r d */ -/******************************************************************************/ - -void XrdCmsCluster::Record(char *path, const char *reason, bool force) -{ - EPNAME("Record") - static int msgcnt = 255; - static XrdSysMutex mcMutex; - int skipmsg; - - DEBUG(reason <g2nLock(STMutex); - Sel.Resp.DLen = nP->netIF.GetName(Sel.Resp.Data, Sel.Resp.Port, nType); - if (!Sel.Resp.DLen) {nP->UnLock(); return Unreachable(Sel, false);} - Sel.Resp.DLen++; Sel.smask = nP->NodeMask; - if (isalt || (Sel.Opts & XrdCmsSelect::Create) || Sel.iovN) - {if (isalt || (Sel.Opts & XrdCmsSelect::Create)) - {Sel.Opts |= (XrdCmsSelect::Pending | XrdCmsSelect::Advisory); - if (Sel.Opts & XrdCmsSelect::noBind) act = " handling "; - else Cache.AddFile(Sel, nP->NodeMask); - } - if (Sel.iovN && Sel.iovP) - {nP->Send(Sel.iovP, Sel.iovN); act = " staging ";} - else if (!act) act = " assigned "; - } else act = " serving "; - nP->UnLock(); - TRACE(Stage, Sel.Resp.Data <g2nLock(STMutex); - Sel.Resp.DLen = nP->netIF.GetName(Sel.Resp.Data,Sel.Resp.Port,nType); - if (!Sel.Resp.DLen) {nP->UnLock(); return Unreachable(Sel, false);} - Sel.Resp.DLen++; Sel.smask = nP->NodeMask; - if (Sel.iovN && Sel.iovP) nP->Send(Sel.iovP, Sel.iovN); - nP->UnLock(); - TRACE(Stage, "Peer " <RefTotW++; sP->RefW++;} \ - else {SelRcnt++; sP->RefTotR++; sP->RefR++;} \ - if (sPMulti && sP->Share && !sP->Shrem--) \ - {sP->RefW += sP->Shrip; sP->RefR += sP->Shrip; \ - sP->Shrem = sP->Share; sP->Shrin++; \ - } - -/******************************************************************************/ -/* S e l b y C o s t */ -/******************************************************************************/ - -// Cost selection is used only for peer node selection as peers do not -// report a load and handle their own scheduling. - -// Caller must have the STMutex locked. The returned node. if any, is unlocked. - -XrdCmsNode *XrdCmsCluster::SelbyCost(SMask_t mask, XrdCmsSelector &selR) -{ - XrdCmsNode *np, *sp = 0; - bool Multi = false; - -// Scan for a node (sp points to the selected one) -// - selR.Reset(); SelTcnt++; - for (int i = 0; i <= STHi; i++) - if ((np = NodeTab[i]) && (np->NodeMask & mask)) - {if (!(selR.needNet & np->hasNet)) {selR.xNoNet= true; continue;} - selR.nPick++; - if (np->isOffline) {selR.xOff = true; continue;} - if (np->isBad) {selR.xSusp = true; continue;} - if (selR.needSpace && np->isNoStage) {selR.xFull = true; continue;} - if (!sp) sp = np; - else{if (abs(sp->myCost - np->myCost) <= Config.P_fuzz) - { if (selR.selPack) - {if (sp->Inst() > np->Inst()) sp=np;} - else if (selR.needSpace) - {if (sp->RefW > (np->RefW+Config.DiskLinger)) - sp=np; - } - else if (sp->RefR > np->RefR) sp=np; - } - else if (sp->myCost > np->myCost) sp=np; - Multi = true; - } - } - -// Check for overloaded node and return result -// - if (!sp) return calcDelay(selR); - RefCount(sp, Multi, selR.needSpace); - return sp; -} - -/******************************************************************************/ -/* S e l b y L o a d */ -/******************************************************************************/ - -// Caller must have the STMutex locked. The returned node. if any, is unlocked. - -XrdCmsNode *XrdCmsCluster::SelbyLoad(SMask_t mask, XrdCmsSelector &selR) -{ - XrdCmsNode *np, *sp = 0; - bool Multi = false, reqSS = (selR.needSpace & XrdCmsNode::allowsSS) != 0; - -// Scan for a node (preset possible, suspended, overloaded, full, and dead) -// - selR.Reset(); SelTcnt++; - for (int i = 0; i <= STHi; i++) - if ((np = NodeTab[i]) && (np->NodeMask & mask)) - {if (!(selR.needNet & np->hasNet)) {selR.xNoNet= true; continue;} - selR.nPick++; - if (np->isOffline) {selR.xOff = true; continue;} - if (np->isBad) {selR.xSusp = true; continue;} - if (np->myLoad > Config.MaxLoad) {selR.xOvld = true; continue;} - if (selR.needSpace && (np->DiskFree < np->DiskMinF - || (reqSS && np->isNoStage))) - {selR.xFull = true; continue;} - if (!sp) sp = np; - else{if (selR.needSpace) - {if (abs(sp->myMass - np->myMass) <= Config.P_fuzz) - {if (selR.selPack) - {if (sp->Inst() > np->Inst()) sp=np;} - else - if (sp->RefW > (np->RefW+Config.DiskLinger)) sp=np; - } - else if (sp->myMass > np->myMass) sp=np; - } else { - if (abs(sp->myLoad - np->myLoad) <= Config.P_fuzz) - {if (selR.selPack) - {if (sp->Inst() > np->Inst()) sp=np;} - else if (sp->RefR > np->RefR) sp=np; - } - else if (sp->myLoad > np->myLoad) sp=np; - } - Multi = true; - } - } - -// Check for overloaded node and return result -// - if (!sp) return calcDelay(selR); - RefCount(sp, Multi, selR.needSpace); - return sp; -} - -/******************************************************************************/ -/* S e l b y R e f */ -/******************************************************************************/ - -// Caller must have the STMutex locked. The returned node. if any, is unlocked. - -XrdCmsNode *XrdCmsCluster::SelbyRef(SMask_t mask, XrdCmsSelector &selR) -{ - XrdCmsNode *np, *sp = 0; - bool Multi = false, reqSS = (selR.needSpace & XrdCmsNode::allowsSS) != 0; - -// Scan for a node (sp points to the selected one) -// - selR.Reset(); SelTcnt++; - for (int i = 0; i <= STHi; i++) - if ((np = NodeTab[i]) && (np->NodeMask & mask)) - {if (!(selR.needNet & np->hasNet)) {selR.xNoNet= true; continue;} - selR.nPick++; - if (np->isOffline) {selR.xOff = true; continue;} - if (np->isBad) {selR.xSusp = true; continue;} - if (selR.needSpace && (np->DiskFree < np->DiskMinF - || (reqSS && np->isNoStage))) - {selR.xFull = true; continue;} - if (!sp) sp = np; - else {Multi = true; - if (selR.selPack) {if (sp->Inst() > np->Inst())sp=np;} - else if (selR.needSpace) - {if (sp->RefW > (np->RefW+Config.DiskLinger)) sp=np;} - else if (sp->RefR > np->RefR) sp=np; - } - } - -// Check for overloaded node and return result -// - if (!sp) return calcDelay(selR); - RefCount(sp, Multi, selR.needSpace); - return sp; -} - -/******************************************************************************/ -/* S e l D F S */ -/******************************************************************************/ - -int XrdCmsCluster::SelDFS(XrdCmsSelect &Sel, SMask_t amask, - SMask_t &pmask, SMask_t &smask, int isRW) -{ - EPNAME("SelDFS"); - static const SMask_t allNodes(~0); - int oldOpts, rc; - -// The first task is to find out if the file exists somewhere. If we are doing -// local queries, then the answer will be immediate. Otherwise, forward it. -// - if ((Sel.Opts & XrdCmsSelect::Refresh) || !(rc = Cache.GetFile(Sel, amask))) - {if (!baseFS.Local()) - {CmsStateRequest QReq = {{Sel.Path.Hash, kYR_state, kYR_raw, 0}}; - TRACE(Files, "seeking " <= AltMend) - {AltNext = AltMans; - iov[1].iov_len = 0; - iov[2].iov_len = dlen = AltMend - AltMans; - } else { - iov[1].iov_base = (caddr_t)AltNext; - iov[1].iov_len = AltMend - AltNext; - iov[2].iov_len = AltNext - AltMans; - dlen = iov[1].iov_len + iov[2].iov_len; - } - -// Complete the request (account for trailing null character) -// - dlen++; - Req.Hdr.datalen = htons(static_cast(dlen+sizeof(Req.sLen))); - Req.sLen = htons(static_cast(dlen)); - -// Send the list of alternates (rotated once) -// - lp->Send(iov, 4, dlen+HdrSize); -} - -/******************************************************************************/ -/* s e t A l t M a n */ -/******************************************************************************/ - -// Single entry at a time, protected by STMutex! - -void XrdCmsCluster::setAltMan(int snum, XrdLink *lp, int port) -{ - XrdNetAddr altAddr = *(lp->NetAddr()); - char *ap = &AltMans[snum*AltSize]; - int i; - -// Preset the buffer and pre-screen the port number -// - if (!port || (port > 0x0000ffff)) port = Config.PortTCP; - memset(ap, int(' '), AltSize); - -// First tr to use the hostname:port which may be too large (unlikely). Else -// Insert the ip address of this node into the list of nodes. We made sure that -// the size of he buffer was big enough so no need to check for overflow. -// - altAddr.Port(port); - if (Config.DoHnTry) i = altAddr.Format(ap, AltSize, XrdNetAddr::fmtName); - else i = 0; - if (!i) i=altAddr.Format(ap,AltSize,XrdNetAddr::fmtAddr,XrdNetAddr::prefipv4); - ap[i] = ' '; - -// Compute new fence -// - if (ap >= AltMend) {AltMend = ap + AltSize; AltMent = snum;} -} - -/******************************************************************************/ -/* U n r e a c h a b l e */ -/******************************************************************************/ - -int XrdCmsCluster::Unreachable(XrdCmsSelect &Sel, bool none) -{ - XrdNetIF::ifType nType=(XrdNetIF::ifType)(Sel.Opts & XrdCmsSelect::ifWant); - const char *Amode = (Sel.Opts & XrdCmsSelect::Write ? "write" : "read"); - const char *Xmode = (Sel.Opts & XrdCmsSelect::Online ? "immediately " : ""); - - if (none) - {Sel.Resp.DLen = snprintf(Sel.Resp.Data, sizeof(Sel.Resp.Data)-1, - "No servers are reachable via %s network to %s%s the file.", - XrdNetIF::Name(nType), Xmode, Amode) + 1; - } else { - Sel.Resp.DLen = snprintf(Sel.Resp.Data, sizeof(Sel.Resp.Data)-1, - "Eligible server is unreachable via %s network to %s%s the file.", - XrdNetIF::Name(nType), Xmode, Amode) + 1; - } - - return -1; -} - -/******************************************************************************/ -/* U n u s e a b l e */ -/******************************************************************************/ - -int XrdCmsCluster::Unuseable(XrdCmsSelect &Sel) -{ - const char *Amode = (Sel.Opts & XrdCmsSelect::Write ? "write" : "read"); - const char *Xmode = (Sel.Opts & XrdCmsSelect::Online ? "immediately " : ""); - - Sel.Resp.DLen = snprintf(Sel.Resp.Data, sizeof(Sel.Resp.Data)-1, - "No servers are available to %s%s the file.",Xmode,Amode)+1; - return -1; -} diff --git a/src/XrdCms/XrdCmsCluster.hh b/src/XrdCms/XrdCmsCluster.hh deleted file mode 100644 index b3cd14323c1..00000000000 --- a/src/XrdCms/XrdCmsCluster.hh +++ /dev/null @@ -1,270 +0,0 @@ -#ifndef __CMS_CLUSTER__H -#define __CMS_CLUSTER__H -/******************************************************************************/ -/* */ -/* X r d C m s C l u s t e r . h h */ -/* */ -/* (c) 2007 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include - -#include "XrdCms/XrdCmsTypes.hh" -#include "XrdOuc/XrdOucTList.hh" -#include "XrdOuc/XrdOucEnum.hh" -#include "XrdSys/XrdSysPthread.hh" - -class XrdLink; -class XrdCmsDrop; -class XrdCmsNode; -class XrdCmsSelect; -class XrdCmsSelector; -class XrdNetAddr; - -namespace XrdCms -{ -struct CmsRRHdr; -} - -/******************************************************************************/ -/* O p t i o n F l a g s */ -/******************************************************************************/ - -namespace XrdCms -{ - -// Flags passed to Add() -// -static const int CMS_noStage = 1; -static const int CMS_Suspend = 2; -static const int CMS_Perm = 4; -static const int CMS_isMan = 8; -static const int CMS_Lost = 16; -static const int CMS_isPeer = 32; -static const int CMS_isProxy = 64; -static const int CMS_noSpace =128; -static const int CMS_isSuper =256; - -static const int CMS_isVers3 =0x01000000; - -static const int CMS_notServ =CMS_isMan|CMS_isPeer|CMS_isSuper; -static const int CMS_hasAlts =CMS_isMan|CMS_isPeer; - -// Class passed to Space() -// -class SpaceData -{ -public: - -long long Total; // Total space -long long TotFr; // Total space free -int wMinF; // Free space minimum to select wFree node -int wFree; // Free space for nodes providing r/w access (largest one) -int wNum; // Number of nodes providing r/w access -int wUtil; // Average utilization (largest one) -int sFree; // Free space for nodes providing staging (largest one) -int sNum; // Number of nodes providing staging -int sUtil; // Average utilization (largest one) - - SpaceData() : Total(0), TotFr(0),wMinF(0), - wFree(0), wNum(0), wUtil(0), - sFree(0), sNum(0), sUtil(0) {} - ~SpaceData() {} -}; -} - -/******************************************************************************/ -/* C l a s s X r d C m s C l u s t e r */ -/******************************************************************************/ - -// This a single-instance global class -// -class XrdCmsBaseFR; -class XrdCmsClustID; -class XrdCmsSelected; -class XrdOucTList; - -class XrdCmsCluster -{ -public: -friend class XrdCmsDrop; - -int NodeCnt; // Number of active nodes - -// Called to add a new node to the cluster. Status values are defined above. -// -XrdCmsNode *Add(XrdLink *lp, int dport, int Status, - int sport, const char *theNID, const char *theIF); - -// Put nodes in or remove from a blacklist -// -virtual void BlackList(XrdOucTList *blP); - -// Sends a message to all nodes matching smask (three forms for convenience) -// -SMask_t Broadcast(SMask_t, const struct iovec *, int, int tot=0); - -SMask_t Broadcast(SMask_t smask, XrdCms::CmsRRHdr &Hdr, - char *Data, int Dlen=0); - -SMask_t Broadcast(SMask_t smask, XrdCms::CmsRRHdr &Hdr, - void *Data, int Dlen); - -// Sends a message to a single node in a round-robbin fashion. -// -int Broadsend(SMask_t smask, XrdCms::CmsRRHdr &Hdr, - void *Data, int Dlen); - -// Returns the node mask matching the given IP address -// -SMask_t getMask(const XrdNetAddr *addr); - -// Returns the node mask matching the given cluster ID -// -SMask_t getMask(const char *Cid); - -// Extracts out node information. Opts are one or more of CmsLSOpts -// -enum CmsLSOpts {LS_NULL=0, LS_IPO=0x0100, LS_IDNT=0x0200, - LS_ANY =0x0400, LS_IFMASK = 0x0f}; - -XrdCmsSelected *List(SMask_t mask, CmsLSOpts opts, bool &oksel); - -// Returns the location of a file -// -int Locate(XrdCmsSelect &Sel); - -// Always run as a separate thread to monitor subscribed node performance -// -void *MonPerf(); - -// Alwats run as a separate thread to maintain the node reference count -// -void *MonRefs(); - -// Return total number of redirect references (sloppy as we don't lock it) -// -long long Refs() {return SelWcnt+SelWtot+SelRcnt+SelRtot;} - -// Called to remove a node from the cluster -// -void Remove(XrdCmsNode *theNode); - -void Remove(const char *reason, XrdCmsNode *theNode, int immed=0); - -// Called to reset the node reference counts for nodes matching smask -// -void ResetRef(SMask_t smask); - -// Called to select the best possible node to serve a file (two forms) -// -static const int RetryErr = -3; -int Select(XrdCmsSelect &Sel); - -int Select(SMask_t pmask, int &port, char *hbuff, int &hlen, - int isrw, int isMulti, int ifWant); - -// Manipulate the global selection lock -// -void SLock(bool dolock) - {if (dolock) STMutex.Lock(); - else STMutex.UnLock(); - } - -// Called to get cluster space (for managers and supervisors only) -// -void Space(XrdCms::SpaceData &sData, SMask_t smask); - -// Called to return statistics -// -int Stats(char *bfr, int bln); // Server -int Statt(char *bfr, int bln); // Manager - - XrdCmsCluster(); -virtual ~XrdCmsCluster() {} // This object should never be deleted - -private: -XrdCmsNode *AddAlt(XrdCmsClustID *cidP, XrdLink *lp, int port, int Status, - int sport, const char *theNID, const char *theIF); -XrdCmsNode *calcDelay(XrdCmsSelector &selR); -int Drop(int sent, int sinst, XrdCmsDrop *djp=0); -void Record(char *path, const char *reason, bool force=false); -bool maxBits(SMask_t mVec, int mbits); -int Multiple(SMask_t mVec); -enum {eExists, eDups, eROfs, eNoRep, eNoSel, eNoEnt}; // Passed to SelFail -int SelFail(XrdCmsSelect &Sel, int rc); -int SelNode(XrdCmsSelect &Sel, SMask_t pmask, SMask_t amask); -XrdCmsNode *SelbyCost(SMask_t, XrdCmsSelector &selR); -XrdCmsNode *SelbyLoad(SMask_t, XrdCmsSelector &selR); -XrdCmsNode *SelbyRef (SMask_t, XrdCmsSelector &selR); -int SelDFS(XrdCmsSelect &Sel, SMask_t amask, - SMask_t &pmask, SMask_t &smask, int isRW); -void sendAList(XrdLink *lp); -void setAltMan(int snum, XrdLink *lp, int port); -int Unreachable(XrdCmsSelect &Sel, bool none); -int Unuseable(XrdCmsSelect &Sel); - -// Number of :Port characters per entry was INET6_ADDRSTRLEN+10 -// -static const int AltSize = 254; // We may revert to IP address - -XrdSysMutex XXMutex; // Protects cluster summary state variables -XrdSysMutex STMutex; // Protects all node information variables -XrdCmsNode *NodeTab[STMax]; // Current set of nodes - -int STHi; // NodeTab high watermark -int doReset; // Must send reset event to Managers[resetMask] -long long SelWcnt; // Curr number of r/w selections (successful) -long long SelWtot; // Total number of r/w selections (successful) -long long SelRcnt; // Curr number of r/o selections (successful) -long long SelRtot; // Total number of r/o selections (successful) -long long SelTcnt; // Total number of all selections - -// The following is a list of IP:Port tokens that identify supervisor nodes. -// The information is sent via the try request to redirect nodes; as needed. -// The list is alays rotated by one entry each time it is sent. -// -char AltMans[STMax*AltSize]; // ||123.123.123.123:12345|| = 21 -char *AltMend; -int AltMent; - -// The foloowing three variables are protected by the STMutex -// -SMask_t resetMask; // Nodes to receive a reset event -SMask_t peerHost; // Nodes that are acting as peers -SMask_t peerMask; // Always ~peerHost -}; - -XRDOUC_ENUM_OPERATORS(XrdCmsCluster::CmsLSOpts) - -namespace XrdCms -{ -extern XrdCmsCluster Cluster; -} -#endif diff --git a/src/XrdCms/XrdCmsConfig.cc b/src/XrdCms/XrdCmsConfig.cc deleted file mode 100644 index 0ff3a87a06a..00000000000 --- a/src/XrdCms/XrdCmsConfig.cc +++ /dev/null @@ -1,3111 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d C m s C o n f i g . c c */ -/* */ -/* (c) 2011 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -/* - The methods in this file handle cmsd() initialization. -*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "XrdVersion.hh" -#include "Xrd/XrdScheduler.hh" -#include "Xrd/XrdSendQ.hh" - -#include "XrdCms/XrdCmsAdmin.hh" -#include "XrdCms/XrdCmsBaseFS.hh" -#include "XrdCms/XrdCmsBlackList.hh" -#include "XrdCms/XrdCmsCache.hh" -#include "XrdCms/XrdCmsCluster.hh" -#include "XrdCms/XrdCmsConfig.hh" -#include "XrdCms/XrdCmsManager.hh" -#include "XrdCms/XrdCmsMeter.hh" -#include "XrdCms/XrdCmsNode.hh" -#include "XrdCms/XrdCmsPrepare.hh" -#include "XrdCms/XrdCmsPrepArgs.hh" -#include "XrdCms/XrdCmsProtocol.hh" -#include "XrdCms/XrdCmsRole.hh" -#include "XrdCms/XrdCmsRRQ.hh" -#include "XrdCms/XrdCmsSecurity.hh" -#include "XrdCms/XrdCmsState.hh" -#include "XrdCms/XrdCmsSupervisor.hh" -#include "XrdCms/XrdCmsTrace.hh" -#include "XrdCms/XrdCmsUtils.hh" - -#include "XrdNet/XrdNetOpts.hh" -#include "XrdNet/XrdNetUtils.hh" -#include "XrdNet/XrdNetSecurity.hh" -#include "XrdNet/XrdNetSocket.hh" - -#include "XrdOss/XrdOss.hh" - -#include "XrdOuc/XrdOuca2x.hh" -#include "XrdOuc/XrdOucEnv.hh" -#include "XrdOuc/XrdOucExport.hh" -#include "XrdOuc/XrdOucN2NLoader.hh" -#include "XrdOuc/XrdOucProg.hh" -#include "XrdOuc/XrdOucStream.hh" -#include "XrdOuc/XrdOucUtils.hh" - -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdSys/XrdSysPlatform.hh" -#include "XrdSys/XrdSysPthread.hh" -#include "XrdSys/XrdSysTimer.hh" - -using namespace XrdCms; - -/******************************************************************************/ -/* G l o b a l O b j e c t s */ -/******************************************************************************/ - -namespace XrdCms -{ - XrdOucEnv theEnv; - - XrdCmsAdmin Admin; - - XrdCmsBaseFS baseFS(&XrdCmsNode::do_StateDFS); - - XrdCmsConfig Config; - - XrdSysError Say(0, ""); - - XrdOucTrace Trace(&Say); - - XrdScheduler *Sched = 0; -}; - -/******************************************************************************/ -/* S e c u r i t y S y m b o l T i e - I n */ -/******************************************************************************/ - -// The following is a bit of a kludge. The client side will use the xrootd -// security infrastructure if it exists. This is tipped off by the presence -// of the following symbol being non-zero. On the server side, we have no -// such symbol and need to provide one initialized to zero. -// - XrdSecProtocol *(*XrdXrootdSecGetProtocol) - (const char *hostname, - const struct sockaddr &netaddr, - const XrdSecParameters &parms, - XrdOucErrInfo *einfo)=0; - -/******************************************************************************/ -/* E x t e r n a l T h r e a d I n t e r f a c e s */ -/******************************************************************************/ - -void *XrdCmsStartMonPerf(void *carg) { return Cluster.MonPerf(); } - -void *XrdCmsStartMonRefs(void *carg) { return Cluster.MonRefs(); } - -void *XrdCmsStartMonStat(void *carg) { return CmsState.Monitor(); } - -void *XrdCmsStartAdmin(void *carg) - {return XrdCms::Admin.Start((XrdNetSocket *)carg); - } - -void *XrdCmsStartAnote(void *carg) - {XrdCmsAdmin Anote; - return Anote.Notes((XrdNetSocket *)carg); - } - -void *XrdCmsStartPreparing(void *carg) - {XrdCmsPrepArgs::Process(); - return (void *)0; - } - -void *XrdCmsStartSupervising(void *carg) - {XrdCmsSupervisor::Start(); - return (void *)0; - } - -/******************************************************************************/ -/* P i n g C l o c k H a n d l e r */ -/******************************************************************************/ - -namespace XrdCms -{ - -class PingClock : XrdJob -{ -public: - - void DoIt() {Config.PingTick++; - Sched->Schedule((XrdJob *)this,time(0)+Config.AskPing); - } - -static void Start() {static PingClock selfie;} - - PingClock() : XrdJob(".ping clock") {DoIt();} - ~PingClock() {} -private: -}; -}; - -/******************************************************************************/ -/* d e f i n e s */ -/******************************************************************************/ - -#define TS_String(x,m) if (!strcmp(x,var)) {free(m); m = strdup(val); return 0;} - -#define TS_Xeq(x,m) if (!strcmp(x,var)) return m(eDest, CFile); -#define TS_Xer(x,m,v) if (!strcmp(x,var)) return m(eDest, CFile, v); - -#define TS_Set(x,v) if (!strcmp(x,var)) {v=1; CFile.Echo(); return 0;} - -#define TS_unSet(x,v) if (!strcmp(x,var)) {v=0; CFile.Echo(); return 0;} - -/******************************************************************************/ -/* C o n f i g u r e 1 */ -/******************************************************************************/ - -int XrdCmsConfig::Configure1(int argc, char **argv, char *cfn) -{ -/* - Function: Establish phase 1 configuration at start up time. - - Input: argc - argument count - argv - argument vector - cfn - optional configuration file name - - Output: 0 upon success or !0 otherwise. -*/ - int NoGo = 0, immed = 0; - char c, buff[512]; - extern int opterr, optopt; - -// Prohibit this program from executing as superuser -// - if (geteuid() == 0) - {Say.Emsg("Config", "Security reasons prohibit cmsd running as " - "superuser; cmsd is terminating."); - _exit(8); - } - -// Process the options -// - opterr = 0; optind = 1; - if (argc > 1 && '-' == *argv[1]) - while ((c=getopt(argc,argv,"iw")) && ((unsigned char)c != 0xff)) - { switch(c) - { - case 'i': immed = 1; - break; - case 'w': immed = -1; // Backward compatability only - break; - default: buff[0] = '-'; buff[1] = optopt; buff[2] = '\0'; - Say.Say("Config warning: unrecognized option, ",buff,", ignored."); - } - } - -// Accept a single parameter defining the overiding major role -// - if (optind < argc) - { if (!strcmp(argv[optind], "manager")) isManager = 1; - else if (!strcmp(argv[optind], "server" )) isServer = 1; - else if (!strcmp(argv[optind], "super" )) isServer = isManager = 1; - else Say.Say("Config warning: unrecognized parameter, ", - argv[optind],", ignored."); - } - -// Bail if no configuration file specified -// - inArgv = argv; inArgc = argc; - if ((!(ConfigFN = cfn) && !(ConfigFN = getenv("XrdCmsCONFIGFN"))) - || !*ConfigFN) - {Say.Emsg("Config", "Required config file not specified."); - Usage(1); - } - -// Establish my instance name -// - sprintf(buff, "%s@%s", XrdOucUtils::InstName(myInsName), myName); - myInstance = strdup(buff); - -// This is somewhat poor but we need to establish the default non-blocking -// message queue limit for the cms (this being 30) which can be overriden. -// - XrdSendQ::SetQM(30); - -// Print herald -// - Say.Say("++++++ ", myInstance, " phase 1 initialization started."); - -// If we don't know our role yet then we must find out before processing the -// config file. This means a double scan, sigh. -// - if (!(isManager || isServer)) - if (!(NoGo |= ConfigProc(1)) && !(isManager || isServer)) - {Say.Say("Config warning: role not specified; manager role assumed."); - isManager = -1; - } - -// Process the configuration file -// - if (!NoGo) NoGo |= ConfigProc(); - -// Override the trace option -// - if (getenv("XRDDEBUG")) Trace.What = TRACE_ALL; - -// Override the wait/nowait from the command line -// - if (immed) doWait = (immed > 0 ? 0 : 1); - -// Determine the role -// - if (isManager < 0) isManager = 1; - if (isPeer < 0) isPeer = 1; - if (isProxy < 0) isProxy = 1; - if (isServer < 0) isServer = 1; - -// Create a text description of our role for use in messages -// - if (!myRole) - {XrdCmsRole::RoleID rid = XrdCmsRole::noRole; - if (isMeta) rid = XrdCmsRole::MetaManager; - else if (isPeer) rid = XrdCmsRole::Peer; - else if (isProxy) - {if (isManager) rid = (isServer ? XrdCmsRole::ProxySuper - : XrdCmsRole::ProxyManager); - else rid = XrdCmsRole::ProxyServer; - } - else if (isManager) - {if (isManager) rid = (isServer ? XrdCmsRole::Supervisor - : XrdCmsRole::Manager); - } - else rid = XrdCmsRole::Server; - strcpy(myRType, XrdCmsRole::Type(rid)); - myRole = strdup(XrdCmsRole::Name(rid)); - myRoleID = static_cast(rid); - } - -// Export the role IN basic form and expanded form -// - XrdOucEnv::Export("XRDROLE", myRole); - XrdOucEnv::Export("XRDROLETYPE", myRType); - -// For managers, make sure that we have a well designated port. -// For servers or supervisors, force an ephemeral port to be used. -// - if (!NoGo) - {if ((isManager && !isServer) || isPeer) - {if (PortTCP <= 0) - {Say.Emsg("Config","port for this", myRole, "not specified."); - NoGo = 1; - } - } - else if ((isManager && isServer)) PortTCP = PortSUP; - else PortTCP = 0; - } - -// If we are configured in proxy mode then we are running a shared filesystem -// - if (isProxy) baseFS.Init(XrdCmsBaseFS::DFSys | XrdCmsBaseFS::Immed | - (baseFS.Local() ? XrdCmsBaseFS::Cntrl : 0), 0, 0); - -// Determine how we ended and return status -// - sprintf(buff, " phase 1 %s initialization %s.", myRole, - (NoGo ? "failed" : "completed")); - Say.Say("------ ", myInstance, buff); - return NoGo; -} - -/******************************************************************************/ -/* C o n f i g u r e 2 */ -/******************************************************************************/ - -int XrdCmsConfig::Configure2() -{ -/* - Function: Establish phase 2 configuration at start up time. - - Input: None. - - Output: 0 upon success or !0 otherwise. -*/ - int Who, NoGo = 0; - char *p, buff[512]; - std::string envData; - -// Print herald -// - sprintf(buff, " phase 2 %s initialization started.", myRole); - Say.Say("++++++ ", myInstance, buff); - -// Fix up the QryMinum (we hard code 64 as the max) and P_gshr values. -// The QryMinum only applies to a metamanager and is set as 1 minus the min. -// - if (!isMeta) QryMinum = 0; - else if (QryMinum < 2) QryMinum = 0; - else if (QryMinum > 64) QryMinum = 64; - if (P_gshr < 0) P_gshr = 0; - else if (P_gshr > 100) P_gshr = 100; - -// Determine who we are. If we are a manager or supervisor start the file -// location cache scrubber. -// - if (QryDelay < 0) QryDelay = LUPDelay; - if (isManager) - NoGo = !Cache.Init(cachelife,LUPDelay,QryDelay,baseFS.isDFS(),emptylife); - -// Issue warning if the adminpath resides in /tmp -// - if (!strncmp(AdminPath, "/tmp/", 5)) - Say.Say("Config warning: adminpath resides in /tmp and may be unstable!"); - - -// Establish the path to be used for admin functions -// - p = XrdOucUtils::genPath(AdminPath,XrdOucUtils::InstName(myInsName,0),".olb"); - free(AdminPath); - AdminPath = p; - -// Setup the admin path (used in all roles) -// - if (!NoGo) NoGo = !(AdminSock = XrdNetSocket::Create(&Say, AdminPath, - (isManager|isPeer ? "olbd.nimda":"olbd.admin"),AdminMode)); - -// Develop a stable unique identifier for this cmsd independent of the port -// - if (!NoGo) - {if (!(mySID = setupSid())) NoGo = 1; - else {if (QTRACE(Debug)) - Say.Say("Config ", "Global System Identification: ", mySID); - if (Config.mySite) - {envData += "site="; - envData += mySite; - } - } - } - -// Create envCGI string for logins -// - envCGI = (envData.length() > 0 ? strdup(envData.c_str()) : 0); - -// If we need a name library, load it now -// - if ((LocalRoot || RemotRoot || N2N_Lib) && ConfigN2N()) NoGo = 1; - -// Configure the OSS, the base filesystem, and initialize the prep queue -// - if (!NoGo) NoGo = ConfigOSS(); - if (!NoGo) baseFS.Start(); - if (!NoGo) PrepQ.Init(); - -// Setup manager or server, as needed -// - if (!NoGo && isManager) NoGo = setupManager(); - if (!NoGo && (isServer || ManList)) NoGo = setupServer(); - -// If we are a solo peer then we have no servers and a lot of space and -// connections don't matter. Only one connection matters for a meta-manager. -// Servers, supervisors, and managers who have a meta manager must wait for -// for the local data server to connect so port mapping occurs. Otherwise, -// we indicate that it doesn't matter as the local server won't connect. -// - if (isPeer && isSolo) - {SUPCount = SUPLevel = 0; Meter.setVirtual(XrdCmsMeter::peerFS);} - else if (isManager) - {Meter.setVirtual(XrdCmsMeter::manFS); - if (isMeta) {SUPCount = 1; SUPLevel = 0;} - if (!ManList) CmsState.Update(XrdCmsState::FrontEnd, 1); - } - if (isManager) Who = (isServer ? -1 : 1); - else Who = 0; - CmsState.Set(SUPCount, Who, AdminPath); - -// Create the pid file -// - if (!NoGo) NoGo |= PidFile(); - -// All done, check for success or failure -// - sprintf(buff, " phase 2 %s initialization %s.", myRole, - (NoGo ? "failed" : "completed")); - Say.Say("------ ", myInstance, buff); - -// The remainder of the configuration needs to be run in a separate thread -// - if (!NoGo) Sched->Schedule((XrdJob *)this); - -// All done -// - return NoGo; -} - -/******************************************************************************/ -/* C o n f i g X e q */ -/******************************************************************************/ - -int XrdCmsConfig::ConfigXeq(char *var, XrdOucStream &CFile, XrdSysError *eDest) -{ - int dynamic; - - // Determine whether is is dynamic or not - // - if (eDest) dynamic = 1; - else {dynamic = 0; eDest = &Say;} - - // Process items - // - TS_Xeq("delay", xdelay); // Manager, dynamic - TS_Xeq("fxhold", xfxhld); // Manager, dynamic - TS_Xeq("ping", xping); // Manager, dynamic - TS_Xeq("sched", xsched); // Any, dynamic - TS_Xeq("space", xspace); // Any, dynamic - TS_Xeq("trace", xtrace); // Any, dynamic - - if (!dynamic) - { - TS_Xeq("adminpath", xapath); // Any, non-dynamic - TS_Xeq("allow", xallow); // Manager, non-dynamic - TS_Xeq("altds", xaltds); // Server, non-dynamic - TS_Xeq("blacklist", xblk); // Manager, non-dynamic - TS_Xeq("cidtag", xcid); // Any, non-dynamic - TS_Xeq("defaults", xdefs); // Server, non-dynamic - TS_Xeq("dfs", xdfs); // Any, non-dynamic - TS_Xeq("export", xexpo); // Any, non-dynamic - TS_Xeq("fsxeq", xfsxq); // Server, non-dynamic - TS_Xeq("localroot", xlclrt); // Any, non-dynamic - TS_Xeq("manager", xmang); // Server, non-dynamic - TS_Xeq("namelib", xnml); // Server, non-dynamic - TS_Xeq("vnid", xvnid); // Server, non-dynamic - TS_Xeq("nbsendq", xnbsq); // Any non-dynamic - TS_Xeq("osslib", xolib); // Any, non-dynamic - TS_Xeq("perf", xperf); // Server, non-dynamic - TS_Xeq("pidpath", xpidf); // Any, non-dynamic - TS_Xeq("prep", xprep); // Any, non-dynamic - TS_Xeq("prepmsg", xprepm); // Any, non-dynamic - TS_Xeq("remoteroot", xrmtrt); // Any, non-dynamic - TS_Xeq("repstats", xreps); // Any, non-dynamic - TS_Xeq("role", xrole); // Server, non-dynamic - TS_Xeq("seclib", xsecl); // Server, non-dynamic - TS_Xeq("subcluster", xsubc); // Manager, non-dynamic - TS_Xeq("superport", xsupp); // Super, non-dynamic - TS_Set("wait", doWait); // Server, non-dynamic (backward compat) - TS_unSet("nowait", doWait); // Server, non-dynamic - TS_Xer("whitelist", xblk,true);//Manager, non-dynamic - } - - // The following are client directives that we will ignore - // - if (!strcmp(var, "conwait") - || !strcmp(var, "request")) return 0; - - // No match found, complain. - // - eDest->Say("Config warning: ignoring unknown directive '", var, "'."); - CFile.Echo(); - return 0; -} - -/******************************************************************************/ -/* D o I t */ -/******************************************************************************/ - -void XrdCmsConfig::DoIt() -{ - XrdSysSemaphore SyncUp(0); - pthread_t tid; - time_t eTime = time(0); - int wTime; - -// Set doWait correctly. We only wait if we have to provide a data path. This -// include server, supervisors, and managers who have a meta-manager, only. -// Why? Because we never get a primary login if we are a mere manager. -// - if (isManager && !isServer && !ManList) doWait = 0; - else if (isServer && adsMon) doWait = 1; - -// Start the notification thread if we need to -// - if (AnoteSock) - if (XrdSysThread::Run(&tid, XrdCmsStartAnote, (void *)AnoteSock, - 0, "Notification handler")) - Say.Emsg("cmsd", errno, "start notification handler"); - -// Start the prepare handler -// - if (XrdSysThread::Run(&tid,XrdCmsStartPreparing, - (void *)0, 0, "Prep handler")) - Say.Emsg("cmsd", errno, "start prep handler"); - -// Start the supervisor subsystem -// - if (XrdCmsSupervisor::superOK) - {if (XrdSysThread::Run(&tid,XrdCmsStartSupervising, - (void *)0, 0, "supervisor")) - {Say.Emsg("cmsd", errno, "start", myRole); - return; - } - } - -// Start the ping clock if we are a manager of any kind -// - if (isManager) PingClock::Start(); - -// Start the admin thread if we need to, we will not continue until told -// to do so by the admin interface. -// - if (AdminSock) - {XrdCmsAdmin::setSync(&SyncUp); - if (XrdSysThread::Run(&tid, XrdCmsStartAdmin, (void *)AdminSock, - 0, "Admin traffic")) - Say.Emsg("cmsd", errno, "start admin handler"); - SyncUp.Wait(); - } - -// Start the manager subsystem. -// - if (isManager || isServer || isPeer) XrdCmsManager::Start(ManList); - -// Start state monitoring thread -// - if (XrdSysThread::Run(&tid, XrdCmsStartMonStat, (void *)0, - 0, "State monitor")) - {Say.Emsg("Config", errno, "create state monitor thread"); - return; - } - -// If we are a manager then we must do a service enable after a service delay -// - if ((isManager || isPeer) && SRVDelay) - {wTime = SRVDelay - static_cast((time(0) - eTime)); - if (wTime > 0) XrdSysTimer::Wait(wTime*1000); - } - -// All done -// - if (!SUPCount) CmsState.Update(XrdCmsState::Counts, 0, 0); - CmsState.Enable(); - Say.Emsg("Config", myRole, "service enabled."); -} - -/******************************************************************************/ -/* G e n L o c a l P a t h */ -/******************************************************************************/ - -/* GenLocalPath() generates the path that a file will have in the local file - system. The decision is made based on the user-given path (typically what - the user thinks is the local file system path). The output buffer where the - new path is placed must be at least XrdCmsMAX_PATH_LEN bytes long. -*/ -int XrdCmsConfig::GenLocalPath(const char *oldp, char *newp) -{ - if (lcl_N2N) return -(lcl_N2N->lfn2pfn(oldp, newp, XrdCmsMAX_PATH_LEN)); - if (strlen(oldp) >= XrdCmsMAX_PATH_LEN) return -ENAMETOOLONG; - strcpy(newp, oldp); - return 0; -} - -/******************************************************************************/ -/* P r i v a t e F u n c t i o n s */ -/******************************************************************************/ -/******************************************************************************/ -/* C o n f i g D e f a u l t s */ -/******************************************************************************/ - -void XrdCmsConfig::ConfigDefaults(void) -{ - static XrdVERSIONINFODEF(myVer, cmsd, XrdVNUMBER, XrdVERSION); - int myTZ, isEast = 0; - -// Preset all variables with common defaults -// - myName = (char *)"localhost"; // Correctly set in Configure() - myDomain = 0; - LUPDelay = 5; - QryDelay =-1; - QryMinum = 0; - LUPHold = 178; - DELDelay = 960; // 15 minutes - DRPDelay = 10*60; - PSDelay = 0; - RWDelay = 2; - SRVDelay = 90; - SUPCount = 1; - SUPLevel = 80; - SUPDelay = 15; - SUSDelay = 30; - MaxLoad = 0x7fffffff; - MsgTTL = 7; - PortTCP = 0; - PortSUP = 0; - P_cpu = 0; - P_fuzz = 20; - P_gsdf = 0; - P_gshr = 0; - P_io = 0; - P_load = 0; - P_mem = 0; - P_pag = 0; - AskPerf = 10; // Every 10 pings - AskPing = 60; // Every 1 minute - PingTick = 0; - DoMWChk = 1; - DoHnTry = 1; - MaxDelay = -1; - LogPerf = 10; // Every 10 usage requests - DiskMin = 10240; // 10GB*1024 (Min partition space) in MB - DiskHWM = 11264; // 11GB*1024 (High Water Mark SUO) in MB - DiskMinP = 2; - DiskHWMP = 5; - DiskAsk = 12; // 15 Seconds between space calibrations. - DiskWT = 0; // Do not defer when out of space - DiskSS = 0; // Not a staging server - DiskOK = 0; // Does not have any disk - myPaths = (char *)""; // Default is 'r /' - ConfigFN = 0; - sched_RR = sched_Pack = sched_Level = 0; sched_Force = 1; - isManager= 0; - isMeta = 0; - isPeer = 0; - isSolo = 0; - isProxy = 0; - isServer = 0; - VNID_Lib = 0; - VNID_Parms=0; - N2N_Lib = 0; - N2N_Parms= 0; - lcl_N2N = 0; - xeq_N2N = 0; - LocalRoot= 0; - RemotRoot= 0; - myInsName= 0; - RepStats = 0; - myRole =0; - myRType[0]=0; - myRoleID = XrdCmsRole::noRole; - ManList =0; - NanList =0; - SanList =0; - myVNID = 0; - mySID = 0; - mySite = 0; - envCGI = 0; - cidTag = 0; - ifList =0; - perfint = 3*60; - perfpgm = 0; - AdminPath= strdup("/tmp/"); - AdminMode= 0700; - AdminSock= 0; - AnoteSock= 0; - RedirSock= 0; - pidPath = strdup("/tmp"); - Police = 0; - cachelife= 8*60*60; - emptylife= 0; - pendplife= 60*60*24*7; - DiskLinger=0; - ProgCH = 0; - ProgMD = 0; - ProgMV = 0; - ProgRD = 0; - ProgRM = 0; - doWait = 1; - RefReset = 60*60; - RefTurn = 3*STMax*(DiskLinger+1); - DirFlags = 0; - blkList = 0; - blkChk = 0; - SecLib = 0; - ossLib = 0; - ossParms = 0; - ossFS = 0; - myVInfo = &myVer; - adsPort = 0; - adsMon = 0; - adsProt = 0; - nbSQ = 1; - -// Compute the time zone we are in -// - myTZ = XrdSysTimer::TimeZone(); - if (myTZ <= 0) {isEast = 0x10; myTZ = -myTZ;} - if (myTZ > 12) myTZ = 12; - TimeZone = (myTZ | isEast); -} - -/******************************************************************************/ -/* C o n f i g N 2 N */ -/******************************************************************************/ - -int XrdCmsConfig::ConfigN2N() -{ - XrdOucN2NLoader n2nLoader(&Say, ConfigFN, N2N_Parms, LocalRoot, RemotRoot); - -// Get the plugin -// - if (!(xeq_N2N = n2nLoader.Load(N2N_Lib, *myVInfo, &theEnv))) return 1; - -// Optimize the local case -// - if (N2N_Lib || LocalRoot) lcl_N2N = xeq_N2N; - -// All done -// - PrepQ.setParms(lcl_N2N); - return 0; -} - -/******************************************************************************/ -/* C o n f i g O S S */ -/******************************************************************************/ - -int XrdCmsConfig::ConfigOSS() -{ - extern XrdOss *XrdOssGetSS(XrdSysLogger *, const char *, const char *, - const char *, XrdOucEnv *, XrdVersionInfo &); - void *arFunc; - -// Set up environment for the OSS to keep it relevant for cmsd -// - XrdOucEnv::Export("XRDREDIRECT", "Q"); - XrdOucEnv::Export("XRDOSSTYPE", "cms"); - XrdOucEnv::Export("XRDOSSCSCAN", "off"); - -// If no osslib was specified but we are a proxy, then we must load the -// the proxy osslib. -// - if (!ossLib && isProxy) ossLib = strdup("libXrdPss.so"); - -// Load and return result -// - ossFS=XrdOssGetSS(Say.logger(),ConfigFN,ossLib,ossParms,&theEnv,*myVInfo); - if (!ossFS) return 1; - -// Check if we should elay add/remove events to the statinfo function -// - if (!isManager && isServer && (arFunc = theEnv.GetPtr("XrdOssStatInfo2*"))) - return (XrdCmsAdmin::InitAREvents(arFunc) ? 0 : 1); - return 0; -} - -/******************************************************************************/ -/* C o n f i g P r o c */ -/******************************************************************************/ - -int XrdCmsConfig::ConfigProc(int getrole) -{ - char *var; - int cfgFD, retc, NoGo = 0; - XrdOucEnv myEnv; - XrdOucStream CFile(&Say, getenv("XRDINSTANCE"), &myEnv, "=====> "); - -// Try to open the configuration file. -// - if ( (cfgFD = open(ConfigFN, O_RDONLY, 0)) < 0) - {Say.Emsg("Config", errno, "open config file", ConfigFN); - return 1; - } - CFile.Attach(cfgFD); - -// Turn off echoing if we are doing a pre-scan -// - if (getrole) CFile.SetEroute(0); - -// Now start reading records until eof. -// - while((var = CFile.GetMyFirstWord())) - if (getrole) - {if (!strcmp("all.role", var) || !strcmp("olb.role", var)) - if (xrole(&Say, CFile)) - {CFile.SetEroute(&Say); CFile.Echo(); NoGo = 1; - CFile.SetEroute(0); - } - } - else if (!strncmp(var, "cms.", 4) - || !strncmp(var, "olb.", 4) // Backward compatability - || !strcmp(var, "ofs.osslib") - || !strcmp(var, "oss.defaults") - || !strcmp(var, "oss.localroot") - || !strcmp(var, "oss.remoteroot") - || !strcmp(var, "oss.namelib") - || !strcmp(var, "all.adminpath") - || !strcmp(var, "all.export") - || !strcmp(var, "all.manager") - || !strcmp(var, "all.pidpath") - || !strcmp(var, "all.role") - || !strcmp(var, "all.seclib") - || !strcmp(var, "all.subcluster")) - {if (ConfigXeq(var+4, CFile, 0)) {CFile.Echo(); NoGo = 1;}} - else if (!strcmp(var, "oss.stagecmd")) DiskSS = 1; - -// Now check if any errors occured during file i/o -// - if ((retc = CFile.LastError())) - NoGo = Say.Emsg("Config", retc, "read config file", ConfigFN); - CFile.Close(); - -// Merge Paths as needed -// - if (!getrole && (ManList || SanList)) NoGo |= MergeP(); - -// Return final return code -// - return NoGo; -} - -/******************************************************************************/ -/* i s E x e c */ -/******************************************************************************/ - -int XrdCmsConfig::isExec(XrdSysError *eDest, const char *ptype, char *prog) -{ - char buff[512], pp, *mp = prog; - -// Isolate the program name -// - while(*mp && *mp != ' ') mp++; - pp = *mp; *mp ='\0'; - -// Make sure the program is executable by us -// - if (access(prog, X_OK)) - {sprintf(buff, "find %s execuatble", ptype); - eDest->Emsg("Config", errno, buff, prog); - *mp = pp; - return 0; - } - -// All is well -// - *mp = pp; - return 1; -} - -/******************************************************************************/ -/* M e r g e P */ -/******************************************************************************/ - -int XrdCmsConfig::MergeP() -{ - static const unsigned long long stage4MM = XRDEXP_STAGEMM & ~XRDEXP_STAGE; - XrdOucPList *plp = PexpList.First(); - XrdCmsPList *pp; - XrdCmsPInfo opinfo, npinfo; - const char *ptype; - char *pbP; - unsigned long long Opts; - int pbLen = 0, NoGo = 0, export2MM = isManager && !isServer; - npinfo.rovec = 1; - -// For each path in the export list merge it into the path list -// - while(plp) - {Opts = plp->Flag(); - if (!(Opts & XRDEXP_LOCAL)) - {npinfo.rwvec = (Opts & (XRDEXP_GLBLRO | XRDEXP_NOTRW) ? 0 : 1); - if (export2MM) npinfo.ssvec = (Opts & stage4MM ? 1 : 0); - else npinfo.ssvec = (Opts & XRDEXP_STAGE ? 1 : 0); - if (!PathList.Add(plp->Path(), &npinfo)) - Say.Emsg("Config","Ignoring duplicate export path",plp->Path()); - else if (npinfo.ssvec) DiskSS = 1; - } - plp = plp->Next(); - } - -// Document what we will be declaring as available -// - if (!NoGo) - {const char *Who; - if (isManager) - {if (SanList) Who = "subcluster manager:"; - else Who = (isServer ? "manager:" : "meta-manager:"); - } else Who = "redirector:"; - Say.Say("The following paths are available to the ", Who); - if (!(pp = PathList.First())) Say.Say("r /"); - else while(pp) - {ptype = pp->PType(); - Say.Say(ptype, (strlen(ptype) > 1 ? " " : " "), pp->Path()); - pbLen += strlen(pp->Path())+8; pp = pp->Next(); - } - Say.Say(" "); - } - -// Now allocate a buffer and place all of the paths into that buffer to be -// sent during the login phase. -// - if (pbLen != 0 && (pp = PathList.First())) - {pbP = myPaths = (char *)malloc(pbLen); - while(pp) - {pbP += sprintf(pbP, "\n%s %s", pp->PType(), pp->Path()); - pp = pp->Next(); - } - myPaths++; - } - -// All done update the staging status (it's nostage by default) -// - if (DiskSS) CmsState.Update(XrdCmsState::Counts, 0, 1); - return NoGo; -} - -/******************************************************************************/ -/* P i d F i l e */ -/******************************************************************************/ - -int XrdCmsConfig::PidFile() -{ - int rc, xfd; - const char *clID; - char buff[1024]; - char pidFN[1200], *ppath=XrdOucUtils::genPath(pidPath, - XrdOucUtils::InstName(myInsName,0)); - const char *xop = 0; - - if ((rc = XrdOucUtils::makePath(ppath, XrdOucUtils::pathMode))) - {Say.Emsg("Config", rc, "create pid file path", ppath); - free(ppath); - return 1; - } - - if ((clID = index(mySID, ' '))) clID++; - else clID = mySID; - - if (isManager && isServer) - snprintf(pidFN, sizeof(pidFN), "%s/cmsd.super.pid", ppath); - else if (isServer) - snprintf(pidFN, sizeof(pidFN), "%s/cmsd.pid", ppath); - else snprintf(pidFN, sizeof(pidFN), "%s/cmsd.mangr.pid", ppath); - - if ((xfd = open(pidFN, O_WRONLY|O_CREAT|O_TRUNC,0644)) < 0) xop = "open"; - else {if ((write(xfd,buff,snprintf(buff,sizeof(buff),"%d", - static_cast(getpid()))) < 0) - || (LocalRoot && - (write(xfd,(void *)"\n&pfx=",6) < 0 || - write(xfd,(void *)LocalRoot,strlen(LocalRoot)) < 0 - ) ) - || (AdminPath && - (write(xfd,(void *)"\n&ap=", 5) < 0 || - write(xfd,(void *)AdminPath,strlen(AdminPath)) < 0 - ) ) - || write(xfd,(void *)"\n&cn=", 5) < 0 - || write(xfd,(void *)clID, strlen(clID)) < 0 - ) xop = "write"; - close(xfd); - } - - if (xop) Say.Emsg("Config", errno, xop, pidFN); - else XrdOucEnv::Export("XRDCMSPIDFN", pidFN); - - free(ppath); - return xop != 0; -} - -/******************************************************************************/ -/* s e t u p M a n a g e r */ -/******************************************************************************/ - -int XrdCmsConfig::setupManager() -{ - pthread_t tid; - int rc; - -// If we are a subcluster then we need to replace the manager list with the -// one specified on the subcluster directive. -// - if (SanList) - {XrdOucTList *nP, *tP = ManList; - const char *urDom, *myDom = index(myName, '.'); - bool isBad = false; - while(tP) {nP = tP; tP = tP->next; delete nP;} - ManList = tP = SanList; - if (myDom) while(tP) - {if ((urDom = index(tP->text, '.')) && strcmp(urDom, myDom)) - {Say.Emsg("Config", "Subcluster's manager", tP->text, - "is in a different domain."); - isBad = true; - } - tP = tP->next; - } - if (isBad) {Say.Emsg("Config","Cross domain subclusters disallowed!"); - return 1; - } - } - -// Setup supervisor mode if we are also a server -// - if (isServer && !XrdCmsSupervisor::Init(AdminPath, AdminMode)) return 1; - -// Compute the scheduling policy -// - sched_RR = (100 == P_fuzz) || !AskPerf - || !(P_cpu || P_io || P_load || P_mem || P_pag); - if (sched_RR) - {Say.Say("Config round robin scheduling in effect."); - sched_Level = 0; - } - -// Create statistical monitoring thread -// - if ((rc = XrdSysThread::Run(&tid, XrdCmsStartMonPerf, (void *)0, - 0, "Performance monitor"))) - {Say.Emsg("Config", rc, "create perf monitor thread"); - return 1; - } - -// Create reference monitoring thread -// - RefTurn = 3*STMax*(DiskLinger+1); - if (RefReset) - {if ((rc = XrdSysThread::Run(&tid, XrdCmsStartMonRefs, (void *)0, - 0, "Refcount monitor"))) - {Say.Emsg("Config", rc, "create refcount monitor thread"); - return 1; - } - } - -// Initialize the fast redirect queue -// - RRQ.Init(LUPHold, LUPDelay); - -// Initialize the security interface -// - if (SecLib && !XrdCmsSecurity::Configure(SecLib, ConfigFN)) return 1; - -// Initialize the black list -// - if (!isServer && blkChk) - XrdCmsBlackList::Init(Sched, &Cluster, blkList, blkChk); - -// All done -// - return 0; -} - -/******************************************************************************/ -/* s e t u p S e r v e r */ -/******************************************************************************/ - -int XrdCmsConfig::setupServer() -{ - XrdOucTList *tp; - int n = 0; - -// Make sure we have enough info to be a server -// - if (!ManList) - {Say.Emsg("Config", "Manager node not specified for", myRole, "role"); - return 1; - } - -// Count the number of managers. Make sure there are not too many. -// - tp = ManList; - while(tp) {n++; tp = tp->next;} - if (n > XrdCmsManager::MTMax) - {Say.Emsg("Config", "Too many managers have been specified"); return 1;} - -// Calculate overload delay time -// - if (MaxDelay < 0) MaxDelay = AskPerf*AskPing+30; - if (DiskWT < 0) DiskWT = AskPerf*AskPing+30; - -// Setup notification path -// - if (!(AnoteSock = XrdNetSocket::Create(&Say, AdminPath, - (isManager|isPeer ? "olbd.seton":"olbd.notes"), - AdminMode, XRDNET_UDPSOCKET))) return 1; - -// We have data only if we are a pure data server (the default is noData) -// If we have no data, then we are done (the rest is for pure servers) -// - if (isManager || isPeer) return 0; - SUPCount = 0; SUPLevel = 0; - if (isProxy) return 0; - DiskOK = 1; - -// If this is a staging server then set up the Prepq object -// - if (DiskSS) PrepQ.Reset(myInsName, AdminPath, AdminMode); - -// Setup file system metering (skip it for peers) -// - Meter.Init(); - if (perfpgm && Meter.Monitor(perfpgm, perfint)) - Say.Say("Config warning: load based scheduling disabled."); - -// All done -// - return 0; -} - -/******************************************************************************/ -/* s e t u p S i d */ -/******************************************************************************/ - -char *XrdCmsConfig::setupSid() -{ - XrdOucTList *tp = (NanList ? NanList : ManList); - char *sidVal, sfx; - -// Grab the interfaces. This is normally set as an envar. If present then -// we will copy it because we must use it permanently. -// - if (getenv("XRDIFADDRS")) ifList = strdup(getenv("XRDIFADDRS")); - -// Grab the site name -// - if ((mySite = getenv("XRDSITE")) && *mySite) mySite = strdup(mySite); - else mySite = 0; - -// Determine what type of role we are playing -// - if (isManager && isServer) sfx = 'u'; - else sfx = (isManager ? 'm' : 's'); - if (isProxy) sfx = toupper(sfx); - -// Get the node ID if we need to -// - if (VNID_Lib) - {myVNID = XrdCmsSecurity::getVnId(Say,ConfigFN,VNID_Lib,VNID_Parms,sfx); - if (!myVNID) return 0; - } - -// Generate the system ID and set the cluster ID -// - sidVal = XrdCmsSecurity::setSystemID(tp, myVNID, cidTag, sfx); - if (!sidVal || *sidVal == '!') - {const char *msg; - if (!sidVal) msg = "too many managers."; - else msg = sidVal+1; - Say.Emsg("cmsd","Unable to generate system ID; ", msg); - return 0; - } - return sidVal; -} - -/******************************************************************************/ -/* U s a g e */ -/******************************************************************************/ - -void XrdCmsConfig::Usage(int rc) -{ -cerr <<"\nUsage: cmsd [xrdopts] [-i] [-m] [-s] -c " < - - The dns name of the host that is allowed to connect or the - netgroup name the host must be a member of. For DNS names, - a single asterisk may be specified anywhere in the name. - - Type: Manager only, non-dynamic. - - Output: 0 upon success or !0 upon failure. -*/ - -int XrdCmsConfig::xallow(XrdSysError *eDest, XrdOucStream &CFile) -{ - char *val; - int ishost; - - if (!isManager) return CFile.noEcho(); - - if (!(val = CFile.GetWord())) - {eDest->Emsg("Config", "allow type not specified"); return 1;} - - if (!strcmp(val, "host")) ishost = 1; - else if (!strcmp(val, "netgroup")) ishost = 0; - else {eDest->Emsg("Config", "invalid allow type -", val); - return 1; - } - - if (!(val = CFile.GetWord())) - {eDest->Emsg("Config", "allow target name not specified"); return 1;} - - if (!Police) Police = new XrdNetSecurity(); - if (ishost) Police->AddHost(val); - else Police->AddNetGroup(val); - - return 0; -} - -/******************************************************************************/ -/* x a l t d s */ -/******************************************************************************/ - -/* Function: xaltds - - Purpose: To parse the directive: altds xroot [[no]monitor] - - xroot The protocol used by the alternate data server. - The port being used by the alternate data server. - mon Actively monitor alternate data server by connecting to it. - This is the default. - nomon Do not monitor the alternate data server. - it and if is greater than zero, send "ping" requests - every seconds. Zero merely connects. - - Type: Manager only, non-dynamic. - - Output: 0 upon success or !0 upon failure. -*/ - -int XrdCmsConfig::xaltds(XrdSysError *eDest, XrdOucStream &CFile) -{ - char *val; - - if (isManager) return CFile.noEcho(); - - if (!(val = CFile.GetWord())) - {eDest->Emsg("Config", "protocol not specified"); return 1;} - - if (strcmp(val, "xroot")) - {eDest->Emsg("Config", "unsupported protocol, '", val, "'."); return 1;} - if (adsProt) free(adsProt); - adsProt = strdup(val); - - if (!(val = CFile.GetWord())) - {eDest->Emsg("Config", "data server port not specified"); return 1;} - - if (isdigit(*val)) - {if (XrdOuca2x::a2i(*eDest,"data server port",val,&adsPort,1,65535)) - return 1; - } - else if (!(adsPort = XrdNetUtils::ServPort(val, "tcp"))) - {eDest->Emsg("Config", "Unable to find tcp service '",val,"'."); - return 1; - } - - if (!(val = CFile.GetWord()) || !strcmp(val, "monitor")) adsMon = 1; - else if (!strcmp(val, "nomonitor")) adsMon = 0; - else {eDest->Emsg("Config", "invalid option, '", val, "'."); - return 1; - } - - return 0; -} - -/******************************************************************************/ -/* x a p a t h */ -/******************************************************************************/ - -/* Function: xapath - - Purpose: To parse the directive: adminpath - - the path of the named socket to use for admin requests. - - Type: Manager and Server, non-dynamic. - - Output: 0 upon success or !0 upon failure. -*/ - -int XrdCmsConfig::xapath(XrdSysError *eDest, XrdOucStream &CFile) -{ - char *pval, *val; - mode_t mode = S_IRWXU; - -// Get the path -// - pval = CFile.GetWord(); - if (!pval || !pval[0]) - {eDest->Emsg("Config", "adminpath not specified"); return 1;} - -// Make sure it's an absolute path -// - if (*pval != '/') - {eDest->Emsg("Config", "adminpath not absolute"); return 1;} - pval = strdup(pval); - -// Get the optional access rights -// - if ((val = CFile.GetWord()) && val[0]) - {if (!strcmp("group", val)) mode |= S_IRWXG; - else {eDest->Emsg("Config", "invalid admin path modifier -", val); - free(pval); return 1; - } - } - -// Record the path -// - if (AdminPath) free(AdminPath); - AdminPath = pval; - AdminMode = mode; - return 0; -} - -/******************************************************************************/ -/* x b l k */ -/******************************************************************************/ - -/* Function: xblk - - Purpose: To parse the directive: blacklist [check "; - -// If caller wants only size, give it to him -// - if (!buff) return sizeof(statfmt)+16; - -// We have only one statistic -- number of successful matches, ignore do_sync -// - return snprintf(buff, blen, statfmt, Count); -} diff --git a/src/XrdRootd/XrdRootdProtocol.hh b/src/XrdRootd/XrdRootdProtocol.hh deleted file mode 100644 index 3ada7a349f9..00000000000 --- a/src/XrdRootd/XrdRootdProtocol.hh +++ /dev/null @@ -1,73 +0,0 @@ -#ifndef __XrdRootdProtocol_H__ -#define __XrdRootdProtocol_H__ -/******************************************************************************/ -/* */ -/* X r d R o o t d P r o t o c o l . h h */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "Xrd/XrdProtocol.hh" - -/******************************************************************************/ -/* x r d _ P r o t o c o l _ R o o t d */ -/******************************************************************************/ - -class XrdSysError; -class XrdOucTrace; -class XrdLink; -class XrdScheduler; - -class XrdRootdProtocol : XrdProtocol -{ -public: - - void DoIt() {} - - XrdProtocol *Match(XrdLink *lp); - - int Process(XrdLink *lp) {return -1;} - - void Recycle(XrdLink *lp, int x, const char *y) {} - - int Stats(char *buff, int blen, int do_sync); - - XrdRootdProtocol(XrdProtocol_Config *pi, - const char *pgm, const char **pap); - ~XrdRootdProtocol() {} // Never gets destroyed - -private: - -XrdScheduler *Scheduler; -const char *Program; -const char **ProgArg; -XrdSysError *eDest; -XrdOucTrace *XrdTrace; -int stderrFD; -int ReadWait; -static int Count; -static const char *TraceID; -}; -#endif diff --git a/src/XrdSec.cmake b/src/XrdSec.cmake deleted file mode 100644 index 9a07a88c3d1..00000000000 --- a/src/XrdSec.cmake +++ /dev/null @@ -1,165 +0,0 @@ - -include( XRootDCommon ) - -#------------------------------------------------------------------------------- -# Modules -#------------------------------------------------------------------------------- -set( LIB_XRD_SEC XrdSec-${PLUGIN_VERSION} ) -set( LIB_XRD_SEC_PROT XrdSecProt-${PLUGIN_VERSION} ) -set( LIB_XRD_SEC_PWD XrdSecpwd-${PLUGIN_VERSION} ) -set( LIB_XRD_SEC_SSS XrdSecsss-${PLUGIN_VERSION} ) -set( LIB_XRD_SEC_UNIX XrdSecunix-${PLUGIN_VERSION} ) - -#------------------------------------------------------------------------------- -# The XrdSec module -#------------------------------------------------------------------------------- -add_library( - ${LIB_XRD_SEC} - MODULE - XrdSec/XrdSecClient.cc - XrdSec/XrdSecEntity.hh - XrdSec/XrdSecInterface.hh - XrdSec/XrdSecPManager.cc XrdSec/XrdSecPManager.hh - XrdSec/XrdSecProtocolhost.cc XrdSec/XrdSecProtocolhost.hh - XrdSec/XrdSecServer.cc XrdSec/XrdSecServer.hh - XrdSec/XrdSecTLayer.cc XrdSec/XrdSecTLayer.hh - XrdSec/XrdSecTrace.hh ) - -target_link_libraries( - ${LIB_XRD_SEC} - XrdUtils - pthread - dl ) - -set_target_properties( - ${LIB_XRD_SEC} - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) - -#------------------------------------------------------------------------------- -# The XrdSecpwd module -#------------------------------------------------------------------------------- -add_library( - ${LIB_XRD_SEC_PROT} - MODULE - XrdSec/XrdSecProtect.cc XrdSec/XrdSecProtect.hh - XrdSec/XrdSecProtector.cc XrdSec/XrdSecProtector.hh ) - -if( BUILD_CRYPTO ) - target_link_libraries( - ${LIB_XRD_SEC_PROT} - XrdUtils - pthread - ${OPENSSL_CRYPTO_LIBRARY} ) -else() - target_link_libraries( - ${LIB_XRD_SEC_PROT} - XrdUtils - pthread ) -endif() - -set_target_properties( - ${LIB_XRD_SEC_PROT} - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) - -#------------------------------------------------------------------------------- -# The XrdSecpwd module -#------------------------------------------------------------------------------- -add_library( - ${LIB_XRD_SEC_PWD} - MODULE - XrdSecpwd/XrdSecProtocolpwd.cc XrdSecpwd/XrdSecProtocolpwd.hh - XrdSecpwd/XrdSecpwdPlatform.hh ) - -target_link_libraries( - ${LIB_XRD_SEC_PWD} - XrdCrypto - XrdUtils - pthread - ${CRYPT_LIBRARY} ) - -set_target_properties( - ${LIB_XRD_SEC_PWD} - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) - -#------------------------------------------------------------------------------- -# xrdpwdadmin -#------------------------------------------------------------------------------- -add_executable( - xrdpwdadmin - XrdSecpwd/XrdSecpwdSrvAdmin.cc ) - -target_link_libraries( - xrdpwdadmin - XrdCrypto - XrdUtils ) - -#------------------------------------------------------------------------------- -# The XrdSecsss module -#------------------------------------------------------------------------------- -add_library( - ${LIB_XRD_SEC_SSS} - MODULE - XrdSecsss/XrdSecProtocolsss.cc XrdSecsss/XrdSecProtocolsss.hh - XrdSecsss/XrdSecsssRR.hh ) - -target_link_libraries( - ${LIB_XRD_SEC_SSS} - XrdCryptoLite - XrdUtils ) - -set_target_properties( - ${LIB_XRD_SEC_SSS} - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) - -#------------------------------------------------------------------------------- -# xrdsssadmin -#------------------------------------------------------------------------------- -add_executable( - xrdsssadmin - XrdSecsss/XrdSecsssAdmin.cc ) - -target_link_libraries( - xrdsssadmin - XrdUtils ) - -#------------------------------------------------------------------------------- -# The XrdSecunix module -#------------------------------------------------------------------------------- -add_library( - ${LIB_XRD_SEC_UNIX} - MODULE - XrdSecunix/XrdSecProtocolunix.cc ) - -target_link_libraries( - ${LIB_XRD_SEC_UNIX} - XrdUtils ) - -set_target_properties( - ${LIB_XRD_SEC_UNIX} - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) - -#------------------------------------------------------------------------------- -# Install -#------------------------------------------------------------------------------- -install( - TARGETS - ${LIB_XRD_SEC} ${LIB_XRD_SEC_PWD} ${LIB_XRD_SEC_SSS} ${LIB_XRD_SEC_UNIX} ${LIB_XRD_SEC_PROT} - xrdsssadmin xrdpwdadmin - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ) - -install( - FILES - ${PROJECT_SOURCE_DIR}/docs/man/xrdsssadmin.8 - ${PROJECT_SOURCE_DIR}/docs/man/xrdpwdadmin.8 - DESTINATION ${CMAKE_INSTALL_MANDIR}/man8 ) diff --git a/src/XrdSec/XrdSecClient.cc b/src/XrdSec/XrdSecClient.cc deleted file mode 100644 index 3f9d9a9f92c..00000000000 --- a/src/XrdSec/XrdSecClient.cc +++ /dev/null @@ -1,120 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S e c C l i e n t . c c */ -/* */ -/* (c) 2003 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "XrdNet/XrdNetAddrInfo.hh" -#include "XrdOuc/XrdOucErrInfo.hh" -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdSys/XrdSysPthread.hh" -#include "XrdSec/XrdSecPManager.hh" -#include "XrdSec/XrdSecInterface.hh" - -/******************************************************************************/ -/* M i s c e l l a n e o u s D e f i n e s */ -/******************************************************************************/ - -#define DEBUG(x) {if (DebugON) cerr <<"sec_Client: " <setErrInfo(ENOPROTOOPT, noperr); - else cerr <. */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -//------------------------------------------------------------------------------ -//! This object is returned during authentication. This is most relevant for -//! client authentication unless mutual authentication has been implemented -//! in which case the client can also authenticate the server. It is embeded -//! in each protocol object to facilitate mutual authentication. Note that the -//! destructor does nothing and it is the responsibility of the protocol object -//! to delete the XrdSecEntity data members, if need be. -//! -//! Note: The host member contents are depdent on the dnr/nodnr setting and -//! and contain a host name or an IP address. To get the real host name -//! use addrInfo->Name(), this is required for any hostname comparisons. -//------------------------------------------------------------------------------ - -#include - -#define XrdSecPROTOIDSIZE 8 - -class XrdNetAddrInfo; - -class XrdSecEntity -{ -public: - char prot[XrdSecPROTOIDSIZE]; // Protocol used - char *name; // Entity's name - char *host; // Entity's host name dnr dependent - char *vorg; // Entity's virtual organization - char *role; // Entity's role - char *grps; // Entity's group names - char *endorsements; // Protocol specific endorsements - char *moninfo; // Additional information for monitoring - char *creds; // Raw client credentials or certificate - int credslen; // Length of the 'creds' field - int rsvd; // Reserved field -XrdNetAddrInfo *addrInfo; // Connection details from getProtocol -const char *tident; // Trace identifier always preset - void *sessvar; // Plugin settable storage pointer - // that is common to the session. Free - // it in your XrdSfsFileSystem::Disc() - // implementation, as needed. - XrdSecEntity(const char *pName = "") - {Reset(); - strncpy(prot, pName, XrdSecPROTOIDSIZE-1); - prot[XrdSecPROTOIDSIZE-1] = '\0'; - } - ~XrdSecEntity() {} - - void Reset() - { - memset( prot, 0, XrdSecPROTOIDSIZE ); - name = 0; - host = 0; - vorg = 0; - role = 0; - grps = 0; - endorsements = 0; - moninfo = 0; - creds = 0; - credslen = 0; - rsvd = 0; - addrInfo = 0; - tident = 0; - sessvar = 0; - } -}; - -#define XrdSecClientName XrdSecEntity -#define XrdSecServerName XrdSecEntity -#endif diff --git a/src/XrdSec/XrdSecInterface.hh b/src/XrdSec/XrdSecInterface.hh deleted file mode 100644 index 75795408eb9..00000000000 --- a/src/XrdSec/XrdSecInterface.hh +++ /dev/null @@ -1,638 +0,0 @@ -#ifndef __SEC_INTERFACE_H__ -#define __SEC_INTERFACE_H__ -/******************************************************************************/ -/* */ -/* X r d S e c I n t e r f a c e . h h */ -/* */ -/* (c) 2005 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#ifndef WIN32 -#include -#endif -#include -#include -#include - -#include "XrdSec/XrdSecEntity.hh" - -/******************************************************************************/ -/* X r d S e c C r e d e n t i a l s & X r d S e c P a r a m e t e r s */ -/******************************************************************************/ - -//------------------------------------------------------------------------------ -//! Generic structure to pass security information back and forth. -//------------------------------------------------------------------------------ - -struct XrdSecBuffer -{ - int size; //!< Size of the buffer or length of data in the buffer - char *buffer; //!< Pointer to the buffer - - XrdSecBuffer(char *bp=0, int sz=0) : size(sz), buffer(bp), membuf(bp) {} - ~XrdSecBuffer() {if (membuf) free(membuf);} - -private: - char *membuf; // Stable copy of the buffer address -}; - -//------------------------------------------------------------------------------ -//! When the buffer is used for credentials, the start of the buffer always -//! holds the credential protocol name (e.g., krb4) as a string. The client -//! will get credentials and the size will be filled out so that the contents -//! of buffer can be easily transmitted to the server. -//------------------------------------------------------------------------------ - -typedef XrdSecBuffer XrdSecCredentials; - -//------------------------------------------------------------------------------ -//! When the buffer is used for parameters, the contents must be interpreted -//! in the context that it is used. For instance, the server will send the -//! security configuration parameters on the initial login. The format differs -//! from, say, the x.500 continuation paremeters that would be sent during -//! PKI authentication via an "authmore" return status. -//------------------------------------------------------------------------------ - -typedef XrdSecBuffer XrdSecParameters; - -/******************************************************************************/ -/* X r d S e c P r o t o c o l */ -/******************************************************************************/ -/*! - The XrdSecProtocol is used to generate authentication credentials and to - authenticate those credentials. For example, When a server indicates - that authentication is needed (i.e., it returns security parameters), the - client must call XrdSecgetProtocol() to get an appropriate XrdSecProtocol - (i.e., one specific to the authentication protocol that needs to be used). - Then the client can use the first form getCredentials() to generate the - appropriate identification information. On subsequent calls in response to - "authmore" the client must use the second form, providing the additional - parameters the the server sends. The server uses Authenticate() to verify - the credentials. When XrdOucErrInfo is null (as it will usually be), error - messages are routed to standard error. So, for example, a client would - - 1) Call XrdSecGetProtocol() to get an appropriate XrdSecProtocol - (i.e., one specific to the authentication protocol that needs to be used). - Note that successive calls to XrdSecGetProtocol() using the same - XrdSecParameters will use the subsequent protocol named in the list of - protocols that the server returned. Failure is indicated when the list - is exhausted or none of the protocols apply (which exhausts the list). - - 2) Call getCredentials() without supplying any parameters so as to - generate identification information and send them to the server. - - 3) If the server indicates "authmore", call getCredentials() supplying - the additional parameters sent by the server. The returned credentials - are then sent to the server using the "authneticate" request code. - - 4) Repeat step (3) as often as "authmore" is requested by the server. - - The server uses Authenticate() to verify the credentials and getParms() - to generate initial parameters to start the authentication process. - - When XrdOucErrInfo is null (as it will usually be), error messages are - are routed to standard error. - - Server-side security is handled by the XrdSecService object and, while - it uses XrdSecProtocol objects to perform authentication, the XrdSecService - object is used to initialize the security environment and to generate - the appropriate protocol objects at run-time. For an implementation, see - XrdSecServer.hh and XrdSecServer.cc. - - MT Requirements: Must be MT_Safe. -*/ - -class XrdOucErrInfo; - -class XrdSecProtocol -{ -public: - -//------------------------------------------------------------------------------ -//! Structure holding the entity's identification. It is filled in by a -//! successful call to Authenticate() (i.e. it returns 0). -//------------------------------------------------------------------------------ - -XrdSecEntity Entity; - -//------------------------------------------------------------------------------ -//! Authenticate a client. -//! -//! @param cred Credentials supplied by the client. -//! @param parms Place where the address of additional authentication data is -//! to be placed for another autrhentication handshake. -//! @param einfo The error information object where error messages should be -//! placed. The messages are returned to the client. Should einfo -//! be null, messages should be written to stderr. -//! -//! @return > 0 -> parms present (more authentication needed) -//! = 0 -> Entity present (authentication suceeded) -//! < 0 -> einfo present (error has occured) -//------------------------------------------------------------------------------ - -virtual int Authenticate (XrdSecCredentials *cred, - XrdSecParameters **parms, - XrdOucErrInfo *einfo=0)=0; - -//------------------------------------------------------------------------------ -//! Generate client credentials to be used in the authentication process. -//! -//! @param parm Pointer to the information returned by the server either in -//! the initial login response or the authmore response. -//! @param einfo The error information object where error messages should be -//! placed. The messages are returned to the client. Should einfo -//! be null, messages should be written to stderr. -//! -//! @return Success: Pointer to credentials to sent to the server. The caller -//! is responsible for deleting the object. -//! Failure: Null pointer with einfo, if supplied, containing the -//! reason for the failure. -//------------------------------------------------------------------------------ - -virtual XrdSecCredentials *getCredentials(XrdSecParameters *parm=0, - XrdOucErrInfo *einfo=0)=0; - -//------------------------------------------------------------------------------ -//! Encrypt data in inbuff using the session key. -//! -//! @param inbuff buffer holding data to be encrypted. -//! @param inlen length of the data. -//! @param outbuff place where a pointer to the encrypted data is placed. -//! -//! @return < 0 Failed, the return value is -errno of the reason. Typically, -//! -EINVAL - one or more arguments are invalid. -//! -NOTSUP - encryption not supported by the protocol -//! -ENOENT - Context not innitialized -//! = 0 Success, outbuff contains a pointer to the encrypted data. -//! The caller is responsible for deleting the returned object. -//------------------------------------------------------------------------------ - -virtual int Encrypt(const char *inbuff, // Data to be encrypted - int inlen, // Length of data in inbuff - XrdSecBuffer **outbuff // Returns encrypted data - ) -{ - (void) inbuff; (void) inlen; (void) outbuff; - return -ENOTSUP; -} - -//------------------------------------------------------------------------------ -//! Decrypt data in inbuff using the session key. -//! -//! @param inbuff buffer holding data to be decrypted. -//! @param inlen length of the data. -//! @param outbuff place where a pointer to the decrypted data is placed. -//! -//! @return < 0 Failed,the return value is -errno (see Encrypt). -//! = 0 Success, outbuff contains a pointer to the decrypted data. -//! The caller is responsible for deleting the returned object. -//------------------------------------------------------------------------------ - -virtual int Decrypt(const char *inbuff, // Data to be decrypted - int inlen, // Length of data in inbuff - XrdSecBuffer **outbuff // Buffer for decrypted data - ) -{ - (void) inbuff; (void) inlen; (void) outbuff; - return -ENOTSUP; -} - -//------------------------------------------------------------------------------ -//! Sign data in inbuff using the session key. -//! -//! @param inbuff buffer holding data to be signed. -//! @param inlen length of the data. -//! @param outbuff place where a pointer to the signature is placed. -//! -//! @return < 0 Failed,the return value is -errno (see Encrypt). -//! = 0 Success, outbuff contains a pointer to the signature. -//! The caller is responsible for deleting the returned object. -//------------------------------------------------------------------------------ - -virtual int Sign(const char *inbuff, // Data to be signed - int inlen, // Length of data in inbuff - XrdSecBuffer **outbuff // Buffer for the signature - ) -{ - (void) inbuff; (void) inlen; (void) outbuff; - return -ENOTSUP; -} - -//------------------------------------------------------------------------------ -//! Verify a signature using the session key. -//! -//! @param inbuff buffer holding data to be verified. -//! @param inlen length of the data. -//! @param sigbuff pointer to the signature data. -//! @param siglen length of the signature data. -//! -//! @return < 0 Failed,the return value is -errno (see Encrypt). -//! = 0 Success, signature is correct. -//! > 0 Failed to verify, signature does not match inbuff data. -//------------------------------------------------------------------------------ - -virtual int Verify(const char *inbuff, // Data to be decrypted - int inlen, // Length of data in inbuff - const char *sigbuff, // Buffer for signature - int siglen) // Length if signature -{ - (void) inbuff; (void) inlen; (void) sigbuff; (void) siglen; - return -ENOTSUP; -} - -//------------------------------------------------------------------------------ -//! Get the current encryption key (i.e. session key) -//! -//! @param buff buffer to hold the key, and may be null. -//! @param size size of the buffer. -//! -//! @returns < 0 Failed, returned value if -errno (see Encrypt) -//! >= 0 The size of the encyption key. The supplied buffer of length -//! size hold the key. If the buffer address is supplied, the -//! key is placed in the buffer. -//! -//------------------------------------------------------------------------------ - -virtual int getKey(char *buff = 0, int size = 0) -{ - (void) buff; (void) size; - return -ENOTSUP; -} - -//------------------------------------------------------------------------------ -//! Set the current encryption key -//! -//! @param buff buffer that holds the key. -//! @param size size of the key. -//! -//! @returns: < 0 Failed, returned value if -errno (see Encrypt) -//! = 0 The new key has been set. -//------------------------------------------------------------------------------ - -virtual int setKey(char *buff, int size) -{ - (void) buff; (void) size; - return -ENOTSUP; -} - -//------------------------------------------------------------------------------ -//! Delete the protocol object. DO NOT use C++ delete() on this object. -//------------------------------------------------------------------------------ - -virtual void Delete()=0; // Normally does "delete this" - -//------------------------------------------------------------------------------ -//! Constructor -//------------------------------------------------------------------------------ - - XrdSecProtocol(const char *pName) : Entity(pName) {} -protected: - -//------------------------------------------------------------------------------ -//! Destructor (prevents use of direct delete). -//------------------------------------------------------------------------------ - -virtual ~XrdSecProtocol() {} -}; - -/******************************************************************************/ -/* P r o t o c o l N a m i n g C o n v e n t i o n s */ -/******************************************************************************/ - -/*! Each specific protocol resides in a shared library named "libXrdSec

.so" - where

is the protocol identifier (e.g., krb5, gsi, etc). The library - contains a class derived from the XrdSecProtocol object. The library must - also contain a three extern "C" functions: - 1) XrdSecProtocol

Init() - Called for one-time protocol ininitialization. - 2) XrdSecProtocol

Object() - Called for protocol object instantiation. - 3) XrdSecProtocol

Object_ - Inspected for the protocol object xrootd version number used in - compilation; defined by the XrdVERSIONINFO macro (see later comments). -*/ - -//------------------------------------------------------------------------------ -//! Perform one-time initialization when the shared library containing the -//! the protocol is loaded. -//! -//! @param who contains 'c' when called on the client side and 's' when -//! called on the server side. -//! @param parms when who == 'c' (client) the pointer is null. -//! when who == 's' (server) argument points to any parameters -//! specified in the config file with the seclib directive. If no -//! parameters were specified, the pointer may be null. -//! @param einfo The error information object where error messages should be -//! placed. Should einfo be null, messages should be written to -//! stderr. -//! -//! @return Success: The initial security token to be sent to the client during -//! the login phase (i.e. authentication handshake). If no -//! token need to be sent, a pointer to null string should be -//! returned. -//! Failure: A null pointer with einfo, if supplied, holding the reason -//! for the failure. -//! -//! Notes: 1) Function is called ince in single-thread mode and need not be -//! thread-safe. -//------------------------------------------------------------------------------ - -/*! extern "C" char *XrdSecProtocol

Init (const char who, - const char *parms, - XrdOucErrInfo *einfo) {. . . .} -*/ - -//------------------------------------------------------------------------------ -//! Obtain an instance of a protocol object. -//! -//! @param who contains 'c' when called on the client side and 's' when -//! called on the server side. -//! @param host The client's host name or the IP address as text. An IP -//! may be supplied if the host address is not resolvable. Use -//! endPoint to get the hostname only if it's actually needed. -//! @param endPoint the XrdNetAddrInfo object describing the end-point. When -//! who == 'c' this is the client, otherwise it is the server. -//! @param parms when who == 'c' (client) points to the parms sent by the -//! server upon the initial handshake (see Init() above). -//! when who == 's' (server) is null. -//! @param einfo The error information object where error messages should be -//! placed. Should einfo be null, messages should be written to -//! stderr. -//! -//! @return Success: A pointer to the protocol object. -//! Failure: A null pointer with einfo, if supplied, holding the reason -//! for the failure. -//! -//! Notes 1) Warning! The protocol *must* allow both 'c' and 's' calls within -//! the same execution context. This occurs when a server acts like -//! a client. -//! 2) This function must be thread-safe. -//! 3) Additionally, you *should* declare the xrootd version you used -//! to compile your plug-in using XrdVERSIONINFO where \ is -//! the 1- to 15-character unquoted name of your plugin. This is a -//! mandatory requirement! -//------------------------------------------------------------------------------ - -/*! Example: - - #include "XrdVersion.hh" - XrdVERSIONINFO(XrdSecProtocol

Object,); - - extern "C" XrdSecProtocol *XrdSecProtocol

Object - (const char who, - const char *hostname, - XrdnetAddrInfo &endPoint, - const char *parms, - XrdOucErrInfo *einfo - ) {. . .} -*/ - -/******************************************************************************/ -/* P r o t o c o l O b j e c t M a n a g e m e n t */ -/******************************************************************************/ - -//! The following extern "C" functions provide protocol object management. That -//! is, coordinating the use of the right authentication protocol between the -//! client and server. The default implementation may be replaced via a plugin. - -/******************************************************************************/ -/* X r d S e c G e t P r o t o c o l */ -/* */ -/* C l i e n t S i d e U S e O n l y */ -/******************************************************************************/ - -//------------------------------------------------------------------------------ -//! Create a client security context and get a supported XrdSecProtocol object -//! for one of the protocols suggested by the server and possibly based on the -//! server's hostname or host address, as needed. -//! -//! @param hostname The client's host name or the IP address as text. An IP -//! may be supplied if the host address is not resolvable. Use -//! endPoint to get the hostname only if it's actually needed. -//! @param endPoint the XrdNetAddrInfo object describing the server end-point. -//! @param sectoken The security token supplied by the server. -//! @param einfo The structure to record any error messages. These are -//! normally sent to the client. If einfo is a null pointer, -//! the messages should be sent to standard error. -//! -//! @return Success: Address of protocol object to be used for authentication. -//! If cred was null, a host protocol object should be -//! returned if so allowed. The object's delete method should -//! be called to release the storage. -//! Failure: Null, no protocol can be returned. The einfo parameter, -//! if supplied, has the reason. -//! -//! Notes: 1) There should be one protocol object per physical TCP/IP -//! connections. -//! 2) When the connection is closed, the protocol's Delete() method -//! should be called to properly delete the object. -//! 3) The method and the returned object should be MT-safe. -//! 4) When replacing the default implementation with a plug-in the -//! extern "C" function below must exist in your shared library. -//! 5) Additionally, you *should* declare the xrootd version you used -//! to compile your plug-in using XrdVERSIONINFO where \ is -//! the 1- to 15-character unquoted name of your plugin. This is a -//! mandatory requirement! -//------------------------------------------------------------------------------ - -//------------------------------------------------------------------------------ -//! Typedef to simplify the encoding of methods returning XrdSecProtocol. -//------------------------------------------------------------------------------ - -typedef XrdSecProtocol *(*XrdSecGetProt_t)(const char *, - XrdNetAddrInfo &, - XrdSecParameters &, - XrdOucErrInfo *); - -/*! Example: - - #include "XrdVersion.hh" - XrdVERSIONINFO(XrdSecGetProtocol,); - - extern "C" XrdSecProtocol *XrdSecGetProtocol - (const char *hostname, - XrdNetAddrInfo &endPoint, - XrdSecParameters §oken, - XrdOucErrInfo *einfo=0) - {....} -*/ - -/******************************************************************************/ -/* X r d S e c G e t P r o t e c t i o n */ -/* */ -/* C l i e n t S i d e U s e O n l y */ -/******************************************************************************/ - -/*! The XrdSecGetProtection function returns a protection object to secure - an XRootD request stream from injection attacks. An object is returned - when the response to kXR_protocol request indicates that the server - requires that the client secure the connection. This protection is based - on the authentication method used. Therefore, authentication must occur - before a protection object can be obtained. Usually, a protection object - is requested right after authentication. The function description is - - @param rc Where an error return code is to be placed. - @param aprot Uses the authentication protocol to protect requests. It - must be supplied and must be he protocol the client used - for authentication. Hence, authentication must occur first. - @param presp The protocol value returned in response to kXR_protocol. - The value must be host byte order. - - @return >0 pointer to the protect object placed in protP. - @return =0 No protection is needed, protP set to zero. - @return <0 An error occured getting the protection object the - return value is -errno and protP has been set to zero. - - Simply declare the following in the place where this is called: - - extern int XrdSecGetProtection(XrdSecProtect *&protP, - XrdSecProtocol &aprot, - kXR_int32 presp); -*/ - -/******************************************************************************/ -/* X r d S e c S e r v i c e */ -/* */ -/* S e r v e r S i d e U s e O n l y */ -/******************************************************************************/ - -/*! The XrdSecService object is the the object that the server uses to obtain - parameters to be passed to the client on initial contact and to create the - appropriate protocol on the initial receipt of the client's credentials. - Server-side processing is a bit more complicated because the set of valid - protocols needs to be configured and that configuration needs to be supplied - to the client so that both can agree on a compatible protocol. This object - is created via a call to XrdSecgetService, defined later on. You may replace - the default implementation by defining a plugin via the seclib directive. - - Warning: The XrdSecService object as well as any objects returned by it - should be MT-safe. -*/ - -class XrdSecService -{ -public: - -//------------------------------------------------------------------------------ -//! Obtain security parameters to be sent to the client upon initial contact. -//! -//! @param size Where the length of the return parameters are to be placed. -//! @param endPoint The client's address information. It may also be a null -//! pointer if the client's host is immaterial. -//! -//! @return EITHER The address of the parameter string (which may be -//! host-specific if hname was supplied). The length of the -//! string must be returned in size parameter. -//! OR A null pointer if authentication need not occur for the -//! client. The size parameter should be set to zero as well. -//------------------------------------------------------------------------------ - -virtual const char *getParms(int &size, XrdNetAddrInfo *endPoint=0) = 0; - -//------------------------------------------------------------------------------ -//! Obtain a protocol object suitable for authentication based on cred and -//! possibly based on the hostname or host address, as needed. -//! -//! @param host The client's host name or the IP address as text. An IP -//! may be supplied if the host address is not resolvable or -//! resolution has been suppressed (i.e. nodnr). Use endPoint -//! to get the hostname if it's actually needed. -//! @param endPoint the XrdNetAddrInfo object describing the client end-point. -//! @param cred The initial credentials supplied by the client, the pointer -//! may be null if the client did not supply credentials. -//! @param einfo The structure to record any error messages. These are -//! normally sent to the client. If einfo is a null pointer, -//! the messages should be sent to standard error via an -//! XrdSysError object using the supplied XrdSysLogger when the -//! the plugin was initialized. -//! -//! @return Success: Address of protocol object to be used for authentication. -//! If cred was null, a host protocol object shouldpo be -//! returned if so allowed. -//! Failure: Null, no protocol can be returned. The einfo parameter, -//! if supplied, has the reason. -//------------------------------------------------------------------------------ - -virtual XrdSecProtocol *getProtocol(const char *host, // In - XrdNetAddrInfo &endPoint,// In - const XrdSecCredentials *cred, // In - XrdOucErrInfo *einfo)=0;// Out - -//------------------------------------------------------------------------------ -//! Constructor -//------------------------------------------------------------------------------ - - XrdSecService() {} - -//------------------------------------------------------------------------------ -//! Destructor -//------------------------------------------------------------------------------ - -virtual ~XrdSecService() {} -}; - -/******************************************************************************/ -/* X r d g e t S e c S e r v i c e */ -/******************************************************************************/ - -//------------------------------------------------------------------------------ -//! Create a server's security context and get an XrdSecService object. -//! -//! @param lp The logging object that should be associated with an -//! XrdSysError object to route messages. -//! @param cfn The configuration file name. -//! -//! @return Success: Pointer to the XrdSecService object. This object is never -//! deleted by the server. -//! Failure: Null pointer which causes initialization to fail. -//! -//! Notes: 1) The XrdSecSgetService function is called once during server -//! initialization. So, it need not be thread safe. -//! 2) If you choose to replace the default implementation with your -//! own plugin, the extern "C" function below must be defined in -//! your plugin shared library. -//! 3) Additionally, you *should* declare the xrootd version you used -//! to compile your plug-in using XrdVERSIONINFO where \ is -//! the 1- to 15-character unquoted name of your plugin. This is a -//! mandatory requirement! -//------------------------------------------------------------------------------ - - -//------------------------------------------------------------------------------ -//! Typedef to simplify the encoding of methods returning XrdSecService. -//------------------------------------------------------------------------------ - -class XrdSysLogger; -typedef XrdSecService *(*XrdSecGetServ_t)(XrdSysLogger *, const char *); - -/*! Example: - - #include "XrdVersion.hh" - XrdVERSIONINFO(XrdSecgetService,); - - extern "C" XrdSecService *XrdSecgetService(XrdSysLogger *lp, const char *cfn) -*/ -#endif diff --git a/src/XrdSec/XrdSecLoadSecurity.cc b/src/XrdSec/XrdSecLoadSecurity.cc deleted file mode 100644 index e1f5c47e20f..00000000000 --- a/src/XrdSec/XrdSecLoadSecurity.cc +++ /dev/null @@ -1,296 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S e c L o a d S e c u r i t y . c c */ -/* */ -/* (c) 2014 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -#include "XrdVersion.hh" - -#include "XProtocol/XProtocol.hh" - -#include "XrdOuc/XrdOucPinLoader.hh" -#include "XrdSec/XrdSecLoadSecurity.hh" -#include "XrdSec/XrdSecProtector.hh" -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysPlatform.hh" -#include "XrdSys/XrdSysPthread.hh" - -/******************************************************************************/ -/* G l o b a l s */ -/******************************************************************************/ - -namespace -{ -static XrdVERSIONINFODEF(myVersion, XrdSecLoader, XrdVNUMBER, XrdVERSION); - -XrdSysMutex protMutex; -} - -namespace XrdSecProtection -{ -XrdSecProtector *theProtector = 0; -int protRC = 0; -} - -/******************************************************************************/ -/* P l u g */ -/******************************************************************************/ - -namespace -{ -int Plug(XrdOucPinLoader *piP, XrdSecGetProt_t *getP, XrdSecGetServ_t *ep) -{ -// If we need to load the protocol factory do so now -// - if (getP && !(*getP=(XrdSecGetProt_t)piP->Resolve("XrdSecGetProtocol"))) - return 1; - -// If we do not need to load the security service we are done -// - if (!ep) return 0; - -// Load the security service creator -// - if ((*ep = (XrdSecGetServ_t)piP->Resolve("XrdSecgetService"))) return 0; - -// We failed this is eiter soft or hard depending on what else we loaded -// - return (getP ? -1 : 1); -} -} - -/******************************************************************************/ -/* Private: L o a d */ -/******************************************************************************/ - -namespace -{ -int Load( char *eBuff, int eBlen, - const char *cfn, const char *seclib, - XrdSecGetProt_t *getP, XrdSecService **secP=0, - XrdSysError *eDest=0) -{ - XrdSecGetServ_t ep; - XrdOucPinLoader *piP; - const char *mySecLib = "libXrdSec.so"; - int rc; - -// Check for default path -// - if (!seclib) seclib = mySecLib; - -// Get a plugin loader object -// - if (eDest) piP = new XrdOucPinLoader(eDest, - &myVersion, "seclib", seclib); - else piP = new XrdOucPinLoader(eBuff, eBlen, - &myVersion, "seclib", seclib); - -// Load the appropriate pointers and get required objects. -// - rc = Plug(piP, getP, &ep); - if (rc == 0) - {if (secP && !(*secP = (*ep)(eDest->logger(), cfn))) rc = 1; - if (!rc) {delete piP; return 0;} - } - -// We failed, so bail out -// - if (eDest) - eDest->Say("Config ","Unable to create security framework via ", seclib); - piP->Unload(true); - return 1; -} -} - -/******************************************************************************/ - -namespace -{ -int Load( char *eBuff, int eBlen, - const char *protlib, XrdSysError *eDest=0) -{ - XrdSecProtector **protPP; - XrdOucPinLoader *piP; - const char *myProtLib = "libXrdSecProt.so"; - -// Check for default path -// - if (!protlib) protlib = myProtLib; - -// Get a plugin loader object -// - if (eDest) piP = new XrdOucPinLoader(eDest, - &myVersion, "protlib", protlib); - else piP = new XrdOucPinLoader(eBuff, eBlen, - &myVersion, "protlib", protlib); - -// Get the protection object which also is a factory object. -// - protPP = (XrdSecProtector **)piP->Resolve("XrdSecProtObjectP"); - if (protPP) - {XrdSecProtection::theProtector = *protPP; - delete piP; - return 0; - } - return 1; - -// We failed, so bail out -// - if (eDest) - eDest->Say("Config ","Unable to create protection framework via ",protlib); - piP->Unload(true); - return ENOENT; -} -} - -/******************************************************************************/ -/* X r d S e c L o a d F a c t o r y */ -/******************************************************************************/ - -XrdSecGetProt_t XrdSecLoadSecFactory(char *eBuff, int eBlen, const char *seclib) -{ - XrdSecGetProt_t getP; - int rc; - -// Load required plugin nd obtain pointers -// - rc = Load(eBuff, eBlen, 0, seclib, &getP); - if (!rc) return getP; - -// Issue correct error message, if any -// - if (!seclib) seclib = "default"; - - if (rc < 0) - snprintf(eBuff, eBlen, - "Unable to create security framework via %s; invalid path.", - seclib); - else if (!(*eBuff)) - snprintf(eBuff, eBlen, - "Unable to create security framework via %s", seclib); - return 0; -} - -/******************************************************************************/ -/* X r d S e c G e t P r o t e c t i o n */ -/******************************************************************************/ - -// This is used client-side only - -int XrdSecGetProtection(XrdSecProtect *&protP, - XrdSecProtocol &aprot, - ServerResponseBody_Protocol &resp, - unsigned int resplen) -{ - static const unsigned int hdrLen = sizeof(ServerResponseReqs_Protocol) - 2; - static const unsigned int minLen = kXR_ShortProtRespLen + hdrLen; - XrdSecProtector *pObj; - unsigned int vLen; - int rc; - -// First validate the response before passing it to anyone -// - protP = 0; - if (resplen <= kXR_ShortProtRespLen) return 0; - if (resplen < minLen) return -EINVAL; - vLen = static_cast(resp.secreq.secvsz) - * sizeof(ServerResponseSVec_Protocol); - if (vLen + minLen > resplen) return -EINVAL; - -// Our first step is to see if any protection is required -// - if (vLen == 0 && resp.secreq.seclvl == kXR_secNone) return 0; - -// The next step is to see if we have a protector object. If we do not then -// we need to load the library that provides such objects. This needs to be -// MT-safe as it may be called at any time by any thread. -// - protMutex.Lock(); - if (!(pObj = XrdSecProtection::theProtector)) - {if (!XrdSecProtection::protRC) - {char eBuff[2048]; - if ((XrdSecProtection::protRC = Load(eBuff, sizeof(eBuff), 0))) - std::cerr <<"SecLoad: " <New4Client(aprot, resp.secreq, resplen-kXR_ShortProtRespLen); - return (protP ? 1 : 0); -} - -/******************************************************************************/ -/* X r d S e c L o a d P r o t e c t i o n */ -/******************************************************************************/ - -// This is a one-time server-side call - -XrdSecProtector *XrdSecLoadProtection(XrdSysError &erP) -{ - -// Load the protection object. This is done in the main thread do no mutex -// - XrdSecProtection::protRC = Load(0, 0, 0, &erP); - -// All done, return result -// - return (XrdSecProtection::protRC ? 0 : XrdSecProtection::theProtector); -} - -/******************************************************************************/ -/* X r d S e c L o a d S e c S e r v i c e */ -/******************************************************************************/ - -XrdSecService *XrdSecLoadSecService(XrdSysError *eDest, - const char *cfn, - const char *seclib, - XrdSecGetProt_t *getP, - XrdSecProtector**proP) -{ - XrdSecService *CIA; - -// Load required plugin nd obtain pointers -// - if (Load(0, 0, cfn, seclib, getP, &CIA, eDest)) return 0; - -// Set the protectorobject. Note that the securityservice will load it if -// is needed and we will havecaptured its pointer. This sort of a hack but -// we can't change the SecService object as it is a public interface. -// - if (proP) *proP = XrdSecProtection::theProtector; - return CIA; -} diff --git a/src/XrdSec/XrdSecLoadSecurity.hh b/src/XrdSec/XrdSecLoadSecurity.hh deleted file mode 100644 index 27f16f72ae3..00000000000 --- a/src/XrdSec/XrdSecLoadSecurity.hh +++ /dev/null @@ -1,130 +0,0 @@ -#ifndef __XRDSECLOADSECURITY_HH__ -#define __XRDSECLOADSECURITY_HH__ -/******************************************************************************/ -/* */ -/* X r d S e c L o a d S e c u r i t y . h h */ -/* */ -/* (c) 2014 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdSec/XrdSecInterface.hh" - -//------------------------------------------------------------------------------ -//! This include file defines utility functions that load the security -//! framework plugin specialized for server-side or client-side use. -//! These functions are public and remain ABI stable! -//------------------------------------------------------------------------------ - -/******************************************************************************/ -/* X r d S e c L o a d S e c F a c t o r y */ -/******************************************************************************/ - -//------------------------------------------------------------------------------ -//! Load the Security Protocol Factory (used client-side) -//! -//! @param eBuff Pointer to a buffer tat is to receive any messages. Upon -//! failure it will contain an eror message. Upon success it -//! will contain an informational message that describes the -//! version that was loaded. -//! @param eBlen The length of the eBuff, it should be at least 1K to -//! avoid message truncation as the message may have a path. -//! @param seclib Pointer to the shared library path that contains the -//! framework implementation. If a nill pointer is passed, then -//! the default library is used. -//! -//! @return !0 Pointer to the to XrdSegGetProtocol() function is returned. -//! returned in getP if it is not nil. -//! @return =0 The security frmaework could not be loaded. The error -//! message describing the problem is in eBuff. -//------------------------------------------------------------------------------ - -XrdSecGetProt_t XrdSecLoadSecFactory( char *eBuff, - int eBlen, - const char *seclib=0); - -/******************************************************************************/ -/* X r d S e c G e t P r o t e c t i o n */ -/******************************************************************************/ - -class XrdSecProtect; -class XrdSecProtector; -struct ServerResponseBody_Protocol; - -//------------------------------------------------------------------------------ -//! Obtain an instance of a security protection object based on the kXR_protocol -//! response. This is only used client-side. -//! -//! @param protP Place where the protection object point is placed. -//! @param aprot Uses the authentication protocol to protect requests. It -//! must be supplied and must be he protocol the client used -//! for authentication. Hence, authentication must occur first. -//! @param resp Reference to the response body returned by kXR_protocol. -//! @param resplen Length of the response body. -//! -//! @return >0 pointer to the protect object placed in protP. -//! @return =0 No protection is needed, protP set to zero. -//! @return <0 An error occured getting the protection object the -//! return value is -errno and protP has been set to zero. -//------------------------------------------------------------------------------ - -int XrdSecGetProtection(XrdSecProtect *&protP, - XrdSecProtocol &aprot, - ServerResponseBody_Protocol &resp, - unsigned int resplen); - -/******************************************************************************/ -/* X r d S e c L o a d S e c S e r v i c e */ -/******************************************************************************/ - -//------------------------------------------------------------------------------ -//! Load the Security Protocol Service and obtain an instance (server-side). -//! -//! @param eDest Pointer to the error object that routes error messages. -//! @param cfn Pointer to the configuration file path. -//! @param seclib Pointer to the shared library path that contains the -//! framework implementation. If the filename is XrdSec.xx -//! then this library name is dynamically versioned. If a nil -//! pointer is passed, then the defalt library is used. -//! @param getP Upon success and if supplied, the pointer to the function -//! XrdSecGetProtocol() used to get protocol objects. -//! @param proP Upon success and ifsupplied, the pointer to the class -//! that provides protection services (nill means non wanted). -//! -//! @return !0 Pointer to the XrdSecService object suitable for server use. -//! This object is persisted and will not be deleted until exit. -//! Additionally, the pointer to XrdSegGetProtocol() function is -//! returned in getP if it is not nil. -//! @return =0 The security frmaework could not be loaded. Error messages -//! describing the problem have been issued. -//------------------------------------------------------------------------------ - -class XrdSysError; - -XrdSecService *XrdSecLoadSecService(XrdSysError *eDest, - const char *cfn, - const char *seclib=0, - XrdSecGetProt_t *getP=0, - XrdSecProtector **proP=0); -#endif diff --git a/src/XrdSec/XrdSecPManager.cc b/src/XrdSec/XrdSecPManager.cc deleted file mode 100644 index a1ae8d38719..00000000000 --- a/src/XrdSec/XrdSecPManager.cc +++ /dev/null @@ -1,369 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S e c P M a n a g e r . c c */ -/* */ -/* (c) 2003 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include - -#include "XrdVersion.hh" -#include "XrdVersionPlugin.hh" - -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdSec/XrdSecInterface.hh" -#include "XrdSec/XrdSecPManager.hh" -#include "XrdSec/XrdSecProtocolhost.hh" -#include "XrdOuc/XrdOucEnv.hh" -#include "XrdOuc/XrdOucErrInfo.hh" -#include "XrdOuc/XrdOucPinLoader.hh" -#include "XrdOuc/XrdOucVerName.hh" -#include "XrdNet/XrdNetAddrInfo.hh" - -#include "XrdSys/XrdSysPlatform.hh" - -/******************************************************************************/ -/* M i s c e l l a n e o u s D e f i n e s */ -/******************************************************************************/ - -#define DEBUG(x) {if (DebugON) cerr <<"sec_PM: " <protargs; - return plp->protnum; - } - return 0; -} - -/******************************************************************************/ -/* G e t */ -/******************************************************************************/ - -XrdSecProtocol *XrdSecPManager::Get(const char *hname, - XrdNetAddrInfo &endPoint, - const char *pname, - XrdOucErrInfo *erp) -{ - XrdSecProtList *pl; - const char *msgv[2]; - -// Find the protocol and get an instance of the protocol object -// - if ((pl = Lookup(pname))) - {DEBUG("Using " <ep('s', hname, endPoint, 0, erp); - } - -// Protocol is not supported -// - msgv[0] = pname; - msgv[1] = " security protocol is not supported."; - erp->setErrInfo(EPROTONOSUPPORT, msgv, 2); - return 0; -} - -XrdSecProtocol *XrdSecPManager::Get(const char *hname, - XrdNetAddrInfo &endPoint, - XrdSecParameters &secparm, - XrdOucErrInfo *eri) -{ - char secbuff[4096], *nscan, *pname, *pargs, *bp = secbuff; - char pcomp[XrdSecPROTOIDSIZE+4], *compProt; - XrdSecProtList *pl; - XrdSecProtocol *pp; - XrdOucErrInfo ei; - XrdOucErrInfo *erp = (eri) ? eri : &ei; - int i; - -// We support passing the list of protocols via Url parameter -// - char *wp = (eri && eri->getEnv()) ? eri->getEnv()->Get("xrd.wantprot") : 0; - const char *wantProt = wp ? (const char *)wp : getenv("XrdSecPROTOCOL"); - -// We only scan the buffer once -// - if (secparm.size <= 0) return (XrdSecProtocol *)0; - -// Copy out the wanted protocols and frame them for easy comparison -// - if (wantProt) - {i = strlen(wantProt); - compProt = (char *)malloc(i+3); - *compProt = ','; - strcpy(compProt+1, wantProt); - compProt[i+1] = ','; compProt[i+2] = 0; *pcomp = ','; - } else compProt = 0; - -// Copy the string into a local buffer so that we can simplify some comparisons -// and isolate ourselves from server protocol errors. -// - if (secparm.size < (int)sizeof(secbuff)) i = secparm.size; - else i = sizeof(secbuff)-1; - strncpy(secbuff, secparm.buffer, i); - secbuff[i] = '\0'; - -// Find a protocol marker in the info block and check if acceptable -// - while(*bp) - {if (*bp != '&') {bp++; continue;} - else if (!*(++bp) || *bp != 'P' || !*(++bp) || *bp != '=') continue; - bp++; pname = bp; pargs = 0; - while(*bp && *bp != ',' && *bp != '&') bp++; - if (!*bp) nscan = 0; - else {if (*bp == '&') {*bp = '\0'; pargs = 0; nscan = bp;} - else {*bp = '\0'; pargs = ++bp; - while (*bp && *bp != '&') bp++; - if (*bp) {*bp ='\0'; nscan = bp;} - else nscan = 0; - } - } - if (wantProt) - {strncpy(pcomp+1, pname, XrdSecPROTOIDSIZE); - pcomp[XrdSecPROTOIDSIZE+1] = 0; - strcat(pcomp, ","); - } - if (!wantProt || strstr(compProt, pcomp)) - {XrdSysMutexHelper pmHelper(pmMutex); - if ((pl = Lookup(pname)) || (pl = ldPO(erp, 'c', pname))) - {DEBUG("Using " <ep('c', hname, endPoint, pargs, erp))) - {if (nscan) {i = nscan - secbuff; - secparm.buffer += i; secparm.size -= i; - } else secparm.size = -1; - if (compProt) free(compProt); - return pp; - } - } - if (erp->getErrInfo() != ENOENT) cerr <getErrText() <setErrInfo(-1, "XrdSec: Too many protocols defined."); - return 0; - } - -// Add this protocol to our protocol stack -// - plp = new XrdSecProtList((char *)pid, parg); - plp->ep = ep; - myMutex.Lock(); - if (Last) {Last->Next = plp; Last = plp;} - else First = Last = plp; - plp->protnum = protnum; - if (protnum & 0x40000000) protnum = 0; - else protnum = protnum<<1; - myMutex.UnLock(); - -// All went well -// - return plp; -} - -/******************************************************************************/ -/* l d P O */ -/******************************************************************************/ - -#define INITPARMS const char, const char *, XrdOucErrInfo * - -XrdSecProtList *XrdSecPManager::ldPO(XrdOucErrInfo *eMsg, // In - const char pmode, // In 'c' | 's' - const char *pid, // In - const char *parg, // In - const char *spath) // In -{ - extern XrdSecProtocol *XrdSecProtocolhostObject(PROTPARMS); - static XrdVERSIONINFODEF(clVer, SecClnt, XrdVNUMBER, XrdVERSION); - static XrdVERSIONINFODEF(srVer, SecSrvr, XrdVNUMBER, XrdVERSION); - XrdVersionInfo *myVer = (pmode == 'c' ? &clVer : &srVer); - XrdOucPinLoader *secLib; - XrdSecProtocol *(*ep)(PROTPARMS); - char *(*ip)(INITPARMS); - const char *sep, *libloc; - char poname[80], libpath[2048], *newargs, *bP; - int i; - -// Set plugin debugging if needed (this only applies to client calls) -// - if (DebugON && pmode == 'c' && !DebugON) XrdOucEnv::Export("XRDPIHUSH", "1"); - -// The "host" protocol is builtin. -// - if (!strcmp(pid, "host")) return Add(eMsg,pid,XrdSecProtocolhostObject,0); - -// Form library name (versioned) and object creator name and bundle id -// - snprintf(poname, sizeof(poname), "libXrdSec%s.so", pid); - i = (spath ? strlen(spath) : 0); - if (!i) {spath = ""; sep = "";} - else sep = (spath[i-1] == '/' ? "" : "/"); - snprintf(libpath, sizeof(libpath), "%s%s%s", spath, sep, poname); - libloc = libpath; - -// Get the plugin loader. -// - if (errP) secLib = new XrdOucPinLoader(errP, myVer, "sec.protocol", libloc); - else {bP = eMsg->getMsgBuff(i); - secLib = new XrdOucPinLoader(bP,i,myVer, "sec.protocol", libloc); - } - -// Get the protocol object creator. -// - if (eMsg) eMsg->setErrInfo(0, ""); - snprintf(poname, sizeof(poname), "XrdSecProtocol%sObject", pid); - if (!(ep = (XrdSecProtocol *(*)(PROTPARMS))secLib->Resolve(poname))) - {secLib->Unload(true); return 0;} - -// Get the protocol initializer -// - sprintf(poname, "XrdSecProtocol%sInit", pid); - if (!(ip = (char *(*)(INITPARMS))secLib->Resolve(poname))) - {secLib->Unload(true); return 0;} - -// Get the true path and do some debugging -// - libloc = secLib->Path(); - DEBUG("Loaded " <getErrText())) - {const char *eTxt[] = {"XrdSec: ", pid, - " initialization failed in sec.protocol ", libloc}; - eMsg->setErrInfo(-1, eTxt, sizeof(eTxt)); - } - secLib->Unload(true); - return 0; - } - -// Add this protocol to our protocol stack -// - delete secLib; - return Add(eMsg, pid, ep, newargs); -} - -/******************************************************************************/ -/* L o o k u p */ -/******************************************************************************/ - -XrdSecProtList *XrdSecPManager::Lookup(const char *pid) // In -{ - XrdSecProtList *plp; - -// Since we only add protocols and never remove them, we need only to lock -// the protocol list to get the first item. -// - myMutex.Lock(); - plp = First; - myMutex.UnLock(); - -// Now we can go and find a matching protocol -// - while(plp && strcmp(plp->protid, pid)) plp = plp->Next; - - return plp; -} diff --git a/src/XrdSec/XrdSecPManager.hh b/src/XrdSec/XrdSecPManager.hh deleted file mode 100644 index 8c230f3c496..00000000000 --- a/src/XrdSec/XrdSecPManager.hh +++ /dev/null @@ -1,103 +0,0 @@ -#ifndef __SEC_PMANAGER_HH__ -#define __SEC_PMANAGER_HH__ -/******************************************************************************/ -/* */ -/* X r d S e c P M a n a g e r . h h */ -/* */ -/* (c) 2003 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdSec/XrdSecInterface.hh" -#include "XrdSys/XrdSysPthread.hh" - -class XrdNetAddrInfo; -class XrdOucErrInfo; -class XrdSecProtList; -class XrdSecProtocol; -class XrdSysError; - -typedef int XrdSecPMask_t; - -#define PROTPARMS const char, const char *, XrdNetAddrInfo &, \ - const char *, XrdOucErrInfo * - -class XrdSecPManager -{ -public: - -XrdSecPMask_t Find(const char *pid, // In - char **parg=0); // Out - -XrdSecProtocol *Get(const char *hname, - XrdNetAddrInfo &endPoint, - const char *pname, - XrdOucErrInfo *erp); - -XrdSecProtocol *Get(const char *hname, - XrdNetAddrInfo &netaddr, - XrdSecParameters &secparm) - {return Get(hname, netaddr, secparm, (XrdOucErrInfo *)0);} - -XrdSecProtocol *Get(const char *hname, - XrdNetAddrInfo &netaddr, - XrdSecParameters &secparm, - XrdOucErrInfo *erp); - -int Load(XrdOucErrInfo *eMsg, // In - const char pmode, // In 'c' | 's' - const char *pid, // In - const char *parg, // In - const char *path) // In - {return (0 != ldPO(eMsg, pmode, pid, parg, path));} - -void setDebug(int dbg) {DebugON = dbg;} - -void setErrP(XrdSysError *eP) {errP = eP;} - - XrdSecPManager(int dbg=0) - : protnum(1), First(0), Last(0), errP(0), - DebugON(dbg) {} - ~XrdSecPManager() {} - -private: - -XrdSecProtList *Add(XrdOucErrInfo *eMsg, const char *pid, - XrdSecProtocol *(*ep)(PROTPARMS), const char *parg); -XrdSecProtList *ldPO(XrdOucErrInfo *eMsg, // In - const char pmode, // In 'c' | 's' - const char *pid, // In - const char *parg=0, // In - const char *spath=0);// In -XrdSecProtList *Lookup(const char *pid); - -XrdSecPMask_t protnum; -XrdSysMutex myMutex; -XrdSecProtList *First; -XrdSecProtList *Last; -XrdSysError *errP; -int DebugON; -}; -#endif diff --git a/src/XrdSec/XrdSecProtect.cc b/src/XrdSec/XrdSecProtect.cc deleted file mode 100644 index b4e2e0fa788..00000000000 --- a/src/XrdSec/XrdSecProtect.cc +++ /dev/null @@ -1,455 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S e c P r o t e c t . c c */ -/* */ -/* (c) 2016 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include - -#ifdef __APPLE__ -#define COMMON_DIGEST_FOR_OPENSSL -#include "CommonCrypto/CommonDigest.h" -#else -#include "openssl/sha.h" -#endif - -#include "XrdVersion.hh" - -#include "XProtocol/XProtocol.hh" -#include "XrdSec/XrdSecInterface.hh" -#include "XrdSec/XrdSecProtect.hh" -#include "XrdSec/XrdSecProtector.hh" -#include "XrdSys/XrdSysAtomics.hh" -#include "XrdSys/XrdSysPlatform.hh" -#include "XrdSys/XrdSysPthread.hh" - -/******************************************************************************/ -/* S t r u c t X r d S e c R e q */ -/******************************************************************************/ - -namespace XrdSecProtection -{ -struct XrdSecReq -{ - SecurityRequest secReq; - unsigned char secSig; // The encrypted hash follows starting here -}; -} - -using namespace XrdSecProtection; // Fix warnings from slc5 compiler! - -/******************************************************************************/ -/* C l a s s X r d S e c V e c */ -/******************************************************************************/ - -namespace -{ -class XrdSecVec -{ -public: - -char Vec[XrdSecProtectParms::secFence-1][kXR_REQFENCE-kXR_auth]; - - XrdSecVec(int arg, ...) - {va_list ap; - int reqCode, sVal; - memset(Vec, 0, sizeof(Vec)); - va_start(ap, arg); - reqCode = va_arg(ap, int); - while(reqCode >= kXR_auth && reqCode < kXR_REQFENCE) - {for (int i=0; i < (int)XrdSecProtectParms::secFence-1; i++) - {sVal = va_arg(ap, int); - Vec[i][reqCode-kXR_auth] = static_cast(sVal); - } - reqCode = va_arg(ap, int); - } - } - ~XrdSecVec() {} -}; -} - -/******************************************************************************/ -/* S e c u r i t y T a b l e */ -/******************************************************************************/ - -namespace -{ - -XrdSecVec secTable(0, -// Compatible Standard Intense Pedantic -kXR_admin, kXR_signNeeded, kXR_signNeeded, kXR_signNeeded, kXR_signNeeded, -kXR_auth, kXR_signIgnore, kXR_signIgnore, kXR_signIgnore, kXR_signIgnore, -kXR_bind, kXR_signIgnore, kXR_signIgnore, kXR_signNeeded, kXR_signNeeded, -kXR_chmod, kXR_signNeeded, kXR_signNeeded, kXR_signNeeded, kXR_signNeeded, -kXR_close, kXR_signIgnore, kXR_signIgnore, kXR_signNeeded, kXR_signNeeded, -kXR_decrypt, kXR_signIgnore, kXR_signIgnore, kXR_signIgnore, kXR_signIgnore, -kXR_dirlist, kXR_signIgnore, kXR_signIgnore, kXR_signIgnore, kXR_signNeeded, -kXR_endsess, kXR_signIgnore, kXR_signIgnore, kXR_signNeeded, kXR_signNeeded, -kXR_getfile, kXR_signNeeded, kXR_signNeeded, kXR_signNeeded, kXR_signNeeded, -kXR_locate, kXR_signIgnore, kXR_signIgnore, kXR_signIgnore, kXR_signNeeded, -kXR_login, kXR_signIgnore, kXR_signIgnore, kXR_signIgnore, kXR_signIgnore, -kXR_mkdir, kXR_signIgnore, kXR_signNeeded, kXR_signNeeded, kXR_signNeeded, -kXR_mv, kXR_signNeeded, kXR_signNeeded, kXR_signNeeded, kXR_signNeeded, -kXR_open, kXR_signLikely, kXR_signNeeded, kXR_signNeeded, kXR_signNeeded, -kXR_ping, kXR_signIgnore, kXR_signIgnore, kXR_signIgnore, kXR_signIgnore, -kXR_prepare, kXR_signIgnore, kXR_signIgnore, kXR_signIgnore, kXR_signNeeded, -kXR_protocol, kXR_signIgnore, kXR_signIgnore, kXR_signIgnore, kXR_signIgnore, -kXR_putfile, kXR_signNeeded, kXR_signNeeded, kXR_signNeeded, kXR_signNeeded, -kXR_query, kXR_signIgnore, kXR_signIgnore, kXR_signLikely, kXR_signNeeded, -kXR_read, kXR_signIgnore, kXR_signIgnore, kXR_signIgnore, kXR_signNeeded, -kXR_readv, kXR_signIgnore, kXR_signIgnore, kXR_signIgnore, kXR_signNeeded, -kXR_rm, kXR_signNeeded, kXR_signNeeded, kXR_signNeeded, kXR_signNeeded, -kXR_rmdir, kXR_signNeeded, kXR_signNeeded, kXR_signNeeded, kXR_signNeeded, -kXR_set, kXR_signLikely, kXR_signLikely, kXR_signNeeded, kXR_signNeeded, -kXR_sigver, kXR_signIgnore, kXR_signIgnore, kXR_signIgnore, kXR_signIgnore, -kXR_stat, kXR_signIgnore, kXR_signIgnore, kXR_signIgnore, kXR_signNeeded, -kXR_statx, kXR_signIgnore, kXR_signIgnore, kXR_signIgnore, kXR_signNeeded, -kXR_sync, kXR_signIgnore, kXR_signIgnore, kXR_signIgnore, kXR_signNeeded, -kXR_truncate, kXR_signNeeded, kXR_signNeeded, kXR_signNeeded, kXR_signNeeded, -kXR_verifyw, kXR_signIgnore, kXR_signIgnore, kXR_signNeeded, kXR_signNeeded, -kXR_write, kXR_signIgnore, kXR_signIgnore, kXR_signNeeded, kXR_signNeeded, -0); -} - -/******************************************************************************/ -/* Private: G e t S H A 2 */ -/******************************************************************************/ - -bool XrdSecProtect::GetSHA2(unsigned char *hBuff, struct iovec *iovP, int iovN) -{ - SHA256_CTX sha256; - -// Initialize the hash calculattion -// - if (0 == SHA256_Init(&sha256)) return false; - -// Go through the iovec updating the hash -// - for (int i = 0; i < iovN; i++) - {if (1 != SHA256_Update(&sha256, iovP[i].iov_base, iovP[i].iov_len)) - return false; - } - -// Compute final hash and return result -// - return (1 == SHA256_Final(hBuff, &sha256)); -} - -/******************************************************************************/ -/* Private: S c r e e n */ -/******************************************************************************/ - -bool XrdSecProtect::Screen(ClientRequest &thereq) -{ - static const int rwOpen = kXR_delete|kXR_new|kXR_open_apnd|kXR_open_updt; - - kXR_unt16 reqCode = ntohs(thereq.header.requestid); - char theLvl; - -// Validate the request code. Invalid codes are never secured -// - if (reqCode < kXR_auth || reqCode >= kXR_REQFENCE || !secVec) return false; - -// Get the security level -// - theLvl = secVec[reqCode-kXR_auth]; - -// If we need not secure this or we definitely do then return result -// - if (theLvl == kXR_signIgnore) return false; - if (theLvl != kXR_signLikely) return true; - -// Security is conditional based on open() trying to modify something. -// - if (reqCode == kXR_open) - {kXR_int16 opts = ntohs(thereq.open.options); - return (opts & rwOpen) != 0; - } - -// Security is conditional based on query() trying to modify something. -// - if (reqCode == kXR_query) - {short qopt = (short)ntohs(thereq.query.infotype); - switch(qopt) - {case kXR_QStats: return false; - case kXR_Qcksum: return false; - case kXR_Qckscan: return false; - case kXR_Qconfig: return false; - case kXR_Qspace: return false; - case kXR_Qxattr: return false; - case kXR_Qopaque: - case kXR_Qopaquf: return true; - case kXR_Qopaqug: return true; - default: return false; - } - } - -// Security is conditional based on set() trying to modify something. -// - if (reqCode == kXR_set) return thereq.set.modifier != 0; - -// At this point we force security as we don't understand this code -// - return true; -} - -/******************************************************************************/ -/* S e c u r e */ -/******************************************************************************/ - -int XrdSecProtect::Secure(SecurityRequest *&newreq, - ClientRequest &thereq, - const char *thedata) -{ - static const ClientSigverRequest initSigVer = {{0,0}, htons(kXR_sigver), - 0, kXR_secver_0, 0, 0, - kXR_SHA256, {0, 0, 0}, 0 - }; - struct buffHold {XrdSecReq *P; - XrdSecBuffer *bP; - buffHold() : P(0), bP(0) {} - ~buffHold() {if (P) free(P); if (bP) delete bP;} - }; - static const int iovNum = 3; - struct iovec iov[iovNum]; - buffHold myReq; - kXR_unt64 mySeq; - const char *sigBuff, *payload = thedata; - unsigned char secHash[SHA256_DIGEST_LENGTH]; - int sigSize, n, newSize, rc, paysize = 0; - bool nodata = false; - -// Generate a new sequence number -// - mySeq = nextSeqno++; - mySeq = htonll(mySeq); - -// Determine if we are going to sign the payload and its location -// - if (thereq.header.dlen) - {kXR_unt16 reqid = htons(thereq.header.requestid); - paysize = ntohl(thereq.header.dlen); - if (!payload) payload = ((char *)&thereq) + sizeof(ClientRequest); - if (reqid == kXR_write || reqid == kXR_verifyw) n = (secVerData ? 3 : 2); - else n = 3; - } else n = 2; - -// Fill out the iovec -// - iov[0].iov_base = (char *)&mySeq; - iov[0].iov_len = sizeof(mySeq); - iov[1].iov_base = (char *)&thereq; - iov[1].iov_len = sizeof(ClientRequest); - if (n < 3) nodata = true; - else {iov[2].iov_base = (char *)payload; - iov[2].iov_len = paysize; - } - -// Compute the hash -// - if (!GetSHA2(secHash, iov, n)) return -EDOM; - -// Now encrypt the hash -// - if (edOK) - {rc = authProt->Encrypt((const char *)secHash,sizeof(secHash),&myReq.bP); - if (rc < 0) return rc; - sigSize = myReq.bP->size; - sigBuff = myReq.bP->buffer; - } else { - sigSize = sizeof(secHash); - sigBuff = (char *)secHash; - } - -// Allocate a new request object -// - newSize = sizeof(SecurityRequest) + sigSize; - myReq.P = (XrdSecReq *)malloc(newSize); - if (!myReq.P) return -ENOMEM; - -// Setup the security request (we only support signing) -// - memcpy(&(myReq.P->secReq), &initSigVer, sizeof(ClientSigverRequest)); - memcpy(&(myReq.P->secReq.header.streamid ), thereq.header.streamid, - sizeof(myReq.P->secReq.header.streamid)); - memcpy(&(myReq.P->secReq.sigver.expectrid),&thereq.header.requestid, - sizeof(myReq.P->secReq.sigver.expectrid)); - myReq.P->secReq.sigver.seqno = mySeq; - if (nodata) myReq.P->secReq.sigver.flags |= kXR_nodata; - myReq.P->secReq.sigver.dlen = htonl(sigSize); - -// Append the signature to the request -// - memcpy(&(myReq.P->secSig), sigBuff, sigSize); - -// Return pointer to he security request and its size -// - newreq = &(myReq.P->secReq); myReq.P = 0; - return newSize; -} - -/******************************************************************************/ -/* Private: S e t P r o t e c t i o n */ -/******************************************************************************/ - -void XrdSecProtect::SetProtection(const ServerResponseReqs_Protocol &inReqs) -{ - unsigned int lvl, vsz; - -// Check for no security, the simlplest case -// - if (inReqs.secvsz == 0 && inReqs.seclvl == 0) - {memset(&myReqs, 0, sizeof(myReqs)); - secVec = 0; - secVerData = false; - return; - } - -// Precheck the security level -// - lvl = inReqs.seclvl; - if (lvl > kXR_secPedantic) lvl = kXR_secPedantic; - -// Perform the default setup (the usual case) -// - secVec = secTable.Vec[lvl-1]; - myReqs.seclvl = lvl; - myReqs.secvsz = 0; - myReqs.secver = kXR_secver_0; - myReqs.secopt = inReqs.secopt; - -// Set options -// - secVerData = (inReqs.secopt & kXR_secOData) != 0; - -// Create a modified vectr if there are overrides -// - if (inReqs.secvsz != 0) - {const ServerResponseSVec_Protocol *urVec = &inReqs.secvec; - memcpy(myVec, secVec, maxRIX); - vsz = inReqs.secvsz; - for (unsigned int i = 0; i < vsz; i++, urVec++) - {if (urVec->reqindx < maxRIX) - {if (urVec->reqsreq > kXR_signNeeded) - myVec[urVec->reqindx] = kXR_signNeeded; - else myVec[urVec->reqindx] = urVec->reqsreq; - } - } - secVec = myVec; - } -} - -/******************************************************************************/ -/* V e r i f y */ -/******************************************************************************/ - -const char *XrdSecProtect::Verify(SecurityRequest &secreq, - ClientRequest &thereq, - const char *thedata - ) -{ - struct buffHold {XrdSecBuffer *bP; - buffHold() : bP(0) {} - ~buffHold() {if (bP) delete bP;} - }; - static const int iovNum = 3; - struct iovec iov[iovNum]; - buffHold myReq; - unsigned char *inHash, secHash[SHA256_DIGEST_LENGTH]; - int dlen, n, rc; - -// First check for replay attacks. The incomming sequence number must be greater -// the previous one we have seen. Since it is in network byte order we can use -// a simple byte for byte compare (no need for byte swapping). -// - if (memcmp(&lastSeqno, &secreq.sigver.seqno, sizeof(lastSeqno)) >= 0) - return "Incorrect signature sequence"; - -// Do basic verification for this request -// - if (memcmp(secreq.header.streamid, thereq.header.streamid, - sizeof(secreq.header.streamid))) - return "Signature streamid mismatch"; - if (secreq.sigver.expectrid != thereq.header.requestid) - return "Signature requestid mismatch"; - if (secreq.sigver.version != kXR_secver_0) - return "Unsupported signature version"; - if ((secreq.sigver.crypto & kXR_HashMask) != kXR_SHA256) - return "Unsupported signature hash"; - if (secreq.sigver.crypto & kXR_rsaKey) - return "Unsupported signature key"; - -// Now get the hash information -// - dlen = ntohl(secreq.header.dlen); - inHash = ((unsigned char *)&secreq)+sizeof(SecurityRequest); - -// Now decrypt the hash -// - if (edOK) - {rc = authProt->Decrypt((const char *)inHash, dlen, &myReq.bP); - if (rc < 0) return strerror(-rc); - if (myReq.bP->size != (int)sizeof(secHash)) - return "Invalid signature hash length"; - inHash = (unsigned char *)myReq.bP->buffer; - } else { - if (dlen != (int)sizeof(secHash)) - return "Invalid signature hash length"; - } - -// Fill out the iovec to recompute the hash -// - iov[0].iov_base = (char *)&secreq.sigver.seqno; - iov[0].iov_len = sizeof(secreq.sigver.seqno); - iov[1].iov_base = (char *)&thereq; - iov[1].iov_len = sizeof(ClientRequest); - if (thereq.header.dlen == 0 || secreq.sigver.flags & kXR_nodata) n = 2; - else {iov[2].iov_base = (char *)thedata; - iov[2].iov_len = ntohl(thereq.header.dlen); - n = 3; - } - -// Compute the hash -// - if (!GetSHA2(secHash, iov, n)) - return "Signature hash computation failed"; - -// Compare this hash with the hash we were given -// - if (memcmp(secHash, inHash, sizeof(secHash))) - return "Signature hash mismatch"; - -// This request has been verified (update the seqno) -// - lastSeqno = secreq.sigver.seqno; - return 0; -} diff --git a/src/XrdSec/XrdSecProtect.hh b/src/XrdSec/XrdSecProtect.hh deleted file mode 100644 index 4eb5c248be8..00000000000 --- a/src/XrdSec/XrdSecProtect.hh +++ /dev/null @@ -1,168 +0,0 @@ -#ifndef __XRDSECPROTECT_H__ -#define __XRDSECPROTECT_H__ -/******************************************************************************/ -/* */ -/* X r d S e c P r o t e c t . h h */ -/* */ -/* (c) 2016 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XProtocol/XProtocol.hh" - -//------------------------------------------------------------------------------ -//! This class implements the XRootD protocol security protection. -//------------------------------------------------------------------------------ -//------------------------------------------------------------------------------ -//! Provide a replacement for the std::invoke() function available in C++17 to -//! invoke the Need2Secure member function without a vtable lookup. The -//! calling convention is: if (NEED2SECURE())() and where: -//! is a pointer to the relevant XrdSecProtect object (may be nil). -//! is a reference to the ClientRequest object to be inspected. -//------------------------------------------------------------------------------ - -#define NEED2SECURE(protP) protP && ((*protP).*(protP->Need2Secure)) - -/******************************************************************************/ -/* X r d S e c P r o t e c t */ -/******************************************************************************/ - -struct iovec; -class XrdSecProtectParms; -class XrdSecProtocol; - -class XrdSecProtect -{ -public: -friend class XrdSecProtector; - -//------------------------------------------------------------------------------ -//! Delete this object. Use this method as opposed to operator delete. -//------------------------------------------------------------------------------ - -virtual void Delete() {delete this;} - -//------------------------------------------------------------------------------ -//! Test whether or not a request needs to be secured. This method pointer -//! should only be invoked via the NEED2SECURE macro (see above). -//! -//! @param thereq Reference to the request header/body in network byte order. -//! -//! @return false - request need not be secured (equals false). -//! @return true - request needs to be secured. -//------------------------------------------------------------------------------ - - bool (XrdSecProtect::*Need2Secure)(ClientRequest &thereq); - -//------------------------------------------------------------------------------ -//! Secure a request. -//! -//! Request securement is optional and this call should be gaurded by an if -//! statement to avoid securing requests that need not be secured as follows: -//! -//! if (NEED2SECURE()(thereq)) result = ->Secure(....); -//! else result = 0; -//! -//! Modify the above to your particuar needs but gaurd the call! -//! -//! @param newreq A reference to a pointer where the new request, if needed, -//! will be placed. The new request will consist of a either a -//! kXR_sigver or kXR_decrypt request followed by hash if the -//! request is kXR_sigver. The request buffer must be freed -//! using free() when it is no longer needed. -//! @param thereq Reference to the client request header/body that needs to -//! be secured. The request must be in network byte order. -//! @aparam thedata The request data whose length resides in theReq.dlen. If -//! thedata is nil but thereq.dlen is not zero then the request -//! data must follow the request header in the thereq buffer. -//! -//! @return <0 An error occurred and the return value is -errno. -//! @return >0 The length of the new request whose pointer is in newreq. -//! This is the nuber of bytes that must be sent. -//------------------------------------------------------------------------------ - -virtual int Secure(SecurityRequest *&newreq, - ClientRequest &thereq, - const char *thedata - ); - -//------------------------------------------------------------------------------ -//! Verify that a request was properly secured. -//! -//! @param secreq A reference to the security request (kxr_sigver or -//! kXR_decrypt) followed by whatever data was sent (normally -//! an encrypted verification hash for kXR_sigver). All but -//! the request code must be in network byte order. -//! @param thereq Reference to the client request header/body that needs to -//! be verified. The request must be in network byte order. -//! @aparam thedata The request data whose length resides in theReq.dlen. -//! -//! @return Upon success zero is returned. Otherwise a pointer to a null -//! delimited string describing the problem is returned. -//------------------------------------------------------------------------------ - -virtual const char *Verify(SecurityRequest &secreq, - ClientRequest &thereq, - const char *thedata - ); - -//------------------------------------------------------------------------------ -//! Destructor -//------------------------------------------------------------------------------ - -virtual ~XrdSecProtect() {} - -protected: - - XrdSecProtect(XrdSecProtocol *aprot=0, bool edok=true) // Client! - : Need2Secure(&XrdSecProtect::Screen), - authProt(aprot), secVec(0), lastSeqno(1), - edOK(edok), secVerData(false) - {} - - XrdSecProtect(XrdSecProtocol *aprot, XrdSecProtect &pRef, // Server! - bool edok=true) - : Need2Secure(&XrdSecProtect::Screen), - authProt(aprot), secVec(pRef.secVec), - lastSeqno(0), edOK(edok), - secVerData(pRef.secVerData) {} - -void SetProtection(const ServerResponseReqs_Protocol &inReqs); - -private: -bool GetSHA2(unsigned char *hBuff, struct iovec *iovP, int iovN); -bool Screen(ClientRequest &thereq); - -XrdSecProtocol *authProt; -const char *secVec; -ServerResponseReqs_Protocol myReqs; -union {kXR_unt64 lastSeqno; // Used by Secure() - kXR_unt64 nextSeqno; // Used by Verify() - }; -bool edOK; -bool secVerData; -static const unsigned int maxRIX = kXR_REQFENCE-kXR_auth; -char myVec[maxRIX]; -}; -#endif diff --git a/src/XrdSec/XrdSecProtector.cc b/src/XrdSec/XrdSecProtector.cc deleted file mode 100644 index 77107ac7ef1..00000000000 --- a/src/XrdSec/XrdSecProtector.cc +++ /dev/null @@ -1,312 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S e c P r o t e c t o r . c c */ -/* */ -/* (c) 2016 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include - -#include "XrdVersion.hh" - -#include "XrdNet/XrdNetIF.hh" -#include "XrdSec/XrdSecInterface.hh" -#include "XrdSec/XrdSecProtect.hh" -#include "XrdSec/XrdSecProtector.hh" -#include "XrdSys/XrdSysError.hh" - -/******************************************************************************/ -/* L i b r a r y L i n k a g e */ -/******************************************************************************/ - -namespace -{ -class protoProtector : public XrdSecProtector -{ -public: - protoProtector() {} - ~protoProtector() {} -}; - -protoProtector baseProtector; -} - -XrdSecProtector *XrdSecProtObjectP = &baseProtector; - -XrdVERSIONINFO(XrdSecProtObjectP,"secProt"); - -/******************************************************************************/ -/* S e r v e r - S i d e C o n f i g u r a t i o n */ -/******************************************************************************/ - -namespace -{ -struct ProtInfo {XrdSecProtect *theProt; - ServerResponseReqs_Protocol reqs; - bool relaxed; - bool force; - ProtInfo() : theProt(0), relaxed(false), force(false) - {reqs.theTag = 'S'; - reqs.rsvd = 0; - reqs.secver = kXR_secver_0; - reqs.secopt = 0; - reqs.seclvl = kXR_secNone; - reqs.secvsz = 0; - } - } lrTab[XrdSecProtector::isLR]; - -bool lrSame = true; -bool noProt = true; -} - -/******************************************************************************/ -/* S e r v e r - S i d e E r r o r M e s s a g e R o u t i n g */ -/******************************************************************************/ - -namespace -{ -XrdSysError Say(0, "sec_"); -} - -/******************************************************************************/ -/* C o n f i g */ -/******************************************************************************/ - -bool XrdSecProtector::Config(const XrdSecProtectParms &lclParms, - const XrdSecProtectParms &rmtParms, - XrdSysLogger &logr) -{ - -// Set the logger right off -// - Say.logger(&logr); - -// Setup local protection -// - if (lclParms.level != XrdSecProtectParms::secNone) - {Config(lclParms, lrTab[isLcl].reqs); - lrTab[isLcl].theProt = new XrdSecProtect; - lrTab[isLcl].theProt->SetProtection(lrTab[isLcl].reqs); - } - -// Setup remote protection (check for reuse of local protection) -// - if (rmtParms.level == lclParms.level) - {lrTab[isRmt] = lrTab[isLcl]; - lrSame = true; - } else { - lrSame = false; - if (rmtParms.level != XrdSecProtectParms::secNone) - {Config(rmtParms, lrTab[isRmt].reqs); - lrTab[isRmt].theProt = new XrdSecProtect; - lrTab[isRmt].theProt->SetProtection(lrTab[isRmt].reqs); - } - } - -// Record relax flags -// - lrTab[isLcl].relaxed = (lclParms.opts & XrdSecProtectParms::relax) != 0; - lrTab[isLcl].force = (lclParms.opts & XrdSecProtectParms::force) != 0; - lrTab[isRmt].relaxed = (rmtParms.opts & XrdSecProtectParms::relax) != 0; - lrTab[isRmt].force = (rmtParms.opts & XrdSecProtectParms::force) != 0; - -// Setup shortcut flag -// - noProt = (lrTab[isLcl].theProt == 0) && (lrTab[isRmt].theProt == 0); - -// All done -// - return true; -} - -/******************************************************************************/ - -void XrdSecProtector::Config(const XrdSecProtectParms &parms, - ServerResponseReqs_Protocol &reqs) -{ - unsigned int lvl; - -// Setup options -// - if ((parms.opts & XrdSecProtectParms::doData) != 0) - reqs.secopt |= kXR_secOData; - if ((parms.opts & XrdSecProtectParms::force) != 0) - reqs.secopt |= kXR_secOFrce; - -// Setup level -// - switch(parms.level) - {case XrdSecProtectParms::secCompatible: lvl = kXR_secCompatible; - break; - case XrdSecProtectParms::secStandard: lvl = kXR_secStandard; - break; - case XrdSecProtectParms::secIntense: lvl = kXR_secIntense; - break; - case XrdSecProtectParms::secPedantic: lvl = kXR_secPedantic; - break; - default: lvl = kXR_secNone; - break; - } - reqs.seclvl = lvl; -} - -/******************************************************************************/ -/* L N a m e */ -/******************************************************************************/ - -const char *XrdSecProtector::LName(XrdSecProtectParms::secLevel level) -{ - static const char *lvlVec[] = {"none", "compatible", "standard", - "intense", "pedantic"}; - -// Validate the level -// - if (level < XrdSecProtectParms::secNone) level = XrdSecProtectParms::secNone; - else if (level > XrdSecProtectParms::secPedantic) - level = XrdSecProtectParms::secPedantic; - -// Return the level name -// - return lvlVec[level]; -} - -/******************************************************************************/ -/* N e w 4 C l i e n t */ -/******************************************************************************/ - -XrdSecProtect *XrdSecProtector::New4Client(XrdSecProtocol &aprot, - const ServerResponseReqs_Protocol &inReqs, - unsigned int reqLen) -{ - static const unsigned int hdrLen = sizeof(ServerResponseBody_Protocol) - - sizeof(ServerResponseSVec_Protocol); - XrdSecProtect *secP; - unsigned int vLen = static_cast(inReqs.secvsz) - * sizeof(ServerResponseSVec_Protocol); - bool okED; - -// Validate the incomming struct (if it's bad skip the security) and that any -// security is actually wanted. -// - if (vLen+hdrLen > reqLen - || (inReqs.secvsz == 0 && inReqs.seclvl == kXR_secNone)) return 0; - -// If the auth protocol doesn't support encryption, see if we still need to -// send off signed requests (mostly for testng) -// - okED = aprot.getKey() > 0; - if (!okED && (inReqs.secopt & kXR_secOFrce) == 0) return 0; - -// Get a new security object and set its security level -// - secP = new XrdSecProtect(&aprot, okED); - secP->SetProtection(inReqs); - -// All done -// - return secP; -} - -/******************************************************************************/ -/* N e w 4 S e r v e r */ -/******************************************************************************/ - -XrdSecProtect *XrdSecProtector::New4Server(XrdSecProtocol &aprot, int plvl) -{ - static const char *wFrc = "authentication can't encrypt; " - "continuing without it!"; - static const char *wIgn = "authentication can't encrypt; " - "allowing unsigned requests!"; - XrdSecProtect *secP; - lrType theLR; - bool okED; - -// Check if we need any security at all -// - if (noProt) return 0; - -// Now we need to see whether this is local or remote of if it matters -// - if (lrSame) theLR = isLcl; - else theLR = (XrdNetIF::InDomain(aprot.Entity.addrInfo) ? isLcl : isRmt); - -// Now check again, as may not need any protection for the domain -// - if (lrTab[theLR].theProt == 0) return 0; - -// Check for relaxed processing -// - if (plvl < kXR_PROTSIGNVERSION && lrTab[theLR].relaxed) return 0; - -// Check if protocol supports encryption -// - okED = aprot.getKey() > 0; - if (!okED) - {char pName[XrdSecPROTOIDSIZE+1]; - const char *action; - strncpy(pName, aprot.Entity.prot, XrdSecPROTOIDSIZE); - pName[XrdSecPROTOIDSIZE] = 0; - action = (lrTab[theLR].force ? wFrc : wIgn); - Say.Emsg("Protect", aprot.Entity.tident, pName, action); - if (!lrTab[theLR].force) return 0; - } - -// Get a new security object and make it a clone of this right one -// - secP = new XrdSecProtect(&aprot, *lrTab[theLR].theProt, okED); - -// All done -// - return secP; -} - -/******************************************************************************/ -/* P r o t R e s p */ -/******************************************************************************/ - -int XrdSecProtector::ProtResp(ServerResponseReqs_Protocol &resp, - XrdNetAddrInfo &nai, int pver) -{ - static const int rsplen = sizeof(ServerResponseReqs_Protocol) - - sizeof(ServerResponseSVec_Protocol); - ServerResponseReqs_Protocol *myResp; - -// Check if we need any response at all -// - if (noProt) return 0; - -// Get the right response -// - if (lrSame || XrdNetIF::InDomain(&nai)) myResp = &lrTab[isLcl].reqs; - else myResp = &lrTab[isRmt].reqs; - -// Return result -// - memcpy(&resp, myResp, rsplen); - return rsplen; -} diff --git a/src/XrdSec/XrdSecProtector.hh b/src/XrdSec/XrdSecProtector.hh deleted file mode 100644 index 7ef5205da17..00000000000 --- a/src/XrdSec/XrdSecProtector.hh +++ /dev/null @@ -1,162 +0,0 @@ -#ifndef __XRDSECPROTECTOR_H__ -#define __XRDSECPROTECTOR_H__ -/******************************************************************************/ -/* */ -/* X r d S e c P r o t e c t o r . h h */ -/* */ -/* (c) 2016 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XProtocol/XPtypes.hh" - -/******************************************************************************/ -/* X r d S e c P r o t e c t P a r m s */ -/******************************************************************************/ - -class XrdSecProtectParms -{ -public: - -enum secLevel {secNone = 0, - secCompatible, secStandard, secIntense, secPedantic, - secFence - }; - -secLevel level; //!< In: The desired level. -int opts; //!< In: Options: - -static const int doData = 0x0000001; //!< Secure data -static const int relax = 0x0000002; //!< relax old clients -static const int force = 0x0000004; //!< Allow unencryted hash - - XrdSecProtectParms() : level(secNone), opts(0) {} - ~XrdSecProtectParms() {} -}; - -/******************************************************************************/ -/* X r d S e c P r o t e c t o r */ -/******************************************************************************/ - -//------------------------------------------------------------------------------ -//! The XrdSecProtector manages the XrdSecProtect objects. -//------------------------------------------------------------------------------ - -struct ServerResponseReqs_Protocol; -class XrdNetAddrInfo; -class XrdSecProtect; -class XrdSecProtocol; -class XrdSysLogger; - -class XrdSecProtector -{ -public: - -//------------------------------------------------------------------------------ -//! Configure protect for server-side use (not need for client) -//! -//! @param lclParms Reference to local client parameters. -//! @param rmtParms Reference to remote client parameters. -//! @param logr Reference to the message logging object. -//! -//! @return true upon success and false upon failure. -//------------------------------------------------------------------------------ - -virtual bool Config(const XrdSecProtectParms &lclParms, - const XrdSecProtectParms &rmtParms, - XrdSysLogger &logr); - -//------------------------------------------------------------------------------ -//! Convert protection level to its corresponding name. -//! -//! @param level The level value. -//! -//! @return Pointer to the name of the level. -//------------------------------------------------------------------------------ - -virtual const char *LName(XrdSecProtectParms::secLevel level); - -//------------------------------------------------------------------------------ -//! Obtain a new instance of a protection object based on protocol response. -//! This is meant to be used client-side. -//! -//! @param aprot Sets the authentication protocol used and is the protocol -//! used to secure requests. It must be supplied. Security is -//! meaningless unless successful authentication has occured. -//! @param inReqs Reference to the security information returned in the -//! kXR_protocol request. -//! @param reqLen The actual length of inReqs (is validated). -//! -//! @return Pointer to a security object upon success and nil if security is -//! not needed. -//------------------------------------------------------------------------------ - -virtual XrdSecProtect *New4Client( XrdSecProtocol &aprot, - const ServerResponseReqs_Protocol &inReqs, - unsigned int reqLen); - -//------------------------------------------------------------------------------ -//! Obtain a new instance of a security object based on security setting for -//! this object. This is meant to be used severt-side. -//! -//! @param aprot Sets the authentication protocol used and is the protocol -//! used to secure requests. It must be supplied. -//! @param plvl The client's protocol level. -//! -//! @return Pointer to a security object upon success and nil if security is -//! not needed. -//------------------------------------------------------------------------------ - -virtual XrdSecProtect *New4Server(XrdSecProtocol &aprot, int plvl); - -//------------------------------------------------------------------------------ -//! Obtain the proper kXR_protocol response (server-side only) -//! -//! @param resp Reference to the place where the response is to be placed. -//! @param nai Reference to the client's network address. -//! @param pver Client's protocol version in host byte order. -//! -//! @return The length of the protocol response security information. -//------------------------------------------------------------------------------ - -virtual int ProtResp(ServerResponseReqs_Protocol &resp, - XrdNetAddrInfo &nai, int pver); - -//------------------------------------------------------------------------------ -//! Destructor -//------------------------------------------------------------------------------ - -virtual ~XrdSecProtector() {} - -enum lrType {isLcl=0, isRmt=1, isLR=2}; - -protected: - - XrdSecProtector() {} - -private: -void Config(const XrdSecProtectParms &parms, - ServerResponseReqs_Protocol &reqs); -}; -#endif diff --git a/src/XrdSec/XrdSecProtocolhost.cc b/src/XrdSec/XrdSecProtocolhost.cc deleted file mode 100644 index 7b6a22fd449..00000000000 --- a/src/XrdSec/XrdSecProtocolhost.cc +++ /dev/null @@ -1,88 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S e c P r o t o c o l h o s t . c c */ -/* */ -/* (c) 2005 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -#include "XrdSec/XrdSecProtocolhost.hh" - -/******************************************************************************/ -/* A u t h e n t i c a t e */ -/******************************************************************************/ - -int XrdSecProtocolhost::Authenticate(XrdSecCredentials *cred, - XrdSecParameters **parms, - XrdOucErrInfo *einfo) -{ - strcpy(Entity.prot, "host"); - Entity.host = theHost; - Entity.addrInfo = &epAddr; - return 0; -} - -/******************************************************************************/ -/* g e t C r e d e n t i a l s */ -/******************************************************************************/ - -XrdSecCredentials *XrdSecProtocolhost::getCredentials(XrdSecParameters *parm, - XrdOucErrInfo *einfo) -{XrdSecCredentials *cp = new XrdSecCredentials; - - cp->size = 5; cp->buffer = (char *)"host"; - return cp; -} - -/******************************************************************************/ -/* X r d S e c P r o t o c o l h o s t I n i t */ -/******************************************************************************/ - -// This is a builtin protocol so we don't define an Init method. Anyway, this -// protocol need not be initialized. It works as is. - -/******************************************************************************/ -/* X r d S e c P r o t o c o l h o s t O b j e c t */ -/******************************************************************************/ - -// Normally this would be defined as an extern "C", however, this function is -// statically linked into the shared library as a native protocol so there is -// no reason to define it as such. Imitators, beware! Read the comments in -// XrdSecInterface.hh -// -XrdSecProtocol *XrdSecProtocolhostObject(const char who, - const char *hostname, - XrdNetAddrInfo &endPoint, - const char *parms, - XrdOucErrInfo *einfo) -{ - -// Simply return an instance of the host protocol object -// - return new XrdSecProtocolhost(hostname, endPoint); -} diff --git a/src/XrdSec/XrdSecProtocolhost.hh b/src/XrdSec/XrdSecProtocolhost.hh deleted file mode 100644 index 8796cc0b8ef..00000000000 --- a/src/XrdSec/XrdSecProtocolhost.hh +++ /dev/null @@ -1,67 +0,0 @@ -#ifndef __SEC_PROTOCOL_HOST_H__ -#define __SEC_PROTOCOL_HOST_H__ -/******************************************************************************/ -/* */ -/* X r d S e c P r o t o c o l h o s t . h h */ -/* */ -/* (c) 2003 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -#include "XrdNet/XrdNetAddrInfo.hh" -#include "XrdSec/XrdSecInterface.hh" - -class XrdSecProtocolhost : public XrdSecProtocol -{ -public: - - int Authenticate (XrdSecCredentials *cred, - XrdSecParameters **parms, - XrdOucErrInfo *einfo=0); - - XrdSecCredentials *getCredentials(XrdSecParameters *parm=0, - XrdOucErrInfo *einfo=0); - - const char *getParms(int &psize, const char *hname=0) - {psize = 5; return "host";} - - -void Delete() {delete this;} - - XrdSecProtocolhost(const char *host, XrdNetAddrInfo &endPoint) - : XrdSecProtocol("host") - {theHost = strdup(host); - epAddr = endPoint; - } - ~XrdSecProtocolhost() {if (theHost) free(theHost);} -private: - -XrdNetAddrInfo epAddr; -char *theHost; -}; -#endif diff --git a/src/XrdSec/XrdSecServer.cc b/src/XrdSec/XrdSecServer.cc deleted file mode 100644 index 9102b63b425..00000000000 --- a/src/XrdSec/XrdSecServer.cc +++ /dev/null @@ -1,1066 +0,0 @@ -/***************************************************************************/ -/* */ -/* X r d S e c S e r v e r . c c */ -/* */ -/* (c) 2005 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "XrdVersion.hh" - -#include "XrdSys/XrdSysLogger.hh" -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdSys/XrdSysError.hh" -#include "XrdOuc/XrdOucEnv.hh" -#include "XrdOuc/XrdOucErrInfo.hh" -#include "XrdNet/XrdNetAddr.hh" - -#include "XrdSec/XrdSecInterface.hh" -#include "XrdSec/XrdSecProtector.hh" -#include "XrdSec/XrdSecServer.hh" -#include "XrdSec/XrdSecTrace.hh" - -/******************************************************************************/ -/* S e c u r i t y L e v e l s */ -/******************************************************************************/ - -namespace -{ -XrdSecProtectParms lclParms; -XrdSecProtectParms rmtParms; -} - -/******************************************************************************/ -/* X r d S e c P r o t B i n d */ -/******************************************************************************/ - -class XrdSecProtBind -{ -public: -XrdSecProtBind *next; -char *thost; -int tpfxlen; -char *thostsfx; -int tsfxlen; -XrdSecParameters SecToken; -XrdSecPMask_t ValidProts; - -XrdSecProtBind *Find(const char *hname); - -int Match(const char *hname); - - XrdSecProtBind(char *th, char *st, XrdSecPMask_t pmask=0); - ~XrdSecProtBind() - {free(thost); - if (SecToken.buffer) free(SecToken.buffer); - } -}; - -/******************************************************************************/ -/* C o n s t r u c t o r */ -/******************************************************************************/ - -XrdSecProtBind::XrdSecProtBind(char *th, char *st, XrdSecPMask_t pmask) -{ - char *starp; - next = 0; - thost = th; - if (!(starp = index(thost, '*'))) - {tsfxlen = -1; - thostsfx = (char *)0; - tpfxlen = 0; - } else { - *starp = '\0'; - tpfxlen = strlen(thost); - thostsfx = starp+1; - tsfxlen = strlen(thostsfx); - } - if (st) {SecToken.buffer = strdup(st); SecToken.size = strlen(st);} - else {SecToken.buffer = 0; SecToken.size = 0;} - ValidProts = (pmask ? pmask : ~(XrdSecPMask_t)0); -} - -/******************************************************************************/ -/* F i n d */ -/******************************************************************************/ - -XrdSecProtBind *XrdSecProtBind::Find(const char *hname) -{ - XrdSecProtBind *bp = this; - - while(bp && !bp->Match(hname)) bp = bp->next; - - return bp; -} - -/******************************************************************************/ -/* M a t c h */ -/******************************************************************************/ - -int XrdSecProtBind::Match(const char *hname) -{ - int i; - -// If an exact match wanted, return the result -// - if (tsfxlen < 0) return !strcmp(thost, hname); - -// Try to match the prefix -// - if (tpfxlen && strncmp(thost, hname, tpfxlen)) return 0; - -// If no suffix matching is wanted, then we have succeeded -// - if (!(thostsfx)) return 1; - -// Try to match the suffix -// - if ((i = (strlen(hname) - tsfxlen)) < 0) return 0; - return !strcmp(&hname[i], thostsfx); -} - -/******************************************************************************/ -/* X r d S e c P r o t P a r m */ -/******************************************************************************/ - -class XrdSecProtParm -{ -public: - - void Add() {Next = First; First = this;} - - int Cat(char *token); - -static XrdSecProtParm *Find(char *pid, int remove=0); - - int Insert(char oct); - - int isProto(char *proto) {return !strcmp(ProtoID, proto);} - - char *Result(int &size) {size = bp-buff; return buff;} - - void setProt(char *pid) {strcpy(ProtoID, pid);} - -static XrdSecProtParm *First; - XrdSecProtParm *Next; - -char ProtoID[XrdSecPROTOIDSIZE+1]; - - XrdSecProtParm(XrdSysError *erp, const char *cid) : who(cid) - {*ProtoID = '\0'; - bsize = 4096; - buff = (char *)malloc(bsize); - *buff = '\0'; - bp = buff; - eDest = erp; - Next = 0; - } - ~XrdSecProtParm() {free(buff);} -private: - -XrdSysError *eDest; -int bsize; -char *buff; -char *bp; -const char *who; -}; - -XrdSecProtParm *XrdSecProtParm::First = 0; - -/******************************************************************************/ -/* C a t */ -/******************************************************************************/ - -int XrdSecProtParm::Cat(char *token) -{ - int alen; - alen = strlen(token); - if (alen+1 > bsize-(bp-buff)) - {eDest->Emsg("Config",who,ProtoID,"argument string too long"); - return 0; - } - *bp++ = ' '; - strcpy(bp, token); - bp += alen; - return 1; -} - -/******************************************************************************/ -/* F i n d */ -/******************************************************************************/ - -XrdSecProtParm *XrdSecProtParm::Find(char *pid, int remove) -{ - XrdSecProtParm *mp, *pp; - - mp = 0; pp = First; - while(pp && !pp->isProto(pid)){mp = pp; pp = pp->Next;} - if (pp && remove) - {if (mp) mp->Next = pp->Next; - else First = pp->Next; - } - return pp; -} - -/******************************************************************************/ -/* I n s e r t */ -/******************************************************************************/ - -int XrdSecProtParm::Insert(char oct) -{ - if (bsize-(bp-buff) < 1) - {eDest->Emsg("Config",who,ProtoID,"argument string too long"); - return 0; - } - *bp++ = oct; - return 1; -} - -/******************************************************************************/ -/* X r d S e c S e r v e r */ -/******************************************************************************/ -XrdSecPManager XrdSecServer::PManager; -/******************************************************************************/ -/* C o n s t r u c t o r */ -/******************************************************************************/ -XrdSecServer::XrdSecServer(XrdSysLogger *lp) : eDest(lp, "sec_") -{ - -// Set default values -// - PManager.setErrP(&eDest); - bpFirst = 0; - bpLast = 0; - bpDefault = 0; - STBlen = 4096; - STBuff = (char *)malloc(STBlen); - *STBuff = '\0'; - SToken = STBuff; - SecTrace = new XrdOucTrace(&eDest); - if (getenv("XRDDEBUG") || getenv("XrdSecDEBUG")) - {SecTrace->What = TRACE_ALL; - PManager.setDebug(1); - } - Enforce = 0; - implauth = 0; -} - -/******************************************************************************/ -/* g e t P a r m s */ -/******************************************************************************/ - -const char *XrdSecServer::getParms(int &size, XrdNetAddrInfo *endPoint) -{ - EPNAME("getParms") - XrdSecProtBind *bp; - char buff[256]; - -// Try to find a specific token binding for a host or return default binding -// - if (!endPoint || !bpFirst) bp = 0; - else {const char *hname = endPoint->Name("*unknown*"); - bp = bpFirst; - do {if (bp->Match(hname)) break;} while((bp = bp->next)); - } - -// Get endpoint info if we are debugging -// - if (endPoint && QTRACE(Debug)) - endPoint->Format(buff, sizeof(buff), XrdNetAddrInfo::fmtAuto, - XrdNetAddrInfo::noPort); - else *buff = 0; - -// If we have a binding, return that else return the default -// - if (!bp) bp = bpDefault; - if (bp->SecToken.buffer) - {DEBUG(buff <<" sectoken=" <SecToken.buffer); - size = bp->SecToken.size; - return bp->SecToken.buffer; - } - - DEBUG(buff <<" sectoken=''"); - size = 0; - return (const char *)0; -} - -/******************************************************************************/ -/* g e t P r o t o c o l */ -/******************************************************************************/ - -XrdSecProtocol *XrdSecServer::getProtocol(const char *host, - XrdNetAddrInfo &endPoint, - const XrdSecCredentials *cred, - XrdOucErrInfo *einfo) -{ - XrdSecProtBind *bp; - XrdSecPMask_t pnum; - XrdSecCredentials myCreds; - const char *msgv[8]; - -// If null credentials supplied, default to host protocol otherwise make sure -// credentials data is actually supplied. -// - if (!cred) {myCreds.buffer=(char *)"host"; myCreds.size = 4; cred=&myCreds;} - else if (cred->size < 1 || !(cred->buffer)) - {einfo->setErrInfo(EACCES, - (char *)"No authentication credentials supplied."); - return 0; - } - -// If protocol binding must be enforced, make sure the host is not using a -// disallowed protocol. -// - if (Enforce) - {if ((pnum = PManager.Find(cred->buffer))) - {if (bpFirst && (bp = bpFirst->Find(host)) - && !(bp->ValidProts & pnum)) - {msgv[0] = host; - msgv[1] = " not allowed to authenticate using "; - msgv[2] = cred->buffer; - msgv[3] = " protocol."; - einfo->setErrInfo(EACCES, msgv, 4); - return 0; - } - } - else {msgv[0] = cred->buffer; - msgv[1] = " security protocol is not supported."; - einfo->setErrInfo(EPROTONOSUPPORT, msgv, 2); - return 0; - } - } - -// If we passed the protocol binding check, try to get an instance of the -// protocol the host is using -// - return PManager.Get(host, endPoint, cred->buffer, einfo); -} - -/******************************************************************************/ -/* C o n f i g F i l e P r o c e s s i n g M e t h o d s */ -/******************************************************************************/ -/******************************************************************************/ -/* d e f i n e s */ -/******************************************************************************/ - -#define TS_Xeq(x,m) if (!strcmp(x,var)) return m(Config,Eroute); - -#define TS_Str(x,m) if (!strcmp(x,var)) {free(m); m = strdup(val); return 0;} - -#define TS_Chr(x,m) if (!strcmp(x,var)) {m = val[0]; return 0;} - -#define TS_Bit(x,m,v) if (!strcmp(x,var)) {m = v; return 0;} - -#define Max(x,y) (x > y ? x : y) - -/******************************************************************************/ -/* C o n f i g u r e */ -/******************************************************************************/ - -int XrdSecServer::Configure(const char *cfn) -/* - Function: Establish default values using a configuration file. - - Input: None. - - Output: 0 upon success or !0 otherwise. -*/ -{ - extern XrdSecProtector *XrdSecLoadProtection(XrdSysError &erP); - static const int isRlx = XrdSecProtectParms::relax; - static const int isFrc = XrdSecProtectParms::force; - XrdSecProtector *protObj; - const char *lName = "none", *rName = "none"; - char *var; - int NoGo; - -// Print warm-up message -// - eDest.Say("++++++ Authentication system initialization started."); - -// Perform initialization -// - NoGo = ConfigFile(cfn); - -// Almost done -// - var = (NoGo > 0 ? (char *)"failed." : (char *)"completed."); - eDest.Say("------ Authentication system initialization ", var); - -// No need to configure protect system if authentication failed -// - if (NoGo) return 1; - -// Put out another banner -// - eDest.Say("++++++ Protection system initialization started."); - -// If local level if greater than remote level, issue a warning -// - if (lclParms.level > rmtParms.level) - eDest.Say("Config warning: local protection level greater than " - "remote level; are you sure?"); - -// Check if we need to initialize protection services -// - if (lclParms.level == XrdSecProtectParms::secNone - && rmtParms.level == XrdSecProtectParms::secNone) - {eDest.Say("Config warning: Security level is set to none; " - "request protection disabled!"); - } else { - if (!(protObj = XrdSecLoadProtection(eDest)) - || !(protObj->Config(lclParms, rmtParms, *eDest.logger()))) NoGo = 1; - else {lName = protObj->LName(lclParms.level); - rName = protObj->LName(rmtParms.level); - } - } - -// Blurt out what we have -// - if (!NoGo) - {eDest.Say("Config ","Local protection level: ", - (lclParms.opts & isRlx ? "relaxed " : 0), lName, - (lclParms.opts & isFrc ? " force" : 0)); - eDest.Say("Config ","Remote protection level: ", - (rmtParms.opts & isRlx ? "relaxed " : 0), rName, - (rmtParms.opts & isFrc ? " force" : 0)); - } - -// Now we are done -// - var = (NoGo > 0 ? (char *)"failed." : (char *)"completed."); - eDest.Say("------ Protection system initialization ", var); - return (NoGo > 0); -} - -/******************************************************************************/ -/* C o n f i g F i l e */ -/******************************************************************************/ - -int XrdSecServer::ConfigFile(const char *ConfigFN) -/* - Function: Establish default values using a configuration file. - - Input: None. - - Output: 1 - Initialization failed. - 0 - Initialization succeeded. -*/ -{ - char *var; - int cfgFD, retc, NoGo = 0, recs = 0; - XrdOucEnv myEnv; - XrdOucStream Config(&eDest, getenv("XRDINSTANCE"), &myEnv, "=====> "); - XrdSecProtParm *pp; - -// If there is no config file, return with the defaults sets. -// - if (!ConfigFN || !*ConfigFN) - {eDest.Emsg("Config", "Authentication configuration file not specified."); - return 1; - } - -// Try to open the configuration file. -// - if ( (cfgFD = open(ConfigFN, O_RDONLY, 0)) < 0) - {eDest.Emsg("Config", errno, "opening config file", ConfigFN); - return 1; - } - -// Now start reading records until eof. -// - Config.Attach(cfgFD); Config.Tabs(0); - while((var = Config.GetMyFirstWord())) - {if (!strncmp(var, "sec.", 4)) - {recs++; - if (ConfigXeq(var+4, Config, eDest)) {Config.Echo(); NoGo = 1;} - } - } - -// Now check if any errors occured during file i/o -// - if ((retc = Config.LastError())) - NoGo = eDest.Emsg("Config",-retc,"reading config file", ConfigFN); - else {char buff[128]; - snprintf(buff, sizeof(buff), - " %d authentication directives processed in ", recs); - eDest.Say("Config", buff, ConfigFN); - } - Config.Close(); - -// Determine whether we should initialize security -// - if (NoGo || ProtBind_Complete(eDest) ) NoGo = 1; - else if ((pp = XrdSecProtParm::First)) - {NoGo = 1; - while(pp) {eDest.Emsg("Config", "protparm", pp->ProtoID, - "does not have a matching protocol."); - pp = pp->Next; - } - } - -// All done -// - return NoGo; -} - -/******************************************************************************/ -/* P r i v a t e M e t h o d s */ -/******************************************************************************/ -/******************************************************************************/ -/* C o n f i g X e q */ -/******************************************************************************/ - -int XrdSecServer::ConfigXeq(char *var, XrdOucStream &Config, XrdSysError &Eroute) -{ - - // Fan out based on the variable - // - TS_Xeq("level", xlevel); - TS_Xeq("protbind", xpbind); - TS_Xeq("protocol", xprot); - TS_Xeq("protparm", xpparm); - TS_Xeq("trace", xtrace); - - // No match found, complain. - // - Eroute.Say("Config warning: ignoring unknown directive '",var,"'."); - Config.Echo(); - return 0; -} - -/******************************************************************************/ -/* x l e v e l */ -/******************************************************************************/ - -/* Function: xlevel - - Purpose: To parse the directive: level [] [relaxed] [force] - - all | local | remote - none | compatible | standard | intense | pedantic - - Output: 0 upon success or !0 upon failure. -*/ - -int XrdSecServer::xlevel(XrdOucStream &Config, XrdSysError &Eroute) -{ - struct lvltab {const char *lname; XrdSecProtectParms::secLevel lvl;} ltab[] = - {{"none", XrdSecProtectParms::secNone}, - {"compatible", XrdSecProtectParms::secCompatible}, - {"standard", XrdSecProtectParms::secStandard}, - {"intense", XrdSecProtectParms::secIntense}, - {"pedantic", XrdSecProtectParms::secPedantic} - }; - int i, numopts = sizeof(ltab)/sizeof(struct lvltab); - bool isLcl = true, isRmt = true, isSpec = false, isRlx = false, isFRC=false; - char *val; - -// Get the template host -// - val = Config.GetWord(); - if (!val || !val[0]) - {Eroute.Emsg("Config","level not specified"); return 1;} - -// Check for optional keyword -// - if (!strcmp(val, "all")) isSpec = true; - else if (!strcmp(val, "local")) {isSpec = true; isRmt = false;} - else if (!strcmp(val, "remote")){isSpec = true; isLcl = false;} - -// Check if we need another token -// - if (isSpec) - {val = Config.GetWord(); - if (!val || !val[0]) - {Eroute.Emsg("Config","level not specified"); return 1;} - } - -// Check for optional relaxed keyword -// - if (!strcmp(val, "relaxed")) - {isRlx = true; - val = Config.GetWord(); - if (!val || !val[0]) - {Eroute.Emsg("Config","level not specified"); return 1;} - } - -// Get the level -// - for (i = 0; i < numopts; i++) if (!strcmp(ltab[i].lname, val)) break; - if (i >= numopts) - {Eroute.Emsg("Config", "invalid level option -", val); return 1;} - -// Check for final keyword -// - val = Config.GetWord(); - if (val && val[0]) - {if (strcmp(val, "force")) - {Eroute.Emsg("Config","invalid level modifier - ", val); return 1;} - isFRC = true; - } - -// Set appropriate levels -// - if (isLcl) - {lclParms.level = ltab[i].lvl; - if (isRlx) lclParms.opts |= XrdSecProtectParms::relax; - else lclParms.opts &= ~XrdSecProtectParms::relax; - if (isFRC) lclParms.opts |= XrdSecProtectParms::force; - else lclParms.opts &= ~XrdSecProtectParms::force; - } - if (isRmt) - {rmtParms.level = ltab[i].lvl; - if (isRlx) rmtParms.opts |= XrdSecProtectParms::relax; - else rmtParms.opts &= ~XrdSecProtectParms::relax; - if (isFRC) rmtParms.opts |= XrdSecProtectParms::force; - else rmtParms.opts &= ~XrdSecProtectParms::force; - } - return 0; -} - -/******************************************************************************/ -/* x p b i n d */ -/******************************************************************************/ - -/* Function: xpbind - - Purpose: To parse the directive: protbind [none | [only] ] - - is a templated host name (e.g., bronco*.slac.stanford.edu) - are the protocols to be bound to the . A special - protocol, none, indicates that no token is to be passed. - - Output: 0 upon success or !0 upon failure. -*/ - -int XrdSecServer::xpbind(XrdOucStream &Config, XrdSysError &Eroute) -{ - EPNAME("xpbind") - char *val, *thost; - XrdSecProtBind *bnow; - char sectoken[4096], *secbuff = sectoken; - int isdflt = 0, only = 0, anyprot = 0, noprot = 0, phost = 0; - int sectlen = sizeof(sectoken)-1; - XrdSecPMask_t PMask = 0; - *secbuff = '\0'; - -// Get the template host -// - val = Config.GetWord(); - if (!val || !val[0]) - {Eroute.Emsg("Config","protbind host not specified"); return 1;} - -// Verify that this host has not been bound before -// - if ((isdflt = !strcmp("*", val))) bnow = bpDefault; - else {bnow = bpFirst; - while(bnow) if (!strcmp(bnow->thost, val)) break; - else bnow = bnow->next; - } - if (bnow) {Eroute.Emsg("Config","duplicate protbind definition - ", val); - return 1; - } - thost = strdup(val); - -// Now get each protocol to be used (there must be one). -// - while((val = Config.GetWord())) - {if (!strcmp(val, "none")) {noprot = 1; break;} - if (!strcmp(val, "only")) {only = 1; Enforce = 1;} - else if (!strcmp(val, "host")) {phost = 1; anyprot = 1;} - else if (!PManager.Find(val)) - {Eroute.Emsg("Config","protbind", val, - "protocol not previously defined."); - return 1; - } - else if (add2token(Eroute, val, &secbuff, sectlen, PMask)) - {Eroute.Emsg("Config","Unable to bind protocols to",thost); - return 1; - } else anyprot = 1; - } - -// Verify that no conflicts arose -// - if (val && (val = Config.GetWord())) - {Eroute.Emsg("Config","conflicting protbind:", thost, val); - return 1; - } - -// Make sure we have some protocols bound to this host -// - if (!(anyprot || noprot)) - {Eroute.Emsg("Config","no protocols bound to", thost); return 1;} - DEBUG("XrdSecConfig: Bound "<< thost<< " to " - << (noprot ? "none" : (phost ? "host" : sectoken))); - -// Issue warning if the host protocol was bound to this host but other -// protocols were also bound, making them rather useless. -// - if (phost && *sectoken) - {Eroute.Say("Config warning: 'protbind", thost, - "host' negates all other bound protocols."); - *sectoken = '\0'; - } - -// Translate "localhost" to our local hostname, if possible. -// - if (!strcmp("localhost", thost)) - {XrdNetAddr myIPAddr(0); - free(thost); - thost = strdup(myIPAddr.Name("localhost")); - } - -// Create new bind object -// - bnow = new XrdSecProtBind(thost,(noprot ? 0:sectoken),(only ? PMask:0)); - -// Push the entry onto our bindings -// - if (isdflt) bpDefault = bnow; - else {if (bpLast) bpLast->next = bnow; - else bpFirst = bnow; - bpLast = bnow; - } - -// All done -// - return 0; -} - -/******************************************************************************/ -/* x p r o t */ -/******************************************************************************/ - -/* Function: xprot - - Purpose: To parse the directive: protocol [] [ ] - - is the absolute path where the protocol library resides - is the 1-to-8 character protocol id. - are the associated protocol specific options such as: - noipcheck - don't check ip address origin - keyfile - the key file associated with protocol - args - associated non-blank arguments - Additional arguments may be passed to the protocol using the - protargs directive. ALl protargs directives must appear - prior to the protocol directive for the given protocol. - - Output: 0 upon success or !0 upon failure. -*/ - -int XrdSecServer::xprot(XrdOucStream &Config, XrdSysError &Eroute) -{ - XrdSecProtParm *pp, myParms(&Eroute, "protocol"); - char *pap, *val, pid[XrdSecPROTOIDSIZE+1], *args = 0; - char pathbuff[1024], *path = 0; - int psize; - XrdOucErrInfo erp; - XrdSecPMask_t mymask = 0; - -// Get the protocol id -// - val = Config.GetWord(); - if (val && *val == '/') - {strlcpy(pathbuff, val, sizeof(pathbuff)); path = pathbuff; - val = Config.GetWord(); - } - if (!val || !val[0]) - {Eroute.Emsg("Config","protocol id not specified"); return 1;} - -// Verify that we don't have this protocol -// - if (strlen(val) > XrdSecPROTOIDSIZE) - {Eroute.Emsg("Config","protocol id too long - ", val); return 1;} - - if (PManager.Find(val)) - {Eroute.Say("Config warning: protocol ",val," previously defined."); - strcpy(pid, val); - return add2token(Eroute, pid, &STBuff, STBlen, mymask);} - -// The builtin host protocol does not accept any parameters. Additionally, the -// host protocol negates any other protocols we may have in the default set. -// - if (!strcmp("host", val)) - {if (Config.GetWord()) - {Eroute.Emsg("Config", "Builtin host protocol does not accept parms."); - return 1; - } - implauth = 1; - return 0; - } - -// Grab additional parameters that we here and that we have accumulated -// - strcpy(pid, val); - while((args = Config.GetWord())) if (!myParms.Cat(args)) return 1; - if ((pp = myParms.Find(pid, 1))) - {if ((*myParms.Result(psize) && !myParms.Insert('\n')) - || !myParms.Cat(pp->Result(psize))) return 1; - else delete pp; - } - -// Load this protocol -// - pap = myParms.Result(psize); - if (!PManager.Load(&erp, 's', pid, (psize ? pap : 0), path)) - {if (*(erp.getErrText())) Eroute.Say(erp.getErrText()); - return 1; - } - -// Add this protocol to the default security token -// - return add2token(Eroute, pid, &STBuff, STBlen, mymask); -} - -/******************************************************************************/ -/* x p p a r m */ -/******************************************************************************/ - -/* Function: xpparm - - Purpose: To parse the directive: protparm - - is the name of the protocol to which these args apply. - are the protocol specific parameters. The remaing tokens - on the line will be passed to the protocol at during - protocol initialization. Each such line is separated by - a new line character. - - Output: 0 upon success or !0 upon failure. -*/ - -int XrdSecServer::xpparm(XrdOucStream &Config, XrdSysError &Eroute) -{ - XrdSecProtParm *pp; - char *val, pid[XrdSecPROTOIDSIZE+1]; - -// Get the protocol name -// - val = Config.GetWord(); - if (!val || !val[0]) - {Eroute.Emsg("Config","protparm protocol not specified"); return 1;} - -// The builtin host protocol does not accept any parameters -// - if (!strcmp("host", val)) - {Eroute.Emsg("Config", "Builtin host protocol does not accept protparms."); - return 1; - } - -// Verify that we don't have this protocol -// - if (strlen(val) > XrdSecPROTOIDSIZE) - {Eroute.Emsg("Config","protocol id too long - ", val); return 1;} - - if (PManager.Find(val)) - {Eroute.Emsg("Config warning: protparm protocol ",val," already defined."); - return 0; - } - - strcpy(pid, val); - -// Make sure we have at least one parameter here -// - if (!(val = Config.GetWord())) - {Eroute.Emsg("Config","protparm", pid, "parameter not specified"); - return 1; - } - -// Try to find a previous incarnation of this parm -// - if ((pp = XrdSecProtParm::Find(pid))) {if (!pp->Insert('\n')) return 1;} - else {pp = new XrdSecProtParm(&Eroute, "protparm"); - pp->setProt(pid); - pp->Add(); - } - -// Grab the options for the protocol. They are pretty much opaque to us here -// - do {if (!pp->Cat(val)) return 1;} while((val = Config.GetWord())); - return 0; -} - -/******************************************************************************/ -/* x t r a c e */ -/******************************************************************************/ - -/* Function: xtrace - - Purpose: To parse the directive: trace - - the blank separated list of events to trace. Trace - directives are cummalative. - - Output: 0 upon success or !0 upon failure. -*/ - -int XrdSecServer::xtrace(XrdOucStream &Config, XrdSysError &Eroute) -{ - static struct traceopts {const char *opname; int opval;} tropts[] = - { - {"all", TRACE_ALL}, - {"debug", TRACE_Debug}, - {"auth", TRACE_Authen}, - {"authentication", TRACE_Authen} - }; - int i, neg, trval = 0, numopts = sizeof(tropts)/sizeof(struct traceopts); - char *val; - - val = Config.GetWord(); - if (!val || !val[0]) - {Eroute.Emsg("Config", "trace option not specified"); return 1;} - while (val && val[0]) - {if (!strcmp(val, "off")) trval = 0; - else {if ((neg = (val[0] == '-' && val[1]))) val++; - for (i = 0; i < numopts; i++) - {if (!strcmp(val, tropts[i].opname)) - {if (neg) trval &= ~tropts[i].opval; - else trval |= tropts[i].opval; - break; - } - } - if (i >= numopts) - Eroute.Say("Config warning: ignoring invalid trace option '", val, "'."); - } - val = Config.GetWord(); - } - - SecTrace->What = (SecTrace->What & ~TRACE_Authenxx) | trval; - -// Propogate the debug option -// -#ifndef NODEBUG - if (QTRACE(Debug)) PManager.setDebug(1); - else PManager.setDebug(0); -#endif - return 0; -} - -/******************************************************************************/ -/* M i s c e l l a n e o u s */ -/******************************************************************************/ -/******************************************************************************/ -/* a d d 2 t o k e n */ -/******************************************************************************/ - -int XrdSecServer::add2token(XrdSysError &Eroute, char *pid, - char **tokbuff, int &toklen, XrdSecPMask_t &pmask) -{ - int i; - char *pargs; - XrdSecPMask_t protnum; - -// Find the protocol argument string -// - if (!(protnum = PManager.Find(pid, &pargs))) - {Eroute.Emsg("Config","Protocol",pid,"not found after being added!"); - return 1; - } - -// Make sure we have enough room to add -// - i = 4+strlen(pid)+strlen(pargs); - if (i >= toklen) - {Eroute.Emsg("Config","Protocol",pid,"parms exceed overall maximum!"); - return 1; - } - -// Insert protocol specification (we already checked for an overflow) -// - i = sprintf(*tokbuff, "&P=%s%s%s", pid, (*pargs ? "," : ""), pargs); - toklen -= i; - *tokbuff += i; - pmask |= protnum; - return 0; -} - -/******************************************************************************/ -/* P r o t B i n d _ C o m p l e t e */ -/******************************************************************************/ - -int XrdSecServer::ProtBind_Complete(XrdSysError &Eroute) -{ - EPNAME("ProtBind_Complete") - XrdOucErrInfo erp; - -// Check if we have a default token, create one otherwise -// - if (!bpDefault) - {if (!*SToken) {Eroute.Say("Config warning: No protocols defined; " - "only host authentication available."); - implauth = 1; - } - else if (implauth) - {Eroute.Say("Config warning: enabled builtin host " - "protocol negates default use of any other protocols."); - *SToken = '\0'; - } - bpDefault = new XrdSecProtBind(strdup("*"), SToken); - DEBUG("Default sectoken built: '" <Configure(cfn)) return 0; - -// Return the server object -// - return (XrdSecService *)SecServer; -} -} diff --git a/src/XrdSec/XrdSecServer.hh b/src/XrdSec/XrdSecServer.hh deleted file mode 100644 index f2af07031e2..00000000000 --- a/src/XrdSec/XrdSecServer.hh +++ /dev/null @@ -1,87 +0,0 @@ -#ifndef __XRDSECSERVER_H__ -#define __XRDSECSERVER_H__ -/******************************************************************************/ -/* */ -/* X r d S e c S e r v e r . h h */ -/* */ -/* (c) 2005 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysLogger.hh" -#include "XrdOuc/XrdOucStream.hh" -#include "XrdSec/XrdSecInterface.hh" -#include "XrdSec/XrdSecPManager.hh" - -class XrdSecProtBind; -class XrdOucTrace; -class XrdNetAddrInfo; - -class XrdSecServer : XrdSecService -{ -public: - -const char *getParms(int &size, XrdNetAddrInfo *endPoint=0); - -// = 0 -> No protocol can be returned (einfo has the reason) -// ! 0 -> Address of protocol object is bing returned. -// -XrdSecProtocol *getProtocol(const char *host, // In - XrdNetAddrInfo &endPoint,// In - const XrdSecCredentials *cred, // In - XrdOucErrInfo *einfo=0);// Out - -int Configure(const char *cfn); - - XrdSecServer(XrdSysLogger *lp); - ~XrdSecServer() {} // Server is never deleted - -private: - -static XrdSecPManager PManager; - -XrdSysError eDest; -XrdOucTrace *SecTrace; -XrdSecProtBind *bpFirst; -XrdSecProtBind *bpLast; -XrdSecProtBind *bpDefault; -char *SToken; -char *STBuff; -int STBlen; -int Enforce; -int implauth; - -int add2token(XrdSysError &erp,char *,char **,int &,XrdSecPMask_t &); -int ConfigFile(const char *cfn); -int ConfigXeq(char *var, XrdOucStream &Config, XrdSysError &Eroute); -int ProtBind_Complete(XrdSysError &Eroute); -int xlevel(XrdOucStream &Config, XrdSysError &Eroute); -int xpbind(XrdOucStream &Config, XrdSysError &Eroute); -int xpparm(XrdOucStream &Config, XrdSysError &Eroute); -int xprot(XrdOucStream &Config, XrdSysError &Eroute); -int xtrace(XrdOucStream &Config, XrdSysError &Eroute); -}; -#endif diff --git a/src/XrdSec/XrdSecTLayer.cc b/src/XrdSec/XrdSecTLayer.cc deleted file mode 100644 index df81652ffdb..00000000000 --- a/src/XrdSec/XrdSecTLayer.cc +++ /dev/null @@ -1,361 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S e c T L a y e r . c c */ -/* */ -/* */ -/* (c) 2008 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include - -#include "XrdOuc/XrdOucErrInfo.hh" -#include "XrdSec/XrdSecTLayer.hh" -#include "XrdSys/XrdSysFD.hh" -#include "XrdSys/XrdSysHeaders.hh" - -/******************************************************************************/ -/* S t a t i c V a l u e s */ -/******************************************************************************/ - -// Some compilers are incapable of optimizing away inline static const char's. -// -const char XrdSecTLayer::TLayerRR::endData; -const char XrdSecTLayer::TLayerRR::xfrData; - -/******************************************************************************/ -/* C o n s t r u c t o r */ -/******************************************************************************/ - -XrdSecTLayer::XrdSecTLayer(const char *pName, Initiator who1st) - : XrdSecProtocol(pName), - secTid(0), mySem(0), Starter(who1st), myFD(-1), urFD(-1), - Tmax(275), Tcur(0), eCode(0), eText(0) -{ - -// Do the standard stuff -// - memset((void *)&Hdr, 0, sizeof(Hdr)); - strncpy(Hdr.protName,pName,sizeof(Hdr.protName)-1); -} - -/******************************************************************************/ -/* C l i e n t O r i e n t e d M e t h o d s */ -/******************************************************************************/ -/******************************************************************************/ -/* g e t C r e d e n t i a l s */ -/******************************************************************************/ - -XrdSecCredentials *XrdSecTLayer::getCredentials(XrdSecParameters *parm, - XrdOucErrInfo *einfo) -{ - char Buff[dataSz]; - int Blen = 0, wrLen = 0; - char *bP, Req = TLayerRR::xfrData; - -// If this is the first time call, perform boot-up sequence and start the flow -// - eDest = einfo; - if (!parm) - {if (!bootUp(isClient)) return 0; - if (Starter == isServer) - {Hdr.protCode = TLayerRR::xfrData; - bP = (char *)malloc(hdrSz); - memcpy(bP, (char *)&Hdr, hdrSz); - return new XrdSecCredentials(bP, hdrSz); - } - } else { - if (parm->size < hdrSz) - {secError("Invalid parms length", EPROTO); - return 0; - } - Req = ((TLayerRR *)parm->buffer)->protCode; - wrLen= parm->size - hdrSz; - } - -// Perform required action -// xfrData -> xfrData | endData if socket gets closed -// endData -> endData if socket still open else protocol error -// - switch(Req) - {case TLayerRR::xfrData: - if (wrLen > 0 && write(myFD, parm->buffer+hdrSz, wrLen) < 0) - {secError("Socket write failed", errno); return 0;} - Blen = Read(myFD, Buff, dataSz); - if (Blen < 0 && (Blen != -EPIPE) && (Blen != -ECONNRESET)) - {secError("Socket read failed", -Blen); return 0;} - break; - case TLayerRR::endData: - if (myFD < 0) {secError("Protocol violation", EPROTO); return 0;} - Blen = -1; - break; - default: secError("Unknown parms request", EINVAL); return 0; - } - -// Set correct protocol code based on value in Blen. On the client side we -// check for proper completion upon socket close or when we get endData. -// Note that we apply self-pacing here as well since either side can pace, -// - if (Blen < 0) {if (!secDone()) return 0; - Blen = 0; Hdr.protCode = TLayerRR::endData;} - else if (Blen || wrLen) {Tcur = 0; Hdr.protCode = TLayerRR::xfrData;} - else if (++Tcur <= Tmax) Hdr.protCode = TLayerRR::xfrData; - else {Tcur = 0; Hdr.protCode = TLayerRR::endData;} - -// Return the credentials -// - bP = (char *)malloc(hdrSz+Blen); - memcpy(bP, (char *)&Hdr, hdrSz); - if (Blen) memcpy(bP+hdrSz, Buff, Blen); - return new XrdSecCredentials(bP, hdrSz+Blen); -} - -/******************************************************************************/ -/* S e r v e r O r i e n t e d M e t h o d s */ -/******************************************************************************/ - -int XrdSecTLayer::Authenticate (XrdSecCredentials *cred, - XrdSecParameters **parms, - XrdOucErrInfo *einfo) -{ - char Buff[dataSz]; - int Blen = 0, wrLen; - char *bP, Req; - -// If this is the first time call, perform boot-up sequence and start the flow -// - eDest = einfo; - if (myFD < 0 && !bootUp(isServer)) return -1; - -// Get the request code -// - if (cred->size < hdrSz) {secError("Invalid credentials",EBADMSG); return -1;} - Req = ((TLayerRR *)cred->buffer)->protCode; - wrLen= cred->size - hdrSz; - -// Perform required action -// xfrData -> xfrData | endData if socket gets closed -// endData -> noresponse -// - switch(Req) - {case TLayerRR::xfrData: - if (wrLen > 0 && write(myFD, cred->buffer+hdrSz, wrLen) < 0) - {secError("Socket write failed", errno); return -1;} - Blen = Read(myFD, Buff, dataSz); - if (Blen < 0 && (Blen != -EPIPE) && (Blen != -ECONNRESET)) - {secError("Socket read failed", -Blen); return 0;} - break; - case TLayerRR::endData: return (secDone() ? 0 : -1); - default: secError("Unknown parms request", EINVAL); return -1; - } - -// Set correct protocol code based on value in Blen and wrLen. Note that if -// both are zero then we decrease the pace count and bail if it reaches zero. -// Otherwise, we reset the pace count to it initial value. On the server side, -// we defer the socket drain until we receive a endData notification. -// - if (Blen < 0) {Blen = 0; Hdr.protCode = TLayerRR::endData;} - else if (Blen || wrLen) {Tcur = 0; Hdr.protCode = TLayerRR::xfrData;} - else if (++Tcur <= Tmax) Hdr.protCode = TLayerRR::xfrData; - else {Tcur = 0; Hdr.protCode = TLayerRR::endData;} - -// Return the credentials -// - bP = (char *)malloc(hdrSz+Blen); - memcpy(bP, (char *)&Hdr, hdrSz); - if (Blen) memcpy(bP+hdrSz, Buff, Blen); - *parms = new XrdSecParameters(bP, hdrSz+Blen); - - return 1; -} - - -/******************************************************************************/ -/* P r i v a t e M e t h o d s */ -/******************************************************************************/ -/******************************************************************************/ -/* b o o t U p */ -/******************************************************************************/ - -void *XrdSecTLayerBootUp(void *carg) - {XrdSecTLayer *tP = (XrdSecTLayer *)carg; - tP->secXeq(); - return (void *)0; - } - -/******************************************************************************/ - -int XrdSecTLayer::bootUp(Initiator whoami) -{ - int sv[2]; - -// Create a socket pair -// - if (XrdSysFD_Socketpair(AF_UNIX, SOCK_STREAM, 0, sv)) - {secError("Unable to create socket pair", errno); return 0;} - myFD = sv[0]; urFD = sv[1]; - Responder = whoami; - -// Start a thread to handle the socket interaction -// - if (XrdSysThread::Run(&secTid,XrdSecTLayerBootUp,(void *)this, XRDSYSTHREAD_HOLD)) - {int rc = errno; - close(myFD); myFD = -1; - close(urFD); urFD = -1; - secError("Unable to create thread", rc); - return 0; - } - -// All done -// - return 1; -} - -/******************************************************************************/ -/* R e a d */ -/******************************************************************************/ - -int XrdSecTLayer::Read(int FD, char *Buff, int rdLen) -{ - struct pollfd polltab = {FD, POLLIN|POLLRDNORM|POLLHUP, 0}; - int retc, xWt, Tlen = 0; - -// Read the data. We will employ a self-pacing read schedule where the -// assumptioon is that should a read produce zero bytes after a small amount -// of time then the data supplier needs additional data (i.e., writes) before -// it can supply data to be read. This occurs because certain transport layer -// protocols issue several writes in a row to complete the client/server -// interaction. We cannot use a fixed schedule for this because streams may -// coalesce adjacent writes, sigh. - -// Compute the actual poll wait time -// - if (Tcur) xWt = (Tcur+10)/10; - else xWt = 1; - -// Now do the interaction -// - do {do {retc = poll(&polltab, 1, xWt);} while(retc < 0 && errno == EINTR); - if (retc <= 0) return (retc ? -errno : Tlen); - do {retc = read(FD, Buff, rdLen);} while(retc < 0 && errno == EINTR); - if (retc <= 0) return (retc ? -errno : (Tlen ? Tlen : -EPIPE)); - Tlen += retc; Buff += retc; rdLen -= retc; xWt = 1; - } while(rdLen > 0); - - return Tlen; -} - -/******************************************************************************/ -/* s e c D o n e */ -/******************************************************************************/ - -int XrdSecTLayer::secDone() -{ - -// First close the socket and wait for thread completion -// - secDrain(); - -// Next, check if everything went well -// - if (!eCode) return 1; - -// Diagnose the problem and return failure -// - secError((eText ? eText : "?"), eCode, 0); - return 0; -} - -/******************************************************************************/ -/* s e c D r a i n */ -/******************************************************************************/ - -void XrdSecTLayer::secDrain() -{ - if (myFD >= 0) - {close(myFD); myFD = -1; - mySem.Wait(); - } -} - -/******************************************************************************/ -/* s e c E r r n o */ -/******************************************************************************/ - -const char *XrdSecTLayer::secErrno(int rc, char *buff) -{ - sprintf(buff, "err %d", rc); - return buff; -} - -/******************************************************************************/ -/* s e c E r r o r */ -/******************************************************************************/ - -void XrdSecTLayer::secError(const char *Msg, int rc, int iserrno) -{ - char buff[32]; - const char *tlist[] = {"XrdSecProtocol", Hdr.protName, ": ", Msg, "; ", - (iserrno ? strerror(rc) : secErrno(rc,buff)) - }; - int i, n = sizeof(tlist)/sizeof(const char *); - - if (eDest) eDest->setErrInfo(rc, tlist, n); - else {for (i = 0; i < n; i++) cerr <0) close(urFD); - urFD = -1; - mySem.Post(); -} diff --git a/src/XrdSec/XrdSecTLayer.hh b/src/XrdSec/XrdSecTLayer.hh deleted file mode 100644 index 64d1b11751b..00000000000 --- a/src/XrdSec/XrdSecTLayer.hh +++ /dev/null @@ -1,159 +0,0 @@ -#ifndef XRDSECTLAYER_HH -#define XRDSECTLAYER_HH -/******************************************************************************/ -/* */ -/* X r d S e c T L a y e r . h h */ -/* */ -/* */ -/* (c) 2008 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdSec/XrdSecInterface.hh" -#include "XrdSys/XrdSysPthread.hh" - -/* The XrdSecTLayer class is meant to be used as a wrapper for security - protocols that require transport-layer interactions to complete the - authentication exchange (e.g., native ssl). This class virtualizes a - transport-layer socket and provides the proper framing to allow stream - socket level interactions to occur across an existing client/xrootd - connection. To that extent, there are certain limitations in this - virtualization: - 1) Interactions must complete within a window whose upper bound is set to - CPU 10 seconds (i.e., Network RTT and artificial delays do not apply). - The window has no lower bound so that an interaction may complete as fast - as conditions allow. An interaction is whatever bytes produce a single - request/response. These bytes need not be produced all at once but the - last required byte of an interaction must be produced within 10 CPU - seconds of the 1st byte. There is no limit on the number of interactions. - 2) The use of the supplied socket must use standard and common socket - operations (e.g., read(), write(), send(), recv(), close()). - 3) The protocol must not be sensitive to the fact that the socket will - identify itself as a local socket with an IPV4 address of 127.0.0.1. - - For more information, see pure abstract methods secClient() and secServer() - which must be implemented by the derived class (in addition to delete()). - Finally, consider the parameters you may need to pass to the constructor of - this class. -*/ - -class XrdOucErrInfo; - -class XrdSecTLayer : public XrdSecProtocol -{ -public: - -// The object inheriting this class should call the initializer indicating -// the true name of the protocol (no more that 7 characters). To optimize the -// start-up, indicate who is the initiator (i.e., first one to send data). Using -// the enum below, specify isClient (the default) or isServer. If the initiator -// is not known, use the default and the class will dynamically determine it. -// -enum Initiator {isClient = 0, isServer}; - - XrdSecTLayer(const char *pName, Initiator who1st=isClient); - -// This is a symmetric wrapper. At the start on each end, secClient() is -// called on the client-side and secServer() is called on the server side. -// The 1st parameter is the filedescriptor to be used for the security exchange. -// It is the responsibility of each routine to close the file descriptor prior -// to returning to the caller! No return value is expected as success or failure -// is communicated via the esecond paramter, the XrdOucErrInfo object. - -// Upon success, the error code must be set to zero (the initial value) and -// for secServer() the Entity object defined in the topmost -// XrdSecProtocol object must contain the client's identity. - -// Upon failure, the error code must be set to a positive error number (usually -// some errno value) as well as text explaining the problem. - -// Client: theFD - file descriptor to be used -// einfo - the error object where ending status must be returned -// -virtual void secClient(int theFD, XrdOucErrInfo *einfo)=0; - -// Server: theFD - file descriptor to be used -// einfo - the error object where ending status must be returned -// -virtual void secServer(int theFD, XrdOucErrInfo *einfo)=0; - -// You must implete the proper delete(). Normally, do a "delete this" and join -// the secTid thread: "if (secTid) {XrdSysThread::Join(secTid,NULL);secTid=0;}". -// -virtual void Delete()=0; - -// Classes that must be public are only internally used -// - -virtual int Authenticate (XrdSecCredentials *cred, - XrdSecParameters **parms, - XrdOucErrInfo *einfo=0); - -virtual XrdSecCredentials *getCredentials(XrdSecParameters *parm=0, - XrdOucErrInfo *einfo=0); - - void secXeq(); - -protected: -pthread_t secTid; - -virtual ~XrdSecTLayer() {if (eText) {free(eText);eText=0;} - if (myFD>0) {close(myFD);myFD=-1;} - } - -private: - -int bootUp(Initiator Who); -int Read(int FD, char *Buff, int rdLen); -int secDone(); -void secDrain(); -const char *secErrno(int rc, char *buff); -void secError(const char *Msg, int rc, int iserrno=1); - -XrdSysSemaphore mySem; -Initiator Starter; -Initiator Responder; -int myFD; -int urFD; -int Tmax; // Maximum timeslices per interaction -int Tcur; // Current timeslice -int eCode; -char *eText; -XrdOucErrInfo *eDest; - -struct TLayerRR -{ - char protName[8]; // via Constructor - char protCode; // One of the below -static const char endData = 0x00; -static const char xfrData = 0x01; - char protRsvd[7]; // Reserved -} Hdr; - -static const int buffSz = 8192; -static const int hdrSz = sizeof(TLayerRR); -static const int dataSz = buffSz - hdrSz; -}; -#endif diff --git a/src/XrdSec/XrdSecTrace.hh b/src/XrdSec/XrdSecTrace.hh deleted file mode 100644 index 50450117674..00000000000 --- a/src/XrdSec/XrdSecTrace.hh +++ /dev/null @@ -1,65 +0,0 @@ -#ifndef ___SEC_TRACE_H___ -#define ___SEC_TRACE_H___ -/******************************************************************************/ -/* */ -/* X r d S e c T r a c e . h h */ -/* */ -/* (C) 2003 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Deprtment of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdOuc/XrdOucTrace.hh" - -#ifndef NODEBUG - -#include "XrdSys/XrdSysHeaders.hh" - -#define QTRACE(act) SecTrace->What & TRACE_ ## act - -#define TRACE(act, x) \ - if (QTRACE(act)) \ - {SecTrace->Beg(epname,tident); cerr <End();} - -#define DEBUG(y) if (QTRACE(Debug)) \ - {SecTrace->Beg(epname); cerr <End();} -#define EPNAME(x) static const char *epname = x; - -#else - -#define TRACE(x, y) -#define QTRACE(x) false -#define DEBUG(x) -#define EPNAME(x) - -#endif - -// Trace flags -// -#define TRACE_ALL 0x000f -#define TRACE_Authenxx 0x0007 -#define TRACE_Authen 0x0004 -#define TRACE_Debug 0x0001 - -#endif diff --git a/src/XrdSec/XrdSectestClient.cc b/src/XrdSec/XrdSectestClient.cc deleted file mode 100644 index 9ce1d66e268..00000000000 --- a/src/XrdSec/XrdSectestClient.cc +++ /dev/null @@ -1,202 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S e c t e s t C l i e n t . c c */ -/* */ -/* (c) 2003 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -/* Syntax: testClient [-b] [-d] [-h host] [-l] [sectoken] - - See the help() function for an explanation of the above. -*/ - -#include -#include -#include -#include -#include -#include -#include - -#include "XrdNet/XrdNetAddr.hh" -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdSec/XrdSecInterface.hh" - -/******************************************************************************/ -/* G l o b a l D e f i n i t i o n s */ -/******************************************************************************/ - -extern "C" -{ -extern XrdSecProtocol *XrdSecGetProtocol(const char *hostname, - XrdNetAddrInfo &endPoint, - XrdSecParameters &parms, - XrdOucErrInfo *einfo=0); -} - -/******************************************************************************/ -/* L O C A L D E F I N I T I O N S */ -/******************************************************************************/ - -#define H(x) fprintf(stderr,x); fprintf(stderr, "\n"); -#define I(x) fprintf(stderr, "\n"); H(x) - -/******************************************************************************/ -/* m a i n */ -/******************************************************************************/ - -int main(int argc, char **argv) -{ -char *tohex(char *inbuff, int inlen, char *outbuff); - -char *protocols=0, *hostspec=0; - -XrdNetAddr theAddr; - -int putbin = 0, putlen = 0; -char kbuff[8192]; -char c; - -XrdSecCredentials *cred; -XrdSecParameters SecToken; -XrdSecProtocol *pp; -int DebugON = 0; -void help(int); - - - /*Get all of the options. - */ - while ((c=getopt(argc,argv,"bdlh:")) != (char)EOF) - { switch(c) - { - case 'b': putbin = 1; break; - case 'd': DebugON = 1; break; - case 'h': hostspec = optarg; break; - case 'l': putlen = 1; break; - default: help(1); - } - } - -// Check if the security token is the last argument -// - if (optind < argc) protocols = argv[optind++]; - -/*Make sure no more parameters exist. -*/ - if (optind < argc) - {cerr <<"testClient: Extraneous parameter, '" <addrInfo = &theAddr; - cred = pp->getCredentials(); - if (!cred) - {cerr << "Unable to get credentials," <buffer, cred->size, 1, stdout) != (size_t) cred->size) - {cerr << "Unable to write credentials" <size, sizeof(cred->size), kbuff)); - printf("%s\n", tohex((char *) cred->buffer, cred->size, kbuff)); - } - -// All done. -// - pp->Delete(); -} - -char *tohex(char *inbuff, int inlen, char *outbuff) { - static char hv[] = "0123456789abcdef"; - int i, j = 0; - for (i = 0; i < inlen; i++) { - outbuff[j++] = hv[(inbuff[i] >> 4) & 0x0f]; - outbuff[j++] = hv[ inbuff[i] & 0x0f]; - } - outbuff[j] = '\0'; - return outbuff; - } - -/*help prints hout the obvious. -*/ -void help(int rc) { -/* Use H macro to avoid Sun string catenation bug. */ -I("Syntax: testClient [ options ] [sectoken]") -I("Options: -b -d -l -h host") -I("Function: Request for credentials relative to an operation.") - -if (rc > 1) exit(rc); -I("options: (defaults: -o 01") -I("-b output the ticket in binary format (i.e., not hexchar).") -I("-d turns on debugging.") -I("-l prefixes the ticket with its 4-byte length.") -I("-h host the requesting hostname (default is localhost).") -I("Notes: 1. Variable XrdSecSECTOKEN must contain the security token,") -H(" sectoken, if it is not specified on the command line.") -exit(rc); -} diff --git a/src/XrdSec/XrdSectestServer.cc b/src/XrdSec/XrdSectestServer.cc deleted file mode 100644 index 37edebe004d..00000000000 --- a/src/XrdSec/XrdSectestServer.cc +++ /dev/null @@ -1,333 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S e c t e s t S e r v e r . c c */ -/* */ -/* (c) 2003 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include - -#include "XrdNet/XrdNetAddr.hh" -#include "XrdOuc/XrdOucErrInfo.hh" -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdSys/XrdSysLogger.hh" -#include "XrdSec/XrdSecInterface.hh" - -/******************************************************************************/ -/* L O C A L D E F I N I T I O N S */ -/******************************************************************************/ - -#define H(x) fprintf(stderr,x); fprintf(stderr, "\n"); -#define I(x) fprintf(stderr, "\n"); H(x) -#define insx(a,b) sprintf(errbuff,a,b) -#define insy(a,b,c) sprintf(errbuff,a,b,c) - -typedef unsigned char uchar; - -/******************************************************************************/ -/* g l o b a l v a r i a b l e s */ -/******************************************************************************/ - -/* Define the execution control structure. -*/ -struct myOpts { - int debug; /* 1 -> Enable debugging. */ - int bin; /* 1 -> Input cred in binary format. */ - int xtra; /* 1 -> Perform null cred test */ - int online; /* 1 -> Filename is actual hex cred. */ - char *cfn; /* -> config file */ - char *host; /* -> hostname */ - char *inpt; /* -> Input stream name. */ - FILE *infid; /* -> Input stream (normally stdin). */ - } opts; - -/* Define global variables. -*/ -char errbuff[256]; -#ifndef C_Block -char hexbuff[256]; -#else -char hexbuff[sizeof(C_Block)+8]; -#endif - -extern "C" -{ -extern XrdSecService *XrdSecgetService(XrdSysLogger *lp, const char *cfn); -} - -/******************************************************************************/ -/* f u n c t i o n d e f i n i t i o n s */ -/******************************************************************************/ - -int getbintix(uchar *buff, int blen); -void getargs(int argc, char **argv); -int unhex(uchar *ibuff, uchar *obuff, int blen); -int cvtx(uchar idig, uchar *odig); -void getline(uchar *buff, int blen); -char *Ereason( ); -int emsg(int rc,char *msg); -void help(int rc); -void xerr(int x); - -/******************************************************************************/ -/* M A I N P R O G R A M */ -/******************************************************************************/ - -int main(int argc, char **argv) -{ - XrdNetAddr theAddr; - XrdOucErrInfo einfo; - XrdSysLogger Logger; - XrdSecService *ServerSecurity; - XrdSecParameters *parmp; - XrdSecCredentials cred((char *)malloc(8192), 8192); - XrdSecProtocol *pp; - const char *eText; - unsigned char bbuff[4096]; - int i, rc; - -// Parse the argument list. -// - getargs(argc, argv); - -// if hostname given, get the hostname address -// - if (opts.host) - {if ((eText = theAddr.Set(opts.host,0))) - {cerr <<"testServer: Unable to resolve '" <getParms(i, opts.host); - if (!sect) cerr <<"testServer: No security token for " <getProtocol(opts.host, theAddr, - (const XrdSecCredentials *)&cred, - &einfo))) - {rc = einfo.getErrInfo(); - cerr << "testServer: getProtocol error " <Authenticate(&cred, &parmp, &einfo) < 0) - {rc = einfo.getErrInfo(); - cerr << "testServer: Authenticate error " <Entity.name ? pp->Entity.name : "?") - <<"@" <<(pp->Entity.host ? pp->Entity.host : "?") - <<" prot=" <Entity.prot <= 0) buff[i] = (uchar)j; - else if (j == EOF) return i; - else xerr(insx("Error reading cred; %s.", Ereason())); - xerr(insx("Cred longer than %d bytes.", blen)); - return -1; -} - -/******************************************************************************/ -/* Command Line Processing */ -/******************************************************************************/ - -/* getargs: parse through argv obtaining options and parameters. -*/ -void getargs(int argc, char **argv) - { - extern int optind; extern char *optarg; char c; - -/* Establish defaults here. -*/ - opts.debug = 0; - opts.bin = 0; - opts.online = 0; - opts.cfn = 0; - opts.host = 0; - opts.xtra = 0; - opts.inpt = (char *)""; - opts.infid = stdin; - opts.cfn = 0; - -/* Process the options -*/ -while ((c=getopt(argc,argv,"c:h:i:k:p:bdx")) != (char)EOF) - { switch(c) - { - case 'b': opts.bin = 1; break; - case 'c': opts.cfn = optarg; break; - case 'd': opts.debug = 1; break; - case 'h': opts.host = optarg; break; - case 'i': opts.inpt = optarg; break; - case 'x': opts.xtra = 1; break; - case '?': help(1); - } - } - -/*Get the credentials, if specified on the command line. -*/ -if (optind < argc) {opts.inpt = argv[optind++]; opts.online = 1;} - -/*Make sure no more parameters exist. -*/ -if (optind < argc) xerr(insx("Extraneous parameter, '%s'.", argv[optind])); - -/*If the input stream is other than stdin, verify that it exists. -*/ -if (opts.inpt[0] != '\000' && !opts.online - && (!(opts.infid = fopen(opts.inpt, "r"))) ) - xerr(insy("Cannot open '%s'; %s.", opts.inpt, Ereason() )); - -/* Make sure that -i * and -b are not specified together. -*/ -if (opts.online && opts.bin) - emsg(8, (char *)"-b is incompatible with inline creds."); - -/*All done -*/ - return; - } - -/******************************************************************************/ -/* Utility Function */ -/******************************************************************************/ - -/* unhex() converts a hex character string to its binary equivalent. The result - is placed in the passed buffer. It returns the number of bytes extracted. - An error results in a -1 response (including uneven hex digits). The - input buffer must be terminated with a null. -*/ -int unhex(uchar *ibuff, uchar *obuff, int blen) { -int i=0, j; -uchar dig1, dig2; - -for (j = 0; j < blen; j++) { - if (!ibuff[i]) return j; - if (!cvtx(ibuff[i++], &dig1) || !cvtx(ibuff[i++], &dig2)) return -1; - obuff[j] = (dig1 << 4) | dig2; - } -return -1; /* Buffer overflow */ - } - -int cvtx(uchar idig, uchar *odig) { -if (idig >= '0' && idig <= '9') {*odig = idig & (uchar)0x0f; return 1;} -idig = idig | (uchar)0x20; /* Change to lower case. */ -if (idig < 'a' || idig > 'f') return 0; -*odig = (idig & (uchar)0x0f) + (uchar)0x09; -return 1; -} - -/*getline() gets a newline terminated string from the expected input source. -*/ -void getline(uchar *buff, int blen) { - int i; - if (!fgets((char *)buff, blen, opts.infid)) return; - for (i = 0; i < blen; i++) - if (buff[i] == '\n') {buff[i] = '\000'; break;} - return; - } - -char *Ereason( ) { - return strerror(errno); - } - -/*xerr: print message on standard error using the errbuff as source of message. -*/ -void xerr(int x) { emsg(8, errbuff); } - -/*emsg: print message on standard error. -*/ -int emsg(int rc,char *msg) { - cerr << "testServer: " < 1) exit(rc); -I("options: (defaults: -k /etc/srvtab\\n") -I("-b indicates the cred is in binary format (i.e., not hexchar).") -I("-c cfn the config file.") -I("-d turns on debugging.") -I("-h host the incomming hostname.") -I("-i input specifies the input stream (e.g., fname) if other than stdin.") -H(" This -i is ignored if cred is specified on the command line.") -exit(rc); -} diff --git a/src/XrdSecgsi.cmake b/src/XrdSecgsi.cmake deleted file mode 100644 index 1d5890513e1..00000000000 --- a/src/XrdSecgsi.cmake +++ /dev/null @@ -1,111 +0,0 @@ - -include( XRootDCommon ) - -#------------------------------------------------------------------------------- -# Shared library version -#------------------------------------------------------------------------------- -set( LIB_XRD_SEC_GSI XrdSecgsi-${PLUGIN_VERSION} ) -set( LIB_XRD_SEC_GSI_GMAPDN XrdSecgsiGMAPDN-${PLUGIN_VERSION} ) -set( LIB_XRD_SEC_GSI_AUTHZVO XrdSecgsiAUTHZVO-${PLUGIN_VERSION} ) - -#------------------------------------------------------------------------------- -# The XrdSecgsi library -#------------------------------------------------------------------------------- -add_library( - ${LIB_XRD_SEC_GSI} - MODULE - XrdSecgsi/XrdSecProtocolgsi.cc XrdSecgsi/XrdSecProtocolgsi.hh - XrdSecgsi/XrdSecgsiTrace.hh ) - -target_link_libraries( - ${LIB_XRD_SEC_GSI} - XrdCrypto - XrdUtils - pthread ) - -set_target_properties( - ${LIB_XRD_SEC_GSI} - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) - -#------------------------------------------------------------------------------- -# The XrdSecgsiAuthzVO module -#------------------------------------------------------------------------------- -add_library( - ${LIB_XRD_SEC_GSI_AUTHZVO} - MODULE - XrdSecgsi/XrdSecgsiAuthzFunVO.cc ) - -target_link_libraries( - ${LIB_XRD_SEC_GSI_AUTHZVO} - XrdUtils ) - -set_target_properties( - ${LIB_XRD_SEC_GSI_AUTHZVO} - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) - -#------------------------------------------------------------------------------- -# The XrdSecgsiGMAPDN module -#------------------------------------------------------------------------------- -add_library( - ${LIB_XRD_SEC_GSI_GMAPDN} - MODULE - XrdSecgsi/XrdSecgsiGMAPFunDN.cc ) - -target_link_libraries( - ${LIB_XRD_SEC_GSI_GMAPDN} - XrdUtils ) - -set_target_properties( - ${LIB_XRD_SEC_GSI_GMAPDN} - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) - -#------------------------------------------------------------------------------- -# xrdgsiproxy -#------------------------------------------------------------------------------- -add_executable( - xrdgsiproxy - XrdSecgsi/XrdSecgsiProxy.cc ) - -target_link_libraries( - xrdgsiproxy - XrdCrypto - XrdUtils - ${OPENSSL_CRYPTO_LIBRARY} ) - -#------------------------------------------------------------------------------- -# xrdgsitest -#------------------------------------------------------------------------------- -add_executable( - xrdgsitest - XrdSecgsi/XrdSecgsitest.cc ) - -target_link_libraries( - xrdgsitest - XrdCrypto - XrdUtils - ${OPENSSL_CRYPTO_LIBRARY} ) - -#------------------------------------------------------------------------------- -# Install -#------------------------------------------------------------------------------- -install( - TARGETS - ${LIB_XRD_SEC_GSI} - ${LIB_XRD_SEC_GSI_AUTHZVO} - ${LIB_XRD_SEC_GSI_GMAPDN} - xrdgsiproxy - xrdgsitest - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ) - -install( - FILES - ${PROJECT_SOURCE_DIR}/docs/man/xrdgsiproxy.1 - ${PROJECT_SOURCE_DIR}/docs/man/xrdgsitest.1 - DESTINATION ${CMAKE_INSTALL_MANDIR}/man1 ) diff --git a/src/XrdSecgsi/XrdSecProtocolgsi.cc b/src/XrdSecgsi/XrdSecProtocolgsi.cc deleted file mode 100644 index eab40af561d..00000000000 --- a/src/XrdSecgsi/XrdSecProtocolgsi.cc +++ /dev/null @@ -1,5416 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S e c P r o t o c o l g s i . c c */ -/* */ -/* (c) 2005 G. Ganis / CERN */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/* */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "XrdVersion.hh" - -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdSys/XrdSysLogger.hh" -#include "XrdSys/XrdSysError.hh" -#include "XrdOuc/XrdOucPinLoader.hh" -#include "XrdOuc/XrdOucStream.hh" -#include "XrdOuc/XrdOucEnv.hh" - -#include "XrdSut/XrdSutAux.hh" - -#include "XrdCrypto/XrdCryptoMsgDigest.hh" -#include "XrdCrypto/XrdCryptoX509Req.hh" - -#include "XrdSecgsi/XrdSecProtocolgsi.hh" - -/******************************************************************************/ -/* T r a c i n g I n i t O p t i o n s */ -/******************************************************************************/ -#ifndef NODEBUG -#define POPTS(t,y) {if (t) {t->Beg(epname); cerr <End();}} -#else -#define POPTS(t,y) -#endif - -/******************************************************************************/ -/* S t a t i c D a t a */ -/******************************************************************************/ - -static String Prefix = "xrd"; -static String ProtoID = XrdSecPROTOIDENT; -static const kXR_int32 Version = XrdSecgsiVERSION; - -static const char *gsiClientSteps[] = { - "kXGC_none", - "kXGC_certreq", - "kXGC_cert", - "kXGC_reserved" -}; - -static const char *gsiServerSteps[] = { - "kXGS_none", - "kXGS_init", - "kXGS_cert", - "kXGS_reserved" -}; - -static const char *gGSErrStr[] = { - "ErrParseBuffer", // 10000 - "ErrDecodeBuffer", // 10001 - "ErrLoadCrypto", // 10002 - "ErrBadProtocol", // 10003 - "ErrCreateBucket", // 10004 - "ErrDuplicateBucket", // 10005 - "ErrCreateBuffer", // 10006 - "ErrSerialBuffer", // 10007 - "ErrGenCipher", // 10008 - "ErrExportPuK", // 10009 - "ErrEncRndmTag", // 10010 - "ErrBadRndmTag", // 10011 - "ErrNoRndmTag", // 10012 - "ErrNoCipher", // 10013 - "ErrNoCreds", // 10014 - "ErrBadOpt", // 10015 - "ErrMarshal", // 10016 - "ErrUnmarshal", // 10017 - "ErrSaveCreds", // 10018 - "ErrNoBuffer", // 10019 - "ErrRefCipher", // 10020 - "ErrNoPublic", // 10021 - "ErrAddBucket", // 10022 - "ErrFinCipher", // 10023 - "ErrInit", // 10024 - "ErrBadCreds", // 10025 - "ErrError" // 10026 -}; - -// One day in secs -static const int kOneDay = 86400; -static const char *gUsrPxyDef = "/tmp/x509up_u"; - -/******************************************************************************/ -/* S t a t i c C l a s s D a t a */ -/******************************************************************************/ - -XrdSysMutex XrdSecProtocolgsi::gsiContext; -String XrdSecProtocolgsi::CAdir = "/etc/grid-security/certificates/"; -String XrdSecProtocolgsi::CRLdir = "/etc/grid-security/certificates/"; -String XrdSecProtocolgsi::DefCRLext= ".r0"; -String XrdSecProtocolgsi::GMAPFile = "/etc/grid-security/grid-mapfile"; -String XrdSecProtocolgsi::SrvCert = "/etc/grid-security/xrd/xrdcert.pem"; -String XrdSecProtocolgsi::SrvKey = "/etc/grid-security/xrd/xrdkey.pem"; -String XrdSecProtocolgsi::UsrProxy; -String XrdSecProtocolgsi::UsrCert = "/.globus/usercert.pem"; -String XrdSecProtocolgsi::UsrKey = "/.globus/userkey.pem"; -String XrdSecProtocolgsi::PxyValid = "12:00"; -int XrdSecProtocolgsi::DepLength= 0; -int XrdSecProtocolgsi::DefBits = 512; -int XrdSecProtocolgsi::CACheck = 1; -int XrdSecProtocolgsi::CRLCheck = 1; -int XrdSecProtocolgsi::CRLDownload = 0; -int XrdSecProtocolgsi::CRLRefresh = 86400; -int XrdSecProtocolgsi::GMAPOpt = 1; -bool XrdSecProtocolgsi::GMAPuseDNname = 0; -String XrdSecProtocolgsi::DefCrypto= "ssl"; -String XrdSecProtocolgsi::DefCipher= "aes-128-cbc:bf-cbc:des-ede3-cbc"; -String XrdSecProtocolgsi::DefMD = "sha1:md5"; -String XrdSecProtocolgsi::DefError = "invalid credentials "; -int XrdSecProtocolgsi::PxyReqOpts = 0; -int XrdSecProtocolgsi::AuthzPxyWhat = -1; -int XrdSecProtocolgsi::AuthzPxyWhere = -1; -XrdSecgsiGMAP_t XrdSecProtocolgsi::GMAPFun = 0; -XrdSecgsiAuthz_t XrdSecProtocolgsi::AuthzFun = 0; -XrdSecgsiAuthzKey_t XrdSecProtocolgsi::AuthzKey = 0; -int XrdSecProtocolgsi::AuthzCertFmt = -1; -int XrdSecProtocolgsi::GMAPCacheTimeOut = -1; -int XrdSecProtocolgsi::AuthzCacheTimeOut = 43200; // 12h, default -String XrdSecProtocolgsi::SrvAllowedNames; -int XrdSecProtocolgsi::VOMSAttrOpt = 1; -XrdSecgsiAuthz_t XrdSecProtocolgsi::VOMSFun = 0; -int XrdSecProtocolgsi::VOMSCertFmt = -1; -int XrdSecProtocolgsi::MonInfoOpt = 0; -bool XrdSecProtocolgsi::HashCompatibility = 1; -// -// Crypto related info -int XrdSecProtocolgsi::ncrypt = 0; // Number of factories -XrdCryptoFactory *XrdSecProtocolgsi::cryptF[XrdCryptoMax] = {0}; // their hooks -int XrdSecProtocolgsi::cryptID[XrdCryptoMax] = {0}; // their IDs -String XrdSecProtocolgsi::cryptName[XrdCryptoMax] = {0}; // their names -XrdCryptoCipher *XrdSecProtocolgsi::refcip[XrdCryptoMax] = {0}; // ref for session ciphers -// -// Caches -XrdSutCache XrdSecProtocolgsi::cacheCA; // Server certificates info cache (default size 144) -XrdSutCache XrdSecProtocolgsi::cacheCert(8,13); // Server certificates info cache (Fibonacci-based sizes) -XrdSutCache XrdSecProtocolgsi::cachePxy(8,13); // Client proxies cache (Fibonacci-based sizes) -XrdSutCache XrdSecProtocolgsi::cacheGMAPFun; // Entries mapped by GMAPFun (default size 144) -XrdSutCache XrdSecProtocolgsi::cacheAuthzFun; // Entities filled by AuthzFun (default size 144) -// -// Services -XrdOucGMap *XrdSecProtocolgsi::servGMap = 0; // Grid map service -// -// CA and CRL stacks -GSIStack XrdSecProtocolgsi::stackCA; // Stack of CA in use -GSIStack XrdSecProtocolgsi::stackCRL; // Stack of CRL in use -// -// GMAP control vars -time_t XrdSecProtocolgsi::lastGMAPCheck = -1; // Time of last check -XrdSysMutex XrdSecProtocolgsi::mutexGMAP; // Mutex to control GMAP reloads -// -// Running options / settings -int XrdSecProtocolgsi::Debug = 0; // [CS] Debug level -bool XrdSecProtocolgsi::Server = 1; // [CS] If server mode -int XrdSecProtocolgsi::TimeSkew = 300; // [CS] Allowed skew in secs for time stamps -// -// Debug an tracing -XrdSysError XrdSecProtocolgsi::eDest(0, "secgsi_"); -XrdSysLogger XrdSecProtocolgsi::Logger; -XrdOucTrace *XrdSecProtocolgsi::GSITrace = 0; - -XrdOucTrace *gsiTrace = 0; - -/******************************************************************************/ -/* S t a t i c F u n c t i o n s */ -/******************************************************************************/ -//_____________________________________________________________________________ -static const char *ClientStepStr(int kclt) -{ - // Return string with client step - static const char *ukn = "Unknown"; - - kclt = (kclt < 0) ? 0 : kclt; - kclt = (kclt > kXGC_reserved) ? 0 : kclt; - kclt = (kclt >= kXGC_certreq) ? (kclt - kXGC_certreq + 1) : kclt; - - if (kclt < 0 || kclt > (kXGC_reserved - kXGC_certreq + 1)) - return ukn; - else - return gsiClientSteps[kclt]; -} - -//_____________________________________________________________________________ -static const char *ServerStepStr(int ksrv) -{ - // Return string with server step - static const char *ukn = "Unknown"; - - ksrv = (ksrv < 0) ? 0 : ksrv; - ksrv = (ksrv > kXGS_reserved) ? 0 : ksrv; - ksrv = (ksrv >= kXGS_init) ? (ksrv - kXGS_init + 1) : ksrv; - - if (ksrv < 0 || ksrv > (kXGS_reserved - kXGS_init + 1)) - return ukn; - else - return gsiServerSteps[ksrv]; -} - - -/******************************************************************************/ -/* D u m p o f H a n d s h a k e v a r i a b l e s */ -/******************************************************************************/ - -//_____________________________________________________________________________ -void gsiHSVars::Dump(XrdSecProtocolgsi *p) -{ - // Dump content - EPNAME("HSVars::Dump"); - - PRINT("----------------------------------------------------------------"); - PRINT("protocol instance: "<TimeStamp = time(0); - // Local handshake variables - hs->Tty = (isatty(0) == 0 || isatty(1) == 0) ? 0 : 1; - } else { - PRINT("could not create handshake vars object"); - } - - // Set host name and address - Entity.host = strdup(endPoint.Name("*unknown*")); - epAddr = endPoint; - Entity.addrInfo = &epAddr; - - // Init session variables - sessionCF = 0; - sessionKey = 0; - bucketKey = 0; - sessionMD = 0; - sessionKsig = 0; - sessionKver = 0; - sessionKver = 0; - proxyChain = 0; - - // - // Notify, if required - DEBUG("constructing: host: "<Parms = new XrdSutBuffer(p.c_str(), p.length()); - } - } - - // We are done - String vers = Version; - vers.insert('.',vers.length()-2); - vers.insert('.',vers.length()-5); - DEBUG("object created: v"< -1) ? opt.debug : Debug; - - // We must have the tracing object at this point - // (initialized in XrdSecProtocolgsiInit) - if (!gsiTrace) { - ErrF(erp,kGSErrInit,"tracing object (gsiTrace) not initialized! cannot continue"); - return Parms; - } - // Set debug mask ... also for auxilliary libs - int trace = 0, traceSut = 0, traceCrypto = 0; - if (Debug >= 3) { - trace = cryptoTRACE_Dump; - traceSut = sutTRACE_Dump; - traceCrypto = cryptoTRACE_Dump; - GSITrace->What = TRACE_ALL; - } else if (Debug >= 2) { - trace = cryptoTRACE_Debug; - traceSut = sutTRACE_Debug; - traceCrypto = cryptoTRACE_Debug; - GSITrace->What = TRACE_Debug; - GSITrace->What |= TRACE_Authen; - } else if (Debug >= 1) { - trace = cryptoTRACE_Debug; - traceSut = sutTRACE_Notify; - traceCrypto = cryptoTRACE_Notify; - GSITrace->What = TRACE_Debug; - } - - // ... also for auxilliary libs - XrdSutSetTrace(traceSut); - XrdCryptoSetTrace(traceCrypto); - - // Name hashing algorithm compatibility - if (opt.hashcomp == 0) HashCompatibility = 0; - - // - // Operation mode - Server = (opt.mode == 's'); - - // - // CA verification level - // - // 0 do not verify - // 1 verify if self-signed; warn if not - // 2 verify in all cases; fail if not possible - // - if (opt.ca >= 0 && opt.ca <= 2) - CACheck = opt.ca; - DEBUG("option CACheck: "< 0) { - if (XrdSutExpand(dp) == 0) { - if (stat(dp.c_str(),&st) == -1) { - if (errno == ENOENT) { - ErrF(erp,kGSErrError,"CA directory non existing:",dp.c_str()); - PRINT(erp->getErrText()); - } else { - ErrF(erp,kGSErrError,"cannot stat CA directory:",dp.c_str()); - PRINT(erp->getErrText()); - } - } else { - if (!(dp.endswith('/'))) dp += '/'; - if (!(CAtmp.endswith(','))) CAtmp += ','; - CAtmp += dp; - } - } else { - PRINT("Warning: could not expand: "< 0) - CAdir = CAtmp; - } - DEBUG("using CA dir(s): "<= 10) { - CRLDownload = 1; - opt.crl %= 10; - } - if (opt.crl >= 0 && opt.crl <= 3) - CRLCheck = opt.crl; - DEBUG("option CRLCheck: "< 0) { - if (XrdSutExpand(dp) == 0) { - if (stat(dp.c_str(),&st) == -1) { - if (errno == ENOENT) { - ErrF(erp,kGSErrError,"CRL directory non existing:",dp.c_str()); - PRINT(erp->getErrText()); - } else { - ErrF(erp,kGSErrError,"cannot stat CRL directory:",dp.c_str()); - PRINT(erp->getErrText()); - } - } else { - if (!(dp.endswith('/'))) dp += '/'; - if (!(CRLtmp.endswith(','))) CRLtmp += ','; - CRLtmp += dp; - } - } else { - PRINT("Warning: could not expand: "< 0) - CRLdir = CRLtmp; - - } else { - // Use CAdir - CRLdir = CAdir; - } - if (CRLCheck > 0) - DEBUG("using CRL dir(s): "< 0 && ncpt[0] != '-') { - // Try loading - if ((cf = XrdCryptoFactory::GetCryptoFactory(ncpt.c_str()))) { - // Add it to the list - cryptF[ncrypt] = cf; - cryptID[ncrypt] = cf->ID(); - cryptName[ncrypt].insert(cf->Name(),0,strlen(cf->Name())+1); - cf->SetTrace(trace); - cf->Notify(); - // Ref cipher - if (!(refcip[ncrypt] = cf->Cipher(0,0,0))) { - PRINT("ref cipher for module "<= XrdCryptoMax) { - PRINT("max number of crypto modules (" - << XrdCryptoMax <<") reached "); - break; - } - } - } else { - PRINT("cannot instantiate crypto factory "<getErrText()); - return Parms; - } - // - // List of supported / wanted ciphers - if (opt.cipher) - DefCipher = opt.cipher; - // make sure we support all of them - String cip = ""; - int from = 0; - while ((from = DefCipher.tokenize(cip, from, ':')) != -1) { - if (cip.length() > 0) { - int i = 0; - for (; i < ncrypt; i++) { - if (!(cryptF[i]->SupportedCipher(cip.c_str()))) { - // Not supported: drop from the list - DEBUG("cipher type not supported ("< 0) { - int i = 0; - for (; i < ncrypt; i++) { - if (!(cryptF[i]->SupportedMsgDigest(md.c_str()))) { - // Not supported: drop from the list - PRINT("MD type not supported ("<getErrText()); - return Parms; - } - - DEBUG("CA list: "<= 10) { - GMAPuseDNname = 1; - opt.ogmap %= 10; - } - if (opt.ogmap >= 0 && opt.ogmap <= 2) - GMAPOpt = opt.ogmap; - DEBUG("user mapping file option: "< 0) { - // Initialize the GMap service - // - String pars; - if (Debug) pars += "dbg|"; - if (opt.gmapto > 0) { pars += "to="; pars += (int)opt.gmapto; } - if (!(servGMap = XrdOucgetGMap(&eDest, GMAPFile.c_str(), pars.c_str()))) { - if (GMAPOpt > 1) { - ErrF(erp,kGSErrError,"error loading grid map file:",GMAPFile.c_str()); - PRINT(erp->getErrText()); - return Parms; - } else { - NOTIFY("Grid map file: "< 0) { - if (!(GMAPFun = LoadGMAPFun((const char *) opt.gmapfun, - (const char *) opt.gmapfunparms))) { - ErrF(erp, kGSErrError, "GMAP plug-in could not be loaded", opt.gmapfun); - PRINT(erp->getErrText()); - return Parms; - } else { - hasgmapfun = 1; - } - } - // - // Disable GMAP if neither a grid mapfile nor a GMAP function are available - if (!hasgmap && !hasgmapfun) { - if (GMAPOpt > 1) { - ErrF(erp,kGSErrError,"User mapping required, but neither a grid mapfile" - " nor a mapping function are available"); - PRINT(erp->getErrText()); - return Parms; - } - GMAPOpt = 0; - } - // - // Authorization function - bool hasauthzfun = 0; - if (opt.authzfun) { - if (!(AuthzFun = LoadAuthzFun((const char *) opt.authzfun, - (const char *) opt.authzfunparms, AuthzCertFmt))) { - ErrF(erp, kGSErrError, "Authz plug-in could not be loaded", opt.authzfun); - PRINT(erp->getErrText()); - return Parms; - } else { - hasauthzfun = 1; - // Notify certificate format - if (AuthzCertFmt >= 0 && AuthzCertFmt <= 1) { - const char *ccfmt[] = { "raw", "PEM base64" }; - DEBUG("authzfun: proxy certificate format: "< 0) { - AuthzCacheTimeOut = opt.authzto; - DEBUG("grid-map cache entries expire after "< 0 && !hasauthzfun && opt.gmapto > 0) { - GMAPCacheTimeOut = opt.gmapto; - DEBUG("grid-map cache entries expire after "< 0) { - AuthzPxyWhat = opt.authzpxy / 10; - AuthzPxyWhere = opt.authzpxy % 10; - // Some notification - const char *capxy_what = (AuthzPxyWhat == 1) ? "'last proxy only'" - : "'full proxy chain'"; - const char *capxy_where = (AuthzPxyWhere == 1) ? "XrdSecEntity.creds" - : "XrdSecEntity.endorsements"; - DEBUG("Export proxy for authorization in '"<= 0) ? opt.vomsat : VOMSAttrOpt; - - // - // Alternative VOMS extraction function - if (opt.vomsfun) { - if (!(VOMSFun = LoadVOMSFun((const char *) opt.vomsfun, - (const char *) opt.vomsfunparms, VOMSCertFmt))) { - ErrF(erp, kGSErrError, "VOMS plug-in could not be loaded", opt.vomsfun); - PRINT(erp->getErrText()); - return Parms; - } else { - // We at least check VOMS attributes if we have a function ... - if (VOMSAttrOpt < 1) VOMSAttrOpt = 1; - // Notify certificate format - if (VOMSCertFmt >= 0 && VOMSCertFmt <= 1) { - const char *ccfmt[] = { "raw", "PEM base64" }; - DEBUG("vomsfun: proxy certificate format: "<,c:,ca: - Parms = new char[cryptlist.length()+3+12+certcalist.length()+5]; - if (Parms) { - sprintf(Parms,"v:%d,c:%s,ca:%s", - Version,cryptlist.c_str(),certcalist.c_str()); - } else { - ErrF(erp,kGSErrInit,"no system resources for 'Parms'"); - PRINT(erp->getErrText()); - } - - // Some notification - DEBUG("available crypto modules: "< - struct passwd *pw = getpwuid(getuid()); - if (!pw) { - NOTIFY("WARNING: cannot get user information (uid:"<pw_uid); - } - // Define user certificate file - if (opt.cert) { - String TmpCert = opt.cert; - if (XrdSutExpand(TmpCert) == 0) { - UsrCert = TmpCert; - } else { - PRINT("Could not expand: "< DefBits) - DefBits = opt.bits; - // - // Delegate proxy options - if (opt.dlgpxy == 1) - PxyReqOpts |= kOptsDlgPxy; - if (opt.dlgpxy == 2) - PxyReqOpts |= kOptsFwdPxy; - if (opt.sigpxy > 0 || opt.dlgpxy == 1) - PxyReqOpts |= kOptsSigReq; - // - // Define valid CNs for the server certificates; default is null, which means that - // the server CN must be in the form "*/" - if (opt.srvnames) - SrvAllowedNames = opt.srvnames; - // - // Notify - TRACE(Authen, "using certificate file: "< 0) { - SafeFree(Entity.creds); - } else { - Entity.creds = 0; - } - Entity.credslen = 0; - SafeFree(Entity.moninfo); - // Cleanup the handshake variables, if still there - SafeDelete(hs); - // Cleanup any other instance specific to this protocol - SafeDelete(sessionKey); // Session Key (result of the handshake) - SafeDelete(bucketKey); // Bucket with the key in export form - SafeDelete(sessionMD); // Message Digest instance - SafeDelete(sessionKsig); // RSA key to sign - SafeDelete(sessionKver); // RSA key to verify - SafeDelete(proxyChain); // Chain with delegated proxies - - delete this; -} - - -/******************************************************************************/ -/* E n c r y p t i o n R e l a t e d M e t h o d s */ -/******************************************************************************/ - -//_____________________________________________________________________________ -int XrdSecProtocolgsi::Encrypt(const char *inbuf, // Data to be encrypted - int inlen, // Length of data in inbuff - XrdSecBuffer **outbuf) // Returns encrypted data -{ - // Encrypt data in inbuff and place it in outbuff. - // - // Returns: < 0 Failed, the return value is -errno of the reason. Typically, - // -EINVAL - one or more arguments are invalid. - // -ENOTSUP - encryption not supported by the protocol - // -EOVERFLOW - outbuff is too small to hold result - // -ENOENT - Context not initialized - // = 0 Success, outbuff contains a pointer to the encrypted data. - // - EPNAME("Encrypt"); - - // We must have a key - if (!sessionKey) - return -ENOENT; - - // And something to encrypt - if (!inbuf || inlen <= 0 || !outbuf) - return -EINVAL; - - // Get output buffer - char *buf = (char *)malloc(sessionKey->EncOutLength(inlen)); - if (!buf) - return -ENOMEM; - - // Encrypt - int len = sessionKey->Encrypt(inbuf, inlen, buf); - if (len <= 0) { - SafeFree(buf); - return -EINVAL; - } - - // Create and fill output buffer - *outbuf = new XrdSecBuffer(buf, len); - - // We are done - DEBUG("encrypted buffer has "<DecOutLength(inlen)); - if (!buf) - return -ENOMEM; - - // Decrypt - int len = sessionKey->Decrypt(inbuf, inlen, buf); - if (len <= 0) { - SafeFree(buf); - return -EINVAL; - } - - // Create and fill output buffer - *outbuf = new XrdSecBuffer(buf, len); - - // We are done - DEBUG("decrypted buffer has "<Reset(0); - - // Calculate digest - sessionMD->Update(inbuf, inlen); - sessionMD->Final(); - - // Output length - int lmax = sessionKsig->GetOutlen(sessionMD->Length()); - char *buf = (char *)malloc(lmax); - if (!buf) - return -ENOMEM; - - // Sign - int len = sessionKsig->EncryptPrivate(sessionMD->Buffer(), - sessionMD->Length(), - buf, lmax); - if (len <= 0) { - SafeFree(buf); - return -EINVAL; - } - - // Create and fill output buffer - *outbuf = new XrdSecBuffer(buf, len); - - // We are done - DEBUG("signature has "< 0 Failed to verify, signature does not match inbuff data. - // - EPNAME("Verify"); - - // We must have a PKI and a digest - if (!sessionKver || !sessionMD) - return -ENOENT; - - // And something to verify - if (!inbuf || inlen <= 0 || !sigbuf || siglen <= 0) - return -EINVAL; - - // Reset digest - sessionMD->Reset(0); - - // Calculate digest - sessionMD->Update(inbuf, inlen); - sessionMD->Final(); - - // Output length - int lmax = sessionKver->GetOutlen(siglen); - char *buf = new char[lmax]; - if (!buf) - return -ENOMEM; - - // Decrypt signature - int len = sessionKver->DecryptPublic(sigbuf, siglen, buf, lmax); - if (len <= 0) { - delete[] buf; - return -EINVAL; - } - - // Verify signature - bool bad = 1; - if (len == sessionMD->Length()) { - if (!strncmp(buf, sessionMD->Buffer(), len)) { - // Signature matches - bad = 0; - DEBUG("signature successfully verified"); - } - } - - // Cleanup - if (buf) delete[] buf; - - // We are done - return ((bad) ? 1 : 0); -} - -//_____________________________________________________________________________ -int XrdSecProtocolgsi::getKey(char *kbuf, int klen) -{ - // Get the current encryption key - // - // Returns: < 0 Failed, returned value if -errno (see Encrypt) - // >= 0 The size of the encyption key. The supplied buffer of length - // size hold the key. If the buffer address is 0, only the - // size of the key is returned. - // - EPNAME("getKey"); - - // Check if we have to serialize the key - if (!bucketKey) { - - // We must have a key for that - if (!sessionKey) - // Invalid call - return -ENOENT; - // Create bucket - bucketKey = sessionKey->AsBucket(); - } - - // Prepare output now, if we have any - if (bucketKey) { - // If are asked only the size, we are done - if (kbuf == 0) - return bucketKey->size; - - // Check the size of the buffer - if (klen < bucketKey->size) - // Too small - return -EOVERFLOW; - - // Copy the buffer - memcpy(kbuf, bucketKey->buffer, bucketKey->size); - - // We are done - DEBUG("session key exported"); - return bucketKey->size; - } - - // Key exists but we could export it in bucket format - return -ENOMEM; -} - -//_____________________________________________________________________________ -int XrdSecProtocolgsi::setKey(char *kbuf, int klen) -{ - // Set the current encryption key - // - // Returns: < 0 Failed, returned value if -errno (see Encrypt) - // 0 The new key has been set. - // - EPNAME("setKey"); - - // Make sur that we can initialize the new key - if (!kbuf || klen <= 0) - // Invalid inputs - return -EINVAL; - - if (!sessionCF) - // Invalid context - return -ENOENT; - - // Put the buffer key into a bucket - XrdSutBucket *bck = new XrdSutBucket(); - if (!bck) - // Cannot get buffer: out-of-resources? - return -ENOMEM; - // Set key buffer - bck->SetBuf(kbuf, klen); - - // Init a new cipher from the bucket - XrdCryptoCipher *newKey = sessionCF->Cipher(bck); - if (!newKey) { - SafeDelete(bck); - return -ENOMEM; - } - - // Delete current key - SafeDelete(sessionKey); - - // Set the new key - sessionKey = newKey; - - // Cleanup - SafeDelete(bck); - - // Ok - DEBUG("session key update"); - return 0; -} - -/******************************************************************************/ -/* C l i e n t O r i e n t e d F u n c t i o n s */ -/******************************************************************************/ -/******************************************************************************/ -/* g e t C r e d e n t i a l s */ -/******************************************************************************/ - -XrdSecCredentials *XrdSecProtocolgsi::getCredentials(XrdSecParameters *parm, - XrdOucErrInfo *ei) -{ - // Query client for the password; remote username and host - // are specified in 'parm'. File '.rootnetrc' is checked. - EPNAME("getCredentials"); - - // If we are a server the only reason to be here is to get the forwarded - // or saved client credentials - if (srvMode) { - XrdSecCredentials *creds = 0; - if (proxyChain) { - // Export the proxy chain into a bucket - XrdCryptoX509ExportChain_t ExportChain = sessionCF->X509ExportChain(); - if (ExportChain) { - XrdSutBucket *bck = (*ExportChain)(proxyChain, 1); - if (bck) { - // We need to duplicate it because XrdSecCredentials uses - // {malloc, free} instead of {new, delete} - char *nbuf = (char *) malloc(bck->size); - if (nbuf) { - memcpy(nbuf, bck->buffer, bck->size); - // Import the buffer in a XrdSecCredentials object - creds = new XrdSecCredentials(nbuf, bck->size); - } - delete bck; - } - } - } - return creds; - } - - // Handshake vars container must be initialized at this point - if (!hs) - return ErrC(ei,0,0,0,kGSErrError, - "handshake var container missing","getCredentials"); - // - // Nothing to do if buffer is empty - if ((!parm && !hs->Parms) || (parm && (!(parm->buffer) || parm->size <= 0))) { - if (hs->Iter == 0) - return ErrC(ei,0,0,0,kGSErrNoBuffer,"missing parameters","getCredentials"); - else - return (XrdSecCredentials *)0; - } - - // We support passing the user {proxy, cert, key} paths via Url parameter - char *upp = (ei && ei->getEnv()) ? ei->getEnv()->Get("xrd.gsiusrpxy") : 0; - if (upp) UsrProxy = upp; - upp = (ei && ei->getEnv()) ? ei->getEnv()->Get("xrd.gsiusrcrt") : 0; - if (upp) UsrCert = upp; - upp = (ei && ei->getEnv()) ? ei->getEnv()->Get("xrd.gsiusrkey") : 0; - if (upp) UsrKey = upp; - - // Count interations - (hs->Iter)++; - - // Update time stamp - hs->TimeStamp = time(0); - - // Local vars - int step = 0; - int nextstep = 0; - const char *stepstr = 0; - char *bpub = 0; - int lpub = 0; - String CryptList = ""; - String Host = ""; - String RemID = ""; - String Emsg; - String specID = ""; - String issuerHash = ""; - // Buffer / Bucket related - XrdSutBuffer *bpar = 0; // Global buffer - XrdSutBuffer *bmai = 0; // Main buffer - - // - // Decode received buffer - bpar = hs->Parms; - if (!bpar && !(bpar = new XrdSutBuffer((const char *)parm->buffer,parm->size))) - return ErrC(ei,0,0,0,kGSErrDecodeBuffer,"global",stepstr); - // Ownership has been transferred - hs->Parms = 0; - // - // Check protocol ID name - if (strcmp(bpar->GetProtocol(),XrdSecPROTOIDENT)) - return ErrC(ei,bpar,bmai,0,kGSErrBadProtocol,stepstr); - // - // The step indicates what we are supposed to do - if (!(step = bpar->GetStep())) { - // The first, fake, step - step = kXGS_init; - bpar->SetStep(step); - } - stepstr = ServerStepStr(step); - // Dump, if requested - if (QTRACE(Dump)) { - XrdOucString msg("IN: "); - msg += stepstr; - bpar->Dump(msg.c_str()); - } - // - // Parse input buffer - if (ParseClientInput(bpar, &bmai, Emsg) == -1) { - DEBUG(Emsg<<" CF: "<Dump("IN: main"); - } - // - // Version - DEBUG("version run by server: "<< hs->RemVers); - // - // Check random challenge - if (!CheckRtag(bmai, Emsg)) - return ErrC(ei,bpar,bmai,0,kGSErrBadRndmTag,Emsg.c_str(),stepstr); - // - // Login name if any - String user(Entity.name); - if (user.length() <= 0) user = getenv("XrdSecUSER"); - // - // Now action depens on the step - nextstep = kXGC_none; - - XrdCryptoX509 *c = 0; - - switch (step) { - - case kXGS_init: - // - // Add bucket with cryptomod to the global list - // (This must be always visible from now on) - if (bpar->AddBucket(hs->CryptoMod,kXRS_cryptomod) != 0) - return ErrC(ei,bpar,bmai,0, - kGSErrCreateBucket,XrdSutBuckStr(kXRS_cryptomod),stepstr); - // - // Add bucket with our version to the main list - if (bpar->MarshalBucket(kXRS_version,(kXR_int32)(Version)) != 0) - return ErrC(ei,bpar,bmai,0, kGSErrCreateBucket, - XrdSutBuckStr(kXRS_version),"global",stepstr); - // - // Add our issuer hash - c = hs->PxyChain->Begin(); - if (c->type == XrdCryptoX509::kCA) { - issuerHash = c->SubjectHash(); - if (HashCompatibility && c->SubjectHash(1)) { - issuerHash += "|"; issuerHash += c->SubjectHash(1); } - } else { - issuerHash = c->IssuerHash(); - if (HashCompatibility && c->IssuerHash(1) - && strcmp(c->IssuerHash(1),c->IssuerHash())) { - issuerHash += "|"; issuerHash += c->IssuerHash(1); } - } - while ((c = hs->PxyChain->Next()) != 0) { - if (c->type != XrdCryptoX509::kCA) - break; - issuerHash = c->SubjectHash(); - if (HashCompatibility && c->SubjectHash(1) - && strcmp(c->IssuerHash(1),c->IssuerHash())) { - issuerHash += "|"; issuerHash += c->SubjectHash(1); } - } - - DEBUG("Client issuer hash: " << issuerHash); - if (bpar->AddBucket(issuerHash,kXRS_issuer_hash) != 0) - return ErrC(ei,bpar,bmai,0, kGSErrCreateBucket, - XrdSutBuckStr(kXRS_issuer_hash),stepstr); - // - // Add bucket with our delegate proxy options - if (hs->RemVers >= 10100) { - if (bpar->MarshalBucket(kXRS_clnt_opts,(kXR_int32)(hs->Options)) != 0) - return ErrC(ei,bpar,bmai,0, kGSErrCreateBucket, - XrdSutBuckStr(kXRS_clnt_opts),"global",stepstr); - } - - // - nextstep = kXGC_certreq; - break; - - case kXGS_cert: - // - // We must have a session cipher at this point - if (!(sessionKey)) - return ErrC(ei,bpar,bmai,0, - kGSErrNoCipher,"session cipher",stepstr); - - // - // Extract buffer with public info for the cipher agreement - if (!(bpub = sessionKey->Public(lpub))) - return ErrC(ei,bpar,bmai,0, - kGSErrNoPublic,"session",stepstr); - // - // Add it to the global list - if (bpar->UpdateBucket(bpub,lpub,kXRS_puk) != 0) - return ErrC(ei,bpar,bmai,0, kGSErrAddBucket, - XrdSutBuckStr(kXRS_puk),"global",stepstr); - // - // Add the proxy certificate - bmai->AddBucket(hs->Cbck); - // - // Add login name if any, needed while chosing where to export the proxies - if (user.length() > 0) { - if (bmai->AddBucket(user, kXRS_user) != 0) - return ErrC(ei,bpar,bmai,0, kGSErrCreateBucket, - XrdSutBuckStr(kXRS_user),stepstr); - } - // - nextstep = kXGC_cert; - break; - - case kXGS_pxyreq: - // - // If something went wrong, send explanation - if (Emsg.length() > 0) { - if (bmai->AddBucket(Emsg,kXRS_message) != 0) - return ErrC(ei,bpar,bmai,0, kGSErrCreateBucket, - XrdSutBuckStr(kXRS_message),stepstr); - } - // - // Add login name if any, needed while chosing where to export the proxies - if (user.length() > 0) { - if (bmai->AddBucket(user, kXRS_user) != 0) - return ErrC(ei,bpar,bmai,0, kGSErrCreateBucket, - XrdSutBuckStr(kXRS_user),stepstr); - } - // - // The relevant buckets should already be in the buffers - nextstep = kXGC_sigpxy; - break; - - default: - return ErrC(ei,bpar,bmai,0, kGSErrBadOpt,stepstr); - } - - // - // Serialize and encrypt - if (AddSerialized('c', nextstep, hs->ID, - bpar, bmai, kXRS_main, sessionKey) != 0) { - return ErrC(ei,bpar,bmai,0, - kGSErrSerialBuffer,"main",stepstr); - } - // - // Serialize the global buffer - char *bser = 0; - int nser = bpar->Serialized(&bser,'f'); - - if (QTRACE(Authen)) { - XrdOucString msg("OUT: "); - msg += ClientStepStr(bpar->GetStep()); - bpar->Dump(msg.c_str()); - msg.replace(ClientStepStr(bpar->GetStep()), "main"); - bmai->Dump(msg.c_str()); - } - // - // We may release the buffers now - REL2(bpar,bmai); - // - // Return serialized buffer - if (nser > 0) { - DEBUG("returned " << nser <<" bytes of credentials"); - return new XrdSecCredentials(bser, nser); - } else { - NOTIFY("problems with final serialization"); - return (XrdSecCredentials *)0; - } -} - -/******************************************************************************/ -/* S e r v e r O r i e n t e d M e t h o d s */ -/******************************************************************************/ - -//_____________________________________________________________________________ -static bool AuthzFunCheck(XrdSutCacheEntry *e, void *a) { - - int st_ref = (*((XrdSutCacheArg_t *)a)).arg1; - time_t ts_ref = (time_t)(*((XrdSutCacheArg_t *)a)).arg2; - long to_ref = (*((XrdSutCacheArg_t *)a)).arg3; - int st_exp = (*((XrdSutCacheArg_t *)a)).arg4; - - if (e && (e->status == st_ref)) { - // Check expiration, if required - bool expired = 0; - if (to_ref > 0 && (ts_ref - e->mtime) > to_ref) expired = 1; - int notafter = *((int *) e->buf2.buf); - if (to_ref > notafter) expired = 1; - - if (expired) { - // Invalidate the entry, if the case - e->status = st_exp; - } else { - return true; - } - } - return false; -} - -/******************************************************************************/ -/* A u t h e n t i c a t e */ -/******************************************************************************/ - -int XrdSecProtocolgsi::Authenticate(XrdSecCredentials *cred, - XrdSecParameters **parms, - XrdOucErrInfo *ei) -{ - // - // Check if we have any credentials or if no credentials really needed. - // In either case, use host name as client name - EPNAME("Authenticate"); - - // - // If cred buffer is two small or empty assume host protocol - if (cred->size <= (int)XrdSecPROTOIDLEN || !cred->buffer) { - strncpy(Entity.prot, "host", sizeof(Entity.prot)); - return 0; - } - - // Handshake vars conatiner must be initialized at this point - if (!hs) - return ErrS(Entity.tident,ei,0,0,0,kGSErrError, - "handshake var container missing", - "protocol initialization problems"); - - // Update time stamp - hs->TimeStamp = time(0); - - // - // ID of this handshaking - if (hs->ID.length() <= 0) - hs->ID = Entity.tident; - DEBUG("handshaking ID: " << hs->ID); - - // Local vars - int kS_rc = kgST_more; - int step = 0; - int nextstep = 0; - char *bpub = 0; - int lpub = 0; - const char *stepstr = 0; - String Message; - String CryptList; - String Host; - String SrvPuKExp; - String Salt; - String RndmTag; - String ClntMsg(256); - // Buffer related - XrdSutBuffer *bpar = 0; // Global buffer - XrdSutBuffer *bmai = 0; // Main buffer - // Proxy export related - XrdOucString spxy; - XrdSutBucket *bpxy = 0; - - // - // Decode received buffer - if (!(bpar = new XrdSutBuffer((const char *)cred->buffer,cred->size))) - return ErrS(hs->ID,ei,0,0,0,kGSErrDecodeBuffer,"global",stepstr); - // - // Check protocol ID name - if (strcmp(bpar->GetProtocol(),XrdSecPROTOIDENT)) - return ErrS(hs->ID,ei,bpar,bmai,0,kGSErrBadProtocol,stepstr); - // - // The step indicates what we are supposed to do - step = bpar->GetStep(); - stepstr = ClientStepStr(step); - // Dump, if requested - if (QTRACE(Dump)) { - XrdOucString msg("IN: "); - msg += stepstr; - bpar->Dump(msg.c_str()); - } - // - // Parse input buffer - if (ParseServerInput(bpar, &bmai, ClntMsg) == -1) { - DEBUG(ClntMsg); - return ErrS(hs->ID,ei,bpar,bmai,0,kGSErrParseBuffer,ClntMsg.c_str(),stepstr); - } - // - // Version - DEBUG("version run by client: "<< hs->RemVers); - DEBUG("options req by client: "<< hs->Options); - // - // Dump, if requested - if (QTRACE(Authen)) { - if (bmai) - bmai->Dump("IN: main"); - } - // - // Check random challenge - if (!CheckRtag(bmai, ClntMsg)) - return ErrS(hs->ID,ei,bpar,bmai,0,kGSErrBadRndmTag,stepstr,ClntMsg.c_str()); - - // Extract the VOMS attrbutes, if required - XrdCryptoX509ExportChain_t X509ExportChain = (sessionCF) ? sessionCF->X509ExportChain() : 0; - if (!X509ExportChain) { - // Error - return ErrS(hs->ID,ei,0,0,0,kGSErrError, - "crypto factory function for chain export not found"); - } - - // - // Now action depens on the step - switch (step) { - - case kXGC_certreq: - // - // Client required us to send our certificate and cipher public part: - // add first this last one. - // Extract buffer with public info for the cipher agreement - if (!(bpub = hs->Rcip->Public(lpub))) - return ErrS(hs->ID,ei,bpar,bmai,0, kGSErrNoPublic, - "session",stepstr); - // - // Add it to the global list - if (bpar->AddBucket(bpub,lpub,kXRS_puk) != 0) - return ErrS(hs->ID,ei,bpar,bmai,0, kGSErrAddBucket, - "main",stepstr); - // - // Add bucket with list of supported ciphers - if (bpar->AddBucket(DefCipher,kXRS_cipher_alg) != 0) - return ErrS(hs->ID,ei,bpar,bmai,0, - kGSErrAddBucket,XrdSutBuckStr(kXRS_cipher_alg),stepstr); - // - // Add bucket with list of supported MDs - if (bpar->AddBucket(DefMD,kXRS_md_alg) != 0) - return ErrS(hs->ID,ei,bpar,bmai,0, - kGSErrAddBucket,XrdSutBuckStr(kXRS_md_alg),stepstr); - // - // Add the server certificate - bpar->AddBucket(hs->Cbck); - - // We are done for the moment - nextstep = kXGS_cert; - break; - - case kXGC_cert: - // - // Client sent its own credentials: their are checked in - // ParseServerInput, so if we are here they are OK - kS_rc = kgST_ok; - nextstep = kXGS_none; - - if (GMAPOpt > 0) { - // Get name from gridmap - String name; - QueryGMAP(hs->Chain, hs->TimeStamp, name); - DEBUG("username(s) associated with this DN: "<GetBucket(kXRS_user))) { - bck->ToString(user); - bmai->Deactivate(kXRS_user); - } - DEBUG("target user: "< 0) { - // Check if the wanted username is authorized - String u; - int from = 0; - bool ok = 0; - while ((from = name.tokenize(u, from, ',')) != -1) { - if (user == u) { ok = 1; break; } - } - if (ok) { - name = u; - DEBUG("DN mapping: requested user is authorized: name is '"<Chain->EEChash()) { - Entity.name = strdup(hs->Chain->EEChash()); - } else if (GMAPuseDNname && hs->Chain->EECname()) { - Entity.name = strdup(hs->Chain->EECname()); - } else { - PRINT("WARNING: DN missing: corruption? "); - } - } - - // Add the DN as default moninfo if requested (the authz plugin may change this) - if (MonInfoOpt > 0) { - Entity.moninfo = strdup(hs->Chain->EECname()); - } - - if (VOMSAttrOpt > 0) { - if (VOMSFun) { - // Fill the information needed by the external function - if (VOMSCertFmt == 1) { - // PEM base64 - bpxy = (*X509ExportChain)(hs->Chain, true); - bpxy->ToString(spxy); - delete bpxy; - Entity.creds = strdup(spxy.c_str()); - Entity.credslen = spxy.length(); - } else { - // Raw (opaque) format, to be used with XrdCrypto - Entity.creds = (char *) hs->Chain; - Entity.credslen = 0; - } - if ((*VOMSFun)(Entity) != 0 && VOMSAttrOpt == 2) { - // Error - kS_rc = kgST_error; - PRINT("ERROR: the VOMS extraction plug-in reported a failure for this handshake"); - break; - } - } else { - // Lite version (no validations whatsover) - if (ExtractVOMS(hs->Chain, Entity) != 0 && VOMSAttrOpt == 2) { - // Error - kS_rc = kgST_error; - PRINT("ERROR: VOMS attributes required but not found (default lite-extraction technology)"); - break; - } - } - NOTIFY("VOMS: Entity.vorg: "<< (Entity.vorg ? Entity.vorg : "")); - NOTIFY("VOMS: Entity.grps: "<< (Entity.grps ? Entity.grps : "")); - NOTIFY("VOMS: Entity.role: "<< (Entity.role ? Entity.role : "")); - NOTIFY("VOMS: Entity.endorsements: "<< (Entity.endorsements ? Entity.endorsements : "")); - } - - // Here prepare/extract the information for authorization - spxy = ""; - bpxy = 0; - if (AuthzFun && AuthzKey) { - // Fill the information needed by the external function - if (AuthzCertFmt == 1) { - // May have been already done - if (!Entity.creds || Entity.credslen == 0) { - // PEM base64 - bpxy = (*X509ExportChain)(hs->Chain, true); - bpxy->ToString(spxy); - Entity.creds = strdup(spxy.c_str()); - Entity.credslen = spxy.length(); - } - } else { - // May have been already done - if (!Entity.creds || Entity.credslen > 0) { - free(Entity.creds); - // Raw (opaque) format, to be used with XrdCrypto - Entity.creds = (char *) hs->Chain; - Entity.credslen = 0; - } - } - // Get the key - char *key = 0; - int lkey = 0; - if ((lkey = (*AuthzKey)(Entity, &key)) < 0) { - // Fatal error - kS_rc = kgST_error; - PRINT("ERROR: unable to get the key associated to this user"); - break; - } - const char *dn = (const char *)key; - time_t now = hs->TimeStamp; - // We may have it in the cache - XrdSutCERef ceref; - bool rdlock = false; - XrdSutCacheArg_t arg = {kCE_ok, now, AuthzCacheTimeOut, kCE_disabled}; - XrdSutCacheEntry *cent = cacheAuthzFun.Get(dn, rdlock, AuthzFunCheck, (void *) &arg); - if (!cent) { - // Fatal error - kS_rc = kgST_error; - PRINT("ERROR: unable to get cache entry for dn: "<rwmtx)); - if (!rdlock) { - if (cent->buf1.buf) - FreeEntity((XrdSecEntity *) cent->buf1.buf); - SafeDelete(cent->buf1.buf); - SafeDelete(cent->buf2.buf); - } - if (cent->status != kCE_ok) { - int authzrc = 0; - if ((authzrc = (*AuthzFun)(Entity)) != 0) { - // Error - kS_rc = kgST_error; - PRINT("ERROR: the authorization plug-in reported a failure for this handshake"); - SafeDelete(key); - ceref.UnLock(); - break; - } else { - cent->status = kCE_ok; - // Save a copy of the relevant Entity fields - XrdSecEntity *se = new XrdSecEntity(); - int slen = 0; - CopyEntity(&Entity, se, &slen); - FreeEntity((XrdSecEntity *) cent->buf1.buf); - SafeDelete(cent->buf1.buf); - cent->buf1.buf = (char *) se; - cent->buf1.len = slen; - // Proxy expiration time - int notafter = hs->Chain->End() ? hs->Chain->End()->NotAfter() : -1; - cent->buf2.buf = (char *) new int(notafter); - cent->buf2.len = sizeof(int); - // Fill up the rest - cent->cnt = 0; - cent->mtime = now; // creation time - // Notify - DEBUG("Saved Entity to cacheAuthzFun ("<buf1.buf, &Entity, &slen); - // Notify - DEBUG("Got Entity from cacheAuthzFun ("<= 0) { - if (bpxy && AuthzPxyWhat == 1) { - SafeDelete(bpxy); spxy = ""; - SafeFree(Entity.creds); - Entity.credslen = 0; - } - if (!bpxy) { - if (AuthzPxyWhat == 1 && hs->Chain->End()) { - bpxy = hs->Chain->End()->Export(); - } else { - bpxy = (*X509ExportChain)(hs->Chain, true); - } - bpxy->ToString(spxy); - } - if (AuthzPxyWhere == 1) { - Entity.creds = strdup(spxy.c_str()); - Entity.credslen = spxy.length(); - } else { - // This should be deprecated - Entity.endorsements = strdup(spxy.c_str()); - } - delete bpxy; - NOTIFY("Entity.endorsements: "<<(void *)Entity.endorsements); - NOTIFY("Entity.creds: "<<(void *)Entity.creds); - NOTIFY("Entity.credslen: "<RemVers >= 10100) { - if (hs->PxyChain) { - // The client is going to send over info for delegation - kS_rc = kgST_more; - nextstep = kXGS_pxyreq; - } - } - - break; - - case kXGC_sigpxy: - // - // Nothing to do after this - kS_rc = kgST_ok; - nextstep = kXGS_none; - // - // If something went wrong, print explanation - if (ClntMsg.length() > 0) { - PRINT(ClntMsg); - } - break; - - default: - return ErrS(hs->ID,ei,bpar,bmai,0, kGSErrBadOpt, stepstr); - } - - if (kS_rc == kgST_more) { - // - // Add message to client - if (ClntMsg.length() > 0) - if (bmai->AddBucket(ClntMsg,kXRS_message) != 0) { - NOTIFY("problems adding bucket with message for client"); - } - // - // Serialize, encrypt and add to the global list - if (AddSerialized('s', nextstep, hs->ID, - bpar, bmai, kXRS_main, sessionKey) != 0) { - return ErrS(hs->ID,ei,bpar,bmai,0, kGSErrSerialBuffer, - "main / session cipher",stepstr); - } - // - // Serialize the global buffer - char *bser = 0; - int nser = bpar->Serialized(&bser,'f'); - // - // Dump, if requested - if (QTRACE(Authen)) { - XrdOucString msg("OUT: "); - msg += ServerStepStr(bpar->GetStep()); - bpar->Dump(msg.c_str()); - msg.replace(ServerStepStr(bpar->GetStep()), "main"); - bmai->Dump(msg.c_str()); - } - // - // Create buffer for client - *parms = new XrdSecParameters(bser,nser); - - } else { - // - // Cleanup handshake vars - SafeDelete(hs); - } - // - // We may release the buffers now - REL2(bpar,bmai); - // - // All done - return kS_rc; -} - -/******************************************************************************/ -/* C o p y E n t i ty */ -/******************************************************************************/ - -void XrdSecProtocolgsi::CopyEntity(XrdSecEntity *in, XrdSecEntity *out, int *lout) -{ - // Copy relevant fields of 'in' into 'out'; return length of 'out' - - if (!in || !out) return; - - int slen = sizeof(XrdSecEntity); - if (in->name) { out->name = strdup(in->name); slen += strlen(in->name); } - if (in->host) { out->host = strdup(in->host); slen += strlen(in->host); } - if (in->vorg) { out->vorg = strdup(in->vorg); slen += strlen(in->vorg); } - if (in->role) { out->role = strdup(in->role); slen += strlen(in->role); } - if (in->grps) { out->grps = strdup(in->grps); slen += strlen(in->grps); } - if (in->creds && in->credslen > 0) { - out->creds = strdup(in->creds); slen += in->credslen; - out->credslen = in->credslen; } - if (in->endorsements) { out->endorsements = strdup(in->endorsements); - slen += strlen(in->endorsements); } - if (in->moninfo) { out->moninfo = strdup(in->moninfo); - slen += strlen(in->moninfo); } - - // Save length, if required - if (lout) *lout = slen; - - // Done - return; -} - -/******************************************************************************/ -/* F r e e E n t i ty */ -/******************************************************************************/ - -void XrdSecProtocolgsi::FreeEntity(XrdSecEntity *in) -{ - // Free relevant fields of 'in'; - - if (!in) return; - - if (in->name) SafeFree(in->name); - if (in->host) SafeFree(in->host); - if (in->vorg) SafeFree(in->vorg); - if (in->role) SafeFree(in->role); - if (in->grps) SafeFree(in->grps); - if (in->creds && in->credslen > 0) { SafeFree(in->creds); in->credslen = 0; } - if (in->endorsements) SafeFree(in->endorsements); - if (in->moninfo) SafeFree(in->moninfo); - - // Done - return; -} - -/******************************************************************************/ -/* E x t r a c t V O M S */ -/******************************************************************************/ - -int XrdSecProtocolgsi::ExtractVOMS(X509Chain *c, XrdSecEntity &ent) -{ - // Get the VOMS attributes from proxy file(s) in chain 'c' (either the proxy - // or the limited proxy) and fill the relevant fields in 'ent' - EPNAME("ExtractVOMS"); - - if (!c) return -1; - - XrdCryptoX509 *xp = c->End(); - if (!xp) return -1; - - // Extractor - XrdCryptoX509GetVOMSAttr_t X509GetVOMSAttr = sessionCF->X509GetVOMSAttr(); - if (!X509GetVOMSAttr) return -1; - - // Extract the information - XrdOucString vatts; - int rc = 0; - if ((rc = (*X509GetVOMSAttr)(xp, vatts)) != 0) { - if (strstr(xp->Subject(), "CN=limited proxy")) { - xp = c->SearchBySubject(xp->Issuer()); - rc = (*X509GetVOMSAttr)(xp, vatts); - } - if (rc != 0) { - if (rc > 0) { - NOTIFY("No VOMS attributes in proxy chain"); - } else { - PRINT("ERROR: problem extracting VOMS attributes"); - } - return -1; - } - } - - int from = 0; - XrdOucString vat; - while ((from = vatts.tokenize(vat, from, ',')) != -1) { - XrdOucString vo, role, grp; - if (vat.length() > 0) { - // The attribute is in the form - // /VO[/group[/subgroup(s)]][/Role=role][/Capability=cap] - int isl = vat.find('/', 1); - if (isl != STR_NPOS) vo.assign(vat, 1, isl - 1); - int igr = vat.find("/Role=", 1); - if (igr != STR_NPOS) grp.assign(vat, 0, igr - 1); - int irl = vat.find("Role="); - if (irl != STR_NPOS) { - role.assign(vat, irl + 5); - isl = role.find('/'); - role.erase(isl); - } - if (ent.vorg) { - if (vo != (const char *) ent.vorg) { - DEBUG("WARNING: found a second VO ('"< 0) ent.vorg = strdup(vo.c_str()); - } - if (grp.length() > 0 - && (!ent.grps || grp.length() > int(strlen(ent.grps)))) { - SafeFree(ent.grps); - ent.grps = strdup(grp.c_str()); - } - if (role.length() > 0 && role != "NULL" && !ent.role) { - ent.role = strdup(role.c_str()); - } - } - } - - // Save the whole string in endorsements - SafeFree(ent.endorsements); - if (vatts.length() > 0) ent.endorsements = strdup(vatts.c_str()); - - // Notify if did not find the main info (the VO ...) - if (!ent.vorg) PRINT("WARNING: no VO found! (VOMS attributes: '"< 0) POPTS(t, " CRL refresh time: "<< crlrefresh); - if (mode == 'c') { - POPTS(t, " Certificate: " << (cert ? cert : XrdSecProtocolgsi::UsrCert)); - POPTS(t, " Key: " << (key ? key : XrdSecProtocolgsi::UsrKey)); - POPTS(t, " Proxy file: " << XrdSecProtocolgsi::UsrProxy); - POPTS(t, " Proxy validity: " << (valid ? valid : XrdSecProtocolgsi::PxyValid)); - POPTS(t, " Proxy dep length: " << deplen); - POPTS(t, " Proxy bits: " << bits); - POPTS(t, " Proxy sign option: "<< sigpxy); - POPTS(t, " Proxy delegation option: "<< dlgpxy); - POPTS(t, " Allowed server names: "<< (srvnames ? srvnames : "[*/][/*]")); - } else { - POPTS(t, " Certificate: " << (cert ? cert : XrdSecProtocolgsi::SrvCert)); - POPTS(t, " Key: " << (key ? key : XrdSecProtocolgsi::SrvKey)); - POPTS(t, " Proxy delegation option: "<< dlgpxy); - if (dlgpxy > 1) - POPTS(t, " Template for exported proxy: "<< (exppxy ? exppxy : gUsrPxyDef)); - POPTS(t, " GRIDmap file: " << (gridmap ? gridmap : XrdSecProtocolgsi::GMAPFile)); - POPTS(t, " GRIDmap option: "<< ogmap); - POPTS(t, " GRIDmap cache entries expiration (secs): "<< gmapto); - if (gmapfun) { - POPTS(t, " DN mapping function: " << gmapfun); - if (gmapfunparms) POPTS(t, " DN mapping function parms: " << gmapfunparms); - } else { - if (gmapfunparms) POPTS(t, " DN mapping function parms: ignored (no mapping function defined)"); - } - if (authzfun) { - POPTS(t, " Authorization function: " << authzfun); - if (authzfunparms) POPTS(t, " Authorization function parms: " << authzfunparms); - POPTS(t, " Authorization cache entries expiration (secs): " << authzto); - } else { - if (authzfunparms) POPTS(t, " Authorization function parms: ignored (no authz function defined)"); - } - POPTS(t, " Client proxy availability in XrdSecEntity.endorsement: "<< authzpxy); - POPTS(t, " VOMS option: "<< vomsat); - if (vomsfun) { - POPTS(t, " VOMS extraction function: " << vomsfun); - if (vomsfunparms) POPTS(t, " VOMS extraction function parms: " << vomsfunparms); - } else { - if (vomsfunparms) POPTS(t, " VOMS extraction function parms: ignored (no VOMS extraction function defined)"); - } - POPTS(t, " MonInfo option: "<< moninfo); - if (!hashcomp) - POPTS(t, " Name hashing algorithm compatibility OFF"); - } - // Crypto options - POPTS(t, " Crypto modules: "<< (clist ? clist : XrdSecProtocolgsi::DefCrypto)); - POPTS(t, " Ciphers: "<< (cipher ? cipher : XrdSecProtocolgsi::DefCipher)); - POPTS(t, " MDigests: "<< (md ? md : XrdSecProtocolgsi::DefMD)); - POPTS(t, "*** ------------------------------------------------------------ ***"); -} - -/******************************************************************************/ -/* X r d S e c P r o t o c o l g s i I n i t */ -/******************************************************************************/ - -extern "C" -{ -char *XrdSecProtocolgsiInit(const char mode, - const char *parms, XrdOucErrInfo *erp) -{ - // One-time protocol initialization, filling the static flags and options - // of the protocol. - // For clients (mode == 'c') we use values in envs. - // For servers (mode == 's') the command line options are passed through - // parms. - EPNAME("ProtocolgsiInit"); - - gsiOptions opts; - char *rc = (char *)""; - char *cenv = 0; - - // Initiate error logging and tracing - gsiTrace = XrdSecProtocolgsi::EnableTracing(); - - // - // Clients first - if (mode == 'c') { - // - // Decode envs: - // "XrdSecDEBUG" debug flag ("0","1","2","3") - // "XrdSecGSICADIR" full path to an alternative path - // containing the CA info - // [/etc/grid-security/certificates] - // "XrdSecGSICRLDIR" full path to an alternative path - // containing the CRL info - // [/etc/grid-security/certificates] - // "XrdSecGSICRLEXT" default extension of CRL files [.r0] - // "XrdSecGSIUSERCERT" full path to an alternative file - // containing the user certificate - // [$HOME/.globus/usercert.pem] - // "XrdSecGSIUSERKEY" full path to an alternative file - // containing the user key - // [$HOME/.globus/userkey.pem] - // "XrdSecGSIUSERPROXY" full path to an alternative file - // containing the user proxy - // [/tmp/x509up_u] - // "XrdSecGSIPROXYVALID" validity of proxies in the - // grid-proxy-init format - // ["12:00", i.e. 12 hours] - // "XrdSecGSIPROXYDEPLEN" depth of signature path for proxies; - // use -1 for unlimited [0] - // "XrdSecGSIPROXYKEYBITS" bits in PKI for proxies [512] - // "XrdSecGSICACHECK" CA check level [1]: - // 0 do not verify; - // 1 verify if self-signed, warn if not; - // 2 verify in all cases, fail if not possible - // "XrdSecGSICRLCHECK" CRL check level [2]: - // 0 don't care; - // 1 use if available; - // 2 require, - // 3 require non-expired CRL - // "XrdSecGSIDELEGPROXY" Forwarding of credentials option: - // 0 none; 1 sign request created - // by server; 2 forward local proxy - // (include private key) [0] - // "XrdSecGSISIGNPROXY" permission to sign requests - // 0 no, 1 yes [1] - // "XrdSecGSISRVNAMES" Server names allowed: if the server CN - // does not match any of these, or it is - // explicitely denied by these, or it is - // not in the form "*/", the - // handshake fails. - // "XrdSecGSIUSEDEFAULTHASH" If this variable is set only the default - // name hashing algorithm is used - - // - opts.mode = mode; - // debug - cenv = getenv("XrdSecDEBUG"); - if (cenv) - {if (cenv[0] >= 49 && cenv[0] <= 51) opts.debug = atoi(cenv); - else {PRINT("unsupported debug value from env XrdSecDEBUG: "<] - // [-c:[-]ssl[:[-]] - // [-crldir:] - // [-crlext:] - // [-cert:] - // [-key:] - // [-cipher:] - // [-md:] - // [-ca:] - // [-crl:] - // [-crlrefresh:] - // [-gridmap:] - // [-gmapfun:] - // [-gmapfunparms:] - // [-authzfun:] - // [-authzfunparms:] - // [-authzto:] - // [-gmapto:] - // [-gmapopt:] - // [-dlgpxy:] - // [-exppxy:] - // [-authzpxy] - // [-vomsat:] - // [-vomsfun:] - // [-vomsfunparms:] - // [-defaulthash] - // - int debug = -1; - String clist = ""; - String certdir = ""; - String crldir = ""; - String crlext = ""; - String cert = ""; - String key = ""; - String cipher = ""; - String md = ""; - String gridmap = ""; - String gmapfun = ""; - String gmapfunparms = ""; - String authzfun = ""; - String authzfunparms = ""; - String vomsfun = ""; - String vomsfunparms = ""; - String exppxy = ""; - int ca = 1; - int crl = 1; - int crlrefresh = 86400; - int ogmap = 1; - int gmapto = 600; - int authzto = -1; - int dlgpxy = 0; - int authzpxy = 0; - int vomsat = 1; - int moninfo = 0; - int hashcomp = 1; - char *op = 0; - while (inParms.GetLine()) { - while ((op = inParms.GetToken())) { - if (!strncmp(op, "-d:",3)) { - debug = atoi(op+3); - } else if (!strncmp(op, "-c:",3)) { - clist = (const char *)(op+3); - } else if (!strncmp(op, "-certdir:",9)) { - certdir = (const char *)(op+9); - } else if (!strncmp(op, "-crldir:",8)) { - crldir = (const char *)(op+8); - } else if (!strncmp(op, "-crlext:",8)) { - crlext = (const char *)(op+8); - } else if (!strncmp(op, "-cert:",6)) { - cert = (const char *)(op+6); - } else if (!strncmp(op, "-key:",5)) { - key = (const char *)(op+5); - } else if (!strncmp(op, "-cipher:",8)) { - cipher = (const char *)(op+8); - } else if (!strncmp(op, "-md:",4)) { - md = (const char *)(op+4); - } else if (!strncmp(op, "-ca:",4)) { - ca = atoi(op+4); - } else if (!strncmp(op, "-crl:",5)) { - crl = atoi(op+5); - } else if (!strncmp(op, "-crlrefresh:",12)) { - crlrefresh = atoi(op+12); - } else if (!strncmp(op, "-gmapopt:",9)) { - ogmap = atoi(op+9); - } else if (!strncmp(op, "-gridmap:",9)) { - gridmap = (const char *)(op+9); - } else if (!strncmp(op, "-gmapfun:",9)) { - gmapfun = (const char *)(op+9); - } else if (!strncmp(op, "-gmapfunparms:",14)) { - gmapfunparms = (const char *)(op+14); - } else if (!strncmp(op, "-authzfun:",10)) { - authzfun = (const char *)(op+10); - } else if (!strncmp(op, "-authzfunparms:",15)) { - authzfunparms = (const char *)(op+15); - } else if (!strncmp(op, "-authzto:",9)) { - authzto = atoi(op+9); - } else if (!strncmp(op, "-gmapto:",8)) { - gmapto = atoi(op+8); - } else if (!strncmp(op, "-dlgpxy:",8)) { - dlgpxy = atoi(op+8); - } else if (!strncmp(op, "-exppxy:",8)) { - exppxy = (const char *)(op+8); - } else if (!strncmp(op, "-authzpxy:",10)) { - authzpxy = atoi(op+10); - } else if (!strncmp(op, "-authzpxy",9)) { - authzpxy = 11; - } else if (!strncmp(op, "-vomsat:",8)) { - vomsat = atoi(op+8); - } else if (!strncmp(op, "-vomsfun:",9)) { - vomsfun = (const char *)(op+9); - } else if (!strncmp(op, "-vomsfunparms:",14)) { - vomsfunparms = (const char *)(op+14); - } else if (!strcmp(op, "-moninfo")) { - moninfo = 1; - } else if (!strncmp(op, "-moninfo:",9)) { - moninfo = atoi(op+9); - } else if (!strcmp(op, "-defaulthash")) { - hashcomp = 0; - } else { - PRINT("ignoring unknown switch: "< -1) ? debug : opts.debug; - opts.mode = 's'; - opts.ca = ca; - opts.crl = crl; - opts.crlrefresh = crlrefresh; - opts.ogmap = ogmap; - opts.gmapto = gmapto; - opts.authzto = authzto; - opts.dlgpxy = dlgpxy; - opts.authzpxy = authzpxy; - opts.vomsat = vomsat; - opts.moninfo = moninfo; - opts.hashcomp = hashcomp; - if (clist.length() > 0) - opts.clist = (char *)clist.c_str(); - if (certdir.length() > 0) - opts.certdir = (char *)certdir.c_str(); - if (crldir.length() > 0) - opts.crldir = (char *)crldir.c_str(); - if (crlext.length() > 0) - opts.crlext = (char *)crlext.c_str(); - if (cert.length() > 0) - opts.cert = (char *)cert.c_str(); - if (key.length() > 0) - opts.key = (char *)key.c_str(); - if (cipher.length() > 0) - opts.cipher = (char *)cipher.c_str(); - if (md.length() > 0) - opts.md = (char *)md.c_str(); - if (gridmap.length() > 0) - opts.gridmap = (char *)gridmap.c_str(); - if (gmapfun.length() > 0) - opts.gmapfun = (char *)gmapfun.c_str(); - if (gmapfunparms.length() > 0) - opts.gmapfunparms = (char *)gmapfunparms.c_str(); - if (authzfun.length() > 0) - opts.authzfun = (char *)authzfun.c_str(); - if (authzfunparms.length() > 0) - opts.authzfunparms = (char *)authzfunparms.c_str(); - if (exppxy.length() > 0) - opts.exppxy = (char *)exppxy.c_str(); - if (vomsfun.length() > 0) - opts.vomsfun = (char *)vomsfun.c_str(); - if (vomsfunparms.length() > 0) - opts.vomsfunparms = (char *)vomsfunparms.c_str(); - - // Notify init options, if required - opts.Print(gsiTrace); - - // - // Setup the plug-in with the chosen options - return XrdSecProtocolgsi::Init(opts,erp); - } - - // Notify init options, if required - opts.Print(gsiTrace); - // - // Setup the plug-in with the defaults - return XrdSecProtocolgsi::Init(opts,erp); -}} - - -/******************************************************************************/ -/* X r d S e c P r o t o c o l g s i O b j e c t */ -/******************************************************************************/ - -XrdVERSIONINFO(XrdSecProtocolgsiObject,secgsi); - -namespace -{XrdVersionInfo *gsiVersion = &XrdVERSIONINFOVAR(XrdSecProtocolgsiObject);} - -extern "C" -{ -XrdSecProtocol *XrdSecProtocolgsiObject(const char mode, - const char *hostname, - XrdNetAddrInfo &endPoint, - const char *parms, - XrdOucErrInfo *erp) -{ - XrdSecProtocolgsi *prot; - int options = XrdSecNOIPCHK; - - // - // Get a new protocol object - if (!(prot = new XrdSecProtocolgsi(options, hostname, endPoint, parms))) { - const char *msg = "Secgsi: Insufficient memory for protocol."; - if (erp) - erp->setErrInfo(ENOMEM, msg); - else - cerr < 0) { - bls->SetStep(step); - buf->SetStep(step); - hs->LastStep = step; - } - - // - // If a random tag has been sent and we have a session cipher, - // we sign it - XrdSutBucket *brt = buf->GetBucket(kXRS_rtag); - if (brt && sessionKsig) { - // - // Encrypt random tag with session cipher - if (sessionKsig->EncryptPrivate(*brt) <= 0) { - PRINT("error encrypting random tag"); - return -1; - } - // - // Update type - brt->type = kXRS_signed_rtag; - } - // - // Add an random challenge: if a next exchange is required this will - // allow to prove authenticity of counter part - // - // Generate new random tag and create a bucket - String RndmTag; - XrdSutRndm::GetRndmTag(RndmTag); - // - // Get bucket - brt = 0; - if (!(brt = new XrdSutBucket(RndmTag,kXRS_rtag))) { - PRINT("error creating random tag bucket"); - return -1; - } - buf->AddBucket(brt); - // - // Get cache entry - if (!hs->Cref) { - PRINT("cache entry not found: protocol error"); - return -1; - } - // - // Add random tag to the cache and update timestamp - hs->Cref->buf1.SetBuf(brt->buffer,brt->size); - hs->Cref->mtime = (kXR_int32)hs->TimeStamp; - // - // Now serialize the buffer ... - char *bser = 0; - int nser = buf->Serialized(&bser); - // - // Update bucket with this content - XrdSutBucket *bck = 0;; - if (!(bck = bls->GetBucket(type))) { - // or create new bucket, if not existing - if (!(bck = new XrdSutBucket(bser,nser,type))) { - PRINT("error creating bucket " - <<" - type: "<AddBucket(bck); - } else { - bck->Update(bser,nser); - } - // - // Encrypted the bucket - if (cip) { - if (cip->Encrypt(*bck) == 0) { - PRINT("error encrypting bucket - cipher " - <<" - type: "<GetStep(); - - // Do the right action - switch (step) { - case kXGS_init: - // Process message - if (ClientDoInit(br, bm, cmsg) != 0) - return -1; - break; - case kXGS_cert: - // Process message - if (ClientDoCert(br, bm, cmsg) != 0) - return -1; - break; - case kXGS_pxyreq: - // Process message - if (ClientDoPxyreq(br, bm, cmsg) != 0) - return -1; - break; - default: - cmsg = "protocol error: unknown action: "; cmsg += step; - return -1; - break; - } - - // We are done - return 0; -} - -//_________________________________________________________________________ -int XrdSecProtocolgsi::ClientDoInit(XrdSutBuffer *br, XrdSutBuffer **bm, - String &emsg) -{ - // Client side: process a kXGS_init message. - // Return 0 on success, -1 on error. If the case, a message is returned - // in cmsg. - EPNAME("ClientDoInit"); - - // - // Create the main buffer as a copy of the buffer received - if (!((*bm) = new XrdSutBuffer(br->GetProtocol(),br->GetOptions()))) { - emsg = "error instantiating main buffer"; - return -1; - } - // - // Extract server version from options - String opts = br->GetOptions(); - int ii = opts.find("v:"); - if (ii >= 0) { - String sver(opts,ii+2); - sver.erase(sver.find(',')); - hs->RemVers = atoi(sver.c_str()); - } else { - hs->RemVers = Version; - emsg = "server version information not found in options:" - " assume same as local"; - } - // - // Create cache - if (!(hs->Cref = new XrdSutPFEntry("c"))) { - emsg = "error creating cache"; - return -1; - } - // - // Save server version in cache - hs->Cref->status = hs->RemVers; - // - // Set options - hs->Options = PxyReqOpts; - // - // Extract list of crypto modules - String clist; - ii = opts.find("c:"); - if (ii >= 0) { - clist.assign(opts, ii+2); - clist.erase(clist.find(',')); - } else { - NOTIFY("Crypto list missing: protocol error? (use defaults)"); - clist = DefCrypto; - } - // Parse the list loading the first we can - if (ParseCrypto(clist) != 0) { - emsg = "cannot find / load crypto requested modules :"; - emsg += clist; - return -1; - } - // - // Extract server certificate CA hashes - String srvca; - ii = opts.find("ca:"); - if (ii >= 0) { - srvca.assign(opts, ii+3); - srvca.erase(srvca.find(',')); - } - // Parse the list loading the first we can - if (ParseCAlist(srvca) != 0) { - emsg = "unknown CA: cannot verify server certificate"; - hs->Chain = 0; - return -1; - } - // - // Resolve place-holders in cert, key and proxy file paths, if any - if (XrdSutResolve(UsrCert, Entity.host, Entity.vorg, Entity.grps, Entity.name) != 0) { - PRINT("Problems resolving templates in "<PxyChain, sessionKsig, hs->Cbck }; - if (QueryProxy(1, &cachePxy, "Proxy:0", - sessionCF, hs->TimeStamp, &pi, &po) != 0) { - emsg = "error getting user proxies"; - hs->Chain = 0; - return -1; - } - // Save the result - hs->PxyChain = po.chain; - hs->Cbck = new XrdSutBucket(*((XrdSutBucket *)(po.cbck))); - if (!(sessionKsig = sessionCF->RSA(*(po.ksig)))) { - emsg = "could not get a copy of the signing key:"; - hs->Chain = 0; - return -1; - } - // - // And we are done; - return 0; -} - -//_________________________________________________________________________ -int XrdSecProtocolgsi::ClientDoCert(XrdSutBuffer *br, XrdSutBuffer **bm, - String &emsg) -{ - // Client side: process a kXGS_cert message. - // Return 0 on success, -1 on error. If the case, a message is returned - // in cmsg. - EPNAME("ClientDoCert"); - XrdSutBucket *bck = 0; - - // - // make sure the cache is still there - if (!hs->Cref) { - emsg = "cache entry not found"; - hs->Chain = 0; - return -1; - } - // - // make sure is not too old - int reftime = hs->TimeStamp - TimeSkew; - if (hs->Cref->mtime < reftime) { - emsg = "cache entry expired"; - // Remove: should not be checked a second time - SafeDelete(hs->Cref); - hs->Chain = 0; - return -1; - } - // - // Get from cache version run by server - hs->RemVers = hs->Cref->status; - - // - // Extract list of cipher algorithms supported by the server - String cip = ""; - if ((bck = br->GetBucket(kXRS_cipher_alg))) { - String ciplist; - bck->ToString(ciplist); - // Parse the list - int from = 0; - while ((from = ciplist.tokenize(cip, from, ':')) != -1) { - if (cip.length() > 0) - if (sessionCF->SupportedCipher(cip.c_str())) - break; - cip = ""; - } - if (cip.length() > 0) - // COmmunicate to server - br->UpdateBucket(cip, kXRS_cipher_alg); - } else { - NOTIFY("WARNING: list of ciphers supported by server missing" - " - using default"); - } - - // - // Extract server public part for session cipher - if (!(bck = br->GetBucket(kXRS_puk))) { - emsg = "server public part for session cipher missing"; - hs->Chain = 0; - return -1; - } - // - // Initialize session cipher - SafeDelete(sessionKey); - if (!(sessionKey = - sessionCF->Cipher(0,bck->buffer,bck->size,cip.c_str()))) { - PRINT("could not instantiate session cipher " - "using cipher public info from server"); - emsg = "could not instantiate session cipher "; - } - // - // Extract server certificate - if (!(bck = br->GetBucket(kXRS_x509))) { - emsg = "server certificate missing"; - hs->Chain = 0; - return -1; - } - - // - // Finalize chain: get a copy of it (we do not touch the reference) - hs->Chain = new X509Chain(hs->Chain); - if (!(hs->Chain)) { - emsg = "cannot duplicate reference chain"; - return -1; - } - // The new chain must be deleted at destruction - hs->Options |= kOptsDelChn; - - // Get hook to parsing function - XrdCryptoX509ParseBucket_t ParseBucket = sessionCF->X509ParseBucket(); - if (!ParseBucket) { - emsg = "cannot attach to ParseBucket function!"; - return -1; - } - // Parse bucket - int nci = (*ParseBucket)(bck, hs->Chain); - if (nci != 1) { - emsg += nci; - emsg += " vs 1 expected)"; - return -1; - } - // - // Verify the chain - x509ChainVerifyOpt_t vopt = {0,static_cast(hs->TimeStamp),-1,hs->Crl}; - XrdCryptoX509Chain::EX509ChainErr ecode = XrdCryptoX509Chain::kNone; - if (!(hs->Chain->Verify(ecode, &vopt))) { - emsg = "certificate chain verification failed: "; - emsg += hs->Chain->LastError(); - return -1; - } - // - // Verify server identity - if (!ServerCertNameOK(hs->Chain->End()->Subject(), emsg)) { - return -1; - } - // - // Extract the server key - sessionKver = sessionCF->RSA(*(hs->Chain->End()->PKI())); - if (!sessionKver || !sessionKver->IsValid()) { - emsg = "server certificate contains an invalid key"; - return -1; - } - - // Deactivate what not needed any longer - br->Deactivate(kXRS_puk); - br->Deactivate(kXRS_x509); - - // - // Extract list of MD algorithms supported by the server - String md = ""; - if ((bck = br->GetBucket(kXRS_md_alg))) { - String mdlist; - bck->ToString(mdlist); - // Parse the list - int from = 0; - while ((from = mdlist.tokenize(md, from, ':')) != -1) { - if (md.length() > 0) - if (sessionCF->SupportedMsgDigest(md.c_str())) - break; - md = ""; - } - } else { - NOTIFY("WARNING: list of digests supported by server missing" - " - using default"); - md = "sha256"; - } - if (!(sessionMD = sessionCF->MsgDigest(md.c_str()))) { - emsg = "could not instantiate digest object"; - return -1; - } - // Communicate choice to server - br->UpdateBucket(md, kXRS_md_alg); - - // - // Extract the main buffer (it contains the random challenge - // and will contain our credentials encrypted) - XrdSutBucket *bckm = 0; - if (!(bckm = br->GetBucket(kXRS_main))) { - emsg = "main buffer missing"; - return -1; - } - - // - // Deserialize main buffer - if (!((*bm) = new XrdSutBuffer(bckm->buffer,bckm->size))) { - emsg = "error deserializing main buffer"; - return -1; - } - - // - // And we are done; - return 0; -} - -//_________________________________________________________________________ -int XrdSecProtocolgsi::ClientDoPxyreq(XrdSutBuffer *br, XrdSutBuffer **bm, - String &emsg) -{ - // Client side: process a kXGS_pxyreq message. - // Return 0 on success, -1 on error. If the case, a message is returned - // in cmsg. - XrdSutBucket *bck = 0; - - // - // Extract the main buffer (it contains the random challenge - // and will contain our credentials encrypted) - XrdSutBucket *bckm = 0; - if (!(bckm = br->GetBucket(kXRS_main))) { - emsg = "main buffer missing"; - return -1; - } - // - // Decrypt the main buffer with the session cipher, if available - if (sessionKey) { - if (!(sessionKey->Decrypt(*bckm))) { - emsg = "error with session cipher"; - return -1; - } - } - - // - // Deserialize main buffer - if (!((*bm) = new XrdSutBuffer(bckm->buffer,bckm->size))) { - emsg = "error deserializing main buffer"; - return -1; - } - - // - // Check if we are ready to proces this - if ((hs->Options & kOptsFwdPxy)) { - // We have to send the private key of our proxy - XrdCryptoX509 *pxy = 0; - XrdCryptoRSA *kpxy = 0; - if (!(hs->PxyChain) || - !(pxy = hs->PxyChain->End()) || !(kpxy = pxy->PKI())) { - emsg = "local proxy info missing or corrupted"; - return 0; - } - // Send back the signed request as bucket - String pri; - if (kpxy->ExportPrivate(pri) != 0) { - emsg = "problems exporting private key"; - return 0; - } - // Add it to the main list - if ((*bm)->AddBucket(pri, kXRS_x509) != 0) { - emsg = "problem adding bucket with private key to main buffer"; - return 0; - } - } else { - // Proxy request: check if we are allowed to sign it - if (!(hs->Options & kOptsSigReq)) { - emsg = "Not allowed to sign proxy requests"; - return 0; - } - // Get the request - if (!(bck = (*bm)->GetBucket(kXRS_x509_req))) { - emsg = "bucket with proxy request missing"; - return 0; - } - XrdCryptoX509Req *req = sessionCF->X509Req(bck); - if (!req) { - emsg = "could not resolve proxy request"; - return 0; - } - req->SetVersion(hs->RemVers); - // Get our proxy and its private key - XrdCryptoX509 *pxy = 0; - XrdCryptoRSA *kpxy = 0; - if (!(hs->PxyChain) || - !(pxy = hs->PxyChain->End()) || !(kpxy = pxy->PKI())) { - emsg = "local proxy info missing or corrupted"; - return 0; - } - // Sign the request - XrdCryptoX509SignProxyReq_t X509SignProxyReq = (sessionCF) ? sessionCF->X509SignProxyReq() : 0; - if (!X509SignProxyReq) { - emsg = "problems getting method to sign request"; - return 0; - } - XrdCryptoX509 *npxy = 0; - if ((*X509SignProxyReq)(pxy, kpxy, req, &npxy) != 0) { - emsg = "problems signing the request"; - return 0; - } - // Send back the signed request as bucket - if ((bck = npxy->Export())) { - // Add it to the main list - if ((*bm)->AddBucket(bck) != 0) { - emsg = "problem adding signed request to main buffer"; - return 0; - } - } - } - - // - // And we are done; - return 0; - -} - -//_________________________________________________________________________ -int XrdSecProtocolgsi::ParseServerInput(XrdSutBuffer *br, XrdSutBuffer **bm, - String &cmsg) -{ - // Parse received buffer b, extracting and decrypting the main - // buffer *bm and extracting the session - // cipher, random tag buckets and user name, if any. - // Results used to fill the local handshake variables - EPNAME("ParseServerInput"); - - // Space for pointer to main buffer must be already allocated - if (!br || !bm) { - PRINT("invalid inputs ("<GetStep(); - - // Do the right action - switch (step) { - case kXGC_certreq: - // Process message - if (ServerDoCertreq(br, bm, cmsg) != 0) - return -1; - break; - case kXGC_cert: - // Process message - if (ServerDoCert(br, bm, cmsg) != 0) - return -1; - break; - case kXGC_sigpxy: - // Process message - if (ServerDoSigpxy(br, bm, cmsg) != 0) - return -1; - break; - default: - cmsg = "protocol error: unknown action: "; cmsg += step; - return -1; - break; - } - - // - // We are done - return 0; -} - -//_________________________________________________________________________ -int XrdSecProtocolgsi::ServerDoCertreq(XrdSutBuffer *br, XrdSutBuffer **bm, - String &cmsg) -{ - // Server side: process a kXGC_certreq message. - // Return 0 on success, -1 on error. If the case, a message is returned - // in cmsg. - XrdSutCERef ceref; - XrdSutBucket *bck = 0; - XrdSutBucket *bckm = 0; - - // - // Extract the main buffer - if (!(bckm = br->GetBucket(kXRS_main))) { - cmsg = "main buffer missing"; - return -1; - } - // - // Extract bucket with crypto module - if (!(bck = br->GetBucket(kXRS_cryptomod))) { - cmsg = "crypto module specification missing"; - return -1; - } - String cmod; - bck->ToString(cmod); - // Parse the list loading the first we can - if (ParseCrypto(cmod) != 0) { - cmsg = "cannot find / load crypto requested module :"; - cmsg += cmod; - return -1; - } - // - // Get version run by client, if there - if (br->UnmarshalBucket(kXRS_version,hs->RemVers) != 0) { - hs->RemVers = Version; - cmsg = "client version information not found in options:" - " assume same as local"; - } else { - br->Deactivate(kXRS_version); - } - // - // Extract bucket with client issuer hash - if (!(bck = br->GetBucket(kXRS_issuer_hash))) { - cmsg = "client issuer hash missing"; - return -1; - } - String cahash; - bck->ToString(cahash); - // - // Check if we know it - if (ParseCAlist(cahash) != 0) { - cmsg = "unknown CA: cannot verify client credentials"; - return -1; - } - // Find our certificate in cache - String cadum; - XrdSutCacheEntry *cent = GetSrvCertEnt(ceref, sessionCF, hs->TimeStamp, cadum); - if (!cent) { - cmsg = "cannot find certificate: corruption?"; - return -1; - } - - // Fill some relevant handshake variables - sessionKsig = sessionCF->RSA(*((XrdCryptoRSA *)(cent->buf2.buf))); - hs->Cbck = new XrdSutBucket(*((XrdSutBucket *)(cent->buf3.buf))); - ceref.UnLock(); - - // Create a handshake cache - if (!(hs->Cref = new XrdSutPFEntry(hs->ID.c_str()))) { - cmsg = "cannot create cache entry"; - return -1; - } - // - // Deserialize main buffer - if (!((*bm) = new XrdSutBuffer(bckm->buffer,bckm->size))) { - cmsg = "error deserializing main buffer"; - return -1; - } - - // Deactivate what not need any longer - br->Deactivate(kXRS_issuer_hash); - - // - // Get options, if any - if (br->UnmarshalBucket(kXRS_clnt_opts, hs->Options) == 0) - br->Deactivate(kXRS_clnt_opts); - - // We are done - return 0; -} - -//_________________________________________________________________________ -int XrdSecProtocolgsi::ServerDoCert(XrdSutBuffer *br, XrdSutBuffer **bm, - String &cmsg) -{ - // Server side: process a kXGC_cert message. - // Return 0 on success, -1 on error. If the case, a message is returned - // in cmsg. - EPNAME("ServerDoCert"); - - XrdSutBucket *bck = 0; - XrdSutBucket *bckm = 0; - - // - // Extract the main buffer - if (!(bckm = br->GetBucket(kXRS_main))) { - cmsg = "main buffer missing"; - return -1; - } - // - // Extract cipher algorithm chosen by the client - String cip = ""; - if ((bck = br->GetBucket(kXRS_cipher_alg))) { - bck->ToString(cip); - // Parse the list - if (DefCipher.find(cip) == -1) { - cmsg = "unsupported cipher chosen by the client"; - hs->Chain = 0; - return -1; - } - // Deactivate the bucket - br->Deactivate(kXRS_cipher_alg); - } else { - NOTIFY("WARNING: client choice for cipher missing" - " - using default"); - } - - // First get the session cipher - if ((bck = br->GetBucket(kXRS_puk))) { - // - // Cleanup - SafeDelete(sessionKey); - // - // Prepare cipher agreement: make sure we have the reference cipher - if (!hs->Rcip) { - cmsg = "reference cipher missing"; - hs->Chain = 0; - return -1; - } - // Prepare cipher agreement: get a copy of the reference cipher - if (!(sessionKey = sessionCF->Cipher(*(hs->Rcip)))) { - cmsg = "cannot get reference cipher"; - hs->Chain = 0; - return -1; - } - // - // Instantiate the session cipher - if (!(sessionKey->Finalize(bck->buffer,bck->size,cip.c_str()))) { - cmsg = "cannot finalize session cipher"; - hs->Chain = 0; - return -1; - } - // - // We need it only once - br->Deactivate(kXRS_puk); - } - // - // Decrypt the main buffer with the session cipher, if available - if (sessionKey) { - if (!(sessionKey->Decrypt(*bckm))) { - cmsg = "error decrypting main buffer with session cipher"; - hs->Chain = 0; - return -1; - } - } - // - // Deserialize main buffer - if (!((*bm) = new XrdSutBuffer(bckm->buffer,bckm->size))) { - cmsg = "error deserializing main buffer"; - hs->Chain = 0; - return -1; - } - // - // Get version run by client, if there - if (hs->RemVers == -1) { - if ((*bm)->UnmarshalBucket(kXRS_version,hs->RemVers) != 0) { - hs->RemVers = Version; - cmsg = "client version information not found in options:" - " assume same as local"; - } else { - (*bm)->Deactivate(kXRS_version); - } - } - - // - // Get cache entry - if (!hs->Cref) { - cmsg = "session cache has gone"; - hs->Chain = 0; - return -1; - } - // - // make sure cache is not too old - int reftime = hs->TimeStamp - TimeSkew; - if (hs->Cref->mtime < reftime) { - cmsg = "cache entry expired"; - SafeDelete(hs->Cref); - hs->Chain = 0; - return -1; - } - - // - // Extract the client certificate - if (!(bck = (*bm)->GetBucket(kXRS_x509))) { - cmsg = "client certificate missing"; - SafeDelete(hs->Cref); - hs->Chain = 0; - return -1; - } - // - // Finalize chain: get a copy of it (we do not touch the reference) - hs->Chain = new X509Chain(hs->Chain); - if (!(hs->Chain)) { - cmsg = "cannot duplicate reference chain"; - return -1; - } - // The new chain must be deleted at destruction - hs->Options |= kOptsDelChn; - - // Get hook to parsing function - XrdCryptoX509ParseBucket_t ParseBucket = sessionCF->X509ParseBucket(); - if (!ParseBucket) { - cmsg = "cannot attach to ParseBucket function!"; - return -1; - } - // Parse bucket - int nci = (*ParseBucket)(bck, hs->Chain); - if (nci < 2) { - cmsg = "wrong number of certificates in received bucket ("; - cmsg += nci; - cmsg += " > 1 expected)"; - return -1; - } - // - // Verify the chain - x509ChainVerifyOpt_t vopt = {0,static_cast(hs->TimeStamp),-1,hs->Crl}; - XrdCryptoX509Chain::EX509ChainErr ecode = XrdCryptoX509Chain::kNone; - if (!(hs->Chain->Verify(ecode, &vopt))) { - cmsg = "certificate chain verification failed: "; - cmsg += hs->Chain->LastError(); - return -1; - } - - // - // Check if there will be delegated proxies; these can be through - // normal request+signature, or just forwarded by the client. - // In both cases we need to save the proxy chain. If we need a - // request, we have to prepare it and send it back to the client. - // Get hook to parsing function - XrdCryptoX509CreateProxyReq_t X509CreateProxyReq = sessionCF->X509CreateProxyReq(); - if (!X509CreateProxyReq) { - cmsg = "cannot attach to X509CreateProxyReq function!"; - return -1; - } - bool needReq = - ((PxyReqOpts & kOptsSrvReq) && (hs->Options & kOptsSigReq)) || - (hs->Options & kOptsDlgPxy); - if (needReq || (hs->Options & kOptsFwdPxy)) { - // Create a new proxy chain - hs->PxyChain = new X509Chain(); - // Add the current proxy - if ((*ParseBucket)(bck, hs->PxyChain) > 1) { - // Reorder it - hs->PxyChain->Reorder(); - if (needReq) { - // Create the request - XrdCryptoX509Req *rPXp = (XrdCryptoX509Req *) &(hs->RemVers); - XrdCryptoRSA *krPXp = 0; - if ((*X509CreateProxyReq)(hs->PxyChain->End(), &rPXp, &krPXp) == 0) { - // Save key in the cache - hs->Cref->buf4.buf = (char *)krPXp; - // Prepare export bucket for request - XrdSutBucket *bckr = rPXp->Export(); - // Add it to the main list - if ((*bm)->AddBucket(bckr) != 0) { - SafeDelete(hs->PxyChain); - NOTIFY("WARNING: proxy req: problem adding bucket to main buffer"); - } - } else { - SafeDelete(hs->PxyChain); - NOTIFY("WARNING: proxy req: problem creating request"); - } - } - } else { - SafeDelete(hs->PxyChain); - NOTIFY("WARNING: proxy req: wrong number of certificates"); - } - } - - // - // Extract the client public key - sessionKver = sessionCF->RSA(*(hs->Chain->End()->PKI())); - if (!sessionKver || !sessionKver->IsValid()) { - cmsg = "server certificate contains an invalid key"; - return -1; - } - // Deactivate certificate buffer - (*bm)->Deactivate(kXRS_x509); - - // - // Extract the MD algorithm chosen by the client - String md = ""; - if ((bck = br->GetBucket(kXRS_md_alg))) { - String mdlist; - bck->ToString(md); - // Parse the list - if (DefMD.find(md) == -1) { - cmsg = "unsupported MD chosen by the client"; - return -1; - } - // Deactivate - br->Deactivate(kXRS_md_alg); - } else { - NOTIFY("WARNING: client choice for digests missing" - " - using default"); - md = "md5"; - } - if (!(sessionMD = sessionCF->MsgDigest(md.c_str()))) { - cmsg = "could not instantiate digest object"; - return -1; - } - - // We are done - return 0; -} - -//_________________________________________________________________________ -int XrdSecProtocolgsi::ServerDoSigpxy(XrdSutBuffer *br, XrdSutBuffer **bm, - String &cmsg) -{ - // Server side: process a kXGC_sigpxy message. - // Return 0 on success, -1 on error. If the case, a message is returned - // in cmsg. - EPNAME("ServerDoSigpxy"); - - XrdSutBucket *bck = 0; - XrdSutBucket *bckm = 0; - - // - // Extract the main buffer - if (!(bckm = br->GetBucket(kXRS_main))) { - cmsg = "main buffer missing"; - return 0; - } - // - // Decrypt the main buffer with the session cipher, if available - if (sessionKey) { - if (!(sessionKey->Decrypt(*bckm))) { - cmsg = "error decrypting main buffer with session cipher"; - return 0; - } - } - // - // Deserialize main buffer - if (!((*bm) = new XrdSutBuffer(bckm->buffer,bckm->size))) { - cmsg = "error deserializing main buffer"; - return 0; - } - - // Get the bucket - if (!(bck = (*bm)->GetBucket(kXRS_x509))) { - cmsg = "buffer with requested info missing"; - // Is there a message from the client? - if (!(bck = (*bm)->GetBucket(kXRS_message))) { - // Yes: decode it and print it - String m; - bck->ToString(m); - DEBUG("msg from client: "<PxyChain; - if (!pxyc) { - cmsg = "the proxy chain is gone"; - return 0; - } - - // Action depend on the type of message - if ((hs->Options & kOptsFwdPxy)) { - // The bucket contains a private key to be added to the proxy - // public key - XrdCryptoRSA *kpx = pxyc->End()->PKI(); - if (kpx->ImportPrivate(bck->buffer, bck->size) != 0) { - cmsg = "problems importing private key"; - return 0; - } - } else { - // The bucket contains our request signed by the client - // The full key is in the cache - if (!hs->Cref) { - cmsg = "session cache has gone"; - return 0; - } - // Get the signed certificate - XrdCryptoX509 *npx = sessionCF->X509(bck); - if (!npx) { - cmsg = "could not resolve signed request"; - return 0; - } - // Set full PKI - XrdCryptoRSA *knpx = (XrdCryptoRSA *)(hs->Cref->buf4.buf); - npx->SetPKI((XrdCryptoX509data)(knpx->Opaque())); - // Add the new proxy ecert to the chain - pxyc->PushBack(npx); - } - // Save the chain in the instance - proxyChain = pxyc; - hs->PxyChain = 0; - // Notify - if (QTRACE(Authen)) { proxyChain->Dump(); } - - // - // Extract user login name, if any - String user; - if ((bck = (*bm)->GetBucket(kXRS_user))) { - bck->ToString(user); - (*bm)->Deactivate(kXRS_user); - } - if (user.length() <= 0) user = Entity.name; - - // Dump to file if required - if ((PxyReqOpts & kOptsPxFile)) { - if (user.length() > 0) { - String pxfile = UsrProxy, name; - struct passwd *pw = getpwnam(user.c_str()); - if (pw) { - name = pw->pw_name; - } else { - // Get Hash of the subject - XrdCryptoX509 *c = proxyChain->SearchBySubject(proxyChain->EECname()); - if (c) { - name = c->SubjectHash(); - } else { - cmsg = "proxy chain not dumped to file: could not find subject hash"; - return 0; - } - } - if (XrdSutResolve(pxfile, Entity.host, - Entity.vorg, Entity.grps, name.c_str()) != 0) { - PRINT("Problems resolving templates in "< placeholder - if (pw && pxfile.find("") != STR_NPOS) { - String suid; suid += (int) pw->pw_uid; - pxfile.replace("", suid.c_str()); - } - - // Get the function - XrdCryptoX509ChainToFile_t ctofile = sessionCF->X509ChainToFile(); - if ((*ctofile)(proxyChain,pxfile.c_str()) != 0) { - cmsg = "problems dumping proxy chain to file "; - cmsg += pxfile; - return 0; - } - } else { - cmsg = "proxy chain not dumped to file: entity name undefined"; - return 0; - } - } - - // We are done - return 0; -} - -//__________________________________________________________________ -void XrdSecProtocolgsi::ErrF(XrdOucErrInfo *einfo, kXR_int32 ecode, - const char *msg1, const char *msg2, - const char *msg3) -{ - // Filling the error structure - EPNAME("ErrF"); - - char *msgv[12]; - int k, i = 0, sz = strlen("Secgsi"); - - // - // Code message, if any - int cm = (ecode >= kGSErrParseBuffer && - ecode <= kGSErrError) ? (ecode-kGSErrParseBuffer) : -1; - const char *cmsg = (cm > -1) ? gGSErrStr[cm] : 0; - - // - // Build error message array - msgv[i++] = (char *)"Secgsi"; //0 - if (cmsg) {msgv[i++] = (char *)": "; //1 - msgv[i++] = (char *)cmsg; //2 - sz += strlen(msgv[i-1]) + 2; - } - if (msg1) {msgv[i++] = (char *)": "; //3 - msgv[i++] = (char *)msg1; //4 - sz += strlen(msgv[i-1]) + 2; - } - if (msg2) {msgv[i++] = (char *)": "; //5 - msgv[i++] = (char *)msg2; //6 - sz += strlen(msgv[i-1]) + 2; - } - if (msg3) {msgv[i++] = (char *)": "; //7 - msgv[i++] = (char *)msg3; //8 - sz += strlen(msgv[i-1]) + 2; - } - - // save it (or print it) - if (einfo) { - einfo->setErrInfo(ecode, (const char **)msgv, i); - } - if (QTRACE(Debug)) { - char *bout = new char[sz+10]; - if (bout) { - bout[0] = 0; - for (k = 0; k < i; k++) - sprintf(bout,"%s%s",bout,msgv[k]); - DEBUG(bout); - } else { - for (k = 0; k < i; k++) - DEBUG(msgv[k]); - } - } -} - -//__________________________________________________________________ -XrdSecCredentials *XrdSecProtocolgsi::ErrC(XrdOucErrInfo *einfo, - XrdSutBuffer *b1, - XrdSutBuffer *b2, - XrdSutBuffer *b3, - kXR_int32 ecode, - const char *msg1, - const char *msg2, - const char *msg3) -{ - // Error logging client method - - // Fill the error structure - ErrF(einfo, ecode, msg1, msg2, msg3); - - // Release buffers - REL3(b1,b2,b3); - - // We are done - return (XrdSecCredentials *)0; -} - -//__________________________________________________________________ -int XrdSecProtocolgsi::ErrS(String ID, XrdOucErrInfo *einfo, - XrdSutBuffer *b1, XrdSutBuffer *b2, - XrdSutBuffer *b3, kXR_int32 ecode, - const char *msg1, const char *msg2, - const char *msg3) -{ - // Error logging server method - - // Fill the error structure - ErrF(einfo, ecode, msg1, msg2, msg3); - - // Release buffers - REL3(b1,b2,b3); - - // We are done - return kgST_error; -} - -//______________________________________________________________________________ -bool XrdSecProtocolgsi::CheckRtag(XrdSutBuffer *bm, String &emsg) -{ - // Check random tag signature if it was sent with previous packet - EPNAME("CheckRtag"); - - // Make sure we got a buffer - if (!bm) { - emsg = "Buffer not defined"; - return 0; - } - // - // If we sent out a random tag check its signature - if (hs->Cref && hs->Cref->buf1.len > 0) { - XrdSutBucket *brt = 0; - if ((brt = bm->GetBucket(kXRS_signed_rtag))) { - // Make sure we got the right key to decrypt - if (!(sessionKver)) { - emsg = "Session cipher undefined"; - return 0; - } - // Decrypt it with the counter part public key - if (sessionKver->DecryptPublic(*brt) <= 0) { - emsg = "error decrypting random tag with public key"; - return 0; - } - } else { - emsg = "random tag missing - protocol error"; - return 0; - } - // - // Random tag cross-check: content - if (memcmp(brt->buffer,hs->Cref->buf1.buf,hs->Cref->buf1.len)) { - emsg = "random tag content mismatch"; - SafeDelete(hs->Cref); - // Remove: should not be checked a second time - return 0; - } - // - // Reset the cache entry but we will not use the info a second time - memset(hs->Cref->buf1.buf,0,hs->Cref->buf1.len); - hs->Cref->buf1.SetBuf(); - // - // Flag successful check - hs->RtagOK = 1; - bm->Deactivate(kXRS_signed_rtag); - DEBUG("Random tag successfully checked"); - } else { - DEBUG("Nothing to check"); - } - - // We are done - return 1; -} - -//______________________________________________________________________________ -XrdCryptoX509Crl *XrdSecProtocolgsi::LoadCRL(XrdCryptoX509 *xca, const char *subjhash, - XrdCryptoFactory *CF, int dwld, int &errcrl) -{ - // Scan crldir for a valid CRL certificate associated to CA whose - // certificate is xca. If 'dwld' is true try to download the CRL from - // the relevant URI, if any. - // If the CRL is found and is valid according - // to the chosen option, return its content in a X509Crl object. - // Return 0 in any other case - EPNAME("LoadCRL"); - XrdCryptoX509Crl *crl = 0; - errcrl = 0; - - // make sure we got what we need - if (!xca || !CF) { - PRINT("Invalid inputs"); - errcrl = -1; - return crl; - } - - // Get the CA hash - String cahash(subjhash); - int hashalg = 0; - if (strcmp(subjhash, xca->SubjectHash())) hashalg = 1; - // Drop the extension (".0") - String caroot(cahash, 0, cahash.find(".0")-1); - - // The dir - String crlext = XrdSecProtocolgsi::DefCRLext; - - String crldir; - int from = 0; - while ((from = CRLdir.tokenize(crldir, from, ',')) != -1) { - if (crldir.length() <= 0) continue; - // Add the default CRL extension and the dir - String crlfile = crldir + caroot; - crlfile += crlext; - DEBUG("target file: "<X509Crl(crlfile.c_str()))) { - if ((errcrl = VerifyCRL(crl, xca, crldir, CF, hashalg)) == 0) return crl; - } - SafeDelete(crl); - } - - // If not required, we are done - if (CRLCheck < 2 || (dwld == 0)) { - // Done - return crl; - } - - // If in 'required' mode, we will also try to load the CRL from the - // information found in the CA certificate or in the certificate directory. - // To avoid this overload, the CRL information should be installed offline, e.g. with - // utils/getCRLcert - - errcrl = 0; - // Try to retrieve it from the URI in the CA certificate, if any - if ((crl = CF->X509Crl(xca))) { - if ((errcrl = VerifyCRL(crl, xca, crldir, CF, hashalg)) == 0) return crl; - SafeDelete(crl); - } - - // Finally try the ".crl_url" file - from = 0; - while ((from = CRLdir.tokenize(crldir, from, ',')) != -1) { - if (crldir.length() <= 0) continue; - SafeDelete(crl); - String crlurl = crldir + caroot; - crlurl += ".crl_url"; - DEBUG("target file: "<X509Crl(line, 1))) { - if ((errcrl = VerifyCRL(crl, xca, crldir, CF, hashalg)) == 0) return crl; - SafeDelete(crl); - } - } - } - - // We need to parse the full dirs: make some cleanup first - from = 0; - while ((from = CRLdir.tokenize(crldir, from, ',')) != -1) { - if (crldir.length() <= 0) continue; - SafeDelete(crl); - // Open directory - DIR *dd = opendir(crldir.c_str()); - if (!dd) { - PRINT("could not open directory: "<d_name)) continue; - // File name contain the root CA hash - if (!strstr(dent->d_name,caroot.c_str())) continue; - // candidate name - String crlfile = crldir + dent->d_name; - DEBUG("analysing entry "<X509Crl(crlfile.c_str()))) { - if ((errcrl = VerifyCRL(crl, xca, crldir, CF, hashalg)) == 0) break; - SafeDelete(crl); - } - } - // Close dir - closedir(dd); - // Are we done? - if (crl) break; - } - - // We are done - return crl; -} - -//______________________________________________________________________________ -int XrdSecProtocolgsi::VerifyCRL(XrdCryptoX509Crl *crl, XrdCryptoX509 *xca, String crldir, - XrdCryptoFactory *CF, int hashalg) -{ - EPNAME("VerifyCRL"); - int rc = 0; - // Make sure they have the same issuer - if (!strcmp(xca->SubjectHash(hashalg), crl->IssuerHash(hashalg))) { - // Signing certificate file - String casigfile = crldir + crl->IssuerHash(hashalg); - DEBUG("CA signing certificate file = "<X509(casigfile.c_str()))) { - if (CRLCheck >= 2) { - PRINT("CA certificate to verify the signature ("<IssuerHash(hashalg)<< - ") could not be loaded - exit"); - } else { - DEBUG("CA certificate to verify the signature could not be loaded - verification skipped"); - } - rc = -3; - } else { - // Verify signature - if (crl->Verify(xcasig)) { - // Ok, we are done - if (CRLCheck >= 3 && crl && crl->IsExpired()) { - rc = -5; - NOTIFY("CRL is expired (CRLCheck: "<SubjectHash(hashalg)<< - " does not match CRL issuer "<IssuerHash(hashalg)<<"! "); - } - return rc; -} - -//______________________________________________________________________________ -String XrdSecProtocolgsi::GetCApath(const char *cahash) -{ - // Look in the paths defined by CAdir for the certificate file related to - // 'cahash', in the form /.0 - - String path; - String ent; - int from = 0; - while ((from = CAdir.tokenize(ent, from, ',')) != -1) { - if (ent.length() > 0) { - path = ent; - if (!path.endswith('/')) - path += "/"; - path += cahash; - if (!path.endswith(".0")) - path += ".0"; - if (!access(path.c_str(), R_OK)) - break; - } - path = ""; - } - - // Done - return path; -} -//______________________________________________________________________________ -bool XrdSecProtocolgsi::VerifyCA(int opt, X509Chain *cca, XrdCryptoFactory *CF) -{ - // Verify the CA in 'cca' according to 'opt': - // opt = 2 full check - // 1 only if self-signed - // 0 no check - EPNAME("VerifyCA"); - - bool verified = 0; - XrdCryptoX509Chain::ECAStatus st = XrdCryptoX509Chain::kUnknown; - cca->SetStatusCA(st); - - // We nust have got a chain - if (!cca) { - PRINT("Invalid input "); - return 0; - } - - // Get the parse function - XrdCryptoX509ParseFile_t ParseFile = CF->X509ParseFile(); - if (!ParseFile) { - PRINT("Cannot attach to the ParseFile function"); - return 0; - } - - // Point to the certificate - XrdCryptoX509 *xc = cca->Begin(); - // Is it self-signed ? - bool self = (!strcmp(xc->IssuerHash(), xc->SubjectHash())) ? 1 : 0; - if (!self) { - String inam; - if (opt == 2) { - // We are requested to verify it - bool notdone = 1; - // We need to load the issuer(s) CA(s) - XrdCryptoX509 *xd = xc; - while (notdone) { - X509Chain *ch = 0; - int ncis = -1; - for (int ha = 0; ha < 2; ha++) { - inam = GetCApath(xd->IssuerHash(ha)); - if (inam.length() <= 0) continue; - ch = new X509Chain(); - ncis = (*ParseFile)(inam.c_str(), ch); - if (ncis >= 1) break; - SafeDelete(ch); - } - if (ncis < 1) break; - XrdCryptoX509 *xi = ch->Begin(); - while (xi) { - if (!strcmp(xd->IssuerHash(), xi->SubjectHash())) - break; - xi = ch->Next(); - } - if (xi) { - // Add the certificate to the requested CA chain - ch->Remove(xi); - cca->PutInFront(xi); - SafeDelete(ch); - // We may be over - if (!strcmp(xi->IssuerHash(), xi->SubjectHash())) { - notdone = 0; - break; - } else { - // This becomes the daughter - xd = xi; - } - } else { - break; - } - } - if (!notdone) { - // Verify the chain - X509Chain::EX509ChainErr e; - x509ChainVerifyOpt_t vopt = {kOptsCheckSubCA, 0, -1, 0}; - if (!(verified = cca->Verify(e, &vopt))) - PRINT("CA certificate not self-signed: verification failed ("<SubjectHash()<<")"); - } else { - PRINT("CA certificate not self-signed: cannot verify integrity ("<SubjectHash()<<")"); - } - } else { - // Fill CA information - cca->CheckCA(0); - // Set OK in any case - verified = 1; - // Notify if some sort of check was required - if (opt == 1) { - NOTIFY("Warning: CA certificate not self-signed and" - " integrity not checked: assuming OK ("<SubjectHash()<<")"); - } - } - } else { - if (CACheck > 0) { - // Check self-signature - if (!(verified = cca->CheckCA())) - PRINT("CA certificate self-signed: integrity check failed ("<SubjectHash()<<")"); - } else { - // Set OK in any case - verified = 1; - // Notify if some sort of check was required - NOTIFY("Warning: CA certificate self-signed but" - " integrity not checked: assuming OK ("<SubjectHash()<<")"); - } - } - - // Set the status in the chain - st = (verified) ? XrdCryptoX509Chain::kValid : st; - cca->SetStatusCA(st); - - // Done - return verified; -} - -//_____________________________________________________________________________ -static bool GetCACheck(XrdSutCacheEntry *e, void *a) { - - EPNAME("GetCACheck"); - - int crl_check = (*((XrdSutCacheArg_t *)a)).arg1; - int crl_refresh = (*((XrdSutCacheArg_t *)a)).arg2; - time_t ts_ref = (time_t)(*((XrdSutCacheArg_t *)a)).arg3; - - if (!e) return false; - - X509Chain *chain = 0; - // If we had already something, check it, as we may be done - bool goodca = 0; - if ((chain = (X509Chain *)(e->buf1.buf))) { - // Check the validity of the certificates in the chain; if a certificate became invalid, - // we need to reload a valid one for the same CA. - if (chain->CheckValidity() == 0) { - goodca = 1; - } else { - PRINT("CA entry for '"<name<<"' needs refreshing: clean the related entry cache first"); - return false; - } - } - if (goodca) { - XrdCryptoX509Crl *crl = (XrdCryptoX509Crl *)(e->buf2.buf); - bool goodcrl = 1; - if ((crl_check == 2 && !crl) || (crl_check == 3 && crl->IsExpired())) goodcrl = 0; - if (crl_refresh > 0 && ((ts_ref - e->mtime) > crl_refresh)) goodcrl = 0; - if (goodcrl) { - return true; - } else if (crl) { - PRINT("CRL entry for '"<name<<"' needs refreshing: clean the related entry cache first ("</.0 . - // If 'hs' is defined, store pointers to chain and crl into 'hs'. - // Return 0 if ok, -1 if not available, -2 if CRL not ok - EPNAME("GetCA"); - XrdSutCERef ceref; - int rc = 0; - - // We nust have got a CA hash - if (!cahash || !cf) { - PRINT("Invalid input "); - return -1; - } - - // Timestamp - time_t timestamp = (hs) ? hs->TimeStamp : time(0); - - // The tag - String tag(cahash,20); - tag += ':'; - tag += cf->ID(); - DEBUG("Querying cache for tag: "<rwmtx)); - - // Point to the content - X509Chain *chain = (X509Chain *)(cent->buf1.buf); - XrdCryptoX509Crl *crl = (XrdCryptoX509Crl *)(cent->buf2.buf); - - // If invalid we fail - if (cent->status == kCE_inactive) { - // Cleanup and remove existing invalid entries - if (chain) stackCA.Del(chain); - if (crl) stackCRL.Del(crl); - PRINT("unable to get a valid entry from cache for " << tag); - return -1; - } - - // Check if we are done - if (rdlock) { - // Save chain - if (hs) hs->Chain = chain; - stackCA.Add(chain); - // Save crl - if (crl) { - if (hs) hs->Crl = crl; - // Add to the stack for proper cleaning of invalidated CRLs - stackCRL.Add(crl); - } - return 0; - } - - // Cleanup and remove existing invalid entries - if (chain) stackCA.Del(chain); - if (crl) stackCRL.Del(crl); - - chain = 0; - crl = 0; - cent->buf1.buf = 0; - cent->buf2.buf = 0; - - // If not, prepare the file name - String fnam = GetCApath(cahash); - DEBUG("trying to load CA certificate from "<Chain) ? 0 : 1; - chain = (createchain) ? new X509Chain() : hs->Chain; - if (!chain) { - PRINT("could not attach-to or create new GSI chain"); - rc = -1; - } - - // Get the parse function - XrdCryptoX509ParseFile_t ParseFile = cf->X509ParseFile(); - if (rc == 0 && ParseFile) { - int nci = (createchain) ? (*ParseFile)(fnam.c_str(), chain) : 1; - bool ok = 0, verified = 0; - if (nci == 1) { - // Verify the CA - verified = VerifyCA(CACheck, chain, cf); - XrdCryptoX509Crl *crl = 0; - if (verified) { - // Get CRL, if required - ok = 1; - if (CRLCheck > 0) { - int errcrl = 0; - if ((crl = LoadCRL(chain->EffCA(), cahash, cf, CRLDownload, errcrl))) { - // Good CA - DEBUG("CRL successfully loaded"); - } else { - String em = "missing or expired: ignoring"; - if ((CRLCheck == 1 && errcrl != 0 && errcrl != -5) || (CRLCheck >= 2 && errcrl != 0)) { - ok = 0; - em = "invalid: failing"; - } else if (CRLCheck >= 2) { - ok = 0; - em = "missing or expired: failing"; - } - NOTIFY("CRL is "<buf1.buf = (char *)(chain); - cent->buf1.len = 0; // Just a flag - stackCA.Add(chain); - if (crl) { - cent->buf2.buf = (char *)(crl); - cent->buf2.len = 0; // Just a flag - stackCRL.Add(crl); - } - cent->mtime = timestamp; - cent->status = kCE_ok; - cent->cnt = 0; - // Fill output, if required - if (hs) { - hs->Chain = chain; - hs->Crl = crl; - if (strcmp(cahash, chain->Begin()->SubjectHash())) hs->HashAlg = 1; - } - } else { - SafeDelete(crl); - SafeDelete(chain); - rc = -2; - } - } else { - SafeDelete(chain); - NOTIFY("certificate not found or invalid (nci: "<key, &st) != 0) { - PRINT("cannot access private key file: "<key); - return 1; - } - if (!S_ISREG(st.st_mode) || S_ISDIR(st.st_mode) || - (st.st_mode & (S_IWGRP | S_IWOTH)) != 0 || - (st.st_mode & (S_IRGRP | S_IROTH)) != 0) { - PRINT("wrong permissions for file: "<key<< " (should be 0600)"); - return 1; - } - // - // Validity - int valid = (pi->valid) ? XrdSutParseTime(pi->valid, 1) : -1; - // - // Options - XrdProxyOpt_t pxopt = {pi->bits, // bits in key - valid, // duration validity in secs - pi->deplen}; // signature path depth - // - // Init now - XrdCryptoX509CreateProxy_t X509CreateProxy = cf->X509CreateProxy(); - if (!X509CreateProxy) { - PRINT("cannot attach to X509CreateProxy function!"); - return 1; - } - rc = (*X509CreateProxy)(pi->cert, pi->key, &pxopt, ch, kp, pi->out); -#else - // command string - String cmd(kMAXBUFLEN); - - // Check if GLOBUS_LOCATION is defined - if (getenv("GLOBUS_LOCATION")) - cmd = "source $GLOBUS_LOCATION/etc/globus-user-env.sh;"; - - // Add main command - cmd += " grid-proxy-init"; - - // Add user cert - cmd += " -cert "; - cmd += pi->cert; - - // Add user key - cmd += " -key "; - cmd += pi->key; - - // Add CA dir (no support for multi-dirs) - String cdir(pi->certdir); - cdir.erase(cdir.find(',')); - cmd += " -certdir "; - cmd += cdir; - - // Add validity - if (pi->valid) { - cmd += " -valid "; - cmd += pi->valid; - } - - // Add number of bits in key - if (pi->bits > 512) { - cmd += " -bits "; - cmd += pi->bits; - } - - // Add depth of signature path - if (pi->deplen > -1) { - cmd += " -path-length "; - cmd += pi->deplen; - } - - // Add output proxy coordinates - if (pi->out) { - cmd += " -out "; - cmd += pi->out; - } - // Notify - DEBUG("executing: " << cmd); - - // Execute - rc = system(cmd.c_str()); - DEBUG("return code: "<< rc << " (0x"<<(int *)rc<<")"); -#endif - - // We are done - return rc; -} - -//__________________________________________________________________________ -int XrdSecProtocolgsi::ParseCAlist(String calist) -{ - // Parse received ca list, find the first available CA in the list - // and return a chain initialized with such a CA. - // If nothing found return 0. - EPNAME("ParseCAlist"); - - // Check inputs - if (calist.length() <= 0) { - PRINT("nothing to parse"); - return -1; - } - DEBUG("parsing list: "<Chain = 0; - String cahash = ""; - // Parse list - if (calist.length()) { - int from = 0; - while ((from = calist.tokenize(cahash, from, '|')) != -1) { - // Check this hash - if (cahash.length()) { - // Make sure the extension ".0" if there, as external implementations may not - // include it - if (!cahash.endswith(".0")) cahash += ".0"; - // Get the CA chain - if (GetCA(cahash.c_str(), sessionCF, hs) == 0) - return 0; - } - } - } - - // We did not find it - return -1; -} - -//__________________________________________________________________________ -int XrdSecProtocolgsi::ParseCrypto(String clist) -{ - // Parse crypto list clist, extracting the first available module - // and getting a related local cipher and a related reference - // cipher to be used to agree the session cipher; the local lists - // crypto info is updated, if needed - // The results are used to fill the handshake part of the protocol - // instance. - EPNAME("ParseCrypto"); - - // Check inputs - if (clist.length() <= 0) { - NOTIFY("empty list: nothing to parse"); - return -1; - } - DEBUG("parsing list: "<CryptoMod = ""; - - // Parse list - int from = 0; - while ((from = clist.tokenize(hs->CryptoMod, from, '|')) != -1) { - // Check this module - if (hs->CryptoMod.length() > 0) { - DEBUG("found module: "<CryptoMod); - // Load the crypto factory - if ((sessionCF = - XrdCryptoFactory::GetCryptoFactory(hs->CryptoMod.c_str()))) { - sessionCF->SetTrace(GSITrace->What); - if (QTRACE(Debug)) sessionCF->Notify(); - int fid = sessionCF->ID(); - int i = 0; - // Retrieve the index in local table - while (i < ncrypt) { - if (cryptID[i] == fid) break; - i++; - } - if (i >= ncrypt) { - if (ncrypt == XrdCryptoMax) { - DEBUG("max number of crypto slots reached - do nothing"); - return 0; - } else { - // Add new entry - cryptF[i] = sessionCF; - cryptID[i] = fid; - ncrypt++; - } - } - // On servers the ref cipher should be defined at this point - hs->Rcip = refcip[i]; - // we are done - return 0; - } - } - } - - // Nothing found - return -1; -} - -//_____________________________________________________________________________ -static bool QueryProxyCheck(XrdSutCacheEntry *e, void *a) { - - time_t ts_ref = (time_t)(*((XrdSutCacheArg_t *)a)).arg1; - - if (e && e->buf1.buf) { - X509Chain *chain = (X509Chain *)(e->buf1.buf); - if (chain->CheckValidity(1, ts_ref) == 0) return true; - } - return false; -} - - -//__________________________________________________________________________ -int XrdSecProtocolgsi::QueryProxy(bool checkcache, XrdSutCache *cache, - const char *tag, XrdCryptoFactory *cf, - time_t timestamp, ProxyIn_t *pi, ProxyOut_t *po) -{ - // Query users proxies, initializing if needed - EPNAME("QueryProxy"); - XrdSutCERef ceref; - - bool hasproxy = 0; - // We may already loaded valid proxies - bool rdlock = false; - XrdSutCacheArg_t arg = {timestamp, -1, -1, -1}; - XrdSutCacheEntry *cent = cache->Get(tag, rdlock, QueryProxyCheck, (void *) &arg); - if (!cent) { - PRINT("cannot get cache entry for: "<rwmtx)); - - if (checkcache && rdlock) { - po->chain = (X509Chain *)(cent->buf1.buf); - po->ksig = (XrdCryptoRSA *)(cent->buf2.buf); - po->cbck = (XrdSutBucket *)(cent->buf3.buf); - // We are done - ceref.UnLock(); - return 0; - } - - // Cleanup the chain - po->chain = (X509Chain *)(cent->buf1.buf); - if (po->chain) po->chain->Cleanup(); - SafeDelete(po->chain); - - // Cleanup cache entry - cent->buf1.buf = 0; - cent->buf1.len = 0; - // The key is deleted by the certificate destructor - // Just reset the buffer - cent->buf2.buf = 0; - cent->buf2.len = 0; - // and the related bucket - if (cent->buf3.buf) - delete (XrdSutBucket *)(cent->buf3.buf); - cent->buf3.buf = 0; - cent->buf3.len = 0; - - // - // We do not have good proxies, try load (user may have initialized - // them in the meanwhile) - // Create a new chain first, if needed - if (!(po->chain)) - po->chain = new X509Chain(); - if (!(po->chain)) { - PRINT("cannot create new chain!"); - return -1; - } - int ntry = 3; - bool parsefile = 1; - bool exportbucket = 0; - XrdCryptoX509ParseFile_t ParseFile = 0; - XrdCryptoX509ParseBucket_t ParseBucket = 0; - while (!hasproxy && ntry > 0) { - - // Try init as last option - if (ntry == 1) { - - // Cleanup the chain - po->chain->Cleanup(); - - if (InitProxy(pi, cf, po->chain, &(po->ksig)) != 0) { - NOTIFY("problems initializing proxy via external shell"); - ntry--; - continue; - } - // We need to explicitely export the proxy in a bucket - exportbucket = 1; -#ifndef HASGRIDPROXYINIT - // Chain is already loaded if we used the internal function - // to initialize the proxies - parsefile = 0; - timestamp = time(0); -#endif - } - ntry--; - - // - // A proxy chain may have been passed via XrdSecCREDS: check that first - if (ntry == 2) { - - char *cbuf = getenv("XrdSecCREDS"); - if (cbuf) { - // Import into a bucket - XrdSutBucket xbck(0, 0, kXRS_x509); - // Fill bucket - xbck.SetBuf(cbuf, strlen(cbuf)); - // Parse the bucket - if (!(ParseBucket = cf->X509ParseBucket())) { - PRINT("cannot attach to ParseBucket function!"); - continue; - } - int nci = (*ParseBucket)(&xbck, po->chain); - if (nci < 2) { - NOTIFY("proxy bucket must have at least two certificates" - " (found: "<X509ParseFile())) { - PRINT("cannot attach to ParseFile function!"); - continue; - } - } - // Parse the proxy file - int nci = (*ParseFile)(pi->out, po->chain); - if (nci < 2) { - DEBUG("proxy files must have at least two certificates" - " (found: "< 1) ? 1 : 0; - po->chain->CheckCA(checkselfsigned); - exportbucket = 1; - } - } - - // Check validity in time - if (po->chain->CheckValidity(1, timestamp) != 0) { - NOTIFY("proxy files contains expired certificates"); - continue; - } - - // Reorder chain - if (po->chain->Reorder() != 0) { - NOTIFY("proxy files contains inconsistent certificates"); - continue; - } - - // Check key - po->ksig = po->chain->End()->PKI(); - if (po->ksig->status != XrdCryptoRSA::kComplete) { - NOTIFY("proxy files contain invalid key pair"); - continue; - } - - XrdCryptoX509ExportChain_t ExportChain = cf->X509ExportChain(); - if (!ExportChain) { - PRINT("cannot attach to ExportChain function!"); - continue; - } - - // Create bucket for export - if (exportbucket) { - po->cbck = (*ExportChain)(po->chain, 0); - if (!(po->cbck)) { - PRINT("could not create bucket for export"); - continue; - } - } - - // Save info in cache - cent->mtime = po->chain->End()->NotAfter(); // the expiring time - cent->status = kCE_special; // distinguish from normal certs - cent->cnt = 0; - // The chain - cent->buf1.buf = (char *)(po->chain); - cent->buf1.len = 0; // Just a flag - // The key - cent->buf2.buf = (char *)(po->chain->End()->PKI()); - cent->buf2.len = 0; // Just a flag - // The export bucket - cent->buf3.buf = (char *)(po->cbck); - cent->buf3.len = 0; // Just a flag - - // Set the positive flag - hasproxy = 1; - } - // Always unlock - ceref.UnLock(); - - // We are done - if (!hasproxy) { - // Some cleanup - po->chain->Cleanup(); - SafeDelete(po->chain); - SafeDelete(po->cbck); - return -1; - } - return 0; -} - - -//_____________________________________________________________________________ -static bool QueryGMAPCheck(XrdSutCacheEntry *e, void *a) { - int st_ref = (*((XrdSutCacheArg_t *)a)).arg1; - time_t ts_ref = (time_t)(*((XrdSutCacheArg_t *)a)).arg2; - long to_ref = (*((XrdSutCacheArg_t *)a)).arg3; - if (e) { - // Check expiration, if required - if ((e->status != st_ref) || - ((e->status == st_ref) && - (to_ref > 0) && - ((ts_ref - e->mtime) > to_ref))) { - return false; - } else { - return true; - } - } - return false; -} - -//__________________________________________________________________________ -void XrdSecProtocolgsi::QueryGMAP(XrdCryptoX509Chain *chain, int now, String &usrs) -{ - // Resolve usernames associated with this proxy. The lookup is typically - // based on the 'dn' (either in the grid mapfile or via the 'GMAPFun' plugin) but - // it can also be based on the full proxy via the AuthzFun plugin. - // For 'grid mapfile' and 'GMAPFun' the result is kept valid for a certain amount - // of time, hashed on the 'dn'. - // On return, an empty string in 'usrs' indicates failure. - // Note that 'usrs' can be a comma-separated list of usernames. - EPNAME("QueryGMAP"); - - // List of user names attached to the entity - usrs = ""; - - // The chain must be defined - if (!chain) { - PRINT("input chain undefined!"); - return; - } - - // Now we check the DN-mapping function and eventually the gridmap file. - // The result can be cached for a while. - const char *dn = chain->EECname(); - if (GMAPFun) { - XrdSutCERef ceref; - bool rdlock = false; - XrdSutCacheArg_t arg = {kCE_ok, now, GMAPCacheTimeOut, -1}; - XrdSutCacheEntry *cent = cacheGMAPFun.Get(dn, rdlock, QueryGMAPCheck, (void *) &arg); - if (!cent) { - PRINT("unable to get a valid entry from cache for dn: " << dn); - return; - } - ceref.Set(&(cent->rwmtx)); - - // Check if we need to get/update the content - if (!rdlock) { - // Run the search via the external function - char *name = (*GMAPFun)(dn, now); - if (name) { - cent->status = kCE_ok; - // Add username - SafeDelArray(cent->buf1.buf); - cent->buf1.buf = name; - cent->buf1.len = strlen(name); - } - // Fill up the rest - cent->cnt = 0; - cent->mtime = now; // creation time - } - // Retrieve result form cache - usrs = cent->buf1.buf; - // We are done with the cache - ceref.UnLock(); - } - - // Check the map file, if any - // - if (servGMap) { - char u[65]; - if (servGMap->dn2user(dn, u, sizeof(u), now) == 0) { - if (usrs.length() > 0) usrs += ","; - usrs += (const char *)u; - } - } - - // Done - return; -} - -//_____________________________________________________________________________ -XrdSecgsiGMAP_t XrdSecProtocolgsi::LoadGMAPFun(const char *plugin, - const char *parms) -{ - // Load the DN-Username mapping function from the specified plug-in - EPNAME("LoadGMAPFun"); - char errBuff[2048]; - - // Make sure the input config file is defined - if (!plugin || strlen(plugin) <= 0) { - PRINT("plug-in file undefined"); - return (XrdSecgsiGMAP_t)0; - } - - // Create the plug-in instance - XrdOucPinLoader gmapLib(errBuff,sizeof(errBuff),gsiVersion,"gmaplib",plugin); - - // Use global symbols? - bool useglobals = 0; - XrdOucString params, ps(parms), p; - int from = 0; - while ((from = ps.tokenize(p, from, '|')) != -1) { - if (p == "useglobals") { - useglobals = 1; - } else { - if (params.length() > 0) params += " "; - params += p; - } - } - DEBUG("params: '"<< params<<"'; useglobals: "< 0) params += " "; - params += p; - } - } - DEBUG("params: '"<< params<<"'; useglobals: "< 0) params += " "; - params += p; - } - } - DEBUG("params: '"<< params<<"'; useglobals: "<[/*]" - if (Entity.host) { - if (srvcn != (const char *) Entity.host) { - int ih = srvcn.find((const char *) Entity.host); - if (ih == 0 || (ih > 0 && srvcn[ih-1] == '/')) { - ih += strlen(Entity.host); - if (ih >= srvcn.length() || - srvcn[ih] == '\0' || srvcn[ih] == '/') allowed = 1; - } - } else { - allowed = 1; - } - // Update the error msg, if the case - if (!allowed) { - if (emsg.length() <= 0) { - emsg = "server certificate CN '"; emsg += srvcn; - emsg += "' does not match the expected format(s):"; - } - String defcn("[*/]"); defcn += Entity.host; defcn += "[/*]"; - emsg += " '"; emsg += defcn; emsg += "' (default)"; - } - } - - // Take into account specific requests, if any - if (SrvAllowedNames.length() > 0) { - // The SrvAllowedNames string contains the allowed formats separated by a '|'. - // The specifications can contain the or placeholders which - // are replaced by Entity.host; they can also contain the '*' wildcard, in - // which case XrdOucString::matches is used. A '-' before the specification - // will deny the matching CN's; the last matching wins. - String allowedfmts(SrvAllowedNames); - allowedfmts.replace("", (const char *) Entity.host); - allowedfmts.replace("", (const char *) Entity.host); - int from = 0; - String fmt; - while ((from = allowedfmts.tokenize(fmt, from, '|')) != -1) { - // Check if this should be denied - bool deny = 0; - if (fmt.beginswith("-")) { - deny = 1; - fmt.erasefromstart(1); - } - if (srvcn.matches(fmt.c_str()) > 0) allowed = (deny) ? 0 : 1; - } - // Update the error msg, if the case - if (!allowed) { - if (emsg.length() <= 0) { - emsg = "server certificate CN '"; emsg += srvcn; - emsg += "' does not match the expected format:"; - } - emsg += " '"; emsg += SrvAllowedNames; emsg += "' (exceptions)"; - } - } - // Reset error msg, if the match was successful - if (allowed) - emsg = ""; - else - emsg += "; exceptions are controlled by the env XrdSecGSISRVNAMES"; - - // Done - return allowed; -} - -//_____________________________________________________________________________ -static bool GetSrvCertEntCheck(XrdSutCacheEntry *e, void *a) { - int st_ref = (*((XrdSutCacheArg_t *)a)).arg1; - time_t ts_ref = (time_t)(*((XrdSutCacheArg_t *)a)).arg2; - if (e) { - if (e->status > st_ref) { - if (e->mtime >= ts_ref) - return true; - } - } - return false; -} - -//_____________________________________________________________________________ -XrdSutCacheEntry *XrdSecProtocolgsi::GetSrvCertEnt(XrdSutCERef &ceref, - XrdCryptoFactory *cf, - time_t timestamp, String &certcalist) -{ - // Get cache entry for server certificate. This function checks the cache - // and loads or re-loads the certificate form the specified files if required. - // make sure we got what we need - EPNAME("GetSrvCertEnt"); - - if (!cf) { - PRINT("Invalid inputs"); - return (XrdSutCacheEntry *)0; - } - - bool rdlock = false; - XrdSutCacheArg_t arg = {kCE_allowed, timestamp, -1, -1}; - XrdSutCacheEntry *cent = cacheCert.Get(cf->Name(), rdlock, GetSrvCertEntCheck, (void *) &arg); - if (!cent) { - PRINT("unable to get a valid entry from cache for " << cf->Name()); - return (XrdSutCacheEntry *)0; - } - ceref.Set(&(cent->rwmtx)); - - // Are we done ? - if (rdlock) return cent; - if (cent->buf1.buf) PRINT("entry has expired: trying to renew ..."); - - // Try get one or renew-it - if (cent->status == kCE_special) { - // Try init proxies - ProxyIn_t pi = {SrvCert.c_str(), SrvKey.c_str(), CAdir.c_str(), - UsrProxy.c_str(), PxyValid.c_str(), 0, 512}; - X509Chain *ch = 0; - XrdCryptoRSA *k = 0; - XrdSutBucket *b = 0; - ProxyOut_t po = {ch, k, b }; - // We lock inside - ceref.UnLock(false); - if (QueryProxy(0, &cacheCert, cf->Name(), cf, timestamp, &pi, &po) != 0) { - PRINT("proxy expired and cannot be renewed"); - return (XrdSutCacheEntry *)0; - } - // When successful we return read-locked (this flow needs checking; but it is not mainstream) - ceref.ReadLock(); - return cent; - } - - // Reset the entry - delete (XrdCryptoX509 *) cent->buf1.buf; // Destroys also xsrv->PKI() pointed in cent->buf2.buf - delete (XrdSutBucket *) cent->buf3.buf; - cent->buf1.buf = 0; - cent->buf2.buf = 0; - cent->buf3.buf = 0; - - // - // Get the IDs of the file: we need them to acquire the right privileges when opening - // the certificate - uid_t gsi_uid = geteuid(); - gid_t gsi_gid = getegid(); - struct stat st; - if (!stat(SrvKey.c_str(), &st)) { - if (st.st_uid != gsi_uid || st.st_gid != gsi_gid) { - gsi_uid = st.st_uid; - gsi_gid = st.st_gid; - } - } - - // Check normal certificates - XrdCryptoX509 *xsrv = cf->X509(SrvCert.c_str(), SrvKey.c_str()); - if (xsrv) { - // Must be of EEC type - if (xsrv->type != XrdCryptoX509::kEEC) { - PRINT("problems loading srv cert: not EEC but: "<Type()); - SafeDelete(xsrv); - ceref.UnLock(); - return (XrdSutCacheEntry *)0; - } - // Must be valid - if (!(xsrv->IsValid())) { - PRINT("problems loading srv cert: invalid"); - SafeDelete(xsrv); - ceref.UnLock(); - return (XrdSutCacheEntry *)0; - } - // PKI must have been successfully initialized - if (!xsrv->PKI() || xsrv->PKI()->status != XrdCryptoRSA::kComplete) { - PRINT("problems loading srv cert: invalid PKI"); - SafeDelete(xsrv); - ceref.UnLock(); - return (XrdSutCacheEntry *)0; - } - // Must be exportable - XrdSutBucket *xbck = xsrv->Export(); - if (!xbck) { - PRINT("problems loading srv cert: cannot export into bucket"); - SafeDelete(xsrv); - ceref.UnLock(); - return (XrdSutCacheEntry *)0; - } - // We must have the issuing CA certificate - int rcgetca = 0; - if ((rcgetca = GetCA(xsrv->IssuerHash(), cf)) != 0) { - String emsg(xsrv->IssuerHash()); - // Try different name hash, if it makes sense - if (strcmp(xsrv->IssuerHash(1), xsrv->IssuerHash(0))) { - if ((rcgetca = GetCA(xsrv->IssuerHash(1), cf)) != 0) { - emsg += "|"; - emsg += xsrv->IssuerHash(1); - } - } - if (rcgetca != 0) { - // We do not have it, really - if (rcgetca == -1) { - PRINT("do not have certificate for the issuing CA '"<status = kCE_ok; - cent->cnt = 0; - cent->mtime = xsrv->NotAfter(); // expiration time - // Save pointer to certificate (destroys also xsrv->PKI()) - if (cent->buf1.buf) delete (XrdCryptoX509 *) cent->buf1.buf; - cent->buf1.buf = (char *)xsrv; - cent->buf1.len = 0; // just a flag - // Save pointer to key - cent->buf2.buf = 0; - cent->buf2.buf = (char *)(xsrv->PKI()); - cent->buf2.len = 0; // just a flag - // Save pointer to bucket - if (cent->buf3.buf) delete (XrdSutBucket *) cent->buf3.buf; - cent->buf3.buf = (char *)(xbck); - cent->buf3.len = 0; // just a flag - // Save CA hash in list to communicate to clients - if (certcalist.find(xsrv->IssuerHash()) == STR_NPOS) { - if (certcalist.length() > 0) certcalist += "|"; - certcalist += xsrv->IssuerHash(); - } - // Save also old CA hash in list to communicate to clients, if relevant - if (HashCompatibility && xsrv->IssuerHash(1) && - strcmp(xsrv->IssuerHash(1),xsrv->IssuerHash())) { - if (certcalist.find(xsrv->IssuerHash(1)) == STR_NPOS) { - if (certcalist.length() > 0) certcalist += "|"; - certcalist += xsrv->IssuerHash(1); - } - } - } else { - PRINT("failed to load certificate from files ("<< SrvCert <<","<. */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/* */ -/******************************************************************************/ -#include - -#include "XrdNet/XrdNetAddrInfo.hh" - -#include "XrdOuc/XrdOucErrInfo.hh" -#include "XrdOuc/XrdOucGMap.hh" -#include "XrdOuc/XrdOucHash.hh" -#include "XrdOuc/XrdOucString.hh" -#include "XrdOuc/XrdOucTokenizer.hh" - -#include "XrdSys/XrdSysPthread.hh" - -#include "XrdSec/XrdSecInterface.hh" -#include "XrdSecgsi/XrdSecgsiTrace.hh" - -#include "XrdSut/XrdSutCache.hh" - -#include "XrdSut/XrdSutPFEntry.hh" -#include "XrdSut/XrdSutPFile.hh" -#include "XrdSut/XrdSutBuffer.hh" -#include "XrdSut/XrdSutRndm.hh" - -#include "XrdCrypto/XrdCryptoAux.hh" -#include "XrdCrypto/XrdCryptoCipher.hh" -#include "XrdCrypto/XrdCryptoFactory.hh" -#include "XrdCrypto/XrdCryptoX509Crl.hh" - -#include "XrdCrypto/XrdCryptogsiX509Chain.hh" - -/******************************************************************************/ -/* D e f i n e s */ -/******************************************************************************/ - -typedef XrdOucString String; -typedef XrdCryptogsiX509Chain X509Chain; - -#define XrdSecPROTOIDENT "gsi" -#define XrdSecPROTOIDLEN sizeof(XrdSecPROTOIDENT) -#define XrdSecgsiVERSION 10300 -#define XrdSecNOIPCHK 0x0001 -#define XrdSecDEBUG 0x1000 -#define XrdCryptoMax 10 - -#define kMAXBUFLEN 1024 - -// -// Message codes either returned by server or included in buffers -enum kgsiStatus { - kgST_error = -1, // error occured - kgST_ok = 0, // ok - kgST_more = 1 // need more info -}; - -// Client steps -enum kgsiClientSteps { - kXGC_none = 0, - kXGC_certreq = 1000, // 1000: request server certificate - kXGC_cert, // 1001: packet with (proxy) certificate - kXGC_sigpxy, // 1002: packet with signed proxy certificate - kXGC_reserved // -}; - -// Server steps -enum kgsiServerSteps { - kXGS_none = 0, - kXGS_init = 2000, // 2000: fake code used the first time - kXGS_cert, // 2001: packet with certificate - kXGS_pxyreq, // 2002: packet with proxy req to be signed - kXGS_reserved // -}; - -// Handshake options -enum kgsiHandshakeOpts { - kOptsDlgPxy = 1, // 0x0001: Ask for a delegated proxy - kOptsFwdPxy = 2, // 0x0002: Forward local proxy - kOptsSigReq = 4, // 0x0004: Accept to sign delegated proxy - kOptsSrvReq = 8, // 0x0008: Server request for delegated proxy - kOptsPxFile = 16, // 0x0010: Save delegated proxies in file - kOptsDelChn = 32 // 0x0020: Delete chain -}; - -// Error codes -enum kgsiErrors { - kGSErrParseBuffer = 10000, // 10000 - kGSErrDecodeBuffer, // 10001 - kGSErrLoadCrypto, // 10002 - kGSErrBadProtocol, // 10003 - kGSErrCreateBucket, // 10004 - kGSErrDuplicateBucket, // 10005 - kGSErrCreateBuffer, // 10006 - kGSErrSerialBuffer, // 10007 - kGSErrGenCipher, // 10008 - kGSErrExportPuK, // 10009 - kGSErrEncRndmTag, // 10010 - kGSErrBadRndmTag, // 10011 - kGSErrNoRndmTag, // 10012 - kGSErrNoCipher, // 10013 - kGSErrNoCreds, // 10014 - kGSErrBadOpt, // 10015 - kGSErrMarshal, // 10016 - kGSErrUnmarshal, // 10017 - kGSErrSaveCreds, // 10018 - kGSErrNoBuffer, // 10019 - kGSErrRefCipher, // 10020 - kGSErrNoPublic, // 10021 - kGSErrAddBucket, // 10022 - kGSErrFinCipher, // 10023 - kGSErrInit, // 10024 - kGSErrBadCreds, // 10025 - kGSErrError // 10026 -}; - -#define REL1(x) { if (x) delete x; } -#define REL2(x,y) { if (x) delete x; if (y) delete y; } -#define REL3(x,y,z) { if (x) delete x; if (y) delete y; if (z) delete z; } - -#define SafeDelete(x) { if (x) delete x ; x = 0; } -#define SafeDelArray(x) { if (x) delete [] x ; x = 0; } -#define SafeFree(x) { if (x) free(x) ; x = 0; } - -// External functions for generic mapping -typedef char *(*XrdSecgsiGMAP_t)(const char *, int); -typedef int (*XrdSecgsiAuthz_t)(XrdSecEntity &); -typedef int (*XrdSecgsiAuthzInit_t)(const char *); -typedef int (*XrdSecgsiAuthzKey_t)(XrdSecEntity &, char **); -// VOMS extraction -typedef XrdSecgsiAuthz_t XrdSecgsiVOMS_t; -typedef XrdSecgsiAuthzInit_t XrdSecgsiVOMSInit_t; -// -// This a small class to set the relevant options in one go -// -class XrdOucGMap; -class XrdOucTrace; -class gsiOptions { -public: - short debug; // [cs] debug flag - char mode; // [cs] 'c' or 's' - char *clist; // [s] list of crypto modules ["ssl" ] - char *certdir;// [cs] dir with CA info [/etc/grid-security/certificates] - char *crldir; // [cs] dir with CRL info [/etc/grid-security/certificates] - char *crlext; // [cs] extension of CRL files [.r0] - char *cert; // [s] server certificate [/etc/grid-security/root/rootcert.pem] - // [c] user certificate [$HOME/.globus/usercert.pem] - char *key; // [s] server private key [/etc/grid-security/root/rootkey.pem] - // [c] user private key [$HOME/.globus/userkey.pem] - char *cipher; // [s] list of ciphers [aes-128-cbc:bf-cbc:des-ede3-cbc] - char *md; // [s] list of MDs [sha256:md5] - int crl; // [cs] check level of CRL's [1] - int ca; // [cs] verification level of CA's [1] - int crlrefresh; // [cs] CRL refresh or expiration period in secs [1 day] - char *proxy; // [c] user proxy [/tmp/x509up_u] - char *valid; // [c] proxy validity [12:00] - int deplen; // [c] depth of signature path for proxies [0] - int bits; // [c] bits in PKI for proxies [512] - char *gridmap;// [s] gridmap file [/etc/grid-security/gridmap] - int gmapto; // [s] validity in secs of grid-map cache entries [600 s] - char *gmapfun;// [s] file with the function to map DN to usernames [0] - char *gmapfunparms;// [s] parameters for the function to map DN to usernames [0] - char *authzfun;// [s] file with the function to fill entities [0] - char *authzfunparms;// [s] parameters for the function to fill entities [0] - int authzto; // [s] validity in secs of authz cache entries [-1 => unlimited] - int ogmap; // [s] gridmap file checking option - int dlgpxy; // [c] explicitely ask the creation of a delegated proxy - // [s] ask client for proxies - int sigpxy; // [c] accept delegated proxy requests - char *srvnames;// [c] '|' separated list of allowed server names - char *exppxy; // [s] template for the exported file with proxies (dlgpxy == 3) - int authzpxy; // [s] if 1 make proxy available in exported form in the 'endorsement' - // field of the XrdSecEntity object for use in XrdAcc - int vomsat; // [s] 0 do not look for; 1 extract if any - char *vomsfun;// [s] file with the function to fill VOMS [0] - char *vomsfunparms;// [s] parameters for the function to fill VOMS [0] - int moninfo; // [s] 0 do not look for; 1 use DN as default - int hashcomp; // [cs] 1 send hash names with both algorithms; 0 send only the default [1] - - gsiOptions() { debug = -1; mode = 's'; clist = 0; - certdir = 0; crldir = 0; crlext = 0; cert = 0; key = 0; - cipher = 0; md = 0; ca = 1 ; crl = 1; crlrefresh = 86400; - proxy = 0; valid = 0; deplen = 0; bits = 512; - gridmap = 0; gmapto = 600; - gmapfun = 0; gmapfunparms = 0; authzfun = 0; authzfunparms = 0; authzto = -1; - ogmap = 1; dlgpxy = 0; sigpxy = 1; srvnames = 0; - exppxy = 0; authzpxy = 0; - vomsat = 1; vomsfun = 0; vomsfunparms = 0; moninfo = 0; hashcomp = 1; } - virtual ~gsiOptions() { } // Cleanup inside XrdSecProtocolgsiInit - void Print(XrdOucTrace *t); // Print summary of gsi option status -}; - -class XrdSecProtocolgsi; -class gsiHSVars; - -// From a proxy query -typedef struct { - X509Chain *chain; - XrdCryptoRSA *ksig; - XrdSutBucket *cbck; -} ProxyOut_t; - -// To query proxies -typedef struct { - const char *cert; - const char *key; - const char *certdir; - const char *out; - const char *valid; - int deplen; - int bits; -} ProxyIn_t; - -template -class GSIStack { -public: - void Add(T *t) { - char k[40]; snprintf(k, 40, "%p", t); - mtx.Lock(); - if (!stack.Find(k)) stack.Add(k, t, 0, Hash_count); // We need an additional count - stack.Add(k, t, 0, Hash_count); - mtx.UnLock(); - } - void Del(T *t) { - char k[40]; snprintf(k, 40, "%p", t); - mtx.Lock(); - if (stack.Find(k)) stack.Del(k, Hash_count); - mtx.UnLock(); - } -private: - XrdSysMutex mtx; - XrdOucHash stack; -}; - -/******************************************************************************/ -/* X r d S e c P r o t o c o l g s i C l a s s */ -/******************************************************************************/ - -class XrdSecProtocolgsi : public XrdSecProtocol -{ -friend class gsiOptions; -friend class gsiHSVars; -public: - int Authenticate (XrdSecCredentials *cred, - XrdSecParameters **parms, - XrdOucErrInfo *einfo=0); - - XrdSecCredentials *getCredentials(XrdSecParameters *parm=0, - XrdOucErrInfo *einfo=0); - - XrdSecProtocolgsi(int opts, const char *hname, XrdNetAddrInfo &endPoint, - const char *parms = 0); - virtual ~XrdSecProtocolgsi() {} // Delete() does it all - - // Initialization methods - static char *Init(gsiOptions o, XrdOucErrInfo *erp); - - void Delete(); - - // Encrypt / Decrypt methods - int Encrypt(const char *inbuf, int inlen, - XrdSecBuffer **outbuf); - int Decrypt(const char *inbuf, int inlen, - XrdSecBuffer **outbuf); - // Sign / Verify methods - int Sign(const char *inbuf, int inlen, - XrdSecBuffer **outbuf); - int Verify(const char *inbuf, int inlen, - const char *sigbuf, int siglen); - - // Export session key - int getKey(char *kbuf=0, int klen=0); - // Import a key - int setKey(char *kbuf, int klen); - - // Enable tracing - static XrdOucTrace *EnableTracing(); - -private: - XrdNetAddrInfo epAddr; - - // Static members initialized at startup - static XrdSysMutex gsiContext; - static String CAdir; - static String CRLdir; - static String DefCRLext; - static String SrvCert; - static String SrvKey; - static String UsrProxy; - static String UsrCert; - static String UsrKey; - static String PxyValid; - static int DepLength; - static int DefBits; - static int CACheck; - static int CRLCheck; - static int CRLDownload; - static int CRLRefresh; - static String DefCrypto; - static String DefCipher; - static String DefMD; - static String DefError; - static String GMAPFile; - static int GMAPOpt; - static bool GMAPuseDNname; - static int GMAPCacheTimeOut; - static XrdSecgsiGMAP_t GMAPFun; - static XrdSecgsiAuthz_t AuthzFun; - static XrdSecgsiAuthzKey_t AuthzKey; - static int AuthzCertFmt; - static int AuthzCacheTimeOut; - static int PxyReqOpts; - static int AuthzPxyWhat; - static int AuthzPxyWhere; - static String SrvAllowedNames; - static int VOMSAttrOpt; - static XrdSecgsiVOMS_t VOMSFun; - static int VOMSCertFmt; - static int MonInfoOpt; - static bool HashCompatibility; - // - // Crypto related info - static int ncrypt; // Number of factories - static XrdCryptoFactory *cryptF[XrdCryptoMax]; // their hooks - static int cryptID[XrdCryptoMax]; // their IDs - static String cryptName[XrdCryptoMax]; // their names - static XrdCryptoCipher *refcip[XrdCryptoMax]; // ref for session ciphers - // - // Caches - static XrdSutCache cacheCA; // Info about trusted CA's - static XrdSutCache cacheCert; // Server certificates info cache - static XrdSutCache cachePxy; // Client proxies cache; - static XrdSutCache cacheGMAPFun; // Cache for entries mapped by GMAPFun - static XrdSutCache cacheAuthzFun; // Cache for entities filled by AuthzFun - // - // Services - static XrdOucGMap *servGMap; // Grid mapping service - // - // CA and CRL stacks - static GSIStack stackCA; // Stack of CA in use - static GSIStack stackCRL; // Stack of CRL in use - // - // GMAP control vars - static time_t lastGMAPCheck; // time of last check on GMAP - static XrdSysMutex mutexGMAP; // mutex to control GMAP reloads - // - // Running options / settings - static int Debug; // [CS] Debug level - static bool Server; // [CS] If server mode - static int TimeSkew; // [CS] Allowed skew in secs for time stamps - // - // for error logging and tracing - static XrdSysLogger Logger; - static XrdSysError eDest; - static XrdOucTrace *GSITrace; - - // Information local to this instance - int options; - XrdCryptoFactory *sessionCF; // Chosen crypto factory - XrdCryptoCipher *sessionKey; // Session Key (result of the handshake) - XrdSutBucket *bucketKey; // Bucket with the key in export form - XrdCryptoMsgDigest *sessionMD; // Message Digest instance - XrdCryptoRSA *sessionKsig; // RSA key to sign - XrdCryptoRSA *sessionKver; // RSA key to verify - X509Chain *proxyChain; // Chain with the delegated proxy on servers - bool srvMode; // TRUE if server mode - - // Temporary Handshake local info - gsiHSVars *hs; - - // Parsing received buffers: client - int ParseClientInput(XrdSutBuffer *br, XrdSutBuffer **bm, - String &emsg); - int ClientDoInit(XrdSutBuffer *br, XrdSutBuffer **bm, - String &cmsg); - int ClientDoCert(XrdSutBuffer *br, XrdSutBuffer **bm, - String &cmsg); - int ClientDoPxyreq(XrdSutBuffer *br, XrdSutBuffer **bm, - String &cmsg); - - // Parsing received buffers: server - int ParseServerInput(XrdSutBuffer *br, XrdSutBuffer **bm, - String &cmsg); - int ServerDoCertreq(XrdSutBuffer *br, XrdSutBuffer **bm, - String &cmsg); - int ServerDoCert(XrdSutBuffer *br, XrdSutBuffer **bm, - String &cmsg); - int ServerDoSigpxy(XrdSutBuffer *br, XrdSutBuffer **bm, - String &cmsg); - - // Auxilliary functions - int ParseCrypto(String cryptlist); - int ParseCAlist(String calist); - - // Load CA certificates - static int GetCA(const char *cahash, - XrdCryptoFactory *cryptof, gsiHSVars *hs = 0); - static String GetCApath(const char *cahash); - static bool VerifyCA(int opt, X509Chain *cca, XrdCryptoFactory *cf); - static int VerifyCRL(XrdCryptoX509Crl *crl, XrdCryptoX509 *xca, XrdOucString crldir, - XrdCryptoFactory *CF, int hashalg); - bool ServerCertNameOK(const char *subject, String &e); - static XrdSutCacheEntry *GetSrvCertEnt(XrdSutCERef &gcref, - XrdCryptoFactory *cf, - time_t timestamp, String &cal); - - // Load CRLs - static XrdCryptoX509Crl *LoadCRL(XrdCryptoX509 *xca, const char *sjhash, - XrdCryptoFactory *CF, int dwld, int &err); - - // Updating proxies - static int QueryProxy(bool checkcache, XrdSutCache *cache, const char *tag, - XrdCryptoFactory *cf, time_t timestamp, - ProxyIn_t *pi, ProxyOut_t *po); - static int InitProxy(ProxyIn_t *pi, XrdCryptoFactory *cf, - X509Chain *ch = 0, XrdCryptoRSA **key = 0); - - // Error functions - static void ErrF(XrdOucErrInfo *einfo, kXR_int32 ecode, - const char *msg1, const char *msg2 = 0, - const char *msg3 = 0); - XrdSecCredentials *ErrC(XrdOucErrInfo *einfo, XrdSutBuffer *b1, - XrdSutBuffer *b2,XrdSutBuffer *b3, - kXR_int32 ecode, const char *msg1 = 0, - const char *msg2 = 0, const char *msg3 = 0); - int ErrS(String ID, XrdOucErrInfo *einfo, XrdSutBuffer *b1, - XrdSutBuffer *b2, XrdSutBuffer *b3, - kXR_int32 ecode, const char *msg1 = 0, - const char *msg2 = 0, const char *msg3 = 0); - - // Check Time stamp - bool CheckTimeStamp(XrdSutBuffer *b, int skew, String &emsg); - - // Check random challenge - bool CheckRtag(XrdSutBuffer *bm, String &emsg); - - // Auxilliary methods - int AddSerialized(char opt, kXR_int32 step, String ID, - XrdSutBuffer *bls, XrdSutBuffer *buf, - kXR_int32 type, XrdCryptoCipher *cip); - // Grid map cache handling - static XrdSecgsiGMAP_t // Load alternative function for mapping - LoadGMAPFun(const char *plugin, const char *parms); - static XrdSecgsiAuthz_t // Load alternative function to fill XrdSecEntity - LoadAuthzFun(const char *plugin, const char *parms, int &fmt); - static XrdSecgsiVOMS_t // Load alternative function to extract VOMS - LoadVOMSFun(const char *plugin, const char *parms, int &fmt); - static void QueryGMAP(XrdCryptoX509Chain* chain, int now, String &name); //Lookup info for DN - - // Entity handling - void CopyEntity(XrdSecEntity *in, XrdSecEntity *out, int *lout = 0); - void FreeEntity(XrdSecEntity *in); - - // VOMS parsing - int ExtractVOMS(X509Chain *c, XrdSecEntity &ent); -}; - -class gsiHSVars { -public: - int Iter; // iteration number - time_t TimeStamp; // Time of last call - String CryptoMod; // crypto module in use - int RemVers; // Version run by remote counterpart - XrdCryptoCipher *Rcip; // reference cipher - XrdSutBucket *Cbck; // Bucket with the certificate in export form - String ID; // Handshake ID (dummy for clients) - XrdSutPFEntry *Cref; // Cache reference - XrdSutPFEntry *Pent; // Pointer to relevant file entry - X509Chain *Chain; // Chain to be eventually verified - XrdCryptoX509Crl *Crl; // Pointer to CRL, if required - X509Chain *PxyChain; // Proxy Chain on clients - bool RtagOK; // Rndm tag checked / not checked - bool Tty; // Terminal attached / not attached - int LastStep; // Step required at previous iteration - int Options; // Handshake options; - int HashAlg; // Hash algorithm of peer hash name; - XrdSutBuffer *Parms; // Buffer with server parms on first iteration - - gsiHSVars() { Iter = 0; TimeStamp = -1; CryptoMod = ""; - RemVers = -1; Rcip = 0; - Cbck = 0; - ID = ""; Cref = 0; Pent = 0; Chain = 0; Crl = 0; PxyChain = 0; - RtagOK = 0; Tty = 0; LastStep = 0; Options = 0; HashAlg = 0; Parms = 0;} - - ~gsiHSVars() { SafeDelete(Cref); - if (Options & kOptsDelChn) { - // Do not delete the CA certificate in the cached reference - if (Chain) Chain->Cleanup(1); - SafeDelete(Chain); - } - if (Crl) { - // This decreases the counter and actually deletes the object only - // when no instance is using it - XrdSecProtocolgsi::stackCRL.Del(Crl); - Crl = 0; - } - // The proxy chain is owned by the proxy cache; invalid proxies are - // detected (and eventually removed) by QueryProxy - PxyChain = 0; - SafeDelete(Parms); } - void Dump(XrdSecProtocolgsi *p = 0); -}; diff --git a/src/XrdSecgsi/XrdSecgsiAuthzFunDN.cc b/src/XrdSecgsi/XrdSecgsiAuthzFunDN.cc deleted file mode 100644 index c07238148bf..00000000000 --- a/src/XrdSecgsi/XrdSecgsiAuthzFunDN.cc +++ /dev/null @@ -1,191 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S e c g s i G M A P F u n D N . c c */ -/* */ -/* (c) 2011, G. Ganis / CERN */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/* */ -/******************************************************************************/ - -/* ************************************************************************** */ -/* */ -/* GMAP function implementation extracting info from the DN */ -/* */ -/* ************************************************************************** */ - -#include -#include -#include -#include - -#include "XrdVersion.hh" - -#include "XrdCrypto/XrdCryptosslAux.hh" -#include "XrdCrypto/XrdCryptoX509.hh" -#include "XrdCrypto/XrdCryptoX509Chain.hh" -#include "XrdOuc/XrdOucString.hh" -#include "XrdSec/XrdSecEntity.hh" -#include "XrdSecgsi/XrdSecgsiTrace.hh" -#include "XrdSut/XrdSutBucket.hh" - -/******************************************************************************/ -/* V e r s i o n I n f o r m a t i o n */ -/******************************************************************************/ - -XrdVERSIONINFO(XrdSecgsiAuthzFun,secgsiauthz); - -XrdVERSIONINFO(XrdSecgsiAuthzKey,secgsiauthz); - -XrdVERSIONINFO(XrdSecgsiAuthzInit,secgsiauthz); - -/******************************************************************************/ -/* G l o b a l s & S t a t i c s */ -/******************************************************************************/ - -extern XrdOucTrace *gsiTrace; - -static int gCertfmt = 1; - -/******************************************************************************/ -/* X r d S e c g s i A u t h z F u n */ -/******************************************************************************/ - -// -// Main function -// -extern "C" -{ -int XrdSecgsiAuthzFun(XrdSecEntity &entity) -{ - // Implementation of XrdSecgsiAuthzFun extracting the information from the - // proxy chain in entity.creds - EPNAME("AuthzFunDN"); - - // Notify - DEBUG("dummy call for '"<Reorder() < 0) { - PRINT("ERROR: problems re-ordering proxy chain"); - delete b; - delete chain; chain = 0; - return -1; - } - } - // Point to the last certificate - XrdCryptoX509 *proxy = chain->End(); - if (!proxy) { - PRINT("ERROR: chain is empty!"); - return -1; - } - // Get the DN - const char *dn = proxy->Subject(); - int ldn = 0; - if (!dn || (ldn = strlen(dn)) <= 0) { - PRINT("ERROR: proxy dn undefined!"); - return -1; - } - - // Set the key - *key = new char[ldn+1]; - strcpy(*key, dn); - - // Done - DEBUG("key is: '"<<*key<<"'"); - return 0; -}} - -// -// Init the relevant parameters from a dedicated config file -// -extern "C" -{ -int XrdSecgsiAuthzInit(const char *cfg) -{ - // Initialize the relevant parameters from the 'cfg' string. - // Return -1 on failure. - // Otherwise, the return code indicates the format required by the mai function for - // the proxy chain: - // 0 proxy chain in 'raw' (opaque) format, to be processed - // using the XrdCrypto tools - // 1 proxy chain in 'PEM base64' - EPNAME("AuthzInitDN"); - - gCertfmt = 1; - - // Parse the config string - XrdOucString cs(cfg), tkn; - int from = 0; - while ((from = cs.tokenize(tkn, from, ' ')) != -1) { - if (tkn == "certfmt=raw") { - gCertfmt = 0; - } - } - // Notify - PRINT("initialized! (certfmt:"<. */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -/* Trivial AuthzKey/Func(), propogates the VO as a unix user and/or group. - -1. To be used with gsi parametrized like: - - sec.protocol /opt/xrootd/lib/x86_64_linux_26 gsi \ - -certdir:/etc/grid-security/certificates \ - -cert:/etc/grid-security/xrd/xrdcert.pem \ - -key:/etc/grid-security/xrd/xrdkey.pem -crl:3 \ - -authzfun:libXrdAuthzVO.so -authzfunparms: \ - -gmapopt:10 -gmapto:0 - -2. The optional authzfunparms is formatted as a CGI string with one or more - of the following key-value pairs: - - debug=1 - valido= - vo2grp= - vo2usr= - - Where: debug - turns debugging on. - vlist - specifies a comma-separated list of vo names that are - acceptable. If not specified, all vo's are accepted. - Otherwise, failure is returned if the the vo is not in - the list of vo's. - gspec - specifies how the vo is to be inserted into a group name. - Specify a printf-like format string with a single %s. This - is where the vo name is inserted. So, "%s" simply makes the - group name the vo name. - uspec - specifies how the vo is to be inserted into a user name. - The same rules apply as for gspec. If uspec is not specified - then the name comes from distinguished name in the - certificate (i.e. text after '/CN=') with spaces turned - into underscores and the vo is not used. If uspec is - specified as a single asterisk (*) then the name field is - not touched and is as set by the gsi module. -*/ - -#include -#include -#include -#include - -#include "XrdVersion.hh" - -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdSys/XrdSysPthread.hh" -#include "XrdSec/XrdSecEntity.hh" -#include "XrdOuc/XrdOucEnv.hh" -#include "XrdOuc/XrdOucLock.hh" - -/******************************************************************************/ -/* V e r s i o n I n f o r m a t i o n */ -/******************************************************************************/ - -XrdVERSIONINFO(XrdSecgsiAuthzFun,secgsiauthz); - -XrdVERSIONINFO(XrdSecgsiAuthzKey,secgsiauthz); - -XrdVERSIONINFO(XrdSecgsiAuthzInit,secgsiauthz); - -/******************************************************************************/ -/* E x t e r n a l F u n c t i o n s */ -/******************************************************************************/ - -// The following functions are called by the authz plug-in driver. -// -extern "C" -{ - int XrdSecgsiAuthzInit(const char *cfg); - int XrdSecgsiAuthzFun(XrdSecEntity &entity); - int XrdSecgsiAuthzKey(XrdSecEntity &entity, char **key); -} - -/******************************************************************************/ -/* G l o b a l V a r i a b l e s */ -/******************************************************************************/ - -namespace -{ - const int g_certificate_format = 1; - const int g_maxvolen = 255; - static char *g_valido = 0; - static char *g_vo2grp = 0; - static char *g_vo2usr = 0; - static int g_debug = 0; - static int g_cn2usr = 1; -} - -/******************************************************************************/ -/* L o c a l D e f i n e s */ -/******************************************************************************/ - -#undef PRINT -#define PRINT(y) if (g_debug) {std::cerr << y << "\n";} -#undef PROUT -#define PROUT(_x_) \ - std::cerr <0 error (this will still log the guy in, it seems) - 0 success, local username in entity.name -*/ - -int XrdSecgsiAuthzFun(XrdSecEntity &entity) -{ - static const char* inf_pfx = "INFO in AuthzFun: "; - static XrdSysMutex Mutex; - const char *vtxt = "", *etxt = 0; - char vbuff[(g_maxvolen+1)*2]; - int i, n; - -// We must have a vo, it must be shorter than 255 bytes, and it must be in our -// vo list of we have one -// - if (!entity.vorg) etxt = "missing"; - else if ((n = strlen(entity.vorg)) > g_maxvolen) etxt = "too long"; - else if (g_valido) - {*vbuff = ','; - strcpy(vbuff+1, entity.vorg); - if (!strstr(g_valido, vbuff)) - {vtxt = entity.vorg; etxt = " not allowed";} - } - -// Check if we passed the tests -// - if (etxt) - {std::cerr <<"AuthzVO: Invalid cert; vo " <= 0; i--) {if (*cP == '_') *cP = 0;} - if (*vbuff) - {if (entity.name) free(entity.name); - entity.name = strdup(vbuff); - } - } - -// If debugging then print information. However, get a global mutex to keep -// from inter-leaving these lines with other threads, as much as possible. -// - if (g_debug) - {XrdOucLock lock(&Mutex); - PROUT(name); PROUT(host); PROUT(grps); PROUT(vorg); PROUT(role); - } - -// All done -// - return 0; -} - -/******************************************************************************/ -/* X r d S e c g s i A u t h z K e y */ -/******************************************************************************/ - -int XrdSecgsiAuthzKey(XrdSecEntity &entity, char **key) -{ - // Return key by which entity.creds will be hashed. - // For now return entity.creds itself. - // The plan is to use DN + VO endorsements in the future. - - static const char* err_pfx = "ERR in AuthzKey: "; - static const char* inf_pfx = "INFO in AuthzKey: "; - - // Must have got something - if (!key) { - PRINT(err_pfx << "'key' is not defined!"); - return -1; - } - - PRINT(inf_pfx << "Returning creds of len " << entity.credslen << " as key."); - - // Set the key - *key = new char[entity.credslen + 1]; - strcpy(*key, entity.creds); - - return entity.credslen; -} - -/******************************************************************************/ -/* X r d S e c g s i A u t h z I n i t */ -/******************************************************************************/ - -int XrdSecgsiAuthzInit(const char *cfg) -{ - // Return: - // -1 on falure - // 0 to get credentials in raw form - // 1 to get credentials in PEM base64 encoded form - - static const char* inf_pfx = "INFO in AuthzInit: "; - XrdOucEnv *envP; - char cfgbuff[2048], *sP; - int i; - -// The configuration string may mistakingly include other parms following -// the auzparms. So, trim the string. -// - if (cfg) - {i = strlen(cfg); - if (i >= (int)sizeof(cfgbuff)) i = sizeof(cfgbuff)-1; - memcpy(cfgbuff, cfg, i); - cfgbuff[i] = 0; - if ((sP = index(cfgbuff, ' '))) *sP = 0; - } - if (!cfg || !(*cfg)) return g_certificate_format; - -// Parse the config line (it's in cgi format) -// - envP = new XrdOucEnv(cfgbuff); - -// Set debug value -// - if ((sP = envP->Get("debug")) && *sP == '1') g_debug = 1; - -// Get the mapping strings -// - if ((g_vo2grp = envP->Get("vo2grp"))) g_vo2grp = strdup(g_vo2grp); - if ((g_vo2usr = envP->Get("vo2usr"))) - {g_cn2usr = 0; - g_vo2usr = (!strcmp(g_vo2usr, "*") ? 0 : strdup(g_vo2usr)); - } - -// Now process the valid vo's -// - if ((sP = envP->Get("valido"))) - {i = strlen(sP); - g_valido = (char *)malloc(i+2); - *g_valido = ','; - strcpy(g_valido+1, sP); - } - -// All done with environment -// - delete envP; - -// All done. -// - PRINT(inf_pfx <<"cfg='"<< (cfg ? cfg : "null") << "'."); - return g_certificate_format; -} diff --git a/src/XrdSecgsi/XrdSecgsiGMAPFunDN.cc b/src/XrdSecgsi/XrdSecgsiGMAPFunDN.cc deleted file mode 100644 index 9ede9e68981..00000000000 --- a/src/XrdSecgsi/XrdSecgsiGMAPFunDN.cc +++ /dev/null @@ -1,243 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S e c g s i G M A P F u n D N . c c */ -/* */ -/* (c) 2011, G. Ganis / CERN */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/* */ -/******************************************************************************/ - -/* ************************************************************************** */ -/* */ -/* GMAP function implementation extracting info from the DN */ -/* */ -/* ************************************************************************** */ - -#include -#include -#include -#include - -#include "XrdVersion.hh" - -#include "XrdOuc/XrdOucHash.hh" -#include "XrdOuc/XrdOucString.hh" -#include "XrdOuc/XrdOucTrace.hh" -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysLogger.hh" - -static XrdSysError dnDest(0, "gmapdn_"); -static XrdSysLogger dnLogger; -static XrdOucTrace *dnTrace = 0; - -#define TRACE_Authen 0x0002 -#define EPNAME(x) static const char *epname = x; -#define PRINT(y) {if (dnTrace) {dnTrace->Beg(epname); cerr <End();}} -#define DEBUG(y) if (dnTrace && (dnTrace->What & TRACE_Authen)) PRINT(y) - - -/******************************************************************************/ -/* V e r s i o n I n f o r m a t i o n */ -/******************************************************************************/ - -XrdVERSIONINFO(XrdSecgsiGMAPFun,secgsigmap); - -/******************************************************************************/ -/* G l o b a l s & S t a t i c s */ -/******************************************************************************/ - -enum XrdSecgsi_Match {kFull = 0, - kBegins = 1, - kEnds = 2, - kContains = 4 - }; - -class XrdSecgsiMapEntry_t -{ -public: - XrdSecgsiMapEntry_t(const char *v, const char *u, int t) : val(v), user(u), type(t) { } - - XrdOucString val; - XrdOucString user; - int type; -}; - -static XrdOucHash gMappings; - -/******************************************************************************/ -/* F u n c t i o n s & M e t h o d s */ -/******************************************************************************/ - -//__________________________________________________________________________ -static int FindMatchingCondition(const char *, XrdSecgsiMapEntry_t *mc, void *xmp) -{ - // Print content of entry 'ui' and go to next - - XrdSecgsiMapEntry_t *mpe = (XrdSecgsiMapEntry_t *)xmp; - - bool match = 0; - if (mc && mpe) { - if (mc->type == kContains) { - if (mpe->val.find(mc->val) != STR_NPOS) match = 1; - } else if (mc->type == kBegins) { - if (mpe->val.beginswith(mc->val)) match = 1; - } else if (mc->type == kEnds) { - if (mpe->val.endswith(mc->val)) match = 1; - } else { - if (mpe->val.matches(mc->val.c_str())) match = 1; - } - if (match) mpe->user = mc->user; - } - - // We stop if matched, otherwise we continue - return (match) ? 1 : 0; -} - - -int XrdSecgsiGMAPInit(const char *cfg); - -// -// Main function -// -extern "C" -{ -char *XrdSecgsiGMAPFun(const char *dn, int now) -{ - // Implementation of XrdSecgsiGMAPFun extracting the information from the - // distinguished name 'dn' - EPNAME("GMAPFunDN"); - - // Init the relevant fields (only once) - if (now <= 0) { - if (XrdSecgsiGMAPInit(dn) != 0) - return (char *)-1; - return (char *)0; - } - - // Output - char *name = 0; - - XrdSecgsiMapEntry_t *mc = 0; - // Try the full match first - if ((mc = gMappings.Find(dn))) { - // Get the associated user - name = new char[mc->val.length() + 1]; - strcpy(name, mc->val.c_str()); - } else { - // Else scan the available mappings - mc = new XrdSecgsiMapEntry_t(dn, "", kFull); - gMappings.Apply(FindMatchingCondition, (void *)mc); - if (mc->user.length() > 0) { - name = new char[mc->user.length() + 1]; - strcpy(name, mc->user.c_str()); - } - } - if (name) { - DEBUG("mapping DN '"< 0) { - if (p == "d" || p == "dbg" || p == "debug") { - debug = 1; - } else { - cfg = p; - } - } - } - // Initiate error logging and tracing - dnDest.logger(&dnLogger); - dnTrace = new XrdOucTrace(&dnDest); - if (debug) dnTrace->What |= TRACE_Authen; - - if (cfg.length() <= 0) cfg = getenv("XRDGSIGMAPDNCF"); - if (cfg.length() <= 0) { - PRINT("ERROR: undefined config file path"); - return -1; - } - - FILE *fcf = fopen(cfg.c_str(), "r"); - if (fcf) { - char l[4096], val[4096], usr[256]; - while (fgets(l, sizeof(l), fcf)) { - int len = strlen(l); - if (len < 2) continue; - if (l[0] == '#') continue; - if (l[len-1] == '\n') l[len-1] = '\0'; - if (sscanf(l, "%4096s %256s", val, usr) >= 2) { - XrdOucString stype = "matching"; - char *p = &val[0]; - int type = kFull; - if (val[0] == '^') { - // Starts-with - type = kBegins; - p = &val[1]; - stype = "beginning with"; - } else { - int vlen = strlen(val); - if (val[vlen-1] == '$') { - // Ends-with - type = kEnds; - val[vlen-1] = '\0'; - stype = "ending with"; - } else if (val[vlen-1] == '+') { - // Contains - type = kContains; - val[vlen-1] = '\0'; - stype = "containing"; - } - } - // Register - gMappings.Add(p, new XrdSecgsiMapEntry_t(p, usr, type)); - // - DEBUG("mapping DNs "<. */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/* */ -/******************************************************************************/ - -/* ************************************************************************** */ -/* */ -/* Manage GSI proxies */ -/* */ -/* ************************************************************************** */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -#include "XrdOuc/XrdOucString.hh" -#include "XrdSys/XrdSysLogger.hh" -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysPwd.hh" - -#include "XrdSut/XrdSutAux.hh" - -#include "XrdCrypto/XrdCryptoAux.hh" -#include "XrdCrypto/XrdCryptoFactory.hh" -#include "XrdCrypto/XrdCryptoX509.hh" -#include "XrdCrypto/XrdCryptoX509Req.hh" -#include "XrdCrypto/XrdCryptoX509Chain.hh" -#include "XrdCrypto/XrdCryptoX509Crl.hh" - -#include "XrdCrypto/XrdCryptogsiX509Chain.hh" - -#include "XrdSecgsi/XrdSecgsiTrace.hh" - -#define PRT(x) {cerr <What |= (TRACE_Authen | TRACE_Debug); - } - // - // Set debug flags in other modules - if (Debug) { - XrdSutSetTrace(sutTRACE_Debug); - XrdCryptoSetTrace(cryptoTRACE_Debug); - } - - // - // Load the crypto factory - if (!(gCryptoFactory = XrdCryptoFactory::GetCryptoFactory(CryptoMod.c_str()))) { - PRT(": cannot instantiate factory "<SetTrace(cryptoTRACE_Debug); - - // Hooks for specific functionality - if (!(ParseFile = gCryptoFactory->X509ParseFile())) { - PRT("cannot attach to X509ParseFile function!"); - exit(1); - } - if (!(CreateProxy = gCryptoFactory->X509CreateProxy())) { - PRT("cannot attach to X509CreateProxy function!"); - exit(1); - } - if (!(ProxyCertInfo = gCryptoFactory->ProxyCertInfo())) { - PRT("cannot attach to ProxyCertInfo function!"); - exit(1); - } - if (!(GetVOMSAttr = gCryptoFactory->X509GetVOMSAttr())) { - PRT("cannot attach to X509GetVOMSAttr function!"); - exit(1); - } - - // - // Depending on the mode - switch (Mode) { - case kM_help: - // - // We should not get here ... print the menu and go - Menu(); - break; - case kM_init: - // - // Init proxies - secValid = XrdSutParseTime(Valid.c_str(), 1); - pxopt.bits = Bits; - pxopt.valid = secValid; - pxopt.depthlen = PathLength; - cPXp = new XrdCryptogsiX509Chain(); - // - // Display info about existing proxies - prc = (*CreateProxy)(EEcert.c_str(), EEkey.c_str(), &pxopt, - cPXp, &kPXp, PXcert.c_str()); - if (prc == 0) { - // The proxy is the first certificate - xPXp = cPXp->Begin(); - if (xPXp) { - Display(xPXp); - } else { - PRT( ": proxy certificate not found"); - } - } else { - PRT( ": problems creating proxy"); - } - break; - case kM_destroy: - // - // Destroy existing proxies - if (unlink(PXcert.c_str()) == -1) { - perror("xrdgsiproxy"); - } - - break; - case kM_info: - // - // Display info about existing proxies - // Parse the proxy file - cPXp = new XrdCryptogsiX509Chain(); - nci = (*ParseFile)(PXcert.c_str(), cPXp); - if (nci < 2) { - if (Exists) { - exitrc = 1; - } else { - PRT("proxy files must have at least two certificates" - " (found only: "<Begin(); - if (xPXp) { - if (!Exists) { - Display(xPXp); - if (strstr(xPXp->Subject(), "CN=limited proxy")) { - xPXPp = cPXp->SearchBySubject(xPXp->Issuer()); - if (xPXPp) { - Display(xPXPp); - } else { - PRT("WARNING: found 'limited proxy' but not the associated proxy!"); - } - } - } else { - // Check time validity - secValid = XrdSutParseTime(Valid.c_str(), 1); - int tl = xPXp->NotAfter() -(int)time(0); - if (Debug) - PRT("secValid: " << secValid<< ", tl: "< tl + ClockSkew) { - exitrc = 1; - break; - } - // Check bit strenght - if (Debug) - PRT("BitStrength: " << xPXp->BitStrength()<< ", Bits: "<BitStrength() < Bits) { - exitrc = 1; - break; - } - } - } else { - if (Exists) { - exitrc = 1; - } else { - PRT( ": proxy certificate not found"); - } - } - break; - default: - // - // Print menu - Menu(); - } - - exit(exitrc); -} - -int ParseArguments(int argc, char **argv) -{ - // Parse application arguments filling relevant global variables - - // Number of arguments - if (argc < 0 || !argv[0]) { - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - PRT("+ Insufficient number or arguments! +"); - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - // Print main menu - Menu(); - return 1; - } - --argc; - ++argv; - - // - // Loop over arguments - while ((argc >= 0) && (*argv)) { - - XrdOucString opt = ""; - int ival = -1; - if(*(argv)[0] == '-') { - - opt = *argv; - opt.erase(0,1); - if (CheckOption(opt,"h",ival) || CheckOption(opt,"help",ival) || - CheckOption(opt,"menu",ival)) { - Mode = kM_help; - } else if (CheckOption(opt,"debug",ival)) { - Debug = ival; - } else if (CheckOption(opt,"e",ival)) { - Exists = 1; - } else if (CheckOption(opt,"exists",ival)) { - Exists = 1; - } else if (CheckOption(opt,"f",ival)) { - --argc; - ++argv; - if (argc >= 0 && (*argv && *(argv)[0] != '-')) { - PXcert = *argv; - } else { - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - PRT("+ Option '-f' requires a proxy file name: ignoring +"); - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - argc++; - argv--; - } - } else if (CheckOption(opt,"file",ival)) { - --argc; - ++argv; - if (argc >= 0 && (*argv && *(argv)[0] != '-')) { - PXcert = *argv; - } else { - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - PRT("+ Option '-file' requires a proxy file name: ignoring +"); - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - argc++; - argv--; - } - } else if (CheckOption(opt,"out",ival)) { - --argc; - ++argv; - if (argc >= 0 && (*argv && *(argv)[0] != '-')) { - PXcert = *argv; - } else { - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - PRT("+ Option '-out' requires a proxy file name: ignoring +"); - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - argc++; - argv--; - } - } else if (CheckOption(opt,"cert",ival)) { - --argc; - ++argv; - if (argc >= 0 && (*argv && *(argv)[0] != '-')) { - EEcert = *argv; - } else { - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - PRT("+ Option '-cert' requires a cert file name: ignoring +"); - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - argc++; - argv--; - } - } else if (CheckOption(opt,"key",ival)) { - --argc; - ++argv; - if (argc >= 0 && (*argv && *(argv)[0] != '-')) { - EEkey = *argv; - } else { - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - PRT("+ Option '-key' requires a key file name: ignoring +"); - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - argc++; - argv--; - } - } else if (CheckOption(opt,"certdir",ival)) { - --argc; - ++argv; - if (argc >= 0 && (*argv && *(argv)[0] != '-')) { - CAdir = *argv; - } else { - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - PRT("+ Option '-certdir' requires a dir path: ignoring +"); - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - argc++; - argv--; - } - } else if (CheckOption(opt,"valid",ival)) { - --argc; - ++argv; - if (argc >= 0 && (*argv && *(argv)[0] != '-')) { - Valid = *argv; - } else { - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - PRT("+ Option '-valid' requires a time string: ignoring +"); - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - argc++; - argv--; - } - } else if (CheckOption(opt,"path-length",ival)) { - --argc; - ++argv; - if (argc >= 0 && (*argv && *(argv)[0] != '-')) { - PathLength = strtol(*argv,0,10); - if (PathLength < -1 || errno == ERANGE) { - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - PRT("+ Option '-path-length' requires a number >= -1: ignoring +"); - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - argc++; - argv--; - } - } else { - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - PRT("+ Option '-path-length' requires a number >= -1: ignoring +"); - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - argc++; - argv--; - } - } else if (CheckOption(opt,"bits",ival)) { - --argc; - ++argv; - if (argc >= 0 && (*argv && *(argv)[0] != '-')) { - Bits = strtol(*argv, 0, 10); - Bits = (Bits > 512) ? Bits : 512; - if (errno == ERANGE) { - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - PRT("+ Option '-bits' requires a number: ignoring +"); - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - argc++; - argv--; - } - } else { - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - PRT("+ Option '-bits' requires a number: ignoring +"); - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - argc++; - argv--; - } - } else if (CheckOption(opt,"clockskew",ival)) { - --argc; - ++argv; - if (argc >= 0 && (*argv && *(argv)[0] != '-')) { - ClockSkew = strtol(*argv, 0, 10); - if (ClockSkew < -1 || errno == ERANGE) { - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - PRT("+ Option '-clockskew' requires a number >= -1: ignoring +"); - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - argc++; - argv--; - } - } else { - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - PRT("+ Option '-clockskew' requires a number >= -1: ignoring +"); - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - argc++; - argv--; - } - } else if (CheckOption(opt,"extensions",ival)) { - DumpExtensions = 1; - } else { - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - PRT("+ Ignoring unrecognized option: "<<*argv); - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - } - - } else { - // - // Mode keyword - opt = *argv; - if (CheckOption(opt,"init",ival)) { - Mode = kM_init; - } else if (CheckOption(opt,"info",ival)) { - Mode = kM_info; - } else if (CheckOption(opt,"destroy",ival)) { - Mode = kM_destroy; - } else if (CheckOption(opt,"help",ival)) { - Mode = kM_help; - } else { - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - PRT("+ Ignoring unrecognized keyword mode: "<pw_uid); - } - // - // Expand Path - XrdSutExpand(PXcert); - // Get info - struct stat st; - if (stat(PXcert.c_str(),&st) != 0) { - if (errno != ENOENT) { - // Path exists but we cannot access it - exit - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - PRT("+ Cannot access requested proxy file: "<] [options] "); - PRT(" "); - PRT(" "); - PRT(" -h display this menu"); - PRT(" "); - PRT(" mode (info, init, destroy) [info]"); - PRT(" "); - PRT(" info: display content of existing proxy file"); - PRT(" "); - PRT(" init: create proxy certificate and related proxy file"); - PRT(" "); - PRT(" destroy: delete existing proxy file"); - PRT(" "); - PRT(" options:"); - PRT(" "); - PRT(" -debug Print more information while running this" - " query (use if something goes wrong) "); - PRT(" "); - PRT(" -f,-file,-out Non-standard location of proxy file"); - PRT(" "); - PRT(" init mode only:"); - PRT(" "); - PRT(" -certdir

Non-standard location of directory" - " with information about known CAs"); - PRT(" -cert Non-standard location of certificate" - " for which proxies are wanted"); - PRT(" -key Non-standard location of the private" - " key to be used to sign the proxy"); - PRT(" -bits strength in bits of the key [512]"); - PRT(" -valid Time validity of the proxy certificate [12:00]"); - PRT(" -path-length max number of descendent levels below" - " this proxy [0] "); - PRT(" -e,-exists [options] returns 0 if valid proxy exists, 1 otherwise;"); - PRT(" valid options: '-valid ', -bits "); - PRT(" -clockskew max clock-skewness allowed when checking time validity [30 secs]"); - PRT(" -extensions low-level dump of certificate extensions"); - PRT(" "); -} - -bool CheckOption(XrdOucString opt, const char *ref, int &ival) -{ - // Check opt against ref - // Return 1 if ok, 0 if not - // Fills ival = 1 if match is exact - // ival = 0 if match is exact with no - // ival = -1 in the other cases - bool rc = 0; - - int lref = (ref) ? strlen(ref) : 0; - if (!lref) - return rc; - XrdOucString noref = ref; - noref.insert("no",0); - - ival = -1; - if (opt == ref) { - ival = 1; - rc = 1; - } else if (opt == noref) { - ival = 0; - rc = 1; - } - - return rc; -} - -void Display(XrdCryptoX509 *xp) -{ - // display content of proxy certificate - - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - if (!xp) { - PRT(" Empty certificate! "); - return; - } - - // File - PRT("file : "<type != XrdCryptoX509::kProxy) { - PRT("type : "<Type()); - } else { - PRT("type : "<Type()<<" ("<ProxyType()<<")"); - } - - // Issuer - PRT("issuer : "<Issuer()); - // Subject - PRT("subject : "<Subject()); - // Path length field - int pathlen = 0; bool b; - if(xp->GetExtension(gsiProxyCertInfo_OID)) - (*ProxyCertInfo)(xp->GetExtension(gsiProxyCertInfo_OID), pathlen, &b); - else - (*ProxyCertInfo)(xp->GetExtension(gsiProxyCertInfo_OLD_OID), pathlen, &b); - PRT("path length : "<BitStrength()); - // Time left - int now = int(time(0)) - XrdCryptoTZCorr(); - int tl = xp->NotAfter() - now; - int hh = (tl >= 3600) ? (tl/3600) : 0; tl -= (hh*3600); - int mm = (tl >= 60) ? (tl/60) : 0; tl -= (mm*60); - int ss = (tl >= 0) ? tl : 0; - PRT("time left : "< 0) PRT("VOMS attributes: "<SetTrace(cryptoTRACE_Debug); - xp->DumpExtensions(0); - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - } -} diff --git a/src/XrdSecgsi/XrdSecgsiTrace.hh b/src/XrdSecgsi/XrdSecgsiTrace.hh deleted file mode 100644 index d7365e93a0e..00000000000 --- a/src/XrdSecgsi/XrdSecgsiTrace.hh +++ /dev/null @@ -1,65 +0,0 @@ -#ifndef ___SECGSI_TRACE_H___ -#define ___SECGSI_TRACE_H___ -/******************************************************************************/ -/* */ -/* X r d S e c g s i T r a c e . h h */ -/* */ -/* (C) 2005 G. Ganis, CERN */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/* */ -/******************************************************************************/ - -#include "XrdOuc/XrdOucTrace.hh" - -#ifndef NODEBUG - -#include "XrdSys/XrdSysHeaders.hh" - -#define QTRACE(act) (gsiTrace && (gsiTrace->What & TRACE_ ## act)) -#define PRINT(y) {if (gsiTrace) {gsiTrace->Beg(epname); \ - cerr <End();}} -#define TRACE(act,x) if (QTRACE(act)) PRINT(x) -#define NOTIFY(y) TRACE(Debug,y) -#define DEBUG(y) TRACE(Authen,y) -#define EPNAME(x) static const char *epname = x; - -#else - -#define QTRACE(x) -#define PRINT(x) -#define TRACE(x,y) -#define NOTIFY(x) -#define DEBUG(x) -#define EPNAME(x) - -#endif - -#define TRACE_ALL 0x000f -#define TRACE_Dump 0x0004 -#define TRACE_Authen 0x0002 -#define TRACE_Debug 0x0001 - -// -// For error logging and tracing -extern XrdOucTrace *gsiTrace; - -#endif diff --git a/src/XrdSecgsi/XrdSecgsiVOMSFunLite.cc b/src/XrdSecgsi/XrdSecgsiVOMSFunLite.cc deleted file mode 100644 index 0ef3e78769f..00000000000 --- a/src/XrdSecgsi/XrdSecgsiVOMSFunLite.cc +++ /dev/null @@ -1,219 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S e c g s i V O M S F u n L i t e . c c */ -/* */ -/* (c) 2012, G. Ganis / CERN */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/* */ -/******************************************************************************/ - -/* ************************************************************************** */ -/* */ -/* Example of fucntion extracting VOMS attributes */ -/* */ -/* To get it build as libXrdSecgsiVOMSLite.so, add the following to */ -/* src/XrdSecgsi.cmake */ -/* */ -/* #------------------------------------------------------------------------------- -/* # The XrdSecgsiVOMSLite library -/* #------------------------------------------------------------------------------- -/* -/* set( XRD_SEC_GSI_VOMSLITE_VERSION 1.0.0 ) -/* set( XRD_SEC_GSI_VOMSLITE_SOVERSION 0 ) -/* -/* add_library( -/* XrdSecgsiVOMSLite -/* SHARED -/* XrdSecgsi/XrdSecgsiVOMSFunLite.cc ) -/* -/* target_link_libraries( -/* XrdSecgsiVOMSLite -/* XrdSecgsi -/* XrdCryptossl -/* XrdCrypto -/* XrdUtils ) -/* -/* set_target_properties( -/* XrdSecgsiVOMSLite -/* PROPERTIES -/* VERSION ${XRD_SEC_GSI_VOMSLITE_VERSION} -/* SOVERSION ${XRD_SEC_GSI_VOMSLITE_SOVERSION} -/* LINK_INTERFACE_LIBRARIES "" ) -/* */ -/* and make sure that XrdSecgsiVOMSLite is added to TARGETS in 'install' */ -/* */ -/* ************************************************************************** */ - -#include -#include -#include -#include - -#include "XrdVersion.hh" - -#include "XrdCrypto/XrdCryptosslAux.hh" -#include "XrdCrypto/XrdCryptosslgsiAux.hh" -#include "XrdCrypto/XrdCryptoX509.hh" -#include "XrdCrypto/XrdCryptoX509Chain.hh" -#include "XrdOuc/XrdOucString.hh" -#include "XrdSec/XrdSecEntity.hh" -#include "XrdSecgsi/XrdSecgsiTrace.hh" -#include "XrdSut/XrdSutBucket.hh" - - -/******************************************************************************/ -/* V e r s i o n I n f o r m a t i o n */ -/******************************************************************************/ - -XrdVERSIONINFO(XrdSecgsiVOMSFun,secgsivoms); - -XrdVERSIONINFO(XrdSecgsiVOMSInit,secgsivoms); - -/******************************************************************************/ -/* G l o b a l s */ -/******************************************************************************/ - -extern XrdOucTrace *gsiTrace; - -#ifndef SafeFree -#define SafeFree(x) { if (x) free(x) ; x = 0; } -#endif - -/******************************************************************************/ -/* X r d S e c g s i V O M S F u n */ -/******************************************************************************/ - -// -// Main function -// -extern "C" -{ -int XrdSecgsiVOMSFun(XrdSecEntity &ent) -{ - // Implementation of XrdSecgsiAuthzFun extracting the information from the - // proxy chain in entity.creds - EPNAME("VOMSFunLite"); - - XrdCryptoX509Chain *c = (XrdCryptoX509Chain *) ent.creds; - if (!c) { - PRINT("ERROR: no proxy chain found!"); - return -1; - } - - XrdCryptoX509 *xp = c->End(); - if (!xp) { - PRINT("ERROR: no proxy certificate in chain!"); - return -1; - } - - // Extract the information - XrdOucString vatts; - int rc = 0; - if ((rc = XrdSslgsiX509GetVOMSAttr(xp, vatts)) != 0) { - if (strstr(xp->Subject(), "CN=limited proxy")) { - xp = c->SearchBySubject(xp->Issuer()); - rc = XrdSslgsiX509GetVOMSAttr(xp, vatts); - } - if (rc != 0) { - if (rc > 0) { - DEBUG("No VOMS attributes in proxy chain"); - } else { - PRINT("ERROR: problem extracting VOMS attributes"); - } - return -1; - } - } - - int from = 0; - XrdOucString vat; - while ((from = vatts.tokenize(vat, from, ',')) != -1) { - XrdOucString vo, role, grp; - if (vat.length() > 0) { - // The attribute is in the form - // /VO[/group[/subgroup(s)]][/Role=role][/Capability=cap] - int isl = vat.find('/', 1); - if (isl != STR_NPOS) vo.assign(vat, 1, isl - 1); - int igr = vat.find("/Role=", 1); - if (igr != STR_NPOS) grp.assign(vat, 0, igr - 1); - int irl = vat.find("Role="); - if (irl != STR_NPOS) { - role.assign(vat, irl + 5); - isl = role.find('/'); - role.erase(isl); - } - if (ent.vorg) { - if (vo != (const char *) ent.vorg) { - DEBUG("WARNING: found a second VO ('"< 0) ent.vorg = strdup(vo.c_str()); - } - if (grp.length() > 0 && (!ent.grps || grp.length() > strlen(ent.grps))) { - SafeFree(ent.grps); - ent.grps = strdup(grp.c_str()); - } - if (role.length() > 0 && role != "NULL" && !ent.role) { - ent.role = strdup(role.c_str()); - } - } - } - - // Save the whole string in endorsements - SafeFree(ent.endorsements); - if (vatts.length() > 0) ent.endorsements = strdup(vatts.c_str()); - - // Notify if did not find the main info (the VO ...) - if (!ent.vorg) { - PRINT("WARNING: no VO found! (VOMS attributes: '"<. */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/* */ -/******************************************************************************/ - -// -// Test program for XrdSecgsi -// - -#include -#include -#include - -#include -#include - -#include "XrdOuc/XrdOucString.hh" -#include "XrdSys/XrdSysLogger.hh" -#include "XrdSys/XrdSysError.hh" - -#include "XrdSut/XrdSutAux.hh" - -#include "XrdCrypto/XrdCryptoAux.hh" -#include "XrdCrypto/XrdCryptoFactory.hh" -#include "XrdCrypto/XrdCryptoX509.hh" -#include "XrdCrypto/XrdCryptoX509Req.hh" -#include "XrdCrypto/XrdCryptoX509Chain.hh" -#include "XrdCrypto/XrdCryptoX509Crl.hh" - -#include "XrdCrypto/XrdCryptosslAux.hh" - -#include "XrdCrypto/XrdCryptogsiX509Chain.hh" - -#include "XrdSecgsi/XrdSecgsiTrace.hh" - -#include -#include - -// -// Globals - -// #define PRINT(x) {cerr < 0) { - printf("|| %s ---", t); - } else { - printf("|| ----"); - } - for (; i < np ; i++) { printf("-"); } - printf("\n"); -} - -static void printHelp() -{ - printf(" \n"); - printf(" Basic test program for crypto functionality in relation to GSI.\n"); - printf(" The program needs access to a user certificate file and its private key, and the related\n"); - printf(" CA file(s); the CRL is downloaded using the information found in the CA certificate.\n"); - printf(" The location of the files are the standard ones and they can modified by the standard\n"); - printf(" environment variables:\n"); - printf(" \n"); - printf(" X509_USER_CERT [$HOME/.globus/usercert.pem] user certificate\n"); - printf(" X509_USER_KEY [$HOME/.globus/userkey.pem] user private key\n"); - printf(" X509_USER_PROXY [/tmp/x509up_u] user proxy\n"); - printf(" X509_CERT_DIR [/etc/grid-security/certificates/] CA certificates and CRL directories\n"); - printf(" \n"); - printf(" Usage:\n"); - printf(" xrdgsitest [-v,--verbose] [-h,--help] \n"); - printf(" \n"); - printf(" -h, --help Print this screen\n"); - printf(" -v, --verbose Dump all details\n"); - printf(" \n"); - printf(" The output is a list of PASSED/FAILED test, interleaved with details when the verbose option\n"); - printf(" is chosen.\n"); - printf(" \n"); -} - -int main( int argc, char **argv ) -{ - // Test implemented functionality - EPNAME("main"); - char cryptomod[64] = "ssl"; - char outname[256] = {0}; - - // Basic argument parsing - int i = 1; - for (; i < argc; i++) { - // Verbosity level - if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose")) Dbg = 1; - if (!strcmp(argv[i], "-vv")) Dbg = 2; - // Help - if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) Help = 1; - } - - // Print help if required - if (Help) { - printHelp(); - exit(0); - } - - // - // Initiate error logging and tracing - eDest.logger(&Logger); - if (!gsiTrace) - gsiTrace = new XrdOucTrace(&eDest); - if (gsiTrace && Dbg > 0) { - // Medium level - gsiTrace->What |= (TRACE_Authen | TRACE_Debug); - } - // - // Set debug flags in other modules - kXR_int32 tracesut = (Dbg > 0) ? sutTRACE_Debug : 0; - kXR_int32 tracecrypto = (Dbg > 0) ? cryptoTRACE_Debug : 0; - XrdSutSetTrace(tracesut); - XrdCryptoSetTrace(tracecrypto); - - // - // Determine application name - char *p = argv[0]; - int k = strlen(argv[0]); - while (k--) - if (p[k] == '/') break; - strcpy(outname,p+k+1); - - // - // Load the crypto factory - if (!(gCryptoFactory = XrdCryptoFactory::GetCryptoFactory(cryptomod))) { - pdots(" Cannot instantiate factory", 0); - exit(1); - } - if (Dbg > 0) - gCryptoFactory->SetTrace(cryptoTRACE_Debug); - - pline(""); - pline("Crypto functionality tests for GSI"); - pline(""); - - // - // Find out the username and locate the relevant certificates and directories - struct passwd *pw = getpwuid(geteuid()); - if (!pw) { - pdots(" Could not resolve user info - exit", 0); - exit(1); - } - NOTIFY("effective user is : "<pw_name<<", $HOME : "<pw_dir); - - // - // User certificate - EEcert = pw->pw_dir; - EEcert += "/.globus/usercert.pem"; - if (getenv("X509_USER_CERT")) EEcert = getenv("X509_USER_CERT"); - NOTIFY("user EE certificate: "<X509(EEcert.c_str()); - if (xEE) { - if (Dbg > 0) xEE->Dump(); - } else { - pdots(" Problems loading user EE cert", 0); - } - if (xEE) pdots("Loading EEC", 1); - - // - // User key - EEkey = pw->pw_dir; - EEkey += "/.globus/userkey.pem"; - if (getenv("X509_USER_KEY")) EEkey = getenv("X509_USER_KEY"); - NOTIFY("user EE key: "<pw_uid; - if (getenv("X509_USER_PROXY")) PXcert = getenv("X509_USER_PROXY"); - NOTIFY("user proxy certificate: "<X509(PXcert.c_str()); - if (xPX) { - if (Dbg > 0) xPX->Dump(); - } else { - pdots(" Problems loading user proxy cert", 0); - } - if (xPX) pdots("Loading User Proxy", 1); - - // - pline(""); - pline("Recreate the proxy certificate"); - XrdProxyOpt_t *pxopt = 0; // defaults - XrdCryptogsiX509Chain *cPXp = new XrdCryptogsiX509Chain(); - XrdCryptoRSA *kPXp = 0; - XrdCryptoX509 *xPXp = 0; - X509_EXTENSION *ext = 0; - int prc = gCryptoFactory->X509CreateProxy()(EEcert.c_str(), EEkey.c_str(), - pxopt, cPXp, &kPXp, PXcert.c_str()); - if (prc == 0) { - if (Dbg > 0) cPXp->Dump(); - if ((xPXp = (XrdCryptoX509 *)(cPXp->Begin()))) { - pdots("Recreating User Proxy", 1); - if ((ext = (X509_EXTENSION *)(xPXp->GetExtension("1.3.6.1.4.1.3536.1.222")))) { - pdots("proxyCertInfo extension OK", 1); - } - } - } else { - pdots("Recreating User Proxy", 0); - exit(1); - } - - // - pline(""); - pline("Load CA certificates"); - // Load CA certificates now - XrdCryptoX509 *xCA[5], *xCAref = 0; - if (getenv("X509_CERT_DIR")) CAdir = getenv("X509_CERT_DIR"); - if (!CAdir.endswith("/")) CAdir += "/"; - XrdCryptoX509 *xc = xEE; - bool rCAfound = 0; - int nCA = 0; - while (!rCAfound && nCA < 5) { - CAcert[nCA] = CAdir; - CAcert[nCA] += xc->IssuerHash(); - NOTIFY("issuer CA certificate path "<X509(CAcert[nCA].c_str()); - if (xCA[nCA]) { - if (Dbg > 0) xCA[nCA]->Dump(); - pdots("Loading CA certificate", 1); - } else { - pdots("Loading CA certificate", 0); - } - // Check if self-signed - if (!strcmp(xCA[nCA]->IssuerHash(), xCA[nCA]->SubjectHash())) { - rCAfound = 1; - break; - } - // If not, parse the issuer ... - xc = xCA[nCA]; - nCA++; - } - - // - pline(""); - pline("Testing ParseFile"); - XrdCryptoX509ParseFile_t ParseFile = gCryptoFactory->X509ParseFile(); - XrdCryptoRSA *key = 0; - XrdCryptoX509Chain *chain = new XrdCryptoX509Chain(); - if (ParseFile) { - int nci = (*ParseFile)(PXcert.c_str(), chain); - if (!(key = chain->Begin()->PKI())) { - pdots("getting PKI", 0); - } - NOTIFY(nci <<" certificates found parsing file"); - if (Dbg > 0) chain->Dump(); - int jCA = nCA + 1; - while (jCA--) { - chain->PushBack(xCA[jCA]); - } - if (Dbg > 0) chain->Dump(); - int rorc = chain->Reorder(); - if (rCAfound) { - if (Dbg > 0) chain->Dump(); - pdots("Chain reorder: ", (rorc != -1)); - XrdCryptoX509Chain::EX509ChainErr ecod = XrdCryptoX509Chain::kNone; - int verc = chain->Verify(ecod); - pdots("Chain verify: ", verc); - } else { - pdots("Full CA chain verification", 0); - } - } else { - pdots("attaching to X509ParseFile", 0); - exit (1); - } - - // - pline(""); - pline("Testing ExportChain"); - XrdCryptoX509ExportChain_t ExportChain = gCryptoFactory->X509ExportChain(); - XrdSutBucket *chainbck = 0; - if (ExportChain) { - chainbck = (*ExportChain)(chain, 0); - pdots("Attach to X509ExportChain", 1); - } else { - pdots("Attach to X509ExportChain", 0); - exit (1); - } - // - pline(""); - pline("Testing Chain Import"); - XrdCryptoX509ParseBucket_t ParseBucket = gCryptoFactory->X509ParseBucket(); - if (!ParseBucket) pdots("attaching to X509ParseBucket", 0); - // Init new chain with CA certificate - int jCA = nCA; - XrdCryptoX509Chain *CAchain = new XrdCryptoX509Chain(xCA[jCA]); - while (jCA) { CAchain->PushBack(xCA[--jCA]); } - if (ParseBucket && CAchain) { - int nci = (*ParseBucket)(chainbck, CAchain); - NOTIFY(nci <<" certificates found parsing bucket"); - if (Dbg > 0) CAchain->Dump(); - int rorc = CAchain->Reorder(); - pdots("Chain reorder: ", (rorc != -1)); - if (Dbg > 0) CAchain->Dump(); - XrdCryptoX509Chain::EX509ChainErr ecod = XrdCryptoX509Chain::kNone; - int verc = CAchain->Verify(ecod); - pdots("Chain verify: ", verc); - } else { - pdots("creating new X509Chain", 0); - exit (1); - } - - // - pline(""); - pline("Testing GSI chain import and verification"); - // Init new GSI chain with CA certificate - jCA = nCA; - XrdCryptogsiX509Chain *GSIchain = new XrdCryptogsiX509Chain(xCA[jCA], gCryptoFactory); - while (jCA) { GSIchain->PushBack(xCA[--jCA]); } - if (ParseBucket && GSIchain) { - int nci = (*ParseBucket)(chainbck, GSIchain); - NOTIFY(nci <<" certificates found parsing bucket"); - if (Dbg > 0) GSIchain->Dump(); - XrdCryptoX509Chain::EX509ChainErr ecod = XrdCryptoX509Chain::kNone; - x509ChainVerifyOpt_t vopt = { kOptsRfc3820, 0, -1, 0}; - int verc = GSIchain->Verify(ecod, &vopt); - pdots("GSI chain verify: ", verc); - if (!verc) NOTIFY("GSI chain verify ERROR: "<LastError()); - if (Dbg > 0) GSIchain->Dump(); - } else { - pdots("Creating new gsiX509Chain", 0); - exit (1); - } - - // - pline(""); - pline("Testing GSI chain copy"); - // Init new GSI chain with CA certificate - XrdCryptogsiX509Chain *GSInew = new XrdCryptogsiX509Chain(GSIchain, gCryptoFactory); - if (GSInew) { - if (Dbg > 0) GSInew->Dump(); - XrdCryptoX509Chain::EX509ChainErr ecod = XrdCryptoX509Chain::kNone; - x509ChainVerifyOpt_t vopt = { kOptsRfc3820, 0, -1, 0}; - int verc = GSInew->Verify(ecod, &vopt); - if (!verc) NOTIFY("GSI chain copy verify ERROR: "<LastError()); - pdots("GSI chain verify: ", verc); - if (Dbg > 0) GSInew->Dump(); - } else { - pdots("Creating new gsiX509Chain with copy", 0); - exit (1); - } - - // - pline(""); - pline("Testing Cert verification"); - XrdCryptoX509VerifyCert_t VerifyCert = gCryptoFactory->X509VerifyCert(); - if (VerifyCert) { - bool ok; - jCA = nCA; - while (jCA >= 0) { - ok = xEE->Verify(xCA[jCA]); - NOTIFY( ": verify cert: EE signed by CA? " <Subject()<<")"); - if (ok) xCAref = xCA[jCA]; - jCA--; - } - pdots("verify cert: EE signed by CA", (xCAref ? 1 : 0)); - ok = xPX->Verify(xEE); - pdots("verify cert: PX signed by EE", ok); - jCA = nCA; - bool refok = 0; - while (jCA >= 0) { - ok = xPX->Verify(xCA[jCA]); - NOTIFY( ": verify cert: PX signed by CA? " <Subject()<<")"); - if (!refok && ok) refok = 1; - jCA--; - } - pdots("verify cert: PX not signed by CA", !refok); - } else { - pdots("Attaching to X509VerifyCert", 0); - exit (1); - } - - - // - pline(""); - pline("Testing request creation"); - XrdCryptoX509Req *rPXp = 0; - XrdCryptoRSA *krPXp = 0; - prc = gCryptoFactory->X509CreateProxyReq()(xPX, &rPXp, &krPXp); - if (prc == 0) { - pdots("Creating request", 1); - if (Dbg > 0) rPXp->Dump(); - } else { - pdots("Creating request", 0); - exit(1); - } - - // - pline(""); - pline("Testing request signature"); - XrdCryptoX509 *xPXpp = 0; - prc = gCryptoFactory->X509SignProxyReq()(xPX, kPXp, rPXp, &xPXpp); - if (prc == 0) { - if (Dbg > 0) xPXpp->Dump(); - xPXpp->SetPKI((XrdCryptoX509data) krPXp->Opaque()); - bool extok = 0; - if ((ext = (X509_EXTENSION *)xPXpp->GetExtension(gsiProxyCertInfo_OID))) extok = 1; - pdots("Check proxyCertInfo extension", extok); - } else { - pdots("Signing request", 0); - exit(1); - } - - // - pline(""); - pline("Testing export of signed proxy"); - PPXcert = PXcert; - PPXcert += "p"; - NOTIFY(": file for signed proxy chain: "<X509ChainToFile(); - // Init the proxy chain - XrdCryptoX509Chain *PXchain = new XrdCryptoX509Chain(xPXpp); - PXchain->PushBack(xPX); - PXchain->PushBack(xEE); - if (ChainToFile && PXchain) { - if ((*ChainToFile)(PXchain, PPXcert.c_str()) != 0) { - NOTIFY(": problems saving signed proxy chain to file: "<GetExtension("crlDistributionPoints"))) { - pdots("Check CRL distribution points extension OK", 1); - } else { - pdots("Getting extension", 0); - } - } - - // - pline(""); - pline("Testing CRL loading"); - XrdCryptoX509Crl *xCRL1 = gCryptoFactory->X509Crl(xCAref); - if (xCRL1) { - if (Dbg > 0) xCRL1->Dump(); - pdots("Loading CA1 crl", 1); - // Verify CRL signature - bool crlsig = 0, xsig = 0; - for (jCA = 0; jCA <= nCA; jCA++) { - xsig = xCRL1->Verify(xCA[jCA]); - NOTIFY( ": CRL signature OK? "<Subject()<<")"); - if (!crlsig && xsig) crlsig = 1; - } - pdots("CRL signature OK", crlsig); - // Verify a serial number - bool snrev = xCRL1->IsRevoked(25, 0); - NOTIFY( ": SN: 25 revoked? "<IsRevoked(0x20, 0); - NOTIFY( ": SN: 32 revoked? "<. */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -extern "C" { -#include "krb5.h" -#ifdef HAVE_ET_COM_ERR_H -#include "et/com_err.h" -#else -#include "com_err.h" -#endif -} - -#include "XrdVersion.hh" - -#include "XrdNet/XrdNetAddrInfo.hh" -#include "XrdNet/XrdNetUtils.hh" -#include "XrdOuc/XrdOucErrInfo.hh" -#include "XrdOuc/XrdOucEnv.hh" -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdSys/XrdSysPthread.hh" -#include "XrdSys/XrdSysPwd.hh" -#include "XrdOuc/XrdOucTokenizer.hh" -#include "XrdSec/XrdSecInterface.hh" - -/******************************************************************************/ -/* D e f i n e s */ -/******************************************************************************/ - -#define krb_etxt(x) (char *)error_message(x) - -#define XrdSecPROTOIDENT "krb5" -#define XrdSecPROTOIDLEN sizeof(XrdSecPROTOIDENT) -#define XrdSecNOIPCHK 0x0001 -#define XrdSecEXPTKN 0x0002 -#define XrdSecINITTKN 0x0004 -#define XrdSecDEBUG 0x1000 - -#define XrdSecMAXPATHLEN 4096 - -#define CLDBG(x) if (client_options & XrdSecDEBUG) cerr <<"Seckrb5: " <= XrdSecMAXPATHLEN) ? - XrdSecMAXPATHLEN -1 : lt; - memcpy(ExpFile, expfile, lt); - ExpFile[lt] = 0; - } - } - - XrdSecProtocolkrb5(const char *KP, - const char *hname, - XrdNetAddrInfo &endPoint) - : XrdSecProtocol(XrdSecPROTOIDENT) - {Service = (KP ? strdup(KP) : 0); - Entity.host = strdup(hname); - epAddr = endPoint; - Entity.addrInfo = &epAddr; - CName[0] = '?'; CName[1] = '\0'; - Entity.name = CName; - Step = 0; - AuthContext = 0; - AuthClientContext = 0; - Ticket = 0; - Creds = 0; - } - - void Delete(); - -private: - - ~XrdSecProtocolkrb5() {} // Delete() does it all - -static int Fatal(XrdOucErrInfo *erp,int rc,const char *msg1,char *KP=0,int krc=0); -static int get_krbCreds(char *KP, krb5_creds **krb_creds); - void SetAddr(krb5_address &ipadd); - -static XrdSysMutex krbContext; // Server -static XrdSysMutex krbClientContext;// Client -static int options; // Server -static int client_options;// Client -static krb5_context krb_context; // Server -static krb5_context krb_client_context; // Client -static krb5_ccache krb_client_ccache; // Client -static krb5_ccache krb_ccache; // Server -static krb5_keytab krb_keytab; // Server -static krb5_principal krb_principal; // Server - -static char *Principal; // Server's principal name -static char *Parms; // Server parameters - -static char ExpFile[XrdSecMAXPATHLEN]; // Server: (template for) - // file to export token -int exp_krbTkn(XrdSecCredentials *cred, XrdOucErrInfo *erp); -int get_krbFwdCreds(char *KP, krb5_data *outdata); - -XrdNetAddrInfo epAddr; -char CName[256]; // Kerberos limit -char *Service; // Target principal for client -char Step; // Indicates at which step we are -krb5_auth_context AuthContext; // Authetication context -krb5_auth_context AuthClientContext; // Authetication context -krb5_ticket *Ticket; // Ticket associated to client authentication -krb5_creds *Creds; // Client: credentials -}; - -/******************************************************************************/ -/* S t a t i c D a t a */ -/******************************************************************************/ - -XrdSysMutex XrdSecProtocolkrb5::krbContext; // Server -XrdSysMutex XrdSecProtocolkrb5::krbClientContext; // Client - -int XrdSecProtocolkrb5::client_options = 0;// Client -int XrdSecProtocolkrb5::options = 0; // Server -krb5_context XrdSecProtocolkrb5::krb_context; // Server -krb5_context XrdSecProtocolkrb5::krb_client_context; // Client -krb5_ccache XrdSecProtocolkrb5::krb_client_ccache; // Client -krb5_ccache XrdSecProtocolkrb5::krb_ccache; // Server -krb5_keytab XrdSecProtocolkrb5::krb_keytab = NULL; // Server -krb5_principal XrdSecProtocolkrb5::krb_principal; // Server - -char *XrdSecProtocolkrb5::Principal = 0; // Server -char *XrdSecProtocolkrb5::Parms = 0; // Server - -char XrdSecProtocolkrb5::ExpFile[XrdSecMAXPATHLEN] = "/tmp/krb5cc_"; - -/******************************************************************************/ -/* D e l e t e */ -/******************************************************************************/ - -void XrdSecProtocolkrb5::Delete() -{ - if (Parms) {free(Parms); Parms = 0;} - if (Creds) krb5_free_creds(krb_context, Creds); - if (Ticket) krb5_free_ticket(krb_context, Ticket); - if (AuthContext) krb5_auth_con_free(krb_context, AuthContext); - if (AuthClientContext) krb5_auth_con_free(krb_client_context, AuthClientContext); - if (Entity.host) free(Entity.host); - if (Service) free(Service); - delete this; -} - -/******************************************************************************/ -/* g e t C r e d e n t i a l s */ -/******************************************************************************/ - -XrdSecCredentials *XrdSecProtocolkrb5::getCredentials(XrdSecParameters *noparm, - XrdOucErrInfo *error) -{ - char *buff; - int bsz; - krb_rc rc; - krb5_data outbuf; - CLDBG("getCredentials"); -// Supply null credentials if so needed for this protocol -// - if (!Service) - {CLDBG("Null credentials supplied."); - return new XrdSecCredentials(0,0); - } - - CLDBG("context lock"); - krbClientContext.Lock(); - CLDBG("context locked"); - -// We support passing the credential cache path via Url parameter -// - char *ccn = (error && error->getEnv()) ? error->getEnv()->Get("xrd.k5ccname") : 0; - const char *kccn = ccn ? (const char *)ccn : getenv("KRB5CCNAME"); - char ccname[128]; - if (!kccn) - {snprintf(ccname, 128, "/tmp/krb5cc_%d", geteuid()); - if (access(ccname, R_OK) == 0) - {kccn = ccname;} - } - CLDBG((kccn ? kccn : "credentials cache unset")); - -// Initialize the context and get the cache default. -// - if ((rc = krb5_init_context(&krb_client_context))) - {Fatal(error, ENOPROTOOPT, "Kerberos initialization failed", Service, rc); - return (XrdSecCredentials *)0; - } - - CLDBG("init context"); - -// Set the name of the default credentials cache for the Kerberos context -// - if ((rc = krb5_cc_set_default_name(krb_client_context, kccn))) - {Fatal(error, ENOPROTOOPT, "Kerberos default credentials cache setting failed", Service, rc); - return (XrdSecCredentials *)0; - } - - CLDBG("cc set default name"); - -// Obtain the default cache location -// - if ((rc = krb5_cc_default(krb_client_context, &krb_client_ccache))) - {Fatal(error, ENOPROTOOPT, "Unable to locate cred cache", Service, rc); - return (XrdSecCredentials *)0; - } - - CLDBG("cc default"); -// Check if the server asked for a forwardable ticket -// - char *pfwd = 0; - if ((pfwd = (char *) strstr(Service,",fwd"))) - { - client_options |= XrdSecEXPTKN; - *pfwd = 0; - } - -// Clear outgoing ticket and lock the kerberos context -// - outbuf.length = 0; outbuf.data = 0; - -// If this is not the first call, we are asked to send over a delegated ticket: -// we must create it first -// we save it into a file and return signalling the end of the hand-shake -// - - if (Step > 0) - {if ((rc = get_krbFwdCreds(Service, &outbuf))) - {krbClientContext.UnLock(); - Fatal(error, ESRCH, "Unable to get forwarded credentials", Service, rc); - return (XrdSecCredentials *)0; - } else - {bsz = XrdSecPROTOIDLEN+outbuf.length; - if (!(buff = (char *)malloc(bsz))) - {krbClientContext.UnLock(); - Fatal(error, ENOMEM, "Insufficient memory for credentials.", Service); - return (XrdSecCredentials *)0; - } - strcpy(buff, XrdSecPROTOIDENT); - memcpy((void *)(buff+XrdSecPROTOIDLEN), - (const void *)outbuf.data, (size_t)outbuf.length); - CLDBG("Returned " <ticket_flags & TKT_FLG_FORWARDABLE)) - { if ((client_options & XrdSecINITTKN) && !reinitdone && caninittkn) - { // Need to re-init - CLPRT("Existing ticket is not forwardable: re-init "); - rc = system(reinitcmd); - CLDBG("getCredentials: return code from '"<size <= (int)XrdSecPROTOIDLEN || !cred->buffer) - {strncpy(Entity.prot, "host", sizeof(Entity.prot)); - return 0; - } - -// Check if this is a recognized protocol -// - if (strcmp(cred->buffer, XrdSecPROTOIDENT)) - {char emsg[256]; - snprintf(emsg, sizeof(emsg), - "Authentication protocol id mismatch (%.4s != %.4s).", - XrdSecPROTOIDENT, cred->buffer); - Fatal(error, EINVAL, emsg, Principal); - return -1; - } - - CLDBG("protocol check"); - - char printit[4096]; - sprintf(printit,"Step is %d",Step); - CLDBG(printit); -// If this is not the first call the buffer contains a forwarded token: -// we save it into a file and return signalling the end of the hand-shake -// - if (Step > 0) - {if ((rc = exp_krbTkn(cred, error))) - iferror = (char *)"Unable to export the token to file"; - if (rc && iferror) { - krbContext.UnLock(); - return Fatal(error, EINVAL, iferror, Principal, rc); - } - krbContext.UnLock(); - - return 0; - } - - CLDBG("protocol check"); - -// Increment the step -// - Step += 1; - -// Indicate who we are -// - strncpy(Entity.prot, XrdSecPROTOIDENT, sizeof(Entity.prot)); - -// Create a kerberos style ticket and obtain the kerberos mutex -// - - CLDBG("Context Lock"); - - inbuf.length = cred->size -XrdSecPROTOIDLEN; - inbuf.data = &cred->buffer[XrdSecPROTOIDLEN]; - - krbContext.Lock(); - -// Check if whether the IP address in the credentials must match that of -// the incomming host. -// - CLDBG("Context Locked"); - if (!(XrdSecProtocolkrb5::options & XrdSecNOIPCHK)) - {SetAddr(ipadd); - iferror = (char *)"Unable to validate ip address;"; - if (!(rc=krb5_auth_con_init(krb_context, &AuthContext))) - rc=krb5_auth_con_setaddrs(krb_context, AuthContext, NULL, &ipadd); - } - -// Decode the credentials and extract client's name -// - if (!rc) - {if ((rc = krb5_rd_req(krb_context, &AuthContext, &inbuf, - (krb5_const_principal)krb_principal, - krb_keytab, NULL, &Ticket))) - iferror = (char *)"Unable to authenticate credentials;"; - else if ((rc = krb5_aname_to_localname(krb_context, - Ticket->enc_part2->client, - sizeof(CName)-1, CName))) - iferror = (char *)"Unable to extract client name;"; - } - -// Make sure the name is null-terminated -// - CName[sizeof(CName)-1] = '\0'; - -// If requested, ask the client for a forwardable token - int hsrc = 0; - if (!rc && XrdSecProtocolkrb5::options & XrdSecEXPTKN) { - // We just ask for more; the client knows what to send over - hsrc = 1; - // We need to fill-in a fake buffer - int len = strlen("fwdtgt") + 1; - char *buf = (char *) malloc(len); - memcpy(buf, "fwdtgt", len-1); - buf[len-1] = 0; - *parms = new XrdSecParameters(buf, len); - } - -// Release any allocated storage at this point and unlock mutex -// - krbContext.UnLock(); - -// Diagnose any errors -// - if (rc && iferror) - return Fatal(error, EACCES, iferror, Principal, rc); - -// All done -// - return hsrc; -} - -/******************************************************************************/ -/* I n i t i a l i z a t i o n M e t h o d s */ -/******************************************************************************/ -/******************************************************************************/ -/* I n i t */ -/******************************************************************************/ - -int XrdSecProtocolkrb5::Init(XrdOucErrInfo *erp, char *KP, char *kfn) -{ - krb_rc rc; - char buff[2048]; - -// Create a kerberos context. There is one such context per protocol object. -// - -// If we have no principal then this is a client-side call: initializations are done -// in getCredentials to allow for multiple client principals -// - if (!KP) return 0; - - if ((rc = krb5_init_context(&krb_context))) - return Fatal(erp, ENOPROTOOPT, "Kerberos initialization failed", KP, rc); - -// Obtain the default cache location -// - if ((rc = krb5_cc_default(krb_context, &krb_ccache))) - return Fatal(erp, ENOPROTOOPT, "Unable to locate cred cache", KP, rc); - -// Try to resolve the keyfile name -// - if (kfn && *kfn) - {if ((rc = krb5_kt_resolve(krb_context, kfn, &krb_keytab))) - {snprintf(buff, sizeof(buff), "Unable to find keytab '%s';", kfn); - return Fatal(erp, ESRCH, buff, Principal, rc); - } - } else { - krb5_kt_default(krb_context, &krb_keytab); - } - -// Keytab name -// - char krb_kt_name[1024]; - if ((rc = krb5_kt_get_name(krb_context, krb_keytab, &krb_kt_name[0], 1024))) - {snprintf(buff, sizeof(buff), "Unable to get keytab name;"); - return Fatal(erp, ESRCH, buff, Principal, rc); - } - -// Check if we can read access the keytab file -// - krb5_kt_cursor ktc; - if ((rc = krb5_kt_start_seq_get(krb_context, krb_keytab, &ktc))) - {snprintf(buff, sizeof(buff), "Unable to start sequence on the keytab file %s", krb_kt_name); - return Fatal(erp, EPERM, buff, Principal, rc); - } - if ((rc = krb5_kt_end_seq_get(krb_context, krb_keytab, &ktc))) - {snprintf(buff, sizeof(buff), "WARNING: unable to end sequence on the keytab file %s", krb_kt_name); - CLPRT(buff); - } - -// Now, extract the "principal/instance@realm" from the stream -// - if ((rc = krb5_parse_name(krb_context,KP,&krb_principal))) - return Fatal(erp, EINVAL, "Cannot parse service name", KP, rc); - -// Establish the correct principal to use -// - if ((rc = krb5_unparse_name(krb_context,(krb5_const_principal)krb_principal, - (char **)&Principal))) - return Fatal(erp, EINVAL, "Unable to unparse principal;", KP, rc); - -// All done -// - return 0; -} - -/******************************************************************************/ -/* P r i v a t e M e t h o d s */ -/******************************************************************************/ -/******************************************************************************/ -/* F a t a l */ -/******************************************************************************/ - -int XrdSecProtocolkrb5::Fatal(XrdOucErrInfo *erp, int rc, const char *msg, - char *KP, int krc) -{ - const char *msgv[8]; - int k, i = 0; - - msgv[i++] = "Seckrb5: "; //0 - msgv[i++] = msg; //1 - if (krc) {msgv[i++] = "; "; //2 - msgv[i++] = krb_etxt(krc); //3 - } - if (KP) {msgv[i++] = " (p="; //4 - msgv[i++] = KP; //5 - msgv[i++] = ")."; //6 - } - if (erp) erp->setErrInfo(rc, msgv, i); - else {for (k = 0; k < i; k++) cerr <"); - if (pusr) - {int ln = strlen(CName); - if (ln != 6) { - // Adjust the space - int lm = strlen(ccfile) - (int)(pusr + 6 - &ccfile[0]); - memmove(pusr+ln, pusr+6, lm); - } - // Copy the name - memcpy(pusr, CName, ln); - // Adjust the length - nlen += (ln - 6); - } - char *puid = (char *) strstr(&ccfile[0], ""); - struct passwd *pw; - XrdSysPwd thePwd(CName, &pw); - if (puid) - {char cuid[20] = {0}; - if (pw) - sprintf(cuid, "%d", pw->pw_uid); - int ln = strlen(cuid); - if (ln != 5) { - // Adjust the space - int lm = strlen(ccfile) - (int)(puid + 5 - &ccfile[0]); - memmove(puid+ln, pusr+5, lm); - } - // Copy the name - memcpy(puid, cuid, ln); - // Adjust the length - nlen += (ln - 5); - } - -// Terminate to the new length -// - ccfile[nlen] = 0; - -// Point the received creds -// - krbContext.Lock(); - krb5_data forwardCreds; - forwardCreds.data = &cred->buffer[XrdSecPROTOIDLEN]; - forwardCreds.length = cred->size -XrdSecPROTOIDLEN; - -// Get the replay cache -// - krb5_rcache rcache; - if ((rc = krb5_get_server_rcache(krb_context, - krb5_princ_component(krb_context, krb_principal, 0), - &rcache))) - return rc; - if ((rc = krb5_auth_con_setrcache(krb_context, AuthContext, rcache))) - return rc; - -// Fill-in remote address -// - SetAddr(ipadd); - if ((rc = krb5_auth_con_setaddrs(krb_context, AuthContext, 0, &ipadd))) - return rc; - -// Readout the credentials -// - krb5_creds **creds = 0; - if ((rc = krb5_rd_cred(krb_context, AuthContext, - &forwardCreds, &creds, 0))) - return rc; - -// Resolve cache name - krb5_ccache cache = 0; - if ((rc = krb5_cc_resolve(krb_context, ccfile, &cache))) - return rc; - -// Init cache -// - if ((rc = krb5_cc_initialize(krb_context, cache, - Ticket->enc_part2->client))) - return rc; - -// Store credentials in cache -// - if ((rc = krb5_cc_store_cred(krb_context, cache, *creds))) - return rc; - -// Close cache - if ((rc = krb5_cc_close(krb_context, cache))) - return rc; - -// Change permission and ownership of the file -// - if (chmod(ccfile, 0600) == -1) - return Fatal(erp, errno, "Unable to change file permissions;", ccfile, 0); - -// Done -// - return 0; -} - -/******************************************************************************/ -/* S e t A d d r */ -/******************************************************************************/ - -void XrdSecProtocolkrb5::SetAddr(krb5_address &ipadd) -{ -// The below is a hack but that's how it is actually done! -// - if (epAddr.Family() == AF_INET6) - {struct sockaddr_in6 *ip = (struct sockaddr_in6 *)epAddr.SockAddr(); - ipadd.addrtype = ADDRTYPE_INET6; - ipadd.length = sizeof(ip->sin6_addr); - ipadd.contents = (krb5_octet *)&ip->sin6_addr; - } else { - struct sockaddr_in *ip = (struct sockaddr_in *)epAddr.SockAddr(); - ipadd.addrtype = ADDRTYPE_INET; - ipadd.length = sizeof(ip->sin_addr); - ipadd.contents = (krb5_octet *)&ip->sin_addr; - } -} - -/******************************************************************************/ -/* X r d S e c p r o t o c o l k r b 5 I n i t */ -/******************************************************************************/ - -extern "C" -{ -char *XrdSecProtocolkrb5Init(const char mode, - const char *parms, - XrdOucErrInfo *erp) -{ - char *op, *KPrincipal=0, *Keytab=0, *ExpFile=0; - char parmbuff[1024]; - XrdOucTokenizer inParms(parmbuff); - int options = XrdSecNOIPCHK; - static bool serverinitialized = false; - -// For client-side one-time initialization, we only need to set debug flag and -// initialize the kerberos context and cache location. -// - if ((mode == 'c') || (serverinitialized)) - { - int opts = 0; - if (getenv("XrdSecDEBUG")) opts |= XrdSecDEBUG; - if (getenv("XrdSecKRB5INITTKN")) opts |= XrdSecINITTKN; - XrdSecProtocolkrb5::setClientOpts(opts); - return (XrdSecProtocolkrb5::Init(erp) ? (char *)0 : (char *)""); - } - - if (!serverinitialized) { - serverinitialized = true; - } - -// Duplicate the parms -// - if (parms) strlcpy(parmbuff, parms, sizeof(parmbuff)); - else {char *msg = (char *)"Seckrb5: Kerberos parameters not specified."; - if (erp) erp->setErrInfo(EINVAL, msg); - else cerr <] [-ipchk] [-exptkn[:filetemplate]] -// - if (inParms.GetLine()) - {if ((op = inParms.GetToken()) && *op == '/') - {Keytab = op; op = inParms.GetToken();} - if (op && !strcmp(op, "-ipchk")) - {options &= ~XrdSecNOIPCHK; - op = inParms.GetToken(); - } - if (op && !strncmp(op, "-exptkn", 7)) - {options |= XrdSecEXPTKN; - if (op[7] == ':') ExpFile = op+8; - op = inParms.GetToken(); - } - KPrincipal = strdup(op); - } - - if (ExpFile) - fprintf(stderr,"Template for exports: %s\n", ExpFile); - else - fprintf(stderr,"Template for exports not set\n"); - -// Now make sure that we have all the right info -// - if (!KPrincipal) - {char *msg = (char *)"Seckrb5: Kerberos principal not specified."; - if (erp) erp->setErrInfo(EINVAL, msg); - else cerr <"); - char *phost = (char *) strstr(&KPrincipal[0], ""); - if (phost) - {char *hn = XrdNetUtils::MyHostName(); - if (hn) - {int lhn = strlen(hn); - if (lhn != lkey) { - // Allocate, if needed - int lnew = plen - lkey + lhn; - if (lnew > plen) { - KPrincipal = (char *) realloc(KPrincipal, lnew+1); - KPrincipal[lnew] = 0; - phost = (char *) strstr(&KPrincipal[0], ""); - } - // Adjust the space - int lm = plen - (int)(phost + lkey - &KPrincipal[0]); - memmove(phost + lhn, phost + lkey, lm); - } - // Copy the name - memcpy(phost, hn, lhn); - // Cleanup - free(hn); - } - } - -// Now initialize the server -// - options |= XrdSecDEBUG; - XrdSecProtocolkrb5::setExpFile(ExpFile); - XrdSecProtocolkrb5::setOpts(options); - if (!XrdSecProtocolkrb5::Init(erp, KPrincipal, Keytab)) - {free(KPrincipal); - int lpars = strlen(XrdSecProtocolkrb5::getPrincipal()); - if (options & XrdSecEXPTKN) - lpars += strlen(",fwd"); - char *params = (char *)malloc(lpars+1); - if (params) - {memset(params,0,lpars+1); - strcpy(params,XrdSecProtocolkrb5::getPrincipal()); - if (options & XrdSecEXPTKN) - strcat(params,",fwd"); - XrdSecProtocolkrb5::setParms(params); - return params; - } - return (char *)0; - } - -// Failure -// - free(KPrincipal); - return (char *)0; -} -} - -/******************************************************************************/ -/* X r d S e c P r o t o c o l k r b 5 O b j e c t */ -/******************************************************************************/ - -extern "C" -{ -XrdSecProtocol *XrdSecProtocolkrb5Object(const char mode, - const char *hostname, - XrdNetAddrInfo &endPoint, - const char *parms, - XrdOucErrInfo *erp) -{ - XrdSecProtocolkrb5 *prot; - char *KPrincipal=0; - -// If this is a client call, then we need to get the target principal from the -// parms (which must be the first and only token). For servers, we use the -// context we established at initialization time. -// - if (mode == 'c') - {if ((KPrincipal = (char *)parms)) while(*KPrincipal == ' ') KPrincipal++; - if (!KPrincipal || !*KPrincipal) - {char *msg = (char *)"Seckrb5: Kerberos principal not specified."; - if (erp) erp->setErrInfo(EINVAL, msg); - else cerr <setErrInfo(ENOMEM, msg); - else cerr <. */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "XrdVersion.hh" - -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdSys/XrdSysLogger.hh" -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysPwd.hh" -#include "XrdOuc/XrdOucStream.hh" - -#include "XrdSys/XrdSysPriv.hh" - -#include "XrdSut/XrdSutPFCache.hh" - -#include "XrdSecpwd/XrdSecProtocolpwd.hh" -#include "XrdSecpwd/XrdSecpwdPlatform.hh" - -/******************************************************************************/ -/* T r a c i n g I n i t O p t i o n s */ -/******************************************************************************/ -#ifndef NODEBUG -#define POPTS(t,y) {if (t) {t->Beg(epname); cerr <End();}} -#else -#define POPTS(t,y) -#endif - -/******************************************************************************/ -/* S t a t i c D a t a */ -/******************************************************************************/ - -static String Prefix = "xrd"; -static String ProtoID = XrdSecPROTOIDENT; -static const kXR_int32 Version = XrdSecpwdVERSION; -static String AdminRef = ProtoID + "admin"; -static String SrvPukRef= ProtoID + "srvpuk"; -static String UserRef = ProtoID + "user"; -static String NetRcRef = ProtoID + "netrc"; - -static const char *pwdClientSteps[] = { - "kXPC_none", - "kXPC_normal", - "kXPC_verifysrv", - "kXPC_signedrtag", - "kXPC_creds", - "kXPC_autoreg", - "kXPC_failureack", - "kXPC_reserved" -}; - -static const char *pwdServerSteps[] = { - "kXPS_none", - "kXPS_init", - "kXPS_credsreq", - "kXPS_rtag", - "kXPS_signedrtag", - "kXPS_newpuk", - "kXPS_puk", - "kXPS_failure", - "kXPS_reserved" -}; - -static const char *gPWErrStr[] = { - "parsing buffer", // 10000 - "decoding buffer", // 10001 - "loading crypto factory", // 10002 - "protocol mismatch", // 10003 - "resolving user / host", // 10004 - "user missing", // 10005 - "host missing", // 10006 - "unknown user", // 10007 - "creating bucket", // 10008 - "duplicating bucket", // 10009 - "creating buffer", // 10010 - "serializing buffer", // 10011 - "generating cipher", // 10012 - "exporting public key", // 10013 - "encrypting random tag", // 10014 - "random tag mismatch", // 10015 - "random tag missing", // 10016 - "cipher missing", // 10017 - "getting credentials", // 10018 - "credentials missing", // 10019 - "wrong password for user", // 10020 - "checking cache", // 10021 - "cache entry for link missing", // 10022 - "session handshaking ID missing", // 10023 - "session handshaking ID mismatch", // 10024 - "unknown step option", // 10025 - "marshaling integer", // 10026 - "unmarshaling integer", // 10027 - "saving new credentials", // 10028 - "salt missing", // 10029 - "buffer empty", // 10030 - "obtaining reference cipher", // 10031 - "obtaining cipher public info", // 10032 - "adding bucket to list", // 10033 - "finalizing cipher from public info", // 10034 - "error during initialization", // 10035 - "wrong credentials", // 10035 - "error" // 10036 -}; - -// Masks for options -static const short kOptsServer = 0x0001; -static const short kOptsUserPwd = 0x0002; -static const short kOptsAutoReg = 0x0004; -static const short kOptsAregAll = 0x0008; -static const short kOptsVeriSrv = 0x0020; -static const short kOptsVeriClt = 0x0040; -static const short kOptsClntTty = 0x0080; -static const short kOptsExpCred = 0x0100; -static const short kOptsCrypPwd = 0x0200; -static const short kOptsChngPwd = 0x0400; -static const short kOptsAFSPwd = 0x0800; -// One day in secs -static const int kOneDay = 86400; - -/******************************************************************************/ -/* S t a t i c C l a s s D a t a */ -/******************************************************************************/ -XrdSysMutex XrdSecProtocolpwd::pwdContext; -String XrdSecProtocolpwd::FileAdmin= ""; -String XrdSecProtocolpwd::FileExpCreds= ""; -String XrdSecProtocolpwd::FileUser = ""; -String XrdSecProtocolpwd::FileCrypt= "/.xrdpass"; -String XrdSecProtocolpwd::FileSrvPuk= ""; -String XrdSecProtocolpwd::SrvID = ""; -String XrdSecProtocolpwd::SrvEmail = ""; -String XrdSecProtocolpwd::DefCrypto= "ssl"; -String XrdSecProtocolpwd::DefError = "insufficient credentials - contact "; -XrdSutPFile XrdSecProtocolpwd::PFAdmin(0); // Admin file (server) -XrdSutPFile XrdSecProtocolpwd::PFAlog(0); // Autologin file (client) -XrdSutPFile XrdSecProtocolpwd::PFSrvPuk(0); // File with server public keys (client) -// -// Crypto related info -int XrdSecProtocolpwd::ncrypt = 0; // Number of factories -int XrdSecProtocolpwd::cryptID[XrdCryptoMax] = {0}; // their IDs -String XrdSecProtocolpwd::cryptName[XrdCryptoMax] = {0}; // their names -XrdCryptoCipher *XrdSecProtocolpwd::refcip[XrdCryptoMax] = {0}; // ref for session ciphers -// -// Caches for info files -XrdSutPFCache XrdSecProtocolpwd::cacheAdmin; // Admin file -XrdSutPFCache XrdSecProtocolpwd::cacheSrvPuk; // SrvPuk file -XrdSutPFCache XrdSecProtocolpwd::cacheUser; // User files -XrdSutPFCache XrdSecProtocolpwd::cacheAlog; // Autologin file -// -// Running options / settings -int XrdSecProtocolpwd::Debug = 0; // [CS] Debug level -bool XrdSecProtocolpwd::Server = 1; // [CS] If server mode -int XrdSecProtocolpwd::UserPwd = 0; // [S] Check passwd file in user's -bool XrdSecProtocolpwd::SysPwd = 0; // [S] Check passwd file in user's -int XrdSecProtocolpwd::VeriClnt = 2; // [S] Client authenticity verification level: - // 0 none, 1 timestamp, 2 random tag -int XrdSecProtocolpwd::VeriSrv = 1; // [C] Server authenticity verification level: - // 0 none, 1 random tag -int XrdSecProtocolpwd::AutoReg = kpAR_none; // [S] Autoreg mode -int XrdSecProtocolpwd::LifeCreds = 0; // [S] if > 0, time interval of validity for creds -int XrdSecProtocolpwd::MaxPrompts = 3; // [C] Repeating prompt -int XrdSecProtocolpwd::MaxFailures = 10;// [S] Max passwd failures before blocking -int XrdSecProtocolpwd::AutoLogin = 0; // [C] do-not-check/check/update autologin info -int XrdSecProtocolpwd::TimeSkew = 300; // [CS] Allowed skew in secs for time stamps -bool XrdSecProtocolpwd::KeepCreds = 0; // [S] Keep / Do-Not-Keep client creds -int XrdSecProtocolpwd::FmtExpCreds = 0; // [S] Format for exported credentials -// -// Debug an tracing -XrdSysError XrdSecProtocolpwd::eDest(0, "secpwd_"); -XrdSysLogger XrdSecProtocolpwd::Logger; -XrdOucTrace *XrdSecProtocolpwd::PWDTrace = 0; - -XrdOucTrace *pwdTrace = 0; - -/******************************************************************************/ -/* S t a t i c F u n c t i o n s */ -/******************************************************************************/ -//_____________________________________________________________________________ -static const char *ClientStepStr(int kclt) -{ - // Return string with client step - static const char *ukn = "Unknown"; - - kclt = (kclt < 0) ? 0 : kclt; - kclt = (kclt > kXPC_reserved) ? 0 : kclt; - kclt = (kclt >= kXPC_normal) ? (kclt - kXPC_normal + 1) : kclt; - - if (kclt < 0 || kclt > (kXPC_reserved - kXPC_normal + 1)) - return ukn; - else - return pwdClientSteps[kclt]; -} - -//_____________________________________________________________________________ -static const char *ServerStepStr(int ksrv) -{ - // Return string with server step - static const char *ukn = "Unknown"; - - ksrv = (ksrv < 0) ? 0 : ksrv; - ksrv = (ksrv > kXPS_reserved) ? 0 : ksrv; - ksrv = (ksrv >= kXPS_init) ? (ksrv - kXPS_init + 1) : ksrv; - - if (ksrv < 0 || ksrv > (kXPS_reserved - kXPS_init + 1)) - return ukn; - else - return pwdServerSteps[ksrv]; -} - -/******************************************************************************/ -/* P r o t o c o l I n i t i a l i z a t i o n M e t h o d s */ -/******************************************************************************/ - - -//_____________________________________________________________________________ -XrdSecProtocolpwd::XrdSecProtocolpwd(int opts, const char *hname, - XrdNetAddrInfo &endPoint, - const char *parms) : XrdSecProtocol("pwd") -{ - // Default constructor - EPNAME("XrdSecProtocolpwd"); - - if (QTRACE(Authen)) { PRINT("constructing: "<TimeStamp = time(0); - // Local handshake variables - hs->CryptoMod = ""; // crypto module in use - hs->User = ""; // remote username - hs->Tag.resize(256); // tag for credentials - hs->RemVers = -1; // Version run by remote counterpart - hs->CF = 0; // crypto factory - hs->Hcip = 0; // handshake cipher - hs->Rcip = 0; // reference cipher - hs->ID = ""; // Handshake ID (dummy for clients) - hs->Cref = 0; // Cache reference - hs->Pent = 0; // Pointer to relevant file entry - hs->RtagOK = 0; // Rndm tag checked / not checked - hs->Tty = (isatty(0) == 0 || isatty(1) == 0) ? 0 : 1; - hs->Step = 0; // Current step - hs->LastStep = 0; // Step required at previous iteration - } else { - PRINT("could not create handshake vars object"); - } - - // Used by servers to store forwarded credentials - clientCreds = 0; - - // Save host name and address - if (hname) { - Entity.host = strdup(hname); - } else { - NOTIFY("warning: host name undefined"); - } - epAddr = endPoint; - Entity.addrInfo = &epAddr; - // Init client name - CName[0] = '?'; CName[1] = '\0'; - - // - // Notify, if required - DEBUG("constructing: host: "< 0) { - DEBUG("using autologin file: "< 1) { - DEBUG("running in update-autologin mode"); - } - } - if (VeriSrv > 0) { - DEBUG("server verification ON"); - } else { - DEBUG("server verification OFF"); - } - // Decode received buffer - if (parms) { - XrdOucString p("&P=pwd,"); - p += parms; - hs->Parms = new XrdSutBuffer(p.c_str(), p.length()); - } - } - - // We are done - String vers = Version; - vers.insert('.',vers.length()-2); - vers.insert('.',vers.length()-5); - DEBUG("object created: v"< -1) ? opt.debug : Debug; - - // We must have the tracing object at this point - // (initialized in XrdSecProtocolgsiInit) - if (!pwdTrace) { - ErrF(erp,kPWErrInit,"tracing object (pwdTrace) not initialized! cannot continue"); - return Parms; - } - - // Set debug mask ... also for auxilliary libs - int trace = 0, traceSut = 0, traceCrypto = 0; - if (Debug >= 3) { - trace = cryptoTRACE_Dump; - traceSut = sutTRACE_Dump; - traceCrypto = cryptoTRACE_Dump; - PWDTrace->What = TRACE_ALL; - } else if (Debug >= 2) { - trace = cryptoTRACE_Debug; - traceSut = sutTRACE_Debug; - traceCrypto = cryptoTRACE_Debug; - PWDTrace->What = TRACE_Debug; - PWDTrace->What |= TRACE_Authen; - } else if (Debug >= 1) { - trace = cryptoTRACE_Debug; - traceSut = sutTRACE_Notify; - traceCrypto = cryptoTRACE_Notify; - PWDTrace->What = TRACE_Debug; - } - - // ... also for auxilliary libs - XrdSutSetTrace(traceSut); - XrdCryptoSetTrace(traceCrypto); - - // Get user info - struct passwd *pw; - XrdSysPwd thePwd(getuid(), &pw); - - if (!pw) { - PRINT("no user info available - invalid "); - ErrF(erp, kPWErrInit, "could not get user info from pwuid"); - return Parms; - } - - // - // Operation mode - Server = (opt.mode == 's'); - - // - // Directory with admin pwd files - bool argdir = 0; - String infodir(512); - if (opt.dir) { - infodir = opt.dir; - // Expand - if (XrdSutExpand(infodir) != 0) { - PRINT("cannot expand "< - infodir = XrdSutHome(); - infodir += ("/." + Prefix); - } - if (!infodir.endswith("/")) infodir += "/"; - // - // If defined, check existence of the infodir and admin file - if (infodir.length()) { - // Acquire the privileges, if needed - XrdSysPrivGuard priv(pw->pw_uid, pw->pw_gid); - if (priv.Valid()) { - struct stat st; - if (stat(infodir.c_str(),&st) == -1) { - if (errno == ENOENT) { - if (argdir) { - DEBUG("infodir non existing: "< -1) ? opt.areg : AutoReg; - // - // Client verification level - VeriClnt = (opt.vericlnt > -1) ? opt.vericlnt : VeriClnt; - // - // Whether to check pwd files in users' $HOME - UserPwd = (opt.upwd > -1) ? opt.upwd : UserPwd; - // - // Whether to check system pwd files (if allowed) - SysPwd = (opt.syspwd > -1) ? opt.syspwd : SysPwd; - if (SysPwd) { - // Make sure this setting makes sense - if (pw) { -#ifdef HAVE_SHADOWPW - // Acquire the privileges, if needed - XrdSysPrivGuard priv((uid_t) 0, (gid_t) 0); - if (priv.Valid()) { - // System V Rel 4 style shadow passwords - struct spwd *spw = getspnam(pw->pw_name); - if (!spw) { - SysPwd = 0; - DEBUG("no privileges to access shadow passwd file"); - } - } else { - DEBUG("problems acquiring credentials" - " to access the system password file"); - } -#else - // Normal passwd file - if (!pw->pw_passwd && - (pw->pw_passwd && strlen(pw->pw_passwd) <= 1)) { - SysPwd = 0; - DEBUG("no privileges to access system passwd file"); - } -#endif - } else - SysPwd = 0; - } - // - // Credential lifetime - LifeCreds = (opt.lifecreds > -1) ? opt.lifecreds : LifeCreds; - // - // Max number of failures - MaxFailures = (opt.maxfailures > -1) ? opt.maxfailures : MaxFailures; - - // - // If defined, check existence of the infodir and admin file - if (infodir.length()) { - // Acquire the privileges, if needed - XrdSysPrivGuard priv(pw->pw_uid, pw->pw_gid); - if (priv.Valid()) { - struct stat st; - // - // Define admin file and check its existence - FileAdmin = infodir + AdminRef; - if (stat(FileAdmin.c_str(),&st) == -1) { - if (errno == ENOENT) { - PRINT("FileAdmin non existing: "< 0) { - // - // Load server ID - PFAdmin.Init(FileAdmin.c_str(),0); - if (PFAdmin.IsValid()) { - // - // Init cache for admin file - if (cacheAdmin.Load(FileAdmin.c_str()) != 0) { - PRINT("problems init cache for file admin "); - ErrF(erp,kPWErrError,"initializing cache for file admin"); - return Parms; - } - if (QTRACE(Authen)) { cacheAdmin.Dump(); } - XrdSutPFEntry *ent = cacheAdmin.Get(pfeRef, "+++SrvID"); - if (ent) - {SrvID.insert(ent->buf1.buf, 0, ent->buf1.len); - pfeRef.UnLock(); - } - ent = cacheAdmin.Get(pfeRef, "+++SrvEmail"); - if (ent) - SrvEmail.insert(ent->buf1.buf, 0, ent->buf1.len); - // Default error message - DefError += SrvEmail; - pfeRef.UnLock(); - } - DEBUG("server ID: "< 0 || SysPwd) { - if (cacheUser.Init(100) != 0) { - PRINT("problems init cache for user pwd info" - " - passwd files in user accounts will not be used"); - UserPwd = 0; - } - } - - // - // List of crypto modules - String cryptlist = opt.clist ? (const char *)(opt.clist) : DefCrypto; - - // - // Load crypto modules - XrdSutPFEntry ent; - XrdCryptoFactory *cf = 0; - String clist = cryptlist; - if (clist.length()) { - String ncpt = ""; - int from = 0; - while ((from = clist.tokenize(ncpt, from, '|')) != -1) { - if (ncpt.length() > 0) { - // Try loading - if ((cf = XrdCryptoFactory::GetCryptoFactory(ncpt.c_str()))) { - // Add it to the list - cryptID[ncrypt] = cf->ID(); - cryptName[ncrypt].insert(cf->Name(),0,strlen(cf->Name())+1); - cf->SetTrace(trace); - // Ref cipher - String ptag("+++SrvPuk_"); - ptag += cf->ID(); - if (FileAdmin.length() > 0) { - // Acquire the privileges, if needed - XrdSysPrivGuard priv(pw->pw_uid, pw->pw_gid); - if (priv.Valid()) { - if (PFAdmin.ReadEntry(ptag.c_str(),ent) <= 0) { - PRINT("ref cipher for module "<Cipher(&bck))) { - PRINT("ref cipher for module "<= XrdCryptoMax) { - PRINT("max number of crypto modules (" - << XrdCryptoMax <<") reached "); - break; - } - } - } - } - } - } else { - PRINT("cannot instantiate crypto factory "< 0) { - FileUser = ("/" + UserRef); - if (opt.udir) { - FileUser.insert(opt.udir,0); - if (FileUser[0] != '/') FileUser.insert('/',0); - } else { - // Use default $(HOME)/. - FileUser.insert(Prefix,0); - FileUser.insert("/.",0); - } - // - // Crypt-hash file name, if requested - if (opt.cpass) { - UserPwd = 2; - FileCrypt = opt.cpass; - if (FileCrypt[0] != '/') FileCrypt.insert('/',0); - } - } - - // - // Whether to save client creds - KeepCreds = (opt.keepcreds > -1) ? opt.keepcreds : KeepCreds; - if (KeepCreds > 0) - NOTIFY("Exporting client creds to internal buffer"); - - // - // Whether to export client creds to a file - FileExpCreds = (opt.expcreds) ? opt.expcreds : FileExpCreds; - if (FileExpCreds.length() > 0) { - // Export format - FmtExpCreds = opt.expfmt; - const char *efmts[4] = {"PFile", "hex", "raw", "raw/nokeyword"}; - NOTIFY("Exporting client creds (fmt:"<,v:,id: - Parms = new char[cryptlist.length()+3+12+SrvID.length()+5+popt.length()+3]; - if (Parms) { - if (popt.length() > 0) - sprintf(Parms,"v:%d,id:%s,c:%s,po:%s", - Version,SrvID.c_str(),cryptlist.c_str(),popt.c_str()); - else - sprintf(Parms,"v:%d,id:%s,c:%s", - Version,SrvID.c_str(),cryptlist.c_str()); - } else { - PRINT("no system resources for 'Parms'"); - ErrF(erp,kPWErrInit,"no system resources for 'Parms'"); - } - - // Some notification - NOTIFY("using FileAdmin: "< 0) { - NOTIFY("using private pwd files: $(HOME)"< 1) { - NOTIFY("using private crypt-hash files: $(HOME)"< -1) ? opt.verisrv : VeriSrv; - // - // Server puks file - FileSrvPuk = ""; - if (opt.srvpuk) { - FileSrvPuk = opt.srvpuk; - if (XrdSutExpand(FileSrvPuk) != 0) { - PRINT("cannot expand "< 0) - FileSrvPuk = infodir + SrvPukRef; - - if (FileSrvPuk.length() > 0) { - kXR_int32 openmode = 0; - struct stat st; - // - if (stat(FileSrvPuk.c_str(),&st) == -1) { - if (errno == ENOENT) { - PRINT("server public key file "< -1) ? opt.alog : AutoLogin; - NOTIFY("AutoLogin level: "< -1) ? opt.maxprompts : MaxPrompts; - // - // Attach autologin file name, if requested - if (AutoLogin > 0) { - bool filefound = 0; - String fnrc(256); - if (opt.alogfile) { - fnrc = opt.alogfile; - if (XrdSutExpand(fnrc) != 0) { - PRINT("cannot expand "< 0) { - kXR_int32 openmode = 0; - struct stat st; - if (stat(fnrc.c_str(),&st) == -1) { - if (errno == ENOENT) { - PRINT("Autologin file "< 0) { - // Attach to file - PFAlog.Init(fnrc.c_str(),openmode); - if (PFAlog.IsValid()) { - // Init cache for autologin file - if (cacheAlog.Load(fnrc.c_str()) == 0) { - if (QTRACE(Authen)) { cacheAlog.Dump(); } - filefound =1; - } else { - PRINT("problems init cache for autologin file"); - } - } else { - PRINT("problems attaching-to / creating autologin file"); - } - } - } - // - // Notify if not found - if (!filefound) { - NOTIFY("could not init properly autologin - switch off "); - AutoLogin = 0; - } - } - // - // Notify if not found - if (AutoLogin <= 0) { - // Init anyhow cache to cache information during session - if (cacheAlog.Init(100) != 0) { - PRINT("problems init cache for user temporary autolog"); - } - } - // We are done - Parms = (char *)""; - } - - // We are done - return Parms; -} - -/******************************************************************************/ -/* D e l e t e */ -/******************************************************************************/ -void XrdSecProtocolpwd::Delete() -{ - // Deletes the protocol - if (Entity.host) free(Entity.host); - // Cleanup the handshake variables, if still there - SafeDelete(hs); - delete this; -} - -/******************************************************************************/ -/* C l i e n t O r i e n t e d F u n c t i o n s */ -/******************************************************************************/ -/******************************************************************************/ -/* g e t C r e d e n t i a l s */ -/******************************************************************************/ - -XrdSecCredentials *XrdSecProtocolpwd::getCredentials(XrdSecParameters *parm, - XrdOucErrInfo *ei) -{ - // Query client for the password; remote username and host - // are specified in 'parm'. File '.rootnetrc' is checked. - EPNAME("getCredentials"); - - // If we are a server the only reason to be here is to get the forwarded - // or saved client credentials - if (srvMode) { - XrdSecCredentials *creds = 0; - if (clientCreds) { - // Duplicate the buffer (otherwise it will get deleted ...) - int sz = clientCreds->size; - char *nbuf = (char *) malloc(sz); - if (nbuf) { - memcpy(nbuf, clientCreds->buffer, sz); - creds = new XrdSecCredentials(nbuf, sz); - } - } - return creds; - } - - // Handshake vars conatiner must be initialized at this point - if (!hs) - return ErrC(ei,0,0,0,kPWErrError, - "handshake var container missing","getCredentials"); - hs->ErrMsg = ""; - - // - // Nothing to do if buffer is empty and not filled during construction - if ((!parm && !hs->Parms) || (parm && (!(parm->buffer) || parm->size <= 0))) - return ErrC(ei,0,0,0,kPWErrNoBuffer,"missing parameters","getCredentials"); - - // Count interations - (hs->Iter)++; - - // Update time stamp - hs->TimeStamp = time(0); - - // Local vars - int nextstep = 0; - const char *stepstr = 0; - kXR_int32 status = 0; - char *bpub = 0; - int lpub = 0; - String CryptList = ""; - String Host = ""; - String RemID = ""; - String Emsg; - String specID = ""; - // Buffer / Bucket related - XrdSutBucket *bck = 0; - XrdSutBuffer *bpar = 0; // Global buffer - XrdSutBuffer *bmai = 0; // Main buffer - // Session status - pwdStatus_t SessionSt; - memset(&SessionSt,0,sizeof(SessionSt)); - - // - // Unlocks automatically returning - XrdSysMutexHelper pwdGuard(&pwdContext); - // - // Decode received buffer - bpar = hs->Parms; - if (!bpar && !(bpar = new XrdSutBuffer((const char *)parm->buffer,parm->size))) - return ErrC(ei,0,0,0,kPWErrDecodeBuffer,"global",stepstr); - // Ownership has been transferred - hs->Parms = 0; - // - // Check protocol ID name - if (strcmp(bpar->GetProtocol(),XrdSecPROTOIDENT)) - return ErrC(ei,bpar,bmai,0,kPWErrBadProtocol,stepstr); - // - // The step indicates what we are supposed to do - hs->Step = (bpar->GetStep()) ? bpar->GetStep() : kXPS_init; - stepstr = ServerStepStr(hs->Step); - // Dump, if requested - if (QTRACE(Dump)) { - bpar->Dump(stepstr); - } - // - // Find first crypto module to be used - if (ParseCrypto(bpar) != 0) - return ErrC(ei,bpar,0,0,kPWErrLoadCrypto,stepstr); - // - // Parse input buffer - if (ParseClientInput(bpar, &bmai, Emsg) == -1) { - PRINT(Emsg); - return ErrC(ei,bpar,bmai,0,kPWErrParseBuffer,Emsg.c_str(),stepstr); - } - // - // Version - DEBUG("version run by server: "<< hs->RemVers); - // - // Dump what we got - if (QTRACE(Dump)) { - bmai->Dump("Main IN"); - } - // - // Print server messages, if any - if (hs->Iter > 1) { - bmai->Message(); - bmai->Deactivate(kXRS_message); - } - // - // Check random challenge - if (!CheckRtag(bmai, Emsg)) - return ErrC(ei,bpar,bmai,0,kPWErrBadRndmTag,Emsg.c_str(),stepstr); - - // - // Get the status bucket, if any - if ((bck = bmai->GetBucket(kXRS_status))) { - int pst = 0; - memcpy(&pst,bck->buffer,sizeof(pwdStatus_t)); - pst = ntohl(pst); - memcpy(&SessionSt, &pst, sizeof(pwdStatus_t)); - bmai->Deactivate(kXRS_status); - } else { - SessionSt.ctype = kpCT_normal; - } - // - // Now action depens on the step - nextstep = kXPC_none; - switch (hs->Step) { - - case kXPS_init: // The following 3 cases may fall through - case kXPS_puk: - case kXPS_signedrtag: // (after kXRC_verifysrv) -if (hs->Step == kXPS_init) - { - // - // Add bucket with cryptomod to the global list - // (This must be always visible from now on) - if (bpar->AddBucket(hs->CryptoMod,kXRS_cryptomod) != 0) - return ErrC(ei,bpar,bmai,0, - kPWErrCreateBucket,XrdSutBuckStr(kXRS_cryptomod),stepstr); - // - // Add bucket with our version to the main list - if (bmai->MarshalBucket(kXRS_version,(kXR_int32)(Version)) != 0) - return ErrC(ei,bpar,bmai,0, kPWErrCreateBucket, - XrdSutBuckStr(kXRS_version),"(main list)",stepstr); - // - // We set some options in the option field of a pwdStatus_t structure - if (hs->Tty || (AutoLogin > 0)) - SessionSt.options = kOptsClntTty; - } -// case kXPS_puk: -if ((hs->Step == kXPS_init) || (hs->Step == kXPS_puk)) - { - // After auto-reg request, server puk have been saved in ParseClientInput: - // we need to start a full normal login now - - // - // If we have a session cipher we extract the public part - // and add to the main packet for transmission to server - if (hs->Hcip) { - // - // Extract buffer with public info for the cipher agreement - if (!(bpub = hs->Hcip->Public(lpub))) - return ErrC(ei,bpar,bmai,0, - kPWErrNoPublic,"session",stepstr); - // - // Add it to the global list - if (bpar->UpdateBucket(bpub,lpub,kXRS_puk) != 0) - return ErrC(ei,bpar,bmai,0, kPWErrAddBucket, - XrdSutBuckStr(kXRS_puk),"global",stepstr); - SafeDelArray(bpub); - // - // If we are requiring server verification of puk ownership - // we are done for this step - if (VeriSrv == 1) { - nextstep = kXPC_verifysrv; - break; - } - } - } -// case kXPS_signedrtag: // (after kXRC_verifysrv) - // - // Add the username - if (hs->User.length()) { - if (bmai->AddBucket(hs->User,kXRS_user) != 0) - return ErrC(ei,bpar,bmai,0, kPWErrDuplicateBucket, - XrdSutBuckStr(kXRS_user),stepstr); - } else - return ErrC(ei,bpar,bmai,0, kPWErrNoUser,stepstr); - - // - // If we do not have a session cipher, the only thing we can - // try is auto-registration - if (!(hs->Hcip)) { - nextstep = kXPC_autoreg; - break; - } - - // - // Normal attempt: add credentials - status = kpCT_normal; - if (hs->SysPwd == 1) - status = kpCT_crypt; - if (hs->SysPwd == 2) - status = kpCT_afs; - if (!(bck = QueryCreds(bmai, (AutoLogin > 0), status))) - return ErrC(ei,bpar,bmai,0, kPWErrQueryCreds, - hs->Tag.c_str(),stepstr); - bmai->AddBucket(bck); - // - // Tell the server we want to change the password, if so - if (hs->Pent->status == kPFE_onetime) - SessionSt.options |= kOptsChngPwd; - // - nextstep = kXPC_normal; - break; - - case kXPS_credsreq: - // - // If this is not the first time, during the handshake, that - // we query credentials, any save buffer must insufficient, - // so invalidate it - if (hs->Pent) - hs->Pent->cnt = 1; - // - // Server requires additional credentials: the status bucket - // tells us what she wants exactly - status = SessionSt.ctype; - if (!(bck = QueryCreds(bmai, 0, status))) - return ErrC(ei,bpar,bmai,0, kPWErrQueryCreds, - hs->Tag.c_str(),stepstr); - bmai->AddBucket(bck); - // - nextstep = kXPC_creds; - break; - - case kXPS_failure: - // - // Failure: invalidate cache - hs->Pent->buf1.SetBuf(); - hs->Pent->buf2.SetBuf(); - // - nextstep = kXPC_failureack; - break; - - case kXPS_newpuk: - // - // New server puk have been saved in ParseClientInput: we - // just need to sign the random tag - case kXPS_rtag: - // - // Not much to do: the random tag is signed in AddSerialized - nextstep = kXPC_signedrtag; - break; - - default: - return ErrC(ei,bpar,bmai,0, kPWErrBadOpt,stepstr); - } - // - // Add / Update status - int *pst = (int *) new char[sizeof(pwdStatus_t)]; - memcpy(pst,&SessionSt,sizeof(pwdStatus_t)); - *pst = htonl(*pst); - if (bmai->AddBucket((char *)pst,sizeof(pwdStatus_t), kXRS_status) != 0) { - PRINT("problems adding bucket kXRS_status"); - } - // - // Serialize and encrypt - if (AddSerialized('c', nextstep, hs->ID, - bpar, bmai, kXRS_main, hs->Hcip) != 0) - return ErrC(ei,bpar,bmai,0, - kPWErrSerialBuffer,"main",stepstr); - // - // Serialize the global buffer - char *bser = 0; - int nser = bpar->Serialized(&bser,'f'); - - if (QTRACE(Dump)) { - bpar->Dump(ClientStepStr(bpar->GetStep())); - bmai->Dump("Main OUT"); - } - // - // We may release the buffers now - REL2(bpar,bmai); - // - // Return serialized buffer - if (nser > 0) { - DEBUG("returned " << nser <<" bytes of credentials"); - return new XrdSecCredentials(bser, nser); - } else { - DEBUG("problems with final serialization"); - return (XrdSecCredentials *)0; - } -} - -/******************************************************************************/ -/* S e r v e r O r i e n t e d M e t h o d s */ -/******************************************************************************/ -/******************************************************************************/ -/* A u t h e n t i c a t e */ -/******************************************************************************/ - -int XrdSecProtocolpwd::Authenticate(XrdSecCredentials *cred, - XrdSecParameters **parms, - XrdOucErrInfo *ei) -{ - // - // Check if we have any credentials or if no credentials really needed. - // In either case, use host name as client name - EPNAME("Authenticate"); - - // - // If cred buffer is two small or empty assume host protocol - if (cred->size <= (int)XrdSecPROTOIDLEN || !cred->buffer) { - strncpy(Entity.prot, "host", sizeof(Entity.prot)); - return 0; - } - - // Handshake vars container must be initialized at this point - if (!hs) - return ErrS(String("none"),ei,0,0,0,kPWErrError, - "handshake var container missing", - "protocol initialization problems"); - hs->ErrMsg = ""; - // - // Update time stamp - hs->TimeStamp = time(0); - - // - // ID of this handshaking - hs->ID = Entity.tident; - DEBUG("handshaking ID: " << hs->ID); - - // Local vars - int i = 0; - int kS_rc = kpST_more; - int rc = 0; - int entst = 0; - int nextstep = 0; - int ctype = kpCT_normal; - char *bpub = 0, *bpid = 0; - int lpub = 0; - const char *stepstr = 0; - String Message; - String CryptList; - String Host; - String SrvPuKExp; - String Salt; - String RndmTag; - String ClntMsg(256); - // Buffer related - XrdSutBuffer *bpar = 0; // Global buffer - XrdSutBuffer *bmai = 0; // Main buffer - XrdSutBucket *bck = 0; // Generic bucket - // The local status info - pwdStatus_t SessionSt = { 0, 0, 0}; - - // - // Unlocks automatically returning - XrdSysMutexHelper pwdGuard(&pwdContext); - // - // Decode received buffer - if (!(bpar = new XrdSutBuffer((const char *)cred->buffer,cred->size))) - return ErrS(hs->ID,ei,0,0,0,kPWErrDecodeBuffer,"global",stepstr); - // - // Check protocol ID name - if (strcmp(bpar->GetProtocol(),XrdSecPROTOIDENT)) - return ErrS(hs->ID,ei,bpar,bmai,0,kPWErrBadProtocol,stepstr); - // - // The step indicates what we are supposed to do - hs->Step = bpar->GetStep(); - stepstr = ClientStepStr(hs->Step); - // Dump, if requested - if (QTRACE(Dump)) { - bpar->Dump(stepstr); - } - - // - // Find first crypto module to be used - if (ParseCrypto(bpar) != 0) - return ErrS(hs->ID,ei,bpar,0,0,kPWErrLoadCrypto,stepstr); - // - // Parse input buffer - if (ParseServerInput(bpar, &bmai, ClntMsg) == -1) { - PRINT(ClntMsg); - return ErrS(hs->ID,ei,bpar,bmai,0,kPWErrParseBuffer,ClntMsg.c_str(),stepstr); - } - // - // Get handshake status - if ((bck = bmai->GetBucket(kXRS_status))) { - int pst = 0; - memcpy(&pst,bck->buffer,sizeof(pwdStatus_t)); - pst = ntohl(pst); - memcpy(&SessionSt, &pst, sizeof(pwdStatus_t)); - bmai->Deactivate(kXRS_status); - } else { - NOTIFY("no bucket kXRS_status found in main buffer"); - } - hs->Tty = SessionSt.options & kOptsClntTty; - // - // Client name - unsigned int ulen = hs->User.length(); - ulen = (ulen > sizeof(CName)-1) ? sizeof(CName)-1 : ulen; - if (ulen) - strcpy(CName, hs->User.c_str()); - // And set link to entity - Entity.name = strdup(CName); - - // - // Version - DEBUG("version run by client: "<< hs->RemVers); - // - // Dump, if requested - if (QTRACE(Dump)) { - bmai->Dump("main IN"); - } - // - // Check random challenge - if (!CheckRtag(bmai, ClntMsg)) - return ErrS(hs->ID,ei,bpar,bmai,0,kPWErrBadRndmTag,stepstr,ClntMsg.c_str()); - // - // Check also host / time stamp (it will be done only if really neede) - if (!CheckTimeStamp(bmai, TimeSkew, ClntMsg)) - return ErrS(hs->ID,ei,bpar,bmai,0,kPWErrBadRndmTag,stepstr,ClntMsg.c_str()); - // - // Now action depens on the step - bool savecreds = (SessionSt.options & kOptsExpCred); - switch (hs->Step) { - - case kXPC_verifysrv: - // - // Client required us to sign a random challenge: this is done - // in AddSerialized, so nothing to do here - nextstep = kXPS_signedrtag; - break; - - case kXPC_signedrtag: - // - // Client signed the random challenge we sent: if we are here, - // everything was fine - kS_rc = kpST_ok; - nextstep = kXPS_none; - break; - - case kXPC_failureack: - // - // Client acknowledged failure - kS_rc = kpST_error; - nextstep = kXPS_none; - break; - - case kXPC_autoreg: - // - // Client has lost the key or requested auto-registration: we - // check the username: if it has a good entry or it is allowed - // to auto-register (the check is done in QueryUser) we send - // the public part of the key; otherwise we fail - rc = QueryUser(entst, ClntMsg); - if (rc < 0 || (entst == kPFE_disabled)) - return ErrS(hs->ID,ei,bpar,bmai,0, kPWErrBadCreds, - DefError.c_str(),stepstr); - // - // We have to send the public key - for (i = 0; i < ncrypt; i++) { - if (refcip[i]) { - // - // Extract buffer with public info for the cipher agreement - if (!(bpub = refcip[i]->Public(lpub))) - return ErrS(hs->ID,ei,bpar,bmai,0, kPWErrNoPublic, - "session",stepstr); - bpid = new char[lpub+5]; - if (bpid) { - char cid[5] = {0}; - sprintf(cid,"%d",cryptID[i]); - memcpy(bpid,cid,5); - memcpy(bpid+5, bpub, lpub); - // - // Add it to the global list - if (bmai->AddBucket(bpid,lpub+5,kXRS_puk) != 0) - return ErrS(hs->ID,ei,bpar,bmai,0, kPWErrAddBucket, - "main",stepstr); - } else - return ErrS(hs->ID,ei,bpar,bmai,0, kPWErrError, - "out-of-memory",stepstr); - SafeDelArray(bpub); // bpid is taken by the bucket - } - } - // client should now go through a complete login - nextstep = kXPS_puk; - break; - - case kXPC_normal: - case kXPC_creds: -if (hs->Step == kXPC_normal) - { - // - // Complete login sequence: check user and creds - if (QueryUser(entst,ClntMsg) != 0) - return ErrS(hs->ID,ei,bpar,bmai,0, kPWErrBadCreds, - ": user ",hs->User.c_str(),stepstr); - // Nothing to do, if disabled - if (entst == kPFE_disabled) - return ErrS(hs->ID,ei,bpar,bmai,0, kPWErrBadCreds, - ": user ",hs->User.c_str(),stepstr); - - if (entst == kPFE_expired || entst == kPFE_onetime) { - // New credentials should asked upon success first check - SessionSt.options |= kOptsExpCred; - } - if (entst == kPFE_crypt) { - // User credentials are either in crypt form (private or - // system ones) or of AFS type; in case of failure - // this flag allows the client to send the right creds - // at next iteration - if (ClntMsg.beginswith("afs:")) { - SessionSt.options |= kOptsAFSPwd; - } else - SessionSt.options |= kOptsCrypPwd; - // Reset the message - ClntMsg = ""; - } - // Creds, if any, should be checked, unles we allow auto-registration - savecreds = (entst != kPFE_allowed) ? 0 : 1; - } - -// case kXPC_creds: (falls into here from _normal) - // - // Final login sequence: extract and check creds - // Extract credentials from main buffer - if (!(bck = bmai->GetBucket(kXRS_creds))) { - // - // If credentials are missing, require them - kS_rc = kpST_more; - nextstep = kXPS_credsreq; - break; - } - // - // If we required new credentials at previous step, just save them - if (savecreds) { - if (SaveCreds(bck) != 0) { - ClntMsg = "Warning: could not correctly update credentials database"; - } - kS_rc = kpST_ok; - nextstep = kXPS_none; - bmai->Deactivate(kXRS_creds); - break; - } - // - // Credential type - ctype = kpCT_normal; - if (SessionSt.options & kOptsCrypPwd) - ctype = kpCT_crypt; - else if (SessionSt.options & kOptsAFSPwd) { - ctype = kpCT_afs; - String afsInfo; - XrdSutBucket *bafs = bmai->GetBucket(kXRS_afsinfo); - if (bafs) - bafs->ToString(afsInfo); - if (afsInfo == "c") - ctype = kpCT_afsenc; - } - // - // Check credentials - if (!CheckCreds(bck, ctype)) { - // - // Count temporary failures - (hs->Cref->cnt)++; - // Reset expired credentials flag - SessionSt.options &= ~kOptsExpCred; - // Repeat if not too many attempts - ClntMsg = DefError; - if (hs->Cref->cnt < MaxPrompts) { - // Set next step to credential request - nextstep = kXPS_credsreq; - kS_rc = kpST_more; - // request again creds - if (hs->Pent->status == kPFE_crypt) { - SessionSt.ctype = kpCT_crypt; - if (ctype == kpCT_afs || ctype == kpCT_afsenc) { - SessionSt.ctype = kpCT_afs; - String afsinfo = hs->ErrMsg; - bmai->UpdateBucket(afsinfo, kXRS_afsinfo); - } - ClntMsg = ""; - } else { - SessionSt.ctype = kpCT_normal; - ClntMsg = "insufficient credentials"; - } - } else { - // We communicate failure - kS_rc = kpST_more; - nextstep = kXPS_failure; - // Count failures - (hs->Pent->cnt)++; - // Count failures - hs->Pent->mtime = (kXR_int32)time(0); - // Flush cache content to source file - XrdSysPrivGuard priv(getuid(), getgid()); - if (priv.Valid()) { - if (cacheAdmin.Flush() != 0) { - PRINT("WARNING: some problem flushing to admin" - " file after updating "<Pent->name); - } - } - } - } else { - // Reset counter for temporary failures - hs->Cref->cnt = 0; - // Reset counter in file if needed - if (hs->Pent->cnt > 0) { - hs->Pent->cnt = 0; - // Count failures - hs->Pent->mtime = (kXR_int32)time(0); - // Flush cache content to source file - XrdSysPrivGuard priv(getuid(), getgid()); - if (priv.Valid()) { - if (cacheAdmin.Flush() != 0) { - PRINT("WARNING: some problem flushing to admin" - " file after updating "<Pent->name); - } - } - } - kS_rc = kpST_ok; - nextstep = kXPS_none; - if (SessionSt.options & kOptsExpCred || - // Client requested a pwd change - SessionSt.options & kOptsChngPwd) { - kS_rc = kpST_more; - nextstep = kXPS_credsreq; - if (SessionSt.options & kOptsExpCred) { - ClntMsg = "Credentials expired"; - } else if (SessionSt.options & kOptsChngPwd) { - ClntMsg = "Password change requested"; - } - // request new creds - SessionSt.ctype = kpCT_new; - // So we can save at next round - SessionSt.options |= kOptsExpCred; - } - // Create buffer to keep the credentials, if required - if (KeepCreds) { - int sz = bck->size+5; - char *buf = (char *) malloc(sz); - if (buf) { - memcpy(buf, "&pwd", 4); - buf[4] = 0; - memcpy(buf+5, bck->buffer, bck->size); - // Put in hex - char *out = new char[2*sz+1]; - XrdSutToHex(buf, sz, out); - // Cleanup any existing info - SafeDelete(clientCreds); - clientCreds = new XrdSecCredentials(out, 2*sz+1); - } - } - // Export creds to a file, if required - if (FileExpCreds.length() > 0) { - if (ExportCreds(bck) != 0) - PRINT("WARNING: some problem exporting creds to file;" - " template is :"<Deactivate(kXRS_creds); - - break; - - default: - return ErrS(hs->ID,ei,bpar,bmai,0, kPWErrBadOpt, stepstr); - } - - // - // If strong signature checking is required add random tag - if (kS_rc == kpST_ok) { - if (VeriClnt == 2 && !(hs->RtagOK)) { - // Send only the random tag to sign - nextstep = kXPS_rtag; - kS_rc = kpST_more; - } - } - - // - // If we need additional info but the client caa not reply, just fail - if (kS_rc == kpST_more && !(hs->Tty)) { - PRINT("client cannot reply to additional request: failure"); - // Deactivate everything - bpar->Deactivate(-1); - bmai->Deactivate(-1); - kS_rc = kpST_error; - } - // - if (kS_rc == kpST_more) { - // - // Add message to client - if (ClntMsg.length() > 0) - if (bmai->AddBucket(ClntMsg,kXRS_message) != 0) { - PRINT("problems adding bucket with message for client"); - } - // - // We set some options in the option field of a pwdStatus_t structure - int *pst = (int *) new char[sizeof(pwdStatus_t)]; - memcpy(pst,&SessionSt,sizeof(pwdStatus_t)); - *pst = htonl(*pst); - if (bmai->AddBucket((char *)pst,sizeof(pwdStatus_t), kXRS_status) != 0) { - PRINT("problems adding bucket kXRS_status"); - } - // - // Serialize, encrypt and add to the global list - if (AddSerialized('s', nextstep, hs->ID, - bpar, bmai, kXRS_main, hs->Hcip) != 0) - return ErrS(hs->ID,ei,bpar,bmai,0, kPWErrSerialBuffer, - "main / session cipher",stepstr); - // - // Serialize the global buffer - char *bser = 0; - int nser = bpar->Serialized(&bser,'f'); - // - // Dump, if requested - if (QTRACE(Dump)) { - bpar->Dump(ServerStepStr(bpar->GetStep())); - bmai->Dump("Main OUT"); - } - // - // Create buffer for client - *parms = new XrdSecParameters(bser,nser); - } else { - // - // Cleanup handshake vars - SafeDelete(hs); - } - // - // We may release the buffers now - REL2(bpar,bmai); - // - // All done - return kS_rc; -} - -/******************************************************************************/ -/* E n a b l e T r a c i n g */ -/******************************************************************************/ - -XrdOucTrace *XrdSecProtocolpwd::EnableTracing() -{ - // Initiate error logging and tracing - - eDest.logger(&Logger); - PWDTrace = new XrdOucTrace(&eDest); - return PWDTrace; -} - -/******************************************************************************/ -/* p w d O p t i o n s :: P r i n t */ -/******************************************************************************/ - -void pwdOptions::Print(XrdOucTrace *t) -{ - // Dump summary of GSI init options - EPNAME("InitOpts"); - - // For clients print only if really required (for servers we notified it - // always once for all) - if ((mode == 'c') && debug <= 0) return; - - POPTS(t, "*** ------------------------------------------------------------ ***"); - POPTS(t, " Mode: "<< ((mode == 'c') ? "client" : "server")); - POPTS(t, " Debug: "<< debug); - if (mode == 'c') { - POPTS(t, " Check user's autologin info: " << (alog != 0 ? "yes" : "no")); - POPTS(t, " Verification level of server ownership on public key: " << verisrv); - POPTS(t, " Max number of empty prompts:" << maxprompts); - if (alogfile) - POPTS(t, " Autologin file:" << alogfile); - if (srvpuk) - POPTS(t, " File with known servers public keys:" << srvpuk); - POPTS(t, " Update auto-login info option:" << areg); - } else { - POPTS(t, " Check pwd file in user's home: " << (upwd != 0 ? "yes" : "no")); - POPTS(t, " Verification level of client ownership on public key: " << vericlnt); - POPTS(t, " Autoregistration option:" << areg); - POPTS(t, " Check system pwd file option: " << syspwd); - POPTS(t, " Credentials lifetime (seconds): " << lifecreds); - POPTS(t, " Max number of failures: " << maxfailures); - if (clist) - POPTS(t, " List of supported crypto modules: " << clist); - if (dir) - POPTS(t, " Directory with admin pwd files: " << dir); - if (udir) - POPTS(t, " User's sub-directory with pwd files: " << udir); - if (cpass) - POPTS(t, " User's crypt hash pwd file: " << cpass); - POPTS(t, " Keep client credentials in memory: " << (keepcreds != 0 ? "yes" : "no")); - if (expcreds) { - POPTS(t, " File for exported client credentials: " << expcreds); - POPTS(t, " Format for exported client credentials: " << expfmt); - } else { - POPTS(t, " Client credentials not exported to file"); - } - } - POPTS(t, "*** ------------------------------------------------------------ ***"); -} - -/******************************************************************************/ -/* X r d S e c P r o t o c o l p w d I n i t */ -/******************************************************************************/ - -XrdVERSIONINFO(XrdSecProtocolpwdInit,secpwd); - -extern "C" -{ -char *XrdSecProtocolpwdInit(const char mode, - const char *parms, XrdOucErrInfo *erp) -{ - // One-time protocol initialization, filling the static flags and options - // of the protocol. - // For clients (mode == 'c') we use values in envs. - // For servers (mode == 's') the command line options are passed through - // parms. - EPNAME("ProtocolpwdInit"); - - pwdOptions opts; - char *rc = (char *)""; - char *cenv = 0; - - // Initiate error logging and tracing - pwdTrace = XrdSecProtocolpwd::EnableTracing(); - - // - // Clients first - if (mode == 'c') { - // - // Decode envs: - // "XrdSecDEBUG" debug flag ("0","1","2","3") - // "XrdSecPWDVERIFYSRV" "1" server verification ON [default] - // "0" server verification OFF - // "XrdSecPWDSRVPUK" full path to file with server puks - // [default: $HOME/.xrd/pwdsrvpuk] - // "XrdSecPWDAUTOLOG" "1" autologin ON [default] - // "0" autologin OFF - // "XrdSecPWDALOGFILE" full path to file with autologin - // info [default: $HOME/.xrd/pwdnetrc] - // "XrdSecPWDALOGUPDT" update autologin file option: - // "0" never [default] - // "1" remove_obsolete_info - // "2" "1" + register_new_valid_info - // "XrdSecPWDMAXPROMPT" max number of attemts to get valid - // input info by prompting the client - // - opts.mode = mode; - // debug - cenv = getenv("XrdSecDEBUG"); - if (cenv) - {if (cenv[0] >= 49 && cenv[0] <= 51) opts.debug = atoi(cenv); - else {PRINT("unsupported debug value from env XrdSecDEBUG: "<= 48 && cenv[0] <= 49) opts.verisrv = atoi(cenv); - // file with server public keys - cenv = getenv("XrdSecPWDSRVPUK"); - if (cenv) - opts.srvpuk = strdup(cenv); - // autologin - cenv = getenv("XrdSecPWDAUTOLOG"); - if (cenv) - if (cenv[0] >= 48 && cenv[0] <= 50) opts.alog = atoi(cenv); - // autologin file - cenv = getenv("XrdSecPWDALOGFILE"); - if (cenv) - opts.alogfile = strdup(cenv); - // max re-prompts - cenv = getenv("XrdSecPWDMAXPROMPT"); - if (cenv) { - opts.maxprompts = strtol(cenv, (char **)0, 10); - if (errno == ERANGE) opts.maxprompts = -1; - } - // - // Setup the object with the chosen options - rc = XrdSecProtocolpwd::Init(opts,erp); - - // Notify init options, if required or in case of init errors - if (!rc) opts.debug = 1; - opts.Print(pwdTrace); - - // Some cleanup - if (opts.srvpuk) free(opts.srvpuk); - if (opts.alogfile) free(opts.alogfile); - - // We are done - return rc; - } - - // Take into account xrootd debug flag - cenv = getenv("XRDDEBUG"); - if (cenv && !strcmp(cenv,"1")) opts.debug = 1; - - // - // Server initialization - if (parms) { - // - // Duplicate the parms - char parmbuff[1024]; - strlcpy(parmbuff, parms, sizeof(parmbuff)); - // - // The tokenizer - XrdOucTokenizer inParms(parmbuff); - - // - // Decode parms: - // for servers: [-upwd:] - // [-a:] - // [-vc:] - // [-dir:] - // [-udir:] - // [-c:[-]ssl[:[-]] - // [-syspwd] - // [-lf:] - // [-maxfail:] - // [-keepcreds] - // [-expcreds:] - // [-expfmt:] - // - // = 0 (do-not-use), 1 (use), 2 (also-crypt-hash) - // = 0 (none), 1 (low), 2 (medium), 3 (high) [0] - // = 0 (none), 1 (local users + allowed tags), 2 (all) [0] - // = 1d, 5h:10m, ... (see XrdSutAux::ParseTime) - // = 0 (none), 1 (timestamp), 2 (random tag) [2] - // = can be a fully specified path or in the templated form - // /path//file, with expanded at the moment - // of use with the login name. - // = 0 (XrdSutPFEntry in dedicated file), - // 1 (hex form), 2 (plain), 3 (plain, no keywords) [0] - // - int debug = -1; - int areg = -1; - int vc = -1; - int upw = -1; - int syspwd = -1; - int lifetime = -1; - int maxfail = -1; - String dir = ""; - String udir = ""; - String clist = ""; - String cpass = ""; - int keepcreds = -1; - String expcreds = ""; - int expfmt = 0; - char *op = 0; - while (inParms.GetLine()) { - while ((op = inParms.GetToken())) { - if (!strncmp(op, "-upwd:",6)) { - upw = atoi(op+6); - } else if (!strncmp(op, "-dir:",5)) { - dir = (const char *)(op+5); - } else if (!strncmp(op, "-udir:",6)) { - udir = (const char *)(op+6); - } else if (!strncmp(op, "-c:",3)) { - clist = (const char *)(op+3); - } else if (!strncmp(op, "-d:",3)) { - debug = atoi(op+3); - } else if (!strncmp(op, "-a:",3)) { - areg = atoi(op+3); - } else if (!strncmp(op, "-vc:",4)) { - vc = atoi(op+4); - } else if (!strncmp(op, "-syspwd",7)) { - syspwd = 1; - } else if (!strncmp(op, "-lf:",4)) { - lifetime = XrdSutParseTime(op+4); - } else if (!strncmp(op, "-maxfail:",9)) { - maxfail = atoi(op+9); - } else if (!strncmp(op, "-cryptfile:",11)) { - cpass = (const char *)(op+11); - } else if (!strncmp(op, "-keepcreds",10)) { - keepcreds = 1; - } else if (!strncmp(op, "-expcreds:",10)) { - expcreds = (const char *)(op+10); - } else if (!strncmp(op, "-expfmt:",8)) { - expfmt = atoi(op+8); - } - } - // Check inputs - areg = (areg >= 0 && areg <= 2) ? areg : 0; - vc = (vc >= 0 && vc <= 2) ? vc : 2; - } - - // - // Build the option object - opts.debug = (debug > -1) ? debug : opts.debug; - opts.mode = 's'; - opts.areg = areg; - opts.vericlnt = vc; - opts.upwd = upw; - opts.syspwd = syspwd; - opts.lifecreds = lifetime; - opts.maxfailures = maxfail; - opts.expfmt = expfmt; - if (dir.length() > 0) - opts.dir = (char *)dir.c_str(); - if (udir.length() > 0) - opts.udir = (char *)udir.c_str(); - if (clist.length() > 0) - opts.clist = (char *)clist.c_str(); - if (cpass.length() > 0) - opts.cpass = (char *)cpass.c_str(); - opts.keepcreds = keepcreds; - if (expcreds.length() > 0) - opts.expcreds = (char *)expcreds.c_str(); - - // Notify init options, if required - opts.Print(pwdTrace); - // - // Setup the plug-in with the chosen options - return XrdSecProtocolpwd::Init(opts,erp); - } - - // Notify init options, if required - opts.Print(pwdTrace); - // - // Setup the plug-in with the defaults - return XrdSecProtocolpwd::Init(opts,erp); -}} - - -/******************************************************************************/ -/* X r d S e c P r o t o c o l p w d O b j e c t */ -/******************************************************************************/ - -XrdVERSIONINFO(XrdSecProtocolpwdObject,secpwd); - -extern "C" -{ -XrdSecProtocol *XrdSecProtocolpwdObject(const char mode, - const char *hostname, - XrdNetAddrInfo &endPoint, - const char *parms, - XrdOucErrInfo *erp) -{ - XrdSecProtocolpwd *prot; - int options = XrdSecNOIPCHK; - - // - // Get a new protocol object - if (!(prot = new XrdSecProtocolpwd(options, hostname, endPoint, parms))) { - const char *msg = "Secpwd: Insufficient memory for protocol."; - if (erp) - erp->setErrInfo(ENOMEM, msg); - else - cerr <GetNBuckets()) { - // If the bucket list is empty we assume this being the first iteration - // step (the step is not defined at this point). - // The option field should contain the relevant information - String opts = buf->GetOptions(); - if (!(opts.length())) { - DEBUG("missing options - bad format"); - return -1; - } - // - // Extract crypto module list, if any - int ii = opts.find("c:"); - if (ii >= 0) { - clist.assign(opts, ii+2); - clist.erase(clist.find(',')); - } else { - PRINT("crypto information not found in options"); - return -1; - } - } else { - // - // Extract crypto module name from the buffer - if (!(bck = buf->GetBucket(kXRS_cryptomod))) { - PRINT("cryptomod buffer missing"); - return -1; - } - bck->ToString(clist); - } - DEBUG("parsing list: "<CryptoMod = ""; - // Parse list - if (clist.length()) { - int from = 0; - while ((from = clist.tokenize(hs->CryptoMod, from, '|')) != -1) { - // Check this module - if (hs->CryptoMod.length()) { - // Load the crypto factory - if ((hs->CF = XrdCryptoFactory::GetCryptoFactory(hs->CryptoMod.c_str()))) { - int fid = hs->CF->ID(); - int i = 0; - // Retrieve the index in local table - while (i < ncrypt) { - if (cryptID[i] == fid) break; - i++; - } - if (i >= ncrypt) { - if (ncrypt == XrdCryptoMax) { - PRINT("max number of crypto slots reached - do nothing"); - return 0; - } else { - // Add new entry - cryptID[i] = fid; - ncrypt++; - } - } - // On servers the ref cipher should be defined at this point - hs->Rcip = refcip[i]; - // we are done - return 0; - } - } - } - } - - return 1; -} - -//____________________________________________________________________ -bool XrdSecProtocolpwd::CheckCreds(XrdSutBucket *creds, int ctype) -{ - // Check credentials against information in password file - EPNAME("CheckCreds"); - bool match = 0; - - // Check inputs - if (!hs->CF || !creds || !hs->Pent) { - PRINT("Invalid inputs ("<CF<<","<Pent<<")"); - return match; - } - // Make sure there is something to check against - if (ctype != kpCT_afs && ctype != kpCT_afsenc && - (!(hs->Pent->buf1.buf) || hs->Pent->buf1.len <= 0)) { - NOTIFY("Cached information about creds missing"); - return match; - } - // - // Create a buffer to store credentials, if required - int len = creds->size+4; - char *cbuf = (KeepCreds) ? new char[len] : (char *)0; - - // - // Separate treatment for crypt-like creds - if (ctype != kpCT_crypt && ctype != kpCT_afs && ctype != kpCT_afsenc) { - // - // Create a bucket for the salt to easy encryption - XrdSutBucket *tmps = new XrdSutBucket(); - if (!tmps) { - PRINT("Could not allocate working buckets area for the salt"); - return match; - } - tmps->SetBuf(hs->Pent->buf1.buf, hs->Pent->buf1.len); - // - // Save input bucket if creds have to be kept - if (KeepCreds) { - memcpy(cbuf, "pwd:", 4); - memcpy(cbuf+4, creds->buffer, creds->size); - } - // - // Hash received buffer for the comparison - DoubleHash(hs->CF,creds,tmps); - // Compare - if (hs->Pent->buf2.len == creds->size) - if (!memcmp(creds->buffer, hs->Pent->buf2.buf, creds->size)) - match = 1; - SafeDelete(tmps); - // - // recover input creds - if (match && KeepCreds) - creds->SetBuf(cbuf, len); - - } else { -#ifdef HAVE_CRYPT - // Crypt-like: get the pwhash - String passwd(creds->buffer,creds->size+1); - passwd.reset(0,creds->size,creds->size); - // Get the crypt - char *pass_crypt = crypt(passwd.c_str(), hs->Pent->buf1.buf); - // Compare - if (!strncmp(pass_crypt, hs->Pent->buf1.buf, hs->Pent->buf1.len + 1)) - match = 1; - if (match && KeepCreds) { - memcpy(cbuf, "cpt:", 4); - memcpy(cbuf+4, creds->buffer, creds->size); - creds->SetBuf(cbuf, len); - } -#else - NOTIFY("Crypt-like passwords (via crypt(...)) not supported"); - match = 0; -#endif - } - - // Cleanup - if (cbuf) - delete[] cbuf; - - // We are done - return match; -} - -//________________________________________________________________________ -bool XrdSecProtocolpwd::CheckCredsAFS(XrdSutBucket *, int) -{ - // Check AFS credentials - not supported - return 0; -} - -//____________________________________________________________________ -int XrdSecProtocolpwd::SaveCreds(XrdSutBucket *creds) -{ - // Save credentials in creds in the password file - // Returns 0 if ok, -1 otherwise - EPNAME("SaveCreds"); - XrdSutPFCacheRef pfeRef; - - // Check inputs - if ((hs->User.length() <= 0) || !hs->CF || !creds) { - PRINT("Bad inputs ("<User.length()<<","<CF<<"," - <Tag + '_'; wTag += hs->CF->ID(); - // - // Update entry in cache, if there, or add one - XrdSutPFEntry *cent = cacheAdmin.Add(pfeRef, wTag.c_str()); - if (!cent) { - PRINT("Could not get entry in cache"); - return -1; - } - // Generate a salt and fill it in - char *tmps = XrdSutRndm::GetBuffer(8,3); - if (!tmps) { - PRINT("Could not generate salt: out-of-memory"); - return -1; - } - XrdSutBucket *salt = new XrdSutBucket(tmps,8); - if (!salt) { - PRINT("Could not create salt bucket"); - return -1; - } - cent->buf1.SetBuf(salt->buffer,salt->size); - // - // Now we sign the creds with the salt - DoubleHash(hs->CF,creds,salt); - // and fill in the creds - cent->buf2.SetBuf(creds->buffer,creds->size); - // - // Set entry status OK - cent->status = kPFE_ok; - // - // Save entry - cent->mtime = hs->TimeStamp; - // - DEBUG("Entry for tag: "<User.length() <= 0) || !hs->CF || !creds) { - PRINT("Bad inputs ("<User.length()<<","<CF<<"," - <Tag + '_'; wTag += hs->CF->ID(); - // - // Create and fill a new entry - XrdSutPFEntry ent; - ent.SetName(wTag.c_str()); - ent.status = kPFE_ok; - ent.cnt = 0; - if (!strncmp(creds->buffer, "pwd:", 4)) { - // Skip initial "pwd:" - ent.buf1.SetBuf(creds->buffer+4, creds->size-4); - } else { - // For crypt and AFS we keep that to be able to distinguish - // later on - ent.buf1.SetBuf(creds->buffer,creds->size); - } - // - // Write entry - ent.mtime = time(0); - pfcreds.WriteEntry(ent); - DEBUG("New entry for "<size + 5; - if ((buf = (char *) malloc(sz))) { - memcpy(buf, "&pwd", 4); - buf[4] = 0; - memcpy(buf+5, creds->buffer, creds->size); - // Put in hex - if (FmtExpCreds == 1) { - out = new char[2*sz+1]; - XrdSutToHex(buf, sz, out); - } - } else { - PRINT("Problem creating buffer for exported credentials!"); - return -1; - } - - // Open the file, truncating if already existing - int fd = open(filecreds.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0600); - if (fd < 0) { - PRINT("problems creating file - errno: " << errno); - if (buf) {free(buf); buf = 0;} - SafeDelete(out); - return -1; - } - - const char *pw = 0; - int lw = -1; - if (FmtExpCreds == 1) { - // Hex form - pw = (const char *)out; - lw = 2*sz + 1; - } else if (FmtExpCreds == 3) { - // Ignore keywords - int offs = (hs->SysPwd == 2) ? 9 : 5; - pw = (const char *)(buf + offs); - lw = sz - offs; - } else { - pw = (const char *)buf; - lw = sz; - } - // Write out now - int nw = 0, written = 0; - while (lw) { - if ((nw = write(fd, pw + written, lw)) < 0) { - if (errno == EINTR) { - errno = 0; - continue; - } else { - break; - } - } - // Count - written += nw; - lw -= nw; - } - - // Cleanup temporary buffers - if (buf) {free(buf); buf = 0;} - SafeDelete(out); - close(fd); - } - // We are done - return 0; -} - -//____________________________________________________________________ -XrdSutBucket *XrdSecProtocolpwd::QueryCreds(XrdSutBuffer *bm, - bool netrc, int &status) -{ - // Get credential information to be sent to the server - EPNAME("QueryCreds"); - XrdSutPFCacheRef pfeRef; - - // Check inputs - if (!bm || !hs->CF || hs->Tag.length() <= 0) { - PRINT("bad inputs ("<CF<<","<Tag.length()<<")"); - return (XrdSutBucket *)0; - } - - // - // Type of creds (for the prompt) - int ctype = (status > kpCT_undef) ? status : kpCT_normal; - netrc = ((ctype == kpCT_normal || ctype == kpCT_onetime || - ctype == kpCT_old || ctype == kpCT_crypt)) ? netrc : 0; - // - // reset status - status = kpCI_undef; - // Output bucket - XrdSutBucket *creds = new XrdSutBucket(); - if (!creds) { - PRINT("Could allocate bucket for creds"); - return (XrdSutBucket *)0; - } - creds->type = kXRS_creds; - - // - // Build effective tag - String wTag = hs->Tag + '_'; wTag += hs->CF->ID(); - - // - // If creds are available in the environment pick them up and use them - char *cf = 0; - char *cbuf = getenv("XrdSecCREDS"); - if (cbuf) { - int len = strlen(cbuf); - // From hex - int sz = len; - char *out = new char[sz/2+2]; - XrdSutFromHex((const char *)cbuf, out, len); - if ((cf = strstr(out, "&pwd"))) { - cf += 5; - len -= 5; - if (len > 0) { - // Get prefix - char pfx[5] = {0}; - memcpy(pfx, cf, 4); - cf += 4; - len -= 4; - if (len > 0) { - DEBUG("using "<Pent = cacheAlog.Add(pfeRef, wTag.c_str()); - if (hs->Pent) { - // Try only once - if (hs->Pent->cnt == 0) { - // Set buf - creds->SetBuf(cf,len); - // Fill entry - if (strncmp(pfx,"pwd",3)) - hs->Pent->status = kPFE_crypt; - hs->Pent->mtime = hs->TimeStamp; - hs->Pent->buf1.SetBuf(cf, len); - // Just in case we need the passwd itself (like in crypt) - hs->Pent->buf2.SetBuf(cf, len); - // Tell the server - if (!strncmp(pfx,"afs",3)) { - String afsInfo = "c"; - if (bm->UpdateBucket(afsInfo, kXRS_afsinfo) != 0) - PRINT("Warning: problems updating bucket with AFS info"); - } - // Update status - status = kpCI_exact; - // We are done - return creds; - } else { - // Cleanup - hs->Pent->buf1.SetBuf(); - hs->Pent->buf2.SetBuf(); - } - } else { - PRINT("Could create new entry in cache"); - return (XrdSutBucket *)0; - } - } - } - } - } - pfeRef.UnLock(); // Unlock pointer if we got a lock on it! - - // - // Extract AFS info (the cell), if any - String afsInfo; - if (ctype == kpCT_afs || ctype == kpCT_afsenc) { - XrdSutBucket *bafs = bm->GetBucket(kXRS_afsinfo); - if (bafs) - bafs->ToString(afsInfo); - } - // - // Search information in autolog file(s) first, if required - if (netrc) { - // - // Make sure cache it is up-to-date - if (PFAlog.IsValid()) { - if (cacheAlog.Refresh() != 0) { - PRINT("problems assuring cache update for file alog "); - } - } - // - // We may already have an entry in the cache - bool wild = 0; - hs->Pent = cacheAlog.Get(pfeRef, wTag.c_str(),&wild); - // Retrieve pwd information if ok - if (hs->Pent && hs->Pent->buf1.buf) { - if (hs->Pent->cnt == 0) { - cf = hs->Pent->buf1.buf; - bool afspwd = strncmp(cf,"afs",3) ? 0 : 1; - if (!strncmp(cf,"cpt",3) || afspwd) { - int len = hs->Pent->buf1.len; - cf += 4; - len -= 4; - hs->Pent->status = kPFE_crypt; - hs->Pent->mtime = hs->TimeStamp; - hs->Pent->buf1.SetBuf(cf, len); - // Just in case we need the passwd itself (like in crypt) - hs->Pent->buf2.SetBuf(cf, len); - // Tell the server - if (afspwd) { - afsInfo = "c"; - if (bm->UpdateBucket(afsInfo, kXRS_afsinfo) != 0) - PRINT("Warning: problems updating bucket with AFS info"); - } - } - // Fill output with double hash - creds->SetBuf(hs->Pent->buf1.buf,hs->Pent->buf1.len); - // Update status - status = wild ? kpCI_wildcard : kpCI_exact; - // We are done - return creds; - } else { - // Entry not ok: probably previous attempt failed: discard - hs->Pent->buf1.SetBuf(); - } - } - pfeRef.UnLock(); // In case we have the lock release it. - - // for crypt-like, look also into a .netrc-like file, if any - String passwd; - String host(hs->Tag,hs->Tag.find("@",0)+1,hs->Tag.find(":",0)-1); - if (QueryNetRc(host, passwd, status) == 0) { - // Create or Fill entry in cache - if ((hs->Pent = cacheAlog.Add(pfeRef, wTag.c_str()))) { - // Fill entry - hs->Pent->status = kPFE_crypt; - hs->Pent->mtime = hs->TimeStamp; - hs->Pent->buf1.SetBuf(passwd.c_str(),passwd.length()); - // Fill output - creds->SetBuf(passwd.c_str(),passwd.length()); - // Update status - status = kpCI_exact; - // We are done - return creds; - } else { - PRINT("Could create new entry in cache"); - return (XrdSutBucket *)0; - } - } - } - // - // Create or Fill entry in cache - if (!(hs->Pent) && !(hs->Pent = cacheAlog.Add(pfeRef, wTag.c_str()))) { - PRINT("Could create new entry in cache"); - return (XrdSutBucket *)0; - } - - // - // If a previous attempt was successful re-use same passwd - if (hs->Pent && hs->Pent->buf1.buf && hs->Pent->cnt == 0) { - // Fill output - creds->SetBuf(hs->Pent->buf1.buf,hs->Pent->buf1.len); - // Update status - status = kpCI_exact; - // We are done - return creds; - } - - // - // We are here because: - // 1) autologin disabled or no autologin info found - // ==> hs->Pent empty ==> prompt for password - // 2) we need to send a new password hash because it was wrong - // ==> hs->Pent->buf2 empty ==> prompt for password - // 3) we need to send a new password hash because it has expired - // (either one-time or too old) - // ==> query hs->Pent->buf2 before prompting - // 4) we need to send a real password because the server uses crypt() - // or AFS - // ==> query hs->Pent->buf2 from previous prompt - - // - // If the previously cached entry has a second (final) passwd - // use it. This is the case when the real passwd is required (like in - // crypt), we may have it in cache from a previous prompt - if (ctype == kpCT_crypt || ctype == kpCT_afs) { - if (hs->Pent && hs->Pent->buf2.buf) { - if (ctype == kpCT_afs) { - String passwd(hs->Pent->buf2.buf,hs->Pent->buf2.len); - // Fill output - creds->SetBuf(hs->Pent->buf2.buf,hs->Pent->buf2.len); - // Not needed anymore - bm->Deactivate(kXRS_afsinfo); - } else { - // Fill output - creds->SetBuf(hs->Pent->buf2.buf,hs->Pent->buf2.len); - } - // Save info in the first buffer and reset the second buffer - hs->Pent->buf1.SetBuf(hs->Pent->buf2.buf,hs->Pent->buf2.len); - hs->Pent->buf2.SetBuf(); - // Update status - status = kpCI_exact; - // We are done - return creds; - } - } - - // - // From now we need to prompt the user: we can do this only if - // connected to a terminal - if (!(hs->Tty)) { - NOTIFY("Not connected to tty: cannot prompt user for credentials"); - return (XrdSutBucket *)0; - } - - // - // Prompt - char prompt[XrdSutMAXPPT] = {0}; - if (ctype == kpCT_onetime) - snprintf(prompt,XrdSutMAXPPT, "Password for %s not active: " - "starting activation handshake.",hs->Tag.c_str()); - // - // Prepare the prompt - if (ctype == kpCT_new) { - snprintf(prompt,XrdSutMAXPPT, "Enter new password: "); - } else if (ctype == kpCT_crypt) { - String host(hs->Tag,hs->Tag.find("@",0)+1,hs->Tag.find(":",0)-1); - snprintf(prompt,XrdSutMAXPPT, "Password for %s@%s: ", - hs->User.c_str(), host.c_str()); - } else if (ctype == kpCT_afs || ctype == kpCT_afsenc) { - snprintf(prompt,XrdSutMAXPPT, "AFS password for %s@%s: ", - hs->User.c_str(), hs->AFScell.c_str()); - } else { - // Normal prompt - snprintf(prompt,XrdSutMAXPPT,"Password for %s:",hs->Tag.c_str()); - } - // - // Inquire password - int natt = MaxPrompts; - String passwd = ""; - bool changepwd =0; - while (natt-- && passwd.length() <= 0) { - XrdSutGetPass(prompt, passwd); - // If in the format $changepwd$ we are asking for - // a password change - if (passwd.beginswith("$changepwd$")) { - PRINT("Requesting a password change"); - changepwd = 1; - passwd.erase("$changepwd$",0,strlen("$changepwd$")); - } - if (passwd.length()) { - // Fill in password - creds->SetBuf(passwd.c_str(),passwd.length()); - if (ctype != kpCT_crypt && ctype != kpCT_afs) { - // Self-Hash - DoubleHash(hs->CF,creds,creds); - // Update status - status = kpCI_prompt; - } else if (ctype == kpCT_afs) { - - } - // Save creds to update auto-login file later - // It will be flushed to file if required - if (changepwd) - hs->Pent->status = kPFE_onetime; - else - hs->Pent->status = kPFE_ok; - hs->Pent->buf1.SetBuf(creds->buffer,creds->size); - // - // Just in case we need the passwd itself (like in crypt) - hs->Pent->buf2.SetBuf(passwd.c_str(),passwd.length()); - // Update autologin, if required - if (AutoLogin > 0) - UpdateAlog(); - } - } - // Cleanup, if we did not get anything - if (passwd.length() <= 0) { - delete creds; - creds = 0; - } - // We are done - return creds; -} - -//____________________________________________________________________ -int XrdSecProtocolpwd::UpdateAlog() -{ - // Save pass hash in autologin file - // Returns 0 if ok, -1 otherwise - EPNAME("UpdateAlog"); - - // Check inputs - if (hs->Tag.length() <= 0) { - PRINT("Tag undefined - do nothing"); - return -1; - } - // Check inputs - if (!(hs->Pent) || !(hs->Pent->buf1.buf)) { - NOTIFY("Nothing to do"); - return 0; - } - // - // Build effective tag - String wTag = hs->Tag + '_'; wTag += hs->CF->ID(); - // - // Make sure the other buffers are reset - hs->Pent->buf2.SetBuf(); - hs->Pent->buf3.SetBuf(); - hs->Pent->buf4.SetBuf(); - // - // Set entry status OK - hs->Pent->status = kPFE_ok; - // - // Reset count - hs->Pent->cnt = 0; - // - // Save entry - hs->Pent->mtime = hs->TimeStamp; - // - DEBUG("Entry for tag: "<User); - - // Check inputs - if (hs->User.length() <= 0 || !hs->CF || !hs->Cref) { - PRINT("Invalid inputs ("<User.length()<<","<CF<<","<Cref<<")"); - return -1; - } - // - // Build effective tag - String wTag = hs->Tag + '_'; wTag += hs->CF->ID(); - // - // Default status - status = kPFE_disabled; - int bad = -1; - cmsg = ""; - // - // Check first info in user's home, if allowed - if (UserPwd) { - // Get userinfo - struct passwd *pw; - XrdSysPwd thePwd(hs->User.c_str(), &pw); - int rcst = 0; - kXR_int32 mtime = -1; - bool fcrypt = 0; - String File; - if (pw) { - File.resize(strlen(pw->pw_dir)+FileUser.length()+10); - File.assign(pw->pw_dir, 0); - File += FileUser; - // Get status - struct stat st; - if ((rcst = stat(File.c_str(),&st)) != 0 && errno == ENOENT) { - if (UserPwd > 1) { - // Try special crypt like file - File.replace(FileUser,FileCrypt); - fcrypt = 1; - rcst = 0; - } - } - mtime = (rcst == 0) ? st.st_mtime : mtime; - } - - if (rcst == 0) { - // - // Check cache first - hs->Pent = cacheUser.Get(pfeRef, wTag.c_str()); - if (!hs->Pent || (hs->Pent->mtime < mtime)) { - if (!(hs->Pent)) hs->Pent = cacheUser.Add(pfeRef, wTag.c_str()); - if (hs->Pent) { - // - // Try the files - if (!fcrypt) { - // Try to attach to File - XrdSutPFile ff(File.c_str(), kPFEopen,0,0); - if (ff.IsValid()) { - // Retrieve pwd information - if (ff.ReadEntry(wTag.c_str(),*(hs->Pent)) > 0) { - bad = 0; - status = hs->Pent->status; - ff.Close(); - return 0; - } - ff.Close(); - } - } else if (UserPwd > 1) { - String pwhash; - if (QueryCrypt(FileCrypt, pwhash) > 0) { - bad = 0; - status = kPFE_crypt; - // Fill entry - hs->Pent->mtime = hs->TimeStamp; - hs->Pent->status = status; - hs->Pent->cnt = 0; - if (!FileCrypt.beginswith("afs:")) - hs->Pent->buf1.SetBuf(pwhash.c_str(),pwhash.length()+1); - // Trasmit the type of credentials we have found - cmsg = FileCrypt; - return 0; - } - } - } - } else { - // Fill entry - bad = 0; - status = hs->Pent->status; - hs->Pent->mtime = hs->TimeStamp; - if (status == kPFE_crypt) - cmsg = FileCrypt; - return 0; - } - } - } - - // - // Check system info, if enabled - if (SysPwd) { - String pwhash, fn; - if (QueryCrypt(fn, pwhash) > 0) { - bad = 0; - status = kPFE_crypt; - // Fill entry - hs->Pent = cacheUser.Add(pfeRef, wTag.c_str()); - hs->Pent->mtime = hs->TimeStamp; - hs->Pent->status = status; - hs->Pent->cnt = 0; - if (!fn.beginswith("afs:")) - hs->Pent->buf1.SetBuf(pwhash.c_str(),pwhash.length()+1); - // Trasmit the type of credentials we have found - cmsg = fn; - return 0; - } - } - // - // Check server admin files - if (PFAdmin.IsValid()) { - // - // Make sure it is uptodate - XrdSysPrivGuard priv(getuid(), getgid()); - if (priv.Valid()) { - if (cacheAdmin.Refresh() != 0) { - PRINT("problems assuring cache update for file admin "); - return -1; - } - } - hs->Pent = cacheAdmin.Get(pfeRef, wTag.c_str()); - // Retrieve pwd information - if (hs->Pent) { - bad = 0; - status = hs->Pent->status; - if (status == kPFE_allowed) { - if (AutoReg == kpAR_none) { - // No auto-registration: disable - status = kPFE_disabled; - bad = 1; - } - } else if (status >= kPFE_ok) { - // Check failure counter, if required - if (MaxFailures > 0 && hs->Pent->cnt >= MaxFailures) { - status = kPFE_disabled; - bad = 2; - } - // Check expiration time, if required - if (LifeCreds > 0) { - int expt = hs->Pent->mtime + LifeCreds; - int now = hs->TimeStamp; - if (expt < now) - status = kPFE_expired; - } - if (status != kPFE_disabled) - return 0; - } - } - pfeRef.UnLock(); // Unlock hs->Pent if we ever got a lock on it! - } - - // - // If nothing found, auto-registration is enabled, and the tag - // corresponds to a local user, propose auto-registration - if (bad == -1) { - if (AutoReg != kpAR_none) { - status = kPFE_allowed; - if (AutoReg == kpAR_users) { - struct passwd *pw; - XrdSysPwd thePwd(hs->User.c_str(), &pw); - if (!pw) { - status = kPFE_disabled; - bad = 1; - } - } - } else - bad = 1; - } - // - // If disabled, fill salt string with message for the client - if (status == kPFE_disabled) { - char msg[XrdSutMAXPPT]; - switch (bad) { - case 1: - snprintf(msg,XrdSutMAXPPT,"user '%s' unknown: auto-registration" - " not allowed: contact %s to register", - hs->User.c_str(),SrvEmail.c_str()); - break; - case 2: - snprintf(msg,XrdSutMAXPPT,"max number of failures (%d) reached" - " for user '%s': contact %s to re-activate", - MaxFailures,hs->User.c_str(),SrvEmail.c_str()); - break; - default: - msg[0] = '\0'; - } - cmsg.insert(msg,0,strlen(msg)); - } - // - // We are done - return 0; -} - -//_________________________________________________________________________ -int XrdSecProtocolpwd::GetUserHost(String &user, String &host) -{ - // Resolve user and host - EPNAME("GetUserHost"); - - // Host - host = Entity.host; - if (host.length() <= 0) host = getenv("XrdSecHOST"); - - // User - user = Entity.name; - if (user.length() <= 0) user = getenv("XrdSecUSER"); - - // If user not given, prompt for it - if (user.length() <= 0) { - // - // Make sure somebody can be prompted - if (!(hs->Tty)) { - NOTIFY("user not defined:" - "not tty: cannot prompt for user"); - return -1; - } - // - // This is what we want - String prompt = "Enter user or tag"; - if (host.length()) { - prompt.append(" for host "); - prompt.append(host); - } - prompt.append(":"); - XrdSutGetLine(user,prompt.c_str()); - } - - DEBUG(" user: "< 0) { - bls->SetStep(step); - buf->SetStep(step); - hs->LastStep = step; - } - - // - // If a random tag has been sent and we have a session cipher, - // we sign it - XrdSutBucket *brt = buf->GetBucket(kXRS_rtag); - if (brt && cip) { - // - // Encrypt random tag with session cipher - if (cip->Encrypt(*brt) == 0) { - PRINT("error encrypting random tag"); - return -1; - } - // - // Update type - brt->type = kXRS_signed_rtag; - } - // Clients send in any case something session dependent: the server - // may optionally decide that's enough and save one exchange. - if (opt == 'c') { - // - // Add bucket with our timestamp to the main list - if (buf->MarshalBucket(kXRS_timestamp,(kXR_int32)(hs->TimeStamp)) != 0) { - PRINT("error adding bucket with time stamp"); - return -1; - } - } - // - // Add an random challenge: if a next exchange is required this will - // allow to prove authenticity of counter part - if (opt == 's' || step != kXPC_autoreg) { - // - // Generate new random tag and create/update bucket - String RndmTag; - XrdSutRndm::GetRndmTag(RndmTag); - // - // Get bucket - if (!(brt = new XrdSutBucket(RndmTag,kXRS_rtag))) { - PRINT("error creating random tag bucket"); - return -1; - } - buf->AddBucket(brt); - // - // Get cache entry - if (!hs->Cref) { - PRINT("cache entry not found: protocol error"); - return -1; - } - // - // Add random tag to the cache and update timestamp - hs->Cref->buf1.SetBuf(brt->buffer,brt->size); - hs->Cref->mtime = (kXR_int32)hs->TimeStamp; - } - // - // Now serialize the buffer ... - char *bser = 0; - int nser = buf->Serialized(&bser); - // - // Update bucket with this content - XrdSutBucket *bck = 0;; - if (!(bck = bls->GetBucket(type))) { - // or create new bucket, if not existing - if (!(bck = new XrdSutBucket(bser,nser,type))) { - PRINT("error creating bucket " - <<" - type: "<AddBucket(bck); - } else { - bck->Update(bser,nser); - } - // - // Encrypted the bucket - if (cip) { - if (cip->Encrypt(*bck) == 0) { - PRINT("error encrypting bucket - cipher " - <<" - type: "<GetNBuckets()) { - // Create the main buffer as a copy of the buffer received - if (!((*bm) = new XrdSutBuffer(br->GetProtocol(),br->GetOptions()))) { - emsg = "error instantiating main buffer"; - return -1; - } - // - // Extract server version from options - String opts = br->GetOptions(); - int ii = opts.find("v:"); - if (ii >= 0) { - String sver(opts,ii+2); - sver.erase(sver.find(',')); - hs->RemVers = atoi(sver.c_str()); - } else { - hs->RemVers = Version; - emsg = "server version information not found in options:" - " assume same as local"; - } - // - // Create cache - if (!(hs->Cref = new XrdSutPFEntry("c"))) { - emsg = "error creating cache"; - return -1; - } - // - // Save server version in cache - hs->Cref->status = hs->RemVers; - // - // Extract server ID - String srvid; - ii = opts.find("id:"); - if (ii >= 0) { - srvid.assign(opts, ii+3); - srvid.erase(srvid.find(',')); - } - // - // Extract priority options - String popt; - ii = opts.find("po:"); - if (ii >= 0) { - popt.assign(opts, ii+3); - popt.erase(popt.find(',')); - // Parse it - if (popt.beginswith("sys")) { - hs->SysPwd = 1; - } else if (popt.beginswith("afs")) { - hs->SysPwd = 2; - hs->AFScell.assign(popt,3); - } - } - // - // Get user and host - String host; - if (GetUserHost(hs->User,host) != 0) { - emsg = "error getting user and host"; - return -1; - } - // - // Build tag and save it into the cache - hs->Tag.resize(hs->User.length()+host.length()+srvid.length()+5); - hs->Tag = hs->User; - if (host.length() > 0) - hs->Tag += ("@" + host); - if (srvid.length() > 0) - hs->Tag += (":" + srvid); - // - // Get server puk from cache and initialize handshake cipher - if (!PFSrvPuk.IsValid()) { - emsg = "file with server public keys invalid"; - return -1; - } - char *ptag = new char[host.length()+srvid.length()+10]; - if (ptag) { - sprintf(ptag,"%s:%s_%d",host.c_str(),srvid.c_str(),hs->CF->ID()); - bool wild = 0; - XrdSutPFEntry *ent = cacheSrvPuk.Get(pfeRef, (const char *)ptag, &wild); - if (ent) { - // Initialize cipher - SafeDelete(hs->Hcip); - if (!(hs->Hcip = - hs->CF->Cipher(0,ent->buf1.buf,ent->buf1.len))) { - PRINT("could not instantiate session cipher " - "using cipher public info from server"); - emsg = "could not instantiate session cipher "; - } else { - DEBUG("hsHcip: 0x"<Hcip->AsHexString()); - } - pfeRef.UnLock(); - } else { - // Autoreg is the only alternative at this point ... - emsg = "server puk not found in cache - tag: "; - emsg += ptag; - } - SafeDelArray(ptag); - } else - emsg = "could not allocate buffer for server puk tag"; - // - // And we are done; - return 0; - } - // - // make sure the cache is still there - if (!hs->Cref) { - emsg = "cache entry not found"; - return -1; - } - // - // make sure is not too old - int reftime = hs->TimeStamp - TimeSkew; - if (hs->Cref->mtime < reftime) { - emsg = "cache entry expired"; - // Remove: should not be checked a second time - SafeDelete(hs->Cref); - return -1; - } - // - // Get from cache version run by server - hs->RemVers = hs->Cref->status; - // - // Extract the main buffer - if (!(bckm = br->GetBucket(kXRS_main))) { - emsg = "main buffer missing"; - return -1; - } - // - // Decrypt, if it makes sense - if (hs->LastStep != kXPC_autoreg) { - // - // make sure the cache is still there - if (!hs->Hcip) { - emsg = "session cipher not found"; - return -1; - } - // - // Decrypt it - if (!(hs->Hcip->Decrypt(*bckm))) { - emsg = "error decrypting main buffer with session cipher"; - return -1; - } - } - // - // Deserialize main buffer - if (!((*bm) = new XrdSutBuffer(bckm->buffer,bckm->size))) { - emsg = "error deserializing main buffer"; - return -1; - } - // - // If (new) server public keys are there extract and save them - bool newpuk = 0; - XrdSutBuckList *bcklst = (*bm)->GetBuckList(); - XrdSutBucket *bp = bcklst->Begin(); - while (bp) { - if (bp->type == kXRS_puk) { - newpuk = 1; - // ID is in the first 4 chars ( ....'\0') - char cid[5] = {0}; - memcpy(cid, bp->buffer, 5); - int id = atoi(cid); - // Build tag - String ptag(hs->Tag); - ptag.erase(0,ptag.find('@')+1); - ptag += '_'; - ptag += cid; - // Update or create new entry - XrdSutPFEntry *ent = cacheSrvPuk.Add(pfeRef, ptag.c_str()); - if (ent) { - // Set buffer - ent->buf1.SetBuf((bp->buffer)+5,(bp->size)-5); - ent->mtime = hs->TimeStamp; - if (id == hs->CF->ID()) { - // Initialize cipher - SafeDelete(hs->Hcip); - if (!(hs->Hcip = - hs->CF->Cipher(0,ent->buf1.buf,ent->buf1.len))) { - PRINT("could not instantiate session cipher " - "using cipher public info from server"); - emsg = "could not instantiate session cipher "; - } else { - DEBUG("hsHcip: 0x"<Hcip->AsHexString()); - } - } - pfeRef.UnLock(); - } else { - // Autoreg is the only alternative at this point ... - PRINT("could not create entry in cache - tag: "<Next(); - } - (*bm)->Deactivate(kXRS_puk); - // Update the puk file (for the other sessions ...) - if (newpuk) - cacheSrvPuk.Flush(); - // - // We are done - return 0; -} - -//_________________________________________________________________________ -int XrdSecProtocolpwd::ParseServerInput(XrdSutBuffer *br, XrdSutBuffer **bm, - String &cmsg) -{ - // Parse received buffer b, extracting and decrypting the main - // buffer *bm and extracting the session - // cipher, random tag buckets and user name, if any. - // Results used to fill the local handshake variables - EPNAME("ParseServerInput"); - - // Space for pointer to main buffer must be already allocated - if (!br || !bm) { - PRINT("invalid inputs ("<GetBucket(kXRS_main))) { - cmsg = "main buffer missing"; - return -1; - } - // - // First get the session cipher - if ((bck = br->GetBucket(kXRS_puk))) { - // - // Cleanup - SafeDelete(hs->Hcip); - // - // Prepare cipher agreement: make sure we have the reference cipher - if (!hs->Rcip) { - cmsg = "reference cipher missing"; - return -1; - } - // Prepare cipher agreement: get a copy of the reference cipher - if (!(hs->Hcip = hs->CF->Cipher(*hs->Rcip))) { - cmsg = "cannot get reference cipher"; - return -1; - } - // - // Instantiate the session cipher - if (!(hs->Hcip->Finalize(bck->buffer,bck->size,0))) { - cmsg = "cannot finalize session cipher"; - return -1; - } - // - // We need it only once - br->Deactivate(kXRS_puk); - } - - // - // Decrypt the main buffer with the session cipher, if available - if (hs->Hcip) { - if (!(hs->Hcip->Decrypt(*bckm))) { - cmsg = "error decrypting main buffer with session cipher"; - return -1; - } - } - // - // Deserialize main buffer - if (!((*bm) = new XrdSutBuffer(bckm->buffer,bckm->size))) { - cmsg = "error deserializing main buffer"; - return -1; - } - // - // Get version run by client, if there - if (hs->RemVers == -1) { - if ((*bm)->UnmarshalBucket(kXRS_version,hs->RemVers) != 0) { - hs->RemVers = Version; - cmsg = "client version information not found in options:" - " assume same as local"; - } else { - (*bm)->Deactivate(kXRS_version); - } - } - - // - // Get cache entry or create a new one - if (!hs->Cref) { - // Create it - if (!(hs->Cref = new XrdSutPFEntry(hs->ID.c_str()))) { - cmsg = "cannot create cache entry"; - return -1; - } - } else { - // - // make sure cache is not too old - int reftime = hs->TimeStamp - TimeSkew; - if (hs->Cref->mtime < reftime) { - cmsg = "cache entry expired"; - SafeDelete(hs->Cref); - return -1; - } - } - - // - // Extract user name, if any - if ((bck = (*bm)->GetBucket(kXRS_user))) { - if (hs->User.length() <= 0) { - bck->ToString(hs->User); - // Build tag - hs->Tag = hs->User; - } - (*bm)->Deactivate(kXRS_user); - } - // - // We are done - return 0; -} - -//__________________________________________________________________ -void XrdSecProtocolpwd::ErrF(XrdOucErrInfo *einfo, kXR_int32 ecode, - const char *msg1, const char *msg2, - const char *msg3) -{ - // Filling the error structure - EPNAME("ErrF"); - - char *msgv[12]; - int k, i = 0, sz = strlen("Secpwd"); - - // - // Code message, if any - int cm = (ecode >= kPWErrParseBuffer && - ecode <= kPWErrError) ? (ecode-kPWErrParseBuffer) : -1; - const char *cmsg = (cm > -1) ? gPWErrStr[cm] : 0; - - // - // Build error message array - msgv[i++] = (char *)"Secpwd"; //0 - if (cmsg) {msgv[i++] = (char *)": "; //1 - msgv[i++] = (char *)cmsg; //2 - sz += strlen(msgv[i-1]) + 2; - } - if (msg1) {msgv[i++] = (char *)": "; //3 - msgv[i++] = (char *)msg1; //4 - sz += strlen(msgv[i-1]) + 2; - } - if (msg2) {msgv[i++] = (char *)": "; //5 - msgv[i++] = (char *)msg2; //6 - sz += strlen(msgv[i-1]) + 2; - } - if (msg3) {msgv[i++] = (char *)": "; //7 - msgv[i++] = (char *)msg3; //8 - sz += strlen(msgv[i-1]) + 2; - } - - // save it (or print it) - if (einfo) { - einfo->setErrInfo(ecode, (const char **)msgv, i); - } - if (QTRACE(Debug)) { - char *bout = new char[sz+10]; - if (bout) { - bout[0] = 0; - for (k = 0; k < i; k++) - sprintf(bout,"%s%s",bout,msgv[k]); - PRINT(bout); - } else { - for (k = 0; k < i; k++) - PRINT(msgv[k]); - } - } -} - -//__________________________________________________________________ -XrdSecCredentials *XrdSecProtocolpwd::ErrC(XrdOucErrInfo *einfo, - XrdSutBuffer *b1, - XrdSutBuffer *b2, - XrdSutBuffer *b3, - kXR_int32 ecode, - const char *msg1, - const char *msg2, - const char *msg3) -{ - // Error logging client method - - // Fill the error structure - ErrF(einfo, ecode, msg1, msg2, msg3); - - // Release buffers - REL3(b1,b2,b3); - - // We are done - return (XrdSecCredentials *)0; -} - -//__________________________________________________________________ -int XrdSecProtocolpwd::ErrS(String ID, XrdOucErrInfo *einfo, - XrdSutBuffer *b1, XrdSutBuffer *b2, - XrdSutBuffer *b3, kXR_int32 ecode, - const char *msg1, const char *msg2, - const char *msg3) -{ - // Error logging server method - - // Fill the error structure - ErrF(einfo, ecode, msg1, msg2, msg3); - - // Release buffers - REL3(b1,b2,b3); - - // We are done - return kpST_error; -} - -//_______________________________________________________________________ -int XrdSecProtocolpwd::DoubleHash(XrdCryptoFactory *cf, XrdSutBucket *bck, - XrdSutBucket *s1, XrdSutBucket *s2, - const char *tag) -{ - // Apply single or double hash to bck using salts - // in s1 and (if defined) s2. - // Store result in *buf, with the new length in len. - // Return 0 if ok or -1 otherwise - EPNAME("DoubleHash"); - - // - // Check inputs - if (!cf || !bck) { - PRINT("Bad inputs "<size <= 0) && (!s2 || s2->size <= 0)) { - PRINT("Both salts undefined - do nothing"); - return 0; - } - // - // Tag length, if there - int ltag = (tag) ? strlen(tag) + 1 : 0; - // - // Get one-way hash function - XrdCryptoKDFun_t KDFun = cf->KDFun(); - XrdCryptoKDFunLen_t KDFunLen = cf->KDFunLen(); - if (!KDFun || !KDFunLen) { - PRINT("Could not get hooks to one-way hash functions (" - <buffer; - int nhlen = bck->size; - if (s1 && s1->size > 0) { - if (!(nhash = new char[(*KDFunLen)() + ltag])) { - PRINT("Could not allocate memory for hash - s1"); - return -1; - } - if ((nhlen = (*KDFun)(thash,nhlen, - s1->buffer,s1->size,nhash+ltag,0)) <= 0) { - PRINT("Problems hashing - s1"); - delete[] nhash; - return -1; - } - thash = nhash; - } - // - // Apply second salt, if defined - if (s2 && s2->size > 0) { - if (!(nhash = new char[(*KDFunLen)() + ltag])) { - PRINT("Could not allocate memory for hash - s2"); - return -1; - } - if (thash && thash != bck->buffer) thash += ltag; - if ((nhlen = (*KDFun)(thash,nhlen, - s2->buffer,s2->size,nhash+ltag,0)) <= 0) { - PRINT("Problems hashing - s2"); - delete[] nhash; - if (thash && thash != bck->buffer) delete[] thash; - return -1; - } - if (thash && thash != bck->buffer) delete[] thash; - thash = nhash; - } - // - // Add tag if there - if (tag) - memcpy(thash,tag,ltag); - // - // Save result - bck->SetBuf(thash,nhlen+ltag); - // - // We are done - return 0; -} - -//______________________________________________________________________________ -int XrdSecProtocolpwd::QueryCrypt(String &fn, String &pwhash) -{ - // Retrieve crypt-like password-hash from $HOME/fn or from system password files, - // if accessible. - // To avoid problems with NFS-root-squashing, if 'root' changes temporarly the - // uid/gid to those of the target user (usr). - // If OK, returns pass length and fill 'pass' with the password, null-terminated. - // ('pass' is allocated externally to contain max lpwmax bytes). - // If the file does not exists, return 0 and an empty pass. - // If any problems with the file occurs, return a negative - // code, -2 indicating wrong file permissions. - // If any problem with changing ugid's occurs, prints a warning trying anyhow - // to read the password hash. - EPNAME("QueryCrypt"); - - int rc = -1; - int len = 0, n = 0, fid = -1; - pwhash = ""; - DEBUG("analyzing file: "<User.c_str(), &pw); - if (!pw) { - PRINT("Cannot get pwnam structure for user "<User); - return -1; - } - // - // Check the user specific file first, if requested - if (fn.length() > 0) { - - // target uid - int uid = pw->pw_uid; - - // Acquire the privileges, if needed - XrdSysPrivGuard priv(uid, pw->pw_gid); - bool go = priv.Valid(); - if (!go) { - PRINT("problems acquiring temporarly identity: "<User); - } - - // The file - String fpw(pw->pw_dir, strlen(pw->pw_dir) + fn.length() + 5); - if (go) { - fpw += ("/" + fn); - DEBUG("checking file "<User); - } - - // Check first the permissions: should be 0600 - struct stat st; - if (go && stat(fpw.c_str(), &st) == -1) { - if (errno != ENOENT) { - PRINT("cannot stat password file "< -1) - close(fid); - - // Get rid of special trailing chars - if (go) { - len = n; - while (len-- && (pass[len] == '\n' || pass[len] == 32)) - pass[len] = 0; - // Null-terminate - pass[++len] = 0; - rc = len; - // Prepare for output - pwhash = pass; - } - } - // - // If we go a pw-hash we are done - if (pwhash.length() > 0) - return rc; - // - // If not, we check the system files -#ifdef HAVE_SHADOWPW - { // Acquire the privileges; needs to be 'superuser' to access the - // shadow password file - XrdSysPrivGuard priv((uid_t)0, (gid_t)0); - if (priv.Valid()) { - struct spwd *spw = 0; - // System V Rel 4 style shadow passwords - if ((spw = getspnam(hs->User.c_str())) == 0) { - NOTIFY("shadow passwd not accessible to this application"); - } else - pwhash = spw->sp_pwdp; - } else { - NOTIFY("problems acquiring temporarly superuser privileges"); - } - } -#else - pwhash = pw->pw_passwd; -#endif - // - // This is send back to the client to locate autologin info - fn = "system"; - // Check if successful - if ((rc = pwhash.length()) <= 2) { - NOTIFY("passwd hash not available for user "<User); - pwhash = ""; - fn = ""; - rc = -1; - } - - // We are done - return rc; -} - -//______________________________________________________________________________ -int XrdSecProtocolpwd::QueryNetRc(String host, String &passwd, int &status) -{ - // Check netrc-like file defined by env 'XrdSecNETRC' for password information - // matching ('user','host') and return the password in 'passwd'. - // If found, 'status' is filled with 'kpCI_exact' or 'kpCI_wildcard' - // depending the type of match. - // Same syntax as $HOME/.netrc is required; wild cards for hosts are - // supported: examples - // - // machine oplapro027.cern.ch login qwerty password Rt8dsAvV0 - // machine lxplus*.cern.ch login poiuyt password WtHAyD0iG - // - // Returns 0 is something found, -1 otherwise. - // NB: file permissions must be: readable/writable by the owner only - EPNAME("QueryNetRc"); - passwd = ""; - // - // Make sure a file name is defined - String fnrc = getenv("XrdSecNETRC"); - if (fnrc.length() <= 0) { - PRINT("File name undefined"); - return -1; - } - // Resolve place-holders, if any - if (XrdSutResolve(fnrc, Entity.host, Entity.vorg, Entity.grps, Entity.name) != 0) { - PRINT("Problems resolving templates in "<User); - - // Check first the permissions: should be 0600 - struct stat st; - if (stat(fnrc.c_str(), &st) == -1) { - if (errno != ENOENT) { - PRINT("cannot stat password file "< 0) { - // Host matches - if (!strcmp(hs->User.c_str(),word[3])) { - // User matches: if exact match we are done - if (nm == host.length()) { - passwd = word[5]; - status = kpCI_exact; - break; - } - // Else, we focalise on the best match - if (nm > nmmx) { - nmmx = nm; - passwd = word[5]; - status = kpCI_wildcard; - } - } - } - } - // - // Close the file - fclose(fid); - // - // We are done - if (passwd.length() > 0) - return 0; - return -1; -} - -//______________________________________________________________________________ -bool XrdSecProtocolpwd::CheckTimeStamp(XrdSutBuffer *bm, int skew, String &emsg) -{ - // Check consistency of the time stamp in bucket kXRS_timestamp in bm; - // skew is the allowed difference in times. - // Return 1 if ok, 0 if not - EPNAME("CheckTimeStamp"); - - // Check inputs - if (!bm || skew <= 0) { - if (!bm) - emsg = "input buffer undefined "; - else - emsg = "negative skew: invalid "; - return 0; - } - - // We check only if requested and a stronger check has not been done - // successfully already - if (hs->RtagOK || VeriClnt != 1) { - NOTIFY("Nothing to do"); - // Deactivate the buffer, if there - if (bm->GetBucket(kXRS_timestamp)) - bm->Deactivate(kXRS_timestamp); - return 1; - } - - // - // Add bucket with our version to the main list - kXR_int32 tstamp = 0; - if (bm->UnmarshalBucket(kXRS_timestamp,tstamp) != 0) { - emsg = "bucket with time stamp not found"; - return 0; - } - - kXR_int32 dtim = hs->TimeStamp - tstamp; - dtim = (dtim < 0) ? -dtim : dtim; - if (dtim > skew) { - emsg = "time difference too big: "; emsg += (int)dtim; - emsg += " - allowed skew: "; emsg += skew; - bm->Deactivate(kXRS_timestamp); - return 0; - } - bm->Deactivate(kXRS_timestamp); - - DEBUG("Time stamp successfully checked"); - - // Ok - return 1; -} - -//______________________________________________________________________________ -bool XrdSecProtocolpwd::CheckRtag(XrdSutBuffer *bm, String &emsg) -{ - // Check random tag signature if it was sent with previous packet - EPNAME("CheckRtag"); - - // Make sure we got a buffer - if (!bm) { - emsg = "Buffer not defined"; - return 0; - } - // - // If we sent out a random tag check it signature - if (hs->Cref && hs->Cref->buf1.len > 0) { - XrdSutBucket *brt = 0; - if ((brt = bm->GetBucket(kXRS_signed_rtag))) { - // Make suer we got a cipher - if (!(hs->Hcip)) { - emsg = "Session cipher undefined"; - return 0; - } - // Decrypt it with the session cipher - if (!(hs->Hcip->Decrypt(*brt))) { - emsg = "error decrypting random tag with session cipher"; - return 0; - } - } else { - emsg = "random tag missing - protocol error"; - return 0; - } - // - // Random tag cross-check: content - if (memcmp(brt->buffer,hs->Cref->buf1.buf,hs->Cref->buf1.len)) { - emsg = "random tag content mismatch"; - SafeDelete(hs->Cref); - // Remove: should not be checked a second time - return 0; - } - // - // Reset the cache entry but we will not use the info a second time - memset(hs->Cref->buf1.buf,0,hs->Cref->buf1.len); - hs->Cref->buf1.SetBuf(); - // - // Flag successful check - hs->RtagOK = 1; - bm->Deactivate(kXRS_signed_rtag); - DEBUG("Random tag successfully checked"); - } else { - NOTIFY("Nothing to check"); - } - - // We are done - return 1; -} diff --git a/src/XrdSecpwd/XrdSecProtocolpwd.hh b/src/XrdSecpwd/XrdSecProtocolpwd.hh deleted file mode 100644 index a085b49873a..00000000000 --- a/src/XrdSecpwd/XrdSecProtocolpwd.hh +++ /dev/null @@ -1,422 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S e c P r o t o c o l p w d . h h */ -/* */ -/* (c) 2005 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Gerri Ganis for CERN */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdNet/XrdNetAddrInfo.hh" - -#include "XrdOuc/XrdOucErrInfo.hh" -#include "XrdSys/XrdSysPthread.hh" -#include "XrdOuc/XrdOucString.hh" -#include "XrdOuc/XrdOucTokenizer.hh" - -#include "XrdSec/XrdSecInterface.hh" -#include "XrdSecpwd/XrdSecpwdTrace.hh" - -#include "XrdSut/XrdSutPFEntry.hh" -#include "XrdSut/XrdSutPFile.hh" -#include "XrdSut/XrdSutBuffer.hh" -#include "XrdSut/XrdSutRndm.hh" - -#include "XrdCrypto/XrdCryptoAux.hh" -#include "XrdCrypto/XrdCryptoCipher.hh" -#include "XrdCrypto/XrdCryptoFactory.hh" - -/******************************************************************************/ -/* D e f i n e s */ -/******************************************************************************/ - -typedef XrdOucString String; - -#define XrdSecPROTOIDENT "pwd" -#define XrdSecPROTOIDLEN sizeof(XrdSecPROTOIDENT) -#define XrdSecpwdVERSION 10100 -#define XrdSecNOIPCHK 0x0001 -#define XrdSecDEBUG 0x1000 -#define XrdCryptoMax 10 - -#define kMAXBUFLEN 1024 -#define kMAXUSRLEN 9 -#define kMAXPWDLEN 64 - -// -// Message codes either returned by server or included in buffers -enum kpwdStatus { - kpST_error = -1, // error occured - kpST_ok = 0, // ok - kpST_more = 1 // need more info -}; - -// -// Auto-reg modes -enum kpwdAutoreg { - kpAR_none = 0, // autoreg disabled - kpAR_users = 1, // only for tags in password files (local, system's) - kpAR_all = 2 // for all tags -}; - -// -// Client update autologin modes -enum kpwdUpdate { - kpUP_none = 0, // no update - kpUP_remove = 1, // remove obsolete entries only - kpUP_all = 2 // remove obsolete entries and register new valid info -}; - -// -// Creds input type -enum kpwdCredsInput { - kpCI_undef = -1, // undefined - kpCI_prompt = 0, // from prompt - kpCI_exact = 1, // from FileNetRc, exact tag - kpCI_wildcard = 2 // from FileNetRc, wildcard tag -}; - -// -// Creds type (for prompt) -enum kpwdCredType { - kpCT_undef = -1, // undefined - kpCT_normal = 0, // confirmed credentials - kpCT_onetime = 1, // one-time credentials - kpCT_old = 2, // old credentials to be changed - kpCT_new = 3, // new credentials to be confirmed - kpCT_newagain = 4, // new credentials again for confirmation - kpCT_autoreg = 5, // autoreg: new creds to be confirmed - kpCT_ar_again = 6, // autoreg: new creds again for confirmation - kpCT_crypt = 7, // standard crypt hash - kpCT_afs = 8, // AFS plain password - kpCT_afsenc = 9 // AFS encrypted password -}; - -// -// Creds actions -enum kpwdCredsActions { - kpCA_undef = -1, // undefined - kpCA_check = 0, // normal check of credentials - kpCA_checkold = 1, // check current creds before asking for new ones - kpCA_cache = 2, // cache received (new) credentials - kpCA_checkcache = 3 // check cached credentials and save them, if ok -}; - -// Client steps -enum kpwdClientSteps { - kXPC_none = 0, - kXPC_normal = 1000, // 1000: standard packet - kXPC_verifysrv, // 1001: request for server verification - kXPC_signedrtag, // 1002: signed rtag (after server request for verification) - kXPC_creds, // 1003: credentials packet - kXPC_autoreg, // 1004: query for autoregistration - kXPC_failureack, // 1005: failure acknowledgement - kXPC_reserved // -}; - -// Server steps -enum kpwdServerSteps { - kXPS_none = 0, - kXPS_init = 2000, // 2000: fake code used the first time - kXPS_credsreq, // 2001: request for credentials - kXPS_rtag, // 2002: rndm tag to be signed (strong verification) - kXPS_signedrtag, // 2003: signed rtag (after client request for verification) - kXPS_newpuk, // 2004: new public part for session ciphers - kXPS_puk, // 2005: public part for session ciphers (after autoreg) - kXPS_failure, // 2006: signal failure to client to drop invalid cached info - kXPS_reserved // -}; - -// Error codes -enum kpwdErrors { - kPWErrParseBuffer = 10000, // 10000 - kPWErrDecodeBuffer, // 10001 - kPWErrLoadCrypto, // 10002 - kPWErrBadProtocol, // 10003 - kPWErrNoUserHost, // 10004 - kPWErrNoUser, // 10005 - kPWErrNoHost, // 10006 - kPWErrBadUser, // 10007 - kPWErrCreateBucket, // 10008 - kPWErrDuplicateBucket, // 10009 - kPWErrCreateBuffer, // 10010 - kPWErrSerialBuffer, // 10011 - kPWErrGenCipher, // 10012 - kPWErrExportPuK, // 10013 - kPWErrEncRndmTag, // 10014 - kPWErrBadRndmTag, // 10015 - kPWErrNoRndmTag, // 10016 - kPWErrNoCipher, // 10017 - kPWErrQueryCreds, // 10018 - kPWErrNoCreds, // 10019 - kPWErrBadPasswd, // 10020 - kPWErrBadCache, // 10021 - kPWErrNoCache, // 10022 - kPWErrNoSessID, // 10023 - kPWErrBadSessID, // 10024 - kPWErrBadOpt, // 10025 - kPWErrMarshal, // 10026 - kPWErrUnmarshal, // 10027 - kPWErrSaveCreds, // 10028 - kPWErrNoSalt, // 10029 - kPWErrNoBuffer, // 10030 - kPWErrRefCipher, // 10031 - kPWErrNoPublic, // 10032 - kPWErrAddBucket, // 10033 - kPWErrFinCipher, // 10034 - kPWErrInit, // 10034 - kPWErrBadCreds, // 10035 - kPWErrError // 10036 -}; - -// Structuring the status word -typedef struct { - char ctype; - char action; - short options; -} pwdStatus_t; - -#define REL1(x) { if (x) delete x; } -#define REL2(x,y) { if (x) delete x; if (y) delete y; } -#define REL3(x,y,z) { if (x) delete x; if (y) delete y; if (z) delete z; } -#if 0 -#ifndef NODEBUG -#define PRINT(y) {{SecTrace->Beg(epname); cerr <End();}} -#else -#define PRINT(y) { } -#endif -#endif -#define SafeDelete(x) { if (x) delete x ; x = 0; } -#define SafeDelArray(x) { if (x) delete [] x ; x = 0; } - -// -// This a small class to set the relevant options in one go -// -class pwdOptions { -public: - short debug; // [cs] debug flag - short mode; // [cs] 'c' or 's' - short areg; // [cs] auto-registration opt (s); update-autolog-info opt (c) - short upwd; // [s] check / do-not-check pwd file in user's $HOME - short alog; // [c] check / do-not-check user's autologin info - short verisrv; // [c] verify / do-not-verify server ownership of srvpuk - short vericlnt; // [s] level of verification client ownership of clntpuk - short syspwd; // [s] check / do-not-check system pwd (requires privileges) - int lifecreds; // [s] lifetime in seconds of credentials - int maxprompts; // [c] max number of empty prompts - int maxfailures; // [s] max passwd failures before blocking - char *clist; // [s] list of crypto modules ["ssl"] - char *dir; // [s] directory with admin pwd files [$HOME/.xrd] - char *udir; // [s] users's sub-directory with pwd files [$HOME/.xrd] - char *cpass; // [s] users's crypt hash pwd file [$HOME/.xrootdpass] - char *alogfile; // [c] autologin file [$HOME/.xrd/pwdnetrc] - char *srvpuk; // [c] file with server puks [$HOME/.xrd/pwdsrvpuk] - short keepcreds; // [s] keep / do-not-keep client credentials - char *expcreds; // [s] (template for) file with exported creds - int expfmt; // [s] formta for exported credentials - - pwdOptions() { debug = -1; mode = 's'; areg = -1; upwd = -1; alog = -1; - verisrv = -1; vericlnt = -1; - syspwd = -1; lifecreds = -1; maxprompts = -1; maxfailures = -1; - clist = 0; dir = 0; udir = 0; cpass = 0; - alogfile = 0; srvpuk = 0; keepcreds = 0; expcreds = 0; expfmt = 0;} - virtual ~pwdOptions() { } // Cleanup inside XrdSecProtocolpwdInit - void Print(XrdOucTrace *t); // Print summary of pwd option status -}; - -class pwdHSVars { -public: - int Iter; // iteration number - int TimeStamp; // Time of last call - String CryptoMod; // crypto module in use - String User; // remote username - String Tag; // tag for credentials - int RemVers; // Version run by remote counterpart - XrdCryptoFactory *CF; // crypto factory - XrdCryptoCipher *Hcip; // handshake cipher - XrdCryptoCipher *Rcip; // reference cipher - String ID; // Handshake ID (dummy for clients) - XrdSutPFEntry *Cref; // Cache reference - XrdSutPFEntry *Pent; // Pointer to relevant file entry - bool RtagOK; // Rndm tag checked / not checked - pwdStatus_t Status; // Some state flags - bool Tty; // Terminal attached / not attached - int Step; // Current step - int LastStep; // Step required at previous iteration - String ErrMsg; // Last error message - int SysPwd; // 0 = no, 1 = Unix sys pwd, 2 = AFS pwd - String AFScell; // AFS cell if it makes sense - XrdSutBuffer *Parms; // Buffer with server parms on first iteration - - pwdHSVars() { Iter = 0; TimeStamp = -1; CryptoMod = ""; User = ""; Tag = ""; - RemVers = -1; CF = 0; Hcip = 0; Rcip = 0; - ID = ""; Cref = 0; Pent = 0; RtagOK = 0; Tty = 0; - Step = 0; LastStep = 0; ErrMsg = ""; - SysPwd = 0; AFScell = ""; - Status.ctype = 0; Status.action = 0; Status.options = 0; Parms = 0;} - - ~pwdHSVars() { SafeDelete(Cref); SafeDelete(Hcip); SafeDelete(Parms); } -}; - - -/******************************************************************************/ -/* X r d S e c P r o t o c o l p w d C l a s s */ -/******************************************************************************/ - -class XrdSecProtocolpwd : public XrdSecProtocol -{ -public: - int Authenticate (XrdSecCredentials *cred, - XrdSecParameters **parms, - XrdOucErrInfo *einfo=0); - - XrdSecCredentials *getCredentials(XrdSecParameters *parm=0, - XrdOucErrInfo *einfo=0); - - XrdSecProtocolpwd(int opts, const char *hname, - XrdNetAddrInfo &endPoint, - const char *parms = 0); - virtual ~XrdSecProtocolpwd() {} // Delete() does it all - - // Initialization methods - static char *Init(pwdOptions o, XrdOucErrInfo *erp); - - void Delete(); - - static void PrintTimeStat(); - - // Enable tracing - static XrdOucTrace *EnableTracing(); - -private: - - // Static members initialized at startup - static XrdSysMutex pwdContext; - static String FileAdmin; - static String FileExpCreds; // (Template for) file with exported creds [S] - static String FileUser; - static String FileCrypt; - static String FileSrvPuk; - static String SrvID; - static String SrvEmail; - static String DefCrypto; - static String DefError; - static XrdSutPFile PFAdmin; // Admin file [S] - static XrdSutPFile PFAlog; // Autologin file [CS] - static XrdSutPFile PFSrvPuk; // File with server public keys [CS] - // - // Crypto related info - static int ncrypt; // Number of factories - static int cryptID[XrdCryptoMax]; // their IDs - static String cryptName[XrdCryptoMax]; // their names - static XrdCryptoCipher *loccip[XrdCryptoMax]; // local ciphers - static XrdCryptoCipher *refcip[XrdCryptoMax]; // ref for session ciphers - // - // Caches for info files - static XrdSutPFCache cacheAdmin; // Admin file - static XrdSutPFCache cacheSrvPuk; // SrvPuk file - static XrdSutPFCache cacheUser; // User files - static XrdSutPFCache cacheAlog; // Autologin file - // - // Running options / settings - static int Debug; // [CS] Debug level - static bool Server; // [CS] If server mode - static int UserPwd; // [S] Check passwd file in user's - static bool SysPwd; // [S] Check system passwd file if allowed - static int VeriClnt; // [S] Client verification level - static int VeriSrv; // [C] Server verification level - static int AutoReg; // [S] Autoreg mode - static int LifeCreds; // [S] if > 0, credential lifetime in secs - static int MaxPrompts; // [C] Repeating prompt - static int MaxFailures; // [S] Max passwd failures before blocking - static int AutoLogin; // [C] do-not-check/check/update autolog info - static int TimeSkew; // [CS] Allowed skew in secs for time stamps - static bool KeepCreds; // [S] Keep / Do-Not-Keep client creds - static int FmtExpCreds; // [S] Format for the exported credentials - // - // for error logging and tracing - static XrdSysLogger Logger; - static XrdSysError eDest; - static XrdOucTrace *PWDTrace; - - // Information local to this instance - XrdNetAddrInfo epAddr; - int options; - char CName[256]; // Client-name - bool srvMode; // TRUE if server mode - - // Handshake local info - pwdHSVars *hs; - - // Acquired credentials (server side) - XrdSecCredentials *clientCreds; - - // Parsing received buffers - int ParseClientInput(XrdSutBuffer *br, XrdSutBuffer **bm, - String &emsg); - int ParseServerInput(XrdSutBuffer *br, XrdSutBuffer **bm, - String &cmsg); - int ParseCrypto(XrdSutBuffer *buf); - - // Error functions - static void ErrF(XrdOucErrInfo *einfo, kXR_int32 ecode, - const char *msg1, const char *msg2 = 0, - const char *msg3 = 0); - XrdSecCredentials *ErrC(XrdOucErrInfo *einfo, XrdSutBuffer *b1, - XrdSutBuffer *b2,XrdSutBuffer *b3, - kXR_int32 ecode, const char *msg1 = 0, - const char *msg2 = 0, const char *msg3 = 0); - int ErrS(String ID, XrdOucErrInfo *einfo, XrdSutBuffer *b1, - XrdSutBuffer *b2, XrdSutBuffer *b3, - kXR_int32 ecode, const char *msg1 = 0, - const char *msg2 = 0, const char *msg3 = 0); - - // Query methods - XrdSutBucket *QueryCreds(XrdSutBuffer *bm, bool netrc, int &status); - int QueryUser(int &status, String &cmsg); - int QueryCrypt(String &fn, String &pwhash); - int QueryNetRc(String host, String &passwd, int &status); - - // Check credentials - bool CheckCreds(XrdSutBucket *creds, int credtype); - bool CheckCredsAFS(XrdSutBucket *creds, int ctype); - - // Check Time stamp - bool CheckTimeStamp(XrdSutBuffer *b, int skew, String &emsg); - - // Check random challenge - bool CheckRtag(XrdSutBuffer *bm, String &emsg); - - // Saving / Updating - int ExportCreds(XrdSutBucket *creds); - int SaveCreds(XrdSutBucket *creds); - int UpdateAlog(); - - // Auxilliary methods - int GetUserHost(String &usr, String &host); - int AddSerialized(char opt, kXR_int32 step, String ID, - XrdSutBuffer *bls, XrdSutBuffer *buf, - kXR_int32 type, XrdCryptoCipher *cip); - int DoubleHash(XrdCryptoFactory *cf, XrdSutBucket *bck, - XrdSutBucket *s1, XrdSutBucket *s2 = 0, - const char *tag = 0); -}; diff --git a/src/XrdSecpwd/XrdSecpwdPlatform.hh b/src/XrdSecpwd/XrdSecpwdPlatform.hh deleted file mode 100644 index e17f0abb7aa..00000000000 --- a/src/XrdSecpwd/XrdSecpwdPlatform.hh +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef __SECPWD_PLATFORM_ -#define __SECPWD_PLATFORM_ -/******************************************************************************/ -/* */ -/* X r d S e c p w d P l a t f o r m. h h */ -/* */ -/* (c) 2005 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Gerri Ganis for CERN */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -// -// crypt -// -#if defined(__linux__) || defined(__solaris__) -#include -#endif -#if defined(__osf__) || defined(__sgi) || defined(__APPLE__) -extern "C" char *crypt(const char *, const char *); -#endif - -// -// shadow passwords -// -#include - -#ifdef HAVE_SHADOWPW -#include -#endif - -#endif diff --git a/src/XrdSecpwd/XrdSecpwdSrvAdmin.cc b/src/XrdSecpwd/XrdSecpwdSrvAdmin.cc deleted file mode 100644 index 7620a98544d..00000000000 --- a/src/XrdSecpwd/XrdSecpwdSrvAdmin.cc +++ /dev/null @@ -1,2465 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S e c p w d S r v A d m i n . c c */ -/* */ -/* (c) 2005 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Gerri Ganis for CERN */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - - -// ---------------------------------------------------------------------- // -// // -// Password file administration // -// // -// Use this application to: // -// // -// - Create / Modify a password file for servers under your // -// administration. // -// Default location and name $(HOME)/.xrd/pwdadmin] // -// // -// XrdSecpwdSrvAdmin [] // -// // -// NB: permissions must be such that the file is // -// readable and writable by owner only, e.g. 0600 // -// // -// // -// - Create / Modify a password file for servers enabled to verify // -// user passwords [default location and name $(HOME)/.xrd/pwduser] // -// // -// XrdSecpwdSrvAdmin -m user [] // -// // -// NB: copy the file on the server machine if you are producing // -// it elsewhere; permissions must be such that the file is // -// writable by owner only, e.g. 0644 // -// // -// // -// - Create / Modify a autologin file // -// [default location and name $(HOME)/.xrd/pwdnetrc] // -// // -// XrdSecpwdSrvAdmin -m netrc [] // -// // -// NB: permissions must be such that the file is // -// readable and writable by owner only, e.g. 0600 // -// // -// - Create / Modify the file with server public cipher initiators // -// [default location and name $(HOME)/.xrd/pwdsrvpuk] // -// // -// XrdSecpwdSrvAdmin -m srvpuk [] // -// // -// NB: permissions must be such that the file is // -// writable by owner only, e.g. 0644 // -// // -// // -// Author: G.Ganis, 2005 // -// ---------------------------------------------------------------------- // -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "XrdOuc/XrdOucString.hh" - -#include "XrdSut/XrdSutAux.hh" -#include "XrdSut/XrdSutPFEntry.hh" -#include "XrdSut/XrdSutPFile.hh" -#include "XrdSut/XrdSutRndm.hh" - -#include "XrdCrypto/XrdCryptoCipher.hh" -#include "XrdCrypto/XrdCryptoFactory.hh" - -// -// enum -enum kModes { - kM_undef = 0, - kM_admin = 1, - kM_user, - kM_netrc, - kM_srvpuk, - kM_help -}; -const char *gModesStr[] = { - "kM_undef", - "kM_admin", - "kM_user", - "kM_netrc", - "kM_srvpuk", - "kM_help" -}; -enum kActions { - kA_undef = 0, - kA_add = 1, - kA_update, - kA_read, - kA_remove, - kA_disable, - kA_copy, - kA_trim, - kA_browse -}; -const char *gActionsStr[] = { - "kA_undef", - "kA_add", - "kA_update", - "kA_read", - "kA_remove", - "kA_disable", - "kA_copy", - "kA_trim", - "kA_browse" -}; - -// -// Globals -int DebugON = 1; -XrdOucString DirRef = "~/.xrd/"; -XrdOucString AdminRef = "pwdadmin"; -XrdOucString UserRef = "pwduser"; -XrdOucString NetRcRef = "pwdnetrc"; -XrdOucString SrvPukRef= "pwdsrvpuk"; -XrdOucString GenPwdRef= "/genpwd/"; -XrdOucString GenPukRef= "/genpuk/"; -XrdOucString IDTag = "+++SrvID"; -XrdOucString EmailTag = "+++SrvEmail"; -XrdOucString HostTag = "+++SrvHost"; -XrdOucString PukTag = "+++SrvPuk"; -XrdOucString PwdFile = ""; -XrdOucString PukFile = "/home/ganis/.xrd/genpuk/puk.07May2005-0849"; -int Mode = kM_undef; -int Action = kA_undef; -int NoBackup = 1; -XrdOucString NameTag = ""; -XrdOucString CopyTag = ""; -XrdOucString File = ""; -XrdOucString Path = ""; -XrdOucString Dir = ""; -XrdOucString SrvID = ""; -XrdOucString SrvName = ""; -XrdOucString Email = ""; -XrdOucString IterNum = ""; -bool Backup = 1; -bool DontAsk = 0; -bool Force = 0; -bool Passwd = 1; -bool Change = 1; -bool Random = 0; -bool SavePw = 1; -bool SetID = 0; -bool SetEmail = 0; -bool SetHost = 0; -bool Create = 0; -bool Confirm = 1; -bool Import = 0; -bool Hash = 1; -bool ChangePuk = 0; -bool ChangePwd = 0; -bool ExportPuk = 0; - -#define NCRYPTMAX 10 // max number of crypto factories - -XrdOucString DefCrypto = "ssl"; -XrdOucString CryptList = ""; -int ncrypt = 0; // number of available crypto factories -XrdOucString CryptMod[NCRYPTMAX] = {""}; // .. and their names -XrdCryptoCipher **RefCip = 0; // .. and their ciphers -XrdCryptoFactory **CF = 0; -XrdCryptoKDFun_t KDFun = 0; -XrdCryptoKDFunLen_t KDFunLen = 0; - -void Menu(int opt = 0); -int ParseArguments(int argc, char **argv); -void ParseCrypto(); -bool CheckOption(XrdOucString opt, const char *ref, int &ival); -bool AddPassword(XrdSutPFEntry &ent, XrdOucString salt, - XrdOucString &ranpwd, - bool random, bool checkpw, bool &newpw); -bool AddPassword(XrdSutPFEntry &ent, bool &newpw, const char *pwd = 0); -void SavePasswd(XrdOucString tag, XrdOucString pwd, bool onetime); -bool ReadPasswd(XrdOucString &tag, XrdOucString &pwd, int &st); -bool ReadPuk(int &npuk, XrdOucString *tpuk, XrdOucString *puk); -int GeneratePuk(); -bool SavePuk(); -bool ReadPuk(); -bool ExpPuk(const char *puk = 0, bool read = 1); -bool GetEntry(XrdSutPFile *ff, XrdOucString tag, - XrdSutPFEntry &ent, bool &check); -bool AskConfirm(const char *msg1, bool defact, const char *msg2 = 0); -int LocateFactoryIndex(char *tag, int &id); - -#define PRT(x) {cerr < 32) - SrvID.erase(32); - } else { - PRT("Server ID will be generated randomly. It can be changed"); - PRT("at any time with 'add -srvID '."); - // - // Set random ID - XrdSutRndm::Init(); - XrdSutRndm::GetString(1,8,SrvID); - // - // Add local user name - struct passwd *pw = getpwuid(getuid()); - if (pw) { - SrvID.insert(':',0); - SrvID.insert(pw->pw_name,0); - } - } - } else if (DontAsk) { - // This is a force creation where no prompt request can be answered - SetID = 0; - } - PRT("Server ID: " << SrvID); - if (SrvID.length() > 0) { - // - // Fill entry - ent.SetName(IDTag.c_str()); - ent.status = kPFE_special; - ent.cnt = 1; - ent.buf1.SetBuf(SrvID.c_str(),SrvID.length()+1); - // - // Write entry - ent.mtime = time(0); - ff.WriteEntry(ent); - PRT(" File successfully created with server ID set to: " - <ID(); - // - // Serialize in a buffer - XrdSutBucket *bck = RefCip[i]->AsBucket(); - if (bck) { - // - // Prepare Entry - ent.SetName(tag.c_str()); - ent.status = kPFE_special; - ent.cnt = 2; // protected - ent.buf1.SetBuf(bck->buffer,bck->size); - // - // Write entry - ent.mtime = time(0); - ff.WriteEntry(ent); - PRT(" Server Puk saved for crypto: "<Name()); - delete bck; - bck = 0; - } - } - } - // - // Backup also on separate file - if (!SavePuk()) { - PRT("// Problems with puk backup "); - } - } - } else { - PRT(" File successfully created "); - } - } - - // If admin, check for special entries - // (Server Unique ID, Email, Host name) - if (Mode == kM_admin) { - // - // Ref ciphers - ent.Reset(); - nm = ff.SearchEntries(PukTag.c_str(),0); - if (nm) { - int *ofs = new int[nm]; - ff.SearchEntries(PukTag.c_str(),0,ofs,nm); - for ( i = 0; i < nm ; i++) { - nr = ff.ReadEntry(ofs[i],ent); - if (nr > 0) { - XrdSutBucket bck; - bck.SetBuf(ent.buf1.buf,ent.buf1.len); - // Locate factory ID - int id = 0; - int ii = LocateFactoryIndex(ent.name, id); - if (ii < 0) { - PRT("// Factory ID not found: corruption ?"); - exit(1); - } - if (!(RefCip[i] = CF[ii]->Cipher(&bck))) { - PRT("// Could not instantiate cipher for factory "<Name()); - exit(1); - } - } - } - } else { - PRT("// Ref puk ciphers not found: corruption ?"); - exit(1); - } - - - if (ff.ReadEntry(IDTag.c_str(),ent) <= 0 && !SetID) { - PRT(" Unique ID missing: 'add -srvID' to set it"); - } else if (!SetID) { - SrvID.insert(ent.buf1.buf,0,ent.buf1.len); - } - // - // Unique ID - ent.Reset(); - if (ff.ReadEntry(IDTag.c_str(),ent) <= 0 && !SetID) { - PRT(" Unique ID missing: 'add -srvID' to set it"); - } else if (!SetID) { - SrvID.insert(ent.buf1.buf,0,ent.buf1.len); - } - // - // Email - ent.Reset(); - if (ff.ReadEntry(EmailTag.c_str(),ent) <= 0 && !SetEmail) { - PRT(" Contact E-mail not set: 'add -email ' to set it"); - } else if (!SetEmail) { - Email.insert(ent.buf1.buf,0,ent.buf1.len); - } - // - // Server Host name - ent.Reset(); - if (ff.ReadEntry(HostTag.c_str(),ent) <= 0 && !SetHost) { - PRT(" Local host name not set: 'add -host ' to set it"); - } else if (!SetHost) { - SrvName.insert(ent.buf1.buf,0,ent.buf1.len); - } - } - - switch (Action) { - case kA_update: - // Like 'add', forcing write - case kA_add: - if (Action == kA_update) Force = 1; - // - // Add / Update entry - // - // If admin, check first if we are required to update/create - // some special entry (Server Unique ID, Email, Host Name) - if (Mode == kM_admin) { - // - // Export current Server PUK - if (ExportPuk) { - if (!ExpPuk()) { - PRT("// Could not export public keys"); - } - // - // We are done - break; - } - // - // Server PUK - ent.Reset(); - if (ChangePuk) { - if (!DontAsk && !AskConfirm("Override server PUK?",0,0)) - break; - // - // If we are given a file name, try import from the file - if (Import && PukFile.length() > 0) { - if (!ReadPuk()) { - PRT("// Problem importing puks from "< 0) { - // - // Locate factory ID - int id; - int j = LocateFactoryIndex(ent.name,id); - if (j < 0) break; - // Serialize in a buffer - XrdSutBucket *bck = RefCip[j]->AsBucket(); - if (bck) { - // Shift up buffer content (buf 4 is removed) - if (ent.buf4.buf) - delete[] ent.buf4.buf; - ent.buf4.buf = ent.buf3.buf; - ent.buf4.len = ent.buf3.len; - ent.buf3.buf = ent.buf2.buf; - ent.buf3.len = ent.buf2.len; - ent.buf2.buf = ent.buf1.buf; - ent.buf2.len = ent.buf1.len; - // fill buf 1 with new puk - ent.buf1.SetBuf(bck->buffer,bck->size); - // - // Write entry - ent.mtime = time(0); - ff.WriteEntry(ent); - PRT(" Server Puk updated for crypto: "<Name()); - delete bck; - bck = 0; - } - // - // Flag user entries - char stag[4]; - sprintf(stag,"*_%d",id); - int nofs = ff.SearchEntries(stag,2); - if (nofs > 0) { - int *uofs = new int[nofs]; - ff.SearchEntries(stag,2,uofs,nofs); - XrdSutPFEntry uent; - int k = 0, nnr = 0; - for (; k < nofs; k++) { - uent.Reset(); - nnr = ff.ReadEntry(uofs[k],uent); - if (nnr > 0 && !strstr(uent.name,PukTag.c_str())) { - char c = 0; - if (uent.buf4.buf) { - c = *(uent.buf4.buf); - c++; - if (c > 4) - c = 1; - *(uent.buf4.buf) = c; - } else { - uent.buf4.buf = new char[1]; - uent.buf4.len = 1; - *(uent.buf4.buf) = 2; - } - // Write entry - uent.mtime = time(0); - ff.WriteEntry(uent); - } - } - } - } else { - PRT("// warning: problems reading entry: corruption?"); - break; - } - } - } else { - PRT("// WARNING: No entry for tag '"<' "); - break; - } - if (!ReadPuk(nHostPuk,TagHostPuk,HostPuk)) - break; - // - // Now we loop over tags - for (i = 0; i < nHostPuk; i++) { - // Check if not already existing - ent.Reset(); - if (GetEntry(&ff,TagHostPuk[i],ent,check)) { - break; - } - // Fill in new puk - ent.buf1.SetBuf(HostPuk[i].c_str(),HostPuk[i].length()+1); - // Write entry - ent.mtime = time(0); - ff.WriteEntry(ent); - if (check) { - PRT("// Server puk "< 0) { - // Insert non default iteration number in salt - salt.insert(IterNum,0); - } - } - // - for ( i = 0; i < ncrypt; i++ ) { - // Get hook to crypto factory - CF[i] = XrdCryptoFactory::GetCryptoFactory(CryptMod[i].c_str()); - if (!CF[i]) { - PRT("Hook for crypto factory undefined: "<KDFun(); - KDFunLen = CF[i]->KDFunLen(); - if (!KDFun || !KDFunLen) { - PRT("Error resolving one-way hash functions "); - break; - } - // - // Build tag - tag = NameTag + '_'; - tag += CF[i]->ID(); - // Check if not already existing - ent.Reset(); - if (GetEntry(&ff,tag,ent,checkpwd)) { - break; - } - if (Mode == kM_netrc) { - // If just a request for password change not much to do - if (ChangePwd) { - if (!checkpwd) - break; - else - // Update the status - ent.status = kPFE_onetime; - } else { - // Reset status and cnt - if (pwdimp) - ent.status = entst; - else - ent.status = kPFE_ok; - ent.cnt = 0; - // - // Fill with password - if (!AddPassword(ent, newpw, pwdimp)) { - PRT("Error creating new password: "< 0) { - PRT("// #:"< 0) { - // Disable entry - ent.status = kPFE_disabled; - ent.cnt = 0; - ent.buf1.SetBuf(); - ent.buf2.SetBuf(); - ent.buf3.SetBuf(); - ent.buf4.SetBuf(); - // Save (or update) entry - ent.mtime = time(0); - ff.WriteEntry(ent); - PRT("// Entry for tag '"<SetName(CopyTag.c_str()); - // - // Write entry - nent->mtime = time(0); - ff.WriteEntry(*nent); - PRT("// Entry for tag '"<name<< - "' created"); - delete nent; - } else { - PRT("// Cannot create new entry: out of memory"); - break; - } - PRT("//"); - PRT("//-----------------------------------------------------" - "--------------------//"); - break; - - case kA_trim: - case kA_browse: - default: - // - // Trim the file first before browsing - if (Action == kA_trim) ff.Trim(); - // - // Browse - ff.Browse(); - break; - } - - exit(0); -} - - -void Menu(int opt) -{ - // Print the menu - // Options: 0 intro w/ head/tail - // 1 intro w/o head/tail - // 2 keywords - - // Head - if (opt == 0) { - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - PRT("+ +"); - PRT("+ x r d p w d a d m i n +"); - PRT("+ +"); - PRT("+ Administration of pwd files +"); - } - - // Intro - if (opt <= 1) { - PRT("+ +"); - PRT("+ Syntax: +"); - PRT("+ +"); - PRT("+ xrdpwdadmin [-h] [-m ] [options] +"); - PRT("+ +"); - PRT("+ -h display this menu +"); - PRT("+ +"); - PRT("+ -m choose mode (admin, user, netrc, srvpuk) [admin] +"); - PRT("+ +"); - PRT("+ admin: +"); - PRT("+ create / modify the main file used by servers +"); - PRT("+ started from this account to validate clients +"); - PRT("+ credentials. Default location and name: +"); - PRT("+ $(HOME)/.xrd/pwdadmin +"); - PRT("+ +"); - PRT("+ NB: file must readable and writable by owner +"); - PRT("+ only e.g. 0600 +"); - PRT("+ +"); - PRT("+ user: +"); - PRT("+ create / modify local file used by servers +"); - PRT("+ to validate this user credentials. +"); - PRT("+ Default location and name: +"); - PRT("+ $(HOME)/.xrd/pwduser +"); - PRT("+ +"); - PRT("+ NB: the file must be copied on the server machine +"); - PRT("+ if produced elsewhere; file must be writable +"); - PRT("+ by the owner only, e.g. 0644 +"); - PRT("+ +"); - PRT("+ netrc: +"); - PRT("+ create / modify local autologin file +"); - PRT("+ Default location and name: +"); - PRT("+ $(HOME)/.xrd/pwdnetrc +"); - PRT("+ +"); - PRT("+ NB: file must readable and writable by owner +"); - PRT("+ only e.g. 0600 +"); - PRT("+ +"); - PRT("+ srvpuk: +"); - PRT("+ create / modify local file with known server +"); - PRT("+ public cipher initializers. +"); - PRT("+ Default location and name: +"); - PRT("+ $(HOME)/.xrd/pwdsrvpuk +"); - PRT("+ +"); - PRT("+ NB: file must be writable by the owner only +"); - PRT("+ e.g. 0644 +"); - } - - // Intro - if (opt <= 2) { - PRT("+ +"); - PRT("+ Options: +"); - PRT("+ +"); - PRT("+ add [-[no]force] [-[no]random] [-[no]savepw] +"); - PRT("+ add entry with tag ; the application prompts +"); - PRT("+ for the password +"); - PRT("+ +"); - PRT("+ add -import +"); - PRT("+ add entry with tag importing the pwd from +"); - PRT("+ the file send by the server administrator +"); - PRT("+ [netrc only] +"); - PRT("+ +"); - PRT("+ add -import +"); - PRT("+ add new server key importing the key from +"); - PRT("+ the file send by the server administrator +"); - PRT("+ [srvpuk only] +"); - PRT("+ +"); - PRT("+ update [options] +"); - PRT("+ equivalent to 'add -force' +"); - PRT("+ +"); - PRT("+ read +"); - PRT("+ list some information of entry associated with tag +"); - PRT("+ (status, count, date of last change, buffer +"); - PRT("+ lengths); buffer contents not listed +"); - PRT("+ +"); - PRT("+ remove +"); - PRT("+ Make entry associated with tag inactive +"); - PRT("+ (Spce is recovered during next trim operation) +"); - PRT("+ +"); - PRT("+ copy +"); - PRT("+ Create new entry with tag and content of +"); - PRT("+ existing entry with tag +"); - PRT("+ +"); - PRT("+ trim [-nobackup] +"); - PRT("+ Trim the file content eliminating all the inactive +"); - PRT("+ entries; a backup is created in .bak unless +"); - PRT("+ the option '-nobackup' is specified +"); - PRT("+ +"); - PRT("+ browse +"); - PRT("+ list a table about the file content +"); - } - - // Intro - if (opt <= 3) { - PRT("+ +"); - PRT("+ -dontask +"); - PRT("+ do not prompt for questions: when in doubt use +"); - PRT("+ defaults or fail +"); - PRT("+ [default: ask] +"); - PRT("+ -force +"); - PRT("+ overwrite entry if it exists already +"); - PRT("+ [default: do not overwrite] +"); - PRT("+ -[no]change +"); - PRT("+ do [not] require user to change info on first use +"); - PRT("+ [default: admin: change / user: no change +"); - PRT("+ -crypto [-]|[-]|... +"); - PRT("+ create information for the given crypto modules +"); - PRT("+ ('|' separated list) in addition to default ones +"); - PRT("+ (normally ssl and local); use '-' in front to avoid +"); - PRT("+ avoid creating a entry for a module; one entry is +"); - PRT("+ for each module with effective tag of the form +"); - PRT("+ name_ [default list: ssl] +"); - PRT("+ [default: create backup] +"); - } - - // Tail - PRT("+ +"); - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); -} - -int ParseArguments(int argc, char **argv) -{ - // Parse application arguments filling relevant global variables - bool changeset = 0; - bool randomset = 0; - bool savepwset = 0; - bool randomid = 0; - - // Number of arguments - if (argc < 0 || !argv[0]) { - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - PRT("+ Insufficient number or arguments! +"); - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - // Print main menu - Menu(0); - return 1; - } - --argc; - ++argv; - - // - // Loop over arguments - while ((argc >= 0) && (*argv)) { - - XrdOucString opt = ""; - int ival = -1; - if(*(argv)[0] == '-') { - - opt = *argv; - opt.erase("-"); - if (CheckOption(opt,"m",ival)) { - if (Mode != kM_undef) { - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - PRT("+ Only one valid '-m' option allowed: ignoring +"); - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - --argc; - ++argv; - if (argc >= 0 && (*argv && *(argv)[0] == '-')) { - argc++; - argv--; - } - } - --argc; - ++argv; - if (argc >= 0 && (*argv && *(argv)[0] != '-')) { - XrdOucString mode = *argv; - if (CheckOption(mode,"admin",ival)) { - Mode = kM_admin; - } else if (CheckOption(mode,"user",ival)) { - Mode = kM_user; - } else if (CheckOption(mode,"netrc",ival)) { - Mode = kM_netrc; - } else if (CheckOption(mode,"srvpuk",ival)) { - Mode = kM_srvpuk; - } else if (CheckOption(mode,"help",ival)) { - Mode = kM_help; - } else { - PRT("++++++++++++++++++++++++++++++++++++++" - "++++++++++++++++++++++"); - PRT("+ Ignoring unrecognized more: "<= 0 && (*argv && *(argv)[0] != '-')) { - Path = *argv; - } else { - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - PRT("+ Option '-f' requires a file or directory name: ignoring +"); - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - argc++; - argv--; - } - } else if (CheckOption(opt,"dontask",ival)) { - DontAsk = ival; - } else if (CheckOption(opt,"force",ival)) { - Force = ival; - } else if (CheckOption(opt,"change",ival)) { - Change = ival; - changeset = 1; - } else if (CheckOption(opt,"passwd",ival)) { - Passwd = ival; - } else if (CheckOption(opt,"backup",ival)) { - Backup = ival; - } else if (CheckOption(opt,"random",ival)) { - Random = ival; - randomset = 1; - } else if (CheckOption(opt,"savepw",ival)) { - SavePw = ival; - savepwset = 1; - } else if (CheckOption(opt,"confirm",ival)) { - Confirm = ival; - } else if (CheckOption(opt,"create",ival)) { - Create = ival; - } else if (CheckOption(opt,"hash",ival)) { - Hash = ival; - } else if (CheckOption(opt,"changepuk",ival)) { - ChangePuk = ival; - } else if (CheckOption(opt,"changepwd",ival)) { - ChangePwd = ival; - } else if (CheckOption(opt,"exportpuk",ival)) { - ExportPuk = ival; - } else if (CheckOption(opt,"iternum",ival)) { - --argc; - ++argv; - if (argc >= 0 && (*argv && *(argv)[0] != '-')) { - int iter = strtol(*argv,0,10); - if (iter > 0 && errno != ERANGE) { - IterNum = "$$"; - IterNum += *argv; - IterNum += "$"; - } else { - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - PRT("+ Option '-iternum' requires a positive number: ignoring +"); - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - argc++; - argv--; - } - } else { - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - PRT("+ Option '-iternum' requires a positive number: ignoring +"); - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - argc++; - argv--; - } - } else if (CheckOption(opt,"crypto",ival)) { - --argc; - ++argv; - if (argc >= 0 && (*argv && *(argv)[0] != '-')) { - CryptList = *argv; - } else { - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - PRT("+ Option '-crypto' requires a list of modules: ignoring +"); - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - argc++; - argv--; - } - } else if (CheckOption(opt,"import",ival)) { - --argc; - ++argv; - if (argc >= 0 && (*argv && *(argv)[0] != '-')) { - if (Mode == kM_netrc) { - PwdFile = *argv; - } else { - PukFile = *argv; - } - Import = 1; - } else { - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - PRT("+ Option '-import' requires a file name: ignoring +"); - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - argc++; - argv--; - } - } else if (CheckOption(opt,"srvID",ival)) { - --argc; - ++argv; - SetID = 1; - if (argc >= 0 && (*argv && *(argv)[0] != '-')) { - SrvID = *argv; - } else { - SrvID = ""; - randomid = 1; - argc++; - argv--; - } - } else if (CheckOption(opt,"email",ival)) { - --argc; - ++argv; - if (argc >= 0 && (*argv && *(argv)[0] != '-')) { - Email = *argv; - SetEmail = 1; - } else { - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - PRT("+ Option '-email' requires an email string: ignoring +"); - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - argc++; - argv--; - } - } else if (CheckOption(opt,"host",ival)) { - --argc; - ++argv; - if (argc >= 0 && (*argv && *(argv)[0] != '-')) { - SrvName = *argv; - SetHost = 1; - } else { - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - PRT("+ Option '-host' requires the local host name: ignoring +"); - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - argc++; - argv--; - } - } else { - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - PRT("+ Ignoring unrecognized option: "<<*argv); - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - } - - } else { - // - // Action keyword - opt = *argv; - int iad = -1, iup = -1, ird = -1, irm = -1, idi = -1, icp = -1; - if (CheckOption(opt,"add",iad) || CheckOption(opt,"update",iup) || - CheckOption(opt,"read",ird) || CheckOption(opt,"remove",irm) || - CheckOption(opt,"disable",idi) || CheckOption(opt,"copy",icp)) { - Action = (Action == kA_undef && iad == 1) ? kA_add : Action; - Action = (Action == kA_undef && iup == 1) ? kA_update : Action; - Action = (Action == kA_undef && ird == 1) ? kA_read : Action; - Action = (Action == kA_undef && irm == 1) ? kA_remove : Action; - Action = (Action == kA_undef && idi == 1) ? kA_disable : Action; - Action = (Action == kA_undef && icp == 1) ? kA_copy : Action; - --argc; - ++argv; - if (argc >= 0 && (*argv && *(argv)[0] != '-')) { - NameTag = *argv; - if (icp == 1) { - --argc; - ++argv; - if (argc >= 0 && (*argv && *(argv)[0] != '-')) { - CopyTag = *argv; - } else { - PRT("+++++++++++++++++++++++++++++++++++++++++" - "+++++++++++++++++++"); - PRT("+ 'copy': missing destination tag: ignoring" - " +"); - PRT("+++++++++++++++++++++++++++++++++++++++++" - "+++++++++++++++++++"); - CopyTag = ""; - argc++; - argv--; - } - } - } else { - NameTag = ""; - argc++; - argv--; - } - } else if (CheckOption(opt,"trim",ival)) { - Action = kA_trim; - } else if (CheckOption(opt,"browse",ival)) { - Action = kA_browse; - } else { - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - PRT("+ Ignoring unrecognized keyword action: "<pw_name,0); - } else { - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - PRT("+ WARNING: could not get local user info for srv ID +"); - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - } - } else { - if (SrvID.length() > 32) { - SrvID.erase(32); - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - PRT("+ WARNING: srv ID too long: truncating to 32 chars: " - < 0 && (Mode != kM_admin && Mode != kM_user)) { - IterNum = ""; - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - PRT("+ WARNING: ignore iter num change request (not admin/user) +"); - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - } - - // - // Requesting a password change only makes sense in netrc mode - if (ChangePwd && Mode != kM_netrc) { - ChangePwd = 0; - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - PRT("+ WARNING: ignore password change request (not netrc) +"); - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - } - - // - // If user mode, check if NameTag contains the local user - // name: if not, warn the user about possible problems with - // servers ignoring this kind of entries for users files - if (Mode == kM_user && NameTag.length()) { - struct passwd *pw = getpwuid(getuid()); - if (pw) { - XrdOucString locusr = pw->pw_name; - if (NameTag != locusr) { - PRT("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - PRT("+ WARNING: name tag does not match local user name: "); - PRT("+ "< - // ival = -1 in the other cases - bool rc = 0; - - int lref = (ref) ? strlen(ref) : 0; - if (!lref) - return rc; - XrdOucString noref = ref; - noref.insert("no",0); - - ival = -1; - if (opt == ref) { - ival = 1; - rc = 1; - } else if (opt == noref) { - ival = 0; - rc = 1; - } - - return rc; -} - -bool AddPassword(XrdSutPFEntry &ent, XrdOucString salt, XrdOucString &ranpwd, - bool random, bool checkpw, bool &newpw) -{ - // Generate (prompting or randomly) new password and add it - // to entry ent - // If checkpw, make sure that it is different from the existing - // one (check is done on the hash, cannot decide if the change - // is significant or not). - // Return generated random password in ranpwd. - // Randoms passwords are 8 char lengths filled with upper and - // lower case letters and numbers - // If !newpw, the a pwd saved during a previous call is used, - // if any. - // Return 1 if ok, 0 otherwise. - static XrdOucString pwdref; - - XrdSutPFBuf oldsalt; - XrdSutPFBuf oldhash; - // - // Save existing salt and hash, if required - if (checkpw) { - if (ent.buf1.len > 0 && ent.buf1.buf) { - oldsalt.SetBuf(ent.buf1.buf,ent.buf1.len); - if (ent.buf2.len > 0 && ent.buf2.buf) { - oldhash.SetBuf(ent.buf2.buf,ent.buf2.len); - } else { - checkpw = 0; - } - } else { - checkpw = 0; - } - } - // - // Save salt - ent.buf1.SetBuf(salt.c_str(),salt.length()); - // - // Prepare to get password - XrdOucString passwd = ""; - if (newpw || !pwdref.length()) { - newpw = 1; - pwdref = ""; - } - char *pwhash = 0; - int pwhlen = 0; - int natt = 0; - while (!passwd.length()) { - // - // - if (natt == kMAXPWDATT) { - PRT("AddPassword: max number of attempts reached: "< 0) { - // Set a non-default iteration number (we are going to hash - // the password with itself) - passwd.insert(IterNum,0); - } - pwdref = passwd; - ranpwd = passwd; - newpw = 0; - checkpw = 0; // not needed - } - } else { - passwd = pwdref; - } - // Get pw hash encoding password with itself - pwhash = new char[(*KDFunLen)()]; - pwhlen = (*KDFun)(passwd.c_str(),passwd.length(), - passwd.c_str(),passwd.length(),pwhash,0); - // - // Check the password if required - if (checkpw) { - // Get hash with old salt - char *osahash = new char[(*KDFunLen)()]; - // Encode the pw hash with the salt - (*KDFun)(pwhash,pwhlen, - oldsalt.buf,oldsalt.len,osahash,0); - if (!memcmp(oldhash.buf,osahash,oldhash.len)) { - // Do not accept this password - PRT("AddPassword: Password seems to be the same" - ": please enter a different one"); - passwd.hardreset(); - pwdref.hardreset(); - ranpwd.hardreset(); - newpw = 1; - } - // Cleanup - if (osahash) delete[] osahash; - } - } - // - // Calculate new hash, now - if (passwd.length()) { - // Get new hash - char *nsahash = new char[(*KDFunLen)()]; - // Encode first the hash with the salt - int hlen = (*KDFun)(pwhash,pwhlen, - salt.c_str(),salt.length(),nsahash,0); - // Copy result in buf 2 - ent.buf2.SetBuf(nsahash,hlen); - // Cleanup - if (nsahash) delete[] nsahash; - } - // - // Cleanup - if (pwhash) delete[] pwhash; - // We are done - return 1; -} - -bool AddPassword(XrdSutPFEntry &ent, bool &newpw, const char *pwd) -{ - // Prompt new password and save in hash form to entry ent - // (if pwd is defined, take password from pwd). - // If !newpw, the a pwd saved during a previous call is used, - // if any. - // Return 1 if ok, 0 otherwise. - static XrdOucString pwdref; - - // - // Prepare to get passwrod - XrdOucString passwd = ""; - if (newpw || !pwdref.length()) { - newpw = 1; - pwdref = ""; - } - // - // If we are given a password, use it - if (pwd && strlen(pwd) > 0) { - PRT("AddPassword: using input password ("<ID(); - buf += "puk: "; buf += ptag; buf += "\n"; - int lpub = 0; - char *pub = RefCip[i]->Public(lpub); - if (pub) { - buf += pub; buf += "\n"; - delete[] pub; - } - buf += "epuk\n"; - } - buf += "\n"; - buf += "*********************************************"; - // - // Write it to file - // Now write the buffer to the stream - while (write(fd, buf.c_str(), buf.length()) < 0 && errno == EINTR) - errno = 0; - // - // Close file - close (fd); - - // We are done - return; -} - -bool GetEntry(XrdSutPFile *ff, XrdOucString tag, - XrdSutPFEntry &ent, bool &check) -{ - // Get antry from file, checking force - // Returns 1 if it exists and should not be updated - // 0 otherwise - - int nr = ff->ReadEntry(tag.c_str(),ent); - check = 0; - if (nr > 0) { - if (!Force) { - PRT(" Entry for tag '"<Name()); - PRT(" Details: "<@' and associated password - - // Make sure that the filename is defined - if (PwdFile.length() <= 0) { - PRT("ReadPasswd: file name undefined - do nothing"); - return 0; - } - // - // Open file in read mode - FILE *fd = fopen(PwdFile.c_str(),"r"); - if (fd == 0) { - PRT("ReadPasswd: could not open file: "< 0) { - tag += '@'; - tag += host; - tag += ':'; - } - // - // Add srv ID, if any - if (id.length() > 0) { - tag += id; - } - // - // Notify tag - PRT("ReadPasswd: build tag: "<:_' - - // Make sure that the filename is defined - if (PukFile.length() <= 0) { - PRT("ReadPuk: file name undefined - do nothing"); - return 0; - } - // - // Open file in read mode - FILE *fd = fopen(PukFile.c_str(),"r"); - if (fd == 0) { - PRT("ReadPuk: could not open file: "<AsBucket(); - if (!bck[i]) continue; - // - // Count - lout += (bck[i]->size + 2*sizeof(kXR_int32)); - } - // - // Get the buffer - char *bout = new char[lout]; - if (!bout) { - PRT("SavePuk: Cannot create output buffer"); - close(fd); - return 0; - } - // - // Loop over ciphers to fill the buffer - int lp = 0; - for (i = 0; i < ncrypt; i++) { - // - // Make sure it is defined - if (!CF[i] || !bck[i]) continue; - // - // The crypto ID first - kXR_int32 id = CF[i]->ID(); - memcpy(bout+lp,&id,sizeof(kXR_int32)); - lp += sizeof(kXR_int32); - // - // The length second - kXR_int32 lpuk = bck[i]->size; - memcpy(bout+lp,&lpuk,sizeof(kXR_int32)); - lp += sizeof(kXR_int32); - // - // Finally the content - memcpy(bout+lp,bck[i]->buffer,lpuk); - lp += lpuk; - // - // Cleanup - delete bck[i]; - bck[i] = 0; - } - delete[] bck; - // - // Write it to file - // Now write the buffer to the stream - while (write(fd, bout, lout) < 0 && errno == EINTR) - errno = 0; - PRT("SavePuk: "<= 0) { - if (CF[i] && CF[i]->ID() == id) break; - i--; - } - if (i < 0) { - PRT("ReadPuk: warning: factory with ID "<< id << " not found"); - delete bck; - continue; - } - // Instantiate cipher from bucket - RefCip[i] = CF[i]->Cipher(bck); - if (!RefCip[i]) { - PRT("ReadPuk: warning: could not instantiate cipher" - " from bucket for factory "<Name()); - } else { - PRT("ReadPuk: instantiate cipher for factory "<Name()); - } - // Count good ciphers - ncip++; - delete bck; - } - // - // Close file - close (fd); - - PRT("ReadPuk: "<Cipher(0,0,0); - if (!RefCip[i]) continue; - // - // Count success - ncf++; - } - - // We are done - return ncf; -} - -int LocateFactoryIndex(char *tag, int &id) -{ - // Searches tag for "_" final strings - // Extracts id and locate position in crypto array - - // - // Locate factory ID - XrdOucString sid(tag); - sid.erase(0,sid.rfind('_')+1); - id = atoi(sid.c_str()); - int j = ncrypt - 1; - while (j >= 0) { - if (CF[j] && CF[j]->ID() == id) break; - j--; - } - if (j < 0) - PRT("// warning: factory with ID "<< id << " not found"); - - return j; -} - -bool ExpPuk(const char *puk, bool read) -{ - // Export public part of key contained in file 'puk'. The file - // name can be absolute or relative to the standard 'genpuk' or - // a date to be looked for in the genpuk directory. The public - // key is exported in a file adding the extension ".export" - // to 'puk'. If the file name is not defined the most recent - // key in the standard genpuk directory is exported. - // Return 0 in case of failure, 1 in case of success. - - // Read the keys in, if needed - if (read) { - // Standard genpuk dir - XrdOucString genpukdir = Dir; - genpukdir += GenPukRef; - - // Locate the file with the full key - if (puk && strlen(puk) > 0) { - // If not absolute, expand with respect to the standard genpuk dir - if (puk[0] != '/') - PukFile = genpukdir; - PukFile += puk; - } else { - // Scan the standard genpuk to find the most recent key - DIR *dir = opendir(genpukdir.c_str()); - if (!dir) { - PRT("ExpPuk: cannot open standard genpuk dir "<d_name, "puk.", 4)) - continue; - // Get the modification date - XrdOucString fn = genpukdir; - fn += ent->d_name; - struct stat st; - if (stat(fn.c_str(), &st) != 0) { - PRT("ExpPuk: cannot stat "< latest) { - PukFile = fn; - latest = st.st_mtime; - } - } - } - - // Read the keys in - if (!ReadPuk()) { - PRT("ExpPuk: problem reading the key in"); - return 0; - } - } - - // Build the export file name - XrdOucString expfile = PukFile; - expfile += ".export"; - PRT("ExpPuk: exporting key from file "<ID(); - buf += "puk: "; buf += ptag; buf += "\n"; - int lpub = 0; - char *pub = RefCip[i]->Public(lpub); - if (pub) { - buf += pub; buf += "\n"; - delete[] pub; - } - buf += "epuk\n"; - } - buf += "\n"; - buf += "*********************************************"; - // - // Write it to file - // Now write the buffer to the stream - while (write(fd, buf.c_str(), buf.length()) < 0 && errno == EINTR) - errno = 0; - // - // Close file - close (fd); - - // We are done - return 1; -} diff --git a/src/XrdSecpwd/XrdSecpwdTrace.hh b/src/XrdSecpwd/XrdSecpwdTrace.hh deleted file mode 100644 index e2b2fe3b1e9..00000000000 --- a/src/XrdSecpwd/XrdSecpwdTrace.hh +++ /dev/null @@ -1,65 +0,0 @@ -#ifndef ___SECPWD_TRACE_H___ -#define ___SECPWD_TRACE_H___ -/******************************************************************************/ -/* */ -/* X r d S e c g s i T r a c e . h h */ -/* */ -/* (C) 2012 G. Ganis, CERN */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/* */ -/******************************************************************************/ - -#include "XrdOuc/XrdOucTrace.hh" - -#ifndef NODEBUG - -#include "XrdSys/XrdSysHeaders.hh" - -#define QTRACE(act) (pwdTrace && (pwdTrace->What & TRACE_ ## act)) -#define PRINT(y) {if (pwdTrace) {pwdTrace->Beg(epname); \ - cerr <End();}} -#define TRACE(act,x) if (QTRACE(act)) PRINT(x) -#define NOTIFY(y) TRACE(Debug,y) -#define DEBUG(y) TRACE(Authen,y) -#define EPNAME(x) static const char *epname = x; - -#else - -#define QTRACE(x) -#define PRINT(x) -#define TRACE(x,y) -#define NOTIFY(x) -#define DEBUG(x) -#define EPNAME(x) - -#endif - -#define TRACE_ALL 0x000f -#define TRACE_Dump 0x0004 -#define TRACE_Authen 0x0002 -#define TRACE_Debug 0x0001 - -// -// For error logging and tracing -extern XrdOucTrace *pwdTrace; - -#endif diff --git a/src/XrdSecsss/XrdSecProtocolsss.cc b/src/XrdSecsss/XrdSecProtocolsss.cc deleted file mode 100644 index 3252a9f0942..00000000000 --- a/src/XrdSecsss/XrdSecProtocolsss.cc +++ /dev/null @@ -1,934 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S e c P r o t o c o l s s s . c c */ -/* */ -/* (c) 2008 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "XrdVersion.hh" - -#include "XrdNet/XrdNetUtils.hh" -#include "XrdOuc/XrdOucCRC.hh" -#include "XrdOuc/XrdOucErrInfo.hh" -#include "XrdOuc/XrdOucEnv.hh" -#include "XrdOuc/XrdOucPup.hh" -#include "XrdOuc/XrdOucTokenizer.hh" -#include "XrdSecsss/XrdSecProtocolsss.hh" -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdSys/XrdSysPlatform.hh" -#include "XrdSys/XrdSysPthread.hh" - -/******************************************************************************/ -/* D e f i n e s */ -/******************************************************************************/ - -#define XrdSecPROTOIDENT "sss" -#define XrdSecPROTOIDLEN sizeof(XrdSecPROTOIDENT) -#define XrdSecDEBUG 0x1000 - -#define CLDBG(x) if (options & XrdSecDEBUG) cerr <<"sec_sss: " <buffer); - XrdSecsssRR_Data rrData; - XrdSecsssKT::ktEnt decKey; - XrdSecEntity myID("sss"); - char lidBuff[16], eType, *idP, *dP, *eodP, *theIP = 0, *theHost = 0; - int idTLen, idSz, dLen; - -// Decode the credentials -// - if ((dLen = Decode(einfo, decKey, cred->buffer, &rrData, cred->size)) <= 0) - return -1; - -// Check if we should echo back the LID -// - if (rrData.Options == XrdSecsssRR_Data::SndLID) - {rrData.Options = 0; - getLID(lidBuff, sizeof(lidBuff)); - dP = rrData.Data; - *dP++ = XrdSecsssRR_Data::theLgid; - XrdOucPup::Pack(&dP, lidBuff); - *parms = Encode(einfo, decKey, rrHdr, &rrData, dP-(char *)&rrData); - return (*parms ? 1 : -1); - } - -// Set the minimum size of the id buffer. This is likely going to wind up -// a bit larger than we need but at least it will big enough. -// - idTLen = (decKey.Data.User[0] ? strlen(decKey.Data.User) : 0); - idTLen += (decKey.Data.Grup[0] ? strlen(decKey.Data.Grup) : 0); - if (idTLen < 16) idTLen = 16; - -// Extract out the entity ID -// - dP = rrData.Data; eodP = dLen + (char *)&rrData; - while(dP < eodP) - {eType = *dP++; - if (!XrdOucPup::Unpack(&dP, eodP, &idP, idSz) || *idP == '\0') - {Fatal(einfo, "Authenticate", EINVAL, "Invalid id string."); - return -1; - } - idTLen += idSz; - switch(eType) - {case XrdSecsssRR_Data::theName: myID.name = idP; break; - case XrdSecsssRR_Data::theVorg: myID.vorg = idP; break; - case XrdSecsssRR_Data::theRole: myID.role = idP; break; - case XrdSecsssRR_Data::theGrps: myID.grps = idP; break; - case XrdSecsssRR_Data::theEndo: myID.endorsements = idP; break; - case XrdSecsssRR_Data::theHost: if (*idP == '[') theIP = idP; - else theHost = idP; - break; - case XrdSecsssRR_Data::theRand: idTLen -= idSz; break; - default: break; - } - } - -// Verify that we have some kind of identification -// - if (!idTLen) - {Fatal(einfo, "Authenticate", ENOENT, "No id specified."); - return -1; - } - -// Verify the source of the information to largely prevent packet stealing. New -// version of the protocol will send an IP address which we prefrentially use. -// Older version used a hostname. This causes problems for multi-homed machines. -// -if (!(decKey.Data.Opts & XrdSecsssKT::ktEnt::noIPCK)) - {if (!theHost && !theIP) - {Fatal(einfo,"Authenticate",ENOENT,"No hostname or IP address specified."); - return -1; - } - CLDBG(urName <<' ' <0) cerr <<"; " <setErrInfo(rc, etxt); - CLDBG(epn <<": " <getKey(encKey)) - {Fatal(einfo, "getCredentials", ENOENT, "Encryption key not found."); - return (XrdSecCredentials *)0; - } - -// Fill out the header -// - strcpy(rrHdr.ProtID, XrdSecPROTOIDENT); - memset(rrHdr.Pad, 0, sizeof(rrHdr.Pad)); - rrHdr.KeyID = htonll(encKey.Data.ID); - rrHdr.EncType = Crypto->Type(); - -// Now simply encode the data and return the result -// - return Encode(einfo, encKey, &rrHdr, &rrData, dLen); -} - -/******************************************************************************/ -/* I n i t _ C l i e n t */ -/******************************************************************************/ - -namespace -{ -XrdSysMutex initMutex; -}; - -int XrdSecProtocolsss::Init_Client(XrdOucErrInfo *erp, const char *pP) -{ - XrdSysMutexHelper initMon(&initMutex); - XrdSecsssKT *ktP; - struct stat buf; - char *Colon; - int lifeTime; - -// We must have :[] -// - if (!pP || !*pP) return Fatal(erp, "Init_Client", EINVAL, - "Client parameters missing."); - -// Get encryption object -// - if (!*pP || *(pP+1) != '.') return Fatal(erp, "Init_Client", EINVAL, - "Encryption type missing."); - if (!(Crypto = Load_Crypto(erp, *pP))) return 0; - pP += 2; - -// The next item is the cred lifetime -// - lifeTime = strtol(pP, &Colon, 10); - if (!lifeTime || *Colon != ':') return Fatal(erp, "Init_Client", EINVAL, - "Credential lifetime missing."); - deltaTime = lifeTime; pP = Colon+1; - -// Get the correct keytab -// - if (ktFixed || (ktObject && ktObject->Same(pP))) keyTab = ktObject; - else if (*pP == '/' && !stat(pP, &buf)) - {if (!(ktP=new XrdSecsssKT(erp,pP,XrdSecsssKT::isClient,3600))) - return Fatal(erp, "Init_Client", ENOMEM, - "Unable to create keytab object."); - if (erp->getErrInfo()) {delete ktP; return 0;} - if (!ktObject) ktObject = ktP; - keyTab = ktP; - CLDBG("Client keytab='" <getErrInfo()) - {delete ktObject, ktObject = 0; return (char *)0;} - CLDBG("Client keytab='" <Type()) return CryptObj; - -// Find correct crypto object -// - while(CryptoTab[i].cName && CryptoTab[i].cType != eT) i++; - -// If we didn't find it, complain -// - if (!CryptoTab[i].cName) - {sprintf(buff, "Secsss: 0x%hhx cryptography not supported.", eT); - Fatal(erp, "Load_Crypto", EINVAL, buff); - return (XrdCryptoLite *)0; - } - -// Return load result -// - if ((cP = XrdCryptoLite::Create(rc, CryptoTab[i].cName, eT))) return cP; - sprintf(buff,"Secsss: 0x%hhx cryptography load failed; %s",eT,strerror(rc)); - Fatal(erp, "Load_Crypto", EINVAL, buff); - return (XrdCryptoLite *)0; -} - -/******************************************************************************/ -/* L o a d _ S e r v e r */ -/******************************************************************************/ - -char *XrdSecProtocolsss::Load_Server(XrdOucErrInfo *erp, const char *parms) -{ - const char *msg = 0; - const char *encName = "bf32", *ktClient = "", *ktServer = 0; - char buff[2048], parmbuff[2048], *op, *od, *eP; - int lifeTime = 13, rfrTime = 60*60; - XrdOucTokenizer inParms(parmbuff); - -// Duplicate the parms -// - if (parms) strlcpy(parmbuff, parms, sizeof(parmbuff)); - -// Expected parameters: [-c ] [-e ] -// [-r ] [-l ] [-s ] -// - if (parms && inParms.GetLine()) - while((op = inParms.GetToken())) - {if (!(od = inParms.GetToken())) - {sprintf(buff,"Secsss: Missing %s parameter argument",op); - msg = buff; break; - } - if (!strcmp("-c", op)) ktClient = od; - else if (!strcmp("-e", op)) encName = od; - else if (!strcmp("-l", op)) - {lifeTime = strtol(od, &eP, 10) * 60; - if (errno || *eP || lifeTime < 1) - {msg = "Secsss: Invalid life time"; break;} - } - else if (!strcmp("-r", op)) - {rfrTime = strtol(od, &eP, 10) * 60; - if (errno || *eP || rfrTime < 600) - {msg = "Secsss: Invalid refresh time"; break;} - } - else if (!strcmp("-s", op)) ktServer = od; - else {sprintf(buff,"Secsss: Invalid parameter - %s",op); - msg = buff; break; - } - } - -// Check for errors -// - if (msg) {Fatal(erp, "Load_Server", EINVAL, msg); return (char *)0;} - -// Load the right crypto object -// - if (!(CryptObj = Load_Crypto(erp, encName))) return (char *)0; - -// Supply default keytab location if not specified -// - if (!ktServer) ktServer = XrdSecsssKT::genFN(); - -// Set the delta time used to expire credentials -// - deltaTime = lifeTime; - -// Create a keytab object (only one for the server) -// - if (!(ktObject = new XrdSecsssKT(erp, ktServer, XrdSecsssKT::isServer, - rfrTime))) - {Fatal(erp, "Load_Server", ENOMEM, "Unable to create keytab object."); - return (char *)0; - } - if (erp->getErrInfo()) return (char *)0; - ktFixed = 1; - CLDBG("Server keytab='" <: -// - sprintf(buff, "%c.%d:%s", CryptObj->Type(), lifeTime, ktClient); - CLDBG("client parms='" <= maxLen) - return Fatal(error,"Decode",EINVAL,"Credentials missing or of invalid size."); - -// Check if this is a recognized protocol -// - if (strcmp(rrHdr->ProtID, XrdSecPROTOIDENT)) - {char emsg[256]; - snprintf(emsg, sizeof(emsg), - "Authentication protocol id mismatch (%.4s != %.4s).", - XrdSecPROTOIDENT, rrHdr->ProtID); - return Fatal(error, "Decode", EINVAL, emsg); - } - -// Verify decryption method -// - if (rrHdr->EncType != Crypto->Type()) - return Fatal(error, "Decode", ENOTSUP, "Crypto type not supported."); - -// Get the key -// - decKey.Data.ID = ntohll(rrHdr->KeyID); - decKey.Data.Name[0] = '\0'; - if (keyTab->getKey(decKey)) - return Fatal(error, "Decode", ENOENT, "Decryption key not found."); - -// Decrypt -// - if ((rc = Crypto->Decrypt(decKey.Data.Val, decKey.Data.Len, - iBuff+sizeof(XrdSecsssRR_Hdr), dLen, - (char *)rrData, sizeof(XrdSecsssRR_Data))) <= 0) - return Fatal(error, "Decode", -rc, "Unable to decrypt credentials."); - -// Verify that the packet has not expired (OK to do before CRC check) -// - genTime = ntohl(rrData->GenTime); - if (genTime + deltaTime <= myClock()) - return Fatal(error, "Decode", ESTALE, - "Credentials expired (check for clock skew)."); - -// Return success (size of decrypted info) -// - return rc; -} - -/******************************************************************************/ -/* E n c o d e */ -/******************************************************************************/ - -XrdSecCredentials *XrdSecProtocolsss::Encode(XrdOucErrInfo *einfo, - XrdSecsssKT::ktEnt &encKey, - XrdSecsssRR_Hdr *rrHdr, - XrdSecsssRR_Data *rrData, - int dLen) -{ - static const int hdrSZ = sizeof(XrdSecsssRR_Hdr); - XrdOucEnv *errEnv = 0; - char *myIP = 0, *credP, *eodP = ((char *)rrData) + dLen; - char ipBuff[256]; - int knum, cLen; - -// Make sure we have enought space left in the buffer -// - if (dLen > (int)sizeof(rrData->Data) - (16+myNLen)) - {Fatal(einfo,"Encode",ENOBUFS,"Insufficient buffer space for credentials."); - return (XrdSecCredentials *)0; - } - -// We first insert our IP address which will be followed by our host name. -// New version of the protocol will use the IP address, older version will -// use the last hostname we actually send. -// - if (einfo && (errEnv = einfo->getEnv()) && (myIP = errEnv->Get("sockname"))) - {*eodP++ = XrdSecsssRR_Data::theHost; - if (!strncmp(myIP, "[::ffff:", 8)) - {strcpy(ipBuff, "[::"); strcpy(ipBuff+3, myIP+8); myIP = ipBuff;} - XrdOucPup::Pack(&eodP, myIP); - dLen = eodP - (char *)rrData; - } else { - if (epAddr.SockFD() > 0 - && XrdNetUtils::IPFormat(-(epAddr.SockFD()), ipBuff, sizeof(ipBuff), - XrdNetUtils::oldFmt)) - {*eodP++ = XrdSecsssRR_Data::theHost; - XrdOucPup::Pack(&eodP, ipBuff); - dLen = eodP - (char *)rrData; - } else { - CLDBG("No IP address to encode (" <<(einfo==0) <<(errEnv==0) - <<(myIP==0) <<")!"); - } - } - -// Add in our host name for source verification -// - if (myName) - {*eodP++ = XrdSecsssRR_Data::theHost; - XrdOucPup::Pack(&eodP, myName, myNLen); - dLen = eodP - (char *)rrData; - } - -// Make sure we have at least 128 bytes of encrypted data -// - if (dLen < 128) - {char rBuff[128]; - int rLen = 128 - dLen; - *eodP++ = XrdSecsssRR_Data::theRand; - XrdSecsssKT::genKey(rBuff, rLen); - if (!(*rBuff)) *rBuff = ~(*rBuff); - XrdOucPup::Pack(&eodP, rBuff, rLen); - dLen = eodP - (char *)rrData; - } - -// Complete the packet -// - XrdSecsssKT::genKey(rrData->Rand, sizeof(rrData->Rand)); - rrData->GenTime = htonl(myClock()); - memset(rrData->Pad, 0, sizeof(rrData->Pad)); - -// Allocate an output buffer -// - cLen = hdrSZ + dLen + Crypto->Overhead(); - if (!(credP = (char *)malloc(cLen))) - {Fatal(einfo, "Encode", ENOMEM, "Insufficient memory for credentials."); - return (XrdSecCredentials *)0; - } - -// Copy the header and encrypt the data -// - memcpy(credP, (const void *)rrHdr, hdrSZ); - if ((dLen = Crypto->Encrypt(encKey.Data.Val, encKey.Data.Len, (char *)rrData, - dLen, credP+hdrSZ, cLen-hdrSZ)) <= 0) - {Fatal(einfo, "Encode", -dLen, "Unable to encrypt credentials."); - return (XrdSecCredentials *)0; - } - -// Return new credentials -// - dLen += hdrSZ; knum = encKey.Data.ID&0x7fffffff; - CLDBG("Ret " <Find(lidP, rrData.Data, sizeof(rrData.Data))) <= 0) - return Fatal(einfo, "getCred", ESRCH, "No loginid mapping."); - -// All done -// - rrData.Options = XrdSecsssRR_Data::UseData; - return XrdSecsssRR_Data_HdrLen + dLen; -} - -/******************************************************************************/ -/* g e t L I D */ -/******************************************************************************/ - -char *XrdSecProtocolsss::getLID(char *buff, int blen) -{ - const char *dot; - -// Extract out the loginid from the trace id -// - if (!Entity.tident - || !(dot = index(Entity.tident,'.')) - || dot == Entity.tident - || dot >= (Entity.tident+blen)) strcpy(buff,"nobody"); - else {int idsz = dot - Entity.tident; - strncpy(buff, Entity.tident, idsz); - *(buff+idsz) = '\0'; - } - -// All done -// - return buff; -} - -/******************************************************************************/ -/* m y C l o c k */ -/******************************************************************************/ - -int XrdSecProtocolsss::myClock() -{ - static const time_t baseTime = 1222183880; - - return static_cast(time(0)-baseTime); -} - -/******************************************************************************/ -/* s e t I D */ -/******************************************************************************/ - -char *XrdSecProtocolsss::setID(char *id, char **idP) -{ - if (id) - {int n = strlen(id); - strcpy(*idP, id); id = *idP; *idP = *idP + n + 1; - } - return id; -} - -/******************************************************************************/ -/* s e t I P */ -/******************************************************************************/ - -void XrdSecProtocolsss::setIP(XrdNetAddrInfo &endPoint) -{ - if (!endPoint.Format(urIP, sizeof(urIP), XrdNetAddrInfo::fmtAdv6)) *urIP=0; - if (!endPoint.Format(urIQ, sizeof(urIQ), XrdNetAddrInfo::fmtAdv6, - XrdNetAddrInfo::old6Map4)) *urIQ=0; - epAddr = endPoint; - Entity.addrInfo = &epAddr; -} - -/******************************************************************************/ -/* X r d S e c P r o t o c o l s s s I n i t */ -/******************************************************************************/ - -extern "C" -{ -char *XrdSecProtocolsssInit(const char mode, - const char *parms, - XrdOucErrInfo *erp) -{ - -// Set debug option -// - if (getenv("XrdSecDEBUG")) XrdSecProtocolsss::setOpts(XrdSecDEBUG); - -// Perform load-time initialization -// - return (mode == 'c' ? XrdSecProtocolsss::Load_Client(erp, parms) - : XrdSecProtocolsss::Load_Server(erp, parms)); -} -} - -/******************************************************************************/ -/* X r d S e c P r o t o c o l s s s O b j e c t */ -/******************************************************************************/ - -XrdVERSIONINFO(XrdSecProtocolsssObject,secsss); - -extern "C" -{ -XrdSecProtocol *XrdSecProtocolsssObject(const char mode, - const char *hostname, - XrdNetAddrInfo &endPoint, - const char *parms, - XrdOucErrInfo *erp) -{ - XrdSecProtocolsss *prot; - int Ok; - -// Get a new protocol object -// - if (!(prot = new XrdSecProtocolsss(endPoint.Name(hostname), endPoint))) - XrdSecProtocolsss::Fatal(erp, "sss_Object", ENOMEM, - "Secsss: Insufficient memory for protocol."); - else {Ok = (mode == 'c' ? prot->Init_Client(erp, parms) - : prot->Init_Server(erp, parms)); - - if (!Ok) {prot->Delete(); prot = 0;} - } - -// All done -// - return (XrdSecProtocol *)prot; -} -} diff --git a/src/XrdSecsss/XrdSecProtocolsss.hh b/src/XrdSecsss/XrdSecProtocolsss.hh deleted file mode 100644 index a8bb1108249..00000000000 --- a/src/XrdSecsss/XrdSecProtocolsss.hh +++ /dev/null @@ -1,125 +0,0 @@ -#ifndef _SECPROTOCOLSSS_ -#define _SECPROTOCOLSSS_ -/******************************************************************************/ -/* */ -/* X r d S e c P r o t o c o l s s s . h h */ -/* */ -/* (c) 2008 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdCrypto/XrdCryptoLite.hh" -#include "XrdNet/XrdNetAddrInfo.hh" -#include "XrdSec/XrdSecInterface.hh" -#include "XrdSecsss/XrdSecsssID.hh" -#include "XrdSecsss/XrdSecsssKT.hh" -#include "XrdSecsss/XrdSecsssRR.hh" - -class XrdOucErrInfo; - -class XrdSecProtocolsss : public XrdSecProtocol -{ -public: -friend class XrdSecProtocolDummy; // Avoid stupid gcc warnings about destructor - - - int Authenticate (XrdSecCredentials *cred, - XrdSecParameters **parms, - XrdOucErrInfo *einfo=0); - - void Delete(); - -static int eMsg(const char *epn, int rc, const char *txt1, - const char *txt2=0, const char *txt3=0, - const char *txt4=0); - -static int Fatal(XrdOucErrInfo *erP, const char *epn, int rc, - const char *etxt); - - XrdSecCredentials *getCredentials(XrdSecParameters *parms=0, - XrdOucErrInfo *einfo=0); - - int Init_Client(XrdOucErrInfo *erp, const char *Parms); - - int Init_Server(XrdOucErrInfo *erp, const char *Parms); - -static char *Load_Client(XrdOucErrInfo *erp, const char *Parms); - -static char *Load_Server(XrdOucErrInfo *erp, const char *Parms); - -static void setOpts(int opts) {options = opts;} - - XrdSecProtocolsss(const char *hname, XrdNetAddrInfo &endPoint) - : XrdSecProtocol("sss"), - keyTab(0), Crypto(0), idBuff(0), Sequence(0) - {urName = strdup(hname); setIP(endPoint);} - -struct Crypto {const char *cName; char cType;}; - -private: - ~XrdSecProtocolsss() {} // Delete() does it all - -int Decode(XrdOucErrInfo *error, XrdSecsssKT::ktEnt &decKey, - char *iBuff, XrdSecsssRR_Data *rrData, int iSize); -XrdSecCredentials *Encode(XrdOucErrInfo *error, XrdSecsssKT::ktEnt &encKey, - XrdSecsssRR_Hdr *rrHdr, XrdSecsssRR_Data *rrData, - int dLen); -int getCred(XrdOucErrInfo *, XrdSecsssRR_Data &); -int getCred(XrdOucErrInfo *, XrdSecsssRR_Data &, XrdSecParameters *); -char *getLID(char *buff, int blen); -static -XrdCryptoLite *Load_Crypto(XrdOucErrInfo *erp, const char *eN); -static -XrdCryptoLite *Load_Crypto(XrdOucErrInfo *erp, const char eT); -int myClock(); -char *setID(char *id, char **idP); -void setIP(XrdNetAddrInfo &endPoint); - -static struct Crypto CryptoTab[]; - -static const char *myName; -static int myNLen; - char *urName; - char urIP[48]; // New format - char urIQ[48]; // Old format -static int options; -static int isMutual; -static int deltaTime; -static int ktFixed; - XrdNetAddrInfo epAddr; - -static XrdSecsssKT *ktObject; // Both: Default Key Table object - XrdSecsssKT *keyTab; // Both: Active Key Table - -static XrdCryptoLite *CryptObj; // Both: Default Cryptogrophy object - XrdCryptoLite *Crypto; // Both: Active Cryptogrophy object - -static XrdSecsssID *idMap; // Client: Registry - char *idBuff; // Server: Underlying buffer for XrdSecEntity -static char *staticID; // Client: Static identity -static int staticIDsz;// Client: Static identity length - int Sequence; // Client: Check for sequencing -}; -#endif diff --git a/src/XrdSecsss/XrdSecsssAdmin.cc b/src/XrdSecsss/XrdSecsssAdmin.cc deleted file mode 100644 index 76d48e9ebc5..00000000000 --- a/src/XrdSecsss/XrdSecsssAdmin.cc +++ /dev/null @@ -1,530 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S e c s s s A d m i n . c c */ -/* */ -/* (c) 2008 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "XrdOuc/XrdOucErrInfo.hh" -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdSys/XrdSysPlatform.hh" -#include "XrdSys/XrdSysTimer.hh" - -#include "XrdSecsss/XrdSecsssKT.hh" - -/******************************************************************************/ -/* D e f i n e s */ -/******************************************************************************/ - -#define eMsg(x) cerr < 1 && '-' == *argv[1]) - while ((c = getopt(argc,argv,validOpts)) - && ((unsigned char)c != 0xff)) - { switch(c) - { - case 'd': Opt.Debug = 1; - break; - case 'g': Opt.KeyGrup = optarg; - break; - case 'h': if ((Opt.Keep = atoi(optarg)) <= 0) Usage(1, "-s", optarg); - break; - case 'k': Opt.KeyName = optarg; - break; - case 'l': if ((Opt.KeyLen = atoi(optarg)) <= 0 - || Opt.KeyLen > XrdSecsssKT::ktEnt::maxKLen) - Usage(1, "-l", optarg); - break; - case 'n': if ((Opt.KeyNum = atoi(optarg)) <= 0) Usage(1, "-n", optarg); - break; - case 's': if ((int)strlen(optarg) > 1 || !index("cgknux", *optarg)) - Usage(1, "-s", optarg); - Opt.Sort = *optarg; - break; - case 'u': Opt.KeyUser = optarg; - break; - case 'x': if ((Opt.Expdt = getXDate(optarg)) < 0 - || Opt.Expdt < (time(0)+60)) Usage(1, "-x", optarg); - break; - default: if (index(validOpts, optopt)) Usage(1, argv[optind-1], optarg); - else {eMsg("Invalid option '" <= argc) {eMsg("Action not specified."); Usage(1);} - -// Verify the action -// - if (!strcmp(argv[optind], "add")) doIt = doAdd; - else if (!strcmp(argv[optind], "install")) doIt = doInst; - else if (!strcmp(argv[optind], "del")) doIt = doDel; - else if (!strcmp(argv[optind], "list")) doIt = doList; - else Usage(1, "parameter", argv[optind]); - Opt.Action = argv[optind++]; - -// Make sure keyname is not too long -// - if (Opt.KeyName && (int)strlen(Opt.KeyName) >= XrdSecsssKT::ktEnt::NameSZ) - {eMsg("Key name must be less than " <= XrdSecsssKT::ktEnt::UserSZ) - {eMsg("User name must be less than " <= XrdSecsssKT::ktEnt::GrupSZ) - {eMsg("group name must be less than " <(theVal); - } - -// Do a date conversion -// - eP = strptime(cDate, "%D", &myTM); - if (*eP) return -1; - return mktime(&myTM); -} - -/******************************************************************************/ -/* i s N o */ -/******************************************************************************/ - -int isNo(int dflt, const char *Msg1, const char *Msg2, const char *Msg3) -{ - char Answer[8]; - - cerr <Data.Name, (Opt.KeyName ? Opt.KeyName : "nowhere")); - strcpy(ktEnt->Data.User, (Opt.KeyUser ? Opt.KeyUser : "nobody")); - strcpy(ktEnt->Data.Grup, (Opt.KeyGrup ? Opt.KeyGrup : "nogroup")); - if (Opt.KeyLen > XrdSecsssKT::ktEnt::maxKLen) - ktEnt->Data.Len = XrdSecsssKT::ktEnt::maxKLen; - else if (Opt.KeyLen < 4) ktEnt->Data.Len = 4; - else ktEnt->Data.Len = Opt.KeyLen/4*4; - ktEnt->Data.Exp = Opt.Expdt; - Opt.kTab->addKey(*ktEnt); - -// Now rewrite the file -// - if ((retc = Opt.kTab->Rewrite(Opt.Keep, numKeys, numTot, numExp))) - {eMsg("Unable to add key to '" <(Opt.KeyNum); - -// Delete the keys from the key table -// - if (!(numDel = Opt.kTab->delKey(ktEnt))) - {eMsg("No matching key(s) found."); - return 4; - } - -// It's possible that all of the keys were deleted. Check for that -// - if (Opt.kTab->keyList() == 0) - {if (isNo(1, "No keys will remain in ", Opt.KeyFile, - ". Delete file? (n | y): ")) - {eMsg("No keys deleted!"); return 2;} - unlink(Opt.KeyFile); - return 0; - } - -// Now rewrite the file -// - if ((retc = Opt.kTab->Rewrite(Opt.Keep, numKeys, numTot, numExp))) - {eMsg("Unable to del key from '" <keyList(); - while(ktP) - {if (!XrdSecsssAdmin_isKey(Opt, ktP)) ktP->Data.Name[0] = '\0'; - else numKeys++; - ktP = ktP->Next; - } - if (!numKeys) - {eMsg("No keys named " <setPath(Opt.KeyFile); - if ((retc = Opt.kTab->Rewrite(Opt.Keep, numKeys, numTot, numExp))) - {eMsg("Unable to install keytab '" <Data.Name, Opt.KeyName)) return 0; - if (Opt.KeyUser && strcmp(ktP->Data.User, Opt.KeyUser)) return 0; - if (Opt.KeyGrup && strcmp(ktP->Data.Grup, Opt.KeyGrup)) return 0; - return 1; -} - -/******************************************************************************/ -/* X r d S e c s s s A d m i n _ H e r e */ -/******************************************************************************/ - -int XrdSecsssAdmin_Here(char sType, XrdSecsssKT::ktEnt *ktX, - XrdSecsssKT::ktEnt *ktS) -{ - int n; - char *sf1, *sf2; - - switch(sType) - {case 'c': return ktX->Data.Crt < ktS->Data.Crt; - case 'g': sf1 = ktX->Data.Grup; sf2 = ktS->Data.Grup; break; - case 'k': sf1 = ktX->Data.Name; sf2 = ktS->Data.Name; break; - case 'n': return (ktX->Data.ID & 0x7fffffff) < (ktS->Data.ID & 0x7fffffff); - case 'u': sf1 = ktX->Data.User; sf2 = ktS->Data.User; break; - case 'x': return ktX->Data.Exp < ktS->Data.Exp; - default: return 0; - } - - if ((n = strcmp(sf1, sf2))) return n < 0; - return (ktX->Data.ID & 0x7fffffff) < (ktS->Data.ID & 0x7fffffff); -} - -/******************************************************************************/ -/* X r d S e c s s s A d m i n _ l s t K e y */ -/******************************************************************************/ - -int XrdSecsssAdmin_lstKey(XrdsecsssAdmin_Opts &Opt) -{ - static const char Hdr1[] = - " Number Len Date/Time Created Expires Keyname User & Group\n"; -// 12345678901 123 mm/dd/yy hh:mm:ss mm/dd/yy - static const char Hdr2[] = - " ------ --- --------- ------- -------- -------\n"; - - extern int XrdSecsssAdmin_isKey(XrdsecsssAdmin_Opts &Opt, - XrdSecsssKT::ktEnt *ktP); - XrdOucErrInfo eInfo; - XrdSecsssKT::ktEnt *ktP, *ktSort = 0, *ktS, *ktSP, *ktX; - char crfmt[] = "%D %T", exfmt[] = "%D"; - char buff[128], crbuff[64], exbuff[16]; - int retc, pHdr = 1; - -// Allocate the initial keytab -// - Opt.kTab = new XrdSecsssKT(&eInfo, Opt.KeyFile, XrdSecsssKT::isAdmin); - if ((retc = eInfo.getErrInfo())) - {if (retc == ENOENT) - {eMsg("Keyfile '" <keyList())) - {ktSort = ktP; ktP = ktP->Next; ktSort->Next = 0;} - -// Sort the list -// - while(ktP) - {ktS = ktSort; ktSP = 0; ktX = ktP; ktP = ktP->Next; ktX->Next = 0; - while(ktS) - {if (XrdSecsssAdmin_Here(Opt.Sort, ktX, ktS)) - {if (ktSP) {ktX->Next = ktS; ktSP->Next = ktX;} - else {ktX->Next = ktSort; ktSort = ktX;} - break; - } - ktSP = ktS; ktS = ktS->Next; - } - if (!ktS) ktSP->Next = ktX; - } - -// List the keys -// - ktP = ktSort; - while(ktP) - {if (XrdSecsssAdmin_isKey(Opt, ktP)) - {if (pHdr) {cout <Data.ID & 0x7fffffff), ktP->Data.Len); - strftime(crbuff, sizeof(crbuff), crfmt, localtime(&ktP->Data.Crt)); - if (!ktP->Data.Exp) strcpy(exbuff, "--------"); - else strftime(exbuff,sizeof(exbuff),exfmt,localtime(&ktP->Data.Exp)); - cout <Data.Name <<' ' - <Data.User <<' ' <Data.Grup <Next; - } - -// Check if we printed anything -// - if (pHdr) - {if (Opt.KeyName) eMsg(Opt.KeyName <<" key not found in " <. */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include - -#include "XrdSecsss/XrdSecsssID.hh" -#include "XrdSecsss/XrdSecsssRR.hh" - -#include "XrdOuc/XrdOucPup.hh" -#include "XrdOuc/XrdOucUtils.hh" -#include "XrdSys/XrdSysHeaders.hh" - -/******************************************************************************/ -/* D e f i n e s */ -/******************************************************************************/ - -#define XRDSECSSSID "XrdSecsssID" - -XrdSysMutex XrdSecsssID::InitMutex; - -/******************************************************************************/ -/* C o n s t r u c t o r */ -/******************************************************************************/ - -XrdSecsssID::XrdSecsssID(authType aType, XrdSecEntity *idP) : defaultID(0) -{ - static char buff[64]; - union {unsigned long val; XrdSecsssID *myP;} p2i; - -// Check if we have initialized already. If so, indicate warning -// - InitMutex.Lock(); - if (getenv(XRDSECSSSID)) - {InitMutex.UnLock(); - cerr <<"SecsssID: Already instantiated; new instance ineffective!" <iLen > Blen) {myMutex.UnLock(); return 0;} - -// Return the data -// - memcpy(Buff, fP->iData, fP->iLen); - rc = fP->iLen; - myMutex.UnLock(); - return rc; -} - -/******************************************************************************/ -/* g e t O b j */ -/******************************************************************************/ - -XrdSecsssID *XrdSecsssID::getObj(authType &aType, char **dID, int &dIDsz) -{ - int freeIDP = 0; - sssID *idP; - char *eP, *xP; - union {long long llval; long lval; XrdSecsssID *idP;} i2p; - -// Prevent changes -// - InitMutex.Lock(); - -// Convert to pointer -// - aType = idStatic; - if ((eP = getenv(XRDSECSSSID)) && *eP) - {if (sizeof(XrdSecsssID *) > 4) i2p.llval = strtoll(eP, &xP, 16); - else i2p.lval = strtol (eP, &xP, 16); - if (*xP) i2p.idP = 0; - else aType = i2p.idP->myAuth; - } else i2p.idP = 0; - -// Establish the default ID -// - if (!i2p.idP || !(idP = i2p.idP->defaultID)) - {idP = genID(aType == idDynamic); freeIDP = 1;} - -// Copy out the default id to the caller -// - dIDsz = idP->iLen; - *dID = (char *)malloc(dIDsz); - memcpy(*dID, idP->iData, dIDsz); - -// Return result -// - InitMutex.UnLock(); - if (freeIDP) free(idP); - return i2p.idP; -} - -/******************************************************************************/ -/* R e g i s t e r */ -/******************************************************************************/ - -int XrdSecsssID::Register(const char *lid, XrdSecEntity *eP, int doRep) -{ - sssID *idP; - int rc; - int hOpt = (doRep ? Hash_replace : Hash_default) | Hash_dofree; - -// Check if we are simply deleting an entry -// - if (!eP) - {myMutex.Lock(); Registry.Del(lid); myMutex.UnLock(); return 1;} - -// Generate an ID and add it to registry -// - if (!(idP = genID(eP))) return 0; - myMutex.Lock(); - rc = (Registry.Add(lid, idP, hOpt) ? 0 : 1); - myMutex.UnLock(); - return rc; -} - -/******************************************************************************/ -/* P r i v a t e M e t h o d s */ -/******************************************************************************/ -/******************************************************************************/ -/* g e n I D */ -/******************************************************************************/ - -XrdSecsssID::sssID *XrdSecsssID::genID(int Secure) -{ - XrdSecEntity myID("sss"); - static const int pgSz = 256; - char pBuff[pgSz], gBuff[pgSz]; - -// Use either our own uid/gid or a generic -// - myID.name = (Secure || XrdOucUtils:: UserName(geteuid(), pBuff, pgSz)) - ? (char *)"nobody" : pBuff; - myID.grps = (Secure || XrdOucUtils::GroupName(getegid(), gBuff, pgSz) == 0) - ? (char *)"nogroup" : gBuff; - -// Just return the sssID -// - return genID(&myID); -} - -/******************************************************************************/ - -XrdSecsssID::sssID *XrdSecsssID::genID(XrdSecEntity *eP) -{ - sssID *idP; - char *bP; - int tLen; - -// Calculate the length needed for the entity (4 bytes overhead for each item) -// - tLen = (eP->name ? strlen(eP->name) + 4 : 0) - + (eP->vorg ? strlen(eP->vorg) + 4 : 0) - + (eP->role ? strlen(eP->role) + 4 : 0) - + (eP->grps ? strlen(eP->grps) + 4 : 0) - + (eP->endorsements ? strlen(eP->endorsements) + 4 : 0); - -// If no identity information, return failure otherwise allocate a struct -// - if (!tLen || !(idP = (sssID *)malloc(tLen + sizeof(sssID)))) return 0; - -// Now stick each entry into the iData field -// - bP = idP->iData; - if (eP->name) - {*bP++ = XrdSecsssRR_Data::theName; XrdOucPup::Pack(&bP,eP->name);} - if (eP->vorg) - {*bP++ = XrdSecsssRR_Data::theVorg; XrdOucPup::Pack(&bP,eP->vorg);} - if (eP->role) - {*bP++ = XrdSecsssRR_Data::theRole; XrdOucPup::Pack(&bP,eP->role);} - if (eP->grps) - {*bP++ = XrdSecsssRR_Data::theGrps; XrdOucPup::Pack(&bP,eP->grps);} - if (eP->endorsements) - {*bP++ = XrdSecsssRR_Data::theEndo; XrdOucPup::Pack(&bP,eP->endorsements);} - idP->iLen = bP - (idP->iData); - -// All done -// - return idP; -} diff --git a/src/XrdSecsss/XrdSecsssID.hh b/src/XrdSecsss/XrdSecsssID.hh deleted file mode 100644 index 33ae1c40600..00000000000 --- a/src/XrdSecsss/XrdSecsssID.hh +++ /dev/null @@ -1,116 +0,0 @@ -#ifndef __SecsssID__ -#define __SecsssID__ -/******************************************************************************/ -/* */ -/* X r d S e c s s s I D . h h */ -/* */ -/* (c) 2008 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -#include "XrdOuc/XrdOucHash.hh" -#include "XrdSec/XrdSecEntity.hh" -#include "XrdSys/XrdSysPthread.hh" - -// The XrdSecsssID class allows you to establish a registery to map loginid's -// to arbitrary entities. By default, the sss security protocol uses the -// username as the authenticated username and, if possible, the corresponding -// primary group membership of username (i.e., static mapping). The server is -// will ignore the username and/or the groupname unless the key is designated -// as anyuser, anygroup, respectively. By creating an instance of this class -// you can over-ride the default and map the loginid (i.e., the id supplied -// at login time which is normally the first 8-characters of the username or -// the id specified in the url; i.e., id@host) to arbitrary entities using -// the Register() method. You must create one, and only one, such instance -// prior to making any contact with a sss security enabled server. - -// In order to include XrdSecsssID methods, you should either link with -// libXrdSecsss.so (preferable) or include XrdSecsssID.o and link with -// libXrdOuc.a and libXrdSys.a. - -class XrdSecsssID -{ -public: - -// Register() creates a mapping from a loginid to an entity description. Only -// name, vo, role, group, and endorements pointers in XrdSecEntity -// are supported. To de-register a loginid, make the Ident arg zero. -// To replace an existing entry, specify 1 for doReplace argument. -// TRUE is returned if successful; FALSE otherwise (including the -// case where idDynamic was not specified in the constructor or -// doReplace is zero and the loginid has already been registered). -// -int Register(const char *loginid, XrdSecEntity *Ident, int doReplace=0); - -// Find() is an internal look-up method that returns the identification -// string in the provided buffer corresponding to the loginid. -// If loginid is registered and the data will fit into the buffer the -// length moved into the buffer is returned. Otherwise, the default ID -// is moved into the buffer and the length copied is returned. If that -// is not possible, 0 is returned. -// -int Find(const char *loginid, char *Buff, int Blen); - -// A single instance of this class may be instantiated. The first parameter -// indicates how authentication is to be handled. The second parameter provides -// either a fixed or default authenticated identity under control of the aType -// parameter, as follows: -// -enum authType {idDynamic = 0, // Mutual: Map loginid to registered identity - // Ident is default; if 0 nobody/nogroup - idStatic = 1, // 1Sided: fixed identity sent to the server - // Ident as specified; if 0 process uid/gid - // Default if XrdSecsssID not instantiated! - idStaticM = 2 // Mutual: fixed identity sent to the server - // Ident as specified; if 0 process uid/gid - }; - -// getObj() returns the address of a previous created instance of this object or -// zero if no instance exists. It also returns authType and default ID -// to be used regardless of the return value. -// -static -XrdSecsssID *getObj(authType &aType, char **dID, int &dIDsz); - - XrdSecsssID(authType aType=idStatic, XrdSecEntity *Ident=0); - - ~XrdSecsssID() {if (defaultID) free(defaultID);} - -private: - -struct sssID {int iLen; char iData[1];}; // Sized appropriately -static sssID *genID(int Secure); -static sssID *genID(XrdSecEntity *eP); - -static XrdSysMutex InitMutex; - sssID *defaultID; -XrdSysMutex myMutex; -XrdOucHash Registry; -authType myAuth; -}; -#endif diff --git a/src/XrdSecsss/XrdSecsssKT.cc b/src/XrdSecsss/XrdSecsssKT.cc deleted file mode 100644 index 8fc8a0a6e0c..00000000000 --- a/src/XrdSecsss/XrdSecsssKT.cc +++ /dev/null @@ -1,688 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S e c s s s K T . c c */ -/* */ -/* (c) 2008 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "XrdSecsss/XrdSecsssKT.hh" - -#include "XrdOuc/XrdOucErrInfo.hh" -#include "XrdOuc/XrdOucStream.hh" -#include "XrdOuc/XrdOucUtils.hh" -#include "XrdSys/XrdSysHeaders.hh" - -/******************************************************************************/ -/* S t a t i c D e f i n i t i o n s */ -/******************************************************************************/ - -int XrdSecsssKT::randFD = -1; - -/******************************************************************************/ -/* X r d S e c s s s K T R e f r */ -/******************************************************************************/ - -void *XrdSecsssKTRefresh(void *Data) -{ - XrdSecsssKT *theKT = (XrdSecsssKT *)Data; - struct timespec naptime = {theKT->RefrTime(), 0}; - -// Loop and check if keytab has changed -// - while(1) {nanosleep(&naptime, 0); theKT->Refresh();} - - return (void *)0; -} - -/******************************************************************************/ -/* C o n s t r u c t o r */ -/******************************************************************************/ - -XrdSecsssKT::XrdSecsssKT(XrdOucErrInfo *eInfo, const char *kPath, - xMode oMode, int refrInt) -{ - static const char *eText = "Unable to start keytab refresh thread"; - const char *devRand = "/dev/urandom"; - struct stat sbuf; - int retc; - -// Do some common initialization -// - ktRefID= 0; - ktPath = (kPath ? strdup(kPath) : 0); - ktList = 0; kthiID = 0; ktMode = oMode; ktRefT = (time_t)refrInt; - if (eInfo) eInfo->setErrCode(0); - -// Prepare /dev/random if we have it -// - if (stat(devRand, &sbuf)) devRand = "/dev/random"; - if ((randFD = open(devRand, O_RDONLY)) < 0 - && oMode != isClient && errno != ENOENT) - eMsg("sssKT",errno,"Unable to generate random key"," opening ",devRand); - -// First get the stat information for the file -// - if (!kPath) - {if (oMode != isAdmin) - {eMsg("sssKT", -1, "Keytable path not specified."); - if (eInfo) eInfo->setErrInfo(EINVAL, "Keytable path missing."); - return; - } - sbuf.st_mtime = 0; sbuf.st_mode = S_IRWXU; - } else if (stat(kPath, &sbuf)) - {if (eInfo) eInfo->setErrInfo(errno, "Keytable not found"); - if (errno != ENOENT || oMode != isAdmin) - eMsg("sssKT",errno,"Unable process keytable ",kPath); - return; - } - -// Now read in the whole key table and start possible refresh thread -// - if ((ktList = getKeyTab(eInfo, sbuf.st_mtime, sbuf.st_mode)) - && (oMode != isAdmin) && (!eInfo || eInfo->getErrInfo() == 0)) - {if ((retc = XrdSysThread::Run(&ktRefID,XrdSecsssKTRefresh, (void *)this, - XRDSYSTHREAD_HOLD))) - {eMsg("sssKT", errno, eText); eInfo->setErrInfo(-1, eText);} - } -} - -/******************************************************************************/ -/* D e s t r u c t o r */ -/******************************************************************************/ - -XrdSecsssKT::~XrdSecsssKT() -{ - ktEnt *ktP; - void *Dummy; - -// Lock against others -// - myMutex.Lock(); - -// Kill the refresh thread first -// - if (ktRefID && !XrdSysThread::Kill(ktRefID)) - XrdSysThread::Join(ktRefID, &Dummy); - ktRefID= 0; - -// Now we can safely clean up -// - if (ktPath) {free(ktPath); ktPath = 0;} - - while((ktP = ktList)) {ktList = ktList->Next; delete ktP;} - - myMutex.UnLock(); -} - -/******************************************************************************/ -/* a d d K e y */ -/******************************************************************************/ - -void XrdSecsssKT::addKey(ktEnt &ktNew) -{ - ktEnt *ktPP = 0, *ktP; - -// Generate a key for this entry -// - genKey(ktNew.Data.Val, ktNew.Data.Len); - ktNew.Data.Crt = time(0); - ktNew.Data.ID = static_cast(ktNew.Data.Crt & 0x7fffffff) << 32L - | static_cast(++kthiID); - -// Locate place to insert this key -// - ktP = ktList; - while(ktP && !isKey(*ktP, &ktNew, 0)) {ktPP = ktP; ktP = ktP->Next;} - -// Now chain in the entry -// - if (ktPP) ktPP->Next = &ktNew; - else ktList = &ktNew; - ktNew.Next = ktP; -} - -/******************************************************************************/ -/* d e l K e y */ -/******************************************************************************/ - -int XrdSecsssKT::delKey(ktEnt &ktDel) -{ - ktEnt *ktN, *ktPP = 0, *ktP = ktList; - int nDel = 0; - -// Remove all matching keys -// - while(ktP) - {if (isKey(ktDel, ktP)) - {if (ktPP) ktPP->Next = ktP->Next; - else ktList = ktP->Next; - ktN = ktP; ktP = ktP->Next; delete ktN; nDel++; - } else {ktPP = ktP; ktP = ktP->Next;} - } - - return nDel; -} - -/******************************************************************************/ -/* g e t K e y */ -/******************************************************************************/ - -int XrdSecsssKT::getKey(ktEnt &theEnt) -{ - ktEnt *ktP, *ktN; - -// Lock the keytab to prevent modification -// - myMutex.Lock(); - ktP = ktList; - -// Find first key by key name (used normally by clients) or by keyID -// - if (!*theEnt.Data.Name) - {if (theEnt.Data.ID >= 0) - while(ktP && ktP->Data.ID != theEnt.Data.ID) ktP = ktP->Next; - } - else {while(ktP && strcmp(ktP->Data.Name,theEnt.Data.Name)) ktP=ktP->Next; - while(ktP && ktP->Data.Exp <= time(0)) - {if (!(ktN=ktP->Next) - || strcmp(ktN->Data.Name,theEnt.Data.Name)) break; - ktP = ktN; - } - } - -// If we found a match, export it -// - if (ktP) theEnt = *ktP; - myMutex.UnLock(); - -// Indicate if key expired -// - if (!ktP) return ENOENT; - return (theEnt.Data.Exp && theEnt.Data.Exp <= time(0) ? -1 : 0); -} - -/******************************************************************************/ -/* g e n F N */ -/******************************************************************************/ - -char *XrdSecsssKT::genFN() -{ - static char fnbuff[1040]; - const char *pfx; - -// Get the path prefix -// - if (!(pfx = getenv("HOME")) || !*pfx) pfx = ""; - -// Format the name -// - snprintf(fnbuff, sizeof(fnbuff), "%s/.xrd/sss.keytab", pfx); - return fnbuff; -} - -/******************************************************************************/ -/* g e n K e y */ -/******************************************************************************/ - -void XrdSecsssKT::genKey(char *kBP, int kLen) -{ - struct timeval tval; - int kTemp; - -// See if we can directly service the key. Make sure that we get some entropy -// because some /dev/random devices start out really cold. -// - if (randFD >= 0) - {char *buffP = kBP; - int i, Got, Want = kLen, zcnt = 0, maxZ = kLen*25/100; - while(Want) - do { {do {Got = read(randFD, buffP, Want);} - while(Got < 0 && errno == EINTR); - if (Got > 0) {buffP += Got; Want -= Got;} - } - } while(Got > 0 && Want); - if (!Want) - {for (i = 0; i < kLen; i++) if (!kBP[i]) zcnt++; - if (zcnt <= maxZ) return; - } - } - -// Generate a seed -// - gettimeofday(&tval, 0); - if (tval.tv_usec == 0) tval.tv_usec = tval.tv_sec; - tval.tv_usec = tval.tv_usec ^ getpid(); - srand48(static_cast(tval.tv_usec)); - -// Now generate the key (we ignore he fact that longs may be 4 or 8 bytes) -// - while(kLen > 0) - {kTemp = mrand48(); - memcpy(kBP, &kTemp, (4 > kLen ? kLen : 4)); - kBP += 4; kLen -= 4; - } -} - -/******************************************************************************/ -/* R e f r e s h */ -/******************************************************************************/ - -void XrdSecsssKT::Refresh() -{ - XrdOucErrInfo eInfo; - ktEnt *ktNew, *ktOld, *ktNext; - struct stat sbuf; - int retc = 0; - -// Get change time of keytable and if changed, update it -// - if (stat(ktPath, &sbuf) == 0) - {if (sbuf.st_mtime == ktMtime) return; - if ((ktNew = getKeyTab(&eInfo, sbuf.st_mtime, sbuf.st_mode)) - && eInfo.getErrInfo() == 0) - {myMutex.Lock(); ktOld = ktList; ktList = ktNew; myMutex.UnLock(); - } else ktOld = ktNew; - while(ktOld) {ktNext = ktOld->Next; delete ktOld; ktOld = ktNext;} - if ((retc == eInfo.getErrInfo()) == 0) return; - } else retc = errno; - -// Refresh failed -// - eMsg("Refresh",retc,"Unable to refresh keytable",ktPath); -} - -/******************************************************************************/ -/* R e w r i t e */ -/******************************************************************************/ - -int XrdSecsssKT::Rewrite(int Keep, int &numKeys, int &numTot, int &numExp) -{ - char tmpFN[2048], buff[2048], kbuff[4096], *Slash; - int ktFD, numID = 0, n, retc = 0; - ktEnt ktCurr, *ktP, *ktN; - mode_t theMode = fileMode(ktPath); - -// Invoke mkpath in case the path is missing -// - strcpy(tmpFN, ktPath); - if ((Slash = rindex(tmpFN, '/'))) *Slash = '\0'; - retc = XrdOucUtils::makePath(tmpFN,S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH); - if (retc) return (retc < 0 ? -retc : retc); - if (Slash) *Slash = '/'; - -// Construct temporary filename -// - sprintf(buff, ".%d", static_cast(getpid())); - strcat(tmpFN, buff); - -// Open the file for output -// - if ((ktFD = open(tmpFN, O_WRONLY|O_CREAT|O_TRUNC, theMode)) < 0) - return errno; - -// Write all of the keytable -// - ktCurr.Data.Name[0] = ktCurr.Data.User[0] = ktCurr.Data.Grup[0] = 3; - ktN = ktList; numKeys = numTot = numExp = 0; - while((ktP = ktN)) - {ktN = ktN->Next; numTot++; - if (ktP->Data.Name[0] == '\0') continue; - if (ktP->Data.Exp && ktP->Data.Exp <= time(0)) {numExp++; continue;} - if (!isKey(ktCurr, ktP, 0)) {ktCurr.NUG(ktP); numID = 0;} - else if (Keep && numID >= Keep) continue; - n = sprintf(buff, "%s0 u:%s g:%s n:%s N:%lld c:%ld e:%ld f:%lld k:", - (numKeys ? "\n" : ""), - ktP->Data.User,ktP->Data.Grup,ktP->Data.Name,ktP->Data.ID, - ktP->Data.Crt, ktP->Data.Exp, ktP->Data.Flags); - numID++; numKeys++; keyB2X(ktP, kbuff); - if (write(ktFD, buff, n) < 0 - || write(ktFD, kbuff, ktP->Data.Len*2) < 0) break; - } - -// Check for errors -// - if (ktP) retc = errno; - else if (!numKeys) retc = ENODATA; - -// Atomically trounce the original file if we can -// - close(ktFD); - if (!retc && rename(tmpFN, ktPath) < 0) retc = errno; - -// All done -// - unlink(tmpFN); - return retc; -} - -/******************************************************************************/ -/* P r i v a t e M e t h o d s */ -/******************************************************************************/ -/******************************************************************************/ -/* e M s g */ -/******************************************************************************/ - -int XrdSecsssKT::eMsg(const char *epname, int rc, - const char *txt1, const char *txt2, - const char *txt3, const char *txt4) -{ - cerr <<"Secsss (" << epname <<"): "; - cerr <0) {cerr <<"; " <setErrInfo(EACCES, "Keytab file is not secure!"); - eMsg("getKeyTab",-1,"Unable to process ",ktPath,"; file is not secure!"); - return 0; - } - -// Open the file -// - if (ktPath) - {if ((ktFD = open(ktPath, O_RDONLY)) < 0) - {if (eInfo) eInfo->setErrInfo(errno, "Unable to open keytab file."); - eMsg("getKeyTab", errno, "Unable to open ", ktPath); - return 0; - } else ktFN = ktPath; - } else {ktFD = dup(STDIN_FILENO); ktFN = "stdin";} - -// Attach the fd to the stream -// - myKT.Attach(ktFD); - -// Now start reading the keytable which always has the form: -// -// -// -do{while((lp = myKT.GetLine())) - {recno++; What = 0; - if (!*lp) continue; - if (!(tp = myKT.GetToken()) || (strcmp("0", tp) && strcmp("1", tp))) - {What = "keytable format missing or unsupported"; break;} - if (!(ktNew = ktDecode0(myKT, eInfo))) - {What = (eInfo ? eInfo->getErrText(): "invalid data"); break;} - if (ktMode!=isAdmin && ktNew->Data.Exp && ktNew->Data.Exp <= time(0)) - {delete ktNew; continue;} - tmpID = static_cast(ktNew->Data.ID & 0x7fffffff); - if (tmpID > kthiID) kthiID = tmpID; - - ktP = ktBase; ktPP = 0; - while(ktP && !isKey(*ktP, ktNew, 0)) {ktPP=ktP; ktP=ktP->Next;} - if (!ktP) {ktNew->Next = ktBase; ktBase = ktNew;} - else {if (ktMode == isClient) - {if ((ktNew->Data.Exp == 0 && ktP->Data.Exp != 0) - || (ktP->Data.Exp!=0 && ktP->Data.Exp < ktNew->Data.Exp)) - ktP->Set(*ktNew); - delete ktNew; - } else { - while(ktNew->Data.Crt < ktP->Data.Crt) - {ktPP = ktP; ktP = ktP->Next; - if (!ktP || !isKey(*ktP, ktNew, 0)) break; - } - if (ktPP) {ktPP->Next = ktNew; ktNew->Next = ktP;} - else {ktNew->Next= ktBase; ktBase = ktNew;} - } - } - } - if (What) - {sprintf(rbuff, "; line %d in ", recno); - NoGo = eMsg("getKeyTab", -1, What, rbuff, ktFN); - } - } while(lp); - -// Check for problems -// - if (NoGo) {if (eInfo) eInfo->setErrInfo(EINVAL,"Invalid keytab file.");} - else if ((retc = myKT.LastError())) - {if (eInfo) eInfo->setErrInfo(retc,"Unable to read keytab file."); - NoGo = eMsg("getKeyTab", retc, "Unable to read keytab ",ktFN); - } - else if (!ktBase) - {if (eInfo) eInfo->setErrInfo(ESRCH,"Keytable is empty."); - NoGo = eMsg("getKeyTab",-1,"No keys found in ",ktFN); - } - - -// Check if an error should be returned -// - if (!NoGo) eInfo->setErrCode(0); - -// All done -// - myKT.Close(); - return ktBase; -} - -/******************************************************************************/ -/* g r p F i l e */ -/******************************************************************************/ - -mode_t XrdSecsssKT::fileMode(const char *Path) -{ - int n; - - return (!Path || (n = strlen(Path)) < 5 || strcmp(".grp", &Path[n-4]) - ? S_IRUSR|S_IWUSR : S_IRUSR|S_IWUSR|S_IRGRP); -} - -/******************************************************************************/ -/* i s K e y */ -/******************************************************************************/ - -int XrdSecsssKT::isKey(ktEnt &ktRef, ktEnt *ktP, int Full) -{ - if (*ktRef.Data.Name && strcmp(ktP->Data.Name, ktRef.Data.Name)) return 0; - if (*ktRef.Data.User && strcmp(ktP->Data.User, ktRef.Data.User)) return 0; - if (*ktRef.Data.Grup && strcmp(ktP->Data.Grup, ktRef.Data.Grup)) return 0; - if (Full && ktRef.Data.ID > 0 - && (ktP->Data.ID & 0x7fffffff) != ktRef.Data.ID) return 0; - return 1; -} - -/******************************************************************************/ -/* k e y B 2 X */ -/******************************************************************************/ - -void XrdSecsssKT::keyB2X(ktEnt *theKT, char *buff) -{ - static const char xTab[] = "0123456789abcdef"; - int kLen = theKT->Data.Len; - char *kP = theKT->Data.Val, Val; - -// Convert -// - while(kLen--) - {Val = *kP++; - *buff++ = xTab[(Val>>4) & 0x0f]; - *buff++ = xTab[ Val & 0x0f]; - } - *buff = '\0'; -} - -/******************************************************************************/ -/* k e y X 2 B */ -/******************************************************************************/ - -void XrdSecsssKT::keyX2B(ktEnt *theKT, char *xKey) -{ -// 0 1 2 3 4 5 6 7 - static const char xtab[] = {10, 10, 11, 12, 13, 14, 15, 15}; - int n = strlen(xKey); - char *kp, kByte; - -// Make sure we don't overflow -// - n = (n%2 ? (n+1)/2 : n/2); - if (n > ktEnt::maxKLen) n = ktEnt::maxKLen; - kp = theKT->Data.Val; - theKT->Data.Val[n-1] = 0; - -// Now convert (we need this to be just consistent not necessarily correct) -// - while(*xKey) - {if (*xKey <= '9') kByte = (*xKey & 0x0f) << 4; - else kByte = xtab[*xKey & 0x07] << 4; - xKey++; - if (*xKey <= '9') kByte |= (*xKey & 0x0f); - else kByte |= xtab[*xKey & 0x07]; - *kp++ = kByte; xKey++; - } - -// Return data via the structure -// - theKT->Data.Len = n; -} - -/******************************************************************************/ -/* k t D e c o d e 0 */ -/******************************************************************************/ - -XrdSecsssKT::ktEnt *XrdSecsssKT::ktDecode0(XrdOucStream &kTab, - XrdOucErrInfo *eInfo) -{ - static const short haveCRT = 0x0001; - static const short haveEXP = 0x0002; - static const short isTIMET = 0x0003; - static const short haveGRP = 0x0004; - static const short haveKEY = 0x0008; - static const short haveNAM = 0x0010; - static const short haveNUM = 0x0020; - static const short haveUSR = 0x0040; - static const short haveFLG = 0x0080; - - static struct - {const char *Name; size_t Offset; int Ctl; short What; char Tag;} - ktDesc[] = { - {"crtdt", offsetof(ktEnt::ktData,Crt), 0, haveCRT, 'c'}, - {"expdt", offsetof(ktEnt::ktData,Exp), 0, haveEXP, 'e'}, - {"flags", offsetof(ktEnt::ktData,Flags),0, haveFLG, 'f'}, - {"group", offsetof(ktEnt::ktData,Grup), ktEnt::GrupSZ, haveGRP, 'g'}, - {"keyval", offsetof(ktEnt::ktData,Val), ktEnt::maxKLen*2, haveKEY, 'k'}, - {"keyname", offsetof(ktEnt::ktData,Name), ktEnt::NameSZ, haveNAM, 'n'}, - {"keynum", offsetof(ktEnt::ktData,ID), 0, haveNUM, 'N'}, - {"user", offsetof(ktEnt::ktData,User), ktEnt::UserSZ, haveUSR, 'u'} - }; - static const int ktDnum = sizeof(ktDesc)/sizeof(ktDesc[0]); - - ktEnt *ktNew = new ktEnt; - const char *Prob = 0, *What = "Whatever"; - char Tag, *Dest, *ep, *tp; - long long nVal; - short Have = 0; - int i = 0; - -// Decode the record using the tags described in the above table -// -while((tp = kTab.GetToken()) && !Prob) - {Tag = *tp++; - if (*tp++ == ':') - for (i = 0; i < ktDnum; i++) - if (ktDesc[i].Tag == Tag) - {Dest = (char *)&(ktNew->Data) + ktDesc[i].Offset; - Have |= ktDesc[i].What; What = ktDesc[i].Name; - if (ktDesc[i].Ctl) - {if ((int)strlen(tp) > ktDesc[i].Ctl) Prob=" is too long"; - else if (Tag == 'k') keyX2B(ktNew, tp); - else strcpy(Dest, tp); - } else { - nVal = strtoll(tp, &ep, 10); - if (ep && *ep) Prob = " has invalid value"; - else if (ktDesc[i].What & isTIMET) - *(time_t *)Dest = static_cast(nVal); - else *(long long *)Dest = nVal; - } - } - } - -// If no problem, make sure we have the essential elements -// - if (!Prob) - {if (!(Have & haveGRP)) strcpy(ktNew->Data.Grup, "nogroup"); - if (!(Have & haveNAM)) strcpy(ktNew->Data.Name, "nowhere"); - else {int n = strlen(ktNew->Data.Name); - if (ktNew->Data.Name[n-1] == '+') - ktNew->Data.Opts |= ktEnt::noIPCK; - } - if (!(Have & haveUSR)) strcpy(ktNew->Data.User, "nobody"); - if (!(Have & haveKEY)) {What = "keyval"; Prob = " not found";} - else if (!(Have & haveNUM)) {What = "keynum"; Prob = " not found";} - } - -// Check if we have a problem -// - if (Prob) - {const char *eVec[] = {What, Prob}; - if (eInfo) eInfo->setErrInfo(-1, eVec, 2); - delete ktNew; - return 0; - } - -// Set special value options -// - if (!strcmp(ktNew->Data.Grup, "anygroup")) - ktNew->Data.Opts|=ktEnt::anyGRP; - else if (!strcmp(ktNew->Data.Grup, "usrgroup")) - ktNew->Data.Opts|=ktEnt::usrGRP; - if (!strcmp(ktNew->Data.User, "anybody")) - ktNew->Data.Opts|=ktEnt::anyUSR; - -// All done -// - return ktNew; -} diff --git a/src/XrdSecsss/XrdSecsssKT.hh b/src/XrdSecsss/XrdSecsssKT.hh deleted file mode 100644 index a29edc63fab..00000000000 --- a/src/XrdSecsss/XrdSecsssKT.hh +++ /dev/null @@ -1,138 +0,0 @@ -#ifndef __SecsssKT__ -#define __SecsssKT__ -/******************************************************************************/ -/* */ -/* X r d S e c s s s K T . h h */ -/* */ -/* (c) 2008 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include "XrdSys/XrdSysPthread.hh" - -class XrdOucErrInfo; -class XrdOucStream; - -class XrdSecsssKT -{ -public: - -class ktEnt -{ -public: - -static const int maxKLen = 128; -static const int NameSZ = 192; -static const int UserSZ = 128; -static const int GrupSZ = 64; - -struct ktData - {long long ID; - long long Flags; // Future! - time_t Crt; - time_t Exp; - int Opts; - int Len; - char Val[maxKLen];// Key strings are 1024 bits or less - char Name[NameSZ];// Key names are null terminated - char User[UserSZ];// Usr names are null terminated - char Grup[GrupSZ];// Grp names are null terminated - } Data; - -static const int anyUSR = 2; -static const int anyGRP = 4; -static const int usrGRP = 8; -static const int noIPCK =16; - - void NUG(ktEnt *ktP) {strcpy(Data.Name, ktP->Data.Name); - strcpy(Data.User, ktP->Data.User); - strcpy(Data.Grup, ktP->Data.Grup); - } - void Set(ktEnt &rhs) {Data.ID=rhs.Data.ID; Data.Len = rhs.Data.Len; - memcpy(Data.Val, rhs.Data.Val, Data.Len); - Data.Crt=rhs.Data.Crt; Data.Exp=rhs.Data.Exp; - } - ktEnt *Next; - - ktEnt() : Next(0) { Data.ID = -1; Data.Flags= 0; Data.Opts = 0; - *Data.Val = '\0'; *Data.Name = '\0'; - *Data.User= '\0'; *Data.Grup = '\0'; - } - ~ktEnt() {} -}; - -void addKey(ktEnt &ktNew); - -int delKey(ktEnt &ktDel); - -static -char *genFN(); - -static -void genKey(char *Buff, int blen); - -int getKey(ktEnt &ktEql); - -ktEnt *keyList() {return ktList;} - -void Refresh(); - -time_t RefrTime() {return ktRefT;} - -int Rewrite(int Keep, int &numKeys, int &numTot, int &numExp); - -int Same(const char *path) {return (ktPath && !strcmp(ktPath, path));} - -void setPath(const char *Path) - {if (ktPath) free(ktPath); ktPath = strdup(Path);} - -enum xMode {isAdmin = 0, isClient, isServer}; - - XrdSecsssKT(XrdOucErrInfo *, const char *, xMode, int refr=60*60); - ~XrdSecsssKT(); - -private: -int eMsg(const char *epn, int rc, const char *txt1, - const char *txt2=0, const char *txt3=0, const char *txt4=0); -ktEnt *getKeyTab(XrdOucErrInfo *eInfo, time_t Mtime, mode_t Amode); -mode_t fileMode(const char *Path); -int isKey(ktEnt &ktRef, ktEnt *ktP, int Full=1); -void keyB2X(ktEnt *theKT, char *buff); -void keyX2B(ktEnt *theKT, char *xKey); -ktEnt *ktDecode0(XrdOucStream &kTab, XrdOucErrInfo *eInfo); - -XrdSysMutex myMutex; -char *ktPath; -ktEnt *ktList; -time_t ktMtime; -xMode ktMode; -time_t ktRefT; -int kthiID; -pthread_t ktRefID; -static int randFD; -}; -#endif diff --git a/src/XrdSecsss/XrdSecsssRR.hh b/src/XrdSecsss/XrdSecsssRR.hh deleted file mode 100644 index 7c3ad7e4ab6..00000000000 --- a/src/XrdSecsss/XrdSecsssRR.hh +++ /dev/null @@ -1,78 +0,0 @@ -#ifndef __SecsssRR__ -#define __SecsssRR__ -/******************************************************************************/ -/* */ -/* X r d S e c s s s R R . h h */ -/* */ -/* (c) 2008 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -// The following is the packet header and is always unencrypted. -// -struct XrdSecsssRR_Hdr -{ -char ProtID[4]; // Protocol ID ("sss") -char Pad[3]; // Padding bytes -char EncType; // Encryption type as one of: -static const char etBFish32 = '0'; // Blowfish - -long long KeyID; // Key ID for encryption -}; - -// The data portion of the packet is encrypted with the private shared key -// It immediately follows the header and has a maximum size (defined here). -// -struct XrdSecsssRR_Data -{ -char Rand[32]; // 256-bit random string (avoid text attacks) -int GenTime; // Time data generated (time(0) - BaseTime) -char Pad[3]; // Reserved -char Options; // One of the following: -static const char UseData= 0x00; // Use the ID data as authenticated name -static const char SndLID = 0x01; // Server to send login ID - -static const int DataSz = 4040; -char Data[DataSz]; // Optional data, as follows: - -// ()+ -// -static const char theName = 0x01; -static const char theVorg = 0x02; -static const char theRole = 0x03; -static const char theGrps = 0x04; -static const char theEndo = 0x05; -// theCert = 0x06; // Reserved for future use -static const char theRand = 0x07; // Random string (ignored) -static const char theLgid = 0x10; // from server only -static const char theHost = 0x20; // from client only (required) -}; - -static const int XrdSecsssRR_Data_HdrLen = sizeof(XrdSecsssRR_Data) - - XrdSecsssRR_Data::DataSz; -#endif diff --git a/src/XrdSecunix/XrdSecProtocolunix.cc b/src/XrdSecunix/XrdSecProtocolunix.cc deleted file mode 100644 index 875b0f22747..00000000000 --- a/src/XrdSecunix/XrdSecProtocolunix.cc +++ /dev/null @@ -1,220 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S e c P r o t o c o l u n i x . c c */ -/* */ -/* (c) 2007 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include - -#include "XrdVersion.hh" - -#include "XrdNet/XrdNetAddrInfo.hh" -#include "XrdOuc/XrdOucErrInfo.hh" -#include "XrdOuc/XrdOucUtils.hh" -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdSys/XrdSysPthread.hh" -#include "XrdSec/XrdSecInterface.hh" - -/******************************************************************************/ -/* X r d S e c P r o t o c o l u n i x C l a s s */ -/******************************************************************************/ - -class XrdSecProtocolunix : public XrdSecProtocol -{ -public: -friend class XrdSecProtocolDummy; // Avoid stupid gcc warnings about destructor - - - int Authenticate (XrdSecCredentials *cred, - XrdSecParameters **parms, - XrdOucErrInfo *einfo=0); - - XrdSecCredentials *getCredentials(XrdSecParameters *parm=0, - XrdOucErrInfo *einfo=0); - - XrdSecProtocolunix(const char *hname, XrdNetAddrInfo &endPoint) - : XrdSecProtocol("unix") - {Entity.host = strdup(hname); - Entity.name = (char *)"?"; - epAddr = endPoint; - Entity.addrInfo = &epAddr; - credBuff = 0; - } - - void Delete() {delete this;} - -private: - - ~XrdSecProtocolunix() {if (credBuff) free(credBuff); - if (Entity.host) free(Entity.host); - } // via Delete() - -XrdNetAddrInfo epAddr; -char *credBuff; // Credentials buffer (server) -}; - -/******************************************************************************/ -/* C l i e n t O r i e n t e d F u n c t i o n s */ -/******************************************************************************/ -/******************************************************************************/ -/* g e t C r e d e n t i a l s */ -/******************************************************************************/ - - -XrdSecCredentials *XrdSecProtocolunix::getCredentials(XrdSecParameters *noparm, - XrdOucErrInfo *error) -{ - char Buff[512], *Bp; - int Blen, n; - -// Set protocol ID in the buffer -// - strcpy(Buff, "unix"); Bp = Buff + 5; - -// Get the username -// - if (XrdOucUtils::UserName(geteuid(), Bp, 256)) strcpy(Bp, "*"); - Bp += strlen(Bp); Blen = (Bp - Buff) + 1; - -// Get the group name -// - if ((n = XrdOucUtils::GroupName(getegid(), Bp+1, sizeof(Buff)-Blen))) - {*Bp = ' '; Blen += (n+1);} - -// Return the credentials -// - Bp = (char *)malloc(Blen); - memcpy(Bp, Buff, Blen); - return new XrdSecCredentials(Bp, Blen); -} - -/******************************************************************************/ -/* S e r v e r O r i e n t e d M e t h o d s */ -/******************************************************************************/ -/******************************************************************************/ -/* A u t h e n t i c a t e */ -/******************************************************************************/ - -int XrdSecProtocolunix::Authenticate(XrdSecCredentials *cred, - XrdSecParameters **parms, - XrdOucErrInfo *erp) -{ - char *bp, *ep; - -// Check if we have any credentials or if no credentials really needed. -// In either case, use host name as client name -// - if (cred->size <= int(4) || !cred->buffer) - {strncpy(Entity.prot, "host", sizeof(Entity.prot)); - Entity.name = (char *)"?"; - return 0; - } - -// Check if this is our protocol -// - if (strcmp(cred->buffer, "unix")) - {char msg[256]; - snprintf(msg, sizeof(msg), - "Secunix: Authentication protocol id mismatch (unix != %.4s).", - cred->buffer); - if (erp) erp->setErrInfo(EINVAL, msg); - else cerr <buffer)+5); - ep = bp + strlen(bp); - -// Extract out username -// - while(*bp && *bp == ' ') bp++; - Entity.name = bp; - while(*bp && *bp != ' ') bp++; - *bp++ = '\0'; - -// Extract out the group name -// - if (bp >= ep) return 0; - while(*bp && *bp == ' ') bp++; - Entity.grps = bp; - -// All done -// - return 0; -} - -/******************************************************************************/ -/* X r d S e c p r o t o c o l u n i x I n i t */ -/******************************************************************************/ - -extern "C" -{ -char *XrdSecProtocolunixInit(const char mode, - const char *parms, - XrdOucErrInfo *erp) -{ - return (char *)""; -} -} - -/******************************************************************************/ -/* X r d S e c P r o t o c o l u n i x O b j e c t */ -/******************************************************************************/ - -XrdVERSIONINFO(XrdSecProtocolunixObject,secunix); - -extern "C" -{ -XrdSecProtocol *XrdSecProtocolunixObject(const char mode, - const char *hostname, - XrdNetAddrInfo &endPoint, - const char *parms, - XrdOucErrInfo *erp) -{ - XrdSecProtocolunix *prot; - -// Return a new protocol object -// - if (!(prot = new XrdSecProtocolunix(hostname, endPoint))) - {const char *msg = "Seckunix: Insufficient memory for protocol."; - if (erp) erp->setErrInfo(ENOMEM, msg); - else cerr <. */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -// _POSIX_ASYNCHRONOUS_IO, if it is defined, is in unistd.h. -#include -#ifdef _POSIX_ASYNCHRONOUS_IO -#ifdef __APPLE__ -#include -#include -#else -#include -#endif -#else -struct aiocb { // Minimal structure to avoid compiler errors - int aio_fildes; - void *aio_buf; - size_t aio_nbytes; - off_t aio_offset; - int aio_reqprio; - struct sigevent aio_sigevent; - }; -#endif - -// The XrdSfsAIO class is meant to be derived. This object provides the -// basic interface to handle AIO control block queues not processing. -// -class XrdSfsAio -{ -public: - -struct aiocb sfsAio; - -ssize_t Result; // If >= 0 valid result; else is -errno - -const char *TIdent; // Trace information (optional) - -// Method to handle completed reads -// -virtual void doneRead() = 0; - -// Method to hand completed writes -// -virtual void doneWrite() = 0; - -// Method to recycle free object -// -virtual void Recycle() = 0; - - XrdSfsAio() { -#if defined(__APPLE__) && (!defined(MAC_OS_X_VERSION_10_4) || \ - MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_4) - sfsAio.aio_sigevent.sigev_value.sigval_ptr = (void *)this; -#else - sfsAio.aio_sigevent.sigev_value.sival_ptr = (void *)this; -#endif - sfsAio.aio_sigevent.sigev_notify = SIGEV_SIGNAL; - sfsAio.aio_reqprio = 0; - TIdent = (char *)""; - } -virtual ~XrdSfsAio() {} -}; -#endif diff --git a/src/XrdSfs/XrdSfsDio.hh b/src/XrdSfs/XrdSfsDio.hh deleted file mode 100644 index f9dab152b15..00000000000 --- a/src/XrdSfs/XrdSfsDio.hh +++ /dev/null @@ -1,106 +0,0 @@ -#ifndef __SFS_DIO_H__ -#define __SFS_DIO_H__ -/******************************************************************************/ -/* */ -/* X r d S f s D i o . h h */ -/* */ -/* (c) 2013 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -#include "XrdOuc/XrdOucSFVec.hh" - - -//----------------------------------------------------------------------------- -//! XrdSfsDio.hh -//! -//! This class is used to define specialized I/O interfaces that can be -//! provided to the underlying filesystem. This object is normally passed via -//! the read() call should fctl() indicate this interface is to be used. -//----------------------------------------------------------------------------- - -class XrdSfsDio -{ -public: - -//----------------------------------------------------------------------------- -//! Send data to a client using the sendfile() system interface. -//! -//! @param fildes - The file descriptor to use to effect a sendfile() for -//! all of the requested data. The original offset and -//! length are used relative to this file descriptor. -//! -//! @return >0 - data has been sent in a previous call. This is indicative -//! of a logic error in SendData() as only one call is allowed. -//! @return =0 - data has been sent. -//! @return <0 - A fatal transmission error occurred. SendData() should -//! return SFS_ERROR to force the connection to be closed. -//----------------------------------------------------------------------------- - -virtual int SendFile(int fildes) = 0; - -//----------------------------------------------------------------------------- -//! Send data to a client using the sendfile() system interface. -//! -//! @param sfvec - One or more XrdOucSFVec elements describing what should be -//! transferred. The first element of the vector *must* be -//! available for use by the interface for proper framing. -//! That is, start filling in elements at sfvec[1] and sfvnum -//! should be the count of elements filled in plus 1. -//! @param sfvnum - total number of elements in sfvec and includes the first -//! unused element. There is a maximum number of elements -//! that the vector may have; defined inside XrdOucSFVec. -//! -//! @return >0 - either data has been sent in a previous call or the total -//! amount of data in sfvec is greater than the original -//! request. This is indicative of a SendData() logic error. -//! @return =0 - data has been sent. -//! @return <0 - A fatal transmission error occurred. SendData() should -//! return SFS_ERROR to force the connection to be closed. -//----------------------------------------------------------------------------- - -virtual int SendFile(XrdOucSFVec *sfvec, int sfvnum) = 0; - -//----------------------------------------------------------------------------- -//! Change the file descriptor setting and, consequently, interface processing. -//! -//! @param fildes - The file descriptor to use in the future, as follows: -//! < 0 - Disable sendfile and always use read(). -//! >= 0 - Enable sendfile and always use sendfile() w/o -//! invoking this interface (i.e. fast path). -//----------------------------------------------------------------------------- - -virtual void SetFD(int fildes) = 0; - -//----------------------------------------------------------------------------- -//! Constructor and destructor -//----------------------------------------------------------------------------- - - XrdSfsDio() {} -virtual ~XrdSfsDio() {} -}; -#endif diff --git a/src/XrdSfs/XrdSfsFlags.hh b/src/XrdSfs/XrdSfsFlags.hh deleted file mode 100644 index 8939a405571..00000000000 --- a/src/XrdSfs/XrdSfsFlags.hh +++ /dev/null @@ -1,66 +0,0 @@ -#ifndef __SFS_FLAGS_H__ -#define __SFS_FLAGS_H__ -/******************************************************************************/ -/* */ -/* X r d S f s F l a g s . h h */ -/* */ -/*(c) 2014 by the Board of Trustees of the Leland Stanford, Jr., University */ -/*Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Deprtment of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -//----------------------------------------------------------------------------- -//! This include file defines certain falgs that can be used by various Sfs -//! plug-ins to passthrough special attributes of regular files. -//----------------------------------------------------------------------------- - -//----------------------------------------------------------------------------- -//! The following flags define the mode bit that can be used to mark a file -//! as close pending. This varies depending on the platform. This supports the -//! Persist On Successful Close (POSC) feature in an efficient way. -//----------------------------------------------------------------------------- - -#ifdef __solaris__ -#define XRDSFS_POSCPEND S_ISUID -#else -#define XRDSFS_POSCPEND S_ISVTX -#endif - -//----------------------------------------------------------------------------- -//! The following bits may be set in the st_rdev member of the stat() structure -//! to indicate special attributes of a regular file. These bits are inspected -//! only when the remaining bits identified by XRD_RDVMASK are set to zero. -//! For backward compatability, offline status is also assumed when st_dev and -//! st_ino are both set to zero. -//----------------------------------------------------------------------------- - -static const dev_t XRDSFS_OFFLINE = - static_cast(0x80LL<<((sizeof(dev_t)*8)-8)); -static const dev_t XRDSFS_HASBKUP = - static_cast(0x40LL<<((sizeof(dev_t)*8)-8)); -static const dev_t XRDSFS_RDVMASK = - static_cast(~(0xffLL<<((sizeof(dev_t)*8)-8))); -#endif diff --git a/src/XrdSfs/XrdSfsInterface.hh b/src/XrdSfs/XrdSfsInterface.hh deleted file mode 100644 index 6312fa6e35f..00000000000 --- a/src/XrdSfs/XrdSfsInterface.hh +++ /dev/null @@ -1,1077 +0,0 @@ -#ifndef __SFS_INTERFACE_H__ -#define __SFS_INTERFACE_H__ -/******************************************************************************/ -/* */ -/* X r d S f s I n t e r f a c e . h h */ -/* */ -/* (c) 2010 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include // For strlcpy() -#include -#include -#include - -#include "XrdOuc/XrdOucErrInfo.hh" -#include "XrdOuc/XrdOucIOVec.hh" -#include "XrdOuc/XrdOucSFVec.hh" - -/******************************************************************************/ -/* O p e n M o d e s */ -/******************************************************************************/ - -#define SFS_O_RDONLY 0 // open read/only -#define SFS_O_WRONLY 1 // open write/only -#define SFS_O_RDWR 2 // open read/write -#define SFS_O_CREAT 0x100 // used for file creation -#define SFS_O_TRUNC 0x200 // used for file truncation -#define SFS_O_MULTIW 0x400 // used for multi-write locations -#define SFS_O_POSC 0x0100000 // persist on successful close -#define SFS_O_FORCE 0x0200000 // used for locate only -#define SFS_O_HNAME 0x0400000 // used for locate only -#define SFS_O_LOCAL 0x0800000 // used for locate only (local cmd) -#define SFS_O_NOWAIT 0x01000000 // do not impose operational delays -#define SFS_O_RAWIO 0x02000000 // allow client-side decompression -#define SFS_O_RESET 0x04000000 // Reset any cached information -#define SFS_O_REPLICA 0x08000000 // Open for replication - -// The following flag may be set in the access mode arg for open() & mkdir() -// Note that on some systems mode_t is 16-bits so we use a careful value! -// -#define SFS_O_MKPTH 0x00004000 // Make directory path if missing - -// The following options are here to provide a uniform clustering interface. -// They may be passed through open/locate/stat, as applicable. -// -#define SFS_O_LOCATE 0x10000000 // This request generated by locate() -#define SFS_O_STAT 0x20000000 // This request generated by stat() -#define SFS_O_META 0x40000000 // This request generated by metaop - -/******************************************************************************/ -/* D e f i n e s */ -/******************************************************************************/ - -// Common fctl command values (0 to 255) -// -#define SFS_FCTL_GETFD 1 // Return file descriptor if possible -#define SFS_FCTL_STATV 2 // Return visa information -#define SFS_FCTL_SPEC1 3 // Return implementation defined information - -#define SFS_SFIO_FDVAL 0x80000000 // Use SendData() method GETFD response value - -// Common fsctl command values (0 to 255) -// -#define SFS_FSCTL_CMD 255 - -#define SFS_FSCTL_LOCATE 1 // Locate a file -#define SFS_FSCTL_STATFS 2 // Return FS data -#define SFS_FSCTL_STATLS 3 // Return LS data -#define SFS_FSCTL_STATXA 4 // Return XA data -#define SFS_FSCTL_STATCC 5 // Return Cluster Config status -#define SFS_FSCTL_PLUGIN 8 // Return Implementation Dependent Data -#define SFS_FSCTL_PLUGIO 16 // Return Implementation Dependent Data - -// Return values for integer & XrdSfsXferSize returning XrdSfs methods -// -#define SFS_STALL 1 // Return value -> Seconds to stall client -#define SFS_OK 0 // ErrInfo code -> All is well -#define SFS_ERROR -1 // ErrInfo code -> Error occurred -#define SFS_REDIRECT -256 // ErrInfo code -> Port number to redirect to -#define SFS_STARTED -512 // ErrInfo code -> Estimated seconds to completion -#define SFS_DATA -1024 // ErrInfo code -> Length of data -#define SFS_DATAVEC -2048 // ErrInfo code -> Num iovec elements in msgbuff - -// The following macros are used for dealing with special local paths -// -#define SFS_LCLPRFX "/=/" -#define SFS_LCLPLEN 3 -#define SFS_LCLPATH(x) !strncmp(x, SFS_LCLPRFX, SFS_LCLPLEN) -#define SFS_LCLPRFY "/=" -#define SFS_LCLROOT(x) !strncmp(x, SFS_LCLPRFX, SFS_LCLPLEN-1) \ - && (*(x+SFS_LCLPLEN-1) == '/' || *(x+SFS_LCLPLEN-1) == 0) - -/******************************************************************************/ -/* S t r u c t u r e s & T y p e d e f s */ -/******************************************************************************/ - -typedef long long XrdSfsFileOffset; -typedef int XrdSfsFileOpenMode; -typedef int XrdSfsMode; -typedef int XrdSfsXferSize; - -enum XrdSfsFileExistence -{ - XrdSfsFileExistNo, - XrdSfsFileExistIsFile, - XrdSfsFileExistIsDirectory, - XrdSfsFileExistIsOffline, - XrdSfsFileExistIsOther -}; -//------------------------------------------------ - -#define Prep_PRTY0 0 -#define Prep_PRTY1 1 -#define Prep_PRTY2 2 -#define Prep_PRTY3 3 -#define Prep_PMASK 3 -#define Prep_SENDAOK 4 -#define Prep_SENDERR 8 -#define Prep_SENDACK 12 -#define Prep_WMODE 16 -#define Prep_STAGE 32 -#define Prep_COLOC 64 -#define Prep_FRESH 128 - -class XrdOucTList; - -struct XrdSfsFSctl //!< SFS_FSCTL_PLUGIN/PLUGIO parameters -{ - const char *Arg1; //!< PLUGIO & PLUGIN - int Arg1Len; //!< Length - int Arg2Len; //!< Length - const char *Arg2; //!< PLUGIN opaque string -}; - -struct XrdSfsPrep //!< Prepare parameters -{ - char *reqid; //!< Request ID - char *notify; //!< Notification path or 0 - int opts; //!< Prep_xxx - XrdOucTList *paths; //!< List of paths - XrdOucTList *oinfo; //!< 1-to-1 correspondence of opaque info -}; - -/******************************************************************************/ -/* A b s t r a c t C l a s s e s */ -/******************************************************************************/ - -class XrdSfsFile; -class XrdSfsDirectory; -class XrdOucEnv; -class XrdOucTList; -class XrdSecEntity; - -/******************************************************************************/ -/* X r d S f s F i l e S y s t e m */ -/******************************************************************************/ - -//----------------------------------------------------------------------------- -//! Common parameters: Many of the methods have certain common parameters. -//! These are documented here to avoid lengthy duplicate descriptions. -//! -//! @param eInfo - The object where error info or results are to be returned. -//! For errors, you should return information as follows: -//! SFS_OK eInfo may contain results, as described in -//! specified method description that follows. -//! SFS_ERROR eInfo.code - errno number -//! eInfo.message - error message text -//! SFS_REDIRECT eInfo.code - target port number -//! eInfo.message - target host address/name -//! SFS_STALL eInfo.code - expected seconds to stall -//! eInfo.message - reason for the delay -//! SFS_STARTED eInfo.code - expected seconds to completion -//! eInfo.message - reason for the delay -//! SFS_DATA eInfo.code - length of data in message -//! eInfo.message - the request data -//! -//! @param client - Pointer to the client's identity information or nil if -//! the identity is not known. -//! -//! @param opaque - Pointer to the CGI information associated with Path or -//! nil if there is no opaque information. -//----------------------------------------------------------------------------- - -class XrdSfsFileSystem -{ -public: - -//----------------------------------------------------------------------------- -//! Obtain a new director object to be used for future directory requests. -//! -//! @param user - Text identifying the client responsible for this call. -//! The pointer may be null if identification is missing. -//! @param MonID - The monitoring identifier assigned to this and all -//! future requests using the returned object. -//! -//! @return pointer- Pointer to an XrdSfsDirectory object. -//! @return nil - Insufficient memory to allocate an object. -//----------------------------------------------------------------------------- - -virtual XrdSfsDirectory *newDir(char *user=0, int MonID=0) = 0; - -//----------------------------------------------------------------------------- -//! Obtain a new file object to be used for a future file requests. -//! -//! @param user - Text identifying the client responsible for this call. -//! The pointer may be null if identification is missing. -//! @param MonID - The monitoring identifier assigned to this and all -//! future requests using the returned object. -//! -//! @return pointer- Pointer to an XrdSfsFile object. -//! @return nil - Insufficient memory to allocate an object. -//----------------------------------------------------------------------------- - -virtual XrdSfsFile *newFile(char *user=0, int MonID=0) = 0; - -//----------------------------------------------------------------------------- -//! Obtain checksum information for a file. -//! -//! @param Func - The checksum operation to be performed: -//! csCalc - (re)calculate and return the checksum value -//! csGet - return the existing checksum value, if any -//! csSize - return the size of the checksum value that -//! corresponds to csName (path may be null). -//! @param csName - The name of the checksum value wanted. -//! @param path - Pointer to the path of the file in question. -//! @param eInfo - The object where error info or results are to be returned. -//! @param client - Client's identify (see common description). -//! @param opaque - Path's CGI information (see common description). -//! -//! @return One of SFS_OK, SFS_ERROR, or SFS_REDIRECT. When SFS_OK is returned, -//! eInfo should contain results, as follows: -//! csCalc/csGet eInfo.message - null terminated string with the -//! checksum value in ASCII hex. -//! csSize eInfo.code - size of binary checksum value. -//----------------------------------------------------------------------------- - -enum csFunc {csCalc = 0, csGet, csSize}; - -virtual int chksum( csFunc Func, - const char *csName, - const char *path, - XrdOucErrInfo &eInfo, - const XrdSecEntity *client = 0, - const char *opaque = 0) -{ - (void)Func; (void)csName; (void)path; (void)eInfo; (void)client; - (void)opaque; - eInfo.setErrInfo(ENOTSUP, "Not supported."); - return SFS_ERROR; -} - -//----------------------------------------------------------------------------- -//! Change file mode settings. -//! -//! @param path - Pointer to the path of the file in question. -//! @param mode - The new file mode setting. -//! @param eInfo - The object where error info or results are to be returned. -//! @param client - Client's identify (see common description). -//! @param opaque - Path's CGI information (see common description). -//! -//! @return One of SFS_OK, SFS_ERROR, SFS_REDIRECT or SFS_STALL -//----------------------------------------------------------------------------- - -virtual int chmod(const char *path, - XrdSfsMode mode, - XrdOucErrInfo &eInfo, - const XrdSecEntity *client = 0, - const char *opaque = 0) = 0; - -//----------------------------------------------------------------------------- -//! Notify filesystem that a client has disconnected. -//! -//! @param client - Client's identify (see common description). -//----------------------------------------------------------------------------- - -virtual void Disc(const XrdSecEntity *client = 0) -{ - (void)client; -} - -//----------------------------------------------------------------------------- -//! Notify filesystem about implmentation dependent environment. This method -//! may be called only once, if at all, right after obtaining this object. -//! -//! @param envP - Pointer to environmental information. -//----------------------------------------------------------------------------- - -virtual void EnvInfo(XrdOucEnv *envP) -{ - (void)envP; -} - -//----------------------------------------------------------------------------- -//! Perform a filesystem control operation (version 1) -//! -//! @param cmd - The operation to be performed: -//! SFS_FSCTL_LOCATE Locate a file or file servers -//! SFS_FSCTL_STATCC Return cluster config status -//! SFS_FSCTL_STATFS Return physical filesystem information -//! SFS_FSCTL_STATLS Return logical filesystem information -//! SFS_FSCTL_STATXA Return extended attributes -//! @param args - Arguments specific to cmd. -//! SFS_FSCTL_LOCATE args points to the path to be located -//! "" path is the first exported path -//! "*" return all current servers -//! "*/" return servers exporting path -//! o/w return servers having the path -//! SFS_FSCTL_STATFS Path in the filesystem in question. -//! SFS_FSCTL_STATLS Path in the filesystem in question. -//! SFS_FSCTL_STATXA Path of the file whose xattr is wanted. -//! @param eInfo - The object where error info or results are to be returned. -//! @param client - Client's identify (see common description). -//! -//! @return SFS_OK a null response is sent. -//! @return SFS_DATA error.code length of the data to be sent. -//! error.message contains the data to be sent. -//! @return SFS_STARTED Operation started result will be returned via callback. -//! Valid only for for SFS_FSCTL_LOCATE, SFS_FSCTL_STATFS, and -//! SFS_FSCTL_STATXA -//! o/w one of SFS_ERROR, SFS_REDIRECT, or SFS_STALL. -//----------------------------------------------------------------------------- - -virtual int FSctl(const int cmd, - XrdSfsFSctl &args, - XrdOucErrInfo &eInfo, - const XrdSecEntity *client = 0) -{ - (void)cmd; (void)args; (void)eInfo; (void)client; - return SFS_OK; -} - -//----------------------------------------------------------------------------- -//! Perform a filesystem control operation (version 2) -//! -//! @param cmd - The operation to be performed: -//! SFS_FSCTL_PLUGIN Return Implementation Dependent Data v1 -//! SFS_FSCTL_PLUGIO Return Implementation Dependent Data v2 -//! @param args - Arguments specific to cmd. -//! SFS_FSCTL_PLUGIN path and opaque information. -//! SFS_FSCTL_PLUGIO Unscreened argument string. -//! @param eInfo - The object where error info or results are to be returned. -//! @param client - Client's identify (see common description). -//! -//! @return SFS_OK a null response is sent. -//! SFS_DATA error.code length of the data to be sent. -//! error.message contains the data to be sent. -//! o/w one of SFS_ERROR, SFS_REDIRECT, or SFS_STALL. -//----------------------------------------------------------------------------- - -virtual int fsctl(const int cmd, - const char *args, - XrdOucErrInfo &eInfo, - const XrdSecEntity *client = 0) = 0; - -//----------------------------------------------------------------------------- -//! Return statistical information. -//! -//! @param buff - Pointer to the buffer where results are to be returned. -//! Statistics should be in standard XML format. If buff is -//! nil then only maximum size information is wanted. -//! @param blen - The length available in buff. -//! -//! @return Number of bytes placed in buff. When buff is nil, the maximum -//! number of bytes that could have been placed in buff. -//----------------------------------------------------------------------------- - -virtual int getStats(char *buff, int blen) = 0; - -//----------------------------------------------------------------------------- -//! Get version string. -//! -//! @return The version string. Normally this is the XrdVERSION value. -//----------------------------------------------------------------------------- - -virtual const char *getVersion() = 0; - -//----------------------------------------------------------------------------- -//! Return directory/file existence information (short stat). -//! -//! @param path - Pointer to the path of the file/directory in question. -//! @param eFlag - Where the results are to be returned. -//! @param eInfo - The object where error info is to be returned. -//! @param client - Client's identify (see common description). -//! @param opaque - Path's CGI information (see common description). -//! -//! @return One of SFS_OK, SFS_ERROR, SFS_REDIRECT, SFS_STALL, or SFS_STARTED -//! When SFS_OK is returned, eFlag must be properly set, as follows: -//! XrdSfsFileExistNo - path does not exist -//! XrdSfsFileExistIsFile - path refers to an online file -//! XrdSfsFileExistIsDirectory - path refers to an online directory -//! XrdSfsFileExistIsOffline - path refers to an offline file -//! XrdSfsFileExistIsOther - path is neither a file nor directory -//----------------------------------------------------------------------------- - -virtual int exists(const char *path, - XrdSfsFileExistence &eFlag, - XrdOucErrInfo &eInfo, - const XrdSecEntity *client = 0, - const char *opaque = 0) = 0; - -//----------------------------------------------------------------------------- -//! Create a directory. -//! -//! @param path - Pointer to the path of the directory to be created. -//! @param mode - The directory mode setting. -//! @param eInfo - The object where error info is to be returned. -//! @param client - Client's identify (see common description). -//! @param opaque - Path's CGI information (see common description). -//! -//! @return One of SFS_OK, SFS_ERROR, SFS_REDIRECT, or SFS_STALL -//----------------------------------------------------------------------------- - -virtual int mkdir(const char *path, - XrdSfsMode mode, - XrdOucErrInfo &eInfo, - const XrdSecEntity *client = 0, - const char *opaque = 0) = 0; - -//----------------------------------------------------------------------------- -//! Preapre a file for future processing. -//! -//! @param pargs - The preapre arguments. -//! @param eInfo - The object where error info is to be returned. -//! @param client - Client's identify (see common description). -//! -//! @return One of SFS_OK, SFS_ERROR, SFS_REDIRECT, or SFS_STALL -//----------------------------------------------------------------------------- - -virtual int prepare( XrdSfsPrep &pargs, - XrdOucErrInfo &eInfo, - const XrdSecEntity *client = 0) = 0; - -//----------------------------------------------------------------------------- -//! Remove a file. -//! -//! @param path - Pointer to the path of the file to be removed. -//! @param eInfo - The object where error info is to be returned. -//! @param client - Client's identify (see common description). -//! @param opaque - Path's CGI information (see common description). -//! -//! @return One of SFS_OK, SFS_ERROR, SFS_REDIRECT, or SFS_STALL -//----------------------------------------------------------------------------- - -virtual int rem(const char *path, - XrdOucErrInfo &eInfo, - const XrdSecEntity *client = 0, - const char *opaque = 0) = 0; - -//----------------------------------------------------------------------------- -//! Remove a directory. -//! -//! @param path - Pointer to the path of the directory to be removed. -//! @param eInfo - The object where error info is to be returned. -//! @param client - Client's identify (see common description). -//! @param opaque - Path's CGI information (see common description). -//! -//! @return One of SFS_OK, SFS_ERROR, SFS_REDIRECT, or SFS_STALL -//----------------------------------------------------------------------------- - -virtual int remdir(const char *path, - XrdOucErrInfo &eInfo, - const XrdSecEntity *client = 0, - const char *opaque = 0) = 0; - -//----------------------------------------------------------------------------- -//! Rename a file or directory. -//! -//! @param oPath - Pointer to the path to be renamed. -//! @param nPath - Pointer to the path oPath is to have. -//! @param eInfo - The object where error info is to be returned. -//! @param client - Client's identify (see common description). -//! @param opaqueO - oPath's CGI information (see common description). -//! @param opaqueN - nPath's CGI information (see common description). -//! -//! @return One of SFS_OK, SFS_ERROR, SFS_REDIRECT, or SFS_STALL -//----------------------------------------------------------------------------- - -virtual int rename(const char *oPath, - const char *nPath, - XrdOucErrInfo &eInfo, - const XrdSecEntity *client = 0, - const char *opaqueO = 0, - const char *opaqueN = 0) = 0; - -//----------------------------------------------------------------------------- -//! Return state information on a file or directory. -//! -//! @param path - Pointer to the path in question. -//! @param buf - Pointer to the structure where info it to be returned. -//! @param eInfo - The object where error info is to be returned. -//! @param client - Client's identify (see common description). -//! @param opaque - path's CGI information (see common description). -//! -//! @return One of SFS_OK, SFS_ERROR, SFS_REDIRECT, SFS_STALL, or SFS_STARTED -//! When SFS_OK is returned, buf must contain stat information. -//----------------------------------------------------------------------------- - -virtual int stat(const char *Name, - struct stat *buf, - XrdOucErrInfo &eInfo, - const XrdSecEntity *client = 0, - const char *opaque = 0) = 0; - -//----------------------------------------------------------------------------- -//! Return mode information on a file or directory. -//! -//! @param path - Pointer to the path in question. -//! @param mode - Where full mode information is to be returned. -//! @param eInfo - The object where error info is to be returned. -//! @param client - Client's identify (see common description). -//! @param opaque - path's CGI information (see common description). -//! -//! @return One of SFS_OK, SFS_ERROR, SFS_REDIRECT, SFS_STALL, or SFS_STARTED -//! When SFS_OK is returned, mode must contain mode information. If -//! teh mode is -1 then it is taken as an offline file. -//----------------------------------------------------------------------------- - -virtual int stat(const char *path, - mode_t &mode, - XrdOucErrInfo &eInfo, - const XrdSecEntity *client = 0, - const char *opaque = 0) = 0; - -//----------------------------------------------------------------------------- -//! Truncate a file. -//! -//! @param path - Pointer to the path of the file to be truncated. -//! @param fsize - The size that the file is to have. -//! @param eInfo - The object where error info is to be returned. -//! @param client - Client's identify (see common description). -//! @param opaque - path's CGI information (see common description). -//! -//! @return One of SFS_OK, SFS_ERROR, SFS_REDIRECT, or SFS_STALL -//----------------------------------------------------------------------------- - -virtual int truncate(const char *path, - XrdSfsFileOffset fsize, - XrdOucErrInfo &eInfo, - const XrdSecEntity *client = 0, - const char *opaque = 0) = 0; - -//----------------------------------------------------------------------------- -//! Constructor and Destructor -//----------------------------------------------------------------------------- - - XrdSfsFileSystem() {} -virtual ~XrdSfsFileSystem() {} -}; - -/******************************************************************************/ -/* F i l e S y s t e m I n s t a n t i a t o r */ -/******************************************************************************/ - -//----------------------------------------------------------------------------- -/*! When building a shared library plugin, the following "C" entry point must - exist in the library: - - @param nativeFS - the filesystem that would have been used. You may return - this pointer if you wish. - @param Logger - The message logging object to be used for messages. - @param configFN - pointer to the path of the configuration file. If nil - there is no configuration file. - - @return Pointer to the file system object to be used or nil if an error - occurred. - - extern "C" - {XrdSfsFileSystem *XrdSfsGetFileSystem(XrdSfsFileSystem *nativeFS, - XrdSysLogger *Logger, - const char *configFn); - } - - An alternate entry point may be defined in lieu of the previous entry point. - This normally identified by a version option in the configuration file (e.g. - xrootd.fslib -2 ). It differs in that an extra parameter is passed: - - @param envP - Pointer to the environment containing implementation - specific information. - - extern "C" - {XrdSfsFileSystem *XrdSfsGetFileSystem2(XrdSfsFileSystem *nativeFS, - XrdSysLogger *Logger, - const char *configFn, - XrdOucEnv *envP); - } -*/ - -typedef XrdSfsFileSystem *(*XrdSfsFileSystem_t) (XrdSfsFileSystem *nativeFS, - XrdSysLogger *Logger, - const char *configFn); - -typedef XrdSfsFileSystem *(*XrdSfsFileSystem2_t)(XrdSfsFileSystem *nativeFS, - XrdSysLogger *Logger, - const char *configFn, - XrdOucEnv *envP); - -//----------------------------------------------------------------------------- - -//------------------------------------------------------------------------------ -/*! Specify the compilation version. - - Additionally, you *should* declare the xrootd version you used to compile - your plug-in. The plugin manager automatically checks for compatability. - Declare it as follows: - - #include "XrdVersion.hh" - XrdVERSIONINFO(XrdSfsGetFileSystem,); - - where is a 1- to 15-character unquoted name identifying your plugin. -*/ -//------------------------------------------------------------------------------ - -/******************************************************************************/ -/* X r d S f s F i l e */ -/******************************************************************************/ - -//------------------------------------------------------------------------------ -//! The XrdSfsFile object is returned by XrdSfsFileSystem::newFile() when -//! the caller wants to be able to perform file oriented operations. -//------------------------------------------------------------------------------ - -class XrdSfsAio; -class XrdSfsDio; -class XrdSfsXio; - -class XrdSfsFile -{ -public: - -//----------------------------------------------------------------------------- -//! The error object is used to return details whenever something other than -//! SFS_OK is returned from the methods in this class, when noted. -//----------------------------------------------------------------------------- - - XrdOucErrInfo error; - -//----------------------------------------------------------------------------- -//! Open a file. -//! -//! @param path - Pointer to the path of the file to be opened. -//! @param oMode - Flags indicating how the open is to be handled. -//! SFS_O_CREAT create the file -//! SFS_O_MKPTH Make directory path if missing -//! SFS_O_NOWAIT do not impose operational delays -//! SFS_O_POSC persist only on successful close -//! SFS_O_RAWIO allow client-side decompression -//! SFS_O_RDONLY open read/only -//! SFS_O_RDWR open read/write -//! SFS_O_REPLICA Open for replication -//! SFS_O_RESET Reset any cached information -//! SFS_O_TRUNC truncate existing file to zero length -//! SFS_O_WRONLY open write/only -//! @param cMode - The file's mode if it will be created. -//! @param client - Client's identify (see common description). -//! @param opaque - path's CGI information (see common description). -//! -//! @return One of SFS_OK, SFS_ERROR, SFS_REDIRECT, SFS_STALL, or SFS_STARTED -//----------------------------------------------------------------------------- - -virtual int open(const char *fileName, - XrdSfsFileOpenMode openMode, - mode_t createMode, - const XrdSecEntity *client = 0, - const char *opaque = 0) = 0; - -//----------------------------------------------------------------------------- -//! Close the file. -//! -//! @return One of SFS_OK or SFS_ERROR. -//----------------------------------------------------------------------------- - -virtual int close() = 0; - -//----------------------------------------------------------------------------- -//! Execute a special operation on the file (version 1) -//! -//! @param cmd - The operation to be performed (see below). -//! SFS_FCTL_GETFD Return file descriptor if possible -//! SFS_FCTL_STATV Reserved for future use. -//! @param args - specific arguments to cmd -//! SFS_FCTL_GETFD Set to zero. -//! @param eInfo - The object where error info or results are to be returned. -//! This is legacy and the error onject may be used as well. -//! -//! @return If an error occurs or the operation is not support, SFS_ERROR -//! should be returned with error.code set to errno. Otherwise, -//! SFS_FCTL_GETFD error.code holds the real file descriptor number -//! If the value is negative, sendfile() is not used. -//! If the value is SFS_SFIO_FDVAL then the SendData() -//! method is used for future read requests. -//----------------------------------------------------------------------------- - -virtual int fctl(const int cmd, - const char *args, - XrdOucErrInfo &eInfo) = 0; - -//----------------------------------------------------------------------------- -//! Execute a special operation on the file (version 2) -//! -//! @param cmd - The operation to be performed: -//! SFS_FCTL_SPEC1 Perform implementation defined action -//! @param alen - Length of data pointed to by args. -//! @param args - Data sent with request, zero if alen is zero. -//! @param client - Client's identify (see common description). -//! -//! @return SFS_OK a null response is sent. -//! @return SFS_DATA error.code length of the data to be sent. -//! error.message contains the data to be sent. -//! o/w one of SFS_ERROR, SFS_REDIRECT, or SFS_STALL. -//----------------------------------------------------------------------------- - -virtual int fctl(const int cmd, - int alen, - const char *args, - const XrdSecEntity *client = 0) -{ - (void)cmd; (void)alen; (void)args; (void)client; - return SFS_OK; -} - -//----------------------------------------------------------------------------- -//! Get the file path. -//! -//! @return Null terminated string of the path used in open(). -//----------------------------------------------------------------------------- - -virtual const char *FName() = 0; - - -//----------------------------------------------------------------------------- -//! Get file's memory mapping if one exists (memory mapped files only). -//! -//! @param addr - Place where the starting memory address is returned. -//! @param size - Place where the file's size is returned. -//! -//! @return SFS_OK when the file is memory mapped or any other code otherwise. -//----------------------------------------------------------------------------- - -virtual int getMmap(void **Addr, off_t &Size) = 0; - -//----------------------------------------------------------------------------- -//! Preread file blocks into the file system cache. -//! -//! @param offset - The offset where the read is to start. -//! @param size - The number of bytes to pre-read. -//! -//! @return >= 0 The number of bytes that will be pre-read. -//! @return SFS_ERROR File could not be preread, error holds the reason. -//----------------------------------------------------------------------------- - -virtual XrdSfsXferSize read(XrdSfsFileOffset offset, - XrdSfsXferSize size) = 0; - -//----------------------------------------------------------------------------- -//! Read file bytes into a buffer. -//! -//! @param offset - The offset where the read is to start. -//! @param buffer - pointer to buffer where the bytes are to be placed. -//! @param size - The number of bytes to read. -//! -//! @return >= 0 The number of bytes that placed in buffer. -//! @return SFS_ERROR File could not be read, error holds the reason. -//----------------------------------------------------------------------------- - -virtual XrdSfsXferSize read(XrdSfsFileOffset offset, - char *buffer, - XrdSfsXferSize size) = 0; - -//----------------------------------------------------------------------------- -//! Read file bytes using asynchrnous I/O. -//! -//! @param aioparm - Pointer to async I/O object controlling the I/O. -//! -//! @return SFS_OK Request accepted and will be scheduled. -//! @return SFS_ERROR File could not be read, error holds the reason. -//----------------------------------------------------------------------------- - -virtual XrdSfsXferSize read(XrdSfsAio *aioparm) = 0; - -//----------------------------------------------------------------------------- -//! Given an array of read requests (size rdvCnt), read them from the file -//! and place the contents consecutively in the provided buffer. A dumb default -//! implementation is supplied but should be replaced to increase performance. -//! -//! @param readV pointer to the array of read requests. -//! @param rdvcnt the number of elements in readV. -//! -//! @return >=0 The numbe of bytes placed into the buffer. -//! @return SFS_ERROR File could not be read, error holds the reason. -//----------------------------------------------------------------------------- - -virtual XrdSfsXferSize readv(XrdOucIOVec *readV, - int rdvCnt) - {XrdSfsXferSize rdsz, totbytes = 0; - for (int i = 0; i < rdvCnt; i++) - {rdsz = read(readV[i].offset, - readV[i].data, readV[i].size); - if (rdsz != readV[i].size) - {if (rdsz < 0) return rdsz; - error.setErrInfo(ESPIPE,"read past eof"); - return SFS_ERROR; - } - totbytes += rdsz; - } - return totbytes; - } - -//----------------------------------------------------------------------------- -//! Send file bytes via a XrdSfsDio sendfile object to a client (optional). -//! -//! @param sfDio - Pointer to the sendfile object for data transfer. -//! @param offset - The offset where the read is to start. -//! @param size - The number of bytes to read and send. -//! -//! @return SFS_ERROR File not read, error object has reason. -//! @return SFS_OK Either data has been successfully sent via sfDio or no -//! data has been sent and a normal read() should be issued. -//----------------------------------------------------------------------------- - -virtual int SendData(XrdSfsDio *sfDio, - XrdSfsFileOffset offset, - XrdSfsXferSize size) -{ - (void)sfDio; (void)offset; (void)size; - return SFS_OK; -} - -//----------------------------------------------------------------------------- -//! Write file bytes from a buffer. -//! -//! @param offset - The offset where the write is to start. -//! @param buffer - pointer to buffer where the bytes reside. -//! @param size - The number of bytes to write. -//! -//! @return >= 0 The number of bytes that were written. -//! @return SFS_ERROR File could not be written, error holds the reason. -//----------------------------------------------------------------------------- - -virtual XrdSfsXferSize write(XrdSfsFileOffset offset, - const char *buffer, - XrdSfsXferSize size) = 0; - -//----------------------------------------------------------------------------- -//! Write file bytes using asynchrnous I/O. -//! -//! @param aioparm - Pointer to async I/O object controlling the I/O. -//! -//! @return 0 Request accepted and will be scheduled. -//! @return !0 Request not accepted, returned value is errno. -//----------------------------------------------------------------------------- - -virtual int write(XrdSfsAio *aioparm) = 0; - -//----------------------------------------------------------------------------- -//! Given an array of write requests (size wdvcnt), write them to the file -//! from the provided associated buffer. A dumb default implementation is -//! supplied but should be replaced to increase performance. -//! -//! @param writeV pointer to the array of write requests. -//! @param wdvcnt the number of elements in writeV. -//! -//! @return >=0 The total number of bytes written to the file. -//! @return SFS_ERROR File could not be written, error holds the reason. -//----------------------------------------------------------------------------- - -virtual XrdSfsXferSize writev(XrdOucIOVec *writeV, - int wdvCnt) - {XrdSfsXferSize wrsz, totbytes = 0; - for (int i = 0; i < wdvCnt; i++) - {wrsz = write(writeV[i].offset, - writeV[i].data, writeV[i].size); - if (wrsz != writeV[i].size) - {if (wrsz < 0) return wrsz; - error.setErrInfo(ESPIPE,"write past eof"); - return SFS_ERROR; - } - totbytes += wrsz; - } - return totbytes; - } - -//----------------------------------------------------------------------------- -//! Return state information on the file. -//! -//! @param buf - Pointer to the structure where info it to be returned. -//! -//! @return One of SFS_OK, SFS_ERROR, SFS_REDIRECT, or SFS_STALL. When SFS_OK -//! is returned, buf must hold stat information. -//----------------------------------------------------------------------------- - -virtual int stat(struct stat *buf) = 0; - -//----------------------------------------------------------------------------- -//! Make sure all outstanding data is actually written to the file (sync). -//! -//! @return One of SFS_OK, SFS_ERROR, SFS_REDIRECT, SFS_STALL, or SFS_STARTED -//----------------------------------------------------------------------------- - -virtual int sync() = 0; - -//----------------------------------------------------------------------------- -//! Make sure all outstanding data is actually written to the file (async). -//! -//! @return SFS_OK Request accepted and will be scheduled. -//! @return SFS_ERROR Request could not be accepted, return error has reason. -//----------------------------------------------------------------------------- - -virtual int sync(XrdSfsAio *aiop) = 0; - -//----------------------------------------------------------------------------- -//! Truncate the file. -//! -//! @param fsize - The size that the file is to have. -//! -//! @return One of SFS_OK, SFS_ERROR, SFS_REDIRECT, or SFS_STALL -//----------------------------------------------------------------------------- - -virtual int truncate(XrdSfsFileOffset fsize) = 0; - -//----------------------------------------------------------------------------- -//! Get compression information for the file. -//! -//! @param cxtype - Place where the compression algorithm name is to be placed -//! @param cxrsz - Place where the compression page size is to be returned -//! -//! @return One of the valid SFS return codes described above. If the file -//! is not compressed or an error is returned, cxrsz must be set to 0. -//----------------------------------------------------------------------------- - -virtual int getCXinfo(char cxtype[4], int &cxrsz) = 0; - -//----------------------------------------------------------------------------- -//! Enable exchange buffer I/O for write calls. -//! -//! @param - Pointer to the XrdSfsXio object to be used for buffer exchanges. -//----------------------------------------------------------------------------- - -virtual void setXio(XrdSfsXio *xioP) { (void)xioP; } - -//----------------------------------------------------------------------------- -//! Constructor (user and MonID are the ones passed to newFile()!) -//! -//! @param user - Text identifying the client responsible for this call. -//! The pointer may be null if identification is missing. -//! @param MonID - The monitoring identifier assigned to this and all -//! future requests using the returned object. -//----------------------------------------------------------------------------- - - XrdSfsFile(const char *user=0, int MonID=0) - : error(user, MonID) {} - -//----------------------------------------------------------------------------- -//! Destructor -//----------------------------------------------------------------------------- - -virtual ~XrdSfsFile() {} - -}; // class XrdSfsFile - -/******************************************************************************/ -/* X r d S f s D i r e c t o r y */ -/******************************************************************************/ - -//------------------------------------------------------------------------------ -//! The XrdSfsDirectory object is returned by XrdSfsFileSystem::newFile() when -//! the caller wants to be able to perform directory oriented operations. -//------------------------------------------------------------------------------ - -class XrdSfsDirectory -{ -public: - -//----------------------------------------------------------------------------- -//! The error object is used to return details whenever something other than -//! SFS_OK is returned from the methods in this class, when noted. -//----------------------------------------------------------------------------- - - XrdOucErrInfo error; - -//----------------------------------------------------------------------------- -//! Open a directory. -//! -//! @param path - Pointer to the path of the directory to be opened. -//! @param client - Client's identify (see common description). -//! @param opaque - path's CGI information (see common description). -//! -//! @return One of SFS_OK, SFS_ERROR, SFS_REDIRECT, ir SFS_STALL -//----------------------------------------------------------------------------- - -virtual int open(const char *path, - const XrdSecEntity *client = 0, - const char *opaque = 0) = 0; - -//----------------------------------------------------------------------------- -//! Get the next directory entry. -//! -//! @return A null terminated string with the directory name. Normally, "." -//! ".." are not returned. If a null pointer is returned then if this -//! is due to an error, error.code should contain errno. Otherwise, -//! error.code should contain zero to indicate that no more entries -//! exist (i.e. end of list). -//----------------------------------------------------------------------------- - -virtual const char *nextEntry() = 0; - -//----------------------------------------------------------------------------- -//! Close the file. -//! -//! @return One of SFS_OK or SFS_ERROR -//----------------------------------------------------------------------------- - -virtual int close() = 0; - -//----------------------------------------------------------------------------- -//! Get the directory path. -//! -//! @return Null terminated string of the path used in open(). -//----------------------------------------------------------------------------- - -virtual const char *FName() = 0; - -//----------------------------------------------------------------------------- -//! Set the stat() buffer where stat information is to be placed corresponding -//! to the directory entry returned by nextEntry(). -//! -//! @return If supported, SFS_OK should be returned. If not supported, then -//! SFS_ERROR should be returned with error.code set to ENOTSUP. -//----------------------------------------------------------------------------- - -virtual int autoStat(struct stat *buf) - {(void)buf; - error.setErrInfo(ENOTSUP, "Not supported."); - return SFS_ERROR; - } - -//----------------------------------------------------------------------------- -//! Constructor (user and MonID are the ones passed to newDir()!) -//! -//! @param user - Text identifying the client responsible for this call. -//! The pointer may be null if identification is missing. -//! @param MonID - The monitoring identifier assigned to this and all -//! future requests using the returned object. -//----------------------------------------------------------------------------- - - XrdSfsDirectory(const char *user=0, int MonID=0) - : error(user, MonID) {} - -//----------------------------------------------------------------------------- -//! Destructor -//----------------------------------------------------------------------------- - -virtual ~XrdSfsDirectory() {} - -}; // class XrdSfsDirectory -#endif diff --git a/src/XrdSfs/XrdSfsNative.cc b/src/XrdSfs/XrdSfsNative.cc deleted file mode 100644 index 34578af38fc..00000000000 --- a/src/XrdSfs/XrdSfsNative.cc +++ /dev/null @@ -1,1051 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d X f s N a t i v e . c c */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Deprtment of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "XrdVersion.hh" -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdSys/XrdSysLogger.hh" -#include "XrdSys/XrdSysPthread.hh" -#include "XrdSec/XrdSecInterface.hh" -#include "XrdSfs/XrdSfsAio.hh" -#include "XrdSfs/XrdSfsNative.hh" - -#ifdef AIX -#include -#endif - -/******************************************************************************/ -/* O S D i r e c t o r y H a n d l i n g I n t e r f a c e */ -/******************************************************************************/ - -#ifndef S_IAMB -#define S_IAMB 0x1FF -#endif - -/******************************************************************************/ -/* G l o b a l O b j e c t s */ -/******************************************************************************/ - -XrdSysError *XrdSfsNative::eDest; - -/******************************************************************************/ -/* U n i x F i l e S y s t e m I n t e r f a c e */ -/******************************************************************************/ - -class XrdSfsUFS -{ -public: - -static int Chmod(const char *fn, mode_t mode) {return chmod(fn, mode);} - -static int Close(int fd) {return close(fd);} - -static int Mkdir(const char *fn, mode_t mode) {return mkdir(fn, mode);} - -static int Open(const char *path, int oflag, mode_t omode) - {return open(path, oflag, omode);} - -static int Rem(const char *fn) {return unlink(fn);} - -static int Remdir(const char *fn) {return rmdir(fn);} - -static int Rename(const char *ofn, const char *nfn) {return rename(ofn, nfn);} - -static int Statfd(int fd, struct stat *buf) {return fstat(fd, buf);} - -static int Statfn(const char *fn, struct stat *buf) {return stat(fn, buf);} - -static int Truncate(const char *fn, off_t flen) {return truncate(fn, flen);} -}; - -/******************************************************************************/ -/* C o n s t r u c t o r */ -/******************************************************************************/ - -XrdSfsNative::XrdSfsNative(XrdSysError *ep) -{ - eDest = ep; -} - -/******************************************************************************/ -/* G e t F i l e S y s t e m */ -/******************************************************************************/ - -XrdSfsFileSystem *XrdSfsGetFileSystem(XrdSfsFileSystem *native_fs, - XrdSysLogger *lp) -{ - static XrdSysError Eroute(lp, "XrdSfs"); - static XrdSfsNative myFS(&Eroute); - - Eroute.Say("Copr. 2007 Stanford University/SLAC " - "sfs (Standard File System) v 9.0n"); - - return &myFS; -} - -/******************************************************************************/ -/* D i r e c t o r y O b j e c t I n t e r f a c e s */ -/******************************************************************************/ -/******************************************************************************/ -/* o p e n */ -/******************************************************************************/ - -int XrdSfsNativeDirectory::open(const char *dir_path, // In - const XrdSecClientName *client, // In - const char *info) // In -/* - Function: Open the directory `path' and prepare for reading. - - Input: path - The fully qualified name of the directory to open. - cred - Authentication credentials, if any. - info - Opaque information, if any. - - Output: Returns SFS_OK upon success, otherwise SFS_ERROR. -*/ -{ - static const char *epname = "opendir"; - -// Verify that this object is not already associated with an open directory -// - if (dh) return - XrdSfsNative::Emsg(epname, error, EADDRINUSE, - "open directory", dir_path); - -// Set up values for this directory object -// - ateof = 0; - fname = strdup(dir_path); - -// Open the directory and get it's id -// - if (!(dh = opendir(dir_path))) return - XrdSfsNative::Emsg(epname,error,errno,"open directory",dir_path); - -// All done -// - return SFS_OK; -} - -/******************************************************************************/ -/* n e x t E n t r y */ -/******************************************************************************/ - -const char *XrdSfsNativeDirectory::nextEntry() -/* - Function: Read the next directory entry. - - Input: None. - - Output: Upon success, returns the contents of the next directory entry as - a null terminated string. Returns a null pointer upon EOF or an - error. To differentiate the two cases, getErrorInfo will return - 0 upon EOF and an actual error code (i.e., not 0) on error. -*/ -{ - static const char *epname = "nextEntry"; - struct dirent *rp; - int retc; - -// Lock the direcrtory and do any required tracing -// - if (!dh) - {XrdSfsNative::Emsg(epname,error,EBADF,"read directory",fname); - return (const char *)0; - } - -// Check if we are at EOF (once there we stay there) -// - if (ateof) return (const char *)0; - -// Read the next directory entry -// -#ifdef __linux__ - errno = 0; - rp = readdir(dh); - if (!rp) - {if (!(retc = errno)) {ateof = 1; error.clear();} - else XrdSfsNative::Emsg(epname,error,retc,"read directory",fname); - d_pnt->d_name[0] = '\0'; - return (const char *)0; - } - return (const char *)(rp->d_name); -#else - if ((retc = readdir_r(dh, d_pnt, &rp))) - {XrdSfsNative::Emsg(epname,error,retc,"read directory",fname); - d_pnt->d_name[0] = '\0'; - return (const char *)0; - } - -// Check if we have reached end of file -// - if (!rp || !d_pnt->d_name[0]) - {ateof = 1; - error.clear(); - return (const char *)0; - } - -// Return the actual entry -// - return (const char *)(d_pnt->d_name); -#endif -} - -/******************************************************************************/ -/* c l o s e */ -/******************************************************************************/ - -int XrdSfsNativeDirectory::close() -/* - Function: Close the directory object. - - Input: cred - Authentication credentials, if any. - - Output: Returns SFS_OK upon success and SFS_ERROR upon failure. -*/ -{ - static const char *epname = "closedir"; - -// Release the handle -// - if (dh && closedir(dh)) - {XrdSfsNative::Emsg(epname, error, errno, "close directory", fname); - return SFS_ERROR; - } - -// Do some clean-up -// - if (fname) free(fname); - dh = (DIR *)0; - return SFS_OK; -} - -/******************************************************************************/ -/* F i l e O b j e c t I n t e r f a c e s */ -/******************************************************************************/ -/******************************************************************************/ -/* o p e n */ -/******************************************************************************/ - -int XrdSfsNativeFile::open(const char *path, // In - XrdSfsFileOpenMode open_mode, // In - mode_t Mode, // In - const XrdSecClientName *client, // In - const char *info) // In -/* - Function: Open the file `path' in the mode indicated by `open_mode'. - - Input: path - The fully qualified name of the file to open. - open_mode - One of the following flag values: - SFS_O_RDONLY - Open file for reading. - SFS_O_WRONLY - Open file for writing. - SFS_O_RDWR - Open file for update - SFS_O_CREAT - Create the file open in RDWR mode - SFS_O_TRUNC - Trunc the file open in RDWR mode - Mode - The Posix access mode bits to be assigned to the file. - These bits correspond to the standard Unix permission - bits (e.g., 744 == "rwxr--r--"). Mode may also conatin - SFS_O_MKPTH is the full path is to be created. The - agument is ignored unless open_mode = SFS_O_CREAT. - client - Authentication credentials, if any. - info - Opaque information to be used as seen fit. - - Output: Returns OOSS_OK upon success, otherwise SFS_ERROR is returned. -*/ -{ - static const char *epname = "open"; - const int AMode = S_IRWXU|S_IRWXG|S_IROTH|S_IXOTH; // 775 - char *opname; - mode_t acc_mode = Mode & S_IAMB; - int retc, open_flag = 0; - struct stat buf; - -// Verify that this object is not already associated with an open file -// - if (oh >= 0) - return XrdSfsNative::Emsg(epname,error,EADDRINUSE,"open file",path); - fname = strdup(path); - -// Set the actual open mode -// - switch(open_mode & (SFS_O_RDONLY | SFS_O_WRONLY | SFS_O_RDWR)) - { - case SFS_O_RDONLY: open_flag = O_RDONLY; break; - case SFS_O_WRONLY: open_flag = O_WRONLY; break; - case SFS_O_RDWR: open_flag = O_RDWR; break; - default: open_flag = O_RDONLY; break; - } - -// Prepare to create or open the file, as needed -// - if (open_mode & SFS_O_CREAT) - {open_flag = O_RDWR | O_CREAT | O_EXCL; - opname = (char *)"create"; - if ((Mode & SFS_O_MKPTH) && (retc = XrdSfsNative::Mkpath(path,AMode,info))) - return XrdSfsNative::Emsg(epname,error,retc,"create path for",path); - } else if (open_mode & SFS_O_TRUNC) - {open_flag = O_RDWR | O_CREAT | O_TRUNC; - opname = (char *)"truncate"; - } else opname = (char *)"open"; - -// Open the file and make sure it is a file -// - if ((oh = XrdSfsUFS::Open(path, open_flag, acc_mode)) >= 0) - {do {retc = XrdSfsUFS::Statfd(oh, &buf);} while(retc && errno == EINTR); - if (!retc && !(buf.st_mode & S_IFREG)) - {close(); oh = (buf.st_mode & S_IFDIR ? -EISDIR : -ENOTBLK);} - } else { - oh = -errno; - if (errno == EEXIST) - {do {retc = XrdSfsUFS::Statfn(path, &buf);} - while(retc && errno == EINTR); - if (!retc && (buf.st_mode & S_IFDIR)) oh = -EISDIR; - } - } - -// All done. -// - if (oh < 0) return XrdSfsNative::Emsg(epname, error, oh, opname, path); - return SFS_OK; -} - -/******************************************************************************/ -/* c l o s e */ -/******************************************************************************/ - -int XrdSfsNativeFile::close() -/* - Function: Close the file object. - - Input: None - - Output: Returns SFS_OK upon success and SFS_ERROR upon failure. -*/ -{ - static const char *epname = "close"; - -// Release the handle and return -// - if (oh >= 0 && XrdSfsUFS::Close(oh)) - return XrdSfsNative::Emsg(epname, error, errno, "close", fname); - oh = -1; - if (fname) {free(fname); fname = 0;} - return SFS_OK; -} - -/******************************************************************************/ -/* f c t l */ -/******************************************************************************/ - -int XrdSfsNativeFile::fctl(const int cmd, - const char *args, - XrdOucErrInfo &out_error) -{ -// See if we can do this -// - if (cmd == SFS_FCTL_GETFD) - {out_error.setErrCode(oh); - return SFS_OK; - } - -// We don't support this -// - out_error.setErrInfo(EEXIST, "fctl operation not supported"); - return SFS_ERROR; -} - -/******************************************************************************/ -/* r e a d */ -/******************************************************************************/ - -XrdSfsXferSize XrdSfsNativeFile::read(XrdSfsFileOffset offset, // In - char *buff, // Out - XrdSfsXferSize blen) // In -/* - Function: Read `blen' bytes at `offset' into 'buff' and return the actual - number of bytes read. - - Input: offset - The absolute byte offset at which to start the read. - buff - Address of the buffer in which to place the data. - blen - The size of the buffer. This is the maximum number - of bytes that will be read from 'fd'. - - Output: Returns the number of bytes read upon success and SFS_ERROR o/w. -*/ -{ - static const char *epname = "read"; - XrdSfsXferSize nbytes; - -// Make sure the offset is not too large -// -#if _FILE_OFFSET_BITS!=64 - if (offset > 0x000000007fffffff) - return XrdSfsNative::Emsg(epname, error, EFBIG, "read", fname); -#endif - -// Read the actual number of bytes -// - do { nbytes = pread(oh, (void *)buff, (size_t)blen, (off_t)offset); } - while(nbytes < 0 && errno == EINTR); - - if (nbytes < 0) - return XrdSfsNative::Emsg(epname, error, errno, "read", fname); - -// Return number of bytes read -// - return nbytes; -} - -/******************************************************************************/ -/* r e a d v */ -/******************************************************************************/ - -XrdSfsXferSize XrdSfsNativeFile::readv(XrdOucIOVec *readV, // In - int readCount) // In -/* - Function: Perform all the reads specified in the readV vector. - - Input: readV - A description of the reads to perform; includes the - absolute offset, the size of the read, and the buffer - to place the data into. - readCount - The size of the readV vector. - - Output: Returns the number of bytes read upon success and SFS_ERROR o/w. - If the number of bytes read is less than requested, it is considered - an error. -*/ -{ - static const char *epname = "readv"; - XrdSfsXferSize nbytes = 0; - ssize_t curCount; - int i; - - for (i=0; i 0) errno = ESPIPE; - return XrdSfsNative::Emsg(epname, error, errno, "readv", fname); - } - nbytes += curCount; - } - - return nbytes; -} - -/******************************************************************************/ -/* r e a d A I O */ -/******************************************************************************/ - -int XrdSfsNativeFile::read(XrdSfsAio *aiop) -{ - -// Execute this request in a synchronous fashion -// - aiop->Result = this->read((XrdSfsFileOffset)aiop->sfsAio.aio_offset, - (char *)aiop->sfsAio.aio_buf, - (XrdSfsXferSize)aiop->sfsAio.aio_nbytes); - aiop->doneRead(); - return 0; -} - -/******************************************************************************/ -/* w r i t e */ -/******************************************************************************/ - -XrdSfsXferSize XrdSfsNativeFile::write(XrdSfsFileOffset offset, // In - const char *buff, // In - XrdSfsXferSize blen) // In -/* - Function: Write `blen' bytes at `offset' from 'buff' and return the actual - number of bytes written. - - Input: offset - The absolute byte offset at which to start the write. - buff - Address of the buffer from which to get the data. - blen - The size of the buffer. This is the maximum number - of bytes that will be written to 'fd'. - - Output: Returns the number of bytes written upon success and SFS_ERROR o/w. - - Notes: An error return may be delayed until the next write(), close(), or - sync() call. -*/ -{ - static const char *epname = "write"; - XrdSfsXferSize nbytes; - -// Make sure the offset is not too large -// -#if _FILE_OFFSET_BITS!=64 - if (offset > 0x000000007fffffff) - return XrdSfsNative::Emsg(epname, error, EFBIG, "write", fname); -#endif - -// Write the requested bytes -// - do { nbytes = pwrite(oh, (void *)buff, (size_t)blen, (off_t)offset); } - while(nbytes < 0 && errno == EINTR); - - if (nbytes < 0) - return XrdSfsNative::Emsg(epname, error, errno, "write", fname); - -// Return number of bytes written -// - return nbytes; -} - -/******************************************************************************/ -/* w r i t e A I O */ -/******************************************************************************/ - -int XrdSfsNativeFile::write(XrdSfsAio *aiop) -{ - -// Execute this request in a synchronous fashion -// - aiop->Result = this->write((XrdSfsFileOffset)aiop->sfsAio.aio_offset, - (char *)aiop->sfsAio.aio_buf, - (XrdSfsXferSize)aiop->sfsAio.aio_nbytes); - aiop->doneWrite(); - return 0; -} - -/******************************************************************************/ -/* s t a t */ -/******************************************************************************/ - -int XrdSfsNativeFile::stat(struct stat *buf) // Out -/* - Function: Return file status information - - Input: buf - The stat structiure to hold the results - - Output: Returns SFS_OK upon success and SFS_ERROR upon failure. -*/ -{ - static const char *epname = "stat"; - -// Execute the function -// - if (XrdSfsUFS::Statfd(oh, buf)) - return XrdSfsNative::Emsg(epname, error, errno, "state", fname); - -// All went well -// - return SFS_OK; -} - -/******************************************************************************/ -/* s y n c */ -/******************************************************************************/ - -int XrdSfsNativeFile::sync() -/* - Function: Commit all unwritten bytes to physical media. - - Input: None - - Output: Returns SFS_OK upon success and SFS_ERROR upon failure. -*/ -{ - static const char *epname = "sync"; - -// Perform the function -// - if (fsync(oh)) - return XrdSfsNative::Emsg(epname,error,errno,"synchronize",fname); - -// All done -// - return SFS_OK; -} - -/******************************************************************************/ -/* s y n c A I O */ -/******************************************************************************/ - -int XrdSfsNativeFile::sync(XrdSfsAio *aiop) -{ - -// Execute this request in a synchronous fashion -// - aiop->Result = this->sync(); - aiop->doneWrite(); - return 0; -} - -/******************************************************************************/ -/* t r u n c a t e */ -/******************************************************************************/ - -int XrdSfsNativeFile::truncate(XrdSfsFileOffset flen) // In -/* - Function: Set the length of the file object to 'flen' bytes. - - Input: flen - The new size of the file. - - Output: Returns SFS_OK upon success and SFS_ERROR upon failure. - - Notes: If 'flen' is smaller than the current size of the file, the file - is made smaller and the data past 'flen' is discarded. If 'flen' - is larger than the current size of the file, a hole is created - (i.e., the file is logically extended by filling the extra bytes - with zeroes). -*/ -{ - static const char *epname = "trunc"; - -// Make sure the offset is not too larg -// - if (sizeof(off_t) < sizeof(flen) && flen > 0x000000007fffffff) - return XrdSfsNative::Emsg(epname, error, EFBIG, "truncate", fname); - -// Perform the function -// - if (ftruncate(oh, flen)) - return XrdSfsNative::Emsg(epname, error, errno, "truncate", fname); - -// All done -// - return SFS_OK; -} - -/******************************************************************************/ -/* F i l e S y s t e m O b j e c t I n t e r f a c e s */ -/******************************************************************************/ -/******************************************************************************/ -/* c h m o d */ -/******************************************************************************/ - -int XrdSfsNative::chmod(const char *path, // In - XrdSfsMode Mode, // In - XrdOucErrInfo &error, // Out - const XrdSecClientName *client, // In - const char *info) // In -/* - Function: Change the mode on a file or directory. - - Input: path - Is the fully qualified name of the file to be removed. - einfo - Error information object to hold error details. - client - Authentication credentials, if any. - info - Opaque information, if any. - - Output: Returns SFS_OK upon success and SFS_ERROR upon failure. -*/ -{ - static const char *epname = "chmod"; - mode_t acc_mode = Mode & S_IAMB; - -// Perform the actual deletion -// - if (XrdSfsUFS::Chmod(path, acc_mode) ) - return XrdSfsNative::Emsg(epname,error,errno,"change mode on",path); - -// All done -// - return SFS_OK; -} - -/******************************************************************************/ -/* e x i s t s */ -/******************************************************************************/ - -int XrdSfsNative::exists(const char *path, // In - XrdSfsFileExistence &file_exists, // Out - XrdOucErrInfo &error, // Out - const XrdSecClientName *client, // In - const char *info) // In -/* - Function: Determine if file 'path' actually exists. - - Input: path - Is the fully qualified name of the file to be tested. - file_exists - Is the address of the variable to hold the status of - 'path' when success is returned. The values may be: - XrdSfsFileExistsIsDirectory - file not found but path is valid. - XrdSfsFileExistsIsFile - file found. - XrdSfsFileExistsIsNo - neither file nor directory. - einfo - Error information object holding the details. - client - Authentication credentials, if any. - info - Opaque information, if any. - - Output: Returns SFS_OK upon success and SFS_ERROR upon failure. - - Notes: When failure occurs, 'file_exists' is not modified. -*/ -{ - static const char *epname = "exists"; - struct stat fstat; - -// Now try to find the file or directory -// - if (!XrdSfsUFS::Statfn(path, &fstat) ) - { if (S_ISDIR(fstat.st_mode)) file_exists=XrdSfsFileExistIsDirectory; - else if (S_ISREG(fstat.st_mode)) file_exists=XrdSfsFileExistIsFile; - else file_exists=XrdSfsFileExistNo; - return SFS_OK; - } - if (errno == ENOENT) - {file_exists=XrdSfsFileExistNo; - return SFS_OK; - } - -// An error occured, return the error info -// - return XrdSfsNative::Emsg(epname, error, errno, "locate", path); -} - -/******************************************************************************/ -/* f s c t l */ -/******************************************************************************/ - -int XrdSfsNative::fsctl(const int cmd, - const char *args, - XrdOucErrInfo &out_error, - const XrdSecClientName *client) -{ - out_error.setErrInfo(ENOTSUP, "Operation not supported."); - return SFS_ERROR; -} - -/******************************************************************************/ -/* g e t V e r s i o n */ -/******************************************************************************/ - -const char *XrdSfsNative::getVersion() {return XrdVERSION;} - -/******************************************************************************/ -/* m k d i r */ -/******************************************************************************/ - -int XrdSfsNative::mkdir(const char *path, // In - XrdSfsMode Mode, // In - XrdOucErrInfo &error, // Out - const XrdSecClientName *client, // In - const char *info) // In -/* - Function: Create a directory entry. - - Input: path - Is the fully qualified name of the file to be removed. - Mode - Is the POSIX mode setting for the directory. If the - mode contains SFS_O_MKPTH, the full path is created. - einfo - Error information object to hold error details. - client - Authentication credentials, if any. - info - Opaque information, if any. - - Output: Returns SFS_OK upon success and SFS_ERROR upon failure. -*/ -{ - static const char *epname = "mkdir"; - mode_t acc_mode = Mode & S_IAMB; - -// Create the path if it does not already exist -// - if (Mode & SFS_O_MKPTH) Mkpath(path, acc_mode, info); - -// Perform the actual deletion -// - if (XrdSfsUFS::Mkdir(path, acc_mode) ) - return XrdSfsNative::Emsg(epname,error,errno,"create directory",path); - -// All done -// - return SFS_OK; -} - -/******************************************************************************/ -/* M k p a t h */ -/******************************************************************************/ -/* - Function: Create a directory path - - Input: path - Is the fully qualified name of the new path. - mode - The new mode that each new directory is to have. - info - Opaque information, of any. - - Output: Returns 0 upon success and -errno upon failure. -*/ - -int XrdSfsNative::Mkpath(const char *path, mode_t mode, const char *info) -{ - char actual_path[MAXPATHLEN], *local_path, *next_path; - unsigned int plen; - struct stat buf; - -// Extract out the path we should make -// - if (!(plen = strlen(path))) return -ENOENT; - if (plen >= sizeof(actual_path)) return -ENAMETOOLONG; - strcpy(actual_path, path); - if (actual_path[plen-1] == '/') actual_path[plen-1] = '\0'; - -// Typically, the path exist. So, do a quick check before launching into it -// - if (!(local_path = rindex(actual_path, (int)'/')) - || local_path == actual_path) return 0; - *local_path = '\0'; - if (!XrdSfsUFS::Statfn(actual_path, &buf)) return 0; - *local_path = '/'; - -// Start creating directories starting with the root. Notice that we will not -// do anything with the last component. The caller is responsible for that. -// - local_path = actual_path+1; - while((next_path = index(local_path, int('/')))) - {*next_path = '\0'; - if (XrdSfsUFS::Mkdir(actual_path,mode) && errno != EEXIST) - return -errno; - *next_path = '/'; - local_path = next_path+1; - } - -// All done -// - return 0; -} - -/******************************************************************************/ -/* r e m */ -/******************************************************************************/ - -int XrdSfsNative::rem(const char *path, // In - XrdOucErrInfo &error, // Out - const XrdSecClientName *client, // In - const char *info) // In -/* - Function: Delete a file from the namespace. - - Input: path - Is the fully qualified name of the file to be removed. - einfo - Error information object to hold error details. - client - Authentication credentials, if any. - info - Opaque information, if any. - - Output: Returns SFS_OK upon success and SFS_ERROR upon failure. -*/ -{ - static const char *epname = "rem"; - -// Perform the actual deletion -// - if (XrdSfsUFS::Rem(path) ) - return XrdSfsNative::Emsg(epname, error, errno, "remove", path); - -// All done -// - return SFS_OK; -} - -/******************************************************************************/ -/* r e m d i r */ -/******************************************************************************/ - -int XrdSfsNative::remdir(const char *path, // In - XrdOucErrInfo &error, // Out - const XrdSecClientName *client, // In - const char *info) // In -/* - Function: Delete a directory from the namespace. - - Input: path - Is the fully qualified name of the dir to be removed. - einfo - Error information object to hold error details. - client - Authentication credentials, if any. - info - Opaque information, if any. - - Output: Returns SFS_OK upon success and SFS_ERROR upon failure. -*/ -{ - static const char *epname = "remdir"; - -// Perform the actual deletion -// - if (XrdSfsUFS::Remdir(path) ) - return XrdSfsNative::Emsg(epname, error, errno, "remove", path); - -// All done -// - return SFS_OK; -} - -/******************************************************************************/ -/* r e n a m e */ -/******************************************************************************/ - -int XrdSfsNative::rename(const char *old_name, // In - const char *new_name, // In - XrdOucErrInfo &error, //Out - const XrdSecClientName *client, // In - const char *infoO, // In - const char *infoN) // In -/* - Function: Renames a file/directory with name 'old_name' to 'new_name'. - - Input: old_name - Is the fully qualified name of the file to be renamed. - new_name - Is the fully qualified name that the file is to have. - error - Error information structure, if an error occurs. - client - Authentication credentials, if any. - info - old_name opaque information, if any. - info - new_name opaque information, if any. - - Output: Returns SFS_OK upon success and SFS_ERROR upon failure. -*/ -{ - static const char *epname = "rename"; - -// Perform actual rename operation -// - if (XrdSfsUFS::Rename(old_name, new_name) ) - return XrdSfsNative::Emsg(epname, error, errno, "rename", old_name); - -// All done -// - return SFS_OK; -} - -/******************************************************************************/ -/* s t a t */ -/******************************************************************************/ - -int XrdSfsNative::stat(const char *path, // In - struct stat *buf, // Out - XrdOucErrInfo &error, // Out - const XrdSecClientName *client, // In - const char *info) // In -/* - Function: Get info on 'path'. - - Input: path - Is the fully qualified name of the file to be tested. - buf - The stat structiure to hold the results - error - Error information object holding the details. - client - Authentication credentials, if any. - info - Opaque information, if any. - - Output: Returns SFS_OK upon success and SFS_ERROR upon failure. -*/ -{ - static const char *epname = "stat"; - -// Execute the function -// - if (XrdSfsUFS::Statfn(path, buf) ) - return XrdSfsNative::Emsg(epname, error, errno, "state", path); - -// All went well -// - return SFS_OK; -} - -/******************************************************************************/ -/* t r u n c a t e */ -/******************************************************************************/ - -int XrdSfsNative::truncate(const char *path, // In - XrdSfsFileOffset flen, // In - XrdOucErrInfo &error, // Out - const XrdSecClientName *client, // In - const char *info) // In -/* - Function: Set the length of the file object to 'flen' bytes. - - Input: path - The path to the file. - flen - The new size of the file. - einfo - Error information object to hold error details. - client - Authentication credentials, if any. - info - Opaque information, if any. - - Output: Returns SFS_OK upon success and SFS_ERROR upon failure. - - Notes: If 'flen' is smaller than the current size of the file, the file - is made smaller and the data past 'flen' is discarded. If 'flen' - is larger than the current size of the file, a hole is created - (i.e., the file is logically extended by filling the extra bytes - with zeroes). -*/ -{ - static const char *epname = "trunc"; - -// Make sure the offset is not too larg -// - if (sizeof(off_t) < sizeof(flen) && flen > 0x000000007fffffff) - return XrdSfsNative::Emsg(epname, error, EFBIG, "truncate", path); - -// Perform the function -// - if (XrdSfsUFS::Truncate(path, flen) ) - return XrdSfsNative::Emsg(epname, error, errno, "truncate", path); - -// All done -// - return SFS_OK; -} - -/******************************************************************************/ -/* E m s g */ -/******************************************************************************/ - -int XrdSfsNative::Emsg(const char *pfx, // Message prefix value - XrdOucErrInfo &einfo, // Place to put text & error code - int ecode, // The error code - const char *op, // Operation being performed - const char *target) // The target (e.g., fname) -{ - char *etext, buffer[MAXPATHLEN+80], unkbuff[64]; - -// Get the reason for the error -// - if (ecode < 0) ecode = -ecode; - if (!(etext = strerror(ecode))) - {sprintf(unkbuff, "reason unknown (%d)", ecode); etext = unkbuff;} - -// Format the error message -// - snprintf(buffer,sizeof(buffer),"Unable to %s %s; %s", op, target, etext); - -// Print it out if debugging is enabled -// -#ifndef NODEBUG - eDest->Emsg(pfx, buffer); -#endif - -// Place the error message in the error object and return -// - einfo.setErrInfo(ecode, buffer); - - return SFS_ERROR; -} diff --git a/src/XrdSfs/XrdSfsNative.hh b/src/XrdSfs/XrdSfsNative.hh deleted file mode 100644 index 89c153ec487..00000000000 --- a/src/XrdSfs/XrdSfsNative.hh +++ /dev/null @@ -1,252 +0,0 @@ -#ifndef __SFS_NATIVE_H__ -#define __SFS_NATIVE_H__ -/******************************************************************************/ -/* */ -/* X r d S f s N a t i v e . h h */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include - -#include "XrdSfs/XrdSfsInterface.hh" - -class XrdSysError; -class XrdSysLogger; - -/******************************************************************************/ -/* X r d S f s N a t i v e D i r e c t o r y */ -/******************************************************************************/ - -class XrdSfsNativeDirectory : public XrdSfsDirectory -{ -public: - - int open(const char *dirName, - const XrdSecClientName *client = 0, - const char *opaque = 0); - - const char *nextEntry(); - - int close(); - -const char *FName() {return (const char *)fname;} - - XrdSfsNativeDirectory(char *user=0, int monid=0) - : XrdSfsDirectory(user, monid) - {ateof = 0; fname = 0; - dh = (DIR *)0; - d_pnt = &dirent_full.d_entry; - } - - ~XrdSfsNativeDirectory() {if (dh) close();} -private: - -DIR *dh; // Directory stream handle -char ateof; -char *fname; - -struct {struct dirent d_entry; - char pad[MAXNAMLEN]; // This is only required for Solaris! - } dirent_full; - -struct dirent *d_pnt; - -}; - -/******************************************************************************/ -/* X r d S f s N a t i v e F i l e */ -/******************************************************************************/ - -class XrdSfsAio; - -class XrdSfsNativeFile : public XrdSfsFile -{ -public: - - int open(const char *fileName, - XrdSfsFileOpenMode openMode, - mode_t createMode, - const XrdSecClientName *client = 0, - const char *opaque = 0); - - int close(); - - using XrdSfsFile::fctl; - int fctl(const int cmd, - const char *args, - XrdOucErrInfo &out_error); - - const char *FName() {return fname;} - - int getMmap(void **Addr, off_t &Size) - {if (Addr) Addr = 0; Size = 0; return SFS_OK;} - - int read(XrdSfsFileOffset fileOffset, - XrdSfsXferSize preread_sz) {return SFS_OK;} - - XrdSfsXferSize read(XrdSfsFileOffset fileOffset, - char *buffer, - XrdSfsXferSize buffer_size); - - int read(XrdSfsAio *aioparm); - - XrdSfsXferSize readv(XrdOucIOVec *readV, - int readCount); - - XrdSfsXferSize write(XrdSfsFileOffset fileOffset, - const char *buffer, - XrdSfsXferSize buffer_size); - - int write(XrdSfsAio *aioparm); - - int sync(); - - int sync(XrdSfsAio *aiop); - - int stat(struct stat *buf); - - int truncate(XrdSfsFileOffset fileOffset); - - int getCXinfo(char cxtype[4], int &cxrsz) {return cxrsz = 0;} - - XrdSfsNativeFile(char *user=0, int monid=0) - : XrdSfsFile(user, monid) - {oh = -1; fname = 0;} - ~XrdSfsNativeFile() {if (oh) close();} -private: - -int oh; -char *fname; - -}; - -/******************************************************************************/ -/* X r d S f s N a t i v e */ -/******************************************************************************/ - -class XrdSfsNative : public XrdSfsFileSystem -{ -public: - -// Object Allocation Functions -// - XrdSfsDirectory *newDir(char *user=0, int monid=0) - {return (XrdSfsDirectory *)new XrdSfsNativeDirectory(user,monid);} - - XrdSfsFile *newFile(char *user=0,int monid=0) - {return (XrdSfsFile *)new XrdSfsNativeFile(user,monid);} - -// Other Functions -// - int chmod(const char *Name, - XrdSfsMode Mode, - XrdOucErrInfo &out_error, - const XrdSecClientName *client = 0, - const char *opaque = 0); - - int exists(const char *fileName, - XrdSfsFileExistence &exists_flag, - XrdOucErrInfo &out_error, - const XrdSecClientName *client = 0, - const char *opaque = 0); - - int fsctl(const int cmd, - const char *args, - XrdOucErrInfo &out_error, - const XrdSecClientName *client = 0); - - int getStats(char *buff, int blen) {return 0;} - -const char *getVersion(); - - int mkdir(const char *dirName, - XrdSfsMode Mode, - XrdOucErrInfo &out_error, - const XrdSecClientName *client = 0, - const char *opaque = 0); - - int prepare( XrdSfsPrep &pargs, - XrdOucErrInfo &out_error, - const XrdSecClientName *client = 0) {return 0;} - - int rem(const char *path, - XrdOucErrInfo &out_error, - const XrdSecClientName *client = 0, - const char *opaque = 0); - - int remdir(const char *dirName, - XrdOucErrInfo &out_error, - const XrdSecClientName *client = 0, - const char *opaque = 0); - - int rename(const char *oldFileName, - const char *newFileName, - XrdOucErrInfo &out_error, - const XrdSecClientName *client = 0, - const char *opaqueO = 0, - const char *opaqueN = 0); - - int stat(const char *Name, - struct stat *buf, - XrdOucErrInfo &out_error, - const XrdSecClientName *client = 0, - const char *opaque = 0); - - int stat(const char *Name, - mode_t &mode, - XrdOucErrInfo &out_error, - const XrdSecClientName *client = 0, - const char *opaque = 0) - {struct stat bfr; - int rc = stat(Name, &bfr, out_error, client); - if (!rc) mode = bfr.st_mode; - return rc; - } - - int truncate(const char *Name, - XrdSfsFileOffset fileOffset, - XrdOucErrInfo &out_error, - const XrdSecEntity *client = 0, - const char *opaque = 0); - -// Common functions -// -static int Mkpath(const char *path, mode_t mode, - const char *info=0); - -static int Emsg(const char *, XrdOucErrInfo&, int, const char *x, - const char *y=""); - - XrdSfsNative(XrdSysError *lp); -virtual ~XrdSfsNative() {} - -private: - -static XrdSysError *eDest; -}; -#endif diff --git a/src/XrdSfs/XrdSfsXio.hh b/src/XrdSfs/XrdSfsXio.hh deleted file mode 100644 index 702260fcbf6..00000000000 --- a/src/XrdSfs/XrdSfsXio.hh +++ /dev/null @@ -1,124 +0,0 @@ -#ifndef __SFS_XIO_H__ -#define __SFS_XIO_H__ -/******************************************************************************/ -/* */ -/* X r d S f s X i o . h h */ -/* */ -/* (c) 2013 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -//----------------------------------------------------------------------------- -//! XrdSfsXio.hh -//! -//! This class is used to allow file I/O interfaces to perform exchange buffer -//! I/O in order to minimize data copying. When this feature is enabled, the -//! XrdSfsInterface::setXio() method is called on a newly created XrdSfsFile -//! object. Ideally, all oustanding buffers should be be released when the file -//! is closed. Alternatively, the XrdSfsXioHandle::Recycle() method may be used -//! at any time when it is convenient to do so. For best performance, use -//! XrdSfsXio::Swap() as it provides memory locality and is kind to the cache. -//! Buffer swapping is only supported for file write operations. -//----------------------------------------------------------------------------- - -//----------------------------------------------------------------------------- -//! The XrdSfsXioHandle class describes a handle to a buffer returned by -//! XrdSfsXio::Swap(). -//----------------------------------------------------------------------------- - -class XrdSfsXioHandle -{ -public: - -//----------------------------------------------------------------------------- -//! Obtain te address and, optionally, the length of the associated buffer. -//! -//! @param blen When not null will hold the length of the buffer. This is -//! not to be confused with the length of data in the buffer! -//! -//! @return Pointer to the buffer. -//----------------------------------------------------------------------------- - -virtual char *Buffer(int **blen=0) = 0; - -//----------------------------------------------------------------------------- -//! Recycle a buffer that was previously given to the caller via -//! XrdSfsXio::Swap(). Use it when future swaps will no longer be requested. -//----------------------------------------------------------------------------- - -virtual void Recycle() = 0; - - XrdSfsXioHandle() {} -virtual ~XrdSfsXioHandle() {} -}; - -/******************************************************************************/ -/* C l a s s X r d S f s X i o */ -/******************************************************************************/ - -class XrdSfsXio -{ -public: - -//----------------------------------------------------------------------------- -//! Values return by Swap(). -//----------------------------------------------------------------------------- - -enum XioStatus {allOK = 0, //!< Successful completion - BadBuff, //!< Swap failed, curBuff is bad. - BadHandle, //!< Swap failed, oHandle is bad - NotWrite, //!< Swap failed, not from a write() call - TooMany //!< Swap failed, too many buffs outstanding - }; - -//----------------------------------------------------------------------------- -//! Swap the current I/O buffer -//! -//! @param curBuff - The address of the current buffer. It must match the -//! the buffer that was most recently passed to the caller. -//! @param curHand - Where the handle associated with curBuff is to be placed. -//! @param oldHand - The handle associated with a buffer returned by a -//! previous call to Swap(). A value of zero indicates that -//! the caller is taking control of the buffer but has no -//! replacement buffer. -//! @return !allOK One or more arguments or context is invalid. Nothing was -//! swapped. The returned value describes the problem. The -//! curHand has been set to zero. -//! @return =allOK The handle associated with curBuff has been placed in -//! curHand. This handle must be used in a future Swap() call. -//----------------------------------------------------------------------------- - -virtual XioStatus Swap(const char * curBuff, - XrdSfsXioHandle *&curHand, - XrdSfsXioHandle * oldHand=0 - ) = 0; - -//----------------------------------------------------------------------------- -//! Constructor and destructor -//----------------------------------------------------------------------------- - - XrdSfsXio() {} -virtual ~XrdSfsXio() {} -}; -#endif diff --git a/src/XrdSsi.cmake b/src/XrdSsi.cmake deleted file mode 100644 index f3105e5fe85..00000000000 --- a/src/XrdSsi.cmake +++ /dev/null @@ -1,140 +0,0 @@ - -include( XRootDCommon ) - -#------------------------------------------------------------------------------- -# Modules -#------------------------------------------------------------------------------- -set( LIB_XRD_SSI XrdSsi-${PLUGIN_VERSION} ) -set( LIB_XRD_SSILOG XrdSsiLog-${PLUGIN_VERSION} ) - -#------------------------------------------------------------------------------- -# Shared library version -#------------------------------------------------------------------------------- -set( XRD_SSI_LIB_VERSION 1.0.0 ) -set( XRD_SSI_LIB_SOVERSION 1 ) -set( XRD_SSI_SHMAP_VERSION 1.0.0 ) -set( XRD_SSI_SHMAP_SOVERSION 1 ) - -#------------------------------------------------------------------------------- -# The XrdSsiLib library -#------------------------------------------------------------------------------- -add_library( - XrdSsiLib - SHARED -XrdSsi/XrdSsiAlert.cc XrdSsi/XrdSsiAlert.hh - XrdSsi/XrdSsiAtomics.hh - XrdSsi/XrdSsiBVec.hh -XrdSsi/XrdSsiClient.cc - XrdSsi/XrdSsiCluster.hh -XrdSsi/XrdSsiCms.cc XrdSsi/XrdSsiCms.hh - XrdSsi/XrdSsiErrInfo.hh -XrdSsi/XrdSsiEvent.cc XrdSsi/XrdSsiEvent.hh -XrdSsi/XrdSsiFileResource.cc XrdSsi/XrdSsiFileResource.hh -XrdSsi/XrdSsiLogger.cc XrdSsi/XrdSsiLogger.hh -XrdSsi/XrdSsiPacer.cc XrdSsi/XrdSsiPacer.hh - XrdSsi/XrdSsiProvider.hh - XrdSsi/XrdSsiRRAgent.hh - XrdSsi/XrdSsiRRInfo.hh - XrdSsi/XrdSsiRRTable.hh -XrdSsi/XrdSsiRequest.cc XrdSsi/XrdSsiRequest.hh -XrdSsi/XrdSsiResponder.cc XrdSsi/XrdSsiResponder.hh - XrdSsi/XrdSsiResource.hh - XrdSsi/XrdSsiScale.hh -XrdSsi/XrdSsiServReal.cc XrdSsi/XrdSsiServReal.hh -XrdSsi/XrdSsiService.cc XrdSsi/XrdSsiService.hh -XrdSsi/XrdSsiSessReal.cc XrdSsi/XrdSsiSessReal.hh - XrdSsi/XrdSsiStream.hh -XrdSsi/XrdSsiTaskReal.cc XrdSsi/XrdSsiTaskReal.hh - XrdSsi/XrdSsiTrace.hh -XrdSsi/XrdSsiUtils.cc XrdSsi/XrdSsiUtils.hh) - -target_link_libraries( - XrdSsiLib - XrdCl - XrdServer - XrdUtils - pthread ) - -set_target_properties( - XrdSsiLib - PROPERTIES - VERSION ${XRD_SSI_LIB_VERSION} - SOVERSION ${XRD_SSI_LIB_SOVERSION} - LINK_INTERFACE_LIBRARIES "" ) - -#------------------------------------------------------------------------------- -# The XrdSsiShMap library -#------------------------------------------------------------------------------- -add_library( - XrdSsiShMap - SHARED -XrdSsi/XrdSsiShMam.cc XrdSsi/XrdSsiShMam.hh -XrdSsi/XrdSsiShMap.icc XrdSsi/XrdSsiShMap.hh -XrdSsi/XrdSsiShMat.cc XrdSsi/XrdSsiShMat.hh) - -target_link_libraries( - XrdSsiShMap - ${ZLIB_LIBRARY} - pthread ) - -set_target_properties( - XrdSsiShMap - PROPERTIES - VERSION ${XRD_SSI_SHMAP_VERSION} - SOVERSION ${XRD_SSI_SHMAP_SOVERSION} - LINK_INTERFACE_LIBRARIES "" ) - -#------------------------------------------------------------------------------- -# The XrdSsi plugin -#------------------------------------------------------------------------------- -add_library( - ${LIB_XRD_SSI} - MODULE - XrdSsi/XrdSsiDir.cc XrdSsi/XrdSsiDir.hh - XrdSsi/XrdSsiFile.cc XrdSsi/XrdSsiFile.hh - XrdSsi/XrdSsiFileReq.cc XrdSsi/XrdSsiFileReq.hh - XrdSsi/XrdSsiFileSess.cc XrdSsi/XrdSsiFileSess.hh - XrdSsi/XrdSsiSfs.cc XrdSsi/XrdSsiSfs.hh - XrdSsi/XrdSsiSfsConfig.cc XrdSsi/XrdSsiSfsConfig.hh - XrdSsi/XrdSsiStat.cc -) - -target_link_libraries( - ${LIB_XRD_SSI} - XrdSsiLib - XrdUtils - XrdServer ) - -set_target_properties( - ${LIB_XRD_SSI} - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) - -#------------------------------------------------------------------------------- -# The XrdSsiLog plugin -#------------------------------------------------------------------------------- -add_library( - ${LIB_XRD_SSILOG} - MODULE - XrdSsi/XrdSsiLogging.cc -) - -target_link_libraries( - ${LIB_XRD_SSILOG} - XrdSsiLib - XrdUtils - XrdServer ) - -set_target_properties( - ${LIB_XRD_SSILOG} - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) - -#------------------------------------------------------------------------------- -# Install -#------------------------------------------------------------------------------- -install( - TARGETS XrdSsiLib XrdSsiShMap ${LIB_XRD_SSI} ${LIB_XRD_SSILOG} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ) diff --git a/src/XrdSsi/XrdSsiAlert.cc b/src/XrdSsi/XrdSsiAlert.cc deleted file mode 100644 index 1217a7415ee..00000000000 --- a/src/XrdSsi/XrdSsiAlert.cc +++ /dev/null @@ -1,159 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S s i A l e r t . c c */ -/* */ -/* (c) 2017 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include - -#include "XrdOuc/XrdOucErrInfo.hh" -#include "XrdSsi/XrdSsiAlert.hh" -#include "XrdSsi/XrdSsiRRInfo.hh" - -/******************************************************************************/ -/* S t a t i c s */ -/******************************************************************************/ - -XrdSysMutex XrdSsiAlert::aMutex; -XrdSsiAlert *XrdSsiAlert::free = 0; -int XrdSsiAlert::fNum = 0; -int XrdSsiAlert::fMax = XrdSsiAlert::fmaxDflt; - -/******************************************************************************/ -/* A l l o c */ -/******************************************************************************/ - -XrdSsiAlert *XrdSsiAlert::Alloc(XrdSsiRespInfoMsg &aMsg) -{ - XrdSsiAlert *aP; - -// Obtain a lock -// - aMutex.Lock(); - -// Allocate via stack or a new call -// - if (!(aP = free)) aP = new XrdSsiAlert(); - else {free = aP->next; fNum--;} - -// Unlock mutex -// - aMutex.UnLock(); - -// Fill out object and return it -// - aP->next = 0; - aP->theMsg = &aMsg; - return aP; -} - -/******************************************************************************/ -/* D o n e */ -/******************************************************************************/ - -// Gets invoked only after query() on wtresp signal was sent - -void XrdSsiAlert::Done(int &retc, XrdOucErrInfo *eiP, const char *name) -{ - -// This is an async callback so we need to delete our errinfo object. -// - delete eiP; - -// Simply recycle this object. -// - Recycle(); -} - -/******************************************************************************/ -/* R e c y c l e */ -/******************************************************************************/ - -void XrdSsiAlert::Recycle() -{ - -// Issue callback to release the message if we have one -// - if (theMsg) theMsg->RecycleMsg(); - -// Place object on the queue unless we have too many -// - aMutex.Lock(); - if (fNum >= fMax) delete this; - else {next = free; free = this; fNum++;} - aMutex.UnLock(); -} - -/******************************************************************************/ -/* S e t I n f o */ -/******************************************************************************/ - -int XrdSsiAlert::SetInfo(XrdOucErrInfo &eInfo, char *aMsg, int aLen) -{ - static const int aIovSz = 3; - struct AlrtResp {struct iovec ioV[aIovSz]; XrdSsiRRInfoAttn aHdr;}; - - AlrtResp *alrtResp; - char *mBuff, *aData; - int n; - -// We will be constructing the response in the message buffer. This is -// gauranteed to be big enough for our purposes so no need to check the size. -// - mBuff = eInfo.getMsgBuff(n); - -// Initialize the response -// - alrtResp = (AlrtResp *)mBuff; - memset(alrtResp, 0, sizeof(AlrtResp)); - alrtResp->aHdr.pfxLen = htons(sizeof(XrdSsiRRInfoAttn)); - -// Fill out iovec to point to our header -// -// alrtResp->ioV[0].iov_len = sizeof(XrdSsiRRInfoAttn) + msgBlen; - alrtResp->ioV[1].iov_base = mBuff+offsetof(struct AlrtResp, aHdr); - alrtResp->ioV[1].iov_len = sizeof(XrdSsiRRInfoAttn); - -// Fill out the iovec for the alert data -// - aData = theMsg->GetMsg(n); - alrtResp->ioV[2].iov_base = aData; - alrtResp->ioV[2].iov_len = n; - alrtResp->aHdr.mdLen = htonl(n); - alrtResp->aHdr.tag = XrdSsiRRInfoAttn::alrtResp; - -// Return up to 8 bytes of alert data for debugging purposes -// - if (aMsg) memcpy(aMsg, aData, (n < (int)sizeof(aMsg) ? n : 8)); - -// Setup to have metadata actually sent to the requestor -// - eInfo.setErrCode(aIovSz); - return n; -} diff --git a/src/XrdSsi/XrdSsiAlert.hh b/src/XrdSsi/XrdSsiAlert.hh deleted file mode 100644 index 65d16e8c703..00000000000 --- a/src/XrdSsi/XrdSsiAlert.hh +++ /dev/null @@ -1,72 +0,0 @@ -#ifndef _XRDSSIALERT_H -#define _XRDSSIALERT_H -/******************************************************************************/ -/* */ -/* X r d S s i A l e r t . h h */ -/* */ -/* (c) 2017 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdOuc/XrdOucErrInfo.hh" -#include "XrdSsi/XrdSsiRequest.hh" -#include "XrdSys/XrdSysPthread.hh" - -class XrdSsiAlert : public XrdOucEICB -{ -public: - -XrdSsiAlert *next; - -static XrdSsiAlert *Alloc(XrdSsiRespInfoMsg &aMsg); - - void Recycle(); - - int SetInfo(XrdOucErrInfo &eInfo, char* aMsg, int aLen); - -static void SetMax(int maxval) {fMax = maxval;} - -// OucEICB methods -// - void Done(int &Result, XrdOucErrInfo *cbInfo, - const char *path=0); - - int Same(unsigned long long arg1, unsigned long long arg2) - {return 0;} - - XrdSsiAlert() {} - ~XrdSsiAlert() {} -private: - -static XrdSysMutex aMutex; -static XrdSsiAlert *free; -static int fNum; -static int fMax; - -static const int fmaxDflt = 100; - -XrdSsiRespInfoMsg *theMsg; -}; -#endif diff --git a/src/XrdSsi/XrdSsiAtomics.hh b/src/XrdSsi/XrdSsiAtomics.hh deleted file mode 100644 index ffe0a938ea6..00000000000 --- a/src/XrdSsi/XrdSsiAtomics.hh +++ /dev/null @@ -1,175 +0,0 @@ -#ifndef __SSIATOMICS_HH__ -#define __SSIATOMICS_HH__ -/******************************************************************************/ -/* */ -/* X r d S s i A t o m i c s . h h */ -/* */ -/* (c) 2015 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -#undef NEED_ATOMIC_MUTEX - -//----------------------------------------------------------------------------- -//! Use native atomics at the c11 or higher level (-std=c++0x -lstdc++) -//----------------------------------------------------------------------------- -#if __cplusplus >= 201103L -#include -#define Atomic(type) std::atomic -#define Atomic_IMP "C++11" -#define Atomic_BEG(x) -#define Atomic_DEC(x) x.fetch_sub(1,std::memory_order_relaxed) -#define Atomic_GET(x) x.load(std::memory_order_relaxed) -#define Atomic_GET_STRICT(x) x.load(std::memory_order_acquire) -#define Atomic_INC(x) x.fetch_add(1,std::memory_order_relaxed) -#define Atomic_SET(x,y) x.store(y,std::memory_order_relaxed) -#define Atomic_SET_STRICT(x,y) x.store(y,std::memory_order_release) -#define Atomic_ZAP(x) x.store(0,std::memory_order_relaxed) -#define Atomic_END(x) - -//----------------------------------------------------------------------------- -//! Use new gcc builtins at 4.7 or above -//----------------------------------------------------------------------------- -#elif __GNUC__ == 4 && __GNUC_MINOR__ > 6 -#define Atomic(type) type -#define Atomic_IMP "gnu-atomic" -#define Atomic_BEG(x) -#define Atomic_DEC(x) __atomic_fetch_sub(&x,1,__ATOMIC_RELAXED) -#define Atomic_GET(x) __atomic_load_n (&x, __ATOMIC_RELAXED) -#define Atomic_GET_STRICT(x) __atomic_load_n (&x, __ATOMIC_ACQUIRE) -#define Atomic_INC(x) __atomic_fetch_add(&x,1,__ATOMIC_RELAXED) -#define Atomic_SET(x,y) __atomic_store_n (&x,y,__ATOMIC_RELAXED) -#define Atomic_SET_STRICT(x,y) __atomic_store_n (&x,y,__ATOMIC_RELEASE) -#define Atomic_ZAP(x) __atomic_store_n (&x,0,__ATOMIC_RELAXED) -#define Atomic_END(x) - -//----------------------------------------------------------------------------- -//! Use old-style gcc builtins if they area available. The STRICT variants -//! are only effective on strict memory compliant machines (e.g. x86, SPARC). -//! This doesn't get resolved until gcc 4.7, sigh. -//----------------------------------------------------------------------------- -#elif HAVE_ATOMICS -#define Atomic(type) type -#define Atomic_IMP "gnu-sync" -#define Atomic_BEG(x) -#define Atomic_DEC(x) __sync_fetch_and_sub(&x, 1) -#define Atomic_GET(x) __sync_fetch_and_or (&x, 0) -#define Atomic_GET_STRICT(x) __sync_fetch_and_or (&x, 0) -#define Atomic_INC(x) __sync_fetch_and_add(&x, 1) -#define Atomic_SET(x,y) x=y,__sync_synchronize() -#define Atomic_SET_STRICT(x,y) __sync_synchronize(),x=y,__sync_synchronize() -#define Atomic_ZAP(x) __sync_fetch_and_and(&x, 0) -#define Atomic_END(x) - -//----------------------------------------------------------------------------- -//! Use ordinary operators since the program needs to use mutexes -//----------------------------------------------------------------------------- -#else -#define NEED_ATOMIC_MUTEX 1 -#define Atomic_IMP "missing" -#define Atomic(type) type -#define Atomic_BEG(x) pthread_mutex_lock(x) -#define Atomic_DEC(x) x-- -#define Atomic_GET(x) x -#define Atomic_INC(x) x++ -#define Atomic_SET(x,y) x = y -#define Atomic_ZAP(x) x = 0 -#define Atomic_END(x) pthread_mutex_unlock(x) -#endif - -/******************************************************************************/ -/* */ -/* X r d S s i M u t e x */ -/******************************************************************************/ - -#include - -class XrdSsiMutex -{ -public: - -inline bool TryLock() {return pthread_mutex_trylock( &cs ) == 0;} - -inline void Lock() {pthread_mutex_lock(&cs);} - -inline void UnLock() {pthread_mutex_unlock(&cs);} - -enum MutexType {Simple = 0, Recursive = 1}; - - XrdSsiMutex(MutexType mt=Simple) - {int rc; - if (mt == Simple) rc = pthread_mutex_init(&cs, NULL); - else {pthread_mutexattr_t attr; - if (!(rc = pthread_mutexattr_init(&attr))) - {pthread_mutexattr_settype(&attr, - PTHREAD_MUTEX_RECURSIVE); - rc = pthread_mutex_init(&cs, &attr); - } - } - if (rc) throw strerror(rc); - } - - ~XrdSsiMutex() {pthread_mutex_destroy(&cs);} - -protected: - -pthread_mutex_t cs; -}; - -/******************************************************************************/ -/* X r d S s i M u t e x M o n */ -/******************************************************************************/ - -class XrdSsiMutexMon -{ -public: - -inline void Lock(XrdSsiMutex *mutex) - {if (mtx) {if (mtx != mutex) mtx->UnLock(); - else return; - } - mutex->Lock(); - mtx = mutex; - }; - -inline void Lock(XrdSsiMutex &mutex) {Lock(&mutex);} - -inline void UnLock() {if (mtx) {mtx->UnLock(); mtx = 0;}} - - XrdSsiMutexMon(XrdSsiMutex *mutex=0) - {if (mutex) mutex->Lock(); - mtx = mutex; - } - XrdSsiMutexMon(XrdSsiMutex &mutex) - {mutex.Lock(); - mtx = &mutex; - } - - ~XrdSsiMutexMon() {if (mtx) UnLock();} -private: -XrdSsiMutex *mtx; -}; -#endif diff --git a/src/XrdSsi/XrdSsiBVec.hh b/src/XrdSsi/XrdSsiBVec.hh deleted file mode 100644 index 1a484dd2145..00000000000 --- a/src/XrdSsi/XrdSsiBVec.hh +++ /dev/null @@ -1,65 +0,0 @@ -#ifndef __XRDSSIBVEC_HH__ -#define __XRDSSIBVEC_HH__ -/******************************************************************************/ -/* */ -/* X r d S s i B V e c . h h */ -/* */ -/* (c) 2013 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -class XrdSsiBVec -{ -public: - -inline void Set(uint32_t bval) - {if (bval < 64) bitVec |= 1LL << bval; - else theSet.insert(bval); - } - -inline bool IsSet(uint32_t bval) - {if (bval < 64) return bitVec & 1LL << bval; - std::set::iterator it = theSet.find(bval); - return it != theSet.end(); - } - -inline void UnSet(uint32_t bval) - {if (bval < 64) bitVec &= ~(1LL< theSet; -}; -#endif diff --git a/src/XrdSsi/XrdSsiClient.cc b/src/XrdSsi/XrdSsiClient.cc deleted file mode 100644 index e1d4dba66a2..00000000000 --- a/src/XrdSsi/XrdSsiClient.cc +++ /dev/null @@ -1,308 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S s i G e t C l i e n t S e r v i c e . c c */ -/* */ -/* (c) 2013 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "Xrd/XrdScheduler.hh" -#include "Xrd/XrdTrace.hh" - -#include "XrdCl/XrdClDefaultEnv.hh" - -#include "XrdNet/XrdNetAddr.hh" - -#include "XrdSsi/XrdSsiAtomics.hh" -#include "XrdSsi/XrdSsiLogger.hh" -#include "XrdSsi/XrdSsiProvider.hh" -#include "XrdSsi/XrdSsiServReal.hh" -#include "XrdSsi/XrdSsiTrace.hh" - -#include "XrdSys/XrdSysLogger.hh" -#include "XrdSys/XrdSysLogging.hh" -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysPthread.hh" -#include "XrdSys/XrdSysTrace.hh" - -/******************************************************************************/ -/* N a m e S p a c e G l o b a l s */ -/******************************************************************************/ - -namespace XrdSsi -{ -extern XrdSysError Log; -extern XrdSysLogger *Logger; -extern XrdSysTrace Trace; -extern XrdSsiLogger::MCB_t *msgCB; -extern XrdSsiLogger::MCB_t *msgCBCl; - - XrdSysMutex clMutex; - XrdScheduler *schedP = 0; - XrdCl::Env *clEnvP = 0; - short maxTCB = 300; - short maxCLW = 30; - Atomic(bool) initDone(false); - bool dsTTLSet = false; - bool reqTOSet = false; - bool strTOSet = false; -} - -using namespace XrdSsi; - -/******************************************************************************/ -/* L o c a l C l a s s e s */ -/******************************************************************************/ - -class XrdSsiClientProvider : public XrdSsiProvider -{ -public: - -XrdSsiService *GetService(XrdSsiErrInfo &eInfo, - const std::string &contact, - int oHold=256 - ); - -virtual bool Init(XrdSsiLogger *logP, - XrdSsiCluster *clsP, - std::string cfgFn, - std::string parms, - int argc, - char **argv - ) {return true;} - -virtual rStat QueryResource(const char *rName, - const char *contact=0 - ) {return notPresent;} - -virtual void SetCBThreads(int cbNum, int ntNum); - -virtual void SetTimeout(tmoType what, int tmoval); - - XrdSsiClientProvider() {} -virtual ~XrdSsiClientProvider() {} - -private: -void SetLogger(); -void SetScheduler(); -}; - -/******************************************************************************/ -/* X r d S s i C l i e n t P r o v i d e r : : G e t S e r v i c e */ -/******************************************************************************/ - -XrdSsiService *XrdSsiClientProvider::GetService(XrdSsiErrInfo &eInfo, - const std::string &contact, - int oHold) -{ - static const int maxTMO = 0x7fffffff; - XrdNetAddr netAddr; - const char *eText; - char buff[512]; - int n; - -// Allocate a scheduler if we do not have one and set default env (1st call) -// - if (!Atomic_GET(initDone)) - {clMutex.Lock(); - if (!Logger) SetLogger(); - if (!schedP) SetScheduler(); - if (!clEnvP) clEnvP = XrdCl::DefaultEnv::GetEnv(); - if (!dsTTLSet) clEnvP->PutInt("DataServerTTL", maxTMO); - if (!reqTOSet) clEnvP->PutInt("RequestTimeout", maxTMO); - if (!strTOSet) clEnvP->PutInt("StreamTimeout", maxTMO); - initDone = true; - clMutex.UnLock(); - } - -// If no contact is given then declare an error -// - if (contact.empty()) - {eInfo.Set("Contact not specified.", EINVAL); return 0;} - -// Validate the given contact -// - if ((eText = netAddr.Set(contact.c_str()))) - {eInfo.Set(eText, EINVAL); return 0;} - -// Construct new binding -// - if (!(n = netAddr.Format(buff, sizeof(buff), XrdNetAddrInfo::fmtName))) - {eInfo.Set("Unable to validate contact.", EINVAL); return 0;} - -// Allocate a service object and return it -// - return new XrdSsiServReal(buff, oHold); -} - -/******************************************************************************/ -/* X r d S s i C l i e n t P r o v i d e r : : S e t C B T h r e a d s */ -/******************************************************************************/ - -void XrdSsiClientProvider::SetCBThreads(int cbNum, int ntNum) -{ -// Validate thread number -// - if (cbNum > 1) - {if (cbNum > 32767) cbNum = 32767; // Max short value - if (ntNum < 1) ntNum = cbNum*10/100; - if (ntNum < 3) ntNum = 0; - else if (ntNum > 100) ntNum = 100; - clMutex.Lock(); - maxTCB = static_cast(cbNum); - maxCLW = static_cast(ntNum); - clMutex.UnLock(); - } -} - -/******************************************************************************/ -/* X r d S s i C l i e n t P r o v i d e r : : S e t L o g g e r */ -/******************************************************************************/ - -void XrdSsiClientProvider::SetLogger() -{ - int eFD; - -// Get a file descriptor mirroring standard error -// -#if defined(__linux__) && defined(O_CLOEXEC) - eFD = fcntl(STDERR_FILENO, F_DUPFD_CLOEXEC, 0); -#else - eFD = dup(STDERR_FILENO); - fcntl(eFD, F_SETFD, FD_CLOEXEC); -#endif - -// Now we need to get a logger object. We make this a real dumb one. -// - Logger = new XrdSysLogger(eFD, 0); - Log.logger(Logger); - Trace.SetLogger(Logger); - if (getenv("XRDSSIDEBUG") != 0) Trace.What = TRACESSI_Debug; - -// Check for a message callback object. This must be set at global init time. -// - if (msgCBCl) - {XrdSysLogging::Parms logParms; - msgCB = msgCBCl; - logParms.logpi = msgCBCl; - logParms.bufsz = 0; - XrdSysLogging::Configure(*Logger, logParms); - } -} - -/******************************************************************************/ -/* X r d S s i C l i e n t P r o v i d e r : : S e t S c h e d u l e r */ -/******************************************************************************/ - -// This may not be called before the logger object is created! - -void XrdSsiClientProvider::SetScheduler() -{ - static XrdOucTrace myTrc(&Log); - -// Now construct the proper trace object (note that we do not set tracing if -// message forwarding is on because these messages will not be forwarded). -// This must be fixed when xrootd starts using XrdSysTrace!!! -// - if (!msgCBCl && Trace.What & TRACESSI_Debug) myTrc.What = TRACE_SCHED; - -// We can now set allocate a scheduler -// - XrdSsi::schedP = new XrdScheduler(&Log, &myTrc); - -// Set thread count for callbacks -// - XrdSsi::schedP->setParms(-1, maxTCB, -1, -1, 0); - -// Set number of framework worker hreads if need be -// - if (maxCLW) - {if (!XrdSsi::clEnvP) clEnvP = XrdCl::DefaultEnv::GetEnv(); - clEnvP->PutInt("WorkerThreads", maxCLW); - } - -// Start the scheduler -// - XrdSsi::schedP->Start(); -} - -/******************************************************************************/ -/* X r d S s i C l i e n t P r o v i d e r : : S e t T i m e o u t */ -/******************************************************************************/ - -void XrdSsiClientProvider::SetTimeout(XrdSsiProvider::tmoType what, int tmoval) -{ - -// Ignore invalid timeouts -// - if (tmoval <= 0) return; - -// Get global environment -// - clMutex.Lock(); - if (!XrdSsi::clEnvP) clEnvP = XrdCl::DefaultEnv::GetEnv(); - -// Set requested timeout -// - switch(what) - {case connect_N: clEnvP->PutInt("ConnectionRetry", tmoval); - break; - case connect_T: clEnvP->PutInt("ConnectionWindow", tmoval); - break; - case idleClose: clEnvP->PutInt("DataServerTTL", tmoval); - dsTTLSet = true; - break; - case request_T: clEnvP->PutInt("RequestTimeout", tmoval); - reqTOSet = true; - break; - case stream_T: clEnvP->PutInt("StreamTimeout", tmoval); - strTOSet = true; - break; - default: break; - } - -// All done -// - clMutex.UnLock(); -} - -/******************************************************************************/ -/* G l o b a l S t a t i c s */ -/******************************************************************************/ - -namespace -{ -XrdSsiClientProvider ClientProvider; -} - -XrdSsiProvider *XrdSsiProviderClient = &ClientProvider; diff --git a/src/XrdSsi/XrdSsiCluster.hh b/src/XrdSsi/XrdSsiCluster.hh deleted file mode 100644 index 8cdccee979c..00000000000 --- a/src/XrdSsi/XrdSsiCluster.hh +++ /dev/null @@ -1,145 +0,0 @@ -#ifndef __SSICLUSTER__ -#define __SSICLUSTER__ -/******************************************************************************/ -/* */ -/* X r d S s i C l u s t e r . h h */ -/* */ -/* (c) 2013 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -//------------------------------------------------------------------------------ -//! The XrdSsiCluster object provides methods to manage the names and resources -//! of a server node relative to the cluster in which it resides. A pointer to -//! object is passed to the XrdSsiServer object loaded as start-up. It remains -//! valid for the duration of the program. -//------------------------------------------------------------------------------ - -class XrdSsiCluster -{ -public: - -//------------------------------------------------------------------------------ -//! Notify the cluster of a newly added endpoint name or whose state has -//! changed on on this server node. -//! -//! @param name The logical name. -//! @param pend When true, the name is scheduled to be present in the future. -//------------------------------------------------------------------------------ - -virtual void Added(const char *name, bool pend=false) = 0; - -//------------------------------------------------------------------------------ -//! Determine whether or not the SSI plug-in is running in a data context. -//! -//! @return true running in a data context (i.e. xrootd). -//! @return false running is a meta context (i.e. cmsd). -//------------------------------------------------------------------------------ - -virtual bool DataContext() = 0; - -//------------------------------------------------------------------------------ -//! Obtain the list of nodes that are managing this cluster. -//! -//! @param mNum Place to put the number of managers in the returned array. -//! -//! @return The vector of nodes being used with mNum set to the number of -//! elements. The list is considered permanent and is not deleted. -//------------------------------------------------------------------------------ - -virtual -const char * const *Managers(int &mNum) = 0; - -//------------------------------------------------------------------------------ -//! Notify the cluster that a name is no longer available on this server node. -//! -//! @param name The logical name that is no longer available. -//------------------------------------------------------------------------------ - -virtual void Removed(const char *name) = 0; - -//------------------------------------------------------------------------------ -//! Resume service after a suspension. -//! -//! @param perm When true the resume persist across server restarts. Otherwise, -//! it is treated as a temporary request. -//------------------------------------------------------------------------------ - -virtual void Resume (bool perm=true) = 0; - -//------------------------------------------------------------------------------ -//! Suspend service. -//! -//! @param perm When true the suspend persist across server restarts. -//! Otherwise, it is treated as a temporary request. -//------------------------------------------------------------------------------ - -virtual void Suspend(bool perm=true) = 0; - -//------------------------------------------------------------------------------ -//! Enable the Reserve() & Release() methods. -//! -//! @param n a positive integer that specifies the amount of resource units -//! that are available. It may be reset at any time. -//! -//! @return The previous resource value. This first call returns 0. -//------------------------------------------------------------------------------ - -virtual int Resource(int n) = 0; - -//------------------------------------------------------------------------------ -//! Decrease the amount of resources available. When the available resources -//! becomes non-positive, perform a temporary suspend to prevent additional -//! clients from being dispatched to this server. -//! -//! @param n The value by which resources are decreased (default 1). -//! -//! @return The amount of resource left. -//------------------------------------------------------------------------------ - -virtual int Reserve (int n=1) = 0; - -//------------------------------------------------------------------------------ -//! Increase the amount of resource available. When transitioning from a -//! a non-positive to a positive resource amount, perform a resume so that -//! additional clients may be dispatched to this server. -//! -//! @param n The value to add to the resources available (default 1). The -//! total amount is capped by the amount specified by Resource(). -//! -//! @return The amount of resource left. -//------------------------------------------------------------------------------ - -virtual int Release (int n=1) = 0; - -//------------------------------------------------------------------------------ -//! Destructor -//------------------------------------------------------------------------------ - - XrdSsiCluster() {} -virtual ~XrdSsiCluster() {} - -}; -#endif diff --git a/src/XrdSsi/XrdSsiCms.cc b/src/XrdSsi/XrdSsiCms.cc deleted file mode 100644 index 683bc3d2918..00000000000 --- a/src/XrdSsi/XrdSsiCms.cc +++ /dev/null @@ -1,77 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S s i C m s . c c */ -/* */ -/* (c) 2014 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -#include "XrdOuc/XrdOucTList.hh" - -#include "XrdSsi/XrdSsiCms.hh" - -/******************************************************************************/ -/* C o n s t r u c t o r */ -/******************************************************************************/ - -XrdSsiCms::XrdSsiCms(XrdCmsClient *cmsP) : theCms(cmsP) -{ - XrdOucTList *tP, *stP = cmsP->Managers(); - char buff[1024]; - int i; - -// Count up the number of entries in the manager list -// - manNum = 0; - tP = stP; - while(tP) {manNum++; tP = tP->next;} - -// Allocate an array of teh right size -// - manList = new char*[manNum]; - -// Format out the managers -// - for (i = 0; i < manNum; i++) - {sprintf(buff, "%s:%d", stP->text, stP->val); - manList[i] = strdup(buff); - stP = stP->next; - } -} - -/******************************************************************************/ -/* D e s t r u c t o r */ -/******************************************************************************/ - -XrdSsiCms::~XrdSsiCms() -{ - int i; - - for (i = 0; i < manNum; i++) free(manList[i]); - - delete[] manList; -} diff --git a/src/XrdSsi/XrdSsiCms.hh b/src/XrdSsi/XrdSsiCms.hh deleted file mode 100644 index 5e92fe3551c..00000000000 --- a/src/XrdSsi/XrdSsiCms.hh +++ /dev/null @@ -1,85 +0,0 @@ -#ifndef __XRDSSICMS_H__ -#define __XRDSSICMS_H__ -/******************************************************************************/ -/* */ -/* X r d S s i C m s . h h */ -/* */ -/* (c) 2014 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdCms/XrdCmsClient.hh" -#include "XrdSsi/XrdSsiCluster.hh" - - -class XrdSsiCms : public XrdSsiCluster -{ -public: - - void Added(const char *name, bool pend=false) - {if (theCms) theCms->Added(name, pend);} - - bool DataContext() {return true;} - -const char * -const * Managers(int &mNum) {mNum = manNum; return manList;} - - void Removed(const char *name) - {if (theCms) theCms->Removed(name);} - - void Resume (bool perm=true) - {if (theCms) theCms->Resume(perm);} - - void Suspend(bool perm=true) - {if (theCms) theCms->Suspend(perm);} - - int Resource(int n) - {if (theCms) return theCms->Resource(n); - return 0; - } - - int Reserve (int n=1) - {if (theCms) return theCms->Reserve(n); - return 0; - } - - int Release (int n=1) - {if (theCms) return theCms->Release(n); - return 0; - } - - XrdSsiCms() : theCms(0), manList(0), manNum(0) {} - - XrdSsiCms(XrdCmsClient *cmsP); - -virtual ~XrdSsiCms(); - -private: - -XrdCmsClient *theCms; -char **manList; -int manNum; -}; -#endif diff --git a/src/XrdSsi/XrdSsiDir.cc b/src/XrdSsi/XrdSsiDir.cc deleted file mode 100644 index 19b97a652c0..00000000000 --- a/src/XrdSsi/XrdSsiDir.cc +++ /dev/null @@ -1,205 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S s i D i r . c c */ -/* */ -/* (c) 2015 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -#include "XrdOuc/XrdOucPList.hh" -#include "XrdSsi/XrdSsiDir.hh" -#include "XrdSsi/XrdSsiUtils.hh" - -/******************************************************************************/ -/* G l o b a l s */ -/******************************************************************************/ - -namespace XrdSsi -{ -extern XrdSfsFileSystem *theFS; -extern XrdOucPListAnchor FSPath; -extern bool fsChk; -}; - -using namespace XrdSsi; - -/******************************************************************************/ -/* o p e n */ -/******************************************************************************/ - -int XrdSsiDir::open(const char *dir_path, // In - const XrdSecEntity *client, // In - const char *info) // In -/* - Function: Open the directory `path' and prepare for reading. - - Input: path - The fully qualified name of the directory to open. - client - Authentication credentials, if any. - info - Opaque information to be used as seen fit. - - Output: Returns SFS_OK upon success, otherwise SFS_ERROR. -*/ -{ - static const char *epname = "opendir"; - int eNum; - -// Verify that this object is not already associated with an open file -// - if (dirP) - return XrdSsiUtils::Emsg(epname,EADDRINUSE,"open directory",dir_path,error); - -// Open a regular file if this is wanted -// - if (fsChk) - {if (!FSPath.Find(dir_path)) - {if (!(dirP = theFS->newDir((char *)tident, error.getErrMid()))) - return XrdSsiUtils::Emsg(epname, ENOMEM, epname, dir_path, error); - error.Reset(); dirP->error = error; - if ((eNum = dirP->open(dir_path, client, info))) - {error = dirP->error; - delete dirP; dirP = 0; - } else return SFS_OK; - } else error.setErrInfo(ENOTSUP, "Directory operations not " - "not supported on given path."); - } else error.setErrInfo(ENOTSUP, "Directory operations not supported."); - -// All done -// - return SFS_ERROR; -} - -/******************************************************************************/ -/* n e x t E n t r y */ -/******************************************************************************/ - -const char *XrdSsiDir::nextEntry() -/* - Function: Read the next directory entry. - - Input: n/a - - Output: Upon success, returns the contents of the next directory entry as - a null terminated string. Returns a null pointer upon EOF or an - error. To differentiate the two cases, getErrorInfo will return - 0 upon EOF and an actual error code (i.e., not 0) on error. -*/ -{ - const char *epname = "readdir"; - const char *dent; - -// Check if this directory is actually open -// - if (!dirP) {XrdSsiUtils::Emsg(epname, EBADF, epname, "???", error); - return 0; - } - -// Read the next directory entry -// - dent = dirP->nextEntry(); - if (!dent) error = dirP->error; - -// Return the actual entry -// - return dent; -} - -/******************************************************************************/ -/* c l o s e */ -/******************************************************************************/ - -int XrdSsiDir::close() -/* - Function: Close the directory object. - - Input: n/a - - Output: Returns SFS_OK upon success and SFS_ERROR upon failure. -*/ -{ - const char *epname = "closedir"; - int retc; - -// Check if this directory is actually open -// - if (!dirP) return XrdSsiUtils::Emsg(epname, EBADF, epname, "???", error); - -// Close this directory -// - if ((retc = dirP->close())) error = dirP->error; - -// All done -// - delete dirP; - dirP = 0; - return retc; -} - -/******************************************************************************/ -/* a u t o S t a t */ -/******************************************************************************/ - -int XrdSsiDir::autoStat(struct stat *buf) -/* - Function: Set stat buffer to automaticaly return stat information - - Input: Pointer to stat buffer which will be filled in on each - nextEntry() and represent stat information for that entry. - - Output: Upon success, returns zero. Upon error returns SFS_ERROR and sets - the error object to contain the reason. -*/ -{ - const char *epname = "autoStat"; - int retc; - -// Check if this directory is actually open -// - if (!dirP) return XrdSsiUtils::Emsg(epname, EBADF, epname, "???", error); - -// Do the autostat -// - if ((retc = dirP->autoStat(buf))) error = dirP->error; - -// All done -// - return retc; -} - -/******************************************************************************/ -/* F N a m e */ -/******************************************************************************/ - -const char *XrdSsiDir::FName() -{ - const char *epname = "fname"; - -// Check if this directory is actually open -// - if (!dirP) return dirP->FName(); - XrdSsiUtils::Emsg(epname, EBADF, epname, "???", error); - return ""; -} diff --git a/src/XrdSsi/XrdSsiDir.hh b/src/XrdSsi/XrdSsiDir.hh deleted file mode 100644 index 5420eea8115..00000000000 --- a/src/XrdSsi/XrdSsiDir.hh +++ /dev/null @@ -1,64 +0,0 @@ -#ifndef __SSI_DIR_H__ -#define __SSI_DIR_H__ -/******************************************************************************/ -/* */ -/* X r d S s i D i r . h h */ -/* */ -/* (c) 2015 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -#include "XrdSfs/XrdSfsInterface.hh" - -class XrdSsiDir : public XrdSfsDirectory -{ -public: - - int open(const char *dirName, - const XrdSecEntity *client, - const char *opaque = 0); - - const char *nextEntry(); - - int close(); - -inline void copyError(XrdOucErrInfo &einfo) {einfo = error;} - -const char *FName(); - - int autoStat(struct stat *buf); - - XrdSsiDir(const char *user, int MonID) - : XrdSfsDirectory(user, MonID), dirP(0) - {tident = (user ? user : "");} - -virtual ~XrdSsiDir() {if (dirP) delete dirP;} - -private: -XrdSfsDirectory *dirP; -const char *tident; -}; -#endif diff --git a/src/XrdSsi/XrdSsiEntity.hh b/src/XrdSsi/XrdSsiEntity.hh deleted file mode 100644 index 3cdd247990d..00000000000 --- a/src/XrdSsi/XrdSsiEntity.hh +++ /dev/null @@ -1,68 +0,0 @@ -#ifndef __SSI_ENTITY_H__ -#define __SSI_ENTITY_H__ -/******************************************************************************/ -/* */ -/* X r d S s i E n t i t y . h h */ -/* */ -/* (c) 2014 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -//------------------------------------------------------------------------------ -//! This object describes the authenticated identification of a client and may -//! be used to restrct certain functions based on the identification. Presence -//! of certain members is determined by the actual authnetication method used. -//! This object, if supplied, is only supplied server-side. -//------------------------------------------------------------------------------ - -#include - -#define XrdSsiPROTOIDSIZE 8 - -class XrdSsiEntity -{ -public: - char prot[XrdSsiPROTOIDSIZE]; //!< Protocol used -const char *name; //!< Entity's name -const char *host; //!< Entity's host name or address -const char *vorg; //!< Entity's virtual organization -const char *role; //!< Entity's role -const char *grps; //!< Entity's group names -const char *endorsements; //!< Protocol specific endorsements -const char *creds; //!< Raw client credentials or cert - int credslen; //!< Length of the 'creds' field - int rsvd; //!< Reserved field -const char *tident; //!< Trace identifier always preset - - XrdSsiEntity(const char *pName = "") - : name(0), host(0), vorg(0), role(0), grps(0), - endorsements(0), creds(0), credslen(0), - rsvd(0), tident("") - {memset(prot, 0, XrdSsiPROTOIDSIZE); - strncpy(prot, pName, XrdSsiPROTOIDSIZE-1); - prot[XrdSsiPROTOIDSIZE-1] = '\0'; - } - ~XrdSsiEntity() {} -}; -#endif diff --git a/src/XrdSsi/XrdSsiErrInfo.hh b/src/XrdSsi/XrdSsiErrInfo.hh deleted file mode 100644 index 55dc1409ee5..00000000000 --- a/src/XrdSsi/XrdSsiErrInfo.hh +++ /dev/null @@ -1,145 +0,0 @@ -#ifndef __XRDSSIERRINFO_HH__ -#define __XRDSSIERRINFO_HH__ -/******************************************************************************/ -/* */ -/* X r d S s i E r r I n f o . h h */ -/* */ -/* (c) 2013 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -//----------------------------------------------------------------------------- -//! The XrdSsiErrInfo object is used to hold error information for many ssi -//! client-oriented requests. -//----------------------------------------------------------------------------- - -class XrdSsiErrInfo -{ -public: - -//----------------------------------------------------------------------------- -//! Reset and clear error information. -//----------------------------------------------------------------------------- - - void Clr() {errText.clear(); errArg = errNum = 0;} - -//----------------------------------------------------------------------------- -//! Get current error information. -//! -//! @param eNum place where the error number is to be placed. -//! -//! @return The error text and the error number value. -//----------------------------------------------------------------------------- - -const -std::string &Get(int &eNum) const {eNum = errNum; return errText;} - -//----------------------------------------------------------------------------- -//! Get current error text. -//! -//! @return The error text. -//----------------------------------------------------------------------------- - -const -std::string &Get() const {return errText;} - -//----------------------------------------------------------------------------- -//! Get current error argument. -//! -//! @return the error argument value. -//----------------------------------------------------------------------------- - - int GetArg() const {return errArg;} - -//----------------------------------------------------------------------------- -//! Check if there is an error. -//! -//! @return True if an error exists and false otherwise. -//----------------------------------------------------------------------------- - - bool hasError() const {return errNum != 0;} - -//----------------------------------------------------------------------------- -//! Check if there is no error. -//! -//! @return True if no error exists and false otherwise. -//----------------------------------------------------------------------------- - - bool isOK() const {return errNum == 0;} - -//----------------------------------------------------------------------------- -//! Set new error information. There are two obvious variations. -//! -//! @param eMsg pointer to a string describing the error. If nil, the eNum -//! is taken as errno and strerror(eNum) is used. -//! @param eNum the error number associated with the error. -//! @param eArg the error argument, if any (see XrdSsiService::Provision()). -//----------------------------------------------------------------------------- - - void Set(const char *eMsg=0, int eNum=0, int eArg=0) - {errText = (eMsg && *eMsg ? eMsg : strerror(eNum)); - errNum = eNum; - errArg = eArg; - } - - void Set(const std::string &eMsg, int eNum=0, int eArg=0) - {errText = (eMsg.empty() ? strerror(eNum) : eMsg); - errNum = eNum; - errArg = eArg; - } - -//------------------------------------------------------------------------------ -//! Assignment operator -//------------------------------------------------------------------------------ - -XrdSsiErrInfo &operator=(XrdSsiErrInfo const &rhs) - {if (&rhs != this) Set(rhs.errText, rhs.errNum, rhs.errArg); - return *this; - } - -//------------------------------------------------------------------------------ -//! Copy constructor -//------------------------------------------------------------------------------ - - XrdSsiErrInfo(XrdSsiErrInfo const &oP) - {Set(oP.errText, oP.errNum, oP.errArg);} - -//----------------------------------------------------------------------------- -//! Constructor and Destructor -//----------------------------------------------------------------------------- - - XrdSsiErrInfo() : errNum(0), errArg(0) {} - - ~XrdSsiErrInfo() {} - -private: - -std::string errText; -int errNum; -int errArg; -}; -#endif diff --git a/src/XrdSsi/XrdSsiEvent.cc b/src/XrdSsi/XrdSsiEvent.cc deleted file mode 100644 index 0b7c73592bd..00000000000 --- a/src/XrdSsi/XrdSsiEvent.cc +++ /dev/null @@ -1,158 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S s i E v e n t . c c */ -/* */ -/* (c) 2015 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdSsi/XrdSsiEvent.hh" -#include "Xrd/XrdScheduler.hh" - -/******************************************************************************/ -/* S t a t i c s & G l o b a l s */ -/******************************************************************************/ - -namespace -{ -XrdSsiMutex frMutex; -} - -XrdSsiEvent::EventData *XrdSsiEvent::freeEvent = 0; - -namespace XrdSsi -{ -extern XrdScheduler *schedP; -} - -/******************************************************************************/ -/* A d d E v e n t */ -/******************************************************************************/ - -void XrdSsiEvent::AddEvent(XrdCl::XRootDStatus *st, XrdCl::AnyObject *resp) -{ - XrdSsiMutexMon monMutex(evMutex); - -// If the base object has no status then we need to set it and schedule -// ourselves for processing if not already running. -// - if (!thisEvent.status) - {thisEvent.status = st; - thisEvent.response = resp; - if (!running) - {running = true; - XrdSsi::schedP->Schedule(this); - } - return; - } - -// Allocate a new event object and chain it from the base event. This also -// implies that we doesn't need to be scheduled as it already was scheduled. -// - frMutex.Lock(); - EventData *edP = freeEvent; - if (!edP) edP = new EventData(st, resp); - else {freeEvent = edP->next; - edP->status = st; - edP->response = resp; - edP->next = 0; - } - frMutex.UnLock(); - -// Establish the last event -// - if (lastEvent) lastEvent->next = edP; - else thisEvent .next = edP; - lastEvent = edP; -} - -/******************************************************************************/ -/* C l r E v e n t */ -/******************************************************************************/ - -void XrdSsiEvent::ClrEvent(XrdSsiEvent::EventData *fdP) -{ - EventData *xdP, *edP = fdP; - -// This method may be safely called on a undeleted EventData object even if -// this event object has been deleted; as can happen in XeqEvent(). -// Clear any chained events. This loop ends with edP pointing to the last event. -// - while(edP->next) - {edP = edP->next; - delete edP->status; - delete edP->response; - } - -// Place all chained elements, if any, in the free list -// - if (fdP->next) - {frMutex.Lock(); - xdP = fdP->next; edP->next = freeEvent; freeEvent = xdP; - frMutex.UnLock(); - fdP->next = 0; - } - -// Clear the base event -// - if (fdP->status) {delete fdP->status; fdP->status = 0;} - if (fdP->response) {delete fdP->response; fdP->response = 0;} - -// If we are clearing our events then indicate we are not running. Note that -// this method is only called when cleaning up so we can't be running -// - if (fdP == &thisEvent) - {lastEvent = 0; - running = false; - } -} - -/******************************************************************************/ -/* D o I t */ -/******************************************************************************/ - -void XrdSsiEvent::DoIt() -{ - EventData myEvent, *edP = &myEvent; - -// Process all of the events in our list. This is a tricky proposition because -// the event executor may delete us when false is returned. To prevent double -// frees and the like, we move out the event and work off a local copy. -// - evMutex.Lock(); -do{thisEvent.Move2(myEvent); - lastEvent = 0; - evMutex.UnLock(); - edP = &myEvent; - while(edP && XeqEvent(edP->status, &edP->response)) {edP = edP->next;} - ClrEvent(&myEvent); - if (edP) return; - evMutex.Lock(); - } while(thisEvent.status); - -// We are done, indicate we are no longer running -// - running = false; - evMutex.UnLock(); -} diff --git a/src/XrdSsi/XrdSsiEvent.hh b/src/XrdSsi/XrdSsiEvent.hh deleted file mode 100644 index e41e339b9bb..00000000000 --- a/src/XrdSsi/XrdSsiEvent.hh +++ /dev/null @@ -1,88 +0,0 @@ -#ifndef __XRDSSIEVENT_HH__ -#define __XRDSSIEVENT_HH__ -/******************************************************************************/ -/* */ -/* X r d S s i E v e n t . h h */ -/* */ -/* (c) 2015 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "Xrd/XrdJob.hh" -#include "XrdCl/XrdClXRootDResponses.hh" -#include "XrdSsi/XrdSsiAtomics.hh" - -class XrdSsiEvent : public XrdJob, public XrdCl::ResponseHandler -{ -public: - - void AddEvent(XrdCl::XRootDStatus *st, XrdCl::AnyObject *resp); - - void ClrEvent() {evMutex.Lock();ClrEvent(&thisEvent);evMutex.UnLock();} - -virtual void DoIt(); - -virtual void HandleResponse(XrdCl::XRootDStatus *status, - XrdCl::AnyObject *response) - {myCaller = pthread_self(); - AddEvent(status, response); - } - -virtual bool XeqEvent(XrdCl::XRootDStatus *st, XrdCl::AnyObject **resp) = 0; - - XrdSsiEvent(const char *hName="") : XrdJob(hName), lastEvent(0), - running(false) - {} - - ~XrdSsiEvent() {ClrEvent();} - -protected: -pthread_t myCaller; - -private: -struct EventData - {XrdCl::XRootDStatus *status; - XrdCl::AnyObject *response; - EventData *next; - - void Move2(EventData &dest) {dest.status = status; status = 0; - dest.response = response; response = 0; - dest.next = next; next = 0; - } - - EventData(XrdCl::XRootDStatus *st=0, XrdCl::AnyObject *resp=0) - : status(st), response(resp), next(0) {} - ~EventData() {} - }; - -void ClrEvent(EventData *fdP); - -XrdSsiMutex evMutex; -EventData thisEvent; -EventData *lastEvent; -bool running; -static -EventData *freeEvent; -}; -#endif diff --git a/src/XrdSsi/XrdSsiFile.cc b/src/XrdSsi/XrdSsiFile.cc deleted file mode 100644 index c2288a411c4..00000000000 --- a/src/XrdSsi/XrdSsiFile.cc +++ /dev/null @@ -1,638 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S s i F i l e . c c */ -/* */ -/* (c) 2013 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include - -#include "XrdOuc/XrdOucBuffer.hh" -#include "XrdOuc/XrdOucEnv.hh" -#include "XrdOuc/XrdOucPList.hh" - -#include "XrdSfs/XrdSfsAio.hh" -#include "XrdSfs/XrdSfsXio.hh" - -#include "XrdSsi/XrdSsiFile.hh" -#include "XrdSsi/XrdSsiFileSess.hh" -#include "XrdSsi/XrdSsiSfs.hh" -#include "XrdSsi/XrdSsiUtils.hh" - -/******************************************************************************/ -/* G l o b a l s */ -/******************************************************************************/ - -namespace XrdSsi -{ - XrdOucBuffPool EmsgPool; -extern XrdSfsFileSystem *theFS; -extern XrdOucPListAnchor FSPath; -extern bool fsChk; -}; - -using namespace XrdSsi; - -/******************************************************************************/ -/* X r d S s i F i l e C o n s t r u c t o r */ -/******************************************************************************/ - -XrdSsiFile::XrdSsiFile(const char *user, int monid) - : XrdSfsFile(user, monid), fsFile(0), fSessP(0), xioP(0) {} - -/******************************************************************************/ -/* X r d S s i F i l e D e s t r u c t o r */ -/******************************************************************************/ - -XrdSsiFile::~XrdSsiFile() -{ - -// If we have a file object then delete it -- it needs to close. Else do it. -// - if (fsFile) delete fsFile; - if (fSessP) fSessP->Recycle(); -} - -/******************************************************************************/ -/* c l o s e */ -/******************************************************************************/ - -int XrdSsiFile::close() -/* - Function: Close the file object. - - Input: None - - Output: Always returns SFS_OK -*/ -{ - -// Route this request as needed (no callback possible) -// - if (fsFile) - {int rc = fsFile->close(); - return (rc ? CopyErr("close", rc) : rc); - } - -// Forward this to the file session object -// - return fSessP->close(); -} - -/******************************************************************************/ -/* Private: C o p y E C B */ -/******************************************************************************/ - -void XrdSsiFile::CopyECB(bool forOpen) -{ - unsigned long long cbArg; - XrdOucEICB *cbVal = error.getErrCB(cbArg); - -// We only need to copy some information -// - if (forOpen) fsFile->error.setUCap(error.getUCap()); - fsFile->error.setErrCB(cbVal, cbArg); -} - -/******************************************************************************/ -/* Private: C o p y E r r */ -/******************************************************************************/ - -int XrdSsiFile::CopyErr(const char *op, int rc) -{ - XrdOucBuffer *buffP; - const char *eText; - int eTLen, eCode; - -// Get the error information -// - eText = fsFile->error.getErrText(eCode); - -// Handle callbacks -// - if (rc == SFS_STARTED || rc == SFS_DATAVEC) - {unsigned long long cbArg; - XrdOucEICB *cbVal = fsFile->error.getErrCB(cbArg); - error.setErrCB(cbVal, cbArg); - if (rc == SFS_DATAVEC) - {struct iovec *iovP = (struct iovec *)eText; - char *mBuff = error.getMsgBuff(eTLen); - eTLen = iovP->iov_len; - memcpy(mBuff, eText, eTLen); - error.setErrCode(eCode); - return SFS_DATAVEC; - } - } - -// Check if we need to copy an external buffer. If this fails then if there is -// an ofs callback pending, we must tell the ofs plugin we failed. -// - if (!(fsFile->error.extData())) error.setErrInfo(eCode, eText); - else {eTLen = fsFile->error.getErrTextLen(); - buffP = EmsgPool.Alloc(eTLen); - if (buffP) - {memcpy(buffP->Buffer(), eText, eTLen); - error.setErrInfo(eCode, buffP); - } else { - XrdSsiUtils::Emsg("CopyErr",ENOMEM,op,fsFile->FName(),error); - if (rc == SFS_STARTED && fsFile->error.getErrCB()) - {rc = eCode = SFS_ERROR; - fsFile->error.getErrCB()->Done(eCode, &error); - } - } - } - -// All done -// - return rc; -} - -/******************************************************************************/ -/* f c t l */ -/******************************************************************************/ - -int XrdSsiFile::fctl(const int cmd, - const char *args, - XrdOucErrInfo &out_error) -{ - -// Route this request as needed -// - if (fsFile) return fsFile->fctl(cmd, args, out_error); - -// Indicate we would like to use SendData() -// - if (cmd == SFS_FCTL_GETFD) - {out_error.setErrCode(SFS_SFIO_FDVAL); - return SFS_OK; - } - -// We don't support any other kind of command -// - return XrdSsiUtils::Emsg("fctl",ENOTSUP,"fctl",fSessP->FName(),out_error); -} - -/******************************************************************************/ - -int XrdSsiFile::fctl(const int cmd, - int alen, - const char *args, - const XrdSecEntity *client) -{ - -// Route this request as needed (callback possible) -// - if (fsFile) - {CopyECB(); - int rc = fsFile->fctl(cmd, alen, args, client); - return (rc ? CopyErr("fctl", rc) : rc); - } - -// Forward this to the session object -// - return fSessP->fctl(cmd, alen, args, client); -} - -/******************************************************************************/ -/* F N a m e */ -/******************************************************************************/ - -const char *XrdSsiFile::FName() -{ - -// Route to filesystem if need be -// - if (fsFile) return fsFile->FName(); - -// Return our name -// - return fSessP->FName(); -} - -/******************************************************************************/ -/* g e t C X i n f o */ -/******************************************************************************/ - -int XrdSsiFile::getCXinfo(char cxtype[4], int &cxrsz) -/* - Function: Set the length of the file object to 'flen' bytes. - - Input: n/a - - Output: cxtype - Compression algorithm code - cxrsz - Compression region size - - Returns SFS_OK upon success and SFS_ERROR upon failure. -*/ -{ -// Route this request as needed (no callback possible) -// - if (fsFile) - {int rc = fsFile->getCXinfo(cxtype, cxrsz); - return (rc ? CopyErr("getcx", rc) : rc); - } - -// Indicate we don't support compression -// - cxrsz = 0; - return SFS_OK; -} - -/******************************************************************************/ -/* g e t M m a p */ -/******************************************************************************/ - -int XrdSsiFile::getMmap(void **Addr, off_t &Size) // Out -/* - Function: Return memory mapping for file, if any. - - Output: Addr - Address of memory location - Size - Size of the file or zero if not memory mapped. - Returns SFS_OK upon success and SFS_ERROR upon failure. -*/ -{ -// Route this request as needed (no callback possible) -// - if (fsFile) - {int rc = fsFile->getMmap(Addr, Size); - return (rc ? CopyErr("getmmap", rc) : rc); - } - -// Indicate we don't support memory mapping -// - if (Addr) *Addr = 0; - Size = 0; - return SFS_OK; -} - -/******************************************************************************/ -/* o p e n */ -/******************************************************************************/ - -int XrdSsiFile::open(const char *path, // In - XrdSfsFileOpenMode open_mode, // In - mode_t Mode, // In - const XrdSecEntity *client, // In - const char *info) // In -/* - Function: Open the file `path' in the mode indicated by `open_mode'. - - Input: path - The fully qualified name of the resource. - open_mode - It must contain only SFS_O_RDWR. - Mode - Ignored. - client - Authentication credentials, if any. - info - Opaque information to be used as seen fit. - - Output: Returns OOSS_OK upon success, otherwise SFS_ERROR is returned. -*/ -{ - static const char *epname = "open"; - int eNum; - -// Verify that this object is not already associated with an open file -// - if (fsFile || fSessP) - return XrdSsiUtils::Emsg(epname, EADDRINUSE, "open session", path, error); - -// Open a regular file if this is wanted -// - if (fsChk && FSPath.Find(path)) - {if (!(fsFile = theFS->newFile((char *)error.getErrUser(), - error.getErrMid()))) - return XrdSsiUtils::Emsg(epname, ENOMEM, "open file", path, error); - CopyECB(true); - if ((eNum = fsFile->open(path, open_mode, Mode, client, info))) - {eNum = CopyErr(epname, eNum); - delete fsFile; fsFile = 0; - } - return eNum; - } - -// Convert opaque and security into an environment -// - XrdOucEnv Open_Env(info, 0, client); - -// Allocate file session and issue open -// - fSessP = XrdSsiFileSess::Alloc(error, error.getErrUser()); - eNum = fSessP->open(path, Open_Env, open_mode); - if (eNum) {fSessP->Recycle(); fSessP = 0;} - return eNum; -} - -/******************************************************************************/ -/* r e a d */ -/******************************************************************************/ - -int XrdSsiFile::read(XrdSfsFileOffset offset, // In - XrdSfsXferSize blen) // In -/* - Function: Preread `blen' bytes at `offset' - - Input: offset - The absolute byte offset at which to start the read. - blen - The amount to preread. - - Output: Returns SFS_OK upon success and SFS_ERROR o/w. -*/ -{ - -// Route to file system if need be (no callback possible) -// - if (fsFile) - {int rc = fsFile->read(offset, blen); - return (rc ? CopyErr("read", rc) : rc); - } - -// We ignore these -// - return SFS_OK; -} - -/******************************************************************************/ -/* r e a d */ -/******************************************************************************/ - -XrdSfsXferSize XrdSsiFile::read(XrdSfsFileOffset offset, // In - char *buff, // Out - XrdSfsXferSize blen) // In -/* - Function: Read `blen' bytes at `offset' into 'buff' and return the actual - number of bytes read. - - Input: offset - Contains request information. - buff - Address of the buffer in which to place the data. - blen - The size of the buffer. This is the maximum number - of bytes that will be returned. - - Output: Returns the number of bytes read upon success and SFS_ERROR o/w. -*/ -{ - -// Route to file system if need be (no callback possible) -// - if (fsFile) - {int rc = fsFile->read(offset, buff, blen); - return (rc ? CopyErr("read", rc) : rc); - } - -// Forward this to the file session -// - return fSessP->read(offset, buff, blen); -} - -/******************************************************************************/ -/* r e a d A I O */ -/******************************************************************************/ - -int XrdSsiFile::read(XrdSfsAio *aiop) -{ - -// Route to file system if need be (no callback possible) -// - if (fsFile) - {int rc = fsFile->read(aiop); - return (rc ? CopyErr("readaio", rc) : rc); - } - -// Execute this request in a synchronous fashion -// - aiop->Result = fSessP->read((XrdSfsFileOffset)aiop->sfsAio.aio_offset, - (char *)aiop->sfsAio.aio_buf, - (XrdSfsXferSize)aiop->sfsAio.aio_nbytes); - aiop->doneRead(); - return 0; -} - -/******************************************************************************/ -/* r e a d v */ -/******************************************************************************/ - -XrdSfsXferSize XrdSsiFile::readv(XrdOucIOVec *readV, // In - int readCount) // In -/* - Function: Perform all the reads specified in the readV vector. - - Input: readV - A description of the reads to perform; includes the - absolute offset, the size of the read, and the buffer - to place the data into. - readCount - The size of the readV vector. - - Output: Returns an error as this is not supported. -*/ -{ - -// Route this request to file system if need be (no callback possible) -// - if (fsFile) - {int rc = fsFile->readv(readV, readCount); - return (rc ? CopyErr("readv", rc) : rc); - } - - return XrdSsiUtils::Emsg("readv", ENOSYS, "readv", fSessP->FName(), error); -} - -/******************************************************************************/ -/* S e n d D a t a */ -/******************************************************************************/ - -int XrdSsiFile::SendData(XrdSfsDio *sfDio, - XrdSfsFileOffset offset, - XrdSfsXferSize size) -{ - -// Route this request to file system if need be (no callback possible) -// - if (fsFile) - {int rc = fsFile->SendData(sfDio, offset, size); - return (rc ? CopyErr("SendData", rc) : rc); - } - -// Forward this to the file session object -// - return fSessP->SendData(sfDio, offset, size); -} - -/******************************************************************************/ -/* s t a t */ -/******************************************************************************/ - -int XrdSsiFile::stat(struct stat *buf) // Out -/* - Function: Return file status information - - Input: buf - The stat structure to hold the results - - Output: Returns SFS_OK upon success and SFS_ERROR upon failure. -*/ -{ - -// Route this request to file system if need be (no callback possible) -// - if (fsFile) - {int rc = fsFile->stat(buf); - return (rc ? CopyErr("stat", rc) : rc); - } - -// Otherwise there is no stat information -// - memset(buf, 0 , sizeof(struct stat)); - return SFS_OK; -} - -/******************************************************************************/ -/* s y n c */ -/******************************************************************************/ - -int XrdSsiFile::sync() -/* - Function: Commit all unwritten bytes to physical media. - - Input: None - - Output: Returns SFS_OK if a response is ready or SFS_STARTED otherwise. -*/ -{ - -// Route this request to file system if need be (callback possible) -// - if (fsFile) - {CopyECB(); - int rc = fsFile->sync(); - return (rc ? CopyErr("sync", rc) : rc); - } - -// We don't support this -// - return XrdSsiUtils::Emsg("sync", ENOSYS, "sync", fSessP->FName(), error); -} - -/******************************************************************************/ -/* s y n c A I O */ -/******************************************************************************/ - -int XrdSsiFile::sync(XrdSfsAio *aiop) -{ - -// Route this request to file system if need be (callback possible) -// - if (fsFile) - {CopyECB(); - int rc = fsFile->sync(aiop); - return (rc ? CopyErr("syncaio", rc) : rc); - } - -// We don't support this -// - return XrdSsiUtils::Emsg("syncaio", ENOSYS, "sync", fSessP->FName(), error); -} - -/******************************************************************************/ -/* t r u n c a t e */ -/******************************************************************************/ - -int XrdSsiFile::truncate(XrdSfsFileOffset flen) // In -/* - Function: Set the length of the file object to 'flen' bytes. - - Input: flen - The new size of the file. - - Output: Returns SFS_ERROR a this function is not supported. -*/ -{ - -// Route this request to file system if need be (callback possible) -// - if (fsFile) - {CopyECB(); - int rc = fsFile->truncate(flen); - return (rc ? CopyErr("trunc", rc) : rc); - } - -// Route this to the file session object -// - return fSessP->truncate(flen); -} - -/******************************************************************************/ -/* w r i t e */ -/******************************************************************************/ - -XrdSfsXferSize XrdSsiFile::write(XrdSfsFileOffset offset, // In - const char *buff, // In - XrdSfsXferSize blen) // In -/* - Function: Write `blen' bytes at `offset' from 'buff' and return the actual - number of bytes written. - - Input: offset - The absolute byte offset at which to start the write. - buff - Address of the buffer from which to get the data. - blen - The size of the buffer. This is the maximum number - of bytes that will be written to 'fd'. - - Output: Returns the number of bytes written upon success and SFS_ERROR o/w. - - Notes: An error return may be delayed until the next write(), close(), or - sync() call. -*/ -{ - -// Route this request to file system if need be (no callback possible) -// - if (fsFile) - {int rc = fsFile->write(offset, buff, blen); - return (rc ? CopyErr("write", rc) : rc); - } - -// Route this to the file session object -// - return fSessP->write(offset, buff, blen); -} - -/******************************************************************************/ -/* w r i t e A I O */ -/******************************************************************************/ - -int XrdSsiFile::write(XrdSfsAio *aiop) -{ - -// Route to file system if need be (no callback possible) -// - if (fsFile) - {int rc = fsFile->write(aiop); - return (rc ? CopyErr("writeaio", rc) : rc); - } - -// Execute this request in a synchronous fashion -// - aiop->Result = fSessP->write((XrdSfsFileOffset)aiop->sfsAio.aio_offset, - (char *)aiop->sfsAio.aio_buf, - (XrdSfsXferSize)aiop->sfsAio.aio_nbytes); - aiop->doneWrite(); - return 0; -} diff --git a/src/XrdSsi/XrdSsiFile.hh b/src/XrdSsi/XrdSsiFile.hh deleted file mode 100644 index 69bf5f5b436..00000000000 --- a/src/XrdSsi/XrdSsiFile.hh +++ /dev/null @@ -1,114 +0,0 @@ -#ifndef __SSI_FILE_H__ -#define __SSI_FILE_H__ -/******************************************************************************/ -/* */ -/* X r d S s i F i l e . h h */ -/* */ -/* (c) 2013 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -#include "XrdSfs/XrdSfsInterface.hh" - -class XrdSsiFileSess; - -class XrdSsiFile : public XrdSfsFile -{ -public: - -// SfsFile methods -// - int open(const char *fileName, - XrdSfsFileOpenMode openMode, - mode_t createMode, - const XrdSecEntity *client = 0, - const char *opaque = 0); - - int close(); - - int fctl(const int cmd, - const char *args, - XrdOucErrInfo &out_error); - - int fctl(const int cmd, - int alen, - const char *args, - const XrdSecEntity *client); - - const char *FName(); - - int getCXinfo(char cxtype[4], int &cxrsz); - - int getMmap(void **Addr, off_t &Size); - - int read(XrdSfsFileOffset fileOffset, - XrdSfsXferSize preread_sz); - - XrdSfsXferSize read(XrdSfsFileOffset fileOffset, - char *buffer, - XrdSfsXferSize buffer_size); - - int read(XrdSfsAio *aioparm); - - XrdSfsXferSize readv(XrdOucIOVec *readV, - int readCount); - - int SendData(XrdSfsDio *sfDio, - XrdSfsFileOffset offset, - XrdSfsXferSize size); - - void setXio(XrdSfsXio *xP) {xioP = xP;} - - int stat(struct stat *buf); - - int sync(); - - int sync(XrdSfsAio *aiop); - - int truncate(XrdSfsFileOffset fileOffset); - - XrdSfsXferSize write(XrdSfsFileOffset fileOffset, - const char *buffer, - XrdSfsXferSize buffer_size); - - int write(XrdSfsAio *aioparm); - -// Constructor and destructor -// - XrdSsiFile(const char *user, int MonID); - -virtual ~XrdSsiFile(); - -private: -void CopyECB(bool forOpen=false); -int CopyErr(const char *op, int rc); - -XrdSfsFile *fsFile; -XrdSsiFileSess *fSessP; -XrdSfsXio *xioP; -}; -#endif diff --git a/src/XrdSsi/XrdSsiFileReq.cc b/src/XrdSsi/XrdSsiFileReq.cc deleted file mode 100644 index 62c96c62c76..00000000000 --- a/src/XrdSsi/XrdSsiFileReq.cc +++ /dev/null @@ -1,1003 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S s i F i l e R e q . c c */ -/* */ -/* (c) 2013 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include - -#include "XrdOuc/XrdOucBuffer.hh" -#include "XrdOuc/XrdOucERoute.hh" -#include "XrdOuc/XrdOucErrInfo.hh" -#include "XrdSfs/XrdSfsDio.hh" -#include "XrdSfs/XrdSfsXio.hh" -#include "XrdSsi/XrdSsiAlert.hh" -#include "XrdSsi/XrdSsiFileReq.hh" -#include "XrdSsi/XrdSsiFileResource.hh" -#include "XrdSsi/XrdSsiFileSess.hh" -#include "XrdSsi/XrdSsiRRAgent.hh" -#include "XrdSsi/XrdSsiService.hh" -#include "XrdSsi/XrdSsiSfs.hh" -#include "XrdSsi/XrdSsiStream.hh" -#include "XrdSsi/XrdSsiTrace.hh" -#include "XrdSsi/XrdSsiUtils.hh" -#include "XrdSys/XrdSysError.hh" - -/******************************************************************************/ -/* L o c a l M a c r o s */ -/******************************************************************************/ - -#define DEBUGXQ(x) DEBUG(rID<Schedule((XrdJob *)this); - } - return; - break; - default: break; - } - -// If we get here then we have an invalid state. Report it but otherwise we -// can't really do anything else. This means some memory may be lost. -// - Log.Emsg(epname, tident, "Invalid req/rsp state; giving up on object!"); -} - -/******************************************************************************/ -/* D i s p o s e */ -/******************************************************************************/ - -void XrdSsiFileReq::Dispose() -{ - EPNAME("Dispose"); - -// Do some debugging -// - DEBUGXQ("Recycling request..."); - -// Simply recycle the object -// - Recycle(); -} - -/******************************************************************************/ -/* D o I t */ -/******************************************************************************/ - -void XrdSsiFileReq::DoIt() -{ - EPNAME("DoIt"); - bool cancel; - -// Processing is determined by the responder's state. Only listed states are -// valid. Others should never occur in this context. -// - frqMutex.Lock(); - switch(urState) - {case isNew: myState = xqReq; urState = isBegun; - DEBUGXQ("Calling service processor"); - frqMutex.UnLock(); - Service->ProcessRequest((XrdSsiRequest &)*this, - (XrdSsiFileResource &)*fileR); - return; - break; - case isAbort: DEBUGXQ("Skipped calling service processor"); - frqMutex.UnLock(); - Recycle(); - return; - break; - case isDone: cancel = (myState != odRsp); - DEBUGXQ("Calling Finished(" <Post(); - frqMutex.UnLock(); - Finished(cancel); // This object may be deleted! - return; - break; - default: break; - } - -// If we get here then we have an invalid state. Report it but otherwise we -// can't really do anything else. This means some memory may be lost. -// - frqMutex.UnLock(); - Log.Emsg(epname, tident, "Invalid req/rsp state; giving up on object!"); -} - -/******************************************************************************/ -/* D o n e */ -/******************************************************************************/ - -// Gets invoked only after query() waitresp signal was sent - -void XrdSsiFileReq::Done(int &retc, XrdOucErrInfo *eiP, const char *name) -{ - EPNAME("Done"); - XrdSsiMutexMon mHelper(frqMutex); - -// We may need to delete the errinfo object if this callback was async. Note -// that the following test is valid even if the file object has been deleted. -// - if (eiP != fileP->errInfo()) delete eiP; - -// Check if we should finalize this request. This will be the case if the -// complete response was sent. -// - if (myState == odRsp) - {DEBUGXQ("resp sent; no additional data remains"); - Finalize(); - return; - } - -// Do some debugging -// - DEBUGXQ("wtrsp sent; resp " <<(haveResp ? "here" : "pend")); - -// We are invoked when sync() waitresp has been sent, check if a response was -// posted while this was going on. If so, make sure to send a wakeup. Note -// that the respWait flag is at this moment false as this is called in the -// sync response path for fctl() and the response may have been posted. -// - if (!haveResp) respWait = true; - else WakeUp(); -} - -/******************************************************************************/ -/* Private: E m s g */ -/******************************************************************************/ - -int XrdSsiFileReq::Emsg(const char *pfx, // Message prefix value - int ecode, // The error code - const char *op) // Operation being performed -{ - char buffer[2048]; - -// Get correct error code -// - if (ecode < 0) ecode = -ecode; - -// Format the error message -// - XrdOucERoute::Format(buffer, sizeof(buffer), ecode, op, sessN); - -// Put the message in the log -// - Log.Emsg(pfx, tident, buffer); - -// Place the error message in the error object and return -// - if (cbInfo) cbInfo->setErrInfo(ecode, buffer); - return SFS_ERROR; -} - -/******************************************************************************/ - -int XrdSsiFileReq::Emsg(const char *pfx, // Message prefix value - XrdSsiErrInfo &eObj, // The error description - const char *op) // Operation being performed -{ - const char *eMsg; - char buffer[2048]; - int eNum; - -// Get correct error code and message -// - eMsg = eObj.Get(eNum).c_str(); - if (eNum <= 0) eNum = EFAULT; - if (!eMsg || !(*eMsg)) eMsg = "reason unknown"; - -// Format the error message -// - snprintf(buffer, sizeof(buffer), "Unable to %s %s; %s", op, sessN, eMsg); - -// Put the message in the log -// - Log.Emsg(pfx, tident, buffer); - -// Place the error message in the error object and return -// - if (cbInfo) cbInfo->setErrInfo(eNum, buffer); - return SFS_ERROR; -} - -/******************************************************************************/ -/* F i n a l i z e */ -/******************************************************************************/ - -void XrdSsiFileReq::Finalize() -{ - EPNAME("Finalize"); - XrdSsiMutexMon mHelper(frqMutex); - bool cancel = (myState != odRsp); - -// Release any unsent alerts (prevent any new alerts from being accepted) -// - isEnding = true; - if (alrtSent || alrtPend) - {XrdSsiAlert *dP, *aP = alrtSent; - if (aP) aP->next = alrtPend; - else aP = alrtPend; - mHelper.UnLock(); - while((dP = aP)) {aP = aP->next; dP->Recycle();} - mHelper.Lock(frqMutex); - } - -// Processing is determined by the responder's state -// - switch(urState) - // Request is being scheduled, so we can simply abort it. - // - {case isNew: DEBUGXQ("Aborting request processing"); - urState = isAbort; - cbInfo = 0; - sessN = "???"; - return; - break; - - // Request already handed off but not yet bound. Defer until bound. - // We need to wait until this occurs to sequence Unprovision(). - // - case isBegun: urState = isDone; - {XrdSysSemaphore wt4fin(0); - finWait = &wt4fin; - mHelper.UnLock(); - wt4fin.Wait(); - } - return; - - // Request is bound so we can finish right off. - // - case isBound: if (strBuff) {strBuff->Recycle(); strBuff = 0;} - DEBUGXQ("Calling Finished(" <rType) - {case XrdSsiRespInfo::isData: - if (respLen <= 0) {done = true; myState = odRsp; return 0;} - if (blen >= respLen) - {memcpy(buff, Resp->buff+respOff, respLen); - blen = respLen; myState = odRsp; done = true; - } else { - memcpy(buff, Resp->buff+respOff, blen); - respLen -= blen; respOff += blen; - } - return blen; - break; - case XrdSsiRespInfo::isError: - cbInfo->setErrInfo(Resp->eNum, Resp->eMsg); - myState = odRsp; done = true; - return SFS_ERROR; - break; - case XrdSsiRespInfo::isFile: - if (fileSz <= 0) {done = true; myState = odRsp; return 0;} - nbytes = pread(Resp->fdnum, buff, blen, respOff); - if (nbytes <= 0) - {done = true; - if (!nbytes) {myState = odRsp; return 0;} - myState = erRsp; - return Emsg(epname, errno, "read"); - } - respOff += nbytes; fileSz -= nbytes; - return nbytes; - break; - case XrdSsiRespInfo::isStream: - nbytes = (Resp->strmP->Type() == XrdSsiStream::isActive ? - readStrmA(Resp->strmP, buff, blen) - : readStrmP(Resp->strmP, buff, blen)); - done = strmEOF && strBuff == 0; - return nbytes; - break; - default: break; - }; - -// We should never get here -// - myState = erRsp; - done = true; - return Emsg(epname, EFAULT, "read"); -} - -/******************************************************************************/ -/* Private: r e a d S t r m A */ -/******************************************************************************/ - -XrdSfsXferSize XrdSsiFileReq::readStrmA(XrdSsiStream *strmP, - char *buff, XrdSfsXferSize blen) -{ - static const char *epname = "readStrmA"; - XrdSsiErrInfo eObj; - XrdSfsXferSize xlen = 0; - - -// Copy out data from the stream to fill the buffer -// -do{if (strBuff) - {if (respLen > blen) - {memcpy(buff, strBuff->data+respOff, blen); - respLen -= blen; respOff += blen; - return xlen+blen; - } - memcpy(buff, strBuff->data+respOff, respLen); - xlen += respLen; - strBuff->Recycle(); strBuff = 0; - blen -= respLen; buff += respLen; - } - - if (!strmEOF && blen) - {respLen = blen; respOff = 0; - strBuff = strmP->GetBuff(eObj, respLen, strmEOF); - } - } while(strBuff); - -// Check if we have data to return -// - if (strmEOF) {myState = odRsp; return xlen;} - else if (!blen) return xlen; - -// Report the error -// - myState = erRsp; strmEOF = true; - return Emsg(epname, eObj, "read stream"); -} - -/******************************************************************************/ -/* Private: r e a d S t r m P */ -/******************************************************************************/ - -XrdSfsXferSize XrdSsiFileReq::readStrmP(XrdSsiStream *strmP, - char *buff, XrdSfsXferSize blen) -{ - static const char *epname = "readStrmP"; - XrdSsiErrInfo eObj; - XrdSfsXferSize xlen = 0; - int dlen = 0; - -// Copy out data from the stream to fill the buffer -// - while(!strmEOF && (dlen = strmP->SetBuff(eObj, buff, blen, strmEOF)) > 0) - {xlen += dlen; - if (dlen == blen) return xlen; - if (dlen > blen) {eObj.Set(0,EOVERFLOW); break;} - buff += dlen; blen -= dlen; - } - -// Check if we ended with an zero length read -// - if (strmEOF || !dlen) {myState = odRsp; strmEOF = true; return xlen;} - -// Return an error -// - myState = erRsp; strmEOF = true; - return Emsg(epname, eObj, "read stream"); -} - -/******************************************************************************/ -/* Private: R e c y c l e */ -/******************************************************************************/ - -void XrdSsiFileReq::Recycle() -{ - -// If we have an oucbuffer then we need to recycle it, otherwise if we have -// and sfs buffer, put it on the defered release queue. -// - if (oucBuff) {oucBuff->Recycle(); oucBuff = 0;} - else if (sfsBref) {sfsBref->Recycle(); sfsBref = 0;} - reqSize = 0; - -// Add to queue unless we have too many of these. If we add it back to the -// queue; make sure it's a cleaned up object! -// - aqMutex.Lock(); - if (tident) {free(tident); tident = 0;} - if (freeCnt >= freeMax) {aqMutex.UnLock(); delete this;} - else {XrdSsiRRAgent::CleanUp(*this); - nextReq = freeReq; - freeReq = this; - freeCnt++; - aqMutex.UnLock(); - } -} - -/******************************************************************************/ -/* R e l R e q u e s t B u f f e r */ -/******************************************************************************/ - -void XrdSsiFileReq::RelRequestBuffer() -{ - EPNAME("RelReqBuff"); - XrdSsiMutexMon mHelper(frqMutex); - -// Do some debugging -// - DEBUGXQ("called"); - -// Release buffers -// - if (oucBuff) {oucBuff->Recycle(); oucBuff = 0;} - else if (sfsBref) {sfsBref->Recycle(); sfsBref = 0;} - reqSize = 0; -} - -/******************************************************************************/ -/* S e n d */ -/******************************************************************************/ - -int XrdSsiFileReq::Send(XrdSfsDio *sfDio, XrdSfsXferSize blen) -{ - static const char *epname = "send"; - XrdSsiRespInfo const *Resp = XrdSsiRRAgent::RespP(this); - XrdOucSFVec sfVec[2]; - int rc; - -// A send should never be issued unless a response has been set. Return a -// continuation which will cause Read() to be called to return the error. -// - if (myState != doRsp) return 1; - -// Fan out based on the kind of response we have -// - switch(Resp->rType) - {case XrdSsiRespInfo::isData: - if (blen > 0) - {sfVec[1].buffer = (char *)Resp->buff+respOff; - sfVec[1].fdnum = -1; - if (blen > respLen) - {blen = respLen; myState = odRsp; - } else { - respLen -= blen; respOff += blen; - } - } else blen = 0; - break; - case XrdSsiRespInfo::isError: - return 1; // Causes error to be returned via Read() - break; - case XrdSsiRespInfo::isFile: - if (fileSz > 0) - {sfVec[1].offset = respOff; sfVec[1].fdnum = Resp->fdnum; - if (blen > fileSz) - {blen = fileSz; myState = odRsp;} - respOff += blen; fileSz -= blen; - } else blen = 0; - break; - case XrdSsiRespInfo::isStream: - if (Resp->strmP->Type() == XrdSsiStream::isPassive) return 1; - return sendStrmA(Resp->strmP, sfDio, blen); - break; - default: myState = erRsp; - return Emsg(epname, EFAULT, "send"); - break; - }; - -// Send off the data -// - if (!blen) {sfVec[1].buffer = rID; myState = odRsp;} - sfVec[1].sendsz = blen; - rc = sfDio->SendFile(sfVec, 2); - -// If send succeeded, indicate the action to be taken -// - if (!rc) return myState != odRsp; - -// The send failed, diagnose the problem -// - rc = (rc < 0 ? EIO : EFAULT); - myState = erRsp; - return Emsg(epname, rc, "send"); -} - -/******************************************************************************/ -/* Private: s e n d S t r m A */ -/******************************************************************************/ - -int XrdSsiFileReq::sendStrmA(XrdSsiStream *strmP, - XrdSfsDio *sfDio, XrdSfsXferSize blen) -{ - static const char *epname = "sendStrmA"; - XrdSsiErrInfo eObj; - XrdOucSFVec sfVec[2]; - int rc; - -// Check if we need a buffer -// - if (!strBuff) - {respLen = blen; - if (strmEOF || !(strBuff = strmP->GetBuff(eObj, respLen, strmEOF))) - {myState = odRsp; strmEOF = true; - if (!strmEOF) Emsg(epname, eObj, "read stream"); - return 1; - } - respOff = 0; - } - -// Complete the sendfile vector -// - sfVec[1].buffer = strBuff->data+respOff; - sfVec[1].fdnum = -1; - if (respLen > blen) - {sfVec[1].sendsz = blen; - respLen -= blen; respOff += blen; - } else { - sfVec[1].sendsz = respLen; - respLen = 0; - } - -// Send off the data -// - rc = sfDio->SendFile(sfVec, 2); - -// Release any completed buffer -// - if (strBuff && !respLen) {strBuff->Recycle(); strBuff = 0;} - -// If send succeeded, indicate the action to be taken -// - if (!rc) return myState != odRsp; - -// The send failed, diagnose the problem -// - rc = (rc < 0 ? EIO : EFAULT); - myState = erRsp; strmEOF = true; - return Emsg(epname, rc, "send"); -} - -/******************************************************************************/ -/* W a n t R e s p o n s e */ -/******************************************************************************/ - -bool XrdSsiFileReq::WantResponse(XrdOucErrInfo &eInfo) -{ - EPNAME("WantResp"); - XrdSsiMutexMon frqMon; - const XrdSsiRespInfo *rspP; - -// Check if we have a previos alert that was sent (we need to recycle it). We -// don't need a lock for this as it's fully serialized via serial fsctl calls. -// - if (alrtSent) {alrtSent->Recycle(); alrtSent = 0;} - -// Serialize the remainder of this code -// - frqMon.Lock(frqMutex); - rspP = XrdSsiRRAgent::RespP(this); - -// If we have a pending alert then we need to send it now. Suppress the callback -// as we will recycle the alert on the next call (there should be one). -// - if (alrtPend) - {char hexBuff[16], binBuff[8], dotBuff[4]; - alrtSent = alrtPend; - if (!(alrtPend = alrtPend->next)) alrtLast = 0; - int n = alrtSent->SetInfo(eInfo, binBuff, sizeof(binBuff)); - eInfo.setErrCB((XrdOucEICB *)0); - DEBUGXQ(n <<" byte alert (0x" <rType) - if (haveResp) - {respCBarg = 0; - if (fileP->AttnInfo(eInfo, rspP, reqID)) - { eInfo.setErrCB((XrdOucEICB *)this); myState = odRsp;} - else eInfo.setErrCB((XrdOucEICB *)0); - return true; - } - -// Defer this and record the callback arguments. We defer setting respWait -// to true until we know the deferal request has been sent (i.e. when Done() -// is called). This forces ProcessResponse() to not prematurely wakeup the -// client. This is necessitated by the fact that we must release the request -// lock upon return; allowing a response to come in while the deferal request -// is still in transit. -// - respCB = eInfo.getErrCB(respCBarg); - respWait = false; - return false; -} - -/******************************************************************************/ -/* Private: W a k e U p */ -/******************************************************************************/ - -void XrdSsiFileReq::WakeUp(XrdSsiAlert *aP) // Called with frqMutex locked! -{ - EPNAME("WakeUp"); - XrdOucErrInfo *wuInfo = - new XrdOucErrInfo(tident,(XrdOucEICB *)0,respCBarg); - const XrdSsiRespInfo *rspP = XrdSsiRRAgent::RespP(this); - int respCode = SFS_DATAVEC; - -// Do some debugging -// - DEBUGXQ("respCBarg=" <SetInfo(*wuInfo, binBuff, sizeof(binBuff)); - wuInfo->setErrCB((XrdOucEICB *)aP, respCBarg); - DEBUGXQ(n <<" byte alert (0x" <AttnInfo(*wuInfo, rspP, reqID)) - {wuInfo->setErrCB((XrdOucEICB *)this, respCBarg); myState = odRsp;} - } - -// Tell the client to issue a read now or handle the alert or full response. -// - respWait = false; - respCB->Done(respCode, wuInfo, sessN); -} diff --git a/src/XrdSsi/XrdSsiFileReq.hh b/src/XrdSsi/XrdSsiFileReq.hh deleted file mode 100644 index ac4584b7714..00000000000 --- a/src/XrdSsi/XrdSsiFileReq.hh +++ /dev/null @@ -1,171 +0,0 @@ -#ifndef __SSI_FILEREQ_H__ -#define __SSI_FILEREQ_H__ -/******************************************************************************/ -/* */ -/* X r d S s i F i l e R e q . h h */ -/* */ -/* (c) 2013 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -#include "Xrd/XrdJob.hh" -#include "Xrd/XrdScheduler.hh" -#include "XrdSfs/XrdSfsInterface.hh" -#include "XrdSsi/XrdSsiRequest.hh" -#include "XrdSsi/XrdSsiResponder.hh" -#include "XrdSsi/XrdSsiStream.hh" -#include "XrdSys/XrdSysPthread.hh" - -class XrdOucErrInfo; -class XrdSfsXioHandle; -class XrdSsiAlert; -class XrdSsiFileResource; -class XrdSsiFileSess; -class XrdSsiRespInfoMsg; -class XrdSsiRRInfo; -class XrdSsiService; -class XrdSsiStream; - -class XrdSsiFileReq : public XrdSsiRequest, public XrdOucEICB, public XrdJob -{ -public: - - -// SsiRequest methods -// - void Activate(XrdOucBuffer *oP, XrdSfsXioHandle *bR, int rSz); - - void Alert(XrdSsiRespInfoMsg &aMsg); - -static XrdSsiFileReq *Alloc(XrdOucErrInfo *eP, XrdSsiFileResource *rP, - XrdSsiFileSess *fP, const char *sn, - const char *id, unsigned int rnum); - - void Finalize(); - - using XrdSsiRequest::Finished; - - void Finished( XrdSsiRequest &rqstR, - const XrdSsiRespInfo &rInfo, - bool cancel=false) {} - - char *GetRequest(int &rLen); - - bool ProcessResponse(const XrdSsiErrInfo &eInfo, - const XrdSsiRespInfo &resp); - - XrdSfsXferSize Read(bool &done, - char *buffer, - XrdSfsXferSize blen); - - void RelRequestBuffer(); - - int Send(XrdSfsDio *sfDio, XrdSfsXferSize size); - -static void SetMax(int mVal) {freeMax = mVal;} - - bool WantResponse(XrdOucErrInfo &eInfo); - -// OucEICB methods -// - void Done(int &Result, XrdOucErrInfo *cbInfo, - const char *path=0); - - int Same(unsigned long long arg1, unsigned long long arg2) - {return 0;} -// Job methods -// - void DoIt(); - -// Constructor and destructor -// - XrdSsiFileReq(const char *cID=0) - : frqMutex(XrdSsiMutex::Recursive) - {Init(cID);} - -virtual ~XrdSsiFileReq() {if (tident) free(tident);} - -enum reqState {wtReq=0, xqReq, wtRsp, doRsp, odRsp, erRsp, rsEnd}; -enum rspState {isNew=0, isBegun, isBound, isAbort, isDone, isMax}; - -private: - -void BindDone(); // Override -void Dispose(); // Override -int Emsg(const char *pfx, int ecode, const char *op); -int Emsg(const char *pfx, XrdSsiErrInfo &eObj, - const char *op); -void Init(const char *cID=0); -XrdSfsXferSize readStrmA(XrdSsiStream *strmP, char *buff, - XrdSfsXferSize blen); -XrdSfsXferSize readStrmP(XrdSsiStream *strmP, char *buff, - XrdSfsXferSize blen); -int sendStrmA(XrdSsiStream *strmP, XrdSfsDio *sfDio, - XrdSfsXferSize blen); -void Recycle(); -void WakeUp(XrdSsiAlert *aP=0); - -static XrdSysMutex aqMutex; -static XrdSsiFileReq *freeReq; -static int freeCnt; -static int freeMax; - -XrdSsiMutex frqMutex; -XrdSsiFileReq *nextReq; -XrdSysSemaphore *finWait; -XrdOucEICB *respCB; -unsigned long long respCBarg; - -XrdSsiAlert *alrtSent; -XrdSsiAlert *alrtPend; -XrdSsiAlert *alrtLast; - -char *tident; -const char *sessN; -XrdOucErrInfo *cbInfo; -XrdSsiFileResource *fileR; -XrdSsiFileSess *fileP; -char *respBuf; -long long respOff; -union {long long fileSz; - int respLen; - }; -XrdSfsXioHandle *sfsBref; -XrdOucBuffer *oucBuff; -XrdSsiStream::Buffer *strBuff; -reqState myState; -rspState urState; -int reqSize; -unsigned int reqID; -bool haveResp; -bool respWait; -bool strmEOF; -bool schedDone; -bool isEnding; -char rID[8]; -}; -#endif diff --git a/src/XrdSsi/XrdSsiFileResource.cc b/src/XrdSsi/XrdSsiFileResource.cc deleted file mode 100644 index 809c126589a..00000000000 --- a/src/XrdSsi/XrdSsiFileResource.cc +++ /dev/null @@ -1,76 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S s i F i l e R e s o u r c e . c c */ -/* */ -/* (c) 2017 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -#include "XrdOuc/XrdOucEnv.hh" - -#include "XrdNet/XrdNetAddrInfo.hh" -#include "XrdSec/XrdSecEntity.hh" -#include "XrdSsi/XrdSsiFileResource.hh" - -/******************************************************************************/ -/* I n i t */ -/******************************************************************************/ - -void XrdSsiFileResource::Init(const char *path, XrdOucEnv &envX, bool aDNS) -{ - const XrdSecEntity *entP = envX.secEnv(); - const char *rVal; - int n; - -// Construct the security information -// - if (entP) - {strncpy(mySec.prot, entP->prot, XrdSsiPROTOIDSIZE); - mySec.name = entP->name; - mySec.host = (!aDNS ? entP->host : entP->addrInfo->Name(entP->host)); - mySec.role = entP->vorg; - mySec.role = entP->role; - mySec.grps = entP->grps; - mySec.endorsements = entP->endorsements; - mySec.creds = entP->creds; - mySec.credslen = entP->credslen; - } else mySec.tident = "ssi"; - client = &mySec; - -// Fill out the resource name and user -// - rName = path; - if ((rVal = envX.Get("ssi.user"))) rUser = rVal; - else rUser.clear(); - -// Fill out the the optional cgi info -// - if (!(rVal = envX.Get("ssi.cgi"))) rInfo.clear(); - else {rVal = envX.Env(n); - if (!(rVal = strstr(rVal, "ssi.cgi="))) rInfo.clear(); - else rInfo = rVal+8; - } -} diff --git a/src/XrdSsi/XrdSsiFileResource.hh b/src/XrdSsi/XrdSsiFileResource.hh deleted file mode 100644 index cb88e0f073a..00000000000 --- a/src/XrdSsi/XrdSsiFileResource.hh +++ /dev/null @@ -1,55 +0,0 @@ -#ifndef __SSI_FILERESOURCE_H__ -#define __SSI_FILERESOURCE_H__ -/******************************************************************************/ -/* */ -/* X r d S s i F i l e R e s o u r c e . h h */ -/* */ -/* (c) 2017 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include - -#include "XrdSsi/XrdSsiEntity.hh" -#include "XrdSsi/XrdSsiResource.hh" - -class XrdOucEnv; - -class XrdSsiFileResource : public XrdSsiResource -{ -public: - -void Init(const char *path, XrdOucEnv &envP, bool aDNS); - - XrdSsiFileResource() : XrdSsiResource(std::string("")), mySec() - {} - - ~XrdSsiFileResource() {} - -private: -XrdSsiEntity mySec; -}; -#endif diff --git a/src/XrdSsi/XrdSsiFileSess.cc b/src/XrdSsi/XrdSsiFileSess.cc deleted file mode 100644 index 4d08ff8219d..00000000000 --- a/src/XrdSsi/XrdSsiFileSess.cc +++ /dev/null @@ -1,781 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S s i F i l e S e s s . c c */ -/* */ -/* (c) 2016 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "XrdNet/XrdNetAddrInfo.hh" - -#include "XrdOuc/XrdOucBuffer.hh" -#include "XrdOuc/XrdOucEnv.hh" -#include "XrdOuc/XrdOucERoute.hh" -#include "XrdOuc/XrdOucPList.hh" - -#include "XrdSec/XrdSecEntity.hh" - -#include "XrdSfs/XrdSfsAio.hh" -#include "XrdSfs/XrdSfsXio.hh" - -#include "XrdSsi/XrdSsiEntity.hh" -#include "XrdSsi/XrdSsiFileSess.hh" -#include "XrdSsi/XrdSsiProvider.hh" -#include "XrdSsi/XrdSsiRRInfo.hh" -#include "XrdSsi/XrdSsiService.hh" -#include "XrdSsi/XrdSsiSfs.hh" -#include "XrdSsi/XrdSsiStream.hh" -#include "XrdSsi/XrdSsiTrace.hh" -#include "XrdSsi/XrdSsiUtils.hh" - -#include "XrdSys/XrdSysError.hh" - -/******************************************************************************/ -/* G l o b a l s */ -/******************************************************************************/ - -namespace XrdSsi -{ -extern XrdOucBuffPool *BuffPool; -extern XrdSsiProvider *Provider; -extern XrdSsiService *Service; -extern XrdSysError Log; -extern int respWT; -}; - -using namespace XrdSsi; - -/******************************************************************************/ -/* L o c a l M a c r o s */ -/******************************************************************************/ - -#define DUMPIT(x,y) XrdSsiUtils::b2x(x,y,hexBuff,sizeof(hexBuff),dotBuff)<nextFree; - arMutex.UnLock(); - fsP->Init(einfo, user, true); - } else { - freeNew++; - if (freeMax <= freeAbs && freeNew >= freeMax/2) - {freeMax += freeMax/2; - freeNew = 0; - } - arMutex.UnLock(); - fsP = new XrdSsiFileSess(einfo, user); - } - -// Return the object -// - return fsP; -} - -/******************************************************************************/ -/* A t t n I n f o */ -/******************************************************************************/ - -bool XrdSsiFileSess::AttnInfo(XrdOucErrInfo &eInfo, const XrdSsiRespInfo *respP, - unsigned int reqID) -// Called with the request mutex locked! -{ - EPNAME("AttnInfo"); - struct AttnResp {struct iovec ioV[4]; XrdSsiRRInfoAttn aHdr;}; - - AttnResp *attnResp; - char *mBuff; - int n, ioN = 2; - bool doFin; - -// If there is no data we can send back to the client in the attn response, -// then simply reply with a short message to make the client come back. -// - if (!respP->mdlen) - {if (respP->rType != XrdSsiRespInfo::isData - || respP->blen > XrdSsiResponder::MaxDirectXfr) - {eInfo.setErrInfo(0, ""); - return false; - } - } - -// We will be constructing the response in the message buffer. This is -// gauranteed to be big enough for our purposes so no need to check the size. -// - mBuff = eInfo.getMsgBuff(n); - -// Initialize the response -// - attnResp = (AttnResp *)mBuff; - memset(attnResp, 0, sizeof(AttnResp)); - attnResp->aHdr.pfxLen = htons(sizeof(XrdSsiRRInfoAttn)); - -// Fill out iovec to point to our header -// -//?attnResp->ioV[0].iov_len = sizeof(XrdSsiRRInfoAttn) + respP->mdlen; - attnResp->ioV[1].iov_base = mBuff+offsetof(struct AttnResp, aHdr); - attnResp->ioV[1].iov_len = sizeof(XrdSsiRRInfoAttn); - -// Fill out the iovec for the metadata if we have some -// - if (respP->mdlen) - {attnResp->ioV[2].iov_base = (void *)respP->mdata; - attnResp->ioV[2].iov_len = respP->mdlen; ioN = 3; - attnResp->aHdr.mdLen = htonl(respP->mdlen); - if (QTRACE(Debug)) - {char hexBuff[16],dotBuff[4]; - DEBUG(reqID <<':' <mdlen <<" byte metadata (0x" - <mdata,respP->mdlen) <<") sent."); - } - } - -// Check if we have actual data here as well and can send it along -// - if (respP->rType == XrdSsiRespInfo::isData - && respP->blen+respP->mdlen <= XrdSsiResponder::MaxDirectXfr) - {if (respP->blen) - {attnResp->ioV[ioN].iov_base = (void *)respP->buff; - attnResp->ioV[ioN].iov_len = respP->blen; ioN++; - } - attnResp->aHdr.tag = XrdSsiRRInfoAttn::fullResp; doFin = true; - } - else {attnResp->aHdr.tag = XrdSsiRRInfoAttn::pendResp; doFin = false;} - -// If we sent the full response we must remove the request from the request -// table as it will get finished off when the response is actually sent. -// - if (doFin) rTab.Del(reqID, false); - -// Setup to have metadata actually sent to the requestor -// - eInfo.setErrCode(ioN); - return doFin; -} - -/******************************************************************************/ -/* c l o s e */ -/******************************************************************************/ - -int XrdSsiFileSess::close(bool viaDel) -/* - Function: Close the file object. - - Input: None - - Output: Always returns SFS_OK -*/ -{ - const char *epname = "close"; - -// Do some debugging -// - DEBUG((gigID ? gigID : "???") <<" del=" <Recycle(); oucBuff = 0;} - inProg = false; - } - -// Clean up storage -// - isOpen = false; - return SFS_OK; -} - -/******************************************************************************/ -/* f c t l */ -/******************************************************************************/ - -int XrdSsiFileSess::fctl(const int cmd, - int alen, - const char *args, - const XrdSecEntity *client) -{ - static const char *epname = "fctl"; - XrdSsiRRInfo *rInfo; - XrdSsiFileReq *rqstP; - unsigned int reqID; - -// If this isn't the special query, then return an error -// - if (cmd != SFS_FCTL_SPEC1) - return XrdSsiUtils::Emsg(epname, ENOTSUP, "fctl", gigID, *eInfo); - -// Caller wishes to find out if a request is ready and wait if it is not -// - if (!args || alen < (int)sizeof(XrdSsiRRInfo)) - return XrdSsiUtils::Emsg(epname, EINVAL, "fctl", gigID, *eInfo); - -// Grab the request identifier -// - rInfo = (XrdSsiRRInfo *)args; - reqID = rInfo->Id(); - -// Do some debugging -// - DEBUG(reqID <<':' <WantResponse(*eInfo)) - {DEBUG(reqID <<':' <setErrCB((XrdOucEICB *)rqstP); - eInfo->setErrInfo(respWT, ""); - return SFS_STARTED; -} - -/******************************************************************************/ -/* Private: I n i t */ -/******************************************************************************/ - -void XrdSsiFileSess::Init(XrdOucErrInfo &einfo, const char *user, bool forReuse) -{ - tident = (user ? strdup(user) : strdup("")); - eInfo = &einfo; - gigID = 0; - fsUser = 0; - xioP = 0; - oucBuff = 0; - reqSize = 0; - reqLeft = 0; - isOpen = false; - inProg = false; - if (forReuse) - {eofVec.Reset(); - rTab.Clear(); - } -} - -/******************************************************************************/ -/* Private: N e w R e q u e s t */ -/******************************************************************************/ - -bool XrdSsiFileSess::NewRequest(unsigned int reqid, - XrdOucBuffer *oP, - XrdSfsXioHandle *bR, - int rSz) -{ - XrdSsiFileReq *reqP; - -// Allocate a new request object -// - if (!(reqP=XrdSsiFileReq::Alloc(eInfo,&fileResource,this,gigID,tident,reqid))) - return false; - -// Add it to the table -// - rTab.Add(reqP, reqid); - -// Activate the request -// - inProg = false; - reqP->Activate(oP, bR, rSz); - return true; -} - -/******************************************************************************/ -/* o p e n */ -/******************************************************************************/ - -int XrdSsiFileSess::open(const char *path, // In - XrdOucEnv &theEnv, // In - XrdSfsFileOpenMode open_mode) // In -/* - Function: Open the file `path' in the mode indicated by `open_mode'. - - Input: path - The fully qualified name of the resource. - theEnv - Environmental information. - open_mode - It must contain only SFS_O_RDWR. - - Output: Returns SFS_OK upon success, otherwise SFS_ERROR is returned. -*/ -{ - static const char *epname = "open"; - XrdSsiErrInfo errInfo; - const char *eText; - int eNum; - -// Verify that this object is not already associated with an open file -// - if (isOpen) - return XrdSsiUtils::Emsg(epname, EADDRINUSE, "open session", path, *eInfo); - -// Make sure the open flag is correct (we now open this R/O so don't check) -// -// if (open_mode != SFS_O_RDWR) -// return XrdSsiUtils::Emsg(epname, EPROTOTYPE, "open session", path, *eInfo); - -// Setup the file resource object -// - fileResource.Init(path, theEnv, authDNS); - -// Notify the provider that we will be executing a request -// - if (Service->Prepare(errInfo, fileResource)) - {const char *usr = fileResource.rUser.c_str(); - if (!(*usr)) gigID = strdup(path); - else {char gBuff[2048]; - snprintf(gBuff, sizeof(gBuff), "%s:%s", usr, path); - gigID = strdup(gBuff); - } - DEBUG(gigID <<" prepared."); - isOpen = true; - return SFS_OK; - } - -// Get error information -// - eText = errInfo.Get(eNum).c_str(); - if (!eNum) - {eNum = ENOMSG; eText = "Provider returned invalid prepare response.";} - -// Decode the error -// - switch(eNum) - {case EAGAIN: - if (!eText || !(*eText)) break; - eNum = errInfo.GetArg(); - DEBUG(path <<" --> " <setErrInfo(eNum, eText); - return SFS_REDIRECT; - break; - case EBUSY: - eNum = errInfo.GetArg(); - if (!eText || !(*eText)) eText = "Provider is busy."; - DEBUG(path <<" dly " <setErrInfo(eNum, eText); - return eNum; - break; - default: - if (!eText || !(*eText)) eText = strerror(eNum); - DEBUG(path <<" err " <setErrInfo(eNum, eText); - return SFS_ERROR; - break; - }; - -// Something is quite wrong here -// - Log.Emsg(epname, "Provider redirect returned no target host name!"); - eInfo->setErrInfo(ENOMSG, "Server logic error"); - return SFS_ERROR; -} - -/******************************************************************************/ -/* r e a d */ -/******************************************************************************/ - -XrdSfsXferSize XrdSsiFileSess::read(XrdSfsFileOffset offset, // In - char *buff, // Out - XrdSfsXferSize blen) // In -/* - Function: Read `blen' bytes at `offset' into 'buff' and return the actual - number of bytes read. - - Input: offset - Contains request information. - buff - Address of the buffer in which to place the data. - blen - The size of the buffer. This is the maximum number - of bytes that will be returned. - - Output: Returns the number of bytes read upon success and SFS_ERROR o/w. -*/ -{ - static const char *epname = "read"; - XrdSsiRRInfo rInfo(offset); - XrdSsiFileReq *rqstP; - XrdSfsXferSize retval; - unsigned int reqID = rInfo.Id(); - bool noMore = false; - -// Find the request object. If not there we may have encountered an eof -// - if (!(rqstP = rTab.LookUp(reqID))) - {if (eofVec.IsSet(reqID)) - {eofVec.UnSet(reqID); - return 0; - } - return XrdSsiUtils::Emsg(epname, ESRCH, "read", gigID, *eInfo); - } - -// Simply effect the read via the request object -// - retval = rqstP->Read(noMore, buff, blen); - -// See if we just completed this request -// - if (noMore) - {rqstP->Finalize(); - rTab.Del(reqID); - eofVec.Set(reqID); - } - -// All done -// - return retval; -} - -/******************************************************************************/ -/* R e c y c l e */ -/******************************************************************************/ - -void XrdSsiFileSess::Recycle() -{ - -// Do an immediate reset on ourselves to avoid getting too many locks -// - Reset(); - -// Get a lock -// - arMutex.Lock(); - -// Check if we should place this on the free list or simply delete it -// - if (freeNum < freeMax) - {nextFree = freeList; - freeList = this; - freeNum++; - arMutex.UnLock(); - } else { - arMutex.UnLock(); - delete this; - } -} - -/******************************************************************************/ -/* Private: R e s e t */ -/******************************************************************************/ - -void XrdSsiFileSess::Reset() -{ - -// Close this session -// - if (isOpen) close(true); - -// Release other buffers -// - if (tident) free(tident); - if (fsUser) free(fsUser); - if (gigID) free(gigID); -} - -/******************************************************************************/ -/* S e n d D a t a */ -/******************************************************************************/ - -int XrdSsiFileSess::SendData(XrdSfsDio *sfDio, - XrdSfsFileOffset offset, - XrdSfsXferSize size) -{ - static const char *epname = "SendData"; - XrdSsiRRInfo rInfo(offset); - XrdSsiFileReq *rqstP; - unsigned int reqID = rInfo.Id(); - int rc; - -// Find the request object -// - if (!(rqstP = rTab.LookUp(reqID))) - return XrdSsiUtils::Emsg(epname, ESRCH, "send", gigID, *eInfo); - -// Simply effect the send via the request object -// - rc = rqstP->Send(sfDio, size); - -// Determine how this ended -// - if (rc > 0) rc = SFS_OK; - else {rqstP->Finalize(); - rTab.Del(reqID); - } - return rc; -} - -/******************************************************************************/ -/* t r u n c a t e */ -/******************************************************************************/ - -int XrdSsiFileSess::truncate(XrdSfsFileOffset flen) // In -/* - Function: Set the length of the file object to 'flen' bytes. - - Input: flen - The new size of the file. - - Output: Returns SFS_ERROR a this function is not supported. -*/ -{ - static const char *epname = "trunc"; - XrdSsiFileReq *rqstP; - XrdSsiRRInfo rInfo(flen); - XrdSsiRRInfo::Opc reqXQ = rInfo.Cmd(); - unsigned int reqID = rInfo.Id(); - -// Find the request object. If not there we may have encountered an eof -// - if (!(rqstP = rTab.LookUp(reqID))) - {if (eofVec.IsSet(reqID)) - {eofVec.UnSet(reqID); - return 0; - } - return XrdSsiUtils::Emsg(epname, ESRCH, "cancel", gigID, *eInfo); - } - -// Process request (this can only be a cancel request) -// - if (reqXQ != XrdSsiRRInfo::Can) - return XrdSsiUtils::Emsg(epname, ENOSYS, "trunc", gigID, *eInfo); - -// Perform the cancellation -// - DEBUG(reqID <<':' <Finalize(); - rTab.Del(reqID); - return SFS_OK; -} - -/******************************************************************************/ -/* w r i t e */ -/******************************************************************************/ - -XrdSfsXferSize XrdSsiFileSess::write(XrdSfsFileOffset offset, // In - const char *buff, // In - XrdSfsXferSize blen) // In -/* - Function: Write `blen' bytes at `offset' from 'buff' and return the actual - number of bytes written. - - Input: offset - The absolute byte offset at which to start the write. - buff - Address of the buffer from which to get the data. - blen - The size of the buffer. This is the maximum number - of bytes that will be written to 'fd'. - - Output: Returns the number of bytes written upon success and SFS_ERROR o/w. - - Notes: An error return may be delayed until the next write(), close(), or - sync() call. -*/ -{ - static const char *epname = "write"; - XrdSsiRRInfo rInfo(offset); - unsigned int reqID = rInfo.Id(); - int reqPass; - -// Check if we are reading a request segment and handle that. This assumes that -// writes to different requests cannot be interleaved (which they can't be). -// - if (inProg) return writeAdd(buff, blen, reqID); - -// Make sure this request does not refer to an active request -// - if (rTab.LookUp(reqID)) - return XrdSsiUtils::Emsg(epname, EADDRINUSE, "write", gigID, *eInfo); - -// The offset contains the actual size of the request, make sure it's OK. Note -// that it can be zero and by convention the blen must be one if so. -// - reqPass = reqSize = rInfo.Size(); - if (reqSize < blen) - {if (reqSize || blen != 1) - return XrdSsiUtils::Emsg(epname, EPROTO, "write", gigID, *eInfo); - reqSize = 1; - } else if (reqSize < 0 || reqSize > maxRSZ) - return XrdSsiUtils::Emsg(epname, EFBIG, "write", gigID, *eInfo); - -// Indicate we are in the progress of collecting the request arguments -// - inProg = true; - eofVec.UnSet(reqID); - -// Do some debugging -// - DEBUG(reqID <<':' <Alloc(reqSize))) - return XrdSsiUtils::Emsg(epname, ENOMEM, "write", gigID, *eInfo); - -// Setup to buffer this -// - reqLeft = reqSize - blen; - memcpy(oucBuff->Data(), buff, blen); - if (!reqLeft) - {oucBuff->SetLen(reqSize); - - if (!NewRequest(reqID, oucBuff, 0, reqPass)) - return XrdSsiUtils::Emsg(epname, ENOMEM, "write", gigID, *eInfo); - oucBuff = 0; - } else oucBuff->SetLen(blen, blen); - return blen; -} - -/******************************************************************************/ -/* Private: w r i t e A d d */ -/******************************************************************************/ - -XrdSfsXferSize XrdSsiFileSess::writeAdd(const char *buff, // In - XrdSfsXferSize blen, // In - unsigned int rid) -/* - Function: Add `blen' bytes from 'buff' to request and return the actual - number of bytes added. - - Input: buff - Address of the buffer from which to get the data. - blen - The size of the buffer. This is the maximum number - of bytes that will be added. - - Output: Returns the number of bytes added upon success and SFS_ERROR o/w. - - Notes: An error return may be delayed until the next write(), close(), or - sync() call. -*/ -{ - static const char *epname = "writeAdd"; - int dlen; - -// Make sure the caller is not exceeding the size stated on the first write -// - if (blen > reqLeft) - return XrdSsiUtils::Emsg(epname, EFBIG, "writeAdd", gigID, *eInfo); - -// Append the bytes -// - memcpy(oucBuff->Data(dlen), buff, blen); - -// Adjust how much we have left -// - reqLeft -= blen; - DEBUG(rid <<':' <SetLen(dlen, dlen); - return blen; -} diff --git a/src/XrdSsi/XrdSsiFileSess.hh b/src/XrdSsi/XrdSsiFileSess.hh deleted file mode 100644 index 837c2d55a7e..00000000000 --- a/src/XrdSsi/XrdSsiFileSess.hh +++ /dev/null @@ -1,137 +0,0 @@ -#ifndef __SSI_FILESESS_H__ -#define __SSI_FILESESS_H__ -/******************************************************************************/ -/* */ -/* X r d S s i F i l e S e s s . h h */ -/* */ -/* (c) 2016 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -#include "XrdSfs/XrdSfsInterface.hh" -#include "XrdSsi/XrdSsiBVec.hh" -#include "XrdSsi/XrdSsiFileReq.hh" -#include "XrdSsi/XrdSsiFileResource.hh" -#include "XrdSsi/XrdSsiRRTable.hh" -#include "XrdSys/XrdSysPthread.hh" - -class XrdOucEnv; -class XrdSfsXioHandle; -struct XrdSsiRespInfo; - -class XrdSsiFileSess -{ -public: - -static XrdSsiFileSess *Alloc(XrdOucErrInfo &einfo, const char *user); - - bool AttnInfo( XrdOucErrInfo &eInfo, - const XrdSsiRespInfo *respP, - unsigned int reqID); - - XrdOucErrInfo *errInfo() {return eInfo;} - - int close(bool viaDel=false); - - int fctl(const int cmd, - int alen, - const char *args, - const XrdSecEntity *client); - - const char *FName() {return gigID;} - - int open(const char *fileName, - XrdOucEnv &theEnv, - XrdSfsFileOpenMode openMode); - - XrdSfsXferSize read(XrdSfsFileOffset fileOffset, - char *buffer, - XrdSfsXferSize buffer_size); - - void Recycle(); - -XrdSsiFileResource &Resource() {return fileResource;} - - int SendData(XrdSfsDio *sfDio, - XrdSfsFileOffset offset, - XrdSfsXferSize size); - -static void SetAuthDNS() {authDNS = true;} - -static void SetMaxSz(int mSz) {maxRSZ = mSz;} - - void setXio(XrdSfsXio *xP) {xioP = xP;} - - int truncate(XrdSfsFileOffset fileOffset); - - XrdSfsXferSize write(XrdSfsFileOffset fileOffset, - const char *buffer, - XrdSfsXferSize buffer_size); - -private: - -// Constructor (via Alloc()) and destructor (via Recycle()) -// - XrdSsiFileSess(XrdOucErrInfo &einfo, const char *user) - {Init(einfo, user, false);} - ~XrdSsiFileSess() {} // Recycle() calls Reset() - -void Init(XrdOucErrInfo &einfo, const char *user, bool forReuse); -bool NewRequest(unsigned int reqid, XrdOucBuffer *oP, - XrdSfsXioHandle *bR, int rSz); -void Reset(); -XrdSfsXferSize writeAdd(const char *buff, XrdSfsXferSize blen, - unsigned int rid); - -static XrdSysMutex arMutex; // Alloc and Recycle protector -static XrdSsiFileSess *freeList; -static int freeNum; -static int freeNew; -static int freeMax; -static int freeAbs; - -static int maxRSZ; -static bool authDNS; - -XrdSsiFileResource fileResource; -char *tident; -XrdOucErrInfo *eInfo; -char *gigID; -char *fsUser; -XrdSysMutex myMutex; -XrdSfsXio *xioP; -XrdOucBuffer *oucBuff; -XrdSsiFileSess *nextFree; -int reqSize; -int reqLeft; -bool isOpen; -bool inProg; - -XrdSsiBVec eofVec; -XrdSsiRRTable rTab; -}; -#endif diff --git a/src/XrdSsi/XrdSsiGCS.cc b/src/XrdSsi/XrdSsiGCS.cc deleted file mode 100644 index c89a75b1dee..00000000000 --- a/src/XrdSsi/XrdSsiGCS.cc +++ /dev/null @@ -1,63 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S s i G e t C l i e n t S e r v i c e . c c */ -/* */ -/* (c) 2013 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -#include "XrdNet/XrdNetAddr.hh" -#include "XrdSsi/XrdSsiServReal.hh" - -XrdSsiService *XrdSsiGetClientService(XrdSsiErrInfo &eInfo, - const char *contact, - int oHold) -{ - XrdNetAddr netAddr; - const char *eText; - char buff[512]; - int n; - -// If no contact is given then declare an error -// - if (!contact || !(*contact)) - {eInfo.Set("Contact not specified.", EINVAL); return 0;} - -// Validate the given contact -// - if ((eText = netAddr.Set(contact))) - {eInfo.Set(eText, EINVAL); return 0;} - -// Construct new binding -// - if (!(n = netAddr.Format(buff, sizeof(buff), XrdNetAddrInfo::fmtName))) - {eInfo.Set("Unable to validate contact.", EINVAL); return 0;} - -// Allocate a service object and return it -// - return new XrdSsiServReal(buff, oHold); -} diff --git a/src/XrdSsi/XrdSsiLogger.cc b/src/XrdSsi/XrdSsiLogger.cc deleted file mode 100644 index e8d11424b9c..00000000000 --- a/src/XrdSsi/XrdSsiLogger.cc +++ /dev/null @@ -1,220 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S s i L o g g e r . c c */ -/* */ -/* (c) 2013 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Deprtment of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include - -#include "XrdVersion.hh" -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdCl/XrdClLog.hh" -#include "XrdOuc/XrdOucEnv.hh" -#include "XrdOuc/XrdOucStream.hh" -#include "XrdSsi/XrdSsiSfsConfig.hh" -#include "XrdSsi/XrdSsiLogger.hh" -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdSys/XrdSysLogger.hh" -#include "XrdSys/XrdSysPthread.hh" -#include "XrdSys/XrdSysTrace.hh" - -/******************************************************************************/ -/* G l o b a l O b j e c t s */ -/******************************************************************************/ - -namespace XrdSsi -{ - XrdSysError Log(0, "ssi_"); - XrdSysLogger *Logger = 0; - XrdSysTrace Trace("Ssi", Logger); - XrdSsiLogger::MCB_t *msgCB = 0; - XrdSsiLogger::MCB_t *msgCBCl = 0; -} - -using namespace XrdSsi; - -/******************************************************************************/ -/* C l i e n t L o g g i n g I n t e r c e p t */ -/******************************************************************************/ - -namespace -{ -class LogMCB : public XrdCl::LogOut -{ -public: - -virtual void Write(const std::string &msg); - - LogMCB(XrdSsiLogger::MCB_t *pMCB) : mcbP(pMCB) {} -virtual ~LogMCB() {} - -private: -XrdSsiLogger::MCB_t *mcbP; -}; - -void LogMCB::Write(const std::string &msg) -{ - timeval tNow; - const char *brak, *cBeg, *cMsg = msg.c_str(); - unsigned long tID = XrdSysThread::Num(); - int cLen = msg.size(); - -// Get the actual time right now -// - gettimeofday(&tNow, 0); - -// Client format: [tod][loglvl][topic] and [pid] may follow -// - cBeg = cMsg; - for (int i = 0; i < 4; i++) - {if (*cMsg != '[' || !(brak = index(cMsg, ']'))) break; - cMsg = brak+1; - } - -// Skip leading spaces now -// - while(*cMsg == ' ') cMsg++; - -// Recalculate string length -// - cLen = cLen - (cMsg - cBeg); - if (cLen < 0) cLen = strlen(cMsg); - mcbP(tNow, tID, cMsg, cLen); -} -} - -/******************************************************************************/ -/* M s g */ -/******************************************************************************/ - -void XrdSsiLogger::Msg(const char *pfx, const char *txt1, - const char *txt2, const char *txt3) -{ - -// Route the message appropriately -// - if (pfx) Log.Emsg(pfx, txt1, txt2, txt3); - else {const char *tout[6] = {txt1, 0}; - int i = 1; - if (txt2) {tout[i++] = " "; tout[i++] = txt2;} - if (txt3) {tout[i++] = " "; tout[i++] = txt3;} - tout[i] = txt3; - Log.Say(tout[0], tout[1], tout[2], tout[3], tout[4], tout[5]); - } -} - -/******************************************************************************/ -/* M s g f */ -/******************************************************************************/ - -void XrdSsiLogger::Msgf(const char *pfx, const char *fmt, ...) -{ - char buffer[2048]; - va_list args; - va_start (args, fmt); - -// Format the message -// - vsnprintf(buffer, sizeof(buffer), fmt, args); - -// Route it -// - if (pfx) Log.Emsg(pfx, buffer); - else Log.Say(buffer); -} - -/******************************************************************************/ -/* M s g v */ -/******************************************************************************/ - -void XrdSsiLogger::Msgv(const char *pfx, const char *fmt, va_list aP) -{ - char buffer[2048]; - -// Format the message -// - vsnprintf(buffer, sizeof(buffer), fmt, aP); - -// Route it -// - if (pfx) Log.Emsg(pfx, buffer); - else Log.Say(buffer); -} - -/******************************************************************************/ - -void XrdSsiLogger::Msgv(struct iovec *iovP, int iovN) -{ - Logger->Put(iovN, iovP); -} - -/******************************************************************************/ -/* S e t M C B */ -/******************************************************************************/ - -bool XrdSsiLogger::SetMCB(XrdSsiLogger::MCB_t &mcbP, - XrdSsiLogger::mcbType mcbt) -{ -// Record the callback, this may be on the server or the client -// - if (mcbt == mcbAll || mcbt == mcbServer) msgCB = mcbP; - -// If setting the clientside, get the client logging object and set a new -// logging intercept object that will route the messages here. -// - if (mcbt == mcbAll || mcbt == mcbClient) - {XrdCl::Log *logP = XrdCl::DefaultEnv::GetLog(); - if (!logP) return false; - logP->SetOutput(new LogMCB(&mcbP)); - msgCBCl = mcbP; - } - -// All done -// - return true; -} - -/******************************************************************************/ -/* T B e g */ -/******************************************************************************/ - -const char *XrdSsiLogger::TBeg() {return Logger->traceBeg();} - -/******************************************************************************/ -/* T E n d */ -/******************************************************************************/ - -void XrdSsiLogger::TEnd() -{ - cerr <traceEnd(); -} diff --git a/src/XrdSsi/XrdSsiLogger.hh b/src/XrdSsi/XrdSsiLogger.hh deleted file mode 100644 index 79a96e128b6..00000000000 --- a/src/XrdSsi/XrdSsiLogger.hh +++ /dev/null @@ -1,161 +0,0 @@ -#ifndef __XRDSSILOGGER_HH__ -#define __XRDSSILOGGER_HH__ -/******************************************************************************/ -/* */ -/* X r d S s i L o g g e r . h h */ -/* */ -/* (c) 2013 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Deprtment of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -//----------------------------------------------------------------------------- -//! The XrdSsiLogger object is used to route messages to the default log file. -//----------------------------------------------------------------------------- - -struct iovec; - -class XrdSsiLogger -{ -public: - -//----------------------------------------------------------------------------- -//! Insert a space delimited error message into the log file. -//! -//! @param pfx !0 -> the text to prefix the message; the message is formed as -//! pfx: txt1 [txt2] [txt3]\n -//! pfx =0 -> add message to the log without a time stamp or prefix. -//! @param msg the message to added to the log. -//----------------------------------------------------------------------------- - -static void Msg(const char *pfx, const char *txt1, - const char *txt2=0, const char *txt3=0); - -//----------------------------------------------------------------------------- -//! Insert a formated error message into the log file using variable args. -//! -//! @param pfx !0 -> the text to prefix the message; the message is formed as -//! : \n -//! pfx =0 -> add message to the log without a time stamp or prefix. -//! @param fmt the message formatting template (i.e. sprintf format). Note -//! that a newline character is always appended to the message. -//! @param ... the arguments that should be used with the template. The -//! formatted message is truncated at 2048 bytes. -//----------------------------------------------------------------------------- - -static void Msgf(const char *pfx, const char *fmt, ...); - -//----------------------------------------------------------------------------- -//! Insert a formated error message into the log file using a va_list. -//! -//! @param pfx !0 -> the text to prefix the message; the message is formed as -//! : \n -//! pfx =0 -> add message to the log without a time stamp or prefix. -//! @param fmt the message formatting template (i.e. sprintf format). Note -//! that a newline character is always appended to the message. -//! @param aP the arguments that should be used with the template. The -//! formatted message is truncated at 2048 bytes. -//----------------------------------------------------------------------------- - -static void Msgv(const char *pfx, const char *fmt, va_list aP); - -//----------------------------------------------------------------------------- -//! Insert a formated error message into the log file using a iovec. -//! -//! @param iovP pointer to an iovec that contains the message. -//! that a newline character is always appended to the message. -//! @param iobN the number of elements in the iovec. -//----------------------------------------------------------------------------- - -static void Msgv(struct iovec *iovP, int iovN); - -//----------------------------------------------------------------------------- -//! Set a message callback function for messages issued via this object. This -//! method should be called during static initialization (this means the call -//! needs to occur at global scope). -//! -//! @param mCB Reference to the message callback function as defined by -//! the typedef MCB_t. -//! @param mcbt Specifies the type of callback being set, as follows: -//! mcbAll - callback for client-side and server-side logging. -//! mcbClient - Callback for client-side logging. -//! mcbServer - Callback for server-side logging. -//! -//! @return bool A value of true indicates success, otherwise false returned. -//! The return value can generally be ignored and is provided as -//! a means to call this method via dynamic global initialization. -//----------------------------------------------------------------------------- - -typedef void (MCB_t)(struct timeval const &mtime, //!< TOD of message - unsigned long tID, //!< Thread issuing msg - const char *msg, //!< Message text - int mlen); //!< Length of message text - -enum mcbType {mcbAll=0, mcbClient, mcbServer}; - -static bool SetMCB(MCB_t &mcbP, mcbType mcbt=mcbAll); - -//----------------------------------------------------------------------------- -//! Define helper functions to allow ostream cerr output to appear in the log. -//! The following two functions are used with the macros below. -//! The SSI_LOG macro preceedes the message with a time stamp; SSI_SAY does not. -//! The endl ostream output item is automatically added to all output! -//----------------------------------------------------------------------------- - -#define SSI_LOG(x) {cerr < -//! -//! For instance: -//! -//! void LogMsg(struct timeval const &mtime, unsigned long tID, -//! const char *msg, int mlen) {...} -//! -//! XrdSsiLogger::MCB_t *XrdSsiLoggerMCB = &LogMsg; -//----------------------------------------------------------------------------- -#endif diff --git a/src/XrdSsi/XrdSsiLogging.cc b/src/XrdSsi/XrdSsiLogging.cc deleted file mode 100644 index fd10c3e84dd..00000000000 --- a/src/XrdSsi/XrdSsiLogging.cc +++ /dev/null @@ -1,159 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S s i L o g g i n g . c c */ -/* */ -/* (c) 2016 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Deprtment of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include - -#include "XrdVersion.hh" -#include "XrdOuc/XrdOucEnv.hh" -#include "XrdOuc/XrdOucStream.hh" -#include "XrdSsi/XrdSsiLogger.hh" -#include "XrdSys/XrdSysLogPI.hh" -#include "XrdSys/XrdSysPlugin.hh" - -/******************************************************************************/ -/* G l o b a l O b j e c t s */ -/******************************************************************************/ - -namespace XrdSsi -{ -extern XrdSsiLogger::MCB_t *msgCB; -} - -using namespace std; -using namespace XrdSsi; - -/******************************************************************************/ -/* L o g P l u g i n H o o k s */ -/******************************************************************************/ -/******************************************************************************/ -/* C o n f i g L o g */ -/******************************************************************************/ - -namespace -{ -void ConfigLog(const char *cFN) -{ - XrdVERSIONINFODEF(myVersion, ssi, XrdVNUMBER, XrdVERSION); - const char *lName; - char eBuff[2048], *var, *val, **lDest, *logPath = 0, *svcPath = 0; - XrdSysPlugin *myLib; - XrdSsiLogger::MCB_t **theCB; - XrdOucEnv myEnv; - XrdOucStream cStrm(0, getenv("XRDINSTANCE"), &myEnv, "=====> "); - int cfgFD, retc, NoGo = 0; - -// Try to open the configuration file. -// - if ((cfgFD = open(cFN, O_RDONLY, 0)) < 0) - {cerr <<"Config " <getPlugin("XrdSsiLoggerMCB")); - if (!msgCB && !theCB) cerr <<"Config " <Persist(); - } - } - else myLib->Persist(); - -// All done -// - delete myLib; -} -} - -/******************************************************************************/ -/* X r d S y s L o g P I n i t */ -/******************************************************************************/ - -extern "C" -{ -XrdSysLogPI_t XrdSysLogPInit(const char *cfgfn, char **argv, int argc) - {if (cfgfn && *cfgfn) ConfigLog(cfgfn); - if (!msgCB) - cerr <<"Config '-l@' requires a logmsg callback function " - <<"but it was found!" <. */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -#include "Xrd/XrdScheduler.hh" -#include "XrdOuc/XrdOucHash.hh" -#include "XrdSsi/XrdSsiPacer.hh" - -/******************************************************************************/ -/* G l o b a l O b j e c t s */ -/******************************************************************************/ - -namespace XrdSsi -{ -extern XrdScheduler *schedP; -} - -/******************************************************************************/ -/* L o c a l O b j e c t s */ -/******************************************************************************/ - -namespace -{ -XrdOucHash reqMap; -} - -XrdSsiMutex XrdSsiPacer::pMutex(XrdSsiMutex::Recursive); -XrdSsiPacer XrdSsiPacer::glbQ; - -/******************************************************************************/ -/* H o l d */ -/******************************************************************************/ - -void XrdSsiPacer::Hold(const char *reqID) -{ - XrdSsiMutexMon myLock(pMutex); - -// Establish correct anchor -// - if (!reqID) theQ = &glbQ; - else if (!(theQ = reqMap.Find(reqID))) - {theQ = new XrdSsiPacer; - reqMap.Add(reqID, theQ); - } - -// Before queing, check we can actually run this right away -// - if (theQ->aCnt) - {XrdSsi::schedP->Schedule(this); - theQ->aCnt--; - if (reqID && theQ->Singleton() && theQ->aCnt == 0) reqMap.Del(reqID); - } else theQ->Q_PushBack(this); -} - -/******************************************************************************/ -/* R e s e t */ -/******************************************************************************/ - -void XrdSsiPacer::Reset() -{ - XrdSsiMutexMon myLock(pMutex); - -// If we are in a queue then remove ourselves -// - if (!Singleton()) - {Q_Remove(); - if (theQ && theQ != &glbQ) - {const char *reqID = RequestID(); - if (reqID && theQ->Singleton() && theQ->aCnt == 0) reqMap.Del(reqID); - } - } -} - -/******************************************************************************/ -/* R u n */ -/******************************************************************************/ - -void XrdSsiPacer::Run(XrdSsiRequest::RDR_Info &rInfo, - XrdSsiRequest::RDR_How rHow, const char *reqID) -{ - XrdSsiMutexMon myLock(pMutex); - XrdSsiPacer *anchor, *rItem; - int allowed; - -// Determine which anchor to use -// - if (!reqID) anchor = &glbQ; - else if ((anchor = reqMap.Find(reqID))) {} - else if (rHow == XrdSsiRequest::RDR_One || rHow == XrdSsiRequest::RDR_Post) - {anchor = new XrdSsiPacer; - reqMap.Add(reqID, anchor); - } - else return; - -// Preset the information we will return -// - rInfo.iAllow = allowed = anchor->aCnt; - -// Process as request -// - switch(rHow) - {case XrdSsiRequest::RDR_All: - allowed = anchor->qCnt; - break; - case XrdSsiRequest::RDR_Hold: - rInfo.qCount = anchor->qCnt; - rInfo.fAllow = 0; - anchor->aCnt = 0; - return; - break; - case XrdSsiRequest::RDR_Immed: - allowed = 1; - break; - case XrdSsiRequest::RDR_Query: - rInfo.fAllow = rInfo.iAllow; - rInfo.qCount = anchor->qCnt; - return; - break; - case XrdSsiRequest::RDR_One: - allowed = 1; - break; - case XrdSsiRequest::RDR_Post: - allowed++; - break; - default: return; break; - } - -// Run responses -// - while(allowed && anchor->qCnt) - {rItem = anchor->next; - rItem->Q_Remove(); - XrdSsi::schedP->Schedule(rItem); - rInfo.rCount++; - allowed--; - } - -// Set returned information -// - rInfo.qCount = anchor->qCnt; - if (rHow != XrdSsiRequest::RDR_Immed) anchor->aCnt = allowed; - rInfo.fAllow = anchor->aCnt; - -// If this is a local queue, check if we removed the last element -// - if (reqID && anchor->Singleton() && anchor->aCnt == 0) reqMap.Del(reqID); -} diff --git a/src/XrdSsi/XrdSsiPacer.hh b/src/XrdSsi/XrdSsiPacer.hh deleted file mode 100644 index 927c0c6a934..00000000000 --- a/src/XrdSsi/XrdSsiPacer.hh +++ /dev/null @@ -1,88 +0,0 @@ -#ifndef __XRDSSIPACER_HH__ -#define __XRDSSIPACER_HH__ -/******************************************************************************/ -/* */ -/* X r d S s i P a c e r . h h */ -/* */ -/* (c) 2016 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "Xrd/XrdJob.hh" -#include "XrdSsi/XrdSsiAtomics.hh" -#include "XrdSsi/XrdSsiRequest.hh" - -class XrdSsiPacer : public XrdJob -{ -public: - -void DoIt() {Redrive();} - -void Hold(const char *reqID=0); - -void Q_Insert(XrdSsiPacer *Node) - {Node->next = next; // Chain in the item; - next->prev = Node; - next = Node; - Node->prev = this; - theQ->qCnt++; - } - -void Q_Remove() - {prev->next = next; // Unchain the item - next->prev = prev; - next = this; - prev = this; - theQ->qCnt--; - } - -void Q_PushBack(XrdSsiPacer *Node) {prev->Q_Insert(Node);} - -virtual void Redrive() {} // Meant to be overridden - -virtual -const char *RequestID() {return 0;} // Meant to be overridden - -void Reset(); - -static void Run(XrdSsiRequest::RDR_Info &rInfo, - XrdSsiRequest::RDR_How rhow, const char *reqid=0); - -bool Singleton() {return next == this;} - - XrdSsiPacer() : prev(this), next(this), theQ(this), - qCnt(0), aCnt(0) {} -virtual ~XrdSsiPacer() {Reset();} - -private: - -static XrdSsiMutex pMutex; -static XrdSsiPacer glbQ; -XrdSsiPacer *prev; -XrdSsiPacer *next; -XrdSsiPacer *theQ; -int qCnt; -int aCnt; -}; -#endif diff --git a/src/XrdSsi/XrdSsiProvider.hh b/src/XrdSsi/XrdSsiProvider.hh deleted file mode 100644 index fba808891a6..00000000000 --- a/src/XrdSsi/XrdSsiProvider.hh +++ /dev/null @@ -1,255 +0,0 @@ -#ifndef __XRDSSIPROVIDER_HH__ -#define __XRDSSIPROVIDER_HH__ -/******************************************************************************/ -/* */ -/* X r d S s i P r o v i d e r . h h */ -/* */ -/* (c) 2015 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -//----------------------------------------------------------------------------- -//! The XrdSsiProvider object is used by the Scalable Service Interface -//! for two purposes: -//! 1) To ascertain the availability of a resource on a server node in an SSI -//! cluster. -//! 2) To obtain a service object that can process one or more requests. -//! -//! Client-side: A provider object is predefined in libXrdSsi.so and must be -//! used by the client code to get service objects, as follows: -//! -//! extern XrdSsiProvider *XrdSsiProviderClient; -//! XrdSsiService *ClientService = XrdSsiProviderClient-> -//! GetService("hostname:port"); -//! -//! -//! Server-side: the provider object is obtained from the plugin library which -//! should have the XrdSsiProviderLookup and XrdSsiProviderServer -//! pointer symbols defined at file level (i.e. static global). -//! -//! The object pointed to by XrdSsiProviderLookup is used to obtain -//! resource availability information via QueryResource() and a -//! service object is never obtained (i.e. no call to GetService). -//! -//! The object pointed to by XrdSsiProviderServer is used to effect -//! service requests and thus *does* obtain a service object via -//! a GetService() call. -//! -//! These pointers are typically defined, as follows: -//! -//! XrdSsiProvider *XrdSsiProviderLookup -//! = new MyLookupProvider(....); -//! XrdSsiProvider *XrdSsiProviderServer -//! = new MyServerProvider(....); -//! -//! where MyLookupProvider and MyServerProvider objects must -//! inherit class XrdSsiProvider. -//! -//! You use the following directives to configure the -//! service provider for only the process that runs the cmsd: -//! -//! all.role server -//! all.manager : -//! oss.statlib -2 /libXrdSsi.so -//! -//! Warning! All methods (except Init()) in this class must be thread-safe. -//----------------------------------------------------------------------------- - -#include - -#include "XrdSsi/XrdSsiErrInfo.hh" -#include "XrdSsi/XrdSsiResource.hh" - -class XrdSsiCluster; -class XrdSsiLogger; -class XrdSsiService; - -class XrdSsiProvider -{ -public: - -//----------------------------------------------------------------------------- -//! Obtain a service object (client-side or server-side). -//! -//! @param eInfo the object where error status is to be placed. -//! @param contact the point of first contact when processing a request. -//! The contact may be "host:port" where "host" is a DNS name, -//! an IPV4 address (i.e. d.d.d.d), or an IPV6 address -//! (i.e. [x:x:x:x:x:x]), and "port" is either a numeric port -//! number or the service name assigned to the port number. -//! This is a null string if the call is being made server-side. -//! Note that only one service object is obtained by a server. -//! @param oHold the maximum number of request objects that should be held -//! in reserve for future calls. -//! -//! @return =0 A service object could not be created, eInfo has the reason. -//! @return !0 Pointer to a service object. -//----------------------------------------------------------------------------- - -virtual -XrdSsiService *GetService(XrdSsiErrInfo &eInfo, - const std::string &contact, - int oHold=256 - ) {eInfo.Set("Service not implemented!", ENOTSUP); - return 0; - } - -//----------------------------------------------------------------------------- -//! Obtain the version of the abstract class used by underlying implementation. -//! The version returned must match the version compiled in the loading library. -//! If it does not, initialization fails. -//----------------------------------------------------------------------------- - -static const int SsiVersion = 0x00010000; - - int GetVersion() {return SsiVersion;} - -//----------------------------------------------------------------------------- -//! Initialize server-side processing. This method is invoked prior to any -//! other method in the XrdSsiProvider object. -//! -//! @param logP pointer to the logger object for message routing. -//! @param clsP pointer to the cluster management object. This pointer is nil -//! when a service object is being obtained by an unclustered -//! system (i.e. a stand-alone server). -//! @param cfgFn file path to the the conifiguration file. -//! @param parms conifiguration parameters, if any. -//! @param argc The count of command line arguments (always >= 1). -//! @param argv Pointer to a null terminated array of tokenized command line -//! arguments. These arguments are taken from the command line -//! after the "-+xrdssi" option (see invoking xrootd), if present. -//! The first argument is always the same as argv[0] in main(). -//! -//! @return true Initialization succeeded. -//! @return =0 Initialization failed. The method should include an error -//! message in the log indicating why initialization failed. -//----------------------------------------------------------------------------- - -virtual bool Init(XrdSsiLogger *logP, - XrdSsiCluster *clsP, - std::string cfgFn, - std::string parms, - int argc, - char **argv - ) = 0; - -//----------------------------------------------------------------------------- -//! Obtain the status of a resource. -//! Client-side: This method can be called to obtain the availability of a -//! resource relative to a particular endpoint. -//! Server-Side: When configured via oss.statlib directive, this is called -//! server-side by the XrdSsiCluster object to see if the resource -//! can be provided by the providor via a service object. This -//! method is also used server-side to determine resource status. -//! -//! @param rName Pointer to the resource name. -//! @param contact the point of first contact that would be used to process -//! the request relative to the resource (see ProcessRequest()). -//! A nil pointer indicates a query for availibity at the -//! local node (e.g. a query for local resource availability). -//! -//! @return One of the rStat enums, as follows: -//! notPresent - resource not present on this node. -//! isPresent - resource is present and can be -//! immediately used, if necessary. -//! isPending - resource is present but is not in an -//! immediately usable state, access may wait. -//----------------------------------------------------------------------------- - -enum rStat {notPresent = 0, isPresent, isPending}; - -virtual rStat QueryResource(const char *rName, - const char *contact=0 - ) = 0; - -//----------------------------------------------------------------------------- -//! Notify provider that a resource was added to this node. This method is -//! called by the cmsd process in response to calling XrdSsiCluster::Added() -//! in the xrootd process. This method only is invoked on resource storage -//! nodes (i.e. all.role server). -//! -//! @param rName Pointer to the resource name that was added. -//----------------------------------------------------------------------------- - -virtual void ResourceAdded(const char *rName) {} - -//----------------------------------------------------------------------------- -//! Notify provider that a resource was removed from this node. This method is -//! called by the cmsd process in response to calling XrdSsiCluster::Removed() -//! in the xrootd process. This method only is invoked on resource storage -//! nodes (i.e. all.role server). -//! -//! @param rName Pointer to the resource name that was removed. -//----------------------------------------------------------------------------- - -virtual void ResourceRemoved(const char *rName) {} - -//----------------------------------------------------------------------------- -//! Set the maximum number of threads for handling callbacks (client-side only). -//! When the maximum is reached, callbacks wait until an in-progress callback -//! completes. This method must be called prior to calling GetService(). -//! This method has no meaning server-side and is ignored. -//! -//! @param cbNum The maximum number of threads to be used for callbacks and -//! sets the maximum number of active callbacks (default 300). -//! The maximum value is 32767. Note that the nproc ulimit is -//! final arbiter of the actual number of threads to use. -//! @param ntNum The maximum number of threads to be used to handle network -//! traffic. The minimum is 3, the default is 10% of cbNum but -//! no more than 100. -//----------------------------------------------------------------------------- - -virtual void SetCBThreads(int cbNum, int ntNum=0) {(void)cbNum; (void)ntNum;} - -//----------------------------------------------------------------------------- -//! Set default global timeouts. By default, all timeouts are set to infinity. -//! -//! @param what One of the enums below specifying the timeout is to be set. -//! @param tmoval The timeout valid in seconds. A value of <= 0 is ignored. -//----------------------------------------------------------------------------- - -enum tmoType {connect_N=0, //!< Number of times to try connection (client) - connect_T, //!< Time to wait for a connection (client) - idleClose, //!< Time before an idle socket is closed (client) - request_T, //!< Time to wait for a request to finsish(client) - response_T, //!< Time for client to wait for a resp (Server) - stream_T //!< Time to wait for socket activity (Client) - }; - -virtual void SetTimeout(tmoType what, int tmoval) {(void)what; (void)tmoval;} - -//----------------------------------------------------------------------------- -//! Constructor -//----------------------------------------------------------------------------- - - XrdSsiProvider() {} -protected: - -//----------------------------------------------------------------------------- -//! Destructor. The providor object cannot be and never is explicitly deleted. -//----------------------------------------------------------------------------- - -virtual ~XrdSsiProvider() {} -}; -#endif diff --git a/src/XrdSsi/XrdSsiRRAgent.hh b/src/XrdSsi/XrdSsiRRAgent.hh deleted file mode 100644 index 05f54d1f5dd..00000000000 --- a/src/XrdSsi/XrdSsiRRAgent.hh +++ /dev/null @@ -1,68 +0,0 @@ -#ifndef __XRDSSIRRAGENT_HH__ -#define __XRDSSIRRAGENT_HH__ -/******************************************************************************/ -/* */ -/* X r d S s i R R A g e n t . h h */ -/* */ -/* (c) 2017 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdSsi/XrdSsiRequest.hh" -#include "XrdSsi/XrdSsiResponder.hh" - -class XrdSsiMuex; - -class XrdSsiRRAgent -{ -public: - -static void Alert(XrdSsiRequest &reqR, XrdSsiRespInfoMsg &aMsg) - {reqR.Alert(aMsg);} - -static void CleanUp(XrdSsiRequest &reqR) {reqR.CleanUp();} - -static void Dispose(XrdSsiRequest &reqR) {reqR.Dispose();} - -static XrdSsiErrInfo &ErrInfoRef(XrdSsiRequest *rP) {return rP->errInfo;} - -static void onServer(XrdSsiRequest *rP) {rP->onClient = false;} - -static XrdSsiRequest *Request(XrdSsiResponder *rP) {return rP->reqP;} - -static XrdSsiRespInfo *RespP(XrdSsiRequest *rP) {return &(rP->Resp);} - -static void SetNode(XrdSsiRequest *rP, const char *name) - {rP->epNode = name;} - -static void ResetResponder(XrdSsiResponder *rP) - {rP->spMutex.Lock(); - rP->reqP = 0; - rP->spMutex.UnLock(); - } - -static void SetMutex(XrdSsiRequest *rP, XrdSsiMutex *mP) - {rP->rrMutex = mP;} -}; -#endif diff --git a/src/XrdSsi/XrdSsiRRInfo.hh b/src/XrdSsi/XrdSsiRRInfo.hh deleted file mode 100644 index d1b346dea75..00000000000 --- a/src/XrdSsi/XrdSsiRRInfo.hh +++ /dev/null @@ -1,102 +0,0 @@ -#ifndef _XRDSSIRRINFO_H -#define _XRDSSIRRINFO_H -/******************************************************************************/ -/* */ -/* X r d S s i R R I n f o . h h */ -/* */ -/* (c) 2013 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -#include "XrdSsi/XrdSsiRespInfo.hh" - -class XrdSsiRRInfo -{ -public: - -static const unsigned int idMax = 16777215; - -enum Opc {Rxq = 0, Rwt = 1, Can = 2}; - -inline void Cmd(Opc cmd) - {reqCmd = static_cast(cmd);} - -inline Opc Cmd() {return static_cast(reqCmd);} - -inline const unsigned char *Data() {return &reqCmd;} - -inline void Id(unsigned int id) - {unsigned char tmp = reqCmd; - reqId = htonl(id & idMask); - reqCmd = tmp; - } - -inline unsigned int Id() {return ntohl(reqId) & idMask;} - -inline void Size(unsigned int sz) {reqSize = htonl(sz);} - -inline unsigned int Size() {return ntohl(reqSize);} - -inline unsigned long long Info() - {return (static_cast(reqId & 0xffffffff) <<32LL) - |(static_cast(reqSize & 0xffffffff)); - - } - - XrdSsiRRInfo(unsigned long long ival=0) - : reqId(static_cast( (ival>>32) & 0xffffffff)), - reqSize(static_cast(ival & 0xffffffff)) {} - - ~XrdSsiRRInfo() {} - -private: -static const int idMask = 0x00ffffff; - -union {unsigned char reqCmd; - unsigned int reqId; - }; - unsigned int reqSize; -}; - -/******************************************************************************/ -/* X r d S s i R R I n f o A t t n */ -/******************************************************************************/ - -struct XrdSsiRRInfoAttn -{ -static const int alrtResp = '!'; // In tag: response data is an alert -static const int fullResp = ':'; // In tag: response data is present -static const int pendResp = '*'; // In tag: response data is pending - - char tag; - char flags; -unsigned short pfxLen; // Length of prefix -unsigned int mdLen; // Length of metadata - int rsvd1; - int rsvd2; -}; -#endif diff --git a/src/XrdSsi/XrdSsiRRTable.hh b/src/XrdSsi/XrdSsiRRTable.hh deleted file mode 100644 index 41d8b68dc25..00000000000 --- a/src/XrdSsi/XrdSsiRRTable.hh +++ /dev/null @@ -1,98 +0,0 @@ -#ifndef __XRDSSIRRTABLE_HH__ -#define __XRDSSIRRTABLE_HH__ -/******************************************************************************/ -/* */ -/* X r d S s i R R T a b l e . h h */ -/* */ -/* (c) 2017 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -#include "XrdSsi/XrdSsiAtomics.hh" - -template -class XrdSsiRRTable -{ -public: - -void Add(T *item, uint64_t itemID) - {rrtMutex.Lock(); - if (baseItem != 0) theMap[itemID] = item; - else {baseKey = itemID; - baseItem = item; - } - rrtMutex.UnLock(); - } - -void Clear() {rrtMutex.Lock(); theMap.clear(); rrtMutex.UnLock();} - -void Del(uint64_t itemID, bool finit=false) - {XrdSsiMutexMon lck(rrtMutex); - if (baseItem && baseKey == itemID) - {if (finit) baseItem->Finalize(); - baseItem = 0; - } else { - if (!finit) theMap.erase(itemID); - else {typename std::map::iterator it = theMap.find(itemID); - if (it != theMap.end()) it->second->Finalize(); - theMap.erase(it); - } - } - } - -T *LookUp(uint64_t itemID) - {XrdSsiMutexMon lck(rrtMutex); - if (baseItem && baseKey == itemID) return baseItem; - typename std::map::iterator it = theMap.find(itemID); - return (it == theMap.end() ? 0 : it->second); - } - -void Reset() - {XrdSsiMutexMon lck(rrtMutex); - typename std::map::iterator it = theMap.begin(); - while(it != theMap.end()) - {it->second->Finalize(); - it++; - } - theMap.clear(); - if (baseItem) - {baseItem->Finalize(); - baseItem = 0; - } - } - - XrdSsiRRTable() : baseItem(0), baseKey(0) {} - - ~XrdSsiRRTable() {Reset();} - -private: -XrdSsiMutex rrtMutex; -T *baseItem; -uint64_t baseKey; -std::map theMap; -}; -#endif diff --git a/src/XrdSsi/XrdSsiRequest.cc b/src/XrdSsi/XrdSsiRequest.cc deleted file mode 100644 index d2ab82860b5..00000000000 --- a/src/XrdSsi/XrdSsiRequest.cc +++ /dev/null @@ -1,203 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S s i R e q u e s t . c c */ -/* */ -/* (c) 2016 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include - -#include "XrdSsi/XrdSsiPacer.hh" -#include "XrdSsi/XrdSsiRespInfo.hh" -#include "XrdSsi/XrdSsiResponder.hh" -#include "XrdSsi/XrdSsiRequest.hh" -#include "XrdSsi/XrdSsiStream.hh" - -/******************************************************************************/ -/* S t a t i c M e m b e r s */ -/******************************************************************************/ - -namespace XrdSsi -{ -XrdSsiMutex ubMutex(XrdSsiMutex::Recursive); -} - -/******************************************************************************/ -/* C o n s t r u c t o r */ -/******************************************************************************/ - -XrdSsiRequest::XrdSsiRequest(const char *reqid, uint16_t tmo) - : reqID(reqid), rrMutex(&XrdSsi::ubMutex), - theRespond(0), rsvd1(0), epNode(0), - detTTL(0), tOut(0), onClient(true), rsvd2(0) {} - -/******************************************************************************/ -/* Private: C l e a n U p */ -/******************************************************************************/ - -void XrdSsiRequest::CleanUp() -{ -// Reinitialize the this object in case it is being reused. While we don't -// really need to get a lock, we do so just in case there is a coding error. -// - rrMutex->Lock(); - Resp.Init(); - errInfo.Clr(); - epNode = 0; - XrdSsiMutex *mP = rrMutex; - rrMutex = &XrdSsi::ubMutex; - mP->UnLock(); -} - -/******************************************************************************/ -/* Private: C o p y D a t a */ -/******************************************************************************/ - -bool XrdSsiRequest::CopyData(char *buff, int blen) -{ - bool last; - -// Make sure the buffer length is valid -// - if (blen <= 0) - {errInfo.Set("Buffer length invalid", EINVAL); - return false; - } - -// Check if we have any data here -// - rrMutex->Lock(); - if (Resp.blen > 0) - {if (Resp.blen > blen) last = false; - else {blen = Resp.blen; last = true;} - memcpy(buff, Resp.buff, blen); - Resp.buff += blen; Resp.blen -= blen; - } else {blen = 0; last = true;} - rrMutex->UnLock(); - -// Invoke the callback -// - ProcessResponseData(errInfo, buff, blen, last); - return true; -} - -/******************************************************************************/ -/* F i n i s h e d */ -/******************************************************************************/ - -bool XrdSsiRequest::Finished(bool cancel) -{ - XrdSsiResponder *respP; - -// Obtain the responder -// - rrMutex->Lock(); - respP = theRespond; - theRespond = 0; - rrMutex->UnLock(); - -// Tell any responder we are finished (we might not have one) -// - if (respP) respP->Finished(*this, Resp, cancel); - -// We are done. The object will be reiniialized when UnBindRequest() is -// called which will call UnBind() in this object. Since the timing is not -// known we can't touch anthing in this object at this point. -// Return false if there was no responder associated with this request. -// - return respP != 0; -} - -/******************************************************************************/ -/* G e t E n d P o i n t */ -/******************************************************************************/ - -std::string XrdSsiRequest::GetEndPoint() -{ - XrdSsiMutexMon lck(rrMutex); - std::string epName(epNode ? epNode : ""); - return epName; -} - -/******************************************************************************/ -/* G e t M e t a d a t a */ -/******************************************************************************/ - -const char *XrdSsiRequest::GetMetadata(int &dlen) -{ - XrdSsiMutexMon lck(rrMutex); - if ((dlen = Resp.mdlen)) return Resp.mdata; - return 0; -} - -/******************************************************************************/ -/* G e t R e s p o n s e D a t a */ -/******************************************************************************/ - -void XrdSsiRequest::GetResponseData(char *buff, int blen) -{ - XrdSsiMutexMon mHelper(rrMutex); - -// If this is really a stream then just call the stream object to get the data. -// In the degenrate case, it's actually a data response, then we must copy it. -// - if (Resp.rType == XrdSsiRespInfo::isStream) - {if (Resp.strmP->SetBuff(errInfo, buff, blen)) return;} - else if (Resp.rType == XrdSsiRespInfo::isData) - {if (CopyData(buff, blen)) return;} - else errInfo.Set("Not a stream", ENODATA); - -// If we got here then an error occured during the setup, reflect the error -// via the callback (in the future we will schedule a new thread). -// - ProcessResponseData(errInfo, buff, -1, true); -} - -/******************************************************************************/ -/* R e l e a s e R e q u e s t B u f f e r */ -/******************************************************************************/ - -void XrdSsiRequest::ReleaseRequestBuffer() -{ - XrdSsiMutexMon lck(rrMutex); - RelRequestBuffer(); -} - -/******************************************************************************/ -/* R e s t a r t D a t a R e s p o n s e */ -/******************************************************************************/ - -XrdSsiRequest::RDR_Info XrdSsiRequest::RestartDataResponse - (XrdSsiRequest::RDR_How rhow, - const char *reqid - ) -{ - RDR_Info rInfo; - - XrdSsiPacer::Run(rInfo, rhow, reqid); - return rInfo; -} diff --git a/src/XrdSsi/XrdSsiRequest.hh b/src/XrdSsi/XrdSsiRequest.hh deleted file mode 100644 index dd3f64bfa04..00000000000 --- a/src/XrdSsi/XrdSsiRequest.hh +++ /dev/null @@ -1,364 +0,0 @@ -#ifndef __XRDSSIREQUEST_HH__ -#define __XRDSSIREQUEST_HH__ -/******************************************************************************/ -/* */ -/* X r d S s i R e q u e s t . h h */ -/* */ -/* (c) 2013 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include - -#include "XrdSsi/XrdSsiAtomics.hh" -#include "XrdSsi/XrdSsiErrInfo.hh" -#include "XrdSsi/XrdSsiRespInfo.hh" - -//----------------------------------------------------------------------------- -//! The XrdSsiRequest class describes a client request and is used to effect a -//! response to the request via a companion object described by XrdSsiResponder. -//! Client-Side: Use this object to encapsulate your request and hand it off -//! to XrdSsiService::Execute() either use GetResponseData() or -//! the actual response structure to get the response data once the -//! ProcessResponse() callback is invoked. -//! -//! Server-side: XrdSsiService::ProcessRequest() is called with this object. -//! Use the XrdSsiResponder object to post a response. -//! -//! In either case, the client must invoke XrdSsiRequest::Finished() after the -//! client-server exchange is complete in order to revert ownership of this -//! object to the object's creator to allow it to be deleted or reused. -//! -//! This is an abstract class and several methods need to be implemented: -//! -//! Alert() Optional, allows receiving of server alerts. -//! GetRequest() Mandatory to supply the buffer holding the request -//! along with its length. -//! RelRequestBuffer() Optional, allows memory optimization. -//! ProcessResponse() Initial response: Mandatory -//! ProcessResponseData() Data response: Mandatory only if response data is -//! asynchronously received. -//! -//! All callbacks are invoked with no locks outstanding unless otherwise noted. -//----------------------------------------------------------------------------- - -class XrdSsiResponder; - -class XrdSsiRequest -{ -public: -friend class XrdSsiResponder; -friend class XrdSsiRRAgent; - -//----------------------------------------------------------------------------- -//! Indicate that request processing has been finished. This method calls -//! XrdSsiResponder::Finished() on the associated responder object. -//! -//! Note: This method locks the object's recursive mutex. -//! -//! @param cancel False -> the request/response sequence completed normally. -//! True -> the request/response sequence aborted because of an -//! error or the client cancelled the request. -//! -//! @return true Finish accepted. Request object may be reclaimed. -//! @return false Finish cannot be accepted because this request object is -//! not bound to a responder. This indicates a logic error. -//----------------------------------------------------------------------------- - - bool Finished(bool cancel=false); - -//----------------------------------------------------------------------------- -//! Obtain the detached request time to live value. If the value is non-zero, -//! the request is detached. Otherwise, it is an attached request and requires a -//! live TCP connection during it execution. -//! -//! @return The detached time to live value in seconds. -//----------------------------------------------------------------------------- - -inline uint32_t GetDetachTTL() {return detTTL;} - -//----------------------------------------------------------------------------- -//! Obtain the endpoint host name. -//! -//! @return A string containing the endpoint host name. If a null string is -//! returned, the endpoint has not yet been determined. Generally, the -//! endpoint is available on the first callback to this object. -//----------------------------------------------------------------------------- - -std::string GetEndPoint(); - -//----------------------------------------------------------------------------- -//! Obtain the metadata associated with a response. -//! -//! -//! Note: This method locks the object's recursive mutex. -//! -//! @param dlen holds the length of the metadata after the call. -//! -//! @return =0 No metadata available, dlen has been set to zero. -//! @return !0 Pointer to the buffer holding the metadata, dlen has the length -//----------------------------------------------------------------------------- - -const char *GetMetadata(int &dlen); - -//----------------------------------------------------------------------------- -//! Obtain the request data sent by a client. -//! -//! This method is duplicated in XrdSsiResponder to allow calling consistency. -//! -//! @param dlen holds the length of the request after the call. -//! -//! @return =0 No request data available, dlen has been set to zero. -//! @return !0 Pointer to the buffer holding the request, dlen has the length -//----------------------------------------------------------------------------- - -virtual char *GetRequest(int &dlen) = 0; - -//----------------------------------------------------------------------------- -//! Get the request ID established at object creation time. -//! -//! @return Pointer to the request ID or nil if there is none. -//----------------------------------------------------------------------------- - -inline -const char *GetRequestID() {return reqID;} - -//----------------------------------------------------------------------------- -//! Asynchronously obtain response data. This is a helper method that allows a -//! client to deal with a passive stream response. This method also handles -//! data response, albeit inefficiently by copying the data response. However, -//! this allows for uniform response processing regardless of response type. -//! -//! @param buff pointer to the buffer to receive the data. The buffer must -//! remain valid until ProcessResponseData() is called. -//! @param blen the length of the buffer (i.e. maximum that can be returned). -//----------------------------------------------------------------------------- - - void GetResponseData(char *buff, int blen); - -//----------------------------------------------------------------------------- -//! Get timeout for initiating the request. -//! -//! @return The timeout value. -//----------------------------------------------------------------------------- - - uint16_t GetTimeOut() {return tOut;} - -//----------------------------------------------------------------------------- -//! Notify request that a response is ready to be processed. This method must -//! be supplied by the request object's implementation. -//! -//! @param eInfo Error information. You can check if an error occurred using -//! eInfo.hasError() or eInfo.isOK(). -//! @param rInfo Raw response information. -//! -//! @return true Response processed. -//! @return false Response could not be processed, the request is not active. -//----------------------------------------------------------------------------- - -virtual bool ProcessResponse(const XrdSsiErrInfo &eInfo, - const XrdSsiRespInfo &rInfo)=0; - -//----------------------------------------------------------------------------- -//! Handle incoming async stream data or error. This method is called by a -//! stream object after a successful GetResponseData() or an asynchronous -//! stream SetBuff() call. -//! -//! @param eInfo Error information. You can check if an error occurred using -//! eInfo.hasError() or eInfo.isOK(). -//! @param buff Pointer to the buffer given to XrdSsiStream::SetBuff(). -//! @param blen The number of bytes in buff or an error indication if blen < 0. -//! @param last true This is the last stream segment, no more data remains. -//! @param false More data may remain in the stream. -//! @return One of the enum PRD_Xeq: -//! PRD_Normal - Processing completed normally, continue. -//! PRD_Hold - Processing could not be done now, place request -//! in the global FIFO hold queue and resume when -//! RestartDataResponse() is called. -//! PRD_HoldLcl - Processing could not be done now, place request -//! in the request ID FIFO local queue and resume -//! when RestartDataResponse() is called with the ID -//! that was passed to the this request object -//! constructor. -//----------------------------------------------------------------------------- - -enum PRD_Xeq {PRD_Normal = 0, PRD_Hold = 1, PRD_HoldLcl = 2}; - -virtual PRD_Xeq ProcessResponseData(const XrdSsiErrInfo &eInfo, char *buff, - int blen, bool last) {return PRD_Normal;} - -//----------------------------------------------------------------------------- -//! Release the request buffer of the request bound to this object. This method -//! duplicates the protected method RelRequestBuffer() and exists here for -//! calling safety and consistency relative to the responder. -//----------------------------------------------------------------------------- - - void ReleaseRequestBuffer(); - -//----------------------------------------------------------------------------- -//! Restart a ProcessResponseData() call for a request that was previously held -//! (see return enums on ProcessResponseData method). This is a client-side -//! only call and is ignored server-side. When a data response is restarted, -//! ProcessResponseData() is called again when the same parameters as existed -//! when the call resulted in a hold action. -//! -//! @param rhow An enum (see below) that specifies the action to be taken. -//! RDR_All - runs all queued responses and then deletes the -//! queue identified by reqid, unless it is nil. -//! RDR_Hold - sets the allowed restart count to zero and does -//! not restart any queued responses. -//! RDR_Immed - restarts one response if it is queued. The allowed -//! count is left unchanged. -//! RDR_Query - returns information about the queue but otherwise -//! does not restart any queued responses. -//! RDR_One - Sets the allowed restart count to one. If a -//! response is queued, it is restarted and the count -//! is set to zero. -//! RDR_Post - Adds one to the allowed restart count. If a -//! response is queued, it is restarted and one is -//! subtracted from the allowed restart count. -//! -//! @param reqid Points to the requestID associated with a hold queue. When not -//! specified, then the global queue is used to restart responses. -//! Note that the memory associated with the named queue may be -//! lost if the queue is left with an allowed value > 0.To avoid -//! this issue the call with RDR_All to clean it up when it is no -//! longer needed (this will avoid having hung responses). -//! -//! @return Information about the queue (see struct RDR_Info). -//----------------------------------------------------------------------------- - -enum RDR_How {RDR_All=0, RDR_Hold, RDR_Immed, RDR_Query, RDR_One, RDR_Post}; - -struct RDR_Info{int rCount; //!< Number restarted - int qCount; //!< Number of queued request remaining - int iAllow; //!< Initial value of the allowed restart count - int fAllow; //!< Final value of the allowed restart count - - RDR_Info() : rCount(0), qCount(0), iAllow(0), fAllow(0) {} - }; - -static RDR_Info RestartDataResponse(RDR_How rhow, const char *reqid=0); - -//----------------------------------------------------------------------------- -//! Constructor -//! -//! @param reqid Pointer to a request ID that can be used to group requests. -//! See ProcessResponseData() and RestartDataReponse(). If reqid -//! is nil then held responses are placed in the global queue. -//! The pointer must be valid for the life of this object. -//! -//! @param tmo The request initiation timeout value 0 equals default). -//----------------------------------------------------------------------------- - - XrdSsiRequest(const char *reqid=0, uint16_t tmo=0); - -protected: - -//----------------------------------------------------------------------------- -//! @brief Send or receive a server generated alert. -//! -//! The Alert() method is used server-side to send one or more alerts before a -//! response is posted (alerts afterwards are ignored). To avoid race conditions, -//! server-side alerts should be sent via the Responder's Alert() method. -//! Clients must implement this method in order to receive alerts. -//! -//! @param aMsg Reference to the message object containing the alert message. -//! Non-positive alert lengths cause the alert call to be -//! ignored. You should call the message RecycleMsg() method -//! once you have consumed the message to release its resources. -//----------------------------------------------------------------------------- - -virtual void Alert(XrdSsiRespInfoMsg &aMsg) {aMsg.RecycleMsg(false);} - -//----------------------------------------------------------------------------- -//! Release the request buffer. Use this method to optimize storage use; this -//! is especially relevant for long-running requests. If the request buffer -//! has been consumed and is no longer needed, early return of the buffer will -//! minimize memory usage. This method is also invoked via XrdSsiResponder. -//! -//! -//! Note: This method is called with the object's recursive mutex locked when -//! it is invoked via XrdSsiResponder's ReleaseRequestBuffer(). -//----------------------------------------------------------------------------- - -virtual void RelRequestBuffer() {} - -//----------------------------------------------------------------------------- -//! @brief Set the detached request time to live value. -//! -//! By default, requests are executed in the foreground (i.e. during its -//! execution, if the TCP connection drops, the request is automatically -//! cancelled. When a non-zero time to live is set, the request is executed in -//! the background (i.e. detached) and no persistent TCP connection is required. -//! You must use the XrdSsiService::Attach() method to foreground such a -//! request within the number of seconds specified for dttl or the request is -//! automatically cancelled. The value must be set before passing the request -//! to XrdSsiService::ProcessRequest(). Once the request is started, a request -//! handle is returned which can be passed to XrdSsiService::Attach(). -//! -//! @param detttl The detach time to live value. -//----------------------------------------------------------------------------- - -inline void SetDetachTTL(uint32_t dttl) {detTTL = dttl;} - -//----------------------------------------------------------------------------- -//! Set timeout for initiating the request. If a non-default value is desired, -//! it must be set prior to calling XrdSsiService::ProcessRequest(). -//! -//! @param tmo The timeout value. -//----------------------------------------------------------------------------- - - void SetTimeOut(uint16_t tmo) {tOut = tmo;} - -//----------------------------------------------------------------------------- -//! Destructor. This object can only be deleted by the object creator. Once the -//! object is passed to XrdSsiService::ProcessRequest() it may only be deleted -//! after Finished() is called to allow the service to reclaim any resources -//! allocated for the request object. -//----------------------------------------------------------------------------- - -virtual ~XrdSsiRequest() {} - -private: -virtual void BindDone() {} - void CleanUp(); - bool CopyData(char *buff, int blen); -virtual void Dispose() {} - -const char *reqID; -XrdSsiMutex *rrMutex; -XrdSsiResponder *theRespond; // Set via XrdSsiResponder::BindRequest() -XrdSsiRespInfo Resp; // Set via XrdSsiResponder::SetResponse() -XrdSsiErrInfo errInfo; -long long rsvd1; -const char *epNode; -uint32_t detTTL; -uint16_t tOut; -bool onClient; -char rsvd2; -}; -#endif diff --git a/src/XrdSsi/XrdSsiResource.hh b/src/XrdSsi/XrdSsiResource.hh deleted file mode 100644 index 36481b6a91d..00000000000 --- a/src/XrdSsi/XrdSsiResource.hh +++ /dev/null @@ -1,108 +0,0 @@ -#ifndef __XRDSSIRESOURCE_HH__ -#define __XRDSSIRESOURCE_HH__ -/******************************************************************************/ -/* */ -/* X r d S s i R e s o u r c e . h h */ -/* */ -/* (c) 2016 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -//----------------------------------------------------------------------------- -//! The XrdSsiResource object is used by the Scalable Service Interface to -//! describe the resource that a request needs in order to execute. -//----------------------------------------------------------------------------- - -class XrdSsiEntity; - -class XrdSsiResource -{ -public: - -std::string rName; //!< -> Name of the resource to be used -std::string rUser; //!< -> Name of the resource user (nil if anonymous) -std::string rInfo; //!< -> Additional information in CGI format -std::string hAvoid; //!< -> Comma separated list of hosts to avoid -XrdSsiEntity *client; //!< -> Pointer to client identification (server-side) - -enum Affinity {Default, //!< Use configured affinity - None, //!< Resource has no affinity, any endpoint will do - Weak, //!< Use resource on same node if possible, don't wait - Strong, //!< Use resource on same node even if wait required - Strict //!< Always use same node for resource no matter what - }; -Affinity affinity;//!< Resource affinity - -uint32_t rOpts; //!< Resource options. One or more of he following: -static -const uint32_t Reusable= 1;//!> Resource context may be cached and reused -static -const uint32_t Discard = 2;//!> Discard cached resource if it exists - -//----------------------------------------------------------------------------- -//! Constructor -//! -//! @param rname the name of the resource. If using directory -//! notation (i.e. slash separated names); duplicate slashes -//! and dot-slashes are compressed out. -//! -//! @param havoid if not null then points to a comma separated list of -//! hostnames to avoid when finding the resource. This -//! argument is only meaningful client-side. -//! -//! @param ruser the name of the resource user. If nil the user is -//! anonymous (unnamed). By default, all resources share -//! the TCP connection to any endpoint. Different users have -//! separate connections only if so requested vis the newConn -//! option (see options above). -//! -//! @param rinfo additional information to be passed to the endpoint that -//! that provides the resource. The string should be in cgi -//! format (e.g. var=val&var2=val2&....). -//! -//! @param raff resource affinity (see Affinity enum). -//! -//! @param ropts resource handling options (see individual options) -//----------------------------------------------------------------------------- - - XrdSsiResource(std::string rname, - std::string havoid="", - std::string ruser="", - std::string rinfo="", - uint32_t ropts=0, - Affinity raff=Default - ) : rName(rname), rUser(ruser), rInfo(rinfo), - hAvoid(havoid), client(0), affinity(raff), - rOpts(ropts) {} - -//----------------------------------------------------------------------------- -//! Destructor -//----------------------------------------------------------------------------- - - ~XrdSsiResource() {} -}; -#endif diff --git a/src/XrdSsi/XrdSsiRespInfo.hh b/src/XrdSsi/XrdSsiRespInfo.hh deleted file mode 100644 index e2c67e0a8fd..00000000000 --- a/src/XrdSsi/XrdSsiRespInfo.hh +++ /dev/null @@ -1,131 +0,0 @@ -#ifndef __XRDSSIRESPINFO_HH__ -#define __XRDSSIRESPINFO_HH__ -/******************************************************************************/ -/* */ -/* X r d S s i R e s p I n f o . h h */ -/* */ -/* (c) 2014 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -//----------------------------------------------------------------------------- -//! The RespInfo structure describes the response to be posted to a request. -//! It is used mainly server-side via the inherited XrdSsiResponder class (see -//! XrdSsiResponder::SetResponse). It generally hidden on the client-side since -//! many of the response types valid server-side are converted to simpler -//! responses client-side and requiring a client to fully deal with this struct -//! is largely over-kill and unnecessary. -//----------------------------------------------------------------------------- - -class XrdSsiStream; - -struct XrdSsiRespInfo - {union {const char *buff; //!< ->buffer when rType == isData - //!< ->buffer when rType == isHandle - const char *eMsg; //!< ->msg text when rType == isError - long long fsize; //!< ->file size when rType == isFile - XrdSsiStream *strmP; //!< ->SsiStream when rType == isStream - }; - union { int blen; //!< buffer len When rType == isData - //!< buffer len When rType == isHandle - int eNum; //!< errno When rType == isError - int fdnum; //!< filedesc When rType == isFile - }; - int mdlen; //!< Metadata length - const char *mdata; //!< -> Metadata about response. - - enum Resp_t {isNone = 0, isData, isError, isFile, isStream, isHandle}; - Resp_t rType; - - inline void Init() {fsize=0; blen=0; mdlen=0; mdata=0; rType=isNone;} - - const char *State() const {if (rType == isData ) return "isData"; - if (rType == isError ) return "isError"; - if (rType == isHandle) return "isHandle"; - if (rType == isFile ) return "isFile"; - if (rType == isStream) return "isStream"; - if (rType == isNone ) return "isNone"; - return "isUndef"; - } - - XrdSsiRespInfo() {Init();} - ~XrdSsiRespInfo() {} - }; - -/******************************************************************************/ -/* X r d S s i R e s p M s g */ -/******************************************************************************/ - -//----------------------------------------------------------------------------- -//! The RespInfoMsg class describes an async response message sent to the -//! XrdSsiRequest::Alert() method. It encapsulates the message sent and must -//! recover any resources used by the message when RecycleMsg() is called. -//----------------------------------------------------------------------------- - -class XrdSsiRespInfoMsg -{ -public: - -//----------------------------------------------------------------------------- -//! Obtain the message associated with the message object. -//! -//! @param mlen holds the length of the message after the call. -//! -//! @return =0 No message available, dlen has been set to zero. -//! @return !0 Pointer to the buffer holding the message, dlen has the length -//----------------------------------------------------------------------------- - -inline char *GetMsg(int &mlen) {mlen = msgLen; return msgBuf;} - -//----------------------------------------------------------------------------- -//! Release resources used by the message. This method must be called after the -//! message is processed by the XrdSsiRequest::Alert() method. -//! -//! @param sent When true, the message was sent. Otherwise, it was not sent. -//----------------------------------------------------------------------------- - -virtual void RecycleMsg(bool sent=true) = 0; - -//----------------------------------------------------------------------------- -//! Contructor -//! -//! @param msgP Pointer to the message buffer. -//! @param mlen length of the message. -//----------------------------------------------------------------------------- - - XrdSsiRespInfoMsg(char *msgP, int mlen) - : msgBuf(msgP), msgLen(mlen) {} - -protected: - -//----------------------------------------------------------------------------- -//! Destructor. This object may not be deleted. Use Recycle() instead. -//----------------------------------------------------------------------------- - -virtual ~XrdSsiRespInfoMsg() {} - -char *msgBuf; -int msgLen; -}; -#endif diff --git a/src/XrdSsi/XrdSsiResponder.cc b/src/XrdSsi/XrdSsiResponder.cc deleted file mode 100644 index 25f17d499c6..00000000000 --- a/src/XrdSsi/XrdSsiResponder.cc +++ /dev/null @@ -1,336 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S s i R e s p o n d e r . h h */ -/* */ -/* (c) 2013 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdSsi/XrdSsiAtomics.hh" -#include "XrdSsi/XrdSsiResponder.hh" -#include "XrdSsi/XrdSsiRRAgent.hh" - -/******************************************************************************/ -/* L o c a l D e f i n e s */ -/******************************************************************************/ - -#define SSI_VAL_RESPONSE spMutex.Lock();\ - if (!reqP)\ - {spMutex.UnLock(); return notActive;}\ - reqP->rrMutex->Lock();\ - if (reqP->theRespond != this)\ - {reqP->rrMutex->UnLock(); spMutex.UnLock();\ - return notActive;\ - }\ - if (reqP->Resp.rType)\ - {reqP->rrMutex->UnLock(); spMutex.UnLock();\ - return notPosted;\ - } - -#define SSI_XEQ_RESPONSE if (reqP->onClient)\ - {XrdSsiRequest *rX = reqP;\ - reqP->rrMutex->UnLock(); spMutex.UnLock();\ - return (rX->ProcessResponse(rX->errInfo,rX->Resp)\ - ? wasPosted : notActive);\ - } else {\ - bool isOK = reqP->ProcessResponse(reqP->errInfo,\ - reqP->Resp);\ - reqP->rrMutex->UnLock(); spMutex.UnLock();\ - return (isOK ? wasPosted : notActive);\ - } - -/******************************************************************************/ -/* L o c a l C l a s s e s */ -/******************************************************************************/ - -namespace -{ -class XeqUnBind : public XrdSsiResponder -{ -public: - -virtual void Finished( XrdSsiRequest &rqstR, - const XrdSsiRespInfo &rInfo, - bool cancel=false) - {XrdSsiRRAgent::Dispose(rqstR);} - - XeqUnBind() {} - ~XeqUnBind() {} -}; -} - -/******************************************************************************/ -/* S t a t i c s */ -/******************************************************************************/ - -namespace -{ -XeqUnBind ForceUnBind; -} - -/******************************************************************************/ -/* C o n s t r u c t o r */ -/******************************************************************************/ - -XrdSsiResponder::XrdSsiResponder() - : spMutex(XrdSsiMutex::Recursive), reqP(0), - rsvd1(0), rsvd2(0), rsvd3(0) - {} - -/******************************************************************************/ -/* D e s t r u c t o r */ -/******************************************************************************/ - -XrdSsiResponder::~XrdSsiResponder() -{ -// Lock ourselves (unlikely that we need to). -// - spMutex.Lock(); - -// If we haven't taken a trip to Finished() then we need todo it here. The -// issue we have is that this object may be deleted before Finished() is called -// which is quite dicey. So, we defer it until Finished() is called. This is -// only an issue server-side as we don't control the finish process. -// - if (reqP) - {reqP->rrMutex->Lock(); - if (reqP->theRespond == this) - {reqP->theRespond = &ForceUnBind; - reqP->rrMutex->UnLock(); - } else if (reqP->theRespond == 0) // Finish() has been called - {reqP->rrMutex->UnLock(); - reqP->Dispose(); - } - } - -// All done -// - spMutex.UnLock(); -} - -/******************************************************************************/ -/* A l e r t */ -/******************************************************************************/ - -void XrdSsiResponder::Alert(XrdSsiRespInfoMsg &aMsg) -{ - XrdSsiMutexMon lck(spMutex); - -// If we have a request pointer then forward the alert. Otherwise, deep-six it -// - if (reqP) reqP->Alert(aMsg); - else aMsg.RecycleMsg(false); -} - -/******************************************************************************/ -/* B i n d R e q u e s t */ -/******************************************************************************/ - -void XrdSsiResponder::BindRequest(XrdSsiRequest &rqstR) -{ - XrdSsiMutexMon lck(spMutex); - -// Get the request lock and link the request to this object and vice versa -// - rqstR.rrMutex->Lock(); - reqP = &rqstR; - rqstR.theRespond = this; - -// Initialize the request object -// - rqstR.Resp.Init(); - rqstR.errInfo.Clr(); - -// Notify the request that the bind comleted (this is only used on the -// server to allow a pending finish request to be sent to the responder). -// - rqstR.BindDone(); - -// Unlock the request. The responder is unlocked upon return -// - rqstR.rrMutex->UnLock(); -} - -/******************************************************************************/ -/* G e t R e q u e s t */ -/******************************************************************************/ - -char *XrdSsiResponder::GetRequest(int &dlen) -{ - XrdSsiMutexMon lck(spMutex); - -// If we have a request pointer, forward the call. Otherwise return nothing. -// - if (reqP) return reqP->GetRequest(dlen); - dlen = 0; - return 0; -} - -/******************************************************************************/ -/* R e l e a s e R e q u e s t B u f f e r */ -/******************************************************************************/ - -void XrdSsiResponder::ReleaseRequestBuffer() -{ - XrdSsiMutexMon lck(spMutex); - -// If we have a request, forward the call (note we need to also get the -// the request lock to properly serialize this call). -// - if (reqP) reqP->ReleaseRequestBuffer(); -} - -/******************************************************************************/ -/* S e t M e t a d a t a */ -/******************************************************************************/ - -XrdSsiResponder::Status XrdSsiResponder::SetMetadata(const char *buff, int blen) -{ - XrdSsiMutexMon lck(spMutex); - -// If we don't have a request or the args are invalid, return an error. -// - if (!reqP || blen < 0 || blen > MaxMetaDataSZ) return notPosted; - -// Post the metadata -// - reqP->rrMutex->Lock(); - reqP->Resp.mdata = buff; - reqP->Resp.mdlen = blen; - reqP->rrMutex->UnLock(); - return wasPosted; -} - -/******************************************************************************/ -/* S e t E r r R e s p o n s e */ -/******************************************************************************/ - -XrdSsiResponder::Status XrdSsiResponder::SetErrResponse(const char *eMsg, - int eNum) -{ - -// Validate object for a response -// - SSI_VAL_RESPONSE; - -// Set the error response (we have the right locks now) -// - reqP->errInfo.Set(eMsg, eNum); - reqP->Resp.eMsg = reqP->errInfo.Get(reqP->Resp.eNum).c_str(); - reqP->Resp.rType = XrdSsiRespInfo::isError; - -// Complete the response -// - SSI_XEQ_RESPONSE; -} - -/******************************************************************************/ -/* S e t R e s p o n s e */ -/******************************************************************************/ - -XrdSsiResponder::Status XrdSsiResponder::SetResponse(const char *buff, int blen) -{ - -// Validate object for a response -// - SSI_VAL_RESPONSE; - -// Set the response (we have the right locks now) -// - reqP->Resp.buff = buff; - reqP->Resp.blen = blen; - reqP->Resp.rType = XrdSsiRespInfo::isData; - -// Complete the response -// - SSI_XEQ_RESPONSE; -} - -/******************************************************************************/ - -XrdSsiResponder::Status XrdSsiResponder::SetResponse(long long fsize, int fdnum) -{ - -// Validate object for a response -// - SSI_VAL_RESPONSE; - -// Set the response (we have the right locks now) -// - reqP->Resp.fdnum = fdnum; - reqP->Resp.fsize = fsize; - reqP->Resp.rType = XrdSsiRespInfo::isFile; - -// Complete the response -// - SSI_XEQ_RESPONSE; -} - -/******************************************************************************/ - -XrdSsiResponder::Status XrdSsiResponder::SetResponse(XrdSsiStream *strmP) -{ - -// Validate object for a response -// - SSI_VAL_RESPONSE; - -// Set the response (we have the right locks now) -// - reqP->Resp.eNum = 0; - reqP->Resp.strmP = strmP; - reqP->Resp.rType = XrdSsiRespInfo::isStream; - -// Complete the response -// - SSI_XEQ_RESPONSE; -} - -/******************************************************************************/ -/* U n B i n d R e q u e s t */ -/******************************************************************************/ - -bool XrdSsiResponder::UnBindRequest() -{ - XrdSsiMutexMon spMon(spMutex); - -// If we are not bound to a request, indicate an error. -// - if (!reqP) return false; - -// Lock the request and if Finished() was not called, indicate an error. -// - reqP->rrMutex->Lock(); - if (reqP->theRespond != 0) - {reqP->rrMutex->UnLock(); - return false; - } - -// We have a request pointer and Finish() was called; so do the actual unbind. -// - reqP->rrMutex->UnLock(); - reqP->Dispose(); - reqP = 0; - return true; -} diff --git a/src/XrdSsi/XrdSsiResponder.hh b/src/XrdSsi/XrdSsiResponder.hh deleted file mode 100644 index f1a73835946..00000000000 --- a/src/XrdSsi/XrdSsiResponder.hh +++ /dev/null @@ -1,267 +0,0 @@ -#ifndef __XRDSSIRESPONDER_HH__ -#define __XRDSSIRESPONDER_HH__ -/******************************************************************************/ -/* */ -/* X r d S s i R e s p o n d e r . h h */ -/* */ -/* (c) 2013 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -#include "XrdSsi/XrdSsiRequest.hh" - -//----------------------------------------------------------------------------- -//! The XrdSsiResponder.hh class provides a request processing object a way to -//! respond to a request. It is a companion (friend) class of XrdSsiRequest. -//! This class is only practically meaningful server-side. -//! -//! Any class that needs to post a response, release a request buffer or post -//! stream data to the request object should inherit this class and use its -//! methods to get access to the request object. Typically, a task class that -//! is created by XrdSsiService::Execute() to handle the request would inherit -//! this class so it can respond. The object that wants to post a response must -//! first call BindRequest() to establish the request-responder association. -//! -//! When the XrdSsiResponder::SetResponse() method is called to post a response -//! the request object's ProcessResponse() method is called. Ownership of the -//! request object does not revert back to the object's creator until the -//! XrdSsiRequest::Finished() method returns. That method first calls -//! XrdSsiResponder::Finished() to break the request-responder association and -//! reclaim any response data buffer or stream resource before it gives up -//! control of the request object. This means you must provide an implementation -//! To the Finished() method defined here. -//! -//! Once Finished() is called you must call UnBindRequest() when you are -//! actually through referencing the request object. While that is true most -//! of the time, it may not be so when an async cancellation occurs (i.e. -//! you may need to defer release of the request object). Note that should -//! you delete this object before calling UnBindRequest(), the responder -//! object is forcibly unbound from the request. -//----------------------------------------------------------------------------- - -class XrdSsiStream; - -class XrdSsiResponder -{ -public: -friend class XrdSsiRequest; -friend class XrdSsiRRAgent; - -//----------------------------------------------------------------------------- -//! The maximum amount of metadata+data (i.e. the sum of two blen arguments in -//! SetMetadata() and and SetResponse(const char *buff, int blen), respectively) -//! that may be directly sent to the client without the SSI framework converting -//! the data buffer response into a stream response. -//----------------------------------------------------------------------------- - -static const int MaxDirectXfr = 2097152; //< Max (metadata+data) direct xfr - -//----------------------------------------------------------------------------- -//! Take ownership of a request object by binding the request object to a -//! responder object. This method must be called by the responder before -//! posting any responses. -//! -//! @param rqstR reference to the request object. -//----------------------------------------------------------------------------- - - void BindRequest(XrdSsiRequest &rqstR); - -//----------------------------------------------------------------------------- -//! Unbind this responder from the request object it is bound to. Upon return -//! ownership of the associated request object reverts back to the creator of -//! the object who is responsible for deleting or recycling the request object. -//! UnBindRequest() is also called when the responder object is deleted. -//! -//! @return true Request successfully unbound. -//! false UnBindRequest already called or called prior to Finish(). -//----------------------------------------------------------------------------- - - bool UnBindRequest(); - -protected: - -//----------------------------------------------------------------------------- -//! Send an alert message to the request. This is a convenience method that -//! avoids race conditions with Finished() so it is safe to use in all cases. -//! This is a server-side call. The service is responsible for creating a -//! RespInfoMsg object containing the message and supplying a RecycleMsg() method. -//! -//! @param aMsg reference to the message to be sent. -//----------------------------------------------------------------------------- - - void Alert(XrdSsiRespInfoMsg &aMsg); - -//----------------------------------------------------------------------------- -//! Notify the responder that a request either completed or was canceled. This -//! allows the responder to release any resources given to the request object -//! (e.g. data response buffer or a stream). This method is invoked when -//! XrdSsiRequest::Finished() is called by the client. -//! -//! @param rqstP reference to the object describing the request. -//! @param rInfo reference to the object describing the response. -//! @param cancel False -> the request/response interaction completed. -//! True -> the request/response interaction aborted because -//! of an error or the client requested that the -//! request be canceled. -//----------------------------------------------------------------------------- - -virtual void Finished( XrdSsiRequest &rqstR, - const XrdSsiRespInfo &rInfo, - bool cancel=false) = 0; - -//----------------------------------------------------------------------------- -//! Obtain the request data sent by a client. -//! -//! Note: This method is called with the object's recursive mutex unlocked! -//! -//! @param dlen holds the length of the request after the call. -//! -//! @return =0 No request data available, dlen has been set to zero. -//! @return !0 Pointer to the buffer holding the request, dlen has the length -//----------------------------------------------------------------------------- - - char *GetRequest(int &dlen); - -//----------------------------------------------------------------------------- -//! Release the request buffer of the request bound to this object. This method -//! duplicates the protected method of the same name in XrdSsiRequest and exists -//! here for calling safety and consistency relative to the responder. -//----------------------------------------------------------------------------- - - void ReleaseRequestBuffer(); - -//----------------------------------------------------------------------------- -//! The following enums are returned by SetMetadata() and SetResponse() to -//! indicate ending status. -//----------------------------------------------------------------------------- - -enum Status {wasPosted=0, //!< Success: The response was successfully posted - notPosted, //!< Failure: A request was not bound to this object - //!< or a response has already been posted - //!< or the metadata length was invalid - notActive //!< Failure: Request is no longer active - }; - -//----------------------------------------------------------------------------- -//! Set a pointer to metadata to be sent out-of-band ahead of the response. -//! -//! @param buff pointer to a buffer holding the metadata. The buffer must -//! remain valid until XrdSsiResponder::Finished() is called. -//! @param blen the length of the metadata in buff that is to be sent. It must -//! in the range 0 <= blen <= MaxMetaDataSZ. -//! -//! @return See Status enum for possible values. -//----------------------------------------------------------------------------- - -static const int MaxMetaDataSZ = 2097152; //!< 2MB metadata limit - - Status SetMetadata(const char *buff, int blen); - -//----------------------------------------------------------------------------- -//! Set an error response for a request. -//! -//! @param eMsg the message describing the error. The message is copied to -//! private storage. -//! @param eNum the errno associated with the error. -//! -//! @return See Status enum for possible values. -//----------------------------------------------------------------------------- - - Status SetErrResponse(const char *eMsg, int eNum); - -//----------------------------------------------------------------------------- -//! Set a nil response for a request (used for sending only metadata). -//! -//! @return See Status enum for possible values. -//----------------------------------------------------------------------------- - -inline Status SetNilResponse() {return SetResponse((const char *)0,0);} - -//----------------------------------------------------------------------------- -//! Set a memory buffer containing data as the request response. -//! -//! @param buff pointer to a buffer holding the response. The buffer must -//! remain valid until XrdSsiResponder::Finished() is called. -//! @param blen the length of the response in buff that is to be sent. -//! -//! @return See Status enum for possible values. -//----------------------------------------------------------------------------- - - Status SetResponse(const char *buff, int blen); - -//----------------------------------------------------------------------------- -//! Set a file containing data as the response. -//! -//! @param fsize the size of the file containing the response. -//! @param fdnum the file descriptor of the open file. -//! -//! @return See Status enum for possible values. -//----------------------------------------------------------------------------- - - Status SetResponse(long long fsize, int fdnum); - -//----------------------------------------------------------------------------- -//! Set a stream object that is to provide data as the response. -//! -//! @param strmP pointer to stream object that is to be used to supply response -//! data. See XrdSsiStream for more details. -//! -//! @return See Status enum for possible values. -//----------------------------------------------------------------------------- - - Status SetResponse(XrdSsiStream *strmP); - -//----------------------------------------------------------------------------- -//! This class is meant to be inherited by an object that will actually posts -//! responses. -//----------------------------------------------------------------------------- - - XrdSsiResponder(); - -//----------------------------------------------------------------------------- -//! Destructor is protected. You cannot use delete on a responder object, as it -//! is meant to be inherited by a class and not separately instantiated. -//----------------------------------------------------------------------------- - -protected: - -virtual ~XrdSsiResponder(); - -private: - -// The spMutex protects the reqP pointer. It is a hiearchical mutex in that it -// may be obtained prior to obtaining the mutex protecting the request without -// fear of a deadlock (the reverse is not possible). If reqP is zero then -// this responder is not bound to a request. -// -XrdSsiMutex spMutex; -XrdSsiRequest *reqP; -long long rsvd1; // Reserved fields for extensions with ABI compliance -long long rsvd2; -long long rsvd3; -}; -#endif diff --git a/src/XrdSsi/XrdSsiScale.hh b/src/XrdSsi/XrdSsiScale.hh deleted file mode 100644 index 160911cdc6e..00000000000 --- a/src/XrdSsi/XrdSsiScale.hh +++ /dev/null @@ -1,93 +0,0 @@ -#ifndef __XRDSSISCALE_HH__ -#define __XRDSSISCALE_HH__ -/******************************************************************************/ -/* */ -/* X r d S s i S c a l e . h h */ -/* */ -/* (c) 2013 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -#include "XrdSys/XrdSysPthread.hh" - -class XrdSsiScale -{ -public: - -static const int maxEnt = 32; -static const unsigned int maxPend = 65500; - -int getEnt() {entMutex.Lock(); - if (pendCnt[nowEnt] < maxPend) - {pendCnt[nowEnt]++; - entMutex.UnLock(); - return nowEnt; - } - int xEnt = (nowEnt < maxEnt ? nowEnt+1 : 0); - int zEnt = maxEnt; - do {for (int i = xEnt; i < zEnt; i++) - {if (pendCnt[i] < maxPend) - {pendCnt[i]++; - nowEnt = i; - entMutex.UnLock(); - return i; - } - } - if (!xEnt) break; - xEnt = 0; zEnt = nowEnt; - } while(true); - entMutex.UnLock(); - return -1; - } - -void retEnt(int xEnt) {if (xEnt >= 0 && xEnt < maxEnt) - {entMutex.Lock(); - if (pendCnt[xEnt]) pendCnt[xEnt]--; - entMutex.UnLock(); - } - } - -bool rsvEnt(int xEnt) {if (xEnt < 0 && xEnt >= maxEnt) return false; - entMutex.Lock(); - if (pendCnt[nowEnt] < maxPend) - {pendCnt[nowEnt]++; - entMutex.UnLock(); - return true; - } - entMutex.UnLock(); - return false; - } - - XrdSsiScale() : nowEnt(0) {memset(pendCnt, 0, sizeof(uint16_t)*maxEnt);} - ~XrdSsiScale() {} - -private: - -XrdSysMutex entMutex; -uint16_t pendCnt[maxEnt]; -int nowEnt; -}; -#endif diff --git a/src/XrdSsi/XrdSsiServReal.cc b/src/XrdSsi/XrdSsiServReal.cc deleted file mode 100644 index 883224591f4..00000000000 --- a/src/XrdSsi/XrdSsiServReal.cc +++ /dev/null @@ -1,307 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S s i S e r v R e a l . c c */ -/* */ -/* (c) 2013 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include - -#include "XrdSsi/XrdSsiResource.hh" -#include "XrdSsi/XrdSsiRRAgent.hh" -#include "XrdSsi/XrdSsiScale.hh" -#include "XrdSsi/XrdSsiServReal.hh" -#include "XrdSsi/XrdSsiSessReal.hh" -#include "XrdSsi/XrdSsiTrace.hh" -#include "XrdSsi/XrdSsiUtils.hh" - -/******************************************************************************/ -/* S t a t i c s & G l o b a l s */ -/******************************************************************************/ - -namespace XrdSsi -{ - XrdSsiScale sidScale; -} - -using namespace XrdSsi; - -/******************************************************************************/ -/* D e s t r u c t o r */ -/******************************************************************************/ - -XrdSsiServReal::~XrdSsiServReal() -{ - XrdSsiSessReal *sP; - -// Free pointer to the manager node -// - if (manNode) {free(manNode); manNode = 0;} - -// Delete all free session objects -// - while((sP = freeSes)) - {freeSes = sP->nextSess; - delete sP; - } -} - -/******************************************************************************/ -/* Private: A l l o c */ -/******************************************************************************/ - -XrdSsiSessReal *XrdSsiServReal::Alloc(const char *sName, int uent, bool hold) -{ - XrdSsiSessReal *sP; - -// Reuse or allocate a new session object and return it -// - myMutex.Lock(); - actvSes++; - if ((sP = freeSes)) - {freeCnt--; - freeSes = sP->nextSess; - myMutex.UnLock(); - sP->InitSession(this, sName, uent, hold); - } else { - myMutex.UnLock(); - if (!(sP = new XrdSsiSessReal(this, sName, uent, hold))) - {myMutex.Lock(); actvSes--; myMutex.UnLock();} - } - return sP; -} - -/******************************************************************************/ -/* Private: G e n U R L */ -/******************************************************************************/ - -bool XrdSsiServReal::GenURL(XrdSsiResource *rP, char *buff, int blen, int uEnt) -{ - static const char affTab[] = "\0\0n\0w\0s\0S"; - const char *xUsr, *xAt, *iSep, *iVal, *tVar, *tVal, *uVar, *uVal; - const char *aVar, *aVal, *qVal = ""; - char uBuff[8]; - int n; - -// Preprocess avoid list, if any -// - if (rP->hAvoid.length() == 0) tVar = tVal = ""; - else {tVar = "&tried="; - tVal = rP->hAvoid.c_str(); - qVal = "?"; - } - -// Preprocess affinity -// - if (!(rP->affinity)) aVar = aVal = ""; - else {aVar = "&cms.aff="; - aVal = &affTab[rP->affinity*2]; - qVal = "?"; - } - -// Check if we need to add a user name -// - if (rP->rUser.length() == 0) uVar = uVal = ""; - else {uVar = "&ssi.user="; - uVal = rP->rUser.c_str(); - qVal = "?"; - } - -// Preprocess the cgi information -// - if (rP->rInfo.length() == 0) iSep = iVal = ""; - else {iVal = rP->rInfo.c_str(); - iSep = "&ssi.cgi="; - qVal = "?"; - } - -// Check if we need to qualify the host with a user index -// - if (uEnt == 0) xUsr = xAt = ""; - else {snprintf(uBuff, sizeof(uBuff), "%d", uEnt); - xUsr= uBuff; - xAt = "@"; - } - -// Generate appropriate url -// ? t a u i - n = snprintf(buff, blen, "xroot://%s%s%s/%s%s%s%s%s%s%s%s%s%s", - xUsr, xAt, manNode, rP->rName.c_str(), qVal, - tVar, tVal, aVar, aVal, - uVar, uVal, iSep, iVal); - -// Return overflow or not -// - return n < blen; -} - -/******************************************************************************/ -/* P r o c e s s R e q u e s t */ -/******************************************************************************/ - -void XrdSsiServReal::ProcessRequest(XrdSsiRequest &reqRef, - XrdSsiResource &resRef) -{ - static const uint32_t useCache = XrdSsiResource::Reusable - | XrdSsiResource::Discard; - XrdSysMutexHelper mHelp; - XrdSsiSessReal *sObj; - std::string resKey; - int uEnt; - bool hold = (resRef.rOpts & XrdSsiResource::Reusable) != 0; - char epURL[4096]; - -// Validate the resource name -// - if (resRef.rName.length() == 0) - {XrdSsiUtils::RetErr(reqRef, "Resource name missing.", EINVAL); - return; - } - -// Check if this is a reusable resource. Reusable resources are a bit more -// complicated to pull off. In any case, we need to hold the cache lock. -// - if (resRef.rOpts & useCache) - {mHelp.Lock(&rcMutex); - if (ResReuse(reqRef, resRef, resKey)) return; - } - -// Get a sid entry number -// - if ((uEnt = sidScale.getEnt()) < 0) - {XrdSsiUtils::RetErr(reqRef, "Out of stream resources.", ENOSR); - return; - } - -// Construct url -// - if (!GenURL(&resRef, epURL, sizeof(epURL), uEnt)) - {XrdSsiUtils::RetErr(reqRef, "Resource url is too long.", ENAMETOOLONG); - sidScale.retEnt(uEnt); - return; - } - -// Obtain a new session object -// - if (!(sObj = Alloc(resRef.rName.c_str(), uEnt, hold))) - {XrdSsiUtils::RetErr(reqRef, "Insufficient memory.", ENOMEM); - sidScale.retEnt(uEnt); - return; - } - -// Now just provision this resource which will execute the request should it -// be successful. If Provision() fails, we need to delete the session object -// because its file object now is in an usable state (funky client interface). -// - if (!(sObj->Provision(&reqRef, epURL))) Recycle(sObj, false); - -// If this was started with a reusable resource, put the session in the cache. -// The resource key was constructed by the call to ResReuse() and teh cache -// mutex is still held at this point (will be released upon return). -// - if (hold) resCache[resKey] = sObj; -} - -/******************************************************************************/ -/* R e c y c l e */ -/******************************************************************************/ - -void XrdSsiServReal::Recycle(XrdSsiSessReal *sObj, bool reuse) -{ - EPNAME("Recycle"); - static const char *tident = "ServReal"; - -// Clear all pending events (likely not needed) -// - sObj->ClrEvent(); - -// Add to queue unless we have too many of these -// - myMutex.Lock(); - actvSes--; - DEBUG("reuse=" <second; - if (resRef.rOpts & XrdSsiResource::Discard || !sesP->Run(&reqRef)) - {resCache.erase(it); - sesP->UnHold(); - return false; - } - -// All done, the request should have been sent off via Reusable() call. -// - return true; -} - -/******************************************************************************/ -/* S t o p */ -/******************************************************************************/ - -bool XrdSsiServReal::Stop() -{ -// Make sure we are clean -// - myMutex.Lock(); - if (actvSes) {myMutex.UnLock(); return false;} - myMutex.UnLock(); - delete this; - return true; -} diff --git a/src/XrdSsi/XrdSsiServReal.hh b/src/XrdSsi/XrdSsiServReal.hh deleted file mode 100644 index 53fc08a0c19..00000000000 --- a/src/XrdSsi/XrdSsiServReal.hh +++ /dev/null @@ -1,73 +0,0 @@ -#ifndef __XRDSSISERVREAL_HH__ -#define __XRDSSISERVREAL_HH__ -/******************************************************************************/ -/* */ -/* X r d S s i S e r v R e a l . h h */ -/* */ -/* (c) 2013 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -#include "XrdSsi/XrdSsiService.hh" -#include "XrdSys/XrdSysPthread.hh" - -class XrdSsiResource; -class XrdSsiSessReal; - -class XrdSsiServReal : public XrdSsiService -{ -public: - -void ProcessRequest(XrdSsiRequest &reqRef, XrdSsiResource &resRef); - -void Recycle(XrdSsiSessReal *sObj, bool reuse); - -bool Stop(); - - XrdSsiServReal(const char *contact, int hObj) - : manNode(strdup(contact)), freeSes(0), - freeCnt(0), freeMax(hObj), actvSes(0) {} - - ~XrdSsiServReal(); -private: - -XrdSsiSessReal *Alloc(const char *sName, int uent, bool hold); -bool GenURL(XrdSsiResource *rP, char *buff, int blen, int uEnt); -bool ResReuse(XrdSsiRequest &reqRef, XrdSsiResource &resRef, - std::string &resKey); - -std::map resCache; -XrdSysMutex rcMutex; - -char *manNode; -XrdSysMutex myMutex; -XrdSsiSessReal *freeSes; -int freeCnt; -int freeMax; -int actvSes; -}; -#endif diff --git a/src/XrdSsi/XrdSsiService.cc b/src/XrdSsi/XrdSsiService.cc deleted file mode 100644 index f751f2717c4..00000000000 --- a/src/XrdSsi/XrdSsiService.cc +++ /dev/null @@ -1,58 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S s i S e r v i c e . c c */ -/* */ -/* (c) 2017 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdSsiProvider.hh" -#include "XrdSsiService.hh" - -/******************************************************************************/ -/* G l o b a l I t e m s */ -/******************************************************************************/ - -namespace XrdSsi -{ -XrdSsiProvider *Provider = 0; -} - -/******************************************************************************/ -/* P r e p a r e */ -/******************************************************************************/ - -bool XrdSsiService::Prepare(XrdSsiErrInfo &eInfo, const XrdSsiResource &rDesc) -{ -// The default implementation simply asks the proviuder if the resource exists -// - if (XrdSsi::Provider - && XrdSsi::Provider->QueryResource(rDesc.rName.c_str()) != - XrdSsiProvider::notPresent) return true; - -// Indicate we do not have the resource -// - eInfo.Set("Resource not available.", ENOENT); - return false; -} diff --git a/src/XrdSsi/XrdSsiService.hh b/src/XrdSsi/XrdSsiService.hh deleted file mode 100644 index f98937c9e62..00000000000 --- a/src/XrdSsi/XrdSsiService.hh +++ /dev/null @@ -1,185 +0,0 @@ -#ifndef __XRDSSISERVICE_HH__ -#define __XRDSSISERVICE_HH__ -/******************************************************************************/ -/* */ -/* X r d S s i S e r v i c e . h h */ -/* */ -/* (c) 2015 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -//----------------------------------------------------------------------------- -//! The XrdSsiService object is used by the Scalable Service Interface to -//! process client requests. -//! -//! There may be many client-side service objects, as needed. However, only -//! one such object is obtained server-side. The object is used to effect all -//! service requests and handle responses. -//! -//! Client-side: the service object is obtained via the object pointed to by -//! XrdSsiProviderClient defined in libXrdSsi.so -//! -//! Server-side: the service object is obtained via the object pointed to by -//! XrdSsiProviderServer defined in the plugin shared library. -//----------------------------------------------------------------------------- - -class XrdSsiErrInfo; -class XrdSsiRequest; -class XrdSsiResource; - -class XrdSsiService -{ -public: - -//----------------------------------------------------------------------------- -//! Obtain the version of the abstract class used by underlying implementation. -//! The version returned must match the version compiled in the loading library. -//! If it does not, initialization fails. -//----------------------------------------------------------------------------- - -static const int SsiVersion = 0x00020000; - - int GetVersion() {return SsiVersion;} - -//----------------------------------------------------------------------------- -//! @brief Attach to a backgrounded request. -//! -//! When a client calls Attach() the server-side Attach() is invoked to -//! indicate that the backgrounded request is now a foreground request. Many -//! times such notification is not needed so a default nil implementation is -//! provided. Server-side Attach() calls are always passed the original request -//! object reference so that it can pair up the request with the attach. -//! -//! @param eInfo Reference to an error info object which will contain the -//! error describing why the attach failed (i.e. return false). -//! -//! @param handle Reference to the handle provided to the callback method -//! XrdSsiRequest::ProcessResponse() via isHandle response type. -//! This is always an empty string on server-side calls. -//! -//! @param reqRef Reference to the request object that is to attach to the -//! backgrounded request. It need not be the original request -//! object (client-side) but it always is the original request -//! object server-side. -//! -//! @param resP A pointer to the resource object describing the request -//! resources. This is meaningless for client calls and should -//! not be specified. For server-side calls, it can be used to -//! reauthorize the request since the client performing the -//! attach may be different from the client that actually -//! started the request. -//! -//! @return true Continue normally, no issues detected. -//! false An exception occurred, the eInfo object has the reason. For -//! server side calls this provides the service the ability to -//! reject request reattachment. -//----------------------------------------------------------------------------- - -virtual bool Attach( XrdSsiErrInfo &eInfo, - const std::string &handle, - XrdSsiRequest &reqRef, - XrdSsiResource *resP=0 - ) {return true;} - -//----------------------------------------------------------------------------- -//! @brief Prepare for processing subsequent resource request. -//! -//! This method is meant to be used server-side to optimize subsequent request -//! processing, perform authorization, and allow a service to stall or redirect -//! requests. It is optional and a default implementation is provided that -//! simply asks the provider if the resource exists on the server. Clients need -//! not call or implement this method. -//! -//! @param eInfo The object where error information is to be placed. -//! @param rDesc Reference to the resource object that describes the -//! resource subsequent requests will use. -//! -//! @return true Continue normally, no issues detected. -//! false An exception occurred, the eInfo object has the reason. -//! -//! Special notes for server-side processing: -//! -//! 1) Two special errors are recognized that allow for a client retry: -//! -//! resP->eInfo.eNum = EAGAIN (client should retry elsewhere) -//! resP->eInfo.eMsg = the host name where the client is redirected -//! resP->eInfo.eArg = the port number to be used by the client -//! -//! resP->eInfo.eNum = EBUSY (client should wait and then retry). -//! resP->eInfo.eMsg = an optional reason for the wait. -//! resP->eInfo.eArg = the number of seconds the client should wait. -//----------------------------------------------------------------------------- - -virtual bool Prepare(XrdSsiErrInfo &eInfo, const XrdSsiResource &rDesc); - -//----------------------------------------------------------------------------- -//! @brief Process a request; client-side or server-side. -//! -//! When a client calls ProcessRequest() the same method is called server-side -//! with the same parameters that the client specified except for timeOut which -//! is always set to zero server-side. -//! -//! @param reqRef Reference to the Request object that describes the -//! request. -//! -//! @param resRef Reference to the Resource object that describes the -//! resource that the request will be using. -//! -//! @return All results are returned via the request object callback methods. -//! For background queries, the XrdSsiRequest::ProcessResponse() is -//! called with a response type of isHandle when the request is handed -//! off to the endpoint for execution (see XrdSsiRequest::SetDetachTTL). -//----------------------------------------------------------------------------- - -virtual void ProcessRequest(XrdSsiRequest &reqRef, - XrdSsiResource &resRef - ) = 0; - -//----------------------------------------------------------------------------- -//! @brief Stop the client-side service. This is never called server-side. -//! -//! @return true Service has been stopped and this object has been deleted. -//! @return false Service cannot be stopped because there are still active -//! foreground requests. Cancel the requests then call Stop(). -//----------------------------------------------------------------------------- - -virtual bool Stop() {return false;} - -//----------------------------------------------------------------------------- -//! Constructor -//----------------------------------------------------------------------------- - - XrdSsiService() {} -protected: - -//----------------------------------------------------------------------------- -//! Destructor. The service object cannot be explicitly deleted. Use Stop(). -//----------------------------------------------------------------------------- - -virtual ~XrdSsiService() {} -}; -#endif diff --git a/src/XrdSsi/XrdSsiSessReal.cc b/src/XrdSsi/XrdSsiSessReal.cc deleted file mode 100644 index 38f0b2bf9e3..00000000000 --- a/src/XrdSsi/XrdSsiSessReal.cc +++ /dev/null @@ -1,454 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S s i S e s s R e a l . c c */ -/* */ -/* (c) 2013 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "XrdSsi/XrdSsiAtomics.hh" -#include "XrdSsi/XrdSsiRequest.hh" -#include "XrdSsi/XrdSsiRRAgent.hh" -#include "XrdSsi/XrdSsiRRInfo.hh" -#include "XrdSsi/XrdSsiScale.hh" -#include "XrdSsi/XrdSsiServReal.hh" -#include "XrdSsi/XrdSsiSessReal.hh" -#include "XrdSsi/XrdSsiTaskReal.hh" -#include "XrdSsi/XrdSsiTrace.hh" -#include "XrdSsi/XrdSsiUtils.hh" - -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysHeaders.hh" -#include "Xrd/XrdScheduler.hh" - -using namespace XrdSsi; - -/******************************************************************************/ -/* L o c a l D e f i n e s */ -/******************************************************************************/ - -#define SINGLETON(dlvar, theitem)\ - theitem ->dlvar .next == theitem - -#define INSERT(dlvar, curitem, newitem) \ - newitem ->dlvar .next = curitem; \ - newitem ->dlvar .prev = curitem ->dlvar .prev; \ - curitem ->dlvar .prev-> dlvar .next = newitem; \ - curitem ->dlvar .prev = newitem - -#define REMOVE(dlbase, dlvar, curitem) \ - if (dlbase == curitem) dlbase = (SINGLETON(dlvar,curitem) \ - ? 0 : curitem ->dlvar .next);\ - curitem ->dlvar .prev-> dlvar .next = curitem ->dlvar .next;\ - curitem ->dlvar .next-> dlvar .prev = curitem ->dlvar .prev;\ - curitem ->dlvar .next = curitem;\ - curitem ->dlvar .prev = curitem - -/******************************************************************************/ -/* L o c a l S t a t i c s */ -/******************************************************************************/ - -namespace -{ - std::string dsProperty("DataServer"); - const char *tident = 0; -} - -/******************************************************************************/ -/* G l o b a l s */ -/******************************************************************************/ - -namespace XrdSsi -{ -extern XrdScheduler *schedP; - -extern XrdSysError Log; -extern XrdSsiScale sidScale; -} - -/******************************************************************************/ -/* L o c a l C l a s s e s */ -/******************************************************************************/ - -namespace -{ -class CleanUp : public XrdJob -{ -public: - -void DoIt() {sessP->Lock(); - sessP->Unprovision(); - delete this; - } - - CleanUp(XrdSsiSessReal *sP) : sessP(sP) {} - ~CleanUp() {} - -private: -XrdSsiSessReal *sessP; -}; -} - -/******************************************************************************/ -/* D e s t r u c t o r */ -/******************************************************************************/ - -XrdSsiSessReal::~XrdSsiSessReal() -{ - XrdSsiTaskReal *tP; - - if (sessName) free(sessName); - if (sessNode) free(sessNode); - - while((tP = freeTask)) {freeTask = tP->attList.next; delete tP;} -} - -/******************************************************************************/ -/* I n i t S e s s i o n */ -/******************************************************************************/ - -void XrdSsiSessReal::InitSession(XrdSsiServReal *servP, const char *sName, - int uent, bool hold) -{ - requestP = 0; - uEnt = uent; - attBase = 0; - freeTask = 0; - myService = servP; - nextTID = 0; - alocLeft = XrdSsiRRInfo::idMax; - isHeld = hold; - inOpen = false; - noReuse = false; - if (sessName) free(sessName); - sessName = (sName ? strdup(sName) : 0); - if (sessNode) free(sessNode); - sessNode = 0; -} - -/******************************************************************************/ -/* Private: N e w T a s k */ -/******************************************************************************/ - -// Must be called with sessMutex locked! - -XrdSsiTaskReal *XrdSsiSessReal::NewTask(XrdSsiRequest *reqP) -{ - EPNAME("NewTask"); - XrdSsiTaskReal *ptP, *tP; - -// Allocate a task object for this request -// - if ((tP = freeTask)) freeTask = tP->attList.next; - else {if (!alocLeft || !(tP = new XrdSsiTaskReal(this, nextTID))) - {XrdSsiUtils::RetErr(*reqP, "Too many active requests.", EMLINK); - return 0; - } - alocLeft--; nextTID++; - } - -// Initialize the task and return its pointer -// - tP->Init(reqP, reqP->GetTimeOut()); - DEBUG("Task=" <GetTimeOut()); - -// If there was an error, scuttle the request. Note that errors will be returned -// on a separate thread to avoid hangs here. -// - if (!epStatus.IsOK()) - {std::string eTxt; - int eNum = XrdSsiUtils::GetErr(epStatus, eTxt); - XrdSsiUtils::RetErr(*reqP, eTxt.c_str(), eNum); - XrdSsi::sidScale.retEnt(uEnt); - return false; - } - -// Queue a new task and indicate our state -// - NewTask(reqP); - inOpen = true; - return true; -} - -/******************************************************************************/ -/* Private: R e l T a s k */ -/******************************************************************************/ - -void XrdSsiSessReal::RelTask(XrdSsiTaskReal *tP) // sessMutex locked! -{ - -// Delete this task or place it on the free list -// - if (!isHeld) delete tP; - else {tP->attList.next = freeTask; - freeTask = tP; - } -} - -/******************************************************************************/ -/* R u n */ -/******************************************************************************/ - -bool XrdSsiSessReal::Run(XrdSsiRequest *reqP) -{ - XrdSsiMutexMon sessMon(sessMutex); - XrdSsiTaskReal *tP; - -// If we are not allowed to be reused, return to indicated try someone else -// - if (noReuse) return false; - -// Reserve a stream ID. If we cannot then indicate we cannot be reused -// - if (!XrdSsi::sidScale.rsvEnt(uEnt)) return false; - -// Queue a new task -// - tP = NewTask(reqP); - -// If we are already open and we have a task, send off the request -// - if (!inOpen && tP && !tP->SendRequest(sessNode)) noReuse = true; - return true; -} - -/******************************************************************************/ -/* Private: S h u t d o w n */ -/******************************************************************************/ - -// Called with sessMutex locked and return with it unlocked - -void XrdSsiSessReal::Shutdown(XrdCl::XRootDStatus &epStatus, bool onClose) -{ - XrdSsiTaskReal *tP, *ntP = freeTask; - -// Delete all acccumulated tasks -// - while((tP = ntP)) {ntP = tP->attList.next; delete tP;} - freeTask = 0; - -// If the close failed then we cannot recycle this object as it is not reusable -// - if (onClose && !epStatus.IsOK()) - {std::string eText; - int eNum = XrdSsiUtils::GetErr(epStatus, eText); - char mBuff[1024]; - snprintf(mBuff, sizeof(mBuff), "Unprovision: %s@%s error; %d", - sessName, sessNode, eNum); - Log.Emsg("Shutdown", mBuff, eText.c_str()); - sessMutex.UnLock(); - } else { - if (sessName) {free(sessName); sessName = 0;} - if (sessNode) {free(sessNode); sessNode = 0;} - sessMutex.UnLock(); - myService->Recycle(this, !noReuse); - } -} - -/******************************************************************************/ -/* T a s k F i n i s h e d */ -/******************************************************************************/ - -void XrdSsiSessReal::TaskFinished(XrdSsiTaskReal *tP) -{ -// Lock our mutex -// - sessMutex.Lock(); - -// Remove task from the task list if it's in it -// - if (tP == attBase || tP->attList.next != tP) - {REMOVE(attBase, attList, tP);} - -// Clear any pending task events and decrease active count -// - tP->ClrEvent(); - -// Return the request entry number -// - XrdSsi::sidScale.retEnt(uEnt); - -// Place the task on the free list. If we can shutdown, then unprovision which -// will drive a shutdown. The returns without the sessMutex, otherwise we must -// unlock it before we return. -// - RelTask(tP); - if (!isHeld && !attBase) Unprovision(); - else sessMutex.UnLock(); -} - -/******************************************************************************/ -/* U n H o l d */ -/******************************************************************************/ - -void XrdSsiSessReal::UnHold() -{ - XrdSsiMutexMon sessMon(sessMutex); - -// Turn off the hold flag and if we have no attached tasks, schedule shutdown -// - isHeld = false; - if (!attBase) XrdSsi::schedP->Schedule(new CleanUp(this)); -} - -/******************************************************************************/ -/* Private: U n p r o v i s i o n */ -/******************************************************************************/ - -// Called with sessMutex locked and returns with it unlocked - -void XrdSsiSessReal::Unprovision() // Called with sessMutex locked! -{ - EPNAME("Unprovision"); - XrdCl::XRootDStatus uStat; - -// Clear any pending events -// - DEBUG("Closing " <IsOK(); - -// If we have no requests then we may want to simply shoutdown. -// Note that shutdown and unprovision unlock the sessMutex. -// - if (!tP) - {if (isHeld) - {sessMutex.UnLock(); - return false; - } - if (!status->IsOK()) Shutdown(*status, false); - else {if (!isHeld) Unprovision(); - else sessMutex.UnLock(); - } - return false; - } - -// We are here because the open finally completed. If the open failed, then -// schedule an error for all pending tasks. The Finish() call on each will -// drive the cleanup of this session. -// - if (!status->IsOK()) - {XrdSsiErrInfo eInfo; - XrdSsiUtils::SetErr(*status, eInfo); - do {tP->SchedError(&eInfo); tP = tP->attList.next;} - while(tP != attBase); - sessMutex.UnLock(); - return false; - } - -// Obtain the endpoint name -// - std::string currNode; - if (epFile.GetProperty(dsProperty, currNode)) - {if (sessNode) free(sessNode); - sessNode = strdup(currNode.c_str()); - } else sessNode = strdup("Unknown!"); - -// Execute each pending request. -// - do {if (!tP->SendRequest(sessNode)) noReuse = true; - tP = tP->attList.next; - } while(tP != attBase); - -// We are done, field the next event -// - sessMutex.UnLock(); - return true; -} diff --git a/src/XrdSsi/XrdSsiSessReal.hh b/src/XrdSsi/XrdSsiSessReal.hh deleted file mode 100644 index ea923444118..00000000000 --- a/src/XrdSsi/XrdSsiSessReal.hh +++ /dev/null @@ -1,106 +0,0 @@ -#ifndef __XRDSSISESSREAL_HH__ -#define __XRDSSISESSREAL_HH__ -/******************************************************************************/ -/* */ -/* X r d S s i S e s s R e a l . h h */ -/* */ -/* (c) 2013 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -#include "XrdCl/XrdClFile.hh" - -#include "XrdSsi/XrdSsiAtomics.hh" -#include "XrdSsi/XrdSsiEvent.hh" - -#include "XrdSys/XrdSysPthread.hh" - -class XrdSsiServReal; -class XrdSsiTaskReal; - -class XrdSsiSessReal : public XrdSsiEvent -{ -public: - -XrdSsiSessReal *nextSess; - - void InitSession(XrdSsiServReal *servP, - const char *sName, - int uent, - bool hold); - - void Lock() {sessMutex.Lock();} - -XrdSsiMutex *MutexP() {return &sessMutex;} - - bool Provision(XrdSsiRequest *reqP, const char *epURL); - - bool Run(XrdSsiRequest *reqP); - - void TaskFinished(XrdSsiTaskReal *tP); - - void UnHold(); - - void UnLock() {sessMutex.UnLock();} - - void Unprovision(); - - bool XeqEvent(XrdCl::XRootDStatus *status, - XrdCl::AnyObject **respP); - - XrdSsiSessReal(XrdSsiServReal *servP, - const char *sName, - int uent, - bool hold=false) - : XrdSsiEvent("SessReal"), - sessMutex(XrdSsiMutex::Recursive), - sessName(0), sessNode(0) - {InitSession(servP, sName, uent, hold);} - - ~XrdSsiSessReal(); - -XrdCl::File epFile; - -private: -XrdSsiTaskReal *NewTask(XrdSsiRequest *reqP); -void RelTask(XrdSsiTaskReal *tP); -void Shutdown(XrdCl::XRootDStatus &epStatus, bool onClose); - -XrdSsiMutex sessMutex; -XrdSsiServReal *myService; -XrdSsiTaskReal *attBase; -XrdSsiTaskReal *freeTask; -XrdSsiRequest *requestP; -char *sessName; -char *sessNode; -uint32_t nextTID; -uint32_t alocLeft; -int16_t uEnt; // User index for scaling -bool isHeld; -bool inOpen; -bool noReuse; -}; -#endif diff --git a/src/XrdSsi/XrdSsiSfs.cc b/src/XrdSsi/XrdSsiSfs.cc deleted file mode 100644 index 8e55a59ed39..00000000000 --- a/src/XrdSsi/XrdSsiSfs.cc +++ /dev/null @@ -1,520 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S s i S f s . c c */ -/* */ -/* (c) 2013 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Deprtment of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "XrdNet/XrdNetAddr.hh" -#include "XrdNet/XrdNetIF.hh" - -#include "XrdSsi/XrdSsiDir.hh" -#include "XrdSsi/XrdSsiFile.hh" -#include "XrdSsi/XrdSsiProvider.hh" -#include "XrdSsi/XrdSsiSfs.hh" -#include "XrdSsi/XrdSsiSfsConfig.hh" -#include "XrdSsi/XrdSsiTrace.hh" - -#include "XrdCms/XrdCmsClient.hh" - -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdSys/XrdSysLogger.hh" -#include "XrdSys/XrdSysPlatform.hh" -#include "XrdSys/XrdSysPthread.hh" -#include "XrdSys/XrdSysTrace.hh" - -#include "XrdOuc/XrdOucEnv.hh" -#include "XrdOuc/XrdOucERoute.hh" -#include "XrdOuc/XrdOucLock.hh" -#include "XrdOuc/XrdOucPList.hh" -#include "XrdOuc/XrdOucTList.hh" -#include "XrdSec/XrdSecEntity.hh" -#include "XrdSfs/XrdSfsAio.hh" -#include "XrdSfs/XrdSfsInterface.hh" - -#include "XrdVersion.hh" - -#ifdef AIX -#include -#endif - -/******************************************************************************/ -/* V e r s i o n I d e n t i f i c a t i o n */ -/******************************************************************************/ - -XrdVERSIONINFO(XrdSfsGetFileSystem,ssi); - -/******************************************************************************/ -/* G l o b a l O b j e c t s */ -/******************************************************************************/ - -namespace -{ -XrdSsiSfsConfig *Config; - -}; - -namespace XrdSsi -{ -extern XrdSsiProvider *Provider; - -extern XrdNetIF *myIF; - - XrdSfsFileSystem *theFS = 0; - -extern XrdOucPListAnchor FSPath; - -extern bool fsChk; - -extern XrdSysError Log; - -extern XrdSysLogger *Logger; - -extern XrdSysTrace Trace; -}; - -using namespace XrdSsi; - -/******************************************************************************/ -/* S t a t i c O b j e c t s */ -/******************************************************************************/ - -int XrdSsiSfs::freeMax = 256; - -/******************************************************************************/ -/* X r d S f s G e t F i l e S y s t e m */ -/******************************************************************************/ - -extern "C" -{ -XrdSfsFileSystem *XrdSfsGetFileSystem(XrdSfsFileSystem *nativeFS, - XrdSysLogger *logger, - const char *configFn) -{ - static XrdSsiSfs mySfs; - static XrdSsiSfsConfig myConfig; - -// Set pointer to the config and file system -// - Config = &myConfig; - theFS = nativeFS; - -// No need to herald this as it's now the default filesystem -// - Log.SetPrefix("ssi_"); - Log.logger(logger); - Logger = logger; - Trace.SetLogger(logger); - -// Initialize the subsystems -// - if (!myConfig.Configure(configFn)) return 0; - -// All done, we can return the callout vector to these routines. -// - return &mySfs; -} -} - -/******************************************************************************/ -/* c h k s u m */ -/******************************************************************************/ - -int XrdSsiSfs::chksum( csFunc Func, // In - const char *csName, // In - const char *Path, // In - XrdOucErrInfo &einfo, // Out - const XrdSecEntity *client, // In - const char *opaque) // In -{ -// Reroute this request if we can -// - if (fsChk) return theFS->chksum(Func, csName, Path, einfo, client, opaque); - einfo.setErrInfo(ENOTSUP, "Checksums are not supported."); - return SFS_ERROR; -} - -/******************************************************************************/ -/* c h m o d */ -/******************************************************************************/ - -int XrdSsiSfs::chmod(const char *path, // In - XrdSfsMode Mode, // In - XrdOucErrInfo &einfo, // Out - const XrdSecEntity *client, // In - const char *info) // In -{ -// Reroute this request if we can -// - if (fsChk) - {if (FSPath.Find(path)) - return theFS->chmod(path, Mode, einfo, client, info); - einfo.setErrInfo(ENOTSUP, "chmod is not supported for given path."); - } else einfo.setErrInfo(ENOTSUP, "chmod is not supported."); - return SFS_ERROR; -} - -/******************************************************************************/ -/* Private: E m s g */ -/******************************************************************************/ - -int XrdSsiSfs::Emsg(const char *pfx, // Message prefix value - XrdOucErrInfo &einfo, // Place to put text & error code - int ecode, // The error code - const char *op, // Operation being performed - const char *target) // The target (e.g., fname) -{ - char buffer[MAXPATHLEN+80]; - -// Format the error message -// - XrdOucERoute::Format(buffer, sizeof(buffer), ecode, op, target); - -// Print it out if debugging is enabled -// -#ifndef NODEBUG - Log.Emsg(pfx, einfo.getErrUser(), buffer); -#endif - -// Place the error message in the error object and return -// - einfo.setErrInfo(ecode, buffer); - return SFS_ERROR; -} - -/******************************************************************************/ -/* E n v I n f o */ -/******************************************************************************/ - -void XrdSsiSfs::EnvInfo(XrdOucEnv *envP) -{ - if (!envP) Log.Emsg("EnvInfo", "No environmental information passed!"); - if (!envP || !Config->Configure(envP)) abort(); -} - - -/******************************************************************************/ -/* e x i s t s */ -/******************************************************************************/ - -int XrdSsiSfs::exists(const char *path, // In - XrdSfsFileExistence &file_exists, // Out - XrdOucErrInfo &einfo, // Out - const XrdSecEntity *client, // In - const char *info) // In -{ -// Reroute this request if we can -// - if (fsChk) - {if (FSPath.Find(path)) - return theFS->exists(path, file_exists, einfo, client, info); - einfo.setErrInfo(ENOTSUP, "exists is not supported for given path."); - } else einfo.setErrInfo(ENOTSUP, "exists is not supported."); - return SFS_ERROR; -} - -/******************************************************************************/ -/* f s c t l */ -/******************************************************************************/ - -int XrdSsiSfs::fsctl(const int cmd, - const char *args, - XrdOucErrInfo &einfo, - const XrdSecEntity *client) -/* - Function: Perform filesystem operations: - - Input: cmd - Operation command (currently supported): - SFS_FSCTL_LOCATE - locate resource - args - Command dependent argument: - - Locate: The path whose location is wanted - einfo - Error/Response information structure. - client - Authentication credentials, if any. - - Output: Returns SFS_OK upon success and SFS_ERROR upon failure. -*/ -{ - EPNAME("fsctl"); - const char *tident = einfo.getErrUser(); - - char pbuff[1024], rType[3] = {'S', 'w', 0}; - const char *Resp[2] = {rType, pbuff}; - const char *opq, *Path = Split(args,&opq,pbuff,sizeof(pbuff)); - XrdNetIF::ifType ifType; - int Resp1Len; - -// Do some debugging -// - DEBUG(args); - -// We only process the locate request here. Reroute it if we can otherwise. -// - if ((cmd & SFS_FSCTL_CMD) != SFS_FSCTL_LOCATE) - {if (fsChk) return theFS->fsctl(cmd, args, einfo, client); - einfo.setErrInfo(ENOTSUP, "Requested fsctl operation not supported."); - return SFS_ERROR; - } - -// Preprocess the argument -// - if (*Path == '*') Path++; - else if (cmd & SFS_O_TRUNC) Path = 0; - -// Check if we should reoute this request -// - if (fsChk && Path && FSPath.Find(Path)) - return theFS->fsctl(cmd, args, einfo, client); - -// If we have a path then see if we really have it -// - if (Path) - {if (!Provider) return Emsg(epname, einfo, EHOSTUNREACH, "locate", Path); - else {XrdSsiProvider::rStat rStat = Provider->QueryResource(Path); - if (rStat == XrdSsiProvider::isPresent) rType[0] = 'S'; - else if (rStat == XrdSsiProvider::isPending) rType[0] = 's'; - else return Emsg(epname, einfo, ENOENT, "locate", Path); - } - } - -// Compute interface return options -// - ifType = XrdNetIF::GetIFType((einfo.getUCap() & XrdOucEI::uIPv4) != 0, - (einfo.getUCap() & XrdOucEI::uIPv64) != 0, - (einfo.getUCap() & XrdOucEI::uPrip) != 0); - bool retHN = (cmd & SFS_O_HNAME) != 0; - -// Get our destination -// - if ((Resp1Len = myIF->GetDest(pbuff, sizeof(pbuff), ifType, retHN))) - {einfo.setErrInfo(Resp1Len+3, (const char **)Resp, 2); - return SFS_DATA; - } - -// We failed for some unknown reason -// - return Emsg(epname, einfo, ENETUNREACH, "locate", Path); -} - - -/******************************************************************************/ -/* g e t S t a t s */ -/******************************************************************************/ - -int XrdSsiSfs::getStats(char *buff, int blen) -{ -// Reroute this if we can -// - if (theFS) return theFS->getStats(buff, blen); - return 0; -} - -/******************************************************************************/ -/* g e t V e r s i o n */ -/******************************************************************************/ - -const char *XrdSsiSfs::getVersion() {return XrdVERSION;} - -/******************************************************************************/ -/* m k d i r */ -/******************************************************************************/ - -int XrdSsiSfs::mkdir(const char *path, // In - XrdSfsMode Mode, // In - XrdOucErrInfo &einfo, // Out - const XrdSecEntity *client, // In - const char *info) // In -{ -// Reroute this request if we can -// - if (fsChk) - {if (FSPath.Find(path)) - return theFS->mkdir(path, Mode, einfo, client, info); - einfo.setErrInfo(ENOTSUP, "mkdir is not supported for given path."); - } else einfo.setErrInfo(ENOTSUP, "mkdir is not supported."); - return SFS_ERROR; -} - -/******************************************************************************/ -/* p r e p a r e */ -/******************************************************************************/ - -int XrdSsiSfs::prepare( XrdSfsPrep &pargs, // In - XrdOucErrInfo &out_error, // Out - const XrdSecEntity *client) // In -{ -// Reroute this if we can -// - if (theFS) return theFS->prepare(pargs, out_error, client); - return SFS_OK; -} - -/******************************************************************************/ -/* r e m */ -/******************************************************************************/ - -int XrdSsiSfs::rem(const char *path, // In - XrdOucErrInfo &einfo, // Out - const XrdSecEntity *client, // In - const char *info) // In -{ -// Reroute this request if we can -// - if (fsChk) - {if (FSPath.Find(path)) - return theFS->rem(path, einfo, client, info); - einfo.setErrInfo(ENOTSUP, "rem is not supported for given path."); - } else einfo.setErrInfo(ENOTSUP, "rem is not supported."); - return SFS_ERROR; -} - -/******************************************************************************/ -/* r e m d i r */ -/******************************************************************************/ - -int XrdSsiSfs::remdir(const char *path, // In - XrdOucErrInfo &einfo, // Out - const XrdSecEntity *client, // In - const char *info) // In -{ -// Reroute this request if we can -// - if (fsChk) - {if (FSPath.Find(path)) - return theFS->rem(path, einfo, client, info); - einfo.setErrInfo(ENOTSUP, "remdir is not supported for given path."); - } else einfo.setErrInfo(ENOTSUP, "remdir is not supported."); - return SFS_ERROR; -} - -/******************************************************************************/ -/* r e n a m e */ -/******************************************************************************/ - -int XrdSsiSfs::rename(const char *old_name, // In - const char *new_name, // In - XrdOucErrInfo &einfo, //Out - const XrdSecEntity *client, // In - const char *infoO, // In - const char *infoN) // In -{ -// Reroute this request if we can -// - if (fsChk) - {if (FSPath.Find(old_name)) - return theFS->rename(old_name,new_name,einfo,client,infoO,infoN); - einfo.setErrInfo(ENOTSUP, "rename is not supported for given path."); - } else einfo.setErrInfo(ENOTSUP, "rename is not supported."); - return SFS_ERROR; -} - -/******************************************************************************/ -/* Private: S p l i t */ -/******************************************************************************/ - -const char * XrdSsiSfs::Split(const char *Args, const char **Opq, - char *Path, int Plen) -{ - int xlen; - *Opq = index(Args, '?'); - if (!(*Opq)) return Args; - xlen = (*Opq)-Args; - if (xlen >= Plen) xlen = Plen-1; - strncpy(Path, Args, xlen); - return Path; -} - -/******************************************************************************/ -/* s t a t */ -/******************************************************************************/ - -int XrdSsiSfs::stat(const char *path, // In - struct stat *buf, // Out - XrdOucErrInfo &einfo, // Out - const XrdSecEntity *client, // In - const char *info) // In -{ -// Reroute this request if we can -// - if (fsChk) - {if (FSPath.Find(path)) - return theFS->stat(path, buf, einfo, client, info); - einfo.setErrInfo(ENOTSUP, "stat is not supported for given path."); - } else einfo.setErrInfo(ENOTSUP, "stat is not supported."); - return SFS_ERROR; -} - -/******************************************************************************/ - -int XrdSsiSfs::stat(const char *path, // In - mode_t &mode, // Out - XrdOucErrInfo &einfo, // Out - const XrdSecEntity *client, // In - const char *info) // In -{ -// Reroute this request if we can -// - if (fsChk) - {if (FSPath.Find(path)) - return theFS->stat(path, mode, einfo, client, info); - einfo.setErrInfo(ENOTSUP, "stat is not supported for given path."); - } else einfo.setErrInfo(ENOTSUP, "stat is not supported."); - return SFS_ERROR; -} - -/******************************************************************************/ -/* t r u n c a t e */ -/******************************************************************************/ - -int XrdSsiSfs::truncate(const char *path, // In - XrdSfsFileOffset Size, // In - XrdOucErrInfo &einfo, // Out - const XrdSecEntity *client, // In - const char *info) // In -{ -// Reroute this request if we can -// - if (fsChk) - {if (FSPath.Find(path)) - return theFS->truncate(path, Size, einfo, client, info); - einfo.setErrInfo(ENOTSUP, "truncate is not supported for given path."); - } else einfo.setErrInfo(ENOTSUP, "truncate is not supported."); - return SFS_ERROR; -} diff --git a/src/XrdSsi/XrdSsiSfs.hh b/src/XrdSsi/XrdSsiSfs.hh deleted file mode 100644 index 1dd864d14fc..00000000000 --- a/src/XrdSsi/XrdSsiSfs.hh +++ /dev/null @@ -1,149 +0,0 @@ -#ifndef __SSI_SFS_H__ -#define __SSI_SFS_H__ -/******************************************************************************/ -/* */ -/* X r d S s i S f s . h h */ -/* */ -/* (c) 2013 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -#include "XrdSfs/XrdSfsInterface.hh" - -#include "XrdSsi/XrdSsiDir.hh" -#include "XrdSsi/XrdSsiFile.hh" - -struct XrdVersionInfo; - -class XrdOucEnv; -class XrdSecEntity; - -class XrdSsiSfs : public XrdSfsFileSystem -{ -friend class XrdSsiFile; - -public: - -// Object allocation -// - XrdSfsDirectory *newDir(char *user=0, int MonID=0) - {return new XrdSsiDir(user, MonID);} - - XrdSfsFile *newFile(char *user=0,int MonID=0) - {return new XrdSsiFile(user, MonID);} - -// Other functions -// - int chksum( csFunc Func, - const char *csName, - const char *path, - XrdOucErrInfo &eInfo, - const XrdSecEntity *client = 0, - const char *opaque = 0); - - int chmod(const char *Name, - XrdSfsMode Mode, - XrdOucErrInfo &eInfo, - const XrdSecEntity *client, - const char *opaque = 0); - - void EnvInfo(XrdOucEnv *envP); - - int exists(const char *fileName, - XrdSfsFileExistence &exists_flag, - XrdOucErrInfo &eInfo, - const XrdSecEntity *client, - const char *opaque = 0); - - int fsctl(const int cmd, - const char *args, - XrdOucErrInfo &eInfo, - const XrdSecEntity *client); - - int getStats(char *buff, int blen); - -const char *getVersion(); - - int mkdir(const char *dirName, - XrdSfsMode Mode, - XrdOucErrInfo &eInfo, - const XrdSecEntity *client, - const char *opaque = 0); - - int prepare( XrdSfsPrep &pargs, - XrdOucErrInfo &eInfo, - const XrdSecEntity *client = 0); - - int rem(const char *path, - XrdOucErrInfo &eInfo, - const XrdSecEntity *client, - const char *info = 0); - - int remdir(const char *dirName, - XrdOucErrInfo &eInfo, - const XrdSecEntity *client, - const char *info = 0); - - int rename(const char *oldFileName, - const char *newFileName, - XrdOucErrInfo &eInfo, - const XrdSecEntity *client, - const char *infoO = 0, - const char *infoN = 0); - - int stat(const char *Name, - struct stat *buf, - XrdOucErrInfo &eInfo, - const XrdSecEntity *client, - const char *opaque = 0); - - int stat(const char *Name, - mode_t &mode, - XrdOucErrInfo &eInfo, - const XrdSecEntity *client, - const char *opaque = 0); - - int truncate(const char *Name, - XrdSfsFileOffset fileOffset, - XrdOucErrInfo &eInfo, - const XrdSecEntity *client = 0, - const char *opaque = 0); - -// Management functions -// -static void setMax(int mVal) {freeMax = mVal;} - - XrdSsiSfs() {} -virtual ~XrdSsiSfs() {} // Too complicate to delete :-) - -private: -static int freeMax; - -int Emsg(const char *pfx, XrdOucErrInfo &einfo, int ecode, - const char *op, const char *target); -const char *Split(const char *Args, const char **Opq, char *Path, int Plen); -}; -#endif diff --git a/src/XrdSsi/XrdSsiSfsConfig.cc b/src/XrdSsi/XrdSsiSfsConfig.cc deleted file mode 100644 index c051e1dcf0e..00000000000 --- a/src/XrdSsi/XrdSsiSfsConfig.cc +++ /dev/null @@ -1,738 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S s i S f s C o n f i g . c c */ -/* */ -/* (c) 2013 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Deprtment of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "XrdCms/XrdCmsRole.hh" - -#include "XrdOuc/XrdOuca2x.hh" -#include "XrdOuc/XrdOucBuffer.hh" -#include "XrdOuc/XrdOucStream.hh" -#include "XrdOuc/XrdOucUtils.hh" - -#include "XrdSsi/XrdSsiSfs.hh" -#include "XrdSsi/XrdSsiFileReq.hh" -#include "XrdSsi/XrdSsiFileSess.hh" -#include "XrdSsi/XrdSsiLogger.hh" -#include "XrdSsi/XrdSsiProvider.hh" -#include "XrdSsi/XrdSsiSfsConfig.hh" -#include "XrdSsi/XrdSsiTrace.hh" - -#include "XrdSsi/XrdSsiCms.hh" - -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdSys/XrdSysLogger.hh" -#include "XrdSys/XrdSysPlatform.hh" -#include "XrdSys/XrdSysPlugin.hh" -#include "XrdSys/XrdSysPthread.hh" - -#include "XrdOuc/XrdOucEnv.hh" -#include "XrdOuc/XrdOucERoute.hh" -#include "XrdOuc/XrdOucPList.hh" - -#include "XrdNet/XrdNetIF.hh" - -#include "XrdVersion.hh" - -#ifdef AIX -#include -#endif - -/******************************************************************************/ -/* E x t e r n s */ -/******************************************************************************/ - -class XrdScheduler; - -namespace XrdSsi -{ - XrdSsiCms *SsiCms = 0; - - XrdScheduler *Sched = 0; - - XrdOucBuffPool *BuffPool = 0; - - XrdOucPListAnchor FSPath; - - XrdNetIF *myIF = 0; - -extern XrdSsiProvider *Provider; - - XrdSsiService *Service = 0; - - XrdSsiLogger SsiLogger; - - int respWT = 0x7fffffff; - - bool fsChk = false; - - bool detReqOK = false; - -extern XrdSfsFileSystem *theFS; - -extern XrdSysError Log; -}; - -using namespace XrdSsi; - -/******************************************************************************/ -/* C o n s t r u c t o r */ -/******************************************************************************/ - -XrdSsiSfsConfig::XrdSsiSfsConfig(bool iscms) -{ - static XrdVERSIONINFODEF(myVer, ssi, XrdVNUMBER, XrdVERSION); - char *bp; - -// Establish defaults -// - ConfigFN = 0; - CmsLib = 0; - CmsParms = 0; - SsiCms = 0; - SvcLib = 0; - SvcParms = 0; - myRole = 0; - maxRSZ = 2097152; - respWT = 0x7fffffff; - isServer = true; - isCms = iscms; - myHost = getenv("XRDHOST"); - myProg = getenv("XRDPROG"); - myInsName = XrdOucUtils::InstName(1); - myVersion = &myVer; - myPort = (bp = getenv("XRDPORT")) ? strtol(bp, (char **)NULL, 10) : 0; -} - -/******************************************************************************/ -/* D e s t r u c t o r */ -/******************************************************************************/ - -XrdSsiSfsConfig::~XrdSsiSfsConfig() -{ - if (ConfigFN) free(ConfigFN); - if (CmsLib) free(CmsLib); - if (CmsParms) free(CmsParms); - if (SvcLib) free(SvcLib); - if (SvcParms) free(SvcParms); -} - -/******************************************************************************/ -/* C o n f i g u r e */ -/******************************************************************************/ - -bool XrdSsiSfsConfig::Configure(const char *cFN) -{ - char *var; - const char *tmp; - int cfgFD, retc, NoGo = 0; - XrdOucEnv myEnv; - XrdOucStream cStrm(&Log, getenv("XRDINSTANCE"), &myEnv, "=====> "); - -// Print warm-up message -// - Log.Say("++++++ ssi phase 1 initialization started."); - -// Preset all variables with common defaults -// - if (getenv("XRDDEBUG")) Trace.What = TRACESSI_ALL | TRACESSI_Debug; - -// If there is no config file, return with an error. -// - if( !cFN || !*cFN) - {Log.Emsg("Config", "Configuration file not specified."); - return false; - } - -// Try to open the configuration file. -// - ConfigFN = strdup(cFN); - if ( (cfgFD = open(cFN, O_RDONLY, 0)) < 0) - {Log.Emsg("Config", errno, "open config file", cFN); - return false; - } - cStrm.Attach(cfgFD); - -// Now start reading records until eof. -// - cFile = &cStrm; - while((var = cFile->GetMyFirstWord())) - {if (!strncmp(var, "ssi.", 4) - || !strcmp(var, "all.role")) - {if (ConfigXeq(var+4)) {cFile->Echo(); NoGo=1;}} - } - -// Now check if any errors occured during file i/o -// - if ((retc = cStrm.LastError())) - NoGo = Log.Emsg("Config", -retc, "read config file", cFN); - cStrm.Close(); - -// Make sure we are configured as a server -// - if (!isServer) - {Log.Emsg("Config", "ssi only supports server roles but role is not " - "defined as 'server'."); - return false; - } - -// Configure filesystem callout as needed -// - fsChk = FSPath.NotEmpty(); - if (isServer && !theFS) fsChk = false; - -// All done -// - tmp = (NoGo ? " failed." : " completed."); - Log.Say("------ ssi phase 1 initialization", tmp); - return !NoGo; -} - -/******************************************************************************/ - -bool XrdSsiSfsConfig::Configure(XrdOucEnv *envP) -{ - static char theSSI[] = {'s', 's', 'i', 0}; - static char **myArgv = 0, *dfltArgv[] = {0, 0}; - XrdOucEnv *xrdEnvP; - const char *tmp; - int myArgc = 0, NoGo; - -// Print warm-up message -// - Log.Say("++++++ ssi phase 2 initialization started."); - -// Now find the scheduler -// - if (envP && !(Sched = (XrdScheduler *)envP->GetPtr("XrdScheduler*"))) - {Log.Emsg("Config", "Scheduler pointer is undefined!"); - NoGo = 1; - } else NoGo = 0; - -// Find our arguments, if any -// - if ((xrdEnvP = (XrdOucEnv *)envP->GetPtr("xrdEnv*")) - && (myArgv = (char **)xrdEnvP->GetPtr("xrdssi.argv**"))) - myArgc = xrdEnvP->GetInt("xrdssi.argc"); - -// Verify that we have some and substitute if not -// - if (!myArgv || myArgc < 1) - {if (!(dfltArgv[0] = (char *)xrdEnvP->GetPtr("argv[0]"))) - dfltArgv[0] = theSSI; - myArgv = dfltArgv; - myArgc = 1; - } - -// Establish the network interface that the caller must provide -// - if (!isCms && (!envP || !(myIF = (XrdNetIF *)envP->GetPtr("XrdNetIF*")))) - {Log.Emsg("Finder", "Network i/f undefined; unable to self-locate."); - NoGo = 1; - } - -// Now configure management functions and the cms if we are not the cms -// - if (!NoGo && !isCms && envP) - {if (ConfigObj() || ConfigCms(envP)) NoGo = 1;} - -// Now configure the server -// - if (!NoGo && ConfigSvc(myArgv, myArgc)) NoGo = 1; - -// All done -// - tmp = (NoGo ? " failed." : " completed."); - Log.Say("------ ssi phase 2 initialization", tmp); - return !NoGo; -} - -/******************************************************************************/ -/* C o n f i g C m s */ -/******************************************************************************/ - -class XrdOss; - -int XrdSsiSfsConfig::ConfigCms(XrdOucEnv *envP) -{ - static const int cmsOpt = XrdCms::IsTarget; - XrdCmsClient *cmsP, *(*CmsGC)(XrdSysLogger *, int, int, XrdOss *); - XrdSysLogger *myLogger = Log.logger(); - -// Check if we are configuring a simple standalone server -// - if (!myRole) - {myRole = strdup("standalone"); - Log.Say("Config Configuring standalone server."); - SsiCms = new XrdSsiCms; - return 0; - } - -// If a cmslib was specified then create a plugin object and get the client. -// Otherwise, simply get the default client. -// - if (CmsLib) - {XrdSysPlugin myLib(&Log, CmsLib, "cmslib", myVersion); - CmsGC = (XrdCmsClient *(*)(XrdSysLogger *, int, int, XrdOss *)) - (myLib.getPlugin("XrdCmsGetClient")); - if (!CmsGC) return 1; - myLib.Persist(); - cmsP = CmsGC(myLogger, cmsOpt, myPort, 0); - } - else cmsP = XrdCms::GetDefaultClient(myLogger, cmsOpt, myPort); - -// If we have a client object onfigure it -// - if (!cmsP || !cmsP->Configure(ConfigFN, CmsParms, envP)) - {delete cmsP; - Log.Emsg("Config", "Unable to create cluster object."); - return 1; - } - -// Create the cluster onject and return -// - SsiCms = new XrdSsiCms(cmsP); - return 0; -} - -/******************************************************************************/ -/* C o n f i g O b j */ -/******************************************************************************/ - -int XrdSsiSfsConfig::ConfigObj() -{ - static const int minRSZ = 8192; - -// Allocate a buffer pool -// - if (maxRSZ < minRSZ) maxRSZ = minRSZ; - BuffPool = new XrdOucBuffPool(minRSZ, maxRSZ); - XrdSsiFileSess::SetMaxSz(maxRSZ); - return 0; -} - -/******************************************************************************/ -/* C o n f i g S v c */ -/******************************************************************************/ - -int XrdSsiSfsConfig::ConfigSvc(char **myArgv, int myArgc) -{ - XrdSsiErrInfo eInfo; - XrdSysPlugin *myLib; - XrdSsiProvider **theProvider; - const char *pName = (isCms ? "XrdSsiProviderLookup" - : "XrdSsiProviderServer"); - -// Make sure a library was specified -// - if (!SvcLib) - {Log.Emsg("Config", "svclib not specified; provider cannot be loaded."); - return 1; - } - -// Create a plugin object -// - if (!(myLib = new XrdSysPlugin(&Log, SvcLib, "svclib", myVersion))) - return 1; - -// Now get the entry point of the object creator -// - theProvider = (XrdSsiProvider **)(myLib->getPlugin(pName)); - if (!theProvider) return 1; - Provider = *theProvider; - -// Persist the library -// - myLib->Persist(); delete myLib; - -// Initialize the provider -// - if (!(Provider->Init(&SsiLogger, (XrdSsiCluster *)SsiCms, - std::string(ConfigFN), - std::string(SvcParms ? SvcParms : ""), - myArgc, myArgv))) - {Log.Emsg("Config", "Provider initialization failed."); - return 1; - } - -// If we are the cms then we are done. -// - if (isCms) return 0; - -// Otherwise we need to get the service object (we get only one) -// - if (!(Service = Provider->GetService(eInfo, ""))) - {const char *eText = eInfo.Get().c_str(); - Log.Emsg("Config", "Unable to obtain server-side service object;", - (eText ? eText : "reason unknown.")); - } - return Service == 0; -} - -/******************************************************************************/ -/* C o n f i g X e q */ -/******************************************************************************/ - -int XrdSsiSfsConfig::ConfigXeq(char *var) -{ - -// Now assign the appropriate global variable -// - if (!strcmp("cmslib", var)) return Xlib("cmslib", &CmsLib, &CmsParms); - if (!strcmp("svclib", var)) return Xlib("svclib", &SvcLib, &SvcParms); - if (!strcmp("fspath", var)) return Xfsp(); - if (!strcmp("loglib", var)){char *theLib=0, *theParms=0; - int rc=Xlib("loglib", &theLib, &theParms); - if (theLib) free(theLib); - if (theParms) free(theParms); - return rc; - } - if (!strcmp("opts", var)) return Xopts(); - if (!strcmp("role", var)) return Xrole(); - if (!strcmp("trace", var)) return Xtrace(); - -// No match found, complain. -// - Log.Say("Config warning: ignoring unknown directive '",var,"'."); - cFile->Echo(); - return 0; -} - -/******************************************************************************/ -/* x L i b */ -/******************************************************************************/ - -/* Function: Xlib - - Purpose: To parse the directive: xxxlib [] - - the path of the library to be used. - optional parms to be passed - - Output: 0 upon success or !0 upon failure. -*/ - -int XrdSsiSfsConfig::Xlib(const char *lName, char **lPath, char **lParm) -{ - char *val, parms[2048]; - -// Get the path and parms -// - if (!(val = cFile->GetWord()) || !val[0]) - {Log.Emsg("Config", lName, "not specified"); return 1;} - -// Set the CmsLib pointer -// - if (*lPath) free(*lPath); - *lPath = strdup(val); - -// Combine the path and parameters -// - *parms = 0; - if (!cFile->GetRest(parms, sizeof(parms))) - {Log.Emsg("Config", lName, "parameters too long"); return 1;} - -// Record the parameters, if any -// - if (*lParm) free(*lParm); - *lParm = (*parms ? strdup(parms) : 0); - return 0; -} - -/******************************************************************************/ -/* x f s p */ -/******************************************************************************/ - -/* Function: xfsp - - Purpose: To parse the directive: fspath - - the path that is a file system path. - - Output: 0 upon success or !0 upon failure. -*/ - -int XrdSsiSfsConfig::Xfsp() -{ - XrdOucPList *plp; - char *val, pbuff[1024]; - -// Get the path -// - val = cFile->GetWord(); - if (!val || !val[0]) - {Log.Emsg("Config", "fspath path not specified"); return 1;} - strlcpy(pbuff, val, sizeof(pbuff)); - -// Add path to configuration -// - if (!(plp = FSPath.Match(pbuff))) - {plp = new XrdOucPList(pbuff,1); - FSPath.Insert(plp); - } - return 0; -} - -/******************************************************************************/ -/* X o p t s */ -/******************************************************************************/ - - -/* Function: Xopts - - Purpose: To parse directive: opts [files ] [requests ] [respwt ] - [maxrsz ] [authdns] [detreqok] - - authdns always supply client's resolved host name. - detreqok allow detached requests. - files the maximum number of file objects to hold in reserve. - maxrsz the maximum size of a request. - requests the maximum number of requests objects to hold in reserve. - respwait the number of seconds to place client in response wait. - - Output: 0 upon success or 1 upon failure. -*/ - -int XrdSsiSfsConfig::Xopts() -{ - static const int noArg = 1; - static const int isNum = 2; - static const int isSz = 3; - static const int isTM = 4; - char *val, oBuff[256]; - long long ppp, rMax = -1, rObj = -1, fAut = -1, fDet = -1, fRwt = -1; - int i, xtm; - - struct optsopts {const char *opname; long long *oploc; int maxv; int aOpt;} - opopts[] = - { - {"authinfo", &fAut, 2, noArg}, - {"detreqok", &fDet, 2, noArg}, - {"maxrsz", &rMax, 16*1024*1024, isSz}, - {"requests", &rObj, 64*1024, isNum}, - {"respwt", &fRwt, 0x7fffffffLL, isTM} - }; - int numopts = sizeof(opopts)/sizeof(struct optsopts); - - if (!(val = cFile->GetWord())) - {Log.Emsg("Config", "opts option not specified"); return 1;} - - while (val) - {for (i = 0; i < numopts; i++) - if (!strcmp(val, opopts[i].opname)) - {if (opopts[i].aOpt == noArg) - {*opopts[i].oploc = 1; - break; - } - if (!(val = cFile->GetWord())) - {Log.Emsg("Config", "opts ", opopts[i].opname, - "argument not specified."); - return 1; - } - snprintf(oBuff,sizeof(oBuff),"%s opts value",opopts[i].opname); - if (opopts[i].aOpt == isSz) - {if (XrdOuca2x::a2sz(Log, oBuff, val, &ppp, - 0, opopts[i].maxv)) return 1; - } - else if (opopts[i].aOpt == isTM) - {if (XrdOuca2x::a2tm(Log, oBuff, val, &xtm, - 0, opopts[i].maxv)) return 1; - ppp = xtm; - } - else if (XrdOuca2x::a2ll(Log, oBuff, val, &ppp, - 0, opopts[i].maxv)) return 1; - *opopts[i].oploc = ppp; - break; - } - if (i >= numopts) - Log.Say("Config warning: ignoring invalid opts option '",val,"'."); - val = cFile->GetWord(); - } - -// Set the values that were specified -// - if (fAut >= 0) XrdSsiFileSess::SetAuthDNS(); - if (fAut >= 0) detReqOK = true; - if (rMax >= 0) maxRSZ = static_cast(rMax); - if (rObj >= 0) XrdSsiFileReq::SetMax(static_cast(rObj)); - if (fRwt >= 0) respWT = fRwt; - - return 0; -} - -/******************************************************************************/ -/* x r o l e */ -/******************************************************************************/ - -/* Function: Xrole - Purpose: Parse: role { {[meta] | [proxy]} manager - | proxy | [proxy] server - | [proxy] supervisor - } [if ...] - - manager xrootd: act as a manager (redirecting server). Prefixes: - meta - connect only to manager meta's - proxy - ignored - cmsd: accept server subscribes and redirectors. Prefix - modifiers do the following: - meta - No other managers apply - proxy - manage a cluster of proxy servers - - proxy xrootd: act as a server but supply data from another - server. No local cmsd is present or required. - cmsd: Generates an error as this makes no sense. - - server xrootd: act as a server (supply local data). Prefix - modifications do the following: - proxy - server is part of a cluster. A local - cmsd is required. - cmsd: subscribe to a manager, possibly as a proxy. - - supervisor xrootd: equivalent to manager. - cmsd: equivalent to manager but also subscribe to a - manager. When proxy is specified, subscribe as - a proxy and only accept proxy servers. - - - if Apply the manager directive if "if" is true. See - XrdOucUtils:doIf() for "if" syntax. - - Output: 0 upon success or !0 upon failure. -*/ - -int XrdSsiSfsConfig::Xrole() -{ - XrdCmsRole::RoleID roleID; - char *val, *Tok1, *Tok2; - int rc; - -// Get the first token -// - if (!(val = cFile->GetWord()) || !strcmp(val, "if")) - {Log.Emsg("Config", "role not specified"); return 1;} - Tok1 = strdup(val); - -// Get second token which might be an "if" -// - if ((val = cFile->GetWord()) && strcmp(val, "if")) - {Tok2 = strdup(val); - val = cFile->GetWord(); - } else Tok2 = 0; - -// Process the if at this point -// - if (val && !strcmp("if", val)) - if ((rc = XrdOucUtils::doIf(&Log,*cFile,"role directive", - myHost,myInsName,myProg)) <= 0) - {free(Tok1); if (Tok2) free(Tok2); - if (!rc) cFile->noEcho(); - return (rc < 0); - } - -// Convert the role names to a role ID, if possible -// - roleID = XrdCmsRole::Convert(Tok1, Tok2); - -// Validate the role -// - rc = 0; - if (roleID == XrdCmsRole::noRole) - {Log.Emsg("Config", "invalid role -", Tok1, Tok2); rc = 1;} - -// Release storage and return if an error occured -// - free(Tok1); - if (Tok2) free(Tok2); - if (rc) return rc; - -// Fill out information -// - if (myRole) free(myRole); - myRole = strdup(XrdCmsRole::Name(roleID)); - isServer = (roleID == XrdCmsRole::Server); - return 0; -} - -/******************************************************************************/ -/* x t r a c e */ -/******************************************************************************/ - -/* Function: Xtrace - - Purpose: To parse the directive: trace - - the blank separated list of events to trace. Trace - directives are cummalative. - - Output: 0 upon success or !0 upon failure. -*/ - -int XrdSsiSfsConfig::Xtrace() -{ - static struct traceopts {const char *opname; int opval;} tropts[] = - { - {"all", TRACESSI_ALL}, - {"debug", TRACESSI_Debug} - }; - int i, neg, trval = 0, numopts = sizeof(tropts)/sizeof(struct traceopts); - char *val; - - if (!(val = cFile->GetWord())) - {Log.Emsg("Config", "trace option not specified"); return 1;} - while (val) - {if (!strcmp(val, "off")) trval = 0; - else {if ((neg = (val[0] == '-' && val[1]))) val++; - for (i = 0; i < numopts; i++) - {if (!strcmp(val, tropts[i].opname)) - {if (neg) trval &= ~tropts[i].opval; - else trval |= tropts[i].opval; - break; - } - } - if (i >= numopts) - Log.Say("Config warning: ignoring invalid trace option '",val,"'."); - } - val = cFile->GetWord(); - } - Trace.What = trval; - -// All done -// - return 0; -} diff --git a/src/XrdSsi/XrdSsiSfsConfig.hh b/src/XrdSsi/XrdSsiSfsConfig.hh deleted file mode 100644 index aa0a36971e2..00000000000 --- a/src/XrdSsi/XrdSsiSfsConfig.hh +++ /dev/null @@ -1,81 +0,0 @@ -#ifndef __SSISFS_CONFIG_HH__ -#define __SSISFS_CONFIG_HH__ -/******************************************************************************/ -/* */ -/* X r d S s i S f s C o n f i g . h h */ -/* */ -/* (c) 2013 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Deprtment of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -class XrdOucEnv; -class XrdOucStream; -class XrdSsiCluster; -class XrdSsiServer; -class XrdVersionInfo; - -class XrdSsiSfsConfig -{ -public: - -XrdVersionInfo *myVersion; -const char *myHost; -const char *myProg; -const char *myInsName; -char *myRole; -XrdSsiCluster *SsiCms; -int myPort; -bool isServer; -bool isCms; - -bool Configure(const char *cFN); - -bool Configure(XrdOucEnv *envP); - - XrdSsiSfsConfig(bool iscms=false); - - ~XrdSsiSfsConfig(); - -private: - -XrdOucStream *cFile; -char *ConfigFN; // ->Configuration filename -char *CmsLib; // ->Cms Library -char *CmsParms; // ->Cms Library Parameters -char *SvcLib; // ->Svc Library -char *SvcParms; // ->Svc Library Parameters -int maxRSZ; // Maximum request size -int roleID; - -int ConfigCms(XrdOucEnv *envP); -int ConfigObj(); -int ConfigSvc(char **myArgv, int myArgc); -int ConfigXeq(char *var); -int Xlib(const char *lName, char **lPath, char **lParm); -int Xfsp(); -int Xopts(); -int Xrole(); -int Xtrace(); -}; -#endif diff --git a/src/XrdSsi/XrdSsiShMam.cc b/src/XrdSsi/XrdSsiShMam.cc deleted file mode 100644 index 013dc85551c..00000000000 --- a/src/XrdSsi/XrdSsiShMam.cc +++ /dev/null @@ -1,1405 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S s i S h M a m . c c */ -/* */ -/* (c) 2015 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "XrdSsi/XrdSsiShMam.hh" - -using namespace std; - -/******************************************************************************/ -/* S h a r e d M e m o r y I n f o r m a t i o n S t r u c t u r e */ -/******************************************************************************/ - -namespace -{ -struct ShmInfo - {int verNum; // Always 1st fout bytes - int index; // Offset of index - int slots; // Number of slots in index - int slotsUsed; // Number of entries in use - int itemCount; // Number of items in this map - int typeSz; // Size of the data payload - int itemSz; // Size of each item - int keyPos; // Position of key in item - int freeItem; // Offset to item on the free list - int freeCount; // Number of items on the free list - int infoSz; // Size of header also original lowFree - int lowFree; // Offset to low memory that is free - int highUse; // Offset to high memory that is used - char reUse; // When non-zero items can be reused (r/o locking) - char multW; // When non-zero multiple writers are allowed - char rsvd1; - char rsvd2; - int maxKeys; // Maximum number of keys - int maxKeySz; // Longest allowed key (not including null byte) - int hashID; // The name of the hash - char typeID[64]; // Name of the type stored here - char myName[64]; // Name of the implementation - }; -#define SHMINFO(x) ((ShmInfo *)shmBase)->x - -#define SHMADDR(type, offs) (type *)(shmBase + offs) - -#define SHMOFFS(addr) (char *)addr - shmBase - -#define ITEM_KEY(x) (char *)x + sizeof(MemItem) + keyPos - -#define ITEM_VAL(x) (char *)x + sizeof(MemItem) - -#define ITEM_VOF(x) (char *)x + sizeof(MemItem) - shmBase - -int PageMask = ~(sysconf(_SC_PAGESIZE)-1); -int PageSize = sysconf(_SC_PAGESIZE); -} - -/******************************************************************************/ -/* L o c a l C l a s s e s */ -/******************************************************************************/ - -namespace -{ -class EnumJar -{public: -char *buff; -int fd; -int iNum; - EnumJar(int xfd, int bsz) - : buff(new char[bsz]), fd(xfd), iNum(0) {} - ~EnumJar() {if (fd >= 0) close(fd); - if (buff) delete [] buff; - } -}; - -class FileHelper -{ -public: -bool autoClose; - - FileHelper(XrdSsiShMam *mp) : autoClose(false), shMamP(mp) {} - ~FileHelper() {if (autoClose) - {int rc = errno; shMamP->Detach(); errno = rc;} - } -private: -XrdSsiShMam *shMamP; -}; - -class MutexHelper -{ -public: -pthread_rwlock_t *mtxP; - - MutexHelper(pthread_rwlock_t *mtx, XrdSsiShMam::LockType isrw) - : mtxP(mtx) - {if (mtx) - {if (isrw) pthread_rwlock_wrlock(mtx); - else pthread_rwlock_rdlock(mtx); - } - } - - ~MutexHelper() {if (mtxP) pthread_rwlock_unlock(mtxP);} -}; -} - -/******************************************************************************/ -/* F i l e D e s c r i p t o r H a n d l i n g */ -/******************************************************************************/ - -namespace -{ -#if defined(__linux__) && defined(O_CLOEXEC) -inline int ShMam_Dup(int oldfd) - {return fcntl(oldfd, F_DUPFD_CLOEXEC, 0);} - -inline int ShMam_Open(const char *path, int flags) - {return open(path, flags|O_CLOEXEC);} - -inline int ShMam_Open(const char *path, int flags, mode_t mode) - {return open(path, flags|O_CLOEXEC, mode);} -#else -inline int ShMam_Dup(int oldfd) - {int newfd = dup(oldfd); - if (newfd >= 0) fcntl(newfd, F_SETFD, FD_CLOEXEC); - return newfd; - } - -inline int ShMam_Open(const char *path, int flags) - {int newfd = open(path, flags); - if (newfd >= 0) fcntl(newfd, F_SETFD, FD_CLOEXEC); - return newfd; - } - -inline int ShMam_Open(const char *path, int flags, mode_t mode) - {int newfd = open(path, flags, mode); - if (newfd >= 0) fcntl(newfd, F_SETFD, FD_CLOEXEC); - return newfd; - } -#endif - -inline bool ShMam_Flush(int fd) -{ -#if _POSIX_SYNCHRONIZED_IO > 0 - return fdatasync(fd) == 0; -#else - return fsync(fd) == 0; -#endif -} -/* -inline bool ShMam_Flush(void *memP, int sOpt) -{ - if (msync((void *)((uintptr_t)memP & PageMask), PageSize, sOpt)) - return true; - cerr <<"ShMam: msync() failed; " < maxKLen) {errno = ENAMETOOLONG; return false;} - -// Check if we need to remap this memory (atomic tests is not needed here). -// We need to do this prior to file locking as the requirements may change. -// - if (verNum != SHMINFO(verNum)) ReMap(RWLock); - -// Lock the file if we have multiple writers or recycling items -// - if (lockRW && !lockInfo.FLock()) return false; - -// First try to find the item -// - hEnt = Find(theItem, prvItem, key, hash); - -// If we found it then see if we can replace it. If so and we can reuse the -// the item, then just update the data portion. Otherwise, we need to get a -// new item and replace the existing item. -// - if (hEnt) - {if (olddata) memcpy(olddata, ITEM_VAL(theItem), shmTypeSz); - if (!replace) {errno = EEXIST; return false;} - if (reUse) - {memcpy(ITEM_VAL(theItem), newdata, shmTypeSz); - if (syncOn) Updated(ITEM_VOF(theItem), shmTypeSz); - errno = EEXIST; - return true; - } - retEno = EEXIST; - } - -// Get a new item -// - if (!(newItem = NewItem())) {errno = ENOSPC; return false;} - -// Construct the new item -// - newItem->hash = hash; - memcpy(ITEM_VAL(newItem), newdata, shmTypeSz); - strcpy(ITEM_KEY(newItem), key); - -// If we are replacing an item then We need to bridge over the item we are -// replacing in a way that doesn't make the item disappear for other readers. -// Otherwise, we can patch in the new item either on the last item in the chain -// or directly off the table. Note that releasing the lock creates a memory -// fence. To understand why this this works consider the relationship between: -// hEnt prvItem The state of the table -// 0 0 Not found because index table slot is zero -// 0 !0 Not found in a chain of items, prvItem is the last one -// !0 0 Was found and is the first or only item in the chain -// !0 !0 Was found and is in the middle or end of the chain -// -// - if (hEnt) Atomic_SET(newItem->next, theItem->next); // Atomic - else {hEnt = (unsigned int)hash % shmSlots; - if (hEnt == 0) hEnt = 1; - SHMINFO(itemCount)++; - } - - iOff = SHMOFFS(newItem); - if (prvItem) Atomic_SET_STRICT(prvItem->next, iOff); // Atomic - else {SHMINFO(slotsUsed)++; - Atomic_SET_STRICT(shmIndex[hEnt],iOff); // Atomic - if (syncOn) Updated(SHMOFFS(&shmIndex[hEnt])); - } - -// Indicate which things we changed if we have syncing -// - if (syncOn) - {Updated(0); - Updated(SHMOFFS(newItem)); - if (prvItem) Updated(SHMOFFS(prvItem)); - } - -// All done, return result -// - errno = retEno; - return true; -} - -/******************************************************************************/ -/* A t t a c h */ -/******************************************************************************/ - -bool XrdSsiShMam::Attach(int tout, bool isrw) -{ - FileHelper fileHelp(this); - XLockHelper lockInfo(this, (isrw ? RWLock : ROLock)); - struct stat Stat1, Stat2; - int mMode, oMode; - union {int *intP; Atomic(int) *antP;} xntP; - -// Compute open and mmap options -// - if (isrw) - {oMode = O_RDWR; - mMode = PROT_READ|PROT_WRITE; - isRW = true; - } else { - oMode = O_RDONLY; - mMode = PROT_READ; - isRW = false; - } - -// Attempt to open the file -// - timeOut = tout; - if (tout < 0) tout = 0x7fffffff; - while((shmFD = ShMam_Open(shmPath, oMode)) < 0 && tout >= 0) - {if (errno != ENOENT) return false; - if (!tout) break; - Snooze(3); - tout -= 3; - } - -// Test if we timed out -// - if (tout <= 0) {errno = ETIMEDOUT; return false;} - fileHelp.autoClose = true; - -// Lock this file as we don't want it changing on us for now -// - if (!lockInfo.FLock()) return false; - -// Get the stat information for this file -// - if (fstat(shmFD, &Stat1)) return false; - -// The file is open, try to memory map it -// - shmBase = (char *)mmap(0, Stat1.st_size, mMode, MAP_SHARED, shmFD, 0); - if (shmBase == MAP_FAILED) return false; - shmSize = Stat1.st_size; - -// Make sure we have a valid hash name -// - if (!shmHash) memcpy(&shmHash, "c32 ", sizeof(int)); - -// Verify tha the objects in this mapping are compatible with this object -// - if (SHMINFO(typeSz) != shmTypeSz || strcmp(shmType, SHMINFO(typeID)) - || strcmp(shmImpl, SHMINFO(myName)) || shmHash != SHMINFO(hashID)) - {errno = EDOM; return false;} - -// Copy out the information we can use locally -// - verNum = SHMINFO(verNum); - keyPos = SHMINFO(keyPos); - maxKLen = SHMINFO(maxKeySz); - xntP.intP = SHMADDR(int, SHMINFO(index)); shmIndex = xntP.antP; - shmSlots = SHMINFO(slots); - shmItemSz = SHMINFO(itemSz); - shmInfoSz = SHMINFO(infoSz); - -// Now, there is a loophole here as the file could have been exported while -// we were trying to attach it. If this happened, the inode would change. -// We test for this now. If it changed, tell the caller to try again. -// - if (stat(shmPath, &Stat2) - || Stat1.st_dev != Stat2.st_dev || Stat1.st_ino != Stat2.st_ino) - {errno = EAGAIN; return false;} - accMode = Stat2.st_mode & (S_IRWXU|S_IRWXG|S_IRWXO); - -// Set locking based on how the table was created -// - SetLocking(isrw); - fileHelp.autoClose = false; - return true; -} - -/******************************************************************************/ -/* C r e a t e */ -/******************************************************************************/ - -bool XrdSsiShMam::Create(XrdSsiShMat::CRZParms &parms) -{ - static const int minInfoSz = 256; - static const int okMode = S_IRWXU|S_IRWXG|S_IROTH; - static const int crMode = S_IRWXU|S_IRWXG|S_IROTH; - FileHelper fileHelp(this); - ShmInfo theInfo; - int n, maxEnts, totSz, indexSz; - union {int *intP; Atomic(int) *antP;} xntP; - -// Validate parameter list values -// - if (parms.indexSz <= 0 || parms.maxKeys <= 0 || parms.maxKLen <= 0) - {errno = EINVAL; return false;} - if (parms.mode & ~okMode || ((parms.mode & crMode) != crMode)) - {errno = EACCES; return false;} - -// We need the reuse and multw options later so calclulate them now -// - reUse = (parms.reUse <= 0 ? false : true); - multW = (parms.multW <= 0 ? false : true); - -// Clear the memory segment information we will be constructing -// - memset(&theInfo, 0, sizeof(theInfo)); - -// Calculate the info header size (we round up to 1K) -// - shmInfoSz = (sizeof(ShmInfo)+minInfoSz-1)/minInfoSz*minInfoSz; - theInfo.lowFree = theInfo.infoSz = shmInfoSz; - -// Calculate the size of each item (rounded to a doubleword) -// - shmItemSz = (shmTypeSz + parms.maxKLen+1 + sizeof(MemItem) + 7)/8*8; - theInfo.itemSz = shmItemSz; - -// Calculate total amount we need for the items -// - maxEnts = parms.maxKeys; - totSz = shmItemSz * maxEnts; - totSz = (totSz+7)/8*8; - -// Calculate the amount we need for the index -// - indexSz = parms.indexSz*sizeof(int); - indexSz = (indexSz+7)/8*8; - -// Compute total size and adjust it to be a multiple of the page size -// - totSz = totSz + indexSz + shmInfoSz; - totSz = (totSz/PageSize+1)*PageSize; - -// Generate the hashID if not specified -// - if (!shmHash) memcpy(&shmHash, "c32 ", sizeof(int)); - -// Complete the shared memory segment information structure -// - theInfo.index = totSz-indexSz; - theInfo.slots = parms.indexSz; - theInfo.typeSz = shmTypeSz; - theInfo.highUse = theInfo.index; - theInfo.reUse = reUse; - theInfo.multW = multW; - theInfo.keyPos = keyPos = shmTypeSz + sizeof(MemItem); - theInfo.maxKeys = maxEnts; - theInfo.maxKeySz = maxKLen = parms.maxKLen; - theInfo.hashID = shmHash; - strncpy(theInfo.typeID, shmType, sizeof(theInfo.typeID)-1); - strncpy(theInfo.myName, shmImpl, sizeof(theInfo.myName)-1); - -// Create the new filename of the new file we will create -// - n = strlen(shmPath); - shmTemp = (char *)malloc(n+8); - sprintf(shmTemp, "%s.new", shmPath); - -// Open the file creaing as necessary -// - if ((shmFD = ShMam_Open(shmTemp, O_RDWR|O_CREAT, parms.mode)) < 0) - return false; - accMode = parms.mode; - fileHelp.autoClose = true; - -// Verify that no one else is using this file. -// - if (!Lock(true, true)) {errno = EADDRINUSE; return false;} - -// Make the file as large as need be -// - if (ftruncate(shmFD, 0) || ftruncate(shmFD, totSz)) return false; - -// Map the file as a writable shared segment -// - shmBase = (char *)mmap(0, totSz, PROT_READ|PROT_WRITE, MAP_SHARED, shmFD, 0); - if (shmBase == MAP_FAILED) return false; - shmSize = totSz; - isRW = true; - -// Copy the segment information into the segment -// - memcpy(shmBase, &theInfo, sizeof(theInfo)); - xntP.intP = SHMADDR(int, SHMINFO(index)); shmIndex = xntP.antP; - shmSlots = parms.indexSz; - -// A created table has, by definition, a single writer until it is exported. -// So, we simply keep the r/w lock on the file until we export the file. Other -// threads won't change that and other process will not be able to use the file. -// - lockRO = lockRW = false; - fileHelp.autoClose = false; - return true; -} - -/******************************************************************************/ -/* D e l I t e m */ -/******************************************************************************/ - -bool XrdSsiShMam::DelItem(void *data, const char *key, int hash) -{ - XLockHelper lockInfo(this, RWLock); - MemItem *theItem, *prvItem; - int hEnt, iOff; - -// Make sure we can delete an item -// - if (!shmSize) {errno = ENOTCONN; return false;} - if (!isRW) {errno = EROFS; return false;} - -// Check if we need to remap this memory (atomic tests is not needed here) -// - if (verNum != SHMINFO(verNum)) ReMap(RWLock); - -// Lock the file if we have multiple writers or recycling items -// We need to do this prior to file locking as the requirements may change. -// - if (lockRW && !lockInfo.FLock()) return false; - -// First try to find the item -// - if (!(hEnt = Find(theItem, prvItem, key, hash))) - {if (data) {errno = ENOENT; return false;} - return true; - } - -// Return the contents of the item if the caller wishes that -// - if (data) memcpy(data, ITEM_VAL(theItem), shmTypeSz); - -// Delete the item from the index. The update of the count need not be atomic. -// Also fetching of the next offset need not be atomic as we are the only one. -// - iOff = theItem->next; - SHMINFO(itemCount)--; - if (prvItem) Atomic_SET_STRICT(prvItem->next, iOff); // Atomic - else {if (!iOff) SHMINFO(slotsUsed)--; - Atomic_SET_STRICT(shmIndex[hEnt],iOff); // Atomic - } - RetItem(theItem); - -// Indicate the things we updated if need be -// - if (syncOn) - {Updated(0); - Updated(SHMOFFS(theItem)); - if (prvItem) Updated(SHMOFFS(prvItem)); - else Updated(SHMOFFS(&shmIndex[hEnt])); - } - -// All done -// - return true; -} - -/******************************************************************************/ -/* D e t a c h */ -/******************************************************************************/ - -void XrdSsiShMam::Detach() -{ -// Clean up -// - if (shmFD >= 0) {close(shmFD); shmFD = -1;} - if (shmSize) {munmap(shmBase, shmSize); shmSize = 0;} - if (shmTemp) {free(shmTemp); shmTemp = 0;} - shmIndex = 0; -} - -/******************************************************************************/ -/* E n u m e r a t e */ -/******************************************************************************/ - -bool XrdSsiShMam::Enumerate(void *&jar) -{ - EnumJar *theJar = (EnumJar *)jar; - -// Close off the enumeration -// - if (theJar) {delete theJar; jar = 0;} - return true; -} - -/******************************************************************************/ - -bool XrdSsiShMam::Enumerate(void *&jar, char *&key, void *&val) -{ - XLockHelper lockInfo(this, ROLock); - EnumJar *theJar = (EnumJar *)jar; - MemItem *theItem; - long long iTest; - int rc, newFD, fence, iOff, hash = 0; - -// Make sure we can get an item -// - if (!shmSize) {errno = ENOTCONN; return false;} - -// If this is the first call, initialize the jar. First check if we need to -// remap the segment. We need to do this prior to file locking as the -// requirements may change. Then create a jar and a shadow copy of the segment. -// - if (!jar) - {if (verNum != SHMINFO(verNum)) ReMap(ROLock); - if ((newFD = ShMam_Dup(shmFD)) < 0) return false; - theJar = new EnumJar(newFD, shmItemSz); - jar = theJar; - } else if (theJar->iNum < 0) - {Enumerate(jar); - errno = ENOENT; - return false; - } - -// Lock the file if we have multiple writers or recycling items -// - if (lockRO && !lockInfo.FLock()) - {rc = errno; Enumerate(jar); errno = rc; return false;} - -// Compute the next key we should start the search at but make sure it will not -// generate an overflow. In the process we fetch the stopping point only once. -// - iTest = (static_cast(theJar->iNum) * shmItemSz) + shmInfoSz; - fence = SHMINFO(lowFree); // Atomic?? - if (iTest < fence) iOff = static_cast(iTest); - else iOff = fence; - -// Now start the search. Note that pread() must do a memory fence. -// - theItem = (MemItem *)(theJar->buff); - while(iOff < fence) - {rc = pread(theJar->fd, theJar->buff, shmItemSz, iOff); - if (rc < 0) return false; - if (rc != shmItemSz) break; - if ((hash = theItem->hash)) break; // Atomic - iOff += shmItemSz; - } - -// Check if we found a key -// - if (!hash) {Enumerate(jar); errno = ENOENT; return false;} - -// Return the key and and the associated value -// - key = ITEM_KEY(theItem); - val = ITEM_VAL(theItem); - -// Compute the contents of the new jar -// - theJar->iNum = (iOff - shmInfoSz)/shmItemSz + 1; - return true; -} - -/******************************************************************************/ -/* E x p o r t */ -/******************************************************************************/ - -bool XrdSsiShMam::Export() -{ - MutexHelper mtHelp(&myMutex, RWLock); - -// Make sure we are attached and in R/W mode and exportable -// - if (!shmSize) {errno = ENOTCONN; return false;} - if (!shmTemp) {errno = ENOPROTOOPT; return false;} - if (!isRW) {errno = EROFS; return false;} - -// All that is left is to export the file using the internal interface. Tell -// the exporter that we don't have the original file locked. -// - return ExportIt(false); -} - -/******************************************************************************/ -/* Private: E x p o r t I t */ -/******************************************************************************/ - -bool XrdSsiShMam::ExportIt(bool fLocked) -{ - int rc, oldFD; - -// If data synchronization was wanted, then flush the modified pages to -// disk before we make this file visible. -// - if (syncOn) Flush(); - -// Open the original file. If it exists then lock it. We will need to do this -// locally as the the Lock/Unlock() functions are cognizant of threads and that -// is not the case here. We are a singleton. -// - if ((oldFD = ShMam_Open(shmPath, O_RDWR)) < 0) - {if (errno != ENOENT) return false;} - else if (!fLocked) - {do {rc = flock(oldFD, LOCK_EX);} while(rc < 0 && errno == EINTR); - if (rc) return false; - } - -// Rename the new file on top of the old one (the fd's remain in tact) -// - if (rename(shmTemp, shmPath)) {if (oldFD) close(oldFD); return false;} - free(shmTemp); shmTemp = 0; - -// If there was an original file then we must indicate that a new vesion has -// been exported so current users switch to the new version. This is a lazy -// version update because we just need readers to eventually realize this. -// - if (oldFD >= 0) - {int vnum; bool noGo = false; - if (pread(oldFD, &vnum, sizeof(vnum), 0) == (ssize_t)sizeof(vnum)) - {vnum++; - if (pwrite(oldFD, &vnum, sizeof(vnum), 0) != (ssize_t)sizeof(vnum)) - noGo = true; - } else noGo = true; - if (noGo) cerr <<"SsiShMam: Unable to update version for " <hash && !strcmp(key, ITEM_KEY(theItem))) - return hEnt; - prvItem = theItem; - iOff = Atomic_GET_STRICT(theItem->next); // Atomic? - } - -// We did not find the item -// - return 0; -} - -/******************************************************************************/ -/* Private: F l u s h */ -/******************************************************************************/ - -bool XrdSsiShMam::Flush() -{ - int rc; - -// Do appropriate sync -// -#if _POSIX_SYNCHRONIZED_IO > 0 - rc = fdatasync(shmFD) == 0; -#else - rc = fsync(shmFD) == 0; -#endif - -// If we failed, issue message -// - if (rc) - {rc = errno; - cerr <<"ShMam: msync() failed; " <(crc); - return (hval ? hval : 1); -} - -/******************************************************************************/ -/* Private: L o c k */ -/******************************************************************************/ - -// The caller must have obtained a mutex consistent with the argument passed. - -bool XrdSsiShMam::Lock(bool xrw, bool nowait) -{ - int rc, act = (xrw ? LOCK_EX : LOCK_SH); - -// Make sure we have a file descriptor to lock and is not already locked -// - if (shmFD < 0) {errno = EBADF; return false;} - -// We must keep track of r/o locks as there may be many requests but we can -// only lock the file once for all of them. R/W locks are easier to handle as -// only one thread can ever have such a lock request. Atomics do not help -// for R/O locks because they suffer from an unlock control race and also -// all R/O requestors must wait if the file is locked by another process. -// - if (xrw) lkCount = 1; - else {pthread_mutex_lock(&lkMutex); - if (lkCount++) {pthread_mutex_unlock(&lkMutex); return true;} - } - -// Check if we should not wait for the lock -// - if (nowait) act |= LOCK_NB; - -// Now obtain the lock -// - do {rc = flock(shmFD, act);} while(rc < 0 && errno == EINTR); - -// Decrement lock count if we failed (we were optimistic). Note that we still -// have the mutex locked if this was a T/O request. -// - if (rc) {if (xrw) lkCount = 0; - else lkCount--; - } - -// Unlock the mutex if we still have it locked and return result -// - if (!xrw) pthread_mutex_unlock(&lkMutex); - return rc == 0; -} - -/******************************************************************************/ -/* I n f o */ -/******************************************************************************/ - -int XrdSsiShMam::Info(const char *vname, char *buff, int blen) -{ - MutexHelper mtHelp(&myMutex, ROLock); - -// Make sure we can delete an item -// - if (!shmSize) {errno = ENOTCONN; return 0;} - - if (!strcmp(vname, "atomics")) - {int n = strlen(Atomic_IMP); - strcpy(buff, Atomic_IMP); - return n; - } - - if (!strcmp(vname, "hash")) - {if (!buff || blen < (int)(sizeof(int)+1)) {errno = EMSGSIZE; return -1;} - memcpy(buff, &SHMINFO(hashID), sizeof(int)); buff[sizeof(int)] = 0; - return strlen(buff); - } - if (!strcmp(vname, "impl")) - {int n = strlen(SHMINFO(myName)); - if (!buff || blen < n) {errno = EMSGSIZE; return -1;} - strcpy(buff, SHMINFO(myName)); - return n; - } - if (!strcmp(vname, "flockro")) return lockRO; - if (!strcmp(vname, "flockrw")) return lockRW; - if (!strcmp(vname, "indexsz")) return shmSlots; - if (!strcmp(vname, "indexused")) return SHMINFO(slotsUsed); - if (!strcmp(vname, "keys")) return SHMINFO(itemCount); // Atomic - if (!strcmp(vname, "keysfree")) - return (SHMINFO(highUse) - SHMINFO(lowFree))/shmItemSz - + SHMINFO(freeCount); - if (!strcmp(vname, "maxkeylen")) return SHMINFO(maxKeySz); - if (!strcmp(vname, "multw")) return multW; - if (!strcmp(vname, "reuse")) return reUse; - if (!strcmp(vname, "type")) - {int n = strlen(SHMINFO(typeID)); - if (!buff || blen < n) {errno = EMSGSIZE; return -1;} - strcpy(buff, SHMINFO(typeID)); - return n; - } - if (!strcmp(vname, "typesz")) return SHMINFO(typeSz); - -// Return variable not supported -// - errno = ENOSYS; - return -1; -} - -/******************************************************************************/ -/* Private: N e w I t e m */ -/******************************************************************************/ - -XrdSsiShMam::MemItem *XrdSsiShMam::NewItem() -{ - MemItem *itemP; - int iOff; - -// First see if we can get this from the free chain -// - if (reUse && SHMINFO(freeItem)) - {iOff = SHMINFO(freeItem); - itemP = SHMADDR(MemItem, iOff); - SHMINFO(freeItem) = itemP->next; - SHMINFO(freeCount)--; // Atomic? - } else { - int newFree = SHMINFO(lowFree) + shmItemSz; - if (newFree > SHMINFO(highUse)) itemP = 0; - else {iOff = SHMINFO(lowFree); - itemP = SHMADDR(MemItem, iOff); - SHMINFO(lowFree) = newFree; - } - } - -// Return result -// - return itemP; -} - -/******************************************************************************/ -/* Private: R e M a p */ -/******************************************************************************/ - -bool XrdSsiShMam::ReMap(LockType iHave) -{ - XrdSsiShMat::NewParms parms; - -// If the caller has a read mutex then we must change it to a r/w mutex as we -// may be changing all sorts of variables. It will continue holding this mutex. -// Fortunately, remappings do not occur very often in practice. -// - if (iHave == ROLock) - {pthread_rwlock_unlock(&myMutex); - pthread_rwlock_wrlock(&myMutex); - } - -// Check if the version number no longer differs, then just return. This may -// happen because a previous thread forced the remapping and everyone was -// waiting for that to happen as we hold the r/w mutex. -// - if (verNum == SHMINFO(verNum)) return false; - -// Setup parms -// - parms.impl = shmImpl; - parms.path = shmPath; - parms.typeID = shmType; - parms.typeSz = shmTypeSz; - parms.hashID = shmHash; - -// Attach the new segment. If we fail, then just continue -// - XrdSsiShMam newMap(parms); - if (!newMap.Attach(timeOut, isRW)) return false; - -// Swap the new map with our map -// - SwapMap(newMap); - return true; -} - -/******************************************************************************/ -/* R e s i z e */ -/******************************************************************************/ - -bool XrdSsiShMam::Resize(XrdSsiShMat::CRZParms &parms) -{ - XLockHelper lockInfo(this, RWLock); - XrdSsiShMat::NewParms newParms; - MemItem *theItem; - void *val; - char *key; - int fence, iOff, hash; - -// Make sure we can delete an item -// - if (!shmSize) {errno = ENOTCONN; return false;} - if (!isRW) {errno = EROFS; return false;} - -// Validate parameter list values -// - if (parms.indexSz < 0 || parms.maxKeys < 0 || parms.maxKLen < 0) - {errno = EINVAL; return false;} - -// A resize is not permitted on an un-exported segment -// - if (shmTemp) {errno = EPERM; return false;} - -// Check if we need to remap this memory (atomic tests is not needed here) -// - if (verNum != SHMINFO(verNum)) ReMap(RWLock); - -// Lock the source file -// - if (!lockInfo.FLock()) return false; - -// Setup parms for the segment object -// - newParms.impl = shmImpl; - newParms.path = shmPath; - newParms.typeID = shmType; - newParms.typeSz = shmTypeSz; - newParms.hashID = shmHash; - -// Create a new segment object (this cannot fail). -// - XrdSsiShMam newMap(newParms); - -// Set the values in the parameter list for those wanting the current setting. -// - if (!parms.indexSz) parms.indexSz = shmSlots; - if (!parms.maxKeys) parms.maxKeys = SHMINFO(maxKeys); - if (!parms.maxKLen) parms.maxKLen = maxKLen; - if (parms.reUse < 0) parms.reUse = reUse; - if (parms.multW < 0) parms.multW = multW; - -// Create the new target file -// - parms.mode = accMode; - if (!newMap.Create(parms)) return false; - -// Compute the offset of the first item and get the offset of teh last item. -// - fence = SHMINFO(lowFree); // Atomic?? - iOff = shmInfoSz; - -// For each item found in the current map add it to the new map -// - while(iOff < fence) - {theItem = SHMADDR(MemItem, iOff); - if ((hash = theItem->hash)) - {key = ITEM_KEY(theItem); - val = ITEM_VAL(theItem); - if (!newMap.AddItem(val, 0, key, hash, true)) return false; - } - iOff += shmItemSz; - } - -// We need to drop the lock on the file otherwise the export will hang -// - -// All went well, so export this the new map using the internal interface as -// we already have the source file locked and export normally tries to lock it. -// - if (!newMap.ExportIt(true)) return false; - -// All that we need to do is to swap the map with our map and we are done. -// - SwapMap(newMap); - return true; -} - -/******************************************************************************/ -/* Private: R e t I t e m */ -/******************************************************************************/ - -void XrdSsiShMam::RetItem(MemItem *iP) -{ - -// Zorch the hash so this item cannot be found. This is problematic for -// enumerations. They may or may not include this key, but at least it will -// consistent at the time the enumeration happens. -// - iP->hash = 0; // Atomic? - -// If reuse is allowed, place the item on the free list -// - if (reUse) - {iP->next = SHMINFO(freeItem); - SHMINFO(freeItem) = SHMOFFS(iP); - SHMINFO(freeCount)++; //Atomic?? - } -} - -/******************************************************************************/ -/* Private: S e t L o c k i n g */ -/******************************************************************************/ - -void XrdSsiShMam::SetLocking(bool isrw) -{ -// If we do not have atomics then file locking is mandatory -// -#ifdef NEED_ATOMIC_MUTEX - lockRO = lockRW = true; -#else -// A reader must lock the file R/O if objects are being reused -// - lockRO = reUse = SHMINFO(reUse); - -// A writer must lock the file R/W if objects are being reused or the file may -// have multiple writers -// - multW = SHMINFO(multW); - lockRW = reUse || multW; -#endif -} - -/******************************************************************************/ -/* S n o o z e */ -/******************************************************************************/ - -void XrdSsiShMam::Snooze(int sec) -{ - struct timespec naptime, waketime; - -// Calculate nano sleep time -// - naptime.tv_sec = sec; - naptime.tv_nsec = 0; - -// Wait for a number of seconds -// - while(nanosleep(&naptime, &waketime) && EINTR == errno) - {naptime.tv_sec = waketime.tv_sec; - naptime.tv_nsec = waketime.tv_nsec; - } -} - -/******************************************************************************/ -/* Private: S w a p M a p */ -/******************************************************************************/ - -void XrdSsiShMam::SwapMap(XrdSsiShMam &newMap) -{ - -// Detach the old map -// - Detach(); - -// Swap the maps -// - shmFD = newMap.shmFD; - newMap.shmFD = -1; - shmSize = newMap.shmSize; - newMap.shmSize = 0; - shmBase = newMap.shmBase; - newMap.shmBase = 0; - shmIndex = newMap.shmIndex; - newMap.shmIndex = 0; - lockRO = newMap.lockRO; - lockRW = newMap.lockRW; - reUse = newMap.reUse; - multW = newMap.multW; - verNum = newMap.verNum; -} - -/******************************************************************************/ -/* S y n c */ -/******************************************************************************/ - -bool XrdSsiShMam::Sync() -{ - MutexHelper mtHelp(&myMutex, RWLock); - -// Make sure we are attached and in R/W mode -// - if (!shmSize) {errno = ENOTCONN; return false;} - if (!isRW) {errno = EROFS; return false;} - -// For now do a flush as this works in Linux. We may need to generalize this -// for all platforms using msync, sigh. -// - if (!Flush()) return false; - -// Reset counters -// - syncBase = false; - syncLast = 0; - syncQWR = 0; - return true; -} - -/******************************************************************************/ - -bool XrdSsiShMam::Sync(int syncqsz) -{ - MutexHelper mtHelp(&myMutex, RWLock); - -// Make sure we are attached and in R/W mode -// - if (!shmSize) {errno = ENOTCONN; return false;} - if (!isRW) {errno = EROFS; return false;} - if (syncqsz < 0) {errno = EINVAL; return false;} - -// Flush out pages if sync it turned on -// - if (syncOn && !Flush()) return false; - -// Set new queue size -// - syncQSZ = syncqsz; - return true; -} - -/******************************************************************************/ - -bool XrdSsiShMam::Sync(bool dosync, bool syncdo) -{ - MutexHelper mtHelp(&myMutex, RWLock); - -// Make sure we are attached and in R/W mode -// - if (!shmSize) {errno = ENOTCONN; return false;} - if (!isRW) {errno = EROFS; return false;} - -// Flush out pages if sync it turned on -// - if (syncOn && !Flush()) return false; - -// Set new options -// - syncOn = dosync; - syncOpt = (syncdo ? MS_SYNC : MS_ASYNC); - return true; -} - -/******************************************************************************/ -/* Private: U n L o c k */ -/******************************************************************************/ - -// The caller must have obtained a mutex consistent with the argument passed. - -void XrdSsiShMam::UnLock(bool isrw) -{ - int rc; - -// Make sure we have a file descriptor to unlock -// - if (shmFD < 0) return; - -// If this is a R/W type of lock then we can immediate release it as there -// could have been only one writer. Otherwise, we will need to keep track -// of the number of R/O locks has dropped to zero before unlocking the file. -// Atomics do not help here because of possible thread inversion. -// - if (isrw) lkCount = 0; - else {pthread_mutex_lock(&lkMutex); - lkCount--; - if (lkCount) {pthread_mutex_unlock(&lkMutex); return;} - } - -// Now release the lock -// - do {rc = flock(shmFD, LOCK_UN);} while(rc < 0 && errno == EINTR); - -// If this was a r/o unlock then we have kept the mutex and must unlock it -// We kept the mutex to prevent a control race condition. -// - if (!isrw) pthread_mutex_unlock(&lkMutex); -} - -/******************************************************************************/ -/* Private: U p d a t e d */ -/******************************************************************************/ - -void XrdSsiShMam::Updated(int mOff) -{ -// Check if this refers to the info struct -// - if (!mOff) - {if (!syncBase) {syncBase = true; syncQWR++;} - } else { - if (syncLast != (mOff & PageMask)) - {syncLast = (mOff & PageMask); syncQWR++;} - } - -// Check if we need to flush now -// - if (syncQWR >= syncQSZ) {ShMam_Flush(shmFD); syncQWR = 0;} -} - -/******************************************************************************/ - -void XrdSsiShMam::Updated(int mOff, int mLen) -{ - int memB = mOff & PageMask; - int memE = mOff + mLen; - -// This is a range update. This is not very precise if update the same page -// and the we cross the page boundary. But it should be good enough. -// - if (memB != syncLast) - {syncQWR++; - if (memB != (memE & PageMask)) syncQWR++; - syncLast = memB; - } - -// Check if we need to flush now -// - if (syncQWR >= syncQSZ) {ShMam_Flush(shmFD); syncQWR = 0;} -} diff --git a/src/XrdSsi/XrdSsiShMam.hh b/src/XrdSsi/XrdSsiShMam.hh deleted file mode 100644 index b254e7f86e2..00000000000 --- a/src/XrdSsi/XrdSsiShMam.hh +++ /dev/null @@ -1,152 +0,0 @@ -#ifndef __SSI_SHMAM__ -#define __SSI_SHMAM__ -/******************************************************************************/ -/* */ -/* X r d S s i S h M a m . h h */ -/* */ -/* (c) 2015 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -#include "XrdSsi/XrdSsiAtomics.hh" -#include "XrdSsi/XrdSsiShMat.hh" - -class XrdSsiShMam : public XrdSsiShMat -{ -public: - -bool AddItem(void *newdata, void *olddata, const char *key, - int hash, bool replace=false); - -bool Attach(int tout, bool isrw=false); - -bool Create(XrdSsiShMat::CRZParms &parms); - -bool Export(); - -bool DelItem(void *data, const char *key, int hash); - -void Detach(); - -bool Enumerate(void *&jar, char *&key, void *&val); - -bool Enumerate(void *&jar); - -bool GetItem(void *data, const char *key, int hash); - -int Info(const char *vname, char *buff=0, int blen=0); - -bool Resize(XrdSsiShMat::CRZParms &parms); - -bool Sync(); -bool Sync(bool dosync, bool syncdo); -bool Sync(int syncqsz); - - XrdSsiShMam(XrdSsiShMat::NewParms &parms); - - ~XrdSsiShMam() {Detach(); - pthread_mutex_destroy(&lkMutex); - pthread_rwlock_destroy(&myMutex); - } - -enum LockType {ROLock= 0, RWLock = 1}; - -private: -struct MemItem {int hash; Atomic(int) next;}; - -bool ExportIt(bool fLocked); -int Find(MemItem *&theItem, MemItem *&prvItem, const char *key, int &hash); -bool Flush(); -int HashVal(const char *key); -bool Lock(bool doRW=false, bool nowait=false); -MemItem *NewItem(); -bool ReMap(LockType iHave); -void RetItem(MemItem *iP); -void SetLocking(bool isrw); -void SwapMap(XrdSsiShMam &newMap); -void Snooze(int sec); -void UnLock(bool isrw); -void Updated(int mOff); -void Updated(int mOff, int mLen); - -class XLockHelper -{ -public: -inline bool FLock() {if (!(shmemP->Lock(lkType))) return false; - doUnLock = true; return true; - } - - XLockHelper(XrdSsiShMam *shmemp, LockType lktype) - : shmemP(shmemp), lkType(lktype), doUnLock(false) - {if (lktype == RWLock) - pthread_rwlock_wrlock(&(shmemP->myMutex)); - else pthread_rwlock_rdlock(&(shmemP->myMutex)); - } - ~XLockHelper() {int rc = errno; - if (lkType == RWLock && shmemP->syncOn - && shmemP->syncQWR > shmemP->syncQSZ) - shmemP-> Flush(); - if (doUnLock) shmemP->UnLock(lkType == RWLock); - pthread_rwlock_unlock(&(shmemP->myMutex)); - errno = rc; - } -private: -XrdSsiShMam *shmemP; -LockType lkType; -bool doUnLock; -}; - -pthread_mutex_t lkMutex; -pthread_rwlock_t myMutex; - -char *shmTemp; -long long shmSize; -char *shmBase; -Atomic(int)*shmIndex; -int shmSlots; -int shmItemSz; -int shmInfoSz; -int verNum; -int keyPos; -int maxKLen; -int shmFD; -int timeOut; -int lkCount; -int syncOpt; -int syncQWR; -int syncLast; -int syncQSZ; -int accMode; -bool isRW; -bool lockRO; -bool lockRW; -bool reUse; -bool multW; -bool useAtomic; -bool syncBase; -bool syncOn; -}; -#endif diff --git a/src/XrdSsi/XrdSsiShMap.hh b/src/XrdSsi/XrdSsiShMap.hh deleted file mode 100644 index 4dc77516058..00000000000 --- a/src/XrdSsi/XrdSsiShMap.hh +++ /dev/null @@ -1,429 +0,0 @@ -#ifndef __SSI_SHMAP__ -#define __SSI_SHMAP__ -/******************************************************************************/ -/* */ -/* X r d S s i S h M a p . h h */ -/* */ -/* (c) 2015 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -#include "XrdSsi/XrdSsiShMat.hh" - -//----------------------------------------------------------------------------- -//! This include file defines a simple key-value store interface using shared -//! memory. This allows you to share the map with other processes in read as -//! well as read/write mode. See the XrdSsi::ShMap teplated class within. -//----------------------------------------------------------------------------- - -namespace XrdSsi -{ -//----------------------------------------------------------------------------- -//! The action parameter that must be passed to the Attach() method. -//----------------------------------------------------------------------------- - -enum ShMap_Access //!< Attach existing map for - {ReadOnly = 1, //!< reading - ReadWrite = 2 //!< reading & writing - }; - -//----------------------------------------------------------------------------- -//! Parameters to pass to Create(). For Resize(&parms) initialize the struct -//! as: "ShMap_Parms parms(XrdSsi::ShMap_Parms::ForResize)" so that the default -//! values are appropriate for resizing instead of creation. -//----------------------------------------------------------------------------- - -static const int ShMap_4Resize = -1; - -struct ShMap_Parms - {int indexSize; //!< Number of hash table entries to create - int maxKeyLen; //!< Maximum key length - int maxKeys; //!< Maximum expected keys - int mode; //!< Mode setting for the newly created file. - int options; //!< Bit or'd ShMop_xxxx options below - int reserved; //!< Reserved for future ABI complaint use - -//----------------------------------------------------------------------------- -//! Bit options that may be or'd into he options member above. -//----------------------------------------------------------------------------- - -static const - int MultW = 0x88000000; //!< Multiple external writers -static const - int noMultW = 0x08000000; //!< Opposite (default for Create) -static const - int ReUse = 0x44000000; //!< Reuse map storage -static const - int noReUse = 0x04000000; //!< Opposite (default for Create) - -//----------------------------------------------------------------------------- -//! Constructor suitable for Create() -//----------------------------------------------------------------------------- - - ShMap_Parms() : indexSize(16381), maxKeyLen(63), maxKeys(32768), - mode(0640), options(0), reserved(0) {} - -//----------------------------------------------------------------------------- -//! Constructor suitable for Resize() (use ShMap_Parms(ForResize)). -//----------------------------------------------------------------------------- -static const - int ForResize = 0; //!< Triggers initialization for Resize - - ShMap_Parms(int rsz) : indexSize(0), maxKeyLen(0), maxKeys(0), - mode(0640), options(0), reserved(rsz) {} - -//----------------------------------------------------------------------------- -//! Destructor -//----------------------------------------------------------------------------- - - ~ShMap_Parms() {} - }; - -//----------------------------------------------------------------------------- -//! Options valid for the Sync() method. -//----------------------------------------------------------------------------- - -enum SyncOpt {SyncOff = 0, SyncOn, SyncAll, SyncNow, SyncQSz}; - -//----------------------------------------------------------------------------- -//! Typedef for the optional hash computation function (see constructor) -//! -//! @param parms Pointer to the key whose hash is to be returned. If nil -//! the function should return its 4-character name (e.g. -//! {int hash; memcpy(&hash, "c32 ", sizeof(int)); return hash;} -//! -//! @return Either the hash value of the key or the hash name as an int. -//----------------------------------------------------------------------------- - -typedef int (*ShMap_Hash_t)(const char *key); - -template -class ShMap -{ -public: - -//----------------------------------------------------------------------------- -//! Attach an existing shared memory map. -//! -//! @param path Pointer to the file that is or will represent the map. -//! -//! @param access How to attach the map. Specify one of the following: -//! ReadOnly - Attach the map strictly for reading. -//! ReadWrite - Attach the map in read/write mode. New and -//! -//! @param tmo How many seconds to wait for the map to appear. It is -//! possible that a new map may have not yet been exported, so -//! attach will wait for the map to become visible. Specify, -//! <0 - wait forever. -//! =0 - do not wait at all. -//! >0 - wait the specified number seconds and then timeout. -//! -//! @return true - The shared memory was attached, the map can be used. -//! @return false - The shared memory could not be attached, errno holds reason. -//----------------------------------------------------------------------------- - -bool Attach(const char *path, ShMap_Access access, int tmo=-1); - -//----------------------------------------------------------------------------- -//! Create a new r/w shared memory map possibly replacing an existing one upon -//! export. New maps must be exported to become visible (see Export()). -//! -//! This method first creates a temporary map visible only to the creator. This -//! allows you to fill the map as needed with minimal overhead. Once this is -//! done, call Export() to make the new map visible, possibly replacing an -//! any existing version of a map with the same name. -//! -//! @param parms Reference to the parameters. See the ShMap_Parms struct for -//! for details and constructor defaults. Below is a detailed -//! explanation of the available options: -//! -//! MultW - The map has multiple processes writing to it. -//! All writers must obtain an exclusive file lock -//! before updating the map. No file locks are needed -//! ReUse - Allow reuse of storage in the map. Use this if -//! the map has many inserts/deletes. If set, r/o -//! access will always lock the map file before -//! looking at it. Otherwise, there is no need for -//! file locks as no item is ever reused. ReUse is -//! good when there are few key add/delete cycles. -//! -//! @return true - The shared memory was attached, the map can be used. -//! @return false - The shared memory could not be attached, errno holds reason. -//----------------------------------------------------------------------------- - -bool Create(const char *path, ShMap_Parms &parms); - -//----------------------------------------------------------------------------- -//! Detach the map from the shared memory. -//----------------------------------------------------------------------------- - -void Detach(); - -//----------------------------------------------------------------------------- -//! Export a newly created map (i.e. one that was attached using ShMop_New). -//! -//! @return true - The map has been exported and is now visible to others. -//! @return false - The export failed, the errno value describes the reason. -//----------------------------------------------------------------------------- - -bool Export(); - -//----------------------------------------------------------------------------- -//! Add an item to the map (see the Rep() method for key/data replacement). -//! -//! @param key pointer to the key of length <= MaxKeySize. -//! @param val The associated data to be added to the map. -//! -//! @return true - The key and data added to the map. -//! @return false - The key and data not added, the errno value describes why. -//! Typical reason: the key already exists (errno == EEXIST). -//----------------------------------------------------------------------------- - -bool Add(const char *key, T &val); - -//----------------------------------------------------------------------------- -//! Delete an item from the map. -//! -//! @param key Pointer to the key of length <= MaxKeySize. -//! @param valP Pointer to the area to receive the value of the deleted key. -//! If the pointer is nil, then the key value is not returned. -//! -//! @return true - The key and data have been deleted. This is always returned -//! when valP is nil. -//! @return false - The key and data either not deleted or the key does not -//! exist and valP was not nil. The errno value describes why. -//! Typical reason: the key was not found (errno == ENOENT). -//----------------------------------------------------------------------------- - -bool Del(const char *key, T *valP=0); - -//----------------------------------------------------------------------------- -//! Enumerate the keys and associated values. -//! -//! @param jar An opaque cookie that tracks progress. It should be -//! initialized to zero and otherwise not touched. The same jar -//! must be used for all successive calls. The jar is deleted -//! when false is returned (also see the next Enumerate method). -//! @param key The pointer variable where the location of the key is -//! returned upon success. The key is overwritten on the next -//! call to Enumerate(); so copy it if you want to keep it. -//! @param val The pointer variable where the location of the key value -//! is to be returned upon success. The value is overwritten on -//! the next call to Enumerate(). Copy it if you want to keep it. -//! -//! @return true A key and val pointers have been set. -//! Keys are returned in arbitrary order and not all keys may -//! be returned if the map is being actively updated. -//! @return false Key not returned; errno holds the reason. Typically, -//! ENOENT there ae no more keys. -//! Other errors may also be reflected. Whene false is returned -//! the jar is deleted and the pointer to it set to zero. -//----------------------------------------------------------------------------- - -bool Enumerate(void *&jar, char *&key, T *&val); - -//----------------------------------------------------------------------------- -//! Terminate an active enumeration. An active enumeration is any enumeration -//! where the previous form of Enumerate() did not return false. Terminating -//! an active enumeration releases all of the enumeration resources allocated. -//! -//! @param jar The opaque cookie initialized by a previous call to -//! Enumerate() whose enumeration is to be terminated. -//! -//! @return true The enumeration has been terminated and the jar was -//! deleted and the jar pointer is set to zero. -//! Keys are returned in arbitrary order and not all keys may -//! be returned if the map is being actively updated. -//! @return false The jar pointer was zero; no enumeration was active. -//----------------------------------------------------------------------------- - -bool Enumerate(void *&jar); - -//----------------------------------------------------------------------------- -//! Determine whether or not a key exists in the map. -//! -//! @param key Pointer to the key of length <= MaxKeySize. -//! -//! @return true - The key exists. -//! @return false - The key does not exist. -//----------------------------------------------------------------------------- - -bool Exists(const char *key); - -//----------------------------------------------------------------------------- -//! Find a key in the map and return its value. -//! -//! @param key Pointer to the key of length <= MaxKeySize. -//! @param val Reference to the area to receive the value of the found key. -//! -//! @return true - The key found and its value has been returned. -//! @return false - The key not found, the errno value describes why. -//! Typical reason: the key was not found (errno == ENOENT). -//----------------------------------------------------------------------------- - -bool Get(const char *key, T &val); - -//----------------------------------------------------------------------------- -//! Return information about the map. -//! -//! @param vname Pointer to the variable name whose value is wanted. A -//! particular implementation may not support all variable and -//! may support variables not listed here. These are for the -//! default implementation unless otherwise noted. They are: -//! hash - name of hash being used. -//! impl - The table implementation being used. -//! indexsz - Number of index entries -//! indexused - Number of index entries in use -//! keys - Number of keys in the map. keys/indexused is -//! the hash table collision factor -//! keysfree - Number of keys that can still be added -//! maxkeylen - Longest allowed key -//! multw - If 1 map supports multiple writers, else 0 -//! reuse - If 1 map allows object reuse, else 0 -//! type - Name of the data type in the table. -//! typesz - The number of bytes in the map's data type -//! -//! @param buff - Pointer to the buffer to receive text values. -//! Variables that return text are: hash, impl, -//! and type. A buffer must be supplied in any -//! of these variables are requested. If buff is -//! nill or too small a -1 is returned with errno -//! set to EMSGSIZE. -//! -//! @param blen The length of the buffer. -//! -//! @return >=0 - The variable's value. -//! @return < 0 - The variable's value could not be returned; errno has the -//! error code describing the reason, typically ENOSYS. -//----------------------------------------------------------------------------- - -int Info(const char *vname, char *buff=0, int blen=0); - -//----------------------------------------------------------------------------- -//! Add to or replace an item in the map. -//! -//! @param key Pointer to the key of length <= MaxKeySize. -//! @param val The associated data to be added to or replaced in the map. -//! @param valP Pointer to the area to receive the value of a replaced key. -//! If the pointer is nil, then the key value is not returned. -//! -//! @return true - The key and data added to or replaced in the map. If the -//! key was replaced errno is set to EEXIST else it is set to 0. -//! @return false - The key and data not added, the errno value describes why. -//! Typical reason: the key was too long (errno == ENAMETOOLONG). -//----------------------------------------------------------------------------- - -bool Rep(const char *key, T &val, T *valP=0); - -//----------------------------------------------------------------------------- -//! Resize or change options on an existing map attached in read/write mode. -//! The map must have been exported. -//! -//! @param parms Pointer to the parameters. See the ShMap_Parms struct for -//! for details and constructor defaults. A zero value in the -//! parameter list uses the existing map value allowing you to -//! selectively change the map sizing and options. If a nil -//! pointer is passed, the map is simply compressed. -//! -//! @return true - The shared memory was resized. -//! @return false - The shared memory could not be resized, errno holds reason. -//----------------------------------------------------------------------------- - -bool Resize(ShMap_Parms *parms=0); - -//----------------------------------------------------------------------------- -//! Specify how the memory map is synchronized with its backing file. If sync -//! is already enabled, calling this method writes back any modified pages -//! before effecting any requested changes. -//! -//! @param dosync Controls how synchronization is done (see SyncOpt enum): -//! SyncOff - Turn synchronization off (initial setting). -//! SyncOn - Turn synchronization on; pages are written in the -//! background (i.e. asynchronously). -//! SyncAll - Turn synchronization on; pages are written in the -//! foreground(i.e. synchronously). -//! SyncNow - Write back any queued pages but otherwise keep -//! all other settings the same. -//! SyncQSz - Set the queue size specified in the second -//! argument. This number of modified pages are -//! queued before being written back to disk. No -//! other setting in effect are altered. -//! @param syncqsz Specifies the defer-writeback queue size. This argument -//! is ignored unless SyncQSz has been specified (see above). -//! -//! @return true - Call ended successfully. -//! @return false - Call failed; the errno value describes why. -//----------------------------------------------------------------------------- - -bool Sync(SyncOpt dosync, int syncqsz=256); - -//----------------------------------------------------------------------------- -//! Constructor. First allocate a ShMap object of appropriate type. Then call -//! Attach() to attach it to a shared memory segment before calling any other -//! method in this class. When through either delete the object. -//! -//! @param typeName - A text name of the type in the map. Attach() makes sure -//! that the map has this type. Specify text < 64 characters. -//! Example: XrdSsi::ShMap myMap("int"); -//! -//! @param hFunc - An optional pointer to to the hash computation function -//! to be used. If not specified, a crc32 hash is used. -//! -//! @param implName - A text name of the map implementation desired. Zero uses -//! the default implementation. Currently only the default -//! implementation is available. -//----------------------------------------------------------------------------- - - ShMap(const char *typeName, ShMap_Hash_t hFunc=0, - const char *implName=0) - : shMat(0), hashFunc(hFunc), typeID(strdup(typeName)), - implID((implName ? strdup(implName) : 0)) {} - -//----------------------------------------------------------------------------- -//! Destructor -//----------------------------------------------------------------------------- - - ~ShMap() {Detach(); - if (typeID) free(typeID); - if (implID) free(implID); - } - -private: - -XrdSsiShMat *shMat; -ShMap_Hash_t hashFunc; -char *typeID; -char *implID; -}; -} - -/******************************************************************************/ -/* A c t u a l I m p l e m e n t a t i o n */ -/******************************************************************************/ - -#include "XrdSsi/XrdSsiShMap.icc" -#endif diff --git a/src/XrdSsi/XrdSsiShMap.icc b/src/XrdSsi/XrdSsiShMap.icc deleted file mode 100644 index e24614e888e..00000000000 --- a/src/XrdSsi/XrdSsiShMap.icc +++ /dev/null @@ -1,368 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S s i S h M a p . i c c */ -/* */ -/* (c) 2015 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -/******************************************************************************/ -/* A d d */ -/******************************************************************************/ - -template -bool XrdSsi::ShMap::Add(const char *key, T &val) -{ -// Make sure we have memory -// - if (!shMat) {errno = ENOTCONN; return false;} - -// Compute hash if need be -// - int hash = (hashFunc ? hashFunc(key) : 0); - -// Rteurn the result -// - return shMat->AddItem(&val, 0, key, hash, false); -} - -/******************************************************************************/ -/* A t t a c h */ -/******************************************************************************/ - -template -bool XrdSsi::ShMap::Attach(const char *path, - XrdSsi::ShMap_Access access, - int tmo) -{ - static const int waitSec = 2; - XrdSsiShMat::NewParms newParms; - int rc, tries= (tmo < 0 ? 120 : (tmo < 4 ? tmo : tmo/waitSec)); - bool isRW = (access == XrdSsi::ReadOnly ? false : true); - -// First see if this object is already attached. -// - if (shMat) {errno = EISCONN; return false;} - -// Set the object attach parameters -// - newParms.impl = implID; - newParms.path = path; - newParms.typeID = typeID; - newParms.typeSz = sizeof(T); - newParms.hashID = (hashFunc ? hashFunc(0) : 0); - -// Allocate a new shared memory generic object -// - shMat = XrdSsiShMat::New(newParms); - if (!shMat) return false; - -// Handle the action. There is an edge case where we will need to try to attach -// the map again. We only do that for a limited number of times as this is a -// condition that should not occur for any long amount of time. -// - do {if (shMat->Attach(tmo, isRW)) return true; - if (errno != EAGAIN) break; - if (tries--) sleep(waitSec); - } while(tries > 0); - if (tries) errno = ECANCELED; - -// We failed -// - rc = errno; - delete shMat; shMat = 0; - errno = rc; - return false; -} - -/******************************************************************************/ -/* C r e a t e */ -/******************************************************************************/ - -template -bool XrdSsi::ShMap::Create(const char *path, XrdSsi::ShMap_Parms &parms) -{ - XrdSsiShMat::NewParms newParms; - XrdSsiShMat::CRZParms crzParms; - int rc; - -// First see if this object is already attached. -// - if (shMat) {errno = EISCONN; return false;} - -// Set the object creation parameters -// - newParms.impl = implID; - newParms.path = path; - newParms.typeID = typeID; - newParms.typeSz = sizeof(T); - newParms.hashID = (hashFunc ? hashFunc(0) : 0); - -// Allocate a new shared memory generic object -// - shMat = XrdSsiShMat::New(newParms); - if (!shMat) return false; - -// Copy over the create parameters -// - crzParms.indexSz = parms.indexSize; - crzParms.maxKeys = parms.maxKeys; - crzParms.maxKLen = parms.maxKeyLen; - crzParms.mode = parms.mode; - if (parms.options & XrdSsi::ShMap_Parms::ReUse) - crzParms.reUse = (parms.options & ~XrdSsi::ShMap_Parms::noReUse ? 1 : 0); - if (parms.options & XrdSsi::ShMap_Parms::MultW) - crzParms.multW = (parms.options & ~XrdSsi::ShMap_Parms::noMultW ? 1 : 0); - -// Handle the action -// - if (shMat->Create(crzParms)) return true; - -// We failed -// - rc = errno; - delete shMat; shMat = 0; - errno = rc; - return false; -} - -/******************************************************************************/ -/* D e l */ -/******************************************************************************/ - -template -bool XrdSsi::ShMap::Del(const char *key, T *valP) -{ -// Make sure we have memory -// - if (!shMat) {errno = ENOTCONN; return false;} - -// Compute hash if need be -// - int hash = (hashFunc ? hashFunc(key) : 0); - -// Return the result -// - return shMat->DelItem(valP, key, hash); -} - -/******************************************************************************/ -/* D e t a c h */ -/******************************************************************************/ - -template -void XrdSsi::ShMap::Detach() -{ -// If we have memory, detach it -// - if (shMat) {delete shMat; shMat = 0;} -} - -/******************************************************************************/ -/* E n u m e r a t e */ -/******************************************************************************/ - -template -bool XrdSsi::ShMap::Enumerate(void *&jar, char *&key, T *&val) -{ -// Make sure we have memory -// - if (!shMat) {errno = ENOTCONN; return false;} - -// Return next key and possibly an assocaited value -// - return shMat->Enumerate(jar, key, (void *&)val); -} - -/******************************************************************************/ - -template -bool XrdSsi::ShMap::Enumerate(void *&jar) -{ -// Make sure we have memory -// - if (!shMat) {errno = ENOTCONN; return false;} - -// Terminate the enumeration -// - return shMat->Enumerate(jar); -} - -/******************************************************************************/ -/* E x i s t s */ -/******************************************************************************/ - -template -bool XrdSsi::ShMap::Exists(const char *key) -{ -// Make sure we have memory -// - if (!shMat) {errno = ENOTCONN; return false;} - -// Compute hash if need be -// - int hash = (hashFunc ? hashFunc(key) : 0); - -// Return the result -// - return shMat->GetItem(0, key, hash); -} - -/******************************************************************************/ -/* E x p o r t */ -/******************************************************************************/ - -template -bool XrdSsi::ShMap::Export() -{ -// Make sure we have memory -// - if (!shMat) {errno = ENOTCONN; return false;} - -// Return result -// - return shMat->Export(); -} - -/******************************************************************************/ -/* G e t */ -/******************************************************************************/ - -template -bool XrdSsi::ShMap::Get(const char *key, T &val) -{ -// Make sure we have memory -// - if (!shMat) {errno = ENOTCONN; return false;} - -// Compute hash if need be -// - int hash = (hashFunc ? hashFunc(key) : 0); - -// Return the result -// - return shMat->GetItem(&val, key, hash); -} - -/******************************************************************************/ -/* I n f o */ -/******************************************************************************/ - -template -int XrdSsi::ShMap::Info(const char *vname, char *buff, int blen) -{ -// Make sure we have memory -// - if (!shMat) {errno = ENOTCONN; return -1;} - -// Return the result -// - return shMat->Info(vname, buff, blen); -} - -/******************************************************************************/ -/* R e p */ -/******************************************************************************/ - -template -bool XrdSsi::ShMap::Rep(const char *key, T &val, T *valP) -{ -// Make sure we have memory -// - if (!shMat) {errno = ENOTCONN; return false;} - -// Compute hash if need be -// - int hash = (hashFunc ? hashFunc(key) : 0); - -// Rteurn the result -// - return shMat->AddItem(&val, valP, key, hash, true); -} - -/******************************************************************************/ -/* R e s i z e */ -/******************************************************************************/ - -template -bool XrdSsi::ShMap::Resize(XrdSsi::ShMap_Parms *parms) -{ - XrdSsi::ShMap_Parms rszParms(XrdSsi::ShMap_Parms::ForResize); - XrdSsiShMat::CRZParms crzParms; - -// First see if this object is already attached. -// - if (!shMat) {errno = ENOTCONN; return false;} - -// Check if we need to supply default parms else copy over the parm list -// - if (parms) - {crzParms.indexSz = parms->indexSize; - crzParms.maxKeys = parms->maxKeys; - crzParms.maxKLen = parms->maxKeyLen; - crzParms.mode = parms->mode; - if (parms->options & XrdSsi::ShMap_Parms::ReUse) - crzParms.reUse = - (parms->options & ~XrdSsi::ShMap_Parms::noReUse ? 1 : 0); - if (parms->options & XrdSsi::ShMap_Parms::MultW) - crzParms.multW = - (parms->options & ~XrdSsi::ShMap_Parms::noMultW ? 1 : 0); - } - -// Do the resize -// - return shMat->Resize(crzParms); -} - -/******************************************************************************/ -/* S y n c */ -/******************************************************************************/ - -template -bool XrdSsi::ShMap::Sync(XrdSsi::SyncOpt dosync, int syncqsz) -{ -// Make sure we have memory -// - if (!shMat) {errno = ENOTCONN; return false;} - -// Perform desired action -// - switch(dosync) - {case SyncOff: return shMat->Sync(false, false); - break; - case SyncOn: return shMat->Sync(true, false); - break; - case SyncAll: return shMat->Sync(true, true); - break; - case SyncNow: return shMat->Sync(); - break; - case SyncQSz: return shMat->Sync(syncqsz); - break; - default: errno = EINVAL; return false; - break; - } - return false; -} diff --git a/src/XrdSsi/XrdSsiShMat.cc b/src/XrdSsi/XrdSsiShMat.cc deleted file mode 100644 index a5cb573f5bf..00000000000 --- a/src/XrdSsi/XrdSsiShMat.cc +++ /dev/null @@ -1,57 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S s i S h M a t . h h */ -/* */ -/* (c) 2015 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -#include "XrdSsi/XrdSsiShMat.hh" -#include "XrdSsi/XrdSsiShMam.hh" - -/******************************************************************************/ -/* N e w */ -/******************************************************************************/ - -XrdSsiShMat *XrdSsiShMat::New(XrdSsiShMat::NewParms &parms) -{ -// If no implementation has been specified, use the default one -// - if (!parms.impl) parms.impl = "XrdSsiShMam"; - -// Allocate a new object of the desired implementation -// - if (!strcmp(parms.impl, "XrdSsiShMam")) - return new XrdSsiShMam(parms); - -// Add additional implemenation allocation here -// - -// We do not support the implementation -// - errno = ENOTSUP; - return 0; -} diff --git a/src/XrdSsi/XrdSsiShMat.hh b/src/XrdSsi/XrdSsiShMat.hh deleted file mode 100644 index e3228c68f19..00000000000 --- a/src/XrdSsi/XrdSsiShMat.hh +++ /dev/null @@ -1,366 +0,0 @@ -#ifndef __SSI_SHMAT__ -#define __SSI_SHMAT__ -/******************************************************************************/ -/* */ -/* X r d S s i S h M a t . h h */ -/* */ -/* (c) 2015 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -//----------------------------------------------------------------------------- -//! This class defines an abstract interface to a generic shared memory table -//! that stores key-value pairs. Since this class a pure abstract any number -//! of implementations may be supplied. The default one is named "XrdSsiShMam". -//----------------------------------------------------------------------------- - -class XrdSsiShMat -{ -public: - -//----------------------------------------------------------------------------- -//! Add an item to the shared memory table. -//! -//! @param newdata Pointer to the data to be added. -//! @param olddata Pointer to the area where the replaced data, if any, is -//! to be placed. -//! @param key The key associated with the data that is to be added. -//! @param hash The hash of the key that is to be used to lookup the key. -//! If the value is zero, an internal hash is computed. -//! @param replace When true, if the key exists, the data associated with the -//! key is replaced. When false, if the key exists, the addition -//! fails with errno set to EEXIST. -//! -//! @return true The addition/replacement succeeded. If the key was actually -//! replaced errno is set to EEXIST else it is set to 0. -//! @return false The addition/replacement failed; errno indicates reason. -//----------------------------------------------------------------------------- - -virtual bool AddItem(void *newdata, void *olddata, const char *key, - int hash=0, bool replace=false) = 0; - -//----------------------------------------------------------------------------- -//! Attach this object to the shared memory associated with this object at -//! creation time (see New() method). The attach operation waits until the -//! shared memory file is available. At that time, the file is memory mapped. -//! -//! @param tout The maximum number of seconds to wait for the shared -//! memory file to become available. If tout is zero, then -//! the file must be immediately available. If the value is -//! negative then the attach waits as long as needed. When tout -//! is reached the attach fails with errno set to ETIMEDOUT. -//! @param isrw When true the file is mapped to writable memory and allows -//! updates to the table. If false, the shared memory is made -//! read/only and may be significantly faster to access. -//! -//! @return true - The shared memory was attached, the table can be used. -//! @return false - The shared memory could not be attached, errno holds reason. -//----------------------------------------------------------------------------- - -virtual bool Attach(int tout, bool isrw=false) = 0; - -//----------------------------------------------------------------------------- -//! Create a new shared memory segment and associated file specified at object -//! instantiation (see New() method). Created segments must be made visible to -//! other processes using the Export() method. This allows the table to be -//! preloaded with initial values before the table is made visible. -//! -//! @param parms Create parameters described by CRParms. All uninitialized -//! members in this struct must be specified. -//! -//! @return true - The shared memory was attached, the table can be used. -//! @return false - The shared memory could not be attached, errno holds reason. -//----------------------------------------------------------------------------- - -struct CRZParms - {int indexSz; //!< Number of four byte hash table entries to create. - int maxKeys; //!< Maximum number of keys-value pairs expected in table. - int maxKLen; //!< The maximum acceptable key length. - int mode; //!< Filemode for the newly created file. - signed char multW; - //!< 1: Table can have multiple processes writing. - //!< 0: Table has only one process writing. - //!< -1: Use default or, for resize, previous setting. - signed char reUse; - //!< 1: Reuse deleted objects. - //!< 0: Never reuse deleted objects. - //!< -1: Use default or, for resize, previous setting. - char rsvd[6]; //!< Reserved for future options - - CRZParms() : indexSz(0), maxKeys(0), maxKLen(0), mode(0640), - multW(-1), reUse(-1) - {memset(rsvd, -1, sizeof(rsvd));} - ~CRZParms() {} - }; - -virtual bool Create(CRZParms &parms) = 0; - -//----------------------------------------------------------------------------- -//! Export a newly created table (i.e. see Create()). -//! -//! @return true - The table has been exported and is now visible to others. -//! @return false - The export failed, the errno value describes the reason. -//----------------------------------------------------------------------------- - -virtual bool Export() = 0; - -//----------------------------------------------------------------------------- -//! Delete an item from the table. -//! -//! @param data Pointer to the area to receive the value of the deleted key. -//! If the pointer is nil, then the key value is not returned. -//! @param key Pointer to the key of length <= MaxKLen. -//! @param hash The hash of the key that is to be used to lookup the key. -//! If the value is zero, an internal hash is computed. -//! -//! @return true - The key and data have been deleted. This is always returned -//! when data is nil. -//! @return false - The key and data either not deleted or the key does not -//! exist and data was not nil. The errno value decribes why. -//! Typical reason: the key was not found (errno == ENOENT). -//----------------------------------------------------------------------------- - -virtual bool DelItem(void *data, const char *key, int hash=0) = 0; - -//----------------------------------------------------------------------------- -//! Detach the map from the shared memory. -//----------------------------------------------------------------------------- - -virtual void Detach() = 0; - -//----------------------------------------------------------------------------- -//! Enumerate the keys and assocaited values. -//! -//! @param jar An opaque cookie that tracks progress. It should be -//! initialized to zero and otherwise not touched. The same jar -//! must be used for all successive calls. The jar is deleted -//! when false is returned (also see the next Enumerate method). -//! @param key The pointer variable where the location of the key is -//! returned upon success. -//! @param val The pointer variable where the location f the key values -//! is to be returned upon success. -//! -//! @return true A key and val pointers have been set. -//! Keys are returned in arbitrary order and not all keys may -//! be returned if the map is being actively updated. -//! @return false Key not returned; errno holds the reason. Typically, -//! ENOENT there ae no more keys. -//! Other errors may also be reflected. Whne false is returned -//! the jar is deleted and the pointer to it set to zero. -//----------------------------------------------------------------------------- - -virtual bool Enumerate(void *&jar, char *&key, void *&val) = 0; - -//----------------------------------------------------------------------------- -//! Terminate an active enumeration. An active enumeration is any enumeration -//! where the previous form of Enumerate() did not return false. Terminating -//! an active enumeration releases all of the enumeration resources allocated. -//! -//! @param jar The opaque cookie initialized by a previous call to -//! Enumerate() requesting the next key-value pair. -//! -//! @return true The enumeration has been terminated and the jar was -//! deleted and the jar pointer is set to zero. -//! Keys are returned in arbitrary order and not all keys may -//! be returned if the map is being actively updated. -//! @return false The jar pointer was zero; no enumeration was active. -//----------------------------------------------------------------------------- - -virtual bool Enumerate(void *&jar) = 0; - -//----------------------------------------------------------------------------- -//! Return information about the table. -//! -//! @param vname Pointer to the variable name whose value is wanted. A -//! particular implementation may not support all variable and -//! may support variables not listed here. These are for the -//! default implementation unless otherwise noted. They are: -//! hash - name of hash being used. -//! impl - The table implementation being used. -//! indexsz - Number of index entries -//! indexused - Number of index entries in use -//! keys - Number of keys in the bale. keys/indexused is -//! the hash table collision factor -//! keysfree - Number of keys that can still be added -//! maxkeylen - Longest allowed key -//! multw - If table supports multiple writers, else 0 -//! reuse - If table allows object reuse, else 0 -//! type - Name of the data type in the table. -//! typesz - The number of bytes in the table's data type -//! -//! @param buff - Pointer to the buffer to receive text values. -//! Variables that return text are: hash, impl, -//! and type. A buffer must be supplied in any -//! of these variables are requested. If buff is -//! nill or too small a -1 is returned with errno -//! set to EMSGSIZE. -//! -//! @param blen The length of the buffer. -//! -//! @return >=0 - The variable's value or the length of the text information. -//! @return < 0 - The variable's value could not be returned; errno has the -//! error code describing the reason, typically ENOSYS. -//----------------------------------------------------------------------------- - -virtual int Info(const char *vname, char *buff=0, int blen=0) = 0; - -//----------------------------------------------------------------------------- -//! Get an item from the table. -//! -//! @param data Pointer to an area to receive the value associated with key. -//! If the pointer is nil, then the key value is not returned. -//! @param key Pointer to the key of length <= MaxKLen. -//! @param hash The hash of the key that is to be used to lookup the key. -//! If the value is zero, an internal hash is computed. -//! -//! @return true - The key was found and if data was not nil, contains the -//! value associated key. -//! @return false - The key not found; errno holds the reason (typically is -//! ENOENT but may be some other reason). -//----------------------------------------------------------------------------- - -virtual bool GetItem(void *data, const char *key, int hash=0) = 0; - -//----------------------------------------------------------------------------- -//! Instantiate a shared memory object. -//! -//! @param parms The parameters to use when creating the table. Fields are: -//! impl Pointer to the name of the implementation that is -//! desired. The default implementation (XrdSsiShMam) -//! is used if nil. All processes must specify the same -//! implementation that was used to create the table via -//! the Create() method. If specified it must not exceed -//! 63 characters. -//! path Pointer to the file that is backing the table. The -//! path is used to locate the table in memory. -//! typeID A text name of the data type in the table. All -//! processes must specify the same typeID that the -//! table was created with using Create(). Specify text -//! less than 64 characters. -//! typesz The number of bytes occupied by the data type in -//! the table. -//! hashID A 4-characters text name of the hash used in the -//! table represented as an int. All processes must -//! specify the same hashID that the table was created -//! with using Create(). -//! -//! @return !0 - Pointer to an instance of an XrdSsiShMat object. -//! @return false - The object could not instantiate because of an error; -//! errno holds the error code explaining why. -//----------------------------------------------------------------------------- - -struct NewParms - {const char *impl; //!< Implementation name - const char *path; //!< The path to the backing file for the table - const char *typeID; //!< The name of the type associated with the key - int typeSz; //!< Size of the type in bytes - int hashID; //!< The hash being used (0 means the default) - }; - -static -XrdSsiShMat *New(NewParms &parms); - -//----------------------------------------------------------------------------- -//! Resize a shared memory segment and associated file specified at object -//! instantiation (see New() method). Resizing is implementation specific but -//! may involve creating a new table and exporting it. -//! -//! @param parms Resize parameters. See the CRZParms struct for details. For -//! resize, zero values or unspecified flags use the existing -//! table values. -//! -//! @return true - The shared memory was resized, the table can be used. -//! @return false - The shared memory could not be resized, errno holds reason. -//----------------------------------------------------------------------------- - -virtual bool Resize(CRZParms &parms) = 0; - -//----------------------------------------------------------------------------- -//! Synchronize all modified pages to the associated backing file. -//! -//! @return true - Operation completed successfully. -//! @return false - Operation failed; errno holds the error code explaining why. -//----------------------------------------------------------------------------- - -virtual bool Sync() = 0; - -//----------------------------------------------------------------------------- -//! Turn memry synchronization on or off. -//! -//! @param dosync When true, modified table pages are written back to the -//! backing file. The synchronous or async nature of the -//! write is controlled by the second parameter. When false, -//! memory-file synchronization is turned off (initial setting). -//! @param syncdo When true, synchronization is done in the forground. That -//! is, a call triggering a sync will not return until complete. -//! When false, synchronization is done in the background. -//! -//! @return true - Operation completed successfully. -//! @return false - Operation failed; errno holds the error code explaining why. -//----------------------------------------------------------------------------- - -virtual bool Sync(bool dosync, bool syncdo=false) = 0; - -//----------------------------------------------------------------------------- -//! Set the sync defer queue size. -//! -//! @param synqsz The maximum number of modified pages before flushing. -//! -//! @return true - Operation completed successfully. -//! @return false - Operation failed; errno holds the error code explaining why. -//----------------------------------------------------------------------------- - -virtual bool Sync(int synqsz) = 0; - -//----------------------------------------------------------------------------- -//! Constructor (arguments the same as for New()) -//----------------------------------------------------------------------------- - - XrdSsiShMat(NewParms &parms) - : shmImpl(strdup(parms.impl)), shmPath(strdup(parms.path)), - shmType(strdup(parms.typeID)), shmTypeSz(parms.typeSz), - shmHash(parms.hashID) - {} - -//----------------------------------------------------------------------------- -//! Destructor. Warning, your destructor should call your own Detach()! -//----------------------------------------------------------------------------- - -virtual ~XrdSsiShMat() {if (shmImpl) free(shmImpl); - if (shmPath) free(shmPath); - if (shmType) free(shmType); - } - -protected: - -char *shmImpl; -char *shmPath; -char *shmType; -int shmTypeSz; -int shmHash; -}; -#endif diff --git a/src/XrdSsi/XrdSsiStat.cc b/src/XrdSsi/XrdSsiStat.cc deleted file mode 100644 index 12979ba81f5..00000000000 --- a/src/XrdSsi/XrdSsiStat.cc +++ /dev/null @@ -1,146 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S s i S t a t . c c */ -/* */ -/* (c) 2014 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include - -#include "XrdVersion.hh" -#include "XrdOss/XrdOss.hh" -#include "XrdOss/XrdOssStatInfo.hh" -#include "XrdOuc/XrdOucEnv.hh" -#include "XrdOuc/XrdOucPList.hh" -#include "XrdSsi/XrdSsiProvider.hh" -#include "XrdSsi/XrdSsiSfsConfig.hh" -#include "XrdSsi/XrdSsiService.hh" -#include "XrdSys/XrdSysError.hh" - -//------------------------------------------------------------------------------ -//! This file defines a default plug-in that can be used to handle stat() -//! calls for the Scalable Service Interface. -//------------------------------------------------------------------------------ - - -/******************************************************************************/ -/* E x t e r n s */ -/******************************************************************************/ - -namespace XrdSsi -{ -extern XrdSsiProvider *Provider; - -extern XrdOucPListAnchor FSPath; - -extern bool fsChk; - -extern XrdSysError Log; -}; - -using namespace XrdSsi; - -/******************************************************************************/ -/* X r d S s i S t a t I n f o */ -/******************************************************************************/ - -extern "C" -{ -int XrdSsiStatInfo(const char *path, struct stat *buff, - int opts, XrdOucEnv *envP, const char *lfn) -{ - static const int regFile = S_IFREG | S_IRUSR | S_IWUSR; - XrdSsiProvider::rStat rStat; - -// Check for stat changes -// - if (!buff) - {if (!Provider || (fsChk && FSPath.Find(lfn))) return 0; - if (opts == XrdOssStatEvent::FileRemoved) - Provider->ResourceRemoved(lfn); - else Provider->ResourceAdded(lfn); - return 0; - } - -// Check if this should be issued to the file system -// - if (fsChk && FSPath.Find(lfn)) return stat(path, buff); - -// Check resource availability -// - if (Provider && (rStat = Provider->QueryResource(path))) - {memset(buff, 0, sizeof(struct stat)); - buff->st_mode = regFile; - if (rStat == XrdSsiProvider::isPresent) return 0; - if (!(opts & XRDOSS_resonly)) {buff->st_mode |= S_IFBLK; return 0;} - } - -// Resource is not available -// - errno = ENOENT; - return -1; -} - -/******************************************************************************/ -/* X r d O s s S t a t I n f o I n i t */ -/******************************************************************************/ - -//------------------------------------------------------------------------------ -//! The following function is invoked by the plugin manager to obtain the -//! function that is to be used for stat() calls. -//------------------------------------------------------------------------------ - -XrdOssStatInfo2_t XrdOssStatInfoInit2(XrdOss *native_oss, - XrdSysLogger *Logger, - const char *config_fn, - const char *parms, - XrdOucEnv *envP) -{ - XrdSsiSfsConfig Config(true); - -// Setup the logger -// - Log.logger(Logger); - -// Process the configuration file so that we get the service provider object -// - if (!Config.Configure(config_fn) || !Config.Configure(envP)) - return 0; - -// Return the stat function -// - return (XrdOssStatInfo2_t)XrdSsiStatInfo; -} -}; - -/******************************************************************************/ -/* V e r s i o n I n f o r m a t i o n */ -/******************************************************************************/ - -XrdVERSIONINFO(XrdOssStatInfoInit,XrdSsiStat); diff --git a/src/XrdSsi/XrdSsiStream.hh b/src/XrdSsi/XrdSsiStream.hh deleted file mode 100644 index 481f34178c9..00000000000 --- a/src/XrdSsi/XrdSsiStream.hh +++ /dev/null @@ -1,162 +0,0 @@ -#ifndef __XRDSSISTREAM_HH__ -#define __XRDSSISTREAM_HH__ -/******************************************************************************/ -/* */ -/* X r d S s i S t r e a m . h h */ -/* */ -/* (c) 2013 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -#include "XrdSsi/XrdSsiErrInfo.hh" - -//----------------------------------------------------------------------------- -//! The XrdSsiStream class describes an object capable of providing data for a -//! response in real time. A pointer to such an object may be used to set this -//! response mode via XrdSsiResponder::SetResponse(). Two kinds of streams exist: -//! -//! Active the stream supplies the buffer that contains the response data. -//! The buffer is recycled via Buffer::Recycle() once the response data -//! is sent. Active streams are supported only server-side. -//! Passive the stream requires a buffer to be passed to it where response data -//! will be placed. Only passive streams are created on the client-side. -//! Passive streams can also work in asynchronous mode. However, async -//! mode is never used server-side but may be requested client-side. -//! -//! The type of stream must be declared at the time the stream is created. You -//! must supply an implementation for the associated stream type. -//----------------------------------------------------------------------------- - -class XrdSsiStream -{ -public: - -//----------------------------------------------------------------------------- -//! The Buffer object is returned by active streams as they supply the buffer -//! holding the requested data. Once the buffer is no longer needed it must be -//! recycled by calling Recycle(). -//----------------------------------------------------------------------------- - -class Buffer -{ -public: -virtual void Recycle() = 0; //!> Call to recycle the buffer when finished - -char *data; //!> -> Buffer containing the data -Buffer *next; //!> For chaining by buffer receiver - - Buffer(char *dp=0) : data(dp), next(0) {} -virtual ~Buffer() {} -}; - -//----------------------------------------------------------------------------- -//! Synchronously obtain data from an active stream (server-side only). -//! -//! @param eRef The object to receive any error description. -//! @param dlen input: the optimal amount of data wanted (this is a hint) -//! output: the actual amount of data returned in the buffer. -//! @param last input: should be set to false. -//! output: if true it indicates that no more data remains to be -//! returned either for this call or on the next call. -//! -//! @return =0 No more data remains or an error occurred: -//! last = true: No more data remains. -//! last = false: A fatal error occurred, eRef has the reason. -//! @return !0 Pointer to the Buffer object that contains a pointer to the -//! the data (see below). The buffer must be returned to the -//! stream using Buffer::Recycle(). The next member is usable. -//----------------------------------------------------------------------------- - -virtual Buffer *GetBuff(XrdSsiErrInfo &eRef, int &dlen, bool &last) - {eRef.Set("Not an active stream", EOPNOTSUPP); return 0;} - -//----------------------------------------------------------------------------- -//! Asynchronously obtain data from a passive stream (client-side only). -//! -//! @param eRef reference to where error information is to be placed for -//! encountered before during the stream initiation. When data is -//! ready for processing, the ProcessResponseData() callback is -//! called on the request associated with this stream. -//! Also see XrdSsiRequest::GetResponseData() helper method. -//! @param buff pointer to the buffer to receive the data. The buffer must -//! remain valid until ProcessResponse() is called. -//! @param blen the length of the buffer (i.e. maximum that can be returned). -//! -//! @return true The stream has been successfully scheduled to return the data. -//! @return false The stream could not be scheduled; eRef holds the reason. -//----------------------------------------------------------------------------- - -virtual bool SetBuff(XrdSsiErrInfo &eRef, char *buff, int blen) - {eRef.Set("Not a passive stream", EOPNOTSUPP); return false;} - -//----------------------------------------------------------------------------- -//! Synchronously obtain data from a passive stream (client- or server-side). -//! -//! @param eRef The object to receive any error description. -//! @param buff pointer to the buffer to receive the data. -//! request object is notified that the operation completed. -//! @param blen the length of the buffer (i.e. maximum that can be returned). -//! @param last input: should be set to false. -//! output: if true it indicates that no more data remains to be -//! returned either for this call or on the next call. -//! -//! @return >0 The number of bytes placed in buff. -//! @return =0 No more data remains and the stream becomes invalid. -//! @return <0 Fatal error occurred; eRef holds the reason. -//----------------------------------------------------------------------------- - -virtual int SetBuff(XrdSsiErrInfo &eRef, char *buff, int blen, bool &last) - {eRef.Set("Not a passive stream", EOPNOTSUPP); return 0;} - -//----------------------------------------------------------------------------- -//! Stream type descriptor: -//! -//! isActive - Active stream that supplies it own buffers with data. -//! GetBuff() & RetBuff() must be used. -//! -//! isPassive - Passive stream that provides data via a supplied buffer. -//! SetBuff() must be used. -//----------------------------------------------------------------------------- - - enum StreamType {isActive = 0, isPassive}; - -//----------------------------------------------------------------------------- -//! Get the stream type descriptor. -//! -//! @return The stream type, isActive or isPassive. -//----------------------------------------------------------------------------- - -StreamType Type() {return SType;} - - XrdSsiStream(StreamType stype) : SType(stype) {} - -virtual ~XrdSsiStream() {} - -protected: - -const StreamType SType; -}; -#endif diff --git a/src/XrdSsi/XrdSsiTaskReal.cc b/src/XrdSsi/XrdSsiTaskReal.cc deleted file mode 100644 index d18104ebec1..00000000000 --- a/src/XrdSsi/XrdSsiTaskReal.cc +++ /dev/null @@ -1,767 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S s i T a s k R e a l . c c */ -/* */ -/* (c) 2013 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -#include "XrdSsi/XrdSsiAtomics.hh" -#include "XrdSsi/XrdSsiRequest.hh" -#include "XrdSsi/XrdSsiRRAgent.hh" -#include "XrdSsi/XrdSsiRRInfo.hh" -#include "XrdSsi/XrdSsiScale.hh" -#include "XrdSsi/XrdSsiSessReal.hh" -#include "XrdSsi/XrdSsiTaskReal.hh" -#include "XrdSsi/XrdSsiTrace.hh" -#include "XrdSsi/XrdSsiUtils.hh" - -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysHeaders.hh" -#include "Xrd/XrdScheduler.hh" - -using namespace XrdSsi; - -/******************************************************************************/ -/* L o c a l M a c r o s */ -/******************************************************************************/ - -#define DUMPIT(x,y) XrdSsiUtils::b2x(x,y,hexBuff,sizeof(hexBuff),dotBuff)<SendError(); - delete this; - } - - SchedEmsg(XrdSsiTaskReal *tP) : taskP(tP) {} - ~SchedEmsg() {} - -private: -XrdSsiTaskReal *taskP; -}; -} - -/******************************************************************************/ -/* Private: A s k 4 R e s p */ -/******************************************************************************/ - -// Called with session mutex locked and returns with it unlocked! - -bool XrdSsiTaskReal::Ask4Resp() -{ - EPNAME("Ask4Resp"); - - XrdCl::XRootDStatus epStatus; - XrdSsiRRInfo rInfo; - XrdCl::Buffer qBuff(sizeof(unsigned long long)); - -// Disable read recovery -// - sessP->epFile.SetProperty(pName, pValue); - -// Compose request to wait for the response -// - rInfo.Id(tskID); rInfo.Cmd(XrdSsiRRInfo::Rwt); - memcpy(qBuff.GetBuffer(), rInfo.Data(), sizeof(long long)); - -// Do some debugging -// - DEBUG("Calling fcntl id=" <epFile.Fcntl(qBuff, (ResponseHandler *)this, tmOut); - -// Dianose any errors. If any occurred we simply return an error response but -// otherwise let this go as it really is not a logic error. -// - if (!epStatus.IsOK()) return RespErr(&epStatus); - mhPend = true; - defer = false; - tStat = isSync; - sessP->UnLock(); - return true; -} - -/******************************************************************************/ -/* D e t a c h */ -/******************************************************************************/ - -void XrdSsiTaskReal::Detach(bool force) -{ tStat = isDead; - if (force) sessP = &voidSession; -} - -/******************************************************************************/ -/* F i n i s h e d */ -/******************************************************************************/ - -// Note that if we are called then Finished() must have been called while we -// were still in the open phase. - -void XrdSsiTaskReal::Finished(XrdSsiRequest &rqstR, - const XrdSsiRespInfo &rInfo, bool cancel) -{ - EPNAME("TaskReqFin"); - XrdSsiMutexMon rHelp(sessP->MutexP()); - -// Do some debugging -// - DEBUG("Request="<<&rqstR<<" cancel="<Get(buffP); - if (!buffP || !(cdP = buffP->GetBuffer())) - {DEBUG("Responding with stream id=" <GetSize()) < sizeof(XrdSsiRRInfoAttn)) return isBad; - mdP = (XrdSsiRRInfoAttn *)cdP; - mdL = ntohl(mdP->mdLen); - pxL = ntohs(mdP->pfxLen); - dbL = n - mdL - pxL; - if (pxL < sizeof(XrdSsiRRInfoAttn) || dbL < 0) return isBad; - -// This may be an alert message, check for that now -// - if (mdP->tag == XrdSsiRRInfoAttn::alrtResp) - {char hexBuff[16],dotBuff[4]; - dbuff = cdP+pxL; dbL = mdL; - DEBUG("Posting " <UnLock(); - wSem.Wait(); - sessP->Lock(); - } - -// If we are here then the request is potentially still active at the server. -// We will send a synchronous cancel request. It shouldn't take long. Note -// that, for now, we ignore any errors as we don't have a recovery plan. -// - rInfo.Id(tskID); rInfo.Cmd(XrdSsiRRInfo::Can); - DEBUG("Sending cancel request id=" <epFile.Truncate(rInfo.Info(), tmOut); - - -// If we are in the message handler or if we have a message pending, then -// the message handler will dispose of the task. -// - tStat = isDead; - return !(mhPend || defer); -} - -/******************************************************************************/ -/* R e d r i v e */ -/******************************************************************************/ - -void XrdSsiTaskReal::Redrive() -{ - EPNAME("TaskRedrive"); - XrdSsiRequest::PRD_Xeq prdVal; - bool last = tStat == isDone; - -// Simply call data response method again -// - sessP->UnLock(); - DEBUG("Redriving ProcessResponseData; len="<UnLock(); - -// Reflect an error to the request object. -// - SetErrResponse(eTxt.c_str(), eNum); - return false; -} - -/******************************************************************************/ -/* S c h e d E r r o r */ -/******************************************************************************/ - -// Called with sessMutex locked! - -void XrdSsiTaskReal::SchedError(XrdSsiErrInfo *eInfo) -{ -// Copy the error information if so supplied.s -// - if (eInfo) errInfo = *eInfo; - -// Schedule the error to avoid lock clashes (make sure Finished calls defered) -// - XrdSsi::schedP->Schedule((XrdJob *)(new SchedEmsg(this))); - defer = true; -} - -/******************************************************************************/ -/* S e n d E r r o r */ -/******************************************************************************/ - -void XrdSsiTaskReal::SendError() -{ -// Lock the associated session -// - sessP->Lock(); - -// If there was no call to finished then we need to call to send an error -// response which will precipitate a finished call (or should). -// - if (tStat != isDead) - {int eNum; - const char *eTxt = errInfo.Get(eNum).c_str(); - sessP->UnLock(); - SetErrResponse(eTxt, eNum); - sessP->Lock(); - defer = false; - if (tStat != isDead) - {sessP->UnLock(); - return; - } - } - -// It is now safe to finish this up -// - sessP->UnLock(); - sessP->TaskFinished(this); -} - -/******************************************************************************/ -/* S e n d R e q u e s t */ -/******************************************************************************/ - -// Called with sessMutex locked! - -bool XrdSsiTaskReal::SendRequest(const char *node) -{ - XrdCl::XRootDStatus Status; - XrdSsiRRInfo rrInfo; - char *reqBuff; - int reqBlen; - -// We must be in pend state to send a request. If we are not then the request -// must have been cancelled. It also means we have a logic error since this -// should never have happened. Issue a message and ignore this request. -// - if (tStat != isPend) - {Log.Emsg("SendRequest", "Invalid state", statName[tStat], - "; should be isPend!"); - return false; - } - -// Establish the endpoint -// - XrdSsiRRAgent::SetNode(XrdSsiRRAgent::Request(this), node); - -// Get the request information -// - reqBuff = XrdSsiRRAgent::Request(this)->GetRequest(reqBlen); - -// Construct the info for this request -// - rrInfo.Id(tskID); - rrInfo.Size(reqBlen); - tStat = isWrite; - -// If we are writing a zero length message, we must fake a request as zero -// zero length messages are normally deep-sixed. -// - if (!reqBlen) - {reqBuff = &zedData; - reqBlen = 1; - } - -// Issue the write -// - Status = sessP->epFile.Write(rrInfo.Info(), (uint32_t)reqBlen, reqBuff, - (XrdCl::ResponseHandler *)this, tmOut); - -// Determine ending status. If it's bad, schedule an error. Note that calls to -// Finished() will be defered until the error thread gets control. -// - if (!Status.IsOK()) - {XrdSsiUtils::SetErr(Status, errInfo); - SchedError(); - return false; - } - -// Indicate a message handler call outstanding -// - mhPend = true; - return true; -} - -/******************************************************************************/ -/* S e t B u f f */ -/******************************************************************************/ - -int XrdSsiTaskReal::SetBuff(XrdSsiErrInfo &eRef, - char *buff, int blen, bool &last) -{ - EPNAME("TaskSetBuff"); - XrdSsiMutexMon rHelp(sessP->MutexP()); - XrdCl::XRootDStatus epStatus; - XrdSsiRRInfo rrInfo; - union {uint32_t ubRead; int ibRead;}; - -// Check if this is a proper call or we have reached EOF -// - DEBUG("Sync Status=" <epFile.Read(rrInfo.Info(),(uint32_t)blen,buff,ubRead,tmOut); - if (epStatus.IsOK()) - {if (ibRead < blen) {tStat = isDone; last = true;} - return ibRead; - } - -// We failed, return an error -// - XrdSsiUtils::SetErr(epStatus, eRef); - tStat = isDone; - DEBUG("Task Sync SetBuff error id=" <MutexP()); - XrdCl::XRootDStatus epStatus; - XrdSsiRRInfo rrInfo; - -// Check if this is a proper call or we have reached EOF -// - DEBUG("Async Status=" <epFile.Read(rrInfo.Info(), (uint32_t)blen, buff, - (XrdCl::ResponseHandler *)this, tmOut); - -// If success then indicate we are pending and return -// - if (epStatus.IsOK()) {mhPend = true; return true;} - -// We failed, return an error -// - XrdSsiUtils::SetErr(epStatus, eRef); - tStat = isDone; - DEBUG("Task Async SetBuff error id=" <Lock(); - -// Check if finished has been called while we were defered -// - if (tStat == isDead) - {DEBUG("Task Handler calling TaskFinished."); - sessP->UnLock(); - sessP->TaskFinished(this); - return false; - } - -// We can continue, no deferals are needed at this point -// - defer = false; - sessP->UnLock(); - return true; -} - -/******************************************************************************/ -/* X e q E v e n t */ -/******************************************************************************/ - -bool XrdSsiTaskReal::XeqEvent(XrdCl::XRootDStatus *status, - XrdCl::AnyObject **respP) -{ - EPNAME("TaskXeqEvent"); - - XrdCl::AnyObject *response = *respP; - XrdSsiRespInfoMsg *aMsg; - char *dBuff; - union {uint32_t ubRead; int ibRead;}; - int dLen; - XrdSsiRequest::PRD_Xeq prdVal; - bool last, aOK = status->IsOK(); - -// Obtain a lock and indicate the any Finish() calls should be defered until -// we return from this method. The reason is that any callback that we do here -// may precipitate a Finish() call not to mention some other thread doing so. -// - sessP->Lock(); - defer = true; - mhPend = false; - -// Do some debugging -// - DEBUG(" sess="<<(sessP==&voidSession?"no":"ok") <<" id=" <UnLock(); - SetErrResponse("Missing response", EFAULT); - } - return XeqEnd(true); - - case isReady: - break; - - case isDead: - if (sessP != &voidSession) - {DEBUG("Task Handler calling TaskFinished."); - sessP->UnLock(); - sessP->TaskFinished(this); - } else { - DEBUG("Deleting task."); - sessP->UnLock(); - delete this; - } - return false; - - default: char mBuff[32]; - snprintf(mBuff, sizeof(mBuff), "%d", tStat); - Log.Emsg("TaskXeqEvent", "Invalid state", mBuff); - return false; - } - -// Handle incomming response data -// - if (!aOK || !response) - {ibRead = -1; - if (!aOK) XrdSsiUtils::SetErr(*status, XrdSsiRRAgent::ErrInfoRef(rqstP)); - else XrdSsiRRAgent::ErrInfoRef(rqstP).Set("Missing response", EFAULT); - } else { - XrdCl::ChunkInfo *cInfo = 0; - response->Get(cInfo); - ubRead = (cInfo ? cInfo->length : 0); - } - -// Reflect the response to the request as this was an async receive. We may not -// reference this object after the UnLock() as Finished() might be called. -// - if (ibRead < dataRlen) {tStat = isDone; dataRlen = ibRead;} - dBuff = dataBuff; - last = tStat == isDone; - sessP->UnLock(); - DEBUG("Calling ProcessResponseData; len="<. */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdSsi/XrdSsiErrInfo.hh" -#include "XrdSsi/XrdSsiEvent.hh" -#include "XrdSsi/XrdSsiPacer.hh" -#include "XrdSsi/XrdSsiStream.hh" -#include "XrdSsi/XrdSsiResponder.hh" - -class XrdSsiRequest; -class XrdSsiSessReal; -class XrdSysSemaphore; - -class XrdSsiTaskReal : public XrdSsiEvent, public XrdSsiPacer, - public XrdSsiResponder, public XrdSsiStream -{ -public: - -enum TaskStat {isPend=0, isWrite, isSync, isReady, isDone, isDead}; - -void Detach(bool force=false); - -void Finished( XrdSsiRequest &rqstR, - const XrdSsiRespInfo &rInfo, - bool cancel=false); - -void *Implementation() {return (void *)this;} - -bool Kill(); - -inline -int ID() {return tskID;} - -inline -void Init(XrdSsiRequest *rP, unsigned short tmo=0) - {rqstP = rP, tStat = isPend; tmOut = tmo; wPost = 0; - mhPend = false; defer = false; - attList.next = attList.prev = this; - if (mdResp) {delete mdResp; mdResp = 0;} - } - -void PostError(); - -void Redrive(); -const -char *RequestID() {return rqstP->GetRequestID();} - -void SchedError(XrdSsiErrInfo *eInfo=0); - -void SendError(); - -bool SendRequest(const char *node); - -int SetBuff(XrdSsiErrInfo &eRef, char *buff, int blen, bool &last); - -bool SetBuff(XrdSsiErrInfo &eRef, char *buff, int blen); - -void SetTaskID(short tid) {tskID = tid;} - -bool XeqEvent(XrdCl::XRootDStatus *status, XrdCl::AnyObject **respP); - - XrdSsiTaskReal(XrdSsiSessReal *sP, short tid) - : XrdSsiEvent("TaskReal"), - XrdSsiStream(XrdSsiStream::isPassive), - sessP(sP), mdResp(0), wPost(0), tskID(tid), - mhPend(false), defer(false) - {} - - ~XrdSsiTaskReal() {if (mdResp) delete mdResp;} - -struct dlQ {XrdSsiTaskReal *next; XrdSsiTaskReal *prev;}; -dlQ attList; - -enum respType {isBad=0, isAlert, isData, isStream}; - -private: - -bool Ask4Resp(); -respType GetResp(XrdCl::AnyObject **respP, char *&dbuf, int &dlen); -bool RespErr(XrdCl::XRootDStatus *status); -bool XeqEnd(bool getLock); - -XrdSsiErrInfo errInfo; -XrdSsiSessReal *sessP; -XrdSsiRequest *rqstP; -XrdCl::AnyObject *mdResp; -XrdSysSemaphore *wPost; -char *dataBuff; -int dataRlen; -TaskStat tStat; -unsigned short tmOut; -short tskID; -bool mhPend; -bool defer; -}; -#endif diff --git a/src/XrdSsi/XrdSsiTrace.hh b/src/XrdSsi/XrdSsiTrace.hh deleted file mode 100644 index b15cc534485..00000000000 --- a/src/XrdSsi/XrdSsiTrace.hh +++ /dev/null @@ -1,59 +0,0 @@ -#ifndef _XRDSSI_TRACE_H -#define _XRDSSI_TRACE_H -/******************************************************************************/ -/* */ -/* X r d S s i T r a c e . h h */ -/* */ -/* (c) 2013 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#define TRACESSI_ALL 0xffff -#define TRACESSI_Debug 0x0001 - -#ifndef NODEBUG - -#include "XrdSys/XrdSysTrace.hh" - -#define QTRACE(act) Trace.What & TRACESSI_ ## act - -#define DEBUG(y) if (Trace.What & TRACESSI_Debug) \ - {Trace.Beg(tident, epname) <. */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include - -#include "XProtocol/XProtocol.hh" - -#include "Xrd/XrdScheduler.hh" - -#include "XrdCl/XrdClXRootDResponses.hh" - -#include "XrdOuc/XrdOucERoute.hh" -#include "XrdOuc/XrdOucErrInfo.hh" - -#include "XrdSfs/XrdSfsInterface.hh" - -#include "XrdSsi/XrdSsiAtomics.hh" -#include "XrdSsi/XrdSsiErrInfo.hh" -#include "XrdSsi/XrdSsiRequest.hh" -#include "XrdSsi/XrdSsiResponder.hh" -#include "XrdSsi/XrdSsiRRAgent.hh" -#include "XrdSsi/XrdSsiUtils.hh" - -#include "XrdSys/XrdSysError.hh" - -/******************************************************************************/ -/* G l o b a l s */ -/******************************************************************************/ - -namespace XrdSsi -{ -extern XrdSysError Log; -extern XrdScheduler *schedP; -}; - -using namespace XrdSsi; - -/******************************************************************************/ -/* L o c a l C l a s s e s */ -/******************************************************************************/ - -class PostError : public XrdJob, public XrdSsiResponder -{ -public: - -void DoIt() {myMutex.Lock(); - if ( isActive) SetErrResponse(eTxt, eNum); - if (!isActive) delete this; - else {isActive = false; - myMutex.UnLock(); - } - } - -virtual void Finished( XrdSsiRequest &rqstR, - const XrdSsiRespInfo &rInfo, - bool cancel=false) - {UnBindRequest(); - myMutex.Lock(); - if (!isActive) delete this; - else {isActive = false; - myMutex.UnLock(); - } - } - - PostError(XrdSsiRequest *rP, char *emsg, int ec) - : myMutex(XrdSsiMutex::Recursive), - reqP(rP), eTxt(emsg), eNum(ec), isActive(true) - {XrdSsiRRAgent::SetMutex(rP, &myMutex); - BindRequest(*reqP); - } - -virtual ~PostError() {myMutex.UnLock(); - if (eTxt) free(eTxt); - } - -private: -XrdSsiMutex myMutex; // Allow possible rentry via SetErrResponse() -XrdSsiRequest *reqP; -char *eTxt; -int eNum; -bool isActive; -}; - -/******************************************************************************/ -/* b 2 x */ -/******************************************************************************/ - -char *XrdSsiUtils::b2x(const char *ibuff, int ilen, char *obuff, int olen, - char xbuff[4]) -{ - static char hv[] = "0123456789abcdef"; - char *oP = obuff; - - // Gaurd against too short of an output buffer (minimum if 3 bytes) - // - if (olen < 3) - {*obuff = 0; - strcpy(xbuff, "..."); - return obuff; - } - - // Make sure we have something to format - // - if (ilen < 1) - {*obuff = 0; - *xbuff = 0; - return obuff; - } - - // Do length adjustment, as needed - // - if (ilen*2 < olen) *xbuff = 0; - else {ilen = (olen-1)/2; - strcpy(xbuff, "..."); - } - - // Format the data. We know it will fit with a trailing null byte. - // - for (int i = 0; i < ilen; i++) { - *oP++ = hv[(ibuff[i] >> 4) & 0x0f]; - *oP++ = hv[ ibuff[i] & 0x0f]; - } - *oP = '\0'; - return obuff; -} - -/******************************************************************************/ -/* E m s g */ -/******************************************************************************/ - -int XrdSsiUtils::Emsg(const char *pfx, // Message prefix value - int ecode, // The error code - const char *op, // Operation being performed - const char *path, // Operation target - XrdOucErrInfo &eDest) // Plase to put error -{ - char buffer[2048]; - -// Get correct error code and path -// - if (ecode < 0) ecode = -ecode; - if (!path) path = "???"; - -// Format the error message -// - XrdOucERoute::Format(buffer, sizeof(buffer), ecode, op, path); - -// Put the message in the log -// - Log.Emsg(pfx, eDest.getErrUser(), buffer); - -// Place the error message in the error object and return -// - eDest.setErrInfo(ecode, buffer); - return SFS_ERROR; -} - - -/******************************************************************************/ -/* G e t E r r */ -/******************************************************************************/ - -int XrdSsiUtils::GetErr(XrdCl::XRootDStatus &Status, std::string &eText) -{ - -// If this is an xrootd error then get the xrootd generated error -// - if (Status.code == XrdCl::errErrorResponse) - {eText = Status.GetErrorMessage(); - return MapErr(Status.errNo); - } - -// Internal error, we will need to copy strings here -// - eText = Status.ToStr(); - return (Status.errNo ? Status.errNo : EFAULT); -} - -/******************************************************************************/ -/* M a p E r r */ -/******************************************************************************/ - -int XrdSsiUtils::MapErr(int xEnum) -{ - switch(xEnum) - {case kXR_NotFound: return ENOENT; - case kXR_NotAuthorized: return EACCES; - case kXR_IOError: return EIO; - case kXR_NoMemory: return ENOMEM; - case kXR_NoSpace: return ENOSPC; - case kXR_ArgTooLong: return ENAMETOOLONG; - case kXR_noserver: return EHOSTUNREACH; - case kXR_NotFile: return ENOTBLK; - case kXR_isDirectory: return EISDIR; - case kXR_FSError: return ENOSYS; - default: return ECANCELED; - } -} - -/******************************************************************************/ -/* R e t E r r */ -/******************************************************************************/ - -void XrdSsiUtils::RetErr(XrdSsiRequest &reqP, const char *eTxt, int eNum) -{ - -// Schedule an error callback -// - XrdSsi::schedP->Schedule(new PostError(&reqP, strdup(eTxt), eNum)); -} - -/******************************************************************************/ -/* S e t E r r */ -/******************************************************************************/ - -void XrdSsiUtils::SetErr(XrdCl::XRootDStatus &Status, XrdSsiErrInfo &eInfo) -{ - -// If this is an xrootd error then get the xrootd generated error -// - if (Status.code == XrdCl::errErrorResponse) - {eInfo.Set(Status.GetErrorMessage().c_str(), MapErr(Status.errNo)); - } else { - eInfo.Set(Status.ToStr().c_str(), (Status.errNo ? Status.errNo:EFAULT)); - } -} diff --git a/src/XrdSsi/XrdSsiUtils.hh b/src/XrdSsi/XrdSsiUtils.hh deleted file mode 100644 index 43af9256aa8..00000000000 --- a/src/XrdSsi/XrdSsiUtils.hh +++ /dev/null @@ -1,64 +0,0 @@ -#ifndef __SSI_UTILS_H__ -#define __SSI_UTILS_H__ -/******************************************************************************/ -/* */ -/* X r d S s i U t i l s . h h */ -/* */ -/* (c) 2015 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -namespace XrdCl {class XRootDStatus;} - -class XrdOucErrInfo; -class XrdSsiErrInfo; -class XrdSsiRequest; - -class XrdSsiUtils -{ -public: - -static char *b2x(const char *ibuff, int ilen, char *obuff, int olen, - char xbuff[4]); - -static int Emsg(const char *pfx, // Message prefix value - int ecode, // The error code - const char *op, // Operation being performed - const char *path, // Operation target - XrdOucErrInfo &eDest); // Plase to put error - -static int GetErr(XrdCl::XRootDStatus &Status, std::string &eText); - -static int MapErr(int xEnum); - -static void RetErr(XrdSsiRequest &reqP, const char *eTxt, int eNum); - -static void SetErr(XrdCl::XRootDStatus &Status, XrdSsiErrInfo &eInfo); - - XrdSsiUtils() {} - ~XrdSsiUtils() {} -}; -#endif diff --git a/src/XrdSut/XrdSutAux.cc b/src/XrdSut/XrdSutAux.cc deleted file mode 100644 index cc0723c6e1a..00000000000 --- a/src/XrdSut/XrdSutAux.cc +++ /dev/null @@ -1,646 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S u t A u x . c c */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Gerri Ganis for CERN */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "XrdSys/XrdSysLogger.hh" -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysPwd.hh" -#include "XrdOuc/XrdOucString.hh" - -#include "XrdSut/XrdSutAux.hh" -#include "XrdSut/XrdSutTrace.hh" - -static const char *gXRSBucketTypes[] = { - "kXRS_none", - "kXRS_inactive", - "kXRS_cryptomod", - "kXRS_main", - "kXRS_srv_seal", - "kXRS_clnt_seal", - "kXRS_puk", - "kXRS_cipher", - "kXRS_rtag", - "kXRS_signed_rtag", - "kXRS_user", - "kXRS_host", - "kXRS_creds", - "kXRS_message", - "kXRS_srvID", - "kXRS_sessionID", - "kXRS_version", - "kXRS_status", - "kXRS_localstatus", - "kXRS_othercreds", - "kXRS_cache_idx", - "kXRS_clnt_opts", - "kXRS_error_code", - "kXRS_timestamp", - "kXRS_x509", - "kXRS_issuer_hash", - "kXRS_x509_req", - "kXRS_cipher_alg", - "kXRS_md_alg", - "kXRS_afsinfo", - "kXRS_reserved" -}; - -// -// For error logging and tracing -static XrdSysLogger Logger; -static XrdSysError eDest(0,"sut_"); -XrdOucTrace *sutTrace = 0; - -/******************************************************************************/ -/* X r d S u t S e t T r a c e */ -/******************************************************************************/ -//______________________________________________________________________________ -void XrdSutSetTrace(kXR_int32 trace) -{ - // Set trace flags according to 'trace' - - // - // Initiate error logging and tracing - eDest.logger(&Logger); - if (!sutTrace) - sutTrace = new XrdOucTrace(&eDest); - if (sutTrace) { - // Set debug mask - sutTrace->What = 0; - // Low level only - if ((trace & sutTRACE_Notify)) - sutTrace->What |= sutTRACE_Notify; - // Medium level - if ((trace & sutTRACE_Debug)) - sutTrace->What |= (sutTRACE_Notify | sutTRACE_Debug); - // High level - if ((trace & sutTRACE_Dump)) - sutTrace->What |= sutTRACE_ALL; - } -} - -/******************************************************************************/ -/* X r d S u t B u c k S t r */ -/******************************************************************************/ -//______________________________________________________________________________ -const char *XrdSutBuckStr(int kbck) -{ - // Return bucket string - static const char *ukn = "Unknown"; - - kbck = (kbck < 0) ? 0 : kbck; - kbck = (kbck > kXRS_reserved) ? 0 : kbck; - kbck = (kbck >= kXRS_cryptomod) ? (kbck - kXRS_cryptomod + 2) : kbck; - - if (kbck < 0 || kbck > (kXRS_reserved - kXRS_cryptomod + 2)) - return ukn; - else - return gXRSBucketTypes[kbck]; -} - -/******************************************************************************/ -/* X r d S u t M e m S e t */ -/******************************************************************************/ -//______________________________________________________________________________ -volatile void *XrdSutMemSet(volatile void *dst, int c, int len) -{ - // To avoid problems due to compiler optmization - // Taken from Viega&Messier, "Secure Programming Cookbook", O'Really, #13.2 - // (see discussion there) - volatile char *buf; - - for (buf = (volatile char *)dst; len; buf[--len] = c) {} - return dst; -} - -#ifndef USE_EXTERNAL_GETPASS -/******************************************************************************/ -/* X r d S u t G e t P a s s */ -/******************************************************************************/ -//_____________________________________________________________________________ -int XrdSutGetPass(const char *prompt, XrdOucString &passwd) -{ - // Get password from command line using getpass - // *** Use only if you cannot provide a better alternative *** - // User will be prompted for 'prompt'; the entered password - // is returned in 'passwd'. - // Returns 0 if ok, -1 if any error occurs. - EPNAME("GetPass"); - - char *pw = getpass(prompt); - if (pw) { - // Get rid of special chars, if any - int k = 0, i = 0, len = strlen(pw); - for (; i 0x20) pw[k++] = pw[i]; - pw[k] = 0; - passwd = pw; - XrdSutMemSet((volatile void *)pw,0,len); - } else { - DEBUG("error from getpass"); - return -1; - } - return 0; -} -#endif - -/******************************************************************************/ -/* X r d S u t G e t L i n e */ -/******************************************************************************/ -int XrdSutGetLine(XrdOucString &line, const char *prompt) -{ - // Get line from main input stream. - // Prompt 'prompt' if this is defined. - // Returns number of chars entered. - // NB: at most XrdSutMAXBUF-1 chars will be accepted - char bin[XrdSutMAXBUF] = {0}; - - // Print prompt, if requested - if (prompt) - cout << prompt; - - // Get line - cin.getline(bin,XrdSutMAXBUF-1); - - // Fill input - line = bin; - - return line.length(); -} - -/******************************************************************************/ -/* X r d S u t A s k C o n f i r m */ -/******************************************************************************/ -bool XrdSutAskConfirm(const char *msg1, bool defact, const char *msg2) -{ - // Prompt for confirmation of action - // If defined, msg1 is printed as prompt, followed by the default action - // ( [y] == do-act, for defact = true; - // [n] == do-not-act, for defact = false) - // If defined, msg2 is printed before prompting. - - bool rc = defact; - - if (msg2) - cout << msg2; - XrdOucString ask; - XrdOucString prompt = defact ? " [y]: " : " [n]: "; - if (msg1) - prompt.insert(msg1,0); - XrdSutGetLine(ask,prompt.c_str()); - ask.lower(0); - if (ask.length()) { - if (defact && (ask == 'n' || ask == "no")) { - rc = 0; - } else if (!defact && (ask == 'y' || ask == "yes")) { - rc = 1; - } - } - // we are done - return rc; -} - -/******************************************************************************/ -/* X r d S u t T o H e x */ -/******************************************************************************/ -int XrdSutToHex(const char *in, int lin, char *out) -{ - // Content of lin bytes at in are transformed into an hexadecimal, - // null-terminated, string of length 2*lin; the result is returned - // in the buffer pointed by out, which must be allocated by the caller - // to contain at least 2*lin+1 bytes. - // Return 0 in case of success, -1 in case of error (errno set to EINVAL if - // any of in or out are not defined). - - if (!in || !out) { - errno = EINVAL; - return -1; - } - - int lbuf = 2*lin+1; - int i = 0; - out[0] = 0; - for ( ; i < lin; i++) - sprintf(out,"%s%02x",out,(0xFF & in[i])); - // Null termination - out[lbuf-1] = 0; - - // ok - return 0; -} - -/******************************************************************************/ -/* X r d S u t F r o m H e x */ -/******************************************************************************/ -int XrdSutFromHex(const char *in, char *out, int &lout) -{ - // Content of the hexadecimal, null-terminated, string at in, is - // transformed into lout bytes returned in out. - // The output buffer should be allocated by the caller to contain - // at least lin/2 bytes if lin=strlen(in) is even, and lin/2+1 bytes - // if lin is odd (in this case an additional char equal 0 is appended - // to in). - // Return 0 in case of success, -1 in case of error (errno set to EINVAL if - // any of in or out are not defined). - - lout = 0; - if (!in || !out) { - errno = EINVAL; - return -1; - } - - int lin = strlen(in); - char st[3] = {0}; - int i = 0, k = 0; - for ( ; i 1) - unam.assign(path, 1, iu-1); - sdir.erase(0, iu); - } else - sdir = '/'; - if (unam.length() > 0) { - struct passwd *pw; - XrdSysPwd thePwd(unam.c_str(), &pw); - if (!pw) { - DEBUG("cannot pwnam information for local user "<< - ((unam.length() > 0) ? unam : XrdOucString(""))); - return -errno; - } - home = pw->pw_dir; - } else - home = XrdSutHome(); - if (home.length() > 0) { - sdir.insert(home.c_str(),0); - path = sdir; - } - } else { - // relative path, add local dir - char *pwd = getenv("PWD"); - if (pwd) { - path.insert('/',0); - path.insert(pwd,0); - path.erase("//"); - } else { - DEBUG("PWD undefined "); - return -ENOENT; - } - } - return 0; -} - -/******************************************************************************/ -/* X r d S u t R e s o l v e */ -/******************************************************************************/ -int XrdSutResolve(XrdOucString &path, - const char *ho, const char *vo, const char *gr, const char *us) -{ - // Resolve templates , , , (if any) - // Returns 0 in case of success, -EINVAL if path is not defined. - - // Path must be defined - if (!path.length()) - return -EINVAL; - - // No templates, nothing to do - if (path.find("<") == STR_NPOS) - return 0; - - // Replace , if defined - if (ho && strlen(ho) > 0) path.replace("", ho); - - // Replace , if defined - if (vo && strlen(vo) > 0) path.replace("", vo); - - // Replace , if defined - if (gr && strlen(gr) > 0) path.replace("", gr); - - // Replace , if defined - if (us && strlen(us) > 0) path.replace("", us); - - // Done - return 0; -} - -/******************************************************************************/ -/* X r d S u t H o m e */ -/******************************************************************************/ -const char *XrdSutHome() -{ - // Gets the home directory preferentially from HOME or from pwd entry - EPNAME("Home"); - - // Use the save value, if any - static XrdOucString homedir; - if (homedir.length() <= 0) { - // Check the HOME environment variable - if (getenv("HOME")) - homedir = getenv("HOME"); - if (homedir.length() <= 0) { - struct passwd *pw; - XrdSysPwd thePwd(getuid(), &pw); - if (pw) homedir = pw->pw_dir; - } - if (homedir.length() <= 0) - DEBUG("Warning: home directory undefined! "); - } - - // Done - return homedir.c_str(); -} - -/******************************************************************************/ -/* X r d S u t M k d i r */ -/* */ -/******************************************************************************/ -int XrdSutMkdir(const char *dir, unsigned int mode, const char *opt) -{ - // Make directory dir - // mode specifies permissions - // opt == "-p" : make parent directories as needed - - if (!dir) { - errno = EINVAL; - return -1; - } - - if (!strncmp(opt,"-p",2)) { - // - // make also parent directories, if needed - XrdOucString dd(dir); - XrdSutExpand(dd); - if (dd[dd.length()-1] != '/') - dd.append('/'); - int lsl = dd.find('/',1); - while (lsl > -1) { - XrdOucString pd(dd,0,lsl-1); - struct stat st; - if (stat(pd.c_str(),&st) == -1) { - if (errno == ENOENT) { - // path does not exists: create it - if (mkdir(pd.c_str(),mode) != 0) - return -1; - } else { - return -1; - } - } - // Go to next - lsl = dd.find('/',lsl+1); - } - - } else { - return mkdir(dir,mode); - } - - return 0; -} - -/******************************************************************************/ -/* X r d S u t P a r s e T i m e */ -/* */ -/******************************************************************************/ -//______________________________________________________________________ -int XrdSutParseTime(const char *tstr, int opt) -{ - // Parse time string of the form "::..." - // with any integer and one of the following chars: - // 'y' for years - // 'd' for days - // 'h' for hours - // 'm' for minutes - // 's' for seconds - // (e.g. "34d:10h:20s") - // If opt == 1, assume a string in the form ".hh"[:[:]]" - // (e.g. "12:24:35" for 12 hours, 24 minutes and 35 secs) - // Return the corresponding number of seconds - EPNAME("ParseTime"); - - XrdOucString ts = tstr; - XrdOucString fr = ""; - int i = 0; - int tsec = 0; - // Parse list - if (ts.length()) { - int ls = 0; - int ld = ts.find(':',1); - ld = (ld == -1) ? ts.length() - 1 : ld; - while (ld >= ls) { - fr.assign(ts, ls, ld); - fr.erase(":"); - // Check this fraction - if (opt == 0) { - if (fr.length() > 1) { - // The unit must be known - char u = fr[fr.length()-1]; - fr.erase(fr.length()-1); - if (u == 'y') { - tsec += atoi(fr.c_str())*31536000; - } else if (u == 'd') { - tsec += atoi(fr.c_str())*86400; - } else if (u == 'h') { - tsec += atoi(fr.c_str())*3600; - } else if (u == 'm') { - tsec += atoi(fr.c_str())*60; - } else if (u == 's') { - tsec += atoi(fr.c_str()); - } else { - DEBUG("unknown unit: "<. */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#ifndef WIN32 -#include "XrdSys/XrdSysHeaders.hh" -#endif -#ifndef __XPROTOCOL_H -#include "XProtocol/XProtocol.hh" -#endif - -class XrdCryptoFactory; - -class XrdOucString; -class XrdSutBucket; -class XrdSutBuffer; - -/******************************************************************************/ -/* U t i l i t y D e f i n i t i o n s */ -/******************************************************************************/ - -#define XrdSutMAXBUF 4096 -#define XrdSutMAXPPT 512 -#define XrdSutMAXBUCKS 10 -#define XrdSutMAXINT64LEN 25 -#define XrdSutPRINTLEN 100 - -enum kXRSBucketTypes { - kXRS_none = 0, // end-of-vector - kXRS_inactive = 1, // inactive (dropped at serialization) - kXRS_cryptomod = 3000, // 3000 Name of crypto module to use - kXRS_main, // 3001 Main buffer - kXRS_srv_seal, // 3002 Server secrets sent back as they are - kXRS_clnt_seal, // 3003 Client secrets sent back as they are - kXRS_puk, // 3004 Public Key - kXRS_cipher, // 3005 Cipher - kXRS_rtag, // 3006 Random Tag - kXRS_signed_rtag, // 3007 Random Tag signed by the client - kXRS_user, // 3008 User name - kXRS_host, // 3009 Remote Host name - kXRS_creds, // 3010 Credentials (password, ...) - kXRS_message, // 3011 Message (null-terminated string) - kXRS_srvID, // 3012 Server unique ID - kXRS_sessionID, // 3013 Handshake session ID - kXRS_version, // 3014 Package version - kXRS_status, // 3015 Status code - kXRS_localstatus, // 3016 Status code(s) saved in sealed buffer - kXRS_othercreds, // 3017 Alternative creds (e.g. other crypto) - kXRS_cache_idx, // 3018 Cache entry index - kXRS_clnt_opts, // 3019 Client options, if any - kXRS_error_code, // 3020 Error code - kXRS_timestamp, // 3021 Time stamp - kXRS_x509, // 3022 X509 certificate - kXRS_issuer_hash, // 3023 Issuer hash - kXRS_x509_req, // 3024 X509 certificate request - kXRS_cipher_alg, // 3025 Cipher algorithm (list) - kXRS_md_alg, // 3026 MD algorithm (list) - kXRS_afsinfo, // 3027 AFS information - kXRS_reserved // Reserved -}; - -/******************************************************************************/ -/* X r d S u t B u c k S t r */ -/* Return bucket string */ -/******************************************************************************/ -const char *XrdSutBuckStr(int kbck); - -/******************************************************************************/ -/* E r r o r L o g g i n g / T r a c i n g F l a g s */ -/******************************************************************************/ -#define sutTRACE_ALL 0x0007 -#define sutTRACE_Dump 0x0004 -#define sutTRACE_Debug 0x0002 -#define sutTRACE_Notify 0x0001 - -/******************************************************************************/ -/* U t i l i t y F u n c t i o n s */ -/******************************************************************************/ - -/******************************************************************************/ -/* X r d S u t S e t T r a c e */ -/* */ -/* Set trace flags according to 'trace' */ -/* */ -/******************************************************************************/ -//______________________________________________________________________________ -void XrdSutSetTrace(kXR_int32 trace); - -/******************************************************************************/ -/* X r d S u t M e m S e t */ -/* */ -/* Memory setter avoiding problems from compiler optmization */ -/* Taken from Viega&Messier, "Secure Programming Cookbook", O'Really, #13.2 */ -/* */ -/******************************************************************************/ -volatile void *XrdSutMemSet(volatile void *dst, int c, int len); - -/******************************************************************************/ -/* X r d S u t G e t P a s s */ -/* */ -/* Getter for secret input: can be user defined */ -/* */ -/******************************************************************************/ -#ifdef USE_EXTERNAL_GETPASS -extern int XrdSutGetPass(const char *prompt, XrdOucString &passwd); -#else -int XrdSutGetPass(const char *prompt, XrdOucString &passwd); -#endif - -/******************************************************************************/ -/* X r d S u t G e t L i n e */ -/* */ -/* Get line from main input stream */ -/* */ -/******************************************************************************/ -int XrdSutGetLine(XrdOucString &line, const char *prompt = 0); - -/******************************************************************************/ -/* X r d S u t A s k C o n f i r m */ -/* */ -/* Ask confirmation to main input stream */ -/* */ -/******************************************************************************/ -bool XrdSutAskConfirm(const char *msg1, bool defact, const char *msg2 = 0); - -/******************************************************************************/ -/* X r d S u t T o H e x */ -/* */ -/* Transform a buffer in an hexadecimal string */ -/* */ -/******************************************************************************/ -int XrdSutToHex(const char *in, int lin, char *out); - -/******************************************************************************/ -/* X r d S u t F r o m H e x */ -/* */ -/* Extract buffer from an hexadecimal string */ -/* */ -/******************************************************************************/ -int XrdSutFromHex(const char *in, char *out, int &lout); - -/******************************************************************************/ -/* X r d S u t T i m e S t r i n g */ -/* */ -/* Trasform a time in secs since 1Jan1970 in a string of the format */ -/* 24Apr2006:09:10:23 */ -/* The buffer st must be supplied by the caller to contain at least 20 bytes.*/ -/* This length is returned when calling the function with t=-1. */ -/* */ -/******************************************************************************/ -int XrdSutTimeString(int t, char *st, int opt = 0); - -/******************************************************************************/ -/* X r d S u t E x p a n d */ -/* */ -/* Expand '~' or $PWD for relative paths */ -/******************************************************************************/ -int XrdSutExpand(XrdOucString &path); - -/******************************************************************************/ -/* X r d S u t R e s o l v e */ -/* */ -/* Resolve templates , , , (if any) */ -/******************************************************************************/ -int XrdSutResolve(XrdOucString &path, - const char *ho, const char *vo, const char *gr, const char *us); - -/******************************************************************************/ -/* X r d S u t H o m e */ -/* */ -/* Return the home directory */ -/* Checks, in the order, HOME and pwd entry */ -/******************************************************************************/ -const char *XrdSutHome(); - -/******************************************************************************/ -/* X r d S u t M k d i r */ -/* */ -/* Make directory dir */ -/******************************************************************************/ -int XrdSutMkdir(const char *dir, unsigned int mode = 0777, - const char *opt = "-p"); -/******************************************************************************/ -/* X r d S u t P a r s e T i m e */ -/* */ -/* Parse time string of the form "::..." */ -/* with any integer and one of the following chars: */ -/* 'y' for years */ -/* 'd' for days */ -/* 'h' for hours */ -/* 'm' for minutes */ -/* 's' for seconds */ -/* (e.g. "34d:10h:20s") */ -/* If opt == 1, assume a string in the form ".hh"[:[:]]" */ -/* (e.g. "12:24:35" for 12 hours, 24 minutes and 35 secs) */ -/* Return the corresponding number of seconds */ -/******************************************************************************/ -int XrdSutParseTime(const char *tstr, int opt = 0); - -/******************************************************************************/ -/* X r d S u t F i l e L o c k e r */ -/* */ -/* Guard class for file locking */ -/* Usage: */ -/* { */ -/* XrdSutFileLocker fl(fd,XrdSutFileLocker::kExcl); */ -/* // File exclusively locked */ -/* ... */ -/* } // Unlocks file descriptor 'fd' */ -/* */ -/******************************************************************************/ -class XrdSutFileLocker { -private: - int fdesk; - bool valid; -public: - enum ELockType { kShared = 0, kExcl = 1 }; - XrdSutFileLocker(int fd, ELockType lock); - ~XrdSutFileLocker(); - bool IsValid() const { return valid; } -}; - -#endif - diff --git a/src/XrdSut/XrdSutBuckList.cc b/src/XrdSut/XrdSutBuckList.cc deleted file mode 100644 index e530162e380..00000000000 --- a/src/XrdSut/XrdSutBuckList.cc +++ /dev/null @@ -1,177 +0,0 @@ - -/******************************************************************************/ -/* */ -/* X r d S u t B u c k L i s t . c c */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Gerri Ganis for CERN */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdSut/XrdSutBuckList.hh" - -/******************************************************************************/ -/* */ -/* Light single-linked list for managing buckets inside the exchanged */ -/* buffer */ -/* */ -/******************************************************************************/ - -//___________________________________________________________________________ -XrdSutBuckList::XrdSutBuckList(XrdSutBucket *b) -{ - // Constructor - - previous = current = begin = end = 0; - size = 0; - - if (b) { - XrdSutBuckListNode *f = new XrdSutBuckListNode(b,0); - current = begin = end = f; - size++; - } -} - -//___________________________________________________________________________ -XrdSutBuckList::~XrdSutBuckList() -{ - // Destructor - - XrdSutBuckListNode *n = 0; - XrdSutBuckListNode *b = begin; - while (b) { - n = b->Next(); - delete (b); - b = n; - } -} - -//___________________________________________________________________________ -XrdSutBuckListNode *XrdSutBuckList::Find(XrdSutBucket *b) -{ - // Find node containing bucket b - - XrdSutBuckListNode *nd = begin; - for (; nd; nd = nd->Next()) { - if (nd->Buck() == b) - return nd; - } - return (XrdSutBuckListNode *)0; -} - -//___________________________________________________________________________ -void XrdSutBuckList::PutInFront(XrdSutBucket *b) -{ - // Add at the beginning of the list - // Check to avoid duplicates - - if (!Find(b)) { - XrdSutBuckListNode *nb = new XrdSutBuckListNode(b,begin); - begin = nb; - if (!end) - end = nb; - size++; - } -} - -//___________________________________________________________________________ -void XrdSutBuckList::PushBack(XrdSutBucket *b) -{ - // Add at the end of the list - // Check to avoid duplicates - - if (!Find(b)) { - XrdSutBuckListNode *nb = new XrdSutBuckListNode(b,0); - if (!begin) - begin = nb; - if (end) - end->SetNext(nb); - end = nb; - size++; - } -} - -//___________________________________________________________________________ -void XrdSutBuckList::Remove(XrdSutBucket *b) -{ - // Remove node containing bucket b - - XrdSutBuckListNode *curr = current; - XrdSutBuckListNode *prev = previous; - - if (!curr || curr->Buck() != b || (prev && curr != prev->Next())) { - // We need first to find the address - curr = begin; - prev = 0; - for (; curr; curr = curr->Next()) { - if (curr->Buck() == b) - break; - prev = curr; - } - } - - // The bucket is not in the list - if (!curr) - return; - - // Now we have all the information to remove - if (prev) { - current = curr->Next(); - prev->SetNext(current); - previous = curr; - } else if (curr == begin) { - // First buffer - current = curr->Next(); - begin = current; - previous = 0; - } - - // Cleanup and update size - delete curr; - size--; -} - -//___________________________________________________________________________ -XrdSutBucket *XrdSutBuckList::Begin() -{ - // Iterator functionality: init - - previous = 0; - current = begin; - if (current) - return current->Buck(); - return (XrdSutBucket *)0; -} - -//___________________________________________________________________________ -XrdSutBucket *XrdSutBuckList::Next() -{ - // Iterator functionality: get next - - previous = current; - if (current) { - current = current->Next(); - if (current) - return current->Buck(); - } - return (XrdSutBucket *)0; -} diff --git a/src/XrdSut/XrdSutBuckList.hh b/src/XrdSut/XrdSutBuckList.hh deleted file mode 100644 index 0d3e679a582..00000000000 --- a/src/XrdSut/XrdSutBuckList.hh +++ /dev/null @@ -1,91 +0,0 @@ -#ifndef __SUT_BUCKLIST_H__ -#define __SUT_BUCKLIST_H__ -/******************************************************************************/ -/* */ -/* X r d S u t B u c k L i s t . h h */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Gerri Ganis for CERN */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#ifndef __SUT_BUCKET_H__ -#include "XrdSut/XrdSutBucket.hh" -#endif - -/******************************************************************************/ -/* */ -/* Light single-linked list for managing buckets inside the exchanged */ -/* buffer */ -/* */ -/******************************************************************************/ - -// -// Node definition -// -class XrdSutBuckListNode { -private: - XrdSutBucket *buck; - XrdSutBuckListNode *next; -public: - XrdSutBuckListNode(XrdSutBucket *b = 0, XrdSutBuckListNode *n = 0) - { buck = b; next = n;} - virtual ~XrdSutBuckListNode() { } - - XrdSutBucket *Buck() const { return buck; } - - XrdSutBuckListNode *Next() const { return next; } - - void SetNext(XrdSutBuckListNode *n) { next = n; } -}; - -class XrdSutBuckList { - -private: - XrdSutBuckListNode *begin; - XrdSutBuckListNode *current; - XrdSutBuckListNode *end; - XrdSutBuckListNode *previous; - int size; - - XrdSutBuckListNode *Find(XrdSutBucket *b); - -public: - XrdSutBuckList(XrdSutBucket *b = 0); - virtual ~XrdSutBuckList(); - - // Access information - int Size() const { return size; } - XrdSutBucket *End() const { return end->Buck(); } - - // Modifiers - void PutInFront(XrdSutBucket *b); - void PushBack(XrdSutBucket *b); - void Remove(XrdSutBucket *b); - - // Pseudo - iterator functionality - XrdSutBucket *Begin(); - XrdSutBucket *Next(); -}; - -#endif - diff --git a/src/XrdSut/XrdSutBucket.cc b/src/XrdSut/XrdSutBucket.cc deleted file mode 100644 index 4fba5c8275b..00000000000 --- a/src/XrdSut/XrdSutBucket.cc +++ /dev/null @@ -1,243 +0,0 @@ - -/******************************************************************************/ -/* */ -/* X r d S u t B u c k e t . c c */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Gerri Ganis for CERN */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -#include "XrdOuc/XrdOucString.hh" -#include "XrdSut/XrdSutBucket.hh" -#include "XrdSut/XrdSutTrace.hh" - -/******************************************************************************/ -/* M a s k s f o r A S C I I c h a r a c t e r s */ -/******************************************************************************/ -static kXR_int32 XrdSutCharMsk[4][4] = - { {0x00000000, -1, -1, -2}, // any printable char - {0x00000000, 0x0000ffc0, 0x7fffffe0, 0x7fffffe0}, // letters/numbers (up/low case) - {0x00000000, 0x0000ffc0, 0x7e000000, 0x7e000000}, // hex characters (up/low case) - {0x00000000, 0x03ffc000, 0x07fffffe, 0x07fffffe} }; // crypt like [a-zA-Z0-9./] - -/******************************************************************************/ -/* */ -/* Unit for information exchange */ -/* */ -/******************************************************************************/ -//______________________________________________________________________________ -XrdSutBucket::XrdSutBucket(char *bp, int sz, int ty) -{ - // Default constructor - - buffer = membuf = bp; - size=sz; - type=ty; -} - -//______________________________________________________________________________ -XrdSutBucket::XrdSutBucket(XrdOucString &s, int ty) -{ - // Constructor - - membuf = 0; - size = 0; - type = ty; - - if (s.length()) { - membuf = new char [s.length()]; - if (membuf) { - memcpy(membuf,s.c_str(),s.length()); - buffer = membuf; - size = s.length(); - } - } -} - -//______________________________________________________________________________ -XrdSutBucket::XrdSutBucket(XrdSutBucket &b) -{ - // Copy constructor - - membuf = new char[b.size]; - if (membuf) { - memcpy(membuf,b.buffer,b.size); - buffer = membuf; - type = b.type; - size = b.size; - } -} - -//______________________________________________________________________________ -void XrdSutBucket::Update(char *nb, int ns, int ty) -{ - // Update content - - if (membuf) - delete[] membuf; - buffer = membuf = nb; - size = ns; - - if (ty) - type = ty; -} - -//______________________________________________________________________________ -int XrdSutBucket::Update(XrdOucString &s, int ty) -{ - // Update content - // Returns 0 if ok, -1 otherwise. - - if (membuf) - delete[] membuf; - membuf = buffer = 0; - if (s.length()) { - membuf = new char [s.length()]; - if (membuf) { - memcpy(membuf,s.c_str(),s.length()); - buffer = membuf; - size = s.length(); - if (ty) - type = ty; - return 0; - } - } - return -1; -} - -//______________________________________________________________________________ -int XrdSutBucket::SetBuf(const char *nb, int ns) -{ - // Fill local buffer with ns bytes at nb. - // Memory is properly allocated / deallocated - // Returns 0 if ok, -1 otherwise. - - if (membuf) - delete[] membuf; - size = 0; - membuf = buffer = 0; - if (nb && ns) { - membuf = new char [ns]; - if (membuf) { - memcpy(membuf,nb,ns); - buffer = membuf; - size = ns; - return 0; - } - } - return -1; -} - -//______________________________________________________________________________ -void XrdSutBucket::ToString(XrdOucString &s) -{ - // Convert content into a null terminated string - // (nb: the caller must be sure that the operation makes sense) - - s = ""; - char *b = new char[size+1]; - if (b) { - memcpy(b,buffer,size); - b[size] = 0; - s = (const char *)b; - delete[] b; - } -} - -//_____________________________________________________________________________ -void XrdSutBucket::Dump(int opt) -{ - // Dump content of bucket - // Options: - // 1 print header and tail (default) - // 0 dump only content - EPNAME("Bucket::Dump"); - - if (opt == 1) { - PRINT("//-----------------------------------------------------//"); - PRINT("// //"); - PRINT("// XrdSutBucket DUMP //"); - PRINT("// //"); - } - - PRINT("// addr: " < 127) ? 0 : 1; - if (isascii) { - j = i / 32; - l = i - j * 32; - } - char chex[8]; - sprintf(chex," 0x%02x",(int)(i & 0xFF)); - bhex.append( chex ); - if (isascii && ((XrdSutCharMsk[0][j] & (1 << (31-l+1))) || i == 0x20)) { - bpri[curpri] = i; - } else { - bpri[curpri] = '.'; - } - curpri++; - if (curpri > 7) { - bpri[curpri] = 0; - PRINT("// " < 0) { - while (curpri++ < 8) { - bhex.append( " " ); - } - } - PRINT("// " <. */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#ifndef __SUT_STRING_H__ -#include "XrdSut/XrdSutAux.hh" -#endif - -class XrdOucString; - -/******************************************************************************/ -/* */ -/* Unit for information exchange */ -/* */ -/******************************************************************************/ - -class XrdSutBucket -{ -public: - kXR_int32 type; - kXR_int32 size; - char *buffer; - - XrdSutBucket(char *bp=0, int sz=0, int ty=0); - XrdSutBucket(XrdOucString &s, int ty=0); - XrdSutBucket(XrdSutBucket &b); - virtual ~XrdSutBucket() {if (membuf) delete[] membuf;} - - void Update(char *nb = 0, int ns = 0, int ty = 0); // Uses 'nb' - int Update(XrdOucString &s, int ty = 0); - int SetBuf(const char *nb = 0, int ns = 0); // Duplicates 'nb' - - void Dump(int opt = 1); - void ToString(XrdOucString &s); - - // Equality operator - int operator==(const XrdSutBucket &b); - - // Inequality operator - int operator!=(const XrdSutBucket &b) { return !(*this == b); } - -private: - char *membuf; -}; - -#endif - diff --git a/src/XrdSut/XrdSutBuffer.cc b/src/XrdSut/XrdSutBuffer.cc deleted file mode 100644 index 90041eeb127..00000000000 --- a/src/XrdSut/XrdSutBuffer.cc +++ /dev/null @@ -1,506 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S u t B u f f e r . c c */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Gerri Ganis for CERN */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include - -#include "XrdSec/XrdSecInterface.hh" -#include "XrdOuc/XrdOucString.hh" -#include "XrdSut/XrdSutBuffer.hh" -#include "XrdSut/XrdSutTrace.hh" - -/******************************************************************************/ -/* */ -/* Buffer structure for managing exchanged buckets */ -/* */ -/******************************************************************************/ - -//_____________________________________________________________________________ -XrdSutBuffer::XrdSutBuffer(const char *buf, kXR_int32 len) -{ - // Constructor from compact form (used for exchange over the network) - // If the buffer begins with "&P=", then only the protocol name and - // options are extracted, assuming the format "&P=,". - // Otherwise the format "..." is - // assumed - EPNAME("Buffer::XrdSutBuffer"); - - bool ok = 1; - - // Default initialization - fOptions = ""; - fProtocol = ""; - fStep = 0; - - // - // Check type of buffer - if (!strncmp(buf,"&P=",3)) { - // - // Initial buffer format - // Extract protocol name and options - int cur = 3; - int k = 0; - while (buf[cur+k] && buf[cur+k] != ',' && - k < XrdSecPROTOIDSIZE && (cur+k) < len) k++; - if (!k) { - PRINT("no protocol name - do nothing"); - } else { - // - // Extract protocol name - char proto[XrdSecPROTOIDSIZE]; - strncpy(proto,buf+cur,k); - proto[(k >= XrdSecPROTOIDSIZE ? XrdSecPROTOIDSIZE-1:k)]=0; // null-terminated - fProtocol = proto; - cur += (k+1); - // - // Extract options, if any - if (cur < len) { - k = 0; - while ((cur+k) < len && buf[cur+k]) - k++; - if (k) { - char *opts = new char[k+1]; - if (opts) { - strncpy(opts,buf+cur,k); - opts[k] = 0; // null-terminated - fOptions = opts; - delete[] opts; - } - } - cur += (k+1); - } - } - - } else { - // - // Assume exchange info format - // Check integrity - int k = 0; - while ( k < XrdSecPROTOIDSIZE && k < len && buf[k]) { k++; } - if (!k || k == XrdSecPROTOIDSIZE) { - PRINT("no protocol name: do nothing"); - ok = 0; - } - int cur = k+1; - if (ok) { - // - // Extract protocol name - char proto[XrdSecPROTOIDSIZE]; - strcpy(proto,buf); - fProtocol = proto; - - // - // Step/Iteration number - kXR_int32 step; - memcpy(&step,&buf[cur],sizeof(kXR_int32)); - fStep = ntohl(step); - cur += sizeof(kXR_int32); - } - - // - // Total length of buckets (sizes+buffers) (excluded trailing 0) - int ltot = len - sizeof(kXR_int32); - TRACE(Dump,"ltot: " < ltot) - ok = 0; - else { - // - // Store only active buckets - if (type != kXRS_inactive){ - // - // Normal active bucket: save it in the vector - if ((buck = new char[blen])) { - memcpy(buck,&buf[cur],blen); - if ((tmp = new XrdSutBucket(buck,blen,type))) { - fBuckets.PushBack(tmp); - } else { - PRINT("error creating bucket: "<type); - delete bp; - // Get next bucket - bp = fBuckets.Next(); - } -} - -//_____________________________________________________________________________ -int XrdSutBuffer::UpdateBucket(const char *b, int sz, int ty) -{ - // Update existing bucket (or add a new bucket to the list) - // with sz bytes at 'b'. - // Returns 0 or -1 if error allocating bucket - EPNAME("Buffer::UpdateBucket"); - - XrdSutBucket *bp = GetBucket(ty); - if (!bp) { - bp = new XrdSutBucket(0,0,ty); - if (!bp) { - DEBUG("Out-Of-Memory allocating bucket"); - return -1; - } - AddBucket(bp); - } - bp->SetBuf(b,sz); - // Done - return 0; -} - -//_____________________________________________________________________________ -int XrdSutBuffer::UpdateBucket(XrdOucString s, int ty) -{ - // Update existing bucket (or add a new bucket to the list) - // with string s. - // Returns 0 or -1 if error allocating bucket - - return UpdateBucket(s.c_str(),s.length(),ty); -} - -//_____________________________________________________________________________ -void XrdSutBuffer::Dump(const char *stepstr) -{ - // Dump content of buffer - EPNAME("Buffer::Dump"); - - PRINT("//-----------------------------------------------------//"); - PRINT("// //") - PRINT("// XrdSutBuffer DUMP //") - PRINT("// //") - - int nbuck = fBuckets.Size(); - - PRINT("// Buffer : " <Dump(0); - // Get next - bp = fBuckets.Next(); - } - PRINT("// //") - PRINT("//-----------------------------------------------------//"); -} - -//_____________________________________________________________________________ -void XrdSutBuffer::Message(const char *prepose) -{ - // Print content of any bucket of type kXRS_message - // Prepose 'prepose', if defined - - bool pripre = 0; - if (prepose) - pripre = 1; - - XrdSutBucket *bp = fBuckets.Begin(); - while (bp) { - if (bp->type == kXRS_message) { - if (bp->size > 0 && bp->buffer) { - if (pripre) { - XrdOucString premsg(prepose); - cerr << premsg << endl; - pripre = 0; - } - XrdOucString msg(bp->buffer,bp->size); - cerr << msg << endl; - } - } - // Get next - bp = fBuckets.Next(); - } -} - -//_____________________________________________________________________________ -kXR_int32 XrdSutBuffer::MarshalBucket(kXR_int32 type, kXR_int32 code) -{ - // Search the vector of buckets for the first bucket of - // type 'type'. Reset its content and fill it with 'code' - // in network byte order. If no bucket 'type' exists, add - // a new one. - // Returns -1 if new bucket could be allocated; 0 otherwise . - EPNAME("Buffer::MarshalBucket"); - - // Convert to network byte order - kXR_int32 mcod = htonl(code); - - // Get the bucket - XrdSutBucket *bck = GetBucket(type); - if (!bck) { - // Allocate a new one - bck = new XrdSutBucket(0,0,type); - if (!bck) { - DEBUG("could not allocate new bucket of type:"<SetBuf((char *)(&mcod),sizeof(kXR_int32)); - - // We are done - return 0; -} - -//_____________________________________________________________________________ -kXR_int32 XrdSutBuffer::UnmarshalBucket(kXR_int32 type, kXR_int32 &code) -{ - // Search the vector of buckets for the first bucket of - // type 'type'. Unmarshalled its content to host byte order - // and fill it in code. - // Returns 0 if ok. - // Returns -1 if no bucket of requested 'type' could be - // found; -2 if the bucket size is inconsistent. - EPNAME("Buffer::UnmarshalBucket"); - - code = 0; - // Get the bucket - XrdSutBucket *bck = GetBucket(type); - if (!bck) { - DEBUG("could not find a bucket of type:"<size != sizeof(kXR_int32)) { - DEBUG("Wrong size: type:"<size<<", expected:"<buffer,sizeof(kXR_int32)); - // - // Unmarshal - code = ntohl(code); - - // We are done - return 0; -} - -//_____________________________________________________________________________ -XrdSutBucket *XrdSutBuffer::GetBucket(kXR_int32 type, const char *tag) -{ - // Search the vector of buckets for the first bucket of - // type 'type'. - // If tag is defined, search buckets whose buffer contains tag - // in the form '\0'. - // Returns the pointer to the buffer; 0 if the no bucket - // is found - - // - // Check tag, if any - int ltag = (tag) ? strlen(tag) : 0; - // - // Loop over buckets - XrdSutBucket *bp = fBuckets.Begin(); - while (bp) { - if (type == bp->type && (!tag || (ltag < bp->size && - !strncmp(bp->buffer,tag,ltag) && - (bp->buffer)[ltag] == '\0'))) - return bp; - // Get next - bp = fBuckets.Next(); - } - - // Nothing found - return 0; -} - -//_____________________________________________________________________________ -void XrdSutBuffer::Deactivate(kXR_int32 type) -{ - // Deactivate first bucket of type 'type', if any - // If type == -1, deactivate all buckets (cleanup) - - // - // Loop over buckets - XrdSutBucket *bp = fBuckets.Begin(); - while (bp) { - if (type == bp->type) { - bp->type = kXRS_inactive; - break; - } else if (type == -1) { - bp->type = kXRS_inactive; - } - // Get next - bp = fBuckets.Next(); - } -} - -//_____________________________________________________________________________ -int XrdSutBuffer::Serialized(char **buffer, char opt) -{ - // Serialize the content in a form suited for exchange - // over the net; the result is saved in '*buffer', which - // must be deleted (opt = 'n', default) or freed (opt == 'm') by the caller. - // Returns the length of the buffer in case of success. - // Returns -1 in case of problems allocating the buffer. - EPNAME("Buffer::Serialized"); - - // - // Check that we got a valid argument - if (!buffer) { - DEBUG("invalid input argument"); - errno = EINVAL; - return -1; - } - - // - // Calculate the length of the buffer - int blen = fProtocol.length() + 1 + 2*sizeof(kXR_int32); - // buckets - XrdSutBucket *bp = fBuckets.Begin(); - while (bp) { - if (bp->type != kXRS_inactive) { - blen += 2*sizeof(kXR_int32); - blen += bp->size; - } - // Get next - bp = fBuckets.Next(); - } - - // - // Allocate the buffer - *buffer = (opt == 'n') ? (new char[blen]) : (char *)malloc(blen); - if (!(*buffer)) - return -1; - char *tbuf = *buffer; - int cur = 0; - - // - // Add protocol - memcpy(tbuf,fProtocol.c_str(),fProtocol.length()); - tbuf[fProtocol.length()] = 0; - cur += fProtocol.length() + 1; - - // - // Add step number - kXR_int32 step = htonl(fStep); - memcpy(tbuf+cur,&step,sizeof(kXR_int32)); - cur += sizeof(kXR_int32); - - // - // Add buckets - bp = fBuckets.Begin(); - while (bp) { - if (bp->type != kXRS_inactive) { - kXR_int32 type = htonl(bp->type); - memcpy(tbuf+cur,&type,sizeof(kXR_int32)); - cur += sizeof(kXR_int32); - kXR_int32 size = htonl(bp->size); - memcpy(tbuf+cur,&size,sizeof(kXR_int32)); - cur += sizeof(kXR_int32); - memcpy(tbuf+cur,bp->buffer,bp->size); - cur += bp->size; - } - // Get next bucket - bp = fBuckets.Next(); - } - - // - // Add 0 termination - kXR_int32 ltmp = htonl(kXRS_none); - memcpy(tbuf+cur,<mp,sizeof(kXR_int32)); - - // Return total length - return blen; -} diff --git a/src/XrdSut/XrdSutBuffer.hh b/src/XrdSut/XrdSutBuffer.hh deleted file mode 100644 index 9fb36842f95..00000000000 --- a/src/XrdSut/XrdSutBuffer.hh +++ /dev/null @@ -1,95 +0,0 @@ -#ifndef __SUT_BUFFER_H__ -#define __SUT_BUFFER_H__ -/******************************************************************************/ -/* */ -/* X r d S u t B u f f e r . h h */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Geri Ganis for CERN */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#ifndef __SUT_BUCKLIST_H__ -#include "XrdSut/XrdSutBuckList.hh" -#endif - -/******************************************************************************/ -/* */ -/* Buffer structure for managing exchanged buckets */ -/* */ -/******************************************************************************/ - -class XrdOucString; - -class XrdSutBuffer { - -private: - - XrdSutBuckList fBuckets; - XrdOucString fOptions; - XrdOucString fProtocol; - kXR_int32 fStep; - -public: - XrdSutBuffer(const char *prot, const char *opts = 0) - {fOptions = opts; fProtocol = prot; fStep = 0;} - XrdSutBuffer(const char *buffer, kXR_int32 length); - - virtual ~XrdSutBuffer(); - - int AddBucket(char *bp=0, int sz=0, int ty=0) - { XrdSutBucket *b = new XrdSutBucket(bp,sz,ty); - if (b) { fBuckets.PushBack(b); return 0;} return -1; } - int AddBucket(XrdOucString s, int ty=0) - { XrdSutBucket *b = new XrdSutBucket(s,ty); - if (b) { fBuckets.PushBack(b); return 0;} return -1; } - int AddBucket(XrdSutBucket *b) - { if (b) { fBuckets.PushBack(b); return 0;} return -1; } - - int UpdateBucket(const char *bp, int sz, int ty); - int UpdateBucket(XrdOucString s, int ty); - - // Remove from the list, to avoid destroy by ~XrdSutBuffer - void Remove(XrdSutBucket *b) { fBuckets.Remove(b); } - - void Dump(const char *stepstr = 0); - void Message(const char *prepose = 0); - int Serialized(char **buffer, char opt = 'n'); - - void Deactivate(kXR_int32 type); // Deactivate bucket (type=-1 for cleanup) - - // To fill / access buckets containing 4-byte integers (status codes, versions ...) - kXR_int32 MarshalBucket(kXR_int32 type, kXR_int32 code); - kXR_int32 UnmarshalBucket(kXR_int32 type, kXR_int32 &code); - - XrdSutBucket *GetBucket(kXR_int32 type, const char *tag = 0); - XrdSutBuckList *GetBuckList() const { return (XrdSutBuckList *)&fBuckets; } - int GetNBuckets() const { return fBuckets.Size(); } - const char *GetOptions() const { return fOptions.c_str(); } - const char *GetProtocol() const { return fProtocol.c_str(); } - int GetStep() const { return (int)fStep; } - void SetStep(int s) { fStep = (kXR_int32)s; } - void IncrementStep() { fStep++; } -}; - -#endif - diff --git a/src/XrdSut/XrdSutCache.hh b/src/XrdSut/XrdSutCache.hh deleted file mode 100644 index d24415c2108..00000000000 --- a/src/XrdSut/XrdSutCache.hh +++ /dev/null @@ -1,154 +0,0 @@ -#ifndef __SUT_CACHE_H -#define __SUT_CACHE_H -/******************************************************************************/ -/* */ -/* X r d S u t C a c h e . h h */ -/* */ -/* (c) 2005 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Gerri Ganis for CERN */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdOuc/XrdOucHash.hh" -#include "XrdSut/XrdSutCacheEntry.hh" -#include "XrdSys/XrdSysPthread.hh" - -/******************************************************************************/ -/* */ -/* Class defining the basic memory cache */ -/* */ -/******************************************************************************/ - -typedef bool (*XrdSutCacheGet_t)(XrdSutCacheEntry *, void *); -typedef struct { - long arg1; - long arg2; - long arg3; - long arg4; -} XrdSutCacheArg_t; - -class XrdSutCache { -public: - XrdSutCache(int psize = 89, int size = 144, int load = 80) : table(psize, size, load) {} - virtual ~XrdSutCache() {} - - XrdSutCacheEntry *Get(const char *tag) { - // Get the entry with 'tag'. - // If found the entry is returned rd-locked. - // If rd-locking fails the status is set to kCE_inactive. - // Returns null if not found. - - XrdSutCacheEntry *cent = 0; - - // Exclusive access to the table - XrdSysMutexHelper raii(mtx); - - // Look for an entry - if (!(cent = table.Find(tag))) { - // none found - return cent; - } - - // We found an existing entry: - // lock until we get the ability to read (another thread may be valudating it) - int status = 0; - cent->rwmtx.ReadLock( status ); - if ( status ) { - // A problem occured: fail (set the entry invalid) - cent->status = kCE_inactive; - } - return cent; - } - - XrdSutCacheEntry *Get(const char *tag, bool &rdlock, XrdSutCacheGet_t condition = 0, void *arg = 0) { - // Get or create the entry with 'tag'. - // New entries are always returned write-locked. - // The status of existing ones depends on condition: if condition is undefined or if applied - // to the entry with arguments 'arg' returns true, the entry is returned read-locked. - // Otherwise a write-lock is attempted on the entry: if unsuccessful (another thread is modifing - // the entry) the entry is read-locked. - // The status of the lock is returned in rdlock (true if read-locked). - rdlock = false; - XrdSutCacheEntry *cent = 0; - - // Exclusive access to the table - XrdSysMutexHelper raii(mtx); - - // Look for an entry - if (!(cent = table.Find(tag))) { - // If none, create a new one and write-lock for validation - cent = new XrdSutCacheEntry(tag); - int status = 0; - cent->rwmtx.WriteLock( status ); - if (status) { - // A problem occured: delete the entry and fail - delete cent; - return (XrdSutCacheEntry *)0; - } - // Register it in the table - table.Add(tag, cent); - return cent; - } - - // We found an existing entry: - // lock until we get the ability to read (another thread may be valudating it) - int status = 0; - cent->rwmtx.ReadLock( status ); - if (status) { - // A problem occured: fail (set the entry invalid) - cent->status = kCE_inactive; - return cent; - } - - // Check-it by apply the condition, if required - if (condition) { - if ((*condition)(cent, arg)) { - // Good and valid entry - rdlock = true; - } else { - // Invalid entry: unlock and write-lock to be able to validate it - cent->rwmtx.UnLock(); - int status = 0; - cent->rwmtx.WriteLock( status ); - if (status) { - // A problem occured: fail (set the entry invalid) - cent->status = kCE_inactive; - return cent; - } - } - } else { - // Good and valid entry - rdlock = true; - } - // We are done: return read-locked so we can use it until we need it - return cent; - } - - inline int Num() { return table.Num(); } - inline void Reset() { return table.Purge(); } - -private: - XrdSysRecMutex mtx; // Protect access to table - XrdOucHash table; // table with content -}; - -#endif diff --git a/src/XrdSut/XrdSutCacheEntry.cc b/src/XrdSut/XrdSutCacheEntry.cc deleted file mode 100644 index 64707c5d781..00000000000 --- a/src/XrdSut/XrdSutCacheEntry.cc +++ /dev/null @@ -1,184 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S u t C a c h e E n t r y . c c */ -/* */ -/* (c) 2012 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Gerri Ganis for CERN */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include - -#include "XrdSutAux.hh" -#include "XrdSutCacheEntry.hh" - -//__________________________________________________________________ -XrdSutCacheEntryBuf::XrdSutCacheEntryBuf(char *b, kXR_int32 l) -{ - // Constructor - - len = 0; - buf = 0; - if (b) { - buf = b; - len = l; - } -} - -//__________________________________________________________________ -XrdSutCacheEntryBuf::XrdSutCacheEntryBuf(const XrdSutCacheEntryBuf &b) -{ - //Copy constructor - - buf = 0; - len = 0; - if (b.buf) { - buf = new char[b.len]; - if (buf) { - memcpy(buf,b.buf,b.len); - len = b.len; - } - } -} - -//__________________________________________________________________ -void XrdSutCacheEntryBuf::SetBuf(const char *b, kXR_int32 l) -{ - // Set the buffer - - len = 0; - if (buf) { - delete[] buf; - buf = 0; - } - if (b && l > 0) { - buf = new char[l]; - if (buf) { - memcpy(buf,b,l); - len = l; - } - } -} - -//____________________________________________________________________ -XrdSutCacheEntry::XrdSutCacheEntry(const char *n, short st, short cn, - kXR_int32 mt) -{ - // Constructor - - name = 0; - status = st; - cnt = cn; - mtime = (mt > 0) ? mt : (kXR_int32)time(0); - if (n) { - name = new char[strlen(n)+1]; - if (name) - strcpy(name,n); - } -} - -//_____________________________________________________________________ -XrdSutCacheEntry::XrdSutCacheEntry(const XrdSutCacheEntry &e) : buf1(e.buf1), - buf2(e.buf2), buf3(e.buf3), buf4(e.buf4) -{ - // Copy constructor - - name = 0; - status = e.status; - cnt = e.cnt; - mtime = e.mtime; - if (e.name) { - name = new char[strlen(e.name)+1]; - if (name) - strcpy(name,e.name); - } -} - -//____________________________________________________________________ -void XrdSutCacheEntry::Reset() -{ - // Resetting entry - - if (name) - delete[] name; - name = 0; - status = 0; - cnt = 0; - mtime = (kXR_int32)time(0); - buf1.SetBuf(); - buf2.SetBuf(); - buf3.SetBuf(); - buf4.SetBuf(); -} - -//_____________________________________________________________________ -void XrdSutCacheEntry::SetName(const char *n) -{ - // Set the name - - if (name) { - delete[] name; - name = 0; - } - if (n) { - name = new char[strlen(n)+1]; - if (name) - strcpy(name,n); - } -} - -//_____________________________________________________________________ -char *XrdSutCacheEntry::AsString() const -{ - // Return a string with serialized information - // For print purposes - // The output string points to a static buffer, so it must - // not be deleted by the caller - static char pbuf[2048]; - - char smt[20] = {0}; - XrdSutTimeString(mtime,smt); - - sprintf(pbuf,"st:%d cn:%d buf:%d,%d,%d,%d modified:%s name:%s", - status,cnt,buf1.len,buf2.len,buf3.len,buf4.len,smt,name); - - return pbuf; -} - -//______________________________________________________________________________ -XrdSutCacheEntry& XrdSutCacheEntry::operator=(const XrdSutCacheEntry &e) -{ - // Assign entry e to local entry. - - SetName(name); - status = e.status; - cnt = e.cnt; // counter - mtime = e.mtime; // time of last modification / creation - buf1.SetBuf(e.buf1.buf); - buf2.SetBuf(e.buf2.buf); - buf3.SetBuf(e.buf3.buf); - buf4.SetBuf(e.buf4.buf); - - return (*this); -} diff --git a/src/XrdSut/XrdSutCacheEntry.hh b/src/XrdSut/XrdSutCacheEntry.hh deleted file mode 100644 index 099ca34ac7e..00000000000 --- a/src/XrdSut/XrdSutCacheEntry.hh +++ /dev/null @@ -1,129 +0,0 @@ -#ifndef __SUT_CACHEENTRY_H -#define __SUT_CACHEENTRY_H -/******************************************************************************/ -/* */ -/* X r d S u t C a c h e E n t r y . h h */ -/* */ -/* (c) 2005 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Gerri Ganis for CERN */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XProtocol/XProtocol.hh" -#include "XrdSys/XrdSysPthread.hh" - -/******************************************************************************/ -/* */ -/* Class defining the basic cache entry */ -/* */ -/******************************************************************************/ - -enum kCEntryStatus { - kCE_inactive = -2, // -2 inactive: eliminated at next trim - kCE_disabled, // -1 disabled, cannot be enabled - kCE_allowed, // 0 empty creds, can be enabled - kCE_expired, // 1 enabled, creds to be changed at next used - kCE_ok, // 2 enabled and OK - kCE_special // 3 special (non-creds) entry -}; - -// -// Buffer used internally by XrdGCEntry -// -class XrdSutCacheEntryBuf { -public: - char *buf; - kXR_int32 len; - XrdSutCacheEntryBuf(char *b = 0, kXR_int32 l = 0); - XrdSutCacheEntryBuf(const XrdSutCacheEntryBuf &b); - - virtual ~XrdSutCacheEntryBuf() { if (len > 0 && buf) delete[] buf; } - - void SetBuf(const char *b = 0, kXR_int32 l = 0); -}; - -// -// Generic cache entry: it stores a -// -// name -// status 2 bytes -// cnt 2 bytes -// mtime 4 bytes -// buf1, buf2, buf3, buf4 -// -// The buffers are generic buffers to store bufferized info -// -class XrdSutCacheEntry { -public: - char *name; - short status; - short cnt; // counter - kXR_int32 mtime; // time of last modification / creation - XrdSutCacheEntryBuf buf1; - XrdSutCacheEntryBuf buf2; - XrdSutCacheEntryBuf buf3; - XrdSutCacheEntryBuf buf4; - XrdSysRWLock rwmtx; // Locked when reference is outstanding - XrdSutCacheEntry(const char *n = 0, short st = 0, short cn = 0, - kXR_int32 mt = 0); - XrdSutCacheEntry(const XrdSutCacheEntry &e); - virtual ~XrdSutCacheEntry() { if (name) delete[] name; } - kXR_int32 Length() const { return (buf1.len + buf2.len + 2*sizeof(short) + - buf3.len + buf4.len + 5*sizeof(kXR_int32)); } - void Reset(); - void SetName(const char *n = 0); - char *AsString() const; - - XrdSutCacheEntry &operator=(const XrdSutCacheEntry &pfe); -}; - -class XrdSutCERef -{ -public: - -inline void ReadLock(XrdSysRWLock *lock = 0) - { if (lock) Set(lock); - rwlock->ReadLock(); - }; - -inline void WriteLock(XrdSysRWLock *lock = 0) - { if (lock) Set(lock); - rwlock->WriteLock(); - }; - -inline void Set(XrdSysRWLock *lock) - {if (rwlock) {if (rwlock != lock) rwlock->UnLock(); - else return; - } - rwlock = lock; - }; - -inline void UnLock(bool reset = true) {if (rwlock) {rwlock->UnLock(); if (reset) rwlock = 0; }} - - XrdSutCERef() : rwlock(0) {} - - ~XrdSutCERef() {if (rwlock) UnLock(); rwlock = 0; } -protected: -XrdSysRWLock *rwlock; -}; - -#endif diff --git a/src/XrdSut/XrdSutPFCache.cc b/src/XrdSut/XrdSutPFCache.cc deleted file mode 100644 index 3084892ce95..00000000000 --- a/src/XrdSut/XrdSutPFCache.cc +++ /dev/null @@ -1,808 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S u t C a c h e . c c */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Gerri Ganis for CERN */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include - -#include "XrdSut/XrdSutPFCache.hh" -#include "XrdSut/XrdSutPFile.hh" -#include "XrdSut/XrdSutTrace.hh" -#include "XrdSut/XrdSutAux.hh" -#include "XrdSys/XrdSysTimer.hh" - -/******************************************************************************/ -/* */ -/* For caching temporary information during the authentication handshake */ -/* */ -/******************************************************************************/ - -//__________________________________________________________________ -XrdSutPFCache::~XrdSutPFCache() -{ - // Destructor - - // We are destroying the cache - rwlock.WriteLock(); - - // Cleanup content - while (cachemx > -1) { - if (cachent[cachemx]) { - delete cachent[cachemx]; - cachent[cachemx] = 0; - } - cachemx--; - } - // Cleanup table - if (cachent) - delete[] cachent; - - // Done - rwlock.UnLock(); -} - -//__________________________________________________________________ -int XrdSutPFCache::Init(int capacity, bool lock) -{ - // Initialize the cache to hold up to capacity entries. - // Later on, capacity is double each time more space is needed. - // Return 0 if ok, -1 otherwise - EPNAME("Cache::Init"); - - // Lock for writing - if (lock) rwlock.WriteLock(); - - // Nothing to do if already done - if (isinit) { - if (lock) rwlock.UnLock(); - return 0; - } - - // Make sure capacity makes sense; use a default, if not - capacity = (capacity > 0) ? capacity : 100; - - // Allocate - cachent = new XrdSutPFEntry *[capacity]; - if (cachent) { - for (int i = 0; i < capacity; i++) { cachent[i] = 0; } - cachesz = capacity; - DEBUG("cache allocated for "<pfeMutex.CondLock()) - {urRef.Set(&(pfEnt->pfeMutex)); - return pfEnt; - } - } else return pfEnt; - isg.UnLock(); - XrdSysTimer::Wait(retryMSW); - if (Rehash() != 0) - {DEBUG("problems rehashing"); - return (XrdSutPFEntry *)0 ; - } - isg.Lock(&rwlock, 1); - } - - // Nothing found - return (XrdSutPFEntry *)0 ; -} - -//__________________________________________________________________ -XrdSutPFEntry *XrdSutPFCache::Get(const char *ID, bool *wild) -{ - - // Look in the hash first - kXR_int32 *ie = hashtable.Find(ID); - if (ie && *ie >= 0 && *ie < cachesz) { - // Return the associated entry - return cachent[*ie]; - } - - // If wild cards allowed search sequentially - if (wild) { - XrdOucString sid(ID); - int i = 0, match = 0, nmmax = 0, iref = -1; - for (; i <= cachemx; i++) { - if (cachent[i]) { - match = sid.matches(cachent[i]->name); - if (match > nmmax) { - nmmax = match; - iref = i; - } - } - } - if (iref > -1) { - *wild = 1; - return cachent[iref]; - } - } - - // Nothing found - return (XrdSutPFEntry *)0 ; -} - -//__________________________________________________________________ -XrdSutPFEntry *XrdSutPFCache::Add(XrdSutPFCacheRef &urRef, const char *ID, bool force) -{ - // Add an entry with ID in cache - // Cache buffer is re-allocated with double size, if needed - // Hash is updated - EPNAME("Cache::Add"); - - // - // IF ID is undefined, do nothing - if (!ID || !strlen(ID)) { - DEBUG("empty ID !"); - return (XrdSutPFEntry *)0 ; - } - - // - // If an entry already exists, return it - XrdSutPFEntry *ent = Get(urRef, ID); - if (ent) - return ent; - - // Lock for writing - XrdSysRWLockHelper isg(rwlock, 0); - - // - // Make sure there enough space for a new entry - if (cachemx == cachesz - 1) { - // - // Duplicate buffer - XrdSutPFEntry **newcache = new XrdSutPFEntry *[2*cachesz]; - if (!newcache) { - DEBUG("could not extend cache to size: "<<(2*cachesz)); - return (XrdSutPFEntry *)0 ; - } - // Update info - cachesz *= 2; - // - // Copy existing valid entries, calculating real size - int i = 0, nmx = 0; - for (; i <= cachemx; i++) { - if (cachent[i]) { - newcache[nmx] = cachent[i]; - nmx++; - } - } - // update size - cachemx = nmx - 1; - // - // Reset new entries - for (i = cachemx + 1; i <= cachemx; i++) { - newcache[i] = 0; - } - // - // Cleanup and reassign - delete[] cachent; - cachent = newcache; - // - // Force rehash in this case - force = 1; - } - // - // The next free - int pos = cachemx + 1; - - // - // Add new entry - cachent[pos] = new XrdSutPFEntry(ID); - if (cachent[pos]) { - cachemx = pos; - } else { - DEBUG("could not allocate space for new cache entry"); - return (XrdSutPFEntry *)0 ; - } - // Update time stamp - utime = (kXR_int32)time(0); - - // Rebuild hash table - if (Rehash(force, 0) != 0) { - DEBUG("problems re-hashing"); - return (XrdSutPFEntry *)0 ; - } - - // We are done (we can lock the entry without a wait) - urRef.Lock(&(cachent[pos]->pfeMutex)); - return cachent[pos]; -} - -//__________________________________________________________________ -bool XrdSutPFCache::Remove(const char *ID, int opt) -{ - // If opt==1 remove entry with name matching exactly ID from cache - // If opt==0 all entries with names starting with ID are removed - // Return 1 if ok, 0 otherwise - EPNAME("Cache::Remove"); - - // - // IF ID is undefined, do nothing - if (!ID || !strlen(ID)) { - DEBUG("empty ID !"); - return 0 ; - } - - // Lock for writing - XrdSysRWLockHelper isg(rwlock, 0); - - if (Rehash(0, 0) != 0) { - DEBUG("problems rehashing"); - return 0 ; - } - - bool found = 0; - if (opt == 1) { - int pos = -1; - // Look in the hash first - kXR_int32 *ie = hashtable.Find(ID); - if (*ie >= 0 && *ie < cachesz) { - // Return the associated entry - pos = *ie; - } - - // - // Check if pos makes sense - if (cachent[pos] && !strcmp(cachent[pos]->name,ID)) { - if (!Delete(cachent[pos])) DEBUG("Delete defered for " <= 0; i--) { - if (cachent[i]) { - if (!strncmp(cachent[i]->name,ID,strlen(ID))) { - if (!Delete(cachent[i])) DEBUG("Delete defered for " <next)) - {nTot++; - if (dQ->pfEnt->pfeMutex.CondLock()) - {pQ->next = dQ->next; - dQ->pfEnt->pfeMutex.UnLock(); - delete dQ; - dTot++; - } else pQ = dQ; - } - if (nTot) DEBUG("Defered delete " <pfeMutex.CondLock()) - {pfEnt->pfeMutex.UnLock(); - delete pfEnt; - return true; - } - -// Defer the delete as someone still has a reference to the entry -// - pfDefer.next = new pfQ(pfDefer.next, pfEnt); - return false; -} - -//__________________________________________________________________ -int XrdSutPFCache::Trim(int lifet) -{ - // Remove entries older then lifet seconds. If lifet <=0, compare - // to lifetime, which can be set with SetValidity(). - // Return number of entries removed - - // Lock for writing - EPNAME("Cache::Trim"); - XrdSysRWLockHelper isg(rwlock, 0); - - // - // Make sure lifet makes sense; if not, use internal default - lifet = (lifet > 0) ? lifet : lifetime; - - // - // Reference time - int reftime = time(0) - lifet; - - // Loop over entries - int i = cachemx, nrm = 0; - for (; i >= 0; i--) { - if (cachent[i] && cachent[i]->mtime < reftime) { - if (!Delete(cachent[i])) - DEBUG("Delete defered for " <name); - cachent[i] = 0; - nrm++; - } - if (i == cachemx) { - if (!cachent[i]) - cachemx--; - } - } - - // We are done - return nrm; -} - -//__________________________________________________________________ -int XrdSutPFCache::Reset(int newsz, bool lock) -{ - // Remove all existing entries. - // If newsz > -1, set new capacity to newsz, reallocating if needed - // Return 0 if ok, -1 if problems reallocating. - EPNAME("Cache::Reset"); - - // Lock for writing - if (lock) rwlock.WriteLock(); - - // Loop over entries - int i = cachemx; - for (; i >= 0; i--) { - if (cachent[i]) { - if (!Delete(cachent[i])) - DEBUG("Delete defered for " <name); - cachent[i] = 0; - } - } - - int rc = 0; - // Reallocate, if requested - if (newsz > -1 && newsz != cachesz) { - delete[] cachent; - cachent = 0; - cachesz = 0; - cachemx = -1; - isinit = 0; - rc = Init(newsz, 0); - } - - // Unlock - if (lock) rwlock.UnLock(); - - // We are done - return rc; -} - -//________________________________________________________________ -void XrdSutPFCache::Dump(const char *msg) -{ - // Dump content of the cache - EPNAME("Cache::Dump"); - - PRINT("//-----------------------------------------------------"); - PRINT("//"); - if (msg && strlen(msg) > 0) { - PRINT("// "< 0) { - - XrdSutPFEntry *ent = 0; - int i = 0, nn = 0; - for (; i <= cachemx; i++) { - - // get entry - if ((ent = cachent[i])) { - - char smt[20] = {0}; - XrdSutTimeString(ent->mtime,smt); - - nn++; - PRINT("// #:"<status<<" cn:"<cnt - <<" buf:"<buf1.len<<","<buf2.len<<"," - <buf3.len<<","<buf4.len<<" mod:"<name); - } - - } - PRINT("//"); - } - PRINT("//-----------------------------------------------------"); -} - -//__________________________________________________________________ -int XrdSutPFCache::Load(const char *pfn) -{ - // Initialize the cache from the content of a file of PF entries - // Return 0 if ok, -1 otherwise - EPNAME("Cache::Load"); - - // Make sure file name is defined - if (!pfn) { - DEBUG("invalid input file name"); - return -1; - } - - // Check if file exists and if it has been modified since last load - struct stat st; - if (stat(pfn,&st) == -1) { - DEBUG("cannot stat file (errno: "< -1 && utime > st.st_mtime) { - DEBUG("cached information for file "< 0 && ne < header.entries) { - // - // read index entry - if (ff.ReadInd(nxtofs, ind) < 0) { - DEBUG("problems reading index entry "); - ff.Close(); - return -1; - } - - // If active ... - if (ind.entofs > 0) { - - // Read entry out - XrdSutPFEntry ent; - if (ff.ReadEnt(ind.entofs, ent) < 0) { - ff.Close(); - return -1; - } - - // Copy for the cache - XrdSutPFEntry *cent = new XrdSutPFEntry(ent); - - if (cent) { - // Set the id - cent->SetName(ind.name); - - // Fill the array - cachent[ne] = cent; - - // Count - ne++; - - } else { - DEBUG("problems duplicating entry for cache"); - ff.Close(); - return -1; - } - } - - // Go to next - nxtofs = ind.nxtofs; - } - cachemx = ne-1; - if (nxtofs > 0) - DEBUG("WARNING: inconsistent number of entries: possible file corruption"); - - // Update the time stamp - utime = (kXR_int32)time(0); - - // Save file name - pfile = pfn; - - // Close the file - ff.Close(); - - DEBUG("PF file "<= utime && !force) { - TRACE(Dump, "hash table is up-to-date"); - if (lock) rwlock.UnLock(); - return 0; - } - - // Clean up the hash table - hashtable.Purge(); - - kXR_int32 i = 0, nht = 0; - for (; i <= cachemx; i++) { - if (cachent[i]) { - // Fill the hash table - kXR_int32 *key = new kXR_int32(i); - if (key) { - TRACE(Dump, "Adding ID: "<name<<"; key: "<<*key); - hashtable.Add(cachent[i]->name,key); - nht++; - } - } - } - // Update modification time - htmtime = (kXR_int32)time(0); - - // Unlock - if (lock) rwlock.UnLock(); - - DEBUG("Hash table updated (found "<name, ent)) < 0) { - ff.Close(); - return -1; - } - // - // Write (update) only if older that cache or not found - if (nr == 0 || cachent[i]->mtime > ent.mtime) { - if (ff.WriteEntry(*cachent[i]) < 0) { - ff.Close(); - return -1; - } - nfs++; - } - } - } - - // Close the file - ff.Close(); - - // Update the time stamp (to avoid fake loads later on) - utime = (kXR_int32)time(0); - - // Save file name - if (pfile.length() <= 0) - pfile = pfn; - - DEBUG("Cache flushed to file "< -1 && utime > st.st_mtime) { - DEBUG("cached information for file "<. */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XProtocol/XPtypes.hh" -#include "XrdSut/XrdSutPFEntry.hh" -#include "XrdOuc/XrdOucHash.hh" -#include "XrdOuc/XrdOucString.hh" -#include "XrdSys/XrdSysPthread.hh" - -/******************************************************************************/ -/* */ -/* For caching temporary information during the authentication handshake */ -/* */ -/******************************************************************************/ - -class XrdSutPFCacheRef -{ -public: - -inline void Lock(XrdSysMutex *Mutex) - {if (mtx) {if (mtx != Mutex) mtx->UnLock(); - else return; - } - Mutex->Lock(); - mtx = Mutex; - }; - -inline void Set(XrdSysMutex *Mutex) - {if (mtx) {if (mtx != Mutex) mtx->UnLock(); - else return; - } - mtx = Mutex; - }; - -inline void UnLock() {if (mtx) {mtx->UnLock(); mtx = 0;}} - - XrdSutPFCacheRef() : mtx(0) {} - - ~XrdSutPFCacheRef() {if (mtx) UnLock();} -protected: -XrdSysMutex *mtx; -}; - -class XrdSutPFCache -{ -private: - XrdSysRWLock rwlock; // Access synchronizator - int cachesz; // Number of entries allocated - int cachemx; // Largest Index of allocated entries - XrdSutPFEntry **cachent; // Pointers to filled entries - kXR_int32 utime; // time at which was last updated - int lifetime; // lifetime (in secs) of the cache info - XrdOucHash hashtable; // Reflects the file index structure - kXR_int32 htmtime; // time at which hash table was last rebuild - XrdOucString pfile; // file name (if loaded from file) - bool isinit; // true if already initialized - - XrdSutPFEntry *Get(const char *ID, bool *wild); - bool Delete(XrdSutPFEntry *pfEnt); - - static const int maxTries = 100; // Max time to try getting a lock - static const int retryMSW = 300; // Milliseconds to wait to get lock - -public: - XrdSutPFCache() { cachemx = -1; cachesz = 0; cachent = 0; lifetime = 300; - utime = -1; htmtime = -1; pfile = ""; isinit = 0; } - virtual ~XrdSutPFCache(); - - // Status - int Entries() const { return (cachemx+1); } - bool Empty() const { return (cachemx == -1); } - - // Initialization methods - int Init(int capacity = 100, bool lock = 1); - int Reset(int newsz = -1, bool lock = 1); - int Load(const char *pfname); // build cache of a pwd file - int Flush(const char *pfname = 0); // flush content to pwd file - int Refresh(); // refresh content from source file - int Rehash(bool force = 0, bool lock = 1); // (re)build hash table - void SetLifetime(int lifet = 300) { lifetime = lifet; } - - // Cache management - XrdSutPFEntry *Get(int i) const { return (i<=cachemx) ? cachent[i] : - (XrdSutPFEntry *)0; } - XrdSutPFEntry *Get(XrdSutPFCacheRef &urRef, const char *ID, bool *wild = 0); - XrdSutPFEntry *Add(XrdSutPFCacheRef &urRef, const char *ID, bool force = 0); - bool Remove(const char *ID, int opt = 1); - int Trim(int lifet = 0); - - // For debug purposes - void Dump(const char *msg= 0); -}; - -#endif - diff --git a/src/XrdSut/XrdSutPFEntry.cc b/src/XrdSut/XrdSutPFEntry.cc deleted file mode 100644 index e246671adcd..00000000000 --- a/src/XrdSut/XrdSutPFEntry.cc +++ /dev/null @@ -1,184 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S u t P F E n t r y . c c */ -/* */ -/* (c) 2012 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Gerri Ganis for CERN */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include - -#include "XrdSutAux.hh" -#include "XrdSutPFEntry.hh" - -//__________________________________________________________________ -XrdSutPFBuf::XrdSutPFBuf(char *b, kXR_int32 l) -{ - // Constructor - - len = 0; - buf = 0; - if (b) { - buf = b; - len = l; - } -} - -//__________________________________________________________________ -XrdSutPFBuf::XrdSutPFBuf(const XrdSutPFBuf &b) -{ - //Copy constructor - - buf = 0; - len = 0; - if (b.buf) { - buf = new char[b.len]; - if (buf) { - memcpy(buf,b.buf,b.len); - len = b.len; - } - } -} - -//__________________________________________________________________ -void XrdSutPFBuf::SetBuf(const char *b, kXR_int32 l) -{ - // Set the buffer - - len = 0; - if (buf) { - delete[] buf; - buf = 0; - } - if (b && l > 0) { - buf = new char[l]; - if (buf) { - memcpy(buf,b,l); - len = l; - } - } -} - -//____________________________________________________________________ -XrdSutPFEntry::XrdSutPFEntry(const char *n, short st, short cn, - kXR_int32 mt) -{ - // Constructor - - name = 0; - status = st; - cnt = cn; - mtime = (mt > 0) ? mt : (kXR_int32)time(0); - if (n) { - name = new char[strlen(n)+1]; - if (name) - strcpy(name,n); - } -} - -//_____________________________________________________________________ -XrdSutPFEntry::XrdSutPFEntry(const XrdSutPFEntry &e) : buf1(e.buf1), - buf2(e.buf2), buf3(e.buf3), buf4(e.buf4) -{ - // Copy constructor - - name = 0; - status = e.status; - cnt = e.cnt; - mtime = e.mtime; - if (e.name) { - name = new char[strlen(e.name)+1]; - if (name) - strcpy(name,e.name); - } -} - -//____________________________________________________________________ -void XrdSutPFEntry::Reset() -{ - // Resetting entry - - if (name) - delete[] name; - name = 0; - status = 0; - cnt = 0; - mtime = (kXR_int32)time(0); - buf1.SetBuf(); - buf2.SetBuf(); - buf3.SetBuf(); - buf4.SetBuf(); -} - -//_____________________________________________________________________ -void XrdSutPFEntry::SetName(const char *n) -{ - // Set the name - - if (name) { - delete[] name; - name = 0; - } - if (n) { - name = new char[strlen(n)+1]; - if (name) - strcpy(name,n); - } -} - -//_____________________________________________________________________ -char *XrdSutPFEntry::AsString() const -{ - // Return a string with serialized information - // For print purposes - // The output string points to a static buffer, so it must - // not be deleted by the caller - static char pbuf[2048]; - - char smt[20] = {0}; - XrdSutTimeString(mtime,smt); - - sprintf(pbuf,"st:%d cn:%d buf:%d,%d,%d,%d modified:%s name:%s", - status,cnt,buf1.len,buf2.len,buf3.len,buf4.len,smt,name); - - return pbuf; -} - -//______________________________________________________________________________ -XrdSutPFEntry& XrdSutPFEntry::operator=(const XrdSutPFEntry &e) -{ - // Assign entry e to local entry. - - SetName(name); - status = e.status; - cnt = e.cnt; // counter - mtime = e.mtime; // time of last modification / creation - buf1.SetBuf(e.buf1.buf); - buf2.SetBuf(e.buf2.buf); - buf3.SetBuf(e.buf3.buf); - buf4.SetBuf(e.buf4.buf); - - return (*this); -} diff --git a/src/XrdSut/XrdSutPFEntry.hh b/src/XrdSut/XrdSutPFEntry.hh deleted file mode 100644 index 699e71fc001..00000000000 --- a/src/XrdSut/XrdSutPFEntry.hh +++ /dev/null @@ -1,102 +0,0 @@ -#ifndef __SUT_PFENTRY_H -#define __SUT_PFENTRY_H -/******************************************************************************/ -/* */ -/* X r d S u t P F E n t r y . h h */ -/* */ -/* (c) 2005 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Gerri Ganis for CERN */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XProtocol/XProtocol.hh" -#include "XrdSys/XrdSysPthread.hh" - -/******************************************************************************/ -/* */ -/* Class defining the basic entry into a PFile */ -/* */ -/******************************************************************************/ - -enum kPFEntryStatus { - kPFE_inactive = -2, // -2 inactive: eliminated at next trim - kPFE_disabled, // -1 disabled, cannot be enabled - kPFE_allowed, // 0 empty creds, can be enabled - kPFE_ok, // 1 enabled and OK - kPFE_onetime, // 2 enabled, can be used only once - kPFE_expired, // 3 enabled, creds to be changed at next used - kPFE_special, // 4 special (non-creds) entry - kPFE_anonymous, // 5 enabled, OK, no creds, counter - kPFE_crypt // 6 enabled, OK, crypt-like credentials -}; - -// -// Buffer used internally by XrdSutPFEntry -// -class XrdSutPFBuf { -public: - char *buf; - kXR_int32 len; - XrdSutPFBuf(char *b = 0, kXR_int32 l = 0); - XrdSutPFBuf(const XrdSutPFBuf &b); - - virtual ~XrdSutPFBuf() { if (len > 0 && buf) delete[] buf; } - - void SetBuf(const char *b = 0, kXR_int32 l = 0); -}; - -// -// Generic File entry: it stores a -// -// name -// status 2 bytes -// cnt 2 bytes -// mtime 4 bytes -// buf1, buf2, buf3, buf4 -// -// The buffers are generic buffers to store bufferized info -// -class XrdSutPFEntry { -public: - char *name; - short status; - short cnt; // counter - kXR_int32 mtime; // time of last modification / creation - XrdSutPFBuf buf1; - XrdSutPFBuf buf2; - XrdSutPFBuf buf3; - XrdSutPFBuf buf4; - XrdSysMutex pfeMutex; // Locked when reference is outstanding - XrdSutPFEntry(const char *n = 0, short st = 0, short cn = 0, - kXR_int32 mt = 0); - XrdSutPFEntry(const XrdSutPFEntry &e); - virtual ~XrdSutPFEntry() { if (name) delete[] name; } - kXR_int32 Length() const { return (buf1.len + buf2.len + 2*sizeof(short) + - buf3.len + buf4.len + 5*sizeof(kXR_int32)); } - void Reset(); - void SetName(const char *n = 0); - char *AsString() const; - - XrdSutPFEntry &operator=(const XrdSutPFEntry &pfe); -}; - -#endif diff --git a/src/XrdSut/XrdSutPFile.cc b/src/XrdSut/XrdSutPFile.cc deleted file mode 100644 index bd2a99d7ca2..00000000000 --- a/src/XrdSut/XrdSutPFile.cc +++ /dev/null @@ -1,2308 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S u t P F i l e . c c */ -/* */ -/* (c) 2012 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Gerri Ganis for CERN */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "XrdSut/XrdSutAux.hh" -#include "XrdSut/XrdSutPFEntry.hh" -#include "XrdSut/XrdSutPFile.hh" -#include "XrdSut/XrdSutTrace.hh" - -//_________________________________________________________________ -XrdSutPFEntInd::XrdSutPFEntInd(const char *n, kXR_int32 no, - kXR_int32 eo, kXR_int32 es) -{ - // Constructor - - name = 0; - if (n) { - name = new char[strlen(n)+1]; - if (name) - strcpy(name,n); - } - nxtofs = no; - entofs = eo; - entsiz = es; -} - -//_________________________________________________________________ -XrdSutPFEntInd::XrdSutPFEntInd(const XrdSutPFEntInd &ei) -{ - //Copy constructor - - name = 0; - if (ei.name) { - name = new char[strlen(ei.name)+1]; - if (name) - strcpy(name,ei.name); - } - nxtofs = ei.nxtofs; - entofs = ei.entofs; - entsiz = ei.entsiz; -} - -//_________________________________________________________________ -void XrdSutPFEntInd::SetName(const char *n) -{ - // Name setter - - if (name) { - delete[] name; - name = 0; - } - if (n) { - name = new char[strlen(n)+1]; - if (name) - strcpy(name,n); - } -} - -//______________________________________________________________________________ -XrdSutPFEntInd& XrdSutPFEntInd::operator=(const XrdSutPFEntInd ei) -{ - // Assign index entry ei to local index entry. - - name = 0; - if (ei.name) { - name = new char[strlen(ei.name)+1]; - if (name) - strcpy(name,ei.name); - } - nxtofs = ei.nxtofs; - entofs = ei.entofs; - entsiz = ei.entsiz; - - return *this; -} - -//_________________________________________________________________ -XrdSutPFHeader::XrdSutPFHeader(const char *id, kXR_int32 v, kXR_int32 ct, - kXR_int32 it, kXR_int32 ent, kXR_int32 ofs) -{ - // Constructor - - memset(fileID,0,kFileIDSize); - if (id) { - kXR_int32 lid = strlen(id); - if (lid > kFileIDSize) - lid = kFileIDSize; - memcpy(fileID,id,lid); - } - version = v; - ctime = ct; - itime = it; - entries = ent; - indofs = ofs; - jnksiz = 0; // At start everything is reachable -} - -//_________________________________________________________________ -XrdSutPFHeader::XrdSutPFHeader(const XrdSutPFHeader &fh) -{ - // Copy constructor - - memcpy(fileID,fh.fileID,kFileIDSize); - version = fh.version; - ctime = fh.ctime; - itime = fh.itime; - entries = fh.entries; - indofs = fh.indofs; - jnksiz = fh.jnksiz; -} - -//_________________________________________________________________ -void XrdSutPFHeader::Print() const -{ - // Header printout - - struct tm tst; - - // String form for time of last change - char sctime[256] = {0}; - time_t ttmp = ctime; - localtime_r(&ttmp,&tst); - asctime_r(&tst,sctime); - sctime[strlen(sctime)-1] = 0; - - // String form for time of last index change - char sitime[256] = {0}; - ttmp = itime; - localtime_r(&ttmp,&tst); - asctime_r(&tst,sitime); - sitime[strlen(sitime)-1] = 0; - - fprintf(stdout, - "//------------------------------------" - "------------------------------//\n" - "// \n" - "// File Header dump \n" - "// \n" - "// File ID: %s \n" - "// version: %d \n" - "// last changed on: %s (%d sec) \n" - "// index changed on: %s (%d sec) \n" - "// entries: %d \n" - "// unreachable: %d \n" - "// first ofs: %d \n" - "// \n" - "//------------------------------------" - "------------------------------//\n", - fileID,version,sctime,ctime,sitime,itime,entries,jnksiz,indofs); -} - -//________________________________________________________________ -XrdSutPFile::XrdSutPFile(const char *n, kXR_int32 openmode, - kXR_int32 createmode, bool hashtab) -{ - // Constructor - - name = 0; - if (n) { - name = new char[strlen(n)+1]; - if (name) - strcpy(name,n); - } - valid = 0; - fFd = -1; - fHTutime = -1; - fHashTable = 0; - - valid = Init(n, openmode, createmode, hashtab); -} - -//________________________________________________________________ -XrdSutPFile::XrdSutPFile(const XrdSutPFile &f) -{ - // Copy constructor - - name = 0; - if (f.name) { - name = new char[strlen(f.name)+1]; - if (name) - strcpy(name,f.name); - } - fFd = f.fFd ; -} - -//________________________________________________________________ -XrdSutPFile::~XrdSutPFile() -{ - // Destructor - - if (name) - delete[] name; - name = 0; - if (fHashTable) - delete fHashTable; - fHashTable = 0; - - Close(); -} - -//________________________________________________________________ -bool XrdSutPFile::Init(const char *n, kXR_int32 openmode, - kXR_int32 createmode, bool hashtab) -{ - // (re)initialize PFile - - // Make sure it is closed - Close(); - - // Reset members - if (name) - delete[] name; - name = 0; - if (n) { - name = new char[strlen(n)+1]; - if (name) - strcpy(name,n); - } - valid = 0; - fFd = -1; - fHTutime = -1; - if (fHashTable) - delete fHashTable; - fHashTable = 0; - - // If name is missing nothing can be done - if (!name) - return 0; - - // open modes - bool create = (openmode & kPFEcreate); - bool leaveopen = (openmode & kPFEopen); - - // If file does not exists, create it with default header - struct stat st; - if (stat(name, &st) == -1) { - if (errno == ENOENT) { - if (create) { - if (Open(1,0,0,createmode) > 0) { - kXR_int32 ct = (kXR_int32)time(0); - XrdSutPFHeader hdr(kDefFileID,kXrdIFVersion,ct,ct,0,0); - WriteHeader(hdr); - valid = 1; - if (!leaveopen) - Close(); - } - } else { - Err(kPFErrNoFile,"Init",name); - } - } - } else { - // Fill the the hash table - if (Open(1) > 0) { - if (hashtab) - UpdateHashTable(); - valid = 1; - if (!leaveopen) - Close(); - } - } - // We are done - return valid; -} - -//_________________________________________________________________ -kXR_int32 XrdSutPFile::Open(kXR_int32 opt, bool *wasopen, - const char *nam, kXR_int32 createmode) -{ - // Open the stream, so defining fFd . - // Valid options: - // 0 read only - // 1 read/write append - // 2 read/write truncate - // For options 1 and 2 the file is created, if not existing, - // and permission set to createmode (default: 0600). - // If the file name ends with 'XXXXXX' and it does not exist, - // it is created as temporary using mkstemp. - // The file is also exclusively locked. - // If nam is defined it is used as file name - // If the file is already open and wasopen is allocated, then *wasopen - // is set to true - // The file descriptor of the open file is returned - XrdOucString copt(opt); - - // Reset was open flag - if (wasopen) *wasopen = 0; - - // File name must be defined - char *fnam = (char *)nam; - if (!fnam) - fnam = name; - if (!fnam) - return Err(kPFErrBadInputs,"Open"); - - // If already open, do nothing - if (!nam && fFd > -1) { - if (opt > 0) { - // Make sure that the write flag is set - long omode = 0; - if (fcntl(fFd, F_GETFL, &omode) != -1) { - if (!(omode | O_WRONLY)) - return Err(kPFErrFileAlreadyOpen,"Open"); - } - } - if (wasopen) *wasopen = 1; - return fFd; - } - - // Ok, we have a file name ... check if it exists already - bool newfile = 0; - struct stat st; - if (stat(fnam, &st) == -1) { - if (errno != ENOENT) { - return Err(kPFErrNoFile,"Open",fnam); - } else { - if (opt == 0) - return Err(kPFErrStat,"Open",fnam); - newfile = 1; - } - } - - // Now open it - if (!nam) - fFd = -1; - kXR_int32 fd = -1; - // - // If we have to create a new file and the file name ends with - // 'XXXXXX', make it temporary with mkstemp - char *pn = strstr(fnam,"XXXXXX"); - if (pn && (pn == (fnam + strlen(fnam) - 6))) { - if (opt > 0 && newfile) { - fd = mkstemp(fnam); - if (fd <= -1) - return Err(kPFErrFileOpen,"Open",fnam); - } - } - // - // If normal file act according to requests - if (fd <= -1) { - kXR_int32 mode = 0; - switch (opt) { - case 2: - // - // Forcing truncation in Read / Write mode - mode |= (O_TRUNC | O_RDWR) ; - if (newfile) - mode |= O_CREAT ; - break; - case 1: - // - // Read / Write - mode |= O_RDWR ; - if (newfile) - mode |= O_CREAT ; - break; - case 0: - // - // Read only - mode = O_RDONLY ; - break; - default: - // - // Unknown option - return Err(kPFErrBadOp,"Open",copt.c_str()); - } - - // Open file (createmode is only used if O_CREAT is set) - fd = open(fnam, mode, createmode); - if (fd <= -1) - return Err(kPFErrFileOpen,"Open",fnam); - } - - // - // Shared or exclusive lock of the whole file - int lockmode = (opt > 0) ? (F_WRLCK | F_RDLCK) : F_RDLCK; - int lck = kMaxLockTries; - int rc = 0; - while (lck && rc == -1) { - struct flock flck; - memset(&flck, 0, sizeof(flck)); - flck.l_type = lockmode; - flck.l_whence = SEEK_SET; - if ((rc = fcntl(fd, F_SETLK, &flck)) == 0) - break; - struct timespec lftp, rqtp = {1, 0}; - while (nanosleep(&rqtp, &lftp) < 0 && errno == EINTR) { - rqtp.tv_sec = lftp.tv_sec; - rqtp.tv_nsec = lftp.tv_nsec; - } - } - if (rc == -1) { - if (errno == EACCES || errno == EAGAIN) { - // File locked by other process - int pid = -1; - struct flock flck; - memset(&flck, 0, sizeof(flck)); - flck.l_type = lockmode; - flck.l_whence = SEEK_SET; - if (fcntl(fd,F_GETLK,&flck) != -1) - pid = flck.l_pid; - close(fd); - return Err(kPFErrFileLocked,"Open",fnam,(const char *)&pid); - } else { - // Error - return Err(kPFErrLocking,"Open",fnam,(const char *)&fd); - } - } - - // Ok, we got the file open and locked - if (!nam) - fFd = fd; - return fd; -} - -//_________________________________________________________________ -kXR_int32 XrdSutPFile::Close(kXR_int32 fd) -{ - // Close the open stream or descriptor fd, if > -1 . - // The file is unlocked before. - - // If not open, do nothing - if (fd < 0) - fd = fFd; - if (fd < 0) - return 0; - - // - // Unlock the file - struct flock flck; - memset(&flck, 0, sizeof(flck)); - flck.l_type = F_UNLCK; - flck.l_whence = SEEK_SET; - if (fcntl(fd, F_SETLK, &flck) == -1) { - close(fd); - return Err(kPFErrUnlocking,"Close",(const char *)&fd); - } - - // - // Close it - close(fd); - - // Reset file descriptor - if (fd == fFd) - fFd = -1; - - return 0; -} - -//_________________________________________________________________ -kXR_int32 XrdSutPFile::UpdateHeader(XrdSutPFHeader hd) -{ - // Write/Update header to beginning of file - - // - // Open the file - if (Open(1) < 0) - return -1; - - // Write - kXR_int32 nw = WriteHeader(hd); - - // Close the file - Close(); - - return nw; -} - -//_________________________________________________________________ -kXR_int32 XrdSutPFile::RetrieveHeader(XrdSutPFHeader &hd) -{ - // Retrieve number of entries in the file - - // - // Open the file - bool wasopen = 0; - if (Open(1, &wasopen) < 0) - return -1; - - // Read header - kXR_int32 rc = ReadHeader(hd); - - // Close the file - if (!wasopen) Close(); - - return rc; -} - -//_________________________________________________________________ -kXR_int32 XrdSutPFile::WriteHeader(XrdSutPFHeader hd) -{ - // Write/Update header to beginning of opne stream - - // - // Build output buffer - // Get total lenght needed - kXR_int32 ltot = hd.Length(); - // - // Allocate the buffer - char *bout = new char[ltot]; - if (!bout) - return Err(kPFErrOutOfMemory,"WriteHeader"); - // - // Fill the buffer - kXR_int32 lp = 0; - // File ID - memcpy(bout+lp,hd.fileID,kFileIDSize); - lp += kFileIDSize; - // version - memcpy(bout+lp,&hd.version,sizeof(kXR_int32)); - lp += sizeof(kXR_int32); - // change time - memcpy(bout+lp,&hd.ctime,sizeof(kXR_int32)); - lp += sizeof(kXR_int32); - // index change time - memcpy(bout+lp,&hd.itime,sizeof(kXR_int32)); - lp += sizeof(kXR_int32); - // entries - memcpy(bout+lp,&hd.entries,sizeof(kXR_int32)); - lp += sizeof(kXR_int32); - // offset of the first index entry - memcpy(bout+lp,&hd.indofs,sizeof(kXR_int32)); - lp += sizeof(kXR_int32); - // number of unused bytes - memcpy(bout+lp,&hd.jnksiz,sizeof(kXR_int32)); - lp += sizeof(kXR_int32); - // Check length - if (lp != ltot) { - if (bout) delete[] bout; - return Err(kPFErrLenMismatch,"WriteHeader", - (const char *)&lp, (const char *)<ot); - } - // - // Ready to write: check we got the file - if (fFd < 0) - return Err(kPFErrFileNotOpen,"WriteHeader"); - // - // Set the offset - if (lseek(fFd, 0, SEEK_SET) == -1) { - return Err(kPFErrSeek,"WriteHeader","SEEK_SET",(const char *)&fFd); - } - - kXR_int32 nw = 0; - // Now write the buffer to the stream - while ((nw = write(fFd, bout, ltot)) < 0 && errno == EINTR) - errno = 0; - - return nw; -} - -//______________________________________________________________________ -kXR_int32 XrdSutPFile::WriteEntry(XrdSutPFEntry ent) -{ - // Write entry to file - // Look first if an entry with the same name exists: in such - // case try to overwrite the allocated file region; if the space - // is not enough, set the existing entry inactive and write - // the new entry at the end of the file, updating all the - // pointers. - // File must be opened in read/write mode (O_RDWR). - - // Make sure that the entry is named (otherwise we can't do nothing) - if (!ent.name) - return Err(kPFErrBadInputs,"WriteEntry"); - - // - // Ready to write: open the file - bool wasopen = 0; - if (Open(1, &wasopen) < 0) - return -1; - - kXR_int32 ofs = 0; - kXR_int32 nw = 0; - kXR_int32 indofs = 0; - // Read the header - XrdSutPFHeader header; - if (ReadHeader(header) < 0) { - if (!wasopen) Close(); - return -1; - } - if ((ofs = lseek(fFd, 0, SEEK_CUR)) == -1) { - if (!wasopen) Close(); - return Err(kPFErrSeek,"WriteEntry","SEEK_CUR",(const char *)&fFd); - } - - XrdSutPFEntInd ind; - // If first entry, write it, update the info and return - if (header.entries == 0) { - if ((nw = WriteEnt(ofs, ent)) < 0) { - if (!wasopen) Close(); - return -1; - } - ind.SetName(ent.name); - ind.nxtofs = 0; - ind.entofs = ofs; - ind.entsiz = nw; - indofs = ofs + nw; - if (WriteInd(indofs, ind) < 0) { - if (!wasopen) Close(); - return -1; - } - // Update header - header.entries = 1; - header.indofs = indofs; - header.ctime = time(0); - header.itime = header.ctime; - if (WriteHeader(header) < 0) { - if (!wasopen) Close(); - return -1; - } - if (!wasopen) Close(); - return nw; - } - - // First Localize existing entry, if any - kXR_int32 nr = 1; - bool found = 0; - indofs = header.indofs; - kXR_int32 lastindofs = indofs; - while (!found && nr > 0 && indofs > 0) { - nr = ReadInd(indofs, ind); - if (nr) { - if (ind.entofs > 0 && !strcmp(ent.name,ind.name)) { - found = 1; - break; - } - lastindofs = indofs; - indofs = ind.nxtofs; - } - } - - // - // If an entry already exists and there is enough space to - // store the update, write the update at the already allocated - // space; if not, add it at the end. - if (found) { - // Update - kXR_int32 ct = 0; - if (ind.entsiz >= ent.Length()) { - // The offset is set inside ... - if ((nw = WriteEnt(ind.entofs, ent)) < 0) { - if (!wasopen) Close(); - return -1; - } - } else { - // Add it at the end - kXR_int32 entofs = 0; - if ((entofs = lseek(fFd, 0, SEEK_END)) == -1) { - if (!wasopen) Close(); - return Err(kPFErrSeek,"WriteEntry", - "SEEK_END",(const char *)&fFd); - } - if ((nw = WriteEnt(entofs, ent)) < 0) { - if (!wasopen) Close(); - return -1; - } - // Set existing entry inactive - kXR_int32 wrtofs = ind.entofs; - if (lseek(fFd, wrtofs, SEEK_SET) == -1) { - if (!wasopen) Close(); - return Err(kPFErrSeek,"WriteEntry", - "SEEK_SET",(const char *)&fFd); - } - short status = kPFE_inactive; - while (write(fFd, &status, sizeof(short)) < 0 && - errno == EINTR) errno = 0; - // Reset entry area - if (Reset(wrtofs + sizeof(short), ind.entsiz - sizeof(short)) < 0) { - if (!wasopen) Close(); - return -1; - } - // Count as unused bytes - header.jnksiz += ind.entsiz; - if (lseek(fFd, kOfsJnkSiz, SEEK_SET) == -1) { - if (!wasopen) Close(); - return Err(kPFErrSeek,"WriteEntry", - "SEEK_SET",(const char *)&fFd); - } - while (write(fFd, &header.jnksiz, sizeof(kXR_int32)) < 0 && - errno == EINTR) errno = 0; - // Update the entry index and new size - wrtofs = indofs + 2*sizeof(kXR_int32); - if (lseek(fFd, wrtofs, SEEK_SET) == -1) { - if (!wasopen) Close(); - return Err(kPFErrSeek,"WriteEntry", - "SEEK_SET",(const char *)&fFd); - } - while (write(fFd, &entofs, sizeof(kXR_int32)) < 0 && - errno == EINTR) errno = 0; - while (write(fFd, &nw, sizeof(kXR_int32)) < 0 && - errno == EINTR) errno = 0; - // Update time of change of index - ct = (kXR_int32)time(0); - header.itime = ct; - if (lseek(fFd, kOfsItime, SEEK_SET) == -1) { - if (!wasopen) Close(); - return Err(kPFErrSeek,"WriteEntry", - "SEEK_SET",(const char *)&fFd); - } - while (write(fFd, &header.itime, sizeof(kXR_int32)) < 0 && - errno == EINTR) errno = 0; - } - // Update time of change in header - header.ctime = (ct > 0) ? ct : time(0); - if (lseek(fFd, kOfsCtime, SEEK_SET) == -1) { - if (!wasopen) Close(); - return Err(kPFErrSeek,"WriteEntry", - "SEEK_SET",(const char *)&fFd); - } - while (write(fFd, &header.ctime, sizeof(kXR_int32)) < 0 && - errno == EINTR) errno = 0; - if (!wasopen) Close(); - return nw; - } - - // - // If new name, add the entry at the end - if ((ofs = lseek(fFd, 0, SEEK_END)) == -1) { - if (!wasopen) Close(); - return Err(kPFErrSeek,"WriteEntry", - "SEEK_END",(const char *)&fFd); - } - if ((nw = WriteEnt(ofs, ent)) < 0) { - if (!wasopen) Close(); - return -1; - } - // - // Create new index entry - XrdSutPFEntInd newind(ent.name, 0, ofs, nw); - if (WriteInd(ofs+nw, newind) < 0) { - if (!wasopen) Close(); - return -1; - } - // - // Update previous index entry - ind.nxtofs = ofs + nw; - kXR_int32 wrtofs = lastindofs + sizeof(kXR_int32); - if (lseek(fFd, wrtofs, SEEK_SET) == -1) { - if (!wasopen) Close(); - return Err(kPFErrSeek,"WriteEntry", - "SEEK_SET",(const char *)&fFd); - } - while (write(fFd, &ind.nxtofs, sizeof(kXR_int32)) < 0 && - errno == EINTR) errno = 0; - - // Update header - header.entries += 1; - header.ctime = time(0); - header.itime = header.ctime; - if (WriteHeader(header) < 0) { - if (!wasopen) Close(); - return -1; - } - - // Close the file - if (!wasopen) Close(); - - return nw; -} - -//________________________________________________________________ -kXR_int32 XrdSutPFile::UpdateCount(const char *tag, int *cnt, - int step, bool reset) -{ - // Update counter for entry with 'tag', if any. - // If reset is true, counter is firts reset. - // The counter is updated by 'step'. - // Default: no reset, increase by 1. - // If cnt is defined, fill it with the updated counter. - // Returns 0 or -1 in case of error - - // Make sure that we got a tag (otherwise we can't do nothing) - if (!tag) - return Err(kPFErrBadInputs,"UpdateCount"); - - // Make sure we got an open stream - if (Open(1) < 0) - return -1; - - // Read the header - XrdSutPFHeader header; - if (ReadHeader(header) < 0) { - Close(); - return -1; - } - - // Check if the HashTable needs to be updated - if (fHashTable && header.itime > fHTutime) { - // Update the table - if (UpdateHashTable() < 0) { - Close(); - return -1; - } - } - // - // Get index entry associated with tag, if any - XrdSutPFEntInd ind; - bool found = 0; - if (fHashTable) { - kXR_int32 *refofs = fHashTable->Find(tag); - if (*refofs > 0) { - // Read it out - if (ReadInd(*refofs, ind) < 0) { - Close(); - return -1; - } - found = 1; - } - } else { - // Get offset of the first index entry - kXR_int32 indofs = header.indofs; - while (indofs > 0) { - // Read it out - if (ReadInd(indofs, ind) < 0) { - Close(); - return -1; - } - // Check compatibility - if (strlen(ind.name) == strlen(tag)) { - if (!strncmp(ind.name,tag,strlen(tag))) { - found = 1; - break; - } - } - // Next index entry - indofs = ind.nxtofs; - } - } - // - // Read the entry, if found - XrdSutPFEntry ent; - bool changed = 0; - if (found) { - - // Read entry if active - if (ind.entofs) { - if (ReadEnt(ind.entofs, ent) < 0) { - Close(); - return -1; - } - // - // Reset counter if required - if (reset && ent.cnt != 0) { - changed = 1; - ent.cnt = 0; - } - // - // Update counter - if (step != 0) { - changed = 1; - ent.cnt += step; - } - // - // Update entry in file, if anything changed - if (changed) { - ent.mtime = (kXR_int32)time(0); - if (WriteEnt(ind.entofs, ent) < 0) { - Close(); - return -1; - } - } - // - // Fill output - if (cnt) - *cnt = ent.cnt; - } - } - - // Close the file - Close(); - - return 0; -} - -//________________________________________________________________ -kXR_int32 XrdSutPFile::ReadEntry(const char *tag, - XrdSutPFEntry &ent, int opt) -{ - // Read entry with tag from file - // If it does not exist, if opt == 1 search also for wild-card - // matching entries; if more than 1 return the one that matches - // the best, base on the number of characters matching. - // If more wild-card entries have the same level of matching, - // the first found is returned. - ent.Reset(); - - // Make sure that we got a tag (otherwise we can't do nothing) - if (!tag) - return Err(kPFErrBadInputs,"ReadEntry"); - - // Make sure we got an open stream - bool wasopen = 0; - if (Open(1 &wasopen) < 0) - return -1; - - // Read the header - XrdSutPFHeader header; - if (ReadHeader(header) < 0) { - if (!wasopen) Close(); - return -1; - } - - // Check if the HashTable needs to be updated - if (fHashTable && header.itime > fHTutime) { - // Update the table - if (UpdateHashTable() < 0) { - if (!wasopen) Close(); - return -1; - } - } - // - // Get index entry associated with tag, if any - XrdSutPFEntInd ind; - bool found = 0; - if (fHashTable) { - kXR_int32 *reftmp = fHashTable->Find(tag); - kXR_int32 refofs = reftmp ? *reftmp : -1; - if (refofs > 0) { - // Read it out - if (ReadInd(refofs, ind) < 0) { - if (!wasopen) Close(); - return -1; - } - found = 1; - } - } else { - // Get offset of the first index entry - kXR_int32 indofs = header.indofs; - while (indofs > 0) { - // Read it out - if (ReadInd(indofs, ind) < 0) { - if (!wasopen) Close(); - return -1; - } - // Check compatibility - if (strlen(ind.name) == strlen(tag)) { - if (!strncmp(ind.name,tag,strlen(tag))) { - found = 1; - break; - } - } - // Next index entry - indofs = ind.nxtofs; - } - } - // - // If not found and requested, try also wild-cards - if (!found && opt == 1) { - // - // If > 1 we will keep the best matching, i.e. the one - // matching most of the chars in tag - kXR_int32 refofs = -1; - kXR_int32 nmmax = 0; - kXR_int32 iofs = header.indofs; - XrdOucString stag(tag); - while (iofs) { - // - // Read it out - if (ReadInd(iofs, ind) < 0) { - if (!wasopen) Close(); - return -1; - } - // - // Check compatibility, if active - if (ind.entofs > 0) { - int match = stag.matches(ind.name); - if (match > nmmax && ind.entofs > 0) { - nmmax = match; - refofs = iofs; - } - } - // - // Next index entry - iofs = ind.nxtofs; - } - // - // Read it out - if (refofs > 0) { - if (ReadInd(refofs, ind) < 0) { - if (!wasopen) Close(); - return -1; - } - found = 1; - } - } - - // Read the entry, if found - kXR_int32 nr = 0; - if (found) { - - // Read entry if active - if (ind.entofs) { - if ((nr = ReadEnt(ind.entofs, ent)) < 0) { - if (!wasopen) Close(); - return -1; - } - // Fill the name - ent.SetName(ind.name); - } - } - - // Close the file - if (!wasopen) Close(); - - return nr; -} - -//________________________________________________________________ -kXR_int32 XrdSutPFile::ReadEntry(kXR_int32 ofs, XrdSutPFEntry &ent) -{ - // Read entry at ofs from file - - // Make sure that ofs makes sense - if (ofs <= 0) - return Err(kPFErrBadInputs,"ReadEntry"); - - // Make sure we got an open stream - bool wasopen = 0; - if (Open(1, &wasopen) < 0) - return -1; - - kXR_int32 nr = 0; - - // Read index entry out - XrdSutPFEntInd ind; - if (ReadInd(ofs, ind) < 0) { - if (!wasopen) Close(); - return -1; - } - - // Read entry - if ((nr = ReadEnt(ind.entofs, ent)) < 0) { - if (!wasopen) Close(); - return -1; - } - - // Fill the name - ent.SetName(ind.name); - - // Close the file - if (!wasopen) Close(); - - return nr; -} - -//________________________________________________________________ -kXR_int32 XrdSutPFile::RemoveEntry(const char *tag) -{ - // Remove entry with tag from file - // The entry is set inactive, so that it is hidden and it will - // be physically removed at next Trim - - // Make sure that we got a tag (otherwise we can't do nothing) - if (!tag || !strlen(tag)) - return Err(kPFErrBadInputs,"RemoveEntry"); - - // Make sure we got an open stream - if (Open(1) < 0) - return -1; - - // Read the header - XrdSutPFHeader header; - if (ReadHeader(header) < 0) { - Close(); - return -1; - } - - // Check if the HashTable needs to be updated - if (fHashTable && header.itime > fHTutime) { - // Update the table - if (UpdateHashTable() < 0) { - Close(); - return -1; - } - } - - // Get offset of the index entry associated with tag, if any - XrdSutPFEntInd ind; - bool found = 0; - kXR_int32 indofs = -1; - if (fHashTable) { - kXR_int32 *indtmp = fHashTable->Find(tag); - indofs = indtmp ? *indtmp : indofs; - if (indofs > 0) { - // Read it out - if (ReadInd(indofs, ind) < 0) { - Close(); - return -1; - } - found = 1; - } - } else { - // Get offset of the first index entry - indofs = header.indofs; - while (indofs > 0) { - // Read it out - if (ReadInd(indofs, ind) < 0) { - Close(); - return -1; - } - // Check compatibility - if (strlen(ind.name) == strlen(tag)) { - if (!strncmp(ind.name,tag,strlen(tag))) { - found = 1; - break; - } - } - // Next index entry - indofs = ind.nxtofs; - } - } - // - // Get entry now, if index found - if (found) { - // Reset entry area - short status = kPFE_inactive; - if (lseek(fFd, ind.entofs, SEEK_SET) == -1) { - Close(); - return Err(kPFErrSeek,"RemoveEntry", - "SEEK_SET",(const char *)&fFd); - } - while (write(fFd, &status, sizeof(short)) < 0 && - errno == EINTR) errno = 0; - // Reset entry area - if (Reset(ind.entofs + sizeof(short), ind.entsiz - sizeof(short)) < 0) { - Close(); - return -1; - } - // Set entofs to null - ind.entofs = 0; - if (WriteInd(indofs, ind) < 0) { - Close(); - return -1; - } - // Count as unused bytes - header.jnksiz += ind.entsiz; - // Decrease number of entries - header.entries--; - // Update times - header.ctime = (kXR_int32)time(0); - header.itime = header.ctime; - // Update header - if (WriteHeader(header) < 0) { - Close(); - return -1; - } - - // Ok: close the file and return - Close(); - return 0; - } - - // Close the file - Close(); - // entry non-existing - return -1; -} - -//________________________________________________________________ -kXR_int32 XrdSutPFile::RemoveEntry(kXR_int32 ofs) -{ - // Remove entry at entry index offset ofs from file - // The entry is set inactive, so that it is hidden and it will - // be physically removed at next Trim - - // Make sure that we got a tag (otherwise we can't do nothing) - if (ofs <= 0) - return Err(kPFErrBadInputs,"RemoveEntry"); - - // Make sure we got an open stream - if (Open(1) < 0) - return -1; - - // Read the header - XrdSutPFHeader header; - if (ReadHeader(header) < 0) { - Close(); - return -1; - } - - // Check if the HashTable needs to be updated - if (header.itime > fHTutime) { - // Update the table - if (UpdateHashTable() < 0) { - Close(); - return -1; - } - } - // - // Read it out - XrdSutPFEntInd ind; - if (ReadInd(ofs, ind) < 0) { - Close(); - return -1; - } - // - // Reset entry area - short status = kPFE_inactive; - if (lseek(fFd, ind.entofs, SEEK_SET) == -1) { - Close(); - return Err(kPFErrSeek,"RemoveEntry", - "SEEK_SET",(const char *)&fFd); - } - while (write(fFd, &status, sizeof(short)) < 0 && - errno == EINTR) errno = 0; - // Reset entry area - if (Reset(ind.entofs + sizeof(short), ind.entsiz - sizeof(short)) < 0) { - Close(); - return -1; - } - // Set entofs to null - ind.entofs = 0; - if (WriteInd(ofs, ind) < 0) { - Close(); - return -1; - } - // Count as unused bytes - header.jnksiz += ind.entsiz; - // Decrease number of entries - header.entries--; - // Update times - header.ctime = (kXR_int32)time(0); - header.itime = header.ctime; - // Update header - if (WriteHeader(header) < 0) { - Close(); - return -1; - } - // - // Ok: close the file and return - Close(); - return 0; -} - -//_________________________________________________________________ -kXR_int32 XrdSutPFile::Reset(kXR_int32 ofs, kXR_int32 siz) -{ - // Reset size bytes starting at ofs in the open stream - - // - // Set the offset - if (lseek(fFd, ofs, SEEK_SET) == -1) - return Err(kPFErrSeek,"Reset", - "SEEK_SET",(const char *)&fFd); - - kXR_int32 nrs = 0; - // Now write the buffer to the stream - while (nrs < siz) { - char c = 0; - while (write(fFd, &c, 1) < 0 && errno == EINTR) - errno = 0; - nrs++; - } - - return nrs; -} - - -//__________________________________________________________________ -kXR_int32 XrdSutPFile::WriteInd(kXR_int32 ofs, XrdSutPFEntInd ind) -{ - // Write entry index to open stream fFd - - // Make sure we got an open stream - if (fFd < 0) - return Err(kPFErrFileNotOpen,"WriteInd"); - // - // Set the offset - if (lseek(fFd, ofs, SEEK_SET) == -1) - return Err(kPFErrSeek,"WriteInd", - "SEEK_SET",(const char *)&fFd); - // - // Build output buffer - // - // Get total lenght needed - kXR_int32 ltot = ind.Length(); - // - // Allocate the buffer - char *bout = new char[ltot]; - if (!bout) - return Err(kPFErrOutOfMemory,"WriteInd"); - // - // Fill the buffer - kXR_int32 lp = 0; - // Name length - kXR_int32 lnam = strlen(ind.name); - memcpy(bout+lp,&lnam,sizeof(kXR_int32)); - lp += sizeof(kXR_int32); - // Offset of next index entry - memcpy(bout+lp,&ind.nxtofs,sizeof(kXR_int32)); - lp += sizeof(kXR_int32); - // Offset of entry - memcpy(bout+lp,&ind.entofs,sizeof(kXR_int32)); - lp += sizeof(kXR_int32); - // Size allocated for entry - memcpy(bout+lp,&ind.entsiz,sizeof(kXR_int32)); - lp += sizeof(kXR_int32); - // name - memcpy(bout+lp,ind.name,lnam); - lp += lnam; - // Check length - if (lp != ltot) { - if (bout) delete[] bout; - return Err(kPFErrLenMismatch,"WriteInd", - (const char *)&lp, (const char *)<ot); - } - - kXR_int32 nw = 0; - // Now write the buffer to the stream - while ((nw = write(fFd, bout, ltot)) < 0 && errno == EINTR) - errno = 0; - - return nw; -} - -//__________________________________________________________________ -kXR_int32 XrdSutPFile::WriteEnt(kXR_int32 ofs, XrdSutPFEntry ent) -{ - // Write ent to stream out - - // Make sure we got an open stream - if (fFd < 0) - return Err(kPFErrFileNotOpen,"WriteEnt"); - // - // Set the offset - if (lseek(fFd, ofs, SEEK_SET) == -1) - return Err(kPFErrSeek,"WriteEnt", - "SEEK_SET",(const char *)&fFd); - // - // Build output buffer - // - // Get total lenght needed - kXR_int32 ltot = ent.Length(); - // - // Allocate the buffer - char *bout = new char[ltot]; - if (!bout) - return Err(kPFErrOutOfMemory,"WriteEnt"); - // - // Fill the buffer - kXR_int32 lp = 0; - // status - memcpy(bout+lp,&ent.status,sizeof(short)); - lp += sizeof(short); - // count - memcpy(bout+lp,&ent.cnt,sizeof(short)); - lp += sizeof(short); - // time of modification / creation - memcpy(bout+lp,&ent.mtime,sizeof(kXR_int32)); - lp += sizeof(kXR_int32); - // length of first buffer - memcpy(bout+lp,&ent.buf1.len,sizeof(kXR_int32)); - lp += sizeof(kXR_int32); - // length of second buffer - memcpy(bout+lp,&ent.buf2.len,sizeof(kXR_int32)); - lp += sizeof(kXR_int32); - // length of third buffer - memcpy(bout+lp,&ent.buf3.len,sizeof(kXR_int32)); - lp += sizeof(kXR_int32); - // length of fourth buffer - memcpy(bout+lp,&ent.buf4.len,sizeof(kXR_int32)); - lp += sizeof(kXR_int32); - if (ent.buf1.len > 0) { - // first buffer - memcpy(bout+lp,ent.buf1.buf,ent.buf1.len); - lp += ent.buf1.len; - } - if (ent.buf2.len > 0) { - // second buffer - memcpy(bout+lp,ent.buf2.buf,ent.buf2.len); - lp += ent.buf2.len; - } - if (ent.buf3.len > 0) { - // third buffer - memcpy(bout+lp,ent.buf3.buf,ent.buf3.len); - lp += ent.buf3.len; - } - if (ent.buf4.len > 0) { - // third buffer - memcpy(bout+lp,ent.buf4.buf,ent.buf4.len); - lp += ent.buf4.len; - } - // Check length - if (lp != ltot) { - if (bout) delete[] bout; - return Err(kPFErrLenMismatch,"WriteEnt", - (const char *)&lp, (const char *)<ot); - } - - kXR_int32 nw = 0; - // Now write the buffer to the stream - while ((nw = write(fFd, bout, ltot)) < 0 && errno == EINTR) - errno = 0; - - return nw; -} - -//__________________________________________________________________ -kXR_int32 XrdSutPFile::ReadHeader(XrdSutPFHeader &hd) -{ - // Read header from beginning of stream - - // - // Make sure that we got an open file description - if (fFd < 0) - return Err(kPFErrFileNotOpen,"ReadHeader"); - // - // Set the offset - if (lseek(fFd, 0, SEEK_SET) == -1) - return Err(kPFErrSeek,"ReadHeader", - "SEEK_SET",(const char *)&fFd); - - kXR_int32 nr = 0, nrdt = 0; - // - // Now read the information step by step: - // the file ID ... - if ((nr = read(fFd,hd.fileID,kFileIDSize)) != kFileIDSize) - return Err(kPFErrRead,"ReadHeader",(const char *)&fFd); - hd.fileID[kFileIDSize-1] = 0; - nrdt += nr; - // the version ... - if ((nr = read(fFd,&hd.version,sizeof(kXR_int32))) != sizeof(kXR_int32)) - return Err(kPFErrRead,"ReadHeader",(const char *)&fFd); - nrdt += nr; - // the time of last change ... - if ((nr = read(fFd,&hd.ctime,sizeof(kXR_int32))) != sizeof(kXR_int32)) - return Err(kPFErrRead,"ReadHeader",(const char *)&fFd); - nrdt += nr; - // the time of last index change ... - if ((nr = read(fFd,&hd.itime,sizeof(kXR_int32))) != sizeof(kXR_int32)) - return Err(kPFErrRead,"ReadHeader",(const char *)&fFd); - nrdt += nr; - // the number of entries ... - if ((nr = read(fFd,&hd.entries,sizeof(kXR_int32))) != sizeof(kXR_int32)) - return Err(kPFErrRead,"ReadHeader",(const char *)&fFd); - nrdt += nr; - // the offset of first index entry ... - if ((nr = read(fFd,&hd.indofs,sizeof(kXR_int32))) != sizeof(kXR_int32)) - return Err(kPFErrRead,"ReadHeader",(const char *)&fFd); - nrdt += nr; - // the number of unused bytes ... - if ((nr = read(fFd,&hd.jnksiz,sizeof(kXR_int32))) != sizeof(kXR_int32)) - return Err(kPFErrRead,"ReadHeader",(const char *)&fFd); - nrdt += nr; - - return nrdt; -} - -//_____________________________________________________________________ -kXR_int32 XrdSutPFile::ReadInd(kXR_int32 ofs, XrdSutPFEntInd &ind) -{ - // Read entry index from offset ofs of open stream fFd - - // - // Make sure that we got an open file description - if (fFd < 0) - return Err(kPFErrFileNotOpen,"ReadInd"); - // - // Set the offset - if (lseek(fFd, ofs, SEEK_SET) == -1) - return Err(kPFErrSeek,"ReadInd", - "SEEK_SET",(const char *)&fFd); - - kXR_int32 nr = 0, nrdt = 0; - // - // Now read the information step by step: - // the length of the name ... - kXR_int32 lnam = 0; - if ((nr = read(fFd,&lnam,sizeof(kXR_int32))) != sizeof(kXR_int32)) - return Err(kPFErrRead,"ReadInd",(const char *)&fFd); - nrdt += nr; - // the offset of next entry index ... - if ((nr = read(fFd,&ind.nxtofs,sizeof(kXR_int32))) != sizeof(kXR_int32)) - return Err(kPFErrRead,"ReadInd",(const char *)&fFd); - nrdt += nr; - // the offset of the entry ... - if ((nr = read(fFd,&ind.entofs,sizeof(kXR_int32))) != sizeof(kXR_int32)) - return Err(kPFErrRead,"ReadInd",(const char *)&fFd); - nrdt += nr; - // the size allocated for the entry ... - if ((nr = read(fFd,&ind.entsiz,sizeof(kXR_int32))) != sizeof(kXR_int32)) - return Err(kPFErrRead,"ReadInd",(const char *)&fFd); - nrdt += nr; - // the name ... cleanup first - if (ind.name) { - delete[] ind.name; - ind.name = 0; - } - if (lnam) { - ind.name = new char[lnam+1]; - if (ind.name) { - if ((nr = read(fFd,ind.name,lnam)) != lnam) - return Err(kPFErrRead,"ReadInd",(const char *)&fFd); - ind.name[lnam] = 0; // null-terminated - nrdt += nr; - } else - return Err(kPFErrOutOfMemory,"ReadInd"); - } - - return nrdt; -} - -//____________________________________________________________________ -kXR_int32 XrdSutPFile::ReadEnt(kXR_int32 ofs, XrdSutPFEntry &ent) -{ - // Read ent from current position at stream - - // - // Make sure that we got an open file description - if (fFd < 0) - return Err(kPFErrFileNotOpen,"ReadEnt"); - // - // Set the offset - if (lseek(fFd, ofs, SEEK_SET) == -1) - return Err(kPFErrSeek,"ReadEnt", - "SEEK_SET",(const char *)&fFd); - - kXR_int32 nr = 0, nrdt = 0; - // - // Now read the information step by step: - // the status ... - if ((nr = read(fFd,&ent.status,sizeof(short))) != sizeof(short)) - return Err(kPFErrRead,"ReadEnt",(const char *)&fFd); - nrdt += nr; - // the count var ... - if ((nr = read(fFd,&ent.cnt,sizeof(short))) != sizeof(short)) - return Err(kPFErrRead,"ReadEnt",(const char *)&fFd); - nrdt += nr; - // the the time of modification / creation ... - if ((nr = read(fFd,&ent.mtime,sizeof(kXR_int32))) != sizeof(kXR_int32)) - return Err(kPFErrRead,"ReadEnt",(const char *)&fFd); - nrdt += nr; - // the length of the first buffer ... - if ((nr = read(fFd,&ent.buf1.len,sizeof(kXR_int32))) != sizeof(kXR_int32)) - return Err(kPFErrRead,"ReadEnt",(const char *)&fFd); - nrdt += nr; - // the length of the second buffer ... - if ((nr = read(fFd,&ent.buf2.len,sizeof(kXR_int32))) != sizeof(kXR_int32)) - return Err(kPFErrRead,"ReadEnt",(const char *)&fFd); - nrdt += nr; - // the length of the third buffer ... - if ((nr = read(fFd,&ent.buf3.len,sizeof(kXR_int32))) != sizeof(kXR_int32)) - return Err(kPFErrRead,"ReadEnt",(const char *)&fFd); - nrdt += nr; - // the length of the fourth buffer ... - if ((nr = read(fFd,&ent.buf4.len,sizeof(kXR_int32))) != sizeof(kXR_int32)) - return Err(kPFErrRead,"ReadEnt",(const char *)&fFd); - nrdt += nr; - // Allocate space for the first buffer and read it (if any) ... - if (ent.buf1.len) { - ent.buf1.buf = new char[ent.buf1.len]; - if (!ent.buf1.buf) - return Err(kPFErrOutOfMemory,"ReadEnt"); - if ((nr = read(fFd,ent.buf1.buf,ent.buf1.len)) != ent.buf1.len) - return Err(kPFErrRead,"ReadEnt",(const char *)&fFd); - nrdt += nr; - } - // Allocate space for the second buffer and read it (if any) ... - if (ent.buf2.len) { - ent.buf2.buf = new char[ent.buf2.len]; - if (!ent.buf2.buf) - return Err(kPFErrOutOfMemory,"ReadEnt"); - if ((nr = read(fFd,ent.buf2.buf,ent.buf2.len)) != ent.buf2.len) - return Err(kPFErrRead,"ReadEnt",(const char *)&fFd); - nrdt += nr; - } - // Allocate space for the third buffer and read it (if any) ... - if (ent.buf3.len) { - ent.buf3.buf = new char[ent.buf3.len]; - if (!ent.buf3.buf) - return Err(kPFErrOutOfMemory,"ReadEnt"); - if ((nr = read(fFd,ent.buf3.buf,ent.buf3.len)) != ent.buf3.len) - return Err(kPFErrRead,"ReadEnt",(const char *)&fFd); - nrdt += nr; - } - // Allocate space for the fourth buffer and read it (if any) ... - if (ent.buf4.len) { - ent.buf4.buf = new char[ent.buf4.len]; - if (!ent.buf4.buf) - return Err(kPFErrOutOfMemory,"ReadEnt"); - if ((nr = read(fFd,ent.buf4.buf,ent.buf4.len)) != ent.buf4.len) - return Err(kPFErrRead,"ReadEnt",(const char *)&fFd); - nrdt += nr; - } - - return nrdt; -} - -//________________________________________________________________ -kXR_int32 XrdSutPFile::Browse(void *oout) -{ - // Display the content of the file - - // Make sure we got an open stream - if (Open(1) < 0) - return -1; - - // Read header - XrdSutPFHeader hdr; - if (ReadHeader(hdr) < 0) { - Close(); - return -1; - } - - // Time strings - struct tm tst; - char sctime[256] = {0}; - time_t ttmp = hdr.ctime; - localtime_r(&ttmp,&tst); - asctime_r(&tst,sctime); - sctime[strlen(sctime)-1] = 0; - char sitime[256] = {0}; - ttmp = hdr.itime; - localtime_r(&ttmp,&tst); - asctime_r(&tst,sitime); - sitime[strlen(sitime)-1] = 0; - - // Default is stdout - FILE *out = oout ? (FILE *)oout : stdout; - - fprintf(out,"//-----------------------------------------------------" - "--------------------//\n"); - fprintf(out,"//\n"); - fprintf(out,"// File: %s\n",name); - fprintf(out,"// ID: %s\n",hdr.fileID); - fprintf(out,"// Version: %d\n",hdr.version); - fprintf(out,"// Last change : %s (%d sec)\n",sctime,hdr.ctime); - fprintf(out,"// Index change: %s (%d sec)\n",sitime,hdr.itime); - fprintf(out,"//\n"); - fprintf(out,"// Number of Entries: %d\n",hdr.entries); - fprintf(out,"// Bytes unreachable: %d\n",hdr.jnksiz); - fprintf(out,"//\n"); - - if (hdr.entries > 0) { - - // Special entries first, if any - kXR_int32 ns = SearchSpecialEntries(); - if (ns > 0) { - // Allocate space for offsets - kXR_int32 *sofs = new kXR_int32[ns]; - if (sofs) { - // Get offsets - ns = SearchSpecialEntries(sofs,ns); - fprintf(out,"// Special entries (%d):\n",ns); - int i = 0; - for (; i ns) - fprintf(out,"// Normal entries (%d):\n",hdr.entries-ns); - - kXR_int32 nn = 0; - kXR_int32 nxtofs = hdr.indofs; - while (nxtofs) { - - // Read entry index at ofs - XrdSutPFEntInd ind; - if (ReadInd(nxtofs, ind) < 0) { - Close(); - return -3; - } - - if (ind.entofs) { - // Read entry - XrdSutPFEntry ent; - if (ReadEnt(ind.entofs, ent) < 0) { - Close(); - return -4; - } - if (ent.status != kPFE_special) { - char smt[20] = {0}; - XrdSutTimeString(ent.mtime,smt); - - nn++; - fprintf(out, - "// #:%d st:%d cn:%d buf:%d,%d,%d,%d mod:%s name:%s\n", - nn,ent.status,ent.cnt,ent.buf1.len,ent.buf2.len,ent.buf3.len, - ent.buf4.len,smt,ind.name); - } - } - - // Read next - nxtofs = ind.nxtofs; - } - fprintf(out,"//\n"); - } - fprintf(out,"//-----------------------------------------------------" - "--------------------//\n"); - - // Close the file - Close(); - - return 0; -} - -//________________________________________________________________ -kXR_int32 XrdSutPFile::Trim(const char *fbak) -{ - // Trim away unreachable entries from the file - // Previous content is save in a file name fbak, the default - // being 'name'.bak - EPNAME("PFile::Trim"); - - // Retrieve header, first, to check if there is anything to trim - XrdSutPFHeader header; - if (RetrieveHeader(header) < 0) - return -1; - if (header.jnksiz <= 0) { - DEBUG("nothing to trim - return "); - return -1; - } - - // Get name of backup file - char *nbak = (char *)fbak; - if (!nbak) { - // Use default - nbak = new char[strlen(name)+5]; - if (!nbak) - return Err(kPFErrOutOfMemory,"Trim"); - sprintf(nbak,"%s.bak",name); - DEBUG("backup file: "< 0) { - fFd = fdbck; - if (ReadEnt(ind.entofs,ent) < 0) { - Close(fdnew); Close(fdbck); - return -1; - } - // Update index entry - ind.entofs = wrofs; - - // Write active entry - fFd = fdnew; - if (WriteEnt(wrofs,ent) < 0) { - Close(fdnew); Close(fdbck); - return -1; - } - - // Update write offset - if ((wrofs = lseek(fdnew, 0, SEEK_CUR)) == -1) { - Close(fdnew); Close(fdbck); - return Err(kPFErrSeek,"Trim", - "SEEK_CUR",(const char *)&fdnew); - } - - if (firstind) { - // Update header - header.indofs = wrofs; - firstind = 0; - } else { - // Update previous index entry - indlast.nxtofs = wrofs; - fFd = fdnew; - if (WriteInd(lastofs,indlast) < 0) { - Close(fdnew); Close(fdbck); - return -1; - } - } - - // Save this index for later updates - indlast = ind; - lastofs = wrofs; - - // Last index entry, for now - ind.nxtofs = 0; - - // Write active index entry - fFd = fdnew; - if (WriteInd(wrofs,ind) < 0) { - Close(fdnew); Close(fdbck); - return -1; - } - - // Update write offset - if ((wrofs = lseek(fdnew, 0, SEEK_CUR)) == -1) { - Close(fdnew); Close(fdbck); - return Err(kPFErrSeek,"Trim", - "SEEK_CUR",(const char *)&fdnew); - } - } - } - - // Close backup file - Close(fdbck); - fFd = fdnew; - - // Update header - header.ctime = (kXR_int32)time(0); - header.itime = header.ctime; - header.jnksiz = 0; - - // Copy it to new file - if (WriteHeader(header) < 0) { - Close();; - return -1; - } - - // Close the file - Close(); - - return 0; -} - -//________________________________________________________________ -kXR_int32 XrdSutPFile::UpdateHashTable(bool force) -{ - // Update hash table reflecting the index of the file - // If force is .true. the table is recreated even if no recent - // change in the index has occured. - // Returns the number of entries in the table. - - // The file must be open - if (fFd < 0) - return Err(kPFErrFileNotOpen,"UpdateHashTable"); - - // Read the header - XrdSutPFHeader header; - if (ReadHeader(header) < 0) - return -1; - - // If no recent changes and no force option, return - if (!force && header.itime < fHTutime) - return 0; - - // Clean up the table or create it - if (fHashTable) - fHashTable->Purge(); - else - fHashTable = new XrdOucHash; - // Make sure we have it - if (!fHashTable) - return Err(kPFErrOutOfMemory,"UpdateHashTable"); - - // Read entries - kXR_int32 ne = 0; - if (header.entries > 0) { - XrdSutPFEntInd ind; - kXR_int32 nxtofs = header.indofs; - while (nxtofs > 0) { - if (ReadInd(nxtofs, ind) < 0) - return -1; - ne++; - // Fill the table - kXR_int32 *key = new kXR_int32(nxtofs); - fHashTable->Add(ind.name,key); - // Go to next - nxtofs = ind.nxtofs; - } - } - - // Update the time stamp - fHTutime = (kXR_int32)time(0); - - return ne; -} - -//________________________________________________________________ -kXR_int32 XrdSutPFile::RemoveEntries(const char *tag, char opt) -{ - // Remove entries whose tag is compatible with 'tag', according - // to compatibility option 'opt'. - // For opt = 0 tags starting with 'tag' - // for opt = 1 tags containing the wild card '*' are matched. - // Return number of entries removed - EPNAME("PFile::RemoveEntries"); - - // - // Get number of entries related - int nm = SearchEntries(tag,opt); - if (nm) { - DEBUG("found "< 0 && ind.entofs > 0) { - no++; - if (ofs) { - ofs[no-1] = indofs; - if (no == nofs) { - // We are done - break; - } - } - } - - // Next index entry - indofs = ind.nxtofs; - } - - // Close the file - if (!wasopen) Close(); - - return no; -} - -//________________________________________________________________ -kXR_int32 XrdSutPFile::SearchSpecialEntries(kXR_int32 *ofs, - kXR_int32 nofs) -{ - // Get offsets of the first nofs entries with status - // kPFE_special. - // The caller is responsible for memory pointed by 'ofs'. - // Return number of entries found (<= nofs). - // If ofs = 0, return total number of special entries. - - // Make sure we got an open stream - bool wasopen = 0; - if (Open(1,&wasopen) < 0) - return -1; - - // Read the header - XrdSutPFHeader header; - if (ReadHeader(header) < 0) { - if (!wasopen) Close(); - return -1; - } - - // Get offset of the first index entry - kXR_int32 indofs = header.indofs; - - // Scan entries - kXR_int32 no = 0; - while (indofs) { - - // Read index - XrdSutPFEntInd ind; - if (ReadInd(indofs, ind) < 0) { - if (!wasopen) Close(); - return -1; - } - - // If active ... - if (ind.entofs > 0) { - - // Read entry out - XrdSutPFEntry ent; - if (ReadEnt(ind.entofs, ent) < 0) { - if (!wasopen) Close(); - return -1; - } - // If special ... - if (ent.status == kPFE_special) { - // Record the offset ... - no++; - if (ofs) { - ofs[no-1] = indofs; - if (no == nofs) { - // We are done - break; - } - } - } - } - - // Next index entry - indofs = ind.nxtofs; - } - - // Close the file - if (!wasopen) Close(); - - return no; -} - -//________________________________________________________________ -kXR_int32 XrdSutPFile::Err(kXR_int32 code, const char *loc, - const char *em1, const char *em2) -{ - // Save code and, if requested, format and print an error - // message - EPNAME("PFile::Err"); - - char buf[XrdSutMAXBUF]; - int fd = 0, lp = 0, lt = 0; - - // Save code for later use - fError = code; - - // Build string following the error code - char *errbuf = strerror(errno); - switch (code) { - case kPFErrBadInputs: - snprintf(buf,XrdSutMAXBUF, - "XrdSutPFile::%s: bad input arguments",loc); - break; - case kPFErrFileAlreadyOpen: - snprintf(buf,XrdSutMAXBUF, - "XrdSutPFile::%s: file already open" - " in incompatible mode",loc); - break; - case kPFErrNoFile: - snprintf(buf,XrdSutMAXBUF, - "XrdSutPFile::%s: file %s does not exists", - loc,em1); - break; - case kPFErrFileRename: - snprintf(buf,XrdSutMAXBUF, - "XrdSutPFile::%s: error renaming file %s to %s" - " (%s)",loc,em1,em2,errbuf); - break; - case kPFErrStat: - snprintf(buf,XrdSutMAXBUF, - "XrdSutPFile::%s: cannot file %s (%s)", - loc,em1,errbuf); - break; - case kPFErrFileOpen: - snprintf(buf,XrdSutMAXBUF, - "XrdSutPFile::%s: cannot open file %s (%s)", - loc,em1,errbuf); - break; - case kPFErrFileNotOpen: - snprintf(buf,XrdSutMAXBUF, - "XrdSutPFile::%s: file is not open", loc); - break; - case kPFErrLocking: - fd = *((int *)em1); - snprintf(buf,XrdSutMAXBUF, - "XrdSutPFile::%s: cannot lock file descriptor %d (%s)", - loc,fd,errbuf); - break; - case kPFErrUnlocking: - fd = *((int *)em1); - snprintf(buf,XrdSutMAXBUF, - "XrdSutPFile::%s: cannot unlock file descriptor %d (%s)", - loc,fd,errbuf); - break; - case kPFErrFileLocked: - fd = *((int *)em2); - snprintf(buf,XrdSutMAXBUF, - "XrdSutPFile::%s: file %s is locked by process %d", - loc,em1,fd); - break; - case kPFErrSeek: - fd = *((int *)em2); - snprintf(buf,XrdSutMAXBUF, - "XrdSutPFile::%s: lseek %s error on descriptor %d (%s)", - loc,em1,fd,errbuf); - break; - case kPFErrRead: - fd = *((int *)em1); - snprintf(buf,XrdSutMAXBUF, - "XrdSutPFile::%s: read error on descriptor %d (%s)", - loc,fd,errbuf); - break; - case kPFErrOutOfMemory: - snprintf(buf,XrdSutMAXBUF, - "XrdSutPFile::%s: out of memory (%s)", - loc,errbuf); - break; - case kPFErrLenMismatch: - lp = *((int *)em1); - lt = *((int *)em2); - snprintf(buf,XrdSutMAXBUF, - "XrdSutPFile::%s: length mismatch: %d (expected: %d)", - loc,lp,lt); - break; - case kPFErrBadOp: - snprintf(buf,XrdSutMAXBUF, - "XrdSutPFile::%s: bad option: %s", loc,em1); - break; - default: - DEBUG("unknown error code: "<. */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#ifndef __XPROTOCOL_H -#include "XProtocol/XProtocol.hh" -#endif -#ifndef __OOUC_HASH__ -#include "XrdOuc/XrdOucHash.hh" -#endif -#ifndef __OUC_STRING_H__ -#include "XrdOuc/XrdOucString.hh" -#endif - -/******************************************************************************/ -/* */ -/* Interface class to file to store login-related information */ -/* */ -/******************************************************************************/ - -#define kFileIDSize 8 -#define kDefFileID "XrdIF" -#define kXrdIFVersion 1 - -#define kOfsFileID 0 -#define kOfsVersion 8 // == kFileIDSize (if this changes remember to scale -#define kOfsCtime 12 // accordingly the other offsets ...) -#define kOfsItime 16 -#define kOfsEntries 20 -#define kOfsIndOfs 24 -#define kOfsJnkSiz 28 - -#define kPFEcreate 0x1 -#define kPFEopen 0x2 - -#define kMaxLockTries 3 - -enum EPFileErrors { - kPFErrBadInputs, - kPFErrFileAlreadyOpen, - kPFErrNoFile, - kPFErrFileRename, - kPFErrStat, - kPFErrFileOpen, - kPFErrFileNotOpen, - kPFErrLocking, - kPFErrUnlocking, - kPFErrFileLocked, - kPFErrSeek, - kPFErrRead, - kPFErrOutOfMemory, - kPFErrLenMismatch, - kPFErrBadOp -}; - -class XrdSutPFEntry; - -class XrdSutPFEntInd { -public: - char *name; - kXR_int32 nxtofs; - kXR_int32 entofs; - kXR_int32 entsiz; - XrdSutPFEntInd(const char *n = 0, - kXR_int32 no = 0, kXR_int32 eo = 0, kXR_int32 es = 0); - XrdSutPFEntInd(const XrdSutPFEntInd &ei); - virtual ~XrdSutPFEntInd() { if (name) delete[] name; } - - kXR_int32 Length() const { return (strlen(name) + 4*sizeof(kXR_int32)); } - void SetName(const char *n = 0); - - // Assignement operator - XrdSutPFEntInd &operator=(const XrdSutPFEntInd ei); -}; - -class XrdSutPFHeader { -public: - char fileID[kFileIDSize]; - kXR_int32 version; - kXR_int32 ctime; // time of file change - kXR_int32 itime; // time of index change - kXR_int32 entries; - kXR_int32 indofs; - kXR_int32 jnksiz; // number of unreachable bytes - XrdSutPFHeader(const char *id = " ", kXR_int32 v = 0, kXR_int32 ct = 0, - kXR_int32 it = 0, kXR_int32 ent = 0, kXR_int32 ofs = 0); - XrdSutPFHeader(const XrdSutPFHeader &fh); - virtual ~XrdSutPFHeader() { } - void Print() const; - - static kXR_int32 Length() { return (kFileIDSize + 6*sizeof(kXR_int32)); } -}; - - -class XrdSutPFile { - - friend class XrdSutPFCache; // for open/close operation; - -private: - char *name; - bool valid; // If the file is usable ... - kXR_int32 fFd; - XrdOucHash *fHashTable; // Reflects the file index structure - kXR_int32 fHTutime; // time at which fHashTable was updated - kXR_int32 fError; // last error - XrdOucString fErrStr; // description of last error - - // Entry low level access - kXR_int32 WriteHeader(XrdSutPFHeader hd); - kXR_int32 ReadHeader(XrdSutPFHeader &hd); - kXR_int32 WriteInd(kXR_int32 ofs, XrdSutPFEntInd ind); - kXR_int32 ReadInd(kXR_int32 ofs, XrdSutPFEntInd &ind); - kXR_int32 WriteEnt(kXR_int32 ofs, XrdSutPFEntry ent); - kXR_int32 ReadEnt(kXR_int32 ofs, XrdSutPFEntry &ent); - - // Reset (set inactive) - kXR_int32 Reset(kXR_int32 ofs, kXR_int32 size); - - // Hash table operations - kXR_int32 UpdateHashTable(bool force = 0); - - // For errors - kXR_int32 Err(kXR_int32 code, const char *loc, - const char *em1 = 0, const char *em2 = 0); - -public: - XrdSutPFile(const char *n, kXR_int32 openmode = kPFEcreate, - kXR_int32 createmode = 0600, bool hashtab = 1); - XrdSutPFile(const XrdSutPFile &f); - virtual ~XrdSutPFile(); - - // Initialization method - bool Init(const char *n, kXR_int32 openmode = kPFEcreate, - kXR_int32 createmode = 0600, bool hashtab = 1); - - // Open/Close operations - kXR_int32 Open(kXR_int32 opt, bool *wasopen = 0, - const char *nam = 0, kXR_int32 createmode = 0600); - kXR_int32 Close(kXR_int32 d = -1); - - // File name - const char *Name() const { return (const char *)name; } - // (Un)Successful attachement - bool IsValid() const { return valid; } - // Last error - kXR_int32 LastError() const { return fError; } - const char *LastErrStr() const { return (const char *)fErrStr.c_str(); } - - // Update Methods - kXR_int32 RemoveEntry(const char *name); - kXR_int32 RemoveEntry(kXR_int32 ofs); - kXR_int32 RemoveEntries(const char *name, char opt); - kXR_int32 Trim(const char *fbak = 0); - kXR_int32 UpdateHeader(XrdSutPFHeader hd); - kXR_int32 WriteEntry(XrdSutPFEntry ent); - kXR_int32 UpdateCount(const char *nm, int *cnt = 0, int step = 1, bool reset = 0); - kXR_int32 ResetCount(const char *nm) { return UpdateCount(nm,0,0,1); } - kXR_int32 ReadCount(const char *nm, int &cnt) { return UpdateCount(nm,&cnt,0); } - - // Access methods - kXR_int32 RetrieveHeader(XrdSutPFHeader &hd); - kXR_int32 ReadEntry(const char *name, XrdSutPFEntry &ent, int opt = 0); - kXR_int32 ReadEntry(kXR_int32 ofs, XrdSutPFEntry &ent); - kXR_int32 SearchEntries(const char *name, char opt, - kXR_int32 *ofs = 0, kXR_int32 nofs = 1); - kXR_int32 SearchSpecialEntries(kXR_int32 *ofs = 0, kXR_int32 nofs = 1); - - // Browser - kXR_int32 Browse(void *out = 0); -}; - -#endif diff --git a/src/XrdSut/XrdSutRndm.cc b/src/XrdSut/XrdSutRndm.cc deleted file mode 100644 index de508147ef9..00000000000 --- a/src/XrdSut/XrdSutRndm.cc +++ /dev/null @@ -1,259 +0,0 @@ - -/******************************************************************************/ -/* */ -/* X r d S u t R n d m . c c */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Gerri Ganis for CERN */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "XrdOuc/XrdOucString.hh" -#include "XrdSut/XrdSutRndm.hh" -#include "XrdSut/XrdSutTrace.hh" - -/******************************************************************************/ -/* M a s k s f o r A S C I I c h a r a c t e r s */ -/******************************************************************************/ - -static kXR_unt32 XrdSutCharMsk[4][4] = - { {0x0, 0xffffff08, 0xafffffff, 0x2ffffffe}, // any printable char - {0x0, 0x3ff0000, 0x7fffffe, 0x7fffffe}, // letters/numbers (up/low case) - {0x0, 0x3ff0000, 0x7e, 0x7e}, // hex characters (up/low case) - {0x0, 0x3ffc000, 0x7fffffe, 0x7fffffe} }; // crypt like [a-zA-Z0-9./] - -/******************************************************************************/ -/* */ -/* Provider of random bunches of bits */ -/* */ -/******************************************************************************/ - -bool XrdSutRndm::fgInit = 0; - -//______________________________________________________________________________ -bool XrdSutRndm::Init(bool force) -{ - // Initialize the random machinery; try using /dev/urandom to avoid - // hanging. - // The bool 'force' can be used to force re-initialization. - EPNAME("Rndm::Init"); - - const char *randdev = "/dev/urandom"; - bool rc = 0; - - // We do not do it twice - if (fgInit && !force) - return 1; - - int fd; - unsigned int seed = 0; - if ((fd = open(randdev, O_RDONLY)) != -1) { - DEBUG("taking seed from " < 3) { - opt = 0; - DEBUG("unknown option: " <> m); - j = i / 32; - l = i - j * 32; - if ((XrdSutCharMsk[opt][j] & (1 << l))) { - buf[k] = i; - k++; - } - if (k == len) - break; - } - } - - // null terminated - buf[len] = 0; - DEBUG("got: " <= 0 && opt <= 3); - - kXR_int32 k = 0; - kXR_int32 i, m, frnd, j = 0, l = 0; - while (k < len) { - frnd = rand(); - for (m = 0; m < 32; m += 8) { - i = 0xFF & (frnd >> m); - bool keep = 1; - if (filter) { - j = i / 32; - l = i - j * 32; - keep = (XrdSutCharMsk[opt][j] & (1 << l)); - } - if (keep) { - buf[k] = i; - k++; - } - if (k == len) - break; - } - } - - return buf; -} - -//______________________________________________________________________________ -int XrdSutRndm::GetRndmTag(XrdOucString &rtag) -{ - // Static method generating a 64 bit random tag (8 chars in [a-zA-Z0-9./]) - // saved in rtag. - // Return 0 in case of success; in case of error, -1 is returned - // and errno set accordingly (see XrdSutRndm::GetString) - - return XrdSutRndm::GetString(3,8,rtag); -} - - -//______________________________________________________________________________ -unsigned int XrdSutRndm::GetUInt() -{ - // Static method to return an unsigned int. - - // Init Random machinery ... if needed - if (!fgInit) { - Init(); - fgInit = 1; - } - - // As simple as this - return rand(); -} diff --git a/src/XrdSut/XrdSutRndm.hh b/src/XrdSut/XrdSutRndm.hh deleted file mode 100644 index 6d0728d423c..00000000000 --- a/src/XrdSut/XrdSutRndm.hh +++ /dev/null @@ -1,67 +0,0 @@ -#ifndef __SUT_RNDM_H__ -#define __SUT_RNDM_H__ -/******************************************************************************/ -/* */ -/* X r d S u t R n d m . h h */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Gerri Ganis for CERN */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#ifndef __SUT_AUX_H__ -#include "XrdSut/XrdSutAux.hh" -#endif - -/******************************************************************************/ -/* */ -/* Provider of random bunches of bits */ -/* */ -/******************************************************************************/ - -class XrdOucString; - -class XrdSutRndm { - -public: - static bool fgInit; - - XrdSutRndm() { if (!fgInit) fgInit = XrdSutRndm::Init(); } - virtual ~XrdSutRndm() { } - - // Initializer - static bool Init(bool force = 0); - - // Buffer provider - static char *GetBuffer(int len, int opt = -1); - // String provider - static int GetString(int opt, int len, XrdOucString &s); - static int GetString(const char *copt, int len, XrdOucString &s); - // Integer providers - static unsigned int GetUInt(); - // Random Tag - static int GetRndmTag(XrdOucString &rtag); -} -; - -#endif - diff --git a/src/XrdSut/XrdSutTrace.hh b/src/XrdSut/XrdSutTrace.hh deleted file mode 100644 index 388823a2a18..00000000000 --- a/src/XrdSut/XrdSutTrace.hh +++ /dev/null @@ -1,63 +0,0 @@ -#ifndef ___SUT_TRACE_H___ -#define ___SUT_TRACE_H___ -/******************************************************************************/ -/* */ -/* X r d S u t T r a c e . h h */ -/* */ -/* (C) 2005 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Gerri Ganis for CERN */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#ifndef ___OUC_TRACE_H___ -#include "XrdOuc/XrdOucTrace.hh" -#endif -#ifndef ___SUT_AUX_H___ -#include "XrdSut/XrdSutAux.hh" -#endif - -#ifndef NODEBUG - -#include "XrdSys/XrdSysHeaders.hh" - -#define QTRACE(act) (sutTrace && (sutTrace->What & sutTRACE_ ## act)) -#define PRINT(y) {if (sutTrace) {sutTrace->Beg(epname); \ - cerr <End();}} -#define TRACE(act,x) if (QTRACE(act)) PRINT(x) -#define DEBUG(y) TRACE(Debug,y) -#define EPNAME(x) static const char *epname = x; - -#else - -#define QTRACE(x) -#define PRINT(x) -#define TRACE(x,y) -#define DEBUG(x) -#define EPNAME(x) - -#endif - -// -// For error logging and tracing -extern XrdOucTrace *sutTrace; - -#endif diff --git a/src/XrdSys/XrdSysAtomics.hh b/src/XrdSys/XrdSysAtomics.hh deleted file mode 100644 index 6c8c2d85e6e..00000000000 --- a/src/XrdSys/XrdSysAtomics.hh +++ /dev/null @@ -1,100 +0,0 @@ -#ifndef _XRDSYSATOMICS_ -#define _XRDSYSATOMICS_ -/******************************************************************************/ -/* */ -/* X r d S y s A t o m i c s . h h */ -/* */ -/* (c) 2011 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -/* The following instruction acronyms are used: - AtomicCAS() -> Compare And [if equal] Set - AtomicFAZ() -> Fetch And Zero -*/ - -/* Portability note: When using fetch-add or fetch-sub in the context of an - assignment statement, you must use the the AtomicFAdd - and AtomicFSub macros to ensure portability and correct - results. Additionally, whenever you use AtomicFAZ as the - only consequent of an if-else statement you *must* places - braces around it otherwise the code will not be portable! - Alternatively, always use the AtomicFZAP macro. -*/ - -#ifdef HAVE_ATOMICS -#define AtomicBeg(Mtx) -#define AtomicEnd(Mtx) -#define AtomicAdd(x, y) __sync_fetch_and_add(&x, y) -#define AtomicFAdd(w,x,y) w = __sync_fetch_and_add(&x, y) -#define AtomicCAS(x, y, z) __sync_bool_compare_and_swap(&x, y, z) -#define AtomicDec(x) __sync_fetch_and_sub(&x, 1) -#define AtomicFAZ(x) __sync_fetch_and_and(&x, 0) -#define AtomicFZAP(w,x) w = __sync_fetch_and_and(&x, 0) -#define AtomicGet(x) __sync_fetch_and_or(&x, 0) -#define AtomicInc(x) __sync_fetch_and_add(&x, 1) -#define AtomicSub(x, y) __sync_fetch_and_sub(&x, y) -#define AtomicFSub(w,x,y) w = __sync_fetch_and_sub(&x, y) -#define AtomicZAP(x) __sync_fetch_and_and(&x, 0) -#define AtomicRet(mtx, x) return AtomicGet(x) -#else -#define AtomicBeg(Mtx) Mtx.Lock() -#define AtomicEnd(Mtx) Mtx.UnLock() -#define AtomicAdd(x, y) x += y // When assigning use AtomicFAdd! -#define AtomicFAdd(w,x,y) {w = x; x += y;} -#define AtomicCAS(x, y, z) if (x == y) x = z -#define AtomicDec(x) x-- -#define AtomicFAZ(x) x; x = 0 // Braces when used with if-else! -#define AtomicFZAP(w,x) {w = x; x = 0;} -#define AtomicGet(x) x -#define AtomicInc(x) x++ -#define AtomicSub(x, y) x -= y // When assigning use AtomicFSub! -#define AtomicFSub(w,x,y) {w = x; x -= y;} -#define AtomicZAP(x) x = 0 -#define AtomicRet(mtx, x) {mtx.Lock(); int _ ## x = x; \ - mtx.UnLock(); return _ ## x;} -#endif -#endif - -/* - * The following definitions give a mechanism for using C++ atomics - * (when using at least C++11). *Note* that these can't be relied - * on for correct behavior as they are non-atomic for C++03 compilers. - * - * Only use them for standards correctness (eliminating C++11 undefined - * behavior). - */ -#if __cplusplus >= 201103L -#include -#define CPP_ATOMIC_LOAD(x, order) x.load(order) -#define CPP_ATOMIC_STORE(x, val, order) x.store(val, order) -#define CPP_ATOMIC_TYPE(kind) std::atomic -#else -#define CPP_ATOMIC_LOAD(x, order) x -#define CPP_ATOMIC_STORE(x, val, order) x = val -#define CPP_ATOMIC_TYPE(kind) kind -#endif - - diff --git a/src/XrdSys/XrdSysDNS.cc b/src/XrdSys/XrdSysDNS.cc deleted file mode 100644 index e36e99a3b0b..00000000000 --- a/src/XrdSys/XrdSysDNS.cc +++ /dev/null @@ -1,769 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S y s D N S . c c */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#ifndef WIN32 -#include -#include -#include -#include -#include -#include -#include -#endif - -#include "XrdSys/XrdSysDNS.hh" -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdSys/XrdSysPlatform.hh" -#include "XrdSys/XrdSysPthread.hh" - -/******************************************************************************/ -/* g e t H o s t A d d r */ -/******************************************************************************/ - -int XrdSysDNS::getHostAddr(const char *InetName, - struct sockaddr InetAddr[], - int maxipa, - char **errtxt) -{ -#ifdef HAVE_NAMEINFO - struct addrinfo *rp, *np, *pnp=0; - struct addrinfo myhints; - memset(&myhints, 0, sizeof(myhints)); - myhints.ai_flags = AI_CANONNAME; -#else - unsigned int addr; - struct hostent hent, *hp; - char **p, hbuff[1024]; -#endif - struct sockaddr_in *ip; - int i, rc; - -// Make sure we have something to lookup here -// - if (!InetName || !InetName[0]) - {ip = (struct sockaddr_in *)&InetAddr[0]; - ip->sin_family = AF_INET; - ip->sin_port = 0; - ip->sin_addr.s_addr = INADDR_ANY; - memset((void *)ip->sin_zero, 0, sizeof(ip->sin_zero)); - return 1; - } - -// Determine how we will resolve the name -// - rc = 0; -#ifndef HAVE_NAMEINFO - if (!isdigit((int)*InetName)) -#ifdef __solaris__ - gethostbyname_r(InetName, &hent, hbuff, sizeof(hbuff), &rc); -#else - gethostbyname_r(InetName, &hent, hbuff, sizeof(hbuff), &hp, &rc); -#endif - else if ((int)(addr = inet_addr(InetName)) == -1) - return (errtxt ? setET(errtxt, EINVAL) : 0); -#ifdef __solaris__ - else gethostbyaddr_r(&addr,sizeof(addr), AF_INET, &hent, - hbuff, sizeof(hbuff), &rc); -#else - else gethostbyaddr_r((char *)&addr,sizeof(addr), AF_INET, &hent, - hbuff, sizeof(hbuff), &hp, &rc); -#endif - if (rc) return (errtxt ? setET(errtxt, rc) : 0); - -// Check if we resolved the name -// - for (i = 0, p = hent.h_addr_list; *p != 0 && i < maxipa; p++, i++) - {ip = (struct sockaddr_in *)&InetAddr[i]; - memcpy((void *)&(ip->sin_addr), (const void *)*p, sizeof(ip->sin_addr)); - ip->sin_port = 0; - ip->sin_family = hent.h_addrtype; - memset((void *)ip->sin_zero, 0, sizeof(ip->sin_zero)); - } - -#else - -// Disable IPv6 for 'localhost...' (potential confusion in the -// default /etc/hosts on some platforms, e.g. MacOsX) -// -// if (!strncmp(InetName,"localhost",9)) myhints.ai_family = AF_INET; -// pcal: force ipv4 (was only for MacOS: ifdef __APPLE__) -//#ifdef __APPLE__ -// Disable IPv6 for MacOS X altogether for the time being -// - myhints.ai_family = AF_INET; -//#endif - -// Translate the name to an address list -// - if (isdigit((int)*InetName)) myhints.ai_flags |= AI_NUMERICHOST; - rc = getaddrinfo(InetName,0,(const addrinfo *)&myhints, &rp); - if (rc || !(np = rp)) return (errtxt ? setETni(errtxt, rc) : 0); - -// Return all of the addresses. On some platforms (like linux) this function is -// brain-dead in that it returns all addreses assoacted with all aliases even -// when those addresses are duplicates. -// - i = 0; - do {if (!pnp - || memcmp((const void *)pnp->ai_addr, (const void *)np->ai_addr, - sizeof(struct sockaddr))) - memcpy((void *)&InetAddr[i++], (const void *)np->ai_addr, - sizeof(struct sockaddr)); - pnp = np; np = np->ai_next; - } while(i < maxipa && np); - freeaddrinfo(rp); - -#endif - -// All done -// - return i; -} - - -/******************************************************************************/ -/* g e t A d d r N a m e */ -/******************************************************************************/ - -int XrdSysDNS::getAddrName(const char *InetName, - int maxipa, char **Addr, char **Name, - char **errtxt) -{ - -// Host or address and output arrays must be defined -// - if (!InetName || !Addr || !Name) return 0; - -// Max 10 addresses and names -// - maxipa = (maxipa > 1 && maxipa <= 10) ? maxipa : 1; - -// Number of addresses - struct sockaddr_in ip[10]; - int n = XrdSysDNS::getHostAddr(InetName,(struct sockaddr *)ip, maxipa, errtxt); - - // Fill address / name strings, if required - int i = 0; - for (; i < n; i++ ) { - - // The address - char buf[255]; - inet_ntop(ip[i].sin_family, &ip[i].sin_addr, buf, sizeof(buf)); - Addr[i] = strdup(buf); - - // The name - char *names[1] = {0}; - struct sockaddr *ipaddr = (struct sockaddr *)&ip[i]; - int hn = getHostName(*ipaddr, names, 1, errtxt); - if (hn) - Name[i] = strdup(names[0]); - else - Name[i] = strdup(Addr[i]); - - // Cleanup - if (names[0]) free(names[0]); - } - - // We are done - return n; -} - -/******************************************************************************/ -/* g e t H o s t I D */ -/******************************************************************************/ - -char *XrdSysDNS::getHostID(struct sockaddr &InetAddr) -{ - struct sockaddr_in *ip = (sockaddr_in *)&InetAddr; - char mybuff[256]; - char *hname; - -// Convert address -// - hname = (char *)inet_ntop(ip->sin_family, - (const void *)(&ip->sin_addr), - mybuff, sizeof(mybuff)); - return (hname ? strdup(hname) : strdup("0.0.0.0")); -} - -/******************************************************************************/ -/* g e t H o s t N a m e ( V a r i a n t 1 ) */ -/******************************************************************************/ - -char *XrdSysDNS::getHostName(const char *InetName, char **errtxt) -{ - char myname[256]; - const char *hp; - struct sockaddr InetAddr; - -// Identify ourselves if we don't have a passed hostname -// - if (InetName) hp = InetName; - else if (gethostname(myname, sizeof(myname))) - {if (errtxt) setET(errtxt, errno); return strdup("0.0.0.0");} - else hp = myname; - -// Get the address -// - if (!getHostAddr(hp, InetAddr, errtxt)) return strdup("0.0.0.0"); - -// Convert it to a fully qualified host name and return it -// - return getHostName(InetAddr, errtxt); -} - -/******************************************************************************/ -/* g e t H o s t N a m e ( V a r i a n t 2 ) */ -/******************************************************************************/ - -char *XrdSysDNS::getHostName(struct sockaddr &InetAddr, char **errtxt) -{ - char *result; - if (getHostName(InetAddr, &result, 1, errtxt)) return result; - - {char dnbuff[64]; - unsigned int ipaddr; - struct sockaddr_in *ip = (sockaddr_in *)&InetAddr; - memcpy(&ipaddr, &ip->sin_addr, sizeof(ipaddr)); - IP2String(ipaddr, -1, dnbuff, sizeof(dnbuff)); - return strdup(dnbuff); - } -} - -/******************************************************************************/ -/* g e t H o s t N a m e ( V a r i a n t 3 ) */ -/******************************************************************************/ - -int XrdSysDNS::getHostName(struct sockaddr &InetAddr, - char *InetName[], - int maxipn, - char **errtxt) -{ - char mybuff[256]; - int i, rc; - -// Preset errtxt to zero -// - if (errtxt) *errtxt = 0; - -// Some platforms have nameinfo but getnameinfo() is broken. If so, we revert -// to using the gethostbyaddr(). -// -#if defined(HAVE_NAMEINFO) && !defined(__APPLE__) - struct addrinfo *rp, *np; - struct addrinfo myhints; - memset(&myhints, 0, sizeof(myhints)); - myhints.ai_flags = AI_CANONNAME; - -#elif defined(HAVE_GETHBYXR) - struct sockaddr_in *ip = (sockaddr_in *)&InetAddr; - struct hostent hent, *hp; - char *hname, hbuff[1024]; -#else - static XrdSysMutex getHN; - XrdSysMutexHelper getHNhelper; - struct sockaddr_in *ip = (sockaddr_in *)&InetAddr; - struct hostent *hp; - unsigned int ipaddr; - char *hname; -#endif - -// Make sure we can return something -// - if (maxipn < 1) return (errtxt ? setET(errtxt, EINVAL) : 0); - -// Check for unix family which is equl to localhost -// - if (InetAddr.sa_family == AF_UNIX) - {InetName[0] = strdup("localhost"); return 1;} - -#if !defined(HAVE_NAMEINFO) || defined(__APPLE__) - -// Convert it to a host name -// - rc = 0; -#ifdef HAVE_GETHBYXR -#ifdef __solaris__ - gethostbyaddr_r(&(ip->sin_addr), sizeof(struct in_addr), - AF_INET, &hent, hbuff, sizeof(hbuff), &rc); - hp = &hent; -#else - gethostbyaddr_r(&(ip->sin_addr), sizeof(struct in_addr), - AF_INET, &hent, hbuff, sizeof(hbuff), &hp, &rc); -#endif -#else - memcpy(&ipaddr, &ip->sin_addr, sizeof(ipaddr)); - getHNhelper.Lock(&getHN); - if (!(hp=gethostbyaddr((const char *)&ipaddr, sizeof(InetAddr), AF_INET))) - rc = (h_errno ? h_errno : EINVAL); -#endif - - if (rc) - {hname = (char *)inet_ntop(ip->sin_family, - (const void *)(&ip->sin_addr), - mybuff, sizeof(mybuff)); - if (!hname) return (errtxt ? setET(errtxt, errno) : 0); - InetName[0] = strdup(hname); - return 1; - } - -// Return first result -// - InetName[0] = LowCase(strdup(hp->h_name)); - -// Return additional names -// - hname = *hp->h_aliases; - for (i = 1; i < maxipn && hname; i++, hname++) - InetName[i] = LowCase(strdup(hname)); -#else - -// Do lookup of canonical name. We can't use getaddrinfo() for this on all -// platforms because AI_CANONICAL was never well defined in the spec. -// - if ((rc = getnameinfo(&InetAddr, sizeof(struct sockaddr), - mybuff, sizeof(mybuff), 0, 0, 0))) - return (errtxt ? setETni(errtxt, rc) : 0); - -// Return the result if aliases not wanted -// - if (maxipn < 2) - {InetName[0] = LowCase(strdup(mybuff)); - return 1; - } - -// Disable IPv6 for 'localhost...' (potential confusion in the -// default /etc/hosts on some platforms, e.g. MacOsX) -// -// if (!strncmp(mybuff,"localhost",9)) myhints.ai_family = AF_INET; -// pcal: force ipv4 -// - myhints.ai_family = AF_INET; - -// Get the aliases for this name -// - rc = getaddrinfo(mybuff,0,(const addrinfo *)&myhints, &rp); - if (rc || !(np = rp)) return (errtxt ? setETni(errtxt, rc) : 0); - -// Return all of the names -// - for (i = 0; i < maxipn && np; i++) - {InetName[i] = LowCase(strdup(np->ai_canonname)); - np = np->ai_next; - } - freeaddrinfo(rp); - -#endif - -// All done -// - return i; -} - -/******************************************************************************/ -/* g e t P o r t */ -/******************************************************************************/ - -int XrdSysDNS::getPort(const char *servname, - const char *servtype, - char **errtxt) -{ - int rc; - -#ifdef HAVE_NAMEINFO - struct addrinfo *rp, *np; - struct addrinfo myhints; - int portnum = 0; - memset(&myhints, 0, sizeof(myhints)); -#else - struct servent sent, *sp; - char sbuff[1024]; -#endif - -// Try to find minimum port number -// -#ifndef HAVE_NAMEINFO -#ifdef __solaris__ - if ( !getservbyname_r(servname,servtype,&sent,sbuff,sizeof(sbuff))) - return (errtxt ? setET(errtxt, errno) : 0); -#else - if ((rc=getservbyname_r(servname,servtype,&sent,sbuff,sizeof(sbuff),&sp))) - return (errtxt ? setET(errtxt, rc) : 0); -#endif - return int(ntohs(sent.s_port)); -#else - if ((rc = getaddrinfo(0,servname,(const struct addrinfo *)&myhints,&rp)) - || !(np = rp)) return (errtxt ? setETni(errtxt, rc) : 0); - - while(np) if (np->ai_socktype == SOCK_STREAM && *servtype == 't') break; - else if (np->ai_socktype == SOCK_DGRAM && *servtype == 'u') break; - else np = np->ai_next; - if (np) portnum=int(ntohs(((struct sockaddr_in *)(np->ai_addr))->sin_port)); - freeaddrinfo(rp); - if (!portnum) return (errtxt ? setET(errtxt, ESRCH) : 0); - return portnum; -#endif -} - -/******************************************************************************/ - -int XrdSysDNS::getPort(int fd, char **errtxt) -{ - struct sockaddr InetAddr; - struct sockaddr_in *ip = (struct sockaddr_in *)&InetAddr; - socklen_t slen = (socklen_t)sizeof(InetAddr); - int rc; - - if ((rc = getsockname(fd, &InetAddr, &slen))) - {rc = errno; - if (errtxt) setET(errtxt, errno); - return -rc; - } - - return static_cast(ntohs(ip->sin_port)); -} - -/******************************************************************************/ -/* g e t P r o t o I D */ -/******************************************************************************/ - -#define NET_IPPROTO_TCP 6 - -int XrdSysDNS::getProtoID(const char *pname) -{ -#ifdef HAVE_PROTOR - struct protoent pp; - char buff[1024]; -#else - static XrdSysMutex protomutex; - struct protoent *pp; - int protoid; -#endif - -// Note that POSIX did include getprotobyname_r() in the last minute. Many -// platforms do not document this variant but just about all include it. -// -#ifdef __solaris__ - if (!getprotobyname_r(pname, &pp, buff, sizeof(buff))) - return NET_IPPROTO_TCP; - return pp.p_proto; -#elif !defined(HAVE_PROTOR) - protomutex.Lock(); - if (!(pp = getprotobyname(pname))) protoid = NET_IPPROTO_TCP; - else protoid = pp->p_proto; - protomutex.UnLock(); - return protoid; -#else - struct protoent *ppp; - if (getprotobyname_r(pname, &pp, buff, sizeof(buff), &ppp)) - return NET_IPPROTO_TCP; - return pp.p_proto; -#endif -} - -/******************************************************************************/ -/* H o s t 2 D e s t */ -/******************************************************************************/ - -int XrdSysDNS::Host2Dest(const char *hostname, - struct sockaddr &DestAddr, - char **errtxt) -{ char *cp, hbuff[256]; - int port, i; - struct sockaddr_in InetAddr; - -// Find the colon in the host name -// - if (!(cp = (char *) index(hostname, (int)':'))) - {if (errtxt) *errtxt = (char *)"port not specified"; - return 0; - } - -// Make sure hostname is not too long -// - if ((i = cp-hostname) >= static_cast(sizeof(hbuff))) - {if (errtxt) *errtxt = (char *)"hostname too long"; - return 0; - } - strlcpy(hbuff, hostname, i+1); - -// Convert hostname to an ip address -// - struct sockaddr *ip = (struct sockaddr *)&InetAddr; - if (!getHostAddr(hbuff, *ip, errtxt)) return 0; - -// Insert port number in address -// - if (!(port = atoi(cp+1)) || port > 0xffff) - {if (errtxt) *errtxt = (char *)"invalid port number"; - return 0; - } - -// Compose the destination address -// - InetAddr.sin_family = AF_INET; - InetAddr.sin_port = htons(port); - memcpy((void *)&DestAddr, (const void *)&InetAddr, sizeof(sockaddr)); - memset((void *)&InetAddr.sin_zero, 0, sizeof(InetAddr.sin_zero)); - return 1; -} - -/******************************************************************************/ -/* H o s t 2 I P */ -/******************************************************************************/ - -int XrdSysDNS::Host2IP(const char *hname, unsigned int *ipaddr) -{ - struct sockaddr_in InetAddr; - -// Convert hostname to an ascii ip address -// - struct sockaddr *ip = (struct sockaddr *)&InetAddr; - if (!getHostAddr(hname, *ip)) return 0; - if (ipaddr) memcpy(ipaddr, &InetAddr.sin_addr, sizeof(unsigned int)); - return 1; -} - -/******************************************************************************/ -/* I P A d d r */ -/******************************************************************************/ - -unsigned int XrdSysDNS::IPAddr(struct sockaddr *InetAddr) - {return (unsigned int)(((struct sockaddr_in *)InetAddr)->sin_addr.s_addr);} - -/******************************************************************************/ -/* I P F o r m a t */ -/******************************************************************************/ - -int XrdSysDNS::IPFormat(const struct sockaddr *sAddr, char *bP, int bL, int fP) -{ - union {const struct sockaddr *Vx; - const struct sockaddr_in *V4; - const struct sockaddr_in6 *V6; - } ip; - int TotLen; - -// Make sure the buffer has some space -// - if (bL < (INET_ADDRSTRLEN+4)) return 0; - ip.Vx = sAddr; - -// Format address; we always use the IPV6 RFC recommended representation. -// - if (sAddr->sa_family == AF_INET) - {strcpy(bP, "[::"); - if (!inet_ntop(AF_INET, &(ip.V4->sin_addr), bP+3, bL-3)) return 0; - } - else if (sAddr->sa_family == AF_INET6) - {*bP = '['; - if (!inet_ntop(AF_INET6, &(ip.V6->sin6_addr),bP+1, bL-1)) return 0; - } - else return 0; - -// Recalculate buffer position and length -// - TotLen = strlen(bP); bP += TotLen; bL -= TotLen; - -// Add the port number if so wanted (note that the port number is the same -// position and same size regardless of address type). -// - if (fP) - {int n, pNum = ntohs(ip.V4->sin_port); - if ((n = snprintf(bP, bL, "]:%d", pNum)) >= bL) return 0; - TotLen += n; - } else { - if (bL < 2) return 0; - *bP++ = ']'; *bP++ = 0; TotLen++; - } - -// All done -// - return TotLen; -} - -/******************************************************************************/ -/* I P 2 S t r i n g */ -/******************************************************************************/ - -int XrdSysDNS::IP2String(unsigned int ipaddr, int port, char *buff, int blen) -{ - struct in_addr in; - int sz; - -// Convert the address -// - in.s_addr = ipaddr; - if (port <= 0) - sz = snprintf(buff,blen,"%s", inet_ntoa((const struct in_addr)in)); - else - sz = snprintf(buff,blen,"%s:%d",inet_ntoa((const struct in_addr)in),port); - return (sz > blen ? blen : sz); -} - -/******************************************************************************/ -/* i s D o m a i n */ -/******************************************************************************/ - -int XrdSysDNS::isDomain(const char *Hostname, const char *Domname, int Domlen) -{ - int hlen = strlen(Hostname); - - return (hlen >= Domlen && !strcmp(Hostname+(hlen-Domlen), Domname)); -} - -/******************************************************************************/ -/* i s L o o p b a c k */ -/******************************************************************************/ - -int XrdSysDNS::isLoopback(struct sockaddr &InetAddr) -{ - struct sockaddr_in *ip = (struct sockaddr_in *)&InetAddr; - return ip->sin_addr.s_addr == 0x7f000001; -} - -/******************************************************************************/ -/* i s M a t c h */ -/******************************************************************************/ - -int XrdSysDNS::isMatch(const char *HostName, char *HostPat) -{ - struct sockaddr InetAddr[16]; - char *mval; - int i, j, k, retc; - - if (!strcmp(HostPat, HostName)) return 1; - - if ((mval = index(HostPat, (int)'*'))) - {*mval = '\0'; mval++; - k = strlen(HostName); j = strlen(mval); i = strlen(HostPat); - if ((i+j) > k - || strncmp(HostName, HostPat,i) - || strncmp((HostName+k-j),mval,j)) return 0; - return 1; - } - - i = strlen(HostPat); - if (HostPat[i-1] != '+') i = 0; - else {HostPat[i-1] = '\0'; - if (!(i = getHostAddr(HostPat, InetAddr, 16))) - return 0; - } - - while(i--) - {mval = getHostName(InetAddr[i]); - retc = !strcmp(mval,HostName); - free(mval); - if (retc) return 1; - } - return 0; -} - -/******************************************************************************/ -/* P e e r n a m e */ -/******************************************************************************/ - -char *XrdSysDNS::Peername(int snum, struct sockaddr *sap, char **errtxt) -{ - struct sockaddr addr; - SOCKLEN_t addrlen = sizeof(addr); - -// Get the address on the other side of this socket -// - if (!sap) sap = &addr; - if (getpeername(snum, (struct sockaddr *)sap, &addrlen) < 0) - {if (errtxt) setET(errtxt, errno); - return (char *)0; - } - -// Convert it to a host name -// - return getHostName(*sap, errtxt); -} - -/******************************************************************************/ -/* s e t P o r t */ -/******************************************************************************/ - -void XrdSysDNS::setPort(struct sockaddr &InetAddr, int port, int anyaddr) -{ - unsigned short int sport = static_cast(port); - struct sockaddr_in *ip = (struct sockaddr_in *)&InetAddr; - ip->sin_port = htons(sport); - if (anyaddr) {ip->sin_family = AF_INET; - ip->sin_addr.s_addr = INADDR_ANY; - memset((void *)ip->sin_zero, 0, sizeof(ip->sin_zero)); - } -} - -/******************************************************************************/ -/* P r i v a t e M e t h o d s */ -/******************************************************************************/ -/******************************************************************************/ -/* L o w C a s e */ -/******************************************************************************/ - -char *XrdSysDNS::LowCase(char *str) -{ - char *sp = str; - - while(*sp) {if (isupper((int)*sp)) *sp = (char)tolower((int)*sp); sp++;} - - return str; -} - -/******************************************************************************/ -/* s e t E T */ -/******************************************************************************/ - -int XrdSysDNS::setET(char **errtxt, int rc) -{ - if (rc) *errtxt = strerror(rc); - else *errtxt = (char *)"unexpected error"; - return 0; -} - -/******************************************************************************/ -/* s e t E T n i */ -/******************************************************************************/ - -int XrdSysDNS::setETni(char **errtxt, int rc) -{ -#ifndef HAVE_NAMEINFO - return setET(errtxt, rc); -#else - if (rc) *errtxt = (char *)gai_strerror(rc); - else *errtxt = (char *)"unexpected error"; - return 0; -#endif -} diff --git a/src/XrdSys/XrdSysDNS.hh b/src/XrdSys/XrdSysDNS.hh deleted file mode 100644 index 118221675af..00000000000 --- a/src/XrdSys/XrdSysDNS.hh +++ /dev/null @@ -1,245 +0,0 @@ -#ifndef __XRDSYSDNS__ -#define __XRDSYSDNS__ -/******************************************************************************/ -/* */ -/* X r d S y s D N S . h h */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -/*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/ -/* This class is deprecated and essentially OBSOLETE and no longer mainatined.*/ -/* */ -/* This class only supports IPV4 addresses and contexts. Please use classes */ -/* XrdNetAddr, XrdNetAddrInfo, and XrdNetUtils that provide IP address format */ -/* agnostic replacement methods. SysDNS will be removed the next major release*/ -/*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/ - -#include -#ifndef WIN32 -#include -#else -#include -#endif - -class XrdSysDNS -{ -public: - -// Note: Most methods allow the reason for failure to be returned via an errtxt -// argument. The string returned in errtxt is static and must neither be -// modified not freed. - -// getHostAddr() translates an host name or an ascii host ip address to the -// binary address suitable for use in network system calls. The -// host name or address must be registered in the DNS for the -// translation to be successful. Upon success the either the -// primary address (1st form) or a list of addresses (2nd form) -// up to maxipa is returned. The return values are: -// 0 -> Host name could not be translated, the error text -// is placed in errtxt, if an address is supplied. -// > 0 -> The number of addresses returned. -// -static int getHostAddr(const char *InetName, - struct sockaddr &InetAddr, - char **errtxt=0) - {return getHostAddr(InetName, &InetAddr, 1, errtxt);} - -static int getHostAddr(const char *InetName, - struct sockaddr InetAddr[], - int maxipa=1, - char **errtxt=0); - -// getHostID() returns the ASCII string corresponding to the IP address -// InetAddr. If a translation is successful, the address -// of an strdup'd null terminated name is returned (it must be -// released using free()). Otherwise, an strdup of '0.0.0.0' is -// returned (which must also be freed). -// -static char *getHostID(struct sockaddr &InetAddr); - -// getAddrName() finds addresses and names associated with an host name or -// an ascii host ip address. The host name or address must be -// registered in the DNS for the translation to be successful. -// Upon success a list of addresses and names up to maxipa is -// returned in the arrays haddr and hname. The arrays must be -// previously allocated by the caller for at least maxipa -// 'char *'. The returned char arrays are allocated inside and -// must be freed by the caller. The return values are: -// 0 -> Host name could not be translated, the error text -// is placed in errtxt, if an address is supplied. -// > 0 -> The number of addresses returned. -// -static int getAddrName(const char *InetName, - int maxipa, - char **haddr, - char **hname, - char **errtxt=0); - -// getHostName() returns the fully qualified name of a host. If no partial -// host name is specified (or specifiied as 0), the fully -// qualified name of this host is returned. The name is returned -// as an strdup'd string which must be released using free(). -// If errtxt is supplied, it is set to zero. -// Upon failure, strdup("0.0.0.0") is returned and the error -// text is placed in errtxt if an address is supplied. -// -static char *getHostName(const char *InetName=0, - char **errtxt=0); - -// getHostName() returns the primary name of the host associated with the IP -// address InetAddr. If a translation is successful, the address -// of an strdup'd null terminated name is returned (it must be -// released using free()) and errtxt, of supplied, is set to 0. -// Upon failure, the ascii text version of the address is -// returned and the error text is placed in errtxt if an -// address is supplied. -// -static char *getHostName(struct sockaddr &InetAddr, - char **errtxt=0); - -// getHostName() returns the names of the host associated with the IP address -// InetAddr. The first name is the primary name of the host. -// Upon success, the address of each null terminated name is -// placed in InetName[i]. Up to maxipn names are returned. The -// array must be large enough to hold maxipn entries, Each -// name is returned as an strdup'd string, which must be -// released using free(). Return values are: -// 0 -> No names could be returned; the error text is placed -// in errtxt if an address is supplied. -// >0 -> Number of names returned. -// -static int getHostName(struct sockaddr &InetAddr, - char *InetName[], - int maxipn, - char **errtxt=0); - -// getPort() returns the port number of the service corresponding to the -// supplied name and service type (i.e., "tcp" or "udp"). If the port -// cannot be found, zero is returned and the error text is placed -// in errtxt if an address is supplied. -// -static int getPort(const char *servname, - const char *servtype, - char **errtxt=0); - -// getPort() variant returns the port number associated with the specified -// file descriptor. If an error occurs, a negative errno is returned, -// and errtxt is set if supplied. -// -static int getPort(int fd, char **errtxt=0); - -// getProtoID() returns the protocol number associated with the protocol name -// passed as a parameter. No failures can occur since TCP is -// returned if the protocol cannot be found. -// -static int getProtoID(const char *pname); - -// Host2Dest() returns a sockaddr structure suitable for socket operations -// built from the "host:port" specified in InetName. It returns -// 1 upon success and 0 upon failure with the reason placed in -// errtxt, if as address is supplied. -// -static int Host2Dest(const char *InetName, - struct sockaddr &DestAddr, - char **errtxt=0); - -// Host2IP() converts a host name passed in InetName to an IPV4 address, -// returned in ipaddr (unless it is zero, in which only a conversion -// check is performed). 1 is returned upon success, 0 upon failure. -// -static int Host2IP(const char *InetName, - unsigned int *ipaddr=0); - -// IPFormat() converts an IP address/port (V4 or V6) into the standard V6 RFC -// ASCII representation: "[address]:port". - -// Input: sAddr - Address to convert. This is either sockaddr_in or -// sockaddr_in6 cast to struct sockaddr. -// bP - points to a buffer large enough to hold the result. -// A buffer 64 characters long will always be big enough. -// bL - the actual size of the buffer. -// fP - When true (the default) will format sAddr->sin_port -// (or sin6_port) as ":port" at the end of the address. -// When false the colon and port number is omitted. -// -// Output: Upon success the length of the formatted address is returned. -// Upon failure zero is returned and the buffer state is undefined. -// Failure occurs when the buffer is too small or the address family -// (sAddr->sa_family) is neither AF_INET nor AF_INET6. -// -static int IPFormat(const struct sockaddr *sAddr, char *bP, int bL, int fP=1); - -// IP2String() converts an IPV4 version of the address to ascii dot notation -// If port > 0 then the results is :. The return -// value is the number of characters placed in the buffer. -// -static int IP2String(unsigned int ipaddr, int port, char *buff, int blen); - -// IPAddr() returns the IPV4 version of the address in the address argument -// -static unsigned int IPAddr(struct sockaddr *InetAddr); - -// isDomain() returns true if the domain portion of the hostname matches -// the specified domain name. -// -static int isDomain(const char *Hostname, const char *Domname, int Domlen); - -// isLoopback() returns true if the address in InetAddr is the loopback address. -// This test is used to discover IP address spoofing in UDP packets. -// -static int isLoopback(struct sockaddr &InetAddr); - -// isMatch() returns true if the HostName matches the host pattern HostPat. -// Patterns are formed as {[][*][] | +} -// -static int isMatch(const char *HostNme, char *HostPat); - -// Peername() returns the strdupp'd string name (and optionally the address) of -// the host associated with the socket passed as the first parameter. -// The string must be released using free(). If the host cannot be -// determined, 0 is returned and the error text is placed in errtxt -// if an address is supplied. -// -static char *Peername( int snum, - struct sockaddr *sap=0, - char **errtxt=0); - -// setPort() sets the port number InetAddr. If anyaddr is true,, InetAddr is -// initialized to the network defined "any" IP address. -// -static void setPort(struct sockaddr &InetAddr, int port, int anyaddr=0); - - XrdSysDNS() {} - ~XrdSysDNS() {} - -private: - -static char *LowCase(char *str); -static int setET(char **errtxt, int rc); -static int setETni(char **errtxt, int rc); -}; -#endif diff --git a/src/XrdSys/XrdSysDir.cc b/src/XrdSys/XrdSysDir.cc deleted file mode 100644 index c84aaeef430..00000000000 --- a/src/XrdSys/XrdSysDir.cc +++ /dev/null @@ -1,129 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S y s D i r . h h */ -/* */ -/* (c) 2006 G. Ganis (CERN) */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/* All Rights Reserved. See XrdInfo.cc for complete License Terms */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// XrdSysDir // -// // -// Author: G. Ganis, CERN, 2006 // -// // -// API for handling directories // -// // -////////////////////////////////////////////////////////////////////////// - -#include "XrdSys/XrdSysDir.hh" - -#if !defined(WINDOWS) -#include -#else -#include -#endif - -#include -#include - -//______________________________________________________________________________ -XrdSysDir::XrdSysDir(const char *path) -{ - // Constructor. Initialize a directory handle for 'path'. - // Use isValid() to check the result of this operation, and lastError() - // to get the last error code, if any. - - lasterr = 0; dhandle = 0; - if (path && strlen(path) > 0) { -#if !defined(WINDOWS) - dhandle = (void *) opendir(path); - if (!dhandle) - lasterr = errno; -#else - WIN32_FIND_DATA filedata; - dhandle = (void *) ::FindFirstFile(path, &filedata); - if ((HANDLE)dhandle == INVALID_HANDLE_VALUE) { - lasterr = EINVAL; - dhandle = 0; - } - else if (!(filedata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { - lasterr = ENOTDIR; - dhandle = 0; - } -#endif - } else - // Invalid argument - lasterr = EINVAL; -} - -//______________________________________________________________________________ -XrdSysDir::~XrdSysDir() -{ - // Destructor. - - if (dhandle) { -#if !defined(WINDOWS) - closedir((DIR *)dhandle); -#else - ::FindClose((HANDLE)dhandle); -#endif - } -} - -//______________________________________________________________________________ -char *XrdSysDir::nextEntry() -{ - // Get next entry in directory structure. - // Return 0 if no more entries or error. In the latter case - // the error code can be retrieved via lastError(). - - char *dent = 0; - - lasterr = 0; - if (!dhandle) { - lasterr = EINVAL; - return dent; - } - -#if !defined(WINDOWS) - struct dirent *ent = readdir((DIR *)dhandle); - if (!ent) { - if (errno == EBADF) - lasterr = errno; - } else { - dent = (char *) ent->d_name; - } -#else - WIN32_FIND_DATA filedata; - if (::FindNextFile((HANDLE)dhandle, &filedata)) { - dent = (char *) filedata.cFileName; - } else { - if (::GetLastError() != ERROR_NO_MORE_FILES) - lasterr = EBADF; - } -#endif - // Done - return dent; -} - diff --git a/src/XrdSys/XrdSysDir.hh b/src/XrdSys/XrdSysDir.hh deleted file mode 100644 index 4abdbcc5d2a..00000000000 --- a/src/XrdSys/XrdSysDir.hh +++ /dev/null @@ -1,62 +0,0 @@ -#ifndef __SYS_DIR_H__ -#define __SYS_DIR_H__ -/******************************************************************************/ -/* */ -/* X r d S y s D i r . h h */ -/* */ -/* (c) 2006 G. Ganis (CERN) */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/* All Rights Reserved. See XrdInfo.cc for complete License Terms */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// XrdSysDir // -// // -// Author: G. Ganis, CERN, 2006 // -// // -// API for handling directories // -// // -////////////////////////////////////////////////////////////////////////// - -#if !defined(WINDOWS) -# include -#else -# define uid_t unsigned int -# define gid_t unsigned int -#endif - -class XrdSysDir -{ - public: - XrdSysDir(const char *path); - virtual ~XrdSysDir(); - - bool isValid() { return (dhandle ? 1 : 0); } - int lastError() { return lasterr; } - char *nextEntry(); - - private: - void *dhandle; // Directory handle - int lasterr; // Error occured at last operation -}; -#endif diff --git a/src/XrdSys/XrdSysError.cc b/src/XrdSys/XrdSysError.cc deleted file mode 100644 index 3278f8ee782..00000000000 --- a/src/XrdSys/XrdSysError.cc +++ /dev/null @@ -1,182 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S y s E r r o r . c c */ -/* */ -/*(c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/*Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Deprtment of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#ifndef WIN32 -#include -#include -#include -#include -#include -#include -#include -#include -#else -#include -#include -#include -#include -#include "XrdSys/XrdWin32.hh" -#endif - -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdSys/XrdSysLogger.hh" -#include "XrdSys/XrdSysPlatform.hh" - -/******************************************************************************/ -/* d e f i n e s */ -/******************************************************************************/ - -#define Set_IOV_Item(x, y) {iov[iovpnt].iov_base = (caddr_t)x;\ - iov[iovpnt++].iov_len = y;} - -#define Set_IOV_Buff(x) {iov[iovpnt].iov_base = (caddr_t)x;\ - iov[iovpnt++].iov_len = strlen(x);} - -/******************************************************************************/ -/* G l o b a l s */ -/******************************************************************************/ - -XrdSysError_Table *XrdSysError::etab = 0; - -/******************************************************************************/ -/* b a s e F D */ -/******************************************************************************/ - -int XrdSysError::baseFD() {return Logger->originalFD();} - -/******************************************************************************/ -/* e c 2 t e x t */ -/******************************************************************************/ - -char *XrdSysError::ec2text(int ecode) -{ - int xcode; - char *etxt = 0; - XrdSysError_Table *etp = etab; - - xcode = (ecode < 0 ? -ecode : ecode); - while((etp != 0) && !(etxt = etp->Lookup(xcode))) etp = etp->next; - if (!etxt) etxt = strerror(xcode); - return etxt; -} - -/******************************************************************************/ -/* E m s g */ -/******************************************************************************/ - -int XrdSysError::Emsg(const char *esfx, int ecode, const char *txt1, - const char *txt2) -{ - struct iovec iov[16]; - int iovpnt = 0; - char ebuff[32], etbuff[80], *etxt = 0; - - if (!(etxt = ec2text(ecode))) - {snprintf(ebuff, sizeof(ebuff), "reason unknown (%d)", ecode); - etxt = ebuff; - } else if (isupper(static_cast(*etxt))) - {strlcpy(etbuff, etxt, sizeof(etbuff)); - *etbuff = static_cast(tolower(static_cast(*etxt))); - etxt = etbuff; - } - - Set_IOV_Item(0,0); // 0 - if (epfx && epfxlen) Set_IOV_Item(epfx, epfxlen); // 1 - if (esfx ) Set_IOV_Buff(esfx); // 2 - Set_IOV_Item(": Unable to ", 12); // 3 - Set_IOV_Buff(txt1); // 4 - if (txt2 && txt2[0]){Set_IOV_Item(" ", 1); // 5 - Set_IOV_Buff(txt2); } // 6 - Set_IOV_Item("; ", 2); // 7 - Set_IOV_Buff(etxt); // 8 - Set_IOV_Item("\n", 1); // 9 - Logger->Put(iovpnt, iov); - - return ecode; -} - -void XrdSysError::Emsg(const char *esfx, const char *txt1, - const char *txt2, - const char *txt3) -{ - struct iovec iov[16]; - int iovpnt = 0; - - Set_IOV_Item(0,0); // 0 - if (epfx && epfxlen) Set_IOV_Item(epfx, epfxlen); // 1 - if (esfx ) Set_IOV_Buff(esfx); // 2 - Set_IOV_Item(": ", 2); // 3 - Set_IOV_Buff(txt1); // 4 - if (txt2 && txt2[0]){Set_IOV_Item(" ", 1); // 5 - Set_IOV_Buff(txt2);} // 6 - if (txt3 && txt3[0]){Set_IOV_Item(" ", 1); // 7 - Set_IOV_Buff(txt3);} // 8 - Set_IOV_Item("\n", 1); // 9 - Logger->Put(iovpnt, iov); -} - -/******************************************************************************/ -/* S a y */ -/******************************************************************************/ - -void XrdSysError::Say(const char *txt1, const char *txt2, const char *txt3, - const char *txt4, const char *txt5, const char *txt6) -{ - struct iovec iov[9]; - int iovpnt = 0; - if (txt1) Set_IOV_Buff(txt1) // 0 - else Set_IOV_Item(0,0); - if (txt2 && txt2[0]) Set_IOV_Buff(txt2); // 1 - if (txt3 && txt3[0]) Set_IOV_Buff(txt3); // 2 - if (txt4 && txt4[0]) Set_IOV_Buff(txt4); // 3 - if (txt5 && txt5[0]) Set_IOV_Buff(txt5); // 4 - if (txt6 && txt6[0]) Set_IOV_Buff(txt6); // 5 - Set_IOV_Item("\n", 1); // 6 - Logger->Put(iovpnt, iov); -} - -/******************************************************************************/ -/* T b e g */ -/******************************************************************************/ - -void XrdSysError::TBeg(const char *txt1, const char *txt2, const char *txt3) -{ - cerr <traceBeg(); - if (txt1) cerr <traceEnd();} diff --git a/src/XrdSys/XrdSysError.hh b/src/XrdSys/XrdSysError.hh deleted file mode 100644 index cd74187a69b..00000000000 --- a/src/XrdSys/XrdSysError.hh +++ /dev/null @@ -1,175 +0,0 @@ -#ifndef __SYS_ERROR_H__ -#define __SYS_ERROR_H__ -/******************************************************************************/ -/* */ -/* X r d S y s E r r o r . h h */ -/* */ -/*(c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/*Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Deprtment of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#ifndef WIN32 -#include -#include -#include -#else -#include -#endif - -/******************************************************************************/ -/* o o u c _ E r r o r _ T a b l e */ -/******************************************************************************/ - -class XrdSysError_Table -{ -public: -friend class XrdSysError; - -char *Lookup(int mnum) - {return (char *)(mnum < base_msgnum || mnum > last_msgnum - ? 0 : msg_text[mnum - base_msgnum]); - } - XrdSysError_Table(int base, int last, const char **text) - : next(0), - base_msgnum(base), - last_msgnum(last), - msg_text(text) {} - ~XrdSysError_Table() {} - -private: -XrdSysError_Table *next; // -> Next table or 0; -int base_msgnum; // Starting message number -int last_msgnum; // Ending message number -const char **msg_text; // Array of message text -}; - -/******************************************************************************/ -/* L o g M a s k D e f i n i t i o n s */ -/******************************************************************************/ - -const int SYS_LOG_01 = 1; -const int SYS_LOG_02 = 2; -const int SYS_LOG_03 = 4; -const int SYS_LOG_04 = 8; -const int SYS_LOG_05 = 16; -const int SYS_LOG_06 = 32; -const int SYS_LOG_07 = 64; -const int SYS_LOG_08 = 128; - -/******************************************************************************/ -/* o o u c _ E r r o r */ -/******************************************************************************/ - -class XrdSysLogger; - -class XrdSysError -{ -public: - XrdSysError(XrdSysLogger *lp, const char *ErrPrefix="sys") - : epfx(0), - epfxlen(0), - msgMask(-1), - Logger(lp) - { SetPrefix(ErrPrefix); } - - ~XrdSysError() {} - -// addTable allows you to add a new error table for errno handling. Any -// number of table may be added and must consist of statis message text -// since the table are deleted but the text is not freed. Error tables -// must be setup without multi-threading. There is only one global table. -// -static void addTable(XrdSysError_Table *etp) {etp->next = etab; etab = etp;} - -// baseFD() returns the original FD associated with this object. -// -int baseFD(); - -// ec2text tyranslates an error code to the correspodning error text or returns -// null if matching text cannot be found. -// -static char *ec2text(int ecode); - -// Emsg() produces a message of various forms. The message is written to the -// constructor specified file descriptor. See variations below. -// -// : error (syser[]); " -// (returns abs(ecode)). -// -int Emsg(const char *esfx, int ecode, const char *text1, const char *text2=0); - -// : -// -void Emsg(const char *esfx, const char *text1, - const char *text2=0, - const char *text3=0); - -// : -// -inline void Log(int mask, const char *esfx, - const char *text1, - const char *text2=0, - const char *text3=0) - {if (mask & msgMask) Emsg(esfx, text1, text2, text3);} - -// logger() sets/returns the logger object for this message message handler. -// -XrdSysLogger *logger(XrdSysLogger *lp=0) - {XrdSysLogger *oldp = Logger; - if (lp) Logger = lp; - return oldp; - } - -// Say() route a line without timestamp or prefix -// -void Say(const char *text1, const char *text2=0, const char *txt3=0, - const char *text4=0, const char *text5=0, const char *txt6=0); - -// Set the loging mask (only used by clients of this object) -// -void setMsgMask(int mask) {msgMask = mask;} - -// SetPrefix() dynamically changes the error prefix -// -inline const char *SetPrefix(const char *prefix) - {const char *oldpfx = epfx; - epfx = prefix; epfxlen = strlen(epfx); - return oldpfx; - } - -// TBeg() is used to start a trace on ostream cerr. The TEnd() ends the trace. -// -void TBeg(const char *txt1=0, const char *txt2=0, const char *txt3=0); -void TEnd(); - -private: - -static XrdSysError_Table *etab; -const char *epfx; -int epfxlen; -int msgMask; -XrdSysLogger *Logger; -}; -#endif diff --git a/src/XrdSys/XrdSysFAttr.cc b/src/XrdSys/XrdSysFAttr.cc deleted file mode 100644 index f583988fc2c..00000000000 --- a/src/XrdSys/XrdSysFAttr.cc +++ /dev/null @@ -1,173 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S y s F A t t r . c c */ -/* */ -/* (c) 2014 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include - -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysFAttr.hh" - -/******************************************************************************/ -/* P l a t f o r m D e p e n d e n c i e s */ -/******************************************************************************/ - -#ifndef ENOATTR -#define ENOATTR ENODATA -#endif - -/******************************************************************************/ -/* S t a t i c O b j e c t s */ -/******************************************************************************/ - -namespace -{ -XrdSysFAttr dfltXAttr; -} - -// The following global symbol always points to the native implementation -// -XrdSysXAttr &XrdSysXAttrNative= dfltXAttr; - -// The following global symbol always points to the active implementation -// -XrdSysXAttr *XrdSysXAttrActive= &dfltXAttr; - -XrdSysXAttr *XrdSysFAttr::Xat = &dfltXAttr; - -/******************************************************************************/ -/* X r d S y s F A t t r I m p l e m e n t a t i o n */ -/******************************************************************************/ - -#if defined(__FreeBSD__) -#include "XrdSys/XrdSysFAttrBsd.icc" -#elif defined(__linux__) -#include "XrdSys/XrdSysFAttrLnx.icc" -#elif defined(__APPLE__) -#include "XrdSys/XrdSysFAttrMac.icc" -#elif defined(__solaris__) -#include "XrdSys/XrdSysFAttrSun.icc" -#else -int XrdSysFAttr::Del(const char *Aname, const char *Path) - {return -ENOTSUP;} -int XrdSysFAttr::Del(const char *Aname, int fd) - {return -ENOTSUP;} -int XrdSysFAttr::Get(const char *Aname, void *Aval, int Avsz, const char *Path) - {return -ENOTSUP;} -int XrdSysFAttr::Get(const char *Aname, void *Aval, int Avsz, int fd) - {return -ENOTSUP;} -int XrdSysFAttr::Set(const char *Aname, const void *Aval, int Avsz, - const char *Path, int isNew) - {return -ENOTSUP;} -int XrdSysFAttr::Set(const char *Aname, const void *Aval, int Avsz, - int fd, int isNew) - {return -ENOTSUP;} -int XrdSysFAttr::Set(XrdSysError *erp) {return 0;} -#endif - -/******************************************************************************/ -/* D i a g n o s e */ -/******************************************************************************/ - -int XrdSysFAttr::Diagnose(const char *Op, const char *Var, - const char *Path, int ec) -{ - char buff[512]; - -// Screen out common case -// - if (ec == ENOATTR || ec == ENOENT) return -ENOENT; - -// Format message insert and print if we can actually say anything -// - if (Say) - {snprintf(buff, sizeof(buff), "%s attr %s from", Op, Var); - Say->Emsg("FAttr", ec, buff, Path); - } - -// Return negative code -// - return -ec; -} - -/******************************************************************************/ -/* F r e e */ -/******************************************************************************/ - -void XrdSysFAttr::Free(XrdSysFAttr::AList *aLP) -{ - AList *aNP; - -// Free all teh structs using free as they were allocated using malloc() -// - while(aLP) {aNP = aLP->Next; free(aLP); aLP = aNP;} -} - -/******************************************************************************/ -/* g e t E n t */ -/******************************************************************************/ - -XrdSysFAttr::AList *XrdSysFAttr::getEnt(const char *Path, int fd, - const char *Aname, - XrdSysFAttr::AList *aP, int *msP) -{ - AList *aNew; - int sz = 0, n = strlen(Aname); - -// Get the data size of this attribute if so wanted -// - if (!n || (msP && !(sz = Get(Aname, 0, 0, Path, fd)))) return 0; - -// Allocate a new dynamic struct -// - if (!(aNew = (AList *)malloc(sizeof(AList) + n))) return 0; - -// Initialize the structure -// - aNew->Next = aP; - aNew->Vlen = sz; - aNew->Nlen = n; - strcpy(aNew->Name, Aname); // Gauranteed to fit - -// All done -// - if (msP && *msP < sz) *msP = sz; - return aNew; -} - -/******************************************************************************/ -/* S e t P l u g i n */ -/******************************************************************************/ - -void XrdSysFAttr::SetPlugin(XrdSysXAttr *xaP) -{ - if (Xat && Xat != &dfltXAttr) delete Xat; - XrdSysXAttrActive = Xat = xaP; -} diff --git a/src/XrdSys/XrdSysFAttr.hh b/src/XrdSys/XrdSysFAttr.hh deleted file mode 100644 index ddcdbcd2194..00000000000 --- a/src/XrdSys/XrdSysFAttr.hh +++ /dev/null @@ -1,92 +0,0 @@ -#ifndef __XRDSYSFATTR_HH__ -#define __XRDSYSFATTR_HH__ -/******************************************************************************/ -/* */ -/* X r d S y s F A t t r . h h */ -/* */ -/* (c) 2010 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdSys/XrdSysXAttr.hh" - -//------------------------------------------------------------------------------ -//! This class provides an internal interface to handle extended file attributes -//! either via a default implementation or an external plugin. -//------------------------------------------------------------------------------ - -class XrdSysFAttr : public XrdSysXAttr -{ -public: - -//------------------------------------------------------------------------------ -//! Xat points to the plugin to be used for all operations. The methods -//! inherited from XrdSysXAttr cannot be directly invoked. Instead, -//! use XrdSysFAttr::Xat->. All static -//! methods here, however, can be directly invoked. -//------------------------------------------------------------------------------ - -static XrdSysXAttr *Xat; - -//------------------------------------------------------------------------------ -//! Establish a plugin that is to replace the builtin extended attribute -//! processing methods. -//! -//! @param xaP -> To an instance of an XrdSysXAttr object that is to replace -//! the builtin object that processes extended attributes; -//------------------------------------------------------------------------------ - -static void SetPlugin(XrdSysXAttr *xaP); - -//------------------------------------------------------------------------------ -//! Constructor & Destructor -//------------------------------------------------------------------------------ - - XrdSysFAttr() {} - ~XrdSysFAttr() {} - -//------------------------------------------------------------------------------ -//! The following methods are inherited from the base class as private methods. -//------------------------------------------------------------------------------ - -private: - int Del(const char *Aname, const char *Path, int fd=-1); - - void Free(AList *aPL); - - int Get(const char *Aname, void *Aval, int Avsz, - const char *Path, int fd=-1); - - int List(AList **aPL, const char *Path, int fd=-1, int getSz=0); - - int Set(const char *Aname, const void *Aval, int Avsz, - const char *Path, int fd=-1, int isNew=0); - - int Diagnose(const char *Op, const char *Var, const char *Path, int ec); - - AList *getEnt(const char *Path, int fd, - const char *Aname, AList *aP, int *msP); -}; -#endif diff --git a/src/XrdSys/XrdSysFAttrBsd.icc b/src/XrdSys/XrdSysFAttrBsd.icc deleted file mode 100644 index 4fd25d77b39..00000000000 --- a/src/XrdSys/XrdSysFAttrBsd.icc +++ /dev/null @@ -1,177 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S y s F A t t r B s d . i c c */ -/* */ -/* (c) 2010 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include - -#include "XrdSys/XrdSysFAttr.hh" - -/******************************************************************************/ -/* X r d S y s F A t t r : : D e l */ -/******************************************************************************/ - -int XrdSysFAttr::Del(const char *Aname, const char *Path, int fd) -{ - int ec; - -// Remove the attrbiute but ignore errors if it doesn't exist -// - ec = (fd < 0 ? extattr_delete_file(Path, EXTATTR_NAMESPACE_USER, Aname) - : extattr_delete_fd( fd, EXTATTR_NAMESPACE_USER, Aname)); - -// Diagnose errors but ignore errors if it doesn't exist -// - if (ec && (ec = Diagnose("remove", Aname, Path, errno)) == -ENOENT) ec = 0; - return ec; -} - -/******************************************************************************/ -/* X r d S y s F A t t r : : G e t */ -/******************************************************************************/ - -int XrdSysFAttr::Get(const char *Aname, void *Aval, int Avsz, - const char *Path, int fd) -{ - int ec; - -// Obtain the attribute. -// - ec = (fd < 0 ? extattr_get_file(Path,EXTATTR_NAMESPACE_USER,Aname,Aval,Avsz) - extattr_get_fd( fd, EXTATTR_NAMESPACE_USER,Aname,Aval,Avsz)); - -// Diagnose errors. We return 0 on ENOENT to indicate no attribute. -// - if (ec < 0 && (ec = Diagnose("get", Aname, Path, errno)) == -ENOENT) ec = 0; - return ec; -} - -/******************************************************************************/ -/* X r d S y s F A t t r : : L i s t */ -/******************************************************************************/ - -int XrdSysFAttr::List(AList **aPL, const char *Path, int fd, int getSz) -{ - AList *aNew; - char *Buff, *bP, *bEnd; - int ec, Tlen, maxSz = 0, *msP = (getSz ? &maxSz : 0); - -// First obtain the amount of storage we will need for the whole list -// - *aPL = 0; - Tlen = (fd < 0 ? extattr_get_file(Path,EXTATTR_NAMESPACE_USER, 0, 0) - extattr_list_fd( fd,EXTATTR_NAMESPACE_USER, 0, 0)); - if (Tlen < 0) - {if ((ec = Diagnose("list", "*", Path, errno)) == -ENOENT) ec = 0; - return ec; - } - -// If we don't have any then just return 0. Otherwise, add 4K to the buffer -// size just in case some one is adding attributes while we get the list. -// - if (!Tlen) return 0; - Tlen += 4096; - -// Allocate storage to get the whole list -// - if (!(Buff = (char *)malloc(Tlen))) return -ENOMEM; - -// Now get the actual list. We will not recover if someone added an attribute -// since the time we actual determined the size of the buffer we need. -// - Tlen = (fd < 0 ? extattr_get_file(Path,EXTATTR_NAMESPACE_USER, Buff, Tlen) - extattr_list_fd( fd,EXTATTR_NAMESPACE_USER, Buff, Tlen)); - if (Tlen < 0) - {if ((ec = Diagnose("list", "*", Path, errno)) == -ENOENT) ec = 0; - free(Buff); - return ec; - } - if (!Tlen) return 0; - -// Run through the memory and allocate an AList entry for each. -// -#ifdef __ubuntu__ -// Ubuntu BSD returns attribute names preceeded by the length. -// - int n, i = 0; - char Vname[256]; - while(i < Tlen) - {n = (unsigned char)Buff[i]; - strncpy(Vname, &Buff[i+1], n); - Vname[n] = '\0'; - if (n && (aNew = getEnt(Path, fd, Vname, *aPL, msP))) *aPL = aNew; - i += (n + 1); - } -#else - bP = Buff; bEnd = Buff+Tlen; - while(bP < bEnd) - {if ((aNew = getEnt(Path, fd, bP, *aPL, msP))) *aPL = aNew; - bP = bP + strlen(bP) + 1; - } -#endif - -// All done -// - free(Buff); - return maxSz; -} - -/******************************************************************************/ -/* X r d S y s F A t t r : : S e t */ -/******************************************************************************/ - -int XrdSysFAttr::Set(const char *Aname, const void *Aval, int Avsz, - const char *Path, int fd, int isNew) -{ - int ec; - -// Check if we should see if the attribute already exists as BSD always replaces -// or creates attributes, as needed. This is not MT safe, sigh. -// - if (isNew) - {ec = (fd < 0 ? extattr_get_file(Path,EXTATTR_NAMESPACE_USER,Aname,0,0) - : extattr_get_fd( fd, EXTATTR_NAMESPACE_USER,Aname,0 0)); - if (ec >= 0) return -EEXIST; - } - -// Set the attribute -// - ec = (fd < 0 ? extattr_set_file(Path,EXTATTR_NAMESPACE_USER,Aname,Aval,Avsz) - : extattr_set_fd( fd, EXTATTR_NAMESPACE_USER,Aname,Aval,Avsz)); - if (ec < 0 || ec != Avsz) - ec = Diagnose("set", Aname, Path, (ec < 0 ? errno : ENOSPC)); - -// Return appropriately -// - return ec; -} diff --git a/src/XrdSys/XrdSysFAttrLnx.icc b/src/XrdSys/XrdSysFAttrLnx.icc deleted file mode 100644 index 71ad2622225..00000000000 --- a/src/XrdSys/XrdSysFAttrLnx.icc +++ /dev/null @@ -1,173 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S y s F A t t r L n x . i c c */ -/* */ -/* (c) 2010 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include - -#include "XrdSys/XrdSysFAttr.hh" - -/******************************************************************************/ -/* L o c a l D e f i n i t i o n s */ -/******************************************************************************/ - -#define AttrName(Aname, Abuff, Ablen) snprintf(Abuff, Ablen, "user.%s", Aname); - -/******************************************************************************/ -/* X r d S y s F A t t r : : D e l */ -/******************************************************************************/ - -int XrdSysFAttr::Del(const char *Aname, const char *Path, int fd) -{ - char Avar[512]; - int ec; - -// Qualify the name -// - AttrName(Aname, Avar, sizeof(Avar)); - -// Remove the attribute -// - ec = (fd < 0 ? removexattr(Path, Avar) : fremovexattr(fd, Avar)); - -// Diagnose errors but ignore errors if it doesn't exist -// - if (ec && (ec = Diagnose("remove", Aname, Path, errno)) == -ENOENT) ec = 0; - return ec; -} - -/******************************************************************************/ -/* X r d S y s F A t t r : : G e t */ -/******************************************************************************/ - -int XrdSysFAttr::Get(const char *Aname, void *Aval, int Avsz, - const char *Path, int fd) -{ - char Avar[512]; - int ec; - -// Qualify the name -// - AttrName(Aname, Avar, sizeof(Avar)); - -// Get the attribute -// - ec = (fd < 0 ? getxattr(Path, Avar, Aval, Avsz) - : fgetxattr(fd, Avar, Aval, Avsz)); - -// Diagnose errors. We return 0 on ENOENT to indicate no attribute. -// - if (ec < 0 && (ec = Diagnose("get", Aname, Path, errno)) == -ENOENT) ec = 0; - return ec; -} - -/******************************************************************************/ -/* X r d S y s F A t t r : : L i s t */ -/******************************************************************************/ - -int XrdSysFAttr::List(AList **aPL, const char *Path, int fd, int getSz) -{ - AList *aNew; - char *Buff = 0, *bP, *bEnd; - int ec, Tlen, maxSz = 0, *msP = (getSz ? &maxSz : 0); - -// First obtain the amount of storage we will need for the whole list -// - *aPL = 0; - Tlen = (fd < 0 ? listxattr(Path, Buff, 0) : flistxattr(fd, Buff, 0)); - if (Tlen < 0) - {if ((ec = Diagnose("list", "*", Path, errno)) == -ENOENT) ec = 0; - return ec; - } - -// If we don't have any then just return 0. Otherwise, add 4K to the buffer -// size just in case some one is adding attributes while we get the list. -// - if (!Tlen) return 0; - Tlen += 4096; - -// Allocate storage to get the whole list -// - if (!(Buff = (char *)malloc(Tlen))) return -ENOMEM; - -// Now get the actual list. We will not recover if someone added an attribute -// since the time we actual determined the size of the buffer we need. -// - Tlen = (fd < 0 ? listxattr(Path, Buff, Tlen) : flistxattr(fd, Buff, Tlen)); - if (Tlen < 0) - {if ((ec = Diagnose("list", "*", Path, errno)) == -ENOENT) ec = 0; - free(Buff); - return ec; - } - if (!Tlen) return 0; - -// Run through the memory and allocate an AList entry for each. Note that we -// strip off the name space prefix. -// - bP = Buff; bEnd = Buff+Tlen; - while(bP < bEnd) - {if (!strncmp("user.", bP, 5) && bP[5] - && (aNew = getEnt(Path, fd, bP+5, *aPL, msP))) *aPL = aNew; - bP = bP + strlen(bP) + 1; - } - -// All done -// - free(Buff); - return maxSz; -} - -/******************************************************************************/ -/* X r d S y s F A t t r : : S e t */ -/******************************************************************************/ - -int XrdSysFAttr::Set(const char *Aname, const void *Aval, int Avsz, - const char *Path, int fd, int isNew) -{ - char Avar[512]; - int ec, xFlag = (isNew ? XATTR_CREATE : 0); - -// Qualify the name -// - AttrName(Aname, Avar, sizeof(Avar)); - -// Set the attribute -// - ec = (fd < 0 ? setxattr(Path, Avar, Aval, Avsz, xFlag) - : fsetxattr(fd, Avar, Aval, Avsz, xFlag)); - -// Diagnose any errors -// - if (ec < 0) ec = Diagnose("set", Aname, Path, errno); - return ec; -} diff --git a/src/XrdSys/XrdSysFAttrMac.icc b/src/XrdSys/XrdSysFAttrMac.icc deleted file mode 100644 index 50efc87b81f..00000000000 --- a/src/XrdSys/XrdSysFAttrMac.icc +++ /dev/null @@ -1,151 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S y s F A t t r M a c . i c c */ -/* */ -/* (c) 2010 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include - -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysFAttr.hh" - -/******************************************************************************/ -/* X r d S y s F A t t r : : D e l */ -/******************************************************************************/ - -int XrdSysFAttr::Del(const char *Aname, const char *Path, int fd) -{ - int ec; - -// Remove the attrbiute but ignore errors if it doesn't exist -// - ec = (fd < 0 ? removexattr(Path,Aname,0) : fremovexattr(fd, Aname, 0)); - -// Diagnose errors but ignore errors if it doesn't exist -// - if (ec && (ec = Diagnose("remove", Aname, Path, errno)) == -ENOENT) ec = 0; - return ec; -} - -/******************************************************************************/ -/* X r d S y s F A t t r : : G e t */ -/******************************************************************************/ - -int XrdSysFAttr::Get(const char *Aname, void *Aval, int Avsz, - const char *Path, int fd) -{ - int ec; - -// Obtain the attribute. -// - ec = (fd < 0 ? getxattr(Path, Aname, Aval, Avsz, 0, 0) - : fgetxattr(fd, Aname, Aval, Avsz, 0, 0)); - -// Diagnose errors. We return 0 on ENOENT to indicate no attribute. -// - if (ec < 0 && (ec = Diagnose("get", Aname, Path, errno)) == -ENOENT) ec = 0; - return ec; -} - -/******************************************************************************/ -/* X r d S y s F A t t r : : L i s t */ -/******************************************************************************/ - -int XrdSysFAttr::List(AList **aPL, const char *Path, int fd, int getSz) -{ - AList *aNew; - char *Buff, *bP, *bEnd; - int ec, Tlen, maxSz = 0, *msP = (getSz ? &maxSz : 0); - -// First obtain the amount of storage we will need for the whole list -// - *aPL = 0; - Tlen = (fd < 0 ? listxattr(Path, 0, 0, 0) : flistxattr(fd, 0, 0, 0)); - if (Tlen < 0) - {if ((ec = Diagnose("list", "*", Path, errno)) == -ENOENT) ec = 0; - return ec; - } - -// If we don't have any then just return 0. Otherwise, add 4K to the buffer -// size just in case some one is adding attributes while we get the list. -// - if (!Tlen) return 0; - Tlen += 4096; - -// Allocate storage to get the whole list -// - if (!(Buff = (char *)malloc(Tlen))) return -ENOMEM; - -// Now get the actual list. We will not recover if someone added an attribute -// since the time we actual determined the size of the buffer we need. -// - Tlen = (fd < 0 ? listxattr(Path,Buff,Tlen,0) : flistxattr(fd,Buff,Tlen,0)); - if (Tlen < 0) - {if ((ec = Diagnose("list", "*", Path, errno)) == -ENOENT) ec = 0; - free(Buff); - return ec; - } - if (!Tlen) return 0; - -// Run through the memory and allocate an AList entry for each. -// - bP = Buff; bEnd = Buff+Tlen; - while(bP < bEnd) - {if ((aNew = getEnt(Path, fd, bP, *aPL, msP))) *aPL = aNew; - bP = bP + strlen(bP) + 1; - } - -// All done -// - free(Buff); - return maxSz; -} - -/******************************************************************************/ -/* X r d S y s F A t t r : : S e t */ -/******************************************************************************/ - -int XrdSysFAttr::Set(const char *Aname, const void *Aval, int Avsz, - const char *Path, int fd, int isNew) -{ - int ec, xFlag = (isNew ? XATTR_CREATE : 0); - -// Set the attribute -// - ec = (fd < 0 ? setxattr(Path, Aname, Aval, Avsz, 0, xFlag) - : fsetxattr(fd, Aname, Aval, Avsz, 0, xFlag)); - -// Diagnose any errors -// - if (ec < 0) ec = Diagnose("set", Aname, Path, errno); - return ec; -} diff --git a/src/XrdSys/XrdSysFAttrSun.icc b/src/XrdSys/XrdSysFAttrSun.icc deleted file mode 100644 index e307e410849..00000000000 --- a/src/XrdSys/XrdSysFAttrSun.icc +++ /dev/null @@ -1,199 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S y s F A t t r S u n . i c c */ -/* */ -/* (c) 2010 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include - -#include "XrdSys/XrdSysFAttr.hh" - -/******************************************************************************/ -/* X r d S y s F A t t r : : D e l */ -/******************************************************************************/ - -int XrdSysFAttr::Del(const char *Aname, const char *Path, int fd) -{ - int ec = 0; - -// Open the directory associated with the path or file descriptor -// - fd = (fd < 0 ? attropen(Path, ".", O_RDONLY) - : openat( fd, ".", O_RDONLY|O_XATTR)); - -// If the preceeding open failed, diagnose the problem. We ignore ENOENT. -// - if (fd < 0) - {if (errno == ENOENT) return 0; - return Diagnose("open to remove", Aname, Path, errno); - } - -// Now unlink the attribute file -// - if (unlinkat(fd, Aname, 0) < 0 && errno != ENOENT) - ec = Diagnose("remove", Aname, Path, errno); - -// All done -// - close(fd); - return ec; -} - -/******************************************************************************/ -/* X r d S y s F A t t r : : G e t */ -/******************************************************************************/ - -int XrdSysFAttr::Get(const char *Aname, void *Aval, int Avsz, - const char *Path, int fd) -{ - int ec = 0; - -// Open the directory associated with the path or file descriptor -// - fd = (fd < 0 ? attropen(Path, Aname, O_RDONLY) - : openat( fd, Aname, O_RDONLY|O_XATTR)); - -// If the preceeding open failed, diagnose the problem. We ignore ENOENT. -// - if (fd < 0) - {if (errno == ENOENT) return 0; - return Diagnose("open to get", Aname, Path, errno); - } - -// If a size was passed, then read the variable data. Otherwise, the caller -// wants to know the size of the attribute data. So, return that. -// - if (Avsz) {if ((ec = read(fd, Aval, Avsz)) < 0) ec = -errno;} - else {struct stat Stat; - ec = (fstat(fd, &Stat) ? -errno : Stat.st_size); - } - -// Close the underlying directory and diagnose any errors and return result. -// - close(fd); - if (ec < 0) ec = (ec == -ENOENT ? 0 : Diagnose("get", Aname, Path, -ec)); - return ec; -} - - -/******************************************************************************/ -/* X r d S y s F A t t r : : L i s t */ -/******************************************************************************/ -#if SOLARIS_VERSION < 11 -#if !defined(__XOPEN_OR_POSIX) -#define dirfd(x) x->dd_fd -#else -#define dirfd(x) x->d_fd -#endif -#endif - -int XrdSysFAttr::List(AList **aPL, const char *Path, int fd, int getSz) -{ - AList *aNew; - DIR *dP; - struct dirent *dEnt; - struct stat Stat; - int rc, maxSz = 0; - -// Open the directory associated with the path or file descriptor -// - *aPL = 0; - fd = (fd < 0 ? attropen(Path, ".", O_RDONLY) - : openat( fd, ".", O_RDONLY|O_XATTR)); - -// If the preceeding open failed, diagnose the problem. We ignore ENOENT. -// - if (fd < 0) - {if (errno == ENOENT) return 0; - return Diagnose("open to list", "*", Path, errno); - } - -// Now open the attribute directory -// - if (!(dP = fdopendir(fd))) - {rc = errno; close(fd); - if (rc == ENOENT) return 0; - return Diagnose("open to list", "*", Path, rc); - } - -// Now list the directory entries (less dot and dot-dot) as attributes. -// If the size is wanted, we do this here to avoid multiple opens. -// - while((dEnt = readdir(dP))) - {if ( (strcmp(".", dEnt->d_name) && strcmp("..", dEnt->d_name)) - && (aNew = getEnt(Path, fd, dEnt->d_name, *aPL, 0))) - {if (getSz) - {if (fstatat(dirfd(dP), dEnt->d_name, &Stat, 0)) - {Diagnose("stat", dEnt->d_name, Path, errno); continue;} - aNew->Vlen = Stat.st_size; - if (maxSz < aNew->Vlen) maxSz = aNew->Vlen; - } - *aPL = aNew; - } - } - -// All done -// - closedir(dP); close(fd); - return maxSz; -} - -/******************************************************************************/ -/* X r d S y s F A t t r : : S e t */ -/******************************************************************************/ - -int XrdSysFAttr::Set(const char *Aname, const void *Aval, int Avsz, - const char *Path, int fd, int isNew) -{ - static const mode_t aMode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH; - int oFlag = O_CREAT|O_RDWR|O_TRUNC|O_XATTR | (isNew ? O_EXCL : 0); - int ec = 0; - -// Open the directory associated with the path or file descriptor -// - fd = (fd < 0 ? attropen(Path, Aname, oFlag, aMode) - : openat( fd, Aname, oFlag, aMode)); - -// If the preceeding open failed, diagnose the problem. We ignore ENOENT. -// - if (fd < 0) return Diagnose("open to set", Aname, Path, errno); - -// Write the data, the caller should have made sure the file was truncated. -// - if (write(fd, Aval, Avsz) < 0) ec = Diagnose("set", Aname, Path, errno); - -// All done, close the file and return result -// - close(fd); - return ec; -} diff --git a/src/XrdSys/XrdSysFD.hh b/src/XrdSys/XrdSysFD.hh deleted file mode 100644 index 58cb01092db..00000000000 --- a/src/XrdSys/XrdSysFD.hh +++ /dev/null @@ -1,141 +0,0 @@ -#ifndef __XRDSYS_FD_H__ -#define __XRDSYS_FD_H__ -/******************************************************************************/ -/* */ -/* X r d S y s F D . h h */ -/* */ -/* (c) 2013 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -//----------------------------------------------------------------------------- -//! XrdSysFD defines a set of alternate functions that make sure that the -//! CLOEXEC attribute is associated with any new file descriptors returned by -//! by commonly used functions. This is platform sensitive as some platforms -//! allow atomic setting of the attribute while others do not. These functions -//! are used to provide platform portability. -//----------------------------------------------------------------------------- - -#include -#include -#include -#include -#include - -namespace -{ -#if defined(__linux__) && defined(SOCK_CLOEXEC) && defined(O_CLOEXEC) -inline int XrdSysFD_Accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) - {return accept4(sockfd, addr, addrlen, SOCK_CLOEXEC);} - -inline int XrdSysFD_Dup(int oldfd) - {return fcntl(oldfd, F_DUPFD_CLOEXEC, 0);} - -inline int XrdSysFD_Dup1(int oldfd, int minfd) - {return fcntl(oldfd, F_DUPFD_CLOEXEC, minfd);} - -inline int XrdSysFD_Dup2(int oldfd, int newfd) - {return dup3(oldfd, newfd, O_CLOEXEC);} - -inline int XrdSysFD_Open(const char *path, int flags) - {return open(path, flags|O_CLOEXEC);} - -inline int XrdSysFD_Open(const char *path, int flags, mode_t mode) - {return open(path, flags|O_CLOEXEC, mode);} - -inline int XrdSysFD_Pipe(int pipefd[2]) - {return pipe2(pipefd, O_CLOEXEC);} - -inline int XrdSysFD_Socket(int domain, int type, int protocol) - {return socket(domain, type|SOCK_CLOEXEC, protocol);} - -inline int XrdSysFD_Socketpair(int domain, int type, int protocol, int sfd[2]) - {return socketpair(domain, type|SOCK_CLOEXEC, protocol, sfd);} -#else -inline int XrdSysFD_Accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) - {int newfd = accept(sockfd, addr, addrlen); - if (newfd >= 0) fcntl(newfd, F_SETFD, FD_CLOEXEC); - return newfd; - } - -inline int XrdSysFD_Dup(int oldfd) - {int newfd = dup(oldfd); - if (newfd >= 0) fcntl(newfd, F_SETFD, FD_CLOEXEC); - return newfd; - } - -inline int XrdSysFD_Dup1(int oldfd, int minfd) - {int newfd = fcntl(oldfd, F_DUPFD, minfd); - if (newfd >= 0) fcntl(newfd, F_SETFD, FD_CLOEXEC); - return newfd; - } - -inline int XrdSysFD_Dup2(int oldfd, int newfd) - {int rc = dup2(oldfd, newfd); - if (!rc) fcntl(newfd, F_SETFD, FD_CLOEXEC); - return rc; - } - -inline int XrdSysFD_Open(const char *path, int flags) - {int newfd = open(path, flags); - if (newfd >= 0) fcntl(newfd, F_SETFD, FD_CLOEXEC); - return newfd; - } - -inline int XrdSysFD_Open(const char *path, int flags, mode_t mode) - {int newfd = open(path, flags, mode); - if (newfd >= 0) fcntl(newfd, F_SETFD, FD_CLOEXEC); - return newfd; - } - -inline int XrdSysFD_Pipe(int pipefd[2]) - {int rc = pipe(pipefd); - if (!rc) {fcntl(pipefd[0], F_SETFD, FD_CLOEXEC); - fcntl(pipefd[1], F_SETFD, FD_CLOEXEC); - } - return rc; - } - -inline int XrdSysFD_Socket(int domain, int type, int protocol) - {int newfd = socket(domain, type, protocol); - if (newfd >= 0) fcntl(newfd, F_SETFD, FD_CLOEXEC); - return newfd; - } - -inline int XrdSysFD_Socketpair(int domain, int type, int protocol, int sfd[2]) - {int rc = socketpair(domain, type, protocol, sfd); - if (!rc) {fcntl(sfd[0], F_SETFD, FD_CLOEXEC); - fcntl(sfd[1], F_SETFD, FD_CLOEXEC); - } - return rc; - } -#endif - -inline bool XrdSysFD_Yield(int fd) - {int fdFlags = fcntl(fd, F_GETFD); - if (fdFlags < 0) return false; - return 0 == fcntl(fd, F_SETFD, fdFlags & ~FD_CLOEXEC); - } -} -#endif diff --git a/src/XrdSys/XrdSysHeaders.hh b/src/XrdSys/XrdSysHeaders.hh deleted file mode 100644 index 88230fd5c65..00000000000 --- a/src/XrdSys/XrdSysHeaders.hh +++ /dev/null @@ -1,51 +0,0 @@ -#ifndef __XRDSYS_HEADERS_H__ -#define __XRDSYS_HEADERS_H__ -/******************************************************************************/ -/* */ -/* X r d S y s H e a d e r s . h h */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -// This header has been introduced to help the transition to new versions -// of the gcc compiler which deprecates or even not support some standard -// headers in the form .h -// - -#if !defined(HAVE_OLD_HDRS) || defined(WIN32) - -// gcc >= 4.3, cl require this -# include -using namespace std; - -#else - -# include - -#endif - - - -#endif // __XRDSYS_HEADERS_H__ diff --git a/src/XrdSys/XrdSysIOEvents.cc b/src/XrdSys/XrdSysIOEvents.cc deleted file mode 100644 index 8a41ac5c962..00000000000 --- a/src/XrdSys/XrdSysIOEvents.cc +++ /dev/null @@ -1,1203 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S y s I O E v e n t s . c c */ -/* */ -/* (c) 2012 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include - -#include "XrdSys/XrdSysFD.hh" -#include "XrdSys/XrdSysIOEvents.hh" -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdSys/XrdSysPlatform.hh" -#include "XrdSys/XrdSysPthread.hh" - -/******************************************************************************/ -/* L o c a l D e f i n e s */ -/******************************************************************************/ - -#define SINGLETON(dlvar, theitem)\ - theitem ->dlvar .next == theitem - -#define INSERT(dlvar, curitem, newitem) \ - newitem ->dlvar .next = curitem; \ - newitem ->dlvar .prev = curitem ->dlvar .prev; \ - curitem ->dlvar .prev-> dlvar .next = newitem; \ - curitem ->dlvar .prev = newitem - -#define REMOVE(dlbase, dlvar, curitem) \ - if (dlbase == curitem) dlbase = (SINGLETON(dlvar,curitem) \ - ? 0 : curitem ->dlvar .next);\ - curitem ->dlvar .prev-> dlvar .next = curitem ->dlvar .next;\ - curitem ->dlvar .next-> dlvar .prev = curitem ->dlvar .prev;\ - curitem ->dlvar .next = curitem;\ - curitem ->dlvar .prev = curitem - -#define REVENTS(x) x & Channel:: readEvents - -#define WEVENTS(x) x & Channel::writeEvents - -#define ISPOLLER XrdSysThread::Same(XrdSysThread::ID(),pollTid) - -#define BOOLNAME(x) (x ? "true" : "false") - -#define DO_TRACE(x,fd,y) \ - {PollerInit::traceMTX.Lock(); \ - cerr <<"IOE fd " <pollP; - XrdSysSemaphore *theSem = pollArg->pollSync; - thePoller->pollTid = XrdSysThread::ID(); - - thePoller->Begin(theSem, pollArg->retCode, &(pollArg->retMsg)); - delete theSem; - - return (void *)0; -} - -/******************************************************************************/ -/* P o l l e r E r r 1 */ -/******************************************************************************/ - -// This class is set in the channel when an error occurs but cannot be reflected -// immediately because either there is no callback function or all events are -// disabled. We need to do this because error events can be physically presented -// by the kernel even when logical events are disabled. Note that the error -// number and text will have been set and remain set as the channel was actually -// disabled preventing any new operation on the channel. -// -class PollerErr1 : public Poller -{ -public: - - PollerErr1() : Poller(-1, -1) {} - ~PollerErr1() {} - -protected: -void Begin(XrdSysSemaphore *syncp, int &rc, const char **eTxt) {} - -void Exclude(Channel *cP, bool &isLocked, bool dover=1) {} - -bool Include(Channel *cP, int &eNum, const char **eTxt, bool &isLocked) - {if (!(eNum = GetFault(cP))) eNum = EPROTO; - if (eTxt) *eTxt = "initializing channel"; - return false; - } - -bool Modify (Channel *cP, int &eNum, const char **eTxt, bool &isLocked) - {if (!(eNum = GetFault(cP))) eNum = EPROTO; - if (eTxt) *eTxt = "modifying channel"; - return false; - } - -void Shutdown() {} -}; - -/******************************************************************************/ -/* P o l l e r I n i t */ -/******************************************************************************/ - -// This class is used as the initial poller on a channel. It is responsible -// for adding the file descriptor to the poll set upon the initial enable. This -// avoids enabling a channel prior to it receiving a call back function. -// -class PollerInit : public Poller -{ -public: - - PollerInit() : Poller(-1, -1) {} - ~PollerInit() {} - -static XrdSysMutex traceMTX; -static bool doTrace; - -protected: - -void Begin(XrdSysSemaphore *syncp, int &rc, const char **eTxt) {} - -void Exclude(Channel *cP, bool &isLocked, bool dover=1) {} - -bool Include(Channel *cP, int &eNum, const char **eTxt, bool &isLocked) - {eNum = EPROTO; - if (eTxt) *eTxt = "initializing channel"; - return false; - } - -bool Modify (Channel *cP, int &eNum, const char **eTxt, bool &isLocked) - {bool rc = Init(cP, eNum, eTxt, isLocked); - IF_TRACE(Modify,cP->GetFD(), "Init() returned " <Attach(this); -} - -/******************************************************************************/ -/* D e l e t e */ -/******************************************************************************/ - -void XrdSys::IOEvents::Channel::Delete() -{ - Poller *myPoller; - bool isLocked = true; - -// Lock ourselves during the delete process. If the channel is disassociated -// or the real poller is set to the error poller then this channel is clean -// and can be deleted (i.e. the channel ran through Detach()). -// - chMutex.Lock(); - if (!chPollXQ || chPollXQ == &pollErr1) - {chMutex.UnLock(); - delete this; - return; - } - -// Disable and remove ourselves from all queues -// - myPoller = chPollXQ; - chPollXQ->Detach(this,isLocked,false); - if (!isLocked) chMutex.Lock(); - -// If we are in callback mode then we will need to delay the destruction until -// after the callback completes unless this is the poller thread. In that case, -// we need to tell the poller that we have been destroyed in a shelf-stable way. -// - if (chStat) - {if (XrdSysThread::Same(XrdSysThread::ID(),myPoller->pollTid)) - {myPoller->chDead = true; - chMutex.UnLock(); - } else { - XrdSysSemaphore cbDone(0); - chStat = isDead; - chCBA = (void *)&cbDone; - chMutex.UnLock(); - cbDone.Wait(); - } - } -// It is now safe to release the storage -// - delete this; -} - -/******************************************************************************/ -/* D i s a b l e */ -/******************************************************************************/ - -bool XrdSys::IOEvents::Channel::Disable(int events, const char **eText) -{ - int eNum = 0, newev, curev; - bool retval = true, isLocked = true; - -// Lock this channel -// - chMutex.Lock(); - -// Get correct current events; depending on the state of the channel -// - if (chPoller == &pollWait) curev = static_cast(reMod); - else curev = static_cast(chEvents); - -// Trace this entry -// - IF_TRACE(Disable,chFD,"->Disable(" <Enable("<chPoller == &pollInit ? "init" : - (cP->chPoller == &pollWait ? "wait" : "err"))); - DO_TRACE(CbkXeq,cP->chFD,"callback events=" <chCB ? "present" : "missing") - <<" poller=" <inTOQ) - {TmoDel(cP); - cP->dlType |= (events & CallBack::ValidEvents) << 4; - isRead = events & (CallBack::ReadyToRead | CallBack:: ReadTimeOut); - if (isRead) cP->rdDL = maxTime; - isWrite= events & (CallBack::ReadyToWrite | CallBack::WriteTimeOut); - if (isWrite) cP->wrDL = maxTime; - } else { - cP->dlType &= CallBack::ValidEvents; - isRead = isWrite = false; - } - -// Verify that there is a callback here and the channel is ready. If not, -// disable this channel for the events being refelcted unless the event is a -// fatal error. In this case we need to abandon the channel since error events -// may continue to be generated as we can't always disable them. -// - if (!(cP->chCB) || cP->chPoller != cP->chPollXQ) - {if (eNum) - {cP->chPoller = &pollErr1; cP->chFault = eNum; - cP->inPSet = 0; - return false; - } - oldEvents = cP->chEvents; - cP->chEvents = 0; - retval = cP->chPoller->Modify(cP, eNum, 0, isLocked); - TRACE_MOD(CbkXeq,cP->chFD,0); - if (!isLocked) cP->chMutex.Lock(); - cP->chEvents = oldEvents; - return true; - } - -// Resolve the problem where we get an error event but the channel wants them -// presented as a read or write event. If neither is possible then defer the -// error until the channel is enabled again. -// - if (eNum) - {if (cP->chEvents & Channel::errorEvents) - {cP->chPoller = &pollErr1; cP->chFault = eNum; - cP->chStat = Channel::isCBMode; - chDead = false; - cbkMHelp.UnLock(); - cP->chCB->Fatal(cP,cP->chCBA, eNum, eTxt); - if (chDead) return true; - cbkMHelp.Lock(&(cP->chMutex)); - cP->inPSet = 0; - return false; - } - if (REVENTS(cP->chEvents)) events = CallBack::ReadyToRead; - else if (WEVENTS(cP->chEvents)) events = CallBack::ReadyToWrite; - else {cP->chPoller = &pollErr1; cP->chFault = eNum; cP->inPSet = 0; - return false; - } - } - -// Indicate that we are in callback mode then drop the channel lock and effect -// the callback. This allows the callback to freely manage locks. -// - cP->chStat = Channel::isCBMode; - chDead = false; - cbkMHelp.UnLock(); - IF_TRACE(CbkXeq,cP->chFD,"invoking callback; events=" <chCB->Event(cP,cP->chCBA, events); - IF_TRACE(CbkXeq,cP->chFD,"callback returned " <chMutex)); - -// If the channel is being destroyed; then another thread must have done so. -// Tell it the callback has finished and just return. -// - if (cP->chStat != Channel::isCBMode) - {if (cP->chStat == Channel::isDead) - ((XrdSysSemaphore *)cP->chCBA)->Post(); - return true; - } - cP->chStat = Channel::isClear; - -// Handle enable or disable here. If we keep the channel enabled then reset -// the timeout if it hasn't been handled via a call from the callback. -// - if (!cbok) Detach(cP,isLocked,false); - else if ((isRead || isWrite) && !(cP->inTOQ) && (cP->chRTO || cP->chWTO)) - TmoAdd(cP, 0); - -// All done. While the mutex should not have been unlocked, we relock it if -// it has to keep the mutex helper from croaking. -// - if (!isLocked) cP->chMutex.Lock(); - return true; -} - -/******************************************************************************/ -/* Static: C r e a t e */ -/******************************************************************************/ - -XrdSys::IOEvents::Poller *XrdSys::IOEvents::Poller::Create(int &eNum, - const char **eTxt, - int crOpts) -{ - int fildes[2]; - struct pollArg pArg; - pthread_t tid; - -// Create a pipe used to break the poll wait loop -// - if (XrdSysFD_Pipe(fildes)) - {eNum = errno; - if (eTxt) *eTxt = "creating poll pipe"; - return 0; - } - -// Create an actual implementation of a poller -// - if (!(pArg.pollP = newPoller(fildes, eNum, eTxt))) - {close(fildes[0]); - close(fildes[1]); - return 0; - } - -// Now start a thread to handle this poller object -// - if ((eNum = XrdSysThread::Run(&tid, XrdSys::IOEvents::BootStrap::Start, - (void *)&pArg, XRDSYSTHREAD_BIND, "Poller"))) - {if (eTxt) *eTxt = "creating poller thread"; return 0;} - -// Now wait for the thread to finish initializing before we allow use -// Note that the bootstrap takes ownership of the semaphore and will delete it -// once the thread positing the semaphore actually ends. This is to avoid -// semaphore bugs present in certain (e.g. Linux) kernels. -// - pArg.pollSync->Wait(); - -// Check if all went well -// - if (pArg.retCode) - {if (eTxt) *eTxt = (pArg.retMsg ? pArg.retMsg : "starting poller"); - eNum = pArg.retCode; - delete pArg.pollP; - return 0; - } - -// Set creation options in the new poller -// - if (crOpts & optTOM) - pArg.pollP->tmoMask = ~(CallBack::ReadTimeOut|CallBack::WriteTimeOut); - -// All done -// - eNum = 0; - if (eTxt) *eTxt = ""; - return pArg.pollP; -} - -/******************************************************************************/ -/* D e t a c h */ -/******************************************************************************/ - -void XrdSys::IOEvents::Poller::Detach(XrdSys::IOEvents::Channel *cP, - bool &isLocked, bool keep) -{ -// The caller must hold the channel lock! -// - bool detFD = (cP->inPSet != 0); - -// First remove the channel from the timeout queue -// - if (cP->inTOQ) - {toMutex.Lock(); - REMOVE(tmoBase, tmoList, cP); - toMutex.UnLock(); - } - -// Allow only one detach at a time -// - adMutex.Lock(); - -// Preset channel to prohibit callback if we are not keeping this channel -// - if (!keep) - {cP->Reset(&pollErr1, cP->chFD); - cP->chPollXQ = &pollErr1; - cP->chCB = 0; - cP->chCBA = 0; - if (cP->attList.next != cP) {REMOVE(attBase, attList, cP);} - else if (attBase == cP) attBase = 0; - } - -// Exclude this channel from the associated poll set, don't hold the ad lock -// - adMutex.UnLock(); - if (detFD) - {cP->inPSet = 0; - if (cmdFD >= 0) Exclude(cP, isLocked, !ISPOLLER); - } -} - -/******************************************************************************/ -/* Protected: G e t R e q u e s t */ -/******************************************************************************/ - -// Warning: This method runs unlocked. The caller must have exclusive use of -// the reqBuff otherwise unpredictable results will occur. - -int XrdSys::IOEvents::Poller::GetRequest() -{ - ssize_t rlen; - int rc; - -// See if we are to resume a read or start a fresh one -// - if (!pipeBlen) - {pipeBuff = (char *)&reqBuff; pipeBlen = sizeof(reqBuff);} - -// Wait for the next request. Some OS's (like Linux) don't support non-blocking -// pipes. So, we must front the read with a poll. -// - do {rc = poll(&pipePoll, 1, 0);} - while(rc < 0 && (errno == EAGAIN || errno == EINTR)); - if (rc < 1) return 0; - -// Now we can put up a read without a delay. Normally a full command will be -// present. Under some heavy conditions, this may not be the case. -// - do {rlen = read(reqFD, pipeBuff, pipeBlen);} - while(rlen < 0 && errno == EINTR); - if (rlen <= 0) - {cerr <<"Poll: " <chPoller == &pollWait) - {cP->reMod = cP->chEvents; - cP->chEvents = 0; - IF_TRACE(Init,cP->chFD,"defer events=" <reMod); - return true; - } - -// Trace this entry -// - IF_TRACE(Init,cP->chFD,"begin events=" <chEvents)); - -// If no events are enabled at this point, just return -// - if (!(cP->chEvents)) return true; - -// Refuse to enable a channel without a callback function -// - if (!(cP->chCB)) - {eNum = EDESTADDRREQ; - if (eTxt) *eTxt = "enabling without a callback"; - return false; - } - -// So, now we can include the channel in the poll set. We will include it -// with no events enabled to prevent callbacks prior to completion here. -// - cP->chPoller = &pollWait; cP->reMod = cP->chEvents; cP->chEvents = 0; - retval = cP->chPollXQ->Include(cP, eNum, eTxt, isLocked); - IF_TRACE(Init,cP->chFD,"Include() returned " <chMutex.Lock(); isLocked = true;} - -// Determine what future poller to use. If we can use the regular poller then -// set the correct event mask for the channel. Note that we could have lost -// control but the correct events will be reflected in the "reMod" member. -// - if (!retval) {cP->chPoller = &pollErr1; cP->chFault = eNum;} - else {cP->chPoller = cP->chPollXQ; - cP->inPSet = 1; - if (cP->reMod) - {cP->chEvents = cP->reMod; - retval = cP->chPoller->Modify(cP, eNum, eTxt, isLocked); - TRACE_MOD(Init,cP->chFD,int(cP->reMod)); - if (!isLocked) {cP->chMutex.Lock(); isLocked = true;} - } else { - TRACE_NOD(Init,cP->chFD,0); - } - } - -// All done -// - cP->reMod = 0; - return retval; -} - -/******************************************************************************/ -/* P o l l 2 E n u m */ -/******************************************************************************/ - -int XrdSys::IOEvents::Poller::Poll2Enum(short events) -{ - if (events & POLLERR) return EPIPE; - - if (events & POLLHUP) return ECONNRESET; - - if (events & POLLNVAL) return EBADF; - - return EOPNOTSUPP; -} - -/******************************************************************************/ -/* S e n d C m d */ -/******************************************************************************/ - -int XrdSys::IOEvents::Poller::SendCmd(PipeData &cmd) -{ - int wlen; - -// Pipe writes are atomic so we don't need locks. Some commands require -// confirmation. We handle that here based on the command. Note that pipes -// gaurantee that all of the data will be written or we will block. -// - if (cmd.req >= PipeData::Post) - {XrdSysSemaphore mySem(0); - cmd.theSem = &mySem; - do {wlen = write(cmdFD, (char *)&cmd, sizeof(PipeData));} - while (wlen < 0 && errno == EINTR); - if (wlen > 0) mySem.Wait(); - } else { - do {wlen = write(cmdFD, (char *)&cmd, sizeof(PipeData));} - while (wlen < 0 && errno == EINTR); - } - -// All done -// - return (wlen >= 0 ? 0 : errno); -} - -/******************************************************************************/ -/* Protected: S e t P o l l E n t */ -/******************************************************************************/ - -void XrdSys::IOEvents::Poller::SetPollEnt(XrdSys::IOEvents::Channel *cP, int pe) -{ - cP->pollEnt = pe; -} - -/******************************************************************************/ -/* S t o p */ -/******************************************************************************/ - -void XrdSys::IOEvents::Poller::Stop() -{ - PipeData cmdbuff; - CallBack *theCB; - Channel *cP; - void *cbArg; - int doCB; - -// Initialize the pipdata structure -// - memset(static_cast( &cmdbuff ), 0, sizeof(cmdbuff)); - cmdbuff.req = PipeData::Stop; - -// Lock all of this -// - adMutex.Lock(); - -// If we are already shutdown then we are done -// - if (cmdFD == -1) {adMutex.UnLock(); return;} - -// First we must stop the poller thread in an orderly fashion. -// - SendCmd(cmdbuff); - -// Close the pipe communication mechanism -// - close(cmdFD); cmdFD = -1; - close(reqFD); reqFD = -1; - -// Run through cleaning up the channels. While there should not be any other -// operations happening on this poller, we take the conservative approach. -// - while((cP = attBase)) - {REMOVE(attBase, attList, cP); - adMutex.UnLock(); - cP->chMutex.Lock(); - doCB = cP->chCB != 0 && (cP->chEvents & Channel::stopEvent); - if (cP->inTOQ) TmoDel(cP); - cP->Reset(&pollErr1, cP->chFD, EIDRM); - cP->chPollXQ = &pollErr1; - if (doCB) - {cP->chStat = Channel::isClear; - theCB = cP->chCB; cbArg = cP->chCBA; - cP->chMutex.UnLock(); - theCB->Stop(cP, cbArg); - } else cP->chMutex.UnLock(); - adMutex.Lock(); - } - -// Now invoke the poller specific shutdown -// - Shutdown(); - adMutex.UnLock(); -} - -/******************************************************************************/ -/* T m o A d d */ -/******************************************************************************/ - -bool XrdSys::IOEvents::Poller::TmoAdd(XrdSys::IOEvents::Channel *cP, int tmoSet) -{ - XrdSysMutexHelper mHelper(toMutex); - time_t tNow; - Channel *ncP; - bool setRTO, setWTO; - -// Remove element from timeout queue if it is there -// - if (cP->inTOQ) - {REMOVE(tmoBase, tmoList, cP); - cP->inTOQ = 0; - } - -// Determine which timeouts need to be reset -// - tmoSet|= cP->dlType >> 4; - setRTO = (tmoSet&tmoMask) & (CallBack::ReadyToRead |CallBack:: ReadTimeOut); - setWTO = (tmoSet&tmoMask) & (CallBack::ReadyToWrite|CallBack::WriteTimeOut); - -// Reset the required deadlines -// - tNow = time(0); - if (setRTO && REVENTS(cP->chEvents) && cP->chRTO) - cP->rdDL = cP->chRTO + tNow; - if (setWTO && WEVENTS(cP->chEvents) && cP->chWTO) - cP->wrDL = cP->chWTO + tNow; - -// Calculate the closest enabled deadline -// - if (cP->rdDL < cP->wrDL) - {cP->deadLine = cP->rdDL; cP->dlType = CallBack:: ReadTimeOut; - } else { - cP->deadLine = cP->wrDL; cP->dlType = CallBack::WriteTimeOut; - if (cP->rdDL == cP->wrDL) cP->dlType |= CallBack:: ReadTimeOut; - } - IF_TRACE(TmoAdd, cP->chFD, "t=" <. */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include - -#include "XrdSys/XrdSysPthread.hh" -#include "XrdSys/XrdSysAtomics.hh" - -//----------------------------------------------------------------------------- -//! IOEvents -//! -//! The classes here define a simple I/O event polling architecture suitable -//! for use with non-blocking devices. As it implements an event model, it is -//! not considered a high performance interface. For increased performance, you -//! need to use multiple polling event loops which effectively implements a -//! limited thread model for handling events. The implementation here is similar -//! to libEvent with better handling of timeouts and I/O polling resumption. -//! -//! While, channels are multi-thread safe, they cannot interlock with the state -//! of their file descriptor. You must first disable (via SetFD()) or delete -//! the channel before closing its associated file descriptor. This is the -//! only safe way to keep channels and their file descriptors synchronized. -//----------------------------------------------------------------------------- - -namespace XrdSys -{ -namespace IOEvents -{ - -/******************************************************************************/ -/* C l a s s C a l l B a c k */ -/******************************************************************************/ - -//----------------------------------------------------------------------------- -//! Interface -//! -//! The object used to effect a callback when an event occurs on a channel. -//! During the callback, all channels associated with the poller object doing -//! the callback are suspended until the callback object returns. All queued -//! callbacks are handled before the poller resumes any channels. This provides -//! simple serialization for all channels associated with a single poller. -//! You may call any channel method from a callback to effect appropriate -//! changes. You may also delete the channel at any time. -//----------------------------------------------------------------------------- - -class Channel; -class CallBack -{ -public: - -//----------------------------------------------------------------------------- -//! Events that may cause a callback object to be activated. -//----------------------------------------------------------------------------- - - enum EventType - { - ReadyToRead = 0x01, //!< New data has arrived - ReadTimeOut = 0x02, //!< Read timeout - ReadyToWrite = 0x04, //!< Writing won't block - WriteTimeOut = 0x08, //!< Write timeout - ValidEvents = 0x0f //!< Mask to test for valid events - }; - -//----------------------------------------------------------------------------- -//! Handle event notification. A method must be supplied. The enable/disable -//! status of the channel is not modified. To change the status, use the -//! channel's Enable() and Disable() method prior to returning. After return, -//! the current channel's status is used to determine how it will behave. If -//! the event is a timeout, the timeout becomes infinite for that event unless -//! Enable() is called for the event. This is to prevent timeout loops on -//! channels that remain enabled even after a timeout. Event loop callbacks -//! define a hazardous programming model. If you do not have a well defined -//! threading model, you should restrict yourself to dealing only with the -//! passed channel object in the callback so as to avoid deadlocks. -//! -//! @param chP the associated channel causing the callback. -//! @param cbArg the callback argument specified for the channel. -//! @param evFlags events that caused this callback to be invoked. More than -//! one event may be indicated (see EventType above). -//! -//! @return true Resume handling the channel with current status. -//! false Disable the channel and remove it from associated poller. -//----------------------------------------------------------------------------- - -virtual bool Event(Channel *chP, void *cbArg, int evFlags) = 0; - -//----------------------------------------------------------------------------- -//! Handle fatal error notification. This method is called only when error -//! events are specifically enabled (see Enable() for admonitions). It is -//! passed the reason for the error. Upon return, the channel is disabled but -//! stays attached to the poller so that it can be revitalized with SetFD(). -//! You should replace this method if you specifically enable error events. -//! -//! @param chP the associated channel causing the callback. -//! @param cbArg the callback argument specified for the channel. -//! @param eNum the errno associated with the error. -//! @param eTxt descriptive name of the operation encountering the error. -//----------------------------------------------------------------------------- - -virtual void Fatal(Channel *chP, void *cbArg, int eNum, const char *eTxt) -{ - (void)chP; (void)cbArg; (void)eNum; (void)eTxt; -}; - -//----------------------------------------------------------------------------- -//! Handle poller stop notification. This method is called only when the poller -//! is stopped and the channel enabled the stop event. You should should replace -//! this method if you specifically enable the stop event. You must not invoke -//! channel methods in this callback, otherwise the results are unpredictable. -//! -//! @param chP the associated channel causing the callback. -//! @param cbArg the callback argument specified for the channel. -//----------------------------------------------------------------------------- - -virtual void Stop(Channel *chP, void *cbArg) { (void)chP; (void)cbArg;} - -//----------------------------------------------------------------------------- -//! Constructor -//----------------------------------------------------------------------------- - - CallBack() {} - -//----------------------------------------------------------------------------- -//! Destructor -//----------------------------------------------------------------------------- - -virtual ~CallBack() {} -}; - -/******************************************************************************/ -/* C l a s s C h a n n e l */ -/******************************************************************************/ - -//----------------------------------------------------------------------------- -//! Describe a channel that is capable of fielding events. A valid channel is -//! associated with a Poller object, a CallBack object, and an open file -//! descriptor. These are normally set at construction time. -//----------------------------------------------------------------------------- - -class ChannelWait; -class Poller; -class Channel -{ -friend class Poller; -public: - -//----------------------------------------------------------------------------- -//! Delete a channel. You must use this method instead of delete. The Delete() -//! may block if an channel is being deleted outside of the poller thread. -//! When this object is deleted, all events are disabled, pending callbacks -//! are either completed or canceled, and the channel is removed from the -//! assigned poller. Only then is the storage freed. -//----------------------------------------------------------------------------- - - void Delete(); - -//----------------------------------------------------------------------------- -//! Event bits used to feed Enable() and Disable(); can be or'd. -//----------------------------------------------------------------------------- - -enum EventCode {readEvents = 0x01, //!< Read and Read Timeouts - writeEvents = 0x04, //!< Write and Write Timeouts - rwEvents = 0x05, //!< Both of the above - errorEvents = 0x10, //!< Error event non-r/w specific - stopEvent = 0x20, //!< Poller stop event - allEvents = 0x35 //!< All of the above - }; - -//----------------------------------------------------------------------------- -//! Disable one or more events. Ignored for already disabled events. -//! -//! @param events one or more events or'd together (see EventCode above). -//! @param eText optional pointer to where an operation description is to be -//! placed when an error occurs (i.e. returns false). -//! -//! @return true Events successfully disabled. -//! false Events not disabled; errno holds the error number and if -//! eText is supplied, points to the operation desscription. -//----------------------------------------------------------------------------- - - bool Disable(int events, const char **eText=0); - -//----------------------------------------------------------------------------- -//! Enable one or more events. Events that are already enabled remain enabled -//! but may have their timeout value change. -//! -//! Enable can fail for many reasons. Most importantly, if the channel was -//! disabled for all events when a fatal error occurred; enabling it immediately -//! returns the fatal error without invoking the callback. This happens on -//! platforms that disallow physically masking out error events. -//! -//! Additionally, when an error occurs and the channel is not enabled for error -//! events but is enabled for read or write, the callback is called indicating -//! ReadyToRead or ReadyToWrite. A subsequent write will then end with an error -//! (typically, EPIPE) and a subsequent read will end with an erorr or indicate -//! zero bytes read; either of which should be treated as an error (typically, -//! POLLHUP). Generally, you should always allow separable error events. -//! -//! @param events one or more events or'd together (see EventCode above). -//! @param timeout >0 maximum seconds that may elapsed before a timeout event -//! corresponding to the specified event(s) occurs. -//! =0 Keep whatever timeout is currently in effect from the -//! previous Enable() invocation for the event(s). -//! <0 No timeout applies. -//! There can be separate timeouts for read and write if -//! Enable() is separately called for each event code. -//! Otherwise, the timeout applies to all specified events. -//! The timeout is ignored for error events. -//! @param eText optional pointer to where an operation description is to be -//! placed when an error occurs (i.e. returns false). -//! -//! @return true Events successfully enabled. -//! false Events not enabled; errno holds the error number and if -//! eText is supplied, points to the operation desscription. -//----------------------------------------------------------------------------- - - bool Enable(int events, int timeout=0, const char **eText=0); - -//----------------------------------------------------------------------------- -//! Get the callback object and argument associated with this channel. -//! -//! @param cbP Place where the pointer is to be returned. -//! @param cbArg Place where the callback argument is to be returned. -//----------------------------------------------------------------------------- - - void GetCallBack(CallBack **cbP, void **cbArg); - -//----------------------------------------------------------------------------- -//! Get the events that are currently enabled for this channel. -//! -//! @return >0 Event bits that are enabled (see EventCode above). -//! =0 No events are enabled. -//! <0 Channel not assigned to a Poller object. -//----------------------------------------------------------------------------- - -inline int GetEvents() {return (chPoller ? static_cast(chEvents) : -1);} - -//----------------------------------------------------------------------------- -//! Get the file descriptor number associated with this channel. -//! -//! @return >=0 The file descriptor number. -//! < 0 No file desciptor associated with the channel. -//----------------------------------------------------------------------------- - -inline int GetFD() {return chFD;} - -//----------------------------------------------------------------------------- -//! Set the callback object and argument associated with this channel. -//! -//! @param cbP Pointer to the callback object (see above). The callback -//! object must not be deleted while associated to a channel. A -//! null callback object pointer effectively disables the channel. -//! @param cbArg The argument to be passed to the callback object. -//----------------------------------------------------------------------------- - - void SetCallBack(CallBack *cbP, void *cbArg=0); - -//----------------------------------------------------------------------------- -//! Set a new file descriptor to be associated with this channel. The channel -//! is removed from polling consideration but remains attached to the poller. -//! The new file descriptor is recorded but the channel remains disabled. You -//! must use Enable() to add the file descriptor back to the polling set. This -//! allows you to retract a file descriptor about to be closed without having a -//! new file descriptor handy (e.g., use -1). This facilitates channel re-use. -//! -//! @param fd The associated file descriptor number. -//----------------------------------------------------------------------------- - - void SetFD(int fd); - -//----------------------------------------------------------------------------- -//! Constructor. -//! -//! @param pollP Pointer to the poller object to which this channel will be -//! assigned. Events are initially disabled after assignment and no -//! timeout applies. Poller object assignment is permanent for the -//! life of the channel object. -//! @param fd The associated file descriptor number. It should not be -//! assigned to any other channel and must be valid when the -//! channel is enabled. Use SetFD() to set a new value. -//! @param cbP Pointer to the callback object (see above). The callback -//! object must not be deleted while associated to a channel. -//! A callback object must exist in order for the channel to be -//! enabled. Use SetCallBack() if you defered setting it here. -//! @param cbArg The argument to be passed to the callback object. -//----------------------------------------------------------------------------- - - Channel(Poller *pollP, int fd, CallBack *cbP=0, void *cbArg=0); - -private: - -//----------------------------------------------------------------------------- -//! Destuctor is private, use Delete() to delete this object. -//----------------------------------------------------------------------------- - - ~Channel() {} - -struct dlQ {Channel *next; Channel *prev;}; - -XrdSysRecMutex chMutex; - -dlQ attList; // List of attached channels -dlQ tmoList; // List of channels in the timeout queue - -Poller *chPoller; // The effective poller -Poller *chPollXQ; // The real poller -CallBack *chCB; // CallBack function -void *chCBA; // CallBack argument -int chFD; // Associated file descriptor -int pollEnt; // Used only for poll() type pollers -int chRTO; // Read timeout value (0 means none) -int chWTO; // Write timeout value (0 means none) -time_t rdDL; // Read deadline -time_t wrDL; // Write deadline -time_t deadLine; // The deadline in effect (read or write) -char dlType; // The deadline type in deadLine as CallBack type -char chEvents; // Enabled events as Channel type -char chStat; // Channel status below (!0 -> in callback mode) -enum Status {isClear = 0, isCBMode, isDead}; -char inTOQ; // True if the channel is in the timeout queue -char inPSet; // FD is in the actual poll set -char reMod; // Modify issued while defered, re-issue needed -short chFault; // Defered error, 0 if all is well - -void Reset(Poller *thePoller, int fd, int eNum=0); -}; - -/******************************************************************************/ -/* C l a s s P o l l e r */ -/******************************************************************************/ - -//----------------------------------------------------------------------------- -//! Define a poller object interface. A poller fields and dispatches event -//! callbacks. An actual instance of a poller object is obtained by using the -//! Create() method. You cannot simply create an instance of this object using -//! new or in-place declaration since it is abstract. Any number of these -//! objects may created. Each creation spawns a polling thread. -//----------------------------------------------------------------------------- - -class Poller -{ -friend class BootStrap; -friend class Channel; -public: - -//----------------------------------------------------------------------------- -//! Create a specialized instance of a poller object, initialize it, and start -//! the polling process. You must call Create() to obtain a specialized poller. -//! -//! @param eNum Place where errno is placed upon failure. -//! @param eTxt Place where a pointer to the description of the failing -//! operation is to be set. If null, no description is returned. -//! @param crOpts Poller options (see static const optxxx): -//! optTOM - Timeout resumption after a timeout event must be -//! manually reenabled. By default, event timeouts are -//! automatically renabled after successful callbacks. -//! -//! @return !0 Poller successfully created and started. -//! eNum contains zero. -//! eTxt if not null contains a null string. -//! The returned value is a pointer to the Poller object. -//! 0 Poller could not be created. -//! eNum contains the associated errno value. -//! eTxt if not null contains the failing operation. -//----------------------------------------------------------------------------- - -enum CreateOpts {optTOM}; - -static Poller *Create(int &eNum, const char **eTxt=0, int crOpts=0); - -//----------------------------------------------------------------------------- -//! Stop a poller object. Active callbacks are completed. Pending callbacks are -//! discarded. After which the poller event thread exits. Subsequently, each -//! associated channel is disabled and removed from the poller object. If the -//! channel is enabled for a StopEvent, the stop callback is invoked. However, -//! any attempt to use the channel methods that require an active poller will -//! return an error. -//! -//! Since a stopped poller cannot be restarted; the only thing left is to delete -//! it. This also applies to all the associated channels since they no longer -//! have an active poller. -//----------------------------------------------------------------------------- - - void Stop(); - -//----------------------------------------------------------------------------- -//! Constructor -//! -//! @param cFD The file descriptor to send commands to the poll thread. -//! @param rFD The file descriptor to recv commands in the poll thread. -//----------------------------------------------------------------------------- - - Poller(int cFD, int rFD); - -//----------------------------------------------------------------------------- -//! Destructor. Stop() is effecively called when this object is deleted. -//----------------------------------------------------------------------------- - -virtual ~Poller() {} - -protected: -struct PipeData; - - void CbkTMO(); - bool CbkXeq(Channel *cP, int events, int eNum, const char *eTxt); -inline int GetFault(Channel *cP) {return cP->chFault;} -inline int GetPollEnt(Channel *cP) {return cP->pollEnt;} - int GetRequest(); - bool Init(Channel *cP, int &eNum, const char **eTxt, bool &isLockd); -inline void LockChannel(Channel *cP) {cP->chMutex.Lock();} - int Poll2Enum(short events); - int SendCmd(PipeData &cmd); - void SetPollEnt(Channel *cP, int ptEnt); - bool TmoAdd(Channel *cP, int tmoSet); - void TmoDel(Channel *cP); - int TmoGet(); -inline void UnLockChannel(Channel *cP) {cP->chMutex.UnLock();} - -//! Start the polling event loop. An implementation must be supplied. Begin() -//! is called via the internal BootStrap class from a new thread. -//! -virtual void Begin(XrdSysSemaphore *syncp, int &rc, const char **eTxt) = 0; - -//! Remove a channel to the poll set. An implementation must be supplied. -//! The channel is locked when this method is called but must be unlocked by the -//! method if a command is sent to the poller thread and isLocked set to false. -//! -virtual void Exclude(Channel *cP, bool &isLocked, bool dover=1) = 0; - -//! Add a channel to the poll set. An implementation must be supplied. -//! The channel is locked when this method is called but must be unlocked by the -//! method if a command is sent to the poller thread and isLocked set to false. -//! -virtual bool Include(Channel *cP, - int &eNum, - const char **eTxt, - bool &isLocked) = 0; - -//! Modify the event status of a channel. An implementation must be supplied. -//! The channel is locked when this method is called but must be unlocked by the -//! method if a command is sent to the poller thread and isLocked set to false. -//! -virtual bool Modify (Channel *cP, - int &eNum, - const char **eTxt, - bool &isLocked) = 0; - -//! Shutdown the poller. An implementation must be supplied. The shutdown method -//! must release any allocated storage and close private file descriptors. The -//! polling thread will have already been terminated and x-thread pipe closed. -//! Warning: the derived destructor *must* call Stop() and do nothing else! -// -virtual void Shutdown() = 0; - -// The following is common to all implementations -// -Channel *attBase; // -> First channel in attach queue or 0 -Channel *tmoBase; // -> First channel in timeout queue or 0 - -pthread_t pollTid; // Poller's thread ID - -struct pollfd pipePoll; // Stucture to wait for pipe events -int cmdFD; // FD to send PipeData commands -int reqFD; // FD to recv PipeData requests -struct PipeData {char req; char evt; short ent; int fd; - XrdSysSemaphore *theSem; - enum cmd {NoOp = 0, MdFD = 1, Post = 2, - MiFD = 3, RmFD = 4, Stop = 5}; - PipeData(char reQ=0, char evT=0, short enT=0, - int fD =0, XrdSysSemaphore *sP=0) - : req(reQ), evt(evT), ent(enT), fd(fD), - theSem(sP) {} - ~PipeData() {} - }; -PipeData reqBuff; // Buffer used by poller thread to recv data -char *pipeBuff; // Read resumption point in buffer -int pipeBlen; // Number of outstanding bytes -unsigned char tmoMask; // Timeout mask -CPP_ATOMIC_TYPE(bool) wakePend; // Wakeup is effectively pending (don't send) -bool chDead; // True if channel deleted by callback - -static time_t maxTime; // Maximum time allowed - -private: - -void Attach(Channel *cP); -void Detach(Channel *cP, bool &isLocked, bool keep=true); -void WakeUp(); - -// newPoller() called to get a specialized new poll object at in response to -// Create(). A specialized implementation must be supplied. -// -static Poller *newPoller(int pFD[2], int &eNum, const char **eTxt); - -XrdSysMutex adMutex; // Mutex for adding & detaching channels -XrdSysMutex toMutex; // Mutex for handling the timeout list -}; -}; -}; -#endif diff --git a/src/XrdSys/XrdSysIOEventsPollE.icc b/src/XrdSys/XrdSysIOEventsPollE.icc deleted file mode 100644 index 91cd8c81dc8..00000000000 --- a/src/XrdSys/XrdSysIOEventsPollE.icc +++ /dev/null @@ -1,428 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S y s I O E v e n t s P o l l E . i c c */ -/* */ -/* (c) 2012 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#ifndef __APPLE__ -#include -#endif -#include -#include -#include - -#include "XrdSys/XrdSysAtomics.hh" -#ifndef Atomic -#define Atomic(x) x -#endif - -using namespace std; - -/******************************************************************************/ -/* C l a s s P o l l E */ -/******************************************************************************/ - -namespace XrdSys -{ -namespace IOEvents -{ -class PollE : public Poller -{ -public: - -static int AllocMem(void **memP, int slots); - - PollE(struct epoll_event *ptab, int numfd, int pfd, int pFD[2]) - : Poller(pFD[0], pFD[1]), pollTab(ptab), cbNow(0), - pollDfd(pfd), pollMax(numfd), pollNum(1), numPoll(0), - cbCurr(0) - {} - ~PollE() {Stop();} - -protected: - - void Begin(XrdSysSemaphore *syncp, int &rc, const char **eMsg); - - void Exclude(Channel *cP, bool &isLocked, bool dover=1); - - bool Include(Channel *cP, int &eNum, const char **eTxt, bool &isLocked); - - bool Modify (Channel *cP, int &eNum, const char **eTxt, bool &isLocked); - - void Shutdown(); - -private: - int AllocPT(int slots); - void Dispatch(Channel *cP, uint32_t pollEv); - bool Process(int curr); - -struct epoll_event *pollTab; - Channel *cbNow; - int pollDfd; - int pollMax; - Atomic(int) pollNum; - int numPoll; - int cbCurr; -static void *deadChP; -}; - void *PollE::deadChP = 0; -}; -}; - -/******************************************************************************/ -/* C l a s s P o l l e r */ -/******************************************************************************/ -/******************************************************************************/ -/* Static: n e w P o l l e r */ -/******************************************************************************/ - -XrdSys::IOEvents::Poller * -XrdSys::IOEvents::Poller::newPoller(int pipeFD[2], - int &eNum, - const char **eTxt) - -{ - static const int allocFD = 1024; - struct epoll_event *pp, myEvent = {(EPOLLIN | EPOLLPRI), {0}}; - int pfd; - -// Open the /dev/poll driver -// -#ifndef EPOLL_CLOEXEC - if ((pfd = epoll_create(allocFD)) >= 0) fcntl(pfd, F_SETFD, FD_CLOEXEC); - else -#else - if ((pfd = epoll_create1(EPOLL_CLOEXEC)) < 0) -#endif - {eNum = errno; - if (eTxt) *eTxt = "creating epoll device"; - return 0; - } - -// Add the request side of the pipe fd to the poll set (always fd[0]) -// - if (epoll_ctl(pfd, EPOLL_CTL_ADD, pipeFD[0], &myEvent)) - { eNum = errno; - *eTxt = "adding communication pipe"; - return 0; - } - -// Allocate the poll table -// - if ((eNum = XrdSys::IOEvents::PollE::AllocMem((void **)&pp, allocFD))) - {eNum = ENOMEM; - if (eTxt) *eTxt = "creating epoll table"; - close(pfd); - return 0; - } - -// Create new poll object -// - return (Poller *)new PollE(pp, allocFD, pfd, pipeFD); -} - -/******************************************************************************/ -/* C l a s s P o l l E */ -/******************************************************************************/ -/******************************************************************************/ -/* A l l o c M e m */ -/******************************************************************************/ - -int XrdSys::IOEvents::PollE::AllocMem(void **memP, int slots) -{ - int rc, bytes, alignment, pagsz = getpagesize(); - -// Calculate the size of the poll table and allocate it -// - bytes = slots * sizeof(struct epoll_event); - alignment = (bytes < pagsz ? 1024 : pagsz); - if (!(rc = posix_memalign(memP, alignment, bytes))) memset(*memP, 0, bytes); - return rc; -} - -/******************************************************************************/ -/* Private: A l l o c P T */ -/******************************************************************************/ - -int XrdSys::IOEvents::PollE::AllocPT(int slots) -{ - struct epoll_event *pp; - -// Calclulate new slots -// - if (pollMax >= slots) slots = pollMax + 256; - else slots = pollMax + (slots/256*256) + (slots%256 ? 256 : 0); - -// Allocate a new table and if successful, replace the old one -// - if (!AllocMem((void **)&pp, slots)) - {free(pollTab); - pollTab = pp; - pollMax = slots; - } - -// All done -// - return 0; -} - -/******************************************************************************/ -/* Protected: B e g i n */ -/******************************************************************************/ - -void XrdSys::IOEvents::PollE::Begin(XrdSysSemaphore *syncsem, - int &retcode, - const char **eTxt) -{ - int numpolled, pollN; - Channel *cP; - -// Indicate to the starting thread that all went well -// - retcode = 0; - *eTxt = 0; - syncsem->Post(); - -// Now start dispatching channels that are ready. We use the wakePend flag to -// keep the chatter down when we actually wakeup. -// - do {do {numpolled = epoll_wait(pollDfd, pollTab, pollMax, TmoGet());} - while (numpolled < 0 && errno == EINTR); - CPP_ATOMIC_STORE(wakePend, true, std::memory_order_release); - numPoll = numpolled; - if (numpolled == 0) CbkTMO(); - else if (numpolled < 0) - {int rc = errno; - cerr <<"EPoll: " <GetFD(), 0); - AtomicDec(pollNum); - -// If we need to verify this action, sync with the poller thread (note that the -// poller thread will not ask for this action unless it wants to deadlock). We -// may actually deadlock anyway if the channel lock is held. We are allowed to -// release it if the caller locked it. This will prevent a deadlock. Otherwise, -// if we are in a callback and this channel is not the one that initiated the -// exclude then we must make sure that we cancel any pending callback to the -// excluded channel as it may have been deleted and we won't know that here. -// - if (dover) - {PipeData cmdbuff; - if (isLocked) - {isLocked = false; - UnLockChannel(cP); - } - cmdbuff.req = PipeData::RmFD; - cmdbuff.fd = cP->GetFD(); - SendCmd(cmdbuff); - } else { - if (cbNow && cbNow != cP) - for (int i = cbCurr+1; i < numPoll; i++) - {if (cP == (Channel *)pollTab[i].data.ptr) - pollTab[i].data.ptr = &deadChP; - } - } -} - -/******************************************************************************/ -/* Protected: I n c l u d e */ -/******************************************************************************/ - -bool XrdSys::IOEvents::PollE::Include(XrdSys::IOEvents::Channel *cP, - int &eNum, - const char **eTxt, - bool &isLocked) -{ - struct epoll_event myEvent = {0, {(void *)cP}}; - int events = cP->GetEvents(); - -// Establish new event mask -// - if (events & Channel:: readEvents) myEvent.events = EPOLLIN | EPOLLPRI; - if (events & Channel::writeEvents) myEvent.events |= EPOLLOUT; - -// Add this fd to the poll set -// - if (epoll_ctl(pollDfd, EPOLL_CTL_ADD, cP->GetFD(), &myEvent)) - {eNum = errno; - if (eTxt) *eTxt = "adding channel"; - return false; - } - -// All went well. Bump the number in the set. The poller thread will -// reallocate the poll table if need be. -// - AtomicInc(pollNum); - return true; -} - -/******************************************************************************/ -/* Protected: M o d i f y */ -/******************************************************************************/ - -bool XrdSys::IOEvents::PollE::Modify(XrdSys::IOEvents::Channel *cP, - int &eNum, - const char **eTxt, - bool &isLocked) -{ - struct epoll_event myEvents = {0, {(void *)cP}}; - int events = cP->GetEvents(); - -// Establish new event mask -// - if (events & Channel:: readEvents) myEvents.events |= EPOLLIN | EPOLLPRI; - if (events & Channel::writeEvents) myEvents.events |= EPOLLOUT; - -// Modify this fd. Unlike solaris, epoll_ctl() does not block when the pollfd -// is being waited upon by another thread. -// - if (epoll_ctl(pollDfd, EPOLL_CTL_MOD, cP->GetFD(), &myEvents)) - {eNum = errno; - if (eTxt) *eTxt = "modifying poll events"; - return false; - } - -// All done -// - return true; -} - -/******************************************************************************/ -/* Private: P r o c e s s */ -/******************************************************************************/ - -bool XrdSys::IOEvents::PollE::Process(int curr) -{ -// Get the pipe request and check out actions of interest. -// - if (GetRequest()) - { if (reqBuff.req == PipeData::RmFD) - {Channel *cP; - for (int i = curr+1; i < numPoll; i++) - {if ((cP = (Channel *)pollTab[i].data.ptr) - && cP != (XrdSys::IOEvents::Channel *)&deadChP - && reqBuff.fd == cP->GetFD()) pollTab[i].data.ptr=&deadChP; - } - reqBuff.theSem->Post(); - } - else if (reqBuff.req == PipeData::Stop){reqBuff.theSem->Post(); - return false; - } - } - -// Return true -// - return true; -} - -/******************************************************************************/ -/* Protected: S h u t d o w n */ -/******************************************************************************/ - -void XrdSys::IOEvents::PollE::Shutdown() -{ - static XrdSysMutex shutMutex; - -// To avoid race conditions, we serialize this code -// - shutMutex.Lock(); - -// Release the poll table -// - if (pollTab) {free(pollTab); pollTab = 0;} - -// Close the epoll file descriptor -// - if (pollDfd >= 0) {close(pollDfd); pollDfd = -1;} - -// All done -// - shutMutex.UnLock(); -} diff --git a/src/XrdSys/XrdSysIOEventsPollKQ.icc b/src/XrdSys/XrdSysIOEventsPollKQ.icc deleted file mode 100644 index b5bf07f9e6c..00000000000 --- a/src/XrdSys/XrdSysIOEventsPollKQ.icc +++ /dev/null @@ -1,469 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S y s I O E v e n t s P o l l K Q . i c c */ -/* */ -/* (c) 2014 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#ifndef __APPLE__ -#include -#endif -#include -#include -#include -#include - -#include "XrdSys/XrdSysAtomics.hh" -#ifndef Atomic -#define Atomic(x) x -#endif - -using namespace std; - -/******************************************************************************/ -/* C l a s s P o l l E */ -/******************************************************************************/ - -namespace XrdSys -{ -namespace IOEvents -{ -class PollKQ : public Poller -{ -public: - -static int AllocMem(void **memP, int slots); - - PollKQ(struct kevent *ptab, int numfd, int pfd, int pFD[2]) - : Poller(pFD[0], pFD[1]), pollTab(ptab), cbNext(0), - pollDfd(pfd), pollMax(numfd), pollNum(1), numPoll(0) - {EV_SET(&armPipe, reqFD, EVFILT_READ, - EV_ADD|EV_CLEAR|EV_ENABLE, 0, 0, 0); - } - ~PollKQ() {Stop();} - -protected: - - void Begin(XrdSysSemaphore *syncp, int &rc, const char **eMsg); - - void Exclude(Channel *cP, bool &isLocked, bool dover=1); - - bool Include(Channel *cP, int &eNum, const char **eTxt, bool &isLocked); - - bool Modify (Channel *cP, int &eNum, const char **eTxt, bool &isLocked); - - void Shutdown(); - -private: - int AllocPT(int slots); - void Dispatch(Channel *cP, int i); - bool Process(int next); - -struct kevent *pollTab; -struct kevent armPipe; - int cbNext; - int pollDfd; - int pollMax; - Atomic(int) pollNum; - int numPoll; -static void *deadChP; - -static const int rEnabled = 1; -static const int rFilterX = 2; -static const int wEnabled = 4; -static const int wFilterX = 8; -}; - void *PollKQ::deadChP = 0; -}; -}; - -/******************************************************************************/ -/* C l a s s P o l l e r */ -/******************************************************************************/ -/******************************************************************************/ -/* Static: n e w P o l l e r */ -/******************************************************************************/ - -XrdSys::IOEvents::Poller * -XrdSys::IOEvents::Poller::newPoller(int pipeFD[2], - int &eNum, - const char **eTxt) - -{ - static const int allocFD = 1024; - struct kevent *pp, chlist; - int pfd; - -// Open the kqueue -// - if ((pfd = kqueue()) < 0) - {eNum = errno; - if (eTxt) *eTxt = "creating kqueue"; - return 0; - } - -// Add the request side of the pipe fd to the poll set (always fd[0]) -// - EV_SET(&chlist,pipeFD[0],EVFILT_READ,EV_ADD|EV_ONESHOT|EV_ENABLE,0,0,0); - if (kevent(pfd, &chlist, 1, 0, 0, 0) < 0) - { eNum = errno; - *eTxt = "adding communication pipe"; - return 0; - } - -// Allocate the event table -// - if ((eNum = XrdSys::IOEvents::PollKQ::AllocMem((void **)&pp, allocFD))) - {eNum = ENOMEM; - if (eTxt) *eTxt = "creating kqueue table"; - close(pfd); - return 0; - } - -// Create new poll object -// - return (Poller *)new PollKQ(pp, allocFD, pfd, pipeFD); -} - -/******************************************************************************/ -/* C l a s s P o l l E */ -/******************************************************************************/ -/******************************************************************************/ -/* A l l o c M e m */ -/******************************************************************************/ - -int XrdSys::IOEvents::PollKQ::AllocMem(void **memP, int slots) -{ - int rc, bytes, alignment, pagsz = getpagesize(); - -// Calculate the size of the poll table and allocate it -// - bytes = slots * sizeof(struct kevent); - alignment = (bytes < pagsz ? 1024 : pagsz); - if (!(rc = posix_memalign(memP, alignment, bytes))) memset(*memP, 0, bytes); - return rc; -} - -/******************************************************************************/ -/* Private: A l l o c P T */ -/******************************************************************************/ - -int XrdSys::IOEvents::PollKQ::AllocPT(int slots) -{ - struct kevent *pp; - -// Calclulate new slots -// - if (pollMax >= slots) slots = pollMax + 256; - else slots = pollMax + (slots/256*256) + (slots%256 ? 256 : 0); - -// Allocate a new table and if successful, replace the old one -// - if (!AllocMem((void **)&pp, slots)) - {free(pollTab); - pollTab = pp; - pollMax = slots; - } - -// All done -// - return 0; -} - -/******************************************************************************/ -/* Protected: B e g i n */ -/******************************************************************************/ - -void XrdSys::IOEvents::PollKQ::Begin(XrdSysSemaphore *syncsem, - int &retcode, - const char **eTxt) -{ - struct timespec *tmP, tmOut; - Channel *cP; - long long tmVal; - int numpolled, pollN; - -// Indicate to the starting thread that all went well -// - retcode = 0; - *eTxt = 0; - syncsem->Post(); - tmOut.tv_nsec = 0; - -// Now start dispatching channels that are ready. We use the wakePend flag to -// keep the chatter down when we actually wakeup. -// - do {if ((tmVal = TmoGet()) < 0) tmP = 0; - else {tmOut.tv_sec = tmVal / 1000; tmP = &tmOut;} - do {numpolled = kevent(pollDfd, 0, 0, pollTab, pollMax, tmP);} - while (numpolled < 0 && errno == EINTR); - wakePend = true; numPoll = numpolled; - if (numpolled == 0) CbkTMO(); - else if (numpolled < 0) - {int rc = errno; - cerr <<"KQ: " <GetFD(), kqStatus = GetPollEnt(cP); - -// Setup the removal elements. -// may have been closed prior to this call (though this shouldn't happen). -// - if (kqStatus & rFilterX) - {EV_SET(&chlist[i], theFD, EVFILT_READ, EV_DELETE, 0, 0, cP);} - if (kqStatus & wFilterX) - {EV_SET(&chlist[i], theFD, EVFILT_WRITE, EV_DELETE, 0, 0, cP);} - -// Remove this channel from the poll set. We ignore errors as the descriptor -// may have been closed prior to this call (though this shouldn't happen). -// - if (i) kevent(pollDfd, chlist, i, 0, 0, 0); - SetPollEnt(cP, 0); - AtomicDec(pollNum); - -// If we need to verify this action, sync with the poller thread (note that the -// poller thread will not ask for this action unless it wants to deadlock). We -// may actually deadlock anyway if the channel lock is held. We are allowed to -// release it if the caller locked it. This will prevent a deadlock. Otherwise, -// if we are in a callback and this channel is not the one that initiated the -// exclude then we must make sure that we cancel any pending callback to the -// excluded channel as it may have been deleted and we won't know that here. -// - if (dover) - {PipeData cmdbuff; - if (isLocked) - {isLocked = false; - UnLockChannel(cP); - } - cmdbuff.req = PipeData::RmFD; - cmdbuff.fd = theFD; - SendCmd(cmdbuff); - } else { - if (cbNext) - for (int i = cbNext; i < numPoll; i++) - {if (cP == (Channel *)pollTab[i].udata) - pollTab[i].udata = &deadChP; - } - } -} - -/******************************************************************************/ -/* Protected: I n c l u d e */ -/******************************************************************************/ - -bool XrdSys::IOEvents::PollKQ::Include(XrdSys::IOEvents::Channel *cP, - int &eNum, - const char **eTxt, - bool &isLocked) -{ - -// We simply call modify as this will add events to the kqueue as needed -// - if (!Modify(cP, eNum, eTxt, isLocked)) - {if (eTxt) *eTxt = "adding channel"; - return false; - } - -// All went well. Bump the number in the set. The poller thread will -// reallocate the poll table if need be. -// - AtomicInc(pollNum); - return true; -} - -/******************************************************************************/ -/* Protected: M o d i f y */ -/******************************************************************************/ - -bool XrdSys::IOEvents::PollKQ::Modify(XrdSys::IOEvents::Channel *cP, - int &eNum, - const char **eTxt, - bool &isLocked) -{ - struct kevent chlist[2]; - int i = 0; - int events = cP->GetEvents(), theFD = cP->GetFD(); - int kqStatus = GetPollEnt(cP); - -// Establish new read event mask -// - if (events & Channel:: readEvents) - {if (!(kqStatus & rEnabled)) - {EV_SET(&chlist[i], theFD, EVFILT_READ, EV_ADD|EV_ENABLE, 0, 0, cP); - kqStatus |= rEnabled | rFilterX; - i++; - } - } else { - if (kqStatus & rEnabled) - {EV_SET(&chlist[i], theFD, EVFILT_READ, EV_DISABLE, 0, 0, cP); - kqStatus &= ~rEnabled; - i++; - } - } - -// Establish new write event mask -// - if (events & Channel::writeEvents) - {if (!(kqStatus & wEnabled)) - {EV_SET(&chlist[i], theFD, EVFILT_WRITE, EV_ADD|EV_ENABLE, 0, 0, cP); - kqStatus |= wEnabled | wFilterX; - i++; - } - } else { - if (kqStatus & wEnabled) - {EV_SET(&chlist[i], theFD, EVFILT_WRITE, EV_DISABLE, 0, 0, cP); - kqStatus &= ~wEnabled; - i++; - } - } - -// Modify this fd if anything changed -// - if (i) - {if (kevent(pollDfd, chlist, i, 0, 0, 0) < 0) - {eNum = errno; - if (eTxt) *eTxt = "modifying poll events"; - return false; - } - SetPollEnt(cP, kqStatus); - } - -// All done -// - return true; -} - -/******************************************************************************/ -/* Private: P r o c e s s */ -/******************************************************************************/ - -bool XrdSys::IOEvents::PollKQ::Process(int next) -{ - -// Get the pipe request and check out actions of interest. -// - if (GetRequest()) - { if (reqBuff.req == PipeData::RmFD) - {Channel *cP; - for (int i = next; i < numPoll; i++) - {if ((cP = (Channel *)pollTab[i].udata) - && cP != (XrdSys::IOEvents::Channel *)&deadChP - && reqBuff.fd == (int)pollTab[i].ident) - pollTab[i].udata = &deadChP; - } - reqBuff.theSem->Post(); - } - else if (reqBuff.req == PipeData::Stop){reqBuff.theSem->Post(); - return false; - } - } - -// Renable the pipe as kqueue essentially disables it once we do a read-out -// - kevent(pollDfd, &armPipe, 1, 0, 0, 0); - -// All done -// - return true; -} - -/******************************************************************************/ -/* Protected: S h u t d o w n */ -/******************************************************************************/ - -void XrdSys::IOEvents::PollKQ::Shutdown() -{ - static XrdSysMutex shutMutex; - -// To avoid race conditions, we serialize this code -// - shutMutex.Lock(); - -// Release the poll table -// - if (pollTab) {free(pollTab); pollTab = 0;} - -// Close the kqueue file descriptor -// - if (pollDfd >= 0) {close(pollDfd); pollDfd = -1;} - -// All done -// - shutMutex.UnLock(); -} diff --git a/src/XrdSys/XrdSysIOEventsPollPoll.icc b/src/XrdSys/XrdSysIOEventsPollPoll.icc deleted file mode 100644 index 94158effd86..00000000000 --- a/src/XrdSys/XrdSysIOEventsPollPoll.icc +++ /dev/null @@ -1,528 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d P o l l P o l l . i c c */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#if !defined(__APPLE__) && !defined(__FreeBSD__) -#include -#endif -#include -#include -#include - -#include "XrdSys/XrdSysError.hh" -#include "Xrd/XrdLink.hh" -#include "Xrd/XrdPollPoll.hh" -#include "Xrd/XrdScheduler.hh" - - -using namespace std; - -/******************************************************************************/ -/* C l a s s P o l l P o l l */ -/******************************************************************************/ - -namespace XrdSys -{ -namespace IOEvents -{ -class PollPoll : public Poller -{ -public: - - PollPoll(int &rc, int numfd, int pFD[2]); - ~PollPoll() {Stop();} - -protected: - - void Begin(XrdSysSemaphore *syncp, int &rc, const char **eMsg); - - void Exclude(Channel *cP, bool &isLocked, bool dover=1); - - bool Include(Channel *cP, int &eNum, const char **eTxt, bool &isLocked); - - bool Modify (Channel *cP, int &eNum, const char **eTxt, bool &isLocked); - - void Shutdown(); - -private: - void Dispatch(int ptent, int pollEv); - void FDMod(int ptnum, int fd, int events); - void FDRem(int ptnum); - bool Process(); - -static const int disFD = 0x80000000; - -XrdSysRecMutex pollMutex; -struct pollfd *pollTab; - int pollMax; - int pollNum; -struct pollfd *pnewTab; - Channel **chnlTab; - int chnlMax; - int chnlNum; -}; -}; -}; - -/******************************************************************************/ -/* C l a s s P o l l e r */ -/******************************************************************************/ -/******************************************************************************/ -/* Static: n e w P o l l e r */ -/******************************************************************************/ - -XrdSys::IOEvents::Poller * -XrdSys::IOEvents::Poller::newPoller(int pipeFD[2], - int &eNum, - const char **eTxt) - -{ - PollPoll *myPoller; - -// Allocate new poller -// - if (!(myPoller = new PollPoll(eNum, 1024, pipeFD))) eNum = ENOMEM; - -// Check if all went ell -// - if (!eNum) return (Poller *)myPoller; - delete myPoller; - if (eTxt) *eTxt = "creating poller"; - return 0; -} - -/******************************************************************************/ -/* C l a s s P o l l P o l l */ -/******************************************************************************/ -/******************************************************************************/ -/* C o n s t r c u t o r */ -/******************************************************************************/ - -XrdSys::IOEvents::PollPoll::PollPoll(int &rc, int numfd, int pFD[2]) - : Poller(pFD[0], pFD[1]) -{ - int i; - -// Allocate initial poll table -// - if (!(pollTab = (struct pollfd *)malloc(numfd*sizeof(struct pollfd)))) - {rc = errno; return;} - -// Initialize it -// - for (i = 1; i < numfd; i++) - {pollTab[i].fd = -1; pollTab[i].events = 0; pollTab[i].revents = 0;} - -// The first element of the poll tab is the communications pipe -// - pollTab[0].fd = pFD[0]; - pollTab[0].events = POLLIN | POLLRDNORM; - pollTab[0].revents = 0; - -// Initialize remaining poll data -// - pollNum = 1; - pollMax = numfd; - pnewTab = 0; - -// Allocate initial channel table -// - if (!(chnlTab = (Channel **)malloc(numfd*sizeof(Channel *)))) - {rc = errno; return;} - -// Initialize it -// - memset(chnlTab, 0, numfd*sizeof(Channel *)); - chnlMax = numfd; - chnlNum = 1; - -// All done -// - rc = 0; -} - -/******************************************************************************/ -/* Protected: B e g i n */ -/******************************************************************************/ - -void XrdSys::IOEvents::PollPoll::Begin(XrdSysSemaphore *syncsem, - int &retcode, - const char **eTxt) -{ - int i, num2poll, numpolled; - -// Indicate to the starting thread that all went well -// - retcode = 0; - *eTxt = 0; - syncsem->Post(); - -// Now start dispatching channels that are ready. We use the wakePend flag to -// keep the chatter down when we actually wakeup. -// - pollMutex.Lock(); - do {num2poll = pollNum; - pollMutex.UnLock(); - do {numpolled = poll(pollTab, num2poll, TmoGet());} - while(numpolled < 0 && (errno == EAGAIN || errno == EINTR)); - pollMutex.Lock(); - wakePend = true; - - if (pnewTab) - {memcpy(pnewTab, pollTab, pollMax*sizeof(struct pollfd)); - free(pollTab); pollTab = pnewTab; pnewTab = 0; pollMax = chnlMax; - } - - if (numpolled == 0) CbkTMO(); - else if (numpolled < 0) - {int rc = errno; - cerr <<"EPoll: " <GetFD()); - if (isLocked) {isLocked = false; UnLockChannel(cP);} - SendCmd(cmdbuff); - } -} - -/******************************************************************************/ -/* Private: F D M o d */ -/******************************************************************************/ - -// pollMutex must be locked -// -void XrdSys::IOEvents::PollPoll::FDMod(int ptnum, int fd, int events) -{ - XrdSysMutexHelper mHelper(pollMutex); - -// First step is to see if we need to swap to a new poll table -// - if (pnewTab) - {memcpy(pnewTab, pollTab, pollMax*sizeof(struct pollfd)); - free(pollTab); - pollTab = pnewTab; pnewTab = 0; pollMax = chnlMax; - } - - -// Initialize poll table entry -// - pollTab[ptnum].fd = fd; - pollTab[ptnum].events = 0; - pollTab[ptnum].revents = 0; - if (events & Channel:: readEvents) - pollTab[ptnum].events = POLLIN | POLLRDNORM; - if (events & Channel::writeEvents) - pollTab[ptnum].events |= POLLOUT; - if (!pollTab[ptnum].events && !(events & Channel::errorEvents)) - pollTab[ptnum].fd |= disFD; - -// Reset poll marker, as needed -// - if (chnlNum >= pollNum) pollNum = chnlNum+1; -} - -/******************************************************************************/ -/* Private: F D R e m */ -/******************************************************************************/ - -// pollMutex must be locked -// -void XrdSys::IOEvents::PollPoll::FDRem(int ptnum) -{ - int ctnum = ptnum; - -// Free up the channel -// - chnlTab[ctnum] = 0; - -// See if we need to adjust the channel count -// - if (ctnum == chnlNum-1) - {while(ctnum > 0 && !chnlTab[ctnum]) ctnum--; - chnlNum = ctnum+1; - } - -// Free up this entry -// - pollTab[ptnum].fd = -1; - pollTab[ptnum].events = 0; - pollTab[ptnum].revents = 0; - -// Now see if we need to adjust our poll count -// - if (ptnum == pollNum-1) - {while(ptnum > 0 && pollTab[ptnum].fd == -1) ptnum--; - pollNum = ptnum+1; - } -} - -/******************************************************************************/ -/* I n c l u d e */ -/******************************************************************************/ - -bool XrdSys::IOEvents::PollPoll::Include(XrdSys::IOEvents::Channel *cP, - int &eNum, - const char **eTxt, - bool &isLocked) -{ - static const int incVal = 256; - static const int cpSz = sizeof(Channel *); - static const int ptSz = sizeof(struct pollfd); - int fd, ctnum; - -// Validate the file descriptor -// - fd = cP->GetFD(); - if (fd & 0xffff0000) - {eNum = EBADF; - if (eTxt) *eTxt = "adding channel"; - return false; - } - -// Make sure this channel is not already assigned to this poller -// - if (GetPollEnt(cP)) - {eNum = EEXIST; - if (eTxt) *eTxt = "adding channel"; - return false; - } - -// Get the next channel table entry to be used -// - pollMutex.Lock(); - ctnum = 1; - while((ctnum < chnlMax) && (chnlTab[ctnum] != 0)) ctnum++; - -// Reallocate channel table if we don't have enough space. We also pre-allocate -// a new poll table so that we can reflect failure to the caller as the poller -// can't do that. The poller will swap the new one for the old one. -// - if (ctnum >= chnlMax) - {Channel **cnewTab = (Channel **)realloc(chnlTab,(chnlMax+incVal)*cpSz); - if (pnewTab) free(pnewTab); - pnewTab = (struct pollfd *)malloc((chnlMax+incVal)*ptSz); - if (!cnewTab || !pnewTab) - {pollMutex.UnLock(); - eNum = ENOMEM; - if (eTxt) *eTxt = "adding channel"; - if (cnewTab) free(cnewTab); - if (pnewTab) free(pnewTab); - return false; - } - memset(&cnewTab[ctnum], 0, incVal*cpSz); - memset(&pnewTab[ctnum],-1, incVal*ptSz); - chnlTab = cnewTab; chnlMax += incVal; chnlNum = ctnum+1; - } else if (ctnum > chnlNum) chnlNum = ctnum; - -// Record the poll table entry in the channel -// - chnlTab[ctnum] = cP; - SetPollEnt(cP, ctnum); - pollMutex.UnLock(); - -// If we are the poller thread, then enable the poll entry in-line. Note that -// we will still be holding the poll mutex because the caller also has it. -// Otherwise, send a message to the poller to do this. We will need to release -// the channel lock to prevent deadlocks. The caller will relock as needed. -// - if (ISPOLLER) - {FDMod(ctnum, fd, cP->GetEvents()); - return true; - } else { - PipeData cmdbuff((char)PipeData::MiFD, (char)cP->GetEvents(), - (short)ctnum, fd, 0); - if (isLocked) {isLocked = false; UnLockChannel(cP);} - SendCmd(cmdbuff); - } - -// All done -// - return true; -} - -/******************************************************************************/ -/* Protected: M o d i f y */ -/******************************************************************************/ - -bool XrdSys::IOEvents::PollPoll::Modify(XrdSys::IOEvents::Channel *cP, - int &eNum, - const char **eTxt, - bool &isLocked) -{ - -// If we are the poller thread, then modify the poll entry in-line. Otherwise, -// send a modification message to the poller. This requires that we unlock the -// channel to prevent any deadlocks. The caller will relock it as needed. -// - if (ISPOLLER) - {FDMod(GetPollEnt(cP), cP->GetFD(), cP->GetEvents()); - return true; - } else { - PipeData cmdbuff((char)PipeData::MdFD, (char)cP->GetEvents(), - (short)GetPollEnt(cP), cP->GetFD(), 0); - if (isLocked) {isLocked = false; UnLockChannel(cP);} - SendCmd(cmdbuff); - } - -// All done -// - return true; -} - -/******************************************************************************/ -/* Private: P r o c e s s */ -/******************************************************************************/ - -bool XrdSys::IOEvents::PollPoll::Process() -{ -// Get the pipe request and check out actions of interest. -// - while(GetRequest()) - {switch(reqBuff.req) - {case PipeData::MdFD: FDMod(reqBuff.ent, reqBuff.fd, reqBuff.evt); - break; - case PipeData::MiFD: FDMod(reqBuff.ent, reqBuff.fd, reqBuff.evt); - reqBuff.theSem->Post(); - break; - case PipeData::RmFD: FDRem(reqBuff.ent); - reqBuff.theSem->Post(); - break; - case PipeData::NoOp: break; - case PipeData::Post: reqBuff.theSem->Post(); - break; - case PipeData::Stop: reqBuff.theSem->Post(); - return false; - break; - default: break; - } - } - -// Return true -// - return true; -} - -/******************************************************************************/ -/* Protected: S h u t d o w n */ -/******************************************************************************/ - -void XrdSys::IOEvents::PollPoll::Shutdown() -{ - static XrdSysMutex shutMutex; - -// To avoid race conditions, we serialize this code -// - shutMutex.Lock(); - -// Release the appendages -// - if (pollTab) {free(pollTab); pollTab = 0;} - if (pnewTab) {free(pnewTab); pnewTab = 0;} - if (chnlTab) {free(chnlTab); chnlTab = 0;} - -// All done -// - shutMutex.UnLock(); -} diff --git a/src/XrdSys/XrdSysIOEventsPollPort.icc b/src/XrdSys/XrdSysIOEventsPollPort.icc deleted file mode 100644 index 80e631401de..00000000000 --- a/src/XrdSys/XrdSysIOEventsPollPort.icc +++ /dev/null @@ -1,394 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S y s I O E v e n t s P o l l E . i c c */ -/* */ -/* (c) 2012 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#ifndef __APPLE__ -#include -#endif -#include -#include -#include -#include - -using namespace std; - -/******************************************************************************/ -/* C l a s s P o l l P o r t */ -/******************************************************************************/ - -namespace XrdSys -{ -namespace IOEvents -{ -class PollPort : public Poller -{ -public: - -static int AllocMem(void **memP, int slots); - - PollPort(port_event_t *ptab, int numfd, int pfd, int pFD[2]) - : Poller(pFD[0], pFD[1]), pollTab(ptab), - pollDfd(pfd), pollMax(numfd) - {} - ~PollPort() {Stop();} - -static const int pollER = POLLERR| POLLHUP; -static const int pollOK = POLLIN | POLLRDNORM | POLLPRI | POLLOUT; -static const int pollRD = POLLIN | POLLRDNORM | POLLPRI; -static const int pollWR = POLLOUT; - -protected: - - void Begin(XrdSysSemaphore *syncp, int &rc, const char **eMsg); - -timespec_t *BegTO(timespec_t &theTO) - {int toval = TmoGet(); - if (toval < 0) return 0; - theTO.tv_sec = toval/1000; - theTO.tv_nsec= 0; - return &theTO; - } - - void Exclude(Channel *cP, bool &isLocked, bool dover=1); - - bool Include(Channel *cP, int &eNum, const char **eTxt, bool &isLocked); - - bool Modify (Channel *cP, int &eNum, const char **eTxt, bool &isLocked); - - void Shutdown(); - -private: - - void Dispatch(Channel *cP, int pollEv); - bool Process(int curr); - - port_event_t *pollTab; - Channel *cbNow; - int cbCurr; - int pollDfd; - int pollMax; - unsigned int numPoll; -static void *deadChP; -}; - void *PollPort::deadChP = 0; -}; -}; - -/******************************************************************************/ -/* C l a s s P o l l e r */ -/******************************************************************************/ -/******************************************************************************/ -/* Static: n e w P o l l e r */ -/******************************************************************************/ - -XrdSys::IOEvents::Poller * -XrdSys::IOEvents::Poller::newPoller(int pipeFD[2], - int &eNum, - const char **eTxt) - -{ - static const int allocFD = 170; - port_event_t *pp; - int pfd; - -// reate an event driver -// - if ((pfd = port_create()) < 0) - {eNum = errno; - if (eTxt) *eTxt = "creating event port"; - return 0; - } - fcntl(pfd, F_SETFD, FD_CLOEXEC); - -// Add the request side of the pipe fd to the poll set (always fd[0]) -// - if (port_associate(pfd, PORT_SOURCE_FD, pipeFD[0], PollPort::pollRD, 0)) - { eNum = errno; - *eTxt = "adding communication pipe"; - return 0; - } - -// Allocate the event table -// - if ((eNum = XrdSys::IOEvents::PollPort::AllocMem((void **)&pp, allocFD))) - {if (eTxt) *eTxt = "creating port event table"; - close(pfd); - return 0; - } - -// Create new poll object -// - return (Poller *)new PollPort(pp, allocFD, pfd, pipeFD); -} - -/******************************************************************************/ -/* C l a s s P o l l P o r t */ -/******************************************************************************/ -/******************************************************************************/ -/* A l l o c M e m */ -/******************************************************************************/ - -int XrdSys::IOEvents::PollPort::AllocMem(void **memP, int slots) -{ - int bytes, alignment, pagsz = getpagesize(); - -// Calculate the size of the poll table and allocate it -// - bytes = slots * sizeof(port_event_t); - alignment = (bytes < pagsz ? 1024 : pagsz); - if (!(*memP = memalign(alignment, bytes))) return ENOMEM; - memset(*memP, 0, bytes); - return 0; -} - -/******************************************************************************/ -/* Protected: B e g i n */ -/******************************************************************************/ - -void XrdSys::IOEvents::PollPort::Begin(XrdSysSemaphore *syncsem, - int &retcode, - const char **eTxt) -{ - unsigned int numpolled; - int rc; - timespec_t toVal; - Channel *cP; - -// Indicate to the starting thread that all went well -// - retcode = 0; - *eTxt = 0; - syncsem->Post(); - -// Now start dispatching channels that are ready. We use the wakePend flag to -// keep the chatter down when we actually wakeup. There is also a "feature" of -// poll_getn() that can return an errno of zero upon a timeout, sigh. -// - do {numpolled = 1; errno = 0; - do {rc = port_getn(pollDfd, pollTab, pollMax, &numpolled, BegTO(toVal));} - while (rc < 0 && errno == EINTR); - wakePend = true; numPoll = numpolled; - if (rc) - {if (errno == ETIME || !errno) CbkTMO(); - else {int rc = errno; - cerr <<"PollP: " <GetFD()); - -// If we need to verify this action, sync with the poller thread (note that the -// poller thread will not ask for this action unless it wants to deadlock). We -// may actually deadlock anyway if the channel lock is held. We are allowed to -// release it if the caller locked it. This will prevent a deadlock. -// - if (dover) - {PipeData cmdbuff; - if (isLocked) - {isLocked = false; - UnLockChannel(cP); - } - cmdbuff.req = PipeData::RmFD; - cmdbuff.fd = cP->GetFD(); - SendCmd(cmdbuff); - } else { - if (cbNow && cbNow != cP) - for (int i = cbCurr+1; i < numPoll; i++) - {if (cP == (Channel *)pollTab[i].portev_user) - pollTab[i].portev_user = &deadChP; - } - } -} - -/******************************************************************************/ -/* Protected: I n c l u d e */ -/******************************************************************************/ - -bool XrdSys::IOEvents::PollPort::Include(XrdSys::IOEvents::Channel *cP, - int &eNum, - const char **eTxt, - bool &isLocked) -{ - int pEvents = 0, events = cP->GetEvents(); - -// Establish new event mask -// - if (events & Channel:: readEvents) pEvents = pollRD; - if (events & Channel::writeEvents) pEvents |= pollWR; - -// Add this fd to the poll set -// - if (port_associate(pollDfd, PORT_SOURCE_FD, cP->GetFD(), pEvents, cP)) - {eNum = errno; - if (eTxt) *eTxt = "adding channel"; - return false; - } - -// All went well. -// - return true; -} - -/******************************************************************************/ -/* Protected: M o d i f y */ -/******************************************************************************/ - -bool XrdSys::IOEvents::PollPort::Modify(XrdSys::IOEvents::Channel *cP, - int &eNum, - const char **eTxt, - bool &isLocked) -{ - int pEvents = 0, events = cP->GetEvents(); - -// Establish new event mask -// - if (events & Channel:: readEvents) pEvents = pollRD; - if (events & Channel::writeEvents) pEvents |= pollWR; - -// Associate the fd to the poll set -// - if (port_associate(pollDfd, PORT_SOURCE_FD, cP->GetFD(), pEvents, cP)) - {eNum = errno; - if (eTxt) *eTxt = "modifying poll events"; - return false; - } - -// All done -// - return true; -} - -/******************************************************************************/ -/* Private: P r o c e s s */ -/******************************************************************************/ - -bool XrdSys::IOEvents::PollPort::Process(int curr) -{ -// Get the pipe request and check out actions of interest. -// - if (GetRequest()) - { if (reqBuff.req == PipeData::RmFD) - {Channel *cP; - for (int i = curr+1; i < numPoll; i++) - {if (reqBuff.fd == (int)pollTab[i].portev_object) - pollTab[i].portev_user = &deadChP; - } - reqBuff.theSem->Post(); - } - else if (reqBuff.req == PipeData::Stop){reqBuff.theSem->Post(); - return false; - } - } - -// Associate the pipe again and return true -// - port_associate(pollDfd, PORT_SOURCE_FD, reqFD, pollRD, 0); - return true; -} - -/******************************************************************************/ -/* Protected: S h u t d o w n */ -/******************************************************************************/ - -void XrdSys::IOEvents::PollPort::Shutdown() -{ - static XrdSysMutex shutMutex; - -// To avoid race conditions, we serialize this code -// - shutMutex.Lock(); - -// Release the poll table -// - if (pollTab) {free(pollTab); pollTab = 0;} - -// Close the epoll file descriptor -// - if (pollDfd >= 0) {close(pollDfd); pollDfd = -1;} - -// All done -// - shutMutex.UnLock(); -} diff --git a/src/XrdSys/XrdSysLinuxSemaphore.hh b/src/XrdSys/XrdSysLinuxSemaphore.hh deleted file mode 100644 index f0f299b06cb..00000000000 --- a/src/XrdSys/XrdSysLinuxSemaphore.hh +++ /dev/null @@ -1,318 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2013 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#ifndef __XRD_SYS_LINUX_SEMAPHORE__ -#define __XRD_SYS_LINUX_SEMAPHORE__ - -#if defined(__linux__) && defined(HAVE_ATOMICS) - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace XrdSys -{ - //---------------------------------------------------------------------------- - //! Semaphore exception - //---------------------------------------------------------------------------- - class LinuxSemaphoreError: public std::exception - { - public: - LinuxSemaphoreError( const std::string &error ): pError( error ) {} - virtual ~LinuxSemaphoreError() throw() {}; - - virtual const char *what() const throw() - { - return pError.c_str(); - } - - private: - std::string pError; - }; - - //---------------------------------------------------------------------------- - //! A thread safe semaphore. - //! - //! You might have expected the built-it thread synchronization machanisms - //! to be thread safe, but, unfortunately, this is not the case on Linux. - //! - //! For more details see: - //! - //! https://sourceware.org/bugzilla/show_bug.cgi?id=12674 - //! https://bugzilla.redhat.com/show_bug.cgi?id=1027348 - //! - //! This class attepmts to implement a thread safe semaphore using - //! compiler intrinsics for atomic operations on integers and system futexes - //! for waking and puting thread to sleep. It stores both number of waiters - //! and the value of the semaphore in one variable that is modified atomically. - //! It solves the races at the cost of limiting the maximal value storable in - //! the semaphore to 20 bits and the possible number of threads waiting for - //! the value to change to 12 bits. - //---------------------------------------------------------------------------- - class LinuxSemaphore - { - public: - //------------------------------------------------------------------------ - //! Try to acquire the semaphore without waiting - //! - //! @return 1 on success, 0 otherwise - //------------------------------------------------------------------------ - inline int CondWait() - { - int value = 0; - int val = 0; - int waiters = 0; - int newVal = 0; - - //---------------------------------------------------------------------- - // We get the value of the semaphore try to atomically decrement it if - // it's larger than 0. - //---------------------------------------------------------------------- - while( 1 ) - { - Unpack( pValue, value, val, waiters ); - if( val == 0 ) - return 0; - newVal = Pack( --val, waiters ); - if( __sync_bool_compare_and_swap( pValue, value, newVal ) ) - return 1; - } - } - - //------------------------------------------------------------------------ - //! Acquire the semaphore, wait for it to be risen, if necessary. - //! - //! @throw XrdSys::LinuxSemaphoreError in case of syscall errors or - //! exceeding maximal value or number of waiters - //------------------------------------------------------------------------ - inline void Wait() - { - //---------------------------------------------------------------------- - // Examine the state of the semaphore and atomically decrement it if - // possible. If CondWait fails, it means that the semaphore value was 0. - // In this case we atomically bump the number of waiters and go to sleep - //---------------------------------------------------------------------- - while( !CondWait() ) - { - int value = 0; - int val = 0; - int waiters = 0; - int cancelType = 0; - - Unpack( pValue, value, val, waiters ); - - //-------------------------------------------------------------------- - // We need to make sure again that the value of the semaphore is 0 - // because we fetched it again (first time was in CondWait()) and - // it may have changed in the mean time. - //-------------------------------------------------------------------- - if( val != 0 ) - continue; - - if( waiters == WaitersMask ) - throw LinuxSemaphoreError( "Reached maximum number of waiters" ); - - int newVal = Pack( val, ++waiters ); - - //-------------------------------------------------------------------- - // We have bumped the number of waiters successfuly if neither the - // semaphore value nor the number of waiters changed in the mean time. - // We can safely go to sleep. - // - // Once the number of waiters was bumped we cannot get cancelled - // without decrementing it. - //-------------------------------------------------------------------- - pthread_setcanceltype( PTHREAD_CANCEL_DEFERRED, &cancelType ); - if( __sync_bool_compare_and_swap( pValue, value, newVal ) ) - { - while( 1 ) - { - int r = 0; - - pthread_cleanup_push( Cleanup, pValue ); - pthread_setcanceltype( PTHREAD_CANCEL_ASYNCHRONOUS, 0 ); - - r = syscall( SYS_futex, pValue, FUTEX_WAIT, newVal, 0, 0, 0 ); - - pthread_setcanceltype( PTHREAD_CANCEL_DEFERRED, 0 ); - pthread_cleanup_pop( 0 ); - - if( r == 0 ) // we've been woken up - break; - - if( errno == EINTR ) // interrupt - continue; - - if( errno == EWOULDBLOCK ) // futex value changed - break; - - throw LinuxSemaphoreError( "FUTEX_WAIT syscall error" ); - } - - //------------------------------------------------------------------ - // We have been woken up, so we need to decrement the number of - // waiters - //------------------------------------------------------------------ - do - { - Unpack( pValue, value, val, waiters ); - newVal = Pack( val, --waiters ); - } - while( !__sync_bool_compare_and_swap( pValue, value, newVal ) ); - } - - //-------------------------------------------------------------------- - // We are here if: - // 1) we were unable to increase the number of waiters bacause the - // atomic changed in the mean time in another execution thread - // 2) *pValue != newVal upon futex call, this indicates the state - // change in another thread - // 3) we have been woken up by another thread - // - // In either of the above cases we need to re-examine the atomic and - // decide whether we need to sleep or are free to proceed - //-------------------------------------------------------------------- - pthread_setcanceltype( cancelType, 0 ); - } - } - - //------------------------------------------------------------------------ - //! Unlock the semaphore - //! - //! @throw XrdSys::LinuxSemaphoreError in case of exceeding maximum - //! semaphore value - //------------------------------------------------------------------------ - inline void Post() - { - int value = 0; - int val = 0; - int waiters = 0; - int newVal = 0; - - //---------------------------------------------------------------------- - // We atomically increment the value of the semaphore and wake one of - // the threads that was waiting for the semaphore value to change - //---------------------------------------------------------------------- - while( 1 ) - { - Unpack( pValue, value, val, waiters ); - - if( val == ValueMask ) - throw LinuxSemaphoreError( "Reached maximum value" ); - - newVal = Pack( ++val, waiters ); - if( __sync_bool_compare_and_swap( pValue, value, newVal ) ) - { - if( waiters ) - syscall( SYS_futex, pValue, FUTEX_WAKE, 1, 0, 0, 0 ); - return; - } - } - } - - //------------------------------------------------------------------------ - //! Get the semaphore value - //------------------------------------------------------------------------ - int GetValue() const - { - int value = __sync_fetch_and_add( pValue, 0 ); - return value & ValueMask; - } - - //------------------------------------------------------------------------ - //! Construct the semaphore - //! - //! @param value the initial value - //------------------------------------------------------------------------ - LinuxSemaphore( int value ) - { - pValue = (int *)malloc(sizeof(int)); - *pValue = (value & ValueMask); - } - - //------------------------------------------------------------------------ - //! Destructor - //------------------------------------------------------------------------ - ~LinuxSemaphore() - { - free( pValue ); - } - - private: - static const int ValueMask = 0x000fffff; - static const int WaitersOffset = 20; - static const int WaitersMask = 0x00000fff; - - //------------------------------------------------------------------------ - // Unpack the semaphore value - //------------------------------------------------------------------------ - static inline void Unpack( int *sourcePtr, - int &source, - int &value, - int &nwaiters ) - { - source = __sync_fetch_and_add( sourcePtr, 0 ); - value = source & ValueMask; - nwaiters = (source >> WaitersOffset) & WaitersMask; - } - - //------------------------------------------------------------------------ - // Pack the semaphore value - //------------------------------------------------------------------------ - static inline int Pack( int value, int nwaiters ) - { - return (nwaiters << WaitersOffset) | (value & ValueMask); - } - - //------------------------------------------------------------------------ - // Cancellation cleaner - //------------------------------------------------------------------------ - static void Cleanup( void *param ) - { - int *iParam = (int*)param; - int value = 0; - int val = 0; - int waiters = 0; - int newVal = 0; - - do - { - Unpack( iParam, value, val, waiters ); - newVal = Pack( val, --waiters ); - } - while( !__sync_bool_compare_and_swap( iParam, value, newVal ) ); - } - - int *pValue; - }; -}; - -#endif // __linux__ && HAVE_ATOMICS - -#endif // __XRD_SYS_LINUX_SEMAPHORE__ diff --git a/src/XrdSys/XrdSysLogPI.hh b/src/XrdSys/XrdSysLogPI.hh deleted file mode 100644 index d8d57d55ab0..00000000000 --- a/src/XrdSys/XrdSysLogPI.hh +++ /dev/null @@ -1,94 +0,0 @@ -#ifndef __SYS_LOGPI_H__ -#define __SYS_LOGPI_H__ -/******************************************************************************/ -/* */ -/* X r d S y s L o g P I . h h */ -/* */ -/*(c) 2016 by the Board of Trustees of the Leland Stanford, Jr., University */ -/*Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Deprtment of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -//----------------------------------------------------------------------------- -//! This header file defines the plugin interface used by the logging subsystem. -//! The following function is called for each message. A pointer to the -//! function is returned by XrdSysLogPInit(); see the definition below. The -//! log message function must be thread safe in synchronous mode as any number -//! of threads may call it at the same time. In async mode, only one thread -//! invokes the function for each message. -//! -//! @param mtime The time the message was generated. The time value is -//! zero when tID is zero (see below). -//! @param tID The thread ID that issued the message (Linux -> gettid()). -//! If tID is zero then the msg was captured from stderr. -//! @param msg Pointer to the null-terminaed message text. -//! @param mlen Length of the message text (exclusive of null byte). -//----------------------------------------------------------------------------- - -typedef void (*XrdSysLogPI_t)(struct timeval const &mtime, - unsigned long tID, - const char *msg, - int mlen); - -//----------------------------------------------------------------------------- -//! Initialize and return a pointer to the plugin. This function must reside in -//! the plugin shared library as an extern "C" function. The shared library is -//! library identified by the "-l @library" command line option. This function -//! is called only once during loging initialization. -//! -//! @param cfgfn -> Configuration filename (nil if none). -//! @param argv -> command line arguments after "-+xrdlog". -//! @param argc number of command line arguments in argv. -//! -//! @return Upon success a pointer of type XrdSysLogPI_t which is the function -//! that handles log messages (see above). Upon failure, a nil pointer -//! should be returned. A sample deinition is shown below. -//----------------------------------------------------------------------------- - -/*! - extern "C" XrdSysLogPI_t XrdSysLogPInit(const char *cfgn, - char **argv, - int argc) { . . . } -*/ - -typedef XrdSysLogPI_t (*XrdSysLogPInit_t)(const char *cfgn, - char **argv, - int argc); - -//------------------------------------------------------------------------------ -/*! Specify the compilation version. - - Additionally, you *should* declare the xrootd version you used to compile - your plug-in. The plugin manager automatically checks for compatability. - Declare it as follows: - - #include "XrdVersion.hh" - XrdVERSIONINFO(XrdSysLogPInit,); - - where is a 1- to 15-character unquoted name identifying your plugin. -*/ -//------------------------------------------------------------------------------ -#endif diff --git a/src/XrdSys/XrdSysLogger.cc b/src/XrdSys/XrdSysLogger.cc deleted file mode 100644 index 6f111766cc2..00000000000 --- a/src/XrdSys/XrdSysLogger.cc +++ /dev/null @@ -1,860 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S y s L o g g e r . c c */ -/* */ -/*(c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/*Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Deprtment of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifndef WIN32 -#include -#include -#include -#include -#include -#include -#endif // WIN32 - -#include "XrdOuc/XrdOucTList.hh" - -#include "XrdSys/XrdSysFD.hh" -#include "XrdSys/XrdSysLogger.hh" -#include "XrdSys/XrdSysLogging.hh" -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdSys/XrdSysPlatform.hh" -#include "XrdSys/XrdSysPthread.hh" -#include "XrdSys/XrdSysTimer.hh" -#include "XrdSys/XrdSysUtils.hh" - -/******************************************************************************/ -/* G l o b a l s */ -/******************************************************************************/ - -namespace -{ -XrdOucTListFIFO *tFifo = 0; - -void Snatch(struct iovec *iov, int iovnum) // Called with logger mutex locked! -{ - XrdOucTList *tlP; - char *tBuff, *tbP; - int tLen = 0; - -// Do not save the new line character at the end -// - if (iovnum && *((char *)iov[iovnum-1].iov_base) == '\n') iovnum--; - -// Calculate full length -// - for (int i = 0; i text = tBuff; - tFifo->Add(tlP); -} -} - -/******************************************************************************/ -/* L o c a l D e f i n e s */ -/******************************************************************************/ - -#define BLAB(x) cerr <<"Logger " <Ring(); tP = tP->Next();} - return (void *)0; - } - -struct XrdSysLoggerRP - {XrdSysLogger *logger; - XrdSysSemaphore active; - - XrdSysLoggerRP(XrdSysLogger *lp) : logger(lp), active(0) - {} - ~XrdSysLoggerRP() {} - }; - -void *XrdSysLoggerRT(void *carg) - {XrdSysLoggerRP *rP = (XrdSysLoggerRP *)carg; - XrdSysLogger *lp = rP->logger; - rP->active.Post(); - lp->zHandler(); - return (void *)0; - } - -/******************************************************************************/ -/* C o n s t r u c t o r */ -/******************************************************************************/ - -XrdSysLogger::XrdSysLogger(int ErrFD, int dorotate) -{ - char * logFN; - - ePath = 0; - eInt = 0; - eFD = ErrFD; - eKeep = 0; - doLFR = (dorotate != 0); - msgList = 0; - taskQ = 0; - lfhTID = 0; - hiRes = false; - fifoFN = 0; - reserved1 = 0; - -// Establish default log file name -// - if (!(logFN = getenv("XrdSysLOGFILE"))) logFN = getenv("XrdOucLOGFILE"); - -// Establish message routing -// - if (ErrFD != STDERR_FILENO) baseFD = ErrFD; - else {baseFD = XrdSysFD_Dup(ErrFD); - Bind(logFN, 1); - } -} - -/******************************************************************************/ -/* A d d M s g */ -/******************************************************************************/ - -void XrdSysLogger::AddMsg(const char *msg) -{ - mmMsg *tP, *nP = new mmMsg; - -// Fill out new message -// - nP->next = 0; - nP->msg = strdup(msg); - nP->mlen = strlen(msg); - -// Add new line character if one is missing (we steal the null byte for this) -// - if (nP->mlen > 1 && nP->msg[nP->mlen-1] != '\n') - {nP->msg[nP->mlen] = '\n'; nP->mlen += 1;} - -// Add this message to the end of the list -// - Logger_Mutex.Lock(); - if (!(tP = msgList)) msgList = nP; - else {while(tP->next) tP = tP->next; - tP->next = nP; - } - Logger_Mutex.UnLock(); -} - -/******************************************************************************/ -/* A t M i d n i g h t */ -/******************************************************************************/ - -void XrdSysLogger::AtMidnight(XrdSysLogger::Task *mnTask) -{ - -// Place this task on the task queue -// - Logger_Mutex.Lock(); - mnTask->next = taskQ; - taskQ = mnTask; - Logger_Mutex.UnLock(); -} - -/******************************************************************************/ -/* B i n d */ -/******************************************************************************/ - -int XrdSysLogger::Bind(const char *path, int lfh) -{ - XrdSysLoggerRP rtParms(this); - int rc; - -// Kill logfile handler thread if parameters will be changing -// - if (lfh > 0) lfh = 1; - if (lfhTID && (eInt != lfh || !path)) - {XrdSysThread::Kill(lfhTID); - lfhTID = 0; - } - -// Bind to stderr if no path specified -// - if (ePath) free(ePath); - eInt = 0; - ePath = 0; - if (fifoFN) free(fifoFN); - fifoFN = 0; doLFR = false; - if (!path) return 0; - -// Bind to a log file -// - eInt = lfh; - ePath = strdup(path); - doLFR = (lfh > 0); - if ((rc = ReBind(0))) return rc; - -// Lock the logs if XRootD is suppose to handle log rotation itself -// - rc = HandleLogRotateLock( doLFR ); - if( rc ) - return -rc; - -// Handle specifics of lofile rotation -// - if (eInt == onFifo) {if ((rc = FifoMake())) return -rc;} - else if (eInt < 0 && !XrdSysUtils::SigBlock(-eInt)) - {rc = errno; - BLAB("Unable to block logfile signal " <<-eInt <<"; " - < 0 ? -rc : rc); -} - -/******************************************************************************/ -/* C a p t u r e */ -/******************************************************************************/ - -void XrdSysLogger::Capture(XrdOucTListFIFO *tFIFO) -{ - -// Obtain the serailization mutex -// - Logger_Mutex.Lock(); - -// Set the base for capturing messages -// - tFifo = tFIFO; - -// Release the serailization mutex -// - Logger_Mutex.UnLock(); -} - -/******************************************************************************/ -/* P a r s e K e e p */ -/******************************************************************************/ - -int XrdSysLogger::ParseKeep(const char *arg) -{ - char *eP; - -// First check to see if this is a sig type -// - eKeep = 0; - if (isalpha(*arg)) - {if (!strcmp(arg, "fifo")) return onFifo; - return -XrdSysUtils::GetSigNum(arg); - } - -// Process an actual keep count -// - eKeep = strtoll(arg, &eP, 10); - if (!(*eP) || eKeep < 0) {eKeep = -eKeep; return 1;} - -// Process an actual keep size -// - if (*(eP+1)) return 0; - if (*eP == 'k' || *eP == 'K') eKeep *= 1024LL; - else if (*eP == 'm' || *eP == 'M') eKeep *= 1024LL*1024LL; - else if (*eP == 'g' || *eP == 'G') eKeep *= 1024LL*1024LL*1024LL; - else if (*eP == 't' || *eP == 'T') eKeep *= 1024LL*1024LL*1024LL*1024LL; - else return 0; - -// All done -// - return 1; -} - -/******************************************************************************/ -/* P u t */ -/******************************************************************************/ - -void XrdSysLogger::Put(int iovcnt, struct iovec *iov) -{ - struct timeval tVal; - unsigned long tID = XrdSysThread::Num(); - int retc; - char tbuff[32]; - -// Get current time -// - gettimeofday(&tVal, 0); - -// Forward the message if there is a plugin involved here -// - if (doForward) - {bool xEnd; - if (iov[0].iov_base) xEnd = XrdSysLogging::Forward(tVal,tID,iov,iovcnt); - else xEnd = XrdSysLogging::Forward(tVal, tID, &iov[1], iovcnt-1); - if (xEnd) return; - } - -// Prefix message with time if calle wants it so -// - if (!iov[0].iov_base) - {iov[0].iov_base = tbuff; - iov[0].iov_len = TimeStamp(tVal, tID, tbuff, sizeof(tbuff), hiRes); - } - -// Obtain the serailization mutex if need be -// - Logger_Mutex.Lock(); - -// If we are capturing messages, do so now -// - if (tFifo) - {Snatch(iov, iovcnt); - Logger_Mutex.UnLock(); - return; - } - -// In theory, writev may write out a partial list. This rarely happens in -// practice and so we ignore that possibility (recovery is pretty tough). -// - do { retc = writev(eFD, (const struct iovec *)iov, iovcnt);} - while (retc < 0 && errno == EINTR); - -// Release the serailization mutex if need be -// - Logger_Mutex.UnLock(); -} - -/******************************************************************************/ -/* Private: T i m e */ -/******************************************************************************/ - -int XrdSysLogger::Time(char *tbuff) -{ - struct timeval tVal; - const int minblen = 32; - struct tm tNow; - int i; - -// Get the current time -// - gettimeofday(&tVal, 0); - -// Format the time in human terms -// - localtime_r((const time_t *) &tVal.tv_sec, &tNow); - -// Choose appropriate output -// - if (hiRes) - {i = snprintf(tbuff, minblen, "%02d%02d%02d %02d:%02d:%02d.%06d %03ld ", - tNow.tm_year-100, tNow.tm_mon+1, tNow.tm_mday, - tNow.tm_hour, tNow.tm_min, tNow.tm_sec, - static_cast(tVal.tv_usec), XrdSysThread::Num()); - } else { - i = snprintf(tbuff, minblen, "%02d%02d%02d %02d:%02d:%02d %03ld ", - tNow.tm_year-100, tNow.tm_mon+1, tNow.tm_mday, - tNow.tm_hour, tNow.tm_min, tNow.tm_sec, - XrdSysThread::Num()); - } - return (i >= minblen ? minblen-1 : i); -} - -/******************************************************************************/ -/* Private: T i m e S t a m p */ -/******************************************************************************/ - -int XrdSysLogger::TimeStamp(struct timeval &tVal, unsigned long tID, - char *tbuff, int tbsz, bool hires) -{ - struct tm tNow; - int i; - -// Validate tbuff size -// - if (tbsz <= 0) return 0; - -// Format the time in human terms -// - localtime_r((const time_t *) &tVal.tv_sec, &tNow); - -// Choose appropriate output -// - if (hires) - {i = snprintf(tbuff, tbsz, "%02d%02d%02d %02d:%02d:%02d.%06d %03ld ", - tNow.tm_year-100, tNow.tm_mon+1, tNow.tm_mday, - tNow.tm_hour, tNow.tm_min, tNow.tm_sec, - static_cast(tVal.tv_usec), tID); - } else { - i = snprintf(tbuff, tbsz, "%02d%02d%02d %02d:%02d:%02d %03ld ", - tNow.tm_year-100, tNow.tm_mon+1, tNow.tm_mday, - tNow.tm_hour, tNow.tm_min, tNow.tm_sec, tID); - } - return (i >= tbsz ? tbsz-1 : i); -} - -/******************************************************************************/ -/* P r i v a t e M e t h o d s */ -/******************************************************************************/ -/******************************************************************************/ -/* F i f o M a k e */ -/******************************************************************************/ - -int XrdSysLogger::FifoMake() -{ - struct stat Stat; - char buff[2048], *slash; - int n, rc, saveInt = eInt; - -// Assume failure (just to keep down the code) -// - eInt = 0; - -// Construct the fifo name -// - if (!(slash = rindex(ePath, '/'))) - {*buff = '.'; - strcpy(buff+1, ePath); - } else { - n = slash - ePath + 1; - strncpy(buff, ePath, n); - buff[n] = '.'; - strcpy(&buff[n+1], slash+1); - } - -// Check if the fifo exists and is usable or that we can create it -// - if (!stat(buff, &Stat)) - { if (!S_ISFIFO(Stat.st_mode)) - {BLAB("Logfile fifo " <d_name, logFN, n)) continue; - strcpy(logSfx, dp->d_name); - if (stat(logDir, &buff) || !(buff.st_mode & S_IFREG)) continue; - - totNum++; totSz += buff.st_size; - logEnt = new LogFile(dp->d_name, buff.st_size, buff.st_mtime); - logPrev = &logList; logNow = logList.next; - while(logNow && logNow->tm < buff.st_mtime) - {logPrev = logNow; logNow = logNow->next;} - - logPrev->next = logEnt; - logEnt->next = logNow; - } - -// Check if we received an error -// - rc = errno; closedir(DFD); - if (rc) - {int msz = snprintf(eBuff, 2048, "Error %d (%s) reading log directory %s\n", - rc, strerror(rc), logDir); - putEmsg(eBuff, msz); - return; - } - -// If there is only one log file here no need to -// - if (totNum <= 1) return; - -// Check if we need to trim log files -// - if (eKeep < 0) - {if ((totNum += eKeep) <= 0) return; - } else { - if (totSz <= eKeep) return; - logNow = logList.next; totNum = 0; - while(logNow && totSz > eKeep) - {totNum++; totSz -= logNow->sz; logNow = logNow->next;} - } - -// Now start deleting log files -// - logNow = logList.next; - while(logNow && totNum--) - {strcpy(logSfx, logNow->fn); - if (unlink(logDir)) - rc = snprintf(eBuff, 2048, "Error %d (%s) removing log file %s\n", - errno, strerror(errno), logDir); - else rc = snprintf(eBuff, 2048, "Removed log file %s\n", logDir); - putEmsg(eBuff, rc); - logNow = logNow->next; - } -} -#else -void XrdSysLogger::Trim() -{ -} -#endif - -/******************************************************************************/ -/* z H a n d l e r */ -/******************************************************************************/ -#include - -void XrdSysLogger::zHandler() -{ - mmMsg *mP; - sigset_t sigset; - pthread_t tid; - int signo, rc; - Task *tP; - -// If we will be handling via signals, set it up now -// - if (eInt < 0 && !fifoFN) - {signo = -eInt; - if ((sigemptyset(&sigset) == -1) - || (sigaddset(&sigset,signo) == -1)) - {rc = errno; - BLAB("Unable to use logfile signal " <= 0) XrdSysTimer::Wait4Midnight(); - else if ((sigwait(&sigset, &signo) == -1)) - {rc = errno; - BLAB("Unable to wait on logfile signal " <msg, mP->mlen); - mP = mP->next; - } - tP = taskQ; - Logger_Mutex.UnLock(); - - if (tP) - {if (XrdSysThread::Run(&tid, XrdSysLoggerMN, (void *)tP, 0, - "Midnight Ringer Task")) - {char eBuff[256]; - rc = sprintf(eBuff, "Error %d (%s) running ringer task.\n", - errno, strerror(errno)); - putEmsg(eBuff, rc); - } - } - } -} diff --git a/src/XrdSys/XrdSysLogger.hh b/src/XrdSys/XrdSysLogger.hh deleted file mode 100644 index 0a2b8a13d30..00000000000 --- a/src/XrdSys/XrdSysLogger.hh +++ /dev/null @@ -1,285 +0,0 @@ -#ifndef __SYS_LOGGER_H__ -#define __SYS_LOGGER_H__ -/******************************************************************************/ -/* */ -/* X r d S y s L o g g e r . h h */ -/* */ -/*(c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/*Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Deprtment of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#ifndef WIN32 -#include -#include -#include -#else -#include -#include -#include "XrdSys/XrdWin32.hh" -#endif - -#include "XrdSys/XrdSysPthread.hh" - -//----------------------------------------------------------------------------- -//! XrdSysLogger is the object that is used to route messages to wherever they -//! need to go and also handles log file rotation and trimming. -//----------------------------------------------------------------------------- - -class XrdOucTListFIFO; - -class XrdSysLogger -{ -public: - -//----------------------------------------------------------------------------- -//! Constructor -//! -//! @param ErrFD is the filedescriptor of where error messages normally -//! go if this class is not used. Default is stderr. -//! @param xrotate when not zero performs internal log rotatation. Otherwise, -//! log rotation is suppressed. See also setRotate(). -//----------------------------------------------------------------------------- - - XrdSysLogger(int ErrFD=STDERR_FILENO, int xrotate=1); - -//----------------------------------------------------------------------------- -//! Destructor -//----------------------------------------------------------------------------- - - ~XrdSysLogger() - { - RmLogRotateLock(); - if (ePath) - free(ePath); - } - -//----------------------------------------------------------------------------- -//! Add a message to be printed at midnight. -//! -//! @param msg The message to be printed. A copy of the message is saved. -//----------------------------------------------------------------------------- - -void AddMsg(const char *msg); - -//----------------------------------------------------------------------------- -//! Add a task to be run at midnight. Tasks are run sequentially lifo. -//! -//! @param mnTask Pointer to an instance of the task object below. -//----------------------------------------------------------------------------- - -class Task -{ -public: -friend class XrdSysLogger; - -virtual void Ring() = 0; //!< This method gets called at midnight - -inline Task *Next() {return next;} - - Task() : next(0) {} -virtual ~Task() {} - -private: -Task *next; -}; - -void AtMidnight(Task *mnTask); - -//----------------------------------------------------------------------------- -//! Bind allows you to bind the file descriptor passed at construction time to -//! a file with an optional periodic closing and opening of the file. -//! -//! @param path The log file path. The file is created, if need be. -//! If path is null, messages are routed to stderr and the -//! lfh argument is ignored. -//! @param lfh Log file handling: -//! >0 file is to be closed and opened at midnight. -//! This implies automatic log rotation. -//! =0 file is to be left open all the time. -//! This implies no log rotation. -//! <0 file is to be closed and opened only on signal abs(lfh) -//! unless the value equals onFifo. In this case a fifo is -//! used to control log file rotation. The name of the fifo -//! is path with the filename component prefixed by dot. -//! This implies manual log rotation. Warning! Using -//! signals requires that Bind() be called before starting -//! any threads so that the signal is properly blocked. -//! -//! @return 0 Processing successful. -//! @return <0 Unable to bind, returned value is -errno of the reason. -//----------------------------------------------------------------------------- - -static const int onFifo = (int)0x80000000; - -int Bind(const char *path, int lfh=0); - -//----------------------------------------------------------------------------- -//! Capture allows you to capture all messages (they are not routed). This is -//! a global setting so use with caution! -//! -//! @param tBase Pointer to the XrdOucTListFIFO where messages are saved. -//! If the pointer is nil, capturing is turned off. -//----------------------------------------------------------------------------- - -void Capture(XrdOucTListFIFO *tFIFO); - -//----------------------------------------------------------------------------- -//! Flush any pending output -//----------------------------------------------------------------------------- - -void Flush() {fsync(eFD);} - -//----------------------------------------------------------------------------- -//! Get the file descriptor passed at construction time. -//! -//! @return the file descriptor passed to the constructor. -//----------------------------------------------------------------------------- - -int originalFD() {return baseFD;} - -//----------------------------------------------------------------------------- -//! Parse the keep option argument. -//! -//! @param arg Pointer to the argument. The argument syntax is: -//! \ | \ | fifo | \ -//! -//! @return !0 Parsing succeeded. The return value is the argument that -//! must be passed as the lfh parameter to Bind(). -//! @return =0 Invalid keep argument. -//----------------------------------------------------------------------------- - -int ParseKeep(const char *arg); - -//----------------------------------------------------------------------------- -//! Output data and optionally prefix with date/time -//! -//! @param iovcnt The number of elements in iov vector. -//! @param iov The vector describing what to print. If iov[0].iov_base -//! is zero, the message is prefixed by date and time. -//----------------------------------------------------------------------------- - -void Put(int iovcnt, struct iovec *iov); - -//----------------------------------------------------------------------------- -//! Set call-out to logging plug-in on or off. -//----------------------------------------------------------------------------- - -static -void setForwarding(bool onoff) {doForward = onoff;} - -//----------------------------------------------------------------------------- -//! Set log file timstamp to high resolution (hh:mm:ss.uuuu). -//----------------------------------------------------------------------------- - -void setHiRes() {hiRes = true;} - -//----------------------------------------------------------------------------- -//! Set log file keep value. -//! -//! @param knum The keep value. If knum < 0 then abs(knum) files are kept. -//! Otherwise, only knum bytes of log files are kept. -//----------------------------------------------------------------------------- - -void setKeep(long long knum) {eKeep = knum;} - -//----------------------------------------------------------------------------- -//! Set log file rotation on/off. -//! -//! @param onoff When !0 turns on log file rotations. Otherwise, rotation -//! is turned off. -//----------------------------------------------------------------------------- - -void setRotate(int onoff) {doLFR = onoff;} - -//----------------------------------------------------------------------------- -//! Start trace message serialization. This method must be followed by a call -//! to traceEnd(). -//! -//! @return pointer to the time buffer to be used as the msg timestamp. -//----------------------------------------------------------------------------- - -char *traceBeg() {Logger_Mutex.Lock(); Time(TBuff); return TBuff;} - -//----------------------------------------------------------------------------- -//! Stop trace message serialization. This method must be preceeded by a call -//! to traceBeg(). -//! -//! @return pointer to a new line character to terminate the message. -//----------------------------------------------------------------------------- - -char traceEnd() {Logger_Mutex.UnLock(); return '\n';} - -//----------------------------------------------------------------------------- -//! Get the log file routing. -//! -//! @return the filename of the log file or "stderr". -//----------------------------------------------------------------------------- - -const char *xlogFN() {return (ePath ? ePath : "stderr");} - -//----------------------------------------------------------------------------- -//! Internal method to handle the logfile. This is public because it needs to -//! be called by an external thread. -//----------------------------------------------------------------------------- - -void zHandler(); - -private: -int FifoMake(); -void FifoWait(); -int Time(char *tbuff); -static int TimeStamp(struct timeval &tVal, unsigned long tID, - char *tbuff, int tbsz, bool hires); -int HandleLogRotateLock( bool dorotate ); -void RmLogRotateLock(); - -struct mmMsg - {mmMsg *next; - int mlen; - char *msg; - }; -mmMsg *msgList; -Task *taskQ; -XrdSysMutex Logger_Mutex; -long long eKeep; -char TBuff[32]; // Trace header buffer -int eFD; -int baseFD; -char *ePath; -char Filesfx[8]; -int eInt; -int reserved1; -char *fifoFN; -bool hiRes; -bool doLFR; -pthread_t lfhTID; - -static bool doForward; - -void putEmsg(char *msg, int msz); -int ReBind(int dorename=1); -void Trim(); -}; -#endif diff --git a/src/XrdSys/XrdSysLogging.cc b/src/XrdSys/XrdSysLogging.cc deleted file mode 100644 index 42d3c695733..00000000000 --- a/src/XrdSys/XrdSysLogging.cc +++ /dev/null @@ -1,352 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S y s L o g g i n g . c c */ -/* */ -/*(c) 2016 by the Board of Trustees of the Leland Stanford, Jr., University */ -/*Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Deprtment of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include - -#include "XrdSys/XrdSysLogger.hh" -#include "XrdSys/XrdSysLogging.hh" -#include "XrdSys/XrdSysPlatform.hh" - -/******************************************************************************/ -/* S t a t i c O b j e c t s */ -/******************************************************************************/ - -namespace -{ -static const int buffOvhd = 8; - -XrdSysMutex msgMutex; -XrdSysSemaphore msgAlert(0); -XrdSysLogPI_t piLogger = 0; -char *pendMsg = 0; // msg to be processed, if nil means none -char *lastMsg = 0; // last msg in the processing queue -char *buffOrg = 0; // Base address of global message buffer -char *buffBeg = 0; // buffOrg + overhead -char *buffEnd = 0; // buffOrg + size of buffer -struct timeval todLost; // time last message was lost -int numLost = 0; // Number of messages lost -bool logDone = false; -bool doSync = false; - -static const int syncBSZ = 8192; -}; - -pthread_t XrdSysLogging::lpiTID; -bool XrdSysLogging::lclOut = false; -bool XrdSysLogging::rmtOut = false; - -/******************************************************************************/ -/* C o n f i g u r e */ -/******************************************************************************/ - -bool XrdSysLogging::Configure(XrdSysLogger &logr, Parms &parms) -{ - char eBuff[256]; - int rc; - -// Set logger parameters -// - if (parms.hiRes) logr.setHiRes(); - -// If we are going to send output to a local destination, configure it. -// - if (parms.logfn) - {if (strcmp(parms.logfn, "-") && (rc=logr.Bind(parms.logfn,parms.keepV))) - {sprintf(eBuff, "Error %d (%s) binding to log file %s.\n", - -rc, strerror(-rc), parms.logfn); - return EMsg(logr, eBuff); - } - lclOut = true; - } - -// If we are not sending output to a remote destination, we are done -// - if (!parms.logpi) {lclOut = true; return true;} - piLogger= parms.logpi; - logDone = !lclOut; - rmtOut = true; - -// We have a plugin, setup the synchronous case if so desired -// - if (!parms.bufsz) - {logr.setForwarding(true); - doSync = true; - return true; - } - -// Allocate a log buffer -// - int bsz = (parms.bufsz < 0 ? 65536 : parms.bufsz); - rc = posix_memalign(reinterpret_cast(&buffOrg), getpagesize(), bsz); - if (rc != 0 || !buffOrg) return EMsg(logr, "Unable to allocate log buffer!\n"); - - buffBeg = buffOrg + buffOvhd; - buffEnd = buffOrg + bsz; - -// Start the forwarding thread -// - if (XrdSysThread::Run(&lpiTID, Send2PI, (void *)0, 0, "LogPI handler")) - {sprintf(eBuff, "Error %d (%s) starting LogPI handler.\n", - errno, strerror(errno)); - return EMsg(logr, eBuff); - } - -// We are all done -// - logr.setForwarding(true); - return true; -} - -/******************************************************************************/ -/* Private: C o p y T r u n c */ -/******************************************************************************/ - -int XrdSysLogging::CopyTrunc(char *mbuff, struct iovec *iov, int iovcnt) -{ - char *mbP = mbuff; - int segLen, bLeft = syncBSZ - 1; - -// Copy message with truncation -// - for (int i = 0; i < iovcnt; i++) - {segLen = iov[i].iov_len; - if (segLen >= bLeft) segLen = bLeft; - memcpy(mbP, iov[i].iov_base, segLen); - mbP += segLen; bLeft -= segLen; - if (bLeft <= 0) break; - } - *mbP = 0; - -// Return actual length -// - return mbP - mbuff; -} - -/******************************************************************************/ -/* Private: E M s g */ -/******************************************************************************/ - -bool XrdSysLogging::EMsg(XrdSysLogger &logr, const char *msg) -{ - struct iovec iov[] = {{0,0}, {(char *)msg,0}}; - - iov[1].iov_len = strlen((const char *)iov[1].iov_base); - logr.Put(2, iov); - return false; -} - -/******************************************************************************/ -/* F o r w a r d */ -/******************************************************************************/ - -bool XrdSysLogging::Forward(struct timeval mtime, unsigned long tID, - struct iovec *iov, int iovcnt) -{ - MsgBuff *theMsg; - char *fence, *freeMsg, *msgText; - int dwords, msgLen = 0; - bool doPost = false; - -// Calculate the message length -// - for (int i = 0; i < iovcnt; i++) msgLen += iov[i].iov_len; - -// If we are doing synchronous forwarding, do so now (we do not get a lock) -// - if (doSync) - {char *mbP, mbuff[syncBSZ]; - if (msgLen >= syncBSZ) msgLen = CopyTrunc(mbuff, iov, iovcnt); - else {mbP = mbuff; - for (int i = 0; i < iovcnt; i++) - {memcpy(mbP, iov[i].iov_base, iov[i].iov_len); - mbP += iov[i].iov_len; - } - *mbP = 0; - } - (*piLogger)(mtime, tID, mbuff, msgLen); - return logDone; - } - -// Serialize remainder of code -// - msgMutex.Lock(); - -// If the message is excessively long, treat it as a lost message -// - if (msgLen > maxMsgLen) - {todLost = mtime; - numLost++; - msgMutex.UnLock(); - return logDone; - } - -// Get the actual doublewords bytes we need (account for null byte in the msg). -// We need to increase the size by the header size if there are outsanding -// lost messages. -// - dwords = msgLen+8 + sizeof(MsgBuff); - if (numLost) dwords += sizeof(MsgBuff); - dwords = dwords/8; - -// Compute the allocation fence. The choices are as follows: -// a) When pendMsg is present then the fence is the end of the buffer if -// lastMsg >= pendMsg and pendMsg otherwise. -// b) When pendMsg is nil then we can reset the buffer pointers so that the -// fence is the end of the buffer. -// - if (pendMsg) - {freeMsg = lastMsg + ((MsgBuff *)lastMsg)->buffsz*8; - fence = (lastMsg >= pendMsg ? buffEnd : pendMsg); - } else { - freeMsg = buffBeg; - fence = buffEnd; - lastMsg = 0; - doPost = true; - } - -// Check if there is room for this message. If not, count this as a lost -// message and tell the caller full forwarding did not happen. -// - if ((freeMsg + (dwords*8)) > fence) - {todLost = mtime; - numLost++; - msgMutex.UnLock(); - return logDone; - } - -// We can allocate everything. So, check if we will be inserting a lost -// message entry here. We preallocated this above when numLost != 0; -// - if (numLost) - {theMsg = (MsgBuff *)freeMsg; - theMsg->msgtod = mtime; - theMsg->tID = tID; - theMsg->buffsz = mbDwords; - theMsg->msglen = -numLost; - if (lastMsg) ((MsgBuff *)lastMsg)->next = freeMsg - buffOrg; - lastMsg = freeMsg; - freeMsg += msgOff; - } - -// Insert the message -// - theMsg = (MsgBuff *)freeMsg; - theMsg->msgtod = mtime; - theMsg->tID = tID; - theMsg->next = 0; - theMsg->buffsz = dwords; - theMsg->msglen = msgLen; - if (lastMsg) ((MsgBuff *)lastMsg)->next = freeMsg - buffOrg; - lastMsg = freeMsg; - -// Copy the message text into the buffer -// - msgText = freeMsg + msgOff; - for (int i = 0; i < iovcnt; i++) - {memcpy(msgText, iov[i].iov_base, iov[i].iov_len); - msgText += iov[i].iov_len; - } - *msgText = 0; - -// If we need to write this to another log file do so here. -// - -// Do final post processing (release the lock prior to posting) -// - if (doPost) pendMsg = freeMsg; - msgMutex.UnLock(); - if (doPost) msgAlert.Post(); - return logDone; -} - -/******************************************************************************/ -/* Private: g e t M s g */ -/******************************************************************************/ - -XrdSysLogging::MsgBuff *XrdSysLogging::getMsg(char **msgTxt, bool cont) -{ - XrdSysMutexHelper msgHelp(msgMutex); - MsgBuff *theMsg; - -// If we got incorrectly posted, ignore this call -// - if (!pendMsg) return 0; - -// Check if this is a continuation. If so, skip to next message. If there is no -// next message, clear the pendMsg pointer to indicate we stopped pulling any -// messages (we will get posted when another message arrives). -// - if (cont) - {if (((MsgBuff *)pendMsg)->next) - pendMsg = buffOrg + ((MsgBuff *)pendMsg)->next; - else pendMsg = 0; - } - -// Return the message -// - theMsg = (MsgBuff *)pendMsg; - *msgTxt = pendMsg + msgOff; - return theMsg; -} - -/******************************************************************************/ -/* Private: S e n d 2 P I */ -/******************************************************************************/ - -void *XrdSysLogging::Send2PI(void *arg) -{ - MsgBuff *theMsg; - char *msgTxt, lstBuff[80]; - int msgLen; - bool cont; - -// Infinit loop feeding the logger plugin -// -do{msgAlert.Wait(); - cont = false; - while((theMsg = getMsg(&msgTxt, cont))) - {if ((msgLen = theMsg->msglen) < 0) - {int n = -msgLen; // Note we will never overflow lstBuff! - msgLen = snprintf(lstBuff, sizeof(lstBuff), "%d message%s lost!", - n, (n == 1 ? "" : "s")); - msgTxt = lstBuff; - } - (*piLogger)(theMsg->msgtod, theMsg->tID, msgTxt, msgLen); - cont = true; - } - } while(true); - -// Here to keep the compiler happy -// - return (void *)0; -} diff --git a/src/XrdSys/XrdSysLogging.hh b/src/XrdSys/XrdSysLogging.hh deleted file mode 100644 index e36a8a826b7..00000000000 --- a/src/XrdSys/XrdSysLogging.hh +++ /dev/null @@ -1,122 +0,0 @@ -#ifndef __SYS_LOGGING_H__ -#define __SYS_LOGGING_H__ -/******************************************************************************/ -/* */ -/* X r d S y s L o g g i n g . h h */ -/* */ -/*(c) 2016 by the Board of Trustees of the Leland Stanford, Jr., University */ -/*Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Deprtment of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include - -#include "XrdSys/XrdSysLogPI.hh" -#include "XrdSys/XrdSysPthread.hh" - -//----------------------------------------------------------------------------- -//! XrdSysLogging is the object that is used to route messages to a plugin -//! and is used to configure the base logger. There is only one such object. -//----------------------------------------------------------------------------- - -class XrdSysLogger; - -class XrdSysLogging -{ -public: - -//----------------------------------------------------------------------------- -//! Constructor and destructor -//! -//----------------------------------------------------------------------------- - - XrdSysLogging() {} - - ~XrdSysLogging() {} - -//----------------------------------------------------------------------------- -//! Parameters to be passed to configure. -//----------------------------------------------------------------------------- - -struct Parms - {const char *logfn; //!< -> log file name or nil if none. - XrdSysLogPI_t logpi; //!< -> log plugin object or nil if none - int bufsz; //!< size of message buffer, -1 default, or 0 - int keepV; //!< log keep argument - bool hiRes; //!< log using high resolution timestamp - Parms() : logfn(0), logpi(0), bufsz(-1), keepV(0), hiRes(false) {} - ~Parms() {} - }; - -//----------------------------------------------------------------------------- -//! Configure the logger object using the parameters above. -//! -//! @param logr Reference to the logger object. -//! @param parms Reference to the parameters. -//! -//! @return true if successful and false if log could not be configured. -//----------------------------------------------------------------------------- - -static bool Configure(XrdSysLogger &logr, Parms &parms); - -//----------------------------------------------------------------------------- -//! Forward a log message to a plugin. -//! -//! @param mtime The time the message was generated. -//! @param tID The thread ID that issued the message. -//! @param iov The vector describing what to forward. -//! @param iovcnt The number of elements in iov vector. -//! -//! @return false if the message needs to also be placed in a local log file. -//! true if all processing has completed. -//----------------------------------------------------------------------------- - -static bool Forward(struct timeval mtime, unsigned long tID, - struct iovec *iov, int iovcnt); - -private: -struct MsgBuff - {struct timeval msgtod; // time message was generated - unsigned long tID; // Thread ID issuing message - unsigned int next; // Offset to next message, 0 if none - unsigned short buffsz; // In doublewords (max is 512K-8) - short msglen; // Len of msg text (max 32K-1) if <0 ->lost msgs -// char msgtxt; // Text follows the message header - }; -static const int msgOff = sizeof(MsgBuff); -static const int mbDwords = (sizeof(MsgBuff)+7)/8*8; -static const int maxMsgLen = SHRT_MAX; - -static int CopyTrunc(char *mbuff, struct iovec *iov, int iovcnt); -static bool EMsg(XrdSysLogger &logr, const char *msg); -static MsgBuff *getMsg(char **msgTxt, bool cont); -static void *Send2PI(void *arg); - -static pthread_t lpiTID; -static bool lclOut; -static bool rmtOut; -}; -#endif diff --git a/src/XrdSys/XrdSysPlatform.cc b/src/XrdSys/XrdSysPlatform.cc deleted file mode 100644 index d5d9fe1c1c6..00000000000 --- a/src/XrdSys/XrdSysPlatform.cc +++ /dev/null @@ -1,71 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S y s P l a t f o r m . c c */ -/* */ -/* (c) 2006 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#ifndef WIN32 -#include -#include -#endif -#include - -#if defined(_LITTLE_ENDIAN) || defined(__LITTLE_ENDIAN__) || \ - defined(__IEEE_LITTLE_ENDIAN) || \ - (defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN) -#if !defined(__GNUC__) || defined(__APPLE__) -extern "C" -{ -unsigned long long Swap_n2hll(unsigned long long x) -{ - unsigned long long ret_val; - *( (unsigned int *)(&ret_val) + 1) = ntohl(*( (unsigned int *)(&x))); - *(((unsigned int *)(&ret_val))) = ntohl(*(((unsigned int *)(&x))+1)); - return ret_val; -} -} -#endif - -#endif - -#ifndef HAVE_STRLCPY -extern "C" -{ -size_t strlcpy(char *dst, const char *src, size_t sz) -{ - size_t slen = strlen(src); - size_t tlen =sz-1; - - if (slen <= tlen) strcpy(dst, src); - else if (tlen > 0) {strncpy(dst, src, tlen); dst[tlen] = '\0';} - else if (tlen == 0) dst[0] = '\0'; - - return slen; -} -} -#endif diff --git a/src/XrdSys/XrdSysPlatform.hh b/src/XrdSys/XrdSysPlatform.hh deleted file mode 100644 index 30f54be13dd..00000000000 --- a/src/XrdSys/XrdSysPlatform.hh +++ /dev/null @@ -1,272 +0,0 @@ -#ifndef __XRDSYS_PLATFORM_H__ -#define __XRDSYS_PLATFORM_H__ -/******************************************************************************/ -/* */ -/* X r d S y s P l a t f o r m . h h */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -// Include stdlib so that ENDIAN macros are defined properly -// -#include -#ifdef __linux__ -#include -#include -#include -#include -#include -#define MAXNAMELEN NAME_MAX -#endif -#ifdef __APPLE__ -#include -#include -#define fdatasync(x) fsync(x) -#define MAXNAMELEN NAME_MAX -#ifndef dirent64 -# define dirent64 dirent -#endif -#ifndef off64_t -#define off64_t int64_t -#endif -#if (!defined(MAC_OS_X_VERSION_10_5) || \ - MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5) -#ifndef stat64 -# define stat64 stat -#endif -#endif -#endif -#ifdef __FreeBSD__ -#include -#endif - -#ifdef __solaris__ -#define posix_memalign(memp, algn, sz) \ - ((*memp = memalign(algn, sz)) ? 0 : ENOMEM) -#define __USE_LEGACY_PROTOTYPES__ 1 -#endif - -#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) - -#define S_IAMB 0x1FF /* access mode bits */ - -#define F_DUP2FD F_DUPFD - -#define STATFS statfs -#define STATFS_BUFF struct statfs - -#define FS_BLKFACT 4 - -#define FLOCK_t struct flock - -typedef off_t offset_t; - -#define GTZ_NULL (struct timezone *)0 - -#else - -#define STATFS statvfs -#define STATFS_BUFF struct statvfs - -#define FS_BLKFACT 1 - -#define SHMDT_t char * - -#define FLOCK_t flock_t - -#define GTZ_NULL (void *)0 - -#endif - -#ifdef __linux__ - -#define SHMDT_t const void * -#endif - -// For alternative platforms -// -#ifdef __APPLE__ -#include -#ifndef POLLRDNORM -#define POLLRDNORM 0 -#endif -#ifndef POLLRDBAND -#define POLLRDBAND 0 -#endif -#ifndef POLLWRNORM -#define POLLWRNORM 0 -#endif -#define O_LARGEFILE 0 -#define memalign(pgsz,amt) valloc(amt) -#define posix_memalign(memp, algn, sz) \ - ((*memp = memalign(algn, sz)) ? 0 : ENOMEM) -#define SHMDT_t void * -#ifndef EDEADLOCK -#define EDEADLOCK EDEADLK -#endif -#endif - -#ifdef __FreeBSD__ -#define O_LARGEFILE 0 -typedef off_t off64_t; -#define memalign(pgsz,amt) valloc(amt) -#endif - -// Only sparc platforms have structure alignment problems w/ optimization -// so the h2xxx() variants are used when converting network streams. - -#if defined(_BIG_ENDIAN) || defined(__BIG_ENDIAN__) || \ - defined(__IEEE_BIG_ENDIAN) || \ - (defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN) -#define Xrd_Big_Endian -#ifndef htonll -#define htonll(_x_) _x_ -#endif -#ifndef h2nll -#define h2nll(_x_, _y_) memcpy((void *)&_y_,(const void *)&_x_,sizeof(long long)) -#endif -#ifndef ntohll -#define ntohll(_x_) _x_ -#endif -#ifndef n2hll -#define n2hll(_x_, _y_) memcpy((void *)&_y_,(const void *)&_x_,sizeof(long long)) -#endif - -#elif defined(_LITTLE_ENDIAN) || defined(__LITTLE_ENDIAN__) || \ - defined(__IEEE_LITTLE_ENDIAN) || \ - (defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN) -#if !defined(__GNUC__) || defined(__APPLE__) - -#if !defined(__sun) || (defined(__sun) && (!defined(_LP64) || defined(__SunOS_5_10))) -extern "C" unsigned long long Swap_n2hll(unsigned long long x); -#ifndef htonll -#define htonll(_x_) Swap_n2hll(_x_) -#endif -#ifndef ntohll -#define ntohll(_x_) Swap_n2hll(_x_) -#endif -#endif - -#else - -#ifndef htonll -#define htonll(_x_) __bswap_64(_x_) -#endif -#ifndef ntohll -#define ntohll(_x_) __bswap_64(_x_) -#endif - -#endif - -#ifndef h2nll -#define h2nll(_x_, _y_) memcpy((void *)&_y_,(const void *)&_x_,sizeof(long long));\ - _y_ = htonll(_y_) -#endif -#ifndef n2hll -#define n2hll(_x_, _y_) memcpy((void *)&_y_,(const void *)&_x_,sizeof(long long));\ - _y_ = ntohll(_y_) -#endif - -#else -#ifndef WIN32 -#error Unable to determine target architecture endianness! -#endif -#endif - -#ifndef HAVE_STRLCPY -extern "C" -{extern size_t strlcpy(char *dst, const char *src, size_t size);} -#endif - -// -// To make socklen_t portable use SOCKLEN_t -// -#if defined(__solaris__) && !defined(__linux__) -# if __GNUC__ >= 3 || __GNUC_MINOR__ >= 90 -# define XR__SUNGCC3 -# endif -#endif -#if defined(__linux__) -# include -# if __GNU_LIBRARY__ == 6 -# ifndef XR__GLIBC -# define XR__GLIBC -# endif -# endif -#endif -#if defined(__MACH__) && defined(__i386__) -# define R__GLIBC -#endif -#if defined(_AIX) || \ - (defined(XR__SUNGCC3) && !defined(__arch64__)) -# define SOCKLEN_t size_t -#elif defined(XR__GLIBC) || \ - defined(__FreeBSD__) || \ - (defined(XR__SUNGCC3) && defined(__arch64__)) || defined(__APPLE__) || \ - (defined(__sun) && defined(_SOCKLEN_T)) -# ifndef SOCKLEN_t -# define SOCKLEN_t socklen_t -# endif -#elif !defined(SOCKLEN_t) -# define SOCKLEN_t int -#endif - -#ifdef _LP64 -#define PTR2INT(x) static_cast((long long)x) -#else -#define PTR2INT(x) int(x) -#endif - -#ifdef WIN32 -#include "XrdSys/XrdWin32.hh" -#define Netdata_t void * -#define Sokdata_t char * -#define IOV_INIT(data,dlen) dlen,data -#define MAKEDIR(path,mode) mkdir(path) -#define net_errno WSAGetLastError() -#else -#define O_BINARY 0 -#define Netdata_t char * -#define Sokdata_t void * -#define IOV_INIT(data,dlen) data,dlen -#define MAKEDIR(path,mode) mkdir(path,mode) -#define net_errno errno -#endif - -#ifdef WIN32 -#define MAXNAMELEN 256 -#define MAXPATHLEN 1024 -#else -#include -#endif -// The following gets arround a relative new gcc compiler bug -// -#define XRDABS(x) (x < 0 ? -x : x) - -#ifndef LT_MODULE_EXT -#define LT_MODULE_EXT ".so" -#endif - -#endif // __XRDSYS_PLATFORM_H__ diff --git a/src/XrdSys/XrdSysPlugin.cc b/src/XrdSys/XrdSysPlugin.cc deleted file mode 100644 index 1d6013c580c..00000000000 --- a/src/XrdSys/XrdSysPlugin.cc +++ /dev/null @@ -1,478 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S y s P l u g i n . c c */ -/* */ -/* (c) 2005 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -// Bypass Solaris ELF madness -// -#ifdef __solaris__ -#include -#if defined(_ILP32) && (_FILE_OFFSET_BITS != 32) -#undef _FILE_OFFSET_BITS -#define _FILE_OFFSET_BITS 32 -#undef _LARGEFILE_SOURCE -#endif -#endif - -#ifndef WIN32 -#include -#if !defined(__APPLE__) && !defined(__CYGWIN__) -#include -#endif -#include -#include -#include -#include -#else -#include "XrdSys/XrdWin32.hh" -#endif - -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdSys/XrdSysPlugin.hh" -#include "XrdVersion.hh" -#include "XrdVersionPlugin.hh" - -/******************************************************************************/ -/* S t a t i c M e m b e r s */ -/******************************************************************************/ - -struct XrdSysPlugin::PLlist *XrdSysPlugin::plList = 0; - -/******************************************************************************/ -/* D e s t r u c t o r */ -/******************************************************************************/ - -XrdSysPlugin::~XrdSysPlugin() -{ - if (libHandle) dlclose(libHandle); - if (libPath) free(libPath); -} - -/******************************************************************************/ -/* Private: b a d V e r s i o n */ -/******************************************************************************/ - -XrdSysPlugin::cvResult XrdSysPlugin::badVersion(XrdVersionInfo &urInfo, - char mmv, int majv, int minv) -{ - const char *path; - char buff1[512], buff2[128]; - - if (minv > 99) minv = 99; - snprintf(buff1, sizeof(buff1), "version %s is incompatible with %s " - "(must be %c= %d.%d.x)", - myInfo->vStr, urInfo.vStr, mmv, majv, minv); - - path = msgSuffix(" in ", buff2, sizeof(buff2)); - - Inform(buff1, buff2, path, 0, 0, 1); - - return cvBad; -} - -/******************************************************************************/ -/* Private: c h k V e r s i o n */ -/******************************************************************************/ - -XrdSysPlugin::cvResult XrdSysPlugin::chkVersion(XrdVersionInfo &urInfo, - const char *pname, - void *lHandle) -{ - static XrdVersionPlugin vInfo[] = {XrdVERSIONPLUGINRULES}; - static XrdVersionPlugin vNote[] = {XrdVERSIONPLUGINMAXIMS}; - XrdVersionPlugin *vinP; - char buff[1024], vName[256]; - void *vP; - int i, n=0, pMajor, vMajor, pMinor, vMinor; - -// If no version information supplied, skip version check -// - if (!myInfo) return cvNone; - -// Check if we need to check the version here -// - i = 0; - while(vInfo[i].pName && strcmp(vInfo[i].pName, pname)) i++; - -// If we didn't find it in the rules table then try to match the maxims -// - if (!vInfo[i].pName) - {i = 0; n = strlen(pname); - while(vNote[i].pName) - {if ((vNote[i].vPfxLen + vNote[i].vSfxLen <= n) - && !strncmp(vNote[i].pName, pname, vNote[i].vPfxLen) - && !strncmp(vNote[i].pName+vNote[i].vPfxLen, - pname + n - vNote[i].vSfxLen, vNote[i].vSfxLen)) break; - i++; - } - vinP = &vNote[i]; - } else vinP = &vInfo[i]; - - if (!(vinP->pName)) return cvNone; - if ( vinP->vProcess == XrdVERSIONPLUGIN_DoNotChk) return cvDirty; - -// Construct the version entry point -// - if (!n) n = strlen(pname); - if (n+sizeof(XrdVERSIONINFOSFX) > sizeof(vName)) - return libMsg("Unable to generate version name for", "%s in ", pname); - strcpy(vName, pname); strcpy(vName+n, XrdVERSIONINFOSFX); - -// Find the version number -// - if (!(vP = dlsym(lHandle, vName))) - {if (vinP->vProcess != XrdVERSIONPLUGIN_Required) return cvMissing; - return libMsg(dlerror()," required version information for %s in ",pname); - } - -// Extract the version number from the plugin and do a quick check. We use -// memcpy to avoid instances where the symbol is wrongly defined. Make sure -// the version string ends with a null by copying one less byte than need be. -// The caller provided a struct that is gauranteed to end with nulls. -// - memcpy(static_cast( &urInfo ), vP, sizeof(XrdVersionInfo)-1); - -// If version numbers are identical then we are done -// - if (myInfo->vNum == urInfo.vNum) - if (myInfo->vNum != XrdVNUMUNK - || !strcmp(myInfo->vStr + (myInfo->vOpt & 0x0f)+1, - urInfo. vStr + (urInfo. vOpt & 0x0f)+1)) return cvClean; - -// If the caller or plugin is unreleased, just issue a warning. -// - if (myInfo->vNum == XrdVNUMUNK || urInfo.vNum == XrdVNUMUNK) - {if (eDest) - {char mBuff[128]; - sprintf(buff, "%s%s is using %s%s version", - (myInfo->vNum == XrdVNUMUNK ? "unreleased ":""),myInfo->vStr, - (urInfo.vNum == XrdVNUMUNK ? "unreleased ":""),urInfo.vStr); - msgSuffix(" in ", mBuff, sizeof(mBuff)); - Inform(buff, mBuff, libPath); - } - return cvDirty; - } - -// Extract version numbers -// - vMajor = XrdMajorVNUM(myInfo->vNum); - vMinor = XrdMinorVNUM(myInfo->vNum); - pMajor = XrdMajorVNUM(urInfo. vNum); - pMinor = XrdMinorVNUM(urInfo. vNum); - -// The major version must always be compatible -// - if ((vinP->vMajLow >= 0 && pMajor < vinP->vMajLow) - || (vinP->vMajLow < 0 && pMajor != vMajor)) - return badVersion(urInfo, '>', vinP->vMajLow, vinP->vMinLow); - -// The major version may not be greater than our versin -// - if (pMajor > vMajor) return badVersion(urInfo, '<', vMajor, vMinor); - -// If we do not need to check minor versions then we are done -// - if (vinP->vMinLow > 99) return cvClean; - -// In no case can the plug-in mnor version be greater than our version -// - if (pMajor == vMajor && pMinor > vMinor) - return badVersion(urInfo, '<', vMajor, vMinor); - -// Verify compatible minor versions -// - if ((vinP->vMinLow >= 0 && pMinor >= vinP->vMinLow) - || (vinP->vMinLow < 0 && pMinor == vMinor)) return cvClean; - -// Incompatible versions -// - return badVersion(urInfo, '>', vinP->vMajLow, vinP->vMinLow); -} - -/******************************************************************************/ -/* Private: D L F l a g s */ -/******************************************************************************/ - -int XrdSysPlugin::DLflags() -{ -#if defined(__APPLE__) - return RTLD_FIRST; -#elif defined(__linux__) - return RTLD_NOW; -#else - return RTLD_NOW; -#endif -} - -/******************************************************************************/ -/* Private: F i n d */ -/******************************************************************************/ - -void *XrdSysPlugin::Find(const char *libpath) -{ - struct PLlist *plP = plList; - -// Find the library in the preload list -// - while(plP && strcmp(libpath, plP->libPath)) plP = plP->next; - -// Return result -// - return (plP ? plP->libHandle : 0); -} - -/******************************************************************************/ -/* g e t P l u g i n */ -/******************************************************************************/ - -void *XrdSysPlugin::getPlugin(const char *pname, int optional) -{ - return getPlugin(pname, optional, false); -} - -void *XrdSysPlugin::getPlugin(const char *pname, int optional, bool global) -{ - XrdVERSIONINFODEF(urInfo, unknown, XrdVNUMUNK, ""); - void *ep, *myHandle; - cvResult cvRC; - int flags; - -// If no path is given then we want to just search the executable. This is easy -// for some platforms and more difficult for others. So, we do the best we can. -// - if (libPath) flags = DLflags(); - else { flags = RTLD_NOW; -#ifndef WIN32 - flags|= global ? RTLD_GLOBAL : RTLD_LOCAL; -#else - if (global && eDest) eDest->Emsg("getPlugin", - "request for global symbols unsupported under Windows - ignored"); -#endif - } - -// Check if we should use the preload list -// - if (!(myHandle = libHandle) && plList) myHandle = Find(libPath); - -// Open whatever it is we need to open -// - if (!myHandle) - {if ((myHandle = dlopen(libPath, flags))) libHandle = myHandle; - else {if (optional < 2) libMsg(dlerror(), " loading "); return 0;} - } - -// Get the symbol. In the environment we have defined, null values are not -// allowed and we will issue an error. -// - if (!(ep = dlsym(myHandle, pname))) - {if (optional < 2) libMsg(dlerror(), " plugin %s in ", pname); - return 0; - } - -// Check if we need to verify version compatability -// - if ((cvRC = chkVersion(urInfo, pname, myHandle)) == cvBad) return 0; - -// Print the loaded version unless message is suppressed or not needed -// - if (libPath && optional < 2 && msgCnt - && (cvRC == cvClean || cvRC == cvMissing)) - {char buff[128]; - msgSuffix(" from ", buff, sizeof(buff)); - msgCnt--; - if (cvRC == cvClean) - {const char *wTxt=(urInfo.vNum == XrdVNUMUNK ? "unreleased ":0); - Inform("loaded ", wTxt, urInfo.vStr, buff, libPath); - } - else if (cvRC == cvMissing) - {Inform("loaded unversioned ", pname, buff, libPath);} - } - -// All done -// - return ep; -} - -/******************************************************************************/ -/* Private: I n f o r m */ -/******************************************************************************/ - -void XrdSysPlugin::Inform(const char *txt1, const char *txt2, const char *txt3, - const char *txt4, const char *txt5, int noHush) -{ - const char *eTxt[] = {"Plugin ",txt1, txt2, txt3, txt4, txt5, 0}; - char *bP; - int n, i, bL; - -// Check if we should hush this messages (largely for client-side usage) -// - if (!noHush && getenv("XRDPIHUSH")) return; - -// If we have a messaging object, use that -// - if (eDest) - {char buff[2048]; - i = 1; bP = buff; bL = sizeof(buff); - while(bL > 1 && eTxt[i]) - {n = snprintf(bP, bL, "%s", eTxt[i]); - bP += n; bL -= n; i++; - } - eDest->Say("Plugin ", buff); - return; - } - -// If we have a buffer, set message in the buffer -// - if ((bP = eBuff)) - {i = 0; bL = eBLen; - while(bL > 1 && eTxt[i]) - {n = snprintf(bP, bL, "%s", eTxt[i]); - bP += n; bL -= n; i++; - } - } -} - -/******************************************************************************/ -/* Private: l i b M s g */ -/******************************************************************************/ - -XrdSysPlugin::cvResult XrdSysPlugin::libMsg(const char *txt1, const char *txt2, - const char *mSym) -{ - static const char fndg[] = "Finding"; - static const int flen = sizeof("Finding"); - const char *path; - char mBuff[512], nBuff[512]; - -// Check if this is a lookup or open issue. Trim message for the common case. -// - if (mSym) - {if (!txt1 || strstr(txt1, "undefined")) - {txt1 = "Unable to find "; - snprintf(nBuff, sizeof(nBuff), txt2, mSym); - } else { - strcpy(nBuff, fndg); - snprintf(nBuff+flen-1,sizeof(nBuff)-flen,txt2,mSym); - } - txt2 = nBuff; - } - else if (!txt1) txt1 = "Unknown system error!"; - else if (strstr(txt1, "No such file")) txt1 = "No such file or directory"; - else txt2 = " "; - -// Spit out the message -// - path = msgSuffix(txt2, mBuff, sizeof(mBuff)); - Inform(txt1, mBuff, path, 0, 0, 1); - return cvBad; -} - -/******************************************************************************/ -/* Private: m s g S u f f i x */ -/******************************************************************************/ - -const char *XrdSysPlugin::msgSuffix(const char *Word, char *buff, int bsz) -{ - if (libPath) snprintf(buff, bsz,"%s%s ", Word, libName); - else snprintf(buff, bsz,"%sexecutable image", Word); - return (libPath ? libPath : ""); -} - -/******************************************************************************/ -/* P r e l o a d */ -/******************************************************************************/ - -bool XrdSysPlugin::Preload(const char *path, char *ebuff, int eblen) -{ - struct PLlist *plP; - void *myHandle; - -// First see if this is already in the preload list -// - if (Find(path)) return true; - -// Try to open the library -// - if (!(myHandle = dlopen(path, DLflags()))) - {if (ebuff && eblen > 0) - {const char *dlMsg = dlerror(); - snprintf(ebuff, eblen, "Plugin unable to load %s; %s", path, - (dlMsg ? dlMsg : "unknown system error")); - } - return false; - } - -// Add the library handle -// - plP = new PLlist; - plP->libHandle = myHandle; - plP->libPath = strdup(path); - plP->next = plList; - plList = plP; - -// All done -// - return true; -} - -/******************************************************************************/ -/* V e r C m p */ -/******************************************************************************/ - -bool XrdSysPlugin::VerCmp(XrdVersionInfo &vInfo1, - XrdVersionInfo &vInfo2, bool noMsg) -{ - const char *mTxt; - char v1buff[128], v2buff[128]; - int unRel; - -// Do a quick return if the version need not be checked or are equal -// - if (vInfo1.vNum <= 0 || vInfo1.vNum == vInfo2.vNum) return true; - -// As it works out, many times two modules wind up in different shared -// libraries. For consistency we require that both major.minor version be the -// same unless either is unreleased (i.e. test). Issue warning if need be. -// - mTxt = (vInfo1.vNum == XrdVNUMUNK ? "unreleased " : ""); - sprintf(v1buff, " %sversion %s", mTxt, vInfo1.vStr); - unRel = *mTxt; - - mTxt = (vInfo2.vNum == XrdVNUMUNK ? "unreleased " : ""); - sprintf(v2buff, " %sversion %s", mTxt, vInfo2.vStr); - unRel |= *mTxt; - - if (unRel || vInfo1.vNum/100 == vInfo2.vNum/100) mTxt = ""; - else mTxt = " which is incompatible!"; - - if (!noMsg) - cerr <<"Plugin: " <. */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -struct XrdVersionInfo; - -class XrdSysError; - -//------------------------------------------------------------------------------ -//! Handy class to load run-time plugins and optionally check if the version -//! is compatible with the caller's version number. Version numbers are defined -//! as "aaa.bb.cc" where aaa is a decimnal major version, bb is a decimal minor -//! version, and cc is the decimal patch version number. Only the major and, -//! optionally, minor version numbers are checked. The checking rules are -//! defined in XrdVersion.hh and are rather liberal in nature. In order to -//! check versions, the plugin versioning rule must be defined in XrdVersion.hh -//! and constructor #2 or #3 must be used. The symbolic name of the plugin's -//! version information is the plugin symbol being looked up appended with -//! "Version" and must be defined as an XrdVersionInfo structure. -//------------------------------------------------------------------------------ - -class XrdSysPlugin -{ -public: - -//------------------------------------------------------------------------------ -//! Get the address of a plugin from a shared library, opening the plug-in -//! shared library if not already open. Symbols in the library are local. -//! -//! @param pname the plug-in extern "C" symbolic name -//! @param optional when 0 then issue error message when symbol isn't found. -//! Otherwise, the mising symbol is treated as an error. -//! -//! @return Success: the address of the symbol in the shared library/executable. -//! The address becomes invalid when this object is deleted -//! unless Persist() is called prior to deletion. -//! Failure: Null -//------------------------------------------------------------------------------ - -void *getPlugin(const char *pname, int optional=0); - -//------------------------------------------------------------------------------ -//! Get the address of a plugin from a shared library, opening the plug-in -//! shared library if not already open and optionally make the symbols global. -//! -//! @param pname the plug-in extern "C" symbolic name -//! @param optional when 0 then issue error message when symbol isn't found. -//! Otherwise, the mising symbol is treated as an error. When -//! optional is greater than 1, the load message is suppressed. -//! @param global when !0 then the symbols defined in the plug-in shared -//! library are made available for symbol resolution of -//! subsequently loaded libraries. -//! @return Success: the address of the symbol in the shared library/executable. -//! The address becomes invalid when this object is deleted -//! unless Persist() is called prior to deletion. -//! Failure: Null -//------------------------------------------------------------------------------ - -void *getPlugin(const char *pname, int optional, bool global); - -//------------------------------------------------------------------------------ -//! Make library persistent even when the plugin object is deleted. Note that -//! if getPlugin() is called afterwards, the library will be re-opened! -//! -//! @return pointer to the opened shared library. -//------------------------------------------------------------------------------ - -void *Persist() {void *lHan = libHandle; libHandle = 0; return lHan;} - -//------------------------------------------------------------------------------ -//! Preload a shared library. This method is meant for those threading models -//! that require libraries to be opened in the main thread (e.g. MacOS). This -//! method is meant to be called before therads start and is not thread-safe. -//! -//! @param path -> to the library path, typically this should just be the -//! library filename so that LD_LIBRARY_PATH is used to -//! discover the directory path. This allows getPlugin() -//! to properly match preloaded libraries. -//! @param ebuff -> buffer where eror message is to be placed. The mesage -//! will always end with a null byte. If no error buffer -//! is supplied, any error messages are discarded. -//! @param eblen -> length of the supplied buffer, eBuff. -//! -//! @return True The library was preloaded. -//! False The library could not be preloaded, ebuff, if supplied, -//! contains the error message text. -//------------------------------------------------------------------------------ - -static -bool Preload(const char *path, char *ebuff=0, int eblen=0); - -//------------------------------------------------------------------------------ -//! Compare two versions for compatability, optionally printing a warning. -//! -//! @param vInf1 -> Version information for source. -//! @param vInf2 -> Version information for target. -//! @param noMsg -> If true, no error messages are written to stderr. -//! -//! @return True if versions are compatible (i.e. major and minor versions are -//! identical as required for locally linked code); false otherwise. -//------------------------------------------------------------------------------ - -static -bool VerCmp(XrdVersionInfo &vInf1, XrdVersionInfo &vInf2, bool noMsg=false); - -//------------------------------------------------------------------------------ -//! Constructor #1 (version number checking is not to be performed) -//! -//! @param erp -> error message object to display error messages. -//! @param path -> path to the shared library containing a plug-in. If NULL -//! the the executable image is searched for the plug-in. -//! Storage must persist while this object is alive. -//------------------------------------------------------------------------------ - - XrdSysPlugin(XrdSysError *erp, const char *path) - : eDest(erp), libName(0), libPath(path ? strdup(path) : 0), - libHandle(0), myInfo(0), eBuff(0), eBLen(0), msgCnt(-1) {} - -//------------------------------------------------------------------------------ -//! Constructor #2 (version number checking may be performed) -//! -//! @param erp -> error message object to display error messages. -//! @param path -> path to the shared library containing a plug-in. If NULL -//! the the executable image is searched for the plug-in. -//! Storage must persist while this object is alive. -//! @param lname -> logical name of the plugin library (e.g. osslib) to be -//! used in any error messages. -//! Storage must persist while this object is alive. -//! @param vinf -> permanent version information of the plug-in loader. -//! If zero, then no version checking is performed. -//! @param msgNum -> Number of times getPlugin() is to produce a version -//! message for a loaded plugin. The default is always. -//------------------------------------------------------------------------------ - - XrdSysPlugin(XrdSysError *erp, const char *path, const char *lname, - XrdVersionInfo *vinf=0, int msgNum=-1) - : eDest(erp), libName(lname), - libPath(path ? strdup(path) : 0), libHandle(0), - myInfo(vinf), eBuff(0), eBLen(0), msgCnt(msgNum) {} - -//------------------------------------------------------------------------------ -//! Constructor #3 (version number checking may be performed and any error -//! is returned in a supplied buffer) -//! -//! @param ebuff -> buffer where eror message is to be placed. The mesage -//! will always end with a null byte. -//! @param eblen -> length of the supplied buffer, eBuff. -//! @param path -> path to the shared library containing a plug-in. If NULL -//! the the executable image is searched for the plug-in. -//! Storage must persist while this object is alive. -//! @param lname -> logical name of the plugin library (e.g. osslib) to be -//! used in any error messages. -//! Storage must persist while this object is alive. -//! @param vinf -> permanent version information of the plug-in loader. -//! If Zero, then no version checking is performed. -//! @param msgNum -> Number of times getPlugin() is to produce a version -//! message for a loaded plugin. The default is always. -//------------------------------------------------------------------------------ - - XrdSysPlugin(char *ebuff, int eblen, const char *path, const char *lname, - XrdVersionInfo *vinf=0, int msgNum=-1) - : eDest(0), libName(lname), - libPath(path ? strdup(path) : 0), libHandle(0), - myInfo(vinf), eBuff(ebuff), eBLen(eblen), msgCnt(msgNum) {} - -//------------------------------------------------------------------------------ -//! Destructor -//------------------------------------------------------------------------------ - - ~XrdSysPlugin(); - -private: -enum cvResult {cvBad = 0, cvNone, cvMissing, cvClean, cvDirty}; - -cvResult badVersion(XrdVersionInfo &urInfo,char mmv,int majv,int minv); -cvResult chkVersion(XrdVersionInfo &urInfo, const char *pname, void *lh); -static int DLflags(); -static void *Find(const char *libname); -void Inform(const char *txt1, const char *txt2=0, const char *txt3=0, - const char *txt4=0, const char *txt5=0, int noHush=0); -cvResult libMsg(const char *txt1, const char *txt2, const char *mSym=0); -const char *msgSuffix(const char *Word, char *buff, int bsz); - -XrdSysError *eDest; -const char *libName; -char *libPath; -void *libHandle; -XrdVersionInfo *myInfo; -char *eBuff; -int eBLen; -int msgCnt; - -struct PLlist {PLlist *next; - char *libPath; - void *libHandle; - }; - -static PLlist *plList; -}; -#endif diff --git a/src/XrdSys/XrdSysPriv.cc b/src/XrdSys/XrdSysPriv.cc deleted file mode 100644 index f17e974ef68..00000000000 --- a/src/XrdSys/XrdSysPriv.cc +++ /dev/null @@ -1,424 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S y s P r i v . c c */ -/* */ -/* (c) 2006 G. Ganis (CERN) */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/* All Rights Reserved. See XrdInfo.cc for complete License Terms */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// XrdSysPriv // -// // -// Author: G. Ganis, CERN, 2006 // -// // -// Implementation of a privileges handling API following the paper // -// "Setuid Demystified" by H.Chen, D.Wagner, D.Dean // -// also quoted in "Secure programming Cookbook" by J.Viega & M.Messier. // -// // -////////////////////////////////////////////////////////////////////////// - -#include "XrdSys/XrdSysPriv.hh" - -#if !defined(WINDOWS) -#include -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdSys/XrdSysPwd.hh" -#include -#include -#include - -#define NOUC ((uid_t)(-1)) -#define NOGC ((gid_t)(-1)) -#define XSPERR(x) ((x == 0) ? -1 : -x) - -// Some machine specific stuff -#if defined(__sgi) && !defined(__GNUG__) && (SGI_REL<62) -extern "C" { - int seteuid(int euid); - int setegid(int egid); - int geteuid(); - int getegid(); -} -#endif - -#if defined(_AIX) -extern "C" { - int seteuid(uid_t euid); - int setegid(gid_t egid); - uid_t geteuid(); - gid_t getegid(); -} -#endif - -#if !defined(HAVE_SETRESUID) -static int setresgid(gid_t r, gid_t e, gid_t) -{ - if (r != NOGC && setgid(r) == -1) - return XSPERR(errno); - return ((e != NOGC) ? setegid(e) : 0); -} - -static int setresuid(uid_t r, uid_t e, uid_t) -{ - if (r != NOUC && setuid(r) == -1) - return XSPERR(errno); - return ((e != NOUC) ? seteuid(e) : 0); -} - -static int getresgid(gid_t *r, gid_t *e, gid_t *) -{ - *r = getgid(); - *e = getegid(); - return 0; -} - -static int getresuid(uid_t *r, uid_t *e, uid_t *) -{ - *r = getuid(); - *e = geteuid(); - return 0; -} - -#else -#if (defined(__linux__) || \ - (defined(__CYGWIN__) && defined(__GNUC__))) && !defined(linux) -# define linux -#endif -#if defined(linux) && !defined(HAVE_SETRESUID) -extern "C" { - int setresgid(gid_t r, gid_t e, gid_t s); - int setresuid(uid_t r, uid_t e, uid_t s); - int getresgid(gid_t *r, gid_t *e, gid_t *s); - int getresuid(uid_t *r, uid_t *e, uid_t *s); -} -#endif -#endif -#endif // not WINDOWS - -bool XrdSysPriv::fDebug = 0; // debug switch - -// Gloval mutex -XrdSysRecMutex XrdSysPriv::fgMutex; - -//______________________________________________________________________________ -int XrdSysPriv::Restore(bool saved) -{ - // Restore the 'saved' (saved = TRUE) or 'real' entity as effective. - // Return 0 on success, < 0 (== -errno) if any error occurs. - -#if !defined(WINDOWS) - // Get the UIDs - uid_t ruid = 0, euid = 0, suid = 0; - if (getresuid(&ruid, &euid, &suid) != 0) - return XSPERR(errno); - - // Set the wanted value - uid_t uid = saved ? suid : ruid; - - // Act only if a change is needed - if (euid != uid) { - - // Set uid as effective - if (setresuid(NOUC, uid, NOUC) != 0) - return XSPERR(errno); - - // Make sure the new effective UID is the one wanted - if (geteuid() != uid) - return XSPERR(errno); - } - - // Get the GIDs - uid_t rgid = 0, egid = 0, sgid = 0; - if (getresgid(&rgid, &egid, &sgid) != 0) - return XSPERR(errno); - - // Set the wanted value - gid_t gid = saved ? sgid : rgid; - - // Act only if a change is needed - if (egid != gid) { - - // Set newuid as effective, saving the current effective GID - if (setresgid(NOGC, gid, NOGC) != 0) - return XSPERR(errno); - - // Make sure the new effective GID is the one wanted - if (getegid() != gid) - return XSPERR(errno); - } - -#endif - // Done - return 0; -} - -//______________________________________________________________________________ -int XrdSysPriv::ChangeTo(uid_t newuid, gid_t newgid) -{ - // Change effective to entity newuid. Current entity is saved. - // Real entity is not touched. Use RestoreSaved to go back to - // previous settings. - // Return 0 on success, < 0 (== -errno) if any error occurs. - -#if !defined(WINDOWS) - // Current UGID - uid_t oeuid = geteuid(); - gid_t oegid = getegid(); - - // Restore privileges, if needed - if (oeuid && XrdSysPriv::Restore(0) != 0) - return XSPERR(errno); - - // Act only if a change is needed - if (newgid != oegid) { - - // Set newgid as effective, saving the current effective GID - if (setresgid(NOGC, newgid, oegid) != 0) - return XSPERR(errno); - - // Get the GIDs - uid_t rgid = 0, egid = 0, sgid = 0; - if (getresgid(&rgid, &egid, &sgid) != 0) - return XSPERR(errno); - - // Make sure the new effective GID is the one wanted - if (egid != newgid) - return XSPERR(errno); - } - - // Act only if a change is needed - if (newuid != oeuid) { - - // Set newuid as effective, saving the current effective UID - if (setresuid(NOUC, newuid, oeuid) != 0) - return XSPERR(errno); - - // Get the UIDs - uid_t ruid = 0, euid = 0, suid = 0; - if (getresuid(&ruid, &euid, &suid) != 0) - return XSPERR(errno); - - // Make sure the new effective UID is the one wanted - if (euid != newuid) - return XSPERR(errno); - } - -#endif - // Done - return 0; -} - -//______________________________________________________________________________ -int XrdSysPriv::ChangePerm(uid_t newuid, gid_t newgid) -{ - // Change permanently to entity newuid. Requires super-userprivileges. - // Provides a way to drop permanently su privileges. - // Return 0 on success, < 0 (== -errno) if any error occurs. - - // Atomic action - XrdSysPriv::fgMutex.Lock(); -#if !defined(WINDOWS) - // Get UIDs - uid_t cruid = 0, ceuid = 0, csuid = 0; - if (getresuid(&cruid, &ceuid, &csuid) != 0) { - XrdSysPriv::fgMutex.UnLock(); - return XSPERR(errno); - } - - // Get GIDs - uid_t crgid = 0, cegid = 0, csgid = 0; - if (getresgid(&crgid, &cegid, &csgid) != 0) { - XrdSysPriv::fgMutex.UnLock(); - return XSPERR(errno); - } - // Restore privileges, if needed - if (ceuid && XrdSysPriv::Restore(0) != 0) { - XrdSysPriv::fgMutex.UnLock(); - return XSPERR(errno); - } - // Act only if needed - if (newgid != cegid || newgid != crgid) { - - // Set newgid as GID, all levels - if (setresgid(newgid, newgid, newgid) != 0) { - XrdSysPriv::fgMutex.UnLock(); - return XSPERR(errno); - } - // Get GIDs - uid_t rgid = 0, egid = 0, sgid = 0; - if (getresgid(&rgid, &egid, &sgid) != 0) { - XrdSysPriv::fgMutex.UnLock(); - return XSPERR(errno); - } - // Make sure the new GIDs are all equal to the one asked - if (rgid != newgid || egid != newgid) { - XrdSysPriv::fgMutex.UnLock(); - return XSPERR(errno); - } - } - - // Act only if needed - if (newuid != ceuid || newuid != cruid) { - - // Set newuid as UID, all levels - if (setresuid(newuid, newuid, newuid) != 0) { - XrdSysPriv::fgMutex.UnLock(); - return XSPERR(errno); - } - // Get UIDs - uid_t ruid = 0, euid = 0, suid = 0; - if (getresuid(&ruid, &euid, &suid) != 0) { - XrdSysPriv::fgMutex.UnLock(); - return XSPERR(errno); - } - // Make sure the new UIDs are all equal to the one asked - if (ruid != newuid || euid != newuid) { - XrdSysPriv::fgMutex.UnLock(); - return XSPERR(errno); - } - } -#endif - // Release the mutex - XrdSysPriv::fgMutex.UnLock(); - - // Done - return 0; -} - -//______________________________________________________________________________ -void XrdSysPriv::DumpUGID(const char *msg) -{ - // Dump current entity - -#if !defined(WINDOWS) - XrdSysPriv::fgMutex.Lock(); - // Get the UIDs - uid_t ruid = 0, euid = 0, suid = 0; - if (getresuid(&ruid, &euid, &suid) != 0) - return; - - // Get the GIDs - uid_t rgid = 0, egid = 0, sgid = 0; - if (getresgid(&rgid, &egid, &sgid) != 0) - return; - - cout << "XrdSysPriv: " << endl; - cout << "XrdSysPriv: dump values: " << (msg ? msg : "") << endl; - cout << "XrdSysPriv: " << endl; - cout << "XrdSysPriv: real = (" << ruid <<","<< rgid <<")" << endl; - cout << "XrdSysPriv: effective = (" << euid <<","<< egid <<")" << endl; - cout << "XrdSysPriv: saved = (" << suid <<","<< sgid <<")" << endl; - cout << "XrdSysPriv: " << endl; - XrdSysPriv::fgMutex.UnLock(); -#endif -} - -// -// Guard class -//______________________________________________________________________________ -XrdSysPrivGuard::XrdSysPrivGuard(uid_t uid, gid_t gid) -{ - // Constructor. Create a guard object for temporarly change to privileges - // of {'uid', 'gid'} - - dum = 1; - valid = 0; - - Init(uid, gid); -} - -//______________________________________________________________________________ -XrdSysPrivGuard::XrdSysPrivGuard(const char *usr) -{ - // Constructor. Create a guard object for temporarly change to privileges - // of 'usr' - - dum = 1; - valid = 0; - -#if !defined(WINDOWS) - if (usr && strlen(usr) > 0) { - struct passwd *pw; - XrdSysPwd thePwd(usr, &pw); - if (pw) - Init(pw->pw_uid, pw->pw_gid); - } -#else - if (usr) { } -#endif -} - -//______________________________________________________________________________ -XrdSysPrivGuard::~XrdSysPrivGuard() -{ - // Destructor. Restore state and unlock the global mutex. - - if (!dum) { - XrdSysPriv::Restore(); - XrdSysPriv::fgMutex.UnLock(); - } -} - -//______________________________________________________________________________ -void XrdSysPrivGuard::Init(uid_t uid, gid_t gid) -{ - // Init a change of privileges guard. Act only if superuser. - // The result of initialization can be tested with the Valid() method. - - dum = 1; - valid = 1; - - // Debug hook - if (XrdSysPriv::fDebug) - XrdSysPriv::DumpUGID("before Init()"); - -#if !defined(WINDOWS) - XrdSysPriv::fgMutex.Lock(); - uid_t ruid = 0, euid = 0, suid = 0; - gid_t rgid = 0, egid = 0, sgid = 0; - if (getresuid(&ruid, &euid, &suid) == 0 && - getresgid(&rgid, &egid, &sgid) == 0) { - if ((euid != uid) || (egid != gid)) { - if (!ruid) { - // Change temporarly identity - if (XrdSysPriv::ChangeTo(uid, gid) != 0) - valid = 0; - dum = 0; - } else { - // Change requested but not enough privileges - valid = 0; - } - } - } else { - // Something bad happened: memory corruption? - valid = 0; - } - // Unlock if no action - if (dum) - XrdSysPriv::fgMutex.UnLock(); -#endif - // Debug hook - if (XrdSysPriv::fDebug) - XrdSysPriv::DumpUGID("after Init()"); -} diff --git a/src/XrdSys/XrdSysPriv.hh b/src/XrdSys/XrdSysPriv.hh deleted file mode 100644 index a238e5956c8..00000000000 --- a/src/XrdSys/XrdSysPriv.hh +++ /dev/null @@ -1,99 +0,0 @@ -#ifndef __SYS_PRIV_H__ -#define __SYS_PRIV_H__ -/******************************************************************************/ -/* */ -/* X r d S y s P r i v . h h */ -/* */ -/* (c) 2006 G. Ganis (CERN) */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/* All Rights Reserved. See XrdInfo.cc for complete License Terms */ -/******************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// // -// XrdSysPriv // -// // -// Author: G. Ganis, CERN, 2006 // -// // -// Implementation of a privileges handling API following the paper // -// "Setuid Demystified" by H.Chen, D.Wagner, D.Dean // -// also quoted in "Secure programming Cookbook" by J.Viega & M.Messier. // -// // -// NB: this class can only used via XrdSysPrivGuard (see below) // -// // -////////////////////////////////////////////////////////////////////////// - -#if !defined(WINDOWS) -# include -#else -# define uid_t unsigned int -# define gid_t unsigned int -#endif - -#include "XrdSys/XrdSysPthread.hh" - -class XrdSysPriv -{ - friend class XrdSysPrivGuard; - private: - // Ownership cannot be changed by thread, so there must be an overall - // locking - static XrdSysRecMutex fgMutex; - - XrdSysPriv(); - - static bool fDebug; - - static int ChangeTo(uid_t uid, gid_t gid); - static void DumpUGID(const char *msg = 0); - static int Restore(bool saved = 1); - - public: - virtual ~XrdSysPriv() { } - static int ChangePerm(uid_t uid, gid_t gid); -}; - -// -// Guard class; -// Usage: -// -// { XrdSysPrivGuard priv(tempuid); -// -// // Work as tempuid (maybe superuser) -// ... -// -// } -// -class XrdSysPrivGuard -{ - public: - XrdSysPrivGuard(uid_t uid, gid_t gid); - XrdSysPrivGuard(const char *user); - virtual ~XrdSysPrivGuard(); - bool Valid() const { return valid; } - private: - bool dum; - bool valid; - void Init(uid_t uid, gid_t gid); -}; - -#endif diff --git a/src/XrdSys/XrdSysPthread.cc b/src/XrdSys/XrdSysPthread.cc deleted file mode 100644 index 99034cc7a29..00000000000 --- a/src/XrdSys/XrdSysPthread.cc +++ /dev/null @@ -1,324 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S y s P t h r e a d . c c */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#ifndef WIN32 -#include -#include -#else -#undef ETIMEDOUT // Make sure that the definition from Winsock2.h is used ... -#include -#include -#include "XrdSys/XrdWin32.hh" -#endif -#include -#if defined(__linux__) -#include -#endif - -#include "XrdSys/XrdSysPthread.hh" - -/******************************************************************************/ -/* L o c a l S t r u c t s */ -/******************************************************************************/ - -struct XrdSysThreadArgs - { - XrdSysError *eDest; - const char *tDesc; - void *(*proc)(void *); - void *arg; - - XrdSysThreadArgs(XrdSysError *ed, const char *td, - void *(*p)(void *), void *a) - : eDest(ed), tDesc(td), proc(p), arg(a) {} - ~XrdSysThreadArgs() {} - }; - -/******************************************************************************/ -/* G l o b a l D a t a */ -/******************************************************************************/ - -XrdSysError *XrdSysThread::eDest = 0; - -size_t XrdSysThread::stackSize = 0; - -/******************************************************************************/ -/* T h r e a d I n t e r f a c e P r o g r a m s */ -/******************************************************************************/ - -extern "C" -{ -void *XrdSysThread_Xeq(void *myargs) -{ - XrdSysThreadArgs *ap = (XrdSysThreadArgs *)myargs; - void *retc; - - if (ap->eDest && ap->tDesc) - ap->eDest->Emsg("Xeq", ap->tDesc, "thread started"); - retc = ap->proc(ap->arg); - delete ap; - return retc; -} -} - -/******************************************************************************/ -/* X r d S y s C o n d V a r */ -/******************************************************************************/ -/******************************************************************************/ -/* W a i t */ -/******************************************************************************/ - -int XrdSysCondVar::Wait() -{ - int retc; - -// Wait for the condition -// - if (relMutex) Lock(); - retc = pthread_cond_wait(&cvar, &cmut); - if (relMutex) UnLock(); - return retc; -} - -/******************************************************************************/ - -int XrdSysCondVar::Wait(int sec) {return WaitMS(sec*1000);} - -/******************************************************************************/ -/* W a i t M S */ -/******************************************************************************/ - -int XrdSysCondVar::WaitMS(int msec) -{ - int sec, retc, usec; - struct timeval tnow; - struct timespec tval; - -// Adjust millseconds -// - if (msec < 1000) sec = 0; - else {sec = msec / 1000; msec = msec % 1000;} - usec = msec * 1000; - -// Get the mutex before getting the time -// - if (relMutex) Lock(); - -// Get current time of day -// - gettimeofday(&tnow, 0); - -// Add the second and microseconds -// - tval.tv_sec = tnow.tv_sec + sec; - tval.tv_nsec = tnow.tv_usec + usec; - if (tval.tv_nsec >= 1000000) - {tval.tv_sec += tval.tv_nsec / 1000000; - tval.tv_nsec = tval.tv_nsec % 1000000; - } - tval.tv_nsec *= 1000; - - -// Now wait for the condition or timeout -// - do {retc = pthread_cond_timedwait(&cvar, &cmut, &tval);} - while (retc && (retc == EINTR)); - - if (relMutex) UnLock(); - -// Determine how to return -// - if (retc && retc != ETIMEDOUT) {throw "cond_timedwait() failed";} - return retc == ETIMEDOUT; -} - -/******************************************************************************/ -/* X r d S y s S e m a p h o r e */ -/******************************************************************************/ -/******************************************************************************/ -/* C o n d W a i t */ -/******************************************************************************/ - -#ifdef __APPLE__ - -int XrdSysSemaphore::CondWait() -{ - int rc; - -// Get the semaphore only we can get it without waiting -// - semVar.Lock(); - if ((rc = (semVal > 0) && !semWait)) semVal--; - semVar.UnLock(); - return rc; -} - -/******************************************************************************/ -/* P o s t */ -/******************************************************************************/ - -void XrdSysSemaphore::Post() -{ -// Add one to the semaphore counter. If we the value is > 0 and there is a -// thread waiting for the sempagore, signal it to get the semaphore. -// - semVar.Lock(); - semVal++; - if (semVal && semWait) semVar.Signal(); - semVar.UnLock(); -} - -/******************************************************************************/ -/* W a i t */ -/******************************************************************************/ - -void XrdSysSemaphore::Wait() -{ - -// Wait until the semaphore value is positive. This will not be starvation -// free if the OS implements an unfair mutex. -// Adding a cleanup handler to the stack here enables threads using this OSX -// semaphore to be canceled (which is rare). A scoped lock won't work here -// because OSX is broken and doesn't call destructors properly. -// - semVar.Lock(); - pthread_cleanup_push(&XrdSysSemaphore::CleanUp, (void *) &semVar); - if (semVal < 1 || semWait) - while(semVal < 1) - {semWait++; - semVar.Wait(); - semWait--; - } - -// Decrement the semaphore value, unlock the underlying cond var and return -// - semVal--; - pthread_cleanup_pop(1); -} - -/******************************************************************************/ -/* C l e a n U p */ -/******************************************************************************/ - -void XrdSysSemaphore::CleanUp(void *semVar) -{ - XrdSysCondVar *sv = (XrdSysCondVar *) semVar; - sv->UnLock(); -} -#endif - -/******************************************************************************/ -/* T h r e a d M e t h o d s */ -/******************************************************************************/ -/******************************************************************************/ -/* N u m */ -/******************************************************************************/ - -unsigned long XrdSysThread::Num() -{ -#if defined(__linux__) - return static_cast(syscall(SYS_gettid)); -#elif defined(__solaris__) - return static_cast(pthread_self()); -#elif defined(__APPLE__) - return static_cast(pthread_mach_thread_np(pthread_self())); -#else - return static_cast(getpid()); -#endif -} - -/******************************************************************************/ -/* R u n */ -/******************************************************************************/ - -int XrdSysThread::Run(pthread_t *tid, void *(*proc)(void *), void *arg, - int opts, const char *tDesc) -{ - pthread_attr_t tattr; - XrdSysThreadArgs *myargs; - - myargs = new XrdSysThreadArgs(eDest, tDesc, proc, arg); - - pthread_attr_init(&tattr); - if ( opts & XRDSYSTHREAD_BIND) - pthread_attr_setscope(&tattr, PTHREAD_SCOPE_SYSTEM); - if (!(opts & XRDSYSTHREAD_HOLD)) - pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED); - if (stackSize) - pthread_attr_setstacksize(&tattr, stackSize); - return pthread_create(tid, &tattr, XrdSysThread_Xeq, - static_cast(myargs)); -} - -/******************************************************************************/ -/* W a i t */ -/******************************************************************************/ - -int XrdSysThread::Wait(pthread_t tid) -{ - int retc, *tstat; - if ((retc = pthread_join(tid, reinterpret_cast(&tstat)))) return retc; - return *tstat; -} - - - -/******************************************************************************/ -/* X r d S y s R e c M u t e x */ -/******************************************************************************/ -XrdSysRecMutex::XrdSysRecMutex() -{ - InitRecMutex(); -} - -int XrdSysRecMutex::InitRecMutex() -{ - int rc; - pthread_mutexattr_t attr; - - rc = pthread_mutexattr_init( &attr ); - - if( !rc ) - { - pthread_mutexattr_settype( &attr, PTHREAD_MUTEX_RECURSIVE ); - pthread_mutex_destroy( &cs ); - rc = pthread_mutex_init( &cs, &attr ); - } - - pthread_mutexattr_destroy(&attr); - return rc; -} - -int XrdSysRecMutex::ReInitRecMutex() -{ - pthread_mutex_destroy( &cs ); - return InitRecMutex(); -} diff --git a/src/XrdSys/XrdSysPthread.hh b/src/XrdSys/XrdSysPthread.hh deleted file mode 100644 index 6de3c5d9b29..00000000000 --- a/src/XrdSys/XrdSysPthread.hh +++ /dev/null @@ -1,451 +0,0 @@ -#ifndef __SYS_PTHREAD__ -#define __SYS_PTHREAD__ -/******************************************************************************/ -/* */ -/* X r d S y s P t h r e a d . h h */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#ifdef WIN32 -#define HAVE_STRUCT_TIMESPEC 1 -#endif -#include -#include -#ifdef AIX -#include -#else -#include -#endif - -#include "XrdSys/XrdSysError.hh" - -/******************************************************************************/ -/* X r d S y s C o n d V a r */ -/******************************************************************************/ - -// XrdSysCondVar implements the standard POSIX-compliant condition variable. -// Methods correspond to the equivalent pthread condvar functions. - -class XrdSysCondVar -{ -public: - -inline void Lock() {pthread_mutex_lock(&cmut);} - -inline void Signal() {if (relMutex) pthread_mutex_lock(&cmut); - pthread_cond_signal(&cvar); - if (relMutex) pthread_mutex_unlock(&cmut); - } - -inline void Broadcast() {if (relMutex) pthread_mutex_lock(&cmut); - pthread_cond_broadcast(&cvar); - if (relMutex) pthread_mutex_unlock(&cmut); - } - -inline void UnLock() {pthread_mutex_unlock(&cmut);} - - int Wait(); - int Wait(int sec); - int WaitMS(int msec); - - XrdSysCondVar( int relm=1, // 0->Caller will handle lock/unlock - const char *cid=0 // ID string for debugging only - ) {pthread_cond_init(&cvar, NULL); - pthread_mutex_init(&cmut, NULL); - relMutex = relm; condID = (cid ? cid : "unk"); - } - ~XrdSysCondVar() {pthread_cond_destroy(&cvar); - pthread_mutex_destroy(&cmut); - } -private: - -pthread_cond_t cvar; -pthread_mutex_t cmut; -int relMutex; -const char *condID; -}; - - - -/******************************************************************************/ -/* X r d S y s C o n d V a r H e l p e r */ -/******************************************************************************/ - -// XrdSysCondVarHelper is used to implement monitors with the Lock of a a condvar. -// Monitors are used to lock -// whole regions of code (e.g., a method) and automatically -// unlock with exiting the region (e.g., return). The -// methods should be self-evident. - -class XrdSysCondVarHelper -{ -public: - -inline void Lock(XrdSysCondVar *CndVar) - {if (cnd) {if (cnd != CndVar) cnd->UnLock(); - else return; - } - CndVar->Lock(); - cnd = CndVar; - }; - -inline void UnLock() {if (cnd) {cnd->UnLock(); cnd = 0;}} - - XrdSysCondVarHelper(XrdSysCondVar *CndVar=0) - {if (CndVar) CndVar->Lock(); - cnd = CndVar; - } - XrdSysCondVarHelper(XrdSysCondVar &CndVar) - {CndVar.Lock(); - cnd = &CndVar; - } - - ~XrdSysCondVarHelper() {if (cnd) UnLock();} -private: -XrdSysCondVar *cnd; -}; - - -/******************************************************************************/ -/* X r d S y s M u t e x */ -/******************************************************************************/ - -// XrdSysMutex implements the standard POSIX mutex. The methods correspond -// to the equivalent pthread mutex functions. - -class XrdSysMutex -{ -public: - -inline int CondLock() - {if (pthread_mutex_trylock( &cs )) return 0; - return 1; - } - -inline void Lock() {pthread_mutex_lock(&cs);} - -inline void UnLock() {pthread_mutex_unlock(&cs);} - - XrdSysMutex() {pthread_mutex_init(&cs, NULL);} - ~XrdSysMutex() {pthread_mutex_destroy(&cs);} - -protected: - -pthread_mutex_t cs; -}; - -/******************************************************************************/ -/* X r d S y s R e c M u t e x */ -/******************************************************************************/ - -// XrdSysRecMutex implements the recursive POSIX mutex. The methods correspond -// to the equivalent pthread mutex functions. - -class XrdSysRecMutex: public XrdSysMutex -{ -public: - -XrdSysRecMutex(); - -int InitRecMutex(); -int ReInitRecMutex(); - -}; - - -/******************************************************************************/ -/* X r d S y s M u t e x H e l p e r */ -/******************************************************************************/ - -// XrdSysMutexHelper us ised to implement monitors. Monitors are used to lock -// whole regions of code (e.g., a method) and automatically -// unlock with exiting the region (e.g., return). The -// methods should be self-evident. - -class XrdSysMutexHelper -{ -public: - -inline void Lock(XrdSysMutex *Mutex) - {if (mtx) {if (mtx != Mutex) mtx->UnLock(); - else return; - } - Mutex->Lock(); - mtx = Mutex; - }; - -inline void UnLock() {if (mtx) {mtx->UnLock(); mtx = 0;}} - - XrdSysMutexHelper(XrdSysMutex *mutex=0) - {if (mutex) mutex->Lock(); - mtx = mutex; - } - XrdSysMutexHelper(XrdSysMutex &mutex) - {mutex.Lock(); - mtx = &mutex; - } - - ~XrdSysMutexHelper() {if (mtx) UnLock();} -private: -XrdSysMutex *mtx; -}; - -/******************************************************************************/ -/* X r d S y s R W L o c k */ -/******************************************************************************/ - -// XrdSysRWLock implements the standard POSIX wrlock mutex. The methods correspond -// to the equivalent pthread wrlock functions. - -class XrdSysRWLock -{ -public: - -inline int CondReadLock() - {if (pthread_rwlock_tryrdlock( &lock )) return 0; - return 1; - } -inline int CondWriteLock() - {if (pthread_rwlock_trywrlock( &lock )) return 0; - return 1; - } - -inline void ReadLock() {pthread_rwlock_rdlock(&lock);} -inline void WriteLock() {pthread_rwlock_wrlock(&lock);} - -inline void ReadLock( int &status ) {status = pthread_rwlock_rdlock(&lock);} -inline void WriteLock( int &status ) {status = pthread_rwlock_wrlock(&lock);} - -inline void UnLock() {pthread_rwlock_unlock(&lock);} - - XrdSysRWLock() {pthread_rwlock_init(&lock, NULL);} - ~XrdSysRWLock() {pthread_rwlock_destroy(&lock);} - -inline void ReInitialize() -{ - pthread_rwlock_destroy(&lock); - pthread_rwlock_init(&lock, NULL); -} - -protected: - -pthread_rwlock_t lock; -}; - -/******************************************************************************/ -/* X r d S y s W R L o c k H e l p e r */ -/******************************************************************************/ - -// XrdSysWRLockHelper : helper class for XrdSysRWLock - -class XrdSysRWLockHelper -{ -public: - -inline void Lock(XrdSysRWLock *lock, bool rd = 1) - {if (lck) {if (lck != lock) lck->UnLock(); - else return; - } - if (rd) lock->ReadLock(); - else lock->WriteLock(); - lck = lock; - }; - -inline void UnLock() {if (lck) {lck->UnLock(); lck = 0;}} - - XrdSysRWLockHelper(XrdSysRWLock *l=0, bool rd = 1) - { if (l) {if (rd) l->ReadLock(); - else l->WriteLock(); - } - lck = l; - } - XrdSysRWLockHelper(XrdSysRWLock &l, bool rd = 1) - { if (rd) l.ReadLock(); - else l.WriteLock(); - lck = &l; - } - - ~XrdSysRWLockHelper() {if (lck) UnLock();} -private: -XrdSysRWLock *lck; -}; - -/******************************************************************************/ -/* X r d S y s S e m a p h o r e */ -/******************************************************************************/ - -// XrdSysSemaphore implements the classic counting semaphore. The methods -// should be self-evident. Note that on certain platforms -// semaphores need to be implemented based on condition -// variables since no native implementation is available. - -#ifdef __APPLE__ -class XrdSysSemaphore -{ -public: - - int CondWait(); - - void Post(); - - void Wait(); - -static void CleanUp(void *semVar); - - XrdSysSemaphore(int semval=1,const char *cid=0) : semVar(0, cid) - {semVal = semval; semWait = 0;} - ~XrdSysSemaphore() {} - -private: - -XrdSysCondVar semVar; -int semVal; -int semWait; -}; - -#else - -class XrdSysSemaphore -{ -public: - -inline int CondWait() - {while(sem_trywait( &h_semaphore )) - {if (errno == EAGAIN) return 0; - if (errno != EINTR) { throw "sem_CondWait() failed";} - } - return 1; - } - -inline void Post() {if (sem_post(&h_semaphore)) - {throw "sem_post() failed";} - } - -inline void Wait() {while (sem_wait(&h_semaphore)) - {if (EINTR != errno) - {throw "sem_wait() failed";} - } - } - - XrdSysSemaphore(int semval=1, const char * =0) - {if (sem_init(&h_semaphore, 0, semval)) - {throw "sem_init() failed";} - } - ~XrdSysSemaphore() {if (sem_destroy(&h_semaphore)) - {abort();} - } - -private: - -sem_t h_semaphore; -}; -#endif - -/******************************************************************************/ -/* X r d S y s T h r e a d */ -/******************************************************************************/ - -// The C++ standard makes it impossible to link extern "C" methods with C++ -// methods. Thus, making a full thread object is nearly impossible. So, this -// object is used as the thread manager. Since it is static for all intense -// and purposes, one does not need to create an instance of it. -// - -// Options to Run() -// -// BIND creates threads that are bound to a kernel thread. -// -#define XRDSYSTHREAD_BIND 0x001 - -// HOLD creates a thread that needs to be joined to get its ending value. -// Otherwise, a detached thread is created. -// -#define XRDSYSTHREAD_HOLD 0x002 - -class XrdSysThread -{ -public: - -static int Cancel(pthread_t tid) {return pthread_cancel(tid);} - -static int Detach(pthread_t tid) {return pthread_detach(tid);} - - -static int SetCancelOff() { - return pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, 0); - }; - -static int Join(pthread_t tid, void **ret) { - return pthread_join(tid, ret); - }; - -static int SetCancelOn() { - return pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, 0); - }; - -static int SetCancelAsynchronous() { - return pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, 0); - }; - -static int SetCancelDeferred() { - return pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, 0); - }; - -static void CancelPoint() { - pthread_testcancel(); - }; - - -static pthread_t ID(void) {return pthread_self();} - -static int Kill(pthread_t tid) {return pthread_cancel(tid);} - -static unsigned long Num(void); - -static int Run(pthread_t *, void *(*proc)(void *), void *arg, - int opts=0, const char *desc = 0); - -static int Same(pthread_t t1, pthread_t t2) - {return pthread_equal(t1, t2);} - -static void setDebug(XrdSysError *erp) {eDest = erp;} - -static void setStackSize(size_t stsz) {stackSize = stsz;} - -static int Signal(pthread_t tid, int snum) - {return pthread_kill(tid, snum);} - -static int Wait(pthread_t tid); - - XrdSysThread() {} - ~XrdSysThread() {} - -private: -static XrdSysError *eDest; -static size_t stackSize; -}; -#endif diff --git a/src/XrdSys/XrdSysPwd.hh b/src/XrdSys/XrdSysPwd.hh deleted file mode 100644 index 4355856b2d9..00000000000 --- a/src/XrdSys/XrdSysPwd.hh +++ /dev/null @@ -1,67 +0,0 @@ -#ifndef __XRDSYSPWD_HH__ -#define __XRDSYSPWD_HH__ -/******************************************************************************/ -/* */ -/* X r d S y s P w d . h h */ -/* */ -/* (c) 2011 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -class XrdSysPwd -{ -public: - -int rc; - -struct passwd *Get(const char *Usr) - {rc = getpwnam_r(Usr,&pwStruct,pwBuff,sizeof(pwBuff),&Ppw); - return Ppw; - } - -struct passwd *Get(uid_t Uid) - {rc = getpwuid_r(Uid,&pwStruct,pwBuff,sizeof(pwBuff),&Ppw); - return Ppw; - } - - XrdSysPwd() : rc(2) {} - - XrdSysPwd(const char *Usr, struct passwd **pwP) - {rc = getpwnam_r(Usr,&pwStruct,pwBuff,sizeof(pwBuff),pwP);} - - XrdSysPwd(uid_t Uid, struct passwd **pwP) - {rc = getpwuid_r(Uid,&pwStruct,pwBuff,sizeof(pwBuff),pwP);} - - ~XrdSysPwd() {} - -private: - -struct passwd pwStruct, *Ppw; -char pwBuff[4096]; -}; -#endif diff --git a/src/XrdSys/XrdSysSemWait.hh b/src/XrdSys/XrdSysSemWait.hh deleted file mode 100644 index f91e5eda4d5..00000000000 --- a/src/XrdSys/XrdSysSemWait.hh +++ /dev/null @@ -1,124 +0,0 @@ -#ifndef __SYS_SEMWAIT__ -#define __SYS_SEMWAIT__ - -/******************************************************************************/ -/* X r d S y s S e m W a i t */ -/* */ -/* Author: Fabrizio Furano (INFN, 2005) */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/* */ -/* A counting semaphore with timed out wait primitive */ -/******************************************************************************/ - -#include "XrdSys/XrdSysPthread.hh" - -class XrdSysSemWait { - public: - - int CondWait() { - - int rc = 0; - // Wait until the sempahore value is positive. This will not be starvation - // free is the OS implements an unfair mutex; - // Returns 0 if signalled, non-0 if would block - // - - semVar.Lock(); - if (semVal > 0) semVal--; - else { - rc = 1; - } - - semVar.UnLock(); - - return rc; - - }; - - void Post() { - // Add one to the semaphore counter. If we the value is > 0 and there is a - // thread waiting for the sempagore, signal it to get the semaphore. - // - semVar.Lock(); - - if (semWait > 0) { - semVar.Signal(); - semWait--; - } - else - semVal++; - - semVar.UnLock(); - }; - - void Wait() { - // Wait until the sempahore value is positive. This will not be starvation - // free is the OS implements an unfair mutex; - // - - semVar.Lock(); - if (semVal > 0) semVal--; - else { - semWait++; - semVar.Wait(); - } - - semVar.UnLock(); - - }; - - int Wait(int secs) { - int rc = 0; - // Wait until the sempahore value is positive. This will not be starvation - // free is the OS implements an unfair mutex; - // Returns 0 if signalled, non-0 if timeout - // - - semVar.Lock(); - if (semVal > 0) semVal--; - else { - semWait++; - rc = semVar.Wait(secs); - if (rc) semWait--; - } - - semVar.UnLock(); - - return rc; - }; - - XrdSysSemWait(int semval=1,const char *cid=0) : semVar(0, cid) { - semVal = semval; semWait = 0; - } - - ~XrdSysSemWait() {} - -private: - -XrdSysCondVar semVar; -int semVal; -int semWait; -}; - - - -#endif diff --git a/src/XrdSys/XrdSysTimer.cc b/src/XrdSys/XrdSysTimer.cc deleted file mode 100644 index 1f378adb4e8..00000000000 --- a/src/XrdSys/XrdSysTimer.cc +++ /dev/null @@ -1,272 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S y s T i m e r . c c */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#ifndef WIN32 -#include -#else -#include "XrdSys/XrdWin32.hh" -#endif -#include -#include -#include -#include "XrdSys/XrdSysTimer.hh" -#include - -/******************************************************************************/ -/* D e l t a _ T i m e */ -/******************************************************************************/ - -struct timeval *XrdSysTimer::Delta_Time(struct timeval &tbeg) -{ - gettimeofday(&LastReport, 0); - LastReport.tv_sec = LastReport.tv_sec - tbeg.tv_sec; - LastReport.tv_usec = LastReport.tv_usec - tbeg.tv_usec; - if (LastReport.tv_usec < 0) {LastReport.tv_sec--; LastReport.tv_usec += 1000000;} - return &LastReport; -} - -/******************************************************************************/ -/* M i d n i g h t */ -/******************************************************************************/ - -time_t XrdSysTimer::Midnight(time_t tnow) -{ - struct tm midtime; - time_t add_time; - -// Compute time at midnight -// - if (tnow == 0 || tnow == 1) {add_time = tnow; tnow = time(0);} - else add_time = 0; - localtime_r((const time_t *) &tnow, &midtime); - if (add_time) {midtime.tm_hour = 23; midtime.tm_min = midtime.tm_sec = 59;} - else midtime.tm_hour = midtime.tm_min = midtime.tm_sec = 0; - return mktime(&midtime) + add_time; -} - -/******************************************************************************/ -/* R e p o r t */ -/******************************************************************************/ - -unsigned long XrdSysTimer::Report() -{ - unsigned long current_time; - -// Get current time of day -// - gettimeofday(&LastReport, 0); - current_time = (unsigned long)LastReport.tv_sec; - -// Calculate the time interval thus far -// - LastReport.tv_sec = LastReport.tv_sec - StopWatch.tv_sec; - LastReport.tv_usec = LastReport.tv_usec - StopWatch.tv_usec; - if (LastReport.tv_usec < 0) - {LastReport.tv_sec--; LastReport.tv_usec += 1000000;} - -// Return the current time -// - return current_time; -} - -/******************************************************************************/ - -unsigned long XrdSysTimer::Report(double &Total_Time) -{ - unsigned long report_time = Report(); - -// Add up the time as a double -// - Total_Time += static_cast(LastReport.tv_sec) + - static_cast(LastReport.tv_usec/1000)/1000.0; - -// Return time -// - return report_time; -} - -/******************************************************************************/ - -unsigned long XrdSysTimer::Report(unsigned long &Total_Time) -{ - unsigned long report_time = Report(); - -// Add up the time as a 32-bit value to nearest milliseconds (max = 24 days) -// - Total_Time += (unsigned long)LastReport.tv_sec*1000 + - (unsigned long)(LastReport.tv_usec/1000); - -// Return time -// - return report_time; -} - -/******************************************************************************/ - -unsigned long XrdSysTimer::Report(unsigned long long &Total_Time) -{ - unsigned long report_time = Report(); - -// Add up the time as a 64-bit value to nearest milliseconds -// - Total_Time += (unsigned long long)LastReport.tv_sec*1000 + - (unsigned long long)(LastReport.tv_usec/1000); - -// Return time -// - return report_time; -} - -/******************************************************************************/ - -unsigned long XrdSysTimer::Report(struct timeval &Total_Time) -{ - unsigned long report_time = Report(); - -// Add the interval to the interval total time so far -// - Total_Time.tv_sec += LastReport.tv_sec; - Total_Time.tv_usec += LastReport.tv_usec; - if (Total_Time.tv_usec > 1000000) {Total_Time.tv_sec++; - Total_Time.tv_usec -= 1000000;} - -// Return time -// - return report_time; -} - -/******************************************************************************/ -/* S n o o z e */ -/******************************************************************************/ - -void XrdSysTimer::Snooze(int sec) -{ -#ifndef WIN32 - struct timespec naptime, waketime; - -// Calculate nano sleep time -// - naptime.tv_sec = sec; - naptime.tv_nsec = 0; - -// Wait for a lsoppy number of seconds -// - while(nanosleep(&naptime, &waketime) && EINTR == errno) - {naptime.tv_sec = waketime.tv_sec; - naptime.tv_nsec = waketime.tv_nsec; - } -#else - ::Sleep(sec*1000); -#endif -} -/******************************************************************************/ -/* s 2 h m s */ -/******************************************************************************/ - -char *XrdSysTimer::s2hms(int sec, char *buff, int blen) -{ - int hours, minutes; - - minutes = sec/60; - sec = sec%60; - hours = minutes/60; - minutes = minutes%60; - - snprintf(buff, blen-1, "%d:%02d:%02d", hours, minutes, sec); - buff[blen-1] = '\0'; - return buff; -} - -/******************************************************************************/ -/* T i m e Z o n e */ -/******************************************************************************/ - -int XrdSysTimer::TimeZone() -{ - time_t currTime = time(0); - time_t currTimeGMT = 0; - tm ptm; - - gmtime_r( &currTime, &ptm ); - currTimeGMT = mktime( &ptm ); - currTime /= 60*60; - currTimeGMT /= 60*60; - return currTime - currTimeGMT; -} - -/******************************************************************************/ -/* W a i t */ -/******************************************************************************/ - -void XrdSysTimer::Wait(int mills) -{ -#ifndef WIN32 - struct timespec naptime, waketime; - -// Calculate nano sleep time -// - naptime.tv_sec = mills/1000; - naptime.tv_nsec = (mills%1000)*1000000; - -// Wait for exactly x milliseconds -// - while(nanosleep(&naptime, &waketime) && EINTR == errno) - {naptime.tv_sec = waketime.tv_sec; - naptime.tv_nsec = waketime.tv_nsec; - } -#else - ::Sleep(mills); -#endif -} - -/******************************************************************************/ -/* W a i t 4 M i d n i g h t */ -/******************************************************************************/ - -void XrdSysTimer::Wait4Midnight() -{ - -// Wait until midnight arrives -// -#ifndef __APPLE__ - timespec Midnite = {Midnight(1), 0}; - while(clock_nanosleep(CLOCK_REALTIME,TIMER_ABSTIME,&Midnite,0) == EINTR) {} -#else - timespec tleft, Midnite = {Midnight(1) - time(0), 0}; - int ntpWait = 60; -do{while(nanosleep(&Midnite, &tleft) && EINTR == errno) - {Midnite.tv_sec = tleft.tv_sec; - Midnite.tv_nsec = tleft.tv_nsec; - } - if (Midnight(1) - time(0) >= 60) break; - Midnite.tv_sec = 1; - Midnite.tv_nsec = 0; - } while(ntpWait--); // This avoids multiple wakeups when NTP adjusts clock -#endif -} diff --git a/src/XrdSys/XrdSysTimer.hh b/src/XrdSys/XrdSysTimer.hh deleted file mode 100644 index b6a6457469b..00000000000 --- a/src/XrdSys/XrdSysTimer.hh +++ /dev/null @@ -1,88 +0,0 @@ -#ifndef __XrdSysTimer__ -#define __XrdSysTimer__ -/******************************************************************************/ -/* */ -/* X r d S y s T i m e r . h h */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#ifndef WIN32 -#include -#else -#include -#include -#include "XrdSys/XrdWin32.hh" -#endif - -/* This include file describes the oo elapsed time interval interface. It is - used by the oo Real Time Monitor, among others. -*/ - -class XrdSysTimer { - -public: - struct timeval *Delta_Time(struct timeval &tbeg); - -static time_t Midnight(time_t tnow=0); - -inline int TimeLE(time_t tsec) {return StopWatch.tv_sec <= tsec;} - - // The following routines return the current interval added to the - // passed argument as well as returning the current Unix seconds - // - unsigned long Report(double &); - unsigned long Report(unsigned long &); - unsigned long Report(unsigned long long &); - unsigned long Report(struct timeval &); - -inline void Reset() {gettimeofday(&StopWatch, 0);} - -inline time_t Seconds() {return StopWatch.tv_sec;} - -inline void Set(struct timeval &tod) - {StopWatch.tv_sec = tod.tv_sec; - StopWatch.tv_usec = tod.tv_usec; - } - -static void Snooze(int seconds); - -static char *s2hms(int sec, char *buff, int blen); - -static int TimeZone(); - -static void Wait(int milliseconds); - -static void Wait4Midnight(); - - XrdSysTimer() {Reset();} - -private: - struct timeval StopWatch; // Current running clock - struct timeval LastReport; // Total time from last report - - unsigned long Report(); // Place interval in Last Report -}; -#endif diff --git a/src/XrdSys/XrdSysTrace.cc b/src/XrdSys/XrdSysTrace.cc deleted file mode 100644 index d306338e8af..00000000000 --- a/src/XrdSys/XrdSysTrace.cc +++ /dev/null @@ -1,394 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S y s T r a c e . h h */ -/* */ -/* (c) 2016 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -#include "XrdSys/XrdSysFD.hh" -#include "XrdSys/XrdSysLogger.hh" -#include "XrdSys/XrdSysTrace.hh" - -/******************************************************************************/ -/* G l o b a l T r a c i n g O b j e c t s */ -/******************************************************************************/ - -// The following objects are defined centrally for all components of the stack. -// The naming convention is: XrdSysTrace -// -XrdSysTrace XrdSysTraceXrd("xrd_"); - -/******************************************************************************/ -/* B e g */ -/******************************************************************************/ - -XrdSysTrace& XrdSysTrace::Beg(const char *usr, - const char *epn, - const char *txt) -{ - char fmt[16]; - const char *fmt1, *fmt2, *fmt3; - int n; - -// Generate prefix format (way too complicated) -// - if (usr) fmt1 = "%s "; - else {usr = "", fmt1 = "%s";} - if (epn) fmt2 = "%s_%s: "; - else {epn = ""; fmt2 = "%s%s: ";} - if (txt) fmt3 = "%s"; - else {txt = ""; fmt3 = "";} - sprintf(fmt, "%s%s%s", fmt1, fmt2, fmt3); - -// Format the header -// - myMutex.Lock(); - n = snprintf(pBuff, sizeof(pBuff), fmt, usr, iName, epn, txt); - if (n >= (int)sizeof(pBuff)) n = sizeof(pBuff)-1; - -// Start the trace procedure -// - ioVec[0].iov_base = 0; ioVec[0].iov_len = 0; - ioVec[1].iov_base = pBuff; ioVec[1].iov_len = n; - -// Reset ourselves -// - dPnt = 0; - dFree = txtMax; - vPnt = 2; - -// All done -// - return *this; -} - -/******************************************************************************/ -/* E n d */ -/******************************************************************************/ - -void XrdSysTrace::End() -{ - -// Make sure and endline character appears -// - if (vPnt >= iovMax) vPnt = iovMax-1; - ioVec[vPnt] .iov_base = (char *)"\n"; - ioVec[vPnt++].iov_len = 1; - -// Output the line -// - if (logP) logP->Put(vPnt, ioVec); - else {static XrdSysLogger tLog(XrdSysFD_Dup(STDERR_FILENO), 0); - tLog.Put(vPnt, ioVec); - } - -// All done -// - myMutex.UnLock(); -} - -/******************************************************************************/ -/* < < b o o l */ -/******************************************************************************/ - -XrdSysTrace& XrdSysTrace::operator<<(bool val) -{ - -// If we have enough space then format the value -// - if (vPnt < iovMax) - {if (val) - {ioVec[vPnt] .iov_base = (char *)"True"; - ioVec[vPnt++].iov_len = 4; - } else { - ioVec[vPnt] .iov_base = (char *)"False"; - ioVec[vPnt++].iov_len = 5; - } - } - return *this; -} - -/******************************************************************************/ -/* < < c h a r */ -/******************************************************************************/ - -XrdSysTrace& XrdSysTrace::operator<<(char val) -{ - static char hv[] = "0123456789abcdef"; - -// If we have enough space then format the value -// - if (vPnt < iovMax && dFree > 1) - {if (doHex) - {ioVec[vPnt] .iov_base = (char *)(&dBuff[dPnt]); - ioVec[vPnt++].iov_len = 2; - dBuff[dPnt++] = hv[(val >> 4) & 0x0f]; - dBuff[dPnt++] = hv[ val & 0xf0]; - dFree -= 2; - } else { - ioVec[vPnt] .iov_base = (char *)(&dBuff[dPnt]); - ioVec[vPnt++].iov_len = 1; - dBuff[dPnt++] = val; dFree--; - } - } - return *this; -} - -/******************************************************************************/ -/* < < c o n s t c h a r * */ -/******************************************************************************/ - -XrdSysTrace& XrdSysTrace::operator<<(const char *val) -{ - -// If we have enough space then format the value -// - if (vPnt < iovMax) - {ioVec[vPnt] .iov_base = (char *)val; - ioVec[vPnt++].iov_len = strlen(val); - } - return *this; -} - - -/******************************************************************************/ -/* < < std::string */ -/******************************************************************************/ -XrdSysTrace& XrdSysTrace::operator<<(const std::string& val) -{ - return (*this << val.c_str()); -} - -/******************************************************************************/ -/* < < s h o r t */ -/******************************************************************************/ - -XrdSysTrace& XrdSysTrace::operator<<(short val) -{ - static const int xSz = sizeof("-32768"); - -// If we have enough space then format the value -// - if (dFree >= xSz && vPnt < iovMax) - {const char *fmt = (doHex ? "%hx" : "%hd"); - int n = snprintf(&dBuff[dPnt], dFree, fmt, val); - if (n > dFree) dFree = 0; - else {ioVec[vPnt] .iov_base = &dBuff[dPnt]; - ioVec[vPnt++].iov_len = n; - dPnt += n; dFree -= n; - } - } - return *this; -} - -/******************************************************************************/ -/* < < i n t */ -/******************************************************************************/ - -XrdSysTrace& XrdSysTrace::operator<<(int val) -{ - static const int xSz = sizeof("-2147483648"); - -// If we have enough space then format the value -// - if (dFree >= xSz && vPnt < iovMax) - {const char *fmt = (doHex ? "%x" : "%d"); - int n = snprintf(&dBuff[dPnt], dFree, fmt, val); - if (n > dFree) dFree = 0; - else {ioVec[vPnt] .iov_base = &dBuff[dPnt]; - ioVec[vPnt++].iov_len = n; - dPnt += n; dFree -= n; - } - } - return *this; -} - -/******************************************************************************/ -/* < < l o n g */ -/******************************************************************************/ - -XrdSysTrace& XrdSysTrace::operator<<(long val) -{ - -// Fan out based on length of a long -// - if (sizeof(long) > 4) return *this<(val); - else return *this<(val); -} - -/******************************************************************************/ -/* < < l o n g l o n g */ -/******************************************************************************/ - -XrdSysTrace& XrdSysTrace::operator<<(long long val) -{ - static const int xSz = sizeof("-9223372036854775808"); - -// If we have enough space then format the value -// - if (dFree >= xSz && vPnt < iovMax) - {const char *fmt = (doHex ? "%llx" : "%lld"); - int n = snprintf(&dBuff[dPnt], dFree, fmt, val); - if (n > dFree) dFree = 0; - else {ioVec[vPnt] .iov_base = &dBuff[dPnt]; - ioVec[vPnt++].iov_len = n; - dPnt += n; dFree -= n; - } - } - return *this; -} - -/******************************************************************************/ -/* < < u n s i g n e d s h o r t */ -/******************************************************************************/ - -XrdSysTrace& XrdSysTrace::operator<<(unsigned short val) -{ - static const int xSz = sizeof("65535"); - -// If we have enough space then format the value -// - if (dFree >= xSz && vPnt < iovMax) - {const char *fmt = (doHex ? "%hx" : "%hu"); - int n = snprintf(&dBuff[dPnt], dFree, fmt, val); - if (n > dFree) dFree = 0; - else {ioVec[vPnt] .iov_base = &dBuff[dPnt]; - ioVec[vPnt++].iov_len = n; - dPnt += n; dFree -= n; - } - } - return *this; -} - -/******************************************************************************/ -/* < < u n s i g n e d i n t */ -/******************************************************************************/ - -XrdSysTrace& XrdSysTrace::operator<<(unsigned int val) -{ - static const int xSz = sizeof("4294967295"); - -// If we have enough space then format the value -// - if (dFree >= xSz && vPnt < iovMax) - {const char *fmt = (doHex ? "%x" : "%u"); - int n = snprintf(&dBuff[dPnt], dFree, fmt, val); - if (n > dFree) dFree = 0; - else {ioVec[vPnt] .iov_base = &dBuff[dPnt]; - ioVec[vPnt++].iov_len = n; - dPnt += n; dFree -= n; - } - } - return *this; -} - -/******************************************************************************/ -/* < < u n s i g n e d l o n g */ -/******************************************************************************/ - -XrdSysTrace& XrdSysTrace::operator<<(unsigned long val) -{ - -// Fan out based on length of a long -// - if (sizeof(long) > 4) return *this<(val); - else return *this<(val); -} - -/******************************************************************************/ -/* < < u n s i g n e d l o n g l o n g */ -/******************************************************************************/ - -XrdSysTrace& XrdSysTrace::operator<<(unsigned long long val) -{ - static const int xSz = sizeof("18446744073709551615"); - -// If we have enough space then format the value -// - if (dFree >= xSz && vPnt < iovMax) - {const char *fmt = (doHex ? "%llx" : "%llu"); - int n = snprintf(&dBuff[dPnt], dFree, fmt, val); - if (n > dFree) dFree = 0; - else {ioVec[vPnt] .iov_base = &dBuff[dPnt]; - ioVec[vPnt++].iov_len = n; - dPnt += n; dFree -= n; - } - } - return *this; -} - -/******************************************************************************/ -/* < < v o i d * */ -/******************************************************************************/ - -XrdSysTrace& XrdSysTrace::operator<<(void *val) -{ - static const int xSz = sizeof(void *)*2+1; - -// If we have enough space then format the value -// - if (dFree >= xSz && vPnt < iovMax) - {int n = snprintf(&dBuff[dPnt], dFree, "%p", val); - if (n > dFree) dFree = 0; - else {ioVec[vPnt] .iov_base = &dBuff[dPnt]; - ioVec[vPnt++].iov_len = n; - dPnt += n; dFree -= n; - } - } - return *this; -} - -/******************************************************************************/ -/* < < l o n g d o u b l e */ -/******************************************************************************/ - -XrdSysTrace& XrdSysTrace::Insert(long double val) -{ - char tmp[32]; - int n; - -// Gaurd against iovec overflows -// -if (vPnt < iovMax) - { - -// Convert the value into the temporary buffer -// - n = snprintf(tmp, sizeof(tmp), "%Lg", val); - -// If we have enough space then format the value -// - if (dFree > n && n < (int)sizeof(tmp)) - {ioVec[vPnt] .iov_base = &dBuff[dPnt]; - ioVec[vPnt++].iov_len = n; - strcpy(&dBuff[dPnt], tmp); - dPnt += n; dFree -= n; - } - } - return *this; -} diff --git a/src/XrdSys/XrdSysTrace.hh b/src/XrdSys/XrdSysTrace.hh deleted file mode 100644 index ab9c9bf9841..00000000000 --- a/src/XrdSys/XrdSysTrace.hh +++ /dev/null @@ -1,114 +0,0 @@ -#ifndef __XRDSYSTRACE_HH__ -#define __XRDSYSTRACE_HH__ -/******************************************************************************/ -/* */ -/* X r d S y s T r a c e . h h */ -/* */ -/* (c) 2016 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -class XrdSysLogger; - -#include "XrdSys/XrdSysPthread.hh" - -namespace Xrd -{ -enum Fmt {dec=0, hex}; -} - -class XrdSysTrace -{ -public: - -XrdSysTrace& Beg(const char *usr=0, const char *epn=0, const char *txt=0); - -void End(); - -void SetLogger(XrdSysLogger *logp) {logP = logp;} - -inline bool Tracing(int mask) {return (mask & What) != 0;} - - int What; - -XrdSysTrace& operator<<(bool val); - -XrdSysTrace& operator<<( char val); -XrdSysTrace& operator<<(const char *val); - XrdSysTrace& operator<<(const std::string& val); - -XrdSysTrace& operator<<(short val); -XrdSysTrace& operator<<(int val); -XrdSysTrace& operator<<(long val); -XrdSysTrace& operator<<(long long val); - -XrdSysTrace& operator<<(unsigned short val); -XrdSysTrace& operator<<(unsigned int val); -XrdSysTrace& operator<<(unsigned long val); -XrdSysTrace& operator<<(unsigned long long val); - -XrdSysTrace& operator<<(float val) - {return Insert(static_cast(val));} -XrdSysTrace& operator<<(double val) - {return Insert(static_cast(val));} -XrdSysTrace& operator<<(long double val) - {return Insert(val);} - -XrdSysTrace& operator<<(void* val); - -XrdSysTrace& operator<<(Xrd::Fmt val) - { if (val == Xrd::hex) doHex = true; - else if (val == Xrd::dec) doHex = false; - return *this; - } - - XrdSysTrace(const char *pfx, XrdSysLogger *logp=0, int tf=0) - : What(tf), logP(logp), iName(pfx), dPnt(0), - dFree(txtMax), vPnt(1), doHex(false) {} - ~XrdSysTrace() {} - -private: - -XrdSysTrace& Insert(long double val); - -static const int iovMax = 16; -static const int pfxMax = 256; -static const int txtMax = 256; - -XrdSysMutex myMutex; -XrdSysLogger *logP; -const char *iName; -short dPnt; -short dFree; -short vPnt; -bool doHex; -struct iovec ioVec[iovMax]; -char pBuff[pfxMax]; -char dBuff[txtMax]; -}; -#endif diff --git a/src/XrdSys/XrdSysUtils.cc b/src/XrdSys/XrdSysUtils.cc deleted file mode 100644 index 994ca569197..00000000000 --- a/src/XrdSys/XrdSysUtils.cc +++ /dev/null @@ -1,224 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S y s U t i l s . c c */ -/* */ -/* (c) 2005 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef __APPLE__ -#include -#endif - -#ifdef WIN32 -#include -#include "XrdSys/XrdWin32.hh" -#else -#include -#include -#include -#include -#include -#endif -#include "XrdSysUtils.hh" - -/******************************************************************************/ -/* E x e c N a m e */ -/******************************************************************************/ - -const char *XrdSysUtils::ExecName() -{ - static const char *myEname = 0; - -// If we have been here before, simply return what we discovered. This is -// relatively thread-safe as we might loose some memory but it will work. -// Anyway, this method is unlikely to be called by multiple threads. Also, -// according to gthe Condor team, this code will not be able to return the -// program name if the program is under the control of valgrind! -// - if (myEname) return myEname; - -// Get the exec name based on platform -// -#ifdef __linux__ - {char epBuff[2048]; - int epLen; - if ((epLen = readlink("/proc/self/exe", epBuff, sizeof(epBuff)-1)) > 0) - {epBuff[epLen] = 0; - myEname = strdup(epBuff); - return myEname; - } - } -#elif defined(__APPLE__) - {char epBuff[2048]; - uint32_t epLen = sizeof(epBuff)-1; - if (!_NSGetExecutablePath(epBuff, &epLen)) - {epBuff[epLen] = 0; - myEname = strdup(epBuff); - return myEname; - } - } -#elif defined(__solaris__) - {const char *epBuff = getexecname(); - if (epBuff) - {if (*epBuff == '/') myEname = strdup(epBuff); - else {char *ename, *cwd = getcwd(0, MAXPATHLEN); - ename = (char *)malloc(strlen(cwd)+1+strlen(epBuff)+1); - sprintf(ename, "%s/%s", cwd, epBuff); - myEname = ename; - free(cwd); - } - return myEname; - } - } -#else -#endif - -// If got here then we don't have a valid program name. Return a null string. -// - return ""; -} - -/******************************************************************************/ -/* F m t U n a m e */ -/******************************************************************************/ - -int XrdSysUtils::FmtUname(char *buff, int blen) -{ -#if defined(WINDOWS) - return snprintf(buff, blen, "%s", "windows"); -#else - struct utsname uInfo; - -// Obtain the uname inofmormation -// - if (uname(&uInfo) < 0) return snprintf(buff, blen, "%s", "unknown OS"); - -// Format appropriate for certain platforms -// Linux and MacOs do not add usefull version information -// -#if defined(__linux__) - return snprintf(buff, blen, "%s %s", uInfo.sysname, uInfo.release); -#elif defined(__APPLE__) || defined(__FreeBSD__) - return snprintf(buff, blen, "%s %s %s", uInfo.sysname, uInfo.release, - uInfo.machine); -#else - return snprintf(buff, blen, "%s %s %s %s", uInfo.sysname, uInfo.release, - uInfo.version, uInfo.machine); -#endif -#endif -} - -/******************************************************************************/ -/* G e t S i g N u m */ -/******************************************************************************/ - -namespace -{ - static struct SigTab {const char *sname; int snum;} sigtab[] = - {{"hup", SIGHUP}, {"HUP", SIGHUP}, -#ifdef SIGRTMIN - {"rtmin", SIGRTMIN}, {"RTMIN", SIGRTMIN}, - {"rtmin+1", SIGRTMIN+1}, {"RTMIN+1", SIGRTMIN+1}, - {"rtmin+2", SIGRTMIN+2}, {"RTMIN+2", SIGRTMIN+2}, -#endif - {"ttou", SIGTTOU}, {"TTOU", SIGTTOU}, -// {"usr1", SIGUSR1}, {"USR1", SIGUSR1}, -// {"usr2", SIGUSR2}, {"USR2", SIGUSR2}, - {"winch", SIGWINCH}, {"WINCH", SIGWINCH}, - {"xfsz", SIGXFSZ}, {"XFSZ", SIGXFSZ} - }; - static int snum = sizeof(sigtab)/sizeof(struct SigTab); -}; - -int XrdSysUtils::GetSigNum(const char *sname) -{ - int i; - -// Trim off the "sig" in sname -// - if (!strncmp(sname, "sig", 3) || !strncmp(sname, "SIG", 3)) sname += 3; - -// Convert to signal number -// - for (i = 0; i < snum; i++) - {if (!strcmp(sname, sigtab[i].sname)) return sigtab[i].snum;} - return 0; -} - -/******************************************************************************/ -/* S i g B l o c k */ -/******************************************************************************/ - -bool XrdSysUtils::SigBlock() -{ - sigset_t myset; - -// Ignore pipe signals and prepare to blocks others -// - signal(SIGPIPE, SIG_IGN); // Solaris optimization - -// Add the standard signals we normally always block -// - sigemptyset(&myset); - sigaddset(&myset, SIGPIPE); - sigaddset(&myset, SIGCHLD); - -// Block a couple of real-time signals if they are supported (async I/O) -// -#ifdef SIGRTMAX - sigaddset(&myset, SIGRTMAX); - sigaddset(&myset, SIGRTMAX-1); -#endif - -// Now turn off these signals -// - return pthread_sigmask(SIG_BLOCK, &myset, NULL) == 0; -} - -/******************************************************************************/ - -bool XrdSysUtils::SigBlock(int numsig) -{ - sigset_t myset; - -// Ignore pipe signals and prepare to blocks others -// - if (sigemptyset(&myset) == -1 || sigaddset(&myset, numsig) == -1) - return false; - -// Now turn off these signals -// - return pthread_sigmask(SIG_BLOCK, &myset, NULL) == 0; -} diff --git a/src/XrdSys/XrdSysUtils.hh b/src/XrdSys/XrdSysUtils.hh deleted file mode 100644 index cd71b782ea0..00000000000 --- a/src/XrdSys/XrdSysUtils.hh +++ /dev/null @@ -1,99 +0,0 @@ -#ifndef __XRDSYSUTILS_HH__ -#define __XRDSYSUTILS_HH__ -/******************************************************************************/ -/* */ -/* X r d S y s U t i l s . h h */ -/* */ -/* (c) 2012 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -class XrdSysUtils -{ -public: - -//----------------------------------------------------------------------------- -//! Get the name of the current executable. -//! -//! @return the full path of the executable invoked. -//----------------------------------------------------------------------------- - -static const char *ExecName(); - -//----------------------------------------------------------------------------- -//! Format the uname information -//! -//! @param buff - pointer to the buffer to hold the uname as: -//! [] [] -//! @param blen - length of the buffer. -//! -//! @return the output of snprintf(buff, blen, ...); -//----------------------------------------------------------------------------- - -static int FmtUname(char *buff, int blen); - -//----------------------------------------------------------------------------- -//! Get common signal number. -//! -//! @param sname - the signal name as in sigxxx or just xxx (see kill). -//! -//! @return =0 - unknown or unsupported signal. -//! @return !0 - the corresponding signal number. -//----------------------------------------------------------------------------- - -static int GetSigNum(const char *sname); - -//----------------------------------------------------------------------------- -//! Block common signals. This must be called at program start. -//! -//! @return true - common signals are blocked. -//! @return false - common signals not blocked, errno has teh reason. -//----------------------------------------------------------------------------- - -static bool SigBlock(); - -//----------------------------------------------------------------------------- -//! Block a particular signal. This should be called at program start so that -//! the block applies to all threads. -//! -//! @aparam numsig - The signal value to be blocked. -//! -//! @return true - signal is blocked. -//! @return false - signal not blocked, errno has teh reason. -//----------------------------------------------------------------------------- - -static bool SigBlock(int numsig); - -//----------------------------------------------------------------------------- -//! Constructor and destructor -//----------------------------------------------------------------------------- - - XrdSysUtils() {} - ~XrdSysUtils() {} -}; -#endif diff --git a/src/XrdSys/XrdSysXAttr.cc b/src/XrdSys/XrdSysXAttr.cc deleted file mode 100644 index bd0ff619ebe..00000000000 --- a/src/XrdSys/XrdSysXAttr.cc +++ /dev/null @@ -1,116 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S y s X A t t r . c c */ -/* */ -/* (c) 2014 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include - -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysXAttr.hh" - -/******************************************************************************/ -/* C o p y */ -/******************************************************************************/ - -int XrdSysXAttr::Copy(const char *iPath, int iFD, const char *oPath, int oFD, - const char *Aname) -{ - char *bP; - int sz, rc = 0; - -// Check if all attributes are to be copied. If so, do it. -// - if (!Aname) - {AList *aP = 0, *aNow; - char *Buff; - int maxSz; - - // Get all of the attributes for the input - // - if ((maxSz = List(&aP, iPath, iFD, 1)) <= 0) - return maxSz == 0 || maxSz == -ENOTSUP; - - // Allocate a buffer to hold the largest attribute value (plus some) - // - maxSz += 4096; - Buff = (char *)malloc(maxSz); - - // Get each value and set it - // - aNow = aP; - while(aNow && (rc = Get(aNow->Name, Buff, maxSz, iPath, iFD)) >= 0 - && (rc = Set(aNow->Name, Buff, aNow->Vlen, oPath, oFD)) >= 0) - {aNow = aNow->Next;} - - // Free up resources and return - // - Free(aP); - free(Buff); - return rc; - } - -// First obtain the size of the attribute (if zero ignore it) -// - if ((sz = Get(Aname, 0, 0, iPath, iFD)) <= 0) - return (!sz || sz == -ENOTSUP ? 0 : sz); - -// Obtain storage -// - if (!(bP = (char *)malloc(sz))) - {if (Say) - {char eBuff[512]; - snprintf(eBuff, sizeof(eBuff), "copy attr %s from", Aname); - Say->Emsg("XAttr", ENOMEM, eBuff, iPath); - } - return -ENOMEM; - } - -// Copy over any extended attributes -// - if ((rc = Get(Aname, bP, sz, iPath, iFD)) > 0) - {if ((rc = Set(Aname, bP, rc, oPath, oFD)) < 0 && rc == -ENOTSUP) rc = 0;} - else if (rc < 0 && rc == -ENOTSUP) rc = 0; - -// All done -// - free(bP); - return rc; -} - -/******************************************************************************/ -/* S e t M s g R o u t e */ -/******************************************************************************/ - -XrdSysError *XrdSysXAttr::SetMsgRoute(XrdSysError *errP) -{ - XrdSysError *msgP = Say; - Say = errP; - return msgP; -} diff --git a/src/XrdSys/XrdSysXAttr.hh b/src/XrdSys/XrdSysXAttr.hh deleted file mode 100644 index d50b1cc585d..00000000000 --- a/src/XrdSys/XrdSysXAttr.hh +++ /dev/null @@ -1,248 +0,0 @@ -#ifndef __XRDSYSXATTR_HH__ -#define __XRDSYSXATTR_HH__ -/******************************************************************************/ -/* */ -/* X r d S y s X A t t r . h h */ -/* */ -/* (c) 2014 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -//------------------------------------------------------------------------------ -//! This pure abstract class defines the extended attribute interface and is -//! used by extended attribute plugin writers to implement extended attribute -//! handling. The plugin is loaded via the ofs.xattrlib directive. -//------------------------------------------------------------------------------ - -class XrdSysError; - -class XrdSysXAttr -{ -public: -//------------------------------------------------------------------------------ -//! Definition of a structure to hold an attribute name and the size of the -//! name as well as the size of its associated value. The structure is a list -//! and is used as an argument to Free() and is returned by List(). The size of -//! the struct is dynamic and should be sized to hold all of the information. -//------------------------------------------------------------------------------ - -struct AList - {AList *Next; //!< -> next element. - int Vlen; //!< The length of the attribute value; - int Nlen; //!< The length of the attribute name that follows. - char Name[1]; //!< Start of the name (size of struct is dynamic) - }; - -//------------------------------------------------------------------------------ -//! Copy one or all extended attributes from one file to another (a default -//! implementation is supplied). -//! -//! @param iPath -> Path of the file whose attribute(s) are to be copied. -//! @param iFD If >=0 is the file descriptor of the opened source file. -//! @param oPath -> Path of the file to receive the extended attribute(s). -//! Duplicate attributes are replaced. -//! @param oFD If >=0 is the file descriptor of the opened target file. -//! @param Aname -> if nil, the all of the attributes of the source file are -//! copied. Otherwise, only the attribute name pointed to by -//! Aname is copied. If Aname does not exist or extended -//! attributes are not supported, the operation succeeds by -//! copying nothing. -//! -//! @return =0 Attribute(s) successfully copied, did not exist, or extended -//! attributes are not supported for source or target. -//! @return <0 Attribute(s) not copied, the return value is -errno that -//! describes the reason for the failure. -//------------------------------------------------------------------------------ - -virtual int Copy(const char *iPath, int iFD, const char *oPath, int oFD, - const char *Aname=0); - -//------------------------------------------------------------------------------ -//! Remove an extended attribute. -//! -//! @param Aname -> The attribute name. -//! @param Path -> Path of the file whose attribute is to be removed. -//! @param fd If >=0 is the file descriptor of the opened subject file. -//! -//! @return =0 Attribute was successfully removed or did not exist. -//! @return <0 Attribute was not removed, the return value is -errno that -//! describes the reason for the failure. -//------------------------------------------------------------------------------ - -virtual int Del(const char *Aname, const char *Path, int fd=-1) = 0; - -//------------------------------------------------------------------------------ -//! Release storage occupied by the Alist structure returned by List(). -//! -//! @param aPL -> The first element of the AList structure. -//------------------------------------------------------------------------------ - -virtual void Free(AList *aPL) = 0; - -//------------------------------------------------------------------------------ -//! Get an attribute value and its size. -//! -//! @param Aname -> The attribute name. -//! @param Aval -> Buffer to receive the attribute value. -//! @param Avsz Length of the buffer in bytes. Only up to this number of -//! bytes should be returned. However, should Avsz be zero -//! the the size of the attribute value should be returned -//! and the Aval argument should be ignored. -//! @param Path -> Path of the file whose attribute is to be fetched. -//! @param fd -> If >=0 is the file descriptor of the opened subject file. -//! -//! @return >0 The number of bytes placed in Aval. However, if avsz is zero -//! then the value is the actual size of the attribute value. -//! @return =0 The attribute does not exist. -//! @return <0 The attribute value could not be returned. The returned -//! value is -errno describing the reason. -//------------------------------------------------------------------------------ - -virtual int Get(const char *Aname, void *Aval, int Avsz, - const char *Path, int fd=-1) = 0; - -//------------------------------------------------------------------------------ -//! Get all of the attributes associated with a file. -//! -//! @param aPL -> the pointer to hold the first element of AList. The -//! storage occupied by the returned AList must be released -//! by calling Free(). -//! @param Path -> Path of the file whose attributes are t be returned. -//! @param fd -> If >=0 is the file descriptor of the opened subject file. -//! @param getSz When != 0 then the size of the maximum attribute value -//! should be returned. Otherwise, upon success 0 is returned. -//! -//! @return >0 Attributes were returned and aPL points to the first -//! attribute value. The returned value is the largest size -//! of an attribute value encountered (getSz != 0). -//! @return =0 Attributes were returned and aPL points to the first -//! attribute value (getSz == 0). -//! @return <0 The attribute values could not be returned. The returned -//! value is -errno describing the reason. -//------------------------------------------------------------------------------ - -virtual int List(AList **aPL, const char *Path, int fd=-1, int getSz=0) = 0; - -//------------------------------------------------------------------------------ -//! Set an attribute. -//! -//! @param Aname -> The attribute name. -//! @param Aval -> Buffer holding the attribute value. -//! @param Avsz Length of the buffer in bytes. This is the length of the -//! attribute value which may contain binary data. -//! @param Path -> Path of the file whose attribute is to be set. -//! @param fd -> If >=0 is the file descriptor of the opened subject file. -//! @param isnew When !0 then the attribute must not exist (i.e. new). -//! Otherwise, if it does exist, the value is replaced. In -//! either case, if it does not exist it should be created. -//! -//! @return =0 The attribute was successfully set. -//! @return <0 The attribute values could not be set. The returned -//! value is -errno describing the reason. -//------------------------------------------------------------------------------ - -virtual int Set(const char *Aname, const void *Aval, int Avsz, - const char *Path, int fd=-1, int isNew=0) = 0; - -//------------------------------------------------------------------------------ -//! Establish the error message routing. Unless it's established, no messages -//! should be produced. A default implementation is supplied. -//! -//! @param errP -> Pointer to the error message object. If it is a nil -//! pointer, no error messages should be produced. -//! -//! @return The previous setting. -//------------------------------------------------------------------------------ - -virtual XrdSysError *SetMsgRoute(XrdSysError *errP); - -//------------------------------------------------------------------------------ -//! Constructor and Destructor -//------------------------------------------------------------------------------ - - XrdSysXAttr() : Say(0) {} -virtual ~XrdSysXAttr() {} - -protected: - -XrdSysError *Say; -}; - -/******************************************************************************/ -/* X r d S y s X A t t r O b j e c t I n s t a n t i a t o r */ -/******************************************************************************/ - -//------------------------------------------------------------------------------ -//! Get an instance of a configured XrdSysXAttr object. -//! -//! @param errP -> Error message object for error messages. -//! @param config_fn -> The name of the config file. -//! @param parms -> Any parameters specified on the ofs.xattrlib -//! directive. If there are no parameters parms may be 0. -//! -//! @return Success: -> an instance of the XrdSysXattr object to be used. -//! Failure: Null pointer which causes initialization to fail. -//! -//! The object creation function must be declared as an extern "C" function -//! in the plug-in shared library as follows: -//------------------------------------------------------------------------------ -/*! - extern "C" XrdSysXAttr *XrdSysGetXAttrObject(XrdSysError *errP, - const char *config_fn, - const char *parms); -*/ - -//------------------------------------------------------------------------------ -//! Declare compilation version. -//! -//! Additionally, you *should* declare the xrootd version you used to compile -//! your plug-in. Declare it as: -//------------------------------------------------------------------------------ - -/*! #include "XrdVersion.hh" - XrdVERSIONINFO(XrdSysGetXAttrObject,); - - where is a 1- to 15-character unquoted name identifying your plugin. -*/ - -/******************************************************************************/ -/* X r d S y s X A t t r I m p l e m e n t a t i o n s */ -/******************************************************************************/ - -//------------------------------------------------------------------------------ -//! Access the native implementation in libXrdUtils.so: -//! -//! extern XrdSysXAttr XrdSysXAttrNative; -//------------------------------------------------------------------------------ - -//------------------------------------------------------------------------------ -//! Access the active implementation in libXrdUtils.so: -//! -//! extern XrdSysXAttr *XrdSysXAttrActive; -//! -//! The active implementatiuon is the one being used. This may be a pointer to -//! the native implementation or to a specified plugin loaded by the OFS layer. -//------------------------------------------------------------------------------ -#endif diff --git a/src/XrdSys/XrdSysXSLock.cc b/src/XrdSys/XrdSysXSLock.cc deleted file mode 100644 index e8d0bc0ef29..00000000000 --- a/src/XrdSys/XrdSysXSLock.cc +++ /dev/null @@ -1,134 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S y s X S L o c k . c c */ -/* */ -/* (c) 2003 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdSys/XrdSysXSLock.hh" - -/******************************************************************************/ -/* D e s t r u c t o r */ -/******************************************************************************/ - -XrdSysXSLock::~XrdSysXSLock() -{ - -// Prevent usage while destroying object but make sure no one else is using it -// - LockContext.Lock(); - if (cur_count || shr_wait || exc_wait) - {LockContext.UnLock(); - abort(); - } - LockContext.UnLock(); -} - -/******************************************************************************/ -/* L o c k */ -/******************************************************************************/ - -void XrdSysXSLock::Lock(const XrdSysXS_Type usage) -{ - -// Serialize access to this object -// - LockContext.Lock(); - -// This loop continues until we can acquire the resource. We are gauranteed -// to eventually acquire it regardless of the unblocking order. -// - while(cur_count) - { - // If usage is compatible with current usage get the lock right away - // - if (usage == xs_Shared && cur_usage == xs_Shared && !exc_wait) break; - - // Indicate that we are waiting - // - if (usage == xs_Shared) shr_wait++; - else exc_wait++; - - // Usage is not compatible. We must wait for current lock mode to end - // - LockContext.UnLock(); - if (usage == xs_Shared) WantShr.Wait(); - else WantExc.Wait(); - LockContext.Lock(); - } - -// We obtained the right to use this object -// - cur_usage = usage; - cur_count++; - LockContext.UnLock(); -} - -/******************************************************************************/ -/* U n L o c k */ -/******************************************************************************/ - -void XrdSysXSLock::UnLock(const XrdSysXS_Type usage) -{ - -// Serialize access to our data -// - LockContext.Lock(); - -// Make sure that the lock is currently being used -// - if (!cur_count) - {LockContext.UnLock(); - cerr << "XSLock: Attempt to unlock inactive lock." <. */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include "XrdSys/XrdSysPthread.hh" - -// These are valid usage options -// -enum XrdSysXS_Type {xs_None = 0, xs_Shared = 1, xs_Exclusive = 2}; - -// This class implements the shared lock. Any number of readers are allowed -// by requesting a shared lock. Only one exclusive writer is allowed by -// requesting an exclusive lock. Up/downgrading is not supported. -// -class XrdSysXSLock -{ -public: - -void Lock(const XrdSysXS_Type usage); - -void UnLock(const XrdSysXS_Type usage=xs_None); - - XrdSysXSLock() : cur_usage(xs_None), cur_count(0), exc_wait(0), - shr_wait(0), toggle(0), WantShr(0), WantExc(0) {} - ~XrdSysXSLock(); - -private: - -XrdSysXS_Type cur_usage; -int cur_count; -int exc_wait; -int shr_wait; -int toggle; - -XrdSysMutex LockContext; -XrdSysSemaphore WantShr; -XrdSysSemaphore WantExc; -}; -#endif diff --git a/src/XrdThrottle/README b/src/XrdThrottle/README deleted file mode 100644 index 2b8232d337d..00000000000 --- a/src/XrdThrottle/README +++ /dev/null @@ -1,77 +0,0 @@ - -INTRODUCTION - -This "file system" implementation for Xrootd throttles request rates -passed through to the real underlying filesystem layer. It has two -goals: - -- Prevent users from overloading a filesystem through Xrootd. -- Provide a level of fairness between different users. - -Here, "fairness" is loosely done - while it is done across all open -file handles for a given user, it allows them to opportunistically -utilize bandwidth allocated to, but not used by, others. There's no -concept of fairshare or history beyond the previous time interval (by default, -1 second). Fairness is enforced by trying to delaying IO the same -amount *per user*, regardless of how many open file handles there are. - -When loaded, in order for the plugin to perform timings for IO, asynchronous -requests are handled synchronously and mmap-based reads are disabled. It is -believed this impact is minimal. - -Once a throttle limit is hit, the plugin will start delaying the start of -new IO requests until the server is back below the throttle. The granularity -of measurement, by default, is a 1 second interval; the overall amount of delay -may be minimal if the server is only slightly above limits. - -USAGE - -To load the plugin, add it as the first xrootd.fslib in the configuration file: - -xrootd.fslib throttle default - -Unless limits are explicitly set, the plugin will only record (and, if configured, -log) usage statistics. - -To set a throttle, add a line as follows: - -throttle.throttle [concurrency CONCUR] [data RATE] - -The two options are: - - - CONCUR: Set the level of IO concurrency allowed. This works in a similar - manner to system load in Linux; we sum up the total amount of time spent - waiting on all IO requests per second. So, if there are two simultaneous - requests, each of which take 1 second to service, we have a concurrency of - 2. If we have 100 simultaneous IO requests, each of which is services in - 1 millisecond, then the IO load is 0.1. - - RATE: Limit for the total data rate (MB/s) from the underlying filesystem. - This number is measured in bytes. - -NOTES: -- The throttles are applied to the aggregate of reads and writes; they are not - considered seperately. -- In almost all cases, service administrators will want to set concurrency, NOT, - data. Concurrency measures how much work is being done by the filesystem; - data rate is weak indicator of filesystem activity due to the effects of - the page cache. We do not offer the option to limit number of clients or - IOPS in this plugin for similar reasons: neither metric strongly corresponds - to filesystem activity (due to idle clients or IO requests serviced from cache). -- As sites commonly run multiple servers, setting the data rate throttle is - not useful to limit wide area network traffic. If you want to limit network - activity, investigate QoS on the site's network router. Worst case, configuring - the host-level network queueing will be more CPU-efficient than setting the - data rates from within Xrootd. The sole advantage of throttling data rates - from within Xrootd is being able to provide fairness across users. - -To log throttle-related activity, set: - -throttle.trace [all] [off|none] [bandwidth] [ioload] [debug] - -- all: All debugging statements are enabled. -- off, none: No debugging statements are enabled. -- bandwidth: Log bandwidth-usage-related statistics. -- ioload: Log concurrency-related statistics. -- debug: Log all throttle-related information; this is very chatty and aims - to provide developers with enough information to debug the throttle's activity. - diff --git a/src/XrdThrottle/XrdThrottle.hh b/src/XrdThrottle/XrdThrottle.hh deleted file mode 100644 index a818298a37d..00000000000 --- a/src/XrdThrottle/XrdThrottle.hh +++ /dev/null @@ -1,254 +0,0 @@ -#ifndef __XRDTHROTTLE_H_ -#define __XRDTHROTTLE_H_ - -#include -#include - -#include "XrdVersion.hh" -#include "XrdSys/XrdSysError.hh" -#include "XrdSfs/XrdSfsInterface.hh" - -#include "XrdThrottle/XrdThrottleTrace.hh" -#include "XrdThrottle/XrdThrottleManager.hh" - -class XrdSysLogger; -class XrdOucStream; - - -namespace XrdThrottle { - -#if __cplusplus >= 201103L -typedef std::unique_ptr unique_sfs_ptr; -#else -typedef std::auto_ptr unique_sfs_ptr; -#endif - -class FileSystem; - -class File : public XrdSfsFile { - -friend class FileSystem; - -public: - - virtual int - open(const char *fileName, - XrdSfsFileOpenMode openMode, - mode_t createMode, - const XrdSecEntity *client, - const char *opaque = 0); - - virtual int - close(); - - using XrdSfsFile::fctl; - virtual int - fctl(const int cmd, - const char *args, - XrdOucErrInfo &out_error); - - virtual const char * - FName(); - - virtual int - getMmap(void **Addr, off_t &Size); - - virtual int - read(XrdSfsFileOffset fileOffset, // Preread only - XrdSfsXferSize amount); - - virtual XrdSfsXferSize - read(XrdSfsFileOffset fileOffset, - char *buffer, - XrdSfsXferSize buffer_size); - - virtual int - read(XrdSfsAio *aioparm); - - virtual XrdSfsXferSize - write(XrdSfsFileOffset fileOffset, - const char *buffer, - XrdSfsXferSize buffer_size); - - virtual int - write(XrdSfsAio *aioparm); - - virtual int - sync(); - - virtual int - sync(XrdSfsAio *aiop); - - virtual int - stat(struct stat *buf); - - virtual int - truncate(XrdSfsFileOffset fileOffset); - - virtual int - getCXinfo(char cxtype[4], int &cxrsz); - - virtual int - SendData(XrdSfsDio *sfDio, - XrdSfsFileOffset offset, - XrdSfsXferSize size); - -private: - File(const char *user, int monid, unique_sfs_ptr, XrdThrottleManager &throttle, XrdSysError &eroute); - - virtual - ~File(); - - unique_sfs_ptr m_sfs; - int m_uid; // A unique identifier for this user; has no meaning except for the fairshare. - std::string m_loadshed; - std::string m_user; - XrdThrottleManager &m_throttle; - XrdSysError &m_eroute; -}; - -class FileSystem : public XrdSfsFileSystem -{ - -friend XrdSfsFileSystem * XrdSfsGetFileSystem_Internal(XrdSfsFileSystem *, XrdSysLogger *, const char *); - -public: - - virtual XrdSfsDirectory * - newDir(char *user=0, int monid=0); - - virtual XrdSfsFile * - newFile(char *user=0, int monid=0); - - virtual int - chksum( csFunc Func, - const char *csName, - const char *path, - XrdOucErrInfo &eInfo, - const XrdSecEntity *client = 0, - const char *opaque = 0); - - virtual int - chmod(const char *Name, - XrdSfsMode Mode, - XrdOucErrInfo &out_error, - const XrdSecEntity *client, - const char *opaque = 0); - - virtual void - Disc(const XrdSecEntity *client = 0); - - virtual void - EnvInfo(XrdOucEnv *envP); - - virtual int - exists(const char *fileName, - XrdSfsFileExistence &exists_flag, - XrdOucErrInfo &out_error, - const XrdSecEntity *client, - const char *opaque = 0); - - virtual int - fsctl(const int cmd, - const char *args, - XrdOucErrInfo &out_error, - const XrdSecEntity *client); - - virtual int - getStats(char *buff, int blen); - - virtual const char * - getVersion(); - - virtual int - mkdir(const char *dirName, - XrdSfsMode Mode, - XrdOucErrInfo &out_error, - const XrdSecEntity *client, - const char *opaque = 0); - - virtual int - prepare( XrdSfsPrep &pargs, - XrdOucErrInfo &out_error, - const XrdSecEntity *client = 0); - - virtual int - rem(const char *path, - XrdOucErrInfo &out_error, - const XrdSecEntity *client, - const char *info = 0); - - virtual int - remdir(const char *dirName, - XrdOucErrInfo &out_error, - const XrdSecEntity *client, - const char *info = 0); - - virtual int - rename(const char *oldFileName, - const char *newFileName, - XrdOucErrInfo &out_error, - const XrdSecEntity *client, - const char *infoO = 0, - const char *infoN = 0); - - virtual int - stat(const char *Name, - struct stat *buf, - XrdOucErrInfo &out_error, - const XrdSecEntity *client, - const char *opaque = 0); - - virtual int - stat(const char *Name, - mode_t &mode, - XrdOucErrInfo &out_error, - const XrdSecEntity *client, - const char *opaque = 0); - - virtual int - truncate(const char *Name, - XrdSfsFileOffset fileOffset, - XrdOucErrInfo &out_error, - const XrdSecEntity *client = 0, - const char *opaque = 0); - - virtual int - Configure(XrdSysError &, XrdSfsFileSystem *native_fs); - -private: - static void - Initialize( FileSystem *&fs, - XrdSfsFileSystem *native_fs, - XrdSysLogger *lp, - const char *config_file); - - FileSystem(); - - virtual - ~FileSystem(); - - int - xthrottle(XrdOucStream &Config); - - int - xloadshed(XrdOucStream &Config); - - int - xtrace(XrdOucStream &Config); - - static FileSystem *m_instance; - XrdSysError m_eroute; - XrdOucTrace m_trace; - std::string m_config_file; - XrdSfsFileSystem *m_sfs_ptr; - bool m_initialized; - XrdThrottleManager m_throttle; - XrdVersionInfo *myVersion; - -}; - -} - -#endif - diff --git a/src/XrdThrottle/XrdThrottleFile.cc b/src/XrdThrottle/XrdThrottleFile.cc deleted file mode 100644 index f0c252ebb67..00000000000 --- a/src/XrdThrottle/XrdThrottleFile.cc +++ /dev/null @@ -1,171 +0,0 @@ - -#include "XrdSfs/XrdSfsAio.hh" -#include "XrdSec/XrdSecEntity.hh" - -#include "XrdThrottle.hh" - -using namespace XrdThrottle; - -#define DO_LOADSHED if (m_throttle.CheckLoadShed(m_loadshed)) \ -{ \ - unsigned port; \ - std::string host; \ - m_throttle.PerformLoadShed(m_loadshed, host, port); \ - m_eroute.Emsg("File", "Performing load-shed for client", m_user.c_str()); \ - error.setErrInfo(port, host.c_str()); \ - return SFS_REDIRECT; \ -} - -#define DO_THROTTLE(amount) \ -DO_LOADSHED \ -m_throttle.Apply(amount, 1, m_uid); \ -XrdThrottleTimer xtimer = m_throttle.StartIOTimer(); - -File::File(const char *user, - int monid, - unique_sfs_ptr sfs, - XrdThrottleManager &throttle, - XrdSysError &eroute) -#if __cplusplus >= 201103L - : m_sfs(std::move(sfs)), // Guaranteed to be non-null by FileSystem::newFile -#else - : m_sfs(sfs), -#endif - m_uid(0), - m_user(user), - m_throttle(throttle), - m_eroute(eroute) -{} - -File::~File() -{} - -int -File::open(const char *fileName, - XrdSfsFileOpenMode openMode, - mode_t createMode, - const XrdSecEntity *client, - const char *opaque) -{ - m_uid = XrdThrottleManager::GetUid(client->name); - m_throttle.PrepLoadShed(opaque, m_loadshed); - return m_sfs->open(fileName, openMode, createMode, client, opaque); -} - -int -File::close() -{ - return m_sfs->close(); -} - -int -File::fctl(const int cmd, - const char *args, - XrdOucErrInfo &out_error) -{ - // Disable sendfile - if (cmd == SFS_FCTL_GETFD) return SFS_ERROR; - else return m_sfs->fctl(cmd, args, out_error); -} - -const char * -File::FName() -{ - return m_sfs->FName(); -} - -int -File::getMmap(void **Addr, off_t &Size) -{ // We cannot monitor mmap-based reads, so we disable them. - return SFS_ERROR; -} - -int -File::read(XrdSfsFileOffset fileOffset, - XrdSfsXferSize amount) -{ - DO_THROTTLE(amount) - return m_sfs->read(fileOffset, amount); -} - -XrdSfsXferSize -File::read(XrdSfsFileOffset fileOffset, - char *buffer, - XrdSfsXferSize buffer_size) -{ - DO_THROTTLE(buffer_size); - return m_sfs->read(fileOffset, buffer, buffer_size); -} - -int -File::read(XrdSfsAio *aioparm) -{ // We disable all AIO-based reads. - aioparm->Result = this->read((XrdSfsFileOffset)aioparm->sfsAio.aio_offset, - (char *)aioparm->sfsAio.aio_buf, - (XrdSfsXferSize)aioparm->sfsAio.aio_nbytes); - aioparm->doneRead(); - return SFS_OK; -} - -XrdSfsXferSize -File::write( XrdSfsFileOffset fileOffset, - const char *buffer, - XrdSfsXferSize buffer_size) -{ - DO_THROTTLE(buffer_size); - return m_sfs->write(fileOffset, buffer, buffer_size); -} - -int -File::write(XrdSfsAio *aioparm) -{ - aioparm->Result = this->write((XrdSfsFileOffset)aioparm->sfsAio.aio_offset, - (char *)aioparm->sfsAio.aio_buf, - (XrdSfsXferSize)aioparm->sfsAio.aio_nbytes); - aioparm->doneRead(); - return SFS_OK; - - return m_sfs->write(aioparm); -} - -int -File::sync() -{ - return m_sfs->sync(); -} - -int -File::sync(XrdSfsAio *aiop) -{ - aiop->Result = this->sync(); - aiop->doneWrite(); - return m_sfs->sync(aiop); -} - -int -File::stat(struct stat *buf) -{ - return m_sfs->stat(buf); -} - -int -File::truncate(XrdSfsFileOffset fileOffset) -{ - return m_sfs->truncate(fileOffset); -} - -int -File::getCXinfo(char cxtype[4], int &cxrsz) -{ - return m_sfs->getCXinfo(cxtype, cxrsz); -} - -int -File::SendData(XrdSfsDio *sfDio, - XrdSfsFileOffset offset, - XrdSfsXferSize size) -{ - DO_THROTTLE(size); - return m_sfs->SendData(sfDio, offset, size); -} - diff --git a/src/XrdThrottle/XrdThrottleFileSystem.cc b/src/XrdThrottle/XrdThrottleFileSystem.cc deleted file mode 100644 index 5727cdfdb59..00000000000 --- a/src/XrdThrottle/XrdThrottleFileSystem.cc +++ /dev/null @@ -1,178 +0,0 @@ - -#include "XrdOfs/XrdOfs.hh" - -#include "XrdThrottle/XrdThrottle.hh" - -using namespace XrdThrottle; - -/* - * A whole ton of pass-through functions which chain to the underlying SFS. - */ - -XrdSfsDirectory * -FileSystem::newDir(char *user, - int monid) -{ - return (XrdSfsDirectory *)new XrdOfsDirectory(user, monid); -} - -XrdSfsFile * -FileSystem::newFile(char *user, - int monid) -{ - XrdSfsFile * chain_file = m_sfs_ptr->newFile(user, monid); - if (chain_file) - { - unique_sfs_ptr chain_file_ptr(chain_file); - // We should really be giving out shared_ptrs to m_throttle, but alas, no boost. -#if __cplusplus >= 201103L - return static_cast(new File(user, monid, std::move(chain_file_ptr), m_throttle, m_eroute)); -#else - return static_cast(new File(user, monid, chain_file_ptr, m_throttle, m_eroute)); -#endif - } - return NULL; -} - -int -FileSystem::chksum( csFunc Func, - const char *csName, - const char *path, - XrdOucErrInfo &eInfo, - const XrdSecEntity *client, - const char *opaque) -{ - return m_sfs_ptr->chksum(Func, csName, path, eInfo, client, opaque); -} - -int -FileSystem::chmod(const char *Name, - XrdSfsMode Mode, - XrdOucErrInfo &out_error, - const XrdSecEntity *client, - const char *opaque) -{ - return m_sfs_ptr->chmod(Name, Mode, out_error, client, opaque); -} - -void -FileSystem::Disc(const XrdSecEntity *client) -{ - m_sfs_ptr->Disc(client); -} - -void -FileSystem::EnvInfo(XrdOucEnv *envP) -{ - m_sfs_ptr->EnvInfo(envP); -} - -int -FileSystem::exists(const char *fileName, - XrdSfsFileExistence &exists_flag, - XrdOucErrInfo &out_error, - const XrdSecEntity *client, - const char *opaque) -{ - return m_sfs_ptr->exists(fileName, exists_flag, out_error, client, opaque); -} - -int -FileSystem::fsctl(const int cmd, - const char *args, - XrdOucErrInfo &out_error, - const XrdSecEntity *client) -{ - return m_sfs_ptr->fsctl(cmd, args, out_error, client); -} - -int -FileSystem::getStats(char *buff, - int blen) -{ - return m_sfs_ptr->getStats(buff, blen); -} - -const char * -FileSystem::getVersion() -{ - return XrdVERSION; -} - -int -FileSystem::mkdir(const char *dirName, - XrdSfsMode Mode, - XrdOucErrInfo &out_error, - const XrdSecEntity *client, - const char *opaque) -{ - return m_sfs_ptr->mkdir(dirName, Mode, out_error, client, opaque); -} - -int -FileSystem::prepare( XrdSfsPrep &pargs, - XrdOucErrInfo &out_error, - const XrdSecEntity *client) -{ - return m_sfs_ptr->prepare(pargs, out_error, client); -} - -int -FileSystem::rem(const char *path, - XrdOucErrInfo &out_error, - const XrdSecEntity *client, - const char *info) -{ - return m_sfs_ptr->rem(path, out_error, client, info); -} - -int -FileSystem::remdir(const char *dirName, - XrdOucErrInfo &out_error, - const XrdSecEntity *client, - const char *info) -{ - return m_sfs_ptr->remdir(dirName, out_error, client, info); -} - -int -FileSystem::rename(const char *oldFileName, - const char *newFileName, - XrdOucErrInfo &out_error, - const XrdSecEntity *client, - const char *infoO, - const char *infoN) -{ - return m_sfs_ptr->rename(oldFileName, newFileName, out_error, client, infoO, infoN); -} - -int -FileSystem::stat(const char *Name, - struct stat *buf, - XrdOucErrInfo &out_error, - const XrdSecEntity *client, - const char *opaque) -{ - return m_sfs_ptr->stat(Name, buf, out_error, client, opaque); -} - -int -FileSystem::stat(const char *Name, - mode_t &mode, - XrdOucErrInfo &out_error, - const XrdSecEntity *client, - const char *opaque) -{ - return m_sfs_ptr->stat(Name, mode, out_error, client, opaque); -} - -int -FileSystem::truncate(const char *Name, - XrdSfsFileOffset fileOffset, - XrdOucErrInfo &out_error, - const XrdSecEntity *client, - const char *opaque) -{ - return m_sfs_ptr->truncate(Name, fileOffset, out_error, client, opaque); -} - diff --git a/src/XrdThrottle/XrdThrottleFileSystemConfig.cc b/src/XrdThrottle/XrdThrottleFileSystemConfig.cc deleted file mode 100644 index 22fa6a33778..00000000000 --- a/src/XrdThrottle/XrdThrottleFileSystemConfig.cc +++ /dev/null @@ -1,351 +0,0 @@ - -#include - -#include "XrdSys/XrdSysPlugin.hh" -#include "XrdOuc/XrdOuca2x.hh" -#include "XrdOuc/XrdOucEnv.hh" -#include "XrdOuc/XrdOucStream.hh" - -#include "XrdThrottle/XrdThrottle.hh" -#include "XrdThrottle/XrdThrottleTrace.hh" - -using namespace XrdThrottle; - -#define OFS_NAME "libXrdOfs.so" - -/* - * Note nothing in this file is thread-safe. - */ - -static XrdSfsFileSystem * -LoadFS(const std::string &fslib, XrdSysError &eDest, const std::string &config_file){ - // Load the library - XrdSysPlugin ofsLib(&eDest, fslib.c_str(), "fslib", NULL); - XrdSfsFileSystem *fs; - if (fslib == OFS_NAME) - { - extern XrdSfsFileSystem *XrdSfsGetDefaultFileSystem(XrdSfsFileSystem *native_fs, - XrdSysLogger *lp, - const char *configfn, - XrdOucEnv *EnvInfo); - - if (!(fs = XrdSfsGetDefaultFileSystem(0, eDest.logger(), config_file.c_str(), 0))) - { - eDest.Emsg("Config", "Unable to load OFS filesystem."); - } - } - else - { - XrdSfsFileSystem *(*ep)(XrdSfsFileSystem *, XrdSysLogger *, const char *); - if (!(ep = (XrdSfsFileSystem *(*)(XrdSfsFileSystem *,XrdSysLogger *,const char *)) - ofsLib.getPlugin("XrdSfsGetFileSystem"))) - return NULL; - if (!(fs = (*ep)(0, eDest.logger(), config_file.c_str()))) - { - eDest.Emsg("Config", "Unable to create file system object via", fslib.c_str()); - return NULL; - } - } - ofsLib.Persist(); - - return fs; -} - -namespace XrdThrottle { -XrdSfsFileSystem * -XrdSfsGetFileSystem_Internal(XrdSfsFileSystem *native_fs, - XrdSysLogger *lp, - const char *configfn) -{ - FileSystem* fs = NULL; - FileSystem::Initialize(fs, native_fs, lp, configfn); - return fs; -} -} - -// Export the symbol necessary for this to be dynamically loaded. -extern "C" { -XrdSfsFileSystem * -XrdSfsGetFileSystem(XrdSfsFileSystem *native_fs, - XrdSysLogger *lp, - const char *configfn) -{ - return XrdSfsGetFileSystem_Internal(native_fs, lp, configfn); -} -} - -XrdVERSIONINFO(XrdSfsGetFileSystem, FileSystem); - -FileSystem* FileSystem::m_instance = 0; - -FileSystem::FileSystem() - : m_eroute(0), m_trace(&m_eroute), m_sfs_ptr(0), m_initialized(false), m_throttle(&m_eroute, &m_trace) -{ - myVersion = &XrdVERSIONINFOVAR(XrdSfsGetFileSystem); -} - -FileSystem::~FileSystem() {} - -void -FileSystem::Initialize(FileSystem *&fs, - XrdSfsFileSystem *native_fs, - XrdSysLogger *lp, - const char *configfn) -{ - fs = NULL; - if (m_instance == NULL && !(m_instance = new FileSystem())) - { - return; - } - fs = m_instance; - if (!fs->m_initialized) - { - fs->m_config_file = configfn; - fs->m_eroute.logger(lp); - fs->m_eroute.Say("Initializing a Throttled file system."); - if (fs->Configure(fs->m_eroute, native_fs)) - { - fs->m_eroute.Say("Initialization of throttled file system failed."); - fs = NULL; - return; - } - fs->m_throttle.Init(); - fs->m_initialized = true; - } -} - -#define TS_Xeq(key, func) NoGo = (strcmp(key, var) == 0) ? func(Config) : 0 -int -FileSystem::Configure(XrdSysError & log, XrdSfsFileSystem *native_fs) -{ - XrdOucEnv myEnv; - XrdOucStream Config(&m_eroute, getenv("XRDINSTANCE"), &myEnv, "(Throttle Config)> "); - int cfgFD; - if (m_config_file.length() == 0) - { - log.Say("No filename specified."); - return 1; - } - if ((cfgFD = open(m_config_file.c_str(), O_RDONLY)) < 0) - { - log.Emsg("Config", errno, "Unable to open configuration file", m_config_file.c_str()); - return 1; - } - Config.Attach(cfgFD); - - std::string fslib = OFS_NAME; - - char *var, *val; - int NoGo = 0; - while( (var = Config.GetMyFirstWord()) ) - { - if (strcmp("throttle.fslib", var) == 0) - { - val = Config.GetWord(); - if (!val || !val[0]) {log.Emsg("Config", "fslib not specified."); continue;} - fslib = val; - } - TS_Xeq("throttle.throttle", xthrottle); - TS_Xeq("throttle.loadshed", xloadshed); - TS_Xeq("throttle.trace", xtrace); - if (NoGo) - { - log.Emsg("Config", "Throttle configuration failed."); - } - } - - // Load the filesystem object. - m_sfs_ptr = native_fs ? native_fs : LoadFS(fslib, m_eroute, m_config_file); - if (!m_sfs_ptr) return 1; - - // Overwrite the environment variable saying that throttling is the fslib. - XrdOucEnv::Export("XRDOFSLIB", fslib.c_str()); - - return 0; -} - -/******************************************************************************/ -/* x t h r o t t l e */ -/******************************************************************************/ - -/* Function: xthrottle - - Purpose: To parse the directive: throttle [data ] [iops ] [concurrency ] [interval ] - - maximum bytes per second through the server. - maximum IOPS per second through the server. - maximum number of concurrent IO connections. - minimum interval in milliseconds between throttle re-computing. - - Output: 0 upon success or !0 upon failure. -*/ -int -FileSystem::xthrottle(XrdOucStream &Config) -{ - long long drate = -1, irate = -1, rint = 1000, climit = -1; - char *val; - - while ((val = Config.GetWord())) - { - if (strcmp("data", val) == 0) - { - if (!(val = Config.GetWord())) - {m_eroute.Emsg("Config", "data throttle limit not specified."); return 1;} - if (XrdOuca2x::a2sz(m_eroute,"data throttle value",val,&drate,1)) return 1; - } - else if (strcmp("iops", val) == 0) - { - if (!(val = Config.GetWord())) - {m_eroute.Emsg("Config", "IOPS throttle limit not specified."); return 1;} - if (XrdOuca2x::a2sz(m_eroute,"IOPS throttle value",val,&irate,1)) return 1; - } - else if (strcmp("rint", val) == 0) - { - if (!(val = Config.GetWord())) - {m_eroute.Emsg("Config", "recompute interval not specified."); return 1;} - if (XrdOuca2x::a2sp(m_eroute,"recompute interval value",val,&rint,10)) return 1; - } - else if (strcmp("concurrency", val) == 0) - { - if (!(val = Config.GetWord())) - {m_eroute.Emsg("Config", "Concurrency limit not specified."); return 1;} - if (XrdOuca2x::a2sz(m_eroute,"Concurrency limit value",val,&climit,1)) return 1; - } - else - { - m_eroute.Emsg("Config", "Warning - unknown throttle option specified", val, "."); - } - } - - m_throttle.SetThrottles(drate, irate, climit, static_cast(rint)/1000.0); - return 0; -} - -/******************************************************************************/ -/* x l o a d s h e d */ -/******************************************************************************/ - -/* Function: xloadshed - - Purpose: To parse the directive: loadshed host [port ] [frequency ] - - hostname of server to shed load to. Required - port of server to shed load to. Defaults to 1094 - A value from 1 to 100 specifying how often to shed load - (1 = 1% chance; 100 = 100% chance; defaults to 10). - - Output: 0 upon success or !0 upon failure. -*/ -int FileSystem::xloadshed(XrdOucStream &Config) -{ - long long port = 0, freq = 0; - char *val; - std::string hostname; - - while ((val = Config.GetWord())) - { - if (strcmp("host", val) == 0) - { - if (!(val = Config.GetWord())) - {m_eroute.Emsg("Config", "loadshed hostname not specified."); return 1;} - hostname = val; - } - else if (strcmp("port", val) == 0) - { - if (!(val = Config.GetWord())) - {m_eroute.Emsg("Config", "Port number not specified."); return 1;} - if (XrdOuca2x::a2sz(m_eroute,"Port number",val,&port,1, 65536)) return 1; - } - else if (strcmp("frequency", val) == 0) - { - if (!(val = Config.GetWord())) - {m_eroute.Emsg("Config", "Loadshed frequency not specified."); return 1;} - if (XrdOuca2x::a2sz(m_eroute,"Loadshed frequency",val,&freq,1,100)) return 1; - } - else - { - m_eroute.Emsg("Config", "Warning - unknown loadshed option specified", val, "."); - } - } - - if (hostname.empty()) - { - m_eroute.Emsg("Config", "must specify hostname for loadshed parameter."); - return 1; - } - - m_throttle.SetLoadShed(hostname, port, freq); - return 0; -} - -/******************************************************************************/ -/* x t r a c e */ -/******************************************************************************/ - -/* Function: xtrace - - Purpose: To parse the directive: trace - - the blank separated list of events to trace. Trace - directives are cummalative. - - Output: 0 upon success or 1 upon failure. -*/ - -int FileSystem::xtrace(XrdOucStream &Config) -{ - char *val; - static const struct traceopts {const char *opname; int opval;} tropts[] = - { - {"all", TRACE_ALL}, - {"off", TRACE_NONE}, - {"none", TRACE_NONE}, - {"debug", TRACE_DEBUG}, - {"iops", TRACE_IOPS}, - {"bandwidth", TRACE_BANDWIDTH}, - {"ioload", TRACE_IOLOAD}, - }; - int i, neg, trval = 0, numopts = sizeof(tropts)/sizeof(struct traceopts); - - if (!(val = Config.GetWord())) - { - m_eroute.Emsg("Config", "trace option not specified"); - return 1; - } - while (val) - { - if (!strcmp(val, "off")) - { - trval = 0; - } - else - { - if ((neg = (val[0] == '-' && val[1]))) - { - val++; - } - for (i = 0; i < numopts; i++) - { - if (!strcmp(val, tropts[i].opname)) - { - if (neg) - { - if (tropts[i].opval) trval &= ~tropts[i].opval; - else trval = TRACE_ALL; - } - else if (tropts[i].opval) trval |= tropts[i].opval; - else trval = TRACE_NONE; - break; - } - } - if (i >= numopts) - { - m_eroute.Say("Config warning: ignoring invalid trace option '", val, "'."); - } - } - val = Config.GetWord(); - } - m_trace.What = trval; - return 0; -} - diff --git a/src/XrdThrottle/XrdThrottleManager.cc b/src/XrdThrottle/XrdThrottleManager.cc deleted file mode 100644 index 83e26a829a0..00000000000 --- a/src/XrdThrottle/XrdThrottleManager.cc +++ /dev/null @@ -1,375 +0,0 @@ - -#include "XrdThrottleManager.hh" - -#include "XrdSys/XrdSysAtomics.hh" -#include "XrdSys/XrdSysTimer.hh" - -#include "XrdOuc/XrdOucEnv.hh" - -#define XRD_TRACE m_trace-> -#include "XrdThrottle/XrdThrottleTrace.hh" - -const char * -XrdThrottleManager::TraceID = "ThrottleManager"; - -const -int XrdThrottleManager::m_max_users = 1024; - -#if defined(__linux__) -int clock_id; -int XrdThrottleTimer::clock_id = clock_getcpuclockid(0, &clock_id) != ENOENT ? CLOCK_THREAD_CPUTIME_ID : CLOCK_MONOTONIC; -#else -int XrdThrottleTimer::clock_id = 0; -#endif - -XrdThrottleManager::XrdThrottleManager(XrdSysError *lP, XrdOucTrace *tP) : - m_trace(tP), - m_log(lP), - m_interval_length_seconds(1.0), - m_bytes_per_second(-1), - m_ops_per_second(-1), - m_concurrency_limit(-1), - m_last_round_allocation(100*1024), - m_io_counter(0), - m_loadshed_host(""), - m_loadshed_port(0), - m_loadshed_frequency(0), - m_loadshed_limit_hit(0) -{ - m_stable_io_wait.tv_sec = 0; - m_stable_io_wait.tv_nsec = 0; -} - -void -XrdThrottleManager::Init() -{ - TRACE(DEBUG, "Initializing the throttle manager."); - // Initialize all our shares to zero. - m_primary_bytes_shares.reserve(m_max_users); - m_secondary_bytes_shares.reserve(m_max_users); - m_primary_ops_shares.reserve(m_max_users); - m_secondary_ops_shares.reserve(m_max_users); - // Allocate each user 100KB and 10 ops to bootstrap; - for (int i=0; i(this), 0, "Buffer Manager throttle"))) - m_log->Emsg("ThrottleManager", rc, "create throttle thread"); - -} - -/* - * Take as many shares as possible to fulfill the request; update - * request with current remaining value, or zero if satisfied. - */ -inline void -XrdThrottleManager::GetShares(int &shares, int &request) -{ - int remaining; - AtomicFSub(remaining, shares, request); - if (remaining > 0) - { - request -= (remaining < request) ? remaining : request; - } -} - -/* - * Iterate through all of the secondary shares, attempting - * to steal enough to fulfill the request. - */ -void -XrdThrottleManager::StealShares(int uid, int &reqsize, int &reqops) -{ - if (!reqsize && !reqops) return; - TRACE(BANDWIDTH, "Stealing shares to fill request of " << reqsize << " bytes"); - TRACE(IOPS, "Stealing shares to fill request of " << reqops << " ops."); - - for (int i=uid+1; i % m_max_users == uid; i++) - { - if (reqsize) GetShares(m_secondary_bytes_shares[i % m_max_users], reqsize); - if (reqops) GetShares(m_secondary_ops_shares[ i % m_max_users], reqops); - } - - TRACE(BANDWIDTH, "After stealing shares, " << reqsize << " of request bytes remain."); - TRACE(IOPS, "After stealing shares, " << reqops << " of request ops remain."); -} - -/* - * Apply the throttle. If there are no limits set, returns immediately. Otherwise, - * this applies the limits as best possible, stalling the thread if necessary. - */ -void -XrdThrottleManager::Apply(int reqsize, int reqops, int uid) -{ - if (m_bytes_per_second < 0) - reqsize = 0; - if (m_ops_per_second < 0) - reqops = 0; - while (reqsize || reqops) - { - // Subtract the requested out of the shares - AtomicBeg(m_compute_var); - GetShares(m_primary_bytes_shares[uid], reqsize); - if (reqsize) - { - TRACE(BANDWIDTH, "Using secondary shares; request has " << reqsize << " bytes left."); - GetShares(m_secondary_bytes_shares[uid], reqsize); - TRACE(BANDWIDTH, "Finished with secondary shares; request has " << reqsize << " bytes left."); - } - else - { - TRACE(BANDWIDTH, "Filled byte shares out of primary; " << m_primary_bytes_shares[uid] << " left."); - } - GetShares(m_primary_ops_shares[uid], reqops); - if (reqops) - { - GetShares(m_secondary_ops_shares[uid], reqops); - } - StealShares(uid, reqsize, reqops); - AtomicEnd(m_compute_var); - - if (reqsize || reqops) - { - if (reqsize) TRACE(BANDWIDTH, "Sleeping to wait for throttle fairshare."); - if (reqops) TRACE(IOPS, "Sleeping to wait for throttle fairshare."); - m_compute_var.Wait(); - AtomicBeg(m_compute_var); - AtomicInc(m_loadshed_limit_hit); - AtomicEnd(m_compute_var); - } - } - -} - -void * -XrdThrottleManager::RecomputeBootstrap(void *instance) -{ - XrdThrottleManager * manager = static_cast(instance); - manager->Recompute(); - return NULL; -} - -void -XrdThrottleManager::Recompute() -{ - while (1) - { - TRACE(DEBUG, "Recomputing fairshares for throttle."); - RecomputeInternal(); - TRACE(DEBUG, "Finished recomputing fairshares for throttle; sleeping for " << m_interval_length_seconds << " seconds."); - XrdSysTimer::Wait(static_cast(1000*m_interval_length_seconds)); - } -} - -/* - * The heart of the manager approach. - * - * This routine periodically recomputes the shares of each current user. - * Each user has a "primary" and a "secondary" share. At the end of the - * each time interval, the remaining primary share is moved to secondary. - * A user can utilize both shares; if both are gone, they must block until - * the next recompute interval. - * - * The secondary share can be "stolen" by any other user; so, if a user - * is idle or under-utilizing, their share can be used by someone else. - * However, they can never be completely starved, as no one can steal - * primary share. - * - * In this way, we violate the throttle for an interval, but never starve. - * - */ -void -XrdThrottleManager::RecomputeInternal() -{ - // Compute total shares for this interval; - float intervals_per_second = 1.0/m_interval_length_seconds; - float total_bytes_shares = m_bytes_per_second / intervals_per_second; - float total_ops_shares = m_ops_per_second / intervals_per_second; - - // Compute the number of active users; a user is active if they used - // any primary share during the last interval; - AtomicBeg(m_compute_var); - float active_users = 0; - long bytes_used = 0; - for (int i=0; i= 0) - m_secondary_bytes_shares[i] = primary; - primary = AtomicFAZ(m_primary_ops_shares[i]); - if (primary >= 0) - m_secondary_ops_shares[i] = primary; - bytes_used += (primary < 0) ? m_last_round_allocation : (m_last_round_allocation-primary); - } - } - - if (active_users == 0) - { - active_users++; - } - - // Note we allocate the same number of shares to *all* users, not - // just the active ones. If a new user becomes active in the next - // interval, we'll go over our bandwidth budget just a bit. - m_last_round_allocation = static_cast(total_bytes_shares / active_users); - int ops_shares = static_cast(total_ops_shares / active_users); - TRACE(BANDWIDTH, "Round byte allocation " << m_last_round_allocation << " ; last round used " << bytes_used << "."); - TRACE(IOPS, "Round ops allocation " << ops_shares); - for (int i=0; i(secs * intervals_per_second); - m_stable_io_wait.tv_nsec += static_cast(nsecs * intervals_per_second); - while (m_stable_io_wait.tv_nsec > 1000000000) - { - m_stable_io_wait.tv_nsec -= 1000000000; - m_stable_io_wait.tv_nsec --; - } - m_compute_var.UnLock(); - TRACE(IOLOAD, "Current IO counter is " << m_stable_io_counter << "; total IO wait time is " << (m_stable_io_wait.tv_sec*1000+m_stable_io_wait.tv_nsec/1000000) << "ms."); - m_compute_var.Broadcast(); -} - -/* - * Do a simple hash across the username. - */ -int -XrdThrottleManager::GetUid(const char *username) -{ - const char *cur = username; - int hval = 0; - while (cur && *cur && *cur != '@' && *cur != '.') - { - hval += *cur; - hval %= m_max_users; - cur++; - } - //cerr << "Calculated UID " << hval << " for " << username << endl; - return hval; -} - -/* - * Create an IO timer object; increment the number of outstanding IOs. - */ -XrdThrottleTimer -XrdThrottleManager::StartIOTimer() -{ - AtomicBeg(m_compute_var); - int cur_counter = AtomicInc(m_io_counter); - AtomicEnd(m_compute_var); - while (m_concurrency_limit >= 0 && cur_counter > m_concurrency_limit) - { - AtomicBeg(m_compute_var); - AtomicInc(m_loadshed_limit_hit); - AtomicDec(m_io_counter); - AtomicEnd(m_compute_var); - m_compute_var.Wait(); - AtomicBeg(m_compute_var); - cur_counter = AtomicInc(m_io_counter); - AtomicEnd(m_compute_var); - } - return XrdThrottleTimer(*this); -} - -/* - * Finish recording an IO timer. - */ -void -XrdThrottleManager::StopIOTimer(struct timespec timer) -{ - AtomicBeg(m_compute_var); - AtomicDec(m_io_counter); - AtomicAdd(m_io_wait.tv_sec, timer.tv_sec); - // Note this may result in tv_nsec > 1e9 - AtomicAdd(m_io_wait.tv_nsec, timer.tv_nsec); - AtomicEnd(m_compute_var); -} - -/* - * Check the counters to see if we have hit any throttle limits in the - * current time period. If so, shed the client randomly. - * - * If the client has already been load-shedded once and reconnected to this - * server, then do not load-shed it again. - */ -bool -XrdThrottleManager::CheckLoadShed(const std::string &opaque) -{ - if (m_loadshed_port == 0) - { - return false; - } - if (AtomicGet(m_loadshed_limit_hit) == 0) - { - return false; - } - if (static_cast(rand()) % 100 > m_loadshed_frequency) - { - return false; - } - if (opaque.empty()) - { - return false; - } - return true; -} - -void -XrdThrottleManager::PrepLoadShed(const char * opaque, std::string &lsOpaque) -{ - if (m_loadshed_port == 0) - { - return; - } - if (opaque && opaque[0]) - { - XrdOucEnv env(opaque); - // Do not load shed client if it has already been done once. - if (env.Get("throttle.shed") != 0) - { - return; - } - lsOpaque = opaque; - lsOpaque += "&throttle.shed=1"; - } - else - { - lsOpaque = "throttle.shed=1"; - } -} - -void -XrdThrottleManager::PerformLoadShed(const std::string &opaque, std::string &host, unsigned &port) -{ - host = m_loadshed_host; - host += "?"; - host += opaque; - port = m_loadshed_port; -} diff --git a/src/XrdThrottle/XrdThrottleManager.hh b/src/XrdThrottle/XrdThrottleManager.hh deleted file mode 100644 index 86af12fc573..00000000000 --- a/src/XrdThrottle/XrdThrottleManager.hh +++ /dev/null @@ -1,199 +0,0 @@ - -/* - * XrdThrottleManager - * - * This class provides an implementation of a throttle manager. - * The throttled manager purposely pause if the bandwidth, IOPS - * rate, or number of outstanding IO requests is sustained above - * a certain level. - * - * The XrdThrottleManager is user-aware and provides fairshare. - * - * This works by having a separate thread periodically refilling - * each user's shares. - * - * Note that we do not actually keep close track of users, but rather - * put them into a hash. This way, we can pretend there's a constant - * number of users and use a lock-free algorithm. - */ - -#ifndef __XrdThrottleManager_hh_ -#define __XrdThrottleManager_hh_ - -#ifdef __GNUC__ -#define likely(x) __builtin_expect(!!(x), 1) -#define unlikely(x) __builtin_expect(!!(x), 0) -#else -#define likely(x) x -#define unlikely(x) x -#endif - -#include -#include -#include - -#include "XrdSys/XrdSysPthread.hh" - -class XrdSysError; -class XrdOucTrace; -class XrdThrottleTimer; - -class XrdThrottleManager -{ - -friend class XrdThrottleTimer; - -public: - -void Init(); - -void Apply(int reqsize, int reqops, int uid); - -bool IsThrottling() {return (m_ops_per_second > 0) || (m_bytes_per_second > 0);} - -void SetThrottles(float reqbyterate, float reqoprate, int concurrency, float interval_length) - {m_interval_length_seconds = interval_length; m_bytes_per_second = reqbyterate; - m_ops_per_second = reqoprate; m_concurrency_limit = concurrency;} - -void SetLoadShed(std::string &hostname, unsigned port, unsigned frequency) - {m_loadshed_host = hostname; m_loadshed_port = port; m_loadshed_frequency = frequency;} - -//int Stats(char *buff, int blen, int do_sync=0) {return m_pool.Stats(buff, blen, do_sync);} - -static -int GetUid(const char *username); - -XrdThrottleTimer StartIOTimer(); - -void PrepLoadShed(const char *opaque, std::string &lsOpaque); - -bool CheckLoadShed(const std::string &opaque); - -void PerformLoadShed(const std::string &opaque, std::string &host, unsigned &port); - - XrdThrottleManager(XrdSysError *lP, XrdOucTrace *tP); - - ~XrdThrottleManager() {} // The buffmanager is never deleted - -protected: - -void StopIOTimer(struct timespec); - -private: - -void Recompute(); - -void RecomputeInternal(); - -static -void * RecomputeBootstrap(void *pp); - -int WaitForShares(); - -void GetShares(int &shares, int &request); - -void StealShares(int uid, int &reqsize, int &reqops); - -XrdOucTrace * m_trace; -XrdSysError * m_log; - -XrdSysCondVar m_compute_var; - -// Controls for the various rates. -float m_interval_length_seconds; -float m_bytes_per_second; -float m_ops_per_second; -int m_concurrency_limit; - -// Maintain the shares -static const -int m_max_users; -std::vector m_primary_bytes_shares; -std::vector m_secondary_bytes_shares; -std::vector m_primary_ops_shares; -std::vector m_secondary_ops_shares; -int m_last_round_allocation; - -// Active IO counter -int m_io_counter; -struct timespec m_io_wait; -// Stable IO counters - must hold m_compute_var lock when reading/writing; -int m_stable_io_counter; -struct timespec m_stable_io_wait; - -// Load shed details -std::string m_loadshed_host; -unsigned m_loadshed_port; -unsigned m_loadshed_frequency; -int m_loadshed_limit_hit; - -static const char *TraceID; - -}; - -class XrdThrottleTimer -{ - -friend class XrdThrottleManager; - -public: - -void StopTimer() -{ - struct timespec end_timer = {0, 0}; -#if defined(__linux__) - int retval = clock_gettime(clock_id, &end_timer); -#else - int retval = -1; -#endif - if (likely(retval == 0)) - { - end_timer.tv_sec -= m_timer.tv_sec; - end_timer.tv_nsec -= m_timer.tv_nsec; - if (end_timer.tv_nsec < 0) - { - end_timer.tv_sec--; - end_timer.tv_nsec += 1000000000; - } - } - if (m_timer.tv_nsec != -1) - { - m_manager.StopIOTimer(end_timer); - } - m_timer.tv_sec = 0; - m_timer.tv_nsec = -1; -} - -~XrdThrottleTimer() -{ - if (!((m_timer.tv_sec == 0) && (m_timer.tv_nsec == -1))) - { - StopTimer(); - } -} - -protected: - -XrdThrottleTimer(XrdThrottleManager & manager) : - m_manager(manager) -{ -#if defined(__linux__) - int retval = clock_gettime(clock_id, &m_timer); -#else - int retval = -1; -#endif - if (unlikely(retval == -1)) - { - m_timer.tv_sec = 0; - m_timer.tv_nsec = 0; - } -} - -private: -XrdThrottleManager &m_manager; -struct timespec m_timer; - -static int clock_id; -}; - -#endif diff --git a/src/XrdThrottle/XrdThrottleTrace.hh b/src/XrdThrottle/XrdThrottleTrace.hh deleted file mode 100644 index e601bc949a9..00000000000 --- a/src/XrdThrottle/XrdThrottleTrace.hh +++ /dev/null @@ -1,42 +0,0 @@ - -#ifndef _XRDTHROTTLE_TRACE_H -#define _XRDTHROTTLE_TRACE_H - -// Trace flags -// -#define TRACE_NONE 0x0000 -#define TRACE_ALL 0x0fff -#define TRACE_BANDWIDTH 0x0001 -#define TRACE_IOPS 0x0002 -#define TRACE_IOLOAD 0x0004 -#define TRACE_DEBUG 0x0008 - -#ifndef NODEBUG - -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdOuc/XrdOucTrace.hh" - -#ifndef XRD_TRACE -#define XRD_TRACE m_trace-> -#endif - -#define TRACE(act, x) \ - if (XRD_TRACE What & TRACE_ ## act) \ - {XRD_TRACE Beg(TraceID); cerr <ID); cerr <. */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -/* The following section defines the versioning rules for plugins. The rules are - applied by 'XrdSysPlugin.cc'. The rules defined by the XrdVERSIONPLUGIN_RULE - macro (see below) are used to initialize a data the following data structure. -*/ - struct XrdVersionPlugin - {const char *pName; //!< -> plugin object creator function name - char vPfxLen; //!< Generic rule prefix length - char vSfxLen; //!< Generic rule suffix length for preceeding - int vProcess; //!< version: <0 skip, =0 optional, >0 required - short vMajLow; //!< Lowest compatible major version number - short vMinLow; //!< Lowest compatible minor (>99 don't check). - }; - -/* The rules are defined here because they apply to every class that uses a - plugin. This file *must* be updated whenever a plugin interface materially - changes; including any material changes (layout or size) to any classes - passed as arguments to the plugin. -*/ - -// Macros used to define version checking rule values (see explanation below). -// -#define XrdVERSIONPLUGIN_DoNotChk -1 -#define XrdVERSIONPLUGIN_Optional 0 -#define XrdVERSIONPLUGIN_Required 1 - -#define XrdVERSIONPLUGIN_Rule(procMode, majorVer, minorVer, piSymbol)\ - {#piSymbol, 0, 0, XrdVERSIONPLUGIN_##procMode, majorVer, minorVer}, - -/* Each rule must be defined by the XrdVERSIONPLUGIN_Rule macro which takes four - arguments, as follows: - - procMode: Version procsessing mode: - DoNotChk -> Skip version check as it's already been done by a - previous getPlugin() call for a library symbol. - Optional -> Version check is optional, do it if version information - present but warn if it is missing. - Required -> Version check required; plugin must define a version - number and issue error message if it is missing. - - majorVer: The required major version number. It is checked as follows: - < 0: major version numbers must be identical. - >= 0: is the lowest valid major version number allowed. - - minorVer: The required minor version number, It is check as follows: - < 0: minor version numbers must be identical. - >= 0: the lowest valid minor version for the major number allowed. - > 99: Do not check the minor version number, it's immaterial. - - piSymbol: The plugin's object creator's unquoted function name. When this - symbol is looked-up, the defined version rule is applied. - - Note: a plugin may not have a major.minor version number greater than the - program's major.minor version number unless either one is unreleased. - Unreleased versions can use any version. However, a message is issued. -*/ -#define XrdVERSIONPLUGINRULES \ - XrdVERSIONPLUGIN_Rule(Required, 4, 0, XrdAccAuthorizeObject )\ - XrdVERSIONPLUGIN_Rule(Optional, 4, 0, XrdBwmPolicyObject )\ - XrdVERSIONPLUGIN_Rule(Required, 4, 0, XrdCksCalcInit )\ - XrdVERSIONPLUGIN_Rule(Required, 4, 0, XrdCksInit )\ - XrdVERSIONPLUGIN_Rule(Required, 4, 0, XrdCmsGetClient )\ - XrdVERSIONPLUGIN_Rule(Required, 4, 0, XrdCryptosslFactoryObject )\ - XrdVERSIONPLUGIN_Rule(Optional, 4, 0, XrdFileCacheGetDecision )\ - XrdVERSIONPLUGIN_Rule(DoNotChk, 4, 0, XrdgetProtocol )\ - XrdVERSIONPLUGIN_Rule(Required, 4, 0, XrdgetProtocolPort )\ - XrdVERSIONPLUGIN_Rule(Required, 4, 0, XrdHttpGetSecXtractor )\ - XrdVERSIONPLUGIN_Rule(Required, 4, 8, XrdHttpGetExtHandler )\ - XrdVERSIONPLUGIN_Rule(Required, 4, 0, XrdSysLogPInit )\ - XrdVERSIONPLUGIN_Rule(Required, 4, 0, XrdOssGetStorageSystem )\ - XrdVERSIONPLUGIN_Rule(Required, 4, 0, XrdOssStatInfoInit )\ - XrdVERSIONPLUGIN_Rule(Required, 4, 0, XrdOucGetCache )\ - XrdVERSIONPLUGIN_Rule(Optional, 4, 0, XrdOucgetName2Name )\ - XrdVERSIONPLUGIN_Rule(Required, 4, 0, XrdSecGetProtocol )\ - XrdVERSIONPLUGIN_Rule(Required, 4, 0, XrdSecgetService )\ - XrdVERSIONPLUGIN_Rule(Optional, 4, 0, XrdSecgsiAuthzFun )\ - XrdVERSIONPLUGIN_Rule(DoNotChk, 4, 0, XrdSecgsiAuthzInit )\ - XrdVERSIONPLUGIN_Rule(DoNotChk, 4, 0, XrdSecgsiAuthzKey )\ - XrdVERSIONPLUGIN_Rule(Optional, 4, 0, XrdSecgsiGMAPFun )\ - XrdVERSIONPLUGIN_Rule(Optional, 4, 0, XrdSecgsiVOMSFun )\ - XrdVERSIONPLUGIN_Rule(DoNotChk, 4, 0, XrdSecgsiVOMSInit )\ - XrdVERSIONPLUGIN_Rule(DoNotChk, 4, 0, XrdSecProtocolgsiInit )\ - XrdVERSIONPLUGIN_Rule(Required, 4, 0, XrdSecProtocolgsiObject )\ - XrdVERSIONPLUGIN_Rule(DoNotChk, 4, 0, XrdSecProtocolkrb5Init )\ - XrdVERSIONPLUGIN_Rule(Required, 4, 0, XrdSecProtocolkrb5Object )\ - XrdVERSIONPLUGIN_Rule(DoNotChk, 4, 0, XrdSecProtocolpwdInit )\ - XrdVERSIONPLUGIN_Rule(Required, 4, 0, XrdSecProtocolpwdObject )\ - XrdVERSIONPLUGIN_Rule(DoNotChk, 4, 0, XrdSecProtocolsssInit )\ - XrdVERSIONPLUGIN_Rule(Required, 4, 0, XrdSecProtocolsssObject )\ - XrdVERSIONPLUGIN_Rule(DoNotChk, 4, 0, XrdSecProtocolunixInit )\ - XrdVERSIONPLUGIN_Rule(Required, 4, 0, XrdSecProtocolunixObject )\ - XrdVERSIONPLUGIN_Rule(Required, 4, 0, XrdSfsGetFileSystem )\ - XrdVERSIONPLUGIN_Rule(Required, 4, 0, XrdSfsGetFileSystem2 )\ - XrdVERSIONPLUGIN_Rule(Required, 4, 0, XrdSysGetXAttrObject )\ - XrdVERSIONPLUGIN_Rule(Required, 4, 0, XrdClGetMonitor )\ - XrdVERSIONPLUGIN_Rule(Required, 4, 0, XrdClGetPlugIn )\ - { 0, 0, 0, 0, 0, 0} - -#define XrdVERSIONPLUGIN_Maxim(procMode, majorVer, minorVer, piPfx, piSfx)\ - {#piPfx #piSfx, static_cast(strlen(#piPfx)),\ - static_cast(strlen(#piSfx)),\ - XrdVERSIONPLUGIN_##procMode, majorVer, minorVer}, - -/* Each generic rule must be defined by the XrdVERSIONPLUGIN_Maxim macro which - takes five arguments. The first three are exactly the same as defined for - XrdVERSIONPLUGIN_Rule. The last two define a pefix/suffix match for the - symbol being looked up, as follows: - - piPfx: The leading characters of the plugin's object creator's unquoted - function name. When this symbol is looked-up, the defined version - rule is applied if the suffix, if any, also matches. - - piSfx: The trailing characters of the plugin's object creator's unquoted - function name. When this symbol is looked-up, the defined version - rule is applied if the prefix, if any, also matches. - - Note: An attempt is made to match the symbol using specific rules defined - by XRDVERSIONPLUGIN_Rule before using any generic rules. If a match - is found the same processing as for specific rules is applied. -*/ -#define XrdVERSIONPLUGINMAXIMS\ - XrdVERSIONPLUGIN_Maxim(DoNotChk, 4, 0, XrdSecProtocol, Init )\ - XrdVERSIONPLUGIN_Maxim(Required, 4, 0, XrdSecProtocol, Object )\ - XrdVERSIONPLUGIN_Maxim(Optional, 4, 0, XrdCrypto, FactoryObject)\ - { 0, 0, 0, 0, 0, 0} - -/* The following defines the list of plugins that are included in the base - code and are to be strictly name versioned upon loading (i.e. fallback - to an unversioned name is not allowed). This is enforced by XrdOucVerName. -*/ -#define XrdVERSIONPLUGINSTRICT \ - {"libXrdBwm.so", \ - "libXrdCksCalczcrc32.so", \ - "libXrdCryptossl.so", \ - "libXrdFileCache.so", \ - "libXrdHttp.so", \ - "libXrdOssSIgpfsT.so", \ - "libXrdPss.so", \ - "libXrdSec.so", \ - "libXrdSecgsi.so", \ - "libXrdSecgsiAUTHZVO.so", \ - "libXrdSecgsiGMAPDLAP.so", \ - "libXrdSeckrb5.so", \ - "libXrdSecpwd.so", \ - "libXrdSecsss.so", \ - "libXrdSecunix.so", \ - "libXrdXrootd.so", \ - 0} -#endif diff --git a/src/XrdXml.cmake b/src/XrdXml.cmake deleted file mode 100644 index 820ebe5bb61..00000000000 --- a/src/XrdXml.cmake +++ /dev/null @@ -1,68 +0,0 @@ - -include( XRootDCommon ) - -if ( LIBXML2_FOUND ) - set( XRDXML2_READER_FILES - XrdXml/XrdXmlRdrXml2.cc - XrdXml/XrdXmlRdrXml2.hh ) - set( XRDXML2_LIBRARIES ${LIBXML2_LIBRARIES} ) - if( CMAKE_VERSION VERSION_LESS "2.8" OR ${Solaris} STREQUAL "TRUE") - INCLUDE_DIRECTORIES( ${LIBXML2_INCLUDE_DIR} ) - endif() -else() - set( XRDXML2_READER_FILES "" ) - set( XRDXML2_LIBRARIES "" ) -endif() - -#------------------------------------------------------------------------------- -# Shared library version -#------------------------------------------------------------------------------- -set( XRD_XML_VERSION 2.0.0 ) -set( XRD_XML_SOVERSION 2 ) -set( XRD_XML_PRELOAD_VERSION 1.0.0 ) -set( XRD_XML_PRELOAD_SOVERSION 1 ) - -#------------------------------------------------------------------------------- -# The XrdXml library -#------------------------------------------------------------------------------- -add_library( - XrdXml - SHARED - XrdXml/tinystr.cpp XrdXml/tinystr.h - XrdXml/tinyxml.cpp XrdXml/tinyxml.h - XrdXml/tinyxmlerror.cpp - XrdXml/tinyxmlparser.cpp - XrdXml/XrdXmlMetaLink.cc XrdXml/XrdXmlMetaLink.hh - XrdXml/XrdXmlRdrTiny.cc XrdXml/XrdXmlRdrTiny.hh - XrdXml/XrdXmlReader.cc XrdXml/XrdXmlReader.hh - ${XRDXML2_READER_FILES} ) - -target_link_libraries( - XrdXml - XrdUtils - ${XRDXML2_LIBRARIES} - pthread ) -# INCLUDE_DIRECTORIES /usr/include/libxml2 - -set_target_properties( - XrdXml - PROPERTIES - INCLUDE_DIRECTORIES ${CMAKE_SOURCE_DIR}/src/XrdXml/. - VERSION ${XRD_XML_VERSION} - SOVERSION ${XRD_XML_SOVERSION} - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) - -if ( LIBXML2_FOUND ) - set_target_properties( - XrdXml - PROPERTIES - INCLUDE_DIRECTORIES ${LIBXML2_INCLUDE_DIR} ) -endif() - -#------------------------------------------------------------------------------- -# Install -#------------------------------------------------------------------------------- -install( - TARGETS XrdXml - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ) diff --git a/src/XrdXml/XrdXmlMetaLink.cc b/src/XrdXml/XrdXmlMetaLink.cc deleted file mode 100644 index 8d2d38fef50..00000000000 --- a/src/XrdXml/XrdXmlMetaLink.cc +++ /dev/null @@ -1,590 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d X m l M e t a L i n k . h h */ -/* */ -/* (c) 2015 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include - -#include "XrdSys/XrdSysAtomics.hh" -#include "XrdSys/XrdSysFD.hh" -#include "XrdSys/XrdSysPthread.hh" -#include "XrdXml/XrdXmlMetaLink.hh" - -/******************************************************************************/ -/* L o c a l D e f i n i t i o n s */ -/******************************************************************************/ - -#define SizeOfVec(x) sizeof(x)/sizeof(x[0]) - -namespace -{ -char tmpPath[40]; - -unsigned int GenTmpPath() -{ -// The below will not generate a result more than 31 characters. -// - snprintf(tmpPath, sizeof(tmpPath), "/tmp/.MetaLink%8x.%d.", - static_cast(time(0)), static_cast(getpid())); - return 0; -} - -XrdSysMutex xMutex; - -unsigned int seqNo = GenTmpPath(); -} - -/******************************************************************************/ -/* L o c a l C l a s s e s */ -/******************************************************************************/ - -namespace -{ -class CleanUp -{ -public: - -XrdXmlReader **delRDR; -char *delTFN; - - CleanUp() : delRDR(0), delTFN(0) {} - ~CleanUp() {if (delRDR) {delete *delRDR; *delRDR = 0;} - if (delTFN) unlink(delTFN); - } -}; - -class vecMon -{ -public: - - vecMon(char **vec, int vecn) - : theVec(vec), vecNum(vecn) {} - ~vecMon() {if (theVec) - for (int i = 0; i < vecNum; i++) - if (theVec[i]) free(theVec[i]); - } -private: -char **theVec; -int vecNum; -}; -} - -/******************************************************************************/ -/* C o n v e r t */ -/******************************************************************************/ - -XrdOucFileInfo *XrdXmlMetaLink::Convert(const char *fname, int blen) -{ - static const char *mlV3NS = "http://www.metalinker.org/"; - static const char *mlV4NS = "urn:ietf:params:xml:ns:metalink"; - static const char *mlV3[] = {"metalink", "files", 0}; - static const char *mTag[] = {"", "metalink", 0}; - static const char *mAtr[] = {"xmlns", 0}; - const char *scope = "metalink"; - char *mVal[] = {0}; - CleanUp onReturn; - XrdOucFileInfo *fP; - const char *gLFN; - char *colon, gHdr[272]; - bool chkG; - -// If we are converting a buffer, then generate the file -// - if (blen > 0) - {if (!PutFile(fname, blen)) return 0; - onReturn.delTFN = tmpFn; - fname = tmpFn; - } - -// Check if we should add a global file entry -// - if (rdHost && (rdProt || (prots && (colon = index(prots,':'))))) - {if (!rdProt) {rdProt = prots; *(colon+1) = 0;} - else colon = 0; - snprintf(gHdr, sizeof(gHdr), "%s//%s/", rdProt, rdHost); - if (colon) *(colon+1) = ':'; - chkG = true; - } else chkG = false; - -// Get a file reader -// - if (!(reader = XrdXmlReader::GetReader(fname, encType))) - {eCode = errno; - snprintf(eText, sizeof(eText), "%s trying to read %s", - (errno ? strerror(errno) : "Unknow error"), fname); - return 0; - } - -// Make sure we delete the reader should we return -// - onReturn.delRDR = &reader; - -// We must find the metalink tag -// - if (!reader->GetElement(mTag, true)) - {GetRdrError("looking for 'metalink' tag"); - return 0; - } - -// The input can be in metalink 3 or metalink 4 format. The metalink tag will -// tell us which one it is. It better be in the document with the xmlns attribute -// - if (!reader->GetAttributes(mAtr, mVal)) - {strcpy(eText, "Required metalink tag attribute 'xmlns' not found"); - eCode = ENOMSG; - return 0; - } - -// The namespace tells us what format we are using here. For v3 formt we must -// alignh ourselves on the "files" tag. There can only be one of those present. -// - if (!strcmp(mVal[0], mlV3NS)) - {if (!reader->GetElement(mlV3, true)) - GetRdrError("looking for 'files' tag"); - scope = "files"; - } - else if ( strcmp((const char *)mVal[0], mlV4NS)) - {strcpy(eText, "Metalink format not supported"); - eCode = EPFNOSUPPORT; - } - -// Check if can continue -// - free(mVal[0]); - if (eCode) return 0; - -// Get one or more files -// - currFile = 0; fileCnt = 0; noUrl = true; - do{if (!GetFile(scope)) break; - currFile = new XrdOucFileInfo; - if (GetFileInfo("file")) - {if (lastFile) lastFile ->nextFile = currFile; - else fileList = currFile; - lastFile = currFile; - if (chkG && (gLFN = currFile->GetLfn())) - {char lfnBuff[2048]; - snprintf(lfnBuff, sizeof(lfnBuff), "%s%s", gHdr, gLFN); - currFile->AddUrl(lfnBuff, 0, INT_MAX); - currFile->AddProtocol(rdProt); - } - currFile = 0; - fileCnt++; noUrl = true; - } - } while(doAll); - -// The loop ends when we cannot find a file tag. So, the current file is invalid -// - if (currFile) {delete currFile; currFile = 0;} - -// Check if we have any files at all -// - if (!fileCnt) - {strcpy(eText, "No applicable urls specified for the file entry"); - eCode = EDESTADDRREQ; - } - -// If this is an all call then return to execute the postantem -// - fP = fileList; lastFile = fileList = 0; - if (doAll) return fP; - -// Check if we have clean status. If not, undo all we have and return failure -// - if (!eCode) return fP; - if (fP) delete fP; - return 0; -} - -/******************************************************************************/ -/* C o n v e r t A l l */ -/******************************************************************************/ - -XrdOucFileInfo **XrdXmlMetaLink::ConvertAll(const char *fname, int &count, - int blen) -{ - CleanUp onReturn; - XrdOucFileInfo *fP, **fvP; - -// Indicate this is a call from here -// - doAll = true; - count = 0; - -// If we are converting a buffer, then generate the file -// - if (blen > 0) - {if (!PutFile(fname, blen)) return 0; - onReturn.delTFN = tmpFn; - fname = tmpFn; - } - -// Perform the conversion -// - if (!(fP = Convert(fname))) return 0; - -// Check if we have clean status, if not return nothing -// - if (eCode) - {XrdOucFileInfo *fnP = fP->nextFile; - while((fP = fnP)) - {fnP = fP->nextFile; - delete fP; - } - return 0; - } - -// Return a vector of the file info objects -// - fvP = new XrdOucFileInfo* [fileCnt]; - for (int i = 0; i < fileCnt; i++) {fvP[i] = fP; fP = fP->nextFile;} - count = fileCnt; - return fvP; -} - -/******************************************************************************/ -/* D e l e t e A l l */ -/******************************************************************************/ - -void XrdXmlMetaLink::DeleteAll(XrdOucFileInfo ** vecp, int vecn) -{ -// Delete each object in the vector -// - for (int i = 0; i < vecn; i++) - delete vecp[i]; - -// Now delete the vector -// - delete []vecp; -} - -/******************************************************************************/ -/* Private: G e t F i l e */ -/******************************************************************************/ - -bool XrdXmlMetaLink::GetFile(const char *scope) -{ - const char *fileElem[] = {scope, "file", 0}; - const char *etext; - bool needFile = fileCnt == 0; - -// We align on "file" this is true at this point regardless of version. -// - if (!reader->GetElement(fileElem, needFile)) - {if ((etext = reader->GetError(eCode))) - {size_t len = strlen(etext); - if(len > sizeof(eText)-1) len=sizeof(eText)-1; - memcpy(eText, etext, len); - eText[len]=0; - } - return false; - } - -// We are now aligned on a file tag -// - return true; -} - -/******************************************************************************/ -/* Private: G e t F i l e I n f o */ -/******************************************************************************/ - -bool XrdXmlMetaLink::GetFileInfo(const char *scope) -{ - static const char *fileScope = "file"; - const char *fsubElem[] = {scope, "url", "hash", "size", - "verification", "resources", "glfn", 0}; - int ePos; - - if(strncmp(scope, fileScope, 4) == 0) GetName(); - -// Process the elements in he file section. Both formats have the same tags, -// though not the same attributes. We will take care of the differences later. -// - while((ePos = reader->GetElement(fsubElem))) - switch(ePos) - {case 1: if (!GetUrl()) return false; - break; - case 2: if (!GetHash()) return false; - break; - case 3: if (!GetSize()) return false; - break; - case 4: GetFileInfo("verification"); - if (eCode) return false; - break; - case 5: GetFileInfo("resources"); - if (eCode) return false; - break; - case 6: if (!GetGLfn()) return false; - break; - default: break; - } - -// Return success if we had at least one url -// - return !noUrl; -} - -/******************************************************************************/ -/* Private: G e t G L f n */ -/******************************************************************************/ - -bool XrdXmlMetaLink::GetGLfn() -{ - static const char *gAttr[] = {"name", 0}; - char *gAVal[] = {0}; - vecMon monVec(gAVal, SizeOfVec(gAVal)); - -// Get the name -// - if (!reader->GetAttributes(gAttr, gAVal)) - {strcpy(eText, "Required glfn tag name attribute not found"); - eCode = ENOMSG; - return false; - } - -// Add the the glfn -// - currFile->AddLfn(gAVal[0]); - -// All done -// - return true; -} - -/******************************************************************************/ -/* Private: G e t H a s h */ -/******************************************************************************/ - -bool XrdXmlMetaLink::GetHash() -{ - static const char *hAttr[] = {"type", 0}; - char *hAVal[] = {0}; - vecMon monVec(hAVal, SizeOfVec(hAVal)); - char *value; - -// Get the hash type -// - if (!reader->GetAttributes(hAttr, hAVal)) - {strcpy(eText, "Required hash tag type attribute not found"); - eCode = ENOMSG; - return false; - } - -// Now get the hash value -// - if (!(value = reader->GetText("hash", true))) return false; - -// Add a new digest -// - currFile->AddDigest(hAVal[0], value); - -// All done -// - free(value); - return true; -} - -/******************************************************************************/ -/* G e t R d r E r r o r */ -/******************************************************************************/ - -void XrdXmlMetaLink::GetRdrError(const char *why) -{ - const char *etext = reader->GetError(eCode); - - if (etext) - {size_t len = strlen(etext); - if(len > sizeof(eText)-1) len = sizeof(eText)-1; - memcpy(eText, etext, len); - eText[len]=0; - } - else {snprintf(eText, sizeof(eText), "End of xml while %s", why); - eCode = EIDRM; - } -} - -/******************************************************************************/ -/* Private: G e t S i z e */ -/******************************************************************************/ - -bool XrdXmlMetaLink::GetSize() -{ - char *eP, *value; - long long fsz; - -// Now get the size value -// - if (!(value = reader->GetText("size", true))) return false; - -// Convert size, it must convert clean and be non-negatie -// - fsz = strtoll(value, &eP, 10); - if (fsz < 0 || *eP != 0) - {snprintf(eText,sizeof(eText), "Size tag value '%s' is invalid", value); - eCode = EINVAL; - free(value); - return false; - } - -// Set the size and return -// - currFile->SetSize(fsz); - free(value); - return true; -} - -/******************************************************************************/ -/* Private: G e t U r l */ -/******************************************************************************/ - -bool XrdXmlMetaLink::GetUrl() -{ - static const char *uAttr[] = {"location", "priority", "preference", 0}; - char *uAVal[] = {0, 0, 0}; - vecMon monVec(uAVal, SizeOfVec(uAVal)); - char *value; - int prty = 0; - -// Get the optional attributes -// - reader->GetAttributes(uAttr, uAVal); - -// Now get the url value. There might be one, that is valid and we ignore it. -// - if (!(value = reader->GetText("url"))) return true; - -// Check if we need to screen url protocols -// - if (!UrlOK(value)) - {free(value); - return true; - } - -// Process priority or preference (we ignore errors here) -// - if (uAVal[1]) prty = atoi(uAVal[1]); - else if (uAVal[2]) - {prty = 100 - atoi(uAVal[2]); - if (prty < 0) prty = 0; - } - -// Add the url to the flle -// - currFile->AddUrl(value, uAVal[0], prty); - free(value); - -// All done -// - noUrl = false; - return true; -} - -/******************************************************************************/ -/* Private: G e t N a m e */ -/******************************************************************************/ - -void XrdXmlMetaLink::GetName() -{ - static const char *mAtr[] = {"name", 0}; - char *mVal[] = {0}; - reader->GetAttributes(mAtr, mVal); - currFile->AddFileName(mVal[0]); - free(mVal[0]); -} - -/******************************************************************************/ -/* Private: P u t F i l e */ -/******************************************************************************/ - -bool XrdXmlMetaLink::PutFile(const char *buff, int blen) -{ - static const int oFlags = O_EXCL | O_CREAT | O_TRUNC | O_WRONLY; - const char *what = "opening"; - unsigned int fSeq; - int fd; - -// Get a unique sequence number -// - AtomicBeg(xMutex); - fSeq = AtomicInc(seqNo); - AtomicEnd(xMutex); - -// Generate a unique filepath. Unfortunately, mktemp is unsafe and mkstemp may -// leak a file descriptor. So, we roll our own using above sequence number. -// Note that the target buffer is 64 characters which is suffcient for us. -// - snprintf(tmpFn, sizeof(tmpFn), "%s%u", tmpPath, fSeq); - -// Open the file for output, write out the buffer, and close the file -// - if ((fd = XrdSysFD_Open(tmpFn, oFlags, S_IRUSR|S_IWUSR)) > 0) - {what = "writing"; - if (write(fd, buff, blen) == blen) - {what = "closing"; - if (!close(fd)) return true; - } - } - -// We failed -// - eCode = errno; - snprintf(eText, sizeof(eText), "%s %s %s", strerror(eCode), what, tmpFn); - unlink(tmpFn); - return false; -} - -/******************************************************************************/ -/* Private: U r l O K */ -/******************************************************************************/ - -bool XrdXmlMetaLink::UrlOK(char *url) -{ - char *colon, pBuff[16]; - int n; - -// Find the colon and get the length of the protocol -// - if (!(colon = index(url, ':'))) return false; - n = colon - url + 1; - if (n >= (int)sizeof(pBuff)) return false; - strncpy(pBuff, url, n); - pBuff[n] = 0; - -// Add this protocol to the list we found -// - currFile->AddProtocol(pBuff); - -// Return whether or not this os one of the acceptable protocols -// - if (prots) return (strstr(prots, pBuff) != 0); - return true; -} diff --git a/src/XrdXml/XrdXmlMetaLink.hh b/src/XrdXml/XrdXmlMetaLink.hh deleted file mode 100644 index aee7bf1e785..00000000000 --- a/src/XrdXml/XrdXmlMetaLink.hh +++ /dev/null @@ -1,184 +0,0 @@ -#ifndef __XRDXMLMETALINK_HH__ -#define __XRDXMLMETALINK_HH__ -/******************************************************************************/ -/* */ -/* X r d X m l M e t a L i n k . h h */ -/* */ -/* (c) 2015 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -#include "XrdOuc/XrdOucFileInfo.hh" -#include "XrdXml/XrdXmlReader.hh" - -//----------------------------------------------------------------------------- -//! The XrdXmlMetaLink object provides a uniform interface to convert metalink -//! XML specifications to one or more XrdOucFileInfo objects. This object does -//! not do a rigorous syntactic check of the metalink specification. -//! Specifications that technically violate RFC 5854 (v4 metalinks) or the -//! metalink.org v3 metalinks may be accepted and yield valid information. -//----------------------------------------------------------------------------- - -class XrdXmlMetaLink -{ -public: - -//----------------------------------------------------------------------------- -//! Convert an XML metalink specification to a file info object. Only the first -//! file entry is converted (see ConvertAll()). -//! -//! @param fbuff Pointer to the filepath that contains the metalink -//! specification when blen is 0. Otherwise, fbuff points to a -//! memory buffer of length blen containing the specification. -//! -//! @param blen Length of the buffer. When <=0, the first argument is a -//! file path. Otherwise, it is a memory buffer of length blen -//! whose contents are written into a file in /tmp, converted, -//! and then deleted. -//! -//! @return Pointer to the corresponding file info object upon success. -//! Otherwise, a null pointer is returned indicating that the metalink -//! specification was invalid or had no required protocols. Use the -//! GetStatus() method to obtain the description of the problem. -//----------------------------------------------------------------------------- - -XrdOucFileInfo *Convert(const char *fbuff, int blen=0); - -//----------------------------------------------------------------------------- -//! Convert an XML metalink specification to a file info object. All file -//! entries are converted. -//! -//! @param fbuff Pointer to the filepath that contains the metalink -//! specification when blen is 0. Otherwise, fbuff points to a -//! memory buffer of length blen containing the specification. -//! -//! @param count Place where the number of array elements is returned. -//! -//! @param blen Length of the buffer. When <=0, the first argument is a -//! file path. Otherwise, it is a memory buffer of length blen -//! whose contents are written into a file in /tmp, converted, -//! and then deleted. -//! -//! @return Pointer to the array of corresponding fil info objects upon success. Otherwise, -//! Otherwise, a null pointer is returned indicating that the metalink -//! specification was invalid or had no required protocols. Use the -//! GetStatus() method to obtain the description of the problem. -//! Be aware that you must first delete each file info object before -//! deleting the array. You can do this via DeleteAll(). -//----------------------------------------------------------------------------- - -XrdOucFileInfo **ConvertAll(const char *fbuff, int &count, int blen=0); - -//----------------------------------------------------------------------------- -//! Delete a vector of file info objects and the vector itself as well. -//! -//! @param vecp Pointer to the array. -//! @param vecn Number of elements in the vector. -//----------------------------------------------------------------------------- - -static void DeleteAll(XrdOucFileInfo **vecp, int vecn); - -//----------------------------------------------------------------------------- -//! Obtain ending status of previous conversion. -//! -//! @param ecode Place to return the error code, if any. -//! -//! @return Pointer to the error text describing the error. The string becomes -//! invalid if Convert() is called or the object is deleted. If no -//! error was encountered, a null string is returned with ecode == 0. -//----------------------------------------------------------------------------- - -const char *GetStatus(int &ecode) {ecode = eCode; return eText;} - -//----------------------------------------------------------------------------- -//! Constructor -//! -//! @param protos Pointer to the list of desired protocols. Each protocol ends -//! with a colon. They are specified without embedded spaces. -//! Only urls using one of the listed protocols is returned. -//! A nil pointer returns all urls regardless of the protocol. -//! @param rdprot The protocol to be used when constructing the global file -//! entry. If nil, the first protocol in protos is used. If -//! nil, a global file is not constructed. -//! @param rdhost The "[]" to use when constructing the global -//! file. A global file entry is constructed only if rdhost -//! is specified and a protocol is available, and a global -//! file element exists in the xml file. -//! -//! @param encode Specifies the xml encoding. Currently, only UTF-8 is -//! is supported and is signified by a nil pointer. -//----------------------------------------------------------------------------- - - XrdXmlMetaLink(const char *protos="root:xroot:", - const char *rdprot="xroot:", - const char *rdhost=0, - const char *encode=0 - ) : reader(0), - fileList(0), lastFile(0), currFile(0), - prots(protos ? strdup(protos) : 0), - encType(encode ? strdup(encode) : 0), - rdProt(rdprot), rdHost(rdhost), - fileCnt(0), eCode(0), - doAll(false), noUrl(true) - {*eText = 0; *tmpFn = 0;} - -//----------------------------------------------------------------------------- -//! Destructor. -//----------------------------------------------------------------------------- - - ~XrdXmlMetaLink() {if (prots) free(prots); - if (encType) free(encType); - } - -private: -bool GetFile(const char *scope); -bool GetFileInfo(const char *scope); -bool GetGLfn(); -bool GetHash(); -void GetRdrError(const char *why); -bool GetSize(); -bool GetUrl(); -void GetName(); -bool PutFile(const char *buff, int blen); -bool UrlOK(char *url); - -XrdXmlReader *reader; -XrdOucFileInfo *fileList; -XrdOucFileInfo *lastFile; -XrdOucFileInfo *currFile; -char *prots; -char *encType; -const char *rdProt; -const char *rdHost; -int fileCnt; -int eCode; -bool doAll; -bool noUrl; -char tmpFn[64]; -char eText[256]; -}; -#endif diff --git a/src/XrdXml/XrdXmlRdrTiny.cc b/src/XrdXml/XrdXmlRdrTiny.cc deleted file mode 100644 index 17a8fd40bf0..00000000000 --- a/src/XrdXml/XrdXmlRdrTiny.cc +++ /dev/null @@ -1,300 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d X m l R d r T i n y . c c */ -/* */ -/* (c) 2015 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "XrdXml/tinyxml.h" -#include "XrdXml/XrdXmlRdrTiny.hh" - -/******************************************************************************/ -/* L o c a l D e f i n i t i o n s */ -/******************************************************************************/ - -namespace -{ -// Develop a sane enum structure of xml node types -// -enum XmlNodeType {ntNone = TiXmlNode::TINYXML_UNKNOWN, - ntElmBeg = TiXmlNode::TINYXML_ELEMENT, - ntElmEnd = -1, - ntText = TiXmlNode::TINYXML_TEXT, - ntCmt = TiXmlNode::TINYXML_COMMENT, - ntDoc = TiXmlNode::TINYXML_DOCUMENT, - ntXMLDcl = TiXmlNode::TINYXML_DECLARATION - }; - -/******************************************************************************/ -/* X m l N o d e N a m e */ -/******************************************************************************/ - -const char *NodeName(int ntype) -{ - switch(ntype) - {case ntNone: return "isNode "; break; - case ntElmBeg: return "isElmBeg"; break; - case ntText: return "isText "; break; - case ntCmt: return "isCmt "; break; - case ntDoc: return "isDoc "; break; - case ntElmEnd: return "isElmEnd"; break; - case ntXMLDcl: return "isXMLDcl"; break; - default: break; - }; - return "???"; -} -} - -/******************************************************************************/ -/* C o n s t r c u t o r # 1 */ -/******************************************************************************/ - -XrdXmlRdrTiny::XrdXmlRdrTiny(bool &aOK, const char *fname, const char *enc) : reader(0) // make sure the pointer is nil initialized otherwise if stat fails the destructor segfaults -{ - struct stat Stat; - const char *etext; - -// Initialize the standard values -// - curNode = 0; - curElem = 0; - elmNode = 0; - eCode = 0; - *eText = 0; - debug = getenv("XrdXmlDEBUG") != 0; - -// Make sure this file exists -// - if (stat(fname, &Stat)) - {eCode = errno; - snprintf(eText,sizeof(eText),"%s opening %s", strerror(errno), fname); - aOK = false; - return; - } - -// Get a file reader -// - reader = new TiXmlDocument(fname); - if (reader->LoadFile()) - {curNode = (TiXmlNode *)reader; - curElem = 0; - elmNode = curNode; - aOK = true; - } else { - if (!(etext = reader->ErrorDesc()) || *etext) - {if ((eCode = errno)) etext = strerror(errno); - else etext = "Unknown error"; - } - snprintf(eText,sizeof(eText),"%s opening %s", etext, fname); - eCode = EINVAL; - aOK = false; - } -} - -/******************************************************************************/ -/* D e s t r u c t o r */ -/******************************************************************************/ - -XrdXmlRdrTiny::~XrdXmlRdrTiny() -{ - -// Tear down the reader -// - if (reader) - { - delete reader; - reader = 0; - } -} - -/******************************************************************************/ -/* Private: D e b u g */ -/******************************************************************************/ - -void XrdXmlRdrTiny::Debug(const char *hdr, const char *want, const char *have, - const char *scope, int nType) -{ - char buff[512]; - -// Format the message -// - snprintf(buff,sizeof(buff),"%s %s scope: %s want: %s have: %s\n", - hdr,NodeName(nType),scope,want,have); - std::cerr <Attribute(aname[i]))) - {if (aval[i]) free(aval[i]); - aval[i] = strdup(value); - found = true; - } - i++; - } - -// All done -// - return found; -} - -/******************************************************************************/ -/* G e t E l e m e n t */ -/******************************************************************************/ - -int XrdXmlRdrTiny::GetElement(const char **ename, bool reqd) -{ - TiXmlNode *theChild; - const char *name = (curNode ? curNode->Value() : 0); - int i; - -// If we are positioned either at the current node or the last node we returned -// Complain if that is not the case. -// - if (*ename[0]) - {if (name && strcmp(name, ename[0])) - {if (curElem && !strcmp(elmNode->Value(),ename[0])) curNode = elmNode; - else {snprintf(eText, sizeof(eText), - "Current context '%s' does not match stated scope '%s'", - (name ? name : ""), ename[0]); - eCode = EILSEQ; - return false; - } - } - } - -// Sequence to the next node at appropriate level. -// - if (curNode == elmNode ) theChild = curNode->FirstChild(); - else if (elmNode) theChild = elmNode->NextSibling(); - else theChild = curNode->NextSibling(); - - -// Scan over to the first wanted element -// - while(theChild) - {if ((name = theChild->Value()) && theChild->Type() == ntElmBeg) - {i = 1; - while(ename[i] && strcmp(name, ename[i])) i++; - if (ename[i]) - {if (debug) Debug("getelem:",ename[i],name,ename[0],ntElmBeg); - curElem = theChild->ToElement(); - elmNode = theChild; - return i; - } - } - theChild = theChild->NextSibling(); - } - -// We didn't find any wanted tag here in this scope. Transition to the element's -// parent we finished the tag -// - if (debug) Debug("getelem:",ename[1],ename[0],ename[0],ntElmEnd); - elmNode = curNode; - curNode = curNode->Parent(); - curElem = 0; - return 0; - -// This is an error if this element was required -// - if (reqd) - {snprintf(eText,sizeof(eText),"Required element '%s' not found in '%s'", - (ename[1] ? ename[1] : "???"), ename[0]); - eCode = ESRCH; - } - return 0; -} - -/******************************************************************************/ -/* G e t T e x t */ -/******************************************************************************/ - -char *XrdXmlRdrTiny::GetText(const char *ename, bool reqd) -{ - const char *value; - char *sP; - -// If we are not at the begining of an element, this is a sequence error -// - if (!curElem) - {snprintf(eText, sizeof(eText), - "Illegal position seeking text for tag %s",ename); - eCode = EILSEQ; - return 0; - } - -// Get the text associated with element (simple text only) -// - value = curElem->GetText(); - -// We did not find a value. If not required return. -// - if (value || !reqd) - {if (!value) return 0; - sP = strdup(value); - return sP; - } - -// Create error message -// - snprintf(eText, sizeof(eText), "Required %s tag value not found", ename); - eCode = ENOMSG; - return 0; -} - -/******************************************************************************/ -/* I n i t */ -/******************************************************************************/ - -bool XrdXmlRdrTiny::Init() {return true;} diff --git a/src/XrdXml/XrdXmlRdrTiny.hh b/src/XrdXml/XrdXmlRdrTiny.hh deleted file mode 100644 index a8580c63af9..00000000000 --- a/src/XrdXml/XrdXmlRdrTiny.hh +++ /dev/null @@ -1,75 +0,0 @@ -#ifndef __XRDXMLRDRTINY_HH__ -#define __XRDXMLRDRTINY_HH__ -/******************************************************************************/ -/* */ -/* X r d X m l R d r T i n y . h h */ -/* */ -/* (c) 2015 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdXml/XrdXmlReader.hh" - -//----------------------------------------------------------------------------- -//! The XrdXmlRdrTiny object provides a xml parser based on libxml2. -//----------------------------------------------------------------------------- - -class TiXmlDocument; -class TiXmlElement; -class TiXmlNode; - -class XrdXmlRdrTiny : public XrdXmlReader -{ -public: - -virtual bool GetAttributes(const char **aname, char **aval); - -virtual int GetElement(const char **ename, bool reqd=false); - -virtual -const char *GetError(int &ecode) {return ((ecode = eCode) ? eText : 0);} - -virtual char *GetText(const char *ename, bool reqd=false); - -static bool Init(); - -//----------------------------------------------------------------------------- -//! Constructor & Destructor -//----------------------------------------------------------------------------- - - XrdXmlRdrTiny(bool &aOK, const char *fname, const char *enc=0); -virtual ~XrdXmlRdrTiny(); - -private: -void Debug(const char *,const char *,const char *,const char *,int); - -TiXmlDocument *reader; -TiXmlNode *curNode; -TiXmlElement *curElem; -TiXmlNode *elmNode; -int eCode; -bool debug; -char eText[251]; -}; -#endif diff --git a/src/XrdXml/XrdXmlRdrXml2.cc b/src/XrdXml/XrdXmlRdrXml2.cc deleted file mode 100644 index ef008e4d175..00000000000 --- a/src/XrdXml/XrdXmlRdrXml2.cc +++ /dev/null @@ -1,297 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d X m l R d r X m l 2 . c c */ -/* */ -/* (c) 2015 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include - -#include - -#include "XrdXml/XrdXmlRdrXml2.hh" - -/******************************************************************************/ -/* L o c a l D e f i n i t i o n s */ -/******************************************************************************/ - -namespace -{ -// Develop a sane enum structure of xml node types -// -enum XmlNodeType {ntNone = XML_READER_TYPE_NONE, - ntElmBeg = XML_READER_TYPE_ELEMENT, - ntAttr = XML_READER_TYPE_ATTRIBUTE, - ntText = XML_READER_TYPE_TEXT, - ntCData = XML_READER_TYPE_CDATA, - ntEntRef = XML_READER_TYPE_ENTITY_REFERENCE, - ntEntBeg = XML_READER_TYPE_ENTITY, - ntPI = XML_READER_TYPE_PROCESSING_INSTRUCTION, - ntCmt = XML_READER_TYPE_COMMENT, - ntDoc = XML_READER_TYPE_DOCUMENT, - ntDTD = XML_READER_TYPE_DOCUMENT_TYPE, - ntDFrag = XML_READER_TYPE_DOCUMENT_FRAGMENT, - ntNote = XML_READER_TYPE_NOTATION, - ntWSpace = XML_READER_TYPE_WHITESPACE, - ntWSpSig = XML_READER_TYPE_SIGNIFICANT_WHITESPACE, - ntElmEnd = XML_READER_TYPE_END_ELEMENT, - ntEntEnd = XML_READER_TYPE_END_ENTITY, - ntXMLDcl = XML_READER_TYPE_XML_DECLARATION - }; - -/******************************************************************************/ -/* X m l N o d e N a m e */ -/******************************************************************************/ - -const char *NodeName(int ntype) -{ - switch(ntype) - {case ntNone: return "isNode "; break; - case ntElmBeg: return "isElmBeg"; break; - case ntAttr: return "isAttr "; break; - case ntText: return "isText "; break; - case ntCData: return "isCData "; break; - case ntEntRef: return "isEntRef"; break; - case ntEntBeg: return "isEntBeg"; break; - case ntPI: return "isPI "; break; - case ntCmt: return "isCmt "; break; - case ntDoc: return "isDoc "; break; - case ntDTD: return "isDTD "; break; - case ntDFrag: return "isDFrag "; break; - case ntWSpace: return "isWSpace"; break; - case ntWSpSig: return "isWSpSig"; break; - case ntNote: return "isNote "; break; - case ntElmEnd: return "isElmEnd"; break; - case ntEntEnd: return "isEntEnd"; break; - case ntXMLDcl: return "isXMLDcl"; break; - default: break; - }; - return "???"; -} -} - -/******************************************************************************/ -/* C o n s t r c u t o r # 1 */ -/******************************************************************************/ - -XrdXmlRdrXml2::XrdXmlRdrXml2(bool &aOK, const char *fname, const char *enc) -{ -// Initialize the standard values -// - encType = (enc ? strdup(enc) : 0); - eCode = 0; - *eText = 0; - doDup = true; // We always duplicate memory to avoid allocator issues - debug = getenv("XrdXmlDEBUG") != 0; - -// Get a file reader -// - if (!(reader = xmlNewTextReaderFilename(fname))) - {if ((eCode = errno)) - {size_t size = sizeof(eText) - 1; - strncpy(eText, strerror(errno), size); - eText[size] = '\0'; - } - else strcpy(eText, "Unknown error opening input file"); - aOK = false; - } else aOK = true; -} - -/******************************************************************************/ -/* D e s t r u c t o r */ -/******************************************************************************/ - -XrdXmlRdrXml2::~XrdXmlRdrXml2() -{ - -// Tear down the reader -// - xmlFreeTextReader(reader); reader = 0; -} - -/******************************************************************************/ -/* Private: D e b u g */ -/******************************************************************************/ - -void XrdXmlRdrXml2::Debug(const char *hdr, const char *want, char *have, - const char *scope, int nType) -{ - char buff[512]; - -// Format the message -// - snprintf(buff,sizeof(buff),"%s %s depth: %d scope: %s want: %s have: %s\n", - hdr,NodeName(nType),xmlTextReaderDepth(reader),scope,want,have); - std::cerr <. */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdXml/XrdXmlReader.hh" - -//----------------------------------------------------------------------------- -//! The XrdXmlRdrXml2 object provides a xml parser based on libxml2. -//----------------------------------------------------------------------------- - -struct _xmlTextReader; - -class XrdXmlRdrXml2 : public XrdXmlReader -{ -public: - -virtual void Free(void *strP); - -virtual bool GetAttributes(const char **aname, char **aval); - -virtual int GetElement(const char **ename, bool reqd=false); - -virtual -const char *GetError(int &ecode) {return ((ecode = eCode) ? eText : 0);} - -virtual char *GetText(const char *ename, bool reqd=false); - -static bool Init(); - -//----------------------------------------------------------------------------- -//! Constructor & Destructor -//----------------------------------------------------------------------------- - - XrdXmlRdrXml2(bool &aOK, const char *fname, const char *enc=0); -virtual ~XrdXmlRdrXml2(); - -private: -void Debug(const char *, const char *, char *, const char *, int); -char *GetName(); - -_xmlTextReader *reader; -const char *encType; -int eCode; -bool doDup; -bool debug; -char eText[250]; -}; -#endif diff --git a/src/XrdXml/XrdXmlReader.cc b/src/XrdXml/XrdXmlReader.cc deleted file mode 100644 index 489f5ed2b89..00000000000 --- a/src/XrdXml/XrdXmlReader.cc +++ /dev/null @@ -1,106 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d X m l R e a d e r . c c */ -/* */ -/* (c) 2015 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -#include "XrdXml/XrdXmlRdrTiny.hh" - -#ifdef HAVE_XML2 -#include "XrdXml/XrdXmlRdrXml2.hh" -#endif - -/******************************************************************************/ -/* G e t R e a d e r */ -/******************************************************************************/ - -XrdXmlReader *XrdXmlReader::GetReader(const char *fname, const char *enc, - const char *impl) -{ - XrdXmlReader *rP; - int rc; - bool aOK; - -// Check if this is the default implementation -// c - if (!impl || !strcmp(impl, "tinyxml")) - {rP = new XrdXmlRdrTiny(aOK, fname, enc); - if (aOK) return rP; - rP->GetError(rc); - delete rP; - errno = (rc ? rc : ENOTSUP); - return 0; - } - -// Check for he full blown xml implementation -// -#ifdef HAVE_XML2 - if (!strcmp(impl, "libxml2")) - {rP = new XrdXmlRdrXml2(aOK, fname, enc); - if (aOK) return rP; - rP->GetError(rc); - delete rP; - errno = (rc ? rc : ENOTSUP); - return 0; - } -#endif - -// Add additional implementations here -// - -// Not supported -// - errno = ENOTSUP; - return 0; -} - -/******************************************************************************/ -/* I n i t */ -/******************************************************************************/ - -bool XrdXmlReader::Init(const char *impl) -{ -// Check if this is the default implementation -// - if (!impl || !strcmp(impl, "tinyxml")) return true; - -// Check for the whole hog implmenetation -// -#ifdef HAVE_XML2 - if (!strcmp(impl, "libxml2")) {return XrdXmlRdrXml2::Init();} -#endif - -// Add additional implementations here -// - -// Not supported -// - errno = ENOTSUP; - return false; -} diff --git a/src/XrdXml/XrdXmlReader.hh b/src/XrdXml/XrdXmlReader.hh deleted file mode 100644 index 79f62439629..00000000000 --- a/src/XrdXml/XrdXmlReader.hh +++ /dev/null @@ -1,170 +0,0 @@ -#ifndef __XRDXMLREADER_HH__ -#define __XRDXMLREADER_HH__ -/******************************************************************************/ -/* */ -/* X r d X m l R e a d e r . h h */ -/* */ -/* (c) 2015 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -//----------------------------------------------------------------------------- -//! The XrdXmlReader object provides a virtual interface to read xml files, -//! irrespective of the underlying mplementation. Obtain an XML reader using -//! GetRead(). You may also wish to call Init() prior to obtaining a reader -//! in multi-threader applications. As some implementations may not be MT-safe. -//! See GetReader() for more information. -//----------------------------------------------------------------------------- - -class XrdXmlReader -{ -public: - -//----------------------------------------------------------------------------- -//! Get attributes from an XML tag. GetAttributes() should only be called after -//! a successful GetElement() call. -//! -//! @param aname Pointer to an array of attribute names whose values are -//! to be returned. The last entry in the array must be nil. -//! -//! @param aval Pointer to an array where the corresponding attribute -//! values are to be placed in 1-to-1 correspondence. The -//! values must be freed using free(). -//! -//! @return true One or more attributes have been returned. -//! false No specified attributes were found. -//----------------------------------------------------------------------------- - -virtual bool GetAttributes(const char **aname, char **aval)=0; - -//----------------------------------------------------------------------------- -//! Find an XML tag element. -//! -//! @param ename Pointer to an array of tag names any of which should be -//! searched for. The last entry in the array must be nil. -//! The first element of the array should contain the name of -//! the context tag. Elements are searched only within the -//! scope of that tag. When searching for the first desired -//! tag, use a null string to indicate document scope. -//! -//! @param reqd When true one of the tag elements listed in ename must be -//! found otherwise an error is generated. -//! -//! @return =0 No specified tag was found. Note that this corresponds to -//! encountering the tag present in ename[0], i.e. scope end. -//! >0 A tag was found, the return value is the index into ename -//! that corresponds to the tag's name. -//----------------------------------------------------------------------------- - -virtual int GetElement(const char **ename, bool reqd=false)=0; - -//----------------------------------------------------------------------------- -//! Get the description of the last error encountered. -//! -//! @param ecode The error code associated with the error. -//! -//! @return Pointer to text describing the error. The text may be destroyed on a -//! subsequent call to any other method. Otherwise it is stable. A nil -//! pointer indicates that no error is present. -//----------------------------------------------------------------------------- -virtual -const char *GetError(int &ecode)=0; - -//----------------------------------------------------------------------------- -//! Get a reader object to parse an XML file. -//! -//! @param fname Pointer to the filepath of the file to be parsed. -//! -//! @param enc Pointer to the encoding specification. When nil, UTF-8 is -//! used. Currently, this parameter is ignored. -//! -//! @param impl Pointer to the desired implementation. When nil, the -//! default implementation, tinyxml, is used. The following -//! are supported -//! -//! tinyxml - builtin xml reader. Each instance is independent -//! Since it builds a full DOM tree in memory, it -//! is only good for small amounts of xml. Certain -//! esoteric xml features are not supported. -//! -//! libxml2 - full-fledged xml reader. Instances are not -//! independent if multiple uses involve setting -//! callbacks, allocators, or I/O overrides. For -//! MT-safeness, it must be initialized in the -//! main thread (see Init() below). It is used in -//! streaming mode and is good for large documents. -//! -//! -//! @return !0 Pointer to an XML reader object. -//! @return =0 An XML reader object could not be created; errno holds -//! the error code of the reason. -//----------------------------------------------------------------------------- -static -XrdXmlReader *GetReader(const char *fname, - const char *enc=0, const char *impl=0); - -//----------------------------------------------------------------------------- -//! Get the text portion of an XML tag element. GetText() should only be called -//! after a successful call to GetElement() with a possibly intervening call -//! to GetAttributes(). -//! -//! @param ename Pointer to the corresponding tag name. -//! -//! @param reqd When true text must exist and not be null. Otherwise, an -//! error is generated if the text is missing or null. -//! -//! @return =0 No text found. -//! @return !0 Pointer to the tag's text field. It must be free using -//! free(). -//----------------------------------------------------------------------------- - -virtual char *GetText(const char *ename, bool reqd=false)=0; - -//----------------------------------------------------------------------------- -//! Preinitialze the desired implementation for future use. This is meant to be -//! used in multi-threaded applications, as some implementation must be -//! initialized using the main thread before spawning other threads. An exmaple -//! is libxml2 which is generally MT-unsafe unles preinitialized. -//! -//! @param impl Pointer to the desired implementation. When nil, the -//! default implementation is used. Currently, only "libxml2" -//! and "tinyxml" are supported. -//! -//! @return true Initialization suceeded. -//! @return false Initialization failed, errno has the reason. -//----------------------------------------------------------------------------- - -static bool Init(const char *impl=0); - -//----------------------------------------------------------------------------- -//! Constructor & Destructor -//----------------------------------------------------------------------------- - - XrdXmlReader() {} -virtual ~XrdXmlReader() {} - -private: - -}; -#endif diff --git a/src/XrdXml/tinystr.cpp b/src/XrdXml/tinystr.cpp deleted file mode 100644 index 06657682051..00000000000 --- a/src/XrdXml/tinystr.cpp +++ /dev/null @@ -1,111 +0,0 @@ -/* -www.sourceforge.net/projects/tinyxml - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any -damages arising from the use of this software. - -Permission is granted to anyone to use this software for any -purpose, including commercial applications, and to alter it and -redistribute it freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must -not claim that you wrote the original software. If you use this -software in a product, an acknowledgment in the product documentation -would be appreciated but is not required. - -2. Altered source versions must be plainly marked as such, and -must not be misrepresented as being the original software. - -3. This notice may not be removed or altered from any source -distribution. -*/ - - -#ifndef TIXML_USE_STL - -#include "tinystr.h" - -// Error value for find primitive -const TiXmlString::size_type TiXmlString::npos = static_cast< TiXmlString::size_type >(-1); - - -// Null rep. -TiXmlString::Rep TiXmlString::nullrep_ = { 0, 0, { '\0' } }; - - -void TiXmlString::reserve (size_type cap) -{ - if (cap > capacity()) - { - TiXmlString tmp; - tmp.init(length(), cap); - memcpy(tmp.start(), data(), length()); - swap(tmp); - } -} - - -TiXmlString& TiXmlString::assign(const char* str, size_type len) -{ - size_type cap = capacity(); - if (len > cap || cap > 3*(len + 8)) - { - TiXmlString tmp; - tmp.init(len); - memcpy(tmp.start(), str, len); - swap(tmp); - } - else - { - memmove(start(), str, len); - set_size(len); - } - return *this; -} - - -TiXmlString& TiXmlString::append(const char* str, size_type len) -{ - size_type newsize = length() + len; - if (newsize > capacity()) - { - reserve (newsize + capacity()); - } - memmove(finish(), str, len); - set_size(newsize); - return *this; -} - - -TiXmlString operator + (const TiXmlString & a, const TiXmlString & b) -{ - TiXmlString tmp; - tmp.reserve(a.length() + b.length()); - tmp += a; - tmp += b; - return tmp; -} - -TiXmlString operator + (const TiXmlString & a, const char* b) -{ - TiXmlString tmp; - TiXmlString::size_type b_len = static_cast( strlen(b) ); - tmp.reserve(a.length() + b_len); - tmp += a; - tmp.append(b, b_len); - return tmp; -} - -TiXmlString operator + (const char* a, const TiXmlString & b) -{ - TiXmlString tmp; - TiXmlString::size_type a_len = static_cast( strlen(a) ); - tmp.reserve(a_len + b.length()); - tmp.append(a, a_len); - tmp += b; - return tmp; -} - - -#endif // TIXML_USE_STL diff --git a/src/XrdXml/tinystr.h b/src/XrdXml/tinystr.h deleted file mode 100644 index 89cca334156..00000000000 --- a/src/XrdXml/tinystr.h +++ /dev/null @@ -1,305 +0,0 @@ -/* -www.sourceforge.net/projects/tinyxml - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any -damages arising from the use of this software. - -Permission is granted to anyone to use this software for any -purpose, including commercial applications, and to alter it and -redistribute it freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must -not claim that you wrote the original software. If you use this -software in a product, an acknowledgment in the product documentation -would be appreciated but is not required. - -2. Altered source versions must be plainly marked as such, and -must not be misrepresented as being the original software. - -3. This notice may not be removed or altered from any source -distribution. -*/ - - -#ifndef TIXML_USE_STL - -#ifndef TIXML_STRING_INCLUDED -#define TIXML_STRING_INCLUDED - -#include -#include - -/* The support for explicit isn't that universal, and it isn't really - required - it is used to check that the TiXmlString class isn't incorrectly - used. Be nice to old compilers and macro it here: -*/ -#if defined(_MSC_VER) && (_MSC_VER >= 1200 ) - // Microsoft visual studio, version 6 and higher. - #define TIXML_EXPLICIT explicit -#elif defined(__GNUC__) && (__GNUC__ >= 3 ) - // GCC version 3 and higher.s - #define TIXML_EXPLICIT explicit -#else - #define TIXML_EXPLICIT -#endif - - -/* - TiXmlString is an emulation of a subset of the std::string template. - Its purpose is to allow compiling TinyXML on compilers with no or poor STL support. - Only the member functions relevant to the TinyXML project have been implemented. - The buffer allocation is made by a simplistic power of 2 like mechanism : if we increase - a string and there's no more room, we allocate a buffer twice as big as we need. -*/ -class TiXmlString -{ - public : - // The size type used - typedef size_t size_type; - - // Error value for find primitive - static const size_type npos; // = -1; - - - // TiXmlString empty constructor - TiXmlString () : rep_(&nullrep_) - { - } - - // TiXmlString copy constructor - TiXmlString ( const TiXmlString & copy) : rep_(0) - { - init(copy.length()); - memcpy(start(), copy.data(), length()); - } - - // TiXmlString constructor, based on a string - TIXML_EXPLICIT TiXmlString ( const char * copy) : rep_(0) - { - init( static_cast( strlen(copy) )); - memcpy(start(), copy, length()); - } - - // TiXmlString constructor, based on a string - TIXML_EXPLICIT TiXmlString ( const char * str, size_type len) : rep_(0) - { - init(len); - memcpy(start(), str, len); - } - - // TiXmlString destructor - ~TiXmlString () - { - quit(); - } - - TiXmlString& operator = (const char * copy) - { - return assign( copy, (size_type)strlen(copy)); - } - - TiXmlString& operator = (const TiXmlString & copy) - { - return assign(copy.start(), copy.length()); - } - - - // += operator. Maps to append - TiXmlString& operator += (const char * suffix) - { - return append(suffix, static_cast( strlen(suffix) )); - } - - // += operator. Maps to append - TiXmlString& operator += (char single) - { - return append(&single, 1); - } - - // += operator. Maps to append - TiXmlString& operator += (const TiXmlString & suffix) - { - return append(suffix.data(), suffix.length()); - } - - - // Convert a TiXmlString into a null-terminated char * - const char * c_str () const { return rep_->str; } - - // Convert a TiXmlString into a char * (need not be null terminated). - const char * data () const { return rep_->str; } - - // Return the length of a TiXmlString - size_type length () const { return rep_->size; } - - // Alias for length() - size_type size () const { return rep_->size; } - - // Checks if a TiXmlString is empty - bool empty () const { return rep_->size == 0; } - - // Return capacity of string - size_type capacity () const { return rep_->capacity; } - - - // single char extraction - const char& at (size_type index) const - { - assert( index < length() ); - return rep_->str[ index ]; - } - - // [] operator - char& operator [] (size_type index) const - { - assert( index < length() ); - return rep_->str[ index ]; - } - - // find a char in a string. Return TiXmlString::npos if not found - size_type find (char lookup) const - { - return find(lookup, 0); - } - - // find a char in a string from an offset. Return TiXmlString::npos if not found - size_type find (char tofind, size_type offset) const - { - if (offset >= length()) return npos; - - for (const char* p = c_str() + offset; *p != '\0'; ++p) - { - if (*p == tofind) return static_cast< size_type >( p - c_str() ); - } - return npos; - } - - void clear () - { - //Lee: - //The original was just too strange, though correct: - // TiXmlString().swap(*this); - //Instead use the quit & re-init: - quit(); - init(0,0); - } - - /* Function to reserve a big amount of data when we know we'll need it. Be aware that this - function DOES NOT clear the content of the TiXmlString if any exists. - */ - void reserve (size_type cap); - - TiXmlString& assign (const char* str, size_type len); - - TiXmlString& append (const char* str, size_type len); - - void swap (TiXmlString& other) - { - Rep* r = rep_; - rep_ = other.rep_; - other.rep_ = r; - } - - private: - - void init(size_type sz) { init(sz, sz); } - void set_size(size_type sz) { rep_->str[ rep_->size = sz ] = '\0'; } - char* start() const { return rep_->str; } - char* finish() const { return rep_->str + rep_->size; } - - struct Rep - { - size_type size, capacity; - char str[1]; - }; - - void init(size_type sz, size_type cap) - { - if (cap) - { - // Lee: the original form: - // rep_ = static_cast(operator new(sizeof(Rep) + cap)); - // doesn't work in some cases of new being overloaded. Switching - // to the normal allocation, although use an 'int' for systems - // that are overly picky about structure alignment. - const size_type bytesNeeded = sizeof(Rep) + cap; - const size_type intsNeeded = ( bytesNeeded + sizeof(int) - 1 ) / sizeof( int ); - rep_ = reinterpret_cast( new int[ intsNeeded ] ); - - rep_->str[ rep_->size = sz ] = '\0'; - rep_->capacity = cap; - } - else - { - rep_ = &nullrep_; - } - } - - void quit() - { - if (rep_ != &nullrep_) - { - // The rep_ is really an array of ints. (see the allocator, above). - // Cast it back before delete, so the compiler won't incorrectly call destructors. - delete [] ( reinterpret_cast( rep_ ) ); - } - } - - Rep * rep_; - static Rep nullrep_; - -} ; - - -inline bool operator == (const TiXmlString & a, const TiXmlString & b) -{ - return ( a.length() == b.length() ) // optimization on some platforms - && ( strcmp(a.c_str(), b.c_str()) == 0 ); // actual compare -} -inline bool operator < (const TiXmlString & a, const TiXmlString & b) -{ - return strcmp(a.c_str(), b.c_str()) < 0; -} - -inline bool operator != (const TiXmlString & a, const TiXmlString & b) { return !(a == b); } -inline bool operator > (const TiXmlString & a, const TiXmlString & b) { return b < a; } -inline bool operator <= (const TiXmlString & a, const TiXmlString & b) { return !(b < a); } -inline bool operator >= (const TiXmlString & a, const TiXmlString & b) { return !(a < b); } - -inline bool operator == (const TiXmlString & a, const char* b) { return strcmp(a.c_str(), b) == 0; } -inline bool operator == (const char* a, const TiXmlString & b) { return b == a; } -inline bool operator != (const TiXmlString & a, const char* b) { return !(a == b); } -inline bool operator != (const char* a, const TiXmlString & b) { return !(b == a); } - -TiXmlString operator + (const TiXmlString & a, const TiXmlString & b); -TiXmlString operator + (const TiXmlString & a, const char* b); -TiXmlString operator + (const char* a, const TiXmlString & b); - - -/* - TiXmlOutStream is an emulation of std::ostream. It is based on TiXmlString. - Only the operators that we need for TinyXML have been developped. -*/ -class TiXmlOutStream : public TiXmlString -{ -public : - - // TiXmlOutStream << operator. - TiXmlOutStream & operator << (const TiXmlString & in) - { - *this += in; - return *this; - } - - // TiXmlOutStream << operator. - TiXmlOutStream & operator << (const char * in) - { - *this += in; - return *this; - } - -} ; - -#endif // TIXML_STRING_INCLUDED -#endif // TIXML_USE_STL diff --git a/src/XrdXml/tinyxml.cpp b/src/XrdXml/tinyxml.cpp deleted file mode 100644 index 9c161dfcb93..00000000000 --- a/src/XrdXml/tinyxml.cpp +++ /dev/null @@ -1,1886 +0,0 @@ -/* -www.sourceforge.net/projects/tinyxml -Original code by Lee Thomason (www.grinninglizard.com) - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any -damages arising from the use of this software. - -Permission is granted to anyone to use this software for any -purpose, including commercial applications, and to alter it and -redistribute it freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must -not claim that you wrote the original software. If you use this -software in a product, an acknowledgment in the product documentation -would be appreciated but is not required. - -2. Altered source versions must be plainly marked as such, and -must not be misrepresented as being the original software. - -3. This notice may not be removed or altered from any source -distribution. -*/ - -#include - -#ifdef TIXML_USE_STL -#include -#include -#endif - -#include "tinyxml.h" - -FILE* TiXmlFOpen( const char* filename, const char* mode ); - -bool TiXmlBase::condenseWhiteSpace = true; - -// Microsoft compiler security -FILE* TiXmlFOpen( const char* filename, const char* mode ) -{ - #if defined(_MSC_VER) && (_MSC_VER >= 1400 ) - FILE* fp = 0; - errno_t err = fopen_s( &fp, filename, mode ); - if ( !err && fp ) - return fp; - return 0; - #else - return fopen( filename, mode ); - #endif -} - -void TiXmlBase::EncodeString( const TIXML_STRING& str, TIXML_STRING* outString ) -{ - int i=0; - - while( i<(int)str.length() ) - { - unsigned char c = (unsigned char) str[i]; - - if ( c == '&' - && i < ( (int)str.length() - 2 ) - && str[i+1] == '#' - && str[i+2] == 'x' ) - { - // Hexadecimal character reference. - // Pass through unchanged. - // © -- copyright symbol, for example. - // - // The -1 is a bug fix from Rob Laveaux. It keeps - // an overflow from happening if there is no ';'. - // There are actually 2 ways to exit this loop - - // while fails (error case) and break (semicolon found). - // However, there is no mechanism (currently) for - // this function to return an error. - while ( i<(int)str.length()-1 ) - { - outString->append( str.c_str() + i, 1 ); - ++i; - if ( str[i] == ';' ) - break; - } - } - else if ( c == '&' ) - { - outString->append( entity[0].str, entity[0].strLength ); - ++i; - } - else if ( c == '<' ) - { - outString->append( entity[1].str, entity[1].strLength ); - ++i; - } - else if ( c == '>' ) - { - outString->append( entity[2].str, entity[2].strLength ); - ++i; - } - else if ( c == '\"' ) - { - outString->append( entity[3].str, entity[3].strLength ); - ++i; - } - else if ( c == '\'' ) - { - outString->append( entity[4].str, entity[4].strLength ); - ++i; - } - else if ( c < 32 ) - { - // Easy pass at non-alpha/numeric/symbol - // Below 32 is symbolic. - char buf[ 32 ]; - - #if defined(TIXML_SNPRINTF) - TIXML_SNPRINTF( buf, sizeof(buf), "&#x%02X;", (unsigned) ( c & 0xff ) ); - #else - sprintf( buf, "&#x%02X;", (unsigned) ( c & 0xff ) ); - #endif - - //*ME: warning C4267: convert 'size_t' to 'int' - //*ME: Int-Cast to make compiler happy ... - outString->append( buf, (int)strlen( buf ) ); - ++i; - } - else - { - //char realc = (char) c; - //outString->append( &realc, 1 ); - *outString += (char) c; // somewhat more efficient function call. - ++i; - } - } -} - - -TiXmlNode::TiXmlNode( NodeType _type ) : TiXmlBase() -{ - parent = 0; - type = _type; - firstChild = 0; - lastChild = 0; - prev = 0; - next = 0; -} - - -TiXmlNode::~TiXmlNode() -{ - TiXmlNode* node = firstChild; - TiXmlNode* temp = 0; - - while ( node ) - { - temp = node; - node = node->next; - delete temp; - } -} - - -void TiXmlNode::CopyTo( TiXmlNode* target ) const -{ - target->SetValue (value.c_str() ); - target->userData = userData; - target->location = location; -} - - -void TiXmlNode::Clear() -{ - TiXmlNode* node = firstChild; - TiXmlNode* temp = 0; - - while ( node ) - { - temp = node; - node = node->next; - delete temp; - } - - firstChild = 0; - lastChild = 0; -} - - -TiXmlNode* TiXmlNode::LinkEndChild( TiXmlNode* node ) -{ - assert( node->parent == 0 || node->parent == this ); - assert( node->GetDocument() == 0 || node->GetDocument() == this->GetDocument() ); - - if ( node->Type() == TiXmlNode::TINYXML_DOCUMENT ) - { - delete node; - if ( GetDocument() ) - GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); - return 0; - } - - node->parent = this; - - node->prev = lastChild; - node->next = 0; - - if ( lastChild ) - lastChild->next = node; - else - firstChild = node; // it was an empty list. - - lastChild = node; - return node; -} - - -TiXmlNode* TiXmlNode::InsertEndChild( const TiXmlNode& addThis ) -{ - if ( addThis.Type() == TiXmlNode::TINYXML_DOCUMENT ) - { - if ( GetDocument() ) - GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); - return 0; - } - TiXmlNode* node = addThis.Clone(); - if ( !node ) - return 0; - - return LinkEndChild( node ); -} - - -TiXmlNode* TiXmlNode::InsertBeforeChild( TiXmlNode* beforeThis, const TiXmlNode& addThis ) -{ - if ( !beforeThis || beforeThis->parent != this ) { - return 0; - } - if ( addThis.Type() == TiXmlNode::TINYXML_DOCUMENT ) - { - if ( GetDocument() ) - GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); - return 0; - } - - TiXmlNode* node = addThis.Clone(); - if ( !node ) - return 0; - node->parent = this; - - node->next = beforeThis; - node->prev = beforeThis->prev; - if ( beforeThis->prev ) - { - beforeThis->prev->next = node; - } - else - { - assert( firstChild == beforeThis ); - firstChild = node; - } - beforeThis->prev = node; - return node; -} - - -TiXmlNode* TiXmlNode::InsertAfterChild( TiXmlNode* afterThis, const TiXmlNode& addThis ) -{ - if ( !afterThis || afterThis->parent != this ) { - return 0; - } - if ( addThis.Type() == TiXmlNode::TINYXML_DOCUMENT ) - { - if ( GetDocument() ) - GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); - return 0; - } - - TiXmlNode* node = addThis.Clone(); - if ( !node ) - return 0; - node->parent = this; - - node->prev = afterThis; - node->next = afterThis->next; - if ( afterThis->next ) - { - afterThis->next->prev = node; - } - else - { - assert( lastChild == afterThis ); - lastChild = node; - } - afterThis->next = node; - return node; -} - - -TiXmlNode* TiXmlNode::ReplaceChild( TiXmlNode* replaceThis, const TiXmlNode& withThis ) -{ - if ( !replaceThis ) - return 0; - - if ( replaceThis->parent != this ) - return 0; - - if ( withThis.ToDocument() ) { - // A document can never be a child. Thanks to Noam. - TiXmlDocument* document = GetDocument(); - if ( document ) - document->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); - return 0; - } - - TiXmlNode* node = withThis.Clone(); - if ( !node ) - return 0; - - node->next = replaceThis->next; - node->prev = replaceThis->prev; - - if ( replaceThis->next ) - replaceThis->next->prev = node; - else - lastChild = node; - - if ( replaceThis->prev ) - replaceThis->prev->next = node; - else - firstChild = node; - - delete replaceThis; - node->parent = this; - return node; -} - - -bool TiXmlNode::RemoveChild( TiXmlNode* removeThis ) -{ - if ( !removeThis ) { - return false; - } - - if ( removeThis->parent != this ) - { - assert( 0 ); - return false; - } - - if ( removeThis->next ) - removeThis->next->prev = removeThis->prev; - else - lastChild = removeThis->prev; - - if ( removeThis->prev ) - removeThis->prev->next = removeThis->next; - else - firstChild = removeThis->next; - - delete removeThis; - return true; -} - -const TiXmlNode* TiXmlNode::FirstChild( const char * _value ) const -{ - const TiXmlNode* node; - for ( node = firstChild; node; node = node->next ) - { - if ( strcmp( node->Value(), _value ) == 0 ) - return node; - } - return 0; -} - - -const TiXmlNode* TiXmlNode::LastChild( const char * _value ) const -{ - const TiXmlNode* node; - for ( node = lastChild; node; node = node->prev ) - { - if ( strcmp( node->Value(), _value ) == 0 ) - return node; - } - return 0; -} - - -const TiXmlNode* TiXmlNode::IterateChildren( const TiXmlNode* previous ) const -{ - if ( !previous ) - { - return FirstChild(); - } - else - { - assert( previous->parent == this ); - return previous->NextSibling(); - } -} - - -const TiXmlNode* TiXmlNode::IterateChildren( const char * val, const TiXmlNode* previous ) const -{ - if ( !previous ) - { - return FirstChild( val ); - } - else - { - assert( previous->parent == this ); - return previous->NextSibling( val ); - } -} - - -const TiXmlNode* TiXmlNode::NextSibling( const char * _value ) const -{ - const TiXmlNode* node; - for ( node = next; node; node = node->next ) - { - if ( strcmp( node->Value(), _value ) == 0 ) - return node; - } - return 0; -} - - -const TiXmlNode* TiXmlNode::PreviousSibling( const char * _value ) const -{ - const TiXmlNode* node; - for ( node = prev; node; node = node->prev ) - { - if ( strcmp( node->Value(), _value ) == 0 ) - return node; - } - return 0; -} - - -void TiXmlElement::RemoveAttribute( const char * name ) -{ - #ifdef TIXML_USE_STL - TIXML_STRING str( name ); - TiXmlAttribute* node = attributeSet.Find( str ); - #else - TiXmlAttribute* node = attributeSet.Find( name ); - #endif - if ( node ) - { - attributeSet.Remove( node ); - delete node; - } -} - -const TiXmlElement* TiXmlNode::FirstChildElement() const -{ - const TiXmlNode* node; - - for ( node = FirstChild(); - node; - node = node->NextSibling() ) - { - if ( node->ToElement() ) - return node->ToElement(); - } - return 0; -} - - -const TiXmlElement* TiXmlNode::FirstChildElement( const char * _value ) const -{ - const TiXmlNode* node; - - for ( node = FirstChild( _value ); - node; - node = node->NextSibling( _value ) ) - { - if ( node->ToElement() ) - return node->ToElement(); - } - return 0; -} - - -const TiXmlElement* TiXmlNode::NextSiblingElement() const -{ - const TiXmlNode* node; - - for ( node = NextSibling(); - node; - node = node->NextSibling() ) - { - if ( node->ToElement() ) - return node->ToElement(); - } - return 0; -} - - -const TiXmlElement* TiXmlNode::NextSiblingElement( const char * _value ) const -{ - const TiXmlNode* node; - - for ( node = NextSibling( _value ); - node; - node = node->NextSibling( _value ) ) - { - if ( node->ToElement() ) - return node->ToElement(); - } - return 0; -} - - -const TiXmlDocument* TiXmlNode::GetDocument() const -{ - const TiXmlNode* node; - - for( node = this; node; node = node->parent ) - { - if ( node->ToDocument() ) - return node->ToDocument(); - } - return 0; -} - - -TiXmlElement::TiXmlElement (const char * _value) - : TiXmlNode( TiXmlNode::TINYXML_ELEMENT ) -{ - firstChild = lastChild = 0; - value = _value; -} - - -#ifdef TIXML_USE_STL -TiXmlElement::TiXmlElement( const std::string& _value ) - : TiXmlNode( TiXmlNode::TINYXML_ELEMENT ) -{ - firstChild = lastChild = 0; - value = _value; -} -#endif - - -TiXmlElement::TiXmlElement( const TiXmlElement& copy) - : TiXmlNode( TiXmlNode::TINYXML_ELEMENT ) -{ - firstChild = lastChild = 0; - copy.CopyTo( this ); -} - - -TiXmlElement& TiXmlElement::operator=( const TiXmlElement& base ) -{ - ClearThis(); - base.CopyTo( this ); - return *this; -} - - -TiXmlElement::~TiXmlElement() -{ - ClearThis(); -} - - -void TiXmlElement::ClearThis() -{ - Clear(); - while( attributeSet.First() ) - { - TiXmlAttribute* node = attributeSet.First(); - attributeSet.Remove( node ); - delete node; - } -} - - -const char* TiXmlElement::Attribute( const char* name ) const -{ - const TiXmlAttribute* node = attributeSet.Find( name ); - if ( node ) - return node->Value(); - return 0; -} - - -#ifdef TIXML_USE_STL -const std::string* TiXmlElement::Attribute( const std::string& name ) const -{ - const TiXmlAttribute* attrib = attributeSet.Find( name ); - if ( attrib ) - return &attrib->ValueStr(); - return 0; -} -#endif - - -const char* TiXmlElement::Attribute( const char* name, int* i ) const -{ - const TiXmlAttribute* attrib = attributeSet.Find( name ); - const char* result = 0; - - if ( attrib ) { - result = attrib->Value(); - if ( i ) { - attrib->QueryIntValue( i ); - } - } - return result; -} - - -#ifdef TIXML_USE_STL -const std::string* TiXmlElement::Attribute( const std::string& name, int* i ) const -{ - const TiXmlAttribute* attrib = attributeSet.Find( name ); - const std::string* result = 0; - - if ( attrib ) { - result = &attrib->ValueStr(); - if ( i ) { - attrib->QueryIntValue( i ); - } - } - return result; -} -#endif - - -const char* TiXmlElement::Attribute( const char* name, double* d ) const -{ - const TiXmlAttribute* attrib = attributeSet.Find( name ); - const char* result = 0; - - if ( attrib ) { - result = attrib->Value(); - if ( d ) { - attrib->QueryDoubleValue( d ); - } - } - return result; -} - - -#ifdef TIXML_USE_STL -const std::string* TiXmlElement::Attribute( const std::string& name, double* d ) const -{ - const TiXmlAttribute* attrib = attributeSet.Find( name ); - const std::string* result = 0; - - if ( attrib ) { - result = &attrib->ValueStr(); - if ( d ) { - attrib->QueryDoubleValue( d ); - } - } - return result; -} -#endif - - -int TiXmlElement::QueryIntAttribute( const char* name, int* ival ) const -{ - const TiXmlAttribute* attrib = attributeSet.Find( name ); - if ( !attrib ) - return TIXML_NO_ATTRIBUTE; - return attrib->QueryIntValue( ival ); -} - - -int TiXmlElement::QueryUnsignedAttribute( const char* name, unsigned* value ) const -{ - const TiXmlAttribute* node = attributeSet.Find( name ); - if ( !node ) - return TIXML_NO_ATTRIBUTE; - - int ival = 0; - int result = node->QueryIntValue( &ival ); - *value = (unsigned)ival; - return result; -} - - -int TiXmlElement::QueryBoolAttribute( const char* name, bool* bval ) const -{ - const TiXmlAttribute* node = attributeSet.Find( name ); - if ( !node ) - return TIXML_NO_ATTRIBUTE; - - int result = TIXML_WRONG_TYPE; - if ( StringEqual( node->Value(), "true", true, TIXML_ENCODING_UNKNOWN ) - || StringEqual( node->Value(), "yes", true, TIXML_ENCODING_UNKNOWN ) - || StringEqual( node->Value(), "1", true, TIXML_ENCODING_UNKNOWN ) ) - { - *bval = true; - result = TIXML_SUCCESS; - } - else if ( StringEqual( node->Value(), "false", true, TIXML_ENCODING_UNKNOWN ) - || StringEqual( node->Value(), "no", true, TIXML_ENCODING_UNKNOWN ) - || StringEqual( node->Value(), "0", true, TIXML_ENCODING_UNKNOWN ) ) - { - *bval = false; - result = TIXML_SUCCESS; - } - return result; -} - - - -#ifdef TIXML_USE_STL -int TiXmlElement::QueryIntAttribute( const std::string& name, int* ival ) const -{ - const TiXmlAttribute* attrib = attributeSet.Find( name ); - if ( !attrib ) - return TIXML_NO_ATTRIBUTE; - return attrib->QueryIntValue( ival ); -} -#endif - - -int TiXmlElement::QueryDoubleAttribute( const char* name, double* dval ) const -{ - const TiXmlAttribute* attrib = attributeSet.Find( name ); - if ( !attrib ) - return TIXML_NO_ATTRIBUTE; - return attrib->QueryDoubleValue( dval ); -} - - -#ifdef TIXML_USE_STL -int TiXmlElement::QueryDoubleAttribute( const std::string& name, double* dval ) const -{ - const TiXmlAttribute* attrib = attributeSet.Find( name ); - if ( !attrib ) - return TIXML_NO_ATTRIBUTE; - return attrib->QueryDoubleValue( dval ); -} -#endif - - -void TiXmlElement::SetAttribute( const char * name, int val ) -{ - TiXmlAttribute* attrib = attributeSet.FindOrCreate( name ); - if ( attrib ) { - attrib->SetIntValue( val ); - } -} - - -#ifdef TIXML_USE_STL -void TiXmlElement::SetAttribute( const std::string& name, int val ) -{ - TiXmlAttribute* attrib = attributeSet.FindOrCreate( name ); - if ( attrib ) { - attrib->SetIntValue( val ); - } -} -#endif - - -void TiXmlElement::SetDoubleAttribute( const char * name, double val ) -{ - TiXmlAttribute* attrib = attributeSet.FindOrCreate( name ); - if ( attrib ) { - attrib->SetDoubleValue( val ); - } -} - - -#ifdef TIXML_USE_STL -void TiXmlElement::SetDoubleAttribute( const std::string& name, double val ) -{ - TiXmlAttribute* attrib = attributeSet.FindOrCreate( name ); - if ( attrib ) { - attrib->SetDoubleValue( val ); - } -} -#endif - - -void TiXmlElement::SetAttribute( const char * cname, const char * cvalue ) -{ - TiXmlAttribute* attrib = attributeSet.FindOrCreate( cname ); - if ( attrib ) { - attrib->SetValue( cvalue ); - } -} - - -#ifdef TIXML_USE_STL -void TiXmlElement::SetAttribute( const std::string& _name, const std::string& _value ) -{ - TiXmlAttribute* attrib = attributeSet.FindOrCreate( _name ); - if ( attrib ) { - attrib->SetValue( _value ); - } -} -#endif - - -void TiXmlElement::Print( FILE* cfile, int depth ) const -{ - int i; - assert( cfile ); - for ( i=0; iNext() ) - { - fprintf( cfile, " " ); - attrib->Print( cfile, depth ); - } - - // There are 3 different formatting approaches: - // 1) An element without children is printed as a node - // 2) An element with only a text child is printed as text - // 3) An element with children is printed on multiple lines. - TiXmlNode* node; - if ( !firstChild ) - { - fprintf( cfile, " />" ); - } - else if ( firstChild == lastChild && firstChild->ToText() ) - { - fprintf( cfile, ">" ); - firstChild->Print( cfile, depth + 1 ); - fprintf( cfile, "", value.c_str() ); - } - else - { - fprintf( cfile, ">" ); - - for ( node = firstChild; node; node=node->NextSibling() ) - { - if ( !node->ToText() ) - { - fprintf( cfile, "\n" ); - } - node->Print( cfile, depth+1 ); - } - fprintf( cfile, "\n" ); - for( i=0; i", value.c_str() ); - } -} - - -void TiXmlElement::CopyTo( TiXmlElement* target ) const -{ - // superclass: - TiXmlNode::CopyTo( target ); - - // Element class: - // Clone the attributes, then clone the children. - const TiXmlAttribute* attribute = 0; - for( attribute = attributeSet.First(); - attribute; - attribute = attribute->Next() ) - { - target->SetAttribute( attribute->Name(), attribute->Value() ); - } - - TiXmlNode* node = 0; - for ( node = firstChild; node; node = node->NextSibling() ) - { - target->LinkEndChild( node->Clone() ); - } -} - -bool TiXmlElement::Accept( TiXmlVisitor* visitor ) const -{ - if ( visitor->VisitEnter( *this, attributeSet.First() ) ) - { - for ( const TiXmlNode* node=FirstChild(); node; node=node->NextSibling() ) - { - if ( !node->Accept( visitor ) ) - break; - } - } - return visitor->VisitExit( *this ); -} - - -TiXmlNode* TiXmlElement::Clone() const -{ - TiXmlElement* clone = new TiXmlElement( Value() ); - if ( !clone ) - return 0; - - CopyTo( clone ); - return clone; -} - - -const char* TiXmlElement::GetText() const -{ - const TiXmlNode* child = this->FirstChild(); - if ( child ) { - const TiXmlText* childText = child->ToText(); - if ( childText ) { - return childText->Value(); - } - } - return 0; -} - - -TiXmlDocument::TiXmlDocument() : TiXmlNode( TiXmlNode::TINYXML_DOCUMENT ) -{ - tabsize = 4; - useMicrosoftBOM = false; - ClearError(); -} - -TiXmlDocument::TiXmlDocument( const char * documentName ) : TiXmlNode( TiXmlNode::TINYXML_DOCUMENT ) -{ - tabsize = 4; - useMicrosoftBOM = false; - value = documentName; - ClearError(); -} - - -#ifdef TIXML_USE_STL -TiXmlDocument::TiXmlDocument( const std::string& documentName ) : TiXmlNode( TiXmlNode::TINYXML_DOCUMENT ) -{ - tabsize = 4; - useMicrosoftBOM = false; - value = documentName; - ClearError(); -} -#endif - - -TiXmlDocument::TiXmlDocument( const TiXmlDocument& copy ) : TiXmlNode( TiXmlNode::TINYXML_DOCUMENT ) -{ - copy.CopyTo( this ); -} - - -TiXmlDocument& TiXmlDocument::operator=( const TiXmlDocument& copy ) -{ - Clear(); - copy.CopyTo( this ); - return *this; -} - - -bool TiXmlDocument::LoadFile( TiXmlEncoding encoding ) -{ - return LoadFile( Value(), encoding ); -} - - -bool TiXmlDocument::SaveFile() const -{ - return SaveFile( Value() ); -} - -bool TiXmlDocument::LoadFile( const char* _filename, TiXmlEncoding encoding ) -{ - TIXML_STRING filename( _filename ); - value = filename; - - // reading in binary mode so that tinyxml can normalize the EOL - FILE* file = TiXmlFOpen( value.c_str (), "rb" ); - - if ( file ) - { - bool result = LoadFile( file, encoding ); - fclose( file ); - return result; - } - else - { - SetError( TIXML_ERROR_OPENING_FILE, 0, 0, TIXML_ENCODING_UNKNOWN ); - return false; - } -} - -bool TiXmlDocument::LoadFile( FILE* file, TiXmlEncoding encoding ) -{ - if ( !file ) - { - SetError( TIXML_ERROR_OPENING_FILE, 0, 0, TIXML_ENCODING_UNKNOWN ); - return false; - } - - // Delete the existing data: - Clear(); - location.Clear(); - - // Get the file size, so we can pre-allocate the string. HUGE speed impact. - long length = 0; - fseek( file, 0, SEEK_END ); - length = ftell( file ); - fseek( file, 0, SEEK_SET ); - - // Strange case, but good to handle up front. - if ( length <= 0 ) - { - SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN ); - return false; - } - - // Subtle bug here. TinyXml did use fgets. But from the XML spec: - // 2.11 End-of-Line Handling - // - // - // ...the XML processor MUST behave as if it normalized all line breaks in external - // parsed entities (including the document entity) on input, before parsing, by translating - // both the two-character sequence #xD #xA and any #xD that is not followed by #xA to - // a single #xA character. - // - // - // It is not clear fgets does that, and certainly isn't clear it works cross platform. - // Generally, you expect fgets to translate from the convention of the OS to the c/unix - // convention, and not work generally. - - /* - while( fgets( buf, sizeof(buf), file ) ) - { - data += buf; - } - */ - - char* buf = new char[ length+1 ]; - buf[0] = 0; - - if ( fread( buf, length, 1, file ) != 1 ) { - delete [] buf; - SetError( TIXML_ERROR_OPENING_FILE, 0, 0, TIXML_ENCODING_UNKNOWN ); - return false; - } - - // Process the buffer in place to normalize new lines. (See comment above.) - // Copies from the 'p' to 'q' pointer, where p can advance faster if - // a newline-carriage return is hit. - // - // Wikipedia: - // Systems based on ASCII or a compatible character set use either LF (Line feed, '\n', 0x0A, 10 in decimal) or - // CR (Carriage return, '\r', 0x0D, 13 in decimal) individually, or CR followed by LF (CR+LF, 0x0D 0x0A)... - // * LF: Multics, Unix and Unix-like systems (GNU/Linux, AIX, Xenix, Mac OS X, FreeBSD, etc.), BeOS, Amiga, RISC OS, and others - // * CR+LF: DEC RT-11 and most other early non-Unix, non-IBM OSes, CP/M, MP/M, DOS, OS/2, Microsoft Windows, Symbian OS - // * CR: Commodore 8-bit machines, Apple II family, Mac OS up to version 9 and OS-9 - - const char* p = buf; // the read head - char* q = buf; // the write head - const char CR = 0x0d; - const char LF = 0x0a; - - buf[length] = 0; - while( *p ) { - assert( p < (buf+length) ); - assert( q <= (buf+length) ); - assert( q <= p ); - - if ( *p == CR ) { - *q++ = LF; - p++; - if ( *p == LF ) { // check for CR+LF (and skip LF) - p++; - } - } - else { - *q++ = *p++; - } - } - assert( q <= (buf+length) ); - *q = 0; - - Parse( buf, 0, encoding ); - - delete [] buf; - return !Error(); -} - - -bool TiXmlDocument::SaveFile( const char * filename ) const -{ - // The old c stuff lives on... - FILE* fp = TiXmlFOpen( filename, "w" ); - if ( fp ) - { - bool result = SaveFile( fp ); - fclose( fp ); - return result; - } - return false; -} - - -bool TiXmlDocument::SaveFile( FILE* fp ) const -{ - if ( useMicrosoftBOM ) - { - const unsigned char TIXML_UTF_LEAD_0 = 0xefU; - const unsigned char TIXML_UTF_LEAD_1 = 0xbbU; - const unsigned char TIXML_UTF_LEAD_2 = 0xbfU; - - fputc( TIXML_UTF_LEAD_0, fp ); - fputc( TIXML_UTF_LEAD_1, fp ); - fputc( TIXML_UTF_LEAD_2, fp ); - } - Print( fp, 0 ); - return (ferror(fp) == 0); -} - - -void TiXmlDocument::CopyTo( TiXmlDocument* target ) const -{ - TiXmlNode::CopyTo( target ); - - target->error = error; - target->errorId = errorId; - target->errorDesc = errorDesc; - target->tabsize = tabsize; - target->errorLocation = errorLocation; - target->useMicrosoftBOM = useMicrosoftBOM; - - TiXmlNode* node = 0; - for ( node = firstChild; node; node = node->NextSibling() ) - { - target->LinkEndChild( node->Clone() ); - } -} - - -TiXmlNode* TiXmlDocument::Clone() const -{ - TiXmlDocument* clone = new TiXmlDocument(); - if ( !clone ) - return 0; - - CopyTo( clone ); - return clone; -} - - -void TiXmlDocument::Print( FILE* cfile, int depth ) const -{ - assert( cfile ); - for ( const TiXmlNode* node=FirstChild(); node; node=node->NextSibling() ) - { - node->Print( cfile, depth ); - fprintf( cfile, "\n" ); - } -} - - -bool TiXmlDocument::Accept( TiXmlVisitor* visitor ) const -{ - if ( visitor->VisitEnter( *this ) ) - { - for ( const TiXmlNode* node=FirstChild(); node; node=node->NextSibling() ) - { - if ( !node->Accept( visitor ) ) - break; - } - } - return visitor->VisitExit( *this ); -} - - -const TiXmlAttribute* TiXmlAttribute::Next() const -{ - // We are using knowledge of the sentinel. The sentinel - // have a value or name. - if ( next->value.empty() && next->name.empty() ) - return 0; - return next; -} - -/* -TiXmlAttribute* TiXmlAttribute::Next() -{ - // We are using knowledge of the sentinel. The sentinel - // have a value or name. - if ( next->value.empty() && next->name.empty() ) - return 0; - return next; -} -*/ - -const TiXmlAttribute* TiXmlAttribute::Previous() const -{ - // We are using knowledge of the sentinel. The sentinel - // have a value or name. - if ( prev->value.empty() && prev->name.empty() ) - return 0; - return prev; -} - -/* -TiXmlAttribute* TiXmlAttribute::Previous() -{ - // We are using knowledge of the sentinel. The sentinel - // have a value or name. - if ( prev->value.empty() && prev->name.empty() ) - return 0; - return prev; -} -*/ - -void TiXmlAttribute::Print( FILE* cfile, int /*depth*/, TIXML_STRING* str ) const -{ - TIXML_STRING n, v; - - EncodeString( name, &n ); - EncodeString( value, &v ); - - if (value.find ('\"') == TIXML_STRING::npos) { - if ( cfile ) { - fprintf (cfile, "%s=\"%s\"", n.c_str(), v.c_str() ); - } - if ( str ) { - (*str) += n; (*str) += "=\""; (*str) += v; (*str) += "\""; - } - } - else { - if ( cfile ) { - fprintf (cfile, "%s='%s'", n.c_str(), v.c_str() ); - } - if ( str ) { - (*str) += n; (*str) += "='"; (*str) += v; (*str) += "'"; - } - } -} - - -int TiXmlAttribute::QueryIntValue( int* ival ) const -{ - if ( TIXML_SSCANF( value.c_str(), "%d", ival ) == 1 ) - return TIXML_SUCCESS; - return TIXML_WRONG_TYPE; -} - -int TiXmlAttribute::QueryDoubleValue( double* dval ) const -{ - if ( TIXML_SSCANF( value.c_str(), "%lf", dval ) == 1 ) - return TIXML_SUCCESS; - return TIXML_WRONG_TYPE; -} - -void TiXmlAttribute::SetIntValue( int _value ) -{ - char buf [64]; - #if defined(TIXML_SNPRINTF) - TIXML_SNPRINTF(buf, sizeof(buf), "%d", _value); - #else - sprintf (buf, "%d", _value); - #endif - SetValue (buf); -} - -void TiXmlAttribute::SetDoubleValue( double _value ) -{ - char buf [256]; - #if defined(TIXML_SNPRINTF) - TIXML_SNPRINTF( buf, sizeof(buf), "%g", _value); - #else - sprintf (buf, "%g", _value); - #endif - SetValue (buf); -} - -int TiXmlAttribute::IntValue() const -{ - return atoi (value.c_str ()); -} - -double TiXmlAttribute::DoubleValue() const -{ - return atof (value.c_str ()); -} - - -TiXmlComment::TiXmlComment( const TiXmlComment& copy ) : TiXmlNode( TiXmlNode::TINYXML_COMMENT ) -{ - copy.CopyTo( this ); -} - - -TiXmlComment& TiXmlComment::operator=( const TiXmlComment& base ) -{ - Clear(); - base.CopyTo( this ); - return *this; -} - - -void TiXmlComment::Print( FILE* cfile, int depth ) const -{ - assert( cfile ); - for ( int i=0; i", value.c_str() ); -} - - -void TiXmlComment::CopyTo( TiXmlComment* target ) const -{ - TiXmlNode::CopyTo( target ); -} - - -bool TiXmlComment::Accept( TiXmlVisitor* visitor ) const -{ - return visitor->Visit( *this ); -} - - -TiXmlNode* TiXmlComment::Clone() const -{ - TiXmlComment* clone = new TiXmlComment(); - - if ( !clone ) - return 0; - - CopyTo( clone ); - return clone; -} - - -void TiXmlText::Print( FILE* cfile, int depth ) const -{ - assert( cfile ); - if ( cdata ) - { - int i; - fprintf( cfile, "\n" ); - for ( i=0; i\n", value.c_str() ); // unformatted output - } - else - { - TIXML_STRING buffer; - EncodeString( value, &buffer ); - fprintf( cfile, "%s", buffer.c_str() ); - } -} - - -void TiXmlText::CopyTo( TiXmlText* target ) const -{ - TiXmlNode::CopyTo( target ); - target->cdata = cdata; -} - - -bool TiXmlText::Accept( TiXmlVisitor* visitor ) const -{ - return visitor->Visit( *this ); -} - - -TiXmlNode* TiXmlText::Clone() const -{ - TiXmlText* clone = 0; - clone = new TiXmlText( "" ); - - if ( !clone ) - return 0; - - CopyTo( clone ); - return clone; -} - - -TiXmlDeclaration::TiXmlDeclaration( const char * _version, - const char * _encoding, - const char * _standalone ) - : TiXmlNode( TiXmlNode::TINYXML_DECLARATION ) -{ - version = _version; - encoding = _encoding; - standalone = _standalone; -} - - -#ifdef TIXML_USE_STL -TiXmlDeclaration::TiXmlDeclaration( const std::string& _version, - const std::string& _encoding, - const std::string& _standalone ) - : TiXmlNode( TiXmlNode::TINYXML_DECLARATION ) -{ - version = _version; - encoding = _encoding; - standalone = _standalone; -} -#endif - - -TiXmlDeclaration::TiXmlDeclaration( const TiXmlDeclaration& copy ) - : TiXmlNode( TiXmlNode::TINYXML_DECLARATION ) -{ - copy.CopyTo( this ); -} - - -TiXmlDeclaration& TiXmlDeclaration::operator=( const TiXmlDeclaration& copy ) -{ - Clear(); - copy.CopyTo( this ); - return *this; -} - - -void TiXmlDeclaration::Print( FILE* cfile, int /*depth*/, TIXML_STRING* str ) const -{ - if ( cfile ) fprintf( cfile, "" ); - if ( str ) (*str) += "?>"; -} - - -void TiXmlDeclaration::CopyTo( TiXmlDeclaration* target ) const -{ - TiXmlNode::CopyTo( target ); - - target->version = version; - target->encoding = encoding; - target->standalone = standalone; -} - - -bool TiXmlDeclaration::Accept( TiXmlVisitor* visitor ) const -{ - return visitor->Visit( *this ); -} - - -TiXmlNode* TiXmlDeclaration::Clone() const -{ - TiXmlDeclaration* clone = new TiXmlDeclaration(); - - if ( !clone ) - return 0; - - CopyTo( clone ); - return clone; -} - - -void TiXmlUnknown::Print( FILE* cfile, int depth ) const -{ - for ( int i=0; i", value.c_str() ); -} - - -void TiXmlUnknown::CopyTo( TiXmlUnknown* target ) const -{ - TiXmlNode::CopyTo( target ); -} - - -bool TiXmlUnknown::Accept( TiXmlVisitor* visitor ) const -{ - return visitor->Visit( *this ); -} - - -TiXmlNode* TiXmlUnknown::Clone() const -{ - TiXmlUnknown* clone = new TiXmlUnknown(); - - if ( !clone ) - return 0; - - CopyTo( clone ); - return clone; -} - - -TiXmlAttributeSet::TiXmlAttributeSet() -{ - sentinel.next = &sentinel; - sentinel.prev = &sentinel; -} - - -TiXmlAttributeSet::~TiXmlAttributeSet() -{ - assert( sentinel.next == &sentinel ); - assert( sentinel.prev == &sentinel ); -} - - -void TiXmlAttributeSet::Add( TiXmlAttribute* addMe ) -{ - #ifdef TIXML_USE_STL - assert( !Find( TIXML_STRING( addMe->Name() ) ) ); // Shouldn't be multiply adding to the set. - #else - assert( !Find( addMe->Name() ) ); // Shouldn't be multiply adding to the set. - #endif - - addMe->next = &sentinel; - addMe->prev = sentinel.prev; - - sentinel.prev->next = addMe; - sentinel.prev = addMe; -} - -void TiXmlAttributeSet::Remove( TiXmlAttribute* removeMe ) -{ - TiXmlAttribute* node; - - for( node = sentinel.next; node != &sentinel; node = node->next ) - { - if ( node == removeMe ) - { - node->prev->next = node->next; - node->next->prev = node->prev; - node->next = 0; - node->prev = 0; - return; - } - } - assert( 0 ); // we tried to remove a non-linked attribute. -} - - -#ifdef TIXML_USE_STL -TiXmlAttribute* TiXmlAttributeSet::Find( const std::string& name ) const -{ - for( TiXmlAttribute* node = sentinel.next; node != &sentinel; node = node->next ) - { - if ( node->name == name ) - return node; - } - return 0; -} - -TiXmlAttribute* TiXmlAttributeSet::FindOrCreate( const std::string& _name ) -{ - TiXmlAttribute* attrib = Find( _name ); - if ( !attrib ) { - attrib = new TiXmlAttribute(); - Add( attrib ); - attrib->SetName( _name ); - } - return attrib; -} -#endif - - -TiXmlAttribute* TiXmlAttributeSet::Find( const char* name ) const -{ - for( TiXmlAttribute* node = sentinel.next; node != &sentinel; node = node->next ) - { - if ( strcmp( node->name.c_str(), name ) == 0 ) - return node; - } - return 0; -} - - -TiXmlAttribute* TiXmlAttributeSet::FindOrCreate( const char* _name ) -{ - TiXmlAttribute* attrib = Find( _name ); - if ( !attrib ) { - attrib = new TiXmlAttribute(); - Add( attrib ); - attrib->SetName( _name ); - } - return attrib; -} - - -#ifdef TIXML_USE_STL -std::istream& operator>> (std::istream & in, TiXmlNode & base) -{ - TIXML_STRING tag; - tag.reserve( 8 * 1000 ); - base.StreamIn( &in, &tag ); - - base.Parse( tag.c_str(), 0, TIXML_DEFAULT_ENCODING ); - return in; -} -#endif - - -#ifdef TIXML_USE_STL -std::ostream& operator<< (std::ostream & out, const TiXmlNode & base) -{ - TiXmlPrinter printer; - printer.SetStreamPrinting(); - base.Accept( &printer ); - out << printer.Str(); - - return out; -} - - -std::string& operator<< (std::string& out, const TiXmlNode& base ) -{ - TiXmlPrinter printer; - printer.SetStreamPrinting(); - base.Accept( &printer ); - out.append( printer.Str() ); - - return out; -} -#endif - - -TiXmlHandle TiXmlHandle::FirstChild() const -{ - if ( node ) - { - TiXmlNode* child = node->FirstChild(); - if ( child ) - return TiXmlHandle( child ); - } - return TiXmlHandle( 0 ); -} - - -TiXmlHandle TiXmlHandle::FirstChild( const char * value ) const -{ - if ( node ) - { - TiXmlNode* child = node->FirstChild( value ); - if ( child ) - return TiXmlHandle( child ); - } - return TiXmlHandle( 0 ); -} - - -TiXmlHandle TiXmlHandle::FirstChildElement() const -{ - if ( node ) - { - TiXmlElement* child = node->FirstChildElement(); - if ( child ) - return TiXmlHandle( child ); - } - return TiXmlHandle( 0 ); -} - - -TiXmlHandle TiXmlHandle::FirstChildElement( const char * value ) const -{ - if ( node ) - { - TiXmlElement* child = node->FirstChildElement( value ); - if ( child ) - return TiXmlHandle( child ); - } - return TiXmlHandle( 0 ); -} - - -TiXmlHandle TiXmlHandle::Child( int count ) const -{ - if ( node ) - { - int i; - TiXmlNode* child = node->FirstChild(); - for ( i=0; - child && iNextSibling(), ++i ) - { - // nothing - } - if ( child ) - return TiXmlHandle( child ); - } - return TiXmlHandle( 0 ); -} - - -TiXmlHandle TiXmlHandle::Child( const char* value, int count ) const -{ - if ( node ) - { - int i; - TiXmlNode* child = node->FirstChild( value ); - for ( i=0; - child && iNextSibling( value ), ++i ) - { - // nothing - } - if ( child ) - return TiXmlHandle( child ); - } - return TiXmlHandle( 0 ); -} - - -TiXmlHandle TiXmlHandle::ChildElement( int count ) const -{ - if ( node ) - { - int i; - TiXmlElement* child = node->FirstChildElement(); - for ( i=0; - child && iNextSiblingElement(), ++i ) - { - // nothing - } - if ( child ) - return TiXmlHandle( child ); - } - return TiXmlHandle( 0 ); -} - - -TiXmlHandle TiXmlHandle::ChildElement( const char* value, int count ) const -{ - if ( node ) - { - int i; - TiXmlElement* child = node->FirstChildElement( value ); - for ( i=0; - child && iNextSiblingElement( value ), ++i ) - { - // nothing - } - if ( child ) - return TiXmlHandle( child ); - } - return TiXmlHandle( 0 ); -} - - -bool TiXmlPrinter::VisitEnter( const TiXmlDocument& ) -{ - return true; -} - -bool TiXmlPrinter::VisitExit( const TiXmlDocument& ) -{ - return true; -} - -bool TiXmlPrinter::VisitEnter( const TiXmlElement& element, const TiXmlAttribute* firstAttribute ) -{ - DoIndent(); - buffer += "<"; - buffer += element.Value(); - - for( const TiXmlAttribute* attrib = firstAttribute; attrib; attrib = attrib->Next() ) - { - buffer += " "; - attrib->Print( 0, 0, &buffer ); - } - - if ( !element.FirstChild() ) - { - buffer += " />"; - DoLineBreak(); - } - else - { - buffer += ">"; - if ( element.FirstChild()->ToText() - && element.LastChild() == element.FirstChild() - && element.FirstChild()->ToText()->CDATA() == false ) - { - simpleTextPrint = true; - // no DoLineBreak()! - } - else - { - DoLineBreak(); - } - } - ++depth; - return true; -} - - -bool TiXmlPrinter::VisitExit( const TiXmlElement& element ) -{ - --depth; - if ( !element.FirstChild() ) - { - // nothing. - } - else - { - if ( simpleTextPrint ) - { - simpleTextPrint = false; - } - else - { - DoIndent(); - } - buffer += ""; - DoLineBreak(); - } - return true; -} - - -bool TiXmlPrinter::Visit( const TiXmlText& text ) -{ - if ( text.CDATA() ) - { - DoIndent(); - buffer += ""; - DoLineBreak(); - } - else if ( simpleTextPrint ) - { - TIXML_STRING str; - TiXmlBase::EncodeString( text.ValueTStr(), &str ); - buffer += str; - } - else - { - DoIndent(); - TIXML_STRING str; - TiXmlBase::EncodeString( text.ValueTStr(), &str ); - buffer += str; - DoLineBreak(); - } - return true; -} - - -bool TiXmlPrinter::Visit( const TiXmlDeclaration& declaration ) -{ - DoIndent(); - declaration.Print( 0, 0, &buffer ); - DoLineBreak(); - return true; -} - - -bool TiXmlPrinter::Visit( const TiXmlComment& comment ) -{ - DoIndent(); - buffer += ""; - DoLineBreak(); - return true; -} - - -bool TiXmlPrinter::Visit( const TiXmlUnknown& unknown ) -{ - DoIndent(); - buffer += "<"; - buffer += unknown.Value(); - buffer += ">"; - DoLineBreak(); - return true; -} - diff --git a/src/XrdXml/tinyxml.h b/src/XrdXml/tinyxml.h deleted file mode 100644 index a3589e5b269..00000000000 --- a/src/XrdXml/tinyxml.h +++ /dev/null @@ -1,1805 +0,0 @@ -/* -www.sourceforge.net/projects/tinyxml -Original code by Lee Thomason (www.grinninglizard.com) - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any -damages arising from the use of this software. - -Permission is granted to anyone to use this software for any -purpose, including commercial applications, and to alter it and -redistribute it freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must -not claim that you wrote the original software. If you use this -software in a product, an acknowledgment in the product documentation -would be appreciated but is not required. - -2. Altered source versions must be plainly marked as such, and -must not be misrepresented as being the original software. - -3. This notice may not be removed or altered from any source -distribution. -*/ - - -#ifndef TINYXML_INCLUDED -#define TINYXML_INCLUDED - -#ifdef _MSC_VER -#pragma warning( push ) -#pragma warning( disable : 4530 ) -#pragma warning( disable : 4786 ) -#endif - -#include -#include -#include -#include -#include - -// Help out windows: -#if defined( _DEBUG ) && !defined( DEBUG ) -#define DEBUG -#endif - -#ifdef TIXML_USE_STL - #include - #include - #include - #define TIXML_STRING std::string -#else - #include "tinystr.h" - #define TIXML_STRING TiXmlString -#endif - -// Deprecated library function hell. Compilers want to use the -// new safe versions. This probably doesn't fully address the problem, -// but it gets closer. There are too many compilers for me to fully -// test. If you get compilation troubles, undefine TIXML_SAFE -#define TIXML_SAFE - -#ifdef TIXML_SAFE - #if defined(_MSC_VER) && (_MSC_VER >= 1400 ) - // Microsoft visual studio, version 2005 and higher. - #define TIXML_SNPRINTF _snprintf_s - #define TIXML_SSCANF sscanf_s - #elif defined(_MSC_VER) && (_MSC_VER >= 1200 ) - // Microsoft visual studio, version 6 and higher. - //#pragma message( "Using _sn* functions." ) - #define TIXML_SNPRINTF _snprintf - #define TIXML_SSCANF sscanf - #elif defined(__GNUC__) && (__GNUC__ >= 3 ) - // GCC version 3 and higher.s - //#warning( "Using sn* functions." ) - #define TIXML_SNPRINTF snprintf - #define TIXML_SSCANF sscanf - #else - #define TIXML_SNPRINTF snprintf - #define TIXML_SSCANF sscanf - #endif -#endif - -class TiXmlDocument; -class TiXmlElement; -class TiXmlComment; -class TiXmlUnknown; -class TiXmlAttribute; -class TiXmlText; -class TiXmlDeclaration; -class TiXmlParsingData; - -const int TIXML_MAJOR_VERSION = 2; -const int TIXML_MINOR_VERSION = 6; -const int TIXML_PATCH_VERSION = 2; - -/* Internal structure for tracking location of items - in the XML file. -*/ -struct TiXmlCursor -{ - TiXmlCursor() { Clear(); } - void Clear() { row = col = -1; } - - int row; // 0 based. - int col; // 0 based. -}; - - -/** - Implements the interface to the "Visitor pattern" (see the Accept() method.) - If you call the Accept() method, it requires being passed a TiXmlVisitor - class to handle callbacks. For nodes that contain other nodes (Document, Element) - you will get called with a VisitEnter/VisitExit pair. Nodes that are always leaves - are simply called with Visit(). - - If you return 'true' from a Visit method, recursive parsing will continue. If you return - false, no children of this node or its sibilings will be Visited. - - All flavors of Visit methods have a default implementation that returns 'true' (continue - visiting). You need to only override methods that are interesting to you. - - Generally Accept() is called on the TiXmlDocument, although all nodes suppert Visiting. - - You should never change the document from a callback. - - @sa TiXmlNode::Accept() -*/ -class TiXmlVisitor -{ -public: - virtual ~TiXmlVisitor() {} - - /// Visit a document. - virtual bool VisitEnter( const TiXmlDocument& /*doc*/ ) { return true; } - /// Visit a document. - virtual bool VisitExit( const TiXmlDocument& /*doc*/ ) { return true; } - - /// Visit an element. - virtual bool VisitEnter( const TiXmlElement& /*element*/, const TiXmlAttribute* /*firstAttribute*/ ) { return true; } - /// Visit an element. - virtual bool VisitExit( const TiXmlElement& /*element*/ ) { return true; } - - /// Visit a declaration - virtual bool Visit( const TiXmlDeclaration& /*declaration*/ ) { return true; } - /// Visit a text node - virtual bool Visit( const TiXmlText& /*text*/ ) { return true; } - /// Visit a comment node - virtual bool Visit( const TiXmlComment& /*comment*/ ) { return true; } - /// Visit an unknown node - virtual bool Visit( const TiXmlUnknown& /*unknown*/ ) { return true; } -}; - -// Only used by Attribute::Query functions -enum -{ - TIXML_SUCCESS, - TIXML_NO_ATTRIBUTE, - TIXML_WRONG_TYPE -}; - - -// Used by the parsing routines. -enum TiXmlEncoding -{ - TIXML_ENCODING_UNKNOWN, - TIXML_ENCODING_UTF8, - TIXML_ENCODING_LEGACY -}; - -const TiXmlEncoding TIXML_DEFAULT_ENCODING = TIXML_ENCODING_UNKNOWN; - -/** TiXmlBase is a base class for every class in TinyXml. - It does little except to establish that TinyXml classes - can be printed and provide some utility functions. - - In XML, the document and elements can contain - other elements and other types of nodes. - - @verbatim - A Document can contain: Element (container or leaf) - Comment (leaf) - Unknown (leaf) - Declaration( leaf ) - - An Element can contain: Element (container or leaf) - Text (leaf) - Attributes (not on tree) - Comment (leaf) - Unknown (leaf) - - A Decleration contains: Attributes (not on tree) - @endverbatim -*/ -class TiXmlBase -{ - friend class TiXmlNode; - friend class TiXmlElement; - friend class TiXmlDocument; - -public: - TiXmlBase() : userData(0) {} - virtual ~TiXmlBase() {} - - /** All TinyXml classes can print themselves to a filestream - or the string class (TiXmlString in non-STL mode, std::string - in STL mode.) Either or both cfile and str can be null. - - This is a formatted print, and will insert - tabs and newlines. - - (For an unformatted stream, use the << operator.) - */ - virtual void Print( FILE* cfile, int depth ) const = 0; - - /** The world does not agree on whether white space should be kept or - not. In order to make everyone happy, these global, static functions - are provided to set whether or not TinyXml will condense all white space - into a single space or not. The default is to condense. Note changing this - value is not thread safe. - */ - static void SetCondenseWhiteSpace( bool condense ) { condenseWhiteSpace = condense; } - - /// Return the current white space setting. - static bool IsWhiteSpaceCondensed() { return condenseWhiteSpace; } - - /** Return the position, in the original source file, of this node or attribute. - The row and column are 1-based. (That is the first row and first column is - 1,1). If the returns values are 0 or less, then the parser does not have - a row and column value. - - Generally, the row and column value will be set when the TiXmlDocument::Load(), - TiXmlDocument::LoadFile(), or any TiXmlNode::Parse() is called. It will NOT be set - when the DOM was created from operator>>. - - The values reflect the initial load. Once the DOM is modified programmatically - (by adding or changing nodes and attributes) the new values will NOT update to - reflect changes in the document. - - There is a minor performance cost to computing the row and column. Computation - can be disabled if TiXmlDocument::SetTabSize() is called with 0 as the value. - - @sa TiXmlDocument::SetTabSize() - */ - int Row() const { return location.row + 1; } - int Column() const { return location.col + 1; } ///< See Row() - - void SetUserData( void* user ) { userData = user; } ///< Set a pointer to arbitrary user data. - void* GetUserData() { return userData; } ///< Get a pointer to arbitrary user data. - const void* GetUserData() const { return userData; } ///< Get a pointer to arbitrary user data. - - // Table that returs, for a given lead byte, the total number of bytes - // in the UTF-8 sequence. - static const int utf8ByteTable[256]; - - virtual const char* Parse( const char* p, - TiXmlParsingData* data, - TiXmlEncoding encoding /*= TIXML_ENCODING_UNKNOWN */ ) = 0; - - /** Expands entities in a string. Note this should not contian the tag's '<', '>', etc, - or they will be transformed into entities! - */ - static void EncodeString( const TIXML_STRING& str, TIXML_STRING* out ); - - enum - { - TIXML_NO_ERROR = 0, - TIXML_ERROR, - TIXML_ERROR_OPENING_FILE, - TIXML_ERROR_PARSING_ELEMENT, - TIXML_ERROR_FAILED_TO_READ_ELEMENT_NAME, - TIXML_ERROR_READING_ELEMENT_VALUE, - TIXML_ERROR_READING_ATTRIBUTES, - TIXML_ERROR_PARSING_EMPTY, - TIXML_ERROR_READING_END_TAG, - TIXML_ERROR_PARSING_UNKNOWN, - TIXML_ERROR_PARSING_COMMENT, - TIXML_ERROR_PARSING_DECLARATION, - TIXML_ERROR_DOCUMENT_EMPTY, - TIXML_ERROR_EMBEDDED_NULL, - TIXML_ERROR_PARSING_CDATA, - TIXML_ERROR_DOCUMENT_TOP_ONLY, - - TIXML_ERROR_STRING_COUNT - }; - -protected: - - static const char* SkipWhiteSpace( const char*, TiXmlEncoding encoding ); - - inline static bool IsWhiteSpace( char c ) - { - return ( isspace( (unsigned char) c ) || c == '\n' || c == '\r' ); - } - inline static bool IsWhiteSpace( int c ) - { - if ( c < 256 ) - return IsWhiteSpace( (char) c ); - return false; // Again, only truly correct for English/Latin...but usually works. - } - - #ifdef TIXML_USE_STL - static bool StreamWhiteSpace( std::istream * in, TIXML_STRING * tag ); - static bool StreamTo( std::istream * in, int character, TIXML_STRING * tag ); - #endif - - /* Reads an XML name into the string provided. Returns - a pointer just past the last character of the name, - or 0 if the function has an error. - */ - static const char* ReadName( const char* p, TIXML_STRING* name, TiXmlEncoding encoding ); - - /* Reads text. Returns a pointer past the given end tag. - Wickedly complex options, but it keeps the (sensitive) code in one place. - */ - static const char* ReadText( const char* in, // where to start - TIXML_STRING* text, // the string read - bool ignoreWhiteSpace, // whether to keep the white space - const char* endTag, // what ends this text - bool ignoreCase, // whether to ignore case in the end tag - TiXmlEncoding encoding ); // the current encoding - - // If an entity has been found, transform it into a character. - static const char* GetEntity( const char* in, char* value, int* length, TiXmlEncoding encoding ); - - // Get a character, while interpreting entities. - // The length can be from 0 to 4 bytes. - inline static const char* GetChar( const char* p, char* _value, int* length, TiXmlEncoding encoding ) - { - assert( p ); - if ( encoding == TIXML_ENCODING_UTF8 ) - { - *length = utf8ByteTable[ *((const unsigned char*)p) ]; - assert( *length >= 0 && *length < 5 ); - } - else - { - *length = 1; - } - - if ( *length == 1 ) - { - if ( *p == '&' ) - return GetEntity( p, _value, length, encoding ); - *_value = *p; - return p+1; - } - else if ( *length ) - { - //strncpy( _value, p, *length ); // lots of compilers don't like this function (unsafe), - // and the null terminator isn't needed - for( int i=0; p[i] && i<*length; ++i ) { - _value[i] = p[i]; - } - return p + (*length); - } - else - { - // Not valid text. - return 0; - } - } - - // Return true if the next characters in the stream are any of the endTag sequences. - // Ignore case only works for english, and should only be relied on when comparing - // to English words: StringEqual( p, "version", true ) is fine. - static bool StringEqual( const char* p, - const char* endTag, - bool ignoreCase, - TiXmlEncoding encoding ); - - static const char* errorString[ TIXML_ERROR_STRING_COUNT ]; - - TiXmlCursor location; - - /// Field containing a generic user pointer - void* userData; - - // None of these methods are reliable for any language except English. - // Good for approximation, not great for accuracy. - static int IsAlpha( unsigned char anyByte, TiXmlEncoding encoding ); - static int IsAlphaNum( unsigned char anyByte, TiXmlEncoding encoding ); - inline static int ToLower( int v, TiXmlEncoding encoding ) - { - if ( encoding == TIXML_ENCODING_UTF8 ) - { - if ( v < 128 ) return tolower( v ); - return v; - } - else - { - return tolower( v ); - } - } - static void ConvertUTF32ToUTF8( unsigned long input, char* output, int* length ); - -private: - TiXmlBase( const TiXmlBase& ); // not implemented. - void operator=( const TiXmlBase& base ); // not allowed. - - struct Entity - { - const char* str; - unsigned int strLength; - char chr; - }; - enum - { - NUM_ENTITY = 5, - MAX_ENTITY_LENGTH = 6 - - }; - static Entity entity[ NUM_ENTITY ]; - static bool condenseWhiteSpace; -}; - - -/** The parent class for everything in the Document Object Model. - (Except for attributes). - Nodes have siblings, a parent, and children. A node can be - in a document, or stand on its own. The type of a TiXmlNode - can be queried, and it can be cast to its more defined type. -*/ -class TiXmlNode : public TiXmlBase -{ - friend class TiXmlDocument; - friend class TiXmlElement; - -public: - #ifdef TIXML_USE_STL - - /** An input stream operator, for every class. Tolerant of newlines and - formatting, but doesn't expect them. - */ - friend std::istream& operator >> (std::istream& in, TiXmlNode& base); - - /** An output stream operator, for every class. Note that this outputs - without any newlines or formatting, as opposed to Print(), which - includes tabs and new lines. - - The operator<< and operator>> are not completely symmetric. Writing - a node to a stream is very well defined. You'll get a nice stream - of output, without any extra whitespace or newlines. - - But reading is not as well defined. (As it always is.) If you create - a TiXmlElement (for example) and read that from an input stream, - the text needs to define an element or junk will result. This is - true of all input streams, but it's worth keeping in mind. - - A TiXmlDocument will read nodes until it reads a root element, and - all the children of that root element. - */ - friend std::ostream& operator<< (std::ostream& out, const TiXmlNode& base); - - /// Appends the XML node or attribute to a std::string. - friend std::string& operator<< (std::string& out, const TiXmlNode& base ); - - #endif - - /** The types of XML nodes supported by TinyXml. (All the - unsupported types are picked up by UNKNOWN.) - */ - enum NodeType - { - TINYXML_DOCUMENT, - TINYXML_ELEMENT, - TINYXML_COMMENT, - TINYXML_UNKNOWN, - TINYXML_TEXT, - TINYXML_DECLARATION, - TINYXML_TYPECOUNT - }; - - virtual ~TiXmlNode(); - - /** The meaning of 'value' changes for the specific type of - TiXmlNode. - @verbatim - Document: filename of the xml file - Element: name of the element - Comment: the comment text - Unknown: the tag contents - Text: the text string - @endverbatim - - The subclasses will wrap this function. - */ - const char *Value() const { return value.c_str (); } - - #ifdef TIXML_USE_STL - /** Return Value() as a std::string. If you only use STL, - this is more efficient than calling Value(). - Only available in STL mode. - */ - const std::string& ValueStr() const { return value; } - #endif - - const TIXML_STRING& ValueTStr() const { return value; } - - /** Changes the value of the node. Defined as: - @verbatim - Document: filename of the xml file - Element: name of the element - Comment: the comment text - Unknown: the tag contents - Text: the text string - @endverbatim - */ - void SetValue(const char * _value) { value = _value;} - - #ifdef TIXML_USE_STL - /// STL std::string form. - void SetValue( const std::string& _value ) { value = _value; } - #endif - - /// Delete all the children of this node. Does not affect 'this'. - void Clear(); - - /// One step up the DOM. - TiXmlNode* Parent() { return parent; } - const TiXmlNode* Parent() const { return parent; } - - const TiXmlNode* FirstChild() const { return firstChild; } ///< The first child of this node. Will be null if there are no children. - TiXmlNode* FirstChild() { return firstChild; } - const TiXmlNode* FirstChild( const char * value ) const; ///< The first child of this node with the matching 'value'. Will be null if none found. - /// The first child of this node with the matching 'value'. Will be null if none found. - TiXmlNode* FirstChild( const char * _value ) { - // Call through to the const version - safe since nothing is changed. Exiting syntax: cast this to a const (always safe) - // call the method, cast the return back to non-const. - return const_cast< TiXmlNode* > ((const_cast< const TiXmlNode* >(this))->FirstChild( _value )); - } - const TiXmlNode* LastChild() const { return lastChild; } /// The last child of this node. Will be null if there are no children. - TiXmlNode* LastChild() { return lastChild; } - - const TiXmlNode* LastChild( const char * value ) const; /// The last child of this node matching 'value'. Will be null if there are no children. - TiXmlNode* LastChild( const char * _value ) { - return const_cast< TiXmlNode* > ((const_cast< const TiXmlNode* >(this))->LastChild( _value )); - } - - #ifdef TIXML_USE_STL - const TiXmlNode* FirstChild( const std::string& _value ) const { return FirstChild (_value.c_str ()); } ///< STL std::string form. - TiXmlNode* FirstChild( const std::string& _value ) { return FirstChild (_value.c_str ()); } ///< STL std::string form. - const TiXmlNode* LastChild( const std::string& _value ) const { return LastChild (_value.c_str ()); } ///< STL std::string form. - TiXmlNode* LastChild( const std::string& _value ) { return LastChild (_value.c_str ()); } ///< STL std::string form. - #endif - - /** An alternate way to walk the children of a node. - One way to iterate over nodes is: - @verbatim - for( child = parent->FirstChild(); child; child = child->NextSibling() ) - @endverbatim - - IterateChildren does the same thing with the syntax: - @verbatim - child = 0; - while( child = parent->IterateChildren( child ) ) - @endverbatim - - IterateChildren takes the previous child as input and finds - the next one. If the previous child is null, it returns the - first. IterateChildren will return null when done. - */ - const TiXmlNode* IterateChildren( const TiXmlNode* previous ) const; - TiXmlNode* IterateChildren( const TiXmlNode* previous ) { - return const_cast< TiXmlNode* >( (const_cast< const TiXmlNode* >(this))->IterateChildren( previous ) ); - } - - /// This flavor of IterateChildren searches for children with a particular 'value' - const TiXmlNode* IterateChildren( const char * value, const TiXmlNode* previous ) const; - TiXmlNode* IterateChildren( const char * _value, const TiXmlNode* previous ) { - return const_cast< TiXmlNode* >( (const_cast< const TiXmlNode* >(this))->IterateChildren( _value, previous ) ); - } - - #ifdef TIXML_USE_STL - const TiXmlNode* IterateChildren( const std::string& _value, const TiXmlNode* previous ) const { return IterateChildren (_value.c_str (), previous); } ///< STL std::string form. - TiXmlNode* IterateChildren( const std::string& _value, const TiXmlNode* previous ) { return IterateChildren (_value.c_str (), previous); } ///< STL std::string form. - #endif - - /** Add a new node related to this. Adds a child past the LastChild. - Returns a pointer to the new object or NULL if an error occured. - */ - TiXmlNode* InsertEndChild( const TiXmlNode& addThis ); - - - /** Add a new node related to this. Adds a child past the LastChild. - - NOTE: the node to be added is passed by pointer, and will be - henceforth owned (and deleted) by tinyXml. This method is efficient - and avoids an extra copy, but should be used with care as it - uses a different memory model than the other insert functions. - - @sa InsertEndChild - */ - TiXmlNode* LinkEndChild( TiXmlNode* addThis ); - - /** Add a new node related to this. Adds a child before the specified child. - Returns a pointer to the new object or NULL if an error occured. - */ - TiXmlNode* InsertBeforeChild( TiXmlNode* beforeThis, const TiXmlNode& addThis ); - - /** Add a new node related to this. Adds a child after the specified child. - Returns a pointer to the new object or NULL if an error occured. - */ - TiXmlNode* InsertAfterChild( TiXmlNode* afterThis, const TiXmlNode& addThis ); - - /** Replace a child of this node. - Returns a pointer to the new object or NULL if an error occured. - */ - TiXmlNode* ReplaceChild( TiXmlNode* replaceThis, const TiXmlNode& withThis ); - - /// Delete a child of this node. - bool RemoveChild( TiXmlNode* removeThis ); - - /// Navigate to a sibling node. - const TiXmlNode* PreviousSibling() const { return prev; } - TiXmlNode* PreviousSibling() { return prev; } - - /// Navigate to a sibling node. - const TiXmlNode* PreviousSibling( const char * ) const; - TiXmlNode* PreviousSibling( const char *_prev ) { - return const_cast< TiXmlNode* >( (const_cast< const TiXmlNode* >(this))->PreviousSibling( _prev ) ); - } - - #ifdef TIXML_USE_STL - const TiXmlNode* PreviousSibling( const std::string& _value ) const { return PreviousSibling (_value.c_str ()); } ///< STL std::string form. - TiXmlNode* PreviousSibling( const std::string& _value ) { return PreviousSibling (_value.c_str ()); } ///< STL std::string form. - const TiXmlNode* NextSibling( const std::string& _value) const { return NextSibling (_value.c_str ()); } ///< STL std::string form. - TiXmlNode* NextSibling( const std::string& _value) { return NextSibling (_value.c_str ()); } ///< STL std::string form. - #endif - - /// Navigate to a sibling node. - const TiXmlNode* NextSibling() const { return next; } - TiXmlNode* NextSibling() { return next; } - - /// Navigate to a sibling node with the given 'value'. - const TiXmlNode* NextSibling( const char * ) const; - TiXmlNode* NextSibling( const char* _next ) { - return const_cast< TiXmlNode* >( (const_cast< const TiXmlNode* >(this))->NextSibling( _next ) ); - } - - /** Convenience function to get through elements. - Calls NextSibling and ToElement. Will skip all non-Element - nodes. Returns 0 if there is not another element. - */ - const TiXmlElement* NextSiblingElement() const; - TiXmlElement* NextSiblingElement() { - return const_cast< TiXmlElement* >( (const_cast< const TiXmlNode* >(this))->NextSiblingElement() ); - } - - /** Convenience function to get through elements. - Calls NextSibling and ToElement. Will skip all non-Element - nodes. Returns 0 if there is not another element. - */ - const TiXmlElement* NextSiblingElement( const char * ) const; - TiXmlElement* NextSiblingElement( const char *_next ) { - return const_cast< TiXmlElement* >( (const_cast< const TiXmlNode* >(this))->NextSiblingElement( _next ) ); - } - - #ifdef TIXML_USE_STL - const TiXmlElement* NextSiblingElement( const std::string& _value) const { return NextSiblingElement (_value.c_str ()); } ///< STL std::string form. - TiXmlElement* NextSiblingElement( const std::string& _value) { return NextSiblingElement (_value.c_str ()); } ///< STL std::string form. - #endif - - /// Convenience function to get through elements. - const TiXmlElement* FirstChildElement() const; - TiXmlElement* FirstChildElement() { - return const_cast< TiXmlElement* >( (const_cast< const TiXmlNode* >(this))->FirstChildElement() ); - } - - /// Convenience function to get through elements. - const TiXmlElement* FirstChildElement( const char * _value ) const; - TiXmlElement* FirstChildElement( const char * _value ) { - return const_cast< TiXmlElement* >( (const_cast< const TiXmlNode* >(this))->FirstChildElement( _value ) ); - } - - #ifdef TIXML_USE_STL - const TiXmlElement* FirstChildElement( const std::string& _value ) const { return FirstChildElement (_value.c_str ()); } ///< STL std::string form. - TiXmlElement* FirstChildElement( const std::string& _value ) { return FirstChildElement (_value.c_str ()); } ///< STL std::string form. - #endif - - /** Query the type (as an enumerated value, above) of this node. - The possible types are: TINYXML_DOCUMENT, TINYXML_ELEMENT, TINYXML_COMMENT, - TINYXML_UNKNOWN, TINYXML_TEXT, and TINYXML_DECLARATION. - */ - int Type() const { return type; } - - /** Return a pointer to the Document this node lives in. - Returns null if not in a document. - */ - const TiXmlDocument* GetDocument() const; - TiXmlDocument* GetDocument() { - return const_cast< TiXmlDocument* >( (const_cast< const TiXmlNode* >(this))->GetDocument() ); - } - - /// Returns true if this node has no children. - bool NoChildren() const { return !firstChild; } - - virtual const TiXmlDocument* ToDocument() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. - virtual const TiXmlElement* ToElement() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. - virtual const TiXmlComment* ToComment() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. - virtual const TiXmlUnknown* ToUnknown() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. - virtual const TiXmlText* ToText() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. - virtual const TiXmlDeclaration* ToDeclaration() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. - - virtual TiXmlDocument* ToDocument() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. - virtual TiXmlElement* ToElement() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. - virtual TiXmlComment* ToComment() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. - virtual TiXmlUnknown* ToUnknown() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. - virtual TiXmlText* ToText() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. - virtual TiXmlDeclaration* ToDeclaration() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. - - /** Create an exact duplicate of this node and return it. The memory must be deleted - by the caller. - */ - virtual TiXmlNode* Clone() const = 0; - - /** Accept a hierchical visit the nodes in the TinyXML DOM. Every node in the - XML tree will be conditionally visited and the host will be called back - via the TiXmlVisitor interface. - - This is essentially a SAX interface for TinyXML. (Note however it doesn't re-parse - the XML for the callbacks, so the performance of TinyXML is unchanged by using this - interface versus any other.) - - The interface has been based on ideas from: - - - http://www.saxproject.org/ - - http://c2.com/cgi/wiki?HierarchicalVisitorPattern - - Which are both good references for "visiting". - - An example of using Accept(): - @verbatim - TiXmlPrinter printer; - tinyxmlDoc.Accept( &printer ); - const char* xmlcstr = printer.CStr(); - @endverbatim - */ - virtual bool Accept( TiXmlVisitor* visitor ) const = 0; - -protected: - TiXmlNode( NodeType _type ); - - // Copy to the allocated object. Shared functionality between Clone, Copy constructor, - // and the assignment operator. - void CopyTo( TiXmlNode* target ) const; - - #ifdef TIXML_USE_STL - // The real work of the input operator. - virtual void StreamIn( std::istream* in, TIXML_STRING* tag ) = 0; - #endif - - // Figure out what is at *p, and parse it. Returns null if it is not an xml node. - TiXmlNode* Identify( const char* start, TiXmlEncoding encoding ); - - TiXmlNode* parent; - NodeType type; - - TiXmlNode* firstChild; - TiXmlNode* lastChild; - - TIXML_STRING value; - - TiXmlNode* prev; - TiXmlNode* next; - -private: - TiXmlNode( const TiXmlNode& ); // not implemented. - void operator=( const TiXmlNode& base ); // not allowed. -}; - - -/** An attribute is a name-value pair. Elements have an arbitrary - number of attributes, each with a unique name. - - @note The attributes are not TiXmlNodes, since they are not - part of the tinyXML document object model. There are other - suggested ways to look at this problem. -*/ -class TiXmlAttribute : public TiXmlBase -{ - friend class TiXmlAttributeSet; - -public: - /// Construct an empty attribute. - TiXmlAttribute() : TiXmlBase() - { - document = 0; - prev = next = 0; - } - - #ifdef TIXML_USE_STL - /// std::string constructor. - TiXmlAttribute( const std::string& _name, const std::string& _value ) - { - name = _name; - value = _value; - document = 0; - prev = next = 0; - } - #endif - - /// Construct an attribute with a name and value. - TiXmlAttribute( const char * _name, const char * _value ) - { - name = _name; - value = _value; - document = 0; - prev = next = 0; - } - - const char* Name() const { return name.c_str(); } ///< Return the name of this attribute. - const char* Value() const { return value.c_str(); } ///< Return the value of this attribute. - #ifdef TIXML_USE_STL - const std::string& ValueStr() const { return value; } ///< Return the value of this attribute. - #endif - int IntValue() const; ///< Return the value of this attribute, converted to an integer. - double DoubleValue() const; ///< Return the value of this attribute, converted to a double. - - // Get the tinyxml string representation - const TIXML_STRING& NameTStr() const { return name; } - - /** QueryIntValue examines the value string. It is an alternative to the - IntValue() method with richer error checking. - If the value is an integer, it is stored in 'value' and - the call returns TIXML_SUCCESS. If it is not - an integer, it returns TIXML_WRONG_TYPE. - - A specialized but useful call. Note that for success it returns 0, - which is the opposite of almost all other TinyXml calls. - */ - int QueryIntValue( int* _value ) const; - /// QueryDoubleValue examines the value string. See QueryIntValue(). - int QueryDoubleValue( double* _value ) const; - - void SetName( const char* _name ) { name = _name; } ///< Set the name of this attribute. - void SetValue( const char* _value ) { value = _value; } ///< Set the value. - - void SetIntValue( int _value ); ///< Set the value from an integer. - void SetDoubleValue( double _value ); ///< Set the value from a double. - - #ifdef TIXML_USE_STL - /// STL std::string form. - void SetName( const std::string& _name ) { name = _name; } - /// STL std::string form. - void SetValue( const std::string& _value ) { value = _value; } - #endif - - /// Get the next sibling attribute in the DOM. Returns null at end. - const TiXmlAttribute* Next() const; - TiXmlAttribute* Next() { - return const_cast< TiXmlAttribute* >( (const_cast< const TiXmlAttribute* >(this))->Next() ); - } - - /// Get the previous sibling attribute in the DOM. Returns null at beginning. - const TiXmlAttribute* Previous() const; - TiXmlAttribute* Previous() { - return const_cast< TiXmlAttribute* >( (const_cast< const TiXmlAttribute* >(this))->Previous() ); - } - - bool operator==( const TiXmlAttribute& rhs ) const { return rhs.name == name; } - bool operator<( const TiXmlAttribute& rhs ) const { return name < rhs.name; } - bool operator>( const TiXmlAttribute& rhs ) const { return name > rhs.name; } - - /* Attribute parsing starts: first letter of the name - returns: the next char after the value end quote - */ - virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); - - // Prints this Attribute to a FILE stream. - virtual void Print( FILE* cfile, int depth ) const { - Print( cfile, depth, 0 ); - } - void Print( FILE* cfile, int depth, TIXML_STRING* str ) const; - - // [internal use] - // Set the document pointer so the attribute can report errors. - void SetDocument( TiXmlDocument* doc ) { document = doc; } - -private: - TiXmlAttribute( const TiXmlAttribute& ); // not implemented. - void operator=( const TiXmlAttribute& base ); // not allowed. - - TiXmlDocument* document; // A pointer back to a document, for error reporting. - TIXML_STRING name; - TIXML_STRING value; - TiXmlAttribute* prev; - TiXmlAttribute* next; -}; - - -/* A class used to manage a group of attributes. - It is only used internally, both by the ELEMENT and the DECLARATION. - - The set can be changed transparent to the Element and Declaration - classes that use it, but NOT transparent to the Attribute - which has to implement a next() and previous() method. Which makes - it a bit problematic and prevents the use of STL. - - This version is implemented with circular lists because: - - I like circular lists - - it demonstrates some independence from the (typical) doubly linked list. -*/ -class TiXmlAttributeSet -{ -public: - TiXmlAttributeSet(); - ~TiXmlAttributeSet(); - - void Add( TiXmlAttribute* attribute ); - void Remove( TiXmlAttribute* attribute ); - - const TiXmlAttribute* First() const { return ( sentinel.next == &sentinel ) ? 0 : sentinel.next; } - TiXmlAttribute* First() { return ( sentinel.next == &sentinel ) ? 0 : sentinel.next; } - const TiXmlAttribute* Last() const { return ( sentinel.prev == &sentinel ) ? 0 : sentinel.prev; } - TiXmlAttribute* Last() { return ( sentinel.prev == &sentinel ) ? 0 : sentinel.prev; } - - TiXmlAttribute* Find( const char* _name ) const; - TiXmlAttribute* FindOrCreate( const char* _name ); - -# ifdef TIXML_USE_STL - TiXmlAttribute* Find( const std::string& _name ) const; - TiXmlAttribute* FindOrCreate( const std::string& _name ); -# endif - - -private: - //*ME: Because of hidden/disabled copy-construktor in TiXmlAttribute (sentinel-element), - //*ME: this class must be also use a hidden/disabled copy-constructor !!! - TiXmlAttributeSet( const TiXmlAttributeSet& ); // not allowed - void operator=( const TiXmlAttributeSet& ); // not allowed (as TiXmlAttribute) - - TiXmlAttribute sentinel; -}; - - -/** The element is a container class. It has a value, the element name, - and can contain other elements, text, comments, and unknowns. - Elements also contain an arbitrary number of attributes. -*/ -class TiXmlElement : public TiXmlNode -{ -public: - /// Construct an element. - TiXmlElement (const char * in_value); - - #ifdef TIXML_USE_STL - /// std::string constructor. - TiXmlElement( const std::string& _value ); - #endif - - TiXmlElement( const TiXmlElement& ); - - TiXmlElement& operator=( const TiXmlElement& base ); - - virtual ~TiXmlElement(); - - /** Given an attribute name, Attribute() returns the value - for the attribute of that name, or null if none exists. - */ - const char* Attribute( const char* name ) const; - - /** Given an attribute name, Attribute() returns the value - for the attribute of that name, or null if none exists. - If the attribute exists and can be converted to an integer, - the integer value will be put in the return 'i', if 'i' - is non-null. - */ - const char* Attribute( const char* name, int* i ) const; - - /** Given an attribute name, Attribute() returns the value - for the attribute of that name, or null if none exists. - If the attribute exists and can be converted to an double, - the double value will be put in the return 'd', if 'd' - is non-null. - */ - const char* Attribute( const char* name, double* d ) const; - - /** QueryIntAttribute examines the attribute - it is an alternative to the - Attribute() method with richer error checking. - If the attribute is an integer, it is stored in 'value' and - the call returns TIXML_SUCCESS. If it is not - an integer, it returns TIXML_WRONG_TYPE. If the attribute - does not exist, then TIXML_NO_ATTRIBUTE is returned. - */ - int QueryIntAttribute( const char* name, int* _value ) const; - /// QueryUnsignedAttribute examines the attribute - see QueryIntAttribute(). - int QueryUnsignedAttribute( const char* name, unsigned* _value ) const; - /** QueryBoolAttribute examines the attribute - see QueryIntAttribute(). - Note that '1', 'true', or 'yes' are considered true, while '0', 'false' - and 'no' are considered false. - */ - int QueryBoolAttribute( const char* name, bool* _value ) const; - /// QueryDoubleAttribute examines the attribute - see QueryIntAttribute(). - int QueryDoubleAttribute( const char* name, double* _value ) const; - /// QueryFloatAttribute examines the attribute - see QueryIntAttribute(). - int QueryFloatAttribute( const char* name, float* _value ) const { - double d; - int result = QueryDoubleAttribute( name, &d ); - if ( result == TIXML_SUCCESS ) { - *_value = (float)d; - } - return result; - } - - #ifdef TIXML_USE_STL - /// QueryStringAttribute examines the attribute - see QueryIntAttribute(). - int QueryStringAttribute( const char* name, std::string* _value ) const { - const char* cstr = Attribute( name ); - if ( cstr ) { - *_value = std::string( cstr ); - return TIXML_SUCCESS; - } - return TIXML_NO_ATTRIBUTE; - } - - /** Template form of the attribute query which will try to read the - attribute into the specified type. Very easy, very powerful, but - be careful to make sure to call this with the correct type. - - NOTE: This method doesn't work correctly for 'string' types that contain spaces. - - @return TIXML_SUCCESS, TIXML_WRONG_TYPE, or TIXML_NO_ATTRIBUTE - */ - template< typename T > int QueryValueAttribute( const std::string& name, T* outValue ) const - { - const TiXmlAttribute* node = attributeSet.Find( name ); - if ( !node ) - return TIXML_NO_ATTRIBUTE; - - std::stringstream sstream( node->ValueStr() ); - sstream >> *outValue; - if ( !sstream.fail() ) - return TIXML_SUCCESS; - return TIXML_WRONG_TYPE; - } - - int QueryValueAttribute( const std::string& name, std::string* outValue ) const - { - const TiXmlAttribute* node = attributeSet.Find( name ); - if ( !node ) - return TIXML_NO_ATTRIBUTE; - *outValue = node->ValueStr(); - return TIXML_SUCCESS; - } - #endif - - /** Sets an attribute of name to a given value. The attribute - will be created if it does not exist, or changed if it does. - */ - void SetAttribute( const char* name, const char * _value ); - - #ifdef TIXML_USE_STL - const std::string* Attribute( const std::string& name ) const; - const std::string* Attribute( const std::string& name, int* i ) const; - const std::string* Attribute( const std::string& name, double* d ) const; - int QueryIntAttribute( const std::string& name, int* _value ) const; - int QueryDoubleAttribute( const std::string& name, double* _value ) const; - - /// STL std::string form. - void SetAttribute( const std::string& name, const std::string& _value ); - ///< STL std::string form. - void SetAttribute( const std::string& name, int _value ); - ///< STL std::string form. - void SetDoubleAttribute( const std::string& name, double value ); - #endif - - /** Sets an attribute of name to a given value. The attribute - will be created if it does not exist, or changed if it does. - */ - void SetAttribute( const char * name, int value ); - - /** Sets an attribute of name to a given value. The attribute - will be created if it does not exist, or changed if it does. - */ - void SetDoubleAttribute( const char * name, double value ); - - /** Deletes an attribute with the given name. - */ - void RemoveAttribute( const char * name ); - #ifdef TIXML_USE_STL - void RemoveAttribute( const std::string& name ) { RemoveAttribute (name.c_str ()); } ///< STL std::string form. - #endif - - const TiXmlAttribute* FirstAttribute() const { return attributeSet.First(); } ///< Access the first attribute in this element. - TiXmlAttribute* FirstAttribute() { return attributeSet.First(); } - const TiXmlAttribute* LastAttribute() const { return attributeSet.Last(); } ///< Access the last attribute in this element. - TiXmlAttribute* LastAttribute() { return attributeSet.Last(); } - - /** Convenience function for easy access to the text inside an element. Although easy - and concise, GetText() is limited compared to getting the TiXmlText child - and accessing it directly. - - If the first child of 'this' is a TiXmlText, the GetText() - returns the character string of the Text node, else null is returned. - - This is a convenient method for getting the text of simple contained text: - @verbatim - This is text - const char* str = fooElement->GetText(); - @endverbatim - - 'str' will be a pointer to "This is text". - - Note that this function can be misleading. If the element foo was created from - this XML: - @verbatim - This is text - @endverbatim - - then the value of str would be null. The first child node isn't a text node, it is - another element. From this XML: - @verbatim - This is text - @endverbatim - GetText() will return "This is ". - - WARNING: GetText() accesses a child node - don't become confused with the - similarly named TiXmlHandle::Text() and TiXmlNode::ToText() which are - safe type casts on the referenced node. - */ - const char* GetText() const; - - /// Creates a new Element and returns it - the returned element is a copy. - virtual TiXmlNode* Clone() const; - // Print the Element to a FILE stream. - virtual void Print( FILE* cfile, int depth ) const; - - /* Attribtue parsing starts: next char past '<' - returns: next char past '>' - */ - virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); - - virtual const TiXmlElement* ToElement() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. - virtual TiXmlElement* ToElement() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. - - /** Walk the XML tree visiting this node and all of its children. - */ - virtual bool Accept( TiXmlVisitor* visitor ) const; - -protected: - - void CopyTo( TiXmlElement* target ) const; - void ClearThis(); // like clear, but initializes 'this' object as well - - // Used to be public [internal use] - #ifdef TIXML_USE_STL - virtual void StreamIn( std::istream * in, TIXML_STRING * tag ); - #endif - /* [internal use] - Reads the "value" of the element -- another element, or text. - This should terminate with the current end tag. - */ - const char* ReadValue( const char* in, TiXmlParsingData* prevData, TiXmlEncoding encoding ); - -private: - TiXmlAttributeSet attributeSet; -}; - - -/** An XML comment. -*/ -class TiXmlComment : public TiXmlNode -{ -public: - /// Constructs an empty comment. - TiXmlComment() : TiXmlNode( TiXmlNode::TINYXML_COMMENT ) {} - /// Construct a comment from text. - TiXmlComment( const char* _value ) : TiXmlNode( TiXmlNode::TINYXML_COMMENT ) { - SetValue( _value ); - } - TiXmlComment( const TiXmlComment& ); - TiXmlComment& operator=( const TiXmlComment& base ); - - virtual ~TiXmlComment() {} - - /// Returns a copy of this Comment. - virtual TiXmlNode* Clone() const; - // Write this Comment to a FILE stream. - virtual void Print( FILE* cfile, int depth ) const; - - /* Attribtue parsing starts: at the ! of the !-- - returns: next char past '>' - */ - virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); - - virtual const TiXmlComment* ToComment() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. - virtual TiXmlComment* ToComment() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. - - /** Walk the XML tree visiting this node and all of its children. - */ - virtual bool Accept( TiXmlVisitor* visitor ) const; - -protected: - void CopyTo( TiXmlComment* target ) const; - - // used to be public - #ifdef TIXML_USE_STL - virtual void StreamIn( std::istream * in, TIXML_STRING * tag ); - #endif -// virtual void StreamOut( TIXML_OSTREAM * out ) const; - -private: - -}; - - -/** XML text. A text node can have 2 ways to output the next. "normal" output - and CDATA. It will default to the mode it was parsed from the XML file and - you generally want to leave it alone, but you can change the output mode with - SetCDATA() and query it with CDATA(). -*/ -class TiXmlText : public TiXmlNode -{ - friend class TiXmlElement; -public: - /** Constructor for text element. By default, it is treated as - normal, encoded text. If you want it be output as a CDATA text - element, set the parameter _cdata to 'true' - */ - TiXmlText (const char * initValue ) : TiXmlNode (TiXmlNode::TINYXML_TEXT) - { - SetValue( initValue ); - cdata = false; - } - virtual ~TiXmlText() {} - - #ifdef TIXML_USE_STL - /// Constructor. - TiXmlText( const std::string& initValue ) : TiXmlNode (TiXmlNode::TINYXML_TEXT) - { - SetValue( initValue ); - cdata = false; - } - #endif - - TiXmlText( const TiXmlText& copy ) : TiXmlNode( TiXmlNode::TINYXML_TEXT ) { copy.CopyTo( this ); } - TiXmlText& operator=( const TiXmlText& base ) { base.CopyTo( this ); return *this; } - - // Write this text object to a FILE stream. - virtual void Print( FILE* cfile, int depth ) const; - - /// Queries whether this represents text using a CDATA section. - bool CDATA() const { return cdata; } - /// Turns on or off a CDATA representation of text. - void SetCDATA( bool _cdata ) { cdata = _cdata; } - - virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); - - virtual const TiXmlText* ToText() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. - virtual TiXmlText* ToText() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. - - /** Walk the XML tree visiting this node and all of its children. - */ - virtual bool Accept( TiXmlVisitor* content ) const; - -protected : - /// [internal use] Creates a new Element and returns it. - virtual TiXmlNode* Clone() const; - void CopyTo( TiXmlText* target ) const; - - bool Blank() const; // returns true if all white space and new lines - // [internal use] - #ifdef TIXML_USE_STL - virtual void StreamIn( std::istream * in, TIXML_STRING * tag ); - #endif - -private: - bool cdata; // true if this should be input and output as a CDATA style text element -}; - - -/** In correct XML the declaration is the first entry in the file. - @verbatim - - @endverbatim - - TinyXml will happily read or write files without a declaration, - however. There are 3 possible attributes to the declaration: - version, encoding, and standalone. - - Note: In this version of the code, the attributes are - handled as special cases, not generic attributes, simply - because there can only be at most 3 and they are always the same. -*/ -class TiXmlDeclaration : public TiXmlNode -{ -public: - /// Construct an empty declaration. - TiXmlDeclaration() : TiXmlNode( TiXmlNode::TINYXML_DECLARATION ) {} - -#ifdef TIXML_USE_STL - /// Constructor. - TiXmlDeclaration( const std::string& _version, - const std::string& _encoding, - const std::string& _standalone ); -#endif - - /// Construct. - TiXmlDeclaration( const char* _version, - const char* _encoding, - const char* _standalone ); - - TiXmlDeclaration( const TiXmlDeclaration& copy ); - TiXmlDeclaration& operator=( const TiXmlDeclaration& copy ); - - virtual ~TiXmlDeclaration() {} - - /// Version. Will return an empty string if none was found. - const char *Version() const { return version.c_str (); } - /// Encoding. Will return an empty string if none was found. - const char *Encoding() const { return encoding.c_str (); } - /// Is this a standalone document? - const char *Standalone() const { return standalone.c_str (); } - - /// Creates a copy of this Declaration and returns it. - virtual TiXmlNode* Clone() const; - // Print this declaration to a FILE stream. - virtual void Print( FILE* cfile, int depth, TIXML_STRING* str ) const; - virtual void Print( FILE* cfile, int depth ) const { - Print( cfile, depth, 0 ); - } - - virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); - - virtual const TiXmlDeclaration* ToDeclaration() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. - virtual TiXmlDeclaration* ToDeclaration() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. - - /** Walk the XML tree visiting this node and all of its children. - */ - virtual bool Accept( TiXmlVisitor* visitor ) const; - -protected: - void CopyTo( TiXmlDeclaration* target ) const; - // used to be public - #ifdef TIXML_USE_STL - virtual void StreamIn( std::istream * in, TIXML_STRING * tag ); - #endif - -private: - - TIXML_STRING version; - TIXML_STRING encoding; - TIXML_STRING standalone; -}; - - -/** Any tag that tinyXml doesn't recognize is saved as an - unknown. It is a tag of text, but should not be modified. - It will be written back to the XML, unchanged, when the file - is saved. - - DTD tags get thrown into TiXmlUnknowns. -*/ -class TiXmlUnknown : public TiXmlNode -{ -public: - TiXmlUnknown() : TiXmlNode( TiXmlNode::TINYXML_UNKNOWN ) {} - virtual ~TiXmlUnknown() {} - - TiXmlUnknown( const TiXmlUnknown& copy ) : TiXmlNode( TiXmlNode::TINYXML_UNKNOWN ) { copy.CopyTo( this ); } - TiXmlUnknown& operator=( const TiXmlUnknown& copy ) { copy.CopyTo( this ); return *this; } - - /// Creates a copy of this Unknown and returns it. - virtual TiXmlNode* Clone() const; - // Print this Unknown to a FILE stream. - virtual void Print( FILE* cfile, int depth ) const; - - virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); - - virtual const TiXmlUnknown* ToUnknown() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. - virtual TiXmlUnknown* ToUnknown() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. - - /** Walk the XML tree visiting this node and all of its children. - */ - virtual bool Accept( TiXmlVisitor* content ) const; - -protected: - void CopyTo( TiXmlUnknown* target ) const; - - #ifdef TIXML_USE_STL - virtual void StreamIn( std::istream * in, TIXML_STRING * tag ); - #endif - -private: - -}; - - -/** Always the top level node. A document binds together all the - XML pieces. It can be saved, loaded, and printed to the screen. - The 'value' of a document node is the xml file name. -*/ -class TiXmlDocument : public TiXmlNode -{ -public: - /// Create an empty document, that has no name. - TiXmlDocument(); - /// Create a document with a name. The name of the document is also the filename of the xml. - TiXmlDocument( const char * documentName ); - - #ifdef TIXML_USE_STL - /// Constructor. - TiXmlDocument( const std::string& documentName ); - #endif - - TiXmlDocument( const TiXmlDocument& copy ); - TiXmlDocument& operator=( const TiXmlDocument& copy ); - - virtual ~TiXmlDocument() {} - - /** Load a file using the current document value. - Returns true if successful. Will delete any existing - document data before loading. - */ - bool LoadFile( TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ); - /// Save a file using the current document value. Returns true if successful. - bool SaveFile() const; - /// Load a file using the given filename. Returns true if successful. - bool LoadFile( const char * filename, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ); - /// Save a file using the given filename. Returns true if successful. - bool SaveFile( const char * filename ) const; - /** Load a file using the given FILE*. Returns true if successful. Note that this method - doesn't stream - the entire object pointed at by the FILE* - will be interpreted as an XML file. TinyXML doesn't stream in XML from the current - file location. Streaming may be added in the future. - */ - bool LoadFile( FILE*, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ); - /// Save a file using the given FILE*. Returns true if successful. - bool SaveFile( FILE* ) const; - - #ifdef TIXML_USE_STL - bool LoadFile( const std::string& filename, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ) ///< STL std::string version. - { - return LoadFile( filename.c_str(), encoding ); - } - bool SaveFile( const std::string& filename ) const ///< STL std::string version. - { - return SaveFile( filename.c_str() ); - } - #endif - - /** Parse the given null terminated block of xml data. Passing in an encoding to this - method (either TIXML_ENCODING_LEGACY or TIXML_ENCODING_UTF8 will force TinyXml - to use that encoding, regardless of what TinyXml might otherwise try to detect. - */ - virtual const char* Parse( const char* p, TiXmlParsingData* data = 0, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ); - - /** Get the root element -- the only top level element -- of the document. - In well formed XML, there should only be one. TinyXml is tolerant of - multiple elements at the document level. - */ - const TiXmlElement* RootElement() const { return FirstChildElement(); } - TiXmlElement* RootElement() { return FirstChildElement(); } - - /** If an error occurs, Error will be set to true. Also, - - The ErrorId() will contain the integer identifier of the error (not generally useful) - - The ErrorDesc() method will return the name of the error. (very useful) - - The ErrorRow() and ErrorCol() will return the location of the error (if known) - */ - bool Error() const { return error; } - - /// Contains a textual (english) description of the error if one occurs. - const char * ErrorDesc() const { return errorDesc.c_str (); } - - /** Generally, you probably want the error string ( ErrorDesc() ). But if you - prefer the ErrorId, this function will fetch it. - */ - int ErrorId() const { return errorId; } - - /** Returns the location (if known) of the error. The first column is column 1, - and the first row is row 1. A value of 0 means the row and column wasn't applicable - (memory errors, for example, have no row/column) or the parser lost the error. (An - error in the error reporting, in that case.) - - @sa SetTabSize, Row, Column - */ - int ErrorRow() const { return errorLocation.row+1; } - int ErrorCol() const { return errorLocation.col+1; } ///< The column where the error occured. See ErrorRow() - - /** SetTabSize() allows the error reporting functions (ErrorRow() and ErrorCol()) - to report the correct values for row and column. It does not change the output - or input in any way. - - By calling this method, with a tab size - greater than 0, the row and column of each node and attribute is stored - when the file is loaded. Very useful for tracking the DOM back in to - the source file. - - The tab size is required for calculating the location of nodes. If not - set, the default of 4 is used. The tabsize is set per document. Setting - the tabsize to 0 disables row/column tracking. - - Note that row and column tracking is not supported when using operator>>. - - The tab size needs to be enabled before the parse or load. Correct usage: - @verbatim - TiXmlDocument doc; - doc.SetTabSize( 8 ); - doc.Load( "myfile.xml" ); - @endverbatim - - @sa Row, Column - */ - void SetTabSize( int _tabsize ) { tabsize = _tabsize; } - - int TabSize() const { return tabsize; } - - /** If you have handled the error, it can be reset with this call. The error - state is automatically cleared if you Parse a new XML block. - */ - void ClearError() { error = false; - errorId = 0; - errorDesc = ""; - errorLocation.row = errorLocation.col = 0; - //errorLocation.last = 0; - } - - /** Write the document to standard out using formatted printing ("pretty print"). */ - void Print() const { Print( stdout, 0 ); } - - /* Write the document to a string using formatted printing ("pretty print"). This - will allocate a character array (new char[]) and return it as a pointer. The - calling code pust call delete[] on the return char* to avoid a memory leak. - */ - //char* PrintToMemory() const; - - /// Print this Document to a FILE stream. - virtual void Print( FILE* cfile, int depth = 0 ) const; - // [internal use] - void SetError( int err, const char* errorLocation, TiXmlParsingData* prevData, TiXmlEncoding encoding ); - - virtual const TiXmlDocument* ToDocument() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. - virtual TiXmlDocument* ToDocument() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. - - /** Walk the XML tree visiting this node and all of its children. - */ - virtual bool Accept( TiXmlVisitor* content ) const; - -protected : - // [internal use] - virtual TiXmlNode* Clone() const; - #ifdef TIXML_USE_STL - virtual void StreamIn( std::istream * in, TIXML_STRING * tag ); - #endif - -private: - void CopyTo( TiXmlDocument* target ) const; - - bool error; - int errorId; - TIXML_STRING errorDesc; - int tabsize; - TiXmlCursor errorLocation; - bool useMicrosoftBOM; // the UTF-8 BOM were found when read. Note this, and try to write. -}; - - -/** - A TiXmlHandle is a class that wraps a node pointer with null checks; this is - an incredibly useful thing. Note that TiXmlHandle is not part of the TinyXml - DOM structure. It is a separate utility class. - - Take an example: - @verbatim - - - - - - - @endverbatim - - Assuming you want the value of "attributeB" in the 2nd "Child" element, it's very - easy to write a *lot* of code that looks like: - - @verbatim - TiXmlElement* root = document.FirstChildElement( "Document" ); - if ( root ) - { - TiXmlElement* element = root->FirstChildElement( "Element" ); - if ( element ) - { - TiXmlElement* child = element->FirstChildElement( "Child" ); - if ( child ) - { - TiXmlElement* child2 = child->NextSiblingElement( "Child" ); - if ( child2 ) - { - // Finally do something useful. - @endverbatim - - And that doesn't even cover "else" cases. TiXmlHandle addresses the verbosity - of such code. A TiXmlHandle checks for null pointers so it is perfectly safe - and correct to use: - - @verbatim - TiXmlHandle docHandle( &document ); - TiXmlElement* child2 = docHandle.FirstChild( "Document" ).FirstChild( "Element" ).Child( "Child", 1 ).ToElement(); - if ( child2 ) - { - // do something useful - @endverbatim - - Which is MUCH more concise and useful. - - It is also safe to copy handles - internally they are nothing more than node pointers. - @verbatim - TiXmlHandle handleCopy = handle; - @endverbatim - - What they should not be used for is iteration: - - @verbatim - int i=0; - while ( true ) - { - TiXmlElement* child = docHandle.FirstChild( "Document" ).FirstChild( "Element" ).Child( "Child", i ).ToElement(); - if ( !child ) - break; - // do something - ++i; - } - @endverbatim - - It seems reasonable, but it is in fact two embedded while loops. The Child method is - a linear walk to find the element, so this code would iterate much more than it needs - to. Instead, prefer: - - @verbatim - TiXmlElement* child = docHandle.FirstChild( "Document" ).FirstChild( "Element" ).FirstChild( "Child" ).ToElement(); - - for( child; child; child=child->NextSiblingElement() ) - { - // do something - } - @endverbatim -*/ -class TiXmlHandle -{ -public: - /// Create a handle from any node (at any depth of the tree.) This can be a null pointer. - TiXmlHandle( TiXmlNode* _node ) { this->node = _node; } - /// Copy constructor - TiXmlHandle( const TiXmlHandle& ref ) { this->node = ref.node; } - TiXmlHandle operator=( const TiXmlHandle& ref ) { if ( &ref != this ) this->node = ref.node; return *this; } - - /// Return a handle to the first child node. - TiXmlHandle FirstChild() const; - /// Return a handle to the first child node with the given name. - TiXmlHandle FirstChild( const char * value ) const; - /// Return a handle to the first child element. - TiXmlHandle FirstChildElement() const; - /// Return a handle to the first child element with the given name. - TiXmlHandle FirstChildElement( const char * value ) const; - - /** Return a handle to the "index" child with the given name. - The first child is 0, the second 1, etc. - */ - TiXmlHandle Child( const char* value, int index ) const; - /** Return a handle to the "index" child. - The first child is 0, the second 1, etc. - */ - TiXmlHandle Child( int index ) const; - /** Return a handle to the "index" child element with the given name. - The first child element is 0, the second 1, etc. Note that only TiXmlElements - are indexed: other types are not counted. - */ - TiXmlHandle ChildElement( const char* value, int index ) const; - /** Return a handle to the "index" child element. - The first child element is 0, the second 1, etc. Note that only TiXmlElements - are indexed: other types are not counted. - */ - TiXmlHandle ChildElement( int index ) const; - - #ifdef TIXML_USE_STL - TiXmlHandle FirstChild( const std::string& _value ) const { return FirstChild( _value.c_str() ); } - TiXmlHandle FirstChildElement( const std::string& _value ) const { return FirstChildElement( _value.c_str() ); } - - TiXmlHandle Child( const std::string& _value, int index ) const { return Child( _value.c_str(), index ); } - TiXmlHandle ChildElement( const std::string& _value, int index ) const { return ChildElement( _value.c_str(), index ); } - #endif - - /** Return the handle as a TiXmlNode. This may return null. - */ - TiXmlNode* ToNode() const { return node; } - /** Return the handle as a TiXmlElement. This may return null. - */ - TiXmlElement* ToElement() const { return ( ( node && node->ToElement() ) ? node->ToElement() : 0 ); } - /** Return the handle as a TiXmlText. This may return null. - */ - TiXmlText* ToText() const { return ( ( node && node->ToText() ) ? node->ToText() : 0 ); } - /** Return the handle as a TiXmlUnknown. This may return null. - */ - TiXmlUnknown* ToUnknown() const { return ( ( node && node->ToUnknown() ) ? node->ToUnknown() : 0 ); } - - /** @deprecated use ToNode. - Return the handle as a TiXmlNode. This may return null. - */ - TiXmlNode* Node() const { return ToNode(); } - /** @deprecated use ToElement. - Return the handle as a TiXmlElement. This may return null. - */ - TiXmlElement* Element() const { return ToElement(); } - /** @deprecated use ToText() - Return the handle as a TiXmlText. This may return null. - */ - TiXmlText* Text() const { return ToText(); } - /** @deprecated use ToUnknown() - Return the handle as a TiXmlUnknown. This may return null. - */ - TiXmlUnknown* Unknown() const { return ToUnknown(); } - -private: - TiXmlNode* node; -}; - - -/** Print to memory functionality. The TiXmlPrinter is useful when you need to: - - -# Print to memory (especially in non-STL mode) - -# Control formatting (line endings, etc.) - - When constructed, the TiXmlPrinter is in its default "pretty printing" mode. - Before calling Accept() you can call methods to control the printing - of the XML document. After TiXmlNode::Accept() is called, the printed document can - be accessed via the CStr(), Str(), and Size() methods. - - TiXmlPrinter uses the Visitor API. - @verbatim - TiXmlPrinter printer; - printer.SetIndent( "\t" ); - - doc.Accept( &printer ); - fprintf( stdout, "%s", printer.CStr() ); - @endverbatim -*/ -class TiXmlPrinter : public TiXmlVisitor -{ -public: - TiXmlPrinter() : depth( 0 ), simpleTextPrint( false ), - buffer(), indent( " " ), lineBreak( "\n" ) {} - - virtual bool VisitEnter( const TiXmlDocument& doc ); - virtual bool VisitExit( const TiXmlDocument& doc ); - - virtual bool VisitEnter( const TiXmlElement& element, const TiXmlAttribute* firstAttribute ); - virtual bool VisitExit( const TiXmlElement& element ); - - virtual bool Visit( const TiXmlDeclaration& declaration ); - virtual bool Visit( const TiXmlText& text ); - virtual bool Visit( const TiXmlComment& comment ); - virtual bool Visit( const TiXmlUnknown& unknown ); - - /** Set the indent characters for printing. By default 4 spaces - but tab (\t) is also useful, or null/empty string for no indentation. - */ - void SetIndent( const char* _indent ) { indent = _indent ? _indent : "" ; } - /// Query the indention string. - const char* Indent() { return indent.c_str(); } - /** Set the line breaking string. By default set to newline (\n). - Some operating systems prefer other characters, or can be - set to the null/empty string for no indenation. - */ - void SetLineBreak( const char* _lineBreak ) { lineBreak = _lineBreak ? _lineBreak : ""; } - /// Query the current line breaking string. - const char* LineBreak() { return lineBreak.c_str(); } - - /** Switch over to "stream printing" which is the most dense formatting without - linebreaks. Common when the XML is needed for network transmission. - */ - void SetStreamPrinting() { indent = ""; - lineBreak = ""; - } - /// Return the result. - const char* CStr() { return buffer.c_str(); } - /// Return the length of the result string. - size_t Size() { return buffer.size(); } - - #ifdef TIXML_USE_STL - /// Return the result. - const std::string& Str() { return buffer; } - #endif - -private: - void DoIndent() { - for( int i=0; i -#include - -#include "tinyxml.h" - -//#define DEBUG_PARSER -#if defined( DEBUG_PARSER ) -# if defined( DEBUG ) && defined( _MSC_VER ) -# include -# define TIXML_LOG OutputDebugString -# else -# define TIXML_LOG printf -# endif -#endif - -// Note tha "PutString" hardcodes the same list. This -// is less flexible than it appears. Changing the entries -// or order will break putstring. -TiXmlBase::Entity TiXmlBase::entity[ TiXmlBase::NUM_ENTITY ] = -{ - { "&", 5, '&' }, - { "<", 4, '<' }, - { ">", 4, '>' }, - { """, 6, '\"' }, - { "'", 6, '\'' } -}; - -// Bunch of unicode info at: -// http://www.unicode.org/faq/utf_bom.html -// Including the basic of this table, which determines the #bytes in the -// sequence from the lead byte. 1 placed for invalid sequences -- -// although the result will be junk, pass it through as much as possible. -// Beware of the non-characters in UTF-8: -// ef bb bf (Microsoft "lead bytes") -// ef bf be -// ef bf bf - -const unsigned char TIXML_UTF_LEAD_0 = 0xefU; -const unsigned char TIXML_UTF_LEAD_1 = 0xbbU; -const unsigned char TIXML_UTF_LEAD_2 = 0xbfU; - -const int TiXmlBase::utf8ByteTable[256] = -{ - // 0 1 2 3 4 5 6 7 8 9 a b c d e f - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x00 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x10 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x20 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x30 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x40 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x50 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x70 End of ASCII range - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x80 0x80 to 0xc1 invalid - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x90 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xa0 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xb0 - 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xc0 0xc2 to 0xdf 2 byte - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xd0 - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 0xe0 0xe0 to 0xef 3 byte - 4, 4, 4, 4, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // 0xf0 0xf0 to 0xf4 4 byte, 0xf5 and higher invalid -}; - - -void TiXmlBase::ConvertUTF32ToUTF8( unsigned long input, char* output, int* length ) -{ - const unsigned long BYTE_MASK = 0xBF; - const unsigned long BYTE_MARK = 0x80; - const unsigned long FIRST_BYTE_MARK[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; - - if (input < 0x80) - *length = 1; - else if ( input < 0x800 ) - *length = 2; - else if ( input < 0x10000 ) - *length = 3; - else if ( input < 0x200000 ) - *length = 4; - else - { *length = 0; return; } // This code won't covert this correctly anyway. - - output += *length; - - // Scary scary fall throughs. Reworked as if's for C++17 - if (*length == 4) - { // case 4: - --output; - *output = (char)((input | BYTE_MARK) & BYTE_MASK); - input >>= 6; - } - if (*length >= 3 && *length <= 4) - { // case 3: - --output; - *output = (char)((input | BYTE_MARK) & BYTE_MASK); - input >>= 6; - } - if (*length >= 2 && *length <= 4) - { // case 2: - --output; - *output = (char)((input | BYTE_MARK) & BYTE_MASK); - input >>= 6; - } - if (*length >= 1 && *length <= 4) - { // case 1: - --output; - *output = (char)(input | FIRST_BYTE_MARK[*length]); - } -} - - -/*static*/ int TiXmlBase::IsAlpha( unsigned char anyByte, TiXmlEncoding /*encoding*/ ) -{ - // This will only work for low-ascii, everything else is assumed to be a valid - // letter. I'm not sure this is the best approach, but it is quite tricky trying - // to figure out alhabetical vs. not across encoding. So take a very - // conservative approach. - -// if ( encoding == TIXML_ENCODING_UTF8 ) -// { - if ( anyByte < 127 ) - return isalpha( anyByte ); - else - return 1; // What else to do? The unicode set is huge...get the english ones right. -// } -// else -// { -// return isalpha( anyByte ); -// } -} - - -/*static*/ int TiXmlBase::IsAlphaNum( unsigned char anyByte, TiXmlEncoding /*encoding*/ ) -{ - // This will only work for low-ascii, everything else is assumed to be a valid - // letter. I'm not sure this is the best approach, but it is quite tricky trying - // to figure out alhabetical vs. not across encoding. So take a very - // conservative approach. - -// if ( encoding == TIXML_ENCODING_UTF8 ) -// { - if ( anyByte < 127 ) - return isalnum( anyByte ); - else - return 1; // What else to do? The unicode set is huge...get the english ones right. -// } -// else -// { -// return isalnum( anyByte ); -// } -} - - -class TiXmlParsingData -{ - friend class TiXmlDocument; - public: - void Stamp( const char* now, TiXmlEncoding encoding ); - - const TiXmlCursor& Cursor() const { return cursor; } - - private: - // Only used by the document! - TiXmlParsingData( const char* start, int _tabsize, int row, int col ) - { - assert( start ); - stamp = start; - tabsize = _tabsize; - cursor.row = row; - cursor.col = col; - } - - TiXmlCursor cursor; - const char* stamp; - int tabsize; -}; - - -void TiXmlParsingData::Stamp( const char* now, TiXmlEncoding encoding ) -{ - assert( now ); - - // Do nothing if the tabsize is 0. - if ( tabsize < 1 ) - { - return; - } - - // Get the current row, column. - int row = cursor.row; - int col = cursor.col; - const char* p = stamp; - assert( p ); - - while ( p < now ) - { - // Treat p as unsigned, so we have a happy compiler. - const unsigned char* pU = (const unsigned char*)p; - - // Code contributed by Fletcher Dunn: (modified by lee) - switch (*pU) { - case 0: - // We *should* never get here, but in case we do, don't - // advance past the terminating null character, ever - return; - - case '\r': - // bump down to the next line - ++row; - col = 0; - // Eat the character - ++p; - - // Check for \r\n sequence, and treat this as a single character - if (*p == '\n') { - ++p; - } - break; - - case '\n': - // bump down to the next line - ++row; - col = 0; - - // Eat the character - ++p; - - // Check for \n\r sequence, and treat this as a single - // character. (Yes, this bizarre thing does occur still - // on some arcane platforms...) - if (*p == '\r') { - ++p; - } - break; - - case '\t': - // Eat the character - ++p; - - // Skip to next tab stop - col = (col / tabsize + 1) * tabsize; - break; - - case TIXML_UTF_LEAD_0: - if ( encoding == TIXML_ENCODING_UTF8 ) - { - if ( *(p+1) && *(p+2) ) - { - // In these cases, don't advance the column. These are - // 0-width spaces. - if ( *(pU+1)==TIXML_UTF_LEAD_1 && *(pU+2)==TIXML_UTF_LEAD_2 ) - p += 3; - else if ( *(pU+1)==0xbfU && *(pU+2)==0xbeU ) - p += 3; - else if ( *(pU+1)==0xbfU && *(pU+2)==0xbfU ) - p += 3; - else - { p +=3; ++col; } // A normal character. - } - } - else - { - ++p; - ++col; - } - break; - - default: - if ( encoding == TIXML_ENCODING_UTF8 ) - { - // Eat the 1 to 4 byte utf8 character. - int step = TiXmlBase::utf8ByteTable[*((const unsigned char*)p)]; - if ( step == 0 ) - step = 1; // Error case from bad encoding, but handle gracefully. - p += step; - - // Just advance one column, of course. - ++col; - } - else - { - ++p; - ++col; - } - break; - } - } - cursor.row = row; - cursor.col = col; - assert( cursor.row >= -1 ); - assert( cursor.col >= -1 ); - stamp = p; - assert( stamp ); -} - - -const char* TiXmlBase::SkipWhiteSpace( const char* p, TiXmlEncoding encoding ) -{ - if ( !p || !*p ) - { - return 0; - } - if ( encoding == TIXML_ENCODING_UTF8 ) - { - while ( *p ) - { - const unsigned char* pU = (const unsigned char*)p; - - // Skip the stupid Microsoft UTF-8 Byte order marks - if ( *(pU+0)==TIXML_UTF_LEAD_0 - && *(pU+1)==TIXML_UTF_LEAD_1 - && *(pU+2)==TIXML_UTF_LEAD_2 ) - { - p += 3; - continue; - } - else if(*(pU+0)==TIXML_UTF_LEAD_0 - && *(pU+1)==0xbfU - && *(pU+2)==0xbeU ) - { - p += 3; - continue; - } - else if(*(pU+0)==TIXML_UTF_LEAD_0 - && *(pU+1)==0xbfU - && *(pU+2)==0xbfU ) - { - p += 3; - continue; - } - - if ( IsWhiteSpace( *p ) ) // Still using old rules for white space. - ++p; - else - break; - } - } - else - { - while ( *p && IsWhiteSpace( *p ) ) - ++p; - } - - return p; -} - -#ifdef TIXML_USE_STL -/*static*/ bool TiXmlBase::StreamWhiteSpace( std::istream * in, TIXML_STRING * tag ) -{ - for( ;; ) - { - if ( !in->good() ) return false; - - int c = in->peek(); - // At this scope, we can't get to a document. So fail silently. - if ( !IsWhiteSpace( c ) || c <= 0 ) - return true; - - *tag += (char) in->get(); - } -} - -/*static*/ bool TiXmlBase::StreamTo( std::istream * in, int character, TIXML_STRING * tag ) -{ - //assert( character > 0 && character < 128 ); // else it won't work in utf-8 - while ( in->good() ) - { - int c = in->peek(); - if ( c == character ) - return true; - if ( c <= 0 ) // Silent failure: can't get document at this scope - return false; - - in->get(); - *tag += (char) c; - } - return false; -} -#endif - -// One of TinyXML's more performance demanding functions. Try to keep the memory overhead down. The -// "assign" optimization removes over 10% of the execution time. -// -const char* TiXmlBase::ReadName( const char* p, TIXML_STRING * name, TiXmlEncoding encoding ) -{ - // Oddly, not supported on some comilers, - //name->clear(); - // So use this: - *name = ""; - assert( p ); - - // Names start with letters or underscores. - // Of course, in unicode, tinyxml has no idea what a letter *is*. The - // algorithm is generous. - // - // After that, they can be letters, underscores, numbers, - // hyphens, or colons. (Colons are valid ony for namespaces, - // but tinyxml can't tell namespaces from names.) - if ( p && *p - && ( IsAlpha( (unsigned char) *p, encoding ) || *p == '_' ) ) - { - const char* start = p; - while( p && *p - && ( IsAlphaNum( (unsigned char ) *p, encoding ) - || *p == '_' - || *p == '-' - || *p == '.' - || *p == ':' ) ) - { - //(*name) += *p; // expensive - ++p; - } - if ( p-start > 0 ) { - name->assign( start, p-start ); - } - return p; - } - return 0; -} - -const char* TiXmlBase::GetEntity( const char* p, char* value, int* length, TiXmlEncoding encoding ) -{ - // Presume an entity, and pull it out. - TIXML_STRING ent; - int i; - *length = 0; - - if ( *(p+1) && *(p+1) == '#' && *(p+2) ) - { - unsigned long ucs = 0; - ptrdiff_t delta = 0; - unsigned mult = 1; - - if ( *(p+2) == 'x' ) - { - // Hexadecimal. - if ( !*(p+3) ) return 0; - - const char* q = p+3; - q = strchr( q, ';' ); - - if ( !q || !*q ) return 0; - - delta = q-p; - --q; - - while ( *q != 'x' ) - { - if ( *q >= '0' && *q <= '9' ) - ucs += mult * (*q - '0'); - else if ( *q >= 'a' && *q <= 'f' ) - ucs += mult * (*q - 'a' + 10); - else if ( *q >= 'A' && *q <= 'F' ) - ucs += mult * (*q - 'A' + 10 ); - else - return 0; - mult *= 16; - --q; - } - } - else - { - // Decimal. - if ( !*(p+2) ) return 0; - - const char* q = p+2; - q = strchr( q, ';' ); - - if ( !q || !*q ) return 0; - - delta = q-p; - --q; - - while ( *q != '#' ) - { - if ( *q >= '0' && *q <= '9' ) - ucs += mult * (*q - '0'); - else - return 0; - mult *= 10; - --q; - } - } - if ( encoding == TIXML_ENCODING_UTF8 ) - { - // convert the UCS to UTF-8 - ConvertUTF32ToUTF8( ucs, value, length ); - } - else - { - *value = (char)ucs; - *length = 1; - } - return p + delta + 1; - } - - // Now try to match it. - for( i=0; iappend( cArr, len ); - } - } - else - { - bool whitespace = false; - - // Remove leading white space: - p = SkipWhiteSpace( p, encoding ); - while ( p && *p - && !StringEqual( p, endTag, caseInsensitive, encoding ) ) - { - if ( *p == '\r' || *p == '\n' ) - { - whitespace = true; - ++p; - } - else if ( IsWhiteSpace( *p ) ) - { - whitespace = true; - ++p; - } - else - { - // If we've found whitespace, add it before the - // new character. Any whitespace just becomes a space. - if ( whitespace ) - { - (*text) += ' '; - whitespace = false; - } - int len; - char cArr[4] = { 0, 0, 0, 0 }; - p = GetChar( p, cArr, &len, encoding ); - if ( len == 1 ) - (*text) += cArr[0]; // more efficient - else - text->append( cArr, len ); - } - } - } - if ( p && *p ) - p += strlen( endTag ); - return ( p && *p ) ? p : 0; -} - -#ifdef TIXML_USE_STL - -void TiXmlDocument::StreamIn( std::istream * in, TIXML_STRING * tag ) -{ - // The basic issue with a document is that we don't know what we're - // streaming. Read something presumed to be a tag (and hope), then - // identify it, and call the appropriate stream method on the tag. - // - // This "pre-streaming" will never read the closing ">" so the - // sub-tag can orient itself. - - if ( !StreamTo( in, '<', tag ) ) - { - SetError( TIXML_ERROR_PARSING_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN ); - return; - } - - while ( in->good() ) - { - int tagIndex = (int) tag->length(); - while ( in->good() && in->peek() != '>' ) - { - int c = in->get(); - if ( c <= 0 ) - { - SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN ); - break; - } - (*tag) += (char) c; - } - - if ( in->good() ) - { - // We now have something we presume to be a node of - // some sort. Identify it, and call the node to - // continue streaming. - TiXmlNode* node = Identify( tag->c_str() + tagIndex, TIXML_DEFAULT_ENCODING ); - - if ( node ) - { - node->StreamIn( in, tag ); - bool isElement = node->ToElement() != 0; - delete node; - node = 0; - - // If this is the root element, we're done. Parsing will be - // done by the >> operator. - if ( isElement ) - { - return; - } - } - else - { - SetError( TIXML_ERROR, 0, 0, TIXML_ENCODING_UNKNOWN ); - return; - } - } - } - // We should have returned sooner. - SetError( TIXML_ERROR, 0, 0, TIXML_ENCODING_UNKNOWN ); -} - -#endif - -const char* TiXmlDocument::Parse( const char* p, TiXmlParsingData* prevData, TiXmlEncoding encoding ) -{ - ClearError(); - - // Parse away, at the document level. Since a document - // contains nothing but other tags, most of what happens - // here is skipping white space. - if ( !p || !*p ) - { - SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN ); - return 0; - } - - // Note that, for a document, this needs to come - // before the while space skip, so that parsing - // starts from the pointer we are given. - location.Clear(); - if ( prevData ) - { - location.row = prevData->cursor.row; - location.col = prevData->cursor.col; - } - else - { - location.row = 0; - location.col = 0; - } - TiXmlParsingData data( p, TabSize(), location.row, location.col ); - location = data.Cursor(); - - if ( encoding == TIXML_ENCODING_UNKNOWN ) - { - // Check for the Microsoft UTF-8 lead bytes. - const unsigned char* pU = (const unsigned char*)p; - if ( *(pU+0) && *(pU+0) == TIXML_UTF_LEAD_0 - && *(pU+1) && *(pU+1) == TIXML_UTF_LEAD_1 - && *(pU+2) && *(pU+2) == TIXML_UTF_LEAD_2 ) - { - encoding = TIXML_ENCODING_UTF8; - useMicrosoftBOM = true; - } - } - - p = SkipWhiteSpace( p, encoding ); - if ( !p ) - { - SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN ); - return 0; - } - - while ( p && *p ) - { - TiXmlNode* node = Identify( p, encoding ); - if ( node ) - { - p = node->Parse( p, &data, encoding ); - LinkEndChild( node ); - } - else - { - break; - } - - // Did we get encoding info? - if ( encoding == TIXML_ENCODING_UNKNOWN - && node->ToDeclaration() ) - { - TiXmlDeclaration* dec = node->ToDeclaration(); - const char* enc = dec->Encoding(); - assert( enc ); - - if ( *enc == 0 ) - encoding = TIXML_ENCODING_UTF8; - else if ( StringEqual( enc, "UTF-8", true, TIXML_ENCODING_UNKNOWN ) ) - encoding = TIXML_ENCODING_UTF8; - else if ( StringEqual( enc, "UTF8", true, TIXML_ENCODING_UNKNOWN ) ) - encoding = TIXML_ENCODING_UTF8; // incorrect, but be nice - else - encoding = TIXML_ENCODING_LEGACY; - } - - p = SkipWhiteSpace( p, encoding ); - } - - // Was this empty? - if ( !firstChild ) { - SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, encoding ); - return 0; - } - - // All is well. - return p; -} - -void TiXmlDocument::SetError( int err, const char* pError, TiXmlParsingData* data, TiXmlEncoding encoding ) -{ - // The first error in a chain is more accurate - don't set again! - if ( error ) - return; - - assert( err > 0 && err < TIXML_ERROR_STRING_COUNT ); - error = true; - errorId = err; - errorDesc = errorString[ errorId ]; - - errorLocation.Clear(); - if ( pError && data ) - { - data->Stamp( pError, encoding ); - errorLocation = data->Cursor(); - } -} - - -TiXmlNode* TiXmlNode::Identify( const char* p, TiXmlEncoding encoding ) -{ - TiXmlNode* returnNode = 0; - - p = SkipWhiteSpace( p, encoding ); - if( !p || !*p || *p != '<' ) - { - return 0; - } - - p = SkipWhiteSpace( p, encoding ); - - if ( !p || !*p ) - { - return 0; - } - - // What is this thing? - // - Elements start with a letter or underscore, but xml is reserved. - // - Comments: "; - - if ( !StringEqual( p, startTag, false, encoding ) ) - { - if ( document ) - document->SetError( TIXML_ERROR_PARSING_COMMENT, p, data, encoding ); - return 0; - } - p += strlen( startTag ); - - // [ 1475201 ] TinyXML parses entities in comments - // Oops - ReadText doesn't work, because we don't want to parse the entities. - // p = ReadText( p, &value, false, endTag, false, encoding ); - // - // from the XML spec: - /* - [Definition: Comments may appear anywhere in a document outside other markup; in addition, - they may appear within the document type declaration at places allowed by the grammar. - They are not part of the document's character data; an XML processor MAY, but need not, - make it possible for an application to retrieve the text of comments. For compatibility, - the string "--" (double-hyphen) MUST NOT occur within comments.] Parameter entity - references MUST NOT be recognized within comments. - - An example of a comment: - - - */ - - value = ""; - // Keep all the white space. - while ( p && *p && !StringEqual( p, endTag, false, encoding ) ) - { - value.append( p, 1 ); - ++p; - } - if ( p && *p ) - p += strlen( endTag ); - - return p; -} - - -const char* TiXmlAttribute::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ) -{ - p = SkipWhiteSpace( p, encoding ); - if ( !p || !*p ) return 0; - - if ( data ) - { - data->Stamp( p, encoding ); - location = data->Cursor(); - } - // Read the name, the '=' and the value. - const char* pErr = p; - p = ReadName( p, &name, encoding ); - if ( !p || !*p ) - { - if ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, pErr, data, encoding ); - return 0; - } - p = SkipWhiteSpace( p, encoding ); - if ( !p || !*p || *p != '=' ) - { - if ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, p, data, encoding ); - return 0; - } - - ++p; // skip '=' - p = SkipWhiteSpace( p, encoding ); - if ( !p || !*p ) - { - if ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, p, data, encoding ); - return 0; - } - - const char* end; - const char SINGLE_QUOTE = '\''; - const char DOUBLE_QUOTE = '\"'; - - if ( *p == SINGLE_QUOTE ) - { - ++p; - end = "\'"; // single quote in string - p = ReadText( p, &value, false, end, false, encoding ); - } - else if ( *p == DOUBLE_QUOTE ) - { - ++p; - end = "\""; // double quote in string - p = ReadText( p, &value, false, end, false, encoding ); - } - else - { - // All attribute values should be in single or double quotes. - // But this is such a common error that the parser will try - // its best, even without them. - value = ""; - while ( p && *p // existence - && !IsWhiteSpace( *p ) // whitespace - && *p != '/' && *p != '>' ) // tag end - { - if ( *p == SINGLE_QUOTE || *p == DOUBLE_QUOTE ) { - // [ 1451649 ] Attribute values with trailing quotes not handled correctly - // We did not have an opening quote but seem to have a - // closing one. Give up and throw an error. - if ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, p, data, encoding ); - return 0; - } - value += *p; - ++p; - } - } - return p; -} - -#ifdef TIXML_USE_STL -void TiXmlText::StreamIn( std::istream * in, TIXML_STRING * tag ) -{ - while ( in->good() ) - { - int c = in->peek(); - if ( !cdata && (c == '<' ) ) - { - return; - } - if ( c <= 0 ) - { - TiXmlDocument* document = GetDocument(); - if ( document ) - document->SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN ); - return; - } - - (*tag) += (char) c; - in->get(); // "commits" the peek made above - - if ( cdata && c == '>' && tag->size() >= 3 ) { - size_t len = tag->size(); - if ( (*tag)[len-2] == ']' && (*tag)[len-3] == ']' ) { - // terminator of cdata. - return; - } - } - } -} -#endif - -const char* TiXmlText::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ) -{ - value = ""; - TiXmlDocument* document = GetDocument(); - - if ( data ) - { - data->Stamp( p, encoding ); - location = data->Cursor(); - } - - const char* const startTag = ""; - - if ( cdata || StringEqual( p, startTag, false, encoding ) ) - { - cdata = true; - - if ( !StringEqual( p, startTag, false, encoding ) ) - { - if ( document ) - document->SetError( TIXML_ERROR_PARSING_CDATA, p, data, encoding ); - return 0; - } - p += strlen( startTag ); - - // Keep all the white space, ignore the encoding, etc. - while ( p && *p - && !StringEqual( p, endTag, false, encoding ) - ) - { - value += *p; - ++p; - } - - TIXML_STRING dummy; - p = ReadText( p, &dummy, false, endTag, false, encoding ); - return p; - } - else - { - bool ignoreWhite = true; - - const char* end = "<"; - p = ReadText( p, &value, ignoreWhite, end, false, encoding ); - if ( p && *p ) - return p-1; // don't truncate the '<' - return 0; - } -} - -#ifdef TIXML_USE_STL -void TiXmlDeclaration::StreamIn( std::istream * in, TIXML_STRING * tag ) -{ - while ( in->good() ) - { - int c = in->get(); - if ( c <= 0 ) - { - TiXmlDocument* document = GetDocument(); - if ( document ) - document->SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN ); - return; - } - (*tag) += (char) c; - - if ( c == '>' ) - { - // All is well. - return; - } - } -} -#endif - -const char* TiXmlDeclaration::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding _encoding ) -{ - p = SkipWhiteSpace( p, _encoding ); - // Find the beginning, find the end, and look for - // the stuff in-between. - TiXmlDocument* document = GetDocument(); - if ( !p || !*p || !StringEqual( p, "SetError( TIXML_ERROR_PARSING_DECLARATION, 0, 0, _encoding ); - return 0; - } - if ( data ) - { - data->Stamp( p, _encoding ); - location = data->Cursor(); - } - p += 5; - - version = ""; - encoding = ""; - standalone = ""; - - while ( p && *p ) - { - if ( *p == '>' ) - { - ++p; - return p; - } - - p = SkipWhiteSpace( p, _encoding ); - if ( StringEqual( p, "version", true, _encoding ) ) - { - TiXmlAttribute attrib; - p = attrib.Parse( p, data, _encoding ); - version = attrib.Value(); - } - else if ( StringEqual( p, "encoding", true, _encoding ) ) - { - TiXmlAttribute attrib; - p = attrib.Parse( p, data, _encoding ); - encoding = attrib.Value(); - } - else if ( StringEqual( p, "standalone", true, _encoding ) ) - { - TiXmlAttribute attrib; - p = attrib.Parse( p, data, _encoding ); - standalone = attrib.Value(); - } - else - { - // Read over whatever it is. - while( p && *p && *p != '>' && !IsWhiteSpace( *p ) ) - ++p; - } - } - return 0; -} - -bool TiXmlText::Blank() const -{ - for ( unsigned i=0; i. */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include - -#include "Xrd/XrdLink.hh" -#include "XrdNet/XrdNetSocket.hh" -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysPlatform.hh" -#include "XrdSys/XrdSysPthread.hh" -#include "XrdOuc/XrdOucTList.hh" -#include "XrdXrootd/XrdXrootdAdmin.hh" -#include "XrdXrootd/XrdXrootdJob.hh" -#include "XrdXrootd/XrdXrootdProtocol.hh" -#include "XrdXrootd/XrdXrootdTrace.hh" - -/******************************************************************************/ -/* G l o b a l s & S t a t i c s */ -/******************************************************************************/ - -extern XrdOucTrace *XrdXrootdTrace; - - XrdSysError *XrdXrootdAdmin::eDest; - - XrdXrootdAdmin::JobTable *XrdXrootdAdmin::JobList = 0; - -/******************************************************************************/ -/* E x t e r n a l T h r e a d I n t e r f a c e s */ -/******************************************************************************/ - -void *XrdXrootdInitAdmin(void *carg) - {XrdXrootdAdmin Admin; - return Admin.Start((XrdNetSocket *)carg); - } - -void *XrdXrootdLoginAdmin(void *carg) - {XrdXrootdAdmin *Admin = new XrdXrootdAdmin(); - Admin->Login(*(int *)carg); - delete Admin; - return (void *)0; - } - -/******************************************************************************/ -/* a d d J o b */ -/******************************************************************************/ - -void XrdXrootdAdmin::addJob(const char *jname, XrdXrootdJob *jp) -{ - JobTable *jTabp = new JobTable(); - - jTabp->Jname = strdup(jname); - jTabp->Job = jp; - jTabp->Next = JobList; - JobList = jTabp; -} - -/******************************************************************************/ -/* I n i t */ -/******************************************************************************/ - -int XrdXrootdAdmin::Init(XrdSysError *erp, XrdNetSocket *asock) -{ - const char *epname = "Init"; - pthread_t tid; - - eDest = erp; - if (XrdSysThread::Run(&tid, XrdXrootdInitAdmin, (void *)asock, - 0, "Admin traffic")) - {eDest->Emsg(epname, errno, "start admin"); - return 0; - } - return 1; -} - -/******************************************************************************/ -/* L o g i n */ -/******************************************************************************/ - -void XrdXrootdAdmin::Login(int socknum) -{ - const char *epname = "Admin"; - char *tp; - -// Attach the socket FD to a stream -// - Stream.SetEroute(eDest); - Stream.AttachIO(socknum, socknum); - -// Get the first request -// - if (!Stream.GetLine()) - {eDest->Emsg(epname, "No admin login specified"); - return; - } - -// The first request better be: login -// - if (getreqID() - || !(tp = Stream.GetToken()) - || strcmp("login", tp) - || do_Login()) - {eDest->Emsg(epname, "Invalid admin login sequence"); - return; - } - -// Document the login and go process the stream -// - eDest->Emsg(epname, "Admin", TraceID, "logged in"); - Xeq(); -} - -/******************************************************************************/ -/* S t a r t */ -/******************************************************************************/ - -void *XrdXrootdAdmin::Start(XrdNetSocket *AdminSock) -{ - const char *epname = "Start"; - int InSock; - pthread_t tid; - -// Accept connections in an endless loop -// - while(1) if ((InSock = AdminSock->Accept()) >= 0) - {if (XrdSysThread::Run(&tid,XrdXrootdLoginAdmin,(void *)&InSock)) - {eDest->Emsg(epname, errno, "start admin"); - close(InSock); - } - } else eDest->Emsg(epname, errno, "accept connection"); - return (void *)0; -} - -/******************************************************************************/ -/* P r i v a t e M e t h o d s */ -/******************************************************************************/ -/******************************************************************************/ -/* d o _ A b o r t */ -/******************************************************************************/ - -int XrdXrootdAdmin::do_Abort() -{ - char *msg; - int mlen, rc; - -// Handle: abort [msg] -// - if ((rc = getTarget("abort", &msg))) return 0; - -// Get optional message -// - msg = getMsg(msg, mlen); - -// Send off the unsolicited response -// - if (msg) return sendResp("abort", kXR_asyncab, msg, mlen); - else return sendResp("abort", kXR_asyncab); -} - -/******************************************************************************/ -/* d o _ C j */ -/******************************************************************************/ - -int XrdXrootdAdmin::do_Cj() -{ - const char *fmt1 = "0"; - const char *fmt2 = "%d\n"; - char *tp, buff[1024]; - XrdXrootdJob *jobp; - JobTable *jTabp; - int i, rc; - -// The next token needs to be job type -// - if (!(tp = Stream.GetToken())) - {sendErr(8, "cj", "job type not specified."); - return -1; - } - -// Run through the list of valid job types -// - jTabp = JobList; - while(jTabp && strcmp(tp, jTabp->Jname)) jTabp = jTabp->Next; - -// See if we have a real job list here -// - if (jTabp) jobp = jTabp->Job; - else if (!strcmp(tp, "*")) jobp = 0; - else {sendErr(8, "cj", "invalid job type specified."); - return -1; - } - -// Get optional key -// - tp = Stream.GetToken(); - -// Send the header of the response -// - i = sprintf(buff, fmt1, reqID); - if (Stream.Put(buff, i)) return -1; - -// Cancel the jobs -// - if (jobp) rc = jobp->Cancel(tp); - else {jTabp = JobList; rc = 0; - while(jTabp) {rc += jTabp->Job->Cancel(tp); jTabp = jTabp->Next;} - } - -// Now print the end-framing -// - i = sprintf(buff, fmt2, rc); - return Stream.Put(buff, i); -} - -/******************************************************************************/ -/* d o _ C o n t */ -/******************************************************************************/ - -int XrdXrootdAdmin::do_Cont() -{ - int rc; - -// Handle: cont -// - if ((rc = getTarget("cont"))) return 0; - -// Send off the unsolicited response -// - return sendResp("cont", kXR_asyncgo); -} - -/******************************************************************************/ -/* d o _ D i s c */ -/******************************************************************************/ - -int XrdXrootdAdmin::do_Disc() -{ - kXR_int32 msg[2]; - char *tp; - int rc; - -// Handle: disc -// - if ((rc = getTarget("disc"))) return 0; - -// Make sure times are specified -// - if (!(tp = Stream.GetToken()) || !(msg[0] = strtol(tp, 0, 10))) - return sendErr(8, "disc", " reconnect interval missing or invalid."); - if (!(tp = Stream.GetToken()) || !(msg[1] = strtol(tp, 0, 10))) - return sendErr(8, "disc", "reconnect timeout missing or invalid."); - -// Send off the unsolicited response -// - msg[0] = htonl(msg[0]); msg[1] = htonl(msg[1]); - return sendResp("disc", kXR_asyncdi, (const char *)msg, sizeof(msg)); -} - -/******************************************************************************/ -/* d o _ L o g i n */ -/******************************************************************************/ - -int XrdXrootdAdmin::do_Login() -{ - const char *fmt="0" kXR_PROTOCOLVSTRING - "\n"; - char *tp, buff[1024]; - int blen; - -// Process: login -// - if (!(tp = Stream.GetToken())) - {eDest->Emsg("do_Login", "login name not specified"); - return 0; - } else strlcpy(TraceID, tp, sizeof(TraceID)); - -// Provide good response -// - blen = snprintf(buff, sizeof(buff)-1, fmt, reqID); - buff[sizeof(buff)-1] = '\0'; - return Stream.Put(buff, blen); -} - -/******************************************************************************/ -/* d o _ L s c */ -/******************************************************************************/ - -int XrdXrootdAdmin::do_Lsc() -{ - const char *fmt1 = "0"; - const char *fmt2 = "\n"; - static int fmt2len = strlen(fmt2); - char buff[1024]; - const char *mdat[3] = {buff, " ", 0}; - int mlen[3] = {0, 1, 0}; - int i, rc, curr = -1; - -// Handle: list -// - if ((rc = getTarget("lsc"))) return 0; - -// Send the header of the response -// - i = sprintf(buff, fmt1, reqID); - if (Stream.Put(buff, i)) return -1; - -// Return back matching client list -// - while((mlen[0] = XrdLink::getName(curr, buff, sizeof(buff), &Target))) - if (Stream.Put(mdat, mlen)) return -1; - return Stream.Put(fmt2, fmt2len); -} - -/******************************************************************************/ -/* d o _ L s d */ -/******************************************************************************/ - -int XrdXrootdAdmin::do_Lsd() -{ - const char *fmt1 = "0"; - const char *fmt2 = ""; - const char *fmt2a= "%d

%lld%d

" - "%lld%d%lld%d" - "%d%d
"; - const char *fmt3 = ""; - const char *fmt3e= ""; - const char *fmt4 = "
\n"; - static int fmt3elen= strlen(fmt3e); - static int fmt4len = strlen(fmt4); - char ctyp, monit[3], *mm, cname[1024], buff[100]; - char aprot[XrdSecPROTOIDSIZE+2], abuff[32], iobuff[256]; - const char *mdat[24]= {buff, cname, iobuff}; - int mlen[24]= {0}; - long long conn, inBytes, outBytes; - int i, rc, cver, inuse, stalls, tardies, curr = -1; - XrdLink *lp; - XrdProtocol *xp; - XrdXrootdProtocol *pp; - -// Handle: list -// - if ((rc = getTarget("lsd"))) return 0; - -// Send the header of the response -// - i = sprintf(buff, fmt1, reqID); - if (Stream.Put(buff, i)) return -1; - -// Return back matching client list -// - while((lp = XrdLink::Find(curr, &Target))) - if ((xp = lp->getProtocol()) - && (pp = dynamic_cast(xp))) - {cver = int(pp->CapVer); - ctyp = (pp->Status & XRD_ADMINUSER ? 'a' : 'u'); - conn = static_cast(lp->timeCon()); - mm = monit; - if (pp->Monitor.Files()) *mm++ = 'f'; - if (pp->Monitor.InOut()) *mm++ = 'i'; - *mm = '\0'; - inuse = lp->getIOStats(inBytes, outBytes, stalls, tardies); - mlen[0] = sprintf(buff, fmt2, ctyp, conn, cver, monit); - mlen[1] = lp->Client(cname, sizeof(cname)); - mlen[2] = sprintf(iobuff, fmt2a,inuse-1,pp->numFiles,pp->totReadP, - (pp->cumReadP + pp->numReadP), - inBytes, (pp->cumWrites+ pp->numWrites + - pp->cumWritV + pp->numWritV), - outBytes,(pp->cumReads + pp->numReads + - pp->cumReadV + pp->numReadV), - stalls, tardies); - i = 3; - if ((pp->Client) && pp->Client != &(pp->Entity)) - {strncpy(aprot, pp->Client->prot, XrdSecPROTOIDSIZE); - aprot[XrdSecPROTOIDSIZE] = '\0'; - mdat[i] = abuff; - mlen[i++]= sprintf(abuff, fmt3, aprot); - i = 1; - if (pp->Client->name && (mlen[i] = strlen(pp->Client->name))) - mdat[i++] = pp->Client->name; - mdat[i] = "
"; mlen[i++] = 7; - if (pp->Client->host && (mlen[i] = strlen(pp->Client->host))) - mdat[i++] = pp->Client->host; - mdat[i] = ""; mlen[i++] = 7; - if (pp->Client->vorg && (mlen[i] = strlen(pp->Client->vorg))) - mdat[i++] = pp->Client->vorg; - mdat[i] = ""; mlen[i++] = 7; - if (pp->Client->role && (mlen[i] = strlen(pp->Client->role))) - mdat[i++] = pp->Client->role; - mdat[i] = fmt3e; mlen[i++] = fmt3elen; - } - mdat[i] = ""; mlen[i++] = 4; - mdat[i] = 0; mlen[i] = 0; - if (Stream.Put(mdat, mlen)) {lp->setRef(-1); return -1;} - } - return Stream.Put(fmt4, fmt4len); -} - -/******************************************************************************/ -/* d o _ L s j */ -/******************************************************************************/ - -int XrdXrootdAdmin::do_Lsj() -{ - const char *fmt1 = "0"; - const char *fmt2 = "\n"; - static int fmt2len = strlen(fmt2); - char *tp, buff[1024]; - XrdXrootdJob *jobp; - JobTable *jTabp; - int i, rc = 0; - -// The next token needs to be job type -// - if (!(tp = Stream.GetToken())) - {sendErr(8, "lsj", "job type not specified."); - return -1; - } - -// Run through the list of valid job types -// - jTabp = JobList; - while(jTabp && strcmp(tp, jTabp->Jname)) jTabp = jTabp->Next; - -// See if we have a real job list here -// - if (jTabp) jobp = jTabp->Job; - else if (!strcmp(tp, "*")) jobp = 0; - else {sendErr(8, "lsj", "invalid job type specified."); - return -1; - } - -// Send the header of the response -// - i = sprintf(buff, fmt1, reqID); - if (Stream.Put(buff, i)) return -1; - -// List the jobs -// - if (jobp) rc = do_Lsj_Xeq(jobp); - else {jTabp = JobList; - while(jTabp && !(rc = do_Lsj_Xeq(jTabp->Job))) jTabp = jTabp->Next; - } - -// Now print the end-framing -// - return (rc ? rc : Stream.Put(fmt2, fmt2len)); -} - -/******************************************************************************/ -/* d o _ L s j _ X e q */ -/******************************************************************************/ - -int XrdXrootdAdmin::do_Lsj_Xeq(XrdXrootdJob *jp) -{ - XrdOucTList *tp, *tpprev; - int rc = 0; - - if ((tp = jp->List())) - while(tp && !(rc = Stream.Put(tp->text, tp->val))) - {tpprev = tp; tp = tp->next; delete tpprev;} - - while(tp) {tpprev = tp; tp = tp->next; delete tpprev;} - - return rc; -} - -/******************************************************************************/ -/* d o _ M s g */ -/******************************************************************************/ - -int XrdXrootdAdmin::do_Msg() -{ - char *msg; - int rc, mlen; - -// Handle: msg [msg] -// - if ((rc = getTarget("msg", &msg))) return 0; - -// Get optional message -// - msg = getMsg(msg, mlen); -// Send off the unsolicited response -// - if (msg) return sendResp("msg", kXR_asyncms, msg, mlen); - else return sendResp("msg", kXR_asyncms); -} - -/******************************************************************************/ -/* d o _ P a u s e */ -/******************************************************************************/ - -int XrdXrootdAdmin::do_Pause() -{ - kXR_int32 msg; - char *tp; - int rc; - -// Handle: pause -// - if ((rc = getTarget("pause"))) return 0; - -// Make sure time is specified -// - if (!(tp = Stream.GetToken()) || !(msg = strtol(tp, 0, 10))) - return sendErr(8, "pause", "time missing or invalid."); - -// Send off the unsolicited response -// - msg = htonl(msg); - return sendResp("pause", kXR_asyncwt, (const char *)&msg, sizeof(msg)); -} - -/******************************************************************************/ -/* d o _ R e d */ -/******************************************************************************/ - -int XrdXrootdAdmin::do_Red() -{ - struct msg {kXR_int32 port; char buff[8192];} myMsg; - int rc, hlen, tlen, bsz; - char *tp, *pn, *qq; - -// Handle: redirect :[?token] -// - if ((rc = getTarget("redirect", 0))) return 0; - -// Get the redirect target -// - if (!(tp = Stream.GetToken()) || *tp == ':') - return sendErr(8, "redirect", "destination host not specified."); - -// Get the port number -// - if (!(pn = index(tp, ':')) || !(myMsg.port = strtol(pn+1, &qq, 10))) - return sendErr(8, "redirect", "port missing or invalid."); - myMsg.port = htonl(myMsg.port); - -// Copy out host -// - *pn = '\0'; - hlen = strlcpy(myMsg.buff,tp,sizeof(myMsg.buff)); - if (static_cast(hlen) >= sizeof(myMsg.buff)) - return sendErr(8, "redirect", "destination host too long."); - -// Copy out the token -// - if (qq && *qq == '?') - {bsz = sizeof(myMsg.buff) - hlen; - if ((tlen = strlcpy(myMsg.buff+hlen,qq,bsz)) >= bsz) - return sendErr(8, "redirect", "token too long."); - } else tlen = 0; - -// Send off the unsolicited response -// - return sendResp("redirect", kXR_asyncrd, (const char *)&myMsg, hlen+tlen+4); -} - -/******************************************************************************/ -/* g e t M s g */ -/******************************************************************************/ - -char *XrdXrootdAdmin::getMsg(char *msg, int &mlen) -{ - if (msg) while(*msg == ' ') msg++; - if (msg && *msg) mlen = strlen(msg)+1; - else {msg = 0; mlen = 0;} - return msg; -} - -/******************************************************************************/ -/* g e t r e q I D */ -/******************************************************************************/ - -int XrdXrootdAdmin::getreqID() -{ - char *tp; - - if (!(tp = Stream.GetToken())) - {reqID[0] = '?'; reqID[1] = '\0'; - return sendErr(4, "request", "id not specified."); - } - - if (strlen(tp) >= sizeof(reqID)) - {reqID[0] = '?'; reqID[1] = '\0'; - return sendErr(4, "request", "id too long."); - } - - strcpy(reqID, tp); - return 0; -} - -/******************************************************************************/ -/* g e t T a r g e t */ -/******************************************************************************/ -/* Returns 0 if a target was found, otherwise -1 */ - -int XrdXrootdAdmin::getTarget(const char *act, char **rest) -{ - char *tp; - -// Get the target -// - if (!(tp = Stream.GetToken(rest))) - {sendErr(8, act, "target not specified."); - return -1; - } - Target.Set(tp); - - return 0; -} - -/******************************************************************************/ -/* s e n d E r r */ -/******************************************************************************/ - -int XrdXrootdAdmin::sendErr(int rc, const char *act, const char *msg) -{ - const char *fmt = "%d%s %s\n"; - char buff[1024]; - int blen; - - blen = snprintf(buff, sizeof(buff)-1, fmt, reqID, rc, act, msg); - buff[sizeof(buff)-1] = '\0'; - - return Stream.Put(buff, blen); -} - -/******************************************************************************/ -/* s e n d O K */ -/******************************************************************************/ - -int XrdXrootdAdmin::sendOK(int sent) -{ - const char *fmt = "0%d\n"; - char buff[1024]; - int blen; - - blen = snprintf(buff, sizeof(buff)-1, fmt, reqID, sent); - buff[sizeof(buff)-1] = '\0'; - - return Stream.Put(buff, blen); -} - -/******************************************************************************/ -/* s e n d R e s p */ -/******************************************************************************/ - -int XrdXrootdAdmin::sendResp(const char *act, XActionCode anum) -{ - XrdLink *lp; - const kXR_int32 net4 = htonl(4); - int numsent = 0, curr = -1; - -// Complete the response header -// - usResp.act = htonl(anum); - usResp.len = net4; - -// Send off the messages -// - while((lp = XrdLink::Find(curr, &Target))) - {TRACE(RSP, "sending " <ID <<' ' <Send((const char *)&usResp, sizeof(usResp))>0) numsent++; - } - -// Now send the response to the admin guy -// - return sendOK(numsent); -} - -/******************************************************************************/ - -int XrdXrootdAdmin::sendResp(const char *act, XActionCode anum, - const char *msg, int msgl) -{ - struct iovec iov[2]; - XrdLink *lp; - int numsent = 0, curr = -1, bytes = sizeof(usResp)+msgl; - -// Complete the response header -// - usResp.act = htonl(anum); - usResp.len = htonl(msgl+4); - -// Construct message vector -// - iov[0].iov_base = (caddr_t)&usResp; - iov[0].iov_len = sizeof(usResp); - iov[1].iov_base = (caddr_t)msg; - iov[1].iov_len = msgl; - -// Send off the messages -// - while((lp = XrdLink::Find(curr, &Target))) - {TRACE(RSP, "sending " <ID <<' ' <Send(iov, 2, bytes)>0) numsent++; - } - -// Now send the response to the admin guy -// - return sendOK(numsent); -} - -/******************************************************************************/ -/* X e q */ -/******************************************************************************/ - -void XrdXrootdAdmin::Xeq() -{ - const char *epname = "Xeq"; - int rc; - char *request, *tp; - -// Start receiving requests on this stream -// Format: -// - rc = 0; - while((request = Stream.GetLine()) && !rc) - {TRACE(DEBUG, "received admin request: '" <Emsg(epname, "invalid admin request,", tp); - rc = sendErr(4, tp, "is an invalid request."); - } - } - } - -// The socket disconnected -// - eDest->Emsg("Admin", "Admin", TraceID, "logged out"); - return; -} diff --git a/src/XrdXrootd/XrdXrootdAdmin.hh b/src/XrdXrootd/XrdXrootdAdmin.hh deleted file mode 100644 index 25b04c14014..00000000000 --- a/src/XrdXrootd/XrdXrootdAdmin.hh +++ /dev/null @@ -1,102 +0,0 @@ -#ifndef __XROOTDADMIN__ -#define __XROOTDADMIN__ -/******************************************************************************/ -/* */ -/* X r d X r o o t d A d m i n . h h */ -/* */ -/* (c) 2005 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -#include "Xrd/XrdLinkMatch.hh" -#include "XrdOuc/XrdOucStream.hh" -#include "XProtocol/XProtocol.hh" - -class XrdNetSocket; -class XrdXrootdJob; - -class XrdXrootdAdmin -{ -public: - -static void addJob(const char *jname, XrdXrootdJob *jp); - -static int Init(XrdSysError *erp, XrdNetSocket *asock); - - void Login(int socknum); - - void *Start(XrdNetSocket *AdminSock); - - XrdXrootdAdmin() {} - ~XrdXrootdAdmin() {} - -private: -int do_Abort(); -int do_Cj(); -int do_Cont(); -int do_Disc(); -int do_Login(); -int do_Lsc(); -int do_Lsj(); -int do_Lsj_Xeq(XrdXrootdJob *jp); -int do_Lsd(); -int do_Msg(); -int do_Pause(); -int do_Red(); -char *getMsg(char *msg, int &mlen); -int getreqID(); -int getTarget(const char *act, char **rest=0); -int sendErr(int rc, const char *act, const char *msg); -int sendOK(int sent); -int sendResp(const char *act, XActionCode anum); -int sendResp(const char *act, XActionCode anum, - const char *msg, int mlen); -void Xeq(); - -struct JobTable {struct JobTable *Next; - char *Jname; - XrdXrootdJob *Job; - }; - -static JobTable *JobList; - -static XrdSysError *eDest; - XrdOucStream Stream; - XrdLinkMatch Target; - -struct usr {kXR_unt16 pad; - kXR_unt16 atn; - kXR_int32 len; - kXR_int32 act; - usr() {pad = 0; atn = htons(kXR_attn);} - ~usr() {} - } usResp; - char TraceID[24]; - char reqID[16]; -}; -#endif diff --git a/src/XrdXrootd/XrdXrootdAio.cc b/src/XrdXrootd/XrdXrootdAio.cc deleted file mode 100644 index 73a4c942845..00000000000 --- a/src/XrdXrootd/XrdXrootdAio.cc +++ /dev/null @@ -1,665 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d X r o o t d A i o . c c */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -#include "Xrd/XrdBuffer.hh" -#include "Xrd/XrdLink.hh" -#include "XProtocol/XProtocol.hh" -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysPthread.hh" -#include "XrdSfs/XrdSfsInterface.hh" -#include "XrdXrootd/XrdXrootdAio.hh" -#include "XrdXrootd/XrdXrootdFile.hh" -#include "XrdXrootd/XrdXrootdProtocol.hh" -#include "XrdXrootd/XrdXrootdStats.hh" -#include "XrdXrootd/XrdXrootdTrace.hh" - -/******************************************************************************/ -/* S t a t i c O b j e c t s */ -/******************************************************************************/ - -XrdBuffManager *XrdXrootdAio::BPool; -XrdScheduler *XrdXrootdAio::Sched; -XrdXrootdStats *XrdXrootdAio::SI; - -XrdSysMutex XrdXrootdAio::fqMutex; -XrdXrootdAio *XrdXrootdAio::fqFirst = 0; -const char *XrdXrootdAio::TraceID = "Aio"; - -int XrdXrootdAio::maxAio; - -XrdSysError *XrdXrootdAioReq::eDest; -XrdSysMutex XrdXrootdAioReq::rqMutex; -XrdXrootdAioReq *XrdXrootdAioReq::rqFirst = 0; -const char *XrdXrootdAioReq::TraceID = "AioReq"; - -int XrdXrootdAioReq::QuantumMin; -int XrdXrootdAioReq::Quantum; -int XrdXrootdAioReq::QuantumMax; -int XrdXrootdAioReq::maxAioPR = 8; -int XrdXrootdAioReq::maxAioPR2 =16; - -extern XrdOucTrace *XrdXrootdTrace; - -/******************************************************************************/ -/* X r d X r o o t d A i o : : A l l o c */ -/******************************************************************************/ - -XrdXrootdAio *XrdXrootdAio::Alloc(XrdXrootdAioReq *arp, int bsize) -{ - XrdXrootdAio *aiop; - -// Obtain an aio object -// - fqMutex.Lock(); - if ((aiop = fqFirst)) fqFirst = aiop->Next; - else if (maxAio) aiop = addBlock(); - if (aiop && (++(SI->AsyncNow) > SI->AsyncMax)) SI->AsyncMax = SI->AsyncNow; - fqMutex.UnLock(); - -// Allocate a buffer for this object -// - if (aiop) - {if (bsize && (aiop->buffp = BPool->Obtain(bsize))) - {aiop->sfsAio.aio_buf = (void *)(aiop->buffp->buff); - aiop->aioReq = arp; - aiop->TIdent = arp->Link->ID; - } - else {aiop->Recycle(); aiop = 0;} - } - -// Return what we have -// - return aiop; -} - -/******************************************************************************/ -/* X r d X r o o t d A i o : : d o n e R e a d */ -/******************************************************************************/ - -// Aio read requests are double buffered. So, there is only one aiocb active -// at a time. This is done for two reasons: -// 1) Provide a serial stream to the client, and -// 2) avoid swamping the network adapter. -// Additionally, double buffering requires minimal locking and simplifies the -// redrive logic. While this knowledge violates OO design, it substantially -// speeds up async I/O handling. This method is called out of the async event -// handler so it does very little work. - -void XrdXrootdAio::doneRead() -{ -// Plase this aio request on the completed queue -// - aioReq->aioDone = this; - -// Extract out any error conditions (keep only the first one) -// - if (Result >= 0) aioReq->aioTotal += Result; - else if (!aioReq->aioError) aioReq->aioError = Result; - -// Schedule the associated arp to redrive the I/O -// - Sched->Schedule((XrdJob *)aioReq); -} - -/******************************************************************************/ -/* X r d X r o o t d A i o : : d o n e W r i t e */ -/******************************************************************************/ - -// Writes are more complicated because there may be several in transit. This -// is done to keep the client from swamping the network adapter. We try -// to optimize the handling of the aio object for the common cases. This method -// is called out of the async event handler so it does very little work. - -void XrdXrootdAio::doneWrite() -{ - char recycle = 0; - -// Lock the aioreq object against competition -// - aioReq->Lock(); - aioReq->numActive--; - -// Extract out any error conditions (keep only the first one). -// - if (Result >= 0) {aioReq->myIOLen -= Result; - aioReq->aioTotal += Result; - } - else if (!aioReq->aioError) aioReq->aioError = Result; - -// Redrive the protocol if so requested. It is impossible to have a proocol -// redrive and completed all of the I/O at the same time. -// - if (aioReq->reDrive) - {Sched->Schedule((XrdJob *)aioReq->Link); - aioReq->reDrive = 0; - } - -// If more aio objects are needed, place this one on the free queue. Otherwise, -// schedule the AioReq object to complete handling the request if no more -// requests are outstanding. It is impossible to have a zero length with more -// requests outstanding. -// -//cerr <<"doneWrite left " <numActive <<' ' <myIOLen <myIOLen > 0) - {Next = aioReq->aioFree; aioReq->aioFree = this;} - else {if (!(aioReq->numActive)) Sched->Schedule((XrdJob *)aioReq); - recycle = 1; - } - -// All done, perform early recycling if possible -// - aioReq->UnLock(); - if (recycle) Recycle(); -} - -/******************************************************************************/ -/* X r d X r o o t d A i o : : R e c y c l e */ -/******************************************************************************/ - -void XrdXrootdAio::Recycle() -{ - -// Recycle the buffer -// - if (buffp) {BPool->Release(buffp); buffp = 0;} - -// Add this object to the free queue -// - fqMutex.Lock(); - Next = fqFirst; - fqFirst = this; - if (--(SI->AsyncNow) < 0) SI->AsyncNow=0; - fqMutex.UnLock(); -} - -/******************************************************************************/ -/* P r i v a t e M e t h o d s */ -/******************************************************************************/ -/******************************************************************************/ -/* X r d X r o o t d A i o : : a d d B l o c k */ -/******************************************************************************/ - -XrdXrootdAio *XrdXrootdAio::addBlock() -{ - const int numalloc = 4096/sizeof(XrdXrootdAio); - int i = (numalloc <= maxAio ? numalloc : maxAio); - XrdXrootdAio *aiop; - - TRACE(DEBUG, "Adding " <Next = fqFirst; fqFirst = aiop; aiop++;} - } - - return aiop; -} - -/******************************************************************************/ -/* X r d X r o o t d A i o R e q */ -/******************************************************************************/ -/******************************************************************************/ -/* X r d X r o o t d A i o R e q : : A l l o c */ -/******************************************************************************/ - -// Implicit Parameters: prot->myIOLen // Length of i/o request -// prot->myOffset // Starting offset -// prot->myFile // Target file -// prot->Link // Link object -// prot->response // Response object - -XrdXrootdAioReq *XrdXrootdAioReq::Alloc(XrdXrootdProtocol *prot, - char iotype, int numaio) -{ - int i, cntaio, myQuantum, iolen = prot->myIOLen; - XrdXrootdAioReq *arp; - XrdXrootdAio *aiop; - -// Obtain an aioreq object -// - rqMutex.Lock(); - if ((arp = rqFirst)) rqFirst = arp->Next; - else arp = addBlock(); - rqMutex.UnLock(); - -// Make sure we have one, fully reset it if we do -// - if (!arp) return arp; - arp->Clear(prot->Link); - if (!numaio) numaio = maxAioPR; - -// Compute the number of aio objects should get and the Quantum size we should -// use. This is a delicate balancing act. We don't want too many segments but -// neither do we want too large of an i/o size. So, if the i/o size is less than -// the quantum then use half a quantum. If the number of segments is greater -// than twice what we would like, then use a larger quantum size. -// - if (iolen < Quantum) - {myQuantum = QuantumMin; - if (!(cntaio = iolen / myQuantum)) cntaio = 1; - else if (iolen % myQuantum) cntaio++; - } else {cntaio = iolen / Quantum; - if (cntaio <= maxAioPR2) myQuantum = Quantum; - else {myQuantum = QuantumMax; - cntaio = iolen / myQuantum; - } - if (iolen % myQuantum) cntaio++; - } - -// Get appropriate number of aio objects -// - i = (maxAioPR < cntaio ? maxAioPR : cntaio); - while(i && (aiop = XrdXrootdAio::Alloc(arp, myQuantum))) - {aiop->Next = arp->aioFree; arp->aioFree = aiop; i--;} - -// Make sure we have at least the minimum number of aio objects -// - if (i && (maxAioPR - i) < 2 && cntaio > 1) - {arp->Recycle(0); return (XrdXrootdAioReq *)0;} - -// Complete the request information -// - if (iotype != 'w') prot->Link->setRef(1); - arp->Instance = prot->Link->Inst(); - arp->myIOLen = iolen; // Amount that is left to send - arp->myOffset = prot->myOffset; - arp->myFile = prot->myFile; - arp->Response = prot->Response; - arp->aioType = iotype; - -// Return what we have -// - return arp; -} - -/******************************************************************************/ -/* X r d X r o o t d A i o R e q : : g e t A i o */ -/******************************************************************************/ - -XrdXrootdAio *XrdXrootdAioReq::getAio() -{ - XrdXrootdAio *aiop; - -// Grab the next free aio object. If none, we return a null pointer. While this -// is a classic consumer/producer problem, normally handled by a semaphore, -// doing so would cause more threads to be tied up as the load increases. We -// want the opposite effect for scaling purposes. So, we use a redrive scheme. -// - Lock(); - if ((aiop = aioFree)) {aioFree = aiop->Next; aiop->Next = 0;} - else reDrive = 1; - UnLock(); - return aiop; -} - -/******************************************************************************/ -/* X r d X r o o t d A i o R e q : : I n i t */ -/******************************************************************************/ - -void XrdXrootdAioReq::Init(int iosize, int maxaiopr, int maxaio) -{ - XrdXrootdAio *aiop; - XrdXrootdAioReq *arp; - -// Set the pointer to the buffer pool, scheduler and statistical area, these are -// only used by the Aio object -// - XrdXrootdAio::Sched = XrdXrootdProtocol::Sched; - XrdXrootdAio::BPool = XrdXrootdProtocol::BPool; - XrdXrootdAio::SI = XrdXrootdProtocol::SI; - -// Set the pointer to the error object and compute the limits -// - eDest = &XrdXrootdProtocol::eDest; - Quantum = static_cast(iosize); - QuantumMin = Quantum / 2; - QuantumMax = Quantum * 2; - if (QuantumMax > XrdXrootdProtocol::maxBuffsz) - QuantumMax = XrdXrootdProtocol::maxBuffsz; - -// Set the maximum number of aio objects we can have (used by Aio object only) -// Note that sysconf(_SC_AIO_MAX) usually provides an unreliable number if it -// provides a number at all. -// - maxAioPR = (maxaiopr < 1 ? 8 : maxaiopr); - maxAioPR2 = maxAioPR * 2; - XrdXrootdAio::maxAio = (maxaio < maxAioPR ? maxAioPR : maxaio); - -// Do some debuging -// - TRACE(DEBUG, "Max aio/req=" <Next = rqFirst; rqFirst = arp; arp++;} - - return arp; -} - -/******************************************************************************/ -/* C l e a r */ -/******************************************************************************/ - -void XrdXrootdAioReq::Clear(XrdLink *lnkp) -{ -Next = 0; -myOffset = 0; -myIOLen = 0; -Instance = 0; -Link = lnkp; -myFile = 0; -aioDone = 0; -aioFree = 0; -numActive = 0; -aioTotal = 0; -aioError = 0; -aioType = 0; -respDone = 0; -isLocked = 0; -reDrive = 0; -} - -/******************************************************************************/ -/* X r d X r o o t d A i o R e q : : e n d R e a d */ -/******************************************************************************/ - -void XrdXrootdAioReq::endRead() -{ - XrdXrootdAio *aiop; - int rc; - -// For read requests, schedule the next read request and send the data we -// already have. Since we don't know if that read will complete before we -// can send the data of the just completed read, we must lock the AioReq. -// We do know that if we have the lock, absolutely nothing is in transit. -// - Lock(); - numActive--; - -// Do a sanity check. The link should not have changed hands but stranger -// things have happened. -// - if (!(Link->isInstance(Instance))) {Scuttle("aio read"); return;} - -// Dequeue the completed request (we know we're just double buffered but the -// queueing is structured so this works even we're n-buffered. -// - aiop = aioDone; - aioDone = aiop->Next; - -// If we encountered an error, send off the error message now and terminate -// - if (aioError - || (myIOLen > 0 && aiop->Result == aiop->buffp->bsize && (aioError=Read()))) - {sendError((char *)aiop->TIdent); - Recycle(1, aiop); - return; - } - -// We may or may not have an I/O request in flight. However, send off -// whatever data we have at this point. -// - rc = (numActive ? - Response.Send(kXR_oksofar, aiop->buffp->buff, aiop->Result) : - Response.Send( aiop->buffp->buff, aiop->Result)); - -// Stop the operation if no I/O is in flight. Make the request stop-pending if -// we could not send the data to the client. -// - if (!numActive) - {myFile->Stats.rdOps(aioTotal); - Recycle(1, aiop); - } - else {aiop->Next = aioFree, aioFree = aiop; - if (rc < 0) {aioError = -1; respDone = 1;} - UnLock(); - } -} - -/******************************************************************************/ -/* X r d X r o o t d A i o R e q : : e n d W r i t e */ -/******************************************************************************/ - -void XrdXrootdAioReq::endWrite() -{ - -// For write requests, this method is called when all of the I/O has completed -// There is no need to lock this object since nothing is pending. In any case, -// Do a sanity check. The link should not have changed hands but stranger -// things have happened. -// - if (!(Link->isInstance(Instance))) {Scuttle("aio write"); return;} - -// If we encountered an error, send off the error message else indicate all OK -// - if (aioError) sendError(Link->ID); - else Response.Send(); - -// Add in the bytes written. This is approzimate because it is done without -// obtaining any kind of lock. Fortunately, it only statistical in nature. -// - myFile->Stats.wrOps(aioTotal); - -// We are done, simply recycle ouselves. -// - Recycle(); -} - -/******************************************************************************/ -/* X r d X r o o t d A i o R e q : : S c u t t l e */ -/******************************************************************************/ - -void XrdXrootdAioReq::Scuttle(const char *opname) -{ - -// Log this event. We can't trust much of anything at this point. -// - eDest->Emsg("scuttle",opname,"failed; link reassigned to",Link->ID); - -// We can just recycle ourselves at this point since we know we are in a -// transition window where nothing is active w.r.t. this request. -// - Recycle(0); -} - -/******************************************************************************/ -/* X r d X r o o t d A i o R e q : : s e n d E r r o r */ -/******************************************************************************/ - -// Warning! The caller must have appropriately serialized the use of this method - -void XrdXrootdAioReq::sendError(char *tident) -{ - char mbuff[4096]; - int rc; - -// If a response was sent, don't send one again -// - if (respDone) return; - respDone = 1; - -// Generate message text. We can't rely on the sfs interface to do this since -// that interface is synchronous. -// - snprintf(mbuff, sizeof(mbuff)-1, "XrdXrootdAio: Unable to %s %s; %s", - (aioType == 'r' ? "read" : "write"), myFile->XrdSfsp->FName(), - eDest->ec2text(aioError)); - -// Please the error message in the log -// - eDest->Emsg("aio", tident, mbuff); - -// Remap the error from the filesystem -// - rc = XProtocol::mapError(aioError); - -// Send the erro back to the client (ignore any errors) -// - Response.Send((XErrorCode)rc, mbuff); -} diff --git a/src/XrdXrootd/XrdXrootdAio.hh b/src/XrdXrootd/XrdXrootdAio.hh deleted file mode 100644 index 15e176a2500..00000000000 --- a/src/XrdXrootd/XrdXrootdAio.hh +++ /dev/null @@ -1,172 +0,0 @@ -#ifndef __XRDXROOTDAIO__ -#define __XRDXROOTDAIO__ -/******************************************************************************/ -/* */ -/* X r d X r o o t d A i o . h h */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XProtocol/XPtypes.hh" -#include "XrdSys/XrdSysPthread.hh" -#include "XrdSfs/XrdSfsAio.hh" -#include "Xrd/XrdScheduler.hh" -#include "XrdXrootd/XrdXrootdResponse.hh" - -/******************************************************************************/ -/* X r d X r o o t d A i o */ -/******************************************************************************/ - -// The XrdXrootdAio object represents a single aio read or write operation. One -// or more of these are allocated to the XrdXrootdAioReq and passed as upcast -// arguments to the sfs file object to effect asynchronous I/O. - -class XrdBuffer; -class XrdBuffManager; -class XrdSysError; -class XrdXrootdAioReq; -class XrdXrootdStats; - -class XrdXrootdAio : public XrdSfsAio -{ -friend class XrdXrootdAioReq; -public: - XrdBuffer *buffp; // -> Buffer object - -virtual void doneRead(); - -virtual void doneWrite(); - -virtual void Recycle(); - - - XrdXrootdAio() {Next=0; aioReq=0; buffp=0;} - ~XrdXrootdAio() {}; - -private: - -static XrdXrootdAio *Alloc(XrdXrootdAioReq *arp, int bsize=0); -static XrdXrootdAio *addBlock(); - -static const char *TraceID; -static XrdBuffManager *BPool; // -> Buffer Manager -static XrdScheduler *Sched; // -> System Scheduler -static XrdXrootdStats *SI; // -> System Statistics -static XrdSysMutex fqMutex; // Locks static data -static XrdXrootdAio *fqFirst; // -> Object in free queue -static int maxAio; // Maximum Aio objects we can yet have - - XrdXrootdAio *Next; // Chain pointer - XrdXrootdAioReq *aioReq; // -> Associated request object -}; - -/******************************************************************************/ -/* X r d X r o o t d A i o R e q */ -/******************************************************************************/ - -// The XrdXrootdAioReq object represents a complete aio request. It handles -// the appropriate translation of the synchrnous request to an async one, -// provides the redrive logic, and handles ending status. -// -class XrdLink; -class XrdXrootdFile; -class XrdXrootdProtocol; - -class XrdXrootdAioReq : public XrdJob -{ -friend class XrdXrootdAio; -public: - -static XrdXrootdAioReq *Alloc(XrdXrootdProtocol *p, char iot, int numaio=0); - - void DoIt() {if (aioType == 'r') endRead(); - else endWrite(); - } - - XrdXrootdAio *getAio(); - -inline XrdXrootdAio *Pop() {XrdXrootdAio *aiop = aioDone; - aioDone = aiop->Next; return aiop; - } - -inline void Push(XrdXrootdAio *newp) - {newp->Next = aioDone; aioDone = newp;} - -static void Init(int iosize, int maxaiopr, int maxaio=-80); - - int Read(); - - void Recycle(int deref=1, XrdXrootdAio *aiop=0); - - int Write(XrdXrootdAio *aiop); - - XrdXrootdAioReq() : XrdJob("aio request") {} - ~XrdXrootdAioReq() {} // Never called - -private: - - void Clear(XrdLink *lnkp); - -static XrdXrootdAioReq *addBlock(); - void endRead(); - void endWrite(); -inline void Lock() {aioMutex.Lock(); isLocked = 1;} - void Scuttle(const char *opname); - void sendError(char *tident); -inline void UnLock() {isLocked = 0; aioMutex.UnLock();} - -static const char *TraceID; -static XrdSysError *eDest; // -> Error Object -static XrdSysMutex rqMutex; // Locks static data -static XrdXrootdAioReq *rqFirst; // -> Object in free queue -static int QuantumMin; // aio segment size (Quantum/2) -static int Quantum; // aio segment size -static int QuantumMax; // aio segment size (Quantum*2) -static int maxAioPR; // aio objects per request (max) -static int maxAioPR2; // aio objects per request (max*2) - - XrdSysMutex aioMutex; // Locks private data - XrdXrootdAioReq *Next; // -> Chain pointer - - off_t myOffset; // Next offset (used for read's only) - int myIOLen; // Size remaining (read and write end) - unsigned int Instance; // Network Link Instance - XrdLink *Link; // -> Network link - XrdXrootdFile *myFile; // -> Associated file - - XrdXrootdAio *aioDone; // Next aiocb that completed - XrdXrootdAio *aioFree; // Next aiocb that we can use - int numActive; // Number of aio requests outstanding - int aioTotal; // Actual number of disk bytes transferred - int aioError; // First errno encounetered - char aioType; // 'r' or 'w' or 's' - char respDone; // 1 -> Response has been sent - char isLocked; // 1 -> Object lock being held - char reDrive; // 1 -> Link redrive is needed - - XrdXrootdResponse Response; // Copy of the original response object -}; -#endif diff --git a/src/XrdXrootd/XrdXrootdBridge.cc b/src/XrdXrootd/XrdXrootdBridge.cc deleted file mode 100644 index 0736650763c..00000000000 --- a/src/XrdXrootd/XrdXrootdBridge.cc +++ /dev/null @@ -1,49 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d X r o o t d B r i d g e . c c */ -/* */ -/* (c) 2012 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdXrootd/XrdXrootdBridge.hh" -#include "XrdXrootd/XrdXrootdTransit.hh" - -/******************************************************************************/ -/* L o g i n */ -/******************************************************************************/ - -XrdXrootd::Bridge *XrdXrootd::Bridge::Login(XrdXrootd::Bridge::Result *rsltP, - XrdLink *linkP, - XrdSecEntity *seceP, - const char *nameP, - const char *protP - ) -{ - -// Simply return a new transit object masquerading as a bridge -// - return XrdXrootdTransit::Alloc(rsltP, linkP, seceP, nameP, protP); -} diff --git a/src/XrdXrootd/XrdXrootdBridge.hh b/src/XrdXrootd/XrdXrootdBridge.hh deleted file mode 100644 index b956e6f0520..00000000000 --- a/src/XrdXrootd/XrdXrootdBridge.hh +++ /dev/null @@ -1,499 +0,0 @@ -#ifndef __XRDXROOTDBRIDGE_HH_ -#define __XRDXROOTDBRIDGE_HH_ -/******************************************************************************/ -/* */ -/* X r d X r o o t d B r i d g e . h h */ -/* */ -/* (c) 2012 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -#include "XProtocol/XPtypes.hh" - -//----------------------------------------------------------------------------- -//! Bridge -//! -//! The Bridge object allows other protocols to gain access to the xrootd -//! protocol stack. Almost any kind of request/response protocol can use this -//! class to convert its request to an xrootd protocol request and rewrite the -//! xrootd protocol response to adhere to its protocol specification. Callers -//! of these methods must be thread-safe and must not rely on thread-local -//! storage as bridge requests and responses may or may not be executed using -//! the initiating thread. Also, see the Result object class below. -//----------------------------------------------------------------------------- - -struct iovec; -class XrdLink; -class XrdSecEntity; -class XrdXrootdProtocol; - -namespace XrdXrootd -{ - -/******************************************************************************/ -/* X r d X r o o t d : : B r i d g e */ -/******************************************************************************/ - -class Bridge -{ -public: -class Result; - -//----------------------------------------------------------------------------- -//! Create a Bridge object. -//! -//! The first step is to create a Bridge object via a Login() call. The object -//! should correspond to a session (i.e. tied to a particular client) real or -//! not. The returned object must be used to inject xrootd requests into the -//! protocol stack. Response rewrites are handled by the Result object passed as -//! an argument. A successful Login() takes control of the connection. You can -//! still write using the Link object but reads may only occur when your -//! protocol's Process() method is called. Use Disc() to disband the bridge and -//! free its storage. The bridge is automatically disbanded when your protocol's -//! Recycle() method is called. This happens when you explicitly close the link -//! or implicitly when the Process() method returns a negative error code or -//! a callback method returns false. -//! -//! @param rsltP a pointer to the result object. This object is used to -//! to rewrite xrootd responses to protocol specific responses. -//! It must be allocated by the caller. One such object can be -//! created for each session or, if the protocol allows, be -//! shared by all sessions. It cannot be deleted until all -//! references to the object disappear (see the Result class). -//! linkP a pointer to the link object that the protocol driver -//! created to the client connection. -//! secP a pointer to the XrdSecEntity object that describes the -//! client's identity. -//! nameP An arbitrary 1-to-8 character client name. The Bridge will -//! uniquefy this name so that log file messages will track the -//! the associated client. The link's identity is set to -//! correspond to this name with additional information. -//! protP a 1-to-7 character name of the protocol using this bridge -//! (e.g. "http"). -//! -//! @return bridgeP a pointer to a new instance of this class if a bridge -//! could be created, null otherwise. If null is returned, the -//! retc variable holds the errno indicating why it failed. -//----------------------------------------------------------------------------- - -static -Bridge *Login(Result *rsltP, //!< The result callback object - XrdLink *linkP, //!< Client's network connection - XrdSecEntity *seceP, //!< Client's identity - const char *nameP, //!< Client's name for tracking - const char *protP //!< Protocol name for tracking - ); - -//----------------------------------------------------------------------------- -//! Inject an xrootd request into the protocol stack. -//! -//! The Run() method allows you to inject an xrootd-style request into the -//! stack. It must use the same format as a real xrootd client would use across -//! the network. The xrootd protocol reference describes these requests. The -//! Run() method handles the request as if it came through the network with -//! some notable exceptions (see the xdataP and xdataL arguments). -//! -//! @param xreqP pointer to the xrootd request. This is the standard 24-byte -//! request header common to all xrootd requests in network -//! format. The contents of the buffer may be modified by the -//! this method. The buffer must not be modified by the caller -//! before a response is solicited via the Result object. -//! -//! @param xdataP the associated data for this request. Full or partial data -//! may be supplied as indicated by the xdataL argument. See -//! explanation of xdataL. For write requests, this buffer may -//! not be altered or deleted until the Result Free() callback -//! is invoked. For other requests, it doesn't matter. -//! -//! If the pointer is zero but the "dlen" field is not zero, -//! dlen's worth of data is read from the network using the -//! associated XdLink object to complete the request. -//! -//! @param xdataL specifies the length of data in the buffer pointed to by -//! xdataP. Depending on the value and the value in the "dlen" -//! field, additional data may be read from the network. -//! -//! xdataL < "dlen": dlen-xdataL additional bytes will be read -//! from the network to complete the request. -//! xdataL >= "dlen": no additional bytes will be read from the -//! network. The request data is complete. -//! -//! @return true the request has been accepted. Processing will start when -//! the caller returns from the Process() method. -//! A response will come via a Result object callback. -//! false the request has been rejected because the bridge is still -//! processing a previous request. -//----------------------------------------------------------------------------- - -virtual bool Run(const char *xreqP, //!< xrootd request header - char *xdataP=0, //!< xrootd request data (optional) - int xdataL=0 //!< xrootd request data length - ) = 0; - -//----------------------------------------------------------------------------- -//! Disconnect the session from the bridge. -//! -//! The Disc() method allows you to disconnect the session from the bridge and -//! free the storage associated with this object. It may be called when you want -//! to regain control of the connection and delete the Bridge object (note that -//! you cannot use delete on Bridge). The Disc() method must not be called in -//! your protocol Recycle() method as protocol object recycling already implies -//! a Disc() call (i.e. the connection is disbanding the associated protocol). -//! -//! @return true the bridge has been dismantled. -//! false the bridge cannot be dismantled because it is still -//! processing a previous request. -//----------------------------------------------------------------------------- - -virtual bool Disc() = 0; - -//----------------------------------------------------------------------------- -//! Set file's sendfile capability. -//! -//! The setSF() method allows you to turn on or off the ability of an open -//! file to be used with the sendfile() system call. This is useful when you -//! must see the data prior to sending to the client (e.g. for encryption). -//! -//! @param fhandle the filehandle as returned by kXR_open. -//! @param mode When true, enables sendfile() otherwise it is disabled. -//! -//! @return =0 Sucessful. -//! @return <0 Call failed. The return code is -errno and usually will -//! indicate that the filehandle is not valid. -//----------------------------------------------------------------------------- - -virtual int setSF(kXR_char *fhandle, bool seton=false) = 0; - -//----------------------------------------------------------------------------- -//! Set the maximum delay. -//! -//! The setWait() method allows you to specify the maximum amount of time a -//! request may be delayed (i.e. via kXR_wait result) before it generates a -//! kXR_Cancelled error with the associated Error() result callback. The default -//! maximum time is 1 hour. If you specify a time less than or equal to zero, -//! wait requests are reflected back via the Wait() result callback method and -//! you are responsible for handling them. You can request wait notification -//! while still having the wait internally handled using the second parameter. -//! Maximum delays are bridge specific. There is no global value. If you desire -//! something other than the default you must call SetWait for each Login(). -//! -//! @param wtime the maximum wait time in seconds. -//! @param notify When true, issues a Wait callback whenever a wait occurs. -//! This is the default when wtime is <= 0. -//! -//----------------------------------------------------------------------------- - -virtual void SetWait(int wime, bool notify=false) = 0; - -/******************************************************************************/ -/* X r d X r o o t d : : B r i d g e : : C o n t e x t */ -/******************************************************************************/ - -//----------------------------------------------------------------------------- -//! Provide callback context. -//! -//! The Context object is passed in all Result object callbacks and contains -//! information describing the result context. No public members should be -//! changed by any result callback method. The context object also includes a -//! method that must be used to complete a pending sendfile() result. -//----------------------------------------------------------------------------- - -class Context -{ -public: - - XrdLink *linkP; //!< -> associated session link object (i.e. connection) - kXR_unt16 rCode; //!< associated "kXR" request code in host byte order -union{kXR_unt16 num; //!< associated stream ID as a short - kXR_char chr[2];//!< associated stream ID as the original char[2] - } sID; //!< associated request stream ID - -//----------------------------------------------------------------------------- -//! Complete a File() callback. -//! -//! The Send() method must be called after the File() callback is invoked to -//! complete data transmission using sendfile(). If Send() is not called the -//! pending sendfile() call is not made and no data is sent to the client. -//! -//! @param headP a pointer to the iovec structure containing the data that -//! must be sent before the sendfile() data. If there is none, -//! the pointer can be null. -//! @param headN the number of elements in the headP iovec structure array. -//! @param tailP a pointer to the iovec structure containing the data that -//! must be sent after the sendfile() data. If there is none, -//! the pointer can be null. -//! @param tailN the number of elements in the tailP iovec structure array. -//! -//! @return < 0 transmission error has occurred. This can be due to either -//! connection failure or data source error (i.e. I/O error). -//! = 0 data has been successfully sent. -//! > 0 the supplied context was not generated by a valid File() -//! callback. No data has been sent. -//----------------------------------------------------------------------------- - -virtual int Send(const - struct iovec *headP, //!< pointer to leading data array - int headN, //!< array count - const - struct iovec *tailP, //!< pointer to trailing data array - int tailN //!< array count - ) -{ - (void)headP; (void)headN; (void)tailP; (void)tailN; - return 1; -} - -//----------------------------------------------------------------------------- -//! Constructor and Destructor -//----------------------------------------------------------------------------- - - Context(XrdLink *lP, kXR_char *sid, kXR_unt16 req) - : linkP(lP), rCode(req) - {memcpy(sID.chr, sid, sizeof(sID.chr));} -virtual ~Context() {} -}; - -/******************************************************************************/ -/* X r d X r o o t d : : B r i d g e : : R e s u l t */ -/******************************************************************************/ - -//----------------------------------------------------------------------------- -//! Handle xrootd protocol execution results. -//! -//! The Result object is an abstract class that defines the interface used -//! by the xrootd protocol stack to effect a client response using whatever -//! alternate protocol is needed. You must define an implementation and pass it -//! as an argument to the Login() Bridge method. -//----------------------------------------------------------------------------- - -class Result -{ -public: - -//----------------------------------------------------------------------------- -//! Effect a client data response. -//! -//! The Data() method is called when Run() resulted in a successful data -//! response. The method should rewrite the data and send it to the client using -//! the associated XrdLink object. As an example, -//! 1) Result::Data(info, iovP, iovN, iovL) is called. -//! 2) Inspect iovP, rewrite the data. -//! 3) Send the response: info->linkP->Send(new_iovP, new_iovN, new_iovL); -//! 4) Handle send errors and cleanup(e.g. deallocate storage). -//! 5) Return, the exchange is now complete. -//! -//! @param info the context associated with the result. -//! @param iovP a pointer to the iovec structure containing the xrootd data -//! response about to be sent to the client. The request header -//! is not included in the iovec structure. The elements of this -//! structure must not be modified by the method. -//! @param iovN the number of elements in the iovec structure array. -//! @param iovL total number of data bytes that would be sent to the client. -//! This is simply the sum of all the lengths in the iovec. -//! @param final True is this is the final result. Otherwise, this is a -//! partial result (i.e. kXR_oksofar) and more data will result -//! causing additional callbacks. For write requests, any -//! supplied data buffer may now be reused or freed. -//! -//! @return true continue normal processing. -//! false terminate the bridge and close the link. -//----------------------------------------------------------------------------- - -virtual bool Data(Bridge::Context &info, //!< the result context - const - struct iovec *iovP, //!< pointer to data array - int iovN, //!< array count - int iovL, //!< byte count - bool final //!< true -> final result - ) = 0; - -//----------------------------------------------------------------------------- -//! Effect a client acknowledgement. -//! -//! The Done() method is called when Run() resulted in success and there is no -//! associated data for the client (equivalent to a simple kXR_ok response). -//! -//! @param info the context associated with the result. -//! -//! @return true continue normal processing. -//! false terminate the bridge and close the link. -//----------------------------------------------------------------------------- - -virtual bool Done(Bridge::Context &info)=0;//!< the result context - -//----------------------------------------------------------------------------- -//! Effect a client error response. -//! -//! The Error() method is called when an error was encountered while processing -//! the Run() request. The error should be reflected to the client. -//! -//! @param info the context associated with the result. -//! @param ecode the "kXR" error code describing the nature of the error. -//! The code is in host byte format. -//! @param etext a null terminated string describing the error in human terms -//! -//! @return true continue normal processing. -//! false terminate the bridge and close the link. -//----------------------------------------------------------------------------- - -virtual bool Error(Bridge::Context &info, //!< the result context - int ecode, //!< the "kXR" error code - const char *etext //!< associated error message - ) = 0; - -//----------------------------------------------------------------------------- -//! Notify callback that a sendfile() request is pending. -//! -//! The File() method is called when Run() resulted in a sendfile response (i.e. -//! sendfile() would have been used to send data to the client). This allows -//! the callback to reframe the sendfile() data using the Send() method in the -//! passed context object (see class Context above). -//! -//! @param info the context associated with the result. -//! @param dlen total number of data bytes that would be sent to the client. -//! -//! @return true continue normal processing. -//! false terminate the bridge and close the link. -//----------------------------------------------------------------------------- - -virtual int File(Bridge::Context &info, //!< the result context - int dlen //!< byte count - ) = 0; - -//----------------------------------------------------------------------------- -//! Notify callback that a write buffer is now available for reuse. -//! -//! The Free() method is called when Run() was called to write data and a buffer -//! was supplied. Normally, he buffer is pinned and cannot be reused until the -//! write completes. This callback provides the notification that the buffer is -//! no longer in use. The callback is invoked prior to any other callbacks and -//! is only invoked if a buffer was supplied. -//! -//! @param info the context associated with this call. -//! @param buffP pointer to the buffer. -//! @param buffL the length originally supplied in the Run() call. -//----------------------------------------------------------------------------- - -virtual void Free(Bridge::Context &info, //!< the result context - char *buffP, //!< pointer to the buffer - int buffL //!< original length to Run() - ) -{ - (void)info; (void)buffP; (void)buffL; -} - -//----------------------------------------------------------------------------- -//! Redirect the client to another host:port. -//! -//! The Redir() method is called when the client must be redirected to another -//! host. -//! -//! @param info the context associated with the result. -//! @param port the port number in host byte format. -//! @param hname the DNS name of the host or IP address is IPV4 or IPV6 -//! format (i.e. "n.n.n.n" or "[ipv6_addr]"). -//! -//! @return true continue normal processing. -//! false terminate the bridge and close the link. -//----------------------------------------------------------------------------- - -virtual bool Redir(Bridge::Context &info, //!< the result context - int port, //!< the port number - const char *hname //!< the destination host - ) = 0; - -//----------------------------------------------------------------------------- -//! Effect a client wait. -//! -//! The Wait() method is called when Run() needs to delay a request. Normally, -//! delays are internally handled. However, you can request that delays be -//! reflected via a callback using the Bridge SetWait() method. -//! -//! @param info the context associated with the result. -//! @param wtime the number of seconds to delay the request. -//! @param wtext a null terminated string describing the wait in human terms -//! -//! @return true continue normal processing. -//! false terminate the bridge and close the link. -//----------------------------------------------------------------------------- - -virtual bool Wait(Bridge::Context &info, //!< the result context - int wtime, //!< the wait time - const char *wtext //!< associated message - ) -{ - (void)info; (void)wtime; (void)wtext; - return false; -} - -//----------------------------------------------------------------------------- -//! Effect a client wait response (waitresp) NOT CURRENTLY IMPLEMENTED! -//! -//! The WaitResp() method is called when an operation ended with a wait for -//! response (waitresp) condition. The wait for response condition indicates -//! that the actual response will be delivered at a later time. You can use -//! context object to determine the operation being delayed. This callback -//! provides you the opportunity to say how the waitresp is to be handled. -//! -//! @param info the context associated with the result. -//! @param wtime the number of seconds in which a response is expected. -//! @param wtext a null terminated string describing the delay in human terms -//! -//! @return !0 pointer to the callback object whose appropriate method -//! should be called when the actual response is generated. -//! @return 0 the waitresp will be handled by the bridge application. The -//! application is responsible for re-issuing the request when -//! the final response is a wait. -//----------------------------------------------------------------------------- -virtual -Bridge::Result *WaitResp(Bridge::Context &info, //!< the result context - int wtime, //!< the wait time - const char *wtext //!< associated message - ) -{ - (void)info; (void)wtime; (void)wtext; - return 0; -} - -//----------------------------------------------------------------------------- -//! Constructor & Destructor -//----------------------------------------------------------------------------- - - Result() {} -virtual ~Result() {} -}; - -//----------------------------------------------------------------------------- -//! Constructor & Destructor -//----------------------------------------------------------------------------- - - Bridge() {} -protected: -virtual ~Bridge() {} -}; -} -#endif diff --git a/src/XrdXrootd/XrdXrootdCallBack.cc b/src/XrdXrootd/XrdXrootdCallBack.cc deleted file mode 100644 index 433f2c78a4d..00000000000 --- a/src/XrdXrootd/XrdXrootdCallBack.cc +++ /dev/null @@ -1,392 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d X r o o t d C a l l B a c k . c c */ -/* */ -/* (c) 2006 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include - -#include "Xrd/XrdScheduler.hh" -#include "XProtocol/XProtocol.hh" -#include "XProtocol/XPtypes.hh" -#include "XrdSys/XrdSysError.hh" -#include "XrdSfs/XrdSfsInterface.hh" -#include "XrdXrootd/XrdXrootdCallBack.hh" -#include "XrdXrootd/XrdXrootdMonitor.hh" -#include "XrdXrootd/XrdXrootdProtocol.hh" -#include "XrdXrootd/XrdXrootdStats.hh" -#include "XrdXrootd/XrdXrootdReqID.hh" -#include "XrdXrootd/XrdXrootdTrace.hh" - -/******************************************************************************/ -/* L o c a l C l a s s e s */ -/******************************************************************************/ - -class XrdXrootdCBJob : XrdJob -{ -public: - -static XrdXrootdCBJob *Alloc(XrdXrootdCallBack *cbF, XrdOucErrInfo *erp, - const char *Path, int rval); - - void DoIt(); - -inline void Recycle(){myMutex.Lock(); - Next = FreeJob; - FreeJob = this; - myMutex.UnLock(); - } - - XrdXrootdCBJob(XrdXrootdCallBack *cbp, - XrdOucErrInfo *erp, - const char *path, - int rval) - : XrdJob("async response"), - cbFunc(cbp), eInfo(erp), Path(path), - Result(rval) {} - - ~XrdXrootdCBJob() {} - -private: -void DoStatx(XrdOucErrInfo *eInfo); -static XrdSysMutex myMutex; -static XrdXrootdCBJob *FreeJob; - -XrdXrootdCBJob *Next; -XrdXrootdCallBack *cbFunc; -XrdOucErrInfo *eInfo; -const char *Path; -int Result; -}; - -/******************************************************************************/ -/* G l o b a l s */ -/******************************************************************************/ - -extern XrdOucTrace *XrdXrootdTrace; - - XrdSysError *XrdXrootdCallBack::eDest; - XrdXrootdStats *XrdXrootdCallBack::SI; - XrdScheduler *XrdXrootdCallBack::Sched; - int XrdXrootdCallBack::Port; - - XrdSysMutex XrdXrootdCBJob::myMutex; - XrdXrootdCBJob *XrdXrootdCBJob::FreeJob; - -/******************************************************************************/ -/* X r d X r o o t d C B J o b */ -/******************************************************************************/ -/******************************************************************************/ -/* A l l o c */ -/******************************************************************************/ - -XrdXrootdCBJob *XrdXrootdCBJob::Alloc(XrdXrootdCallBack *cbF, - XrdOucErrInfo *erp, - const char *Path, - int rval) -{ - XrdXrootdCBJob *cbj; - -// Obtain a call back object by trying to avoid new() -// - myMutex.Lock(); - if (!(cbj = FreeJob)) cbj = new XrdXrootdCBJob(cbF, erp, Path, rval); - else {cbj->cbFunc = cbF, cbj->eInfo = erp; - cbj->Result = rval;cbj->Path = Path; - FreeJob = cbj->Next; - } - myMutex.UnLock(); - -// Return the new object -// - return cbj; -} - -/******************************************************************************/ -/* D o I t */ -/******************************************************************************/ - -void XrdXrootdCBJob::DoIt() -{ - -// Some operations differ in the way we handle them. For instance, for open() -// if it succeeds then we must force the client to retry the open request -// because we can't attach the file to the client here. We do this by asking -// the client to wait zero seconds. Protocol demands a client retry. -// - if (SFS_OK == Result) - {if (*(cbFunc->Func()) == 'o'){int rc = 0; cbFunc->sendResp(eInfo, kXR_wait, &rc);} - else {if (*(cbFunc->Func()) == 'x') DoStatx(eInfo); - cbFunc->sendResp(eInfo, kXR_ok, 0, eInfo->getErrText(), - eInfo->getErrTextLen()); - } - } else cbFunc->sendError(Result, eInfo, Path); - -// Tell the requestor that the callback has completed -// - if (eInfo->getErrCB()) eInfo->getErrCB()->Done(Result, eInfo); - else delete eInfo; - eInfo = 0; - Recycle(); -} - -/******************************************************************************/ -/* D o S t a t x */ -/******************************************************************************/ - -void XrdXrootdCBJob::DoStatx(XrdOucErrInfo *einfo) -{ - const char *tp = einfo->getErrText(); - char cflags[2]; - int flags; - -// Skip to the third token -// - while(*tp && *tp == ' ') tp++; - while(*tp && *tp != ' ') tp++; // 1st - while(*tp && *tp == ' ') tp++; - while(*tp && *tp != ' ') tp++; // 2nd - -// Convert to flags -// - flags = atoi(tp); - -// Convert to proper indicator -// - if (flags & kXR_offline) cflags[0] = (char)kXR_offline; - else if (flags & kXR_isDir) cflags[0] = (char)kXR_isDir; - else cflags[0] = (char)kXR_file; - -// Set the new response -// - cflags[1] = '\0'; - einfo->setErrInfo(0, cflags); -} - -/******************************************************************************/ -/* X r d X r o o t d C a l l B a c k */ -/******************************************************************************/ -/******************************************************************************/ -/* D o n e */ -/******************************************************************************/ - -void XrdXrootdCallBack::Done(int &Result, //I/O: Function result - XrdOucErrInfo *eInfo, // In: Error information - const char *Path) // In: Path related -{ - XrdXrootdCBJob *cbj; - -// Sending an async response may take a long time. So, we schedule the task -// to run asynchronously from the forces that got us here. -// - if (!(cbj = XrdXrootdCBJob::Alloc(this, eInfo, Path, Result))) - {eDest->Emsg("Done",ENOMEM,"get call back job; user",eInfo->getErrUser()); - if (eInfo->getErrCB()) eInfo->getErrCB()->Done(Result, eInfo); - else delete eInfo; - } else Sched->Schedule((XrdJob *)cbj); -} - -/******************************************************************************/ -/* S a m e */ -/******************************************************************************/ - -int XrdXrootdCallBack::Same(unsigned long long arg1, unsigned long long arg2) -{ - XrdXrootdReqID ReqID1(arg1), ReqID2(arg2); - unsigned char sid1[2], sid2[2]; - unsigned int inst1, inst2; - int lid1, lid2; - - ReqID1.getID(sid1, lid1, inst1); - ReqID2.getID(sid2, lid2, inst2); - return lid1 == lid2; -} - -/******************************************************************************/ -/* s e n d E r r o r */ -/******************************************************************************/ - -void XrdXrootdCallBack::sendError(int rc, - XrdOucErrInfo *eInfo, - const char *Path) -{ - const char *TraceID = "fsError"; - static int Xserr = kXR_ServerError; - int ecode; - const char *eMsg = eInfo->getErrText(ecode); - const char *User = eInfo->getErrUser(); - -// Process the data response vector (we need to do this here) -// - if (rc == SFS_DATAVEC) - {if (ecode > 1) sendVesp(eInfo, kXR_ok, (struct iovec *)eMsg, ecode); - else sendResp(eInfo, kXR_ok, 0); - return; - } - -// Optimize error message handling here -// - if (eMsg && !*eMsg) eMsg = 0; - -// Process standard errors -// - if (rc == SFS_ERROR) - {SI->errorCnt++; - rc = XProtocol::mapError(ecode); - sendResp(eInfo, kXR_error, &rc, eMsg, eInfo->getErrTextLen()+1); - return; - } - -// Process the redirection (error msg is host:port) -// - if (rc == SFS_REDIRECT) - {SI->redirCnt++; - if (ecode <= 0) ecode = (ecode ? -ecode : Port); - TRACE(REDIR, User <<" async redir to " << eMsg <<':' <getErrTextLen()); - if (XrdXrootdMonitor::Redirect() && Path) - XrdXrootdMonitor::Redirect(eInfo->getErrMid(),eMsg,ecode,Opcode,Path); - return; - } - -// Process the deferal -// - if (rc >= SFS_STALL) - {SI->stallCnt++; - TRACE(STALL, "Stalling " <getErrTextLen()+1); - return; - } - -// Process the data response -// - if (rc == SFS_DATA) - {if (ecode) sendResp(eInfo, kXR_ok, 0, eMsg, ecode); - else sendResp(eInfo, kXR_ok, 0); - return; - } - -// Unknown conditions, report it -// - {char buff[64]; - SI->errorCnt++; - ecode = sprintf(buff, "Unknown sfs response code %d", rc); - eDest->Emsg("sendError", buff); - sendResp(eInfo, kXR_error, &Xserr, buff, ecode+1); - return; - } -} - -/******************************************************************************/ -/* s e n d R e s p */ -/******************************************************************************/ - -void XrdXrootdCallBack::sendResp(XrdOucErrInfo *eInfo, - XResponseType Status, - int *Data, - const char *Msg, - int Mlen) -{ - const char *TraceID = "sendResp"; - struct iovec rspVec[4]; - XrdXrootdReqID ReqID; - int dlen = 0, n = 1; - kXR_int32 xbuf; - - if (Data) - {xbuf = static_cast(htonl(*Data)); - rspVec[n].iov_base = (caddr_t)(&xbuf); - dlen = rspVec[n].iov_len = sizeof(xbuf); n++; // 1 - } - if (Msg && *Msg) - { rspVec[n].iov_base = (caddr_t)Msg; - dlen += rspVec[n].iov_len = Mlen; n++; // 2 - } - -// Set the destination -// - ReqID.setID(eInfo->getErrArg()); - -// Send the async response -// - if (XrdXrootdResponse::Send(ReqID, Status, rspVec, n, dlen) < 0) - eDest->Emsg("sendResp", eInfo->getErrUser(), Opname, - "async resp aborted; user gone."); - else if (TRACING(TRACE_RSP)) - {XrdXrootdResponse theResp; - theResp.Set(ReqID.Stream()); - TRACE(RSP, eInfo->getErrUser() <<" async " <extData()) eInfo->Reset(); -} - -/******************************************************************************/ -/* s e n d V e s p */ -/******************************************************************************/ - -void XrdXrootdCallBack::sendVesp(XrdOucErrInfo *eInfo, - XResponseType Status, - struct iovec *ioV, - int ioN) -{ - const char *TraceID = "sendVesp"; - XrdXrootdReqID ReqID; - int dlen = 0; - -// Calculate the amount of data being sent -// - for (int i = 1; i < ioN; i++) dlen += ioV[i].iov_len; - -// Set the destination -// - ReqID.setID(eInfo->getErrArg()); - -// Send the async response -// - if (XrdXrootdResponse::Send(ReqID, Status, ioV, ioN, dlen) < 0) - eDest->Emsg("sendResp", eInfo->getErrUser(), Opname, - "async resp aborted; user gone."); - else if (TRACING(TRACE_RSP)) - {XrdXrootdResponse theResp; - theResp.Set(ReqID.Stream()); - TRACE(RSP, eInfo->getErrUser() <<" async " <extData()) eInfo->Reset(); -} diff --git a/src/XrdXrootd/XrdXrootdCallBack.hh b/src/XrdXrootd/XrdXrootdCallBack.hh deleted file mode 100644 index 09824b62ed7..00000000000 --- a/src/XrdXrootd/XrdXrootdCallBack.hh +++ /dev/null @@ -1,84 +0,0 @@ -#ifndef __XRDXROOTDCALLBACK_H__ -#define __XRDXROOTDCALLBACK_H__ -/******************************************************************************/ -/* */ -/* X r d X r o o t d C a l l B a c k . h h */ -/* */ -/* (c) 2006 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdOuc/XrdOucErrInfo.hh" -#include "XrdSys/XrdSysPthread.hh" - -class XrdScheduler; -class XrdOurError; -class XrdXrootdStats; - -struct iovec; - -class XrdXrootdCallBack : public XrdOucEICB -{ -public: - - void Done(int &Result, //I/O: Function result - XrdOucErrInfo *eInfo, // In: Error information - const char *Path=0); // In: Path related to call - - const char *Func() {return Opname;} - - char Oper() {return Opcode;} - - int Same(unsigned long long arg1, unsigned long long arg2); - - void sendError(int rc, XrdOucErrInfo *eInfo, const char *Path); - - void sendResp(XrdOucErrInfo *eInfo, - XResponseType xrt, int *Data=0, - const char *Msg=0, int Mlen=0); - - void sendVesp(XrdOucErrInfo *eInfo, - XResponseType xrt, - struct iovec *ioV, int ioN); - -static void setVals(XrdSysError *erp, - XrdXrootdStats *SIp, - XrdScheduler *schp, - int port) - {eDest=erp; SI=SIp; Sched=schp; Port=port;} - - XrdXrootdCallBack(const char *opn, const char opc) - : Opname(opn), Opcode(opc) {} - - ~XrdXrootdCallBack() {} -private: -static XrdSysError *eDest; -static XrdXrootdStats *SI; -static XrdScheduler *Sched; - const char *Opname; - const char Opcode; -static int Port; -}; -#endif diff --git a/src/XrdXrootd/XrdXrootdConfig.cc b/src/XrdXrootd/XrdXrootdConfig.cc deleted file mode 100644 index eb08023672a..00000000000 --- a/src/XrdXrootd/XrdXrootdConfig.cc +++ /dev/null @@ -1,1700 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d X r o o t d C o n f i g . c c */ -/* */ -/* (c) 2010 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Deprtment of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef __solaris__ -#include -#endif - -#include - -#include "XrdVersion.hh" - -#include "XProtocol/XProtocol.hh" - -#include "XrdSfs/XrdSfsInterface.hh" -#include "XrdNet/XrdNetOpts.hh" -#include "XrdNet/XrdNetSocket.hh" -#include "XrdOuc/XrdOuca2x.hh" -#include "XrdOuc/XrdOucEnv.hh" -#include "XrdOuc/XrdOucProg.hh" -#include "XrdOuc/XrdOucReqID.hh" -#include "XrdOuc/XrdOucStream.hh" -#include "XrdOuc/XrdOucTrace.hh" -#include "XrdOuc/XrdOucUtils.hh" -#include "XrdSec/XrdSecLoadSecurity.hh" -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdSys/XrdSysLogger.hh" - -#include "XrdXrootd/XrdXrootdAdmin.hh" -#include "XrdXrootd/XrdXrootdAio.hh" -#include "XrdXrootd/XrdXrootdCallBack.hh" -#include "XrdXrootd/XrdXrootdFile.hh" -#include "XrdXrootd/XrdXrootdFileLock.hh" -#include "XrdXrootd/XrdXrootdFileLock1.hh" -#include "XrdXrootd/XrdXrootdJob.hh" -#include "XrdXrootd/XrdXrootdMonitor.hh" -#include "XrdXrootd/XrdXrootdPrepare.hh" -#include "XrdXrootd/XrdXrootdProtocol.hh" -#include "XrdXrootd/XrdXrootdStats.hh" -#include "XrdXrootd/XrdXrootdTrace.hh" -#include "XrdXrootd/XrdXrootdTransit.hh" -#include "XrdXrootd/XrdXrootdXPath.hh" - -#include "Xrd/XrdBuffer.hh" -#include "Xrd/XrdInet.hh" - -/******************************************************************************/ -/* P r o t o c o l C o m m a n d L i n e O p t i o n s */ -/******************************************************************************/ - -/* This is the XRootd server. The syntax is: - - xrootd [options] - - options: [] [-r] [-t] [-y] [path] - -Where: - xopt are xrd specified options that are screened out. - - -r This is a redirecting server. - - -t This server is a redirection target. - - -y This server is a proxy server. - - path Export path. Any number of paths may be specified. - By default, only '/tmp' is exported. - -*/ -/******************************************************************************/ -/* G l o b a l s */ -/******************************************************************************/ - -extern XrdOucTrace *XrdXrootdTrace; - - XrdXrootdPrepare *XrdXrootdPrepQ; - - const char *XrdXrootdInstance; - - int XrdXrootdPort; - -/******************************************************************************/ -/* C o n f i g u r e */ -/******************************************************************************/ - -int XrdXrootdProtocol::Configure(char *parms, XrdProtocol_Config *pi) -{ -/* - Function: Establish configuration at load time. - - Input: None. - - Output: 0 upon success or !0 otherwise. -*/ - extern XrdSfsFileSystem *XrdSfsGetDefaultFileSystem - (XrdSfsFileSystem *nativeFS, - XrdSysLogger *Logger, - const char *configFn, - XrdOucEnv *EnvInfo); - - extern XrdSfsFileSystem *XrdXrootdloadFileSystem(XrdSysError *, - XrdSfsFileSystem *, - char *, int, - const char *, XrdOucEnv *); - extern XrdSfsFileSystem *XrdDigGetFS - (XrdSfsFileSystem *nativeFS, - XrdSysLogger *Logger, - const char *configFn, - const char *theParms); - extern int optind, opterr; - - XrdOucEnv myEnv; - XrdXrootdXPath *xp; - char *adminp, *rdf, *bP, *tmp, c, buff[1024]; - int i, n, deper = 0; - -// Copy out the special info we want to use at top level -// - eDest.logger(pi->eDest->logger()); - XrdXrootdTrace = new XrdOucTrace(&eDest); - SI = new XrdXrootdStats(pi->Stats); - Sched = pi->Sched; - BPool = pi->BPool; - hailWait = pi->hailWait; - readWait = pi->readWait; - Port = pi->Port; - myInst = pi->myInst; - Window = pi->WSize; - WANPort = pi->WANPort; - WANWindow = pi->WANWSize; - -// Record globally accessible values -// - XrdXrootdInstance = pi->myInst; - XrdXrootdPort = pi->Port; - -// Set the callback object static areas now! -// - XrdXrootdCallBack::setVals(&eDest, SI, Sched, Port); - -// Prohibit this program from executing as superuser -// - if (geteuid() == 0) - {eDest.Emsg("Config", "Security reasons prohibit xrootd running as " - "superuser; xrootd is terminating."); - _exit(8); - } - -// Process any command line options -// - opterr = 0; optind = 1; - if (pi->argc > 1 && '-' == *(pi->argv[1])) - while ((c=getopt(pi->argc,pi->argv,"mrst")) && ((unsigned char)c != 0xff)) - { switch(c) - { - case 'r': deper = 1; - XrdOucEnv::Export("XRDREDIRECT", "R"); - break; - case 'm': XrdOucEnv::Export("XRDREDIRECT", "R"); - break; - case 't': deper = 1; - XrdOucEnv::Export("XRDRETARGET", "1"); - break; - case 's': XrdOucEnv::Export("XRDRETARGET", "1"); - break; - case 'y': XrdOucEnv::Export("XRDREDPROXY", "1"); - break; - default: eDest.Say("Config warning: ignoring invalid option '",pi->argv[optind-1],"'."); - } - } - -// Check for deprecated options -// - if (deper) eDest.Say("Config warning: '-r -t' are deprecated; use '-m -s' instead."); - -// Pick up exported paths -// - for ( ; optind < pi->argc; optind++) xexpdo(pi->argv[optind]); - -// Pre-initialize some i/o values. Note that we now set maximum readv element -// transfer size to the buffer size (before it was a reasonable 256K). -// - if (!(as_miniosz = as_segsize/2)) as_miniosz = as_segsize; - n = (pi->theEnv ? pi->theEnv->GetInt("MaxBuffSize") : 0); - maxTransz = maxBuffsz = (n ? n : BPool->MaxSize()); - memset(Route, 0, sizeof(Route)); - -// Now process and configuration parameters -// - rdf = (parms && *parms ? parms : pi->ConfigFN); - if (rdf && Config(rdf)) return 0; - if (pi->DebugON) XrdXrootdTrace->What = TRACE_ALL; - -// Check if we are exporting a generic object name -// - if (XPList.Opts() & XROOTDXP_NOSLASH) - {eDest.Say("Config exporting ", XPList.Path(n)); n += 2;} - else n = 0; - -// Check if we are exporting anything -// - if (!(xp = XPList.Next()) && !n) - {XPList.Insert("/tmp"); n = 8; - eDest.Say("Config warning: only '/tmp' will be exported."); - } else { - while(xp) {eDest.Say("Config exporting ", xp->Path(i)); - n += i+2; xp = xp->Next(); - } - } - -// Export the exports -// - bP = tmp = (char *)malloc(n); - if (XPList.Opts() & XROOTDXP_NOSLASH) - {strcpy(bP, XPList.Path(i)); bP += i, *bP++ = ' ';} - xp = XPList.Next(); - while(xp) {strcpy(bP, xp->Path(i)); bP += i; *bP++ = ' '; xp = xp->Next();} - *(bP-1) = '\0'; - XrdOucEnv::Export("XRDEXPORTS", tmp); free(tmp); - -// Initialize the security system if this is wanted -// - if (!ConfigSecurity(myEnv, pi->ConfigFN)) return 0; - -// Set up the network for self-identification and display it -// - pi->NetTCP->netIF.Port(Port); - pi->NetTCP->netIF.Display("Config "); - -// Establish our specific environment that will be passed along -// - myEnv.PutPtr("XrdInet*", (void *)(pi->NetTCP)); - myEnv.PutPtr("XrdNetIF*", (void *)(&(pi->NetTCP->netIF))); - myEnv.PutPtr("XrdScheduler*", Sched); - -// Copy over the xrd environment which contains plugin argv's -// - if (pi->theEnv) myEnv.PutPtr("xrdEnv*", pi->theEnv); - -// Get the filesystem to be used -// - if (FSLib[0]) - {TRACE(DEBUG, "Loading base filesystem library " <ConfigFN, &myEnv); - } else { - osFS = XrdSfsGetDefaultFileSystem(0,eDest.logger(),pi->ConfigFN,&myEnv); - } - if (!osFS) - {eDest.Emsg("Config", "Unable to load file system."); - return 0; - } else { - SI->setFS(osFS); - if (FSLib[0]) osFS->EnvInfo(&myEnv); - } - -// Check if we have a wrapper library -// - if (FSLib[1]) - {TRACE(DEBUG, "Loading wrapper filesystem library " <ConfigFN, &myEnv); - if (!osFS) - {eDest.Emsg("Config", "Unable to load file system wrapper."); - return 0; - } else osFS->EnvInfo(&myEnv); - } - -// Check if the diglib should be loaded. We only support the builtin one. In -// the future we will have to change this code to be like the above. -// - if (digParm) - {TRACE(DEBUG, "Loading dig filesystem builtin"); - digFS = XrdDigGetFS(osFS, eDest.logger(), pi->ConfigFN, digParm); - if (!digFS) eDest.Emsg("Config","Unable to load digFS; " - "remote debugging disabled!"); - } - -// Check if we are going to be processing checksums locally -// - if (JobCKT && JobLCL) - {XrdOucErrInfo myError("Config"); - XrdOucTList *tP = JobCKTLST; - do {if (osFS->chksum(XrdSfsFileSystem::csSize,tP->text,0,myError)) - {eDest.Emsg("Config",tP->text,"checksum is not natively supported."); - return 0; - } - tP->ival[1] = myError.getErrInfo(); - tP = tP->next; - } while(tP); - } - -// Initialiaze for AIO -// - if (getenv("XRDXROOTD_NOAIO")) as_noaio = 1; - else if (!as_noaio) XrdXrootdAioReq::Init(as_segsize, as_maxperreq, as_maxpersrv); - else eDest.Say("Config warning: asynchronous I/O has been disabled!"); - -// Create the file lock manager -// - Locker = (XrdXrootdFileLock *)new XrdXrootdFileLock1(); - XrdXrootdFile::Init(Locker, as_nosf == 0); - if (as_nosf) eDest.Say("Config warning: sendfile I/O has been disabled!"); - -// Schedule protocol object cleanup (also advise the transit protocol) -// - ProtStack.Set(pi->Sched, XrdXrootdTrace, TRACE_MEM); - n = (pi->ConnMax/3 ? pi->ConnMax/3 : 30); - ProtStack.Set(n, 60*60); - XrdXrootdTransit::Init(pi->Sched, n, 60*60); - -// Initialize the request ID generation object -// - PrepID = new XrdOucReqID(pi->urAddr, (int)Port); - -// Initialize for prepare processing -// - XrdXrootdPrepQ = new XrdXrootdPrepare(&eDest, pi->Sched); - sprintf(buff, "udp://%s:%d/&L=%%d&U=%%s", pi->myName, pi->Port); - Notify = strdup(buff); - -// Set the redirect flag if we are a pure redirector -// - myRole = kXR_isServer; myRolf = kXR_DataServer; - if ((rdf = getenv("XRDREDIRECT")) - && (!strcmp(rdf, "R") || !strcmp(rdf, "M"))) - {isRedir = *rdf; - myRole = kXR_isManager; myRolf = kXR_LBalServer; - if (!strcmp(rdf, "M")) myRole |=kXR_attrMeta; - } - if (getenv("XRDREDPROXY")) myRole |=kXR_attrProxy; - -// Check if we are redirecting anything -// - if ((xp = RPList.Next())) - {int k; - char buff[2048], puff[1024]; - do {k = xp->Opts(); - if (Route[k].Host[0] == Route[k].Host[1] - && Route[k].Port[0] == Route[k].Port[1]) *puff = 0; - else sprintf(puff, "%%%s:%d", Route[k].Host[1], Route[k].Port[1]); - sprintf(buff," to %s:%d%s",Route[k].Host[0],Route[k].Port[0],puff); - eDest.Say("Config redirect static ", xp->Path(), buff); - xp = xp->Next(); - } while(xp); - } - - if ((xp = RQList.Next())) - {int k; - const char *cgi1, *cgi2; - char buff[2048], puff[1024], xCgi[RD_Num] = {0}; - if (isRedir) {cgi1 = "+"; cgi2 = getenv("XRDCMSCLUSTERID");} - else {cgi1 = ""; cgi2 = pi->myName;} - myCNlen = snprintf(buff, sizeof(buff), "%s%s", cgi1, cgi2); - myCName = strdup(buff); - do {k = xp->Opts(); - if (Route[k].Host[0] == Route[k].Host[1] - && Route[k].Port[0] == Route[k].Port[1]) *puff = 0; - else sprintf(puff, "%%%s:%d", Route[k].Host[1], Route[k].Port[1]); - sprintf(buff," to %s:%d%s",Route[k].Host[0],Route[k].Port[0],puff); - eDest.Say("Config redirect enoent ", xp->Path(), buff); - if (!xCgi[k] && cgi2) - {bool isdup = Route[k].Host[0] == Route[k].Host[1] - && Route[k].Port[0] == Route[k].Port[1]; - for (i = 0; i < 2; i++) - {n = snprintf(buff,sizeof(buff), "%s?tried=%s%s", - Route[k].Host[i], cgi1, cgi2); - free(Route[k].Host[i]); Route[k].Host[i] = strdup(buff); - Route[k].RDSz[i] = n; - if (isdup) {Route[k].Host[1] = Route[k].Host[0]; - Route[k].RDSz[1] = n; break; - } - } - } - xCgi[k] = 1; - xp = xp->Next(); - } while(xp); - } - -// Initialize monitoring (it won't do anything if it wasn't enabled) -// - if (!XrdXrootdMonitor::Init(Sched, &eDest, pi->myName, pi->myProg, - myInst, Port)) return 0; - -// Add all jobs that we can run to the admin object -// - if (JobCKS) XrdXrootdAdmin::addJob("chksum", JobCKS); - -// Establish the path to be used for admin functions. We will loose this -// storage upon an error but we don't care because we'll just exit. -// - adminp = XrdOucUtils::genPath(pi->AdmPath, 0, ".xrootd"); - -// Setup the admin path (used in all roles). -// - if (!(AdminSock = XrdNetSocket::Create(&eDest, adminp, "admin", pi->AdmMode)) - || !XrdXrootdAdmin::Init(&eDest, AdminSock)) return 0; - -// Setup pid file -// - PidFile(); - -// Finally, check if we really need to be in bypass mode if it is set -// - if (OD_Bypass) - {const char *penv = getenv("XRDXROOTD_PROXY"); - if (!penv || *penv != '=') - {OD_Bypass = false; - eDest.Say("Config warning: 'fsoverload bypass' ignored; " - "not a forwarding proxy."); - } - } - -// Return success -// - free(adminp); - return 1; -} - -/******************************************************************************/ -/* C o n f i g */ -/******************************************************************************/ - -#define TS_Xeq(x,m) (!strcmp(x,var)) GoNo = m(Config) - -int XrdXrootdProtocol::Config(const char *ConfigFN) -{ - XrdOucEnv myEnv; - XrdOucStream Config(&eDest, getenv("XRDINSTANCE"), &myEnv, "=====> "); - char *var; - int cfgFD, GoNo, NoGo = 0, ismine; - - // Open and attach the config file - // - if ((cfgFD = open(ConfigFN, O_RDONLY, 0)) < 0) - return eDest.Emsg("Config", errno, "open config file", ConfigFN); - Config.Attach(cfgFD); - - // Process items - // - while((var = Config.GetMyFirstWord())) - { if ((ismine = !strncmp("xrootd.", var, 7)) && var[7]) var += 7; - else if ((ismine = !strcmp("all.export", var))) var += 4; - else if ((ismine = !strcmp("all.pidpath",var))) var += 4; - else if ((ismine = !strcmp("all.seclib", var))) var += 4; - - if (ismine) - { if TS_Xeq("async", xasync); - else if TS_Xeq("chksum", xcksum); - else if TS_Xeq("diglib", xdig); - else if TS_Xeq("export", xexp); - else if TS_Xeq("fslib", xfsl); - else if TS_Xeq("fsoverload", xfso); - else if TS_Xeq("log", xlog); - else if TS_Xeq("monitor", xmon); - else if TS_Xeq("pidpath", xpidf); - else if TS_Xeq("prep", xprep); - else if TS_Xeq("redirect", xred); - else if TS_Xeq("seclib", xsecl); - else if TS_Xeq("trace", xtrace); - else if TS_Xeq("limit", xlimit); - else {eDest.Say("Config warning: ignoring unknown directive '",var,"'."); - Config.Echo(); - continue; - } - if (GoNo) {Config.Echo(); NoGo = 1;} - } - } - return NoGo; -} - -/******************************************************************************/ -/* P r i v a t e F u n c t i o n s */ -/******************************************************************************/ -/******************************************************************************/ -/* P i d F i l e */ -/******************************************************************************/ - -void XrdXrootdProtocol::PidFile() -{ - int rc, xfd; - char buff[32], pidFN[1200]; - char *ppath=XrdOucUtils::genPath(pidPath,XrdOucUtils::InstName(-1)); - const char *xop = 0; - - if ((rc = XrdOucUtils::makePath(ppath,XrdOucUtils::pathMode))) - {xop = "create"; errno = rc;} - else {snprintf(pidFN, sizeof(pidFN), "%s/xrootd.pid", ppath); - - if ((xfd = open(pidFN, O_WRONLY|O_CREAT|O_TRUNC,0644)) < 0) - xop = "open"; - else {if (write(xfd,buff,snprintf(buff,sizeof(buff),"%d", - static_cast(getpid()))) < 0) xop = "write"; - close(xfd); - } - } - - free(ppath); - if (xop) eDest.Emsg("Config", errno, xop, pidFN); -} - -/******************************************************************************/ -/* C o n f i g S e c u r i t y */ -/******************************************************************************/ - -int XrdXrootdProtocol::ConfigSecurity(XrdOucEnv &xEnv, const char *cfn) -{ - XrdSecGetProt_t secGetProt = 0; - -// Check if we need to loadanything -// - if (!SecLib) - {eDest.Say("Config warning: 'xrootd.seclib' not specified;" - " strong authentication disabled!"); - xEnv.PutPtr("XrdSecGetProtocol*", (void *)0); - xEnv.PutPtr("XrdSecProtector*" , (void *)0); - return 1; - } - -// Blad some debugging info -// - TRACE(DEBUG, "Loading security library " <] [maxsegs ] - [maxtot ] [segsize ] - [minsize ] [maxstalls ] - [force] [syncw] [off] [nosf] - - maximum number of async ops per link. Default 8. - maximum number of async ops per request. Default 8. - maximum number of async ops per server. Default is 20% - of maximum connection times aiopl divided by two. - The aio segment size. This is the maximum size that data - will be read or written. The defaults to 64K but is - adjusted for each request to minimize latency. - the minimum number of bytes that must be read or written - to allow async processing to occur (default is maxbsz/2 - typically 1M). - Maximum number of client stalls before synchronous i/o is - used. Async mode is tried after requests. - force Uses async i/o for all requests, even when not explicitly - requested (this is compatible with synchronous clients). - syncw Use synchronous i/o for write requests. - off Disables async i/o - nosf Disables use of sendfile to send data to the client. - - Output: 0 upon success or 1 upon failure. -*/ - -int XrdXrootdProtocol::xasync(XrdOucStream &Config) -{ - char *val; - int i, ppp; - int V_force=-1, V_syncw = -1, V_off = -1, V_mstall = -1, V_nosf = -1; - int V_limit=-1, V_msegs=-1, V_mtot=-1, V_minsz=-1, V_segsz=-1; - int V_minsf=-1; - long long llp; - struct asyncopts {const char *opname; int minv; int *oploc; - const char *opmsg;} asopts[] = - { - {"force", -1, &V_force, ""}, - {"off", -1, &V_off, ""}, - {"nosf", -1, &V_nosf, ""}, - {"syncw", -1, &V_syncw, ""}, - {"limit", 0, &V_limit, "async limit"}, - {"segsize", 4096, &V_segsz, "async segsize"}, - {"maxsegs", 0, &V_msegs, "async maxsegs"}, - {"maxstalls", 0, &V_mstall,"async maxstalls"}, - {"maxtot", 0, &V_mtot, "async maxtot"}, - {"minsfsz", 1, &V_minsf, "async minsfsz"}, - {"minsize", 4096, &V_minsz, "async minsize"}}; - int numopts = sizeof(asopts)/sizeof(struct asyncopts); - - if (!(val = Config.GetWord())) - {eDest.Emsg("Config", "async option not specified"); return 1;} - - while (val) - {for (i = 0; i < numopts; i++) - if (!strcmp(val, asopts[i].opname)) - {if (asopts[i].minv >= 0 && !(val = Config.GetWord())) - {eDest.Emsg("Config","async",(char *)asopts[i].opname, - "value not specified"); - return 1; - } - if (asopts[i].minv > 0) - if (XrdOuca2x::a2sz(eDest,asopts[i].opmsg, val, &llp, - (long long)asopts[i].minv)) return 1; - else *asopts[i].oploc = (int)llp; - else if (asopts[i].minv == 0) - if (XrdOuca2x::a2i(eDest,asopts[i].opmsg,val,&ppp,1)) - return 1; - else *asopts[i].oploc = ppp; - else *asopts[i].oploc = 1; - break; - } - if (i >= numopts) - eDest.Emsg("Config", "Warning, invalid async option", val); - val = Config.GetWord(); - } - -// Make sure max values are consistent -// - if (V_limit > 0 && V_mtot > 0 && V_limit > V_mtot) - {eDest.Emsg("Config", "async limit may not be greater than maxtot"); - return 1; - } - -// Calculate the actual segment size -// - if (V_segsz > 0) - {i = BPool->Recalc(V_segsz); - if (!i) {eDest.Emsg("Config", "async segsize is too large"); return 1;} - if (i != V_segsz) - {char buff[64]; - sprintf(buff, "%d readjusted to %d", V_segsz, i); - eDest.Emsg("Config", "async segsize", buff); - V_segsz = i; - } - } - -// Establish async options -// - if (V_limit > 0) as_maxperlnk = V_limit; - if (V_msegs > 0) as_maxperreq = V_msegs; - if (V_mtot > 0) as_maxpersrv = V_mtot; - if (V_minsz > 0) as_miniosz = V_minsz; - if (V_segsz > 0) as_segsize = V_segsz; - if (V_mstall> 0) as_maxstalls = V_mstall; - if (V_force > 0) as_force = 1; - if (V_off > 0) as_noaio = 1; - if (V_syncw > 0) as_syncw = 1; - if (V_nosf > 0) as_nosf = 1; - if (V_minsf > 0) as_minsfsz = V_minsf; - - return 0; -} - -/******************************************************************************/ -/* x c k s u m */ -/******************************************************************************/ - -/* Function: xcksum - - Purpose: To parse the directive: chksum [chkcgi] [max ] [] - - max maximum number of simultaneous jobs - chkcgi Always check for checksum type in cgo info. - algorithm of checksum (e.g., md5). If more than one - checksum is supported then they should be listed with - each separated by a space. - the path of the program performing the checksum - If no path is given, the checksum is local. - - Output: 0 upon success or !0 upon failure. -*/ - -int XrdXrootdProtocol::xcksum(XrdOucStream &Config) -{ - static XrdOucProg *theProg = 0; - int (*Proc)(XrdOucStream *, char **, int) = 0; - XrdOucTList *tP, *algFirst = 0, *algLast = 0; - char *palg, prog[2048]; - int jmax = 4, anum[2] = {0,0}; - -// Get the algorithm name and the program implementing it -// - JobCKCGI = 0; - while ((palg = Config.GetWord()) && *palg != '/') - {if (!strcmp(palg,"chkcgi")) {JobCKCGI = 1; continue;} - if (strcmp(palg, "max")) - {XrdOucUtils::toLower(palg); - XrdOucTList *xalg = new XrdOucTList(palg, anum); anum[0]++; - if (algLast) algLast->next = xalg; - else algFirst = xalg; - algLast = xalg; - continue; - } - if (!(palg = Config.GetWord())) - {eDest.Emsg("Config", "chksum max not specified"); return 1;} - if (XrdOuca2x::a2i(eDest, "chksum max", palg, &jmax, 0)) return 1; - } - -// Verify we have an algoritm -// - if (!algFirst) - {eDest.Emsg("Config", "chksum algorithm not specified"); return 1;} - if (JobCKT) free(JobCKT); - JobCKT = strdup(algFirst->text); - -// Handle alternate checksums -// - while((tP = JobCKTLST)) {JobCKTLST = tP->next; delete tP;} - JobCKTLST = algFirst; - if (algFirst->next) JobCKCGI = 2; - -// Handle program if we have one -// - if (palg) - {int n = strlen(palg); - if (n+2 >= (int)sizeof(prog)) - {eDest.Emsg("Config", "cksum program too long"); return 1;} - strcpy(prog, palg); palg = prog+n; *palg++ = ' '; n = sizeof(prog)-n-1; - if (!Config.GetRest(palg, n)) - {eDest.Emsg("Config", "cksum parameters too long"); return 1;} - } else *prog = 0; - -// Check if we have a program. If not, then this will be a local checksum and -// the algorithm will be verified after we load the filesystem. -// - if (*prog) JobLCL = 0; - else { JobLCL = 1; Proc = &CheckSum; strcpy(prog, "chksum");} - -// Set up the program and job -// - if (!theProg) theProg = new XrdOucProg(0); - if (theProg->Setup(prog, &eDest, Proc)) return 1; - if (JobCKS) delete JobCKS; - if (jmax) JobCKS = new XrdXrootdJob(Sched, theProg, "chksum", jmax); - else JobCKS = 0; - return 0; -} - -/******************************************************************************/ -/* x d i g */ -/******************************************************************************/ - -/* Function: xdig - - Purpose: To parse the directive: diglib * - - * use builtin digfs library (only one supported now). - parms parameters for digfs. - - Output: 0 upon success or !0 upon failure. -*/ - -int XrdXrootdProtocol::xdig(XrdOucStream &Config) -{ - char parms[4096], *val; - -// Get the path -// - if (!(val = Config.GetWord())) - {eDest.Emsg("Config", "digfslib not specified"); return 1;} - -// Make sure it refers to an internal one -// - if (strcmp(val, "*")) - {eDest.Emsg("Config", "builtin diglib not specified"); return 1;} - -// Grab the parameters -// - if (!Config.GetRest(parms, sizeof(parms))) - {eDest.Emsg("Config", "diglib parameters too long"); return 1;} - if (digParm) free(digParm); - digParm = strdup(parms); - -// All done -// - return 0; -} - -/******************************************************************************/ -/* x e x p */ -/******************************************************************************/ - -/* Function: xexp - - Purpose: To parse the directive: export [lock|nolock] [mwfiles] - - the path to be exported. - - Output: 0 upon success or !0 upon failure. -*/ - -int XrdXrootdProtocol::xexp(XrdOucStream &Config) -{ - char *val, pbuff[1024]; - int popt = 0; - -// Get the path -// - val = Config.GetWord(); - if (!val || !val[0]) - {eDest.Emsg("Config", "export path not specified"); return 1;} - strlcpy(pbuff, val, sizeof(pbuff)); - -// Get export lock option -// - while((val = Config.GetWord())) - { if (!strcmp( "nolock", val)) popt |= XROOTDXP_NOLK; - else if (!strcmp( "lock", val)) popt &= ~XROOTDXP_NOLK; - else if (!strcmp("mwfiles", val)) popt |= XROOTDXP_NOMWCHK; - else {Config.RetToken(); break;} - } - -// Add path to configuration -// - return xexpdo(pbuff, popt); -} - -/******************************************************************************/ - -int XrdXrootdProtocol::xexpdo(char *path, int popt) -{ - char *opaque; - int xopt; - -// Check if we are exporting a generic name -// - if (*path == '*') - {popt |= XROOTDXP_NOSLASH | XROOTDXP_NOCGI; - if (*(path+1)) - {if (*(path+1) == '?') popt &= ~XROOTDXP_NOCGI; - else {eDest.Emsg("Config","invalid export path -",path);return 1;} - } - XPList.Set(popt, path); - return 0; - } - -// Make sure path start with a slash -// - if (rpCheck(path, &opaque)) - {eDest.Emsg("Config", "non-absolute export path -", path); return 1;} - -// Record the path -// - if (!(xopt = Squash(path)) || xopt != (popt|XROOTDXP_OK)) - XPList.Insert(path, popt); - return 0; -} - -/******************************************************************************/ -/* x f s l */ -/******************************************************************************/ - -/* Function: xfsl - - Purpose: To parse the directive: fslib [throttle | [-2] ] - {default | [-2] } - - -2 Uses version2 of the plugin initializer. - This is ignored now because it's always done. - throttle load libXrdThrottle.so as the head interface. - load the named library as the head interface. - default load libXrdOfs.so ro libXrdPss.so as the tail - interface. This is the default. - load the named library as the tail interface. - - Output: 0 upon success or !0 upon failure. -*/ - -int XrdXrootdProtocol::xfsl(XrdOucStream &Config) -{ - char *val; - -// Clear storage pointers -// - if (FSLib[0]) {free(FSLib[0]); FSLib[0] = 0;} - if (FSLib[1]) {free(FSLib[1]); FSLib[1] = 0;} - FSLvn[0] = FSLvn[1] = 0; - -// Get the path -// - if (!(val = Config.GetWord())) - {eDest.Emsg("Config", "fslib not specified"); return 1;} - -// Check if this is "thottle" -// - if (!strcmp("throttle", val)) - {FSLib[1] = strdup("libXrdThrottle.so"); - if (!(val = Config.GetWord())) - {eDest.Emsg("Config","fslib throttle target library not specified"); - return 1; - } - return xfsL(Config, val, 0); - } - -// Check for default or default library, the common case -// - if (xfsL(Config, val, 1)) return 1; - if (!FSLib[1]) return 0; - -// If we dont have another token, then demote the previous library -// - if (!(val = Config.GetWord())) - {FSLib[0] = FSLib[1]; FSLib[1] = 0; - FSLvn[0] = FSLvn[1]; FSLvn[1] = 0; - return 0; - } - -// Check for default or default library, the common case -// - return xfsL(Config, val, 0); -} - -/******************************************************************************/ - -int XrdXrootdProtocol::xfsL(XrdOucStream &Config, char *val, int lix) -{ - char *Slash; - int lvn = 0; - -// Check if this is a version token -// - if (!strcmp(val, "-2")) - {lvn = 2; - if (!(val = Config.GetWord())) - {eDest.Emsg("Config", "fslib not specified"); return 1;} - } - -// We will play fast and furious with the syntax as "default" should not be -// prefixed with a version number but will let that pass. -// - if (!strcmp("default", val)) return 0; - -// If this is the "standard" name tell the user that we are ignoring this lib. -// Otherwise, record the path and return. -// - if (!(Slash = rindex(val, '/'))) Slash = val; - else Slash++; - if (!strcmp(Slash, "libXrdOfs.so")) - eDest.Say("Config warning: 'fslib libXrdOfs.so' is actually built-in."); - else {FSLib[lix] = strdup(val); FSLvn[lix] = lvn;} - return 0; -} - -/******************************************************************************/ -/* x f s o */ -/******************************************************************************/ - -/* Function: xfso - - Purpose: To parse the directive: fsoverload [options] - - options: [[no]bypass] [redirect :[%:]] - [stall ] - - bypass If path is a forwarding path, redirect client to the - location specified in the path to bypass this server. - The default is nobypass. - redirect Redirect the request to the specified destination. - stall Stall the client seconds. The default is 33. -*/ - -int XrdXrootdProtocol::xfso(XrdOucStream &Config) -{ - static const int rHLen = 264; - char rHost[2][rHLen], *hP[2] = {0,0}, *val; - int rPort[2], bypass = -1, stall = -1; - -// Process all of the options -// - while((val = Config.GetWord()) && *val) - { if (!strcmp(val, "bypass")) bypass = 1; - else if (!strcmp(val, "nobypass")) bypass = 0; - else if (!strcmp(val, "redirect")) - {val = Config.GetWord(); - if (!xred_php(val, hP, rPort)) return 1; - for (int i = 0; i < 2; i++) - {if (!hP[i]) rHost[i][0] = 0; - else {strlcpy(rHost[i], hP[i], rHLen); - hP[i] = rHost[i]; - } - } - } - else if (!strcmp(val, "stall")) - {if (!(val = Config.GetWord()) || !(*val)) - {eDest.Emsg("Config", "stall value not specified"); - return 1; - } - if (XrdOuca2x::a2tm(eDest,"stall",val,&stall,0,32767)) - return 1; - } - else {eDest.Emsg("config","invalid fsoverload option",val); return 1;} - } - -// Set all specified values -// - if (bypass >= 0) OD_Bypass = (bypass ? true : false); - if (stall >= 0) OD_Stall = stall; - if (hP[0]) - {if (Route[RD_ovld].Host[0]) free(Route[RD_ovld].Host[0]); - if (Route[RD_ovld].Host[1]) free(Route[RD_ovld].Host[1]); - Route[RD_ovld].Host[0] = strdup(hP[0]); - Route[RD_ovld].Port[0] = rPort[0]; - Route[RD_ovld].RDSz[0] = strlen(hP[0]); - if (hP[1]) - {Route[RD_ovld].Host[1] = strdup(hP[1]); - Route[RD_ovld].Port[1] = rPort[1]; - Route[RD_ovld].RDSz[1] = strlen(hP[1]); - } else { - Route[RD_ovld].Host[1] = Route[RD_ovld].Host[0]; - Route[RD_ovld].Port[1] = Route[RD_ovld].Port[0]; - Route[RD_ovld].RDSz[1] = Route[RD_ovld].RDSz[0]; - } - OD_Redir = true; - } else OD_Redir = false; - - return 0; -} - -/******************************************************************************/ -/* x l o g */ -/******************************************************************************/ - -/* Function: xlog - - Purpose: To parse the directive: log - - the blank separated list of events to log. - - Output: 0 upon success or 1 upon failure. -*/ - -int XrdXrootdProtocol::xlog(XrdOucStream &Config) -{ - char *val; - static struct logopts {const char *opname; int opval;} lgopts[] = - { - {"all", -1}, - {"disc", SYS_LOG_02}, - {"login", SYS_LOG_01} - }; - int i, neg, lgval = -1, numopts = sizeof(lgopts)/sizeof(struct logopts); - - if (!(val = Config.GetWord())) - {eDest.Emsg("config", "log option not specified"); return 1;} - while (val) - {if ((neg = (val[0] == '-' && val[1]))) val++; - for (i = 0; i < numopts; i++) - {if (!strcmp(val, lgopts[i].opname)) - {if (neg) lgval &= ~lgopts[i].opval; - else lgval |= lgopts[i].opval; - break; - } - } - if (i >= numopts) eDest.Emsg("config","invalid log option",val); - val = Config.GetWord(); - } - eDest.setMsgMask(lgval); - return 0; -} - -/******************************************************************************/ -/* x m o n */ -/******************************************************************************/ - -/* Function: xmon - - Purpose: Parse directive: monitor [all] [auth] [flush [io] ] - [fstat [lfn] [ops] [ssq] [xfr ] - [ident ] [mbuff ] [rbuff ] - [rnums ] [window ] - dest [Events] - - Events: [files] [fstat] [info] [io] [iov] [redir] [user] - - all enables monitoring for all connections. - auth add authentication information to "user". - flush [io] time (seconds, M, H) between auto flushes. When - io is given applies only to i/o events. - fstat produces an "f" stream for open & close events - specifies the flush interval (also see xfr) - lfn - adds lfn to the open event - ops - adds the ops record when the file is closed - ssq - computes the sum of squares for the ops rec - xfr - inserts i/o stats for open files every - *. Minimum is 1. - ident time (seconds, M, H) between identification records. - mbuff size of message buffer for event trace monitoring. - rbuff size of message buffer for redirection monitoring. - rnums bumber of redirections monitoring streams. - window time (seconds, M, H) between timing marks. - dest specified routing information. Up to two dests - may be specified. - files only monitors file open/close events. - fstats vectors the "f" stream to the destination - info monitors client appid and info requests. - io monitors I/O requests, and files open/close events. - iov like I/O but also unwinds vector reads. - redir monitors request redirections - user monitors user login and disconnect events. - where monitor records are to be sentvia UDP. - - Output: 0 upon success or !0 upon failure. Ignored by master. -*/ -int XrdXrootdProtocol::xmon(XrdOucStream &Config) -{ static const char *mrMsg[] = {"monitor mbuff value not specified", - "monitor rbuff value not specified", - "monitor mbuff", "monitor rbuff" - }; - char *val = 0, *cp, *monDest[2] = {0, 0}; - long long tempval; - int i, monFlash = 0, monFlush=0, monMBval=0, monRBval=0, monWWval=0; - int monIdent = 3600, xmode=0, monMode[2] = {0, 0}, mrType, *flushDest; - int monRnums = 0, monFSint = 0, monFSopt = 0, monFSion = 0; - int haveWord = 0; - - while(haveWord || (val = Config.GetWord())) - {haveWord = 0; - if (!strcmp("all", val)) xmode = XROOTD_MON_ALL; - else if (!strcmp("auth", val)) - monMode[0] = monMode[1] = XROOTD_MON_AUTH; - else if (!strcmp("flush", val)) - {if ((val = Config.GetWord()) && !strcmp("io", val)) - { flushDest = &monFlash; val = Config.GetWord();} - else flushDest = &monFlush; - if (!val) - {eDest.Emsg("Config", "monitor flush value not specified"); - return 1; - } - if (XrdOuca2x::a2tm(eDest,"monitor flush",val, - flushDest,1)) return 1; - } - else if (!strcmp("fstat",val)) - {if (!(val = Config.GetWord())) - {eDest.Emsg("Config", "monitor fstat value not specified"); - return 1; - } - if (XrdOuca2x::a2tm(eDest,"monitor fstat",val, - &monFSint,0)) return 1; - while((val = Config.GetWord())) - if (!strcmp("lfn", val)) monFSopt |= XROOTD_MON_FSLFN; - else if (!strcmp("ops", val)) monFSopt |= XROOTD_MON_FSOPS; - else if (!strcmp("ssq", val)) monFSopt |= XROOTD_MON_FSSSQ; - else if (!strcmp("xfr", val)) - {if (!(val = Config.GetWord())) - {eDest.Emsg("Config", "monitor fstat xfr count not specified"); - return 1; - } - if (XrdOuca2x::a2i(eDest,"monitor fstat io count", - val, &monFSion,1)) return 1; - monFSopt |= XROOTD_MON_FSXFR; - } - else {haveWord = 1; break;} - } - else if (!strcmp("mbuff",val) || !strcmp("rbuff",val)) - {mrType = (*val == 'r'); - if (!(val = Config.GetWord())) - {eDest.Emsg("Config", mrMsg[mrType]); return 1;} - if (XrdOuca2x::a2sz(eDest,mrMsg[mrType+2], val, - &tempval, 1024, 65536)) return 1; - if (mrType) monRBval = static_cast(tempval); - else monMBval = static_cast(tempval); - } - else if (!strcmp("ident", val)) - {if (!(val = Config.GetWord())) - {eDest.Emsg("Config", "monitor ident value not specified"); - return 1; - } - if (XrdOuca2x::a2tm(eDest,"monitor ident",val, - &monIdent,0)) return 1; - } - else if (!strcmp("rnums", val)) - {if (!(val = Config.GetWord())) - {eDest.Emsg("Config", "monitor rnums value not specified"); - return 1; - } - if (XrdOuca2x::a2i(eDest,"monitor rnums",val, &monRnums,1, - XrdXrootdMonitor::rdrMax)) return 1; - } - else if (!strcmp("window", val)) - {if (!(val = Config.GetWord())) - {eDest.Emsg("Config", "monitor window value not specified"); - return 1; - } - if (XrdOuca2x::a2tm(eDest,"monitor window",val, - &monWWval,1)) return 1; - } - else break; - } - - if (!val) {eDest.Emsg("Config", "monitor dest not specified"); return 1;} - - for (i = 0; i < 2; i++) - {if (strcmp("dest", val)) break; - while((val = Config.GetWord())) - if (!strcmp("files",val)) monMode[i] |= XROOTD_MON_FILE; - else if (!strcmp("fstat",val)) monMode[i] |= XROOTD_MON_FSTA; - else if (!strcmp("info", val)) monMode[i] |= XROOTD_MON_INFO; - else if (!strcmp("io", val)) monMode[i] |= XROOTD_MON_IO; - else if (!strcmp("iov", val)) monMode[i] |= (XROOTD_MON_IO - |XROOTD_MON_IOV); - else if (!strcmp("redir",val)) monMode[i] |= XROOTD_MON_REDR; - else if (!strcmp("user", val)) monMode[i] |= XROOTD_MON_USER; - else break; - if (!val) {eDest.Emsg("Config","monitor dest value not specified"); - return 1; - } - if (!(cp = index(val, (int)':')) || !atoi(cp+1)) - {eDest.Emsg("Config","monitor dest port missing or invalid in",val); - return 1; - } - monDest[i] = strdup(val); - if (!(val = Config.GetWord())) break; - } - - if (val) - {if (!strcmp("dest", val)) - eDest.Emsg("Config", "Warning, a maximum of two dest values allowed."); - else eDest.Emsg("Config", "Warning, invalid monitor option", val); - } - -// Make sure dests differ -// - if (monDest[0] && monDest[1] && !strcmp(monDest[0], monDest[1])) - {eDest.Emsg("Config", "Warning, monitor dests are identical."); - monMode[0] |= monMode[1]; monMode[1] = 0; - free(monDest[1]); monDest[1] = 0; - } - -// Add files option if I/O is enabled -// - if (monMode[0] & XROOTD_MON_IO) monMode[0] |= XROOTD_MON_FILE; - if (monMode[1] & XROOTD_MON_IO) monMode[1] |= XROOTD_MON_FILE; - -// If ssq was specified, make sure we support IEEE754 floating point -// -#if !defined(__solaris__) || !defined(_IEEE_754) - if (monFSopt & XROOTD_MON_FSSSQ && !(std::numeric_limits::is_iec559)) - {monFSopt &= !XROOTD_MON_FSSSQ; - eDest.Emsg("Config","Warning, 'fstat ssq' ignored; platform does not " - "use IEEE754 floating point."); - } -#endif - -// Set the monitor defaults -// - XrdXrootdMonitor::Defaults(monMBval, monRBval, monWWval, - monFlush, monFlash, monIdent, monRnums, - monFSint, monFSopt, monFSion); - - if (monDest[0]) monMode[0] |= (monMode[0] ? xmode : XROOTD_MON_FILE|xmode); - if (monDest[1]) monMode[1] |= (monMode[1] ? xmode : XROOTD_MON_FILE|xmode); - XrdXrootdMonitor::Defaults(monDest[0],monMode[0],monDest[1],monMode[1]); - - return 0; -} - -/******************************************************************************/ -/* x p i d f */ -/******************************************************************************/ - -/* Function: xpidf - - Purpose: To parse the directive: pidpath - - the path where the pid file is to be created. - - Output: 0 upon success or !0 upon failure. -*/ - -int XrdXrootdProtocol::xpidf(XrdOucStream &Config) -{ - char *val; - -// Get the path -// - val = Config.GetWord(); - if (!val || !val[0]) - {eDest.Emsg("Config", "pidpath not specified"); return 1;} - -// Record the path -// - if (pidPath) free(pidPath); - pidPath = strdup(val); - return 0; -} - -/******************************************************************************/ -/* x p r e p */ -/******************************************************************************/ - -/* Function: xprep - - Purpose: To parse the directive: prep [keep ] [scrub ] - [logdir ] - keep time (seconds, M, H) to keep logdir entries. - scrub time (seconds, M, H) between logdir scrubs. - logdir the absolute path to the prepare log directory. - - Output: 0 upon success or !0 upon failure. Ignored by master. -*/ -int XrdXrootdProtocol::xprep(XrdOucStream &Config) -{ int rc, keep = 0, scrub=0; - char *ldir=0,*val,buff[1024]; - - if (!(val = Config.GetWord())) - {eDest.Emsg("Config", "prep options not specified"); return 1;} - - do { if (!strcmp("keep", val)) - {if (!(val = Config.GetWord())) - {eDest.Emsg("Config", "prep keep value not specified"); - return 1; - } - if (XrdOuca2x::a2tm(eDest,"prep keep int",val,&keep,1)) return 1; - } - else if (!strcmp("scrub", val)) - {if (!(val = Config.GetWord())) - {eDest.Emsg("Config", "prep scrub value not specified"); - return 1; - } - if (XrdOuca2x::a2tm(eDest,"prep scrub",val,&scrub,0)) return 1; - } - else if (!strcmp("logdir", val)) - {if (!(ldir = Config.GetWord())) - {eDest.Emsg("Config", "prep logdir value not specified"); - return 1; - } - } - else eDest.Emsg("Config", "Warning, invalid prep option", val); - } while((val = Config.GetWord())); - -// Set the values -// - if (scrub || keep) XrdXrootdPrepare::setParms(scrub, keep); - if (ldir) - if ((rc = XrdOucUtils::genPath(buff, sizeof(buff), ldir, myInst)) < 0 - || (rc = XrdOucUtils::makePath(buff, XrdOucUtils::pathMode)) < 0 - || (rc = XrdXrootdPrepare::setParms(buff)) < 0) - {eDest.Emsg("Config", rc, "process logdir", ldir); - return 1; - } - return 0; -} - -/******************************************************************************/ -/* x r e d */ -/******************************************************************************/ - -/* Function: xred - - Purpose: To parse the directive: redirect :[%:] - {|[?]} - - are one or more of the following functions that will - be immediately redirected to :. Each function - may be prefixed by a minus sign to disable redirection. - - chmod dirlist locate mkdir mv prepare rm rmdir stat - - redirects the client when an attempt is made to open - one of absolute . Up to 4 different redirect - combinations may be specified. When prefixed by "?" - then the redirect applies to any operation on the path - that results in an ENOENT error. - - Output: 0 upon success or !0 upon failure. -*/ - -int XrdXrootdProtocol::xred(XrdOucStream &Config) -{ - static struct rediropts {const char *opname; RD_func opval;} rdopts[] = - { - {"chmod", RD_chmod}, - {"chksum", RD_chksum}, - {"dirlist", RD_dirlist}, - {"locate", RD_locate}, - {"mkdir", RD_mkdir}, - {"mv", RD_mv}, - {"prepare", RD_prepare}, - {"prepstage",RD_prepstg}, - {"rm", RD_rm}, - {"rmdir", RD_rmdir}, - {"stat", RD_stat}, - {"trunc", RD_trunc} - }; - static const int rHLen = 264; - char rHost[2][rHLen], *hP[2], *val; - int i, k, neg, numopts = sizeof(rdopts)/sizeof(struct rediropts); - int rPort[2], isQ = 0; - -// Get the host and port -// - val = Config.GetWord(); - if (!xred_php(val, hP, rPort)) return 1; - -// Copy out he values as the target variable will be lost -// - for (i = 0; i < 2; i++) - {if (!hP[i]) rHost[i][0] = 0; - else {strlcpy(rHost[i], hP[i], rHLen); - hP[i] = rHost[i]; - } - } - -// Set all redirect target functions -// - if (!(val = Config.GetWord())) - {eDest.Emsg("config", "redirect option not specified"); return 1;} - - if (*val == '/' || (isQ = ((*val == '?') || !strcmp(val,"enoent")))) - {if (isQ) - {RQLxist = 1; - if (!(val = Config.GetWord())) - {eDest.Emsg("Config", "redirect path not specified."); - return 1; - } - if (*val != '/') - {eDest.Emsg("Config", "non-absolute redirect path -", val); - return 1; - } - } - for (k = static_cast(RD_open1); k < RD_Num; k++) - if (xred_xok(k, hP, rPort)) break; - if (k >= RD_Num) - {eDest.Emsg("Config", "too many diffrent path redirects"); return 1;} - xred_set(RD_func(k), hP, rPort); - do {if (isQ) RQList.Insert(val, k, 0); - else RPList.Insert(val, k, 0); - if ((val = Config.GetWord()) && *val != '/') - {eDest.Emsg("Config", "non-absolute redirect path -", val); - return 1; - } - } while(val); - return 0; - } - - while (val) - {if (!strcmp(val, "all")) - {for (i = 0; i < numopts; i++) - xred_set(rdopts[i].opval, hP, rPort); - } - else {if ((neg = (val[0] == '-' && val[1]))) val++; - for (i = 0; i < numopts; i++) - {if (!strcmp(val, rdopts[i].opname)) - {if (neg) xred_set(rdopts[i].opval, 0, 0); - else xred_set(rdopts[i].opval, hP, rPort); - break; - } - } - if (i >= numopts) - eDest.Emsg("config", "invalid redirect option", val); - } - val = Config.GetWord(); - } - return 0; -} - -bool XrdXrootdProtocol::xred_php(char *val, char *hP[2], int rPort[2]) -{ - char *pp; - -// Make sure we have a value -// - if (!val || !(*val)) - {eDest.Emsg("config", "redirect option not specified"); return false;} - -// Check if we have two hosts here -// - hP[0] = val; - if (!(pp = index(val, '%'))) hP[1] = 0; - else {hP[1] = pp+1; *pp = 0;} - -// Verify corectness here -// - if (!(*val) || (hP[1] && !*hP[1])) - {eDest.Emsg("Config", "malformed redirect host specification"); - return false; - } - -// Process the hosts -// - for (int i = 0; i < 2; i++) - {if (!(val = hP[i])) break; - if (!val || !val[0] || val[0] == ':') - {eDest.Emsg("Config", "redirect host not specified"); return false;} - if (!(pp = rindex(val, ':'))) - {eDest.Emsg("Config", "redirect port not specified"); return false;} - if (!(rPort[i] = atoi(pp+1))) - {eDest.Emsg("Config", "redirect port is invalid"); return false;} - *pp = '\0'; - } - -// All done -// - return true; -} - -void XrdXrootdProtocol::xred_set(RD_func func, char *rHost[2], int rPort[2]) -{ - -// Reset static redirection -// - if (Route[func].Host[0]) free(Route[func].Host[0]); - if (Route[func].Host[0] != Route[func].Host[1]) free(Route[func].Host[1]); - - if (rHost) - {Route[func].Host[0] = strdup(rHost[0]); - Route[func].Port[0] = rPort[0]; - } else { - Route[func].Host[0] = Route[func].Host[1] = 0; - Route[func].Port[0] = Route[func].Port[1] = 0; - return; - } - - if (!rHost[1]) - {Route[func].Host[1] = Route[func].Host[0]; - Route[func].Port[1] = Route[func].Port[0]; - } else { - Route[func].Host[1] = strdup(rHost[1]); - Route[func].Port[1] = rPort[1]; - } -} - -bool XrdXrootdProtocol::xred_xok(int func, char *rHost[2], int rPort[2]) -{ - if (!Route[func].Host[0]) return true; - - if (strcmp(Route[func].Host[0], rHost[0]) - || Route[func].Port[0] != rPort[0]) return false; - - if (!rHost[1]) return Route[func].Host[0] == Route[func].Host[1]; - - if (strcmp(Route[func].Host[1], rHost[1]) - || Route[func].Port[1] != rPort[1]) return false; - - return true; -} - -/******************************************************************************/ -/* x s e c l */ -/******************************************************************************/ - -/* Function: xsecl - - Purpose: To parse the directive: seclib {default | } - - the path of the security library to be used. - "default" uses the default security library. - - Output: 0 upon success or !0 upon failure. -*/ - -int XrdXrootdProtocol::xsecl(XrdOucStream &Config) -{ - char *val; - -// Get the path -// - val = Config.GetWord(); - if (!val || !val[0]) - {eDest.Emsg("Config", "XRootd seclib not specified"); return 1;} - -// Record the path -// - if (SecLib) free(SecLib); - SecLib = strdup(val); - return 0; -} - -/******************************************************************************/ -/* x t r a c e */ -/******************************************************************************/ - -/* Function: xtrace - - Purpose: To parse the directive: trace - - the blank separated list of events to trace. Trace - directives are cummalative. - - Output: 0 upon success or 1 upon failure. -*/ - -int XrdXrootdProtocol::xtrace(XrdOucStream &Config) -{ - char *val; - static struct traceopts {const char *opname; int opval;} tropts[] = - { - {"all", TRACE_ALL}, - {"emsg", TRACE_EMSG}, - {"debug", TRACE_DEBUG}, - {"fs", TRACE_FS}, - {"login", TRACE_LOGIN}, - {"mem", TRACE_MEM}, - {"stall", TRACE_STALL}, - {"redirect", TRACE_REDIR}, - {"request", TRACE_REQ}, - {"response", TRACE_RSP} - }; - int i, neg, trval = 0, numopts = sizeof(tropts)/sizeof(struct traceopts); - - if (!(val = Config.GetWord())) - {eDest.Emsg("config", "trace option not specified"); return 1;} - while (val) - {if (!strcmp(val, "off")) trval = 0; - else {if ((neg = (val[0] == '-' && val[1]))) val++; - for (i = 0; i < numopts; i++) - {if (!strcmp(val, tropts[i].opname)) - {if (neg) trval &= ~tropts[i].opval; - else trval |= tropts[i].opval; - break; - } - } - if (i >= numopts) - eDest.Emsg("config", "invalid trace option", val); - } - val = Config.GetWord(); - } - XrdXrootdTrace->What = trval; - return 0; -} - -/******************************************************************************/ -/* x l i m i t */ -/******************************************************************************/ - -/* Function: xlimit - - Purpose: To parse the directive: limit [prepare ] [noerror] - - prepare The maximum number of prepares that are allowed - during the course of a single connection - - noerror When possible, do not issue an error when a limit - is hit. - - Output: 0 upon success or 1 upon failure. -*/ -int XrdXrootdProtocol::xlimit(XrdOucStream &Config) -{ - int plimit = -1; - const char *word; - -// Look for various limits set -// - while ( (word = Config.GetWord()) ) { - if (!strcmp(word, "prepare")) { - if (!(word = Config.GetWord())) - { - eDest.Emsg("Config", "'limit prepare' value not specified"); - return 1; - } - if (XrdOuca2x::a2i(eDest, "limit prepare", word, &plimit, 0)) { return 1; } - } else if (!strcmp(word, "noerror")) { - LimitError = false; - } - } - if (plimit >= 0) {PrepareLimit = plimit;} - return 0; -} diff --git a/src/XrdXrootd/XrdXrootdFile.cc b/src/XrdXrootd/XrdXrootdFile.cc deleted file mode 100644 index f2917e2be6f..00000000000 --- a/src/XrdXrootd/XrdXrootdFile.cc +++ /dev/null @@ -1,299 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d X r o o t d F i l e . c c */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include - -#include "XrdSys/XrdSysPthread.hh" -#include "XrdSfs/XrdSfsInterface.hh" -#include "XrdXrootd/XrdXrootdFile.hh" -#include "XrdXrootd/XrdXrootdFileLock.hh" -#include "XrdXrootd/XrdXrootdMonFile.hh" -#include "XrdXrootd/XrdXrootdMonitor.hh" -#define TRACELINK this -#include "XrdXrootd/XrdXrootdTrace.hh" - -/******************************************************************************/ -/* G l o b a l s */ -/******************************************************************************/ - -#ifndef NODEBUG -extern XrdOucTrace *XrdXrootdTrace; -#endif - - XrdXrootdFileLock *XrdXrootdFile::Locker; - - int XrdXrootdFile::sfOK = 1; - const char *XrdXrootdFile::TraceID = "File"; - const char *XrdXrootdFileTable::TraceID = "FileTable"; - -/******************************************************************************/ -/* x r d _ F i l e C l a s s */ -/******************************************************************************/ -/******************************************************************************/ -/* C o n s t r u c t o r */ -/******************************************************************************/ - -XrdXrootdFile::XrdXrootdFile(const char *id, XrdSfsFile *fp, char mode, - char async, int sfok, struct stat *sP) -{ - static XrdSysMutex seqMutex; - struct stat buf; - off_t mmSize; - - XrdSfsp = fp; - FileKey = strdup(fp->FName()); - mmAddr = 0; - FileMode = mode; - AsyncMode= async; - ID = id; - - Stats.Init(); - -// Get the file descriptor number (none if not a regular file) -// - if (fp->fctl(SFS_FCTL_GETFD, 0, fp->error) != SFS_OK) fdNum = -1; - else fdNum = fp->error.getErrInfo(); - sfEnabled = (sfOK && sfok && (fdNum >= 0||fdNum==(int)SFS_SFIO_FDVAL) ? 1:0); - -// Determine if file is memory mapped -// - if (fp->getMmap((void **)&mmAddr, mmSize) != SFS_OK) isMMapped = 0; - else {isMMapped = (mmSize ? 1 : 0); - Stats.fSize = static_cast(mmSize); - } - -// Get file status information (we need it) and optionally return it to caller -// - if (sP || !isMMapped) - {if (!sP) sP = &buf; - fp->stat(sP); - if (!isMMapped) Stats.fSize = static_cast(sP->st_size); - } - -// Develop a unique hash for this file. The key will not be longer than 33 bytes -// including the null character. We now use the filename to avoid plugin -// vagaries. We will keep the code here commented out for now. -// -// if (sP->st_dev != 0 || sP->st_ino != 0) -// {i = bin2hex( FileKey, (char *)&sP->st_dev, sizeof(sP->st_dev)); -// i = bin2hex(&FileKey[i],(char *)&sP->st_ino, sizeof(sP->st_ino)); -// } -// else if (fdNum > 0) -// {strcpy( FileKey, "fdno"); -// bin2hex(&FileKey[4], (char *)&fdNum, sizeof(fdNum)); -// } -// else {strcpy( FileKey, "sfsp"); -// bin2hex(&FileKey[4], (char *)&XrdSfsp, sizeof(XrdSfsp)); -// } -} - -/******************************************************************************/ -/* D e s t r u c t o r */ -/******************************************************************************/ - -XrdXrootdFile::~XrdXrootdFile() -{ - char *fn; - - if (XrdSfsp) {Locker->Unlock(this); - if (TRACING(TRACE_FS)) - {if (!(fn = (char *)XrdSfsp->FName())) fn = (char *)"?"; - TRACEI(FS, "closing " <Stats; - - if (monP) monP->Close(Stats.FileID, - Stats.xfr.read + Stats.xfr.readv, - Stats.xfr.write); - if (Stats.MonEnt != -1) XrdXrootdMonFile::Close(&Stats, false); - delete fp; // Will do the close - } -} - -/******************************************************************************/ -/* R e c y c l e */ -/******************************************************************************/ - -// WARNING! The object subject to this method must be serialized. There can -// be no active requests on link associated with this object at the time the -// destructor is called. The same restrictions apply to Add() and Del(). -// -void XrdXrootdFileTable::Recycle(XrdXrootdMonitor *monP) -{ - int i; - -// Delete all objects from the internal table (see warning) -// - FTfree = 0; - for (i = 0; i < XRD_FTABSIZE; i++) - if (FTab[i]) - {XrdXrootdFileStats &Stats = FTab[i]->Stats; - if (monP) monP->Close(Stats.FileID, - Stats.xfr.read+Stats.xfr.readv, - Stats.xfr.write); - if (Stats.MonEnt != -1) XrdXrootdMonFile::Close(&Stats, true); - delete FTab[i]; FTab[i] = 0; - } - -// Delete all objects from the external table (see warning) -// -if (XTab) - {for (i = 0; i < XTnum; i++) - {if (XTab[i]) - {XrdXrootdFileStats &Stats = XTab[i]->Stats; - if (monP) monP->Close(Stats.FileID, - Stats.xfr.read+Stats.xfr.readv, - Stats.xfr.write); - if (Stats.MonEnt != -1) XrdXrootdMonFile::Close(&Stats, true); - delete XTab[i]; - } - } - free(XTab); XTab = 0; XTnum = 0; XTfree = 0; - } - -// Delete this object -// - delete this; -} - -/******************************************************************************/ -/* P r i v a t e M e t h o d s */ -/******************************************************************************/ -/******************************************************************************/ -/* b i n 2 h e x */ -/******************************************************************************/ - -int XrdXrootdFile::bin2hex(char *outbuff, char *inbuff, int inlen) -{ - static char hv[] = "0123456789abcdef"; - int i, j = 0; - -// Skip leading zeroes -// - for (i = 0; i < inlen; i++) if (inbuff[i]) break; - if (i >= inlen) - {outbuff[0] = '0'; outbuff[1] = '\0'; return 1;} - -// Format the data -// - for ( ; i < inlen; i++) - {outbuff[j++] = hv[(inbuff[i] >> 4) & 0x0f]; - outbuff[j++] = hv[ inbuff[i] & 0x0f]; - } - outbuff[j] = '\0'; - return j; -} diff --git a/src/XrdXrootd/XrdXrootdFile.hh b/src/XrdXrootd/XrdXrootdFile.hh deleted file mode 100644 index 1aca2da2556..00000000000 --- a/src/XrdXrootd/XrdXrootdFile.hh +++ /dev/null @@ -1,126 +0,0 @@ -#ifndef _XROOTD_FILE_H_ -#define _XROOTD_FILE_H_ -/******************************************************************************/ -/* */ -/* X r d X r o o t d F i l e . h h */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -#include "XProtocol/XPtypes.hh" -#include "XrdXrootd/XrdXrootdFileStats.hh" - -/******************************************************************************/ -/* X r d X r o o t d F i l e */ -/******************************************************************************/ - -class XrdSfsFile; -class XrdXrootdFileLock; -class XrdXrootdMonitor; - -class XrdXrootdFile -{ -public: - -XrdSfsFile *XrdSfsp; // -> Actual file object -char *mmAddr; // Memory mapped location, if any -char *FileKey; // -> File hash name (actual file name now) -char FileMode; // 'r' or 'w' -char AsyncMode; // 1 -> if file in async r/w mode -char isMMapped; // 1 -> file is memory mapped -char sfEnabled; // 1 -> file is sendfile enabled -int fdNum; // File descriptor number if regular file -const char *ID; // File user - -XrdXrootdFileStats Stats; // File access statistics - -static void Init(XrdXrootdFileLock *lp, int sfok) {Locker = lp; sfOK = sfok;} - - XrdXrootdFile(const char *id, XrdSfsFile *fp, char mode='r', - char async='\0', int sfOK=0, struct stat *sP=0); - ~XrdXrootdFile(); - -private: -int bin2hex(char *outbuff, char *inbuff, int inlen); -static XrdXrootdFileLock *Locker; -static int sfOK; -static const char *TraceID; -}; - -/******************************************************************************/ -/* X r d X r o o t d F i l e T a b l e */ -/******************************************************************************/ - -// The before define the structure of the file table. We will have FTABSIZE -// internal table entries. We will then provide an external linear table -// that increases by FTABSIZE entries. There is one file table per link and -// it is owned by the base protocol object. -// -#define XRD_FTABSIZE 16 - -// WARNING! Manipulation (i.e., Add/Del/delete) of this object must be -// externally serialized at the link level. Only one thread -// may be active w.r.t this object during manipulation! -// -class XrdXrootdFileTable -{ -public: - - int Add(XrdXrootdFile *fp); - - void Del(XrdXrootdMonitor *monP, int fnum); - -inline XrdXrootdFile *Get(int fnum) - {if (fnum >= 0) - {if (fnum < XRD_FTABSIZE) return FTab[fnum]; - if (XTab && (fnum-XRD_FTABSIZE). */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdXrootd/XrdXrootdFile.hh" - -class XrdXrootdFileLock -{ -public: - -virtual int Lock(XrdXrootdFile *fp, int force=0) = 0; - -virtual void numLocks(XrdXrootdFile *fp, int &rcnt, int &wcnt) = 0; - -virtual int Unlock(XrdXrootdFile *fp) = 0; - - XrdXrootdFileLock() {} -virtual ~XrdXrootdFileLock() {} -}; -#endif diff --git a/src/XrdXrootd/XrdXrootdFileLock1.cc b/src/XrdXrootd/XrdXrootdFileLock1.cc deleted file mode 100644 index dddee686f1a..00000000000 --- a/src/XrdXrootd/XrdXrootdFileLock1.cc +++ /dev/null @@ -1,149 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d X r o o t d F i l e L o c k 1 . c c */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -#include "XrdOuc/XrdOucHash.hh" - -#include "XrdXrootd/XrdXrootdFileLock1.hh" - -/******************************************************************************/ -/* L o c a l C l a s s e s */ -/******************************************************************************/ - -class XrdXrootdFileLockInfo -{ -public: - -int numReaders; -int numWriters; - - XrdXrootdFileLockInfo(char mode) - {if ('r' == mode) {numReaders = 1; numWriters = 0;} - else {numReaders = 0; numWriters = 1;} - } - ~XrdXrootdFileLockInfo() {} -}; - -class XrdXrootdLockFileLock -{ -public: - - XrdXrootdLockFileLock(XrdSysMutex *mutex) - {mp = mutex; mp->Lock();} - ~XrdXrootdLockFileLock() - {mp->UnLock();} -private: -XrdSysMutex *mp; -}; - -/******************************************************************************/ -/* G l o b a l s */ -/******************************************************************************/ - -XrdOucHash XrdXrootdLockTable; - -XrdSysMutex XrdXrootdFileLock1::LTMutex; - -const char *XrdXrootdFileLock1::TraceID = "FileLock1"; - -/******************************************************************************/ -/* L o c k */ -/******************************************************************************/ - -int XrdXrootdFileLock1::Lock(XrdXrootdFile *fp, int force) -{ - XrdXrootdLockFileLock locker(<Mutex); - XrdXrootdFileLockInfo *lp; - -// See if we already have a lock on this file -// - if ((lp = XrdXrootdLockTable.Find(fp->FileKey))) - {if (fp->FileMode == 'r') - {if (lp->numWriters && !force) - return -lp->numWriters; - lp->numReaders++; - } else { - if ((lp->numReaders || lp->numWriters) && !force) - return (lp->numWriters ? -lp->numWriters : lp->numReaders); - lp->numWriters++; - } - return 0; - } - -// Item does not exist, add it to the table -// - XrdXrootdLockTable.Add(fp->FileKey, new XrdXrootdFileLockInfo(fp->FileMode)); - return 0; -} - -/******************************************************************************/ -/* */ -/* n u m L o c k s */ -/* */ -/******************************************************************************/ - -void XrdXrootdFileLock1::numLocks(XrdXrootdFile *fp, int &rcnt, int &wcnt) -{ - XrdXrootdLockFileLock locker(<Mutex); - XrdXrootdFileLockInfo *lp; - - if (!(lp = XrdXrootdLockTable.Find(fp->FileKey))) rcnt = wcnt = 0; - else {rcnt = lp->numReaders; wcnt = lp->numWriters;} -} - -/******************************************************************************/ -/* U n l o c k */ -/******************************************************************************/ - -int XrdXrootdFileLock1::Unlock(XrdXrootdFile *fp) -{ - XrdXrootdLockFileLock locker(<Mutex); - XrdXrootdFileLockInfo *lp; - -// See if we already have a lock on this file -// - if (!(lp = XrdXrootdLockTable.Find(fp->FileKey))) return 1; - -// Adjust the lock information -// - if (fp->FileMode == 'r') - {if (lp->numReaders == 0) return 1; - lp->numReaders--; - } else { - if (lp->numWriters == 0) return 1; - lp->numWriters--; - } - -// Delete the entry if we no longer need it -// - if (lp->numReaders == 0 && lp->numWriters == 0) - XrdXrootdLockTable.Del(fp->FileKey); - return 0; -} diff --git a/src/XrdXrootd/XrdXrootdFileLock1.hh b/src/XrdXrootd/XrdXrootdFileLock1.hh deleted file mode 100644 index 45b71ac5405..00000000000 --- a/src/XrdXrootd/XrdXrootdFileLock1.hh +++ /dev/null @@ -1,55 +0,0 @@ -#ifndef _XROOTD_FILELOCK1_H_ -#define _XROOTD_FILELOCK1_H_ -/******************************************************************************/ -/* */ -/* X r d X r o o t d F i l e L o c k 1 . h h */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdSys/XrdSysPthread.hh" -#include "XrdXrootd/XrdXrootdFile.hh" -#include "XrdXrootd/XrdXrootdFileLock.hh" - -// This class implements a single server per host lock manager by simply using -// an in-memory hash table to keep track of file locks. -// -class XrdXrootdFileLock1 : XrdXrootdFileLock -{ -public: - - int Lock(XrdXrootdFile *fp, int force=0); - - void numLocks(XrdXrootdFile *fp, int &rcnt, int &wcnt); - - int Unlock(XrdXrootdFile *fp); - - XrdXrootdFileLock1() {} - ~XrdXrootdFileLock1() {} // This object is never destroyed! -private: -static const char *TraceID; -static XrdSysMutex LTMutex; -}; -#endif diff --git a/src/XrdXrootd/XrdXrootdFileStats.hh b/src/XrdXrootd/XrdXrootdFileStats.hh deleted file mode 100644 index 6c599a82a38..00000000000 --- a/src/XrdXrootd/XrdXrootdFileStats.hh +++ /dev/null @@ -1,129 +0,0 @@ -#ifndef __XRDXROOTDFILESTATS__ -#define __XRDXROOTDFILESTATS__ -/******************************************************************************/ -/* */ -/* X r d X r o o t d F i l e S t a t s . h h */ -/* */ -/* (c) 2012 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdXrootd/XrdXrootdMonData.hh" - -class XrdXrootdFileStats -{ -public: - -kXR_unt32 FileID; // Unique file id used for monitoring -short MonEnt; // Set by mon: entry in reporting table or -1 -char monLvl; // Set by mon: level of data collection needed -char xfrXeq; // Transfer has occurred -long long fSize; // Size of file when opened -XrdXrootdMonStatXFR xfr; -XrdXrootdMonStatOPS ops; -struct {double read; // sum(read_size[i] **2) i = 1 to Ops.read - double readv; // sum(readv_size[i]**2) i = 1 to Ops.readv - double rsegs; // sum(readv_segs[i]**2) i = 1 to Ops.readv - double write; // sum(write_size[i]**2) i = 1 to Ops.write - } ssq; - -enum monLevel {monOff = 0, monOn = 1, monOps = 2, monSsq = 3}; - - void Init() - {FileID = 0; MonEnt = -1; monLvl = xfrXeq = 0; - memset(&xfr, 0, sizeof(xfr)); - memset(&ops, 0, sizeof(ops)); - ops.rsMin = 0x7fff; - ops.rdMin = ops.rvMin = ops.wrMin = 0x7fffffff; - ssq.read = ssq.readv = ssq.write = ssq.rsegs = 0.0; - }; - -inline void rdOps(int rsz) - {if (monLvl) - {xfr.read += rsz; ops.read++; xfrXeq = 1; - if (monLvl > 1) - {if (rsz < ops.rdMin) ops.rdMin = rsz; - if (rsz > ops.rdMax) ops.rdMax = rsz; - if (monLvl > 2) - ssq.read += static_cast(rsz) - * static_cast(rsz); - } - } - } - -inline void rvOps(int rsz, int ssz) - {if (monLvl) - {xfr.readv += rsz; ops.readv++; ops.rsegs += ssz; xfrXeq=1; - if (monLvl > 1) - {if (rsz < ops.rvMin) ops.rvMin = rsz; - if (rsz > ops.rvMax) ops.rvMax = rsz; - if (ssz < ops.rsMin) ops.rsMin = ssz; - if (ssz > ops.rsMax) ops.rsMax = ssz; - if (monLvl > 2) - {ssq.readv += static_cast(rsz) - * static_cast(rsz); - ssq.rsegs += static_cast(ssz) - * static_cast(ssz); - } - } - } - } - -inline void wrOps(int wsz) - {if (monLvl) - {xfr.write += wsz; ops.write++; xfrXeq = 1; - if (monLvl > 1) - {if (wsz < ops.wrMin) ops.wrMin = wsz; - if (wsz > ops.wrMax) ops.wrMax = wsz; - if (monLvl > 2) - ssq.write += static_cast(wsz) - * static_cast(wsz); - } - } - } - -inline void wvOps(int wsz, int ssz) {} -/* When we start reporting detail of writev's we will uncomment this - {if (monLvl) - {xfr.writev += wsz; ops.writev++; ops.wsegs += ssz; xfrXeq=1; - if (monLvl > 1) - {if (wsz < ops.wvMin) ops.wvMin = wsz; - if (wsz > ops.wvMax) ops.wvMax = wsz; - if (ssz < ops.wsMin) ops.wsMin = ssz; - if (ssz > ops.wsMax) ops.wsMax = ssz; - if (monLvl > 2) - {ssq.writev+= static_cast(wsz) - * static_cast(wsz); - ssq.wsegs += static_cast(ssz) - * static_cast(ssz); - } - } - } - } -*/ - XrdXrootdFileStats() {Init();} - ~XrdXrootdFileStats() {} -}; -#endif diff --git a/src/XrdXrootd/XrdXrootdJob.cc b/src/XrdXrootd/XrdXrootdJob.cc deleted file mode 100644 index 2c219a3f481..00000000000 --- a/src/XrdXrootd/XrdXrootdJob.cc +++ /dev/null @@ -1,676 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d X r o o t d J o b . c c */ -/* */ -/* (c) 2006 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include - -#include "Xrd/XrdLink.hh" -#include "Xrd/XrdScheduler.hh" -#include "XrdOuc/XrdOucProg.hh" -#include "XrdOuc/XrdOucStream.hh" -#include "XrdSys/XrdSysPlatform.hh" -#include "XrdXrootd/XrdXrootdJob.hh" -#include "XrdXrootd/XrdXrootdResponse.hh" -#include "XrdXrootd/XrdXrootdTrace.hh" -#include "XProtocol/XProtocol.hh" -#include "XProtocol/XPtypes.hh" - -/******************************************************************************/ -/* L o c a l C l a s s e s */ -/******************************************************************************/ - -class XrdXrootdJob2Do : public XrdJob -{ -public: -friend class XrdXrootdJob; - -void DoIt(); - -enum JobStatus {Job_Active, Job_Cancel, Job_Done, Job_Waiting}; - -JobStatus Status; // Job Status - - XrdXrootdJob2Do(XrdXrootdJob *job, - int jnum, - const char **args, - XrdXrootdResponse *Resp, - int opts); - ~XrdXrootdJob2Do(); - -private: -int addClient(XrdXrootdResponse *rp, int opts); -void delClient(XrdXrootdResponse *rp); -XrdOucTList *lstClient(void); -int verClient(int dodel=0); -void Redrive(void); -void sendResult(char *lp, int caned=0, int erc=0); - -static const int maxClients = 8; -struct {XrdLink *Link; - unsigned int Inst; - kXR_char streamid[2]; - char isSync; - } Client[maxClients]; - - int numClients; - - XrdOucStream jobStream; // -> Stream for job I/O - XrdXrootdJob *theJob; // -> Job description - char *theArgs[5]; // -> Program arguments (see XrdOucProg) - char *theResult; // -> The result - int JobNum; // Job Number - int JobRC; // Job kXR_ type return code - char JobMark; - char doRedrive; -}; - -/******************************************************************************/ -/* G l o b a l F u n c t i o n s */ -/******************************************************************************/ - -extern XrdOucTrace *XrdXrootdTrace; - -int XrdXrootdJobWaiting(XrdXrootdJob2Do *item, void *arg) -{ - return (item->Status == XrdXrootdJob2Do::Job_Waiting); -} - -/******************************************************************************/ -/* C l a s s X r d X r o o t d J o b 2 D o */ -/******************************************************************************/ -/******************************************************************************/ -/* C o n s t r u c t o r */ -/******************************************************************************/ - -XrdXrootdJob2Do::XrdXrootdJob2Do(XrdXrootdJob *job, - int jnum, - const char **args, - XrdXrootdResponse *resp, - int opts) - : XrdJob(job->JobName) -{ - int i; - for (i = 0; i < 5 && args[i]; i++) theArgs[i] = strdup(args[i]); - for ( ; i < 5; i++) theArgs[i] = (char *)0; - theJob = job; - JobRC = 0; - JobNum = jnum; - JobMark = 0; - numClients = 0; - theResult = 0; - doRedrive = 0; - Status = Job_Waiting; - addClient(resp, opts); -} - -/******************************************************************************/ -/* D e s t r u c t o r */ -/******************************************************************************/ - -XrdXrootdJob2Do::~XrdXrootdJob2Do() -{ - int i; - - for (i = 0; i < numClients; i++) - if (!Client[i].isSync) {sendResult(0, 1); break;} - - for (i = 0; i < 5; i++) - if (theArgs[i]) free(theArgs[i]); -} - -/******************************************************************************/ -/* D o I t */ -/******************************************************************************/ - -void XrdXrootdJob2Do::DoIt() -{ - XrdXrootdJob2Do *jp = 0; - char *lp = 0; - int i, rc = 0; - -// Obtain a lock to prevent status changes -// - theJob->myMutex.Lock(); - -// While we were waiting to run we may have been cancelled. If we were not then -// perform the actual function and get the result and send to any async clients -// - if (Status != Job_Cancel) - {if ((rc = theJob->theProg->Run(&jobStream, theArgs[1], theArgs[2], - theArgs[3], theArgs[4]))) - {Status = Job_Cancel; - lp = jobStream.GetLine(); - } - else {theJob->myMutex.UnLock(); - lp = jobStream.GetLine(); - rc = theJob->theProg->RunDone(jobStream); - theJob->myMutex.Lock(); - if ((rc && rc != -EPIPE) || (rc == -EPIPE && (!lp || !(*lp)))) - Status = Job_Cancel; - else if (Status != Job_Cancel) - {Status = Job_Done; - for (i = 0; i < numClients; i++) - if (!Client[i].isSync) {sendResult(lp); break;} - } - } - } - -// If the number of jobs > than the max allowed, then redrive a waiting job -// if in fact we represent a legitimate job slot (this could a phantom slot -// due to ourselves being cancelled. -// - if (doRedrive) - {if (theJob->numJobs > theJob->maxJobs) Redrive(); - theJob->numJobs--; - } - -// If there are no polling clients left or we have been cancelled, then we -// will delete ourselves and, if cancelled, send a notofication to everyone -// - if (Status != Job_Cancel && numClients) theResult = lp; - else {if (Status == Job_Cancel) sendResult(lp, (rc ? -1 : 1), rc); - jp = theJob->JobTable.Remove(JobNum); - } - -// At this point we may need to delete ourselves. If so, jp will not be zero. -// This must be the last action in this method. -// - theJob->myMutex.UnLock(); - if (jp) delete jp; -} - -/******************************************************************************/ -/* P r i v a t e M e t h o d s */ -/******************************************************************************/ -/******************************************************************************/ -/* a d d C l i e n t */ -/******************************************************************************/ - -int XrdXrootdJob2Do::addClient(XrdXrootdResponse *rp, int opts) -{ - XrdLink *lp = rp->theLink(); - unsigned int Inst = lp->Inst(); - int i; - -// Remove useless clients -// - if (numClients >= maxClients) verClient(); - -// See if we are already here -// - for (i = 0; i < numClients; i++) - if (lp == Client[i].Link && Inst == Client[i].Inst) return 0; - -// Add the client if we can -// - if (numClients >= maxClients) return -1; - Client[numClients].Link = lp; - Client[numClients].Inst = Inst; - if (opts & JOB_Sync) Client[numClients].isSync = 1; - else {rp->StreamID(Client[numClients].streamid); - Client[numClients].isSync = 0; - } - numClients++; - JobMark = 0; - return 1; -} - -/******************************************************************************/ -/* d e l C l i e n t */ -/******************************************************************************/ - -void XrdXrootdJob2Do::delClient(XrdXrootdResponse *rp) -{ - XrdLink *lp = rp->theLink(); - unsigned int Inst = lp->Inst(); - int i, j; - -// See if we are already here -// - for (i = 0; i < numClients; i++) - if (lp == Client[i].Link && Inst == Client[i].Inst) - {for (j = i+1; j < numClients; j++) Client[i++] = Client[j]; - numClients--; - break; - } -} - -/******************************************************************************/ -/* l s t C l i e n t */ -/******************************************************************************/ - -// Warning! The size of buff is large enough for the default number of clients -// per job element. -// -XrdOucTList *XrdXrootdJob2Do::lstClient() -{ - char State, buff[4096], *bp = buff; - int bsz, i, k; - -// Get the state pf the job element -// - switch(Status) - {case Job_Active: State = 'a'; break; - case Job_Cancel: State = 'c'; break; - case Job_Done: State = 'd'; break; - case Job_Waiting: State = 'w'; break; - default: State = 'u'; break; - }; - -// Insert the header (reserve 8 characters for the trailer) -// - bp = buff + sprintf(buff, "%c", State); - bsz = sizeof(buff) - (bp - buff) - 8; - -// Remove all clients from a job whose network connection is no longer valid -// - if (!numClients) bp++; - else for (i = 0; i < numClients; i++) - if (Client[i].Link && Client[i].Link->isInstance(Client[i].Inst)) - {if ((k = strlcpy(bp, Client[i].Link->ID, bsz)) >= bsz - || (bsz -= k) < 1) {bp++; break;} - bp += k; *bp = ' '; bp++; bsz--; - } - -// Insert trailer -// - if (*(bp-1) == ' ') bp--; - strcpy(bp, ""); - -// Return the text -// - return new XrdOucTList(buff, bp-buff+7); -} - -/******************************************************************************/ -/* v e r C l i e n t */ -/******************************************************************************/ - -int XrdXrootdJob2Do::verClient(int dodel) -{ - int i, j, k; - -// Remove all clients from a job whose network connection is no longer valid -// - for (i = 0; i < numClients; i++) - if (!Client[i].Link->isInstance(Client[i].Inst)) - {k = i; - for (j = i+1; j < numClients && j < maxClients; j++,k++) Client[k] = Client[j]; - numClients--; i--; - } - -// If no more clients, delete ourselves if safe to do so (caller has lock) -// - if (!numClients && dodel) - {XrdXrootdJob2Do *jp = theJob->JobTable.Remove(JobNum); - if (jp->Status == XrdXrootdJob2Do::Job_Waiting) theJob->numJobs--; - delete jp; - return 0; - } - return numClients; -} - -/******************************************************************************/ -/* R e d r i v e */ -/******************************************************************************/ - -void XrdXrootdJob2Do::Redrive() -{ - XrdXrootdJob2Do *jp; - int Start = 0; - -// Find the first waiting job -// - - while ((jp = theJob->JobTable.Apply(XrdXrootdJobWaiting, (void *)0, Start))) - if (jp->verClient(jp->JobMark > 0)) break; - else Start = jp->JobNum+1; - -// Schedule this job if we really have one here -// - if (jp) - {jp->Status = Job_Active; jp->doRedrive = 1; - theJob->Sched->Schedule((XrdJob *)jp); - } -} - -/******************************************************************************/ -/* s e n d R e s u l t */ -/******************************************************************************/ - -void XrdXrootdJob2Do::sendResult(char *lp, int caned, int jrc) -{ - static const char *TraceID = "sendResult"; - static const kXR_int32 Xcan = static_cast(htonl(kXR_Cancelled)); - XrdXrootdReqID ReqID; - struct iovec jobVec[6]; - XResponseType jobStat; - const char *trc, *tre; - kXR_int32 erc; - int j, i, dlen = 0, n = 1; - -// Format the message to be sent -// - if (!caned) - {jobStat = kXR_ok; trc = "ok"; - if (theArgs[0]) - { jobVec[n].iov_base = theArgs[0]; // 1 - dlen = jobVec[n].iov_len = strlen(theArgs[0]); n++; - jobVec[n].iov_base = (char *)" "; // 2 - dlen += jobVec[n].iov_len = 1; n++; - } - } else { - jobStat = kXR_error; trc = "error"; - if (caned > 0) {erc = Xcan; lp = (char *)"Cancelled by admin.";} - else {erc = (jrc ? XProtocol::mapError(jrc) : kXR_ServerError); - erc = static_cast(htonl(erc)); - if (!lp || !*lp) lp = (char *)"Program failed."; - } - jobVec[n].iov_base = (char *)&erc; - dlen = jobVec[n].iov_len = sizeof(erc); n++; // 3 - } - jobVec[n].iov_base = lp; // 4 - dlen += jobVec[n].iov_len = strlen(lp)+1; n++; - -// Send the response to each client waiting for it -// - j = 0; - for (i = 0; i < numClients; i++) - {if (!Client[i].isSync) - {ReqID.setID(Client[i].streamid, - Client[i].Link->FDnum(), Client[i].Link->Inst()); - tre = (XrdXrootdResponse::Send(ReqID, jobStat, jobVec, n, dlen) < 0 - ? "skipped" : "sent"); - TRACE(RSP, tre <<" async " <ID); - } else if (i != j) Client[j++] = Client[i]; - } - numClients = j; -} - -/******************************************************************************/ -/* C l a s s X r d X r o o t d J o b */ -/******************************************************************************/ -/******************************************************************************/ -/* C o n s t r u c t o r */ -/******************************************************************************/ - -XrdXrootdJob::XrdXrootdJob(XrdScheduler *schp, - XrdOucProg *pgm, - const char *jname, - int maxjobs) - : XrdJob("Job Scheduler"), - JobTable(maxjobs*3) -{ -// Initialize the base member here -// - Sched = schp; - theProg = pgm; - JobName = strdup(jname); - maxJobs = maxjobs; - numJobs = 0; - -// Schedule ourselves to run 15 minutes from now -// - schp->Schedule((XrdJob *)this, time(0) + (reScan)); -} - -/******************************************************************************/ -/* D e s t r u c t o r */ -/******************************************************************************/ - -// Note! There is no reliable way to delete this object because various -// unsynchronized threads may be pending at various break points. Fortunately, -// there really is no need to ever delete an object of this kind. - -XrdXrootdJob::~XrdXrootdJob() -{ - if (JobName) free(JobName); - myMutex.Lock(); - Sched->Cancel((XrdJob *)this); - myMutex.UnLock(); -} - -/******************************************************************************/ -/* C a n c e l */ -/******************************************************************************/ - -int XrdXrootdJob::Cancel(const char *jkey, XrdXrootdResponse *resp) -{ - XrdXrootdJob2Do *jp = 0; - int i, jNum, jNext = 0, numcaned = 0; - -// Lock our data -// - myMutex.Lock(); - -// Cancel a specific job if a key was passed -// - if (jkey) - {if ((jp = JobTable.Find(jkey))) - {numcaned = 1; - if (resp) {jp->delClient(resp); - if (!jp->numClients) CleanUp(jp); - } - else CleanUp(jp); - } - myMutex.UnLock(); - return numcaned; - } - -// Delete multiple jobs -// - while((jNum = JobTable.Next(jNext)) >= 0) - {jp = JobTable.Item(jNum); - if (resp) - {i = jp->numClients; - jp->delClient(resp); - if (i != jp->numClients) numcaned++; - if (!jp->numClients) CleanUp(jp); - } else { - CleanUp(jp); - numcaned++; - } - } - -// All done -// - myMutex.UnLock(); - return numcaned; -} - -/******************************************************************************/ -/* D o I t */ -/******************************************************************************/ - -void XrdXrootdJob::DoIt() -{ - int jNum, jNext = 0; - XrdXrootdJob2Do *jp; - -// Scan through all of the jobs looking for disconnected clients -// - while((jNum = JobTable.Next(jNext)) >= 0) - {myMutex.Lock(); - if ((jp = JobTable.Item(jNum))) - {if (jp->JobMark) {if (!jp->verClient()) CleanUp(jp);} - else jp->JobMark = 1; - } - myMutex.UnLock(); - } - -// Schedule ourselves to run 15 minutes from now -// - Sched->Schedule((XrdJob *)this, time(0) + (reScan)); -} - -/******************************************************************************/ -/* L i s t */ -/******************************************************************************/ - -// Output: %jobkey%status%clientid ... .... -// -XrdOucTList *XrdXrootdJob::List() -{ - char *jkey, buff[1024]; - int tlen, jNum, jNext = 0; - XrdXrootdJob2Do *jp; - XrdOucTList *tF = 0, *tL = 0, *tp; - -// Scan through all of the jobs listing each, in turn -// - while((jNum = JobTable.Next(jNext)) >= 0) - {myMutex.Lock(); - if ((jp = JobTable.Item(jNum, &jkey)) && (tp = jp->lstClient())) - {tlen = sprintf(buff, "%s", JobName, jkey); - if (tL) tL->next = new XrdOucTList(buff, tlen, tp); - else tF = new XrdOucTList(buff, tlen, tp); - tL = tp->next = new XrdOucTList("", 6); - } - myMutex.UnLock(); - } - -// Return the whole schmear -// - return tF; -} - -/******************************************************************************/ -/* S c h e d u l e */ -/******************************************************************************/ - -int XrdXrootdJob::Schedule(const char *jkey, - const char **args, - XrdXrootdResponse *resp, - int Opts) -{ - XrdXrootdJob2Do *jp; - const char *msg = "Job resources currently not available."; - int jobNum, rc, isSync = Opts & JOB_Sync; - -// Make sure we have a target -// - if (!jkey || !(*jkey)) - return resp->Send(kXR_ArgMissing, "Job target not specified."); - -// First find if this is a duplicate or create a new one -// - myMutex.Lock(); - if (!(Opts & JOB_Unique) && jkey && (jp = JobTable.Find(jkey))) - {if (jp->Status == XrdXrootdJob2Do::Job_Done) - {rc = sendResult(resp, args[0], jp); - myMutex.UnLock(); - return rc; - } - if (jp->addClient(resp, Opts) < 0) isSync = 1; - else msg = "Job scheduled."; - } else { - if ((jobNum = JobTable.Alloc()) < 0) isSync = 1; - else {if ((jp = new XrdXrootdJob2Do(this, jobNum, args, resp, Opts))) - {JobTable.Insert(jp, jkey, jobNum); - if (numJobs < maxJobs) - {Sched->Schedule((XrdJob *)jp); - jp->Status = XrdXrootdJob2Do::Job_Active; - jp->doRedrive = 1; - } - numJobs++; msg = "Job Scheduled"; - } - } - } - -// Tell the client to wait -// - if (isSync) rc = resp->Send(kXR_wait, 30, msg); - else rc = resp->Send(kXR_waitresp, 600, "Job scheduled."); - myMutex.UnLock(); - return rc; -} - -/******************************************************************************/ -/* P r i v a t e M e t h o d s */ -/******************************************************************************/ -/******************************************************************************/ -/* C l e a n U p */ -/******************************************************************************/ - -void XrdXrootdJob::CleanUp(XrdXrootdJob2Do *jp) -{ - int theStatus = jp->Status; - -// Now we have to be careful. If the job is waiting or completed schedule -// it for cancellation. If it's active then kill the associated process. The -// thread waiting for the result will see the cancellation. Otherwise, it -// already has been cancelled and is in the scheduled queue. -// - jp->Status = XrdXrootdJob2Do::Job_Cancel; - if (theStatus == XrdXrootdJob2Do::Job_Waiting - || theStatus == XrdXrootdJob2Do::Job_Done) - Sched->Schedule((XrdJob *)jp); - else{if (theStatus == XrdXrootdJob2Do::Job_Active) jp->jobStream.Drain();} - - if (theStatus == XrdXrootdJob2Do::Job_Waiting) numJobs--; -} - -/******************************************************************************/ -/* s e n d R e s u l t */ -/******************************************************************************/ - -int XrdXrootdJob::sendResult(XrdXrootdResponse *resp, - const char *rpfx, - XrdXrootdJob2Do *job) -{ - struct iovec jobResp[4]; - int dlen, i, rc; - -// Send an error result if no result is present -// - if (!(job->theResult)) rc = resp->Send(kXR_ServerError,"Program failed"); - else {if (!rpfx) {dlen = 0; i = 1;} - else { jobResp[1].iov_base = (char *)rpfx; - dlen = jobResp[1].iov_len = strlen(rpfx); - jobResp[2].iov_base = (char *)" "; - dlen += jobResp[2].iov_len = 1; - i = 3; - } - jobResp[i].iov_base = job->theResult; - dlen += jobResp[i].iov_len = strlen(job->theResult); - rc = resp->Send(jobResp, i+1, dlen); - } - -// Remove the client from the job. Check if clean-up is required -// - job->delClient(resp); - if (!job->numClients) CleanUp(job); - -// All done -// - return rc; -} diff --git a/src/XrdXrootd/XrdXrootdJob.hh b/src/XrdXrootd/XrdXrootdJob.hh deleted file mode 100644 index c95a381eea2..00000000000 --- a/src/XrdXrootd/XrdXrootdJob.hh +++ /dev/null @@ -1,95 +0,0 @@ -#ifndef __XRDXROOTDJOB_HH_ -#define __XRDXROOTDJOB_HH_ -/******************************************************************************/ -/* */ -/* X r d X r o o t d J o b . h h */ -/* */ -/* (c) 2006 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -#include "Xrd/XrdJob.hh" -#include "XrdOuc/XrdOucTList.hh" -#include "XrdSys/XrdSysPthread.hh" -#include "XrdOuc/XrdOucTable.hh" - -class XrdOucProg; -class XrdLink; -class XrdScheduler; -class XrdXrootdJob2Do; -class XrdXrootdResponse; - -// Definition of options that can be passed to Schedule() -// -#define JOB_Sync 0x0001 -#define JOB_Unique 0x0002 - -class XrdXrootdJob : public XrdJob -{ -friend class XrdXrootdJob2Do; -public: - -int Cancel(const char *jkey=0, XrdXrootdResponse *resp=0); - -void DoIt(); - -// List() returns a list of all jobs in xml format -// -XrdOucTList *List(void); - -// args[0] if not null if prefixes the response -// args[1-n] are passed to the prgram -// The return value is whatever resp->Send() returns -// -int Schedule(const char *jkey, // Job Identifier - const char **args, // Zero terminated arglist - XrdXrootdResponse *resp, // Response object - int Opts=0);// Options (see above) - - XrdXrootdJob(XrdScheduler *schp, // -> Scheduler - XrdOucProg *pgm, // -> Program Object - const char *jname, // -> Job name - int maxjobs=4); // Maximum simultaneous jobs - ~XrdXrootdJob(); - -private: -void CleanUp(XrdXrootdJob2Do *jp); -int sendResult(XrdXrootdResponse *resp, - const char *rpfx, - XrdXrootdJob2Do *job); - -static const int reScan = 15*60; - -XrdSysMutex myMutex; -XrdScheduler *Sched; -XrdOucTable JobTable; -XrdOucProg *theProg; -char *JobName; -int maxJobs; -int numJobs; -}; -#endif diff --git a/src/XrdXrootd/XrdXrootdLoadLib.cc b/src/XrdXrootd/XrdXrootdLoadLib.cc deleted file mode 100644 index 8f8afcd905f..00000000000 --- a/src/XrdXrootd/XrdXrootdLoadLib.cc +++ /dev/null @@ -1,84 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d X r o o t d L o a d L i b . c c */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -#include "XrdVersion.hh" - -#include "XrdOuc/XrdOucEnv.hh" -#include "XrdOuc/XrdOucPinLoader.hh" -#include "XrdSfs/XrdSfsInterface.hh" -#include "XrdSys/XrdSysError.hh" - -/******************************************************************************/ -/* x r o o t d _ l o a d F i l e s y s t e m */ -/******************************************************************************/ - -XrdSfsFileSystem *XrdXrootdloadFileSystem(XrdSysError *eDest, - XrdSfsFileSystem *prevFS, - char *fslib, int fsver, - const char *cfn, XrdOucEnv *envP) -{ - static XrdVERSIONINFODEF(myVersion, XrdOfsLoader, XrdVNUMBER, XrdVERSION); - XrdOucPinLoader ofsLib(eDest, &myVersion, "fslib", fslib); - XrdSfsFileSystem_t ep; - XrdSfsFileSystem2_t ep2; - XrdSfsFileSystem *FS = 0; - const char *epname = "XrdSfsGetFileSystem"; - char epbuff[64]; - -// Record the library path in the environment -// - if (!prevFS) XrdOucEnv::Export("XRDOFSLIB", fslib); - -// If a different version is to used for initialization, generate the name -// - if (fsver) - {sprintf(epbuff, "XrdSfsGetFileSystem%d", fsver); // Always fits - epname = epbuff; - } - -// Get the file system object creator and the object -// - if (fsver) - {if ((ep2 = (XrdSfsFileSystem2_t)ofsLib.Resolve(epname))) - FS = (*ep2)(prevFS, eDest->logger(), cfn, envP); - } else { - if ((ep = (XrdSfsFileSystem_t )ofsLib.Resolve(epname))) - FS = (*ep) (prevFS, eDest->logger(), cfn); - } - -// Issue message if we could not load it -// - if (!FS) eDest->Emsg("Config","Unable to create file system object via",fslib); - -// All done -// - return FS; -} diff --git a/src/XrdXrootd/XrdXrootdMonData.hh b/src/XrdXrootd/XrdXrootdMonData.hh deleted file mode 100644 index 9f5cc7de4ac..00000000000 --- a/src/XrdXrootd/XrdXrootdMonData.hh +++ /dev/null @@ -1,284 +0,0 @@ -#ifndef __XRDXROOTDMONDATA__ -#define __XRDXROOTDMONDATA__ -/******************************************************************************/ -/* */ -/* X r d X r o o t d M o n D a t a . h h */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XProtocol/XPtypes.hh" - -/******************************************************************************/ -/* P a c k e t D e f i n i t i o n s */ -/******************************************************************************/ - -struct XrdXrootdMonHeader - {kXR_char code; // '='|'d'|'f'|'i'|'p'|'r'|'t'|'u'|'x' - kXR_char pseq; // packet sequence - kXR_unt16 plen; // packet length - kXR_int32 stod; // Unix time at Server Start - }; - -struct XrdXrootdMonTrace - {union {kXR_int64 val; - kXR_char id[8]; - kXR_unt16 sVal[4]; - kXR_unt32 rTot[2]; } arg0; - union {kXR_int32 buflen; - kXR_int32 Window; - kXR_unt32 wTot; } arg1; - union {kXR_unt32 dictid; - kXR_int32 Window; } arg2; - }; - -struct XrdXrootdMonBuff - {XrdXrootdMonHeader hdr; - XrdXrootdMonTrace info[sizeof(XrdXrootdMonTrace)]; //This is really [n] - }; - -struct XrdXrootdMonRedir - {union {kXR_int32 Window; - struct {kXR_char Type; - kXR_char Dent; - kXR_int16 Port; - } rdr; } arg0; - union {kXR_unt32 dictid; - kXR_int32 Window; } arg1; - }; - -struct XrdXrootdMonBurr - {XrdXrootdMonHeader hdr; - union {kXR_int64 sID; - kXR_char sXX[8]; }; - XrdXrootdMonRedir info[sizeof(XrdXrootdMonRedir)]; //This is really [n] - }; - -struct XrdXrootdMonMap - {XrdXrootdMonHeader hdr; - kXR_unt32 dictid; - char info[1024+256]; - }; - -const kXR_char XROOTD_MON_APPID = 0xa0; -const kXR_char XROOTD_MON_CLOSE = 0xc0; -const kXR_char XROOTD_MON_DISC = 0xd0; -const kXR_char XROOTD_MON_OPEN = 0x80; -const kXR_char XROOTD_MON_READV = 0x90; -const kXR_char XROOTD_MON_READU = 0x91; -const kXR_char XROOTD_MON_REDHOST = 0xf0; // No Modifier -const kXR_char XROOTD_MON_WINDOW = 0xe0; - - -const kXR_char XROOTD_MON_MAPIDNT = '='; -const kXR_char XROOTD_MON_MAPPATH = 'd'; -const kXR_char XROOTD_MON_MAPFSTA = 'f'; // The "f" stream -const kXR_char XROOTD_MON_MAPINFO = 'i'; -const kXR_char XROOTD_MON_MAPMIGR = 'm'; // Internal use only! -const kXR_char XROOTD_MON_MAPPURG = 'p'; -const kXR_char XROOTD_MON_MAPREDR = 'r'; -const kXR_char XROOTD_MON_MAPSTAG = 's'; // Internal use only! -const kXR_char XROOTD_MON_MAPTRCE = 't'; -const kXR_char XROOTD_MON_MAPUSER = 'u'; -const kXR_char XROOTD_MON_MAPXFER = 'x'; - -// The following bits are insert in the low order 4 bits of the MON_REDIRECT -// entry code to indicate the actual operation that was requestded. -// -const kXR_char XROOTD_MON_REDSID = 0xf0; // Server Identification -const kXR_char XROOTD_MON_REDTIME = 0x00; // Timing mark - -const kXR_char XROOTD_MON_REDIRECT = 0x80; // With Modifier below! -const kXR_char XROOTD_MON_REDLOCAL = 0x90; // With Modifier below! - -const kXR_char XROOTD_MON_CHMOD = 0x01; // Modifiers for the above -const kXR_char XROOTD_MON_LOCATE = 0x02; -const kXR_char XROOTD_MON_OPENDIR = 0x03; -const kXR_char XROOTD_MON_OPENC = 0x04; -const kXR_char XROOTD_MON_OPENR = 0x05; -const kXR_char XROOTD_MON_OPENW = 0x06; -const kXR_char XROOTD_MON_MKDIR = 0x07; -const kXR_char XROOTD_MON_MV = 0x08; -const kXR_char XROOTD_MON_PREP = 0x09; -const kXR_char XROOTD_MON_QUERY = 0x0a; -const kXR_char XROOTD_MON_RM = 0x0b; -const kXR_char XROOTD_MON_RMDIR = 0x0c; -const kXR_char XROOTD_MON_STAT = 0x0d; -const kXR_char XROOTD_MON_TRUNC = 0x0e; - -const kXR_char XROOTD_MON_FORCED = 0x01; -const kXR_char XROOTD_MON_BOUNDP = 0x02; - -const int XROOTD_MON_REDMASK = 0x00000ff; -const int XROOTD_MON_SRCMASK = 0x000000f; -const int XROOTD_MON_TRGMASK = 0x7fffff0; -const int XROOTD_MON_NEWSTID = 0x8000000; - -/******************************************************************************/ -/* " f " S t r e a m S p e c i f i c R e c o r d s */ -/******************************************************************************/ - -// The UDP buffer layout is as follows: -// -// XrdXrootdMonHeader with Code == XROOTD_MON_MAPFSTA -// XrdXrootdMonFileTOD with recType == isTime -// XrdXrootdMonFileHdr with recType == one of recTval (variable length) -// ... additional XrdXrootdMonFileHdr's (variable length) -// XrdXrootdMonFileTOD with recType == isTime - -struct XrdXrootdMonFileHdr // 8 -{ -enum recTval {isClose = 0, // Record for close - isOpen, // Record for open - isTime, // Record for time - isXfr, // Record for transfers - isDisc // Record for disconnection - }; - -enum recFval {forced =0x01, // If recFlag == isClose close due to disconnect - hasOPS =0x02, // If recFlag == isClose MonStatXFR + MonStatOPS - hasSSQ =0x04, // If recFlag == isClose XFR + OPS + MonStatSSQ - hasLFN =0x01, // If recFlag == isOpen the lfn is present - hasRW =0x02, // If recFlag == isOpen file opened r/w - hasSID =0x01 // if recFlag == isTime sID is present (new rec) - }; - -char recType; // RecTval: isClose | isOpen | isTime | isXfr -char recFlag; // RecFval: Record type-specific flags -short recSize; // Size of this record in bytes -union -{ -kXR_unt32 fileID; // dictid of file for all rectypes except "disc" & "time" -kXR_unt32 userID; // dictid of user for rectypes equal "disc" -short nRecs[2]; // isTime: nRecs[0] == isXfr recs nRecs[1] == total recs -}; -}; - -// The following record is always be present as the first record in the udp -// udp packet and should be used to establish the recording window. -// -struct XrdXrootdMonFileTOD -{ -XrdXrootdMonFileHdr Hdr; // 8 -int tBeg; // time(0) of following record -int tEnd; // time(0) when packet was sent -kXR_int64 sID; // Server id in lower 48 bits -}; - - -// The following variable length structure exists in XrdXrootdMonFileOPN if -// "lfn" has been specified. It exists only when recFlag & hasLFN is TRUE. -// The user's dictid will be zero (missing) if user monitoring is not enabled. -// -struct XrdXrootdMonFileLFN -{ -kXR_unt32 user; // Monitoring dictid for the user, may be 0. -char lfn[1028];// Variable length, use recSize! -}; - -// The following is reported when a file is opened. If "lfn" was specified and -// Hdr.recFlag & hasLFN is TRUE the XrdXrootdMonFileLFN structure is present. -// However, it variable in size and the next record will be found using recSize. -// The lfn is gauranteed to end with at least one null byte. -// -struct XrdXrootdMonFileOPN -{ -XrdXrootdMonFileHdr Hdr; // 8 -long long fsz; // 8 file size at time of open -XrdXrootdMonFileLFN ufn; // Present ONLY if recFlag & hasLFN is TRUE -}; - -// The following data is collected on a per file basis -// -struct XrdXrootdMonStatOPS // 48 Bytes -{ -int read; // Number of read() calls -int readv; // Number of readv() calls -int write; // Number of write() calls -short rsMin; // Smallest readv() segment count -short rsMax; // Largest readv() segment count -long long rsegs; // Number of readv() segments -int rdMin; // Smallest read() request size -int rdMax; // Largest read() request size -int rvMin; // Smallest readv() request size -int rvMax; // Largest readv() request size -int wrMin; // Smallest write() request size -int wrMax; // Largest write() request size -}; - -union XrdXrootdMonDouble -{ -long long dlong; -double dreal; -}; - -struct XrdXrootdMonStatSSQ // 32 Bytes (all values net ordered IEEE754) -{ -XrdXrootdMonDouble read; // Sum (all read requests)**2 (size) -XrdXrootdMonDouble readv; // Sum (all readv requests)**2 (size as a unit) -XrdXrootdMonDouble rsegs; // Sum (all readv segments)**2 (count as a unit) -XrdXrootdMonDouble write; // Sum (all write requests)**2 (size) -}; - -// The following transfer data is collected for each open file. -// -struct XrdXrootdMonStatXFR -{ -long long read; // Bytes read from file so far using read() -long long readv; // Bytes read from file so far using readv() -long long write; // Bytes written to file so far -}; - -// The following is reported upon file close. This is a variable length record. -// The record always contains XrdXrootdMonStatXFR after XrdXrootdMonFileHdr. -// If (recFlag & hasOPS) TRUE XrdXrootdMonStatOPS follows XrdXrootdMonStatXFR -// If (recFlag & hasSSQ) TRUE XrdXrootdMonStatSQV follows XrdXrootdMonStatOPS -// The XrdXrootdMonStatSSQ information is present only if "ssq" was specified. -// -struct XrdXrootdMonFileCLS // 32 | 80 | 96 Bytes -{ -XrdXrootdMonFileHdr Hdr; // Always present (recSize has full length) -XrdXrootdMonStatXFR Xfr; // Always present -XrdXrootdMonStatOPS Ops; // Only present when (recFlag & hasOPS) is True -XrdXrootdMonStatSSQ Ssq; // Only present when (recFlag & hasSSQ) is True -}; - -// The following is reported when a user ends a session. -// -struct XrdXrootdMonFileDSC -{ -XrdXrootdMonFileHdr Hdr; // 8 -}; - -// The following is reported each interval*count for each open file when "xfr" -// is specified. These records may be interspersed with other records. -// -struct XrdXrootdMonFileXFR // 32 Bytes -{ -XrdXrootdMonFileHdr Hdr; // Always present with recType == isXFR -XrdXrootdMonStatXFR Xfr; // Always present -}; -#endif diff --git a/src/XrdXrootd/XrdXrootdMonFMap.cc b/src/XrdXrootd/XrdXrootdMonFMap.cc deleted file mode 100644 index 5d23ed05fe8..00000000000 --- a/src/XrdXrootd/XrdXrootdMonFMap.cc +++ /dev/null @@ -1,130 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d X r o o t d M o n F M a p . h h */ -/* */ -/* (c) 2012 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include - -#include "XrdSys/XrdSysPlatform.hh" - -#include "XrdXrootd/XrdXrootdFileStats.hh" -#include "XrdXrootd/XrdXrootdMonFMap.hh" - -/******************************************************************************/ -/* S t a t i c M e m b e r s */ -/******************************************************************************/ - -long XrdXrootdMonFMap::invVal = 1; -long XrdXrootdMonFMap::valVal = ~1; - -/******************************************************************************/ -/* F r e e */ -/******************************************************************************/ - -bool XrdXrootdMonFMap::Free(int slotNum) -{ -// Validate the data before freeing the slot -// - if (!fMap || slotNum < 0 || slotNum >= fmSize || fMap[slotNum].cVal & invVal) - return false; - -// Plase this entry on the free list -// - fMap[slotNum].cPtr = free.cPtr; - fMap[slotNum].cVal |= invVal; - free.cPtr = &fMap[slotNum]; - return true; -} - -/******************************************************************************/ -/* Private: I n i t */ -/******************************************************************************/ - -bool XrdXrootdMonFMap::Init() -{ - static const int bytes = fmSize * sizeof(cvPtr); - static int pagsz = getpagesize(); - void *mPtr; - int alignment, i; - -// Allocate memory -// - alignment = (bytes < pagsz ? 1024 : pagsz); - if (posix_memalign(&mPtr, alignment, bytes)) return false; - fMap = (cvPtr *)mPtr; - -// Chain all the entries together -// - for (i = 1; i < fmSize; i++) - {fMap[i-1].cPtr = &fMap[i]; - fMap[i-1].cVal |= invVal; - } - fMap[fmSize-1].cVal = invVal; - free.cPtr = &fMap[0]; - return true; -} - -/******************************************************************************/ -/* I n s e r t */ -/******************************************************************************/ - -int XrdXrootdMonFMap::Insert(XrdXrootdFileStats *fsP) -{ - cvPtr *mEnt; - -// Check if we have a free slot available -// - if (!free.cVal) {if (fMap || !Init()) return -1;} - -// Return the free slot (Init() gaurantees one is available) -// - mEnt = free.cPtr; - free.cPtr = free.cPtr->cPtr; - free.cVal &= valVal; - mEnt->vPtr = fsP; - return mEnt - fMap; -} - -/******************************************************************************/ -/* N e x t */ -/******************************************************************************/ - -XrdXrootdFileStats *XrdXrootdMonFMap::Next(int &slotNum) -{ - -// Return next valid pointer -// - for (; slotNum < fmSize-1; slotNum++) - {if (!(fMap[slotNum].cVal & invVal)) return fMap[slotNum++].vPtr;} - -// At the end of the map -// - return 0; -} diff --git a/src/XrdXrootd/XrdXrootdMonFMap.hh b/src/XrdXrootd/XrdXrootdMonFMap.hh deleted file mode 100644 index 1c27c329b63..00000000000 --- a/src/XrdXrootd/XrdXrootdMonFMap.hh +++ /dev/null @@ -1,65 +0,0 @@ -#ifndef __XRDXROOTDMONFMAP__ -#define __XRDXROOTDMONFMAP__ -/******************************************************************************/ -/* */ -/* X r d X r o o t d M o n F M a p . h h */ -/* */ -/* (c) 2012 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -class XrdXrootdFileStats; - -class XrdXrootdMonFMap -{ -public: - -struct cvPtr {union {long cVal; cvPtr *cPtr; XrdXrootdFileStats *vPtr;};}; - -cvPtr *fMap; -cvPtr free; - -static const int mapNum = 128; -static const int fmSize = 512; -static const int fmHold = 31; -static const int fmMask = 0x01ff; -static const int fmShft = 9; - -bool Free(int slotNum); - -int Insert(XrdXrootdFileStats *fsP); - -XrdXrootdFileStats *Next(int &slotNum); - - XrdXrootdMonFMap() : fMap(0) {free.cVal = 0;} - ~XrdXrootdMonFMap() {} -private: - -bool Init(); - -static long invVal; -static long valVal; -}; -#endif diff --git a/src/XrdXrootd/XrdXrootdMonFile.cc b/src/XrdXrootd/XrdXrootdMonFile.cc deleted file mode 100644 index 16210e8fcf5..00000000000 --- a/src/XrdXrootd/XrdXrootdMonFile.cc +++ /dev/null @@ -1,531 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d X r o o t d M o n F i l e . c c */ -/* */ -/* (c) 2012 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -#include "Xrd/XrdScheduler.hh" - -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysPlatform.hh" - -#include "XrdXrootd/XrdXrootdMonFile.hh" -#include "XrdXrootd/XrdXrootdFileStats.hh" - -/******************************************************************************/ -/* G l o b a l s */ -/******************************************************************************/ - -namespace XrdXrootdMonInfo -{ -extern long long mySID; -} - -/******************************************************************************/ -/* S t a t i c M e m b e r s */ -/******************************************************************************/ - -XrdSysError *XrdXrootdMonFile::eDest = 0; -XrdScheduler *XrdXrootdMonFile::Sched = 0; -XrdSysMutex XrdXrootdMonFile::bfMutex; -XrdSysMutex XrdXrootdMonFile::fmMutex; -XrdXrootdMonFMap XrdXrootdMonFile::fmMap[XrdXrootdMonFMap::mapNum]; -short XrdXrootdMonFile::fmUse[XrdXrootdMonFMap::mapNum] = {0}; -char *XrdXrootdMonFile::repBuff = 0; -XrdXrootdMonHeader *XrdXrootdMonFile::repHdr = 0; -XrdXrootdMonFileTOD *XrdXrootdMonFile::repTOD = 0; -char *XrdXrootdMonFile::repNext = 0; -char *XrdXrootdMonFile::repFirst = 0; -char *XrdXrootdMonFile::repLast = 0; -int XrdXrootdMonFile::totRecs = 0; -int XrdXrootdMonFile::xfrRecs = 0; -int XrdXrootdMonFile::repSize = 0; -int XrdXrootdMonFile::repTime = 0; -int XrdXrootdMonFile::fmHWM =-1; -int XrdXrootdMonFile::crecSize = 0; -int XrdXrootdMonFile::xfrCnt = 0; -int XrdXrootdMonFile::xfrRem = 0; -XrdXrootdMonFileXFR XrdXrootdMonFile::xfrRec; -short XrdXrootdMonFile::crecNLen = 0; -short XrdXrootdMonFile::trecNLen = 0; -char XrdXrootdMonFile::fsLFN = 0; -char XrdXrootdMonFile::fsLVL = 0; -char XrdXrootdMonFile::fsOPS = 0; -char XrdXrootdMonFile::fsSSQ = 0; -char XrdXrootdMonFile::fsXFR = 0; -char XrdXrootdMonFile::crecFlag = 0; - -/******************************************************************************/ -/* C l o s e */ -/******************************************************************************/ - -void XrdXrootdMonFile::Close(XrdXrootdFileStats *fsP, bool isDisc) -{ - XrdXrootdMonFileCLS cRec; - char *cP; - int iEnt, iMap, iSlot; - -// If this object was registered for I/O reporting, deregister it. -// - if (fsP->MonEnt != -1) - {iEnt = fsP->MonEnt & 0xffff; - iMap = iEnt >> XrdXrootdMonFMap::fmShft; - iSlot = iEnt & XrdXrootdMonFMap::fmMask; - fsP->MonEnt = -1; - fmMutex.Lock(); - if (fmMap[iMap].Free(iSlot)) fmUse[iMap]--; - if (iMap == fmHWM) while(fmHWM >= 0 && !fmUse[fmHWM]) fmHWM--; - fmMutex.UnLock(); - } - -// Insert a close record header (mostly precomputed) -// - cRec.Hdr.recType = XrdXrootdMonFileHdr::isClose; - cRec.Hdr.recFlag = crecFlag; - if (isDisc) cRec.Hdr.recFlag |= XrdXrootdMonFileHdr::forced; - cRec.Hdr.recSize = crecNLen; - cRec.Hdr.fileID = fsP->FileID; - -// Insert the I/O bytes -// - cRec.Xfr.read = htonll(fsP->xfr.read); - cRec.Xfr.readv = htonll(fsP->xfr.readv); - cRec.Xfr.write = htonll(fsP->xfr.write); - -// Insert ops if so wanted -// - if (fsOPS) - {cRec.Ops.read = htonl (fsP->ops.read); - if (fsP->ops.read) - {cRec.Ops.rdMin = htonl (fsP->ops.rdMin); - cRec.Ops.rdMax = htonl (fsP->ops.rdMax); - } else { - cRec.Ops.rdMin = cRec.Ops.rdMax = 0; - } - cRec.Ops.readv = htonl (fsP->ops.readv); - cRec.Ops.rsegs = htonll(fsP->ops.rsegs); - if (fsP->ops.readv) - {cRec.Ops.rsMin = htons (fsP->ops.rsMin); - cRec.Ops.rsMax = htons (fsP->ops.rsMax); - cRec.Ops.rvMin = htonl (fsP->ops.rvMin); - cRec.Ops.rvMax = htonl (fsP->ops.rvMax); - } else { - cRec.Ops.rsMin = cRec.Ops.rsMax = 0; - cRec.Ops.rvMin = cRec.Ops.rvMax = 0; - } - cRec.Ops.write = htonl (fsP->ops.write); - if (fsP->ops.write) - {cRec.Ops.wrMin = htonl (fsP->ops.wrMin); - cRec.Ops.wrMax = htonl (fsP->ops.wrMax); - } else { - cRec.Ops.wrMin = cRec.Ops.wrMax = 0; - } - } - -// Record sum of squares if so needed -// - if (fsSSQ) - {XrdXrootdMonDouble xval; - xval.dreal = fsP->ssq.read; - cRec.Ssq.read.dlong = htonll(xval.dlong); - xval.dreal = fsP->ssq.readv; - cRec.Ssq.readv.dlong = htonll(xval.dlong); - xval.dreal = fsP->ssq.rsegs; - cRec.Ssq.rsegs.dlong = htonll(xval.dlong); - xval.dreal = fsP->ssq.write; - cRec.Ssq.write.dlong = htonll(xval.dlong); - } - -// Get a pointer to the next slot (the buffer gets locked) -// - cP = GetSlot(crecSize); - memcpy(cP, &cRec, crecSize); - bfMutex.UnLock(); -} - -/******************************************************************************/ -/* D e f a u l t s */ -/******************************************************************************/ - -void XrdXrootdMonFile::Defaults(int intv, int opts, int xfrcnt) -{ - -// Set the reporting interval and I/O counter -// - repTime = intv; - xfrCnt = xfrcnt; - xfrRem = xfrcnt; - -// Expand out the options -// - fsXFR = (opts & XROOTD_MON_FSXFR) != 0; - fsLFN = (opts & XROOTD_MON_FSLFN) != 0; - fsOPS = (opts & (XROOTD_MON_FSOPS | XROOTD_MON_FSSSQ)) != 0; - fsSSQ = (opts & XROOTD_MON_FSSSQ) != 0; - -// Set monitoring level -// - if (fsSSQ) fsLVL = XrdXrootdFileStats::monSsq; - else if (fsOPS) fsLVL = XrdXrootdFileStats::monOps; - else if (intv) fsLVL = XrdXrootdFileStats::monOn; - else fsLVL = XrdXrootdFileStats::monOff; -} - -/******************************************************************************/ -/* D i s c */ -/******************************************************************************/ - -void XrdXrootdMonFile::Disc(unsigned int usrID) -{ - static short drecSize = htons(sizeof(XrdXrootdMonFileDSC)); - XrdXrootdMonFileDSC *dP; - -// Get a pointer to the next slot (the buffer gets locked) -// - dP = (XrdXrootdMonFileDSC *)GetSlot(sizeof(XrdXrootdMonFileDSC)); - -// Fill out the record. It's pretty simple -// - dP->Hdr.recType = XrdXrootdMonFileHdr::isDisc; - dP->Hdr.recFlag = 0; - dP->Hdr.recSize = drecSize; - dP->Hdr.userID = usrID; - bfMutex.UnLock(); -} - -/******************************************************************************/ -/* D o I t */ -/******************************************************************************/ - -void XrdXrootdMonFile::DoIt() -{ - -// First check if we need to report all the I/O stats -// - xfrRem--; - if (!xfrRem) DoXFR(); - -// Check if we should flush the buffer -// - bfMutex.Lock(); - if (repNext) Flush(); - bfMutex.UnLock(); - -// Reschedule ourselves -// - XrdXrootdMonitor::Sched->Schedule((XrdJob *)this, time(0)+repTime); -} - -/******************************************************************************/ -/* Private: D o X F R */ -/******************************************************************************/ - -void XrdXrootdMonFile::DoXFR() -{ - XrdXrootdFileStats *fsP; - int keep, i, n, hwm; - -// Reset interval counter -// - xfrRem = xfrCnt; - -// Grab the high watermark once -// - fmMutex.Lock(); - hwm = fmHWM; - fmMutex.UnLock(); - -// Report on all the files we have registered. This is a CPU burner as we -// periodically drop the lock to allow open/close requests to come through. -// - for (i = 0; i <= hwm; i++) - {fmMutex.Lock(); - if (fmUse[i]) - {n = 0; - keep = XrdXrootdMonFMap::fmHold; - while((fsP = fmMap[i].Next(n))) - {if (fsP->xfrXeq) DoXFR(fsP); - if (!keep--) - {fmMutex.UnLock(); - keep = XrdXrootdMonFMap::fmHold; - fmMutex.Lock(); - } - } - } - fmMutex.UnLock(); - } -} - -/******************************************************************************/ - -void XrdXrootdMonFile::DoXFR(XrdXrootdFileStats *fsP) -{ - long long xfrRead, xfrReadv, xfrWrite; - char *cP; - -// Turn off the activity flag -// - fsP->xfrXeq = 0; - -// Grab the I/O bytes to get a somewhat consistent image here -// - xfrRead = fsP->xfr.read; - xfrReadv = fsP->xfr.readv; - xfrWrite = fsP->xfr.write; - -// Complete the record -// - xfrRec.Hdr.fileID = fsP->FileID; - xfrRec.Xfr.read = htonll(xfrRead); - xfrRec.Xfr.readv = htonll(xfrReadv); - xfrRec.Xfr.write = htonll(xfrWrite); - -// Get a pointer to the next slot (the buffer gets locked) -// - cP = GetSlot(sizeof(xfrRec)); - memcpy(cP, &xfrRec, sizeof(xfrRec)); - xfrRecs++; - bfMutex.UnLock(); -} - -/******************************************************************************/ -/* I n i t */ -/******************************************************************************/ - -bool XrdXrootdMonFile::Init(XrdScheduler *sp, XrdSysError *errp, int bfsz) -{ - XrdXrootdMonFile *mfP; - int alignment, pagsz = getpagesize(); - -// Set the variables -// - Sched = sp; - eDest = errp; - -// Allocate a socket buffer -// - alignment = (bfsz < pagsz ? 1024 : pagsz); - if (posix_memalign((void **)&repBuff, alignment, bfsz)) - {eDest->Emsg("MonFile", "Unable to allocate monitor buffer."); - return false; - } - -// Set the header (always present) -// - repHdr = (XrdXrootdMonHeader *)repBuff; - repHdr->code = XROOTD_MON_MAPFSTA; - repHdr->pseq = 0; - repHdr->stod = XrdXrootdMonitor::startTime; - -// Set the time record (always present) -// - repTOD = (XrdXrootdMonFileTOD *)(repBuff + sizeof(XrdXrootdMonHeader)); - repTOD->Hdr.recType = XrdXrootdMonFileHdr::isTime; - repTOD->Hdr.recFlag = XrdXrootdMonFileHdr::hasSID; - repTOD->Hdr.recSize = htons(sizeof(XrdXrootdMonFileTOD)); - repTOD->sID = static_cast(XrdXrootdMonInfo::mySID); - -// Establish first real record in the buffer (always fixed) -// - repFirst = repBuff+sizeof(XrdXrootdMonHeader)+sizeof(XrdXrootdMonFileTOD); - -// Calculate the end nut the next slot always starts with a null pointer -// - repLast = repBuff+bfsz-1; - repNext = 0; - -// Calculate the close record size and the initial flags -// - crecSize = sizeof(XrdXrootdMonFileHdr) + sizeof(XrdXrootdMonStatXFR); - if (fsSSQ || fsOPS) - {crecSize += sizeof(XrdXrootdMonStatOPS); - crecFlag = XrdXrootdMonFileHdr::hasOPS; - } else crecFlag = 0; - if (fsSSQ) - {crecSize += sizeof(XrdXrootdMonStatSSQ); - crecFlag |= XrdXrootdMonFileHdr::hasSSQ; - } - crecNLen = htons(static_cast(crecSize)); - -// Preformat the i/o record -// - xfrRec.Hdr.recType = XrdXrootdMonFileHdr::isXfr; - xfrRec.Hdr.recFlag = 0; - xfrRec.Hdr.recSize = htons(static_cast(sizeof(xfrRec))); - -// Calculate the tod record size -// - trecNLen = htons(static_cast(sizeof(XrdXrootdMonFileTOD))); - -// Allocate an instance of ourselves so we can schedule ourselves -// - mfP = new XrdXrootdMonFile(); - -// Schedule an the flushes -// - XrdXrootdMonitor::Sched->Schedule((XrdJob *)mfP, time(0)+repTime); - return true; -} - -/******************************************************************************/ -/* Private: F l u s h */ -/******************************************************************************/ - -void XrdXrootdMonFile::Flush() // The bfMutex must be locked -{ - static int seq = 0; - int bfSize; - -// Update the sequence number -// - repHdr->pseq = static_cast(0x00ff & seq++); - -// Insert ending timestamp and record counts -// - repTOD->Hdr.nRecs[0] = htons(static_cast(xfrRecs)); - repTOD->Hdr.nRecs[1] = htons(static_cast(totRecs)); - repTOD->tEnd = htonl(static_cast(time(0))); - -// Calculate buffer size and stick into the header -// - bfSize = (repNext - repBuff); - repHdr->plen = htons(static_cast(bfSize)); - repNext = 0; - -// Write this out -// - XrdXrootdMonitor::Send(XROOTD_MON_FSTA, repBuff, bfSize); - repTOD->tBeg = repTOD->tEnd; - xfrRecs = totRecs = 0; -} - -/******************************************************************************/ -/* Private: G e t S l o t */ -/******************************************************************************/ - -char *XrdXrootdMonFile::GetSlot(int slotSZ) -{ - char *myRec; - -// Lock this code to prevent interference (we should use double buffering) -// Note that the caller must do the unlock when finished with the slot. -// - bfMutex.Lock(); - -// Check if we need to flush the buffer (sets repNext to zero). Otherwise, -// if this is the first record insert a timestamp. -// - if (repNext) - {if ((repNext + slotSZ) > repLast) - {Flush(); - repNext = repFirst; - } - } else { - repTOD->tBeg = htonl(static_cast(time(0))); - repNext = repFirst; - } - -// Return the slot -// - totRecs++; - myRec = repNext; - repNext += slotSZ; - return myRec; -} - -/******************************************************************************/ -/* O p e n */ -/******************************************************************************/ - -void XrdXrootdMonFile::Open(XrdXrootdFileStats *fsP, const char *Path, - unsigned int uDID, bool isRW) -{ - static const int minRecSz = sizeof(XrdXrootdMonFileOPN) - - sizeof(XrdXrootdMonFileLFN); - XrdXrootdMonFileOPN *oP; - int i = 0, sNum = -1, rLen, pLen = 0; - -// Assign the path a dictionary id if not assigned via file monitoring -// - if (fsP->FileID == 0) fsP->FileID = XrdXrootdMonitor::GetDictID(); - -// Add this open to the map table if we are doing I/O stats. -// - if (fsXFR) - {fmMutex.Lock(); - for (i = 0; i < XrdXrootdMonFMap::mapNum; i++) - if (fmUse[i] < XrdXrootdMonFMap::fmSize) - {if ((sNum = fmMap[i].Insert(fsP)) >= 0) - {fmUse[i]++; - if (i > fmHWM) fmHWM = i; - break; - } - } - fmMutex.UnLock(); - } - -// Generate the cookie (real or virtual) to find the entry in the map table. -// Supply the monitoring options for effeciency. -// - fsP->MonEnt = (sNum | (i << XrdXrootdMonFMap::fmShft)) & 0xffff; - fsP->monLvl = fsLVL; - fsP->xfrXeq = 0; - -// Compute the size of this record -// - rLen = minRecSz; - if (fsLFN) - {pLen = strlen(Path); - rLen += sizeof(kXR_unt32) + pLen; - i = (rLen + 8) & ~0x00000003; - pLen = pLen + (i - rLen); - rLen = i; - } - -// Get a pointer to the next slot (the buffer gets locked) -// - oP = (XrdXrootdMonFileOPN *)GetSlot(rLen); - -// Fill out the record -// - oP->Hdr.recType = XrdXrootdMonFileHdr::isOpen; - oP->Hdr.recFlag = (isRW ? XrdXrootdMonFileHdr::hasRW : 0); - oP->Hdr.recSize = htons(static_cast(rLen)); - oP->Hdr.fileID = fsP->FileID; - oP->fsz = htonll(fsP->fSize); - -// Append user and path if so wanted (sizes have been verified) -// - if (fsLFN) - {oP->Hdr.recFlag |= XrdXrootdMonFileHdr::hasLFN; - oP->ufn.user = uDID; - strncpy(oP->ufn.lfn, Path, pLen); - } - bfMutex.UnLock(); -} diff --git a/src/XrdXrootd/XrdXrootdMonFile.hh b/src/XrdXrootd/XrdXrootdMonFile.hh deleted file mode 100644 index 16fad210b3f..00000000000 --- a/src/XrdXrootd/XrdXrootdMonFile.hh +++ /dev/null @@ -1,101 +0,0 @@ -#ifndef __XRDXROOTDMONFILE__ -#define __XRDXROOTDMONFILE__ -/******************************************************************************/ -/* */ -/* X r d X r o o t d M o n F i l e . h h */ -/* */ -/* (c) 2012 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "Xrd/XrdJob.hh" -#include "XrdSys/XrdSysPthread.hh" -#include "XrdXrootd/XrdXrootdMonFMap.hh" -#include "XrdXrootd/XrdXrootdMonitor.hh" - -class XrdScheduler; -class XrdSysError; -class XrdXrootdFileStats; -class XrdXrootdMonHeader; -class XrdXrootdMonTrace; - -class XrdXrootdMonFile : XrdJob -{ -public: - -static void Close(XrdXrootdFileStats *fsP, bool isDisc=false); - -static void Defaults(int intv, int opts, int iocnt); - -static void Disc(unsigned int usrID); - - void DoIt(); - -static bool Init(XrdScheduler *sp, XrdSysError *errp, int bfsz=65472); - -static void Open(XrdXrootdFileStats *fsP, - const char *Path, unsigned int uDID, bool isRW); - - XrdXrootdMonFile() : XrdJob("monitor fstat") {} - ~XrdXrootdMonFile() {} - -private: - -static void DoXFR(); -static void DoXFR(XrdXrootdFileStats *fsP); -static void Flush(); -static char *GetSlot(int slotSZ); - -static XrdSysError *eDest; -static XrdScheduler *Sched; -static XrdSysMutex bfMutex; -static XrdSysMutex fmMutex; -static XrdXrootdMonFMap fmMap[XrdXrootdMonFMap::mapNum]; -static short fmUse[XrdXrootdMonFMap::mapNum]; -static char *repBuff; -static XrdXrootdMonHeader *repHdr; -static XrdXrootdMonFileTOD *repTOD; -static char *repNext; -static char *repFirst; -static char *repLast; -static int totRecs; -static int xfrRecs; -static int repSize; -static int repTime; -static int fmHWM; -static int crecSize; -static int xfrCnt; -static int xfrRem; -static XrdXrootdMonFileXFR xfrRec; -static short crecNLen; -static short trecNLen; -static char fsLFN; -static char fsLVL; -static char fsOPS; -static char fsSSQ; -static char fsXFR; -static char crecFlag; -}; -#endif diff --git a/src/XrdXrootd/XrdXrootdMonitor.cc b/src/XrdXrootd/XrdXrootdMonitor.cc deleted file mode 100644 index f1fac80b5bd..00000000000 --- a/src/XrdXrootd/XrdXrootdMonitor.cc +++ /dev/null @@ -1,1089 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d X r o o t d M o n i t o r . c c */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#if !defined(__APPLE__) && !defined(__FreeBSD__) -#include -#endif - -#include "XrdVersion.hh" - -#include "XrdNet/XrdNetMsg.hh" -#include "XrdOuc/XrdOucEnv.hh" -#include "XrdOuc/XrdOucUtils.hh" -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysPlatform.hh" - -#include "Xrd/XrdScheduler.hh" -#include "XrdXrootd/XrdXrootdMonitor.hh" -#include "XrdXrootd/XrdXrootdMonFile.hh" -#include "XrdXrootd/XrdXrootdTrace.hh" - -/******************************************************************************/ -/* S t a t i c A l l o c a t i o n */ -/******************************************************************************/ - -XrdScheduler *XrdXrootdMonitor::Sched = 0; -XrdSysError *XrdXrootdMonitor::eDest = 0; -char *XrdXrootdMonitor::idRec = 0; -int XrdXrootdMonitor::idLen = 0; -char *XrdXrootdMonitor::Dest1 = 0; -int XrdXrootdMonitor::monMode1 = 0; -XrdNetMsg *XrdXrootdMonitor::InetDest1 = 0; -char *XrdXrootdMonitor::Dest2 = 0; -int XrdXrootdMonitor::monMode2 = 0; -XrdNetMsg *XrdXrootdMonitor::InetDest2 = 0; -XrdXrootdMonitor *XrdXrootdMonitor::altMon = 0; -XrdSysMutex XrdXrootdMonitor::windowMutex; -kXR_int32 XrdXrootdMonitor::startTime = 0; -int XrdXrootdMonitor::monRlen = 0; -XrdXrootdMonitor::MonRdrBuff - XrdXrootdMonitor::rdrMon[XrdXrootdMonitor::rdrMax]; -XrdXrootdMonitor::MonRdrBuff - *XrdXrootdMonitor::rdrMP = 0; -XrdSysMutex XrdXrootdMonitor::rdrMutex; -int XrdXrootdMonitor::monBlen = 0; -int XrdXrootdMonitor::lastEnt = 0; -int XrdXrootdMonitor::lastRnt = 0; -int XrdXrootdMonitor::isEnabled = 0; -int XrdXrootdMonitor::numMonitor = 0; -int XrdXrootdMonitor::autoFlash = 0; -int XrdXrootdMonitor::autoFlush = 600; -int XrdXrootdMonitor::FlushTime = 0; -int XrdXrootdMonitor::monIdent = 3600; -kXR_int32 XrdXrootdMonitor::currWindow = 0; -int XrdXrootdMonitor::rdrTOD = 0; -int XrdXrootdMonitor::rdrWin = 0; -int XrdXrootdMonitor::rdrNum = 3; -kXR_int32 XrdXrootdMonitor::sizeWindow = 60; -char XrdXrootdMonitor::sidName[16]= {0}; -short XrdXrootdMonitor::sidSize = 0; -char XrdXrootdMonitor::monINFO = 0; -char XrdXrootdMonitor::monIO = 0; -char XrdXrootdMonitor::monFILE = 0; -char XrdXrootdMonitor::monREDR = 0; -char XrdXrootdMonitor::monUSER = 0; -char XrdXrootdMonitor::monAUTH = 0; -char XrdXrootdMonitor::monACTIVE = 0; -char XrdXrootdMonitor::monFSTAT = 0; -char XrdXrootdMonitor::monCLOCK = 0; - -/******************************************************************************/ -/* G l o b a l s */ -/******************************************************************************/ - -extern XrdOucTrace *XrdXrootdTrace; - -namespace XrdXrootdMonInfo -{ -long long mySID = 0; -} - -using namespace XrdXrootdMonInfo; - -/******************************************************************************/ -/* L o c a l D e f i n e s */ -/******************************************************************************/ - -#define setTMark(TM_mb, TM_en, TM_tm) \ - TM_mb->info[TM_en].arg0.val = mySID; \ - TM_mb->info[TM_en].arg0.id[0] = XROOTD_MON_WINDOW; \ - TM_mb->info[TM_en].arg1.Window = \ - TM_mb->info[TM_en].arg2.Window = static_cast(ntohl(TM_tm)); - -#define setTMurk(TM_mb, TM_en, TM_tm) \ - TM_mb->info[TM_en].arg0.Window = rdrWin; \ - TM_mb->info[TM_en].arg1.Window = static_cast(TM_tm); - -/******************************************************************************/ -/* L o c a l C l a s s e s */ -/******************************************************************************/ -/******************************************************************************/ -/* X r d X r o o t d M o n i t o r _ I d e n t */ -/******************************************************************************/ - -class XrdXrootdMonitor_Ident : public XrdJob -{ -public: - -void DoIt() { - XrdXrootdMonitor::Ident(); - Sched->Schedule((XrdJob *)this, time(0)+idInt); - } - - XrdXrootdMonitor_Ident(XrdScheduler *sP, int idt) - : XrdJob("monitor ident"), Sched(sP), idInt(idt) {} - ~XrdXrootdMonitor_Ident() {} - -private: -XrdScheduler *Sched; // System scheduler -int idInt; -}; - -/******************************************************************************/ -/* C l a s s X r d X r o o t d M o n i t o r _ T i c k */ -/******************************************************************************/ - -class XrdXrootdMonitor_Tick : public XrdJob -{ -public: - -void DoIt() { -#ifndef NODEBUG - const char *TraceID = "MonTick"; -#endif - time_t Now = XrdXrootdMonitor::Tick(); - if (Window && Now) - Sched->Schedule((XrdJob *)this, Now+Window); - else {TRACE(DEBUG, "Monitor clock stopping.");} - } - -void Set(XrdScheduler *sp, int intvl) {Sched = sp; Window = intvl;} - - XrdXrootdMonitor_Tick() : XrdJob("monitor window clock"), - Sched(0), Window(0) {} - ~XrdXrootdMonitor_Tick() {} - -private: -XrdScheduler *Sched; // System scheduler -int Window; -}; - -/******************************************************************************/ -/* C l a s s X r d X r o o t d M o n i t o r L o c k */ -/******************************************************************************/ - -class XrdXrootdMonitorLock -{ -public: - -static void Lock() {monLock.Lock();} - -static void UnLock() {monLock.UnLock();} - - XrdXrootdMonitorLock(XrdXrootdMonitor *theMonitor) - {if (theMonitor != XrdXrootdMonitor::altMon) unLock = 0; - else {unLock = 1; monLock.Lock();} - } - ~XrdXrootdMonitorLock() {if (unLock) monLock.UnLock();} - -private: - -static XrdSysMutex monLock; - char unLock; -}; - -XrdSysMutex XrdXrootdMonitorLock::monLock; - -/******************************************************************************/ -/* X r d X r o o t d M o n i t o r : : U s e r : : D i s a b l e */ -/******************************************************************************/ - -void XrdXrootdMonitor::User::Disable() -{ - if (Agent) - {XrdXrootdMonitor::unAlloc(Agent); Agent = 0;} - Fops = Iops = 0; -} - -/******************************************************************************/ -/* X r d X r o o t d M o n i t o r : : U s e r : : E n a b l e */ -/******************************************************************************/ - -void XrdXrootdMonitor::User::Enable() -{ - if (Agent || (Agent = XrdXrootdMonitor::Alloc(1))) - {Iops = XrdXrootdMonitor::monIO; - Fops = XrdXrootdMonitor::monFILE; - } else Iops = Fops = 0; -} - -/******************************************************************************/ -/* X r d X r o o t d M o n i t o r : : U s e r : : R e g i s t e r */ -/******************************************************************************/ - -void XrdXrootdMonitor::User::Register(const char *Uname, - const char *Hname, - const char *Pname) -{ - const char *colonP, *atP; - char uBuff[1024], *uBP; - int n; - -// The identification always starts with the protocol being used -// - n = sprintf(uBuff, "%s/", Pname); - uBP = uBuff + n; - -// Decode the user name as a.b:c@d -// - if ((colonP = index(Uname, ':')) && (atP = index(colonP+1, '@'))) - {n = colonP - Uname + 1; - strncpy(uBP, Uname, n); - strcpy(uBP+n, sidName); - n += sidSize; *(uBP+n) = '@'; n++; - strcpy(uBP+n, Hname); - } else strcpy(uBP, Uname); - -// Generate a monitor identity for this user. We do not assign a dictioary -// identifier unless this entry is reported. -// - Agent = XrdXrootdMonitor::Alloc(); - Did = 0; - Len = strlen(uBuff); - Name = strdup(uBuff); - Iops = XrdXrootdMonitor::monIO; - Fops = XrdXrootdMonitor::monFILE; -} - -/******************************************************************************/ -/* C o n s t r u c t o r */ -/******************************************************************************/ - -XrdXrootdMonitor::XrdXrootdMonitor() -{ - kXR_int32 localWindow; - -// Initialize last window to force a mark as well as the local window -// - lastWindow = 0; - localWindow = currWindow; - -// Allocate a monitor buffer -// - if (!(monBuff = (XrdXrootdMonBuff *)memalign(getpagesize(), monBlen))) - eDest->Emsg("Monitor", "Unable to allocate monitor buffer."); - else {nextEnt = 1; - setTMark(monBuff, 0, localWindow); - } -} - -/******************************************************************************/ -/* D e s t r u c t o r */ -/******************************************************************************/ - -XrdXrootdMonitor::~XrdXrootdMonitor() -{ -// Release buffer - if (monBuff) {Flush(); free(monBuff);} -} - -/******************************************************************************/ -/* a p p I D */ -/******************************************************************************/ - -void XrdXrootdMonitor::appID(char *id) -{ - static const int apInfoSize = sizeof(XrdXrootdMonTrace)-4; - -// Application ID's are only meaningful for io event recording -// - if (this == altMon || !*id) return; - -// Fill out the monitor record -// - if (lastWindow != currWindow) Mark(); - else if (nextEnt == lastEnt) Flush(); - monBuff->info[nextEnt].arg0.id[0] = XROOTD_MON_APPID; - strncpy((char *)(&(monBuff->info[nextEnt])+4), id, apInfoSize); -} - -/******************************************************************************/ -/* A l l o c */ -/******************************************************************************/ - -XrdXrootdMonitor *XrdXrootdMonitor::Alloc(int force) -{ - XrdXrootdMonitor *mp; - int lastVal; - -// If enabled, create a new object (if possible). If we are not monitoring -// i/o then return the global object. -// - if (!isEnabled || (isEnabled < 0 && !force)) mp = 0; - else if (!monIO) mp = altMon; - else if ((mp = new XrdXrootdMonitor())) - if (!(mp->monBuff)) {delete mp; mp = 0;} - -// Check if we should turn on the monitor clock -// - if (mp && isEnabled < 0) - {windowMutex.Lock(); - lastVal = numMonitor; numMonitor++; - if (!lastVal && !monREDR) startClock(); - windowMutex.UnLock(); - } - -// All done -// - return mp; -} - -/******************************************************************************/ -/* C l o s e */ -/******************************************************************************/ - -void XrdXrootdMonitor::Close(kXR_unt32 dictid, long long rTot, long long wTot) -{ - XrdXrootdMonitorLock mLock(this); - unsigned int rVal, wVal; - -// Fill out the monitor record (we allow the compiler to correctly cast data) -// - if (lastWindow != currWindow) Mark(); - else if (nextEnt == lastEnt) Flush(); - monBuff->info[nextEnt].arg0.id[0] = XROOTD_MON_CLOSE; - monBuff->info[nextEnt].arg0.id[1] = do_Shift(rTot, rVal); - monBuff->info[nextEnt].arg0.rTot[1] = htonl(rVal); - monBuff->info[nextEnt].arg0.id[2] = do_Shift(wTot, wVal); - monBuff->info[nextEnt].arg0.id[3] = 0; - monBuff->info[nextEnt].arg1.wTot = htonl(wVal); - monBuff->info[nextEnt++].arg2.dictid = dictid; - -// Check if we need to duplicate this entry -// - if (altMon && this != altMon) altMon->Dup(&monBuff->info[nextEnt-1]); -} - -/******************************************************************************/ -/* D e f a u l t s */ -/******************************************************************************/ - -// This version must be called after the subsequent version! - -void XrdXrootdMonitor::Defaults(char *dest1, int mode1, char *dest2, int mode2) -{ - int mmode; - -// Make sure if we have a dest1 we have mode -// - if (!dest1) - {mode1 = (dest1 = dest2) ? mode2 : 0; - dest2 = 0; mode2 = 0; - } else if (!dest2) mode2 = 0; - - -// Set the default destinations (caller supplied strdup'd strings) -// - if (Dest1) free(Dest1); - Dest1 = dest1; monMode1 = mode1; - if (Dest2) free(Dest2); - Dest2 = dest2; monMode2 = mode2; - -// Set overall monitor mode -// - mmode = mode1 | mode2; - monACTIVE = (mmode ? 1 : 0); - isEnabled = (mmode & XROOTD_MON_ALL ? 1 :-1); - monIO = (mmode & XROOTD_MON_IO ? 1 : 0); - monIO = (mmode & XROOTD_MON_IOV ? 2 : monIO); - monINFO = (mmode & XROOTD_MON_INFO ? 1 : 0); - monFILE = (mmode & XROOTD_MON_FILE ? 1 : 0) | monIO; - monREDR = (mmode & XROOTD_MON_REDR ? 1 : 0); - monUSER = (mmode & XROOTD_MON_USER ? 1 : 0); - monAUTH = (mmode & XROOTD_MON_AUTH ? 1 : 0); - monFSTAT = (mmode & XROOTD_MON_FSTA && monFSTAT ? 1 : 0); - -// Compute whether or not we need the clock running -// - if (monREDR || (isEnabled > 0 && (monIO || monFILE))) monCLOCK = 1; - -// Check where user information should go -// - if (((mode1 & XROOTD_MON_IO) && (mode1 & XROOTD_MON_USER)) - || ((mode2 & XROOTD_MON_IO) && (mode2 & XROOTD_MON_USER))) - {if ((!(mode1 & XROOTD_MON_IO) && (mode1 & XROOTD_MON_USER)) - || (!(mode2 & XROOTD_MON_IO) && (mode2 & XROOTD_MON_USER))) monUSER = 3; - else monUSER = 2; - } - -// If we are monitoring redirections then set an envar saying how often idents -// should be sent (this also tips off other layers to handle such monitoring) -// - if (monREDR) XrdOucEnv::Export("XRDMONRDR", monIdent); - -// Do final check -// - if (Dest1 == 0 && Dest2 == 0) isEnabled = 0; -} - -/******************************************************************************/ - -void XrdXrootdMonitor::Defaults(int msz, int rsz, int wsz, - int flush, int flash, int idt, int rnm, - int fsint, int fsopt, int fsion) -{ - -// Set default window size and flush time -// - sizeWindow = (wsz <= 0 ? 60 : wsz); - autoFlush = (flush <= 0 ? 600 : flush); - autoFlash = (flash <= 0 ? 0 : flash); - monIdent = (idt < 0 ? 0 : idt); - rdrNum = (rnm <= 0 || rnm > rdrMax ? 3 : rnm); - rdrWin = (sizeWindow > 16777215 ? 16777215 : sizeWindow); - rdrWin = htonl(rdrWin); - -// Set the fstat defaults -// - XrdXrootdMonFile::Defaults(fsint, fsopt, fsion); - monFSTAT = fsint != 0; - -// Set default monitor buffer size -// - if (msz <= 0) msz = 16384; - else if (msz < 1024) msz = 1024; - else msz = msz/sizeof(XrdXrootdMonTrace)*sizeof(XrdXrootdMonTrace); - lastEnt = (msz-sizeof(XrdXrootdMonHeader))/sizeof(XrdXrootdMonTrace); - monBlen = (lastEnt*sizeof(XrdXrootdMonTrace))+sizeof(XrdXrootdMonHeader); - lastEnt--; - -// Set default monitor redirect buffer size -// - if (rsz <= 0) rsz = 32768; - else if (rsz < 2048) rsz = 2048; - lastRnt = (rsz-(sizeof(XrdXrootdMonHeader) + 16))/sizeof(XrdXrootdMonRedir); - monRlen = (lastRnt*sizeof(XrdXrootdMonRedir))+sizeof(XrdXrootdMonHeader)+16; - lastRnt--; -} - -/******************************************************************************/ -/* D i s c */ -/******************************************************************************/ - -void XrdXrootdMonitor::Disc(kXR_unt32 dictid, int csec, char Flags) -{ - XrdXrootdMonitorLock mLock(this); - -// Check if this should not be included in the io trace -// - if (this != altMon && monUSER == 1 && altMon) - {altMon->Disc(dictid, csec); return;} - -// Fill out the monitor record (let compiler cast the data correctly) -// - if (lastWindow != currWindow) Mark(); - else if (nextEnt == lastEnt) Flush(); - monBuff->info[nextEnt].arg0.rTot[0] = 0; - monBuff->info[nextEnt].arg0.id[0] = XROOTD_MON_DISC; - monBuff->info[nextEnt].arg0.id[1] = Flags; - monBuff->info[nextEnt].arg1.wTot = htonl(csec); - monBuff->info[nextEnt++].arg2.dictid = dictid; - -// Check if we need to duplicate this entry -// - if (altMon && this != altMon && monUSER == 3) - altMon->Dup(&monBuff->info[nextEnt-1]); -} - -/******************************************************************************/ -/* D u p */ -/******************************************************************************/ - -void XrdXrootdMonitor::Dup(XrdXrootdMonTrace *mrec) -{ - XrdXrootdMonitorLock mLock(this); - -// Fill out the monitor record -// - if (lastWindow != currWindow) Mark(); - else if (nextEnt == lastEnt) Flush(); - memcpy(&monBuff->info[nextEnt],(const void *)mrec,sizeof(XrdXrootdMonTrace)); - nextEnt++; -} - -/******************************************************************************/ -/* Private: F e t c h */ -/******************************************************************************/ - -XrdXrootdMonitor::MonRdrBuff *XrdXrootdMonitor::Fetch() -{ - MonRdrBuff *bP; - -// Get the next available stream and promote another one -// - rdrMutex.Lock(); - if ((bP = rdrMP)) rdrMP = rdrMP->Next; - rdrMutex.UnLock(); - return bP; -} - -/******************************************************************************/ -/* I n i t */ -/******************************************************************************/ - -int XrdXrootdMonitor::Init(XrdScheduler *sp, XrdSysError *errp, - const char *iHost, const char *iProg, - const char *iName, int Port) -{ - static XrdXrootdMonitor_Ident MonIdent(sp, monIdent); - XrdXrootdMonMap *mP; - char iBuff[1024], iPuff[1024], *sName, *cP; - int i, Now = time(0); - bool aOK; - -// Set static variables -// - Sched = sp; - eDest = errp; - startTime = htonl(Now); - -// Generate our server ID -// - strcpy(iBuff, "=/"); - sprintf(iPuff, "%s&ver=%s", iProg, XrdVERSION); - sName = XrdOucUtils::Ident(mySID, iBuff+2, sizeof(iBuff)-2, - iHost, iPuff, iName, Port); - cP = (char *)&mySID; *cP = 0; *(cP+1) = 0; - sidSize = strlen(sName); - if (sidSize >= (int)sizeof(sidName)) sName[sizeof(sidName)-1] = 0; - strcpy(sidName, sName); - free(sName); - -// There is nothing to do unless we have been enabled via Defaults() -// - if (!isEnabled) return 1; - -// Setup the primary destination -// - InetDest1 = new XrdNetMsg(eDest, Dest1, &aOK); - if (!aOK) - {eDest->Emsg("Monitor", "Unable to setup primary monitor collector."); - return 0; - } - -// Setup the secondary destination -// - if (Dest2) - {InetDest2 = new XrdNetMsg(eDest, Dest2, &aOK); - if (!aOK) - {eDest->Emsg("Monitor","Unable to setup secondary monitor collector."); - return 0; - } - } - -// If there is a destination that is only collecting file events, then -// allocate a global monitor object but don't start the timer just yet. -// - if ((monMode1 && !(monMode1 & XROOTD_MON_IO)) - || (monMode2 && !(monMode2 & XROOTD_MON_IO))) - if (!(altMon = new XrdXrootdMonitor()) || !altMon->monBuff) - {if (altMon) {delete altMon; altMon = 0;} - eDest->Emsg("Monitor","allocate monitor; insufficient storage."); - return 0; - } - -// Turn on the monitoring clock if we need it running all the time -// - if (monCLOCK) startClock(); - -// Create identification record -// - idLen = strlen(iBuff) + sizeof(XrdXrootdMonHeader) + sizeof(kXR_int32); - idRec = (char *)malloc(idLen+1); - mP = (XrdXrootdMonMap *)idRec; - fillHeader(&(mP->hdr), XROOTD_MON_MAPIDNT, idLen); - mP->hdr.pseq = 0; - mP->dictid = 0; - strcpy(mP->info, iBuff); - -// Now schedule the first identification record -// - if (Sched && monIdent) Sched->Schedule((XrdJob *)&MonIdent); - -// If we are monitoring file stats then start that up -// - if (!Sched || !monFSTAT) monFSTAT = 0; - else if (!XrdXrootdMonFile::Init(Sched, eDest)) return 0; - -// If we are not monitoring redirections, we are done! -// - if (!monREDR) return 1; - -// Allocate as many redirection monitors as requested -// - for (i = 0; i < rdrNum; i++) - {rdrMon[i].Buff = (XrdXrootdMonBurr *)memalign(getpagesize(),monRlen); - if (!rdrMon[i].Buff) - {eDest->Emsg("Monitor", "Unable to allocate monitor rdr buffer."); - return 0; - } - rdrMon[i].Buff->sID = mySID; - rdrMon[i].Buff->sXX[0] = XROOTD_MON_REDSID; - rdrMon[i].Next = (i ? &rdrMon[i-1] : &rdrMon[0]); - rdrMon[i].nextEnt = 0; - rdrMon[i].flushIt = Now + autoFlush; - rdrMon[i].lastTOD = 0; - } - rdrMon[0].Next = &rdrMon[i-1]; - rdrMP = &rdrMon[0]; - -// All done -// - return 1; -} - -/******************************************************************************/ -/* Private: G e t D i c t I D */ -/******************************************************************************/ - -kXR_unt32 XrdXrootdMonitor::GetDictID() -{ - static XrdSysMutex seqMutex; - static unsigned int monSeqID = 1; - unsigned int mySeqID; - -// Assign a unique ID for this entry -// - seqMutex.Lock(); - mySeqID = monSeqID++; - seqMutex.UnLock(); - -// Return the ID -// - return htonl(mySeqID); -} - -/******************************************************************************/ -/* Private: M a p */ -/******************************************************************************/ - -kXR_unt32 XrdXrootdMonitor::Map(char code, XrdXrootdMonitor::User &uInfo, - const char *path) -{ - XrdXrootdMonMap map; - int size, montype; - -// Copy in the username and path -// - map.dictid = GetDictID(); - strcpy(map.info, uInfo.Name); - size = uInfo.Len; - if (path) - {*(map.info+size) = '\n'; - strlcpy(map.info+size+1, path, sizeof(map.info)-size-1); - size = size + strlen(path) + 1; - } - -// Fill in the header -// - size = sizeof(XrdXrootdMonHeader)+sizeof(kXR_int32)+size; - fillHeader(&map.hdr, code, size); - -// Route the packet to all destinations that need them -// - if (code == XROOTD_MON_MAPPATH) montype = XROOTD_MON_PATH; - else if (code == XROOTD_MON_MAPUSER) montype = XROOTD_MON_USER; - else montype = XROOTD_MON_INFO; - Send(montype, (void *)&map, size); - -// Return the dictionary id -// - return map.dictid; -} - -/******************************************************************************/ -/* O p e n */ -/******************************************************************************/ - -void XrdXrootdMonitor::Open(kXR_unt32 dictid, off_t fsize) -{ - XrdXrootdMonitorLock mLock(this); - - if (lastWindow != currWindow) Mark(); - else if (nextEnt == lastEnt) Flush(); - h2nll(fsize, monBuff->info[nextEnt].arg0.val); - monBuff->info[nextEnt].arg0.id[0] = XROOTD_MON_OPEN; - monBuff->info[nextEnt].arg1.buflen = 0; - monBuff->info[nextEnt++].arg2.dictid = dictid; - -// Check if we need to duplicate this entry -// - if (altMon && this != altMon) altMon->Dup(&monBuff->info[nextEnt-1]); -} - -/******************************************************************************/ -/* R e d i r e c t */ -/******************************************************************************/ - -int XrdXrootdMonitor::Redirect(kXR_unt32 mID, const char *hName, int Port, - char opC, const char *Path) -{ - XrdXrootdMonRedir *mtP; - MonRdrBuff *mP = Fetch(); - int n, slots, hLen, pLen; - char *dest; - -// Take care of the server's name which might actually be a path -// - if (*hName == '/') {Path = hName; hName = ""; hLen = 0;} - else {const char *quest = index(hName, '?'); - hLen = (quest ? quest - hName : strlen(hName)); - if (hLen > 256) hLen = 256; - } - -// Take care of the path -// - pLen = strlen(Path); - if (pLen > 1024) pLen = 1024; - -// Compute number of entries needed here -// - n = (hLen + 1 + pLen + 1); // ":\0" - slots = n / sizeof(XrdXrootdMonRedir); - if (n % sizeof(XrdXrootdMonRedir)) slots++; - pLen = slots * sizeof(XrdXrootdMonRedir) - (hLen+1); - -// Obtain a lock on this buffer -// - if (!mP) return 0; - mP->Mutex.Lock(); - -// If we don't have enough slots, flush this buffer. Note that we account for -// the ending timing mark here (an extra slot). -// - if (mP->nextEnt + slots + 2 >= lastRnt) Flush(mP); - -// Check if we need a timing mark -// - if (mP->lastTOD != rdrTOD) - {mP->lastTOD = rdrTOD; - setTMurk(mP->Buff, mP->nextEnt, mP->lastTOD); - mP->nextEnt++; - } - -// Fill out the buffer -// - mtP = &(mP->Buff->info[mP->nextEnt]); - mtP->arg0.rdr.Type = XROOTD_MON_REDIRECT | opC; - mtP->arg0.rdr.Dent = static_cast(slots); - mtP->arg0.rdr.Port = htons(static_cast(Port)); - mtP->arg1.dictid = mID; - dest = (char *)(mtP+1); - strncpy(dest, hName,hLen); dest += hLen; *dest++ = ':'; - strncpy(dest, Path, pLen); - -// Adjust pointer and return -// - mP->nextEnt = mP->nextEnt + (slots+1); - mP->Mutex.UnLock(); - return 0; -} - -/******************************************************************************/ -/* T i c k */ -/******************************************************************************/ - -time_t XrdXrootdMonitor::Tick() -{ - time_t Now = time(0); - int nextFlush; - -// We can safely set the window as we are the only ones doing so and memory -// access is atomic as long as it sits within a cache line (which it does). -// - currWindow = static_cast(Now); - rdrTOD = htonl(currWindow); - nextFlush = currWindow + autoFlush; - -// Check to see if we should flush the alternate monitor -// - if (altMon && currWindow >= FlushTime) - {XrdXrootdMonitorLock::Lock(); - if (currWindow >= FlushTime) - {if (altMon->nextEnt > 1) altMon->Flush(); - else FlushTime = nextFlush; - } - XrdXrootdMonitorLock::UnLock(); - } - -// Now check to see if we need to flush redirect buffers -// - if (monREDR) - {int n = rdrNum; - while(n--) - {rdrMon[n].Mutex.Lock(); - if (rdrMon[n].nextEnt == 0) rdrMon[n].flushIt = nextFlush; - else if (rdrMon[n].flushIt <= currWindow) Flush(&rdrMon[n]); - rdrMon[n].Mutex.UnLock(); - } - } - -// All done. Stop the clock if there is no reason for it to be running. The -// clock always runs if we are monitoring redirects or all clients. Otherwise, -// the clock only runs if we have a one or more client-specific monitors. -// - if (!monREDR && isEnabled < 0) - {windowMutex.Lock(); - if (!numMonitor) Now = 0; - windowMutex.UnLock(); - } - return Now; -} - -/******************************************************************************/ -/* u n A l l o c */ -/******************************************************************************/ - -void XrdXrootdMonitor::unAlloc(XrdXrootdMonitor *monp) -{ - -// We must delete this object if we are de-allocating the local monitor. -// - if (monp != altMon) delete monp; - -// Decrease number being monitored if in selective mode -// - if (isEnabled < 0) - {windowMutex.Lock(); - numMonitor--; - windowMutex.UnLock(); - } -} - -/******************************************************************************/ -/* P r i v a t e M e t h o d s */ -/******************************************************************************/ -/******************************************************************************/ -/* d o _ S h i f t */ -/******************************************************************************/ - -unsigned char XrdXrootdMonitor::do_Shift(long long xTot, unsigned int &xVal) -{ - const long long smask = 0x7fffffff00000000LL; - const long long xmask = 0x7fffffffffffffffLL; - unsigned char xshift = 0; - - xTot &= xmask; - while(xTot & smask) {xTot = xTot >> 1LL; xshift++;} - xVal = static_cast(xTot); - - return xshift; -} - -/******************************************************************************/ -/* f i l l H e a d e r */ -/******************************************************************************/ - -void XrdXrootdMonitor::fillHeader(XrdXrootdMonHeader *hdr, - const char id, int size) -{ static XrdSysMutex seqMutex; - static int seq = 0; - int myseq; - -// Generate a new sequence number -// - seqMutex.Lock(); - myseq = 0x00ff & (seq++); - seqMutex.UnLock(); - -// Fill in the header -// - hdr->code = static_cast(id); - hdr->pseq = static_cast(myseq); - hdr->plen = htons(static_cast(size)); - hdr->stod = startTime; -} - -/******************************************************************************/ -/* F l u s h */ -/******************************************************************************/ - -void XrdXrootdMonitor::Flush() -{ - int size; - kXR_int32 localWindow, now; - -// Do not flush if the buffer is empty -// - if (nextEnt <= 1) return; - -// Get the current window marker. No need for locks as simple memory accesses -// are sufficiently synchrnozed for our purposes. -// - localWindow = currWindow; - -// Fill in the header and in the process we will have the current time -// - size = (nextEnt+1)*sizeof(XrdXrootdMonTrace)+sizeof(XrdXrootdMonHeader); - fillHeader(&monBuff->hdr, XROOTD_MON_MAPTRCE, size); - -// Punt on the right ending time. We are trying to keep same-sized windows -// This was corrected by Matevz Tadel, as before we were using real time which -// could have been far into the future due to simple inactivity. So, Place the -// computed ending timing mark. -// - now = lastWindow + sizeWindow; - setTMark(monBuff, nextEnt, now); - -// Send off the buffer and reinitialize it -// - if (this != altMon) Send(XROOTD_MON_IO, (void *)monBuff, size); - else {Send(XROOTD_MON_FILE, (void *)monBuff, size); - FlushTime = localWindow + autoFlush; - } - setTMark(monBuff, 0, localWindow); - nextEnt = 1; -} - -/******************************************************************************/ - -void XrdXrootdMonitor::Flush(XrdXrootdMonitor::MonRdrBuff *mP) -{ - int size; - -// Reset flush time but do not flush an empty buffer. We use the current time -// to make sure a record atleast sits in the buffer a full flush period. -// - mP->flushIt = static_cast(time(0)) + autoFlush; - if (mP->nextEnt <= 1) return; - -// Set ending timing mark and force a new one on the next fill -// - setTMurk(mP->Buff, mP->nextEnt, rdrTOD); - mP->lastTOD = 0; - -// Fill in the header and in the process we will have the current time -// - size = (mP->nextEnt+1)*sizeof(XrdXrootdMonRedir)+sizeof(XrdXrootdMonHeader)+8; - fillHeader(&(mP->Buff->hdr), XROOTD_MON_MAPREDR, size); - -// Send off the buffer and reinitialize it -// - Send(XROOTD_MON_REDR, (void *)(mP->Buff), size); - mP->nextEnt = 0; -} - -/******************************************************************************/ -/* M a r k */ -/******************************************************************************/ - -void XrdXrootdMonitor::Mark() -{ - kXR_int32 localWindow; - -// Get the current window marker. Since simple memory accesses are sufficiently -// synchronized, no need to lock this. -// - localWindow = currWindow; - -// Using an update provided by Matevz Tadel, UCSD, if this is an I/O buffer -// mark then we will also flush the I/O buffer if all the following hold: -// a) flushing enabled, b) buffer not empty, and c) covers the flush time. -// We would normally do this during Tick() but that would require too much -// locking in the middle of an I/O path, so we do psudo timed flushing. -// - if (this != altMon && autoFlash && nextEnt > 1) - {kXR_int32 bufStartWindow = - static_cast(ntohl(monBuff->info[0].arg2.Window)); - if (localWindow - bufStartWindow >= autoFlash) - {Flush(); - lastWindow = localWindow; - return; - } - } - -// Now, optimize placing the window mark in the buffer. Using another MT fix we -// set the end of the previous window to be lastwindow + sizeWindow (instead of -// localWindow) to prevent windows from being wrongly zero sized. -// - if (monBuff->info[nextEnt-1].arg0.id[0] == XROOTD_MON_WINDOW) - { - monBuff->info[nextEnt-1].arg2.Window = - static_cast(htonl(localWindow)); - } - else if (nextEnt+8 > lastEnt) - { - Flush(); - } - else - { - monBuff->info[nextEnt].arg0.val = mySID; - monBuff->info[nextEnt].arg0.id[0] = XROOTD_MON_WINDOW; - monBuff->info[nextEnt].arg1.Window = - static_cast(htonl(lastWindow + sizeWindow)); - monBuff->info[nextEnt].arg2.Window = - static_cast(htonl(localWindow)); - nextEnt++; - } - lastWindow = localWindow; -} - -/******************************************************************************/ -/* S e n d */ -/******************************************************************************/ - -int XrdXrootdMonitor::Send(int monMode, void *buff, int blen) -{ -#ifndef NODEBUG - const char *TraceID = "Monitor"; -#endif - static XrdSysMutex sendMutex; - int rc1, rc2; - - sendMutex.Lock(); - if (monMode & monMode1 && InetDest1) - {rc1 = InetDest1->Send((char *)buff, blen); - TRACE(DEBUG,blen <<" bytes sent to " <info[nextEnt].arg0.id[0] = vtype; - monBuff->info[nextEnt].arg0.id[1] = vseq; - monBuff->info[nextEnt].arg0.sVal[1] = vcnt; - monBuff->info[nextEnt].arg0.rTot[1] = 0; - monBuff->info[nextEnt].arg1.buflen = rlen; - monBuff->info[nextEnt++].arg2.dictid = dictid; - } - -inline void Add_wr(kXR_unt32 dictid, - kXR_int32 wlen, - kXR_int64 offset) - {Add_io(dictid,(kXR_int32)htonl(-wlen),offset);} - - void appID(char *id); - - void Close(kXR_unt32 dictid, long long rTot, long long wTot); - - void Disc(kXR_unt32 dictid, int csec, char Flags=0); - -static void Defaults(char *dest1, int m1, char *dest2, int m2); -static void Defaults(int msz, int rsz, int wsz, - int flush, int flash, int iDent, int rnm, - int fsint=0, int fsopt=0, int fsion=0); - -static void Ident() {Send(-1, idRec, idLen);} - -static int Init(XrdScheduler *sp, XrdSysError *errp, - const char *iHost, const char *iProg, - const char *iName, int Port); - - void Open(kXR_unt32 dictid, off_t fsize); - -static int Redirect() {return monREDR;} - -static int Redirect(kXR_unt32 mID, const char *hName, int Port, - const char opC, const char *Path); - -static time_t Tick(); - -class User -{ -public: - -XrdXrootdMonitor *Agent; -kXR_unt32 Did; -char Iops; -char Fops; -short Len; -char *Name; - -inline int Auths() {return XrdXrootdMonitor::monAUTH;} - -void Clear() {if (Name) {free(Name); Name = 0; Len = 0;} - if (Agent) {Agent->unAlloc(Agent); Agent = 0;} - Did = 0; Iops = Fops = 0; - } - - void Enable(); - - void Disable(); - -inline int Files() {return (Agent ? Fops : 0);} - -inline int Fstat() {return monFSTAT;} - -inline int Info() {return (Agent ? XrdXrootdMonitor::monINFO : 0);} - -inline int InOut() {return (Agent ? Iops : 0);} - -inline int Logins() {return (Agent ? XrdXrootdMonitor::monUSER : 0);} - -inline kXR_unt32 MapInfo(const char *Info) - {return XrdXrootdMonitor::Map(XROOTD_MON_MAPINFO, - *this, Info); - } - -inline kXR_unt32 MapPath(const char *Path) - {return XrdXrootdMonitor::Map(XROOTD_MON_MAPPATH, - *this, Path); - } - - void Register(const char *Uname, const char *Hname, - const char *Pname); - - void Report(const char *Info) - {Did=XrdXrootdMonitor::Map(XROOTD_MON_MAPUSER,*this,Info);} - -inline int Ready() {return XrdXrootdMonitor::monACTIVE;} - - User() : Agent(0), Did(0), Iops(0), Fops(0), Len(0), Name(0) {} - ~User() {Clear();} -}; - -static XrdXrootdMonitor *altMon; - - XrdXrootdMonitor(); - -static const int rdrMax = 8; - -private: - ~XrdXrootdMonitor(); - -static -struct MonRdrBuff - {MonRdrBuff *Next; - XrdXrootdMonBurr *Buff; - int nextEnt; - int flushIt; - kXR_int32 lastTOD; - XrdSysMutex Mutex; - } rdrMon[rdrMax]; -static MonRdrBuff *rdrMP; -static XrdSysMutex rdrMutex; - -inline void Add_io(kXR_unt32 duid, kXR_int32 blen, kXR_int64 offs) - {if (lastWindow != currWindow) Mark(); - else if (nextEnt == lastEnt) Flush(); - monBuff->info[nextEnt].arg0.val = offs; - monBuff->info[nextEnt].arg1.buflen = blen; - monBuff->info[nextEnt++].arg2.dictid = duid; - } -static XrdXrootdMonitor *Alloc(int force=0); - unsigned char do_Shift(long long xTot, unsigned int &xVal); - void Dup(XrdXrootdMonTrace *mrec); -static void fillHeader(XrdXrootdMonHeader *hdr, - const char id, int size); -static MonRdrBuff *Fetch(); - void Flush(); -static void Flush(MonRdrBuff *mP); -static kXR_unt32 GetDictID(); -static kXR_unt32 Map(char code, XrdXrootdMonitor::User &uInfo, - const char *path); - void Mark(); -static int Send(int mmode, void *buff, int size); -static void startClock(); -static void unAlloc(XrdXrootdMonitor *monp); - -static XrdScheduler *Sched; -static XrdSysError *eDest; -static XrdSysMutex windowMutex; -static char *idRec; -static int idLen; -static char *Dest1; -static int monMode1; -static XrdNetMsg *InetDest1; -static char *Dest2; -static int monMode2; -static XrdNetMsg *InetDest2; - XrdXrootdMonBuff *monBuff; -static int monBlen; - int nextEnt; -static int lastEnt; -static int lastRnt; -static int autoFlash; -static int autoFlush; -static int FlushTime; -static kXR_int32 startTime; - kXR_int32 lastWindow; -static kXR_int32 currWindow; -static int rdrTOD; -static int rdrWin; -static int rdrNum; -static kXR_int32 sizeWindow; -static int isEnabled; -static int numMonitor; -static int monIdent; -static int monRlen; -static char sidName[16]; -static short sidSize; -static char monIO; -static char monINFO; -static char monFILE; -static char monREDR; -static char monUSER; -static char monAUTH; -static char monACTIVE; -static char monFSTAT; -static char monCLOCK; -}; -#endif diff --git a/src/XrdXrootd/XrdXrootdPio.cc b/src/XrdXrootd/XrdXrootdPio.cc deleted file mode 100644 index dc2b0705dbf..00000000000 --- a/src/XrdXrootd/XrdXrootdPio.cc +++ /dev/null @@ -1,85 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d X r o o t d P i o . c c */ -/* */ -/* (c) 2007 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdXrootd/XrdXrootdPio.hh" - -/******************************************************************************/ -/* S t a t i c V a r i a b l e s */ -/******************************************************************************/ - -XrdSysMutex XrdXrootdPio::myMutex; -XrdXrootdPio *XrdXrootdPio::Free = 0; -int XrdXrootdPio::FreeNum = 0; - -/******************************************************************************/ -/* A l l o c */ -/******************************************************************************/ - -XrdXrootdPio *XrdXrootdPio::Alloc(int Num) -{ - XrdXrootdPio *lqp, *qp=0; - - -// Allocate from the free stack -// - myMutex.Lock(); - if ((qp = Free)) - {do {FreeNum--; Num--; lqp = Free;} - while((Free = Free->Next) && Num); - lqp->Next = 0; - } - myMutex.UnLock(); - -// Allocate additional if we have not allocated enough -// - while(Num--) qp = new XrdXrootdPio(qp); - -// All done -// - return qp; -} - -/******************************************************************************/ -/* R e c y c l e */ -/******************************************************************************/ - -void XrdXrootdPio::Recycle() -{ - -// Check if we can hold on to this or must delete it -// - myMutex.Lock(); - if (FreeNum >= FreeMax) {myMutex.UnLock(); delete this; return;} - -// Clean this up and push the element on the free stack -// - Free = Clear(Free); FreeNum++; - myMutex.UnLock(); -} diff --git a/src/XrdXrootd/XrdXrootdPio.hh b/src/XrdXrootd/XrdXrootdPio.hh deleted file mode 100644 index 496592675f6..00000000000 --- a/src/XrdXrootd/XrdXrootdPio.hh +++ /dev/null @@ -1,78 +0,0 @@ -#ifndef __XRDXROOTDPIO__ -#define __XRDXROOTDPIO__ -/******************************************************************************/ -/* */ -/* X r d X r o o t d P i o . h h */ -/* */ -/* (c) 2007 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XProtocol/XPtypes.hh" -#include "XrdSys/XrdSysPthread.hh" - -class XrdXrootdFile; - -class XrdXrootdPio -{ -public: - - XrdXrootdPio *Next; - XrdXrootdFile *myFile; - long long myOffset; - int myIOLen; - kXR_char StreamID[2]; - char isWrite; - -static XrdXrootdPio *Alloc(int n=1); - -inline XrdXrootdPio *Clear(XrdXrootdPio *np=0) - {const kXR_char zed[2] = {0,0}; - Set(0, 0, 0, zed,'\0'); - Next = np; return this; - } - - void Recycle(); - -inline void Set(XrdXrootdFile *theFile, long long theOffset, - int theIOLen, const kXR_char *theSID, char theW) - {myFile = theFile; - myOffset = theOffset; - myIOLen = theIOLen; - StreamID[0] = theSID[0]; StreamID[1] = theSID[1]; - isWrite = theW; - } - - XrdXrootdPio(XrdXrootdPio *np=0) {Clear(np);} - ~XrdXrootdPio() {} - -private: - -static const int FreeMax = 256; -static XrdSysMutex myMutex; -static XrdXrootdPio *Free; -static int FreeNum; -}; -#endif diff --git a/src/XrdXrootd/XrdXrootdPlugin.cc b/src/XrdXrootd/XrdXrootdPlugin.cc deleted file mode 100644 index 33f26445bf7..00000000000 --- a/src/XrdXrootd/XrdXrootdPlugin.cc +++ /dev/null @@ -1,93 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d X r o o t d P l u g i n . c c */ -/* */ -/* (c) 2014 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdVersion.hh" - -#include "XrdXrootd/XrdXrootdProtocol.hh" - -/******************************************************************************/ -/* P r o t o c o l L o a d e r */ -/* X r d g e t P r o t o c o l */ -/******************************************************************************/ - -// This protocol is defined in a shared library. The interface below is used -// by the protocol driver to obtain a copy of the protocol object that can be -// used to decide whether or not a link is talking a particular protocol. This -// definition is used when XRootD is loaded as an ancillary protocol. -// -XrdVERSIONINFO(XrdgetProtocol,xrootd); - -extern "C" -{ -XrdProtocol *XrdgetProtocol(const char *pname, char *parms, - XrdProtocol_Config *pi) -{ - XrdProtocol *pp = 0; - const char *txt = "completed."; - -// Put up the banner -// - pi->eDest->Say("Copr. 2012 Stanford University, xrootd protocol " - kXR_PROTOCOLVSTRING, " version ", XrdVERSION); - pi->eDest->Say("++++++ xrootd protocol initialization started."); - -// Return the protocol object to be used if static init succeeds -// - if (XrdXrootdProtocol::Configure(parms, pi)) - pp = (XrdProtocol *)new XrdXrootdProtocol(); - else txt = "failed."; - pi->eDest->Say("------ xrootd protocol initialization ", txt); - return pp; -} -} - -/******************************************************************************/ -/* */ -/* P r o t o c o l P o r t D e t e r m i n a t i o n */ -/* X r d g e t P r o t o c o l P o r t */ -/******************************************************************************/ - -// This function is called early on to determine the port we need to use. The -// default is ostensibly 1094 but can be overidden; which we allow. -// -XrdVERSIONINFO(XrdgetProtocolPort,xrootd); - -extern "C" -{ -int XrdgetProtocolPort(const char *pname, char *parms, XrdProtocol_Config *pi) -{ - -// Figure out what port number we should return. In practice only one port -// number is allowed. However, we could potentially have a clustered port -// and several unclustered ports. So, we let this practicality slide. -// - if (pi->Port < 0) return 1094; - return pi->Port; -} -} diff --git a/src/XrdXrootd/XrdXrootdPrepare.cc b/src/XrdXrootd/XrdXrootdPrepare.cc deleted file mode 100644 index dba9d3e3743..00000000000 --- a/src/XrdXrootd/XrdXrootdPrepare.cc +++ /dev/null @@ -1,358 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d X r o o t d P r e p a r e . c c */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef __linux__ -#include -#define getdents(fd, dirp, cnt) syscall(SYS_getdents, fd, dirp, cnt) -#endif - -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysPlatform.hh" -#include "XrdOuc/XrdOucTList.hh" -#include "XrdXrootd/XrdXrootdPrepare.hh" -#include "XrdXrootd/XrdXrootdTrace.hh" - -/******************************************************************************/ -/* G l o b a l O b j e c t s */ -/******************************************************************************/ - -/******************************************************************************/ -/* G l o b a l s */ -/******************************************************************************/ - -#ifndef NODEBUG -extern XrdOucTrace *XrdXrootdTrace; -#endif - - XrdScheduler *XrdXrootdPrepare::SchedP; - - XrdSysError *XrdXrootdPrepare::eDest; // Error message handler - - int XrdXrootdPrepare::scrubtime = 60*60; - int XrdXrootdPrepare::scrubkeep = 60*60*24; - char *XrdXrootdPrepare::LogDir = 0; - int XrdXrootdPrepare::LogDirLen = 0; -const char *XrdXrootdPrepare::TraceID = "Prepare"; - -/******************************************************************************/ -/* C o n s t r u c t o r */ -/******************************************************************************/ - -XrdXrootdPrepare::XrdXrootdPrepare(XrdSysError *errp, XrdScheduler *sp) - : XrdJob("Prep log scrubber") -{eDest = errp; - SchedP = sp; - if (LogDir) SchedP->Schedule((XrdJob *)this, scrubtime+time(0)); - else eDest->Say("Config warning: 'xrootd.prepare logdir' not specified; " - "prepare tracking disabled."); -} - -/******************************************************************************/ -/* L i s t */ -/******************************************************************************/ - -int XrdXrootdPrepare::List(XrdXrootdPrepArgs &pargs, char *resp, int resplen) -{ - char *up, path[2048]; - struct dirent *dp; - struct stat buf; - int rc; - -// If logging is not supported, return eof -// - if (!LogDir) return -1; - -// Check if this is the first call -// - if (!pargs.dirP) - {if (!(pargs.dirP = opendir((const char *)LogDir))) - {eDest->Emsg("List", errno, "open prep log directory", LogDir); - return -1; - } - if (pargs.reqid) pargs.reqlen = strlen(pargs.reqid); - if (pargs.user) pargs.usrlen = strlen(pargs.user); - } - -// Find the next entry that satisfies the search criteria -// - errno = 0; - while((dp = readdir(pargs.dirP))) - {if (!(up = (char *) index((const char *)dp->d_name, '_'))) continue; - if (pargs.reqlen && strncmp(dp->d_name, pargs.reqid, pargs.reqlen)) - continue; - if (pargs.usrlen) - if (!up || strcmp((const char *)up+1,(const char *)pargs.user)) - continue; - strcpy(path, (const char *)LogDir); - strcpy(path+LogDirLen, (const char *)dp->d_name); - if (stat((const char *)path, &buf)) continue; - *up = ' '; - if ((up = (char *) index((const char *)(up+1), (int)'_'))) *up = ' '; - else continue; - if ((up = (char *) index((const char *)(up+1), (int)'_'))) *up = ' '; - else continue; - return snprintf(resp, resplen-1, "%s %ld", dp->d_name, buf.st_mtime); - } - -// Completed -// - if ((rc = errno)) - eDest->Emsg("List", errno, "read prep log directory", LogDir); - closedir(pargs.dirP); - pargs.dirP = 0; - return (rc ? -1 : 0); -} - -/******************************************************************************/ -/* L o g */ -/******************************************************************************/ - -void XrdXrootdPrepare::Log(XrdXrootdPrepArgs &pargs) -{ - int rc, pnum = 0, xfd; - XrdOucTList *tp = pargs.paths; - char buff[2048], blink[2048]; - struct iovec iovec[2]; - -// If logging not enabled, return -// - if (!LogDir) return; - -// Count number of paths in the list -// - while(tp) {pnum++; tp = tp->next;} - -// Construct the file name: ___ -// - snprintf(buff, sizeof(buff)-1, "%s%s_%s_%d_%d", LogDir, - pargs.reqid, pargs.user, pargs.prty, pnum); - -// Create the file -// - if ((xfd = open(buff, O_WRONLY|O_CREAT|O_TRUNC,0644)) < 0) - {eDest->Emsg("Log", errno, "open prep log file", buff); - return; - } - -// Write all the paths into the file, separating each by a space -// - iovec[1].iov_base = (char *)" "; - iovec[1].iov_len = 1; - tp = pargs.paths; - while(tp) - {if (tp->next == 0) iovec[1].iov_base = (char *)"\n"; - iovec[0].iov_base = tp->text; - iovec[0].iov_len = strlen(tp->text); - do {rc = writev(xfd, (const struct iovec *)iovec, 2);} - while(rc < 0 && errno == EINTR); - if (rc < 0) - {eDest->Emsg("Log", errno, "write prep log file", buff); - close(xfd); - return; - } - tp = tp->next; - } - -// Create a symlink to the file -// - close(xfd); - strcpy(blink, LogDir); - strlcpy(blink+LogDirLen, pargs.reqid, sizeof(blink)-1); - if (symlink((const char *)buff, (const char *)blink)) - {eDest->Emsg("Log", errno, "create symlink to prep log file", buff); - return; - } -} - -/******************************************************************************/ -/* L o g d e l */ -/******************************************************************************/ - -void XrdXrootdPrepare::Logdel(char *reqid) -{ - int rc; - char path[MAXPATHLEN+256], buff[MAXPATHLEN+1]; - -// If logging not enabled, return -// - if (!LogDir || strlen(reqid) > 255) return; - -// Construct the file name of the symlink -// - strcpy(path, (const char *)LogDir); - strcpy(&path[LogDirLen], (const char *)reqid); - -// Read the symlink contents for this request -// - if ((rc = readlink((const char *)path, buff, sizeof(buff)-1)) < 0) - {if (errno != ENOENT) eDest->Emsg("Logdel",errno,"read symlink",path); - return; - } - -// Delete the file, then the symlink -// - buff[rc] = '\0'; - if (unlink((const char *)buff) - && errno != ENOENT) eDest->Emsg("Logdel",errno,"remove",buff); - else TRACE(DEBUG, "Logdel removed " <Emsg("Logdel", errno, "remove", path); - else TRACE(DEBUG, "Logdel removed " <Emsg("Scrub", errno, "open prep log directory", LogDir); - return; - } - strcpy(path, (const char *)LogDir); - -// Delete all stale entries -// - errno = 0; - while((dp = readdir(prepD))) - {if (!(up = (char *) index((const char *)dp->d_name, '_'))) continue; - strcpy(fn, (const char *)dp->d_name); - if (stat((const char *)path, &buf)) continue; - if (buf.st_mtime <= stale) - {TRACE(DEBUG, "Scrub removed stale prep log " <d_name)) = '\0'; - unlink((const char *)path); - errno = 0; - } - } - -// All done -// - if (errno) - eDest->Emsg("List", errno, "read prep log directory", LogDir); - closedir(prepD); -} - -/******************************************************************************/ -/* s e t P a r m s */ -/******************************************************************************/ - -int XrdXrootdPrepare::setParms(int stime, int keep) -{if (stime > 0) scrubtime = stime; - if (keep > 0) scrubkeep = keep; - return 0; -} - -int XrdXrootdPrepare::setParms(char *ldir) -{ - char path[2048]; - struct stat buf; - int plen; - -// If parm not supplied, ignore call -// - if (!ldir) return 0; - -// Make sure we have appropriate permissions for this directory -// - if (access((const char *)ldir, X_OK | W_OK | R_OK) || stat(ldir, &buf)) - return -errno; - if ((buf.st_mode & S_IFMT) != S_IFDIR) return -ENOTDIR; - -// Create the path name -// - if (LogDir) free(LogDir); - LogDir = 0; - plen = strlen(ldir); - strcpy(path, ldir); - if (path[plen-1] != '/') path[plen++] = '/'; - path[plen] = '\0'; - -// Save the path and return -// - LogDir = strdup(path); - LogDirLen = strlen(LogDir); - return 0; -} diff --git a/src/XrdXrootd/XrdXrootdPrepare.hh b/src/XrdXrootd/XrdXrootdPrepare.hh deleted file mode 100644 index 26fe85efaf0..00000000000 --- a/src/XrdXrootd/XrdXrootdPrepare.hh +++ /dev/null @@ -1,121 +0,0 @@ -#ifndef __XROOTD_PREPARE__H -#define __XROOTD_PREPARE__H -/******************************************************************************/ -/* */ -/* X r d X r o o t d P r e p a r e . h h */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -#include "Xrd/XrdJob.hh" -#include "Xrd/XrdScheduler.hh" -#include "XrdSys/XrdSysError.hh" -#include "XrdOuc/XrdOucTList.hh" - -/******************************************************************************/ -/* X r d O l b P r e p A r g s */ -/******************************************************************************/ - -class XrdXrootdPrepArgs -{ -public: -friend class XrdXrootdPrepare; - -char *reqid; -char *user; -char *notify; -int prty; -char mode[4]; -XrdOucTList *paths; - - XrdXrootdPrepArgs(int sfree=1, int pfree=1) - {reqid = user = notify = 0; paths = 0; *mode = '\0'; - dirP = 0; prty = reqlen = usrlen = 0; - freestore = sfree; freepaths = pfree; - } - ~XrdXrootdPrepArgs() - {XrdOucTList *tp; - if (freestore) - {if (reqid) free(reqid); - if (notify) free(notify); - } - if (freepaths) while((tp=paths)) {paths=paths->next; delete tp;} - if (dirP) closedir(dirP); - } -private: -DIR *dirP; -int reqlen; -int usrlen; -int freestore; -int freepaths; -}; - -/******************************************************************************/ -/* C l a s s X r d O l b P r e p a r e */ -/******************************************************************************/ - -class XrdXrootdPrepare : public XrdJob -{ -public: - -static int Close(int fd) {return close(fd);} - - void DoIt() {Scrub(); - SchedP->Schedule((XrdJob *)this, scrubtime+time(0)); - } - -static int List(XrdXrootdPrepArgs &pargs, char *resp, int resplen); - -static void Log(XrdXrootdPrepArgs &pargs); - -static void Logdel(char *reqid); - -static int Open(const char *reqid, int &fsz); - -static void Scrub(); - -static int setParms(int stime, int skeep); - -static int setParms(char *ldir); - - XrdXrootdPrepare(XrdSysError *lp, XrdScheduler *sp); - ~XrdXrootdPrepare() {} // Never gets deleted - -private: - -static const char *TraceID; -static XrdScheduler *SchedP; // System scheduler -static XrdSysError *eDest; // Error message handler - -static int scrubtime; -static int scrubkeep; -static char *LogDir; -static int LogDirLen; -}; -#endif diff --git a/src/XrdXrootd/XrdXrootdProtocol.cc b/src/XrdXrootd/XrdXrootdProtocol.cc deleted file mode 100644 index 20b3988c2cc..00000000000 --- a/src/XrdXrootd/XrdXrootdProtocol.cc +++ /dev/null @@ -1,875 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d X r o o t d P r o t o c o l . c c */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdVersion.hh" - -#include "XrdSfs/XrdSfsInterface.hh" -#include "Xrd/XrdBuffer.hh" -#include "Xrd/XrdLink.hh" -#include "XProtocol/XProtocol.hh" -#include "XrdOuc/XrdOucStream.hh" -#include "XrdSec/XrdSecProtect.hh" -#include "XrdSys/XrdSysTimer.hh" -#include "XrdXrootd/XrdXrootdAio.hh" -#include "XrdXrootd/XrdXrootdFile.hh" -#include "XrdXrootd/XrdXrootdFileLock.hh" -#include "XrdXrootd/XrdXrootdFileLock1.hh" -#include "XrdXrootd/XrdXrootdMonFile.hh" -#include "XrdXrootd/XrdXrootdMonitor.hh" -#include "XrdXrootd/XrdXrootdPio.hh" -#include "XrdXrootd/XrdXrootdProtocol.hh" -#include "XrdXrootd/XrdXrootdStats.hh" -#include "XrdXrootd/XrdXrootdTrace.hh" -#include "XrdXrootd/XrdXrootdXPath.hh" - -/******************************************************************************/ -/* G l o b a l s */ -/******************************************************************************/ - -XrdOucTrace *XrdXrootdTrace; - -XrdXrootdXPath XrdXrootdProtocol::RPList; -XrdXrootdXPath XrdXrootdProtocol::RQList; -XrdXrootdXPath XrdXrootdProtocol::XPList; -XrdSfsFileSystem *XrdXrootdProtocol::osFS; -XrdSfsFileSystem *XrdXrootdProtocol::digFS = 0; -char *XrdXrootdProtocol::FSLib[2] = {0, 0}; -int XrdXrootdProtocol::FSLvn[2] = {0, 0}; -char *XrdXrootdProtocol::digLib = 0; -char *XrdXrootdProtocol::digParm = 0; -XrdXrootdFileLock *XrdXrootdProtocol::Locker; -XrdSecService *XrdXrootdProtocol::CIA = 0; -XrdSecProtector *XrdXrootdProtocol::DHS = 0; -char *XrdXrootdProtocol::SecLib = 0; -char *XrdXrootdProtocol::pidPath = strdup("/tmp"); -XrdScheduler *XrdXrootdProtocol::Sched; -XrdBuffManager *XrdXrootdProtocol::BPool; -XrdSysError XrdXrootdProtocol::eDest(0, "Xrootd"); -XrdXrootdStats *XrdXrootdProtocol::SI; -XrdXrootdJob *XrdXrootdProtocol::JobCKS = 0; -char *XrdXrootdProtocol::JobCKT = 0; -XrdOucTList *XrdXrootdProtocol::JobCKTLST= 0; -XrdOucReqID *XrdXrootdProtocol::PrepID = 0; - -char *XrdXrootdProtocol::Notify = 0; -const char *XrdXrootdProtocol::myCName= 0; -int XrdXrootdProtocol::myCNlen= 0; -int XrdXrootdProtocol::hailWait; -int XrdXrootdProtocol::readWait; -int XrdXrootdProtocol::Port; -int XrdXrootdProtocol::Window; -int XrdXrootdProtocol::WANPort; -int XrdXrootdProtocol::WANWindow; -char XrdXrootdProtocol::isRedir = 0; -char XrdXrootdProtocol::JobLCL = 0; -char XrdXrootdProtocol::JobCKCGI=0; -XrdNetSocket *XrdXrootdProtocol::AdminSock= 0; - -int XrdXrootdProtocol::hcMax = 28657; // const for now -int XrdXrootdProtocol::maxBuffsz; -int XrdXrootdProtocol::maxTransz = 262144; // 256KB -int XrdXrootdProtocol::as_maxperlnk = 8; // Max ops per link -int XrdXrootdProtocol::as_maxperreq = 8; // Max ops per request -int XrdXrootdProtocol::as_maxpersrv = 4096;// Max ops per server -int XrdXrootdProtocol::as_segsize = 131072; -int XrdXrootdProtocol::as_miniosz = 32768; -#ifdef __solaris__ -int XrdXrootdProtocol::as_minsfsz = 1; -#else -int XrdXrootdProtocol::as_minsfsz = 8192; -#endif -int XrdXrootdProtocol::as_maxstalls = 5; -int XrdXrootdProtocol::as_force = 0; -int XrdXrootdProtocol::as_noaio = 0; -int XrdXrootdProtocol::as_nosf = 0; -int XrdXrootdProtocol::as_syncw = 0; - -const char *XrdXrootdProtocol::myInst = 0; -const char *XrdXrootdProtocol::TraceID = "Protocol"; -int XrdXrootdProtocol::RQLxist = 0; -int XrdXrootdProtocol::myPID = static_cast(getpid()); - -int XrdXrootdProtocol::myRole = 0; -int XrdXrootdProtocol::myRolf = 0; - -int XrdXrootdProtocol::PrepareLimit = -1; -bool XrdXrootdProtocol::LimitError = true; - -struct XrdXrootdProtocol::RD_Table XrdXrootdProtocol::Route[RD_Num]; -int XrdXrootdProtocol::OD_Stall = 33; -bool XrdXrootdProtocol::OD_Bypass= false; -bool XrdXrootdProtocol::OD_Redir = false; - -/******************************************************************************/ -/* P r o t o c o l M a n a g e m e n t S t a c k s */ -/******************************************************************************/ - -XrdObjectQ - XrdXrootdProtocol::ProtStack("ProtStack", - "xrootd protocol anchor"); - -/******************************************************************************/ -/* P r o t o c o l L o a d e r */ -/* X r d g e t P r o t o c o l */ -/******************************************************************************/ - -// This protocol can live in a shared library. The interface below is used by -// the protocol driver to obtain a copy of the protocol object that can be used -// to decide whether or not a link is talking a particular protocol. -// -XrdVERSIONINFO(XrdgetProtocol,xrootd); - -extern "C" -{ -XrdProtocol *XrdgetProtocol(const char *pname, char *parms, - XrdProtocol_Config *pi) -{ - XrdProtocol *pp = 0; - const char *txt = "completed."; - -// Put up the banner -// - pi->eDest->Say("Copr. 2012 Stanford University, xrootd protocol " - kXR_PROTOCOLVSTRING, " version ", XrdVERSION); - pi->eDest->Say("++++++ xrootd protocol initialization started."); - -// Return the protocol object to be used if static init succeeds -// - if (XrdXrootdProtocol::Configure(parms, pi)) - pp = (XrdProtocol *)new XrdXrootdProtocol(); - else txt = "failed."; - pi->eDest->Say("------ xrootd protocol initialization ", txt); - return pp; -} -} - -/******************************************************************************/ -/* */ -/* P r o t o c o l P o r t D e t e r m i n a t i o n */ -/* X r d g e t P r o t o c o l P o r t */ -/******************************************************************************/ - -// This function is called early on to determine the port we need to use. The -// default is ostensibly 1094 but can be overidden; which we allow. -// -XrdVERSIONINFO(XrdgetProtocolPort,xrootd); - -extern "C" -{ -int XrdgetProtocolPort(const char *pname, char *parms, XrdProtocol_Config *pi) -{ - -// Figure out what port number we should return. In practice only one port -// number is allowed. However, we could potentially have a clustered port -// and several unclustered ports. So, we let this practicality slide. -// - if (pi->Port < 0) return 1094; - return pi->Port; -} -} - -/******************************************************************************/ -/* X r d P r o t o c o l X r o o t d C l a s s */ -/******************************************************************************/ -/******************************************************************************/ -/* C o n s t r u c t o r */ -/******************************************************************************/ - -XrdXrootdProtocol::XrdXrootdProtocol() - : XrdProtocol("xrootd protocol handler"), ProtLink(this), - Entity("") -{ - Reset(); -} - -/******************************************************************************/ -/* A s s i g n m e n t O p e r a t o r */ -/******************************************************************************/ - -XrdXrootdProtocol XrdXrootdProtocol::operator =(const XrdXrootdProtocol &rhs) -{ -// Reset all common fields -// - abort(); - Reset(); - -// Now copy the relevant fields only -// - Link = rhs.Link; - Link->setRef(1); // Keep the link stable until we dereference it - Status = rhs.Status; - myFile = rhs.myFile; - myIOLen = rhs.myIOLen; - myOffset = rhs.myOffset; - Response = rhs.Response; - memcpy((void *)&Request,(const void *)&rhs.Request, sizeof(Request)); - Client = rhs.Client; - AuthProt = rhs.AuthProt; - return *this; -} - -/******************************************************************************/ -/* M a t c h */ -/******************************************************************************/ - -#define TRACELINK lp - -XrdProtocol *XrdXrootdProtocol::Match(XrdLink *lp) -{ -static const int hsSZ = sizeof(ClientInitHandShake); -static const int prSZ = sizeof(ClientProtocolRequest); -static const int hpSZ = hsSZ + prSZ; - char hsbuff[hpSZ]; - struct ClientInitHandShake *hsData = (ClientInitHandShake *)hsbuff; - struct ClientProtocolRequest *hsRqst = (ClientProtocolRequest *)(hsbuff + hsSZ); - -static struct hs_response - {kXR_unt16 streamid; - kXR_unt16 status; - kXR_unt32 rlen; // Specified as kXR_int32 in doc! - kXR_unt32 pval; // Specified as kXR_int32 in doc! - kXR_unt32 styp; // Specified as kXR_int32 in doc! - } hsresp={0, 0, htonl(8), // isRedir == 'M' -> MetaManager - htonl(kXR_PROTOCOLVERSION), - (isRedir ? htonl((unsigned int)kXR_LBalServer) - : htonl((unsigned int)kXR_DataServer))}; - -XrdXrootdProtocol *xp; -int dlen, rc; - -// Peek at the first 20 bytes of data -// - if ((dlen = lp->Peek(hsbuff, hpSZ, hailWait)) < hsSZ) - {if (dlen <= 0) lp->setEtext("handshake not received"); - return (XrdProtocol *)0; - } - -// Trace the data -// -// TRACEI(REQ, "received: " <bin2hex(hsbuff,dlen)); - -// Verify that this is our protocol -// - hsData->fourth = ntohl(hsData->fourth); - hsData->fifth = ntohl(hsData->fifth); - if (hsData->first || hsData->second || hsData->third - || hsData->fourth != 4 || hsData->fifth != ROOTD_PQ) return 0; - -// Optimized clients using protocol 2.9.7 or above will piggy-back a protocol -// request with the handshake. We optimize the response here as well. -// - if (dlen != hpSZ||ntohs(hsRqst->requestid) != kXR_protocol||hsRqst->dlen) - {dlen = hsSZ; - rc = lp->Send((char *)&hsresp, sizeof(hsresp)); - } else { - struct {struct ServerResponseHeader Hdr; - struct ServerResponseBody_Protocol Rsp; - } hsprot; - struct iovec iov[2] = {{(char *)&hsresp, sizeof(hsresp)}, - {(char *)&hsprot, 0} - }; - int rspLen; - memcpy(&Request, hsRqst, sizeof(Request)); - memcpy(hsprot.Hdr.streamid,hsRqst->streamid,sizeof(hsprot.Hdr.streamid)); - rspLen = do_Protocol(&hsprot.Rsp); - hsprot.Hdr.dlen = htonl(rspLen); - hsprot.Hdr.status = 0; - iov[1].iov_len = sizeof(hsprot.Hdr) + rspLen; - rc = lp->Send(iov, 2, sizeof(hsresp)+sizeof(hsprot.Hdr)+rspLen); - } - -// Verify that our handshake response was actually sent -// - if (!rc) - {lp->setEtext("handshake failed"); - return (XrdProtocol *)0; - } - -// We can now read all 20 bytes and discard them (no need to wait for it) -// - if (lp->Recv(hsbuff, dlen) != dlen) - {lp->setEtext("reread failed"); - return (XrdProtocol *)0; - } - -// Get a protocol object off the stack (if none, allocate a new one) -// - if (!(xp = ProtStack.Pop())) xp = new XrdXrootdProtocol(); - -// Bind the protocol to the link and return the protocol -// - SI->Bump(SI->Count); - xp->Link = lp; - xp->Response.Set(lp); - strcpy(xp->Entity.prot, "host"); - xp->Entity.host = (char *)lp->Host(); - xp->Entity.addrInfo = lp->AddrInfo(); - return (XrdProtocol *)xp; -} - -/******************************************************************************/ -/* P r o c e s s */ -/******************************************************************************/ - -#undef TRACELINK -#define TRACELINK Link - -int XrdXrootdProtocol::Process(XrdLink *lp) // We ignore the argument here -{ - int rc; - kXR_unt16 reqID; - -// Check if we are servicing a slow link -// - if (Resume) - {if (myBlen && (rc = getData("data", myBuff, myBlen)) != 0) - {if (rc < 0 && myAioReq) myAioReq->Recycle(-1); - return rc; - } - else if ((rc = (*this.*Resume)()) != 0) return rc; - else {Resume = 0; return 0;} - } - -// Read the next request header -// - if ((rc=getData("request",(char *)&Request,sizeof(Request))) != 0) return rc; - -// Check if we need to copy the request prior to unmarshalling it -// - reqID = ntohs(Request.header.requestid); - if (reqID != kXR_sigver && NEED2SECURE(Protect)(Request)) - {memcpy(&sigReq2Ver, &Request, sizeof(ClientRequest)); - sigNeed = true; - } - -// Deserialize the data -// - Request.header.requestid = reqID; - Request.header.dlen = ntohl(Request.header.dlen); - Response.Set(Request.header.streamid); - TRACEP(REQ, "req=" <setEtext("protocol data length error"); - } - -// Process sigver requests now as they appear ahead of a request -// - if (reqID == kXR_sigver) return ProcSig(); - -// Read any argument data at this point, except when the request is a write. -// The argument may have to be segmented and we're not prepared to do that here. -// - if (reqID != kXR_write && Request.header.dlen) - {if (!argp || Request.header.dlen+1 > argp->bsize) - {if (argp) BPool->Release(argp); - if (!(argp = BPool->Obtain(Request.header.dlen+1))) - {Response.Send(kXR_ArgTooLong, "Request argument is too long"); - return 0; - } - hcNow = hcPrev; halfBSize = argp->bsize >> 1; - } - argp->buff[Request.header.dlen] = '\0'; - if ((rc = getData("arg", argp->buff, Request.header.dlen))) - {Resume = &XrdXrootdProtocol::Process2; return rc;} - } - -// Continue with request processing at the resume point -// - return Process2(); -} - -/******************************************************************************/ -/* p r i v a t e P r o c e s s 2 */ -/******************************************************************************/ - -int XrdXrootdProtocol::Process2() -{ -// If we are verifying requests, see if this request needs to be verified -// - if (sigNeed) - {const char *eText = "Request not signed"; - if (!sigHere || (eText = Protect->Verify(sigReq,sigReq2Ver,argp->buff))) - {Response.Send(kXR_SigVerErr, eText); - TRACEP(REQ, "req=" <Bump(SI->badSCnt); - return Link->setEtext(eText); - } else { - SI->Bump(SI->aokSCnt); - sigNeed = sigHere = false; - } - } else { - if (sigHere) - {TRACEP(REQ, "req=" <Bump(SI->ignSCnt); - sigHere = false; - } - } - -// If the user is not yet logged in, restrict what the user can do -// - if (!Status) - switch(Request.header.requestid) - {case kXR_login: return do_Login(); - case kXR_protocol: return do_Protocol(); - case kXR_bind: return do_Bind(); - default: Response.Send(kXR_InvalidRequest, - "Invalid request; user not logged in"); - return Link->setEtext("protocol sequence error 1"); - } - -// Help the compiler, select the the high activity requests (the ones with -// file handles) in a separate switch statement. A special case exists for -// sync() which return with a callback, so handle it here. -// - switch(Request.header.requestid) // First, the ones with file handles - {case kXR_read: return do_Read(); - case kXR_readv: return do_ReadV(); - case kXR_write: return do_Write(); - case kXR_writev: return do_WriteV(); - case kXR_sync: ReqID.setID(Request.header.streamid); - return do_Sync(); - case kXR_close: return do_Close(); - case kXR_truncate: ReqID.setID(Request.header.streamid); - if (!Request.header.dlen) return do_Truncate(); - break; - case kXR_query: if (!Request.header.dlen) return do_Qfh(); - default: break; - } - -// Now select the requests that do not need authentication -// - switch(Request.header.requestid) - {case kXR_protocol: return do_Protocol(); // dlen ignored - case kXR_ping: return do_Ping(); // dlen ignored - default: break; - } - -// Force authentication at this point, if need be -// - if (Status & XRD_NEED_AUTH) - {if (Request.header.requestid == kXR_auth) return do_Auth(); - else {Response.Send(kXR_InvalidRequest, - "Invalid request; user not authenticated"); - return -1; - } - } - -// Construct request ID as the following functions are async eligible -// - ReqID.setID(Request.header.streamid); - -// Process items that don't need arguments but may have them -// - switch(Request.header.requestid) - {case kXR_stat: return do_Stat(); - case kXR_endsess: return do_Endsess(); - default: break; - } - -// All remaining requests require an argument. Make sure we have one -// - if (!argp || !Request.header.dlen) - {Response.Send(kXR_ArgMissing, "Required argument not present"); - return 0; - } - -// Process items that keep own statistics -// - switch(Request.header.requestid) - {case kXR_open: return do_Open(); - case kXR_getfile: return do_Getfile(); - case kXR_putfile: return do_Putfile(); - default: break; - } - -// Update misc stats count -// - SI->Bump(SI->miscCnt); - -// Now process whatever we have -// - switch(Request.header.requestid) - {case kXR_admin: if (Status & XRD_ADMINUSER) return do_Admin(); - else break; - case kXR_chmod: return do_Chmod(); - case kXR_dirlist: return do_Dirlist(); - case kXR_locate: return do_Locate(); - case kXR_mkdir: return do_Mkdir(); - case kXR_mv: return do_Mv(); - case kXR_query: return do_Query(); - case kXR_prepare: return do_Prepare(); - case kXR_rm: return do_Rm(); - case kXR_rmdir: return do_Rmdir(); - case kXR_set: return do_Set(); - case kXR_statx: return do_Statx(); - case kXR_truncate: return do_Truncate(); - default: break; - } - -// Whatever we have, it's not valid -// - Response.Send(kXR_InvalidRequest, "Invalid request code"); - return 0; -} - -/******************************************************************************/ -/* P r o c S i g */ -/******************************************************************************/ - -int XrdXrootdProtocol::ProcSig() -{ - int rc; - -// Check if we completed reading the signature and if so, we are done -// - if (sigRead) - {sigRead = false; - sigHere = true; - return 0; - } - -// Verify that the hash is not longer that what we support and is present -// - if (Request.header.dlen <= 0 - || Request.header.dlen > (int)sizeof(sigBuff)) - {Response.Send(kXR_ArgInvalid, "Invalid signature data length"); - return Link->setEtext("signature data length error"); - } - -// Save relevant information for the next round -// - memcpy(&sigReq, &Request, sizeof(ClientSigverRequest)); - sigReq.header.dlen = htonl(Request.header.dlen); - -// Now read in the signature -// - sigRead = true; - if ((rc = getData("arg", sigBuff, Request.header.dlen))) - {Resume = &XrdXrootdProtocol::ProcSig; return rc;} - sigRead = false; - -// All done -// - sigHere = true; - return 0; -} - -/******************************************************************************/ -/* R e c y c l e */ -/******************************************************************************/ - -#undef TRACELINK -#define TRACELINK Link - -void XrdXrootdProtocol::Recycle(XrdLink *lp, int csec, const char *reason) -{ - char *sfxp, ctbuff[24], buff[128], Flags = (reason ? XROOTD_MON_FORCED : 0); - const char *What; - -// Check for disconnect or unbind -// - if (Status == XRD_BOUNDPATH) {What = "unbind"; Flags |= XROOTD_MON_BOUNDP;} - else What = "disc"; - -// Document the disconnect or undind -// - if (lp) - {XrdSysTimer::s2hms(csec, ctbuff, sizeof(ctbuff)); - if (reason) {snprintf(buff, sizeof(buff), "%s (%s)", ctbuff, reason); - sfxp = buff; - } else sfxp = ctbuff; - - eDest.Log(SYS_LOG_02, "Xeq", lp->ID, (char *)What, sfxp); - } - -// If this is a bound stream then we cannot release the resources until -// the main stream closes this stream (i.e., lp == 0). On the other hand, the -// main stream will not be trying to do this if we are still tagged as active. -// So, we need to redrive the main stream to complete the full shutdown. -// - if (Status == XRD_BOUNDPATH && Stream[0]) - {Stream[0]->streamMutex.Lock(); - isDead = 1; - if (isActive) - {isActive = 0; - Stream[0]->Link->setRef(-1); - } - Stream[0]->streamMutex.UnLock(); - if (lp) return; // Async close - } - -// Release all appendages -// - Cleanup(); - -// If we are monitoring logins then we are also monitoring disconnects. We do -// this after cleanup so that close records can be generated before we cut a -// disconnect record. This then requires we clear the monitor object here. -// We and the destrcutor are the only ones who call cleanup and a deletion -// will call the monitor clear method. So, we won't leak memeory. -// - if (Monitor.Logins()) Monitor.Agent->Disc(Monitor.Did, csec, Flags); - if (Monitor.Fstat() ) XrdXrootdMonFile::Disc(Monitor.Did); - Monitor.Clear(); - -// Set fields to starting point (debugging mostly) -// - Reset(); - -// Push ourselves on the stack -// - if (Response.isOurs()) ProtStack.Push(&ProtLink); -} - -/******************************************************************************/ -/* S t a t s */ -/******************************************************************************/ - -int XrdXrootdProtocol::Stats(char *buff, int blen, int do_sync) -{ -// Synchronize statistics if need be -// - if (do_sync) - {SI->statsMutex.Lock(); - SI->readCnt += numReads; - cumReads += numReads; numReads = 0; - SI->prerCnt += numReadP; - cumReadP += numReadP; numReadP = 0; - - SI->rvecCnt += numReadV; - cumReadV += numReadV; numReadV = 0; - SI->rsegCnt += numSegsV; - cumSegsV += numSegsV; numSegsV = 0; - - SI->wvecCnt += numWritV; - cumWritV += numWritV; numWritV = 0; - SI->wsegCnt += numSegsW; - cumSegsW += numSegsW, numSegsW = 0; - - SI->writeCnt += numWrites; - cumWrites+= numWrites;numWrites = 0; - SI->statsMutex.UnLock(); - } - -// Now return the statistics -// - return SI->Stats(buff, blen, do_sync); -} - -/******************************************************************************/ -/* P r i v a t e M e t h o d s */ -/******************************************************************************/ -/******************************************************************************/ -/* C h e c k S u m */ -/******************************************************************************/ - -int XrdXrootdProtocol::CheckSum(XrdOucStream *Stream, char **argv, int argc) -{ - XrdOucErrInfo myInfo("CheckSum"); - int rc, ecode; - -// The arguments must have (i.e. argc >= 3) -// - if (argc < 3) - {Stream->PutLine("Internal error; not enough checksum args!"); - return 8; - } - -// Issue the checksum calculation (that's all we do here). -// - rc = osFS->chksum(XrdSfsFileSystem::csCalc, argv[1], argv[2], myInfo); - -// Return result regardless of what it is -// - Stream->PutLine(myInfo.getErrText(ecode)); - if (rc) {SI->errorCnt++; - if (ecode) rc = ecode; - } - return rc; -} - -/******************************************************************************/ -/* C l e a n u p */ -/******************************************************************************/ - -void XrdXrootdProtocol::Cleanup() -{ - XrdXrootdPio *pioP; - int i; - -// Release any internal monitoring information -// - if (Entity.moninfo) {free(Entity.moninfo); Entity.moninfo = 0;} - -// If we have a buffer, release it -// - if (argp) {BPool->Release(argp); argp = 0;} - -// Notify the filesystem of a disconnect prior to deleting file tables -// - if (Status != XRD_BOUNDPATH) osFS->Disc(Client); - -// Delete the FTab if we have it -// - if (FTab) - {FTab->Recycle(Monitor.Files() ? Monitor.Agent : 0); - FTab = 0; - } - -// Handle parallel stream cleanup. The session stream cannot be closed if -// there is any queued activity on subordinate streams. A subordinate -// can either be closed from the session stream or asynchronously only if -// it is active. Which means they could be running while we are running. -// - if (isBound && Status != XRD_BOUNDPATH) - {streamMutex.Lock(); - for (i = 1; i < maxStreams; i++) - if (Stream[i]) - {Stream[i]->isBound = 0; Stream[i]->Stream[0] = 0; - if (Stream[i]->isDead) Stream[i]->Recycle(0, 0, 0); - else Stream[i]->Link->Close(); - Stream[i] = 0; - } - streamMutex.UnLock(); - } - -// Handle statistics -// - SI->statsMutex.Lock(); - SI->readCnt += numReads; SI->writeCnt += numWrites; - SI->statsMutex.UnLock(); - -// Handle authentication protocol -// - if (AuthProt) {AuthProt->Delete(); AuthProt = 0;} - if (Protect) {Protect->Delete(); Protect = 0;} - -// Handle parallel I/O appendages -// - while((pioP = pioFirst)) {pioFirst = pioP->Next; pioP->Recycle();} - while((pioP = pioFree )) {pioFree = pioP->Next; pioP->Recycle();} - -// Handle writev appendage -// - if (wvInfo) {free(wvInfo); wvInfo = 0;} -} - -/******************************************************************************/ -/* g e t D a t a */ -/******************************************************************************/ - -int XrdXrootdProtocol::getData(const char *dtype, char *buff, int blen) -{ - int rlen; - -// Read the data but reschedule he link if we have not received all of the -// data within the timeout interval. -// - rlen = Link->Recv(buff, blen, readWait); - if (rlen < 0) - {if (rlen != -ENOMSG) return Link->setEtext("link read error"); - else return -1; - } - if (rlen < blen) - {myBuff = buff+rlen; myBlen = blen-rlen; - TRACEP(REQ, dtype <<" timeout; read " <. */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include - -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysPthread.hh" -#include "XrdSec/XrdSecInterface.hh" -#include "XrdSfs/XrdSfsDio.hh" - -#include "Xrd/XrdObject.hh" -#include "Xrd/XrdProtocol.hh" -#include "XrdXrootd/XrdXrootdMonitor.hh" -#include "XrdXrootd/XrdXrootdReqID.hh" -#include "XrdXrootd/XrdXrootdResponse.hh" -#include "XProtocol/XProtocol.hh" - -/******************************************************************************/ -/* D e f i n e s */ -/******************************************************************************/ - -#define ROOTD_PQ 2012 - -#define XRD_LOGGEDIN 1 -#define XRD_NEED_AUTH 2 -#define XRD_ADMINUSER 4 -#define XRD_BOUNDPATH 8 - -#ifndef __GNUC__ -#define __attribute__(x) -#endif - -/******************************************************************************/ -/* x r d _ P r o t o c o l _ X R o o t d */ -/******************************************************************************/ - -class XrdNetSocket; -class XrdOucEnv; -class XrdOucErrInfo; -class XrdOucReqID; -class XrdOucStream; -class XrdOucTList; -class XrdOucTokenizer; -class XrdOucTrace; -class XrdSecProtect; -class XrdSecProtector; -class XrdSfsDirectory; -class XrdSfsFileSystem; -class XrdSecProtocol; -class XrdBuffer; -class XrdLink; -class XrdXrootdAioReq; -class XrdXrootdFile; -class XrdXrootdFileLock; -class XrdXrootdFileTable; -class XrdXrootdJob; -class XrdXrootdMonitor; -class XrdXrootdPio; -class XrdXrootdStats; -class XrdXrootdWVInfo; -class XrdXrootdXPath; - -class XrdXrootdProtocol : public XrdProtocol, public XrdSfsDio -{ -friend class XrdXrootdAdmin; -friend class XrdXrootdAioReq; -public: - -static int Configure(char *parms, XrdProtocol_Config *pi); - - void DoIt() {(*this.*Resume)();} - - int do_WriteSpan(); - - XrdProtocol *Match(XrdLink *lp); - - int Process(XrdLink *lp); // Sync: Job->Link.DoIt->Process - - int Process2(); - - int ProcSig(); - - void Recycle(XrdLink *lp, int consec, const char *reason); - - int SendFile(int fildes); - - int SendFile(XrdOucSFVec *sfvec, int sfvnum); - - void SetFD(int fildes); - - int Stats(char *buff, int blen, int do_sync=0); - -static int StatGen(struct stat &buf, char *xxBuff); - -// XrdXrootdProtocol operator =(const XrdXrootdProtocol &rhs) = delete; - XrdXrootdProtocol operator =(const XrdXrootdProtocol &rhs); - XrdXrootdProtocol(); - ~XrdXrootdProtocol() {Cleanup();} - -private: - -// Note that Route[] structure (below) must have RD_Num elements! -// -enum RD_func {RD_chmod = 0, RD_chksum, RD_dirlist, RD_locate, RD_mkdir, - RD_mv, RD_prepare, RD_prepstg, RD_rm, RD_rmdir, - RD_stat, RD_trunc, RD_ovld, - RD_open1, RD_open2, RD_open3, RD_open4, RD_Num}; - - int do_Admin(); - int do_Auth(); - int do_Bind(); - int do_Chmod(); - int do_CKsum(int canit); - int do_CKsum(char *algT, const char *Path, char *Opaque); - int do_Close(); - int do_Dirlist(); - int do_DirStat(XrdSfsDirectory *dp, char *pbuff, char *opaque); - int do_Endsess(); - int do_Getfile(); - int do_Login(); - int do_Locate(); - int do_Mkdir(); - int do_Mv(); - int do_Offload(int pathID, int isRead); - int do_OffloadIO(); - int do_Open(); - int do_Ping(); - int do_Prepare(); - int do_Protocol(ServerResponseBody_Protocol *rsp=0); - int do_Putfile(); - int do_Qconf(); - int do_Qfh(); - int do_Qopaque(short); - int do_Qspace(); - int do_Query(); - int do_Qxattr(); - int do_Read(); - int do_ReadV(); - int do_ReadAll(int asyncOK=1); - int do_ReadNone(int &retc, int &pathID); - int do_Rm(); - int do_Rmdir(); - int do_Set(); - int do_Set_Mon(XrdOucTokenizer &setargs); - int do_Stat(); - int do_Statx(); - int do_Sync(); - int do_Truncate(); - int do_Write(); - int do_WriteAll(); - int do_WriteCont(); - int do_WriteNone(); - int do_WriteV(); - int do_WriteVec(); - - int aio_Error(const char *op, int ecode); - int aio_Read(); - int aio_Write(); - int aio_WriteAll(); - int aio_WriteCont(); - - void Assign(const XrdXrootdProtocol &rhs); -static int CheckSum(XrdOucStream *, char **, int); - void Cleanup(); -static int Config(const char *fn); -static int ConfigSecurity(XrdOucEnv &xEnv, const char *cfn); - int fsError(int rc, char opc, XrdOucErrInfo &myError, - const char *Path, char *Cgi); - int fsOvrld(char opc, const char *Path, char *Cgi); - int fsRedirNoEnt(const char *eMsg, char *Cgi, int popt); - int getBuff(const int isRead, int Quantum); - int getData(const char *dtype, char *buff, int blen); - void logLogin(bool xauth=false); -static int mapMode(int mode); -static void PidFile(); - void Reset(); -static int rpCheck(char *fn, char **opaque); - int rpEmsg(const char *op, char *fn); - int vpEmsg(const char *op, char *fn); -static int Squash(char *); -static int xapath(XrdOucStream &Config); -static int xasync(XrdOucStream &Config); -static int xcksum(XrdOucStream &Config); -static int xdig(XrdOucStream &Config); -static int xexp(XrdOucStream &Config); -static int xexpdo(char *path, int popt=0); -static int xfsl(XrdOucStream &Config); -static int xfsL(XrdOucStream &Config, char *val, int lix); -static int xfso(XrdOucStream &Config); -static int xpidf(XrdOucStream &Config); -static int xprep(XrdOucStream &Config); -static int xlog(XrdOucStream &Config); -static int xmon(XrdOucStream &Config); -static int xred(XrdOucStream &Config); -static bool xred_php(char *val, char *hP[2], int rPort[2]); -static void xred_set(RD_func func, char *rHost[2], int rPort[2]); -static bool xred_xok(int func, char *rHost[2], int rPort[2]); -static int xsecl(XrdOucStream &Config); -static int xtrace(XrdOucStream &Config); -static int xlimit(XrdOucStream &Config); - -static XrdObjectQ ProtStack; -XrdObject ProtLink; - -protected: - - void MonAuth(); - int SetSF(kXR_char *fhandle, bool seton=false); - -static XrdXrootdXPath RPList; // Redirected paths -static XrdXrootdXPath RQList; // Redirected paths for ENOENT -static XrdXrootdXPath XPList; // Exported paths -static XrdSfsFileSystem *osFS; // The filesystem -static XrdSfsFileSystem *digFS; // The filesystem (digFS) -static XrdSecService *CIA; // Authentication Server -static XrdSecProtector *DHS; // Protection Server -static XrdXrootdFileLock *Locker; // File lock handler -static XrdScheduler *Sched; // System scheduler -static XrdBuffManager *BPool; // Buffer manager -static XrdSysError eDest; // Error message handler -static const char *myInst; -static const char *TraceID; -static char *pidPath; -static int RQLxist; // Something is present in RQList -static int myPID; -static int myRole; // Role for kXR_protocol (>= 2.9.7) -static int myRolf; // Role for kXR_protocol (< 2.9.7) - -// Admin control area -// -static XrdNetSocket *AdminSock; - -// Processing configuration values -// -static int hailWait; -static int readWait; -static int Port; -static int Window; -static int WANPort; -static int WANWindow; -static char *SecLib; -static char *FSLib[2]; -static int FSLvn[2]; -static char *digLib; // Normally zero for now -static char *digParm; -static char *Notify; -static const char *myCName; -static int myCNlen; -static char isRedir; -static char JobLCL; -static char JobCKCGI; -static XrdXrootdJob *JobCKS; -static char *JobCKT; -static XrdOucTList *JobCKTLST; -static XrdOucReqID *PrepID; - -// Static redirection -// -static struct RD_Table {char *Host[2]; - unsigned short Port[2]; - short RDSz[2];} Route[RD_Num]; -static int OD_Stall; -static bool OD_Bypass; -static bool OD_Redir; - -// async configuration values -// -static int as_maxperlnk; // Max async requests per link -static int as_maxperreq; // Max async ops per request -static int as_maxpersrv; // Max async ops per server -static int as_miniosz; // Min async request size -static int as_minsfsz; // Min sendf request size -static int as_segsize; // Aio quantum (optimal) -static int as_maxstalls; // Maximum stalls we will tolerate -static int as_force; // aio to be forced -static int as_noaio; // aio is disabled -static int as_nosf; // sendfile is disabled -static int as_syncw; // writes to be synchronous -static int maxBuffsz; // Maximum buffer size we can have -static int maxTransz; // Maximum transfer size we can have -static const int maxRvecsz = 1024; // Maximum read vector size -static const int maxWvecsz = 1024; // Maximum writ vector size - -// Statistical area -// -static XrdXrootdStats *SI; -int numReads; // Count for kXR_read -int numReadP; // Count for kXR_read pre-preads -int numReadV; // Count for kkR_readv -int numSegsV; // Count for kkR_readv segmens -int numWritV; // Count for kkR_write -int numSegsW; // Count for kkR_writev segmens -int numWrites; // Count -int numFiles; // Count - -int cumReads; // Count less numReads -int cumReadP; // Count less numReadP -int cumReadV; // Count less numReadV -int cumSegsV; // Count less numSegsV -int cumWritV; // Count less numWritV -int cumSegsW; // Count less numSegsW -int cumWrites; // Count less numWrites -long long totReadP; // Bytes - -// Data local to each protocol/link combination -// -XrdLink *Link; -XrdBuffer *argp; -XrdXrootdFileTable *FTab; -XrdXrootdMonitor::User Monitor; -int clientPV; -short rdType; -char Status; -unsigned char CapVer; - -// Authentication area -// -XrdSecEntity *Client; -XrdSecProtocol *AuthProt; -XrdSecEntity Entity; -XrdSecProtect *Protect; - -ClientRequest sigReq2Ver; // Request to verify -SecurityRequest sigReq; // Signature request -char sigBuff[64]; // Signature payload SHA256 + blowfish -bool sigNeed; // Signature target present -bool sigHere; // Signature request present -bool sigRead; // Signature being read -bool sigWarn; // Once for unneeded signature - -// Buffer information, used to drive DoIt(), getData(), and (*Resume)() -// -XrdXrootdAioReq *myAioReq; -char *myBuff; -int myBlen; -int myBlast; -int (XrdXrootdProtocol::*Resume)(); -XrdXrootdFile *myFile; -XrdXrootdWVInfo *wvInfo; -union { -long long myOffset; -long long myWVBytes; -int myEInfo[2]; - }; -int myIOLen; -int myStalls; - -// Buffer resize control area -// -static int hcMax; - int hcPrev; - int hcNext; - int hcNow; - int halfBSize; - -// This area is used for parallel streams -// -static const int maxStreams = 16; -XrdSysMutex streamMutex; -XrdSysSemaphore *reTry; -XrdXrootdProtocol *Stream[maxStreams]; -unsigned int mySID; -char isActive; -char isDead; -char isBound; -char isNOP; - -static const int maxPio = 4; -XrdXrootdPio *pioFirst; -XrdXrootdPio *pioLast; -XrdXrootdPio *pioFree; - -short PathID; -char doWrite; -char doWriteC; -unsigned char rvSeq; -unsigned char wvSeq; - -// Track usage limts. -// -static bool LimitError; // Indicates that hitting a limit should result in an error response. - // If false, when possible, silently ignore errors. -int PrepareCount; -static int PrepareLimit; - -// Buffers to handle client requests -// -XrdXrootdReqID ReqID; -ClientRequest Request; -XrdXrootdResponse Response; -}; -#endif diff --git a/src/XrdXrootd/XrdXrootdReqID.hh b/src/XrdXrootd/XrdXrootdReqID.hh deleted file mode 100644 index 90ae86d857c..00000000000 --- a/src/XrdXrootd/XrdXrootdReqID.hh +++ /dev/null @@ -1,76 +0,0 @@ -#ifndef __XRDXROOTDREQID_HH_ -#define __XRDXROOTDREQID_HH_ -/******************************************************************************/ -/* */ -/* X r d X r o o t d R e q I D . h h */ -/* */ -/* (c) 2006 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -class XrdXrootdReqID -{ -public: - -inline unsigned long long getID() {return Req.ID;} - -inline void getID(unsigned char *sid, int &lid,unsigned int &linst) - {memcpy(sid, Req.ids.Sid, sizeof(Req.ids.Sid)); - lid = static_cast(Req.ids.Lid); - linst = Req.ids.Linst; - } - -inline void setID(unsigned long long id) {Req.ID = id;} - -inline void setID(const unsigned char *sid,int lid,unsigned int linst) - {memcpy(Req.ids.Sid, sid, sizeof(Req.ids.Sid)); - Req.ids.Lid = static_cast(lid); - Req.ids.Linst = linst; - } - -inline unsigned long long setID(const unsigned char *sid) - {memcpy(Req.ids.Sid, sid, sizeof(Req.ids.Sid)); - return Req.ID; - } - -inline unsigned char *Stream() {return Req.ids.Sid;} - - XrdXrootdReqID(unsigned long long id) {setID(id);} - XrdXrootdReqID(const unsigned char *sid, int lid, unsigned int linst) - {setID(sid ? (unsigned char *)"\0\0" : sid, lid, linst);} - XrdXrootdReqID() {} - -private: - -union {unsigned long long ID; - struct {unsigned int Linst; - unsigned short Lid; - unsigned char Sid[2]; - } ids; - } Req; -}; -#endif diff --git a/src/XrdXrootd/XrdXrootdResponse.cc b/src/XrdXrootd/XrdXrootdResponse.cc deleted file mode 100644 index 9222267ad23..00000000000 --- a/src/XrdXrootd/XrdXrootdResponse.cc +++ /dev/null @@ -1,415 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d X r o o t d R e s p o n s e . c c */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include - -#include "Xrd/XrdLink.hh" -#include "XrdXrootd/XrdXrootdResponse.hh" -#include "XrdXrootd/XrdXrootdTrace.hh" -#include "XrdXrootd/XrdXrootdTransit.hh" - -/******************************************************************************/ -/* G l o b a l s */ -/******************************************************************************/ - -extern XrdOucTrace *XrdXrootdTrace; - -const char *XrdXrootdResponse::TraceID = "Response"; - -/******************************************************************************/ -/* L o c a l D e f i n e s */ -/******************************************************************************/ - -#define TRACELINK Link - -/******************************************************************************/ -/* S e n d */ -/******************************************************************************/ - -int XrdXrootdResponse::Send() -{ - static kXR_unt16 isOK = static_cast(htons(kXR_ok)); - - TRACES(RSP, "sending OK"); - - if (Bridge) - {if (Bridge->Send(kXR_ok, 0, 0, 0) >= 0) return 0; - return Link->setEtext("send failure"); - } - - Resp.status = isOK; - Resp.dlen = 0; - - if (Link->Send((char *)&Resp, sizeof(Resp)) < 0) - return Link->setEtext("send failure"); - return 0; -} - -/******************************************************************************/ - -int XrdXrootdResponse::Send(const char *msg) -{ - static kXR_unt16 isOK = static_cast(htons(kXR_ok)); - - TRACES(RSP, "sending OK: " <Send(kXR_ok,&RespIO[1],1,RespIO[1].iov_len) >= 0) return 0; - return Link->setEtext("send failure"); - } - - Resp.status = isOK; - Resp.dlen = static_cast(htonl(RespIO[1].iov_len)); - - if (Link->Send(RespIO, 2, sizeof(Resp) + RespIO[1].iov_len) < 0) - return Link->setEtext("send failure"); - return 0; -} - -/******************************************************************************/ - -int XrdXrootdResponse::Send(XResponseType rcode, void *data, int dlen) -{ - - TRACES(RSP, "sending " <(htons(rcode)); - Resp.dlen = static_cast(htonl(dlen)); - - if (Link->Send(RespIO, 2, sizeof(Resp) + dlen) < 0) - return Link->setEtext("send failure"); - return 0; -} - -/******************************************************************************/ - -int XrdXrootdResponse::Send(XResponseType rcode, - struct iovec *IOResp,int iornum, int iolen) -{ - int i, dlen = 0; - - if (iolen < 0) for (i = 1; i < iornum; i++) dlen += IOResp[i].iov_len; - else dlen = iolen; - TRACES(RSP, "sending " <(htons(rcode)); - Resp.dlen = static_cast(htonl(dlen)); - - if (Link->Send(IOResp, iornum, sizeof(Resp) + dlen) < 0) - return Link->setEtext("send failure"); - return 0; -} - -/******************************************************************************/ - -int XrdXrootdResponse::Send(XResponseType rcode, int info, - const char *data, int dsz) -{ - kXR_int32 xbuf = static_cast(htonl(info)); - int dlen; - - RespIO[1].iov_base = (caddr_t)(&xbuf); - RespIO[1].iov_len = sizeof(xbuf); - RespIO[2].iov_base = (caddr_t)data; - RespIO[2].iov_len = dlen = (dsz < 0 ? strlen(data) : dsz); - - TRACES(RSP,"sending " <<(sizeof(xbuf)+dlen) <<" data bytes; status=" <Send(rcode, &RespIO[1], 2, dlen) >= 0) return 0; - return Link->setEtext("send failure"); - } - - Resp.status = static_cast(htons(rcode)); - Resp.dlen = static_cast(htonl((dlen+sizeof(xbuf)))); - - if (Link->Send(RespIO, 3, sizeof(Resp) + dlen + sizeof(xbuf)) < 0) - return Link->setEtext("send failure"); - return 0; -} - -/******************************************************************************/ - -int XrdXrootdResponse::Send(void *data, int dlen) -{ - static kXR_unt16 isOK = static_cast(htons(kXR_ok)); - - TRACES(RSP, "sending " <Send(kXR_ok, &RespIO[1], 1, dlen) >= 0) return 0; - return Link->setEtext("send failure"); - } - - Resp.status = isOK; - Resp.dlen = static_cast(htonl(dlen)); - - if (Link->Send(RespIO, 2, sizeof(Resp) + dlen) < 0) - return Link->setEtext("send failure"); - return 0; -} - -/******************************************************************************/ - -int XrdXrootdResponse::Send(struct iovec *IOResp, int iornum, int iolen) -{ - static kXR_unt16 isOK = static_cast(htons(kXR_ok)); - int i, dlen = 0; - - if (iolen < 0) for (i = 1; i < iornum; i++) dlen += IOResp[i].iov_len; - else dlen = iolen; - TRACES(RSP, "sending " <Send(kXR_ok, &IOResp[1], iornum-1, dlen) >= 0) return 0; - return Link->setEtext("send failure"); - } - - IOResp[0].iov_base = RespIO[0].iov_base; - IOResp[0].iov_len = RespIO[0].iov_len; - Resp.status = isOK; - Resp.dlen = static_cast(htonl(dlen)); - - if (Link->Send(IOResp, iornum, sizeof(Resp) + dlen) < 0) - return Link->setEtext("send failure"); - return 0; -} - -/******************************************************************************/ - -int XrdXrootdResponse::Send(XErrorCode ecode, const char *msg) -{ - int dlen; - kXR_int32 erc = static_cast(htonl(ecode)); - - TRACES(EMSG, "sending err " <Send(kXR_error, &RespIO[1], 2, dlen) >= 0) return 0; - return Link->setEtext("send failure"); - } - - Resp.status = static_cast(htons(kXR_error)); - Resp.dlen = static_cast(htonl(dlen)); - - if (Link->Send(RespIO, 3, sizeof(Resp) + dlen) < 0) - return Link->setEtext("send failure"); - return 0; -} - -/******************************************************************************/ - -int XrdXrootdResponse::Send(int fdnum, long long offset, int dlen) -{ - static kXR_unt16 isOK = static_cast(htons(kXR_ok)); - XrdLink::sfVec myVec[2]; - - TRACES(RSP, "sendfile " <Send(offset, dlen, fdnum) >= 0) return 0; - return Link->setEtext("send failure"); - } - -// We are only called should sendfile be enabled for this response -// - Resp.status = isOK; - Resp.dlen = static_cast(htonl(dlen)); - -// Fill out the sendfile vector -// - myVec[0].buffer = (char *)&Resp; - myVec[0].sendsz = sizeof(Resp); - myVec[0].fdnum = -1; - myVec[1].offset = static_cast(offset); - myVec[1].sendsz = dlen; - myVec[1].fdnum = fdnum; - -// Send off the request -// - if (Link->Send(myVec, 2) < 0) - return Link->setEtext("sendfile failure"); - return 0; -} - -/******************************************************************************/ - -int XrdXrootdResponse::Send(XrdOucSFVec *sfvec, int sfvnum, int dlen) -{ - static kXR_unt16 isOK = static_cast(htons(kXR_ok)); - - TRACES(RSP, "sendfile " <Send(sfvec, sfvnum, dlen) >= 0) return 0; - return Link->setEtext("send failure"); - } - -// We are only called should sendfile be enabled for this response -// - Resp.status = isOK; - Resp.dlen = static_cast(htonl(dlen)); - sfvec[0].buffer = (char *)&Resp; - sfvec[0].sendsz = sizeof(Resp); - sfvec[0].fdnum = -1; - -// Send off the request -// - if (Link->Send(sfvec, sfvnum) < 0) - return Link->setEtext("sendfile failure"); - return 0; -} - -/******************************************************************************/ - -int XrdXrootdResponse::Send(XrdXrootdReqID &ReqID, - XResponseType Status, - struct iovec *IOResp, - int iornum, - int iolen) -{ - static const kXR_unt16 Xattn = static_cast(htons(kXR_attn)); - static const kXR_int32 Xarsp = static_cast(htonl(kXR_asynresp)); - -// We would have used struct ServerResponseBody_Attn_asynresp but the silly -// imbedded 4096 char array causes grief when computing lengths. -// - struct {ServerResponseHeader atnHdr; - kXR_int32 act; - kXR_int32 rsvd; // Same as char[4] - ServerResponseHeader theHdr; - } asynResp; - - static const int sfxLen = sizeof(asynResp) - sizeof(asynResp.atnHdr); - - XrdLink *Link; - unsigned char theSID[2]; - int theFD, rc, ioxlen = iolen; - unsigned int theInst; - -// Fill out the header with constant information -// - asynResp.atnHdr.streamid[0] = '\0'; - asynResp.atnHdr.streamid[1] = '\0'; - asynResp.atnHdr.status = Xattn; - asynResp.act = Xarsp; - asynResp.rsvd = 0; - -// Complete the io vector to send this response -// - IOResp[0].iov_base = (char *)&asynResp; - IOResp[0].iov_len = sizeof(asynResp); // 0 - -// Insert the status code -// - asynResp.theHdr.status = static_cast(htons(Status)); - -// We now insert the length of the delayed response and the full response -// - asynResp.theHdr.dlen = static_cast(htonl(iolen)); - iolen += sfxLen; - asynResp.atnHdr.dlen = static_cast(htonl(iolen)); - iolen += sizeof(ServerResponseHeader); - -// Decode the destination -// - ReqID.getID(theSID, theFD, theInst); - -// Map the destination to an endpoint, and send the response -// - if ((Link = XrdLink::fd2link(theFD, theInst))) - {Link->setRef(1); - if (Link->isInstance(theInst)) - {if (Link->hasBridge()) - rc = XrdXrootdTransit::Attn(Link, (short *)theSID, int(Status), - &IOResp[1], iornum-1, ioxlen); - else {asynResp.theHdr.streamid[0] = theSID[0]; - asynResp.theHdr.streamid[1] = theSID[1]; - rc = Link->Send(IOResp, iornum, iolen); - } - } else rc = -1; - Link->setRef(-1); - return (rc < 0 ? -1 : 0); - } - return -1; -} - -/******************************************************************************/ -/* S e t */ -/******************************************************************************/ - -void XrdXrootdResponse::Set(unsigned char *stream) -{ - static char hv[] = "0123456789abcdef"; - char *outbuff; - int i; - - Resp.streamid[0] = stream[0]; - Resp.streamid[1] = stream[1]; - - if (TRACING((TRACE_REQ|TRACE_RSP))) - {outbuff = trsid; - for (i = 0; i < (int)sizeof(Resp.streamid); i++) - {*outbuff++ = hv[(stream[i] >> 4) & 0x0f]; - *outbuff++ = hv[ stream[i] & 0x0f]; - } - *outbuff++ = ' '; *outbuff = '\0'; - } -} diff --git a/src/XrdXrootd/XrdXrootdResponse.hh b/src/XrdXrootd/XrdXrootdResponse.hh deleted file mode 100644 index 10f725ea6db..00000000000 --- a/src/XrdXrootd/XrdXrootdResponse.hh +++ /dev/null @@ -1,107 +0,0 @@ -#ifndef __XROOTD_RESPONSE_H__ -#define __XROOTD_RESPONSE_H__ -/******************************************************************************/ -/* */ -/* X r d X r o o t d R e s p o n s e . h h */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include - -#include "XProtocol/XProtocol.hh" -#include "XProtocol/XPtypes.hh" -#include "XrdXrootd/XrdXrootdReqID.hh" - -/******************************************************************************/ -/* x r o o t d _ R e s p o n s e */ -/******************************************************************************/ - -class XrdLink; -class XrdOucSFVec; -class XrdXrootdTransit; - -class XrdXrootdResponse -{ -public: - -const char *ID() {return (const char *)trsid;} - - int Send(void); - int Send(const char *msg); - int Send(XErrorCode ecode, const char *msg); - int Send(void *data, int dlen); - int Send(struct iovec *, int iovcnt, int iolen=-1); - int Send(XResponseType rcode, void *data, int dlen); - int Send(XResponseType rcode, struct iovec *IOResp, - int iornum, int iolen=-1); - int Send(XResponseType rcode, int info, const char *data, int dsz=-1); - int Send(int fdnum, long long offset, int dlen); - int Send(XrdOucSFVec *sfvec, int sfvnum, int dlen); -static int Send(XrdXrootdReqID &ReqID, XResponseType Status, - struct iovec *IOResp, int iornum, int iolen); - -inline void Set(XrdLink *lp) {Link = lp;} -inline void Set(XrdXrootdTransit *tp) {Bridge = tp;} - void Set(kXR_char *stream); - - bool isOurs() {return Bridge == 0;} - - XrdLink *theLink() {return Link;} - void StreamID(kXR_char *sid) {sid[0] = Resp.streamid[0]; - sid[1] = Resp.streamid[1]; - } - - XrdXrootdResponse(XrdXrootdResponse &rhs) {Set(rhs.Link); - Set(rhs.Bridge); - Set(rhs.Resp.streamid); - } - - XrdXrootdResponse() {Link = 0; Bridge = 0; *trsid = '\0'; - RespIO[0].iov_base = (caddr_t)&Resp; - RespIO[0].iov_len = sizeof(Resp); - } - ~XrdXrootdResponse() {} - - XrdXrootdResponse &operator =(const XrdXrootdResponse &rhs) - {Set(rhs.Link); - Set(rhs.Bridge); - Set((unsigned char *)rhs.Resp.streamid); - return *this; - } - -private: - - XrdXrootdTransit *Bridge; - ServerResponseHeader Resp; - XrdLink *Link; -struct iovec RespIO[3]; - - char trsid[8]; // sizeof() does not work here -static const char *TraceID; -}; -#endif diff --git a/src/XrdXrootd/XrdXrootdStat.icc b/src/XrdXrootd/XrdXrootdStat.icc deleted file mode 100644 index 0aafdf2e2ad..00000000000 --- a/src/XrdXrootd/XrdXrootdStat.icc +++ /dev/null @@ -1,98 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d X r o o t d S t a t . i c c */ -/* */ -/* (c) 2006 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -// This method has been extracted from this file so that it can be easily -// included in other parts of the system that need to generate a protocol -// version of the stat response. The XRD_CLASS_NAME must be defined prior -// to inclusion (this file undefines it at the end). Be sure to include - -// - -/******************************************************************************/ -/* S t a t G e n */ -/******************************************************************************/ - -#include "XrdSfs/XrdSfsFlags.hh" - -int XRDXROOTD_STAT_CLASSNAME::StatGen(struct stat &buf, char *xxBuff) -{ - const mode_t isReadable = (S_IRUSR | S_IRGRP | S_IROTH); - const mode_t isWritable = (S_IWUSR | S_IWGRP | S_IWOTH); - const mode_t isExecable = (S_IXUSR | S_IXGRP | S_IXOTH); - static uid_t myuid = getuid(); - static gid_t mygid = getgid(); - union {long long uuid; struct {int hi; int lo;} id;} Dev; - long long fsz; - int flags = 0; - -// Compute the unique id -// - Dev.id.lo = buf.st_ino; - Dev.id.hi = buf.st_dev; - -// Compute correct setting of the readable flag -// - if (buf.st_mode & isReadable - &&((buf.st_mode & S_IRUSR && myuid == buf.st_uid) - || (buf.st_mode & S_IRGRP && mygid == buf.st_gid) - || buf.st_mode & S_IROTH)) flags |= kXR_readable; - -// Compute correct setting of the writable flag -// - if (buf.st_mode & isWritable - &&((buf.st_mode & S_IWUSR && myuid == buf.st_uid) - || (buf.st_mode & S_IWGRP && mygid == buf.st_gid) - || buf.st_mode & S_IWOTH)) flags |= kXR_writable; - -// Compute correct setting of the execable flag -// - if (buf.st_mode & isExecable - &&((buf.st_mode & S_IXUSR && myuid == buf.st_uid) - || (buf.st_mode & S_IXGRP && mygid == buf.st_gid) - || buf.st_mode & S_IXOTH)) flags |= kXR_xset; - -// Compute the other flag settings -// - if (!Dev.uuid) flags |= kXR_offline; - if (S_ISDIR(buf.st_mode)) flags |= kXR_isDir; - else if (!S_ISREG(buf.st_mode)) flags |= kXR_other; - else{if (buf.st_mode & XRDSFS_POSCPEND) flags |= kXR_poscpend; - if ((buf.st_rdev & XRDSFS_RDVMASK) == 0) - {if (buf.st_rdev & XRDSFS_OFFLINE) flags |= kXR_offline; - if (buf.st_rdev & XRDSFS_HASBKUP) flags |= kXR_bkpexist; - } - } - fsz = static_cast(buf.st_size); - -// Format the results and return them -// - return sprintf(xxBuff,"%lld %lld %d %ld",Dev.uuid,fsz,flags,buf.st_mtime)+1; -} -#undef XRDXROOTD_STAT_CLASSNAME diff --git a/src/XrdXrootd/XrdXrootdStats.cc b/src/XrdXrootd/XrdXrootdStats.cc deleted file mode 100644 index 8833229bc64..00000000000 --- a/src/XrdXrootd/XrdXrootdStats.cc +++ /dev/null @@ -1,166 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d X r o o t d S t a t s . c c */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -#include "Xrd/XrdStats.hh" -#include "XrdSfs/XrdSfsInterface.hh" -#include "XrdXrootd/XrdXrootdResponse.hh" -#include "XrdXrootd/XrdXrootdStats.hh" - -/******************************************************************************/ -/* C o n s t r c u t o r */ -/******************************************************************************/ - -XrdXrootdStats::XrdXrootdStats(XrdStats *sp) -{ - -xstats = sp; -fsP = 0; - -Count = 0; // Stats: Number of matches -errorCnt = 0; // Stats: Number of errors returned -redirCnt = 0; // Stats: Number of redirects -stallCnt = 0; // Stats: Number of stalls -getfCnt = 0; // Stats: Number of getfiles -putfCnt = 0; // Stats: Number of putfiles -openCnt = 0; // Stats: Number of opens -readCnt = 0; // Stats: Number of reads -prerCnt = 0; // Stats: Number of reads -rvecCnt = 0; // Stats: Number of readv -rsegCnt = 0; // Stats: Number of readv segments -wvecCnt = 0; // Stats: Number of writev -wsegCnt = 0; // Stats: Number of writev segments -writeCnt = 0; // Stats: Number of writes -syncCnt = 0; // Stats: Number of sync -miscCnt = 0; // Stats: Number of miscellaneous -AsyncNum = 0; // Stats: Number of async ops -AsyncMax = 0; // Stats: Number of async max -AsyncRej = 0; // Stats: Number of async rejected -AsyncNow = 0; // Stats: Number of async now (not locked) -Refresh = 0; // Stats: Number of refresh requests -LoginAT = 0; // Stats: Number of attempted logins -LoginAU = 0; // Stats: Number of authenticated logins -LoginUA = 0; // Stats: Number of unauthenticated logins -AuthBad = 0; // Stats: Number of authentication failures -aokSCnt = 0; // Stats: Number of signature successes -badSCnt = 0; // Stats: Number of signature failures -ignSCnt = 0; // Stats: Number of signature ignored -} - -/******************************************************************************/ -/* S t a t s */ -/******************************************************************************/ - -int XrdXrootdStats::Stats(char *buff, int blen, int do_sync) -{ - static const char statfmt[] = "%d" - "%d%d%lld%lld" - "%lld%lld" - "%lld%lld%lld" - "%d%d%d%d" - "%d%d%d" - "%lld%d%lld" - "%d%lld%d" - "%d%d%d%d"; -// 1 2 3 4 5 6 7 8 - static const long long LLMax = 0x7fffffffffffffffLL; - static const int INMax = 0x7fffffff; - int len; - -// If no buffer, caller wants the maximum size we will generate -// - if (!buff) - {char dummy[4096]; // Almost any size will do - len = snprintf(dummy, sizeof(dummy), statfmt, - INMax, INMax, INMax, LLMax, - LLMax, LLMax, LLMax, LLMax, LLMax, LLMax, INMax, INMax, - INMax, INMax, - INMax, INMax, INMax, - LLMax, INMax, LLMax, INMax, LLMax, INMax, - INMax, INMax, INMax, INMax); - return len + (fsP ? fsP->getStats(0,0) : 0); - } - -// Format our statistics -// - statsMutex.Lock(); - len = snprintf(buff, blen, statfmt, - Count, openCnt, Refresh, readCnt, - prerCnt, rvecCnt, rsegCnt, wvecCnt, wsegCnt, writeCnt, - syncCnt, getfCnt, - putfCnt, miscCnt, - aokSCnt, badSCnt, ignSCnt, - AsyncNum, AsyncMax, AsyncRej, errorCnt, redirCnt, stallCnt, - LoginAT, AuthBad, LoginAU, LoginUA); - statsMutex.UnLock(); - -// Now include filesystem statistics and return -// - if (fsP) len += fsP->getStats(buff+len, blen-len); - return len; -} - -/******************************************************************************/ -/* S t a t s */ -/******************************************************************************/ - -int XrdXrootdStats::Stats(XrdXrootdResponse &resp, const char *opts) -{ - class statsInfo : public XrdStats::CallBack - {public: void Info(const char *buff, int bsz) - {rc = respP->Send((void *)buff, bsz+1);} - statsInfo(XrdXrootdResponse *rP) : respP(rP), rc(0) {} - ~statsInfo() {} - XrdXrootdResponse *respP; - int rc; - }; - statsInfo statsResp(&resp); - int xopts = 0; - - while(*opts) - {switch(*opts) - {case 'a': xopts |= XRD_STATS_ALL; break; - case 'b': xopts |= XRD_STATS_BUFF; break; // b_uff - case 'i': xopts |= XRD_STATS_INFO; break; // i_nfo - case 'l': xopts |= XRD_STATS_LINK; break; // l_ink - case 'd': xopts |= XRD_STATS_POLL; break; // d_evice - case 'u': xopts |= XRD_STATS_PROC; break; // u_sage - case 'p': xopts |= XRD_STATS_PROT; break; // p_rotocol - case 's': xopts |= XRD_STATS_SCHD; break; // s_scheduler - default: break; - } - opts++; - } - - if (!xopts) return resp.Send(); - - xstats->Stats(&statsResp, xopts); - return statsResp.rc; -} diff --git a/src/XrdXrootd/XrdXrootdStats.hh b/src/XrdXrootd/XrdXrootdStats.hh deleted file mode 100644 index 6ddb4c76771..00000000000 --- a/src/XrdXrootd/XrdXrootdStats.hh +++ /dev/null @@ -1,84 +0,0 @@ -#ifndef __XROOTD_STATS_H__ -#define __XROOTD_STATS_H__ -/******************************************************************************/ -/* */ -/* X r d X r o o t d S t a t s . h h */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "XrdSys/XrdSysPthread.hh" -#include "XrdOuc/XrdOucStats.hh" - -class XrdSfsFileSystem; -class XrdStats; -class XrdXrootdResponse; - -class XrdXrootdStats : public XrdOucStats -{ -public: -int Count; // Stats: Number of matches -int errorCnt; // Stats: Number of errors returned -long long redirCnt; // Stats: Number of redirects -int stallCnt; // Stats: Number of stalls -int getfCnt; // Stats: Number of getfiles -int putfCnt; // Stats: Number of putfiles -int openCnt; // Stats: Number of opens -long long readCnt; // Stats: Number of reads -long long prerCnt; // Stats: Number of reads (pre) -long long rsegCnt; // Stats: Number of readv segments -long long rvecCnt; // Stats: Number of reads -long long wsegCnt; // Stats: Number of writev segments -long long wvecCnt; // Stats: Number of writev -long long writeCnt; // Stats: Number of writes -int syncCnt; // Stats: Number of sync -int miscCnt; // Stats: Number of miscellaneous -long long AsyncNum; // Stats: Number of async ops -long long AsyncRej; // Stats: Number of async rejected -long long AsyncNow; // Stats: Number of async now (not locked) -int AsyncMax; // Stats: Number of async max -int Refresh; // Stats: Number of refresh requests -int LoginAT; // Stats: Number of attempted logins -int LoginAU; // Stats: Number of authenticated logins -int LoginUA; // Stats: Number of unauthenticated logins -int AuthBad; // Stats: Number of authentication failures -int aokSCnt; // Stats: Number of signature successes -int badSCnt; // Stats: Number of signature failures -int ignSCnt; // Stats: Number of signature ignored - -void setFS(XrdSfsFileSystem *fsp) {fsP = fsp;} - -int Stats(char *buff, int blen, int do_sync=0); - -int Stats(XrdXrootdResponse &resp, const char *opts); - - XrdXrootdStats(XrdStats *sp); - ~XrdXrootdStats() {} -private: - -XrdSfsFileSystem *fsP; -XrdStats *xstats; -}; -#endif diff --git a/src/XrdXrootd/XrdXrootdTrace.hh b/src/XrdXrootd/XrdXrootdTrace.hh deleted file mode 100644 index feca8898894..00000000000 --- a/src/XrdXrootd/XrdXrootdTrace.hh +++ /dev/null @@ -1,80 +0,0 @@ -#ifndef _XROOTD_TRACE_H -#define _XROOTD_TRACE_H -/******************************************************************************/ -/* */ -/* X r d X r o o t d T r a c e . h h */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Deprtment of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -// Trace flags -// -#define TRACE_ALL 0x0fff -#define TRACE_DEBUG 0x0001 -#define TRACE_EMSG 0x0002 -#define TRACE_FS 0x0004 -#define TRACE_LOGIN 0x0008 -#define TRACE_MEM 0x0010 -#define TRACE_REQ 0x0020 -#define TRACE_REDIR 0x0040 -#define TRACE_RSP 0x0080 -#define TRACE_SCHED 0x0100 -#define TRACE_STALL 0x0200 - -#ifndef NODEBUG - -#include "XrdSys/XrdSysHeaders.hh" -#include "XrdOuc/XrdOucTrace.hh" - -#define TRACE(act, x) \ - if (XrdXrootdTrace->What & TRACE_ ## act) \ - {XrdXrootdTrace->Beg(TraceID); cerr <End();} - -#define TRACEI(act, x) \ - if (XrdXrootdTrace->What & TRACE_ ## act) \ - {XrdXrootdTrace->Beg(TraceID,TRACELINK->ID); cerr <End();} - -#define TRACEP(act, x) \ - if (XrdXrootdTrace->What & TRACE_ ## act) \ - {XrdXrootdTrace->Beg(TraceID,TRACELINK->ID,Response.ID()); cerr <End();} - -#define TRACES(act, x) \ - if (XrdXrootdTrace->What & TRACE_ ## act) \ - {XrdXrootdTrace->Beg(TraceID,TRACELINK->ID,(const char *)trsid); cerr <End();} - -#define TRACING(x) XrdXrootdTrace->What & x - -#else - -#define TRACE(act,x) -#define TRACEI(act,x) -#define TRACEP(act,x) -#define TRACES(act,x) -#define TRACING(x) 0 -#endif - -#endif diff --git a/src/XrdXrootd/XrdXrootdTransPend.cc b/src/XrdXrootd/XrdXrootdTransPend.cc deleted file mode 100644 index 48986026a19..00000000000 --- a/src/XrdXrootd/XrdXrootdTransPend.cc +++ /dev/null @@ -1,116 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d X r o o t d B r i d g e . h h */ -/* */ -/* (c) 2012 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -#include "XrdXrootd/XrdXrootdTransit.hh" -#include "XrdXrootd/XrdXrootdTransPend.hh" - -/******************************************************************************/ -/* S t a t i c M e m b e r s */ -/******************************************************************************/ - -XrdSysMutex XrdXrootdTransPend::myMutex; - -XrdXrootdTransPend *XrdXrootdTransPend::rqstQ = 0; - -/******************************************************************************/ -/* C l e a r */ -/******************************************************************************/ - -void XrdXrootdTransPend::Clear(XrdXrootdTransit *trP) -{ - XrdXrootdTransPend *tpP, *tpN, *tpX; - -// Lock this operations -// - myMutex.Lock(); - -// Run through the queue deleting all elements owned by the transit object -// - tpP = 0; tpN = rqstQ; - while(tpN) - {if (tpN->bridge == trP) - {if (tpP) tpP->next = tpN->next; - else rqstQ = tpN->next; - tpX = tpN; tpN = tpN->next; delete tpX; - } else { - tpP = tpN; tpN = tpN->next; - } - } - -// All done -// - myMutex.UnLock(); -} - -/******************************************************************************/ -/* Q u e u e */ -/******************************************************************************/ - -void XrdXrootdTransPend::Queue() -{ -// Now place it on out pending queue -// - myMutex.Lock(); - next = rqstQ; rqstQ = this; - myMutex.UnLock(); -} - -/******************************************************************************/ -/* R e m o v e */ -/******************************************************************************/ - -XrdXrootdTransPend *XrdXrootdTransPend::Remove(XrdLink *lP, short sid) -{ - XrdXrootdTransPend *tpP, *tpN; - -// Lock this operations -// - myMutex.Lock(); - -// Run through the queue and remove matching element -// - tpP = 0; tpN = rqstQ; - while(tpN) - {if (tpN->link == lP && tpN->Pend.theSid == sid) - {if (tpP) tpP->next = tpN->next; - else rqstQ = tpN->next; - break; - } else { - tpP = tpN; tpN = tpN->next; - } - } - -// All done -// - myMutex.UnLock(); - return tpN; -} diff --git a/src/XrdXrootd/XrdXrootdTransPend.hh b/src/XrdXrootd/XrdXrootdTransPend.hh deleted file mode 100644 index 1af6a503d22..00000000000 --- a/src/XrdXrootd/XrdXrootdTransPend.hh +++ /dev/null @@ -1,70 +0,0 @@ -#ifndef __XRDXROOTDTRANSPEND_HH_ -#define __XRDXROOTDTRANSPEND_HH_ -/******************************************************************************/ -/* */ -/* X r d X r o o t d T r a n s P e n d . h h */ -/* */ -/* (c) 2013 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -#include "XProtocol/XProtocol.hh" -#include "XrdSys/XrdSysPthread.hh" - -class XrdLink; -class XrdXrootdTransit; - -class XrdXrootdTransPend -{public: - -XrdXrootdTransPend *next; -XrdLink *link; -XrdXrootdTransit *bridge; -union {ClientRequest Request; - short theSid; - } Pend; - -static void Clear(XrdXrootdTransit *trP); - - void Queue(); - -static XrdXrootdTransPend *Remove(XrdLink *lP, short sid); - - XrdXrootdTransPend(XrdLink *lkP, - XrdXrootdTransit *brP, - ClientRequest *rqP) - : next(0), link(lkP), bridge(brP) - {memcpy(&Pend.Request, rqP, sizeof(Pend.Request));} - - ~XrdXrootdTransPend() {} - -private: -static XrdSysMutex myMutex; -static XrdXrootdTransPend *rqstQ; -}; -#endif diff --git a/src/XrdXrootd/XrdXrootdTransSend.cc b/src/XrdXrootd/XrdXrootdTransSend.cc deleted file mode 100644 index c0d3584e328..00000000000 --- a/src/XrdXrootd/XrdXrootdTransSend.cc +++ /dev/null @@ -1,88 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d X r o o t d T r a n s S e n d . c c */ -/* */ -/* (c) 2013 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include "Xrd/XrdLink.hh" -#include "XrdXrootd/XrdXrootdTransSend.hh" - -/******************************************************************************/ -/* S e n d */ -/******************************************************************************/ - -int XrdXrootdTransSend::Send(const struct iovec *headP, int headN, - const struct iovec *tailP, int tailN) -{ - XrdLink::sfVec *sfVec; - int i, k = 0, numV = headN + tailN + 1; - -// Allocate a new sfVec to accomodate all the items -// - if (sfFD >= 0) sfVec = new XrdLink::sfVec[numV]; - else sfVec = new XrdLink::sfVec[numV-sfFD]; - -// Copy the headers -// - if (headP) for (i = 0; i < headN; i++, k++) - {sfVec[k].buffer = (char *)headP[i].iov_base; - sfVec[k].sendsz = headP[i].iov_len; - sfVec[k].fdnum = -1; - } - -// Insert the sendfile request -// - if (sfFD >= 0) - {sfVec[k].offset = sfOff; - sfVec[k].sendsz = sfLen; - sfVec[k].fdnum = sfFD; - k++; - } else { - for (i = 1; i < -sfFD; i++) - {sfVec[k ].offset = sfVP[i].offset; - sfVec[k ].sendsz = sfVP[i].sendsz; - sfVec[k++].fdnum = sfVP[i].fdnum; - } - } - -// Copy the trailer -// - if (tailP) for (i = 0; i < tailN; i++, k++) - {sfVec[k].buffer = (char *)tailP[i].iov_base; - sfVec[k].sendsz = tailP[i].iov_len; - sfVec[k].fdnum = -1; - } - -// Issue sendfile request -// - k = linkP->Send(sfVec, numV); - -// Deallocate the vector and return the result -// - delete [] sfVec; - return (k < 0 ? -1 : 0); -} diff --git a/src/XrdXrootd/XrdXrootdTransSend.hh b/src/XrdXrootd/XrdXrootdTransSend.hh deleted file mode 100644 index 7ae866f953c..00000000000 --- a/src/XrdXrootd/XrdXrootdTransSend.hh +++ /dev/null @@ -1,72 +0,0 @@ -#ifndef __XRDXROOTDTRANSSEND_HH_ -#define __XRDXROOTDTRANSSEND_HH_ -/******************************************************************************/ -/* */ -/* X r d X r o o t d T r a n s S e n d . h h */ -/* */ -/* (c) 2013 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -#include "XProtocol/XPtypes.hh" -#include "XrdXrootd/XrdXrootdBridge.hh" - -class XrdLink; - -class XrdXrootdTransSend : public XrdXrootd::Bridge::Context -{ -public: - - int Send(const - struct iovec *headP, //!< pointer to leading data array - int headN, //!< array count - const - struct iovec *tailP, //!< pointer to trailing data array - int tailN //!< array count - ); - - XrdXrootdTransSend(XrdLink *lP, kXR_char *sid, kXR_unt16 req, - long long offset, int dlen, int fdnum) - : Context(lP, sid, req), - sfOff(offset), sfLen(dlen), sfFD(fdnum) {} - - XrdXrootdTransSend(XrdLink *lP, kXR_char *sid, kXR_unt16 req, - XrdOucSFVec *sfvec, int sfvnum, int dlen) - : Context(lP, sid, req), - sfVP(sfvec), sfLen(dlen), sfFD(-sfvnum) {} - - ~XrdXrootdTransSend() {} - -private: - -union {long long sfOff; - XrdOucSFVec *sfVP; - }; -int sfLen; -int sfFD; -}; -#endif diff --git a/src/XrdXrootd/XrdXrootdTransit.cc b/src/XrdXrootd/XrdXrootdTransit.cc deleted file mode 100644 index d5b50028117..00000000000 --- a/src/XrdXrootd/XrdXrootdTransit.cc +++ /dev/null @@ -1,806 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d X r o o t d T r a n s i t . c c */ -/* */ -/* (c) 2012 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include - -#include "XProtocol/XProtocol.hh" - -#include "XrdSec/XrdSecEntity.hh" - -#include "Xrd/XrdBuffer.hh" -#include "Xrd/XrdLink.hh" -#include "XrdOuc/XrdOucErrInfo.hh" -#include "XrdSys/XrdSysAtomics.hh" -#include "XrdXrootd/XrdXrootdStats.hh" -#include "XrdXrootd/XrdXrootdTrace.hh" -#include "XrdXrootd/XrdXrootdTransit.hh" -#include "XrdXrootd/XrdXrootdTransPend.hh" -#include "XrdXrootd/XrdXrootdTransSend.hh" - -/******************************************************************************/ -/* C l o b a l S y m b o l s */ -/******************************************************************************/ - -extern XrdOucTrace *XrdXrootdTrace; - -#undef TRACELINK -#define TRACELINK Link - -#define XRD_GETNUM(x)\ - ntohl(*(static_cast(static_cast(x)))) - -/******************************************************************************/ -/* S t a t i c M e m b e r s */ -/******************************************************************************/ - -const char *XrdXrootdTransit::reqTab = XrdXrootdTransit::ReqTable(); - -XrdObjectQ - XrdXrootdTransit::TranStack("TranStack", - "transit protocol anchor"); - -/******************************************************************************/ -/* A l l o c */ -/******************************************************************************/ - -XrdXrootdTransit *XrdXrootdTransit::Alloc(XrdXrootd::Bridge::Result *rsltP, - XrdLink *linkP, - XrdSecEntity *seceP, - const char *nameP, - const char *protP - ) -{ - XrdXrootdTransit *xp; - -// Simply return a new transit object masquerading as a bridge -// - if (!(xp = TranStack.Pop())) xp = new XrdXrootdTransit(); - xp->Init(rsltP, linkP, seceP, nameP, protP); - return xp; -} - -/******************************************************************************/ -/* A t t n */ -/******************************************************************************/ - -int XrdXrootdTransit::Attn(XrdLink *lP, short *theSID, int rcode, - const struct iovec *ioV, int ioN, int ioL) -{ - XrdXrootdTransPend *tP; - -// Find the request -// - if (!(tP = XrdXrootdTransPend::Remove(lP, *theSID))) - {TRACE(REQ, "Unable to find request for " <ID <<" sid=" <<*theSID); - return 0; - } - -// Resume the request as we have been waiting for the response. -// - return tP->bridge->AttnCont(tP, rcode, ioV, ioN, ioL); -} - -/******************************************************************************/ -/* A t t n C o n t */ -/******************************************************************************/ - -int XrdXrootdTransit::AttnCont(XrdXrootdTransPend *tP, int rcode, - const struct iovec *ioV, int ioN, int ioL) -{ - XrdLink *theLink = tP->link; - int rc; - -// Refresh the request structure -// - memcpy(&Request, &(tP->Pend.Request), sizeof(Request)); - delete tP; - runWait = 0; - -// Reissue the request if it's a wait 0 response. -// - if (rcode==kXR_wait - && (!ioN || XRD_GETNUM(ioV[0].iov_base) == 0)) - {Sched->Schedule((XrdJob *)&waitJob); - return 0; - } - -// Send off the defered response -// - rc = Send(rcode, ioV, ioN, ioL); - -// If no wait needed, enable the link. Otherwise, handle the wait (rare) -// - if (rc >= 0) - {if (runDone && !runWait) - {AtomicBeg(runMutex); - AtomicZAP(runStatus); - AtomicEnd(runMutex); - theLink->Enable(); - } else { - if (runWait >= 0) - Sched->Schedule((XrdJob *)&waitJob, time(0)+runWait); - } - } - -// All done -// - return rc; -} - -/******************************************************************************/ -/* D i s c */ -/******************************************************************************/ - -bool XrdXrootdTransit::Disc() -{ - char buff[128]; - int rc; - -// We do not allow disconnection while we are active -// - AtomicBeg(runMutex); - rc = AtomicInc(runStatus); - AtomicEnd(runMutex); - if (rc) return false; - -// Reconnect original protocol to the link -// - Link->setProtocol(realProt); - -// Now we need to recycle our xrootd part -// - sprintf(buff, "%s disconnection", pName); - XrdXrootdProtocol::Recycle(Link, time(0)-cTime, buff); - -// Now just free up our object. -// - TranStack.Push(&TranLink); - return true; -} - -/******************************************************************************/ -/* Private: F a i l */ -/******************************************************************************/ - -bool XrdXrootdTransit::Fail(int ecode, const char *etext) -{ - runError = ecode; - runEText = etext; - return true; -} - -/******************************************************************************/ -/* F a t a l */ -/******************************************************************************/ - -int XrdXrootdTransit::Fatal(int rc) -{ - XrdXrootd::Bridge::Context rInfo(Link, Request.header.streamid, - Request.header.requestid); - - return (respObj->Error(rInfo, runError, runEText) ? rc : -1); -} - -/******************************************************************************/ -/* I n i t */ -/******************************************************************************/ - -void XrdXrootdTransit::Init(XrdScheduler *schedP, int qMax, int qTTL) -{ - TranStack.Set(schedP, XrdXrootdTrace, TRACE_MEM); - TranStack.Set(qMax, qTTL); -} - -/******************************************************************************/ - -void XrdXrootdTransit::Init(XrdXrootd::Bridge::Result *respP, // Private - XrdLink *linkP, - XrdSecEntity *seceP, - const char *nameP, - const char *protP - ) -{ - static XrdSysMutex myMutex; - static int bID = 0; - XrdNetAddrInfo *addrP; - const char *who; - char uname[sizeof(Request.login.username)+1]; - int pID, n; - -// Set standard stuff -// - runArgs = 0; - runALen = 0; - runABsz = 0; - runError = 0; - runStatus = 0; - runWait = 0; - runWTot = 0; - runWMax = 3600; - runWCall = false; - runDone = false; - reInvoke = false; - wBuff = 0; - wBLen = 0; - respObj = respP; - pName = protP; - -// Bind the protocol to the link -// - SI->Bump(SI->Count); - Link = linkP; - Response.Set(linkP); - Response.Set(this); - strcpy(Entity.prot, "host"); - Entity.host = (char *)linkP->Host(); - -// Develop a trace identifier -// - myMutex.Lock(); pID = ++bID; myMutex.UnLock(); - n = strlen(nameP); - if (n >= int(sizeof(uname))) n = sizeof(uname)-1; - strncpy(uname, nameP, sizeof(uname)-1); - uname[n] = 0; - linkP->setID(uname, pID); - -// Indicate that this brige supports asynchronous responses -// - CapVer = kXR_asyncap | kXR_ver002; - -// Mark the client as IPv4 if they came in as IPv4 or mapped IPv4 -// - addrP = Link->AddrInfo(); - if (addrP->isIPType(XrdNetAddrInfo::IPv4) || addrP->isMapped()) - clientPV |= XrdOucEI::uIPv4; - -// Mark the client as being on a private net if the address is private -// - if (addrP->isPrivate()) {clientPV |= XrdOucEI::uPrip; rdType = 1;} - else rdType = 0; - -// Now tie the security information -// - Client = (seceP ? seceP : &Entity); - -// Allocate a monitoring object, if needed for this connection and record login -// - if (Monitor.Ready()) - {Monitor.Register(linkP->ID, linkP->Host(), protP); - if (Monitor.Logins()) - {if (Monitor.Auths() && seceP) MonAuth(); - else Monitor.Report(Monitor.Auths() ? "" : 0); - } - } - -// Complete the request ID object -// - ReqID.setID(Request.header.streamid, linkP->FDnum(), linkP->Inst()); - -// Substitute our protocol for the existing one -// - realProt = linkP->setProtocol(this); - linkP->armBridge(); - -// Document this login -// - who = (seceP && seceP->name ? seceP->name : "nobody"); - eDest.Log(SYS_LOG_01, "Bridge", Link->ID, "login as", who); - -// All done, indicate we are logged in -// - Status = XRD_LOGGEDIN; - cTime = time(0); -} - -/******************************************************************************/ -/* P r o c e s s */ -/******************************************************************************/ - -int XrdXrootdTransit::Process(XrdLink *lp) -{ - int rc, bridgeActive; - -// This entry is serialized via link processing. First, get the run status. -// - AtomicBeg(runMutex); - bridgeActive = AtomicGet(runStatus); - AtomicEnd(runMutex); - -// If we are running then we need to reflect this to the xrootd protocol as -// data is now available. One of the following will be returned. -// -// < 0 -> Stop getting requests, -// -EINPROGRESS leave link disabled but otherwise all is well -// -n Error, disable and close the link -// = 0 -> OK, get next request, if allowed, o/w enable the link -// > 0 -> Slow link, stop getting requests and enable the link -// - if (bridgeActive) - {rc = XrdXrootdProtocol::Process(lp); - if (rc < 0) return rc; - if (runWait) - {if (runWait >= 0) - Sched->Schedule((XrdJob *)&waitJob, time(0)+runWait); - return -EINPROGRESS; - } - if (!runDone) return rc; - AtomicBeg(runMutex); - AtomicZAP(runStatus); - AtomicEnd(runMutex); - if (!reInvoke) return 1; - } - -// Reflect data is present to the underlying protocol and if Run() has been -// called we need to dispatch that request. This may be iterative. -// -do{rc = realProt->Process((reInvoke ? 0 : lp)); - if (rc >= 0 && runStatus) - {reInvoke = (rc == 0); - if (runError) rc = Fatal(rc); - else {runDone = false; - rc = (Resume ? XrdXrootdProtocol::Process(lp) : Process2()); - if (rc >= 0) - {if (runWait) - {if (runWait >= 0) - Sched->Schedule((XrdJob *)&waitJob, time(0)+runWait); - return -EINPROGRESS; - } - if (!runDone) return rc; - AtomicBeg(runMutex); - AtomicZAP(runStatus); - AtomicEnd(runMutex); - } - } - } else reInvoke = false; - } while(rc >= 0 && reInvoke); - -// Make sure that we indicate that we are no longer active -// - if (runStatus) - {AtomicBeg(runMutex); - AtomicZAP(runStatus); - AtomicEnd(runMutex); - } - -// All done -// - return (rc ? rc : 1); -} - -/******************************************************************************/ - -int XrdXrootdTransit::Process() -{ - static int eCode = htonl(kXR_NoMemory); - static char eText[] = "Insufficent memory to re-issue request"; - static struct iovec ioV[] = {{(char *)&eCode,sizeof(eCode)}, - {(char *)&eText,sizeof(eText)}}; - int rc; - -// Update wait statistics -// - runWTot += runWait; - runWait = 0; - -// While we are running asynchronously, there is no way that this object can -// be deleted while a timer is outstanding as the link has been disabled. So, -// we can reissue the request with little worry. -// - if (!runALen || RunCopy(runArgs, runALen)) rc = Process2(); - else rc = Send(kXR_error, ioV, 2, 0); - -// Defer the request if need be -// - if (rc >= 0 && runWait) - {if (runWait > 0) Sched->Schedule((XrdJob *)&waitJob, time(0)+runWait); - return 0; - } - runWTot = 0; - -// Indicate we are no longer active -// - if (runStatus) - {AtomicBeg(runMutex); - AtomicZAP(runStatus); - AtomicEnd(runMutex); - } - -// If the link needs to be terminated, terminate the link. Otherwise, we can -// enable the link for new requests at this point. -// - if (rc < 0) Link->Close(); - else Link->Enable(); - -// All done -// - return 0; -} - -/******************************************************************************/ -/* R e c y c l e */ -/******************************************************************************/ - -void XrdXrootdTransit::Recycle(XrdLink *lp, int consec, const char *reason) -{ - -// Set ourselves as active so we can't get more requests -// - AtomicBeg(runMutex); - AtomicInc(runStatus); - AtomicEnd(runMutex); - -// If we were active then we will need to quiesce before dismantling ourselves. -// Note that Recycle() can only be called if the link is enabled. So, this bit -// of code is improbable but we check it anyway. -// - if (runWait > 0) Sched->Cancel(&waitJob); - -// First we need to recycle the real protocol -// - if (realProt) realProt->Recycle(lp, consec, reason); - -// Now we need to recycle our xrootd part -// - XrdXrootdProtocol::Recycle(lp, consec, reason); - -// Release the argument buffer -// - if (runArgs) {free(runArgs); runArgs = 0;} - -// Delete all pending requests -// - XrdXrootdTransPend::Clear(this); - -// Now just free up our object. -// - TranStack.Push(&TranLink); -} - -/******************************************************************************/ -/* R e q T a b l e */ -/******************************************************************************/ - -#define KXR_INDEX(x) x-kXR_auth - -const char *XrdXrootdTransit::ReqTable() -{ - static char rTab[kXR_truncate-kXR_auth+1]; - -// Initialize the table -// - memset(rTab, 0, sizeof(rTab)); - rTab[KXR_INDEX(kXR_chmod)] = 1; - rTab[KXR_INDEX(kXR_close)] = 1; - rTab[KXR_INDEX(kXR_dirlist)] = 1; - rTab[KXR_INDEX(kXR_locate)] = 1; - rTab[KXR_INDEX(kXR_mkdir)] = 1; - rTab[KXR_INDEX(kXR_mv)] = 1; - rTab[KXR_INDEX(kXR_open)] = 1; - rTab[KXR_INDEX(kXR_prepare)] = 1; - rTab[KXR_INDEX(kXR_protocol)] = 1; - rTab[KXR_INDEX(kXR_query)] = 1; - rTab[KXR_INDEX(kXR_read)] = 2; - rTab[KXR_INDEX(kXR_readv)] = 2; - rTab[KXR_INDEX(kXR_rm)] = 1; - rTab[KXR_INDEX(kXR_rmdir)] = 1; - rTab[KXR_INDEX(kXR_set)] = 1; - rTab[KXR_INDEX(kXR_stat)] = 1; - rTab[KXR_INDEX(kXR_statx)] = 1; - rTab[KXR_INDEX(kXR_sync)] = 1; - rTab[KXR_INDEX(kXR_truncate)] = 1; - rTab[KXR_INDEX(kXR_write)] = 2; - -// Now return the address -// - return rTab; -} - -/******************************************************************************/ -/* Private: R e q W r i t e */ -/******************************************************************************/ - -bool XrdXrootdTransit::ReqWrite(char *xdataP, int xdataL) -{ - -// Make sure we always transit to the resume point -// - myBlen = 0; - -// If nothing was read, then this is a straight-up write -// - if (!xdataL || !xdataP || !Request.header.dlen) - {Resume = 0; wBuff = xdataP; wBLen = xdataL; - return true; - } - -// Partial data was read, we may have to split this between a direct write -// and a network read/write -- somewhat complicated. -// - myBuff = wBuff = xdataP; - myBlast = wBLen = xdataL; - Resume = &XrdXrootdProtocol::do_WriteSpan; - return true; -} - -/******************************************************************************/ -/* R u n */ -/******************************************************************************/ - -bool XrdXrootdTransit::Run(const char *xreqP, char *xdataP, int xdataL) -{ - int movLen, rc; - -// We do not allow re-entry if we are curently processing a request. -// It will be reset, as need, when a response is effected. -// - AtomicBeg(runMutex); - rc = AtomicInc(runStatus); - AtomicEnd(runMutex); - if (rc) return false; - -// Copy the request header -// - memcpy((void *)&Request, (void *)xreqP, sizeof(Request)); - -// Validate that we can actually handle this request -// - Request.header.requestid = ntohs(Request.header.requestid); - if (Request.header.requestid & 0x8000 - || Request.header.requestid > static_cast(kXR_truncate) - || !reqTab[Request.header.requestid - kXR_auth]) - return Fail(kXR_Unsupported, "Unsupported bridge request"); - -// Validate the data length -// - Request.header.dlen = ntohl(Request.header.dlen); - if (Request.header.dlen < 0) - return Fail(kXR_ArgInvalid, "Invalid request data length"); - -// Copy the stream id and trace this request -// - Response.Set(Request.header.streamid); - TRACEP(REQ, "Bridge req=" <buff + movLen; - Resume = &XrdXrootdProtocol::Process2; - return true; - } - } else runALen = 0; - -// If we have all the data, indicate request accepted. -// - runError = 0; - Resume = 0; - return true; -} - -/******************************************************************************/ -/* Privae: R u n C o p y */ -/******************************************************************************/ - -bool XrdXrootdTransit::RunCopy(char *buffP, int buffL) -{ - -// Allocate a buffer if we do not have one or it is too small -// - if (!argp || Request.header.dlen+1 > argp->bsize) - {if (argp) BPool->Release(argp); - if (!(argp = BPool->Obtain(Request.header.dlen+1))) - {Fail(kXR_ArgTooLong, "Request argument too long"); return false;} - hcNow = hcPrev; halfBSize = argp->bsize >> 1; - } - -// Copy the arguments to the buffer -// - memcpy(argp->buff, buffP, buffL); - argp->buff[buffL] = 0; - return true; -} - -/******************************************************************************/ -/* S e n d */ -/******************************************************************************/ - -int XrdXrootdTransit::Send(int rcode, const struct iovec *ioV, int ioN, int ioL) -{ - XrdXrootd::Bridge::Context rInfo(Link, Request.header.streamid, - Request.header.requestid); - const char *eMsg; - int rc; - bool aOK; - -// Invoke the result object (we initially assume this is the final result) -// - runDone = true; - switch(rcode) - {case kXR_error: - rc = XRD_GETNUM(ioV[0].iov_base); - eMsg = (ioN < 2 ? "" : (const char *)ioV[1].iov_base); - if (wBuff) respObj->Free(rInfo, wBuff, wBLen); - aOK = respObj->Error(rInfo, rc, eMsg); - break; - case kXR_ok: - if (wBuff) respObj->Free(rInfo, wBuff, wBLen); - aOK = (ioN ? respObj->Data(rInfo, ioV, ioN, ioL, true) - : respObj->Done(rInfo)); - break; - case kXR_oksofar: - aOK = respObj->Data(rInfo, ioV, ioN, ioL, false); - runDone = false; - break; - case kXR_redirect: - if (wBuff) respObj->Free(rInfo, wBuff, wBLen); - rc = XRD_GETNUM(ioV[0].iov_base); - aOK = respObj->Redir(rInfo,rc,(const char *)ioV[1].iov_base); - break; - case kXR_wait: - return Wait(rInfo, ioV, ioN, ioL); - break; - case kXR_waitresp: - return WaitResp(rInfo, ioV, ioN, ioL); - break; - default: if (wBuff) respObj->Free(rInfo, wBuff, wBLen); - aOK = respObj->Error(rInfo, kXR_ServerError, - "internal logic error"); - break; - }; - -// All done -// - return (aOK ? 0 : -1); -} - -/******************************************************************************/ - -int XrdXrootdTransit::Send(long long offset, int dlen, int fdnum) -{ - XrdXrootdTransSend sfInfo(Link, Request.header.streamid, - Request.header.requestid, - offset, dlen, fdnum); - -// Effect callback (this is always a final result) -// - runDone = true; - return (respObj->File(sfInfo, dlen) ? 0 : -1); -} - -/******************************************************************************/ - -int XrdXrootdTransit::Send(XrdOucSFVec *sfvec, int sfvnum, int dlen) -{ - XrdXrootdTransSend sfInfo(Link, Request.header.streamid, - Request.header.requestid, - sfvec, sfvnum, dlen); - -// Effect callback (this is always a final result) -// - runDone = true; - return (respObj->File(sfInfo, dlen) ? 0 : -1); -} - -/******************************************************************************/ -/* Private: W a i t */ -/******************************************************************************/ - -int XrdXrootdTransit::Wait(XrdXrootd::Bridge::Context &rInfo, - const struct iovec *ioV, int ioN, int ioL) -{ - const char *eMsg; - -// Trace this request if need be -// - runWait = XRD_GETNUM(ioV[0].iov_base); - eMsg = (ioN < 2 ? "reason unknown" : (const char *)ioV[1].iov_base); - -// Check if the protocol wants to handle all waits -// - if (runWMax <= 0) - {int wtime = runWait; - runWait = 0; - return (respObj->Wait(rInfo, wtime, eMsg) ? 0 : -1); - } - -// Check if we have exceeded the maximum wait time -// - if (runWTot >= runWMax) - {runDone = true; - runWait = 0; - return (respObj->Error(rInfo, kXR_Cancelled, eMsg) ? 0 : -1); - } - -// Readjust wait time -// - if (runWait > runWMax) runWait = runWMax; - -// Check if the protocol wants a wait notification -// - if (runWCall && !(respObj->Wait(rInfo, runWait, eMsg))) return -1; - -// All done, the process driver will effect the wait -// - TRACEP(REQ, "Bridge delaying request " <WaitResp(rInfo, runWait, eMsg); - -// Save the current state -// - trP = new XrdXrootdTransPend(Link, this, &Request); - trP->Queue(); - -// Effect a wait -// - runWait = -1; - return 0; -} diff --git a/src/XrdXrootd/XrdXrootdTransit.hh b/src/XrdXrootd/XrdXrootdTransit.hh deleted file mode 100644 index da12679c69d..00000000000 --- a/src/XrdXrootd/XrdXrootdTransit.hh +++ /dev/null @@ -1,214 +0,0 @@ -#ifndef __XRDXROOTDTRANSIT_HH_ -#define __XRDXROOTDTRANSIT_HH_ -/******************************************************************************/ -/* */ -/* X r d X r o o t d T r a n s i t . h h */ -/* */ -/* (c) 2012 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -#include "XrdSys/XrdSysPthread.hh" -#include "XrdXrootd/XrdXrootdBridge.hh" -#include "XrdXrootd/XrdXrootdProtocol.hh" - -#include "Xrd/XrdObject.hh" - -//----------------------------------------------------------------------------- -//! Transit -//! -//! The Bridge object implementation. -//----------------------------------------------------------------------------- - -class XrdOucSFVec; -class XrdScheduler; -class XrdXrootdTransPend; -struct iovec; - -class XrdXrootdTransit : public XrdXrootd::Bridge, public XrdXrootdProtocol -{ -public: - -//----------------------------------------------------------------------------- -//! Get a new transit object. -//----------------------------------------------------------------------------- - -static -XrdXrootdTransit *Alloc(XrdXrootd::Bridge::Result *respP, - XrdLink *linkP, - XrdSecEntity *seceP, - const char *nameP, - const char *protP - ); - -//----------------------------------------------------------------------------- -//! Handle attention response (i.e. async response) -//----------------------------------------------------------------------------- - -static int Attn(XrdLink *lP, short *theSID, int rcode, - const struct iovec *ioVec, int ioNum, int ioLen); - -//----------------------------------------------------------------------------- -//! Handle dismantlement -//----------------------------------------------------------------------------- - -bool Disc(); - -//----------------------------------------------------------------------------- -//! Perform one-time initialization -//----------------------------------------------------------------------------- - -static void Init(XrdScheduler *schedP, int qMax, int qTTL); - -//----------------------------------------------------------------------------- -//! Handle link activation (replaces parent activation). -//----------------------------------------------------------------------------- - -int Process(XrdLink *lp); - -//----------------------------------------------------------------------------- -//! Handle protocol redrive after wait. -//----------------------------------------------------------------------------- - -int Process(); - -//----------------------------------------------------------------------------- -//! Handle link shutdown. -//----------------------------------------------------------------------------- - -void Recycle(XrdLink *lp, int consec, const char *reason); - -//----------------------------------------------------------------------------- -//! Reissue a request after a wait -//----------------------------------------------------------------------------- - -void Reissue(); - -//----------------------------------------------------------------------------- -//! Initialize the valid request table. -//----------------------------------------------------------------------------- - -static -const char *ReqTable(); - -//----------------------------------------------------------------------------- -//! Inject an xrootd request into the protocol stack. -//----------------------------------------------------------------------------- - -bool Run(const char *xreqP, //!< xrootd request header - char *xdataP=0, //!< xrootd request data (optional) - int xdataL=0 //!< xrootd request data length - ); - -//----------------------------------------------------------------------------- -//! Handle request data response. -//----------------------------------------------------------------------------- - -int Send(int rcode, const struct iovec *ioVec, int ioNum, int ioLen); - -//----------------------------------------------------------------------------- -//! Handle request sendfile response. -//----------------------------------------------------------------------------- - -int Send(long long offset, int dlen, int fdnum); - -int Send(XrdOucSFVec *sfvec, int sfvnum, int dlen); - -//----------------------------------------------------------------------------- -//! Set sendfile() enablement. -//----------------------------------------------------------------------------- - -int setSF(kXR_char *fhandle, bool seton=false) - {return SetSF(fhandle, seton);} - -//----------------------------------------------------------------------------- -//! Set maximum wait time. -//----------------------------------------------------------------------------- - -void SetWait(int wtime, bool notify=false) - {runWMax = wtime; runWCall = notify;} - -//----------------------------------------------------------------------------- -//! Constructor & Destructor -//----------------------------------------------------------------------------- - - XrdXrootdTransit() : TranLink(this), waitJob(this) {} -virtual ~XrdXrootdTransit() {} - -private: -int AttnCont(XrdXrootdTransPend *tP, int rcode, - const struct iovec *ioV, int ioN, int ioL); -bool Fail(int ecode, const char *etext); -int Fatal(int rc); -void Init(Result *rsltP, XrdLink *linkP, XrdSecEntity *seceP, - const char *nameP, const char *protP - ); -bool ReqWrite(char *xdataP, int xdataL); -bool RunCopy(char *buffP, int buffL); -int Wait(XrdXrootd::Bridge::Context &rInfo, - const struct iovec *ioV, int ioN, int ioL); -int WaitResp(XrdXrootd::Bridge::Context &rInfo, - const struct iovec *ioV, int ioN, int ioL); - -class WaitReq : public XrdJob - {public: - void DoIt() {spanP->Process();} - - WaitReq(XrdXrootdTransit *tP) - : XrdJob("Transit Redrive"), spanP(tP) - {} - ~WaitReq() {} - private: - XrdXrootdTransit *spanP; - }; - -static XrdObjectQ TranStack; -XrdObject TranLink; - -WaitReq waitJob; -XrdSysMutex runMutex; -static const char *reqTab; -XrdProtocol *realProt; -XrdXrootd::Bridge::Result *respObj; -const char *runEText; -char *runArgs; -int runALen; -int runABsz; -int runError; -int runStatus; -int runWait; -int runWTot; -int runWMax; -bool runDone; -bool reInvoke; -bool runWCall; -int wBLen; -char *wBuff; -const char *pName; -time_t cTime; -}; -#endif diff --git a/src/XrdXrootd/XrdXrootdXPath.hh b/src/XrdXrootd/XrdXrootdXPath.hh deleted file mode 100644 index 0da675a3246..00000000000 --- a/src/XrdXrootd/XrdXrootdXPath.hh +++ /dev/null @@ -1,102 +0,0 @@ -#ifndef __XROOTD_XPATH__ -#define __XROOTD_XPATH__ -/******************************************************************************/ -/* */ -/* X r d X r o o t d X P a t h . h h */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include - -#define XROOTDXP_OK 1 -#define XROOTDXP_NOLK 2 -#define XROOTDXP_NOCGI 4 -#define XROOTDXP_NOSLASH 8 -#define XROOTDXP_NOMWCHK 16 - -class XrdXrootdXPath -{ -public: - -inline XrdXrootdXPath *Next() {return next;} -inline int Opts() {return pathopt;} -inline char *Path() {return path;} -inline char *Path(int &PLen) - {PLen = pathlen; return path;} - void Set(int opts, const char *pathdata=0) - {pathopt = opts; - if (pathdata) - {if (path) free(path); - pathlen = strlen(pathdata); - path = strdup(pathdata); - } - } - - void Insert(const char *pd, int popt=0, int flags=XROOTDXP_OK) - {XrdXrootdXPath *pp = 0, *p = next; - XrdXrootdXPath *newp = new XrdXrootdXPath(pd,popt,flags); - if (popt & ~XROOTDXP_OK) - {while(p && newp->pathlen < p->pathlen) - {pp = p; p = p->next;} - } else { - while(p && newp->pathlen >= p->pathlen) - {pp = p; p = p->next;} - } - newp->next = p; - if (pp) pp->next = newp; - else next = newp; - } - -inline int Validate(const char *pd, const int pl=0) - {int plen = (pl ? pl : strlen(pd)); - XrdXrootdXPath *p = next; - while(p && plen >= p->pathlen) - {if (!strncmp(pd, p->path, p->pathlen)) - return p->pathopt; - p=p->next; - } - return 0; - } - - XrdXrootdXPath(const char *pathdata="",int popt=0,int flags=XROOTDXP_OK) - {next = 0; - pathopt = popt | flags; - pathlen = strlen(pathdata); - path = strdup(pathdata); - } - - ~XrdXrootdXPath() {if (path) free(path);} - -private: - - XrdXrootdXPath *next; - int pathlen; - int pathopt; - char *path; -}; -#endif diff --git a/src/XrdXrootd/XrdXrootdXeq.cc b/src/XrdXrootd/XrdXrootdXeq.cc deleted file mode 100644 index f5cb9a2be9c..00000000000 --- a/src/XrdXrootd/XrdXrootdXeq.cc +++ /dev/null @@ -1,3553 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d X r o o t d X e q . c c */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -#include "XrdSfs/XrdSfsInterface.hh" -#include "XrdSys/XrdSysError.hh" -#include "XrdSys/XrdSysPlatform.hh" -#include "XrdSys/XrdSysTimer.hh" -#include "XrdOuc/XrdOucEnv.hh" -#include "XrdOuc/XrdOucReqID.hh" -#include "XrdOuc/XrdOucTList.hh" -#include "XrdOuc/XrdOucStream.hh" -#include "XrdOuc/XrdOucTokenizer.hh" -#include "XrdOuc/XrdOucUtils.hh" -#include "XrdSec/XrdSecInterface.hh" -#include "XrdSec/XrdSecProtector.hh" -#include "Xrd/XrdBuffer.hh" -#include "Xrd/XrdInet.hh" -#include "Xrd/XrdLink.hh" -#include "XrdXrootd/XrdXrootdAio.hh" -#include "XrdXrootd/XrdXrootdCallBack.hh" -#include "XrdXrootd/XrdXrootdFile.hh" -#include "XrdXrootd/XrdXrootdFileLock.hh" -#include "XrdXrootd/XrdXrootdJob.hh" -#include "XrdXrootd/XrdXrootdMonFile.hh" -#include "XrdXrootd/XrdXrootdMonitor.hh" -#include "XrdXrootd/XrdXrootdPio.hh" -#include "XrdXrootd/XrdXrootdPrepare.hh" -#include "XrdXrootd/XrdXrootdProtocol.hh" -#include "XrdXrootd/XrdXrootdStats.hh" -#include "XrdXrootd/XrdXrootdTrace.hh" -#include "XrdXrootd/XrdXrootdXPath.hh" - -#include "XrdVersion.hh" - -/******************************************************************************/ -/* G l o b a l s */ -/******************************************************************************/ - -extern XrdOucTrace *XrdXrootdTrace; - -/******************************************************************************/ -/* L o c a l S t r u c t u r e s */ -/******************************************************************************/ - -struct XrdXrootdFHandle - {kXR_int32 handle; - - void Set(kXR_char *ch) - {memcpy((void *)&handle, (const void *)ch, sizeof(handle));} - XrdXrootdFHandle() {} - XrdXrootdFHandle(kXR_char *ch) {Set(ch);} - ~XrdXrootdFHandle() {} - }; - -struct XrdXrootdSessID - {unsigned int Sid; - int Pid; - int FD; - unsigned int Inst; - - XrdXrootdSessID() {} - ~XrdXrootdSessID() {} - }; - -struct XrdXrootdWVInfo - {XrdOucIOVec *wrVec; // Prevents compiler array bounds complaint - int curFH; - short vBeg; - short vPos; - short vEnd; - short vMon; - bool doSync; - char wvMon; - bool ioMon; - char vType; - XrdOucIOVec ioVec[1]; // Dynamically sized - }; - -/******************************************************************************/ -/* L o c a l D e f i n e s */ -/******************************************************************************/ - -#define CRED (const XrdSecEntity *)Client - -#define TRACELINK Link - -#define STATIC_REDIRECT(xfnc) \ - if (Route[xfnc].Port[rdType]) \ - return Response.Send(kXR_redirect,Route[xfnc].Port[rdType],\ - Route[xfnc].Host[rdType]) -namespace -{ -static const int op_isOpen = 0x00010000; -static const int op_isRead = 0x00020000; -} - -/******************************************************************************/ -/* d o _ A d m i n */ -/******************************************************************************/ - -int XrdXrootdProtocol::do_Admin() -{ - return Response.Send(kXR_Unsupported, "admin request is not supported"); -} - -/******************************************************************************/ -/* d o _ A u t h */ -/******************************************************************************/ - -int XrdXrootdProtocol::do_Auth() -{ - XrdSecCredentials cred; - XrdSecParameters *parm = 0; - XrdOucErrInfo eMsg; - const char *eText; - int rc, n; - -// Ignore authenticate requests if security turned off -// - if (!CIA) return Response.Send(); - cred.size = Request.header.dlen; - cred.buffer = argp->buff; - -// If we have no auth protocol or the current protocol is being changed by the -// client (the client can do so at any time), try to get it. Track number of -// times we got a protocol object as the read count (we will zero it out later). -// The credtype change check is always done. While the credtype is consistent, -// not all protocols provided this information in the past. So, old clients will -// not necessarily be able to switch protocols mid-stream. -// - if (!AuthProt - || strncmp(Entity.prot, (const char *)Request.auth.credtype, - sizeof(Request.auth.credtype))) - {if (AuthProt) AuthProt->Delete(); - size_t size = sizeof(Request.auth.credtype); - strncpy(Entity.prot, (const char *)Request.auth.credtype, size); - if (!(AuthProt = CIA->getProtocol(Link->Host(), *(Link->AddrInfo()), - &cred, &eMsg))) - {eText = eMsg.getErrText(rc); - eDest.Emsg("Xeq", "User authentication failed;", eText); - return Response.Send(kXR_NotAuthorized, eText); - } - AuthProt->Entity.tident = Link->ID; - numReads++; - } - -// Now try to authenticate the client using the current protocol -// - if (!(rc = AuthProt->Authenticate(&cred, &parm, &eMsg))) - {rc = Response.Send(); Status &= ~XRD_NEED_AUTH; SI->Bump(SI->LoginAU); - Client = &AuthProt->Entity; numReads = 0; strcpy(Entity.prot, "host"); - if (DHS) Protect = DHS->New4Server(*AuthProt,clientPV&XrdOucEI::uVMask); - if (Monitor.Logins() && Monitor.Auths()) MonAuth(); - logLogin(true); - return rc; - } - -// If we need to continue authentication, tell the client as much -// - if (rc > 0) - {TRACEP(LOGIN, "more auth requested; sz=" <<(parm ? parm->size : 0)); - if (parm) {rc = Response.Send(kXR_authmore, parm->buffer, parm->size); - delete parm; - return rc; - } - eDest.Emsg("Xeq", "Security requested additional auth w/o parms!"); - return Response.Send(kXR_ServerError,"invalid authentication exchange"); - } - -// Authentication failed. We will delete the authentication object and zero -// out the pointer. We can do this without any locks because this section is -// single threaded relative to a connection. To prevent guessing attacks, we -// wait a variable amount of time if there have been 3 or more tries. -// - if (AuthProt) {AuthProt->Delete(); AuthProt = 0;} - if ((n = numReads - 2) > 0) XrdSysTimer::Snooze(n > 5 ? 5 : n); - -// We got an error, bail out. -// - SI->Bump(SI->AuthBad); - eText = eMsg.getErrText(rc); - eDest.Emsg("Xeq", "User authentication failed;", eText); - return Response.Send(kXR_NotAuthorized, eText); -} - -/******************************************************************************/ -/* d o _ B i n d */ -/******************************************************************************/ - -int XrdXrootdProtocol::do_Bind() -{ - XrdXrootdSessID *sp = (XrdXrootdSessID *)Request.bind.sessid; - XrdXrootdProtocol *pp; - XrdLink *lp; - int i, pPid, rc; - char buff[64], *cp, *dp; - -// Update misc stats count -// - SI->Bump(SI->miscCnt); - -// Find the link we are to bind to -// - if (sp->FD <= 0 || !(lp = XrdLink::fd2link(sp->FD, sp->Inst))) - return Response.Send(kXR_NotFound, "session not found"); - -// The link may have escaped so we need to hold this link and try again -// - lp->Hold(1); - if (lp != XrdLink::fd2link(sp->FD, sp->Inst)) - {lp->Hold(0); - return Response.Send(kXR_NotFound, "session just closed"); - } - -// Get the protocol associated with the link -// - if (!(pp=dynamic_cast(lp->getProtocol()))||lp != pp->Link) - {lp->Hold(0); - return Response.Send(kXR_ArgInvalid, "session protocol not xroot"); - } - -// Verify that the parent protocol is fully logged in -// - if (!(pp->Status & XRD_LOGGEDIN) || (pp->Status & XRD_NEED_AUTH)) - {lp->Hold(0); - return Response.Send(kXR_ArgInvalid, "session not logged in"); - } - -// Verify that the bind is valid for the requestor -// - if (sp->Pid != myPID || sp->Sid != pp->mySID) - {lp->Hold(0); - return Response.Send(kXR_ArgInvalid, "invalid session ID"); - } - -// For now, verify that the request is comming from the same host -// - if (strcmp(Link->Host(), lp->Host())) - {lp->Hold(0); - return Response.Send(kXR_NotAuthorized, "cross-host bind not allowed"); - } - -// Find a slot for this path in parent protocol -// - for (i = 1; i < maxStreams && pp->Stream[i]; i++) {} - if (i >= maxStreams) - {lp->Hold(0); - return Response.Send(kXR_NoMemory, "bind limit exceeded"); - } - -// Link this protocol to the parent -// - pp->Stream[i] = this; - Stream[0] = pp; - pp->isBound = 1; - PathID = i; - sprintf(buff, "FD %d#%d bound", Link->FDnum(), i); - eDest.Log(SYS_LOG_01, "Xeq", buff, lp->ID); - -// Construct a login name for this bind session -// - cp = strdup(lp->ID); - if ( (dp = rindex(cp, '@'))) *dp = '\0'; - if (!(dp = rindex(cp, '.'))) pPid = 0; - else {*dp++ = '\0'; pPid = strtol(dp, (char **)NULL, 10);} - Link->setID(cp, pPid); - free(cp); - CapVer = pp->CapVer; - Status = XRD_BOUNDPATH; - clientPV = pp->clientPV; - -// Get the required number of parallel I/O objects -// - pioFree = XrdXrootdPio::Alloc(maxPio); - -// There are no errors possible at this point unless the response fails -// - buff[0] = static_cast(i); - if (!(rc = Response.Send(kXR_ok, buff, 1))) rc = -EINPROGRESS; - -// Return but keep the link disabled -// - lp->Hold(0); - return rc; -} - -/******************************************************************************/ -/* d o _ c h m o d */ -/******************************************************************************/ - -int XrdXrootdProtocol::do_Chmod() -{ - int mode, rc; - char *opaque; - XrdOucErrInfo myError(Link->ID, Monitor.Did, clientPV); - -// Check for static routing -// - STATIC_REDIRECT(RD_chmod); - -// Unmarshall the data -// - mode = mapMode((int)ntohs(Request.chmod.mode)); - if (rpCheck(argp->buff, &opaque)) return rpEmsg("Modifying", argp->buff); - if (!Squash(argp->buff)) return vpEmsg("Modifying", argp->buff); - -// Preform the actual function -// - rc = osFS->chmod(argp->buff, (XrdSfsMode)mode, myError, CRED, opaque); - TRACEP(FS, "chmod rc=" <buff, &opaque)) return rpEmsg("Check summing", argp->buff); - if (!Squash(argp->buff)) return vpEmsg("Check summing", argp->buff); - -// If this is a cancel request, do it now -// - if (canit) - {if (JobCKS) JobCKS->Cancel(argp->buff, &Response); - return Response.Send(); - } - -// Check if multiple checksums are supported and if so, pre-process -// - if (JobCKCGI && opaque && *opaque) - {XrdOucEnv jobEnv(opaque); - char *cksT; - if ((cksT = jobEnv.Get("cks.type"))) - {XrdOucTList *tP = JobCKTLST; - while(tP && strcasecmp(tP->text, cksT)) tP = tP->next; - if (!tP) - {char ebuf[1024]; - snprintf(ebuf, sizeof(ebuf), "%s checksum not supported.", cksT); - return Response.Send(kXR_ServerError, ebuf); - } - algT = tP->text; - } - } - -// If we are allowed to locally query the checksum to avoid computation, do it -// - if (JobLCL && (rc = do_CKsum(algT, argp->buff, opaque)) <= 0) return rc; - -// Just make absolutely sure we can continue with a calculation -// - if (!JobCKS) - return Response.Send(kXR_ServerError, "Logic error computing checksum."); - -// Check if multiple checksums are supported and construct right argument list -// - if (JobCKCGI > 1 || JobLCL) - {args[0] = algT; - args[1] = algT; - args[2] = argp->buff; - args[3] = 0; - } else { - args[0] = algT; - args[1] = argp->buff; - args[2] = 0; - } - -// Preform the actual function -// - return JobCKS->Schedule(argp->buff, (const char **)args, &Response, - ((CapVer & kXR_vermask) >= kXR_ver002 ? 0 : JOB_Sync)); -} - -/******************************************************************************/ - -int XrdXrootdProtocol::do_CKsum(char *algT, const char *Path, char *Opaque) -{ - static char Space = ' '; - XrdOucErrInfo myError(Link->ID, Monitor.Did, clientPV); - int CKTLen = strlen(algT); - int ec, rc = osFS->chksum(XrdSfsFileSystem::csGet, algT, Path, - myError, CRED, Opaque); - const char *csData = myError.getErrText(ec); - -// Diagnose any hard errors -// - if (rc) return fsError(rc, 0, myError, Path, Opaque); - -// Return result if it is actually available -// - if (*csData) - {if (*csData == '!') return Response.Send(csData+1); - struct iovec iov[4] = {{0,0}, {algT, (size_t)CKTLen}, {&Space, 1}, - {(char *)csData, strlen(csData)+1}}; - return Response.Send(iov, 4); - } - -// Diagnose soft errors -// - if (!JobCKS) - {const char *eTxt[2] = {JobCKT, " checksum not available."}; - myError.setErrInfo(0, eTxt, 2); - return Response.Send(kXR_ChkSumErr, myError.getErrText()); - } - -// Return indicating that we should try calculating the checksum -// - return 1; -} - -/******************************************************************************/ -/* d o _ C l o s e */ -/******************************************************************************/ - -int XrdXrootdProtocol::do_Close() -{ - XrdXrootdFile *fp; - XrdXrootdFHandle fh(Request.close.fhandle); - int rc; - -// Keep statistics -// - SI->Bump(SI->miscCnt); - -// Find the file object -// - if (!FTab || !(fp = FTab->Get(fh.handle))) - return Response.Send(kXR_FileNotOpen, - "close does not refer to an open file"); - -// Serialize the link to make sure that any in-flight operations on this handle -// have completed (async mode or parallel streams) -// - Link->Serialize(); - -// Do an explicit close of the file here; reflecting any errors -// - rc = fp->XrdSfsp->close(); - TRACEP(FS, "close rc=" <buff); - if (!doDig && !Squash(argp->buff))return vpEmsg("Listing", argp->buff); - -// Get a directory object -// - if (doDig) dp = digFS->newDir(Link->ID, Monitor.Did); - else dp = osFS->newDir(Link->ID, Monitor.Did); - -// Make sure we have the object -// - if (!dp) - {snprintf(ebuff,sizeof(ebuff)-1,"Insufficient memory to open %s",argp->buff); - eDest.Emsg("Xeq", ebuff); - return Response.Send(kXR_NoMemory, ebuff); - } - -// First open the directory -// - dp->error.setUCap(clientPV); - if ((rc = dp->open(argp->buff, CRED, opaque))) - {rc = fsError(rc, XROOTD_MON_OPENDIR, dp->error, argp->buff, opaque); - delete dp; - return rc; - } - -// Check if the caller wants stat information as well -// - if (Request.dirlist.options[0] & kXR_dstat) - return do_DirStat(dp, ebuff, opaque); - -// Start retreiving each entry and place in a local buffer with a trailing new -// line character (the last entry will have a null byte). If we cannot fit a -// full entry in the buffer, send what we have with an OKSOFAR and continue. -// This code depends on the fact that a directory entry will never be longer -// than sizeof( ebuff)-1; otherwise, an infinite loop will result. No errors -// are allowed to be reflected at this point. -// - dname = 0; - do {buff = ebuff; bleft = sizeof(ebuff); - while(dname || (dname = dp->nextEntry())) - {dlen = strlen(dname); - if (dlen > 2 || dname[0] != '.' || (dlen == 2 && dname[1] != '.')) - {if ((bleft -= (dlen+1)) < 0) break; - strcpy(buff, dname); buff += dlen; *buff = '\n'; buff++; cnt++; - } - dname = 0; - } - if (dname) rc = Response.Send(kXR_oksofar, ebuff, buff-ebuff); - } while(!rc && dname); - -// Send the ending packet if we actually have one to send -// - if (!rc) - {if (ebuff == buff) rc = Response.Send(); - else {*(buff-1) = '\0'; - rc = Response.Send((void *)ebuff, buff-ebuff); - } - } - -// Close the directory -// - dp->close(); - delete dp; - if (!rc) {TRACEP(FS, "dirlist entries=" <nextEntry())) - {dlen = strlen(dname); - if (dlen > 2 || dname[0] != '.' || (dlen == 2 && dname[1] != '.')) - {if ((bleft -= (dlen+1)) < 0 || bleft < statSz) break; - strcpy(buff, dname); buff += dlen; *buff = '\n'; buff++; cnt++; - if (dLoc) - {strcpy(dLoc, dname); - rc = osFS->stat(pbuff, &Stat, myError, CRED, opaque); - if (rc != SFS_OK) - return fsError(rc, XROOTD_MON_STAT, myError, - argp->buff, opaque); - } - dlen = StatGen(Stat, buff); - bleft -= dlen; buff += (dlen-1); *buff = '\n'; buff++; - } - dname = 0; - } - if (dname) - {rc = Response.Send(kXR_oksofar, ebuff, buff-ebuff); - buff = ebuff; bleft = sizeof(ebuff); - } - } while(!rc && dname); - -// Send the ending packet if we actually have one to send -// - if (!rc) - {if (ebuff == buff) rc = Response.Send(); - else {*(buff-1) = '\0'; - rc = Response.Send((void *)ebuff, buff-ebuff); - } - } - -// Close the directory -// - dp->close(); - delete dp; - if (!rc) {TRACEP(FS, "dirstat entries=" <Terminate(Link, sessID.FD, sessID.Inst))) return -1; - -// Trace this request -// - TRACEP(LOGIN, "endsess " < 0) - return (rc = Response.Send(kXR_wait, rc, "session still active")) ? rc:1; - - if (rc == -EACCES)return Response.Send(kXR_NotAuthorized, "not session owner"); - if (rc == -ESRCH) return Response.Send(kXR_NotFound, "session not found"); - if (rc == -ETIME) return Response.Send(kXR_Cancelled,"session not ended"); - - return Response.Send(); -} - -/******************************************************************************/ -/* d o G e t f i l e */ -/******************************************************************************/ - -int XrdXrootdProtocol::do_Getfile() -{ -// int gopts, buffsz; - -// Keep Statistics -// - SI->Bump(SI->getfCnt); - -// Unmarshall the data -// -// gopts = int(ntohl(Request.getfile.options)); -// buffsz = int(ntohl(Request.getfile.buffsz)); - - return Response.Send(kXR_Unsupported, "getfile request is not supported"); -} - -/******************************************************************************/ -/* d o _ L o c a t e */ -/******************************************************************************/ - -int XrdXrootdProtocol::do_Locate() -{ - static XrdXrootdCallBack locCB("locate", XROOTD_MON_LOCATE); - int rc, opts, fsctl_cmd = SFS_FSCTL_LOCATE; - char *opaque = 0, *Path, *fn = argp->buff, opt[8], *op=opt; - XrdOucErrInfo myError(Link->ID,&locCB,ReqID.getID(),Monitor.Did,clientPV); - bool doDig = false; - -// Unmarshall the data -// - opts = (int)ntohs(Request.locate.options); - -// Map the options -// - if (opts & kXR_nowait) {fsctl_cmd |= SFS_O_NOWAIT; *op++ = 'i';} - if (opts & kXR_refresh) {fsctl_cmd |= SFS_O_RESET; *op++ = 's';} - if (opts & kXR_force ) {fsctl_cmd |= SFS_O_FORCE; *op++ = 'f';} - if (opts & kXR_prefname){fsctl_cmd |= SFS_O_HNAME; *op++ = 'n';} - if (opts & kXR_compress){fsctl_cmd |= SFS_O_RAWIO; *op++ = 'u';} - *op = '\0'; - TRACEP(FS, "locate " <Path(); - fsctl_cmd |= SFS_O_TRUNC; - } - -// Check for static routing -// - if (!doDig) {STATIC_REDIRECT(RD_locate);} - -// Prescreen the path -// - if (Path) - {if (rpCheck(Path, &opaque)) return rpEmsg("Locating", Path); - if (!doDig && !Squash(Path))return vpEmsg("Locating", Path); - } - -// Preform the actual function -// - if (doDig) rc = digFS->fsctl(fsctl_cmd, fn, myError, CRED); - else rc = osFS->fsctl(fsctl_cmd, fn, myError, CRED); - TRACEP(FS, "rc=" <Bump(SI->LoginAT); - -// Unmarshall the data -// - pid = (int)ntohl(Request.login.pid); - for (i = 0; i < (int)sizeof(Request.login.username); i++) - {if (Request.login.username[i] == '\0' || - Request.login.username[i] == ' ') break; - uname[i] = Request.login.username[i]; - } - uname[i] = '\0'; - -// Make sure the user is not already logged in -// - if (Status) return Response.Send(kXR_InvalidRequest, - "duplicate login; already logged in"); - -// Establish the ID for this link -// - Link->setID(uname, pid); - CapVer = Request.login.capver[0]; - -// Establish the session ID if the client can handle it (protocol version > 0) -// - if ((i = (CapVer & kXR_vermask))) - {sessID.FD = Link->FDnum(); - sessID.Inst = Link->Inst(); - sessID.Pid = myPID; - sessMutex.Lock(); mySID = ++Sid; sessMutex.UnLock(); - sessID.Sid = mySID; - sendSID = 1; - if (!clientPV) - { if (i >= kXR_ver004) clientPV = (int)0x0310; - else if (i == kXR_ver003) clientPV = (int)0x0300; - else if (i == kXR_ver002) clientPV = (int)0x0290; - else if (i == kXR_ver001) clientPV = (int)0x0200; - else clientPV = (int)0x0100; - } - if (CapVer & kXR_asyncap) clientPV |= XrdOucEI::uAsync; - if (Request.login.ability & kXR_fullurl) - clientPV |= XrdOucEI::uUrlOK; - if (Request.login.ability & kXR_multipr) - clientPV |= (XrdOucEI::uMProt | XrdOucEI::uUrlOK); - if (Request.login.ability & kXR_readrdok) - clientPV |= XrdOucEI::uReadR; - if (Request.login.ability & kXR_hasipv64) - clientPV |= XrdOucEI::uIPv64; - } - -// Mark the client as IPv4 if they came in as IPv4 or mapped IPv4 we can only -// return IPv4 addresses. Of course, if the client is dual-stacked then we -// simply indicate the client can accept either (the client better be honest). -// - addrP = Link->AddrInfo(); - if (addrP->isIPType(XrdNetAddrInfo::IPv4) || addrP->isMapped()) - clientPV |= XrdOucEI::uIPv4; -// WORKAROUND: XrdCl 4.0.x often identifies worker nodes as being IPv6-only. -// Rather than breaking a significant number of our dual-stack workers, we -// automatically denote IPv6 connections as also supporting IPv4 - regardless -// of what the remote client claims. This was fixed in 4.3.x but we can't -// tell release differences until 4.5 when we can safely ignore this as we -// also don't want to misidentify IPv6-only clients either. - else if (i < kXR_ver004 && XrdInet::GetAssumeV4()) - clientPV |= XrdOucEI::uIPv64; - -// Mark the client as being on a private net if the address is private -// - if (addrP->isPrivate()) {clientPV |= XrdOucEI::uPrip; rdType = 1;} - else rdType = 0; - -// Check if this is an admin login -// - if (*(Request.login.role) & (kXR_char)kXR_useradmin) - Status = XRD_ADMINUSER; - -// Get the security token for this link. We will either get a token, a null -// string indicating host-only authentication, or a null indicating no -// authentication. We can then optimize of each case. -// - if (CIA) - {const char *pp=CIA->getParms(i, Link->AddrInfo()); - if (pp && i ) {if (!sendSID) rc = Response.Send((void *)pp, i); - else {struct iovec iov[3]; - iov[1].iov_base = (char *)&sessID; - iov[1].iov_len = sizeof(sessID); - iov[2].iov_base = (char *)pp; - iov[2].iov_len = i; - rc = Response.Send(iov,3,int(i+sizeof(sessID))); - } - Status = (XRD_LOGGEDIN | XRD_NEED_AUTH); - } - else {rc = (sendSID ? Response.Send((void *)&sessID, sizeof(sessID)) - : Response.Send()); - Status = XRD_LOGGEDIN; SI->Bump(SI->LoginUA); - } - } - else {rc = (sendSID ? Response.Send((void *)&sessID, sizeof(sessID)) - : Response.Send()); - Status = XRD_LOGGEDIN; SI->Bump(SI->LoginUA); - } - -// We always allow at least host-based authentication. This may be over-ridden -// should strong authentication be enabled. Allocation of the protocol object -// already supplied the protocol name and the host name. We supply the tident -// and the connection details in addrInfo. -// - Entity.tident = Link->ID; - Entity.addrInfo = Link->AddrInfo(); - Client = &Entity; - -// Check if we need to process a login environment -// - if (Request.login.dlen > 8) - {XrdOucEnv loginEnv(argp->buff+1, Request.login.dlen-1); - char *cCode = loginEnv.Get("xrd.cc"); - char *tzVal = loginEnv.Get("xrd.tz"); - char *appXQ = loginEnv.Get("xrd.appname"); - char *aInfo = loginEnv.Get("xrd.info"); - int tzNum = (tzVal ? atoi(tzVal) : 0); - if (cCode && *cCode && tzNum >= -12 && tzNum <= 14) - {XrdNetAddrInfo::LocInfo locInfo; - locInfo.Country[0] = cCode[0]; locInfo.Country[1] = cCode[1]; - locInfo.TimeZone = tzNum & 0xff; - Link->setLocation(locInfo); - } - if (Monitor.Ready() && (appXQ || aInfo)) - {char apBuff[1024]; - snprintf(apBuff, sizeof(apBuff), "&x=%s&y=%s", - (appXQ ? appXQ : ""), (aInfo ? aInfo : "")); - Entity.moninfo = strdup(apBuff); - } - } - -// Allocate a monitoring object, if needed for this connection -// - if (Monitor.Ready()) - {Monitor.Register(Link->ID, Link->Host(), "xroot"); - if (Monitor.Logins() && (!Monitor.Auths() || !(Status & XRD_NEED_AUTH))) - {Monitor.Report(Entity.moninfo); - if (Entity.moninfo) {free(Entity.moninfo); Entity.moninfo = 0;} - } - } - -// Complete the rquestID object -// - ReqID.setID(Request.header.streamid, Link->FDnum(), Link->Inst()); - -// Document this login -// - if (!(Status & XRD_NEED_AUTH)) logLogin(); - return rc; -} - -/******************************************************************************/ -/* d o _ M k d i r */ -/******************************************************************************/ - -int XrdXrootdProtocol::do_Mkdir() -{ - int mode, rc; - char *opaque; - XrdOucErrInfo myError(Link->ID, Monitor.Did, clientPV); - -// Check for static routing -// - STATIC_REDIRECT(RD_mkdir); - -// Unmarshall the data -// - mode = mapMode((int)ntohs(Request.mkdir.mode)) | S_IRWXU; - if (Request.mkdir.options[0] & static_cast(kXR_mkdirpath)) - mode |= SFS_O_MKPTH; - if (rpCheck(argp->buff, &opaque)) return rpEmsg("Creating", argp->buff); - if (!Squash(argp->buff)) return vpEmsg("Creating", argp->buff); - -// Preform the actual function -// - rc = osFS->mkdir(argp->buff, (XrdSfsMode)mode, myError, CRED, opaque); - TRACEP(FS, "rc=" <buff); - if (SFS_OK == rc) return Response.Send(); - -// An error occured -// - return fsError(rc, XROOTD_MON_MKDIR, myError, argp->buff, opaque); -} - -/******************************************************************************/ -/* d o _ M v */ -/******************************************************************************/ - -int XrdXrootdProtocol::do_Mv() -{ - int rc; - char *oldp, *newp, *Opaque, *Npaque; - XrdOucErrInfo myError(Link->ID, Monitor.Did, clientPV); - -// Check for static routing -// - STATIC_REDIRECT(RD_mv); - -// Find the space separator between the old and new paths -// - oldp = newp = argp->buff; - if (Request.mv.arg1len) - {int n = ntohs(Request.mv.arg1len); - if (n < 0 || n >= Request.mv.dlen || *(argp->buff+n) != ' ') - return Response.Send(kXR_ArgInvalid, "invalid path specification"); - *(oldp+n) = 0; - newp += n+1; - } else { - while(*newp && *newp != ' ') newp++; - if (*newp) {*newp = '\0'; newp++; - while(*newp && *newp == ' ') newp++; - } - } - -// Get rid of relative paths and multiple slashes -// - if (rpCheck(oldp, &Opaque)) return rpEmsg("Renaming", oldp); - if (rpCheck(newp, &Npaque)) return rpEmsg("Renaming to", newp); - if (!Squash(oldp)) return vpEmsg("Renaming", oldp); - if (!Squash(newp)) return vpEmsg("Renaming to", newp); - -// Check if new path actually specified here -// - if (*newp == '\0') - Response.Send(kXR_ArgMissing, "new path specfied for mv"); - -// Preform the actual function -// - rc = osFS->rename(oldp, newp, myError, CRED, Opaque, Npaque); - TRACEP(FS, "rc=" <= maxStreams || !(pp = Stream[pathID])) - return Response.Send(kXR_ArgInvalid, "invalid path ID"); - -// Verify that this path is still functional -// - pp->streamMutex.Lock(); - if (pp->isDead || pp->isNOP) - {pp->streamMutex.UnLock(); - return Response.Send(kXR_ArgInvalid, - (pp->isDead ? "path ID is not functional" - : "path ID is not connected")); - } - -// Grab the stream ID -// - Response.StreamID(streamID); - -// Try to schedule this operation. In order to maximize the I/O overlap, we -// will wait until the stream gets control and will have a chance to start -// reading from the device or from the network. -// - do{if (!pp->isActive) - {pp->myFile = myFile; - pp->myOffset = myOffset; - pp->myIOLen = myIOLen; - pp->myBlen = 0; - pp->doWrite = static_cast(isWrite); - pp->doWriteC = 0; - pp->Resume = &XrdXrootdProtocol::do_OffloadIO; - pp->isActive = 1; - pp->reTry = &isAvail; - pp->Response.Set(streamID); - pp->streamMutex.UnLock(); - Link->setRef(1); - Sched->Schedule((XrdJob *)(pp->Link)); - isAvail.Wait(); - return 0; - } - - if ((pioP = pp->pioFree)) break; - pp->reTry = &isAvail; - pp->streamMutex.UnLock(); - TRACEP(FS, (isWrite ? 'w' : 'r') <<" busy path " <pioFree = pioP->Next; pioP->Next = 0; - pioP->Set(myFile, myOffset, myIOLen, streamID, static_cast(isWrite)); - if (pp->pioLast) pp->pioLast->Next = pioP; - else pp->pioFirst = pioP; - pp->pioLast = pioP; - pp->streamMutex.UnLock(); - return 0; -} - -/******************************************************************************/ -/* d o _ O f f l o a d I O */ -/******************************************************************************/ - -int XrdXrootdProtocol::do_OffloadIO() -{ - XrdSysSemaphore *sesSem; - XrdXrootdPio *pioP; - int rc; - -// Entry implies that we just got scheduled and are marked as active. Hence -// we need to post the session thread so that it can pick up the next request. -// We can manipulate the semaphore pointer without a lock as the only other -// thread that can manipulate the pointer is the waiting session thread. -// - if (!doWriteC && (sesSem = reTry)) {reTry = 0; sesSem->Post();} - -// Perform all I/O operations on a parallel stream (suppress async I/O). -// - do {if (!doWrite) rc = do_ReadAll(0); - else if ( (rc = (doWriteC ? do_WriteCont() : do_WriteAll()) ) > 0) - {Resume = &XrdXrootdProtocol::do_OffloadIO; - doWriteC = 1; - return rc; - } - streamMutex.Lock(); - if (rc || !(pioP = pioFirst)) break; - if (!(pioFirst = pioP->Next)) pioLast = 0; - myFile = pioP->myFile; - myOffset = pioP->myOffset; - myIOLen = pioP->myIOLen; - doWrite = pioP->isWrite; - doWriteC = 0; - Response.Set(pioP->StreamID); - pioP->Next = pioFree; pioFree = pioP; - if (reTry) {reTry->Post(); reTry = 0;} - streamMutex.UnLock(); - } while(1); - -// There are no pending operations or the link died -// - if (rc) isNOP = 1; - isActive = 0; - Stream[0]->Link->setRef(-1); - if (reTry) {reTry->Post(); reTry = 0;} - streamMutex.UnLock(); - return -EINPROGRESS; -} - -/******************************************************************************/ -/* d o _ O p e n */ -/******************************************************************************/ - -int XrdXrootdProtocol::do_Open() -{ - static XrdXrootdCallBack openCB("open file", XROOTD_MON_OPENR); - int fhandle; - int rc, mode, opts, openopts, doforce = 0, compchk = 0; - int popt, retStat = 0; - char *opaque, usage, ebuff[2048], opC; - bool doDig; - char *fn = argp->buff, opt[16], *op=opt, isAsync = '\0'; - XrdSfsFile *fp; - XrdXrootdFile *xp; - struct stat statbuf; - struct ServerResponseBody_Open myResp; - int resplen = sizeof(myResp.fhandle); - struct iovec IOResp[3]; // Note that IOResp[0] is completed by Response - -// Keep Statistics -// - SI->Bump(SI->openCnt); - -// Unmarshall the data -// - mode = (int)ntohs(Request.open.mode); - opts = (int)ntohs(Request.open.options); - -// Map the mode and options -// - mode = mapMode(mode) | S_IRUSR | S_IWUSR; usage = 'r'; - if (opts & kXR_open_read) - {openopts = SFS_O_RDONLY; *op++ = 'r'; opC = XROOTD_MON_OPENR;} - else if (opts & kXR_open_updt) - {openopts = SFS_O_RDWR; *op++ = 'u'; usage = 'w'; - opC = XROOTD_MON_OPENW;} - else if (opts & kXR_open_wrto) - {openopts = SFS_O_WRONLY; *op++ = 'o'; usage = 'w'; - opC = XROOTD_MON_OPENW;} - else {openopts = SFS_O_RDONLY; *op++ = 'r'; opC = XROOTD_MON_OPENR;} - - if (opts & kXR_new) - {openopts |= SFS_O_CREAT; *op++ = 'n'; opC = XROOTD_MON_OPENC; - if (opts & kXR_replica) {*op++ = '+'; - openopts |= SFS_O_REPLICA; - } - if (opts & kXR_mkdir) {*op++ = 'm'; - mode |= SFS_O_MKPTH; - } - } - else if (opts & kXR_delete) - {openopts = SFS_O_TRUNC; *op++ = 'd'; opC = XROOTD_MON_OPENW; - if (opts & kXR_mkdir) {*op++ = 'm'; - mode |= SFS_O_MKPTH; - } - } - if (opts & kXR_compress) - {openopts |= SFS_O_RAWIO; *op++ = 'c'; compchk = 1;} - if (opts & kXR_force) {*op++ = 'f'; doforce = 1;} - if ((opts & kXR_async || as_force) && !as_noaio) - {*op++ = 'a'; isAsync = '1';} - if (opts & kXR_refresh) {*op++ = 's'; openopts |= SFS_O_RESET; - SI->Bump(SI->Refresh); - } - if (opts & kXR_retstat) {*op++ = 't'; retStat = 1;} - if (opts & kXR_posc) {*op++ = 'p'; openopts |= SFS_O_POSC;} - *op = '\0'; - TRACEP(FS, "open " <newFile(Link->ID, Monitor.Did); - else fp = osFS->newFile(Link->ID, Monitor.Did); - -// Make sure we got one -// - if (!fp) - {snprintf(ebuff, sizeof(ebuff)-1,"Insufficient memory to open %s",fn); - eDest.Emsg("Xeq", ebuff); - return Response.Send(kXR_NoMemory, ebuff); - } - -// The open is elegible for a defered response, indicate we're ok with that -// - fp->error.setErrCB(&openCB, ReqID.getID()); - fp->error.setUCap(clientPV); - -// Open the file -// - if ((rc = fp->open(fn, (XrdSfsFileOpenMode)openopts, - (mode_t)mode, CRED, opaque))) - {rc = fsError(rc, opC, fp->error, fn, opaque); delete fp; return rc;} - -// Obtain a hyper file object -// - if (!(xp=new XrdXrootdFile(Link->ID,fp,usage,isAsync,Link->sfOK,&statbuf))) - {delete fp; - snprintf(ebuff, sizeof(ebuff)-1, "Insufficient memory to open %s", fn); - eDest.Emsg("Xeq", ebuff); - return Response.Send(kXR_NoMemory, ebuff); - } - -// Serialize the link -// - Link->Serialize(); - *ebuff = '\0'; - -// Lock this file -// - if (!(popt & XROOTDXP_NOLK) && (rc = Locker->Lock(xp, doforce))) - {const char *who; - if (rc > 0) who = (rc > 1 ? "readers" : "reader"); - else { rc = -rc; - who = (rc > 1 ? "writers" : "writer"); - } - snprintf(ebuff, sizeof(ebuff)-1, - "%s file %s is already opened by %d %s; open denied.", - ('r' == usage ? "Input" : "Output"), fn, rc, who); - delete fp; xp->XrdSfsp = 0; delete xp; - eDest.Emsg("Xeq", ebuff); - return Response.Send(kXR_FileLocked, ebuff); - } - -// Create a file table for this link if it does not have one -// - if (!FTab) FTab = new XrdXrootdFileTable(Monitor.Did); - -// Insert this file into the link's file table -// - if (!FTab || (fhandle = FTab->Add(xp)) < 0) - {delete xp; - snprintf(ebuff, sizeof(ebuff)-1, "Insufficient memory to open %s", fn); - eDest.Emsg("Xeq", ebuff); - return Response.Send(kXR_NoMemory, ebuff); - } - -// Document forced opens -// - if (doforce) - {int rdrs, wrtrs; - Locker->numLocks(xp, rdrs, wrtrs); - if (('r' == usage && wrtrs) || ('w' == usage && rdrs) || wrtrs > 1) - {snprintf(ebuff, sizeof(ebuff)-1, - "%s file %s forced opened with %d reader(s) and %d writer(s).", - ('r' == usage ? "Input" : "Output"), fn, rdrs, wrtrs); - eDest.Emsg("Xeq", ebuff); - } - } - -// Determine if file is compressed -// - if (!compchk) - {resplen = sizeof(myResp.fhandle); - memset(&myResp, 0, sizeof(myResp)); - } - else {int cpsize; - fp->getCXinfo((char *)myResp.cptype, cpsize); - if (cpsize) {myResp.cpsize = static_cast(htonl(cpsize)); - resplen = sizeof(myResp); - } else myResp.cpsize = 0; - } - -// If client wants a stat in open, return the stat information -// - if (retStat) - {retStat = StatGen(statbuf, ebuff); - IOResp[1].iov_base = (char *)&myResp; IOResp[1].iov_len = sizeof(myResp); - IOResp[2].iov_base = ebuff; IOResp[2].iov_len = retStat; - resplen = sizeof(myResp) + retStat; - } - -// If we are monitoring, send off a path to dictionary mapping (must try 1st!) -// - if (Monitor.Files()) - {xp->Stats.FileID = Monitor.MapPath(fn); - if (!(xp->Stats.monLvl)) xp->Stats.monLvl = XrdXrootdFileStats::monOn; - Monitor.Agent->Open(xp->Stats.FileID, statbuf.st_size); - } - -// Since file monitoring is deprecated, a dictid may not have been assigned. -// But if fstream monitoring is enabled it will assign the dictid. -// - if (Monitor.Fstat()) - XrdXrootdMonFile::Open(&(xp->Stats), fn, Monitor.Did, usage == 'w'); - -// Insert the file handle -// - memcpy((void *)myResp.fhandle,(const void *)&fhandle,sizeof(myResp.fhandle)); - numFiles++; - -// Respond -// - if (retStat) return Response.Send(IOResp, 3, resplen); - else return Response.Send((void *)&myResp, resplen); -} - -/******************************************************************************/ -/* d o _ P i n g */ -/******************************************************************************/ - -int XrdXrootdProtocol::do_Ping() -{ - -// Keep Statistics -// - SI->Bump(SI->miscCnt); - -// This is a basic nop -// - return Response.Send(); -} - -/******************************************************************************/ -/* d o _ P r e p a r e */ -/******************************************************************************/ - -int XrdXrootdProtocol::do_Prepare() -{ - int rc, hport, pathnum = 0; - char opts, hname[256], reqid[128], nidbuff[512], *path, *opaque; - XrdOucErrInfo myError(Link->ID, Monitor.Did, clientPV); - XrdOucTokenizer pathlist(argp->buff); - XrdOucTList *pFirst=0, *pP, *pLast = 0; - XrdOucTList *oFirst=0, *oP, *oLast = 0; - XrdOucTListHelper pHelp(&pFirst), oHelp(&oFirst); - XrdXrootdPrepArgs pargs(0, 0); - XrdSfsPrep fsprep; - -// Apply prepare limits, as necessary. - if ((PrepareLimit >= 0) && (++PrepareCount > PrepareLimit)) { - if (LimitError) { - return Response.Send(kXR_noserver, - "Surpassed this connection's prepare limit."); - } else { - return Response.Send(); - } - } - -// Grab the options -// - opts = Request.prepare.options; - -// Check for static routing -// - if ((opts & kXR_stage) || (opts & kXR_cancel)) {STATIC_REDIRECT(RD_prepstg);} - STATIC_REDIRECT(RD_prepare); - -// Get a request ID for this prepare and check for static routine -// - if (opts & kXR_stage && !(opts & kXR_cancel)) - {fsprep.reqid = PrepID->ID(reqid, sizeof(reqid)); - fsprep.opts = Prep_STAGE | (opts & kXR_coloc ? Prep_COLOC : 0); - } - else {reqid[0]='*'; reqid[1]='\0'; fsprep.reqid = reqid; fsprep.opts = 0;} - -// Initialize the fsile system prepare arg list -// - fsprep.paths = 0; - fsprep.oinfo = 0; - fsprep.opts |= Prep_PRTY0 | (opts & kXR_fresh ? Prep_FRESH : 0); - fsprep.notify = 0; - -// Check if this is a cancel request -// - if (opts & kXR_cancel) - {if (!(path = pathlist.GetLine())) - return Response.Send(kXR_ArgMissing, "Prepare requestid not specified"); - fsprep.reqid = PrepID->isMine(path, hport, hname, sizeof(hname)); - if (!fsprep.reqid) - {if (!hport) return Response.Send(kXR_ArgInvalid, - "Prepare requestid owned by an unknown server"); - TRACEI(REDIR, Response.ID() <<"redirecting to " << hname <<':' <prepare(fsprep, myError, CRED))) - return fsError(rc, XROOTD_MON_PREP, myError, path, 0); - rc = Response.Send(); - XrdXrootdPrepare::Logdel(path); - return rc; - } - -// Cycle through all of the paths in the list -// - while((path = pathlist.GetLine())) - {if (rpCheck(path, &opaque)) return rpEmsg("Preparing", path); - if (!Squash(path)) return vpEmsg("Preparing", path); - pP = new XrdOucTList(path, pathnum); - (pLast ? (pLast->next = pP) : (pFirst = pP)); pLast = pP; - oP = new XrdOucTList(opaque, 0); - (oLast ? (oLast->next = oP) : (oFirst = oP)); oLast = oP; - pathnum++; - } - -// Make sure we have at least one path -// - if (!pFirst) - return Response.Send(kXR_ArgMissing, "No prepare paths specified"); - -// Issue the prepare -// - if (opts & kXR_notify) - {fsprep.notify = nidbuff; - sprintf(nidbuff, Notify, Link->FDnum(), Link->ID); - fsprep.opts = (opts & kXR_noerrs ? Prep_SENDAOK : Prep_SENDACK); - } - if (opts & kXR_wmode) fsprep.opts |= Prep_WMODE; - fsprep.paths = pFirst; - fsprep.oinfo = oFirst; - if (SFS_OK != (rc = osFS->prepare(fsprep, myError, CRED))) - return fsError(rc, XROOTD_MON_PREP, myError, pFirst->text, oFirst->text); - -// Perform final processing -// - if (!(opts & kXR_stage)) rc = Response.Send(); - else {rc = Response.Send(reqid, strlen(reqid)); - pargs.reqid=reqid; - pargs.user=Link->ID; - pargs.paths=pFirst; - XrdXrootdPrepare::Log(pargs); - pargs.reqid = 0; - } - return rc; -} - -/******************************************************************************/ -/* d o _ P r o t o c o l */ -/******************************************************************************/ - -int XrdXrootdProtocol::do_Protocol(ServerResponseBody_Protocol *rsp) -{ - static kXR_int32 verNum = static_cast(htonl(kXR_PROTOCOLVERSION)); - static kXR_int32 theRle = static_cast(htonl(myRole)); - static kXR_int32 theRlf = static_cast(htonl(myRolf)); - - ServerResponseBody_Protocol theResp; - ServerResponseBody_Protocol *respP = (rsp ? rsp : &theResp); - int RespLen = kXR_ShortProtRespLen; - -// Keep Statistics -// - SI->Bump(SI->miscCnt); - -// Determine which response to provide -// - if (Request.protocol.clientpv) - {int cvn = XrdOucEI::uVMask & ntohl(Request.protocol.clientpv); - if (!Status || !(clientPV & XrdOucEI::uVMask)) - clientPV = (clientPV & ~XrdOucEI::uVMask) | cvn; - else cvn = (clientPV & XrdOucEI::uVMask); - if (DHS && cvn >= kXR_PROTSIGNVERSION - && Request.protocol.flags & kXR_secreqs) - RespLen += DHS->ProtResp(respP->secreq, *(Link->AddrInfo()), cvn); - respP->flags = theRle; - } else { - respP->flags = theRlf; - } - -// Return info -// - respP->pval = verNum; - return (rsp ? RespLen : Response.Send((void *)&theResp,RespLen)); -} - -/******************************************************************************/ -/* d o _ P u t f i l e */ -/******************************************************************************/ - -int XrdXrootdProtocol::do_Putfile() -{ -// int popts, buffsz; - -// Keep Statistics -// - SI->Bump(SI->putfCnt); - -// Unmarshall the data -// -// popts = int(ntohl(Request.putfile.options)); -// buffsz = int(ntohl(Request.putfile.buffsz)); - - return Response.Send(kXR_Unsupported, "putfile request is not supported"); -} - -/******************************************************************************/ -/* d o _ Q c o n f */ -/******************************************************************************/ - -int XrdXrootdProtocol::do_Qconf() -{ - static const int fsctl_cmd = SFS_FSCTL_STATCC|SFS_O_LOCAL; - XrdOucTokenizer qcargs(argp->buff); - char *val, buff[4096], *bp=buff; - int n, bleft = sizeof(buff); - -// Get the first argument -// - if (!qcargs.GetLine() || !(val = qcargs.GetToken())) - return Response.Send(kXR_ArgMissing, "query config argument not specified."); - -// Trace this query variable -// - do {TRACEP(DEBUG, "query config " <next ? ',' :'\n'); - n = snprintf(bp, bleft, "%d:%s%c", tP->ival[0], tP->text, sep); - bp += n; bleft -= n; - tP = tP->next; - } while(tP && bleft > 0); - } - else if (!strcmp("cid", val)) - {const char *cidval = getenv("XRDCMSCLUSTERID"); - if (!cidval || !(*cidval)) cidval = "cid"; - n = snprintf(bp, bleft, "%s\n", cidval); - bp += n; bleft -= n; - } - else if (!strcmp("cms", val)) - {XrdOucErrInfo myError(Link->ID, Monitor.Did, clientPV); - if (osFS->fsctl(fsctl_cmd, ".", myError, CRED) == SFS_DATA) - n = snprintf(bp, bleft, "%s\n", myError.getErrText()); - else n = snprintf(bp, bleft, "%s\n", "cms"); - bp += n; bleft -= n; - } - else if (!strcmp("pio_max", val)) - {n = snprintf(bp, bleft, "%d\n", maxPio+1); - bp += n; bleft -= n; - } - else if (!strcmp("readv_ior_max", val)) - {n = snprintf(bp,bleft,"%d\n",maxTransz-(int)sizeof(readahead_list)); - bp += n; bleft -= n; - } - else if (!strcmp("readv_iov_max", val)) - {n = snprintf(bp, bleft, "%d\n", maxRvecsz); - bp += n; bleft -= n; - } - else if (!strcmp("role", val)) - {const char *theRole = getenv("XRDROLE"); - n = snprintf(bp, bleft, "%s\n", (theRole ? theRole : "none")); - bp += n; bleft -= n; - } - else if (!strcmp("sitename", val)) - {const char *siteName = getenv("XRDSITE"); - n = snprintf(bp, bleft, "%s\n", (siteName ? siteName : "sitename")); - bp += n; bleft -= n; - } - else if (!strcmp("sysid", val)) - {const char *cidval = getenv("XRDCMSCLUSTERID"); - const char *nidval = getenv("XRDCMSVNID"); - if (!cidval || !(*cidval) || !nidval || !(*nidval)) - {cidval = "sysid"; nidval = "";} - n = snprintf(bp, bleft, "%s %s\n", nidval, cidval); - bp += n; bleft -= n; - } - else if (!strcmp("tpc", val)) - {char *tpcval = getenv("XRDTPC"); - n = snprintf(bp, bleft, "%s\n", (tpcval ? tpcval : "tpc")); - bp += n; bleft -= n; - } - else if (!strcmp("wan_port", val) && WANPort) - {n = snprintf(bp, bleft, "%d\n", WANPort); - bp += n; bleft -= n; - } - else if (!strcmp("wan_window", val) && WANPort) - {n = snprintf(bp, bleft, "%d\n", WANWindow); - bp += n; bleft -= n; - } - else if (!strcmp("window", val) && Window) - {n = snprintf(bp, bleft, "%d\n", Window); - bp += n; bleft -= n; - } - else if (!strcmp("version", val)) - {n = snprintf(bp, bleft, "%s\n", XrdVSTRING); - bp += n; bleft -= n; - } - else if (!strcmp("vnid", val)) - {const char *nidval = getenv("XRDCMSVNID"); - if (!nidval || !(*nidval)) nidval = "vnid"; - n = snprintf(bp, bleft, "%s\n", nidval); - bp += n; bleft -= n; - } - else {n = strlen(val); - if (bleft <= n) break; - strcpy(bp, val); bp +=n; *bp = '\n'; bp++; - bleft -= (n+1); - } - } while(bleft > 0 && (val = qcargs.GetToken())); - -// Make sure all ended well -// - if (val) - return Response.Send(kXR_ArgTooLong, "too many query config arguments."); - -// All done -// - return Response.Send(buff, sizeof(buff) - bleft); -} - -/******************************************************************************/ -/* d o _ Q f h */ -/******************************************************************************/ - -int XrdXrootdProtocol::do_Qfh() -{ - static XrdXrootdCallBack qryCB("query", XROOTD_MON_QUERY); - XrdXrootdFHandle fh(Request.query.fhandle); - XrdXrootdFile *fp; - const char *fArg = 0, *qType = ""; - int rc; - short qopt = (short)ntohs(Request.query.infotype); - -// Update misc stats count -// - SI->Bump(SI->miscCnt); - -// Find the file object -// - if (!FTab || !(fp = FTab->Get(fh.handle))) - return Response.Send(kXR_FileNotOpen, - "query does not refer to an open file"); - -// The query is elegible for a defered response, indicate we're ok with that -// - fp->XrdSfsp->error.setErrCB(&qryCB, ReqID.getID()); - -// Perform the appropriate query -// - switch(qopt) - {case kXR_Qopaqug: qType = "Qopaqug"; - fArg = (Request.query.dlen ? argp->buff : 0); - rc = fp->XrdSfsp->fctl(SFS_FCTL_SPEC1, - Request.query.dlen, fArg, - CRED); - break; - case kXR_Qvisa: qType = "Qvisa"; - rc = fp->XrdSfsp->fctl(SFS_FCTL_STATV, 0, - fp->XrdSfsp->error); - break; - default: return Response.Send(kXR_ArgMissing, - "Required query argument not present"); - } - -// Preform the actual function -// - TRACEP(FS, "query " <buff, &opaque)) return rpEmsg("Querying", argp->buff); - if (!Squash(argp->buff)) return vpEmsg("Querying", argp->buff); - - // Setup arguments - // - myData.Arg1 = argp->buff; - myData.Arg1Len = (opaque ? opaque - argp->buff - 1 : dlen); - myData.Arg2 = opaque; - myData.Arg2Len = (opaque ? argp->buff + dlen - opaque : 0); - fsctl_cmd = SFS_FSCTL_PLUGIN; - Act = " qopaquf '"; AData = argp->buff; - } - -// Preform the actual function using the supplied arguments -// - rc = osFS->FSctl(fsctl_cmd, myData, myError, CRED); - TRACEP(FS, "rc=" <ID, Monitor.Did, clientPV); - char *opaque; - int n, rc; - -// Check for static routing -// - STATIC_REDIRECT(RD_stat); - -// Prescreen the path -// - if (rpCheck(argp->buff, &opaque)) return rpEmsg("Stating", argp->buff); - if (!Squash(argp->buff)) return vpEmsg("Stating", argp->buff); - -// Add back the opaque info -// - if (opaque) - {n = strlen(argp->buff); argp->buff[n] = '?'; - if ((argp->buff)+n != opaque-1) - memmove(&argp->buff[n+1], opaque, strlen(opaque)+1); - } - -// Preform the actual function using the supplied logical FS name -// - rc = osFS->fsctl(fsctl_cmd, argp->buff, myError, CRED); - TRACEP(FS, "rc=" <buff <<"'"); - if (rc == SFS_OK) Response.Send(""); - return fsError(rc, XROOTD_MON_QUERY, myError, argp->buff, opaque); -} - -/******************************************************************************/ -/* d o _ Q u e r y */ -/******************************************************************************/ - -int XrdXrootdProtocol::do_Query() -{ - short qopt = (short)ntohs(Request.query.infotype); - -// Perform the appropriate query -// - switch(qopt) - {case kXR_QStats: return SI->Stats(Response, - (Request.header.dlen ? argp->buff : "a")); - case kXR_Qcksum: return do_CKsum(0); - case kXR_Qckscan: return do_CKsum(1); - case kXR_Qconfig: return do_Qconf(); - case kXR_Qspace: return do_Qspace(); - case kXR_Qxattr: return do_Qxattr(); - case kXR_Qopaque: - case kXR_Qopaquf: return do_Qopaque(qopt); - case kXR_Qopaqug: return do_Qfh(); - default: break; - } - -// Whatever we have, it's not valid -// - return Response.Send(kXR_ArgInvalid, - "Invalid information query type code"); -} - -/******************************************************************************/ -/* d o _ Q x a t t r */ -/******************************************************************************/ - -int XrdXrootdProtocol::do_Qxattr() -{ - static XrdXrootdCallBack statCB("stat", XROOTD_MON_QUERY); - static const int fsctl_cmd = SFS_FSCTL_STATXA; - int rc; - char *opaque; - XrdOucErrInfo myError(Link->ID,&statCB,ReqID.getID(),Monitor.Did,clientPV); - -// Check for static routing -// - STATIC_REDIRECT(RD_stat); - -// Prescreen the path -// - if (rpCheck(argp->buff, &opaque)) return rpEmsg("Stating", argp->buff); - if (!Squash(argp->buff)) return vpEmsg("Stating", argp->buff); - -// Preform the actual function -// - rc = osFS->fsctl(fsctl_cmd, argp->buff, myError, CRED); - TRACEP(FS, "rc=" <buff); - return fsError(rc, XROOTD_MON_QUERY, myError, argp->buff, opaque); -} - -/******************************************************************************/ -/* d o _ R e a d */ -/******************************************************************************/ - -int XrdXrootdProtocol::do_Read() -{ - int pathID, retc; - XrdXrootdFHandle fh(Request.read.fhandle); - numReads++; - -// We first handle the pre-read list, if any. We do it this way because of -// a historical glitch in the protocol. One should really not piggy back a -// pre-read on top of a read, though it is allowed. -// - if (!Request.header.dlen) pathID = 0; - else if (do_ReadNone(retc, pathID)) return retc; - -// Unmarshall the data -// - myIOLen = ntohl(Request.read.rlen); - n2hll(Request.read.offset, myOffset); - -// Find the file object -// - if (!FTab || !(myFile = FTab->Get(fh.handle))) - return Response.Send(kXR_FileNotOpen, - "read does not refer to an open file"); - -// Trace and verify read length is not negative -// - TRACEP(FS, pathID <<" fh=" <Add_rd(myFile->Stats.FileID, Request.read.rlen, - Request.read.offset); - -// Short circuit processing if read length is zero -// - if (!myIOLen) return Response.Send(); - -// See if an alternate path is required, offload the read -// - if (pathID) return do_Offload(pathID, 0); - -// Now read all of the data (do pre-reads first) -// - return do_ReadAll(); -} - -/******************************************************************************/ -/* d o _ R e a d A l l */ -/******************************************************************************/ - -// myFile = file to be read -// myOffset = Offset at which to read -// myIOLen = Number of bytes to read from file and write to socket - -int XrdXrootdProtocol::do_ReadAll(int asyncOK) -{ - int rc, xframt, Quantum = (myIOLen > maxBuffsz ? maxBuffsz : myIOLen); - char *buff; - -// If this file is memory mapped, short ciruit all the logic and immediately -// transfer the requested data to minimize latency. -// - if (myFile->isMMapped) - {if (myOffset >= myFile->Stats.fSize) return Response.Send(); - if (myOffset+myIOLen <= myFile->Stats.fSize) - {myFile->Stats.rdOps(myIOLen); - return Response.Send(myFile->mmAddr+myOffset, myIOLen); - } - xframt = myFile->Stats.fSize -myOffset; - myFile->Stats.rdOps(xframt); - return Response.Send(myFile->mmAddr+myOffset, xframt); - } - -// If we are sendfile enabled, then just send the file if possible -// - if (myFile->sfEnabled && myIOLen >= as_minsfsz - && myOffset+myIOLen <= myFile->Stats.fSize) - {myFile->Stats.rdOps(myIOLen); - if (myFile->fdNum >= 0) - return Response.Send(myFile->fdNum, myOffset, myIOLen); - rc = myFile->XrdSfsp->SendData((XrdSfsDio *)this, myOffset, myIOLen); - if (rc == SFS_OK) - {if (!myIOLen) return 0; - if (myIOLen < 0) return -1; // Otherwise retry using read() - } else return fsError(rc, 0, myFile->XrdSfsp->error, 0, 0); - } - -// If we are in async mode, schedule the read to ocur asynchronously -// - if (asyncOK && myFile->AsyncMode) - {if (myIOLen >= as_miniosz && Link->UseCnt() < as_maxperlnk) - if ((rc = aio_Read()) != -EAGAIN) return rc; - SI->AsyncRej++; - } - -// Make sure we have a large enough buffer -// - if (!argp || Quantum < halfBSize || Quantum > argp->bsize) - {if ((rc = getBuff(1, Quantum)) <= 0) return rc;} - else if (hcNow < hcNext) hcNow++; - buff = argp->buff; - -// Now read all of the data. For statistics, we need to record the orignal -// amount of the request even if we really do not get to read that much! -// - myFile->Stats.rdOps(myIOLen); - do {if ((xframt = myFile->XrdSfsp->read(myOffset, buff, Quantum)) <= 0) break; - if (xframt >= myIOLen) return Response.Send(buff, xframt); - if (Response.Send(kXR_oksofar, buff, xframt) < 0) return -1; - myOffset += xframt; myIOLen -= xframt; - if (myIOLen < Quantum) Quantum = myIOLen; - } while(myIOLen); - -// Determine why we ended here -// - if (xframt == 0) return Response.Send(); - return fsError(xframt, 0, myFile->XrdSfsp->error, 0, 0); -} - -/******************************************************************************/ -/* d o _ R e a d N o n e */ -/******************************************************************************/ - -int XrdXrootdProtocol::do_ReadNone(int &retc, int &pathID) -{ - XrdXrootdFHandle fh; - int ralsz = Request.header.dlen; - struct read_args *rargs=(struct read_args *)(argp->buff); - struct readahead_list *ralsp = (readahead_list *)(rargs+sizeof(read_args)); - -// Return the pathid -// - pathID = static_cast(rargs->pathid); - if ((ralsz -= sizeof(read_args)) <= 0) return 0; - -// Make sure that we have a proper pre-read list -// - if (ralsz%sizeof(readahead_list)) - {Response.Send(kXR_ArgInvalid, "Invalid length for read ahead list"); - return 1; - } - -// Run down the pre-read list -// - while(ralsz > 0) - {myIOLen = ntohl(ralsp->rlen); - n2hll(ralsp->offset, myOffset); - memcpy((void *)&fh.handle, (const void *)ralsp->fhandle, - sizeof(fh.handle)); - TRACEP(FS, "fh=" <Get(fh.handle))) - {retc = Response.Send(kXR_FileNotOpen, - "preread does not refer to an open file"); - return 1; - } - myFile->XrdSfsp->read(myOffset, myIOLen); - ralsz -= sizeof(struct readahead_list); - ralsp++; - numReads++; - }; - -// All done -// - return 0; -} - -/******************************************************************************/ -/* d o _ R e a d V */ -/******************************************************************************/ - -int XrdXrootdProtocol::do_ReadV() -{ -// This will read multiple buffers at the same time in an attempt to avoid -// the latency in a network. The information with the offsets and lengths -// of the information to read is passed as a data buffer... then we decode -// it and put all the individual buffers in a single one it's up to the -// client to interpret it. Code originally developed by Leandro Franco, CERN. -// The readv file system code originally added by Brian Bockelman, UNL. -// - const int hdrSZ = sizeof(readahead_list); - struct XrdOucIOVec rdVec[maxRvecsz+1]; - struct readahead_list *raVec, respHdr; - long long totSZ; - XrdSfsXferSize rdVAmt, rdVXfr, xfrSZ = 0; - int rdVBeg, rdVBreak, rdVNow, rdVNum, rdVecNum; - int currFH, i, k, Quantum, Qleft, rdVecLen = Request.header.dlen; - int rvMon = Monitor.InOut(); - int ioMon = (rvMon > 1); - char *buffp, vType = (ioMon ? XROOTD_MON_READU : XROOTD_MON_READV); - -// Compute number of elements in the read vector and make sure we have no -// partial elements. -// - rdVecNum = rdVecLen / sizeof(readahead_list); - if ( (rdVecLen <= 0) || (rdVecNum*hdrSZ != rdVecLen) ) - return Response.Send(kXR_ArgInvalid, "Read vector is invalid"); - -// Make sure that we can copy the read vector to our local stack. We must impose -// a limit on it's size. We do this to be able to reuse the data buffer to -// prevent cross-cpu memory cache synchronization. -// - if (rdVecNum > maxRvecsz) - return Response.Send(kXR_ArgTooLong, "Read vector is too long"); - -// So, now we account for the number of readv requests and total segments -// - numReadV++; numSegsV += rdVecNum; - -// Run down the list and compute the total size of the read. No individual -// read may be greater than the maximum transfer size. We also use this loop -// to copy the read ahead list to our readv vector for later processing. -// - raVec = (readahead_list *)argp->buff; - totSZ = rdVecLen; Quantum = maxTransz - hdrSZ; - for (i = 0; i < rdVecNum; i++) - {totSZ += (rdVec[i].size = ntohl(raVec[i].rlen)); - if (rdVec[i].size < 0) return Response.Send(kXR_ArgInvalid, - "Readv length is negative"); - if (rdVec[i].size > Quantum) return Response.Send(kXR_NoMemory, - "Single readv transfer is too large"); - rdVec[i].offset = ntohll(raVec[i].offset); - memcpy(&rdVec[i].info, raVec[i].fhandle, sizeof(int)); - } - -// Now add an extra dummy element to force flushing of the read vector. -// - rdVec[i].offset = -1; - rdVec[i].size = 0; - rdVec[i].info = -1; - rdVBreak = rdVecNum; - rdVecNum++; - -// We limit the total size of the read to be 2GB for convenience -// - if (totSZ > 0x7fffffffLL) - return Response.Send(kXR_NoMemory, "Total readv transfer is too large"); - -// Calculate the transfer unit which will be the smaller of the maximum -// transfer unit and the actual amount we need to transfer. -// - if ((Quantum = static_cast(totSZ)) > maxTransz) Quantum = maxTransz; - -// Now obtain the right size buffer -// - if ((Quantum < halfBSize && Quantum > 1024) || Quantum > argp->bsize) - {if ((k = getBuff(1, Quantum)) <= 0) return k;} - else if (hcNow < hcNext) hcNow++; - -// Check that we really have at least one file open. This needs to be done -// only once as this code runs in the control thread. -// - if (!FTab) return Response.Send(kXR_FileNotOpen, - "readv does not refer to an open file"); - -// Preset the previous and current file handle to be the handle of the first -// element and make sure the file is actually open. -// - currFH = rdVec[0].info; - memcpy(respHdr.fhandle, &currFH, sizeof(respHdr.fhandle)); - if (!(myFile = FTab->Get(currFH))) return Response.Send(kXR_FileNotOpen, - "readv does not refer to an open file"); - -// Setup variables for running through the list. -// - Qleft = Quantum; buffp = argp->buff; rvSeq++; - rdVBeg = rdVNow = 0; rdVXfr = rdVAmt = 0; - -// Now run through the elements -// - for (i = 0; i < rdVecNum; i++) - {if (rdVec[i].info != currFH) - {xfrSZ = myFile->XrdSfsp->readv(&rdVec[rdVNow], i-rdVNow); - if (xfrSZ != rdVAmt) break; - rdVNum = i - rdVBeg; rdVXfr += rdVAmt; - myFile->Stats.rvOps(rdVXfr, rdVNum); - if (rvMon) - {Monitor.Agent->Add_rv(myFile->Stats.FileID, htonl(rdVXfr), - htons(rdVNum), rvSeq, vType); - if (ioMon) for (k = rdVBeg; k < i; k++) - Monitor.Agent->Add_rd(myFile->Stats.FileID, - htonl(rdVec[k].size), htonll(rdVec[k].offset)); - } - rdVXfr = rdVAmt = 0; - if (i == rdVBreak) break; - rdVBeg = rdVNow = i; currFH = rdVec[i].info; - memcpy(respHdr.fhandle, &currFH, sizeof(respHdr.fhandle)); - if (!(myFile = FTab->Get(currFH))) - return Response.Send(kXR_FileNotOpen, - "readv does not refer to an open file"); - } - - if (Qleft < (rdVec[i].size + hdrSZ)) - {if (rdVAmt) - {xfrSZ = myFile->XrdSfsp->readv(&rdVec[rdVNow], i-rdVNow); - if (xfrSZ != rdVAmt) break; - } - if (Response.Send(kXR_oksofar,argp->buff,Quantum-Qleft) < 0) - return -1; - Qleft = Quantum; - buffp = argp->buff; - rdVNow = i; rdVXfr += rdVAmt; rdVAmt = 0; - } - - xfrSZ = rdVec[i].size; rdVAmt += xfrSZ; - respHdr.rlen = htonl(xfrSZ); - respHdr.offset = htonll(rdVec[i].offset); - memcpy(buffp, &respHdr, hdrSZ); - rdVec[i].data = buffp + hdrSZ; - buffp += (xfrSZ+hdrSZ); Qleft -= (xfrSZ+hdrSZ); - TRACEP(FS,"fh=" <= 0) - {xfrSZ = SFS_ERROR; - myFile->XrdSfsp->error.setErrInfo(-ENODATA,"readv past EOF"); - } - return fsError(xfrSZ, 0, myFile->XrdSfsp->error, 0, 0); - } - -// All done, return result of the last segment or just zero -// - return (Quantum != Qleft ? Response.Send(argp->buff, Quantum-Qleft) : 0); -} - -/******************************************************************************/ -/* d o _ R m */ -/******************************************************************************/ - -int XrdXrootdProtocol::do_Rm() -{ - int rc; - char *opaque; - XrdOucErrInfo myError(Link->ID, Monitor.Did, clientPV); - -// Check for static routing -// - STATIC_REDIRECT(RD_rm); - -// Prescreen the path -// - if (rpCheck(argp->buff, &opaque)) return rpEmsg("Removing", argp->buff); - if (!Squash(argp->buff)) return vpEmsg("Removing", argp->buff); - -// Preform the actual function -// - rc = osFS->rem(argp->buff, myError, CRED, opaque); - TRACEP(FS, "rc=" <buff); - if (SFS_OK == rc) return Response.Send(); - -// An error occured -// - return fsError(rc, XROOTD_MON_RM, myError, argp->buff, opaque); -} - -/******************************************************************************/ -/* d o _ R m d i r */ -/******************************************************************************/ - -int XrdXrootdProtocol::do_Rmdir() -{ - int rc; - char *opaque; - XrdOucErrInfo myError(Link->ID, Monitor.Did, clientPV); - -// Check for static routing -// - STATIC_REDIRECT(RD_rmdir); - -// Prescreen the path -// - if (rpCheck(argp->buff, &opaque)) return rpEmsg("Removing", argp->buff); - if (!Squash(argp->buff)) return vpEmsg("Removing", argp->buff); - -// Preform the actual function -// - rc = osFS->remdir(argp->buff, myError, CRED, opaque); - TRACEP(FS, "rc=" <buff); - if (SFS_OK == rc) return Response.Send(); - -// An error occured -// - return fsError(rc, XROOTD_MON_RMDIR, myError, argp->buff, opaque); -} - -/******************************************************************************/ -/* d o _ S e t */ -/******************************************************************************/ - -int XrdXrootdProtocol::do_Set() -{ - XrdOucTokenizer setargs(argp->buff); - char *val, *rest; - -// Get the first argument -// - if (!setargs.GetLine() || !(val = setargs.GetToken(&rest))) - return Response.Send(kXR_ArgMissing, "set argument not specified."); - -// Trace this set -// - TRACEP(DEBUG, "set " <ID, "appid", rest); - return Response.Send(); - } - else if (!strcmp("monitor", val)) return do_Set_Mon(setargs); - -// All done -// - return Response.Send(kXR_ArgInvalid, "invalid set parameter"); -} - -/******************************************************************************/ -/* d o _ S e t _ M o n */ -/******************************************************************************/ - -// Process: set monitor {off | on} {[appid] | info [info]} - -int XrdXrootdProtocol::do_Set_Mon(XrdOucTokenizer &setargs) -{ - char *val, *appid; - kXR_unt32 myseq = 0; - -// Get the first argument -// - if (!(val = setargs.GetToken(&appid))) - return Response.Send(kXR_ArgMissing,"set monitor argument not specified."); - -// For info requests, nothing changes. However, info events must have been -// enabled for us to record them. Route the information via the static -// monitor entry, since it knows how to forward the information. -// - if (!strcmp(val, "info")) - {if (appid && Monitor.Info()) - {while(*appid && *appid == ' ') appid++; - if (strlen(appid) > 1024) appid[1024] = '\0'; - if (*appid) myseq = Monitor.MapInfo(appid); - } - return Response.Send((void *)&myseq, sizeof(myseq)); - } - -// Determine if on do appropriate processing -// - if (!strcmp(val, "on")) - {Monitor.Enable(); - if (appid && Monitor.InOut()) - {while(*appid && *appid == ' ') appid++; - if (*appid) Monitor.Agent->appID(appid); - } - if (!Monitor.Did && Monitor.Logins()) MonAuth(); - return Response.Send(); - } - -// Determine if off and do appropriate processing -// - if (!strcmp(val, "off")) - {if (appid && Monitor.InOut()) - {while(*appid && *appid == ' ') appid++; - if (*appid) Monitor.Agent->appID(appid); - } - Monitor.Disable(); - return Response.Send(); - } - -// Improper request -// - return Response.Send(kXR_ArgInvalid, "invalid set monitor argument"); -} - -/******************************************************************************/ -/* d o _ S t a t */ -/******************************************************************************/ - -int XrdXrootdProtocol::do_Stat() -{ - static XrdXrootdCallBack statCB("stat", XROOTD_MON_STAT); - static const int fsctl_cmd = SFS_FSCTL_STATFS; - bool doDig; - int rc; - char *opaque, xxBuff[256]; - struct stat buf; - XrdOucErrInfo myError(Link->ID,&statCB,ReqID.getID(),Monitor.Did,clientPV); - -// Update misc stats count -// - SI->Bump(SI->miscCnt); - -// The stat request may refer to an open file handle. So, screen this out. -// - if (!argp || !Request.header.dlen) - {XrdXrootdFile *fp; - XrdXrootdFHandle fh(Request.stat.fhandle); - if (Request.stat.options & kXR_vfs) - {Response.Send(kXR_ArgMissing, "Required argument not present"); - return 0; - } - if (!FTab || !(fp = FTab->Get(fh.handle))) - return Response.Send(kXR_FileNotOpen, - "stat does not refer to an open file"); - rc = fp->XrdSfsp->stat(&buf); - TRACEP(FS, "stat rc=" <buff); - if (!doDig && !Squash(argp->buff))return vpEmsg("Stating", argp->buff); - -// Preform the actual function -// - if (Request.stat.options & kXR_vfs) - {rc = osFS->fsctl(fsctl_cmd, argp->buff, myError, CRED); - TRACEP(FS, "rc=" <buff); - if (rc == SFS_OK) Response.Send(""); - } else { - if (doDig) rc = digFS->stat(argp->buff, &buf, myError, CRED, opaque); - else rc = osFS->stat(argp->buff, &buf, myError, CRED, opaque); - TRACEP(FS, "rc=" <buff); - if (rc == SFS_OK) return Response.Send(xxBuff, StatGen(buf, xxBuff)); - } - return fsError(rc, (doDig ? 0 : XROOTD_MON_STAT),myError,argp->buff,opaque); -} - -/******************************************************************************/ -/* d o _ S t a t x */ -/******************************************************************************/ - -int XrdXrootdProtocol::do_Statx() -{ - static XrdXrootdCallBack statxCB("xstat", XROOTD_MON_STAT); - int rc; - char *path, *opaque, *respinfo = argp->buff; - mode_t mode; - XrdOucErrInfo myError(Link->ID,&statxCB,ReqID.getID(),Monitor.Did,clientPV); - XrdOucTokenizer pathlist(argp->buff); - -// Check for static routing -// - STATIC_REDIRECT(RD_stat); - -// Cycle through all of the paths in the list -// - while((path = pathlist.GetLine())) - {if (rpCheck(path, &opaque)) return rpEmsg("Stating", path); - if (!Squash(path)) return vpEmsg("Stating", path); - rc = osFS->stat(path, mode, myError, CRED, opaque); - TRACEP(FS, "rc=" <buff, respinfo-argp->buff); -} - -/******************************************************************************/ -/* d o _ S y n c */ -/******************************************************************************/ - -int XrdXrootdProtocol::do_Sync() -{ - static XrdXrootdCallBack syncCB("sync", 0); - int rc; - XrdXrootdFile *fp; - XrdXrootdFHandle fh(Request.sync.fhandle); - -// Keep Statistics -// - SI->Bump(SI->syncCnt); - -// Find the file object -// - if (!FTab || !(fp = FTab->Get(fh.handle))) - return Response.Send(kXR_FileNotOpen,"sync does not refer to an open file"); - -// The sync is elegible for a defered response, indicate we're ok with that -// - fp->XrdSfsp->error.setErrCB(&syncCB, ReqID.getID()); - -// Sync the file -// - rc = fp->XrdSfsp->sync(); - TRACEP(FS, "sync rc=" <Bump(SI->miscCnt); - - // Find the file object - // - if (!FTab || !(fp = FTab->Get(fh.handle))) - return Response.Send(kXR_FileNotOpen, - "trunc does not refer to an open file"); - - // Truncate the file (it is eligible for async callbacks) - // - fp->XrdSfsp->error.setErrCB(&truncCB, ReqID.getID()); - rc = fp->XrdSfsp->truncate(theOffset); - TRACEP(FS, "trunc rc=" <buff); - if (!Squash(argp->buff)) return vpEmsg("Truncating",argp->buff); - - // Preform the actual function - // - rc = osFS->truncate(argp->buff, (XrdSfsFileOffset)theOffset, myError, - CRED, opaque); - TRACEP(FS, "rc=" <buff); - if (SFS_OK != rc) - return fsError(rc, XROOTD_MON_TRUNC, myError, argp->buff, opaque); - } - -// Respond that all went well -// - return Response.Send(); -} - -/******************************************************************************/ -/* d o _ W r i t e */ -/******************************************************************************/ - -int XrdXrootdProtocol::do_Write() -{ - int retc, pathID; - XrdXrootdFHandle fh(Request.write.fhandle); - numWrites++; - -// Unmarshall the data -// - myIOLen = Request.header.dlen; - n2hll(Request.write.offset, myOffset); - pathID = static_cast(Request.write.pathid); - -// Find the file object -// . - if (!FTab || !(myFile = FTab->Get(fh.handle))) - {if (argp && !pathID) return do_WriteNone(); - Response.Send(kXR_FileNotOpen,"write does not refer to an open file"); - return Link->setEtext("write protcol violation"); - } - -// Trace and verify that length is not negative -// - TRACEP(FS, "fh=" <Add_wr(myFile->Stats.FileID, Request.write.dlen, - Request.write.offset); - -// If zero length write, simply return -// - if (!myIOLen) return Response.Send(); - -// See if an alternate path is required -// - if (pathID) return do_Offload(pathID, 1); - -// If we are in async mode, schedule the write to occur asynchronously -// - if (myFile->AsyncMode && !as_syncw) - {if (myStalls > as_maxstalls) myStalls--; - else if (myIOLen >= as_miniosz && Link->UseCnt() < as_maxperlnk) - {if ((retc = aio_Write()) != -EAGAIN) - {if (retc != -EIO) return retc; - myEInfo[0] = SFS_ERROR; - myFile->XrdSfsp->error.setErrInfo(retc, "I/O error"); - return do_WriteNone(); - } - } - SI->AsyncRej++; - } - -// Just to the i/o now -// - myFile->Stats.wrOps(myIOLen); // Optimistically correct - return do_WriteAll(); -} - -/******************************************************************************/ -/* d o _ W r i t e A l l */ -/******************************************************************************/ - -// myFile = file to be written -// myOffset = Offset at which to write -// myIOLen = Number of bytes to read from socket and write to file - -int XrdXrootdProtocol::do_WriteAll() -{ - int rc, Quantum = (myIOLen > maxBuffsz ? maxBuffsz : myIOLen); - -// Make sure we have a large enough buffer -// - if (!argp || Quantum < halfBSize || Quantum > argp->bsize) - {if ((rc = getBuff(0, Quantum)) <= 0) return rc;} - else if (hcNow < hcNext) hcNow++; - -// Now write all of the data (XrdXrootdProtocol.C defines getData()) -// - while(myIOLen > 0) - {if ((rc = getData("data", argp->buff, Quantum))) - {if (rc > 0) - {Resume = &XrdXrootdProtocol::do_WriteCont; - myBlast = Quantum; - myStalls++; - } - return rc; - } - if ((rc = myFile->XrdSfsp->write(myOffset, argp->buff, Quantum)) < 0) - {myIOLen = myIOLen-Quantum; myEInfo[0] = rc; - return do_WriteNone(); - } - myOffset += Quantum; myIOLen -= Quantum; - if (myIOLen < Quantum) Quantum = myIOLen; - } - -// All done -// - return Response.Send(); -} - -/******************************************************************************/ -/* d o _ W r i t e C o n t */ -/******************************************************************************/ - -// myFile = file to be written -// myOffset = Offset at which to write -// myIOLen = Number of bytes to read from socket and write to file -// myBlast = Number of bytes already read from the socket - -int XrdXrootdProtocol::do_WriteCont() -{ - int rc; - -// Write data that was finaly finished comming in -// - if ((rc = myFile->XrdSfsp->write(myOffset, argp->buff, myBlast)) < 0) - {myIOLen = myIOLen-myBlast; myEInfo[0] = rc; - return do_WriteNone(); - } - myOffset += myBlast; myIOLen -= myBlast; - -// See if we need to finish this request in the normal way -// - if (myIOLen > 0) return do_WriteAll(); - return Response.Send(); -} - -/******************************************************************************/ -/* d o _ W r i t e N o n e */ -/******************************************************************************/ - -int XrdXrootdProtocol::do_WriteNone() -{ - int rlen, blen = (myIOLen > argp->bsize ? argp->bsize : myIOLen); - -// Discard any data being transmitted -// - TRACEP(REQ, "discarding " < 0) - {rlen = Link->Recv(argp->buff, blen, readWait); - if (rlen < 0) return Link->setEtext("link read error"); - myIOLen -= rlen; - if (rlen < blen) - {myBlen = 0; - Resume = &XrdXrootdProtocol::do_WriteNone; - return 1; - } - if (myIOLen < blen) blen = myIOLen; - } - -// Send our the error message and return -// - if (!myFile) return - Response.Send(kXR_FileNotOpen,"write does not refer to an open file"); - if (myEInfo[0]) return fsError(myEInfo[0], 0, myFile->XrdSfsp->error, 0, 0); - return Response.Send(kXR_FSError, myFile->XrdSfsp->error.getErrText()); -} - -/******************************************************************************/ -/* d o _ W r i t e S p a n */ -/******************************************************************************/ - -int XrdXrootdProtocol::do_WriteSpan() -{ - int rc; - XrdXrootdFHandle fh(Request.write.fhandle); - numWrites++; - -// Unmarshall the data -// - myIOLen = Request.header.dlen; - n2hll(Request.write.offset, myOffset); - -// Find the file object -// . - if (!FTab || !(myFile = FTab->Get(fh.handle))) - {if (argp && !Request.write.pathid) - {myIOLen -= myBlast; return do_WriteNone();} - Response.Send(kXR_FileNotOpen,"write does not refer to an open file"); - return Link->setEtext("write protcol violation"); - } - -// If we are monitoring, insert a write entry -// - if (Monitor.InOut()) - Monitor.Agent->Add_wr(myFile->Stats.FileID, Request.write.dlen, - Request.write.offset); - -// Trace this entry -// - TRACEP(FS, "fh=" <XrdSfsp->write(myOffset, myBuff, myBlast)) < 0) - {myIOLen = myIOLen-myBlast; myEInfo[0] = rc; - return do_WriteNone(); - } - myOffset += myBlast; myIOLen -= myBlast; - -// See if we need to finish this request in the normal way -// - if (myIOLen > 0) return do_WriteAll(); - return Response.Send(); -} - -/******************************************************************************/ -/* d o _ W r i t e V */ -/******************************************************************************/ - -int XrdXrootdProtocol::do_WriteV() -{ -// This will write multiple buffers at the same time in an attempt to avoid -// the disk latency. The information with the offsets and lengths of teh data -// to write is passed as a data buffer. We attempt to optimize as best as -// possible, though certain combinations may result in multiple writes. Since -// socket flushing is nearly impossible when an error occurs, most errors -// simply terminate the connection. -// - const int wveSZ = sizeof(XrdProto::write_list); - struct trackInfo - {XrdXrootdWVInfo **wvInfo; bool doit; - trackInfo(XrdXrootdWVInfo **wvP) : wvInfo(wvP), doit(true) {} - ~trackInfo() {if (doit && *wvInfo) {free(*wvInfo); *wvInfo = 0;}} - } freeInfo(&wvInfo); - - struct XrdProto::write_list *wrLst; - XrdOucIOVec *wrVec; - long long totSZ, maxSZ; - int curFH, k, Quantum, wrVecNum, wrVecLen = Request.header.dlen; - -// Compute number of elements in the write vector and make sure we have no -// partial elements. -// - wrVecNum = wrVecLen / wveSZ; - if ( (wrVecLen <= 0) || (wrVecNum*wveSZ != wrVecLen) ) - {Response.Send(kXR_ArgInvalid, "Write vector is invalid"); - return -1; - } - -// Make sure that we can make a copy of the read vector. So, we impose a limit -// on it's size. -// - if (wrVecNum > maxWvecsz) - {Response.Send(kXR_ArgTooLong, "Write vector is too long"); - return -1; - } - -// Create the verctor write information structure sized as needed. -// - if (wvInfo) free(wvInfo); - wvInfo = (XrdXrootdWVInfo *)malloc(sizeof(XrdXrootdWVInfo) + - sizeof(XrdOucIOVec)*(wrVecNum-1)); - memset(wvInfo, 0, sizeof(XrdXrootdWVInfo) - sizeof(XrdOucIOVec)); - wvInfo->wrVec = wrVec = wvInfo->ioVec; - -// Run down the list and compute the total size of the write. No individual -// write may be greater than the maximum transfer size. We also use this loop -// to copy the write list to our writev vector for later processing. -// - wrLst = (XrdProto::write_list *)argp->buff; - totSZ = 0; maxSZ = 0; k = 0; Quantum = maxTransz; curFH = 0; - for (int i = 0; i < wrVecNum; i++) - {if (wrLst[i].wlen == 0) continue; - memcpy(&wrVec[k].info, wrLst[i].fhandle, sizeof(int)); - wrVec[k].size = ntohl(wrLst[i].wlen); - if (wrVec[k].size < 0) - {Response.Send(kXR_ArgInvalid, "Writev length is negtive"); - return -1; - } - if (wrVec[k].size > Quantum) - {Response.Send(kXR_NoMemory,"Single writev transfer is too large"); - return -1; - } - wrVec[k].offset = ntohll(wrLst[i].offset); - if (wrVec[k].info == curFH) totSZ += wrVec[k].size; - else {if (maxSZ < totSZ) maxSZ = totSZ; - totSZ = wrVec[k].size; - } - k++; - } - -// Check if we are not actually writing anything, simply return success -// - if (maxSZ < totSZ) maxSZ = totSZ; - if (maxSZ == 0) return Response.Send(); - -// So, now we account for the number of writev requests and total segments -// - numWritV++; numSegsW += k; wrVecNum = k; - -// Calculate the transfer unit which will be the smaller of the maximum -// transfer unit and the actual amount we need to transfer. -// - if (maxSZ > maxTransz) Quantum = maxTransz; - else Quantum = static_cast(maxSZ); - -// Now obtain the right size buffer -// - if ((Quantum < halfBSize && Quantum > 1024) || Quantum > argp->bsize) - {if (getBuff(0, Quantum) <= 0) return -1;} - else if (hcNow < hcNext) hcNow++; - -// Check that we really have at least the first file open (part of setup) -// - if (!FTab || !(myFile = FTab->Get(wrVec[0].info))) - {Response.Send(kXR_FileNotOpen, "writev does not refer to an open file"); - return -1; - } - -// Setup to do the complete transfer -// - wvInfo->curFH = wrVec[0].info; - wvInfo->vBeg = 0; - wvInfo->vPos = 0; - wvInfo->vEnd = wrVecNum; - wvInfo->vMon = 0; - wvInfo->doSync= (Request.writev.options & ClientWriteVRequest::doSync) != 0; - wvInfo->wvMon = Monitor.InOut(); - wvInfo->ioMon = (wvInfo->vMon > 1); -// wvInfo->vType = (wvInfo->ioMon ? XROOTD_MON_WRITEU : XROOTD_MON_WRITEV); - myWVBytes = 0; - myIOLen = wrVec[0].size; - myBuff = argp->buff; - myBlast = 0; - -// Now we simply start the write operations -// - freeInfo.doit = false; - return do_WriteVec(); -} - -/******************************************************************************/ -/* d o _ W r i t e V e c */ -/******************************************************************************/ - -int XrdXrootdProtocol::do_WriteVec() -{ - XrdSfsXferSize xfrSZ; - int rc, wrVNum, vNow = wvInfo->vPos; - bool done, newfile; - -// Read the complete data from the socket for the current element. Note that -// should we enter a resume state; upon re-entry all of the data will be read. -// -do{if (myIOLen > 0) - {wvInfo->wrVec[vNow].data = argp->buff + myBlast; - myBlast += myIOLen; - if ((rc = getData("data", myBuff, myIOLen))) - {if (rc < 0) return rc; - myIOLen = 0; - Resume = &XrdXrootdProtocol::do_WriteVec; - myStalls++; - return rc; - } - } - -// Establish the state at this point as this will tell us what to do next. -// - vNow++; - done = newfile = false; - if (vNow >= wvInfo->vEnd) done = true; - else if (wvInfo->wrVec[vNow].info != wvInfo->curFH) newfile = true; - else if (myBlast + wvInfo->wrVec[vNow].size <= argp->bsize) - {myIOLen = wvInfo->wrVec[vNow].size; - myBuff = argp->buff + myBlast; - wvInfo->vPos = vNow; - continue; - } - -// We need to write out what we have. -// - wrVNum = vNow - wvInfo->vBeg; - xfrSZ = myFile->XrdSfsp->writev(&(wvInfo->wrVec[wvInfo->vBeg]), wrVNum); - TRACEP(FS,"fh=" <curFH <<" writeV " << xfrSZ <<':' <vMon; - myFile->Stats.wvOps(myWVBytes, monVnum); -/*!! if (wvMon) - {Monitor.Agent->Add_wv(myFile->Stats.FileID, htonl(myWVBytes), - htons(monVNum), wvSeq++, wvInfo->vType); - if (ioMon) for (int k = wvInfo->vMon; k < vNow; k++) - Monitor.Agent->Add_wr(myFile->Stats.FileID, - htonl(wvInfo->wrVec[k].size), - htonll(wvInfo->wrVec[k].offset)); - } -*/ - wvInfo->vMon = vNow; - myWVBytes = 0; - if (wvInfo->doSync) - {myFile->XrdSfsp->error.setErrCB(0,0); - xfrSZ = myFile->XrdSfsp->sync(); - if (xfrSZ< 0) break; - } - } - -// If we are done, the finish up -// - if (done) - {if (wvInfo) {free(wvInfo); wvInfo = 0;} - return Response.Send(); - } - -// Sequence to a new file if we need to do so -// - if (newfile) - {if (!FTab || !(myFile = FTab->Get(wvInfo->wrVec[vNow].info))) - {Response.Send(kXR_FileNotOpen,"writev does not refer to an open file"); - return -1; - } - wvInfo->curFH = wvInfo->wrVec[vNow].info; - } - -// Setup to resume transfer -// - myBlast = 0; - myBuff = argp->buff; - myIOLen = wvInfo->wrVec[vNow].size; - wvInfo->vBeg = vNow; - wvInfo->vPos = vNow; - -} while(true); - -// If we got here then there was a write error (file pointer is valid). -// - if (wvInfo) {free(wvInfo); wvInfo = 0;} - return fsError((int)xfrSZ, 0, myFile->XrdSfsp->error, 0, 0); -} - -/******************************************************************************/ -/* S e n d F i l e */ -/******************************************************************************/ - -int XrdXrootdProtocol::SendFile(int fildes) -{ - -// Make sure we have some data to send -// - if (!myIOLen) return 1; - -// Send off the data -// - myIOLen = Response.Send(fildes, myOffset, myIOLen); - return myIOLen; -} - -/******************************************************************************/ - -int XrdXrootdProtocol::SendFile(XrdOucSFVec *sfvec, int sfvnum) -{ - int i, xframt = 0; - -// Make sure we have some data to send -// - if (!myIOLen) return 1; - -// Verify the length, it can't be greater than what the client wants -// - for (i = 1; i < sfvnum; i++) xframt += sfvec[i].sendsz; - if (xframt > myIOLen) return 1; - -// Send off the data -// - if (xframt) myIOLen = Response.Send(sfvec, sfvnum, xframt); - else {myIOLen = 0; Response.Send();} - return myIOLen; -} - -/******************************************************************************/ -/* S e t F D */ -/******************************************************************************/ - -void XrdXrootdProtocol::SetFD(int fildes) -{ - if (fildes < 0) myFile->sfEnabled = 0; - else myFile->fdNum = fildes; -} - -/******************************************************************************/ -/* U t i l i t y M e t h o d s */ -/******************************************************************************/ -/******************************************************************************/ -/* f s E r r o r */ -/******************************************************************************/ - -int XrdXrootdProtocol::fsError(int rc, char opC, XrdOucErrInfo &myError, - const char *Path, char *Cgi) -{ - int ecode, popt, rs; - const char *eMsg = myError.getErrText(ecode); - -// Process standard errors -// - if (rc == SFS_ERROR) - {SI->errorCnt++; - rc = XProtocol::mapError(ecode); - - if (Path && (rc == kXR_Overloaded) && (opC == XROOTD_MON_OPENR - || opC == XROOTD_MON_OPENW || opC == XROOTD_MON_OPENC)) - {if (myError.extData()) myError.Reset(); - return fsOvrld(opC, Path, Cgi); - } - - if (Path && (rc == kXR_NotFound) && RQLxist && opC - && (popt = RQList.Validate(Path))) - {if (XrdXrootdMonitor::Redirect()) - XrdXrootdMonitor::Redirect(Monitor.Did, - Route[popt].Host[rdType], - Route[popt].Port[rdType], - opC|XROOTD_MON_REDLOCAL, Path); - if (Cgi) rs = fsRedirNoEnt(eMsg, Cgi, popt); - else rs = Response.Send(kXR_redirect, - Route[popt].Port[rdType], - Route[popt].Host[rdType]); - } else rs = Response.Send((XErrorCode)rc, eMsg); - if (myError.extData()) myError.Reset(); - return rs; - } - -// Process the redirection (error msg is host:port) -// - if (rc == SFS_REDIRECT) - {SI->redirCnt++; - if (ecode < 0 && ecode != -1) ecode = (ecode ? -ecode : Port); - if (XrdXrootdMonitor::Redirect() && Path && opC) - XrdXrootdMonitor::Redirect(Monitor.Did, eMsg, Port, opC, Path); - TRACEI(REDIR, Response.ID() <<"redirecting to " << eMsg <<':' <stallCnt++; - if (ecode <= 0) ecode = 1800; - TRACEI(STALL, Response.ID() <<"delaying client up to " <Done(ecode, &myError); - if (myError.extData()) myError.Reset(); - return (rc ? rc : 1); - } - -// Process the data response -// - if (rc == SFS_DATA) - {if (ecode) rs = Response.Send((void *)eMsg, ecode); - else rs = Response.Send(); - if (myError.extData()) myError.Reset(); - return rs; - } - -// Process the data response via an iovec -// - if (rc == SFS_DATAVEC) - {if (ecode < 2) rs = Response.Send(); - else rs = Response.Send((struct iovec *)eMsg, ecode); - if (myError.getErrCB()) myError.getErrCB()->Done(ecode, &myError); - if (myError.extData()) myError.Reset(); - return rs; - } - -// Process the deferal -// - if (rc >= SFS_STALL) - {SI->stallCnt++; - TRACEI(STALL, Response.ID() <<"stalling client for " <errorCnt++; - sprintf(buff, "%d", rc); - eDest.Emsg("Xeq", "Unknown error code", buff, eMsg); - rs = Response.Send(kXR_ServerError, eMsg); - if (myError.extData()) myError.Reset(); - return rs; - } -} - -/******************************************************************************/ -/* f s O v r l d */ -/******************************************************************************/ - -int XrdXrootdProtocol::fsOvrld(char opC, const char *Path, char *Cgi) -{ - static const char *prot = "root://"; - static int negOne = -1; - static char quest = '?', slash = '/'; - - struct iovec rdrResp[8]; - char *destP=0, dest[512]; - int iovNum=0, pOff, port; - -// If this is a forwarded path and the client can handle full url's then -// redirect the client to the destination in the path. Otherwise, if there is -// an alternate destination, send client there. Otherwise, stall the client. -// - if (OD_Bypass && clientPV & XrdOucEI::uUrlOK - && (pOff = XrdOucUtils::isFWD(Path, &port, dest, sizeof(dest)))) - { rdrResp[1].iov_base = (void *)&negOne; - rdrResp[1].iov_len = sizeof(negOne); - rdrResp[2].iov_base = (void *)prot; - rdrResp[2].iov_len = 7; // root:// - rdrResp[3].iov_base = (void *)dest; - rdrResp[3].iov_len = strlen(dest); // host:port - rdrResp[4].iov_base = (void *)&slash; - rdrResp[4].iov_len = (*Path == '/' ? 1 : 0); // / or nil for objid - rdrResp[5].iov_base = (void *)(Path+pOff); - rdrResp[5].iov_len = strlen(Path+pOff); // path - if (Cgi && *Cgi) - {rdrResp[6].iov_base = (void *)? - rdrResp[6].iov_len = sizeof(quest); // ? - rdrResp[7].iov_base = (void *)Cgi; - rdrResp[7].iov_len = strlen(Cgi); // cgi - iovNum = 8; - } else iovNum = 6; - destP = dest; - } else if ((destP = Route[RD_ovld].Host[rdType])) - port = Route[RD_ovld].Port[rdType]; - -// If a redirect happened, then trace it. -// - if (destP) - {SI->redirCnt++; - if (XrdXrootdMonitor::Redirect()) - XrdXrootdMonitor::Redirect(Monitor.Did, destP, port, - opC|XROOTD_MON_REDLOCAL, Path); - if (iovNum) - {TRACEI(REDIR, Response.ID() <<"redirecting to "<stallCnt++; - return Response.Send(kXR_wait, OD_Stall, "server is overloaded"); - } - -// We were unsuccessful, return overload as an error -// - return Response.Send(kXR_Overloaded, "server is overloaded"); -} - -/******************************************************************************/ -/* f s R e d i r N o E n t */ -/******************************************************************************/ - -int XrdXrootdProtocol::fsRedirNoEnt(const char *eMsg, char *Cgi, int popt) -{ - struct iovec ioV[4]; - char *tried, *trend, *ptried = 0; - kXR_int32 pnum = htonl(static_cast(Route[popt].Port[rdType])); - int tlen; - -// Try to find the last tried token in the cgi -// - if ((trend = Cgi)) - {do {if (!(tried = strstr(Cgi, "tried="))) break; - if (tried == trend || *(tried-1) == '&') - {if (!ptried || (*(tried+6) && *(tried+6) != '&')) ptried=tried;} - Cgi = index(tried+6, '&'); - } while(Cgi); - } - -// If we did find a tried, bracket it out with a leading comma (we can modify -// the passed cgi string here because this is the last time it will be used. -// - if ((tried = ptried)) - {tried += 5; - while(*(tried+1) && *(tried+1) == ',') tried++; - trend = index(tried, '&'); - if (trend) {tlen = trend - tried; *trend = 0;} - else tlen = strlen(tried); - *tried = ','; - } else tlen = 0; - -// Check if we are in a redirect loop (i.e. we are listed in the client's cgi). -// If so, then treat this and file not found as we've been here before. -// - if ((trend = tried) && eMsg) - do {if ((trend = strstr(trend, myCName))) - {if (*(trend+myCNlen) == '\0' || *(trend+myCNlen) == ',') - return Response.Send(kXR_NotFound, eMsg); - trend = index(trend+myCNlen, ','); - } - } while(trend); - - -// If we have not found a tried token or that token far too large to propogate -// (i.e. it's likely we have an undetected loop), then do a simple redirect. -// - if (!tried || !tlen || tlen > 16384) - return Response.Send(kXR_redirect, - Route[popt].Port[rdType], - Route[popt].Host[rdType]); - -// We need to append the client's tried list to the one we have to avoid loops -// - - ioV[1].iov_base = (char *)&pnum; - ioV[1].iov_len = sizeof(pnum); - ioV[2].iov_base = Route[popt].Host[rdType]; - ioV[2].iov_len = Route[popt].RDSz[rdType]; - ioV[3].iov_base = tried; - ioV[3].iov_len = tlen; - -// Compute total length -// - tlen += sizeof(pnum) + Route[popt].RDSz[rdType]; - -// Send off the redirect -// - return Response.Send(kXR_redirect, ioV, 4, tlen); -} - -/******************************************************************************/ -/* g e t B u f f */ -/******************************************************************************/ - -int XrdXrootdProtocol::getBuff(const int isRead, int Quantum) -{ - -// Check if we need to really get a new buffer -// - if (!argp || Quantum > argp->bsize) hcNow = hcPrev; - else if (Quantum >= halfBSize || hcNow-- > 0) return 1; - else if (hcNext >= hcMax) hcNow = hcMax; - else {int tmp = hcPrev; - hcNow = hcNext; - hcPrev = hcNext; - hcNext = tmp+hcNext; - } - -// Get a new buffer -// - if (argp) BPool->Release(argp); - if ((argp = BPool->Obtain(Quantum))) halfBSize = argp->bsize >> 1; - else return Response.Send(kXR_NoMemory, (isRead ? - "insufficient memory to read file" : - "insufficient memory to write file")); - -// Success -// - return 1; -} - -/******************************************************************************/ -/* Private: l o g L o g i n */ -/******************************************************************************/ - -void XrdXrootdProtocol::logLogin(bool xauth) -{ - const char *uName, *ipName; - char lBuff[512]; - -// Determine ip type -// - if (clientPV & XrdOucEI::uIPv4) - ipName = (clientPV & XrdOucEI::uIPv64 ? "IP46" : "IPv4"); - else ipName = (clientPV & XrdOucEI::uIPv64 ? "IP64" : "IPv6"); - -// Determine client name -// - if (xauth) uName = (Client->name ? Client->name : "nobody"); - else uName = 0; - -// Format the line -// - sprintf(lBuff, "%s %s %slogin%s", - (clientPV & XrdOucEI::uPrip ? "pvt" : "pub"), ipName, - (Status & XRD_ADMINUSER ? "admin " : ""), - (xauth ? " as" : "")); - -// Document the login -// - eDest.Log(SYS_LOG_01, "Xeq", Link->ID, lBuff, uName); -} - -/******************************************************************************/ -/* m a p M o d e */ -/******************************************************************************/ - -#define Map_Mode(x,y) if (Mode & kXR_ ## x) newmode |= S_I ## y - -int XrdXrootdProtocol::mapMode(int Mode) -{ - int newmode = 0; - -// Map the mode in the obvious way -// - Map_Mode(ur, RUSR); Map_Mode(uw, WUSR); Map_Mode(ux, XUSR); - Map_Mode(gr, RGRP); Map_Mode(gw, WGRP); Map_Mode(gx, XGRP); - Map_Mode(or, ROTH); Map_Mode(ox, XOTH); - -// All done -// - return newmode; -} - -/******************************************************************************/ -/* M o n A u t h */ -/******************************************************************************/ - -void XrdXrootdProtocol::MonAuth() -{ - char Buff[4096]; - const char *bP = Buff; - - if (Client == &Entity) bP = Entity.moninfo; - else snprintf(Buff,sizeof(Buff), "&p=%s&n=%s&h=%s&o=%s&r=%s&g=%s&m=%s%s", - Client->prot, - (Client->name ? Client->name : ""), - (Client->host ? Client->host : ""), - (Client->vorg ? Client->vorg : ""), - (Client->role ? Client->role : ""), - (Client->grps ? Client->grps : ""), - (Client->moninfo ? Client->moninfo : ""), - (Entity.moninfo ? Entity.moninfo : "") - ); - - Monitor.Report(bP); - if (Entity.moninfo) {free(Entity.moninfo); Entity.moninfo = 0;} -} - -/******************************************************************************/ -/* r p C h e c k */ -/******************************************************************************/ - -int XrdXrootdProtocol::rpCheck(char *fn, char **opaque) -{ - char *cp; - - if (*fn != '/') - {if (!(XPList.Opts() & XROOTDXP_NOSLASH)) return 1; - if ( XPList.Opts() & XROOTDXP_NOCGI) {*opaque = 0; return 0;} - } - - if (!(cp = index(fn, '?'))) *opaque = 0; - else {*cp = '\0'; *opaque = cp+1; - if (!**opaque) *opaque = 0; - } - - if (*fn != '/') return 0; - - while ((cp = index(fn, '/'))) - {fn = cp+1; - if (fn[0] == '.' && fn[1] == '.' && fn[2] == '/') return 1; - } - return 0; -} - -/******************************************************************************/ -/* r p E m s g */ -/******************************************************************************/ - -int XrdXrootdProtocol::rpEmsg(const char *op, char *fn) -{ - char buff[2048]; - snprintf(buff,sizeof(buff)-1,"%s relative path '%s' is disallowed.",op,fn); - buff[sizeof(buff)-1] = '\0'; - return Response.Send(kXR_NotAuthorized, buff); -} - -/******************************************************************************/ -/* S e t S F */ -/******************************************************************************/ - -int XrdXrootdProtocol::SetSF(kXR_char *fhandle, bool seton) -{ - XrdXrootdFHandle fh(fhandle); - XrdXrootdFile *theFile; - - if (!FTab || !(theFile = FTab->Get(fh.handle))) return -EBADF; - -// Turn it off or on if so wanted -// - if (!seton) theFile->sfEnabled = 0; - else if (theFile->fdNum >= 0) theFile->sfEnabled = 1; - -// All done -// - return 0; -} - -/******************************************************************************/ -/* S q u a s h */ -/******************************************************************************/ - -int XrdXrootdProtocol::Squash(char *fn) -{ - char *ofn, *ifn = fn; - - if (*fn != '/') return XPList.Opts(); - - while(*ifn) - {if (*ifn == '/') - if (*(ifn+1) == '/' - || (*(ifn+1) == '.' && *(ifn+1) && *(ifn+2) == '/')) break; - ifn++; - } - - if (!*ifn) return XPList.Validate(fn, ifn-fn); - - ofn = ifn; - while(*ifn) {*ofn = *ifn++; - while(*ofn == '/') - {while(*ifn == '/') ifn++; - if (ifn[0] == '.' && ifn[1] == '/') ifn += 2; - else break; - } - ofn++; - } - *ofn = '\0'; - - return XPList.Validate(fn, ofn-fn); -} - -/******************************************************************************/ -/* S t a t G e n */ -/******************************************************************************/ - -#define XRDXROOTD_STAT_CLASSNAME XrdXrootdProtocol -#include "XrdXrootd/XrdXrootdStat.icc" - -/******************************************************************************/ -/* v p E m s g */ -/******************************************************************************/ - -int XrdXrootdProtocol::vpEmsg(const char *op, char *fn) -{ - char buff[2048]; - snprintf(buff,sizeof(buff)-1,"%s path '%s' is disallowed.",op,fn); - buff[sizeof(buff)-1] = '\0'; - return Response.Send(kXR_NotAuthorized, buff); -} diff --git a/src/XrdXrootd/XrdXrootdXeqAio.cc b/src/XrdXrootd/XrdXrootdXeqAio.cc deleted file mode 100644 index 5f835ea3ea8..00000000000 --- a/src/XrdXrootd/XrdXrootdXeqAio.cc +++ /dev/null @@ -1,248 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d X r o o t d X e q A i o . c c */ -/* */ -/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* All Rights Reserved */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include - -#include "Xrd/XrdBuffer.hh" -#include "Xrd/XrdLink.hh" -#include "XrdSys/XrdSysError.hh" -#include "XrdOuc/XrdOucErrInfo.hh" -#include "XrdSfs/XrdSfsInterface.hh" -#include "XrdXrootd/XrdXrootdAio.hh" -#include "XrdXrootd/XrdXrootdFile.hh" -#include "XrdXrootd/XrdXrootdProtocol.hh" -#include "XrdXrootd/XrdXrootdTrace.hh" - -/******************************************************************************/ -/* G l o b a l s */ -/******************************************************************************/ - -extern XrdOucTrace *XrdXrootdTrace; - -/******************************************************************************/ -/* a i o _ E r r o r */ -/******************************************************************************/ - -int XrdXrootdProtocol::aio_Error(const char *op, int ecode) -{ - char *etext, buffer[MAXPATHLEN+80], unkbuff[64]; - -// Get the reason for the error -// - if (!(etext = eDest.ec2text(ecode))) - {sprintf(unkbuff, "reason unknown (%d)", ecode); etext = unkbuff;} - -// Format the error message -// - snprintf(buffer,sizeof(buffer),"Unable to %s %s; %s", - op, myFile->XrdSfsp->FName(), etext); - -// Print it out if debugging is enabled -// -#ifndef NODEBUG - eDest.Emsg("aio_Error", Link->ID, buffer); -#endif - -// Place the error message in the error object and return -// - myFile->XrdSfsp->error.setErrInfo(ecode, buffer); - -// Prepare for recovery -// - myAioReq = 0; - return -EIO; -} - -/******************************************************************************/ -/* a i o _ R e a d */ -/******************************************************************************/ - -// Implied Arguments: - -// myFile = file to be read -// myOffset = Offset at which to read -// myIOLen = Number of bytes to read from file and write to socket - -// Returns: -// >0 -> n/a -// =0 -> OK to continue with next operation. -// -EAGAIN -> Revert to synchronous I/O -// <0 -> Error, close link. - -int XrdXrootdProtocol::aio_Read() -{ - XrdXrootdAioReq *arp; - -// Allocate a request object to handle this request and fire off the first -// i/o (they are self-sustaining after that). Any errors at this point will -// force us to revert to synchronous i/o. -// - if (!(arp=XrdXrootdAioReq::Alloc(this,'r',2)) || arp->Read()) return -EAGAIN; - -// All done -// - return 0; -} - -/******************************************************************************/ -/* a i o _ W r i t e */ -/******************************************************************************/ - -// Implied Arguments: - -// myFile = file to be read -// myOffset = Offset at which to read -// myIOLen = Number of bytes to read from file and write to socket -// myStalls = Number of stalls encountered last time we did I/O - -// Returns: -// >0 -> Slow link, enable link and wait for more data. -// =0 -> OK to continue with next operation. -// -EAGAIN -> Revert to synchronous I/O -// -EINPROGRESS -> Ran out of aio objects, leave link disabled -// -EIO -> File system error, flush link. -// <0 -> Error, close link. - -int XrdXrootdProtocol::aio_Write() -{ - -// Allocate a request object to handle this request -// - if (!(myAioReq = XrdXrootdAioReq::Alloc(this, 'w'))) return -EAGAIN; - -// Since the socket is synchronous in delivering data to write; only one -// write async request can occur at one time, though several may be in-flight -// after we drain the socket of data. While draining, we remember the AioReq -// object in case we must suspend operations and start the flow. -// - return aio_WriteAll(); -} - -/******************************************************************************/ -/* a i o _ W r i t e A l l */ -/******************************************************************************/ - -// myFile = file to be read -// myOffset = Offset at which to read -// myIOLen = Number of bytes to read from file and write to socket -// myAioReq = -> Aio Request - -// The steps taken are: -// 1) Obtain an aio object. If none available, a redrive will be scheduled for -// the protocol and we return -EINPROGRESS which will keep the link disabled. - -// 2) Read the data from the link into the buffer using getData(). - -// 3) If the link is slow, return a 1 which will re-enable the link and -// redrive the protocol when data is available. We will resume in -// aio_WriteCont() when the buffer has the required amount of data. - -// 4) If the read from the link indicated an error then abort the operation -// by recycling the AioReq object which will synchronize in-flight i/o. - -// 5) Schedule the aio write. Errors will scuttle the operation and proceed to -// flush the socket. The write() call will appropriately recycle the AioReq -// object. We note that no error should be returned if aio resources are -// exhausted, the underlying implementation must revert to synchronous -// handling. That's a lot of overhead but we'll back off. - -int XrdXrootdProtocol::aio_WriteAll() -{ - XrdXrootdAio *aiop; - size_t Quantum; - int rc = 0; - - if (myStalls) myStalls--; - - while (myIOLen > 0) -/*1*/ {if (!(aiop = myAioReq->getAio())) - {Resume = &XrdXrootdProtocol::aio_WriteAll; - myBlen = 0; - return -EINPROGRESS; - } - -/*2*/ Quantum = (aiop->buffp->bsize > myIOLen ? myIOLen - : aiop->buffp->bsize); - if ((rc = getData("aiodata", aiop->buffp->buff, Quantum))) -/*3*/ {if (rc > 0) - {Resume = &XrdXrootdProtocol::aio_WriteCont; - myBlast = Quantum; - myAioReq->Push(aiop); - myStalls++; - return 1; - } -/*4*/ myAioReq->Recycle(-1, aiop); - break; - } -/*5*/ aiop->sfsAio.aio_nbytes = Quantum; - aiop->sfsAio.aio_offset = myOffset; - myIOLen -= Quantum; myOffset += Quantum; - if ((rc = myAioReq->Write(aiop))) return aio_Error("write", rc); - } - -// We have completed -// - if (myStalls <= as_maxstalls) myStalls = 0; - myAioReq = 0; - Resume = 0; - return rc; -} - -/******************************************************************************/ -/* a i o _ W r i t e C o n t */ -/******************************************************************************/ - -// myFile = file to be written -// myOffset = Offset at which to write -// myIOLen = Number of bytes to read from socket and write to file -// myBlast = Number of bytes already read from the socket -// myAio = Pointer to the XrdXrootdAioReq object. - -int XrdXrootdProtocol::aio_WriteCont() -{ - XrdXrootdAio *aiop = myAioReq->Pop(); - int rc; - -// Write data that was finaly finished comming in. Note that we could simply -// pick up the current aio object without locks since this is synchronized -// via protocol object scheduling (only one can occur at a time). -// - if ((rc = myAioReq->Write(aiop))) - {myIOLen = myIOLen-myBlast; - return aio_Error("write", rc); - } - myOffset += myBlast; myIOLen -= myBlast; - -// Either continue the request or return to enable the link -// - if (myIOLen > 0) return aio_WriteAll(); - myAioReq = 0; - return 0; -} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d5dbee4143e..7216d36fd8e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,8 +1 @@ - -add_subdirectory( common ) -add_subdirectory( XrdClTests ) -add_subdirectory( XrdSsiTests ) - -if( BUILD_CEPH ) - add_subdirectory( XrdCephTests ) -endif() +add_subdirectory( XrdCephTests ) diff --git a/tests/XrdCephTests/CMakeLists.txt b/tests/XrdCephTests/CMakeLists.txt index 19ce7b3dca4..35d3c465b0a 100644 --- a/tests/XrdCephTests/CMakeLists.txt +++ b/tests/XrdCephTests/CMakeLists.txt @@ -1,5 +1,9 @@ -include( XRootDCommon ) include_directories( ${CPPUNIT_INCLUDE_DIRS} ) +include_directories( ${XROOTD_INCLUDE_DIR} ) +include_directories( ${RADOS_INCLUDE_DIR} ) +include_directories( ${CMAKE_SOURCE_DIR}/src ) + +message( "XROOTD_INCLUDE_DIR : ${XROOTD_INCLUDE_DIR}" ) add_library( XrdCephTests MODULE diff --git a/tests/XrdClTests/CMakeLists.txt b/tests/XrdClTests/CMakeLists.txt deleted file mode 100644 index 8cee8d9de6d..00000000000 --- a/tests/XrdClTests/CMakeLists.txt +++ /dev/null @@ -1,44 +0,0 @@ - -include( XRootDCommon ) -include_directories( ${CPPUNIT_INCLUDE_DIRS} ../common) - -set( LIB_XRD_CL_TEST_MONITOR XrdClTestMonitor-${PLUGIN_VERSION} ) - -add_library( - XrdClTests MODULE - UtilsTest.cc - SocketTest.cc - PollerTest.cc - PostMasterTest.cc - FileSystemTest.cc - FileTest.cc - FileCopyTest.cc - ThreadingTest.cc - IdentityPlugIn.cc - LocalFileHandlerTest.cc -) - -target_link_libraries( - XrdClTests - XrdClTestsHelper - pthread - ${CPPUNIT_LIBRARIES} - ${ZLIB_LIBRARY} - XrdCl ) - -add_library( - ${LIB_XRD_CL_TEST_MONITOR} MODULE - MonitorTestLib.cc -) - -target_link_libraries( - ${LIB_XRD_CL_TEST_MONITOR} - XrdClTestsHelper - XrdCl ) - -#------------------------------------------------------------------------------- -# Install -#------------------------------------------------------------------------------- -install( - TARGETS XrdClTests ${LIB_XRD_CL_TEST_MONITOR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ) diff --git a/tests/XrdClTests/FileCopyTest.cc b/tests/XrdClTests/FileCopyTest.cc deleted file mode 100644 index 68e93529642..00000000000 --- a/tests/XrdClTests/FileCopyTest.cc +++ /dev/null @@ -1,498 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#include -#include "TestEnv.hh" -#include "CppUnitXrdHelpers.hh" -#include "XrdCl/XrdClFile.hh" -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdCl/XrdClMessage.hh" -#include "XrdCl/XrdClSIDManager.hh" -#include "XrdCl/XrdClPostMaster.hh" -#include "XrdCl/XrdClXRootDTransport.hh" -#include "XrdCl/XrdClMessageUtils.hh" -#include "XrdCl/XrdClXRootDMsgHandler.hh" -#include "XrdCl/XrdClUtils.hh" -#include "XrdCl/XrdClCheckSumManager.hh" -#include "XrdCl/XrdClCopyProcess.hh" - -#include "XrdCks/XrdCks.hh" -#include "XrdCks/XrdCksCalc.hh" -#include "XrdCks/XrdCksData.hh" - -#include -#include -#include - -using namespace XrdClTests; - -//------------------------------------------------------------------------------ -// Declaration -//------------------------------------------------------------------------------ -class FileCopyTest: public CppUnit::TestCase -{ - public: - CPPUNIT_TEST_SUITE( FileCopyTest ); - CPPUNIT_TEST( DownloadTest ); - CPPUNIT_TEST( UploadTest ); - CPPUNIT_TEST( MultiStreamDownloadTest ); - CPPUNIT_TEST( MultiStreamUploadTest ); - CPPUNIT_TEST( ThirdPartyCopyTest ); - CPPUNIT_TEST( NormalCopyTest ); - CPPUNIT_TEST_SUITE_END(); - void DownloadTestFunc(); - void UploadTestFunc(); - void DownloadTest(); - void UploadTest(); - void MultiStreamDownloadTest(); - void MultiStreamUploadTest(); - void CopyTestFunc( bool thirdParty = true ); - void ThirdPartyCopyTest(); - void NormalCopyTest(); -}; - -CPPUNIT_TEST_SUITE_REGISTRATION( FileCopyTest ); - -//------------------------------------------------------------------------------ -// Download test -//------------------------------------------------------------------------------ -void FileCopyTest::DownloadTestFunc() -{ - using namespace XrdCl; - - //---------------------------------------------------------------------------- - // Initialize - //---------------------------------------------------------------------------- - Env *testEnv = TestEnv::GetEnv(); - - std::string address; - std::string remoteFile; - - CPPUNIT_ASSERT( testEnv->GetString( "MainServerURL", address ) ); - CPPUNIT_ASSERT( testEnv->GetString( "RemoteFile", remoteFile ) ); - - URL url( address ); - CPPUNIT_ASSERT( url.IsValid() ); - - std::string fileUrl = address + "/" + remoteFile; - - const uint32_t MB = 1024*1024; - char *buffer = new char[4*MB]; - StatInfo *stat = 0; - File f; - - //---------------------------------------------------------------------------- - // Open and stat the file - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT_XRDST( f.Open( fileUrl, OpenFlags::Read ) ); - - CPPUNIT_ASSERT_XRDST( f.Stat( false, stat ) ); - CPPUNIT_ASSERT( stat ); - CPPUNIT_ASSERT( stat->TestFlags( StatInfo::IsReadable ) ); - - //---------------------------------------------------------------------------- - // Fetch the data - //---------------------------------------------------------------------------- - uint64_t totalRead = 0; - uint32_t bytesRead = 0; - - CheckSumManager *man = DefaultEnv::GetCheckSumManager(); - XrdCksCalc *crc32Sum = man->GetCalculator("zcrc32"); - CPPUNIT_ASSERT( crc32Sum ); - - while( 1 ) - { - CPPUNIT_ASSERT_XRDST( f.Read( totalRead, 4*MB, buffer, bytesRead ) ); - if( bytesRead == 0 ) - break; - totalRead += bytesRead; - crc32Sum->Update( buffer, bytesRead ); - } - - //---------------------------------------------------------------------------- - // Compare the checksums - //---------------------------------------------------------------------------- - char crcBuff[9]; - XrdCksData crc; crc.Set( (const void *)crc32Sum->Final(), 4 ); crc.Get( crcBuff, 9 ); - std::string transferSum = "zcrc32:"; transferSum += crcBuff; - - std::string remoteSum; - std::string dataServer; - CPPUNIT_ASSERT( f.GetProperty( "DataServer", dataServer ) ); - CPPUNIT_ASSERT_XRDST( Utils::GetRemoteCheckSum( remoteSum, "zcrc32", - dataServer, - remoteFile ) ); - CPPUNIT_ASSERT( remoteSum == transferSum ); - - delete stat; - delete crc32Sum; - delete[] buffer; -} - -//------------------------------------------------------------------------------ -// Upload test -//------------------------------------------------------------------------------ -void FileCopyTest::UploadTestFunc() -{ - using namespace XrdCl; - - //---------------------------------------------------------------------------- - // Initialize - //---------------------------------------------------------------------------- - Env *testEnv = TestEnv::GetEnv(); - - std::string address; - std::string dataPath; - std::string localFile; - - CPPUNIT_ASSERT( testEnv->GetString( "MainServerURL", address ) ); - CPPUNIT_ASSERT( testEnv->GetString( "DataPath", dataPath ) ); - CPPUNIT_ASSERT( testEnv->GetString( "LocalFile", localFile ) ); - - URL url( address ); - CPPUNIT_ASSERT( url.IsValid() ); - - std::string fileUrl = address + "/" + dataPath + "/testUpload.dat"; - std::string remoteFile = dataPath + "/testUpload.dat"; - - const uint32_t MB = 1024*1024; - char *buffer = new char[4*MB]; - File f; - - //---------------------------------------------------------------------------- - // Open - //---------------------------------------------------------------------------- - int fd = -1; - CPPUNIT_ASSERT_ERRNO( (fd=open( localFile.c_str(), O_RDONLY )) > 0 ) - CPPUNIT_ASSERT_XRDST( f.Open( fileUrl, - OpenFlags::Delete|OpenFlags::Append ) ); - - //---------------------------------------------------------------------------- - // Read the data - //---------------------------------------------------------------------------- - uint64_t offset = 0; - ssize_t bytesRead; - - CheckSumManager *man = DefaultEnv::GetCheckSumManager(); - XrdCksCalc *crc32Sum = man->GetCalculator("zcrc32"); - CPPUNIT_ASSERT( crc32Sum ); - - while( (bytesRead = read( fd, buffer, 4*MB )) > 0 ) - { - crc32Sum->Update( buffer, bytesRead ); - CPPUNIT_ASSERT_XRDST( f.Write( offset, bytesRead, buffer ) ); - offset += bytesRead; - } - - CPPUNIT_ASSERT( bytesRead >= 0 ); - close( fd ); - CPPUNIT_ASSERT_XRDST( f.Close() ); - delete [] buffer; - - //---------------------------------------------------------------------------- - // Find out which server has the file - //---------------------------------------------------------------------------- - FileSystem fs( url ); - LocationInfo *locations = 0; - CPPUNIT_ASSERT_XRDST( fs.DeepLocate( remoteFile, OpenFlags::Refresh, locations ) ); - CPPUNIT_ASSERT( locations ); - CPPUNIT_ASSERT( locations->GetSize() != 0 ); - FileSystem fs1( locations->Begin()->GetAddress() ); - delete locations; - - //---------------------------------------------------------------------------- - // Verify the size - //---------------------------------------------------------------------------- - StatInfo *stat = 0; - CPPUNIT_ASSERT_XRDST( fs1.Stat( remoteFile, stat ) ); - CPPUNIT_ASSERT( stat ); - CPPUNIT_ASSERT( stat->GetSize() == offset ); - - //---------------------------------------------------------------------------- - // Compare the checksums - //---------------------------------------------------------------------------- - char crcBuff[9]; - XrdCksData crc; crc.Set( (const void *)crc32Sum->Final(), 4 ); crc.Get( crcBuff, 9 ); - std::string transferSum = "zcrc32:"; transferSum += crcBuff; - - std::string remoteSum, dataServer; - f.GetProperty( "DataServer", dataServer ); - CPPUNIT_ASSERT_XRDST( Utils::GetRemoteCheckSum( remoteSum, "zcrc32", - dataServer, remoteFile ) ); - CPPUNIT_ASSERT( remoteSum == transferSum ); - - //---------------------------------------------------------------------------- - // Delete the file - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT_XRDST( fs.Rm( dataPath + "/testUpload.dat" ) ); - - delete stat; - delete crc32Sum; -} - -//------------------------------------------------------------------------------ -// Upload test -//------------------------------------------------------------------------------ -void FileCopyTest::UploadTest() -{ - UploadTestFunc(); -} - -void FileCopyTest::MultiStreamUploadTest() -{ - XrdCl::Env *env = XrdCl::DefaultEnv::GetEnv(); - env->PutInt( "SubStreamsPerChannel", 4 ); - UploadTestFunc(); -} - -//------------------------------------------------------------------------------ -// Download test -//------------------------------------------------------------------------------ -void FileCopyTest::DownloadTest() -{ - DownloadTestFunc(); -} - -void FileCopyTest::MultiStreamDownloadTest() -{ - XrdCl::Env *env = XrdCl::DefaultEnv::GetEnv(); - env->PutInt( "SubStreamsPerChannel", 4 ); - DownloadTestFunc(); -} - -namespace -{ - //---------------------------------------------------------------------------- - // Abort handler - //---------------------------------------------------------------------------- - class CancelProgressHandler: public XrdCl::CopyProgressHandler - { - public: - //------------------------------------------------------------------------ - // Constructor/destructor - //------------------------------------------------------------------------ - CancelProgressHandler(): pCancel( false ) {} - virtual ~CancelProgressHandler() {}; - - //------------------------------------------------------------------------ - // Job progress - //------------------------------------------------------------------------ - virtual void JobProgress( uint16_t jobNum, - uint64_t bytesProcessed, - uint64_t bytesTotal ) - { - if( bytesProcessed > 128*1024*1024 ) - pCancel = true; - } - - //------------------------------------------------------------------------ - // Determine whether the job should be canceled - //------------------------------------------------------------------------ - virtual bool ShouldCancel( uint16_t jobNum ) { return pCancel; } - - private: - bool pCancel; -}; - -} - -//------------------------------------------------------------------------------ -// Third party copy test -//------------------------------------------------------------------------------ -void FileCopyTest::CopyTestFunc( bool thirdParty ) -{ - using namespace XrdCl; - - //---------------------------------------------------------------------------- - // Initialize - //---------------------------------------------------------------------------- - Env *testEnv = TestEnv::GetEnv(); - - std::string metamanager; - std::string manager1; - std::string manager2; - std::string sourceFile; - std::string dataPath; - - CPPUNIT_ASSERT( testEnv->GetString( "MainServerURL", metamanager ) ); - CPPUNIT_ASSERT( testEnv->GetString( "Manager1URL", manager1 ) ); - CPPUNIT_ASSERT( testEnv->GetString( "Manager2URL", manager2 ) ); - CPPUNIT_ASSERT( testEnv->GetString( "RemoteFile", sourceFile ) ); - CPPUNIT_ASSERT( testEnv->GetString( "DataPath", dataPath ) ); - - std::string sourceURL = manager1 + "/" + sourceFile; - std::string targetPath = dataPath + "/tpcFile"; - std::string targetURL = manager2 + "/" + targetPath; - std::string metalinkURL = metamanager + "/" + dataPath + "/metalink/mlTpcTest.meta4"; - std::string zipURL = metamanager + "/" + dataPath + "/data.zip"; - std::string fileInZip = "paper.txt"; - std::string xcpSourceURL = metamanager + "/" + dataPath + "/1db882c8-8cd6-4df1-941f-ce669bad3458.dat"; - std::string localFile = "/data/localfile.dat"; - - CopyProcess process1, process2, process3, process4, process5, process6, process7, process8, process9; - PropertyList properties, results; - FileSystem fs( manager2 ); - - //---------------------------------------------------------------------------- - // Copy from a ZIP archive - //---------------------------------------------------------------------------- - if( !thirdParty ) - { - results.Clear(); - properties.Set( "source", zipURL ); - properties.Set( "target", targetURL ); - properties.Set( "zipArchive", true ); - properties.Set( "zipSource", fileInZip ); - CPPUNIT_ASSERT_XRDST( process6.AddJob( properties, &results ) ); - CPPUNIT_ASSERT_XRDST( process6.Prepare() ); - CPPUNIT_ASSERT_XRDST( process6.Run(0) ); - CPPUNIT_ASSERT_XRDST( fs.Rm( targetPath ) ); - properties.Clear(); - } - - //---------------------------------------------------------------------------- - // Copy from a Metalink - //---------------------------------------------------------------------------- - results.Clear(); - properties.Set( "source", metalinkURL ); - properties.Set( "target", targetURL ); - properties.Set( "checkSumMode", "end2end" ); - properties.Set( "checkSumType", "zcrc32" ); - CPPUNIT_ASSERT_XRDST( process5.AddJob( properties, &results ) ); - CPPUNIT_ASSERT_XRDST( process5.Prepare() ); - CPPUNIT_ASSERT_XRDST( process5.Run(0) ); - CPPUNIT_ASSERT_XRDST( fs.Rm( targetPath ) ); - properties.Clear(); - - // XCp test - results.Clear(); - properties.Set( "source", xcpSourceURL ); - properties.Set( "target", targetURL ); - properties.Set( "checkSumMode", "end2end" ); - properties.Set( "checkSumType", "zcrc32" ); - properties.Set( "xcp", true ); - properties.Set( "nbXcpSources", 3 ); - CPPUNIT_ASSERT_XRDST( process7.AddJob( properties, &results ) ); - CPPUNIT_ASSERT_XRDST( process7.Prepare() ); - CPPUNIT_ASSERT_XRDST( process7.Run(0) ); - CPPUNIT_ASSERT_XRDST( fs.Rm( targetPath ) ); - properties.Clear(); - - //---------------------------------------------------------------------------- - // Initialize and run the copy - //---------------------------------------------------------------------------- - properties.Set( "source", sourceURL ); - properties.Set( "target", targetURL ); - properties.Set( "checkSumMode", "end2end" ); - properties.Set( "checkSumType", "zcrc32" ); - - if( thirdParty ) - properties.Set( "thirdParty", "only" ); - - CPPUNIT_ASSERT_XRDST( process1.AddJob( properties, &results ) ); - CPPUNIT_ASSERT_XRDST( process1.Prepare() ); - CPPUNIT_ASSERT_XRDST( process1.Run(0) ); - - //---------------------------------------------------------------------------- - // Cleanup - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT_XRDST( fs.Rm( targetPath ) ); - - // the further tests are only valid for third party copy for now - if( !thirdParty ) - return; - - //---------------------------------------------------------------------------- - // Abort the copy after 100MB - //---------------------------------------------------------------------------- - CancelProgressHandler progress; - CPPUNIT_ASSERT_XRDST( process2.AddJob( properties, &results ) ); - CPPUNIT_ASSERT_XRDST( process2.Prepare() ); - CPPUNIT_ASSERT_XRDST_NOTOK( process2.Run(&progress), errErrorResponse ); - CPPUNIT_ASSERT_XRDST( fs.Rm( targetPath ) ); - - //---------------------------------------------------------------------------- - // Copy from a non-existent source - //---------------------------------------------------------------------------- - results.Clear(); - properties.Set( "source", "root://localhost:9999//test" ); - properties.Set( "initTimeout", 10 ); - CPPUNIT_ASSERT_XRDST( process3.AddJob( properties, &results ) ); - CPPUNIT_ASSERT_XRDST( process3.Prepare() ); - CPPUNIT_ASSERT_XRDST_NOTOK( process3.Run(&progress), errOperationExpired ); - - //---------------------------------------------------------------------------- - // Copy to a non-existent target - //---------------------------------------------------------------------------- - results.Clear(); - properties.Set( "source", sourceURL ); - properties.Set( "target", "root://localhost:9999//test" ); - properties.Set( "initTimeout", 10 ); - CPPUNIT_ASSERT_XRDST( process4.AddJob( properties, &results ) ); - CPPUNIT_ASSERT_XRDST( process4.Prepare() ); - CPPUNIT_ASSERT_XRDST_NOTOK( process4.Run(0), errOperationExpired ); - properties.Clear(); - - //---------------------------------------------------------------------------- - // Copy to local fs - //---------------------------------------------------------------------------- - results.Clear(); - properties.Set( "source", sourceURL ); - properties.Set( "target", "file://localhost" + localFile ); - properties.Set( "checkSumMode", "end2end" ); - properties.Set( "checkSumType", "zcrc32" ); - CPPUNIT_ASSERT_XRDST( process8.AddJob( properties, &results ) ); - CPPUNIT_ASSERT_XRDST( process8.Prepare() ); - CPPUNIT_ASSERT_XRDST( process8.Run(0) ); - properties.Clear(); - - //---------------------------------------------------------------------------- - // Copy from local fs - //---------------------------------------------------------------------------- - results.Clear(); - properties.Set( "source", "file://localhost" + localFile ); - properties.Set( "target", targetURL ); - properties.Set( "checkSumMode", "end2end" ); - properties.Set( "checkSumType", "zcrc32" ); - CPPUNIT_ASSERT_XRDST( process9.AddJob( properties, &results ) ); - CPPUNIT_ASSERT_XRDST( process9.Prepare() ); - CPPUNIT_ASSERT_XRDST( process9.Run(0) ); - properties.Clear(); - - //---------------------------------------------------------------------------- - // Cleanup - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT_XRDST( fs.Rm( targetPath ) ); - CPPUNIT_ASSERT( remove( localFile.c_str() ) == 0 ); -} - -//------------------------------------------------------------------------------ -// Third party copy test -//------------------------------------------------------------------------------ -void FileCopyTest::ThirdPartyCopyTest() -{ - CopyTestFunc( true ); -} - -//------------------------------------------------------------------------------ -// Cormal copy test -//------------------------------------------------------------------------------ -void FileCopyTest::NormalCopyTest() -{ - CopyTestFunc( false ); -} diff --git a/tests/XrdClTests/FileSystemTest.cc b/tests/XrdClTests/FileSystemTest.cc deleted file mode 100644 index 17fb229efb8..00000000000 --- a/tests/XrdClTests/FileSystemTest.cc +++ /dev/null @@ -1,536 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#include -#include -#include -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdCl/XrdClPlugInManager.hh" -#include "CppUnitXrdHelpers.hh" - -#include - -#include "TestEnv.hh" -#include "IdentityPlugIn.hh" - -using namespace XrdClTests; - -//------------------------------------------------------------------------------ -// Declaration -//------------------------------------------------------------------------------ -class FileSystemTest: public CppUnit::TestCase -{ - public: - CPPUNIT_TEST_SUITE( FileSystemTest ); - CPPUNIT_TEST( LocateTest ); - CPPUNIT_TEST( MvTest ); - CPPUNIT_TEST( ServerQueryTest ); - CPPUNIT_TEST( TruncateRmTest ); - CPPUNIT_TEST( MkdirRmdirTest ); - CPPUNIT_TEST( ChmodTest ); - CPPUNIT_TEST( PingTest ); - CPPUNIT_TEST( StatTest ); - CPPUNIT_TEST( StatVFSTest ); - CPPUNIT_TEST( ProtocolTest ); - CPPUNIT_TEST( DeepLocateTest ); - CPPUNIT_TEST( DirListTest ); - CPPUNIT_TEST( SendInfoTest ); - CPPUNIT_TEST( PrepareTest ); - CPPUNIT_TEST( PlugInTest ); - CPPUNIT_TEST_SUITE_END(); - void LocateTest(); - void MvTest(); - void ServerQueryTest(); - void TruncateRmTest(); - void MkdirRmdirTest(); - void ChmodTest(); - void PingTest(); - void StatTest(); - void StatVFSTest(); - void ProtocolTest(); - void DeepLocateTest(); - void DirListTest(); - void SendInfoTest(); - void PrepareTest(); - void PlugInTest(); -}; - -CPPUNIT_TEST_SUITE_REGISTRATION( FileSystemTest ); - -//------------------------------------------------------------------------------ -// Locate test -//------------------------------------------------------------------------------ -void FileSystemTest::LocateTest() -{ - using namespace XrdCl; - - //---------------------------------------------------------------------------- - // Get the environment variables - //---------------------------------------------------------------------------- - Env *testEnv = TestEnv::GetEnv(); - - std::string address; - std::string remoteFile; - - CPPUNIT_ASSERT( testEnv->GetString( "MainServerURL", address ) ); - CPPUNIT_ASSERT( testEnv->GetString( "RemoteFile", remoteFile ) ); - - URL url( address ); - CPPUNIT_ASSERT( url.IsValid() ); - - //---------------------------------------------------------------------------- - // Query the server for all of the file locations - //---------------------------------------------------------------------------- - FileSystem fs( url ); - - LocationInfo *locations = 0; - CPPUNIT_ASSERT_XRDST( fs.Locate( remoteFile, OpenFlags::Refresh, locations ) ); - CPPUNIT_ASSERT( locations ); - CPPUNIT_ASSERT( locations->GetSize() != 0 ); - delete locations; -} - -//------------------------------------------------------------------------------ -// Mv test -//------------------------------------------------------------------------------ -void FileSystemTest::MvTest() -{ - using namespace XrdCl; - - //---------------------------------------------------------------------------- - // Get the environment variables - //---------------------------------------------------------------------------- - Env *testEnv = TestEnv::GetEnv(); - - std::string address; - std::string dataPath; - std::string remoteFile; - - CPPUNIT_ASSERT( testEnv->GetString( "DiskServerURL", address ) ); - CPPUNIT_ASSERT( testEnv->GetString( "RemoteFile", remoteFile ) ); - - URL url( address ); - CPPUNIT_ASSERT( url.IsValid() ); - - std::string filePath1 = remoteFile; - std::string filePath2 = remoteFile + "2"; - - - LocationInfo *info = 0; - FileSystem fs( url ); - - // move the file - CPPUNIT_ASSERT_XRDST( fs.Mv( filePath1, filePath2 ) ); - // make sure it's not there - CPPUNIT_ASSERT_XRDST_NOTOK( fs.Locate( filePath1, OpenFlags::Refresh, info ), - errErrorResponse ); - // make sure the destination is there - CPPUNIT_ASSERT_XRDST( fs.Locate( filePath2, OpenFlags::Refresh, info ) ); - delete info; - // move it back - CPPUNIT_ASSERT_XRDST( fs.Mv( filePath2, filePath1 ) ); - // make sure it's there - CPPUNIT_ASSERT_XRDST( fs.Locate( filePath1, OpenFlags::Refresh, info ) ); - delete info; - // make sure the other one is gone - CPPUNIT_ASSERT_XRDST_NOTOK( fs.Locate( filePath2, OpenFlags::Refresh, info ), - errErrorResponse ); -} - -//------------------------------------------------------------------------------ -// Query test -//------------------------------------------------------------------------------ -void FileSystemTest::ServerQueryTest() -{ - using namespace XrdCl; - - //---------------------------------------------------------------------------- - // Get the environment variables - //---------------------------------------------------------------------------- - Env *testEnv = TestEnv::GetEnv(); - - std::string address; - std::string remoteFile; - - CPPUNIT_ASSERT( testEnv->GetString( "DiskServerURL", address ) ); - CPPUNIT_ASSERT( testEnv->GetString( "RemoteFile", remoteFile ) ); - - URL url( address ); - CPPUNIT_ASSERT( url.IsValid() ); - - FileSystem fs( url ); - Buffer *response = 0; - Buffer arg; - arg.FromString( remoteFile ); - CPPUNIT_ASSERT_XRDST( fs.Query( QueryCode::Checksum, arg, response ) ); - CPPUNIT_ASSERT( response ); - CPPUNIT_ASSERT( response->GetSize() != 0 ); - delete response; -} - -//------------------------------------------------------------------------------ -// Truncate/Rm test -//------------------------------------------------------------------------------ -void FileSystemTest::TruncateRmTest() -{ - using namespace XrdCl; - - //---------------------------------------------------------------------------- - // Get the environment variables - //---------------------------------------------------------------------------- - Env *testEnv = TestEnv::GetEnv(); - - std::string address; - std::string dataPath; - - CPPUNIT_ASSERT( testEnv->GetString( "MainServerURL", address ) ); - CPPUNIT_ASSERT( testEnv->GetString( "DataPath", dataPath ) ); - - URL url( address ); - CPPUNIT_ASSERT( url.IsValid() ); - - std::string filePath = dataPath + "/testfile"; - std::string fileUrl = address + "/"; - fileUrl += filePath; - - FileSystem fs( url ); - File f; - CPPUNIT_ASSERT_XRDST( f.Open( fileUrl, OpenFlags::Update | OpenFlags::Delete, - Access::UR | Access::UW ) ); - CPPUNIT_ASSERT_XRDST( fs.Truncate( filePath, 10000000 ) ); - CPPUNIT_ASSERT_XRDST( fs.Rm( filePath ) ); -} - -//------------------------------------------------------------------------------ -// Mkdir/Rmdir test -//------------------------------------------------------------------------------ -void FileSystemTest::MkdirRmdirTest() -{ - using namespace XrdCl; - - //---------------------------------------------------------------------------- - // Get the environment variables - //---------------------------------------------------------------------------- - Env *testEnv = TestEnv::GetEnv(); - - std::string address; - std::string dataPath; - - CPPUNIT_ASSERT( testEnv->GetString( "DiskServerURL", address ) ); - CPPUNIT_ASSERT( testEnv->GetString( "DataPath", dataPath ) ); - - URL url( address ); - CPPUNIT_ASSERT( url.IsValid() ); - - std::string dirPath1 = dataPath + "/testdir"; - std::string dirPath2 = dataPath + "/testdir/asdads"; - - FileSystem fs( url ); - - CPPUNIT_ASSERT_XRDST( fs.MkDir( dirPath2, MkDirFlags::MakePath, - Access::UR | Access::UW | Access::UX ) ); - CPPUNIT_ASSERT_XRDST( fs.RmDir( dirPath2 ) ); - CPPUNIT_ASSERT_XRDST( fs.RmDir( dirPath1 ) ); -} - -//------------------------------------------------------------------------------ -// Chmod test -//------------------------------------------------------------------------------ -void FileSystemTest::ChmodTest() -{ - using namespace XrdCl; - - //---------------------------------------------------------------------------- - // Get the environment variables - //---------------------------------------------------------------------------- - Env *testEnv = TestEnv::GetEnv(); - - std::string address; - std::string dataPath; - - CPPUNIT_ASSERT( testEnv->GetString( "DiskServerURL", address ) ); - CPPUNIT_ASSERT( testEnv->GetString( "DataPath", dataPath ) ); - - URL url( address ); - CPPUNIT_ASSERT( url.IsValid() ); - - std::string dirPath = dataPath + "/testdir"; - - FileSystem fs( url ); - - CPPUNIT_ASSERT_XRDST( fs.MkDir( dirPath, MkDirFlags::MakePath, - Access::UR | Access::UW | Access::UX ) ); - CPPUNIT_ASSERT_XRDST( fs.ChMod( dirPath, - Access::UR | Access::UW | Access::UX | - Access::GR | Access::GX ) ); - CPPUNIT_ASSERT_XRDST( fs.RmDir( dirPath ) ); -} - -//------------------------------------------------------------------------------ -// Locate test -//------------------------------------------------------------------------------ -void FileSystemTest::PingTest() -{ - using namespace XrdCl; - - //---------------------------------------------------------------------------- - // Get the environment variables - //---------------------------------------------------------------------------- - Env *testEnv = TestEnv::GetEnv(); - - std::string address; - CPPUNIT_ASSERT( testEnv->GetString( "MainServerURL", address ) ); - URL url( address ); - CPPUNIT_ASSERT( url.IsValid() ); - - FileSystem fs( url ); - CPPUNIT_ASSERT_XRDST( fs.Ping() ); -} - -//------------------------------------------------------------------------------ -// Stat test -//------------------------------------------------------------------------------ -void FileSystemTest::StatTest() -{ - using namespace XrdCl; - - Env *testEnv = TestEnv::GetEnv(); - - std::string address; - std::string remoteFile; - - CPPUNIT_ASSERT( testEnv->GetString( "MainServerURL", address ) ); - CPPUNIT_ASSERT( testEnv->GetString( "RemoteFile", remoteFile ) ); - - URL url( address ); - CPPUNIT_ASSERT( url.IsValid() ); - - FileSystem fs( url ); - StatInfo *response = 0; - CPPUNIT_ASSERT_XRDST( fs.Stat( remoteFile, response ) ); - CPPUNIT_ASSERT( response ); - CPPUNIT_ASSERT( response->GetSize() == 1048576000 ); - CPPUNIT_ASSERT( response->TestFlags( StatInfo::IsReadable ) ); - CPPUNIT_ASSERT( response->TestFlags( StatInfo::IsWritable ) ); - CPPUNIT_ASSERT( !response->TestFlags( StatInfo::IsDir ) ); - delete response; -} - -//------------------------------------------------------------------------------ -// Stat VFS test -//------------------------------------------------------------------------------ -void FileSystemTest::StatVFSTest() -{ - using namespace XrdCl; - - Env *testEnv = TestEnv::GetEnv(); - - std::string address; - std::string dataPath; - - CPPUNIT_ASSERT( testEnv->GetString( "MainServerURL", address ) ); - CPPUNIT_ASSERT( testEnv->GetString( "DataPath", dataPath ) ); - - URL url( address ); - CPPUNIT_ASSERT( url.IsValid() ); - - FileSystem fs( url ); - StatInfoVFS *response = 0; - CPPUNIT_ASSERT_XRDST( fs.StatVFS( dataPath, response ) ); - CPPUNIT_ASSERT( response ); - delete response; -} - -//------------------------------------------------------------------------------ -// Protocol test -//------------------------------------------------------------------------------ -void FileSystemTest::ProtocolTest() -{ - using namespace XrdCl; - - Env *testEnv = TestEnv::GetEnv(); - - std::string address; - CPPUNIT_ASSERT( testEnv->GetString( "MainServerURL", address ) ); - URL url( address ); - CPPUNIT_ASSERT( url.IsValid() ); - - FileSystem fs( url ); - ProtocolInfo *response = 0; - CPPUNIT_ASSERT_XRDST( fs.Protocol( response ) ); - CPPUNIT_ASSERT( response ); - delete response; -} - -//------------------------------------------------------------------------------ -// Deep locate test -//------------------------------------------------------------------------------ -void FileSystemTest::DeepLocateTest() -{ - using namespace XrdCl; - - //---------------------------------------------------------------------------- - // Get the environment variables - //---------------------------------------------------------------------------- - Env *testEnv = TestEnv::GetEnv(); - - std::string address; - std::string remoteFile; - - CPPUNIT_ASSERT( testEnv->GetString( "MainServerURL", address ) ); - CPPUNIT_ASSERT( testEnv->GetString( "RemoteFile", remoteFile ) ); - - URL url( address ); - CPPUNIT_ASSERT( url.IsValid() ); - - //---------------------------------------------------------------------------- - // Query the server for all of the file locations - //---------------------------------------------------------------------------- - FileSystem fs( url ); - - LocationInfo *locations = 0; - CPPUNIT_ASSERT_XRDST( fs.DeepLocate( remoteFile, OpenFlags::Refresh, locations ) ); - CPPUNIT_ASSERT( locations ); - CPPUNIT_ASSERT( locations->GetSize() != 0 ); - LocationInfo::Iterator it = locations->Begin(); - for( ; it != locations->End(); ++it ) - CPPUNIT_ASSERT( it->IsServer() ); - delete locations; -} - -//------------------------------------------------------------------------------ -// Dir list -//------------------------------------------------------------------------------ -void FileSystemTest::DirListTest() -{ - using namespace XrdCl; - - //---------------------------------------------------------------------------- - // Get the environment variables - //---------------------------------------------------------------------------- - Env *testEnv = TestEnv::GetEnv(); - - std::string address; - std::string dataPath; - - CPPUNIT_ASSERT( testEnv->GetString( "MainServerURL", address ) ); - CPPUNIT_ASSERT( testEnv->GetString( "DataPath", dataPath ) ); - - URL url( address ); - CPPUNIT_ASSERT( url.IsValid() ); - - std::string lsPath = dataPath + "/bigdir"; - - //---------------------------------------------------------------------------- - // Query the server for all of the file locations - //---------------------------------------------------------------------------- - FileSystem fs( url ); - - DirectoryList *list = 0; - CPPUNIT_ASSERT_XRDST( fs.DirList( lsPath, DirListFlags::Stat | DirListFlags::Locate, list ) ); - CPPUNIT_ASSERT( list ); - CPPUNIT_ASSERT( list->GetSize() == 40000 ); - - delete list; -} - - -//------------------------------------------------------------------------------ -// Set -//------------------------------------------------------------------------------ -void FileSystemTest::SendInfoTest() -{ - using namespace XrdCl; - - //---------------------------------------------------------------------------- - // Get the environment variables - //---------------------------------------------------------------------------- - Env *testEnv = TestEnv::GetEnv(); - - std::string address; - std::string dataPath; - - CPPUNIT_ASSERT( testEnv->GetString( "MainServerURL", address ) ); - URL url( address ); - CPPUNIT_ASSERT( url.IsValid() ); - - FileSystem fs( url ); - - Buffer *id = 0; - CPPUNIT_ASSERT_XRDST( fs.SendInfo( "test stuff", id ) ); - CPPUNIT_ASSERT( id ); - CPPUNIT_ASSERT( id->GetSize() == 4 ); - delete id; -} - - -//------------------------------------------------------------------------------ -// Set -//------------------------------------------------------------------------------ -void FileSystemTest::PrepareTest() -{ - using namespace XrdCl; - - //---------------------------------------------------------------------------- - // Get the environment variables - //---------------------------------------------------------------------------- - Env *testEnv = TestEnv::GetEnv(); - - std::string address; - std::string dataPath; - - CPPUNIT_ASSERT( testEnv->GetString( "MainServerURL", address ) ); - URL url( address ); - CPPUNIT_ASSERT( url.IsValid() ); - - FileSystem fs( url ); - - Buffer *id = 0; - std::vector list; - list.push_back( "/data/1db882c8-8cd6-4df1-941f-ce669bad3458.dat" ); - list.push_back( "/data/1db882c8-8cd6-4df1-941f-ce669bad3458.dat" ); - - CPPUNIT_ASSERT_XRDST( fs.Prepare( list, PrepareFlags::Stage, 1, id ) ); - CPPUNIT_ASSERT( id ); - CPPUNIT_ASSERT( id->GetSize() ); - delete id; -} - -//------------------------------------------------------------------------------ -// Plug-in test -//------------------------------------------------------------------------------ -void FileSystemTest::PlugInTest() -{ - XrdCl::PlugInFactory *f = new IdentityFactory; - XrdCl::DefaultEnv::GetPlugInManager()->RegisterDefaultFactory(f); - LocateTest(); - MvTest(); - ServerQueryTest(); - TruncateRmTest(); - MkdirRmdirTest(); - ChmodTest(); - PingTest(); - StatTest(); - StatVFSTest(); - ProtocolTest(); - DeepLocateTest(); - DirListTest(); - SendInfoTest(); - PrepareTest(); - XrdCl::DefaultEnv::GetPlugInManager()->RegisterDefaultFactory(0); -} diff --git a/tests/XrdClTests/FileTest.cc b/tests/XrdClTests/FileTest.cc deleted file mode 100644 index d882922fc23..00000000000 --- a/tests/XrdClTests/FileTest.cc +++ /dev/null @@ -1,720 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#include -#include "TestEnv.hh" -#include "Utils.hh" -#include "IdentityPlugIn.hh" - -#include "CppUnitXrdHelpers.hh" -#include "XrdCl/XrdClFile.hh" -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdCl/XrdClPlugInManager.hh" -#include "XrdCl/XrdClMessage.hh" -#include "XrdCl/XrdClSIDManager.hh" -#include "XrdCl/XrdClPostMaster.hh" -#include "XrdCl/XrdClXRootDTransport.hh" -#include "XrdCl/XrdClMessageUtils.hh" -#include "XrdCl/XrdClXRootDMsgHandler.hh" -#include "XrdCl/XrdClCopyProcess.hh" -#include "XrdCl/XrdClZipArchiveReader.hh" -#include "XrdCl/XrdClConstants.hh" - -#include - -using namespace XrdClTests; - -//------------------------------------------------------------------------------ -// Declaration -//------------------------------------------------------------------------------ -class FileTest: public CppUnit::TestCase -{ - public: - CPPUNIT_TEST_SUITE( FileTest ); - CPPUNIT_TEST( RedirectReturnTest ); - CPPUNIT_TEST( ReadTest ); - CPPUNIT_TEST( WriteTest ); - CPPUNIT_TEST( WriteVTest ); - CPPUNIT_TEST( VectorReadTest ); - CPPUNIT_TEST( VectorWriteTest ); - CPPUNIT_TEST( VirtualRedirectorTest ); - CPPUNIT_TEST( PlugInTest ); - CPPUNIT_TEST_SUITE_END(); - void RedirectReturnTest(); - void ReadTest(); - void WriteTest(); - void WriteVTest(); - void VectorReadTest(); - void VectorWriteTest(); - void VirtualRedirectorTest(); - void PlugInTest(); -}; - -CPPUNIT_TEST_SUITE_REGISTRATION( FileTest ); - -//------------------------------------------------------------------------------ -// Redirect return test -//------------------------------------------------------------------------------ -void FileTest::RedirectReturnTest() -{ - using namespace XrdCl; - - //---------------------------------------------------------------------------- - // Initialize - //---------------------------------------------------------------------------- - Env *testEnv = TestEnv::GetEnv(); - - std::string address; - std::string dataPath; - - CPPUNIT_ASSERT( testEnv->GetString( "MainServerURL", address ) ); - CPPUNIT_ASSERT( testEnv->GetString( "DataPath", dataPath ) ); - - URL url( address ); - CPPUNIT_ASSERT( url.IsValid() ); - - std::string path = dataPath + "/cb4aacf1-6f28-42f2-b68a-90a73460f424.dat"; - std::string fileUrl = address + "/" + path; - - //---------------------------------------------------------------------------- - // Get the SID manager - //---------------------------------------------------------------------------- - PostMaster *postMaster = DefaultEnv::GetPostMaster(); - AnyObject sidMgrObj; - SIDManager *sidMgr = 0; - Status st; - st = postMaster->QueryTransport( url, XRootDQuery::SIDManager, sidMgrObj ); - - CPPUNIT_ASSERT( st.IsOK() ); - sidMgrObj.Get( sidMgr ); - - //---------------------------------------------------------------------------- - // Build the open request - //---------------------------------------------------------------------------- - Message *msg; - ClientOpenRequest *req; - MessageUtils::CreateRequest( msg, req, path.length() ); - req->requestid = kXR_open; - req->options = kXR_open_read | kXR_retstat; - req->dlen = path.length(); - msg->Append( path.c_str(), path.length(), 24 ); - XRootDTransport::SetDescription( msg ); - - SyncResponseHandler *handler = new SyncResponseHandler(); - MessageSendParams params; params.followRedirects = false; - MessageUtils::ProcessSendParams( params ); - OpenInfo *response = 0; - CPPUNIT_ASSERT_XRDST( MessageUtils::SendMessage( url, msg, handler, params, 0 ) ); - XRootDStatus st1 = MessageUtils::WaitForResponse( handler, response ); - delete handler; - CPPUNIT_ASSERT_XRDST_NOTOK( st1, errRedirect ); - CPPUNIT_ASSERT( !response ); - delete response; -} - -//------------------------------------------------------------------------------ -// Read test -//------------------------------------------------------------------------------ -void FileTest::ReadTest() -{ - using namespace XrdCl; - - //---------------------------------------------------------------------------- - // Initialize - //---------------------------------------------------------------------------- - Env *testEnv = TestEnv::GetEnv(); - - std::string address; - std::string dataPath; - - CPPUNIT_ASSERT( testEnv->GetString( "MainServerURL", address ) ); - CPPUNIT_ASSERT( testEnv->GetString( "DataPath", dataPath ) ); - - URL url( address ); - CPPUNIT_ASSERT( url.IsValid() ); - - std::string filePath = dataPath + "/cb4aacf1-6f28-42f2-b68a-90a73460f424.dat"; - std::string fileUrl = address + "/"; - fileUrl += filePath; - - //---------------------------------------------------------------------------- - // Fetch some data and checksum - //---------------------------------------------------------------------------- - const uint32_t MB = 1024*1024; - char *buffer1 = new char[40*MB]; - char *buffer2 = new char[40*MB]; - uint32_t bytesRead1 = 0; - uint32_t bytesRead2 = 0; - File f; - StatInfo *stat; - - //---------------------------------------------------------------------------- - // Open the file - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT_XRDST( f.Open( fileUrl, OpenFlags::Read ) ); - - //---------------------------------------------------------------------------- - // Stat1 - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT_XRDST( f.Stat( false, stat ) ); - CPPUNIT_ASSERT( stat ); - CPPUNIT_ASSERT( stat->GetSize() == 1048576000 ); - CPPUNIT_ASSERT( stat->TestFlags( StatInfo::IsReadable ) ); - delete stat; - stat = 0; - - //---------------------------------------------------------------------------- - // Stat2 - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT_XRDST( f.Stat( true, stat ) ); - CPPUNIT_ASSERT( stat ); - CPPUNIT_ASSERT( stat->GetSize() == 1048576000 ); - CPPUNIT_ASSERT( stat->TestFlags( StatInfo::IsReadable ) ); - delete stat; - - //---------------------------------------------------------------------------- - // Read test - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT_XRDST( f.Read( 10*MB, 40*MB, buffer1, bytesRead1 ) ); - CPPUNIT_ASSERT_XRDST( f.Read( 1008576000, 40*MB, buffer2, bytesRead2 ) ); - CPPUNIT_ASSERT( bytesRead1 == 40*MB ); - CPPUNIT_ASSERT( bytesRead2 == 40000000 ); - - uint32_t crc = Utils::ComputeCRC32( buffer1, 40*MB ); - CPPUNIT_ASSERT( crc == 3303853367UL ); - - crc = Utils::ComputeCRC32( buffer2, 40000000 ); - CPPUNIT_ASSERT( crc == 898701504UL ); - - delete [] buffer1; - delete [] buffer2; - - CPPUNIT_ASSERT_XRDST( f.Close() ); - - //---------------------------------------------------------------------------- - // Read ZIP archive test - //---------------------------------------------------------------------------- - std::string archiveUrl = address + "/" + dataPath + "/data.zip"; - - ZipArchiveReader zip; - CPPUNIT_ASSERT_XRDST( zip.Open( archiveUrl ) ); - - //---------------------------------------------------------------------------- - // There are 3 files in the data.zip archive: - // - athena.log - // - paper.txt - // - EastAsianWidth.txt - //---------------------------------------------------------------------------- - - struct - { - std::string file; // file name - uint64_t offset; // offset in the file - uint32_t size; // number of characters to be read - char buffer[100]; // the buffer - std::string expected; // expected result - } testset[] = - { - { "athena.log", 65530, 99, {0}, "D__Jet" }, // reads past the end of the file (there are just 6 characters to read not 99) - { "paper.txt", 1024, 65, {0}, "igh rate (the order of 100 kHz), the data are usually distributed" }, - { "EastAsianWidth.txt", 2048, 18, {0}, "8;Na # DIGIT EIGHT" } - }; - - for( int i = 0; i < 3; ++i ) - { - uint32_t bytesRead; - CPPUNIT_ASSERT_XRDST( zip.Read( testset[i].file, testset[i].offset, testset[i].size, testset[i].buffer, bytesRead ) ); - std::string result( testset[i].buffer, bytesRead ); - CPPUNIT_ASSERT( testset[i].expected == result ); - } - - CPPUNIT_ASSERT_XRDST( zip.Close() ); -} - - -//------------------------------------------------------------------------------ -// Read test -//------------------------------------------------------------------------------ -void FileTest::WriteTest() -{ - using namespace XrdCl; - - //---------------------------------------------------------------------------- - // Initialize - //---------------------------------------------------------------------------- - Env *testEnv = TestEnv::GetEnv(); - - std::string address; - std::string dataPath; - - CPPUNIT_ASSERT( testEnv->GetString( "MainServerURL", address ) ); - CPPUNIT_ASSERT( testEnv->GetString( "DataPath", dataPath ) ); - - URL url( address ); - CPPUNIT_ASSERT( url.IsValid() ); - - std::string filePath = dataPath + "/testFile.dat"; - std::string fileUrl = address + "/"; - fileUrl += filePath; - - //---------------------------------------------------------------------------- - // Fetch some data and checksum - //---------------------------------------------------------------------------- - const uint32_t MB = 1024*1024; - char *buffer1 = new char[4*MB]; - char *buffer2 = new char[4*MB]; - char *buffer3 = new char[4*MB]; - char *buffer4 = new char[4*MB]; - uint32_t bytesRead1 = 0; - uint32_t bytesRead2 = 0; - File f1, f2; - - CPPUNIT_ASSERT( Utils::GetRandomBytes( buffer1, 4*MB ) == 4*MB ); - CPPUNIT_ASSERT( Utils::GetRandomBytes( buffer2, 4*MB ) == 4*MB ); - uint32_t crc1 = Utils::ComputeCRC32( buffer1, 4*MB ); - crc1 = Utils::UpdateCRC32( crc1, buffer2, 4*MB ); - - //---------------------------------------------------------------------------- - // Write the data - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT( f1.Open( fileUrl, OpenFlags::Delete | OpenFlags::Update, - Access::UR | Access::UW ).IsOK() ); - CPPUNIT_ASSERT( f1.Write( 0, 4*MB, buffer1 ).IsOK() ); - CPPUNIT_ASSERT( f1.Write( 4*MB, 4*MB, buffer2 ).IsOK() ); - CPPUNIT_ASSERT( f1.Sync().IsOK() ); - CPPUNIT_ASSERT( f1.Close().IsOK() ); - - //---------------------------------------------------------------------------- - // Read the data and verify the checksums - //---------------------------------------------------------------------------- - StatInfo *stat = 0; - CPPUNIT_ASSERT( f2.Open( fileUrl, OpenFlags::Read ).IsOK() ); - CPPUNIT_ASSERT( f2.Stat( false, stat ).IsOK() ); - CPPUNIT_ASSERT( stat ); - CPPUNIT_ASSERT( stat->GetSize() == 8*MB ); - CPPUNIT_ASSERT( f2.Read( 0, 4*MB, buffer3, bytesRead1 ).IsOK() ); - CPPUNIT_ASSERT( f2.Read( 4*MB, 4*MB, buffer4, bytesRead2 ).IsOK() ); - CPPUNIT_ASSERT( bytesRead1 == 4*MB ); - CPPUNIT_ASSERT( bytesRead2 == 4*MB ); - uint32_t crc2 = Utils::ComputeCRC32( buffer3, 4*MB ); - crc2 = Utils::UpdateCRC32( crc2, buffer4, 4*MB ); - CPPUNIT_ASSERT( f2.Close().IsOK() ); - CPPUNIT_ASSERT( crc1 == crc2 ); - - //---------------------------------------------------------------------------- - // Truncate test - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT( f1.Open( fileUrl, OpenFlags::Delete | OpenFlags::Update, - Access::UR | Access::UW ).IsOK() ); - CPPUNIT_ASSERT( f1.Truncate( 20*MB ).IsOK() ); - CPPUNIT_ASSERT( f1.Close().IsOK() ); - FileSystem fs( url ); - StatInfo *response = 0; - CPPUNIT_ASSERT( fs.Stat( filePath, response ).IsOK() ); - CPPUNIT_ASSERT( response ); - CPPUNIT_ASSERT( response->GetSize() == 20*MB ); - CPPUNIT_ASSERT( fs.Rm( filePath ).IsOK() ); - delete [] buffer1; - delete [] buffer2; - delete [] buffer3; - delete [] buffer4; - delete stat; -} - -//------------------------------------------------------------------------------ -// WriteV test -//------------------------------------------------------------------------------ -void FileTest::WriteVTest() -{ - using namespace XrdCl; - - //---------------------------------------------------------------------------- - // Initialize - //---------------------------------------------------------------------------- - Env *testEnv = TestEnv::GetEnv(); - - std::string address; - std::string dataPath; - - CPPUNIT_ASSERT( testEnv->GetString( "MainServerURL", address ) ); - CPPUNIT_ASSERT( testEnv->GetString( "DataPath", dataPath ) ); - - URL url( address ); - CPPUNIT_ASSERT( url.IsValid() ); - - std::string filePath = dataPath + "/testFile.dat"; - std::string fileUrl = address + "/"; - fileUrl += filePath; - - //---------------------------------------------------------------------------- - // Fetch some data and checksum - //---------------------------------------------------------------------------- - const uint32_t MB = 1024*1024; - char *buffer1 = new char[4*MB]; - char *buffer2 = new char[4*MB]; - char *buffer3 = new char[8*MB]; - uint32_t bytesRead1 = 0; - File f1, f2; - - CPPUNIT_ASSERT( Utils::GetRandomBytes( buffer1, 4*MB ) == 4*MB ); - CPPUNIT_ASSERT( Utils::GetRandomBytes( buffer2, 4*MB ) == 4*MB ); - uint32_t crc1 = Utils::ComputeCRC32( buffer1, 4*MB ); - crc1 = Utils::UpdateCRC32( crc1, buffer2, 4*MB ); - - //---------------------------------------------------------------------------- - // Prepare IO vector - //---------------------------------------------------------------------------- - int iovcnt = 2; - iovec iov[iovcnt]; - iov[0].iov_base = buffer1; - iov[0].iov_len = 4*MB; - iov[1].iov_base = buffer2; - iov[1].iov_len = 4*MB; - - //---------------------------------------------------------------------------- - // Write the data - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT( f1.Open( fileUrl, OpenFlags::Delete | OpenFlags::Update, - Access::UR | Access::UW ).IsOK() ); - CPPUNIT_ASSERT( f1.WriteV( 0, iov, iovcnt ).IsOK() ); - CPPUNIT_ASSERT( f1.Sync().IsOK() ); - CPPUNIT_ASSERT( f1.Close().IsOK() ); - - //---------------------------------------------------------------------------- - // Read the data and verify the checksums - //---------------------------------------------------------------------------- - StatInfo *stat = 0; - CPPUNIT_ASSERT( f2.Open( fileUrl, OpenFlags::Read ).IsOK() ); - CPPUNIT_ASSERT( f2.Stat( false, stat ).IsOK() ); - CPPUNIT_ASSERT( stat ); - CPPUNIT_ASSERT( stat->GetSize() == 8*MB ); - CPPUNIT_ASSERT( f2.Read( 0, 8*MB, buffer3, bytesRead1 ).IsOK() ); - CPPUNIT_ASSERT( bytesRead1 == 8*MB ); - - uint32_t crc2 = Utils::ComputeCRC32( buffer3, 8*MB ); - CPPUNIT_ASSERT( f2.Close().IsOK() ); - CPPUNIT_ASSERT( crc1 == crc2 ); - - FileSystem fs( url ); - CPPUNIT_ASSERT( fs.Rm( filePath ).IsOK() ); - delete [] buffer1; - delete [] buffer2; - delete [] buffer3; - delete stat; -} - -//------------------------------------------------------------------------------ -// Vector read test -//------------------------------------------------------------------------------ -void FileTest::VectorReadTest() -{ - using namespace XrdCl; - - //---------------------------------------------------------------------------- - // Initialize - //---------------------------------------------------------------------------- - Env *testEnv = TestEnv::GetEnv(); - - std::string address; - std::string dataPath; - - CPPUNIT_ASSERT( testEnv->GetString( "MainServerURL", address ) ); - CPPUNIT_ASSERT( testEnv->GetString( "DataPath", dataPath ) ); - - URL url( address ); - CPPUNIT_ASSERT( url.IsValid() ); - - std::string filePath = dataPath + "/a048e67f-4397-4bb8-85eb-8d7e40d90763.dat"; - std::string fileUrl = address + "/"; - fileUrl += filePath; - - //---------------------------------------------------------------------------- - // Fetch some data and checksum - //---------------------------------------------------------------------------- - const uint32_t MB = 1024*1024; - char *buffer1 = new char[40*MB]; - char *buffer2 = new char[40*256000]; - File f; - - //---------------------------------------------------------------------------- - // Build the chunk list - //---------------------------------------------------------------------------- - ChunkList chunkList1; - ChunkList chunkList2; - for( int i = 0; i < 40; ++i ) - { - chunkList1.push_back( ChunkInfo( (i+1)*10*MB, 1*MB ) ); - chunkList2.push_back( ChunkInfo( (i+1)*10*MB, 256000 ) ); - } - - //---------------------------------------------------------------------------- - // Open the file - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT_XRDST( f.Open( fileUrl, OpenFlags::Read ) ); - VectorReadInfo *info = 0; - CPPUNIT_ASSERT_XRDST( f.VectorRead( chunkList1, buffer1, info ) ); - CPPUNIT_ASSERT( info->GetSize() == 40*MB ); - delete info; - uint32_t crc = 0; - crc = Utils::ComputeCRC32( buffer1, 40*MB ); - CPPUNIT_ASSERT( crc == 3695956670UL ); - - info = 0; - CPPUNIT_ASSERT_XRDST( f.VectorRead( chunkList2, buffer2, info ) ); - CPPUNIT_ASSERT( info->GetSize() == 40*256000 ); - delete info; - crc = Utils::ComputeCRC32( buffer2, 40*256000 ); - CPPUNIT_ASSERT( crc == 3492603530UL ); - - CPPUNIT_ASSERT_XRDST( f.Close() ); - - delete [] buffer1; - delete [] buffer2; -} - -void gen_random_str(char *s, const int len) -{ - static const char alphanum[] = - "0123456789" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz"; - - for (int i = 0; i < len - 1; ++i) - { - s[i] = alphanum[rand() % (sizeof(alphanum) - 1)]; - } - - s[len - 1] = 0; -} - -//------------------------------------------------------------------------------ -// Vector write test -//------------------------------------------------------------------------------ -void FileTest::VectorWriteTest() -{ - using namespace XrdCl; - - //---------------------------------------------------------------------------- - // Initialize - //---------------------------------------------------------------------------- - Env *testEnv = TestEnv::GetEnv(); - - std::string address; - std::string dataPath; - - CPPUNIT_ASSERT( testEnv->GetString( "MainServerURL", address ) ); - CPPUNIT_ASSERT( testEnv->GetString( "DataPath", dataPath ) ); - - URL url( address ); - CPPUNIT_ASSERT( url.IsValid() ); - - std::string filePath = dataPath + "/a048e67f-4397-4bb8-85eb-8d7e40d90763.dat"; - std::string fileUrl = address + "/"; - fileUrl += filePath; - - //---------------------------------------------------------------------------- - // Build a random chunk list for vector write - //---------------------------------------------------------------------------- - - const uint32_t MB = 1024*1024; - const uint32_t GB = 1024*MB; - - time_t seed = time( 0 ); - std::default_random_engine generator( seed ); - DefaultEnv::GetLog()->Info( UtilityMsg, - "Carrying out the VectorWrite test with seed: %d", seed ); - - // figure out how many chunks are we going to write/read - std::uniform_int_distribution nbChunksDist( 1, 100); - std::uniform_int_distribution sizeDist( MB, 2*MB); - size_t nbChunks = nbChunksDist( generator ); - - XrdCl::ChunkList chunks; - size_t min_offset = 0; - uint32_t expectedCrc32 = 0; - size_t totalSize = 0; - - for( size_t i = 0; i < nbChunks; ++i ) - { - // figure out the offset - std::uniform_int_distribution offsetDist( min_offset, GB); - size_t offset = offsetDist( generator ); - - // figure out the size - size_t size = sizeDist( generator ); - if( offset + size >= GB ) - size = GB - offset; - - // generate random string of given size - char *buffer = new char[size]; - gen_random_str( buffer, size ); - - // calculate expected checksum - expectedCrc32 = Utils::UpdateCRC32( expectedCrc32, buffer, size ); - totalSize += size; - chunks.push_back( XrdCl::ChunkInfo( offset, size, buffer ) ); - - min_offset = offset + size; - if( min_offset >= GB ) - break; - } - - //---------------------------------------------------------------------------- - // Open the file - //---------------------------------------------------------------------------- - File f; - CPPUNIT_ASSERT_XRDST( f.Open( fileUrl, OpenFlags::Update ) ); - - //---------------------------------------------------------------------------- - // First do a VectorRead so we can revert to the original state - //---------------------------------------------------------------------------- - char *buffer1 = new char[totalSize]; - VectorReadInfo *info1 = 0; - CPPUNIT_ASSERT_XRDST( f.VectorRead( chunks, buffer1, info1 ) ); - - //---------------------------------------------------------------------------- - // Then do the VectorWrite - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT_XRDST( f.VectorWrite( chunks ) ); - - //---------------------------------------------------------------------------- - // Now do a vector read and verify that the checksum is the same - //---------------------------------------------------------------------------- - char *buffer2 = new char[totalSize]; - VectorReadInfo *info2 = 0; - CPPUNIT_ASSERT_XRDST( f.VectorRead( chunks, buffer2, info2 ) ); - - CPPUNIT_ASSERT( info2->GetSize() == totalSize ); - uint32_t crc32 = Utils::ComputeCRC32( buffer2, totalSize ); - CPPUNIT_ASSERT( crc32 == expectedCrc32 ); - - //---------------------------------------------------------------------------- - // And finally revert to the original state - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT_XRDST( f.VectorWrite( info1->GetChunks() ) ); - CPPUNIT_ASSERT_XRDST( f.Close() ); - - delete info1; - delete info2; - delete [] buffer1; - delete [] buffer2; - for( auto itr = chunks.begin(); itr != chunks.end(); ++itr ) - delete[] (char*)itr->buffer; -} - -void FileTest::VirtualRedirectorTest() -{ - using namespace XrdCl; - - //---------------------------------------------------------------------------- - // Initialize - //---------------------------------------------------------------------------- - Env *testEnv = TestEnv::GetEnv(); - - std::string address; - std::string dataPath; - - CPPUNIT_ASSERT( testEnv->GetString( "MainServerURL", address ) ); - CPPUNIT_ASSERT( testEnv->GetString( "DataPath", dataPath ) ); - - URL url( address ); - CPPUNIT_ASSERT( url.IsValid() ); - - std::string mlUrl1 = address + "/" + dataPath + "/metalink/mlFileTest1.meta4"; - std::string mlUrl2 = address + "/" + dataPath + "/metalink/mlFileTest2.meta4"; - std::string mlUrl3 = address + "/" + dataPath + "/metalink/mlFileTest3.meta4"; - std::string mlUrl4 = address + "/" + dataPath + "/metalink/mlFileTest4.meta4"; - - File f1, f2, f3, f4; - - const std::string fileUrl = "root://srv1:1094//data/a048e67f-4397-4bb8-85eb-8d7e40d90763.dat"; - const std::string key = "LastURL"; - std::string value; - - //---------------------------------------------------------------------------- - // Open the 1st metalink file - // (the metalink contains just one file with a correct location) - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT_XRDST( f1.Open( mlUrl1, OpenFlags::Read ) ); - CPPUNIT_ASSERT( f1.GetProperty( key, value ) ); - CPPUNIT_ASSERT( value == fileUrl ); - CPPUNIT_ASSERT_XRDST( f1.Close() ); - - //---------------------------------------------------------------------------- - // Open the 2nd metalink file - // (the metalink contains 2 files, the one with higher priority does not exist) - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT_XRDST( f2.Open( mlUrl2, OpenFlags::Read ) ); - CPPUNIT_ASSERT( f2.GetProperty( key, value ) ); - CPPUNIT_ASSERT( value == fileUrl ); - CPPUNIT_ASSERT_XRDST( f2.Close() ); - - //---------------------------------------------------------------------------- - // Open the 3rd metalink file - // (the metalink contains 2 files, both don't exist) - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT_XRDST_NOTOK( f3.Open( mlUrl3, OpenFlags::Read ), errErrorResponse ); - - //---------------------------------------------------------------------------- - // Open the 4th metalink file - // (the metalink contains 2 files, both exist) - //---------------------------------------------------------------------------- - const std::string replica1 = "root://srv3:1094//data/3c9a9dd8-bc75-422c-b12c-f00604486cc1.dat"; - const std::string replica2 = "root://srv2:1094//data/3c9a9dd8-bc75-422c-b12c-f00604486cc1.dat"; - - CPPUNIT_ASSERT_XRDST( f4.Open( mlUrl4, OpenFlags::Read ) ); - CPPUNIT_ASSERT( f4.GetProperty( key, value ) ); - CPPUNIT_ASSERT( value == replica1 ); - CPPUNIT_ASSERT_XRDST( f4.Close() ); - //---------------------------------------------------------------------------- - // Delete the replica that has been selected by the virtual redirector - //---------------------------------------------------------------------------- - FileSystem fs( replica1 ); - CPPUNIT_ASSERT_XRDST( fs.Rm( "/data/3c9a9dd8-bc75-422c-b12c-f00604486cc1.dat" ) ); - //---------------------------------------------------------------------------- - // Now reopen the file - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT_XRDST( f4.Open( mlUrl4, OpenFlags::Read ) ); - CPPUNIT_ASSERT( f4.GetProperty( key, value ) ); - CPPUNIT_ASSERT( value == replica2 ); - CPPUNIT_ASSERT_XRDST( f4.Close() ); - //---------------------------------------------------------------------------- - // Recreate the deleted file - //---------------------------------------------------------------------------- - CopyProcess process; - PropertyList properties, results; - properties.Set( "source", replica2 ); - properties.Set( "target", replica1 ); - CPPUNIT_ASSERT_XRDST( process.AddJob( properties, &results ) ); - CPPUNIT_ASSERT_XRDST( process.Prepare() ); - CPPUNIT_ASSERT_XRDST( process.Run(0) ); -} - -//------------------------------------------------------------------------------ -// Plug-in test -//------------------------------------------------------------------------------ -void FileTest::PlugInTest() -{ - XrdCl::PlugInFactory *f = new IdentityFactory; - XrdCl::DefaultEnv::GetPlugInManager()->RegisterDefaultFactory(f); - RedirectReturnTest(); - ReadTest(); - WriteTest(); - VectorReadTest(); - XrdCl::DefaultEnv::GetPlugInManager()->RegisterDefaultFactory(0); -} diff --git a/tests/XrdClTests/IdentityPlugIn.cc b/tests/XrdClTests/IdentityPlugIn.cc deleted file mode 100644 index 173c42ce347..00000000000 --- a/tests/XrdClTests/IdentityPlugIn.cc +++ /dev/null @@ -1,488 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2014 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#include "XrdCl/XrdClFile.hh" -#include "XrdCl/XrdClFileSystem.hh" -#include "XrdCl/XrdClPlugInInterface.hh" -#include "XrdCl/XrdClLog.hh" -#include "IdentityPlugIn.hh" -#include "TestEnv.hh" - -using namespace XrdCl; -using namespace XrdClTests; - -namespace -{ - //---------------------------------------------------------------------------- - // A plugin that forwards all the calls to XrdCl::File - //---------------------------------------------------------------------------- - class IdentityFile: public XrdCl::FilePlugIn - { - public: - //------------------------------------------------------------------------ - // Constructor - //------------------------------------------------------------------------ - IdentityFile() - { - XrdCl::Log *log = TestEnv::GetLog(); - log->Debug( 1, "Calling IdentityFile::IdentityFile" ); - pFile = new File( false ); - } - - //------------------------------------------------------------------------ - // Destructor - //------------------------------------------------------------------------ - virtual ~IdentityFile() - { - XrdCl::Log *log = TestEnv::GetLog(); - log->Debug( 1, "Calling IdentityFile::~IdentityFile" ); - delete pFile; - } - - //------------------------------------------------------------------------ - // Open - //------------------------------------------------------------------------ - virtual XRootDStatus Open( const std::string &url, - OpenFlags::Flags flags, - Access::Mode mode, - ResponseHandler *handler, - uint16_t timeout ) - { - XrdCl::Log *log = TestEnv::GetLog(); - log->Debug( 1, "Calling IdentityFile::Open" ); - return pFile->Open( url, flags, mode, handler, timeout ); - } - - //------------------------------------------------------------------------ - // Close - //------------------------------------------------------------------------ - virtual XRootDStatus Close( ResponseHandler *handler, - uint16_t timeout ) - { - XrdCl::Log *log = TestEnv::GetLog(); - log->Debug( 1, "Calling IdentityFile::Close" ); - return pFile->Close( handler, timeout ); - } - - //------------------------------------------------------------------------ - // Stat - //------------------------------------------------------------------------ - virtual XRootDStatus Stat( bool force, - ResponseHandler *handler, - uint16_t timeout ) - { - XrdCl::Log *log = TestEnv::GetLog(); - log->Debug( 1, "Calling IdentityFile::Stat" ); - return pFile->Stat( force, handler, timeout ); - } - - - //------------------------------------------------------------------------ - // Read - //------------------------------------------------------------------------ - virtual XRootDStatus Read( uint64_t offset, - uint32_t size, - void *buffer, - ResponseHandler *handler, - uint16_t timeout ) - { - XrdCl::Log *log = TestEnv::GetLog(); - log->Debug( 1, "Calling IdentityFile::Read" ); - return pFile->Read( offset, size, buffer, handler, timeout ); - } - - //------------------------------------------------------------------------ - // Write - //------------------------------------------------------------------------ - virtual XRootDStatus Write( uint64_t offset, - uint32_t size, - const void *buffer, - ResponseHandler *handler, - uint16_t timeout ) - { - XrdCl::Log *log = TestEnv::GetLog(); - log->Debug( 1, "Calling IdentityFile::Write" ); - return pFile->Write( offset, size, buffer, handler, timeout ); - } - - //------------------------------------------------------------------------ - // Sync - //------------------------------------------------------------------------ - virtual XRootDStatus Sync( ResponseHandler *handler, - uint16_t timeout ) - { - XrdCl::Log *log = TestEnv::GetLog(); - log->Debug( 1, "Calling IdentityFile::Sync" ); - return pFile->Sync( handler, timeout ); - } - - //------------------------------------------------------------------------ - // Truncate - //------------------------------------------------------------------------ - virtual XRootDStatus Truncate( uint64_t size, - ResponseHandler *handler, - uint16_t timeout ) - { - XrdCl::Log *log = TestEnv::GetLog(); - log->Debug( 1, "Calling IdentityFile::Truncate" ); - return pFile->Truncate( size, handler, timeout ); - } - - //------------------------------------------------------------------------ - // VectorRead - //------------------------------------------------------------------------ - virtual XRootDStatus VectorRead( const ChunkList &chunks, - void *buffer, - ResponseHandler *handler, - uint16_t timeout ) - { - XrdCl::Log *log = TestEnv::GetLog(); - log->Debug( 1, "Calling IdentityFile::VectorRead" ); - return pFile->VectorRead( chunks, buffer, handler, timeout ); - } - - //------------------------------------------------------------------------ - // Fcntl - //------------------------------------------------------------------------ - virtual XRootDStatus Fcntl( const Buffer &arg, - ResponseHandler *handler, - uint16_t timeout ) - { - XrdCl::Log *log = TestEnv::GetLog(); - log->Debug( 1, "Calling IdentityFile::Fcntl" ); - return pFile->Fcntl( arg, handler, timeout ); - } - - //------------------------------------------------------------------------ - // Visa - //------------------------------------------------------------------------ - virtual XRootDStatus Visa( ResponseHandler *handler, - uint16_t timeout ) - { - XrdCl::Log *log = TestEnv::GetLog(); - log->Debug( 1, "Calling IdentityFile::Visa" ); - return pFile->Visa( handler, timeout ); - } - - //------------------------------------------------------------------------ - // IsOpen - //------------------------------------------------------------------------ - virtual bool IsOpen() const - { - XrdCl::Log *log = TestEnv::GetLog(); - log->Debug( 1, "Calling IdentityFile::IsOpen" ); - return pFile->IsOpen(); - } - - //------------------------------------------------------------------------ - // SetProperty - //------------------------------------------------------------------------ - virtual bool SetProperty( const std::string &name, - const std::string &value ) - { - XrdCl::Log *log = TestEnv::GetLog(); - log->Debug( 1, "Calling IdentityFile::SetProperty" ); - return pFile->SetProperty( name, value ); - } - - //------------------------------------------------------------------------ - // GetProperty - //------------------------------------------------------------------------ - virtual bool GetProperty( const std::string &name, - std::string &value ) const - { - XrdCl::Log *log = TestEnv::GetLog(); - log->Debug( 1, "Calling IdentityFile::GetProperty" ); - return pFile->GetProperty( name, value ); - } - - private: - XrdCl::File *pFile; - }; - - //---------------------------------------------------------------------------- - // A plug-in that forwards all the calls to a XrdCl::FileSystem object - //---------------------------------------------------------------------------- - class IdentityFileSystem: public FileSystemPlugIn - { - public: - //------------------------------------------------------------------------ - // Constructor - //------------------------------------------------------------------------ - IdentityFileSystem( const std::string &url ) - { - XrdCl::Log *log = TestEnv::GetLog(); - log->Debug( 1, "Calling IdentityFileSystem::IdentityFileSystem" ); - pFileSystem = new XrdCl::FileSystem( URL(url), false ); - } - - //------------------------------------------------------------------------ - // Destructor - //------------------------------------------------------------------------ - virtual ~IdentityFileSystem() - { - XrdCl::Log *log = TestEnv::GetLog(); - log->Debug( 1, "Calling IdentityFileSystem::~IdentityFileSysytem" ); - delete pFileSystem; - } - - //------------------------------------------------------------------------ - // Locate - //------------------------------------------------------------------------ - virtual XRootDStatus Locate( const std::string &path, - OpenFlags::Flags flags, - ResponseHandler *handler, - uint16_t timeout ) - { - XrdCl::Log *log = TestEnv::GetLog(); - log->Debug( 1, "Calling IdentityFileSystem::Locate" ); - return pFileSystem->Locate( path, flags, handler, timeout ); - } - - //------------------------------------------------------------------------ - // Mv - //------------------------------------------------------------------------ - virtual XRootDStatus Mv( const std::string &source, - const std::string &dest, - ResponseHandler *handler, - uint16_t timeout ) - { - XrdCl::Log *log = TestEnv::GetLog(); - log->Debug( 1, "Calling IdentityFileSystem::Mv" ); - return pFileSystem->Mv( source, dest, handler, timeout ); - } - - //------------------------------------------------------------------------ - // Query - //------------------------------------------------------------------------ - virtual XRootDStatus Query( QueryCode::Code queryCode, - const Buffer &arg, - ResponseHandler *handler, - uint16_t timeout ) - { - XrdCl::Log *log = TestEnv::GetLog(); - log->Debug( 1, "Calling IdentityFileSystem::Query" ); - return pFileSystem->Query( queryCode, arg, handler, timeout ); - } - - //------------------------------------------------------------------------ - // Truncate - //------------------------------------------------------------------------ - virtual XRootDStatus Truncate( const std::string &path, - uint64_t size, - ResponseHandler *handler, - uint16_t timeout ) - { - XrdCl::Log *log = TestEnv::GetLog(); - log->Debug( 1, "Calling IdentityFileSystem::Truncate" ); - return pFileSystem->Truncate( path, size, handler, timeout ); - } - - //------------------------------------------------------------------------ - // Rm - //------------------------------------------------------------------------ - virtual XRootDStatus Rm( const std::string &path, - ResponseHandler *handler, - uint16_t timeout ) - { - XrdCl::Log *log = TestEnv::GetLog(); - log->Debug( 1, "Calling IdentityFileSystem::Rm" ); - return pFileSystem->Rm( path, handler, timeout ); - } - - //------------------------------------------------------------------------ - // MkDir - //------------------------------------------------------------------------ - virtual XRootDStatus MkDir( const std::string &path, - MkDirFlags::Flags flags, - Access::Mode mode, - ResponseHandler *handler, - uint16_t timeout ) - { - XrdCl::Log *log = TestEnv::GetLog(); - log->Debug( 1, "Calling IdentityFileSystem::MkDir" ); - return pFileSystem->MkDir( path, flags, mode, handler, timeout ); - } - - //------------------------------------------------------------------------ - // RmDir - //------------------------------------------------------------------------ - virtual XRootDStatus RmDir( const std::string &path, - ResponseHandler *handler, - uint16_t timeout ) - { - XrdCl::Log *log = TestEnv::GetLog(); - log->Debug( 1, "Calling IdentityFileSystem::RmDir" ); - return pFileSystem->RmDir( path, handler, timeout ); - } - - //------------------------------------------------------------------------ - // ChMod - //------------------------------------------------------------------------ - virtual XRootDStatus ChMod( const std::string &path, - Access::Mode mode, - ResponseHandler *handler, - uint16_t timeout ) - { - XrdCl::Log *log = TestEnv::GetLog(); - log->Debug( 1, "Calling IdentityFileSystem::ChMod" ); - return pFileSystem->ChMod( path, mode, handler, timeout ); - } - - //------------------------------------------------------------------------ - // Ping - //------------------------------------------------------------------------ - virtual XRootDStatus Ping( ResponseHandler *handler, - uint16_t timeout ) - { - XrdCl::Log *log = TestEnv::GetLog(); - log->Debug( 1, "Calling IdentityFileSystem::Ping" ); - return pFileSystem->Ping( handler, timeout ); - } - - //------------------------------------------------------------------------ - // Stat - //------------------------------------------------------------------------ - virtual XRootDStatus Stat( const std::string &path, - ResponseHandler *handler, - uint16_t timeout ) - { - XrdCl::Log *log = TestEnv::GetLog(); - log->Debug( 1, "Calling IdentityFileSystem::Stat" ); - return pFileSystem->Stat( path, handler, timeout ); - } - - //------------------------------------------------------------------------ - // StatVFS - //------------------------------------------------------------------------ - virtual XRootDStatus StatVFS( const std::string &path, - ResponseHandler *handler, - uint16_t timeout ) - { - XrdCl::Log *log = TestEnv::GetLog(); - log->Debug( 1, "Calling IdentityFileSystem::StatVFS" ); - return pFileSystem->StatVFS( path, handler, timeout ); - } - - //------------------------------------------------------------------------ - // Protocol - //------------------------------------------------------------------------ - virtual XRootDStatus Protocol( ResponseHandler *handler, - uint16_t timeout = 0 ) - { - XrdCl::Log *log = TestEnv::GetLog(); - log->Debug( 1, "Calling IdentityFileSystem::Protocol" ); - return pFileSystem->Protocol( handler, timeout ); - } - - //------------------------------------------------------------------------ - // DirlList - //------------------------------------------------------------------------ - virtual XRootDStatus DirList( const std::string &path, - DirListFlags::Flags flags, - ResponseHandler *handler, - uint16_t timeout ) - { - XrdCl::Log *log = TestEnv::GetLog(); - log->Debug( 1, "Calling IdentityFileSystem::DirList" ); - return pFileSystem->DirList( path, flags, handler, timeout ); - } - - //------------------------------------------------------------------------ - // SendInfo - //------------------------------------------------------------------------ - virtual XRootDStatus SendInfo( const std::string &info, - ResponseHandler *handler, - uint16_t timeout ) - { - XrdCl::Log *log = TestEnv::GetLog(); - log->Debug( 1, "Calling IdentityFileSystem::SendInfo" ); - return pFileSystem->SendInfo( info, handler, timeout ); - } - - //------------------------------------------------------------------------ - // Prepare - //------------------------------------------------------------------------ - virtual XRootDStatus Prepare( const std::vector &fileList, - PrepareFlags::Flags flags, - uint8_t priority, - ResponseHandler *handler, - uint16_t timeout ) - { - XrdCl::Log *log = TestEnv::GetLog(); - log->Debug( 1, "Calling IdentityFileSystem::Prepare" ); - return pFileSystem->Prepare( fileList, flags, priority, handler, - timeout ); - } - - //------------------------------------------------------------------------ - // SetProperty - //------------------------------------------------------------------------ - virtual bool SetProperty( const std::string &name, - const std::string &value ) - { - XrdCl::Log *log = TestEnv::GetLog(); - log->Debug( 1, "Calling IdentityFileSystem::SetProperty" ); - return pFileSystem->SetProperty( name, value ); - } - - //------------------------------------------------------------------------ - // GetProperty - //------------------------------------------------------------------------ - virtual bool GetProperty( const std::string &name, - std::string &value ) const - { - XrdCl::Log *log = TestEnv::GetLog(); - log->Debug( 1, "Calling IdentityFilesystem::GetProperty" ); - return pFileSystem->GetProperty( name, value ); - } - - private: - XrdCl::FileSystem *pFileSystem; - }; -} - -namespace XrdClTests -{ - //---------------------------------------------------------------------------- - // Create a file plug-in for the given URL - //---------------------------------------------------------------------------- - FilePlugIn *IdentityFactory::CreateFile( const std::string &url ) - { - XrdCl::Log *log = TestEnv::GetLog(); - log->Debug( 1, "Creating an identity file plug-in" ); - return new IdentityFile(); - } - - //---------------------------------------------------------------------------- - // Create a file system plug-in for the given URL - //---------------------------------------------------------------------------- - FileSystemPlugIn *IdentityFactory::CreateFileSystem( const std::string &url ) - { - XrdCl::Log *log = TestEnv::GetLog(); - log->Debug( 1, "Creating an identity file system plug-in" ); - return new IdentityFileSystem( url ); - } -} - diff --git a/tests/XrdClTests/IdentityPlugIn.hh b/tests/XrdClTests/IdentityPlugIn.hh deleted file mode 100644 index ebc7e45600d..00000000000 --- a/tests/XrdClTests/IdentityPlugIn.hh +++ /dev/null @@ -1,55 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2014 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#ifndef __XRDCLTESTS_IDENTITY_PLUGIN_HH__ -#define __XRDCLTESTS_IDENTITY_PLUGIN_HH__ - -#include "XrdCl/XrdClPlugInInterface.hh" - -namespace XrdClTests -{ - //---------------------------------------------------------------------------- - // Plugin factory - //---------------------------------------------------------------------------- - class IdentityFactory: public XrdCl::PlugInFactory - { - public: - //------------------------------------------------------------------------ - // Destructor - //------------------------------------------------------------------------ - virtual ~IdentityFactory() {} - - //------------------------------------------------------------------------ - // Create a file plug-in for the given URL - //------------------------------------------------------------------------ - virtual XrdCl::FilePlugIn *CreateFile( const std::string &url ); - - //------------------------------------------------------------------------ - // Create a file system plug-in for the given URL - //------------------------------------------------------------------------ - virtual XrdCl::FileSystemPlugIn *CreateFileSystem( const std::string &url ); - }; -}; - -#endif // __XRDCLTESTS_IDENTITY_PLUGIN_HH__ diff --git a/tests/XrdClTests/LocalFileHandlerTest.cc b/tests/XrdClTests/LocalFileHandlerTest.cc deleted file mode 100644 index b790adf798e..00000000000 --- a/tests/XrdClTests/LocalFileHandlerTest.cc +++ /dev/null @@ -1,465 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2017 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH -// Author: Paul-Niklas Kramp -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ -#include -#include "TestEnv.hh" -#include "CppUnitXrdHelpers.hh" -#include "XrdCl/XrdClFile.hh" - -#include -#include -#include - -using namespace XrdClTests; - -//------------------------------------------------------------------------------ -// Declaration -//------------------------------------------------------------------------------ -class LocalFileHandlerTest: public CppUnit::TestCase -{ - public: - CPPUNIT_TEST_SUITE( LocalFileHandlerTest ); - CPPUNIT_TEST( OpenCloseTest ); - CPPUNIT_TEST( ReadTest ); - CPPUNIT_TEST( ReadWithOffsetTest ); - CPPUNIT_TEST( WriteTest ); - CPPUNIT_TEST( WriteWithOffsetTest ); - CPPUNIT_TEST( WriteMkdirTest ); - CPPUNIT_TEST( TruncateTest ); - CPPUNIT_TEST( VectorReadTest ); - CPPUNIT_TEST( VectorWriteTest ); - CPPUNIT_TEST( SyncTest ); - CPPUNIT_TEST( WriteVTest ); - CPPUNIT_TEST_SUITE_END(); - void CreateTestFileFunc( std::string url, std::string content = "GenericTestFile" ); - void OpenCloseTest(); - void ReadTest(); - void ReadWithOffsetTest(); - void WriteTest(); - void WriteWithOffsetTest(); - void WriteMkdirTest(); - void TruncateTest(); - void VectorReadTest(); - void VectorWriteTest(); - void SyncTest(); - void WriteVTest(); -}; -CPPUNIT_TEST_SUITE_REGISTRATION( LocalFileHandlerTest ); - -//---------------------------------------------------------------------------- -// Create the file to be tested -//---------------------------------------------------------------------------- -void LocalFileHandlerTest::CreateTestFileFunc( std::string url, std::string content ){ - mode_t openmode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; - int fd = open( url.c_str(), O_RDWR | O_CREAT | O_TRUNC, openmode ); - int rc = write( fd, content.c_str(), content.size() ); - CPPUNIT_ASSERT_EQUAL( rc, int( content.size() ) ); - rc = close( fd ); - CPPUNIT_ASSERT_EQUAL( rc, 0 ); -} - -void LocalFileHandlerTest::SyncTest(){ - using namespace XrdCl; - std::string targetURL = "/tmp/lfilehandlertestfilesync"; - CreateTestFileFunc( targetURL ); - - //---------------------------------------------------------------------------- - // Open and Sync File - //---------------------------------------------------------------------------- - OpenFlags::Flags flags = OpenFlags::Update; - Access::Mode mode = Access::UR | Access::UW | Access::GR | Access::OR; - File *file = new File(); - CPPUNIT_ASSERT_XRDST( file->Open( targetURL, flags, mode ) ); - CPPUNIT_ASSERT_XRDST( file->Sync() ); - CPPUNIT_ASSERT_XRDST( file->Close() ); - CPPUNIT_ASSERT( remove( targetURL.c_str() ) == 0 ); - delete file; -} - -void LocalFileHandlerTest::OpenCloseTest(){ - using namespace XrdCl; - std::string targetURL = "/tmp/lfilehandlertestfileopenclose"; - CreateTestFileFunc( targetURL ); - - //---------------------------------------------------------------------------- - // Open existing file - //---------------------------------------------------------------------------- - OpenFlags::Flags flags = OpenFlags::Update; - Access::Mode mode = Access::UR|Access::UW|Access::GR|Access::OR; - File *file = new File(); - CPPUNIT_ASSERT_XRDST( file->Open( targetURL, flags, mode ) ); - CPPUNIT_ASSERT( file->IsOpen() ); - CPPUNIT_ASSERT_XRDST( file->Close() ); - CPPUNIT_ASSERT( remove( targetURL.c_str() ) == 0 ); - - //---------------------------------------------------------------------------- - // Try open non-existing file - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT( !file->Open( targetURL, flags, mode ).IsOK() ); - CPPUNIT_ASSERT( !file->IsOpen() ); - - //---------------------------------------------------------------------------- - // Try close non-opened file, return has to be error - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT( file->Close().status == stError ); - delete file; -} - -void LocalFileHandlerTest::WriteTest(){ - using namespace XrdCl; - std::string targetURL = "/tmp/lfilehandlertestfilewrite"; - std::string toBeWritten = "tenBytes1\0"; - uint32_t writeSize = toBeWritten.size(); - CreateTestFileFunc( targetURL, "" ); - char *buffer = new char[writeSize]; - //---------------------------------------------------------------------------- - // Open and Write File - //---------------------------------------------------------------------------- - OpenFlags::Flags flags = OpenFlags::Update; - Access::Mode mode = Access::UR|Access::UW|Access::GR|Access::OR; - File *file = new File(); - CPPUNIT_ASSERT_XRDST( file->Open( targetURL, flags, mode ) ); - CPPUNIT_ASSERT( file->IsOpen() ); - CPPUNIT_ASSERT_XRDST( file->Write( 0, writeSize, toBeWritten.c_str()) ); - CPPUNIT_ASSERT_XRDST( file->Close() ); - - //---------------------------------------------------------------------------- - // Read file with POSIX calls to confirm correct write - //---------------------------------------------------------------------------- - int fd = open( targetURL.c_str(), flags ); - int rc = read( fd, buffer, int( writeSize ) ); - CPPUNIT_ASSERT_EQUAL( rc, int( writeSize ) ); - std::string read( (char *)buffer, writeSize ); - CPPUNIT_ASSERT( toBeWritten == read ); - CPPUNIT_ASSERT( remove( targetURL.c_str() ) == 0 ); - delete[] buffer; - delete file; -} - -void LocalFileHandlerTest::WriteWithOffsetTest(){ - using namespace XrdCl; - std::string targetURL = "/tmp/lfilehandlertestfilewriteoffset"; - std::string toBeWritten = "tenBytes10"; - std::string notToBeOverwritten = "front"; - uint32_t writeSize = toBeWritten.size(); - uint32_t offset = notToBeOverwritten.size(); - void *buffer = new char[offset]; - CreateTestFileFunc( targetURL, notToBeOverwritten ); - - //---------------------------------------------------------------------------- - // Open and Write File - //---------------------------------------------------------------------------- - OpenFlags::Flags flags = OpenFlags::Update; - Access::Mode mode = Access::UR|Access::UW|Access::GR|Access::OR; - File *file = new File(); - CPPUNIT_ASSERT_XRDST( file->Open( targetURL, flags, mode ) ); - CPPUNIT_ASSERT( file->IsOpen() ); - CPPUNIT_ASSERT_XRDST( file->Write( offset, writeSize, toBeWritten.c_str()) ); - CPPUNIT_ASSERT_XRDST( file->Close() ); - - //---------------------------------------------------------------------------- - // Read file with POSIX calls to confirm correct write - //---------------------------------------------------------------------------- - int fd = open( targetURL.c_str(), flags ); - int rc = read( fd, buffer, offset ); - CPPUNIT_ASSERT_EQUAL( rc, int( offset ) ); - std::string read( (char *)buffer, offset ); - CPPUNIT_ASSERT( notToBeOverwritten == read ); - CPPUNIT_ASSERT( remove( targetURL.c_str() ) == 0 ); - delete[] (char*)buffer; - delete file; -} - -void LocalFileHandlerTest::WriteMkdirTest(){ - using namespace XrdCl; - std::string targetURL = "/tmp/testdir/further/muchfurther/evenfurther/lfilehandlertestfilewrite"; - std::string toBeWritten = "tenBytes10"; - uint32_t writeSize = toBeWritten.size(); - char *buffer = new char[writeSize]; - - //---------------------------------------------------------------------------- - // Open and Write File - //---------------------------------------------------------------------------- - OpenFlags::Flags flags = OpenFlags::Update | OpenFlags::MakePath | OpenFlags::New; - Access::Mode mode = Access::UR|Access::UW|Access::UX; - File *file = new File(); - CPPUNIT_ASSERT_XRDST( file->Open( targetURL, flags, mode ) ); - CPPUNIT_ASSERT( file->IsOpen() ); - CPPUNIT_ASSERT_XRDST( file->Write( 0, writeSize, toBeWritten.c_str()) ); - CPPUNIT_ASSERT_XRDST( file->Close() ); - - //---------------------------------------------------------------------------- - // Read file with POSIX calls to confirm correct write - //---------------------------------------------------------------------------- - int fd = open( targetURL.c_str(), flags ); - int rc = read( fd, buffer, writeSize ); - CPPUNIT_ASSERT_EQUAL( rc, int( writeSize ) ); - std::string read( buffer, writeSize ); - CPPUNIT_ASSERT( toBeWritten == read ); - CPPUNIT_ASSERT( remove( targetURL.c_str() ) == 0 ); - delete[] buffer; - delete file; -} - -void LocalFileHandlerTest::ReadTest(){ - using namespace XrdCl; - std::string targetURL = "/tmp/lfilehandlertestfileread"; - std::string toBeWritten = "tenBytes10"; - uint32_t offset = 0; - uint32_t writeSize = toBeWritten.size(); - char *buffer = new char[writeSize]; - uint32_t bytesRead = 0; - - //---------------------------------------------------------------------------- - // Write file with POSIX calls to ensure correct write - //---------------------------------------------------------------------------- - CreateTestFileFunc( targetURL, toBeWritten ); - - //---------------------------------------------------------------------------- - // Open and Read File - //---------------------------------------------------------------------------- - OpenFlags::Flags flags = OpenFlags::Update; - Access::Mode mode = Access::UR|Access::UW|Access::GR|Access::OR; - File *file = new File(); - CPPUNIT_ASSERT_XRDST( file->Open( targetURL, flags, mode ) ); - CPPUNIT_ASSERT( file->IsOpen() ); - CPPUNIT_ASSERT_XRDST( file->Read( offset, writeSize, buffer, bytesRead ) ); - CPPUNIT_ASSERT_XRDST( file->Close() ); - - std::string read( (char*)buffer, writeSize ); - CPPUNIT_ASSERT( toBeWritten == read ); - CPPUNIT_ASSERT( remove( targetURL.c_str() ) == 0 ); - - delete[] buffer; - delete file; -} - -void LocalFileHandlerTest::ReadWithOffsetTest(){ - using namespace XrdCl; - std::string targetURL = "/tmp/lfilehandlertestfileread"; - std::string toBeWritten = "tenBytes10"; - uint32_t offset = 3; - std::string expectedRead = "Byte"; - uint32_t readsize = expectedRead.size(); - char *buffer = new char[readsize]; - uint32_t bytesRead = 0; - - //---------------------------------------------------------------------------- - // Write file with POSIX calls to ensure correct write - //---------------------------------------------------------------------------- - CreateTestFileFunc( targetURL, toBeWritten ); - - //---------------------------------------------------------------------------- - // Open and Read File - //---------------------------------------------------------------------------- - OpenFlags::Flags flags = OpenFlags::Update; - Access::Mode mode = Access::UR|Access::UW|Access::GR|Access::OR; - File *file = new File(); - CPPUNIT_ASSERT_XRDST( file->Open( targetURL, flags, mode ) ); - CPPUNIT_ASSERT( file->IsOpen() ); - CPPUNIT_ASSERT_XRDST( file->Read( offset, readsize, buffer, bytesRead ) ); - CPPUNIT_ASSERT_XRDST( file->Close() ); - - std::string read( buffer, readsize ); - CPPUNIT_ASSERT( expectedRead == read ); - CPPUNIT_ASSERT( remove( targetURL.c_str() ) == 0 ); - delete[] buffer; - delete file; -} - -void LocalFileHandlerTest::TruncateTest(){ - using namespace XrdCl; - //---------------------------------------------------------------------------- - // Initialize - //---------------------------------------------------------------------------- - std::string targetURL = "/tmp/lfilehandlertestfiletruncate"; - - CreateTestFileFunc(targetURL); - //---------------------------------------------------------------------------- - // Prepare truncate - //---------------------------------------------------------------------------- - OpenFlags::Flags flags = OpenFlags::Update | OpenFlags::Force; - Access::Mode mode = Access::UR|Access::UW|Access::GR|Access::OR; - File *file = new File(); - uint32_t bytesRead = 0; - uint32_t truncateSize = 5; - - //---------------------------------------------------------------------------- - // Read after truncate, but with greater length. bytesRead must still be - // truncate size if truncate works as intended - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT_XRDST( file->Open( targetURL, flags, mode ) ); - CPPUNIT_ASSERT_XRDST( file->Truncate( truncateSize ) ); - char *buffer = new char[truncateSize + 3]; - CPPUNIT_ASSERT_XRDST( file->Read( 0, truncateSize + 3, buffer, bytesRead ) ); - CPPUNIT_ASSERT_EQUAL( truncateSize, bytesRead ); - CPPUNIT_ASSERT_XRDST( file->Close() ); - CPPUNIT_ASSERT( remove( targetURL.c_str() ) == 0 ); - delete file; - delete[] buffer; -} - -void LocalFileHandlerTest::VectorReadTest() -{ - using namespace XrdCl; - - //---------------------------------------------------------------------------- - // Initialize - //---------------------------------------------------------------------------- - std::string targetURL = "/tmp/lfilehandlertestfilevectorread"; - CreateTestFileFunc( targetURL ); - VectorReadInfo *info = 0; - ChunkList chunks; - - //---------------------------------------------------------------------------- - // Prepare VectorRead - //---------------------------------------------------------------------------- - OpenFlags::Flags flags = OpenFlags::Update; - Access::Mode mode = Access::UR|Access::UW|Access::GR|Access::OR; - File file; - CPPUNIT_ASSERT_XRDST( file.Open( targetURL, flags, mode ) ); - - //---------------------------------------------------------------------------- - // VectorRead no cursor - //---------------------------------------------------------------------------- - - chunks.push_back( ChunkInfo( 0, 5, new char[5] ) ); - chunks.push_back( ChunkInfo( 10, 5, new char[5] ) ); - CPPUNIT_ASSERT_XRDST( file.VectorRead( chunks, NULL, info ) ); - CPPUNIT_ASSERT_XRDST( file.Close() ); - CPPUNIT_ASSERT( info->GetSize() == 10 ); - CPPUNIT_ASSERT_EQUAL( 0, memcmp( "Gener", - info->GetChunks()[0].buffer, - info->GetChunks()[0].length ) ); - CPPUNIT_ASSERT_EQUAL( 0, memcmp( "tFile", - info->GetChunks()[1].buffer, - info->GetChunks()[1].length ) ); - delete[] (char*)chunks[0].buffer; - delete[] (char*)chunks[1].buffer; - delete info; - - //---------------------------------------------------------------------------- - // VectorRead cursor - //---------------------------------------------------------------------------- - char *buffer = new char[10]; - chunks.clear(); - chunks.push_back( ChunkInfo( 0, 5, 0 ) ); - chunks.push_back( ChunkInfo( 10, 5, 0 ) ); - info = 0; - - CPPUNIT_ASSERT_XRDST( file.Open( targetURL, flags, mode ) ); - CPPUNIT_ASSERT_XRDST( file.VectorRead( chunks, buffer, info ) ); - CPPUNIT_ASSERT_XRDST( file.Close() ); - CPPUNIT_ASSERT( info->GetSize() == 10 ); - CPPUNIT_ASSERT_EQUAL( 0, memcmp( "GenertFile", - info->GetChunks()[0].buffer, - info->GetChunks()[0].length ) ); - CPPUNIT_ASSERT( remove( targetURL.c_str() ) == 0 ); - - delete[] buffer; - delete info; -} - -void LocalFileHandlerTest::VectorWriteTest() -{ - using namespace XrdCl; - - //---------------------------------------------------------------------------- - // Initialize - //---------------------------------------------------------------------------- - std::string targetURL = "/tmp/lfilehandlertestfilevectorwrite"; - CreateTestFileFunc( targetURL ); - ChunkList chunks; - - //---------------------------------------------------------------------------- - // Prepare VectorWrite - //---------------------------------------------------------------------------- - OpenFlags::Flags flags = OpenFlags::Update; - Access::Mode mode = Access::UR|Access::UW|Access::GR|Access::OR; - File file; - CPPUNIT_ASSERT_XRDST( file.Open( targetURL, flags, mode ) ); - - //---------------------------------------------------------------------------- - // VectorWrite - //---------------------------------------------------------------------------- - - ChunkInfo chunk( 0, 5, new char[5] ); - memset( chunk.buffer, 'A', chunk.length ); - chunks.push_back( chunk ); - chunk = ChunkInfo( 10, 5, new char[5] ); - memset( chunk.buffer, 'B', chunk.length ); - chunks.push_back( chunk ); - - CPPUNIT_ASSERT_XRDST( file.VectorWrite( chunks ) ); - - //---------------------------------------------------------------------------- - // Verify with VectorRead - //---------------------------------------------------------------------------- - - VectorReadInfo *info = 0; - char *buffer = new char[10]; - CPPUNIT_ASSERT_XRDST( file.VectorRead( chunks, buffer, info ) ); - - CPPUNIT_ASSERT_EQUAL( 0, memcmp( buffer, "AAAAABBBBB", 10 ) ); - - CPPUNIT_ASSERT_XRDST( file.Close() ); - CPPUNIT_ASSERT( info->GetSize() == 10 ); - - delete[] (char*)chunks[0].buffer; - delete[] (char*)chunks[1].buffer; - delete[] buffer; - delete info; -} - -void LocalFileHandlerTest::WriteVTest() -{ - using namespace XrdCl; - - //---------------------------------------------------------------------------- - // Initialize - //---------------------------------------------------------------------------- - std::string targetURL = "/tmp/lfilehandlertestfilewritev"; - CreateTestFileFunc( targetURL ); - - //---------------------------------------------------------------------------- - // Prepare WriteV - //---------------------------------------------------------------------------- - OpenFlags::Flags flags = OpenFlags::Update; - Access::Mode mode = Access::UR|Access::UW|Access::GR|Access::OR; - File file; - CPPUNIT_ASSERT_XRDST( file.Open( targetURL, flags, mode ) ); - - char str[] = "WriteVTest"; - std::vector buffer( 10 ); - std::copy( str, str + sizeof( str ) - 1, buffer.begin() ); - int iovcnt = 2; - iovec iov[iovcnt]; - iov[0].iov_base = buffer.data(); - iov[0].iov_len = 6; - iov[1].iov_base = buffer.data() + 6; - iov[1].iov_len = 4; - CPPUNIT_ASSERT_XRDST( file.WriteV( 7, iov, iovcnt ) ); - - uint32_t bytesRead = 0; - buffer.resize( 17 ); - CPPUNIT_ASSERT_XRDST( file.Read( 0, 17, buffer.data(), bytesRead ) ); - CPPUNIT_ASSERT( buffer.size() == 17 ); - std::string expected = "GenericWriteVTest"; - CPPUNIT_ASSERT( std::string( buffer.data(), buffer.size() ) == expected ); - CPPUNIT_ASSERT_XRDST( file.Close() ); - CPPUNIT_ASSERT( remove( targetURL.c_str() ) == 0 ); -} diff --git a/tests/XrdClTests/MonitorTestLib.cc b/tests/XrdClTests/MonitorTestLib.cc deleted file mode 100644 index cddab1638a8..00000000000 --- a/tests/XrdClTests/MonitorTestLib.cc +++ /dev/null @@ -1,212 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#include "XrdCl/XrdClMonitor.hh" -#include "XrdCl/XrdClLog.hh" -#include "XrdCl/XrdClUtils.hh" -#include "XrdVersion.hh" - -#include "TestEnv.hh" - -XrdVERSIONINFO( XrdClGetMonitor, MonitorTest ); - -class MonitorTest: public XrdCl::Monitor -{ - public: - //-------------------------------------------------------------------------- - // Contructor - //-------------------------------------------------------------------------- - MonitorTest( const std::string &exec, const std::string ¶m ): - pExec( exec ), - pParam( param ), - pInitialized(false) - { - XrdCl::Log *log = XrdClTests::TestEnv::GetLog(); - log->Debug( 2, "Constructed monitoring, exec %s, param %s", - exec.c_str(), param.c_str() ); - } - - //-------------------------------------------------------------------------- - // Destructor - //-------------------------------------------------------------------------- - virtual ~MonitorTest() {} - - //-------------------------------------------------------------------------- - // Event - //-------------------------------------------------------------------------- - virtual void Event( EventCode evCode, void *evData ) - { - using namespace XrdCl; - using namespace XrdClTests; - - Log *log = TestEnv::GetLog(); - switch( evCode ) - { - //---------------------------------------------------------------------- - // Got a connect event - //---------------------------------------------------------------------- - case EvConnect: - { - ConnectInfo *i = (ConnectInfo*)evData; - std::string timeStarted = Utils::TimeToString( i->sTOD.tv_sec ); - std::string timeDone = Utils::TimeToString( i->sTOD.tv_sec ); - log->Debug( 2, "Successfully connected to: %s, started: %s, " - "finished: %s, authentication: %s, streams: %d", - i->server.c_str(), timeStarted.c_str(), timeDone.c_str(), - i->auth.empty() ? "none" : i->auth.c_str(), - i->streams ); - break; - } - - //---------------------------------------------------------------------- - // Got a disconnect event - //---------------------------------------------------------------------- - case EvDisconnect: - { - DisconnectInfo *i = (DisconnectInfo*)evData; - log->Debug( 2, "Disconnected from: %s, bytes sent: %ld, " - "bytes received: %ld, connection time: %d, " - "disconnection status: %s", - i->server.c_str(), i->sBytes, i->rBytes, - i->cTime, i->status.ToString().c_str() ); - break; - } - - //---------------------------------------------------------------------- - // Got an open event - //---------------------------------------------------------------------- - case EvOpen: - { - OpenInfo *i = (OpenInfo*)evData; - log->Debug( 2, "Successfully opened file %s at %s, size %ld", - i->file->GetURL().c_str(), i->dataServer.c_str(), - i->fSize ); - break; - } - - //---------------------------------------------------------------------- - // Got a close event - //---------------------------------------------------------------------- - case EvClose: - { - CloseInfo *i = (CloseInfo*)evData; - std::string timeOpen = Utils::TimeToString( i->oTOD.tv_sec ); - std::string timeClosed = Utils::TimeToString( i->cTOD.tv_sec ); - log->Debug( 2, "Closed file %s, opened: %s, closed: %s, status: %s", - i->file->GetURL().c_str(), timeOpen.c_str(), - timeClosed.c_str(), i->status->ToStr().c_str() ); - log->Debug( 2, "Closed file %s, bytes: read: %ld, readv: %ld, write:" - " %ld", i->file->GetURL().c_str(), i->rBytes, i->vBytes, - i->wBytes ); - log->Debug( 2, "Closed file %s, count: read: %d, readv: %d/%d, " - "write: %d", i->file->GetURL().c_str(), i->rCount, - i->vCount, i->vSegs, i->wCount ); - - break; - } - - //---------------------------------------------------------------------- - // Got an error event - //---------------------------------------------------------------------- - case EvErrIO: - { - ErrorInfo *i = (ErrorInfo*)evData; - std::string op; - switch( i->opCode ) - { - case ErrorInfo::ErrOpen: op = "Open"; break; - case ErrorInfo::ErrRead: op = "Read"; break; - case ErrorInfo::ErrReadV: op = "ReadV"; break; - case ErrorInfo::ErrWrite: op = "Write"; break; - case ErrorInfo::ErrUnc: op = "Unclassified"; break; - }; - log->Debug( 2, "Operation on file %s encountered an error: %s " - "while %s", i->file->GetURL().c_str(), - i->status->ToStr().c_str(), op.c_str() ); - break; - } - - //---------------------------------------------------------------------- - // Got a copy begin event - //---------------------------------------------------------------------- - case EvCopyBeg: - { - CopyBInfo *i = (CopyBInfo*)evData; - log->Debug( 2, "Copy operation started: origin %s, target: %s ", - i->transfer.origin->GetURL().c_str(), - i->transfer.target->GetURL().c_str() ); - break; - } - - //---------------------------------------------------------------------- - // Got a copy end event - //---------------------------------------------------------------------- - case EvCopyEnd: - { - CopyEInfo *i = (CopyEInfo*)evData; - std::string timeStart = Utils::TimeToString( i->bTOD.tv_sec ); - std::string timeEnd = Utils::TimeToString( i->eTOD.tv_sec ); - log->Debug( 2, "Copy operation ended: origin: %s, target: %s, " - "start time %s, end time: %s, status: %s", - i->transfer.origin->GetURL().c_str(), - i->transfer.target->GetURL().c_str(), - timeStart.c_str(), timeEnd.c_str(), - i->status->ToStr().c_str() ); - break; - } - - //---------------------------------------------------------------------- - // Got a copy end event - //---------------------------------------------------------------------- - case EvCheckSum: - { - CheckSumInfo *i = (CheckSumInfo*)evData; - log->Debug( 2, "Checksum for transfer: origin: %s, target: %s, " - "checksum %s, is ok: %d", - i->transfer.origin->GetURL().c_str(), - i->transfer.target->GetURL().c_str(), - i->cksum.c_str(), (int)i->isOK ); - log->Debug( 2, "Checksum for transfer: origin: %s, target: %s, " - "us elapsed at origin %ld, us leapsed at target: %ld", - i->transfer.origin->GetURL().c_str(), - i->transfer.target->GetURL().c_str(), - i->oTime, i->tTime ); - break; - } - } - } - - private: - std::string pExec; - std::string pParam; - bool pInitialized; -}; - -//------------------------------------------------------------------------------ -// C-mangled symbol for dlopen -//------------------------------------------------------------------------------ -extern "C" -{ - void *XrdClGetMonitor( const char *exec, const char *param ) - { - XrdCl::Log *log = XrdClTests::TestEnv::GetLog(); - log->Debug( 2, "Constructing monitoring, exec %s, param %s", - exec, param ? param : "" ); - return new MonitorTest( exec, param ? param : "" ); - } -} diff --git a/tests/XrdClTests/PollerTest.cc b/tests/XrdClTests/PollerTest.cc deleted file mode 100644 index cb5ecfb7bfa..00000000000 --- a/tests/XrdClTests/PollerTest.cc +++ /dev/null @@ -1,280 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#include -#include "XrdCl/XrdClPoller.hh" -#include "Server.hh" -#include "Utils.hh" -#include "TestEnv.hh" -#include "CppUnitXrdHelpers.hh" -#include "XrdCl/XrdClURL.hh" -#include "XrdCl/XrdClUtils.hh" -#include "XrdCl/XrdClSocket.hh" - -#include - - -#include "XrdCl/XrdClPollerBuiltIn.hh" - -using namespace XrdClTests; - -//------------------------------------------------------------------------------ -// Declaration -//------------------------------------------------------------------------------ -class PollerTest: public CppUnit::TestCase -{ - public: - CPPUNIT_TEST_SUITE( PollerTest ); - CPPUNIT_TEST( FunctionTestBuiltIn ); - CPPUNIT_TEST_SUITE_END(); - void FunctionTestBuiltIn(); - void FunctionTest( XrdCl::Poller *poller ); -}; - -CPPUNIT_TEST_SUITE_REGISTRATION( PollerTest ); - -//------------------------------------------------------------------------------ -// Client handler -//------------------------------------------------------------------------------ -class RandomPumpHandler: public ClientHandler -{ - public: - //-------------------------------------------------------------------------- - // Pump some random data through the socket - //-------------------------------------------------------------------------- - virtual void HandleConnection( int socket ) - { - XrdCl::ScopedDescriptor scopetDesc( socket ); - XrdCl::Log *log = TestEnv::GetLog(); - - uint8_t packets = random() % 100; - uint16_t packetSize; - char buffer[50000]; - log->Debug( 1, "Sending %d packets to the client", packets ); - - for( int i = 0; i < packets; ++i ) - { - packetSize = random() % 50000; - log->Dump( 1, "Sending %d packet, %d bytes of data", i, packetSize ); - if( Utils::GetRandomBytes( buffer, packetSize ) != packetSize ) - { - log->Error( 1, "Unable to get %d bytes of random data", packetSize ); - return; - } - - if( ::write( socket, buffer, packetSize ) != packetSize ) - { - log->Error( 1, "Unable to send the %d bytes of random data", - packetSize ); - return; - } - UpdateSentData( buffer, packetSize ); - } - } -}; - -//------------------------------------------------------------------------------ -// Client handler factory -//------------------------------------------------------------------------------ -class RandomPumpHandlerFactory: public ClientHandlerFactory -{ - public: - virtual ClientHandler *CreateHandler() - { - return new RandomPumpHandler(); - } -}; - -//------------------------------------------------------------------------------ -// Socket listener -//------------------------------------------------------------------------------ -class SocketHandler: public XrdCl::SocketHandler -{ - public: - //-------------------------------------------------------------------------- - // Initializer - //-------------------------------------------------------------------------- - virtual void Initialize( XrdCl::Poller *poller ) - { - pPoller = poller; - } - - //-------------------------------------------------------------------------- - // Handle an event - //-------------------------------------------------------------------------- - virtual void Event( uint8_t type, - XrdCl::Socket *socket ) - { - //------------------------------------------------------------------------ - // Read event - //------------------------------------------------------------------------ - if( type & ReadyToRead ) - { - char buffer[50000]; - int desc = socket->GetFD(); - ssize_t ret = 0; - - while( 1 ) - { - char *current = buffer; - uint32_t spaceLeft = 50000; - while( (spaceLeft > 0) && - ((ret = ::read( desc, current, spaceLeft )) > 0) ) - { - current += ret; - spaceLeft -= ret; - } - - UpdateTransferMap( socket->GetSockName(), buffer, 50000-spaceLeft ); - - if( ret == 0 ) - { - pPoller->RemoveSocket( socket ); - return; - } - - if( ret < 0 ) - { - if( errno != EAGAIN && errno != EWOULDBLOCK ) - pPoller->EnableReadNotification( socket, false ); - return; - } - } - } - - //------------------------------------------------------------------------ - // Timeout - //------------------------------------------------------------------------ - if( type & ReadTimeOut ) - pPoller->RemoveSocket( socket ); - } - - //-------------------------------------------------------------------------- - // Update the checksums - //-------------------------------------------------------------------------- - void UpdateTransferMap( const std::string &sockName, - const void *buffer, - uint32_t size ) - { - //------------------------------------------------------------------------ - // Check if we have an entry in the map - //------------------------------------------------------------------------ - std::pair res; - Server::TransferMap::iterator it; - res = pMap.insert( std::make_pair( sockName, std::make_pair( 0, 0 ) ) ); - it = res.first; - if( res.second == true ) - { - it->second.first = 0; - it->second.second = Utils::ComputeCRC32( 0, 0 ); - } - - //------------------------------------------------------------------------ - // Update the entry - //------------------------------------------------------------------------ - it->second.first += size; - it->second.second = Utils::UpdateCRC32( it->second.second, buffer, size ); - } - - //-------------------------------------------------------------------------- - //! Get the stats of the received data - //-------------------------------------------------------------------------- - std::pair GetReceivedStats( - const std::string sockName ) const - { - Server::TransferMap::const_iterator it = pMap.find( sockName ); - if( it == pMap.end() ) - return std::make_pair( 0, 0 ); - return it->second; - } - - private: - Server::TransferMap pMap; - XrdCl::Poller *pPoller; -}; - -//------------------------------------------------------------------------------ -// Test the functionality of a poller -//------------------------------------------------------------------------------ -void PollerTest::FunctionTest( XrdCl::Poller *poller ) -{ - using XrdCl::Socket; - using XrdCl::URL; - - //---------------------------------------------------------------------------- - // Initialize - //---------------------------------------------------------------------------- - Server server( Server::Both ); - Socket s[3]; - CPPUNIT_ASSERT( server.Setup( 9999, 3, new RandomPumpHandlerFactory() ) ); - CPPUNIT_ASSERT( server.Start() ); - CPPUNIT_ASSERT( poller->Initialize() ); - CPPUNIT_ASSERT( poller->Start() ); - - //---------------------------------------------------------------------------- - // Connect the sockets - //---------------------------------------------------------------------------- - SocketHandler *handler = new SocketHandler(); - for( int i = 0; i < 3; ++i ) - { - CPPUNIT_ASSERT_XRDST( s[i].Initialize() ); - CPPUNIT_ASSERT_XRDST( s[i].Connect( "localhost", 9999 ) ); - CPPUNIT_ASSERT( poller->AddSocket( &s[i], handler ) ); - CPPUNIT_ASSERT( poller->EnableReadNotification( &s[i], true, 60 ) ); - CPPUNIT_ASSERT( poller->IsRegistered( &s[i] ) ); - } - - //---------------------------------------------------------------------------- - // All the business happens elsewhere so we have nothing better to do - // here that wait, otherwise server->stop will hang. - //---------------------------------------------------------------------------- - ::sleep(5); - - //---------------------------------------------------------------------------- - // Cleanup - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT( poller->Stop() ); - CPPUNIT_ASSERT( server.Stop() ); - CPPUNIT_ASSERT( poller->Finalize() ); - - std::pair stats[3]; - std::pair statsServ[3]; - for( int i = 0; i < 3; ++i ) - { - CPPUNIT_ASSERT( !poller->IsRegistered( &s[i] ) ); - stats[i] = handler->GetReceivedStats( s[i].GetSockName() ); - statsServ[i] = server.GetSentStats( s[i].GetSockName() ); - CPPUNIT_ASSERT( stats[i].first == statsServ[i].first ); - CPPUNIT_ASSERT( stats[i].second == statsServ[i].second ); - } - - for( int i = 0; i < 3; ++i ) - s[i].Close(); - - delete handler; -} - -//------------------------------------------------------------------------------ -// Test the functionality the built-in poller -//------------------------------------------------------------------------------ -void PollerTest::FunctionTestBuiltIn() -{ - XrdCl::Poller *poller = new XrdCl::PollerBuiltIn(); - FunctionTest( poller ); - delete poller; -} diff --git a/tests/XrdClTests/PostMasterTest.cc b/tests/XrdClTests/PostMasterTest.cc deleted file mode 100644 index bc2be5a4463..00000000000 --- a/tests/XrdClTests/PostMasterTest.cc +++ /dev/null @@ -1,473 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "TestEnv.hh" -#include "CppUnitXrdHelpers.hh" - -using namespace XrdClTests; - -//------------------------------------------------------------------------------ -// Declaration -//------------------------------------------------------------------------------ -class PostMasterTest: public CppUnit::TestCase -{ - public: - CPPUNIT_TEST_SUITE( PostMasterTest ); - CPPUNIT_TEST( FunctionalTest ); - CPPUNIT_TEST( PingIPv6 ); - CPPUNIT_TEST( ThreadingTest ); - CPPUNIT_TEST( MultiIPConnectionTest ); - CPPUNIT_TEST_SUITE_END(); - void FunctionalTest(); - void ThreadingTest(); - void PingIPv6(); - void MultiIPConnectionTest(); -}; - -CPPUNIT_TEST_SUITE_REGISTRATION( PostMasterTest ); - -//------------------------------------------------------------------------------ -// Tear down the post master -//------------------------------------------------------------------------------ -namespace -{ - class PostMasterFinalizer - { - public: - PostMasterFinalizer( XrdCl::PostMaster *pm = 0 ): pPostMaster(pm) {} - ~PostMasterFinalizer() - { - if( pPostMaster ) - { - pPostMaster->Stop(); - pPostMaster->Finalize(); - } - } - void Set( XrdCl::PostMaster *pm ) { pPostMaster = pm; } - XrdCl::PostMaster *Get() { return pPostMaster; } - - private: - XrdCl::PostMaster *pPostMaster; - }; -} - -//------------------------------------------------------------------------------ -// Message filter -//------------------------------------------------------------------------------ -class XrdFilter: public XrdCl::MessageFilter -{ - public: - XrdFilter( unsigned char id0 = 0, unsigned char id1 = 0 ) - { - streamId[0] = id0; - streamId[1] = id1; - } - - virtual bool Filter( const XrdCl::Message *msg ) - { - ServerResponse *resp = (ServerResponse *)msg->GetBuffer(); - if( resp->hdr.streamid[0] == streamId[0] && - resp->hdr.streamid[1] == streamId[1] ) - return true; - return false; - } - - virtual uint16_t GetSid() const - { - return (((uint16_t)streamId[1] << 8) | (uint16_t)streamId[0]); - } - - unsigned char streamId[2]; -}; - -//------------------------------------------------------------------------------ -// Thread argument passing helper -//------------------------------------------------------------------------------ -struct ArgHelper -{ - XrdCl::PostMaster *pm; - int index; -}; - -//------------------------------------------------------------------------------ -// Post master test thread -//------------------------------------------------------------------------------ -void *TestThreadFunc( void *arg ) -{ - using namespace XrdCl; - - std::string address; - Env *testEnv = TestEnv::GetEnv(); - CPPUNIT_ASSERT( testEnv->GetString( "MainServerURL", address ) ); - - ArgHelper *a = (ArgHelper*)arg; - URL host( address ); - XrdFilter f( a->index, 0 ); - - //---------------------------------------------------------------------------- - // Send the ping messages - //---------------------------------------------------------------------------- - time_t expires = time(0)+1200; - Message m; - m.Allocate( sizeof( ClientPingRequest ) ); - m.Zero(); - m.SetDescription( "kXR_ping ()" ); - ClientPingRequest *request = (ClientPingRequest *)m.GetBuffer(); - request->streamid[0] = a->index; - request->requestid = kXR_ping; - request->dlen = 0; - XRootDTransport::MarshallRequest( &m ); - - for( int i = 0; i < 100; ++i ) - { - request->streamid[1] = i; - CPPUNIT_ASSERT_XRDST( a->pm->Send( host, &m, false, expires ) ); - } - - //---------------------------------------------------------------------------- - // Receive the answers - //---------------------------------------------------------------------------- - for( int i = 0; i < 100; ++i ) - { - Message *m = 0; - f.streamId[1] = i; - CPPUNIT_ASSERT_XRDST( a->pm->Receive( host, m, &f, expires ) ); - ServerResponse *resp = (ServerResponse *)m->GetBuffer(); - CPPUNIT_ASSERT( resp != 0 ); - CPPUNIT_ASSERT( resp->hdr.status == kXR_ok ); - CPPUNIT_ASSERT( m->GetSize() == 8 ); - delete m; - } - return 0; -} - -//------------------------------------------------------------------------------ -// Threading test -//------------------------------------------------------------------------------ -void PostMasterTest::ThreadingTest() -{ - using namespace XrdCl; - PostMaster postMaster; - PostMasterFinalizer finalizer( &postMaster ); - postMaster.Initialize(); - postMaster.Start(); - - pthread_t thread[100]; - ArgHelper helper[100]; - - for( int i = 0; i < 100; ++i ) - { - helper[i].pm = &postMaster; - helper[i].index = i; - pthread_create( &thread[i], 0, TestThreadFunc, &helper[i] ); - } - - for( int i = 0; i < 100; ++i ) - pthread_join( thread[i], 0 ); -} - -//------------------------------------------------------------------------------ -// Test the functionality of a poller -//------------------------------------------------------------------------------ -void PostMasterTest::FunctionalTest() -{ - using namespace XrdCl; - - //---------------------------------------------------------------------------- - // Initialize the stuff - //---------------------------------------------------------------------------- - Env *env = DefaultEnv::GetEnv(); - Env *testEnv = TestEnv::GetEnv(); - env->PutInt( "TimeoutResolution", 1 ); - env->PutInt( "ConnectionWindow", 15 ); - - PostMaster postMaster; - PostMasterFinalizer finalizer( &postMaster ); - postMaster.Initialize(); - postMaster.Start(); - - std::string address; - CPPUNIT_ASSERT( testEnv->GetString( "MainServerURL", address ) ); - - //---------------------------------------------------------------------------- - // Send a message and wait for the answer - //---------------------------------------------------------------------------- - time_t expires = ::time(0)+1200; - Message m1, *m2 = 0; - XrdFilter f1( 1, 2 ); - URL host( address ); - - m1.Allocate( sizeof( ClientPingRequest ) ); - m1.Zero(); - - ClientPingRequest *request = (ClientPingRequest *)m1.GetBuffer(); - request->streamid[0] = 1; - request->streamid[1] = 2; - request->requestid = kXR_ping; - request->dlen = 0; - XRootDTransport::MarshallRequest( &m1 ); - - CPPUNIT_ASSERT_XRDST( postMaster.Send( host, &m1, false, expires ) ); - - CPPUNIT_ASSERT_XRDST( postMaster.Receive( host, m2, &f1, expires ) ); - ServerResponse *resp = (ServerResponse *)m2->GetBuffer(); - CPPUNIT_ASSERT( resp != 0 ); - CPPUNIT_ASSERT( resp->hdr.status == kXR_ok ); - CPPUNIT_ASSERT( m2->GetSize() == 8 ); - - //---------------------------------------------------------------------------- - // Wait for an answer to a message that has not been sent - test the - // reception timeout - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT_XRDST_NOTOK( postMaster.Receive( host, m2, &f1, 2 ), - errOperationExpired ); - - //---------------------------------------------------------------------------- - // Send out some stuff to a location where nothing listens - //---------------------------------------------------------------------------- - env->PutInt( "ConnectionWindow", 5 ); - env->PutInt( "ConnectionRetry", 3 ); - URL localhost1( "root://localhost:10101" ); - CPPUNIT_ASSERT_XRDST_NOTOK( postMaster.Send( localhost1, &m1, false, - ::time(0)+3 ), - errOperationExpired ); - CPPUNIT_ASSERT_XRDST_NOTOK( postMaster.Send( localhost1, &m1, false, - expires ), - errConnectionError ); - - //---------------------------------------------------------------------------- - // Test the transport queries - //---------------------------------------------------------------------------- - AnyObject nameObj, sidMgrObj; - Status st1, st2; - const char *name = 0; - SIDManager *sidMgr = 0; - - CPPUNIT_ASSERT_XRDST( postMaster.QueryTransport( host, - TransportQuery::Name, - nameObj ) ); - CPPUNIT_ASSERT_XRDST( postMaster.QueryTransport( host, - XRootDQuery::SIDManager, - sidMgrObj ) ); - - nameObj.Get( name ); - sidMgrObj.Get( sidMgr ); - - CPPUNIT_ASSERT( name ); - CPPUNIT_ASSERT( !::strcmp( name, "XRootD" ) ); - CPPUNIT_ASSERT( sidMgr ); - - postMaster.Stop(); - postMaster.Finalize(); - - //---------------------------------------------------------------------------- - // Reinitialize and try to do something - //---------------------------------------------------------------------------- - env->PutInt( "LoadBalancerTTL", 5 ); - postMaster.Initialize(); - postMaster.Start(); - - m2 = 0; - m1.Zero(); - - request = (ClientPingRequest *)m1.GetBuffer(); - request->streamid[0] = 1; - request->streamid[1] = 2; - request->requestid = kXR_ping; - request->dlen = 0; - XRootDTransport::MarshallRequest( &m1 ); - - CPPUNIT_ASSERT_XRDST( postMaster.Send( host, &m1, false, expires ) ); - - CPPUNIT_ASSERT_XRDST( postMaster.Receive( host, m2, &f1, expires ) ); - resp = (ServerResponse *)m2->GetBuffer(); - CPPUNIT_ASSERT( resp != 0 ); - CPPUNIT_ASSERT( resp->hdr.status == kXR_ok ); - CPPUNIT_ASSERT( m2->GetSize() == 8 ); - - //---------------------------------------------------------------------------- - // Sleep 10 secs waiting for iddle connection to be closed and see - // whether we can reconnect - //---------------------------------------------------------------------------- - sleep( 10 ); - CPPUNIT_ASSERT_XRDST( postMaster.Send( host, &m1, false, expires ) ); - - CPPUNIT_ASSERT_XRDST( postMaster.Receive( host, m2, &f1, expires ) ); - resp = (ServerResponse *)m2->GetBuffer(); - CPPUNIT_ASSERT( resp != 0 ); - CPPUNIT_ASSERT( resp->hdr.status == kXR_ok ); - CPPUNIT_ASSERT( m2->GetSize() == 8 ); -} - - -//------------------------------------------------------------------------------ -// Test the functionality of a poller -//------------------------------------------------------------------------------ -void PostMasterTest::PingIPv6() -{ - using namespace XrdCl; -#if 0 - //---------------------------------------------------------------------------- - // Initialize the stuff - //---------------------------------------------------------------------------- - PostMaster postMaster; - postMaster.Initialize(); - postMaster.Start(); - - //---------------------------------------------------------------------------- - // Build the message - //---------------------------------------------------------------------------- - Message m1, *m2 = 0; - XrdFilter f1( 1, 2 ); - URL localhost1( "root://[::1]" ); - URL localhost2( "root://[::127.0.0.1]" ); - - m1.Allocate( sizeof( ClientPingRequest ) ); - m1.Zero(); - - ClientPingRequest *request = (ClientPingRequest *)m1.GetBuffer(); - request->streamid[0] = 1; - request->streamid[1] = 2; - request->requestid = kXR_ping; - request->dlen = 0; - XRootDTransport::MarshallRequest( &m1 ); - - Status sc; - - //---------------------------------------------------------------------------- - // Send the message - localhost1 - //---------------------------------------------------------------------------- - sc = postMaster.Send( localhost1, &m1, false, 1200 ); - CPPUNIT_ASSERT( sc.IsOK() ); - - sc = postMaster.Receive( localhost1, m2, &f1, false, 1200 ); - CPPUNIT_ASSERT( sc.IsOK() ); - ServerResponse *resp = (ServerResponse *)m2->GetBuffer(); - CPPUNIT_ASSERT( resp != 0 ); - CPPUNIT_ASSERT( resp->hdr.status == kXR_ok ); - CPPUNIT_ASSERT( m2->GetSize() == 8 ); - - //---------------------------------------------------------------------------- - // Send the message - localhost2 - //---------------------------------------------------------------------------- - sc = postMaster.Send( localhost2, &m1, false, 1200 ); - CPPUNIT_ASSERT( sc.IsOK() ); - - sc = postMaster.Receive( localhost2, m2, &f1, 1200 ); - CPPUNIT_ASSERT( sc.IsOK() ); - resp = (ServerResponse *)m2->GetBuffer(); - CPPUNIT_ASSERT( resp != 0 ); - CPPUNIT_ASSERT( resp->hdr.status == kXR_ok ); - CPPUNIT_ASSERT( m2->GetSize() == 8 ); - - //---------------------------------------------------------------------------- - // Clean up - //---------------------------------------------------------------------------- - postMaster.Stop(); - postMaster.Finalize(); -#endif -} - -namespace -{ - //---------------------------------------------------------------------------- - // Create a ping message - //---------------------------------------------------------------------------- - XrdCl::Message *CreatePing( char streamID1, char streamID2 ) - { - using namespace XrdCl; - Message *m = new Message(); - m->Allocate( sizeof( ClientPingRequest ) ); - m->Zero(); - - ClientPingRequest *request = (ClientPingRequest *)m->GetBuffer(); - request->streamid[0] = streamID1; - request->streamid[1] = streamID2; - request->requestid = kXR_ping; - XRootDTransport::MarshallRequest( m ); - return m; - } -} - - -//------------------------------------------------------------------------------ -// Connection test -//------------------------------------------------------------------------------ -void PostMasterTest::MultiIPConnectionTest() -{ - using namespace XrdCl; - - //---------------------------------------------------------------------------- - // Initialize the stuff - //---------------------------------------------------------------------------- - Env *env = DefaultEnv::GetEnv(); - Env *testEnv = TestEnv::GetEnv(); - env->PutInt( "TimeoutResolution", 1 ); - env->PutInt( "ConnectionWindow", 5 ); - - PostMaster postMaster; - PostMasterFinalizer finalizer( &postMaster ); - postMaster.Initialize(); - postMaster.Start(); - - std::string address; - CPPUNIT_ASSERT( testEnv->GetString( "MultiIPServerURL", address ) ); - - time_t expires = ::time(0)+1200; - URL url1( "nenexistent" ); - URL url2( address ); - URL url3( address ); - url2.SetPort( 1111 ); - url3.SetPort( 1099 ); - - //---------------------------------------------------------------------------- - // Sent ping to a nonexistent host - //---------------------------------------------------------------------------- - Message *m = CreatePing( 1, 2 ); - CPPUNIT_ASSERT_XRDST_NOTOK( postMaster.Send( url1, m, false, expires ), - errInvalidAddr ); - - //---------------------------------------------------------------------------- - // Try on the wrong port - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT_XRDST_NOTOK( postMaster.Send( url2, m, false, expires ), - errConnectionError ); - - //---------------------------------------------------------------------------- - // Try on a good one - //---------------------------------------------------------------------------- - Message *m2 = 0; - XrdFilter f1( 1, 2 ); - - CPPUNIT_ASSERT_XRDST( postMaster.Send( url3, m, false, expires ) ); - CPPUNIT_ASSERT_XRDST( postMaster.Receive( url3, m2, &f1, expires ) ); - ServerResponse *resp = (ServerResponse *)m2->GetBuffer(); - CPPUNIT_ASSERT( resp != 0 ); - CPPUNIT_ASSERT( resp->hdr.status == kXR_ok ); - CPPUNIT_ASSERT( m2->GetSize() == 8 ); -} diff --git a/tests/XrdClTests/SocketTest.cc b/tests/XrdClTests/SocketTest.cc deleted file mode 100644 index 0996902e466..00000000000 --- a/tests/XrdClTests/SocketTest.cc +++ /dev/null @@ -1,236 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#include -#include -#include -#include "Server.hh" -#include "Utils.hh" -#include "TestEnv.hh" -#include "XrdCl/XrdClSocket.hh" -#include "XrdCl/XrdClUtils.hh" - -using namespace XrdClTests; - -//------------------------------------------------------------------------------ -// Client handler -//------------------------------------------------------------------------------ -class RandomHandler: public ClientHandler -{ - public: - virtual void HandleConnection( int socket ) - { - XrdCl::ScopedDescriptor scopedDesc( socket ); - XrdCl::Log *log = TestEnv::GetLog(); - - //------------------------------------------------------------------------ - // Pump some data - //------------------------------------------------------------------------ - uint8_t packets = random() % 100; - uint16_t packetSize; - char buffer[50000]; - log->Debug( 1, "Sending %d packets to the client", packets ); - - if( ::write( socket, &packets, 1 ) != 1 ) - { - log->Error( 1, "Unable to send the packet count" ); - return; - } - - for( int i = 0; i < packets; ++i ) - { - packetSize = random() % 50000; - log->Dump( 1, "Sending %d packet, %d bytes of data", i, packetSize ); - if( Utils::GetRandomBytes( buffer, packetSize ) != packetSize ) - { - log->Error( 1, "Unable to get %d bytes of random data", packetSize ); - return; - } - - if( ::write( socket, &packetSize, 2 ) != 2 ) - { - log->Error( 1, "Unable to send the packet size" ); - return; - } - if( ::write( socket, buffer, packetSize ) != packetSize ) - { - log->Error( 1, "Unable to send the %d bytes of random data", - packetSize ); - return; - } - UpdateSentData( buffer, packetSize ); - } - - //------------------------------------------------------------------------ - // Receive some data - //------------------------------------------------------------------------ - ssize_t totalRead; - char *current; - - if( ::read( socket, &packets, 1 ) != 1 ) - { - log->Error( 1, "Unable to receive the packet count" ); - return; - } - - log->Debug( 1, "Receivng %d packets from the client", packets ); - - for( int i = 0; i < packets; ++i ) - { - totalRead = 0; - current = buffer; - if( ::read( socket, &packetSize, 2 ) != 2 ) - { - log->Error( 1, "Unable to receive the packet size" ); - return; - } - - while(1) - { - ssize_t dataRead = ::read( socket, current, packetSize ); - if( dataRead <= 0 ) - { - log->Error( 1, "Unable to receive the %d bytes of data", - packetSize ); - return; - } - - totalRead += dataRead; - current += dataRead; - if( totalRead == packetSize ) - break; - } - UpdateReceivedData( buffer, packetSize ); - log->Dump( 1, "Received %d bytes from the client", packetSize ); - } - } -}; - -//------------------------------------------------------------------------------ -// Client handler factory -//------------------------------------------------------------------------------ -class RandomHandlerFactory: public ClientHandlerFactory -{ - public: - virtual ClientHandler *CreateHandler() - { - return new RandomHandler(); - } -}; - -//------------------------------------------------------------------------------ -// Declaration -//------------------------------------------------------------------------------ -class SocketTest: public CppUnit::TestCase -{ - public: - CPPUNIT_TEST_SUITE( SocketTest ); - CPPUNIT_TEST( TransferTest ); - CPPUNIT_TEST_SUITE_END(); - void TransferTest(); -}; - -CPPUNIT_TEST_SUITE_REGISTRATION( SocketTest ); - -//------------------------------------------------------------------------------ -// Test the transfer -//------------------------------------------------------------------------------ -void SocketTest::TransferTest() -{ - using namespace XrdCl; - srandom( time(0) ); - Server serv( Server::Both ); - Socket sock; - - //---------------------------------------------------------------------------- - // Start up the server and connect to it - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT( serv.Setup( 9999, 1, new RandomHandlerFactory() ) ); - CPPUNIT_ASSERT( serv.Start() ); - - CPPUNIT_ASSERT( sock.GetStatus() == Socket::Disconnected ); - CPPUNIT_ASSERT( sock.Initialize( AF_INET6 ).IsOK() ); - CPPUNIT_ASSERT( sock.Connect( "localhost", 9999 ).IsOK() ); - CPPUNIT_ASSERT( sock.GetStatus() == Socket::Connected ); - - //---------------------------------------------------------------------------- - // Get the number of packets - //---------------------------------------------------------------------------- - uint8_t packets; - uint32_t bytesTransmitted; - uint16_t packetSize; - Status sc; - char buffer[50000]; - uint64_t sentCounter = 0; - uint32_t sentChecksum = ::Utils::ComputeCRC32( 0, 0 ); - uint64_t receivedCounter = 0; - uint32_t receivedChecksum = ::Utils::ComputeCRC32( 0, 0 ); - sc = sock.ReadRaw( &packets, 1, 60, bytesTransmitted ); - CPPUNIT_ASSERT( sc.status == stOK ); - - //---------------------------------------------------------------------------- - // Read each packet - //---------------------------------------------------------------------------- - for( int i = 0; i < packets; ++i ) - { - sc = sock.ReadRaw( &packetSize, 2, 60, bytesTransmitted ); - CPPUNIT_ASSERT( sc.status == stOK ); - sc = sock.ReadRaw( buffer, packetSize, 60, bytesTransmitted ); - CPPUNIT_ASSERT( sc.status == stOK ); - receivedCounter += bytesTransmitted; - receivedChecksum = ::Utils::UpdateCRC32( receivedChecksum, buffer, - bytesTransmitted ); - } - - //---------------------------------------------------------------------------- - // Send the number of packets - //---------------------------------------------------------------------------- - packets = random() % 100; - - sc = sock.WriteRaw( &packets, 1, 60, bytesTransmitted ); - CPPUNIT_ASSERT( (sc.status == stOK) && (bytesTransmitted == 1) ); - - for( int i = 0; i < packets; ++i ) - { - packetSize = random() % 50000; - CPPUNIT_ASSERT( ::Utils::GetRandomBytes( buffer, packetSize ) == packetSize ); - - sc = sock.WriteRaw( (char *)&packetSize, 2, 60, bytesTransmitted ); - CPPUNIT_ASSERT( (sc.status == stOK) && (bytesTransmitted == 2) ); - sc = sock.WriteRaw( buffer, packetSize, 60, bytesTransmitted ); - CPPUNIT_ASSERT( (sc.status == stOK) && (bytesTransmitted == packetSize) ); - sentCounter += bytesTransmitted; - sentChecksum = ::Utils::UpdateCRC32( sentChecksum, buffer, - bytesTransmitted ); - } - - //---------------------------------------------------------------------------- - // Check the counters and the checksums - //---------------------------------------------------------------------------- - std::string socketName = sock.GetSockName(); - - sock.Close(); - CPPUNIT_ASSERT( serv.Stop() ); - - std::pair sent = serv.GetSentStats( socketName ); - std::pair received = serv.GetReceivedStats( socketName ); - CPPUNIT_ASSERT( sentCounter == received.first ); - CPPUNIT_ASSERT( receivedCounter == sent.first ); - CPPUNIT_ASSERT( sentChecksum == received.second ); - CPPUNIT_ASSERT( receivedChecksum == sent.second ); -} diff --git a/tests/XrdClTests/ThreadingTest.cc b/tests/XrdClTests/ThreadingTest.cc deleted file mode 100644 index 1bf35896538..00000000000 --- a/tests/XrdClTests/ThreadingTest.cc +++ /dev/null @@ -1,348 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#include -#include "TestEnv.hh" -#include "Utils.hh" -#include "CppUnitXrdHelpers.hh" -#include "XrdCl/XrdClFile.hh" -#include "XrdCl/XrdClDefaultEnv.hh" -#include "XrdCl/XrdClUtils.hh" -#include -#include -#include -#include -#include -#include "XrdCks/XrdCksData.hh" - -//------------------------------------------------------------------------------ -// Thread helper struct -//------------------------------------------------------------------------------ -struct ThreadData -{ - ThreadData(): - file( 0 ), startOffset( 0 ), length( 0 ), checkSum( 0 ), - firstBlockChecksum(0) {} - XrdCl::File *file; - uint64_t startOffset; - uint64_t length; - uint32_t checkSum; - uint32_t firstBlockChecksum; -}; - -const uint32_t MB = 1024*1024; - -//------------------------------------------------------------------------------ -// Declaration -//------------------------------------------------------------------------------ -class ThreadingTest: public CppUnit::TestCase -{ - public: - typedef void (*TransferCallback)( ThreadData *data ); - CPPUNIT_TEST_SUITE( ThreadingTest ); - CPPUNIT_TEST( ReadTest ); - CPPUNIT_TEST( MultiStreamReadTest ); - CPPUNIT_TEST( ReadForkTest ); - CPPUNIT_TEST( MultiStreamReadForkTest ); - CPPUNIT_TEST( MultiStreamReadMonitorTest ); - CPPUNIT_TEST_SUITE_END(); - void ReadTestFunc( TransferCallback transferCallback ); - void ReadTest(); - void MultiStreamReadTest(); - void ReadForkTest(); - void MultiStreamReadForkTest(); - void MultiStreamReadMonitorTest(); -}; - -CPPUNIT_TEST_SUITE_REGISTRATION( ThreadingTest ); - - -//------------------------------------------------------------------------------ -// Reader thread -//------------------------------------------------------------------------------ -void *DataReader( void *arg ) -{ - using namespace XrdClTests; - - ThreadData *td = (ThreadData*)arg; - - uint64_t offset = td->startOffset; - uint64_t dataLeft = td->length; - uint64_t chunkSize = 0; - uint32_t bytesRead = 0; - char *buffer = new char[4*MB]; - - while( 1 ) - { - chunkSize = 4*MB; - if( chunkSize > dataLeft ) - chunkSize = dataLeft; - - if( chunkSize == 0 ) - break; - - CPPUNIT_ASSERT_XRDST( td->file->Read( offset, chunkSize, buffer, - bytesRead ) ); - - offset += bytesRead; - dataLeft -= bytesRead; - td->checkSum = Utils::UpdateCRC32( td->checkSum, buffer, bytesRead ); - } - - delete [] buffer; - - return 0; -} - -//------------------------------------------------------------------------------ -// Read test -//------------------------------------------------------------------------------ -void ThreadingTest::ReadTestFunc( TransferCallback transferCallback ) -{ - using namespace XrdCl; - - //---------------------------------------------------------------------------- - // Initialize - //---------------------------------------------------------------------------- - Env *testEnv = XrdClTests::TestEnv::GetEnv(); - - std::string address; - std::string dataPath; - - CPPUNIT_ASSERT( testEnv->GetString( "MainServerURL", address ) ); - CPPUNIT_ASSERT( testEnv->GetString( "DataPath", dataPath ) ); - - URL url( address ); - CPPUNIT_ASSERT( url.IsValid() ); - - std::string fileUrl[5]; - std::string path[5]; - path[0] = dataPath + "/1db882c8-8cd6-4df1-941f-ce669bad3458.dat"; - path[1] = dataPath + "/3c9a9dd8-bc75-422c-b12c-f00604486cc1.dat"; - path[2] = dataPath + "/7235b5d1-cede-4700-a8f9-596506b4cc38.dat"; - path[3] = dataPath + "/7e480547-fe1a-4eaf-a210-0f3927751a43.dat"; - path[4] = dataPath + "/89120cec-5244-444c-9313-703e4bee72de.dat"; - - for( int i = 0; i < 5; ++i ) - fileUrl[i] = address + "/" + path[i]; - - //---------------------------------------------------------------------------- - // Open and stat the files - //---------------------------------------------------------------------------- - ThreadData threadData[20]; - - for( int i = 0; i < 5; ++i ) - { - File *f = new File(); - StatInfo *si = 0; - CPPUNIT_ASSERT_XRDST( f->Open( fileUrl[i], OpenFlags::Read ) ); - CPPUNIT_ASSERT_XRDST( f->Stat( false, si ) ); - CPPUNIT_ASSERT( si ); - CPPUNIT_ASSERT( si->TestFlags( StatInfo::IsReadable ) ); - - uint64_t step = si->GetSize()/4; - - for( int j = 0; j < 4; ++j ) - { - threadData[j*5+i].file = f; - threadData[j*5+i].startOffset = j*step; - threadData[j*5+i].length = step; - threadData[j*5+i].checkSum = XrdClTests::Utils::GetInitialCRC32(); - - - //------------------------------------------------------------------------ - // Get the checksum of the first 4MB block at the startOffser - this - // will be verified by the forking test - //------------------------------------------------------------------------ - uint64_t offset = threadData[j*5+i].startOffset; - char *buffer = new char[4*MB]; - uint32_t bytesRead = 0; - - CPPUNIT_ASSERT_XRDST( f->Read( offset, 4*MB, buffer, bytesRead ) ); - CPPUNIT_ASSERT( bytesRead == 4*MB ); - threadData[j*5+i].firstBlockChecksum = - XrdClTests::Utils::ComputeCRC32( buffer, 4*MB ); - delete [] buffer; - } - - threadData[15+i].length = si->GetSize() - threadData[15+i].startOffset; - delete si; - } - - //---------------------------------------------------------------------------- - // Spawn the threads and wait for them to finish - //---------------------------------------------------------------------------- - pthread_t thread[20]; - for( int i = 0; i < 20; ++i ) - CPPUNIT_ASSERT_PTHREAD( pthread_create( &(thread[i]), 0, - ::DataReader, &(threadData[i]) ) ); - - if( transferCallback ) - (*transferCallback)( threadData ); - - for( int i = 0; i < 20; ++i ) - CPPUNIT_ASSERT_PTHREAD( pthread_join( thread[i], 0 ) ); - - //---------------------------------------------------------------------------- - // Glue up and compare the checksums - //---------------------------------------------------------------------------- - uint32_t checkSums[5]; - for( int i = 0; i < 5; ++i ) - { - //-------------------------------------------------------------------------- - // Calculate the local check sum - //-------------------------------------------------------------------------- - checkSums[i] = threadData[i].checkSum; - for( int j = 1; j < 4; ++j ) - { - checkSums[i] = XrdClTests::Utils::CombineCRC32( checkSums[i], - threadData[j*5+i].checkSum, - threadData[j*5+i].length ); - } - - char crcBuff[9]; - XrdCksData crc; crc.Set( &checkSums[i], 4 ); crc.Get( crcBuff, 9 ); - std::string transferSum = "zcrc32:"; transferSum += crcBuff; - - //-------------------------------------------------------------------------- - // Get the checksum - //-------------------------------------------------------------------------- - std::string remoteSum, dataServer; - threadData[i].file->GetProperty( "DataServer", dataServer ); - CPPUNIT_ASSERT_XRDST( Utils::GetRemoteCheckSum( - remoteSum, "zcrc32", dataServer, path[i] ) ); - CPPUNIT_ASSERT( remoteSum == transferSum ); - CPPUNIT_ASSERT_MESSAGE( path[i], remoteSum == transferSum ); - } - - //---------------------------------------------------------------------------- - // Close the files - //---------------------------------------------------------------------------- - for( int i = 0; i < 5; ++i ) - { - CPPUNIT_ASSERT_XRDST( threadData[i].file->Close() ); - delete threadData[i].file; - } -} - - -//------------------------------------------------------------------------------ -// Read test -//------------------------------------------------------------------------------ -void ThreadingTest::ReadTest() -{ - ReadTestFunc(0); -} - -//------------------------------------------------------------------------------ -// Multistream read test -//------------------------------------------------------------------------------ -void ThreadingTest::MultiStreamReadTest() -{ - XrdCl::Env *env = XrdCl::DefaultEnv::GetEnv(); - env->PutInt( "SubStreamsPerChannel", 4 ); - ReadTestFunc(0); -} - -//------------------------------------------------------------------------------ -// Child - read some data from each of the open files and close them -//------------------------------------------------------------------------------ -int runChild( ThreadData *td ) -{ - XrdCl::Log *log = XrdClTests::TestEnv::GetLog(); - log->Debug( 1, "Running the child" ); - - for( int i = 0; i < 20; ++i ) - { - uint64_t offset = td[i].startOffset; - char *buffer = new char[4*MB]; - uint32_t bytesRead = 0; - - CPPUNIT_ASSERT_XRDST( td[i].file->Read( offset, 4*MB, buffer, bytesRead ) ); - CPPUNIT_ASSERT( bytesRead == 4*MB ); - CPPUNIT_ASSERT( td[i].firstBlockChecksum == - XrdClTests::Utils::ComputeCRC32( buffer, 4*MB ) ); - delete [] buffer; - } - - for( int i = 0; i < 5; ++i ) - { - CPPUNIT_ASSERT_XRDST( td[i].file->Close() ); - delete td[i].file; - } - - return 0; -} - -//------------------------------------------------------------------------------ -// Forking function -//------------------------------------------------------------------------------ -void forkAndRead( ThreadData *data ) -{ - XrdCl::Log *log = XrdClTests::TestEnv::GetLog(); - for( int chld = 0; chld < 5; ++chld ) - { - sleep(10); - pid_t pid; - log->Debug( 1, "About to fork" ); - CPPUNIT_ASSERT_ERRNO( (pid=fork()) != -1 ); - - if( !pid ) _exit( runChild( data ) ); - - log->Debug( 1, "Forked successfully, pid of the child: %d", pid ); - int status; - log->Debug( 1, "Waiting for the child" ); - CPPUNIT_ASSERT_ERRNO( waitpid( pid, &status, 0 ) != -1 ); - log->Debug( 1, "Wait done, status: %d", status ); - CPPUNIT_ASSERT( WIFEXITED( status ) ); - CPPUNIT_ASSERT( WEXITSTATUS( status ) == 0 ); - } -} - -//------------------------------------------------------------------------------ -// Read fork test -//------------------------------------------------------------------------------ -void ThreadingTest::ReadForkTest() -{ - XrdCl::Env *env = XrdCl::DefaultEnv::GetEnv(); - env->PutInt( "RunForkHandler", 1 ); - ReadTestFunc(&forkAndRead); -} - -//------------------------------------------------------------------------------ -// Multistream read fork test -//------------------------------------------------------------------------------ -void ThreadingTest::MultiStreamReadForkTest() -{ - XrdCl::Env *env = XrdCl::DefaultEnv::GetEnv(); - env->PutInt( "SubStreamsPerChannel", 4 ); - env->PutInt( "RunForkHandler", 1 ); - ReadTestFunc(&forkAndRead); -} - -//------------------------------------------------------------------------------ -// Multistream read monitor -//------------------------------------------------------------------------------ -void ThreadingTest::MultiStreamReadMonitorTest() -{ - XrdCl::Env *env = XrdCl::DefaultEnv::GetEnv(); - env->PutString( "ClientMonitor", "./libXrdClTestMonitor.so" ); - env->PutString( "ClientMonitorParam", "TestParam" ); - env->PutInt( "SubStreamsPerChannel", 4 ); - ReadTestFunc(0); -} diff --git a/tests/XrdClTests/UtilsTest.cc b/tests/XrdClTests/UtilsTest.cc deleted file mode 100644 index d7d14654c22..00000000000 --- a/tests/XrdClTests/UtilsTest.cc +++ /dev/null @@ -1,461 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// This file is part of the XRootD software suite. -// -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -// -// In applying this licence, CERN does not waive the privileges and immunities -// granted to it by virtue of its status as an Intergovernmental Organization -// or submit itself to any jurisdiction. -//------------------------------------------------------------------------------ - -#include -#include "CppUnitXrdHelpers.hh" -#include "XrdCl/XrdClURL.hh" -#include "XrdCl/XrdClAnyObject.hh" -#include "XrdCl/XrdClTaskManager.hh" -#include "XrdCl/XrdClSIDManager.hh" -#include "XrdCl/XrdClPropertyList.hh" - -//------------------------------------------------------------------------------ -// Declaration -//------------------------------------------------------------------------------ -class UtilsTest: public CppUnit::TestCase -{ - public: - CPPUNIT_TEST_SUITE( UtilsTest ); - CPPUNIT_TEST( URLTest ); - CPPUNIT_TEST( AnyTest ); - CPPUNIT_TEST( TaskManagerTest ); - CPPUNIT_TEST( SIDManagerTest ); - CPPUNIT_TEST( PropertyListTest ); - CPPUNIT_TEST_SUITE_END(); - void URLTest(); - void AnyTest(); - void TaskManagerTest(); - void SIDManagerTest(); - void PropertyListTest(); -}; - -CPPUNIT_TEST_SUITE_REGISTRATION( UtilsTest ); - -//------------------------------------------------------------------------------ -// URL test -//------------------------------------------------------------------------------ -void UtilsTest::URLTest() -{ - XrdCl::URL url1( "root://user1:passwd1@host1:123//path?param1=val1¶m2=val2" ); - XrdCl::URL url2( "root://user1@host1//path?param1=val1¶m2=val2" ); - XrdCl::URL url3( "root://host1" ); - XrdCl::URL url4( "root://user1:passwd1@[::1]:123//path?param1=val1¶m2=val2" ); - XrdCl::URL url5( "root://user1@192.168.1.1:123//path?param1=val1¶m2=val2" ); - XrdCl::URL url6( "root://[::1]" ); - XrdCl::URL url7( "root://lxfsra02a08.cern.ch:1095//eos/dev/SMWZd3pdExample_NTUP_SMWZ.526666._000073.root.1?&cap.sym=sfdDqALWo3W3tWUJ2O5XwQ5GG8U=&cap.msg=eGj/mh+9TrecFBAZBNr/nLau4p0kjlEOjc1JC+9DVjLL1Tq+g311485W0baMBAsM#W8lNFdVQcKNAu8K5yVskIcLDOEi6oNpvoxDA1DN4oCxtHR6LkOWhO91MLn/ZosJ5#Dc7aeBCIz/kKs261mnL4dJeUu6r25acCn4vhyp8UKyL1cVmmnyBnjqe6tz28qFO2#0fQHrHf6Z9N0MNhw1fplYjpGeNwFH2jQSfSo24zSZKGa/PKClGYnXoXBWDGU1spm#kJsGGrErhBHYvLq3eS+jEBr8l+c1BhCQU7ZaLZiyaKOnspYnR/Tw7bMrooWMh7eL#&mgm.logid=766877e6-9874-11e1-a77f-003048cf8cd8&mgm.recdcdcdcdplicaindex=0&mgm.replicahead=0" ); - XrdCl::URL url8( "/etc/passwd" ); - XrdCl::URL url9( "localhost:1094//data/cb4aacf1-6f28-42f2-b68a-90a73460f424.dat" ); - XrdCl::URL url10( "localhost:1094/?test=123" ); - - XrdCl::URL urlInvalid1( "root://user1:passwd1@host1:asd//path?param1=val1¶m2=val2" ); - XrdCl::URL urlInvalid2( "root://user1:passwd1host1:123//path?param1=val1¶m2=val2" ); - XrdCl::URL urlInvalid3( "root:////path?param1=val1¶m2=val2" ); - XrdCl::URL urlInvalid4( "root://@//path?param1=val1¶m2=val2" ); - XrdCl::URL urlInvalid5( "root://:@//path?param1=val1¶m2=val2" ); - XrdCl::URL urlInvalid6( "root://" ); - XrdCl::URL urlInvalid7( "://asds" ); - XrdCl::URL urlInvalid8( "root://asd@://path?param1=val1¶m2=val2" ); - - //---------------------------------------------------------------------------- - // Full url - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT( url1.IsValid() == true ); - CPPUNIT_ASSERT( url1.GetProtocol() == "root" ); - CPPUNIT_ASSERT( url1.GetUserName() == "user1" ); - CPPUNIT_ASSERT( url1.GetPassword() == "passwd1" ); - CPPUNIT_ASSERT( url1.GetHostName() == "host1" ); - CPPUNIT_ASSERT( url1.GetPort() == 123 ); - CPPUNIT_ASSERT( url1.GetPathWithParams() == "/path?param1=val1¶m2=val2" ); - CPPUNIT_ASSERT( url1.GetPath() == "/path" ); - CPPUNIT_ASSERT( url1.GetParams().size() == 2 ); - - XrdCl::URL::ParamsMap::const_iterator it; - it = url1.GetParams().find( "param1" ); - CPPUNIT_ASSERT( it != url1.GetParams().end() ); - CPPUNIT_ASSERT( it->second == "val1" ); - it = url1.GetParams().find( "param2" ); - CPPUNIT_ASSERT( it != url1.GetParams().end() ); - CPPUNIT_ASSERT( it->second == "val2" ); - it = url1.GetParams().find( "param3" ); - CPPUNIT_ASSERT( it == url1.GetParams().end() ); - - //---------------------------------------------------------------------------- - // No password, no port - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT( url2.IsValid() == true ); - CPPUNIT_ASSERT( url2.GetProtocol() == "root" ); - CPPUNIT_ASSERT( url2.GetUserName() == "user1" ); - CPPUNIT_ASSERT( url2.GetPassword() == "" ); - CPPUNIT_ASSERT( url2.GetHostName() == "host1" ); - CPPUNIT_ASSERT( url2.GetPort() == 1094 ); - CPPUNIT_ASSERT( url2.GetPath() == "/path" ); - CPPUNIT_ASSERT( url2.GetPathWithParams() == "/path?param1=val1¶m2=val2" ); - CPPUNIT_ASSERT( url1.GetParams().size() == 2 ); - - it = url2.GetParams().find( "param1" ); - CPPUNIT_ASSERT( it != url2.GetParams().end() ); - CPPUNIT_ASSERT( it->second == "val1" ); - it = url2.GetParams().find( "param2" ); - CPPUNIT_ASSERT( it != url2.GetParams().end() ); - CPPUNIT_ASSERT( it->second == "val2" ); - it = url2.GetParams().find( "param3" ); - CPPUNIT_ASSERT( it == url2.GetParams().end() ); - - //---------------------------------------------------------------------------- - // Just the host and the protocol - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT( url3.IsValid() == true ); - CPPUNIT_ASSERT( url3.GetProtocol() == "root" ); - CPPUNIT_ASSERT( url3.GetUserName() == "" ); - CPPUNIT_ASSERT( url3.GetPassword() == "" ); - CPPUNIT_ASSERT( url3.GetHostName() == "host1" ); - CPPUNIT_ASSERT( url3.GetPort() == 1094 ); - CPPUNIT_ASSERT( url3.GetPath() == "" ); - CPPUNIT_ASSERT( url3.GetPathWithParams() == "" ); - CPPUNIT_ASSERT( url3.GetParams().size() == 0 ); - - //---------------------------------------------------------------------------- - // Full url - IPv6 - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT( url4.IsValid() == true ); - CPPUNIT_ASSERT( url4.GetProtocol() == "root" ); - CPPUNIT_ASSERT( url4.GetUserName() == "user1" ); - CPPUNIT_ASSERT( url4.GetPassword() == "passwd1" ); - CPPUNIT_ASSERT( url4.GetHostName() == "[::1]" ); - CPPUNIT_ASSERT( url4.GetPort() == 123 ); - CPPUNIT_ASSERT( url4.GetPathWithParams() == "/path?param1=val1¶m2=val2" ); - CPPUNIT_ASSERT( url4.GetPath() == "/path" ); - CPPUNIT_ASSERT( url4.GetParams().size() == 2 ); - - it = url4.GetParams().find( "param1" ); - CPPUNIT_ASSERT( it != url4.GetParams().end() ); - CPPUNIT_ASSERT( it->second == "val1" ); - it = url4.GetParams().find( "param2" ); - CPPUNIT_ASSERT( it != url4.GetParams().end() ); - CPPUNIT_ASSERT( it->second == "val2" ); - it = url4.GetParams().find( "param3" ); - CPPUNIT_ASSERT( it == url4.GetParams().end() ); - - //---------------------------------------------------------------------------- - // No password, no port - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT( url5.IsValid() == true ); - CPPUNIT_ASSERT( url5.GetProtocol() == "root" ); - CPPUNIT_ASSERT( url5.GetUserName() == "user1" ); - CPPUNIT_ASSERT( url5.GetPassword() == "" ); - CPPUNIT_ASSERT( url5.GetHostName() == "192.168.1.1" ); - CPPUNIT_ASSERT( url5.GetPort() == 123 ); - CPPUNIT_ASSERT( url5.GetPath() == "/path" ); - CPPUNIT_ASSERT( url5.GetPathWithParams() == "/path?param1=val1¶m2=val2" ); - CPPUNIT_ASSERT( url5.GetParams().size() == 2 ); - - it = url5.GetParams().find( "param1" ); - CPPUNIT_ASSERT( it != url5.GetParams().end() ); - CPPUNIT_ASSERT( it->second == "val1" ); - it = url5.GetParams().find( "param2" ); - CPPUNIT_ASSERT( it != url2.GetParams().end() ); - CPPUNIT_ASSERT( it->second == "val2" ); - it = url5.GetParams().find( "param3" ); - CPPUNIT_ASSERT( it == url5.GetParams().end() ); - - //---------------------------------------------------------------------------- - // Just the host and the protocol - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT( url6.IsValid() == true ); - CPPUNIT_ASSERT( url6.GetProtocol() == "root" ); - CPPUNIT_ASSERT( url6.GetUserName() == "" ); - CPPUNIT_ASSERT( url6.GetPassword() == "" ); - CPPUNIT_ASSERT( url6.GetHostName() == "[::1]" ); - CPPUNIT_ASSERT( url6.GetPort() == 1094 ); - CPPUNIT_ASSERT( url6.GetPath() == "" ); - CPPUNIT_ASSERT( url6.GetPathWithParams() == "" ); - CPPUNIT_ASSERT( url6.GetParams().size() == 0 ); - - //---------------------------------------------------------------------------- - // Local file - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT( url8.IsValid() == true ); - CPPUNIT_ASSERT( url8.GetProtocol() == "file" ); - CPPUNIT_ASSERT( url8.GetUserName() == "" ); - CPPUNIT_ASSERT( url8.GetPassword() == "" ); - CPPUNIT_ASSERT( url8.GetHostName() == "localhost" ); - CPPUNIT_ASSERT( url8.GetPath() == "/etc/passwd" ); - CPPUNIT_ASSERT( url8.GetHostId() == "localhost" ); - CPPUNIT_ASSERT( url8.GetPathWithParams() == "/etc/passwd" ); - CPPUNIT_ASSERT( url8.GetParams().size() == 0 ); - - //---------------------------------------------------------------------------- - // URL without a protocol spec - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT( url9.IsValid() == true ); - CPPUNIT_ASSERT( url9.GetProtocol() == "root" ); - CPPUNIT_ASSERT( url9.GetUserName() == "" ); - CPPUNIT_ASSERT( url9.GetPassword() == "" ); - CPPUNIT_ASSERT( url9.GetHostName() == "localhost" ); - CPPUNIT_ASSERT( url9.GetPort() == 1094 ); - CPPUNIT_ASSERT( url9.GetPath() == "/data/cb4aacf1-6f28-42f2-b68a-90a73460f424.dat" ); - - CPPUNIT_ASSERT( url9.GetPathWithParams() == "/data/cb4aacf1-6f28-42f2-b68a-90a73460f424.dat" ); - CPPUNIT_ASSERT( url9.GetParams().size() == 0 ); - - //---------------------------------------------------------------------------- - // URL cgi without path - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT( url10.IsValid() == true ); - CPPUNIT_ASSERT( url10.GetProtocol() == "root" ); - CPPUNIT_ASSERT( url10.GetUserName() == "" ); - CPPUNIT_ASSERT( url10.GetPassword() == "" ); - CPPUNIT_ASSERT( url10.GetHostName() == "localhost" ); - CPPUNIT_ASSERT( url10.GetPort() == 1094 ); - CPPUNIT_ASSERT( url10.GetPath() == "" ); - - CPPUNIT_ASSERT( url10.GetParams().size() == 1 ); - CPPUNIT_ASSERT( url10.GetParamsAsString() == "?test=123" ); - - //---------------------------------------------------------------------------- - // Bunch od invalid ones - //---------------------------------------------------------------------------- - CPPUNIT_ASSERT( urlInvalid1.IsValid() == false ); - CPPUNIT_ASSERT( urlInvalid2.IsValid() == false ); - CPPUNIT_ASSERT( urlInvalid3.IsValid() == false ); - CPPUNIT_ASSERT( urlInvalid4.IsValid() == false ); - CPPUNIT_ASSERT( urlInvalid5.IsValid() == false ); - CPPUNIT_ASSERT( urlInvalid6.IsValid() == false ); - CPPUNIT_ASSERT( urlInvalid7.IsValid() == false ); - CPPUNIT_ASSERT( urlInvalid8.IsValid() == false ); -} - -class A -{ - public: - A( bool &st ): a(0.0), stat(st) {} - ~A() { stat = true; } - double a; - bool &stat; -}; - -class B -{ - public: - int b; -}; - -//------------------------------------------------------------------------------ -// Any test -//------------------------------------------------------------------------------ -void UtilsTest::AnyTest() -{ - bool destructorCalled1 = false; - bool destructorCalled2 = false; - bool destructorCalled3 = false; - A *a1 = new A( destructorCalled1 ); - A *a2 = new A( destructorCalled2 ); - A *a3 = new A( destructorCalled3 ); - A *a4 = 0; - B *b = 0; - - XrdCl::AnyObject *any1 = new XrdCl::AnyObject(); - XrdCl::AnyObject *any2 = new XrdCl::AnyObject(); - XrdCl::AnyObject *any3 = new XrdCl::AnyObject(); - XrdCl::AnyObject *any4 = new XrdCl::AnyObject(); - - any1->Set( a1 ); - any1->Get( b ); - any1->Get( a4 ); - CPPUNIT_ASSERT( !b ); - CPPUNIT_ASSERT( a4 ); - CPPUNIT_ASSERT( any1->HasOwnership() ); - - delete any1; - CPPUNIT_ASSERT( destructorCalled1 ); - - any2->Set( a2 ); - any2->Set( (int*)0 ); - delete any2; - CPPUNIT_ASSERT( !destructorCalled2 ); - delete a2; - - any3->Set( a3, false ); - CPPUNIT_ASSERT( !any3->HasOwnership() ); - delete any3; - CPPUNIT_ASSERT( !destructorCalled3 ); - delete a3; - - // test destruction of an empty object - delete any4; -} - -//------------------------------------------------------------------------------ -// Some tasks that do something -//------------------------------------------------------------------------------ -class TestTask1: public XrdCl::Task -{ - public: - TestTask1( std::vector &runs ): pRuns( runs ) - { - SetName( "TestTask1" ); - } - virtual time_t Run( time_t now ) - { - pRuns.push_back( now ); - return 0; - } - private: - std::vector &pRuns; -}; - -class TestTask2: public XrdCl::Task -{ - public: - TestTask2( std::vector &runs ): pRuns( runs ) - { - SetName( "TestTask2" ); - } - - virtual time_t Run( time_t now ) - { - pRuns.push_back( now ); - if( pRuns.size() >= 5 ) - return 0; - return now+2; - } - private: - std::vector &pRuns; -}; - -//------------------------------------------------------------------------------ -// Task Manager test -//------------------------------------------------------------------------------ -void UtilsTest::TaskManagerTest() -{ - using namespace XrdCl; - - std::vector runs1, runs2; - Task *tsk1 = new TestTask1( runs1 ); - Task *tsk2 = new TestTask2( runs2 ); - - TaskManager taskMan; - CPPUNIT_ASSERT( taskMan.Start() ); - - time_t now = ::time(0); - taskMan.RegisterTask( tsk1, now+2 ); - taskMan.RegisterTask( tsk2, now+1 ); - - ::sleep( 6 ); - taskMan.UnregisterTask( tsk2 ); - - ::sleep( 2 ); - - CPPUNIT_ASSERT( runs1.size() == 1 ); - CPPUNIT_ASSERT( runs2.size() == 3 ); - CPPUNIT_ASSERT( taskMan.Stop() ); -} - -//------------------------------------------------------------------------------ -// SID Manager test -//------------------------------------------------------------------------------ -void UtilsTest::SIDManagerTest() -{ - using namespace XrdCl; - SIDManager manager; - - uint8_t sid1[2]; - uint8_t sid2[2]; - uint8_t sid3[2]; - uint8_t sid4[2]; - uint8_t sid5[2]; - - CPPUNIT_ASSERT_XRDST( manager.AllocateSID( sid1 ) ); - CPPUNIT_ASSERT_XRDST( manager.AllocateSID( sid2 ) ); - manager.ReleaseSID( sid2 ); - CPPUNIT_ASSERT_XRDST( manager.AllocateSID( sid3 ) ); - CPPUNIT_ASSERT_XRDST( manager.AllocateSID( sid4 ) ); - CPPUNIT_ASSERT_XRDST( manager.AllocateSID( sid5 ) ); - - CPPUNIT_ASSERT( (sid1[0] != sid2[0]) || (sid1[1] != sid2[1]) ); - CPPUNIT_ASSERT( manager.NumberOfTimedOutSIDs() == 0 ); - manager.TimeOutSID( sid4 ); - manager.TimeOutSID( sid5 ); - CPPUNIT_ASSERT( manager.NumberOfTimedOutSIDs() == 2 ); - CPPUNIT_ASSERT( manager.IsTimedOut( sid3 ) == false ); - CPPUNIT_ASSERT( manager.IsTimedOut( sid1 ) == false ); - CPPUNIT_ASSERT( manager.IsTimedOut( sid4 ) == true ); - CPPUNIT_ASSERT( manager.IsTimedOut( sid5 ) == true ); - manager.ReleaseTimedOut( sid5 ); - CPPUNIT_ASSERT( manager.IsTimedOut( sid5 ) == false ); - manager.ReleaseAllTimedOut(); - CPPUNIT_ASSERT( manager.NumberOfTimedOutSIDs() == 0 ); -} - -//------------------------------------------------------------------------------ -// SID Manager test -//------------------------------------------------------------------------------ -void UtilsTest::PropertyListTest() -{ - using namespace XrdCl; - PropertyList l; - l.Set( "s1", "test string 1" ); - l.Set( "i1", 123456789123ULL ); - - uint64_t i1; - std::string s1; - - CPPUNIT_ASSERT( l.Get( "s1", s1 ) ); - CPPUNIT_ASSERT( s1 == "test string 1" ); - CPPUNIT_ASSERT( l.Get( "i1", i1 ) ); - CPPUNIT_ASSERT( i1 == 123456789123ULL ); - CPPUNIT_ASSERT( l.HasProperty( "s1" ) ); - CPPUNIT_ASSERT( !l.HasProperty( "s2" ) ); - CPPUNIT_ASSERT( l.HasProperty( "i1" ) ); - - for( int i = 0; i < 1000; ++i ) - l.Set( "vect_int", i, i+1000 ); - - int i; - int num; - for( i = 0; l.HasProperty( "vect_int", i ); ++i ) - { - CPPUNIT_ASSERT( l.Get( "vect_int", i, num ) ); - CPPUNIT_ASSERT( num = i+1000 ); - } - CPPUNIT_ASSERT( i == 1000 ); - - XRootDStatus st1, st2; - st1.SetErrorMessage( "test error message" ); - l.Set( "status", st1 ); - CPPUNIT_ASSERT( l.Get( "status", st2 ) ); - CPPUNIT_ASSERT( st2.status == st1.status ); - CPPUNIT_ASSERT( st2.code == st1.code ); - CPPUNIT_ASSERT( st2.errNo == st1.errNo ); - CPPUNIT_ASSERT( st2.GetErrorMessage() == st1.GetErrorMessage() ); - - std::vector v1, v2; - v1.push_back( "test string 1" ); - v1.push_back( "test string 2" ); - v1.push_back( "test string 3" ); - l.Set( "vector", v1 ); - CPPUNIT_ASSERT( l.Get( "vector", v2 ) ); - for( size_t i = 0; i < v1.size(); ++i ) - CPPUNIT_ASSERT( v1[i] == v2[i] ); -} diff --git a/tests/XrdClTests/XRootDProtocolHelper.cc b/tests/XrdClTests/XRootDProtocolHelper.cc deleted file mode 100644 index 93cc4654ffc..00000000000 --- a/tests/XrdClTests/XRootDProtocolHelper.cc +++ /dev/null @@ -1,118 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#include "XrdClTests/XRootDProtocolHelper.hh" -#include -#include - -//------------------------------------------------------------------------------ -// Handle XRootD Log-in -//------------------------------------------------------------------------------ -bool XRootDProtocolHelper::HandleLogin( int socket, XrdCl::Log *log ) -{ - //---------------------------------------------------------------------------- - // Handle the handshake - //---------------------------------------------------------------------------- - char handShakeBuffer[20]; - if( ::read( socket, handShakeBuffer, 20 ) != 20 ) - { - log->Error( 1, "Unable to read the handshake: %s", ::strerror( errno ) ); - return false; - } - - //---------------------------------------------------------------------------- - // Respond to the handshake - //---------------------------------------------------------------------------- - char serverHandShake[16]; memset( serverHandShake, 0, 16 ); - ServerInitHandShake *hs = (ServerInitHandShake *)(serverHandShake+4); - hs->msglen = ::htonl(8); - hs->protover = ::htonl( kXR_PROTOCOLVERSION ); - hs->msgval = ::htonl( kXR_DataServer ); - if( ::write( socket, serverHandShake, 16 ) != 16 ) - { - log->Error( 1, "Unable to write the handshake response: %s", - ::strerror( errno ) ); - return false; - } - - //---------------------------------------------------------------------------- - // Handle the protocol request - //---------------------------------------------------------------------------- - char protocolBuffer[24]; - if( ::read( socket, protocolBuffer, 24 ) != 24 ) - { - log->Error( 1, "Unable to read the protocol request: %s", ::strerror( errno ) ); - return false; - } - - //---------------------------------------------------------------------------- - // Respond to protocol - //---------------------------------------------------------------------------- - ServerResponse serverProtocol; memset( &serverProtocol, 0, 16 ); - serverProtocol.hdr.dlen = ::htonl( 8 ); - serverProtocol.body.protocol.pval = ::htonl( kXR_PROTOCOLVERSION ); - serverProtocol.body.protocol.flags = ::htonl( kXR_isServer ); - if( ::write( socket, &serverProtocol, 16 ) != 16 ) - { - log->Error( 1, "Unable to write the protocol response: %s", - ::strerror( errno ) ); - return false; - } - - //---------------------------------------------------------------------------- - // Handle the login - //---------------------------------------------------------------------------- - char loginBuffer[24]; - if( ::read( socket, loginBuffer, 24 ) != 24 ) - { - log->Error( 1, "Unable to read the login request: %s", ::strerror( errno ) ); - return false; - } - - //---------------------------------------------------------------------------- - // Respond to login - //---------------------------------------------------------------------------- - ServerResponse serverLogin; memset( &serverLogin, 0, 24 ); - serverLogin.hdr.dlen = ::htonl( 16 ); - if( ::write( socket, &serverLogin, 24 ) != 24 ) - { - log->Error( 1, "Unable to write the login response: %s", - ::strerror( errno ) ); - return false; - } - - return true; -} - -//------------------------------------------------------------------------------ -// Handle disconnection -//------------------------------------------------------------------------------ -bool XRootDProtocolHelper::HandleClose( int socket, XrdCl::Log *log ) -{ - return true; -} - -//------------------------------------------------------------------------------ -// Receive a message -//------------------------------------------------------------------------------ -bool XRootDProtocolHelper::GetMessage( XrdCl::Message *msg, int socket, - XrdCl::Log *log ) -{ - return true; -} - diff --git a/tests/XrdClTests/XRootDProtocolHelper.hh b/tests/XrdClTests/XRootDProtocolHelper.hh deleted file mode 100644 index c5e1d380465..00000000000 --- a/tests/XrdClTests/XRootDProtocolHelper.hh +++ /dev/null @@ -1,45 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#ifndef XROOTD_PROTOCOL_HELPER_HH -#define XROOTD_PROTOCOL_HELPER_HH - -#include -#include - -class XRootDProtocolHelper -{ - public: - //-------------------------------------------------------------------------- - //! Handle XRootD Log-in - //-------------------------------------------------------------------------- - bool HandleLogin( int socket, XrdCl::Log *log ); - - //-------------------------------------------------------------------------- - //! Handle disconnection - //-------------------------------------------------------------------------- - bool HandleClose( int socket, XrdCl::Log *log ); - - //-------------------------------------------------------------------------- - //! Receive a message - //-------------------------------------------------------------------------- - bool GetMessage( XrdCl::Message *msg, int socket, XrdCl::Log *log ); - private: -}; - -#endif // XROOTD_PROTOCOL_HELPER_HH diff --git a/tests/XrdClTests/cppunit.supp b/tests/XrdClTests/cppunit.supp deleted file mode 100644 index ad70bf372f9..00000000000 --- a/tests/XrdClTests/cppunit.supp +++ /dev/null @@ -1,17 +0,0 @@ -{ - CPPUnit typeinfo leak - Memcheck:Leak - fun:_Znwm - fun:_ZNSs4_Rep9_S_createEmmRKSaIcE - fun:_ZNSs12_S_constructIPcEES0_T_S1_RKSaIcESt20forward_iterator_tag - fun:_ZNSsC1ERKSsmm - fun:_ZN7CppUnit14TypeInfoHelper12getClassNameERKSt9type_info - fun:_ZN7CppUnit9TestNamerC1ERKSt9type_info -} - -{ - CPPUnit factory registry leak - Memcheck:Leak - fun:_Znwm - fun:_ZN7CppUnit19TestFactoryRegistry8makeTestEv -} diff --git a/tests/XrdClTests/printenv.sh b/tests/XrdClTests/printenv.sh deleted file mode 100755 index 3a5076f6078..00000000000 --- a/tests/XrdClTests/printenv.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash - -function printEnv() -{ - if [ $# -ne 1 ]; then - echo "[!] Invalid invocation; need a parameter" - return 1; - fi - - eval VALUE=\$$1 - printf "%-30s: " $1 - if test x"$VALUE" == x; then - echo "default" - else - echo $VALUE; - fi -} - -printEnv XRDTEST_MAINSERVERURL -printEnv XRDTEST_DISKSERVERURL -printEnv XRDTEST_DATAPATH -printEnv XRDTEST_LOCALFILE -printEnv XRDTEST_REMOTEFILE -printEnv XRDTEST_MULTIIPSERVERURL diff --git a/tests/XrdSsiTests/CMakeLists.txt b/tests/XrdSsiTests/CMakeLists.txt deleted file mode 100644 index 1a33668c53e..00000000000 --- a/tests/XrdSsiTests/CMakeLists.txt +++ /dev/null @@ -1,20 +0,0 @@ - -include( XRootDCommon ) - -add_executable( - xrdshmap - XrdShMap.cc -) - -target_link_libraries( - xrdshmap - ${ZLIB_LIBRARIES} - XrdSsiShMap ) - -#------------------------------------------------------------------------------- -# Install -#------------------------------------------------------------------------------- -install( - TARGETS xrdshmap - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ) diff --git a/tests/XrdSsiTests/XrdShMap.cc b/tests/XrdSsiTests/XrdShMap.cc deleted file mode 100644 index bb642ac342b..00000000000 --- a/tests/XrdSsiTests/XrdShMap.cc +++ /dev/null @@ -1,1050 +0,0 @@ -/******************************************************************************/ -/* */ -/* X r d S h M a p . c c */ -/* */ -/* (c) 2015 by the Board of Trustees of the Leland Stanford, Jr., University */ -/* Produced by Andrew Hanushevsky for Stanford University under contract */ -/* DE-AC02-76-SFO0515 with the Department of Energy */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/******************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "XrdSsi/XrdSsiShMap.hh" - -using namespace std; - -/******************************************************************************/ -/* U n i t G l o b a l s */ -/******************************************************************************/ - -namespace -{ - XrdSsi::ShMap *theMap = 0; - XrdSsi::ShMap_Parms rParms(XrdSsi::ShMap_Parms::ForResize); - XrdSsi::ShMap_Parms sParms; - char *keyPfx = strdup("key"); - const char *hashN = "c32"; - XrdSsi::ShMap_Hash_t hashF = 0; - const char *path = "/tmp/shmap.sms"; - const char *MeMe = "shmap: "; - char *uLine = 0; - int tmo = -1; - int xRC = 0; -} - -/******************************************************************************/ -/* D e f i n e s */ -/******************************************************************************/ - -#define FMSG(x) xRC|=1,cerr <", - " Reads input newline separated commands from the file identified by ." -}; - -const char *CLpHelp[] = -{"-p ", - " Specifies the file name, , of the shared memory segment.", - " The default is '/tmp/shmap.sms'." -}; - -const char *CLtHelp[] = -{"-t ", - " Specifies the attach timeout in seconds. The default is -1 which causes", - " attach to wait until the shared memory segment is exported." -}; - -const char *addHelp[] = -{"add ", - " Adds key with an integer value of to the map. The key must", - " not be in the map. Use the rep command to replace it." -}; - -const char *attHelp[] = -{"att[ach] {r|w}", - " Attach a shared memory identified by the -p command line option.", - " The 'r' argument attaches it as read/only while 'w' attaches it read/write." -}; - -const char *creHelp[] = -{"cr[eate] {[m][s][r][u][=]}", - " Create a shared memory identified by the -p command line option.", - " Specify 'm' to enable multiple writers or 's' for a single writer.", - " Specify 'r' to enable space reuse or 'u' to disallow space reuse.", - " Specify '=' for defaults (su). See the setmax command for size options.", - " The map is not visible to other processes until 'export' is executed." -}; - -const char *dleHelp[] = -{"del[ete] ", - " Delete from the map." -}; - -const char *detHelp[] = -{"det[ach]", - " Detach the shared memory segment." -}; - -const char *exiHelp[] = -{"ex[ists] ", - " Try to find in the map and indicate whether or not it exists." -}; - -const char *xitHelp[] = -{"{exit | quit}", - " Exits; a non-zero return code may indicate an unexpected result." -}; - -const char *xplHelp[] = -{"explain { | all}", - " Explain the specified command, , or everything." -}; - -const char *expHelp[] = -{"exp[ort]", - " Make a newly created map visible to other processes." -}; - -const char *getHelp[] = -{"get ", - " Fetch the key from the map and display its associated value." -}; - -const char *hasHelp[] = -{"hash ", - " Print the hash value of using the default or current -h setting." -}; - -const char *hlpHelp[] = -{"{help | ?}", - " Display a synopsis of the xrdshmap command." -}; - -const char *infHelp[] = -{"info", - " Display information about the attached map." -}; - -const char *kysHelp[] = -{"keys", - " Enumerate the map displaying each key and its associated value." -}; - -const char *kpxHelp[] = -{"keypfx", - " Set the key prefix to use with the 'load', 'unload', 'verify', and 'verdel'", - " commands. The default is 'key'. See the commands for details.'" -}; - -const char *lodHelp[] = -{"load ", - " Add to the map. Each key is formed as where", - " 0 <= n < (e.g. key0, key1, etc). The key value is set to n.'" -}; - -const char *qmxHelp[] = -{"qmax", - " Display the size defaults for creating or resizing a map. See 'setmax'." -}; - -const char *repHelp[] = -{"rep[lace] ", - " Replace key with an integer value of in the map. If the key", - " exists, its previous value is displayed." -}; - -const char *rszHelp[] = -{"res[ize] {[m][s][r][u][=]}", - " Resize a shared memory identified by the -p command line option.", - " Specify 'm' to enable multiple writers or 's' for a single writer.", - " Specify 'r' to enable space reuse or 'u' to disallow space reuse.", - " Specify '=' to use the existing values in the map. This compresses the", - " map as much as possible. The map must have been exported. See the setmax", - " command for more size options." -}; - -const char *smxHelp[] = -{"setmax [cr[eate] | res[ize]] {index | keylen | keys | mode} ", - " Set the creation or resizing values for a subsequent create or resize.", - " To only set the create value specify 'cr' or 'res' for just resizing.", - " If neither is specified, the values are set for both commands. Specify", - " index number of hash table entries. For rsz, 0 uses the map's value.", - " keylen maximum key length in bytes. For rsz, 0 uses the map's value.", - " keys maximum number of keys. For rsz, 0 uses the map's value.", - " mode the creation mode for create. The resize command ignores this.", - " is the integer value to use. Use qmax to display the values." -}; - -const char *susHelp[] = -{"sus[pend]", - " Stop execution and wait for a carriage return. This is useful when testing", - " interactively with file or command line input. Also, see 'wait'." -}; - -const char *synHelp[] = -{"sync {all | off | on | now | }", - " Set synchronization parameters between shared memory and its file.", - " all turn sync on; pages are written synchronously in the foreground.", - " off turn sync off (initial setting) and let the kernel do it whenever.", - " on turn sync on; pages are written asynchronously in the background.", - " now write back any modified pages but don't change any sync settings.", - " specifies the maximum number of changed pages before a sync occurs.", - " The setting must be on or all for the sync to actually occur." -}; - -const char *unlHelp[] = -{"unload ", - " Delete from the map. Each key is formed as where", - " 0 <= n < (e.g. key0, key1, etc)." -}; - -const char *verHelp[] = -{"ver[ify] ", - " Verify that have the expected value in the map. Each key is", - " formed as where 0 <= n < (e.g. key0, key1, etc).", - " The key's value must equal n.'" -}; - -const char *vdlHelp[] = -{"verdel ", - " Perform an unload/verify operation for . Each deleted key must", - " have the expected value (see unload and verify)." -}; - -const char *wwtHelp[] = -{"wait ", - " Pause the program for seconds. This is useful when testing in the", - " background. Also, see the suspend command." -}; - -theHelp helpInfo[] = -{theHelp("-e", 0, CLeHelp, sizeof(CLeHelp)), - theHelp("-h", 0, CLhHelp, sizeof(CLhHelp)), - theHelp("-i", 0, CLiHelp, sizeof(CLiHelp)), - theHelp("-p", 0, CLpHelp, sizeof(CLpHelp)), - theHelp("-t", 0, CLtHelp, sizeof(CLtHelp)), - theHelp("add", 0, addHelp, sizeof(addHelp)), - theHelp("att", "attach", attHelp, sizeof(attHelp)), - theHelp("cr", "create", creHelp, sizeof(creHelp)), - theHelp("del", "delete", dleHelp, sizeof(dleHelp)), - theHelp("det", "detach", detHelp, sizeof(detHelp)), - theHelp("ex", "exists", exiHelp, sizeof(exiHelp)), - theHelp("exit", "quit", xitHelp, sizeof(xitHelp)), - theHelp("explain",0, xplHelp, sizeof(xplHelp)), - theHelp("exp", "export",expHelp, sizeof(expHelp)), - theHelp("get", 0, getHelp, sizeof(getHelp)), - theHelp("hash", 0, hasHelp, sizeof(hasHelp)), - theHelp("help","?", hlpHelp, sizeof(hlpHelp)), - theHelp("info", 0, infHelp, sizeof(infHelp)), - theHelp("keys", 0, kysHelp, sizeof(kysHelp)), - theHelp("keypfx", 0, kpxHelp, sizeof(kpxHelp)), - theHelp("load", 0, lodHelp, sizeof(lodHelp)), - theHelp("qmax", 0, qmxHelp, sizeof(qmxHelp)), - theHelp("rep", "replace",repHelp, sizeof(repHelp)), - theHelp("res", "resize", rszHelp, sizeof(rszHelp)), - theHelp("setmax", 0, smxHelp, sizeof(smxHelp)), - theHelp("sus", "suspend",susHelp, sizeof(susHelp)), - theHelp("sync", 0, synHelp, sizeof(synHelp)), - theHelp("unload", 0, unlHelp, sizeof(unlHelp)), - theHelp("ver", "verify", verHelp, sizeof(verHelp)), - theHelp("verdel", 0, vdlHelp, sizeof(vdlHelp)), - theHelp("wt", "wait", wwtHelp, sizeof(wwtHelp)), -}; -} - -/******************************************************************************/ -/* U s a g e */ -/******************************************************************************/ - -namespace -{ -void Usage() -{ - const char *lbeg = "command: "; - const char *lcon = " "; - int ulen = 0, i, n = sizeof(helpInfo)/sizeof(theHelp), plen = strlen(lbeg); - int zlen = 0, zpos; - -// Find where the commands start -// - for (i = 0; i < n; i++) if (*helpInfo[i].cmd != '-') break; - -// Count up characters and llocate a buffer -// - for (int j = i; j < n; j++) ulen += helpInfo[j].cln + plen + 3; - uLine = new char[ulen+(plen*((ulen+80)/80))]; - -// Copy over all of the commands -// - strcpy(uLine, lbeg); zlen = zpos = plen; - for (int j = i; j < n; j++) - {if (zlen + helpInfo[j].cln > 78) - {uLine[zpos-1] = '\n'; - strcpy(&uLine[zpos], lcon); - zlen = plen; zpos += plen; - } - strcpy(&uLine[zpos], helpInfo[j].dtl[0]); - zpos += helpInfo[j].cln; strcpy(&uLine[zpos], " | "); - zpos += 3; - zlen += helpInfo[j].cln + 3; - } - -// Finish off the line -// - uLine[zpos-3] = 0; -} - -/******************************************************************************/ - -int Usage(int rc, bool terse=true) -{ - -cerr <<"Usage: xrdshmap [options] [command [command [...]]]\n\n"; -cerr <<"options: [-e] [-h {a32|c32|x32}] [-i ] [-p ] [-t ]\n\n"; - - if (terse) return rc; - - if (!uLine) Usage(); - cerr <= n) - {cerr < i) cerr <<'\n' <(adler); -// cerr <<"Z a32 sz=" <(crc); -// cerr <<"Z c32 sz=" <Enumerate(myJar, kbuff, dataP)) - {cout <Info("atomics", iBuff, sizeof(iBuff)); - if (n < 0) - {UMSG("get info for atomics"); - if (errno == ENOTCONN) return; - } - cout <<"atomics = " <Info("hash", iBuff, sizeof(iBuff)); - if (n < 0) - {UMSG("get info for hash"); - if (errno == ENOTCONN) return; - } - cout <<"hash = " <Info("impl", iBuff, sizeof(iBuff)); - if (n < 0) - {UMSG("get info for implementation"); - if (errno == ENOTCONN) return; - } - cout <<"impl = " <Info(vname[i]); - if (n >= 0) cout <Info("type", iBuff, sizeof(iBuff)); - if (n < 0) - {UMSG("get info for the type"); - if (errno == ENOTCONN) return; - } - cout <<"type = " <Add(key, k))) - {UMSG("add key " <Del(key)) - {UMSG("ver key " <Get(key, kval) - : theMap->Del(key, &kval))) - {if (k == kval) numOK++; - else EMSG("Key " <= Argc) - {if (cont) return 0; - exit(xRC); - } - return Argv[Apos++]; - } - -// Get input from a file or the terminal -// -do{if (!line || !cont) - {if (line) {delete [] line; line = 0;} - if (prompt) - {cerr <eof()) continue; - else return 0; - } - line = new char[n+1]; - strcpy(line, inLine.c_str()); - if (echo) cerr <Add(key, kval)) SAY("key " <Attach(path, acc, tmo))) UMSG("attach map"); - else SAY("attach OK"); - continue; - } - - IFCMD2("cr", "create") - {int attOpts = 0; - if (!(theOp = Token("create argument"))) continue; - while(*theOp) - {switch(*theOp) - {case 'm': attOpts |= XrdSsi::ShMap_Parms::MultW; - break; - case 's': attOpts |= XrdSsi::ShMap_Parms::noMultW; - break; - case 'r': attOpts |= XrdSsi::ShMap_Parms::ReUse; - break; - case 'u': attOpts |= XrdSsi::ShMap_Parms::noReUse; - break; - case '=': attOpts = 0; - break; - default: EMSG("Unknown create option - " <<*theOp); - theOp++; - continue; - } - theOp++; - } - sParms.options = attOpts; - if (!(theMap->Create(path, sParms))) UMSG("create map"); - else SAY("create OK"); - continue; - } - - IFCMD2("del", "delete") - {if (!(key = Token("key to delete"))) continue; - if (theMap->Del(key, &pval)) - SAY("key " <Detach(); - SAY("detach done!"); - continue; - } - - IFCMD2("ex", "exists") - {if (!(key = Token("key to check"))) continue; - if (theMap->Exists(key)) SAY("key " <Export()) SAY("export OK"); - else UMSG("export map"); - continue; - } - - IFCMD1("get") - {if (!(key = Token("key to get"))) continue; - if (theMap->Get(key, pval)) SAY("key " <Rep(key, kval, &pval))) UMSG("rep key " <Sync(sopt, kval)) SAY("sync OK"); - else UMSG("sync map"); - continue; - } - - IFCMD1("unload") - {if (!(val = Token("unload count"))) continue; - numkeys = strtol(val, &xval, 10); - if (numkeys <= 0 || xval) - EMSG("number of keys to unload is invalid."); - else DoLUV(numkeys, isUnload); - continue; - } - - IFCMD2("ver", "verify") - {if (!(val = Token("verify count"))) continue; - numkeys = strtol(val, &xval, 10); - if (numkeys <= 0 || *xval) - EMSG("number of keys to verify is invalid."); - else DoLUV(numkeys, isVer); - continue; - } - - IFCMD1("verdel") - {if (!(val = Token("verdel count"))) continue; - numkeys = strtol(val, &xval, 10); - if (numkeys <= 0 || *xval) - EMSG("number of keys to ver and del is invalid."); - else DoLUV(numkeys, isUnver); - continue; - } - - IFCMD1("wait") - {if (!(val = Token("seconds to wait"))) continue; - kval = strtol(val, &xval, 10); - if (kval <= 0 || *xval) EMSG("seconds to wait is invalid."); - else sleep(kval); - continue; - } - - EMSG("Unknown command - " < 1 && '-' == *argv[1]) - while ((c = getopt(argc,argv,":eh:i:p:t:")) - && ((unsigned char)c != 0xff)) - { switch(c) - { - case 'e': echo = true; - break; - case 'h': if (!strcmp(optarg,"a32")) {hashF = DoA32; hashN = "a32";} - else if (!strcmp(optarg,"c32")) {hashF = DoC32; hashN = "c32";} - else if (!strcmp(optarg,"x32")) {hashF = DoX32; hashN = "x32";} - else {EMSG(optarg <<" hash is not supported."); - exit(Usage(1)); - } - break; - case 'i': inpath = optarg; - break; - case 'p': path = optarg; - break; - case 't': tmo = atoi(optarg); - break; - case ':': EMSG('-' <("int", hashF); - -// Process command stream -// - Xeq(); - return xRC; -} diff --git a/tests/common/CMakeLists.txt b/tests/common/CMakeLists.txt deleted file mode 100644 index 76a2e852513..00000000000 --- a/tests/common/CMakeLists.txt +++ /dev/null @@ -1,27 +0,0 @@ - -include(XRootDCommon) - -add_library( - XrdClTestsHelper SHARED - Server.cc Server.hh - Utils.cc Utils.hh - TestEnv.cc TestEnv.hh - CppUnitXrdHelpers.hh) - -target_link_libraries( - XrdClTestsHelper - pthread - ${CPPUNIT_LIBRARIES} - ${ZLIB_LIBRARY} - XrdCl) - -add_executable(text-runner TextRunner.cc PathProcessor.hh) -target_link_libraries(text-runner dl ${CPPUNIT_LIBRARIES} pthread) - -#------------------------------------------------------------------------------- -# Install -#------------------------------------------------------------------------------- -install( - TARGETS XrdClTestsHelper text-runner - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) diff --git a/tests/common/CppUnitXrdHelpers.hh b/tests/common/CppUnitXrdHelpers.hh deleted file mode 100644 index 3fbd7c28896..00000000000 --- a/tests/common/CppUnitXrdHelpers.hh +++ /dev/null @@ -1,57 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#ifndef __CPPUNIT_XRD_HELPERS_HH__ -#define __CPPUNIT_XRD_HELPERS_HH__ - -#include -#include -#include - -#define CPPUNIT_ASSERT_XRDST_NOTOK( x, err ) \ -{ \ - XrdCl::XRootDStatus st = x; \ - std::string msg = "["; msg += #x; msg += "]: "; \ - msg += st.ToStr(); \ - CPPUNIT_ASSERT_MESSAGE( msg, !st.IsOK() && st.code == err ); \ -} - -#define CPPUNIT_ASSERT_XRDST( x ) \ -{ \ - XrdCl::XRootDStatus st = x; \ - std::string msg = "["; msg += #x; msg += "]: "; \ - msg += st.ToStr(); \ - CPPUNIT_ASSERT_MESSAGE( msg, st.IsOK() ); \ -} - -#define CPPUNIT_ASSERT_ERRNO( x ) \ -{ \ - std::string msg = "["; msg += #x; msg += "]: "; \ - msg += strerror( errno ); \ - CPPUNIT_ASSERT_MESSAGE( msg, x ); \ -} - -#define CPPUNIT_ASSERT_PTHREAD( x ) \ -{ \ - errno = x; \ - std::string msg = "["; msg += #x; msg += "]: "; \ - msg += strerror( errno ); \ - CPPUNIT_ASSERT_MESSAGE( msg, errno == 0 ); \ -} - -#endif // __CPPUNIT_XRD_HELPERS_HH__ diff --git a/tests/common/PathProcessor.hh b/tests/common/PathProcessor.hh deleted file mode 100644 index 9d294cb3048..00000000000 --- a/tests/common/PathProcessor.hh +++ /dev/null @@ -1,83 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#ifndef EOS_NS_PATH_PROCESSOR_HH -#define EOS_NS_PATH_PROCESSOR_HH - -#include -#include -#include - -namespace eos -{ - //---------------------------------------------------------------------------- - //! Helper class responsible for spliting the path - //---------------------------------------------------------------------------- - class PathProcessor - { - public: - //------------------------------------------------------------------------ - //! Split the path and put its elements in a vector, the tokens are - //! copied, the buffer is not overwritten - //------------------------------------------------------------------------ - static void splitPath( std::vector &elements, - const std::string &path ) - { - elements.clear(); - std::vector elems; - char buffer[path.length()+1]; - strcpy( buffer, path.c_str() ); - splitPath( elems, buffer ); - for( size_t i = 0; i < elems.size(); ++i ) - elements.push_back( elems[i] ); - } - - //------------------------------------------------------------------------ - //! Split the path and put its element in a vector, the split is done - //! in-place and the buffer is overwritten - //------------------------------------------------------------------------ - static void splitPath( std::vector &elements, char *buffer ) - { - elements.clear(); - elements.reserve( 10 ); - - char *cursor = buffer; - char *beg = buffer; - - //---------------------------------------------------------------------- - // Go by the characters one by one - //---------------------------------------------------------------------- - while( *cursor ) - { - if( *cursor == '/' ) - { - *cursor = 0; - if( beg != cursor ) - elements.push_back( beg ); - beg = cursor+1; - } - ++cursor; - } - - if( beg != cursor ) - elements.push_back( beg ); - } - }; -} - -#endif // EOS_NS_PATH_PROCESSOR_HH diff --git a/tests/common/Server.cc b/tests/common/Server.cc deleted file mode 100644 index 89701c28f0a..00000000000 --- a/tests/common/Server.cc +++ /dev/null @@ -1,372 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#include "Server.hh" -#include "Utils.hh" -#include "XrdNet/XrdNetUtils.hh" -#include "XrdCl/XrdClLog.hh" -#include "TestEnv.hh" - -#include -#include -#include -#include -#include - - -namespace XrdClTests -{ - //---------------------------------------------------------------------------- - // Define the client helper - //---------------------------------------------------------------------------- - struct ClientHelper - { - ClientHandler *handler; - int socket; - pthread_t thread; - std::string name; - }; -} - -//------------------------------------------------------------------------------ -// Stuff that needs C linkage -//------------------------------------------------------------------------------ -extern "C" -{ - void *HandleConnections( void *arg ) - { - XrdClTests::Server *srv = (XrdClTests::Server*)arg; - long ret = srv->HandleConnections(); - return (void *)ret; - } - - void *HandleClient( void *arg ) - { - XrdClTests::ClientHelper *helper = (XrdClTests::ClientHelper*)arg; - helper->handler->HandleConnection( helper->socket ); - return 0; - } -} - -namespace XrdClTests { - -//------------------------------------------------------------------------------ -// Constructor -//------------------------------------------------------------------------------ -ClientHandler::ClientHandler(): pSentBytes(0), pReceivedBytes(0) -{ - pSentChecksum = Utils::ComputeCRC32( 0, 0 ); - pReceivedChecksum = Utils::ComputeCRC32( 0, 0 ); -} - -//------------------------------------------------------------------------------ -// Destructor -//------------------------------------------------------------------------------ -ClientHandler::~ClientHandler() -{ -} - -//------------------------------------------------------------------------------ -// Update statistics of the received data -//------------------------------------------------------------------------------ -void ClientHandler::UpdateSentData( char *buffer, uint32_t size ) -{ - pSentBytes += size; - pSentChecksum = Utils::UpdateCRC32( pSentChecksum, buffer, size ); -} - -//------------------------------------------------------------------------------ -// Update statistics of the sent data -//------------------------------------------------------------------------------ -void ClientHandler::UpdateReceivedData( char *buffer, uint32_t size ) -{ - pReceivedBytes += size; - pReceivedChecksum = Utils::UpdateCRC32( pReceivedChecksum, buffer, size ); -} - -//------------------------------------------------------------------------------ -// Constructor -//------------------------------------------------------------------------------ -Server::Server( ProtocolFamily family ): - pServerThread(0), pListenSocket(-1), pHandlerFactory(0), - pProtocolFamily( family ) -{ -} - -//------------------------------------------------------------------------------ -// Destructor -//------------------------------------------------------------------------------ -Server::~Server() -{ - delete pHandlerFactory; - close( pListenSocket ); -} - - -//------------------------------------------------------------------------------ -// Listen for incomming connections and handle clients -//------------------------------------------------------------------------------ -bool Server::Setup( int port, int accept, ClientHandlerFactory *factory ) -{ - XrdCl::Log *log = TestEnv::GetLog(); - log->Debug( 1, "Seting up the server, port: %d, clients: %d", port, accept ); - - //---------------------------------------------------------------------------- - // Set up the handler factory - //---------------------------------------------------------------------------- - if( !factory ) - { - log->Error( 1, "Invalid client handler factory" ); - return false; - } - pHandlerFactory = factory; - - //---------------------------------------------------------------------------- - // Create the socket - //---------------------------------------------------------------------------- - int protocolFamily = AF_INET; - if( pProtocolFamily == Inet6 || pProtocolFamily == Both ) - protocolFamily = AF_INET6; - - pListenSocket = socket( protocolFamily, SOCK_STREAM, 0 ); - if( pListenSocket < 0 ) - { - log->Error( 1, "Unable to create listening socket: %s", strerror( errno ) ); - return false; - } - - int optVal = 1; - if( setsockopt( pListenSocket, SOL_SOCKET, SO_REUSEADDR, - &optVal, sizeof(optVal) ) == -1 ) - { - log->Error( 1, "Unable to set the REUSEADDR option: %s", strerror( errno ) ); - return false; - } - - if( pProtocolFamily == Both ) - { - optVal = 0; - if( setsockopt( pListenSocket, IPPROTO_IPV6, IPV6_V6ONLY, &optVal, - sizeof(optVal) ) == -1 ) - { - log->Error( 1, "Unable to disable the IPV6_V6ONLY option: %s", - strerror( errno ) ); - return false; - } - } - - //---------------------------------------------------------------------------- - // Bind the socket - //---------------------------------------------------------------------------- - sockaddr *servAddr = 0; - sockaddr_in6 servAddr6; - sockaddr_in servAddr4; - int servAddrSize = 0; - - if( pProtocolFamily == Inet4 ) - { - memset( &servAddr4, 0, sizeof(servAddr4) ); - servAddr4.sin_family = AF_INET; - servAddr4.sin_addr.s_addr = htonl(INADDR_ANY); - servAddr4.sin_port = htons(port); - servAddr = (sockaddr*)&servAddr4; - servAddrSize = sizeof(servAddr4); - } - else - { - memset( &servAddr6, 0, sizeof(servAddr6) ); - servAddr6.sin6_family = AF_INET6; - servAddr6.sin6_addr = in6addr_any; - servAddr6.sin6_port = htons( port ); - servAddr = (sockaddr*)&servAddr6; - servAddrSize = sizeof(servAddr6); - } - - if( bind( pListenSocket, servAddr, servAddrSize ) < 0 ) - { - log->Error( 1, "Unable to bind the socket: %s", strerror( errno ) ); - return false; - } - - if( listen( pListenSocket, accept ) < 0 ) - { - log->Error( 1, "Unable to listen on the socket: %s", strerror( errno ) ); - return false; - } - - pClients.resize( accept, 0 ); - - return true; -} - -//------------------------------------------------------------------------------ -// Start the server -//------------------------------------------------------------------------------ -bool Server::Start() -{ - XrdCl::Log *log = TestEnv::GetLog(); - log->Debug( 1, "Spawning the server thread" ); - if( pthread_create( &pServerThread, 0, ::HandleConnections, this ) < 0 ) - { - log->Error( 1, "Unable to spawn the server thread: %s", strerror(errno) ); - return false; - } - return true; -} - -//------------------------------------------------------------------------------ -// Stop the server -//------------------------------------------------------------------------------ -bool Server::Stop() -{ - XrdCl::Log *log = TestEnv::GetLog(); - log->Debug( 1, "Waiting for the server thread to finish" ); - long ret; - if( pthread_join( pServerThread, (void**)&ret ) < 0 ) - { - log->Error( 1, "Unable to join the server thread: %s", strerror(errno) ); - return false; - } - - if( ret < 0 ) - return false; - - return true; -} - -//------------------------------------------------------------------------------ - // Get the statisctics of the sent data -//------------------------------------------------------------------------------ -std::pair Server::GetSentStats( const std::string host) const -{ - TransferMap::const_iterator it = pSent.find( host ); - if( it == pSent.end() ) - return std::make_pair( 0, 0 ); - return it->second; -} - -//------------------------------------------------------------------------------ -// Get the stats of the received data -//------------------------------------------------------------------------------ -std::pair Server::GetReceivedStats( const std::string host ) const -{ - TransferMap::const_iterator it = pReceived.find( host ); - if( it == pReceived.end() ) - return std::make_pair( 0, 0 ); - return it->second; -} - -//------------------------------------------------------------------------------ -// Handle clients -//------------------------------------------------------------------------------ -int Server::HandleConnections() -{ - XrdCl::Log *log = TestEnv::GetLog(); - - //---------------------------------------------------------------------------- - // Initiate the connections - //---------------------------------------------------------------------------- - for( uint32_t i = 0; i < pClients.size(); ++i ) - { - //-------------------------------------------------------------------------- - // Accept the connection - //-------------------------------------------------------------------------- - sockaddr *inetAddr = 0; - socklen_t inetLen = 0; - sockaddr_in inetAddr4; - sockaddr_in6 inetAddr6; - if( pProtocolFamily == Inet4 ) - { - inetAddr = (sockaddr*)&inetAddr4; - inetLen = sizeof( inetAddr4 ); - } - else - { - inetAddr = (sockaddr*)&inetAddr6; - inetLen = sizeof( inetAddr6 ); - } - - int connectionSocket = accept( pListenSocket, inetAddr, &inetLen ); - if( connectionSocket < 0 ) - { - log->Error( 1, "Unable to accept a connection: %s", strerror( errno ) ); - return 1; - } - - //-------------------------------------------------------------------------- - // Create the handler - //-------------------------------------------------------------------------- - ClientHandler *handler = pHandlerFactory->CreateHandler(); - char nameBuff[1024]; - ClientHelper *helper = new ClientHelper(); - XrdNetUtils::IPFormat( connectionSocket, nameBuff, sizeof(nameBuff) ); - log->Debug( 1, "Accepted a connection from %s", nameBuff ); - - //-------------------------------------------------------------------------- - // Spawn the thread - //-------------------------------------------------------------------------- - helper->name = nameBuff; - helper->handler = handler; - helper->socket = connectionSocket; - if( pthread_create( &helper->thread, 0, ::HandleClient, helper ) < 0 ) - { - log->Error( 1, "Unable to spawn a new thread for client no %d: %s", - i, nameBuff ); - delete handler; - close( connectionSocket ); - delete helper; - helper = 0; - } - pClients[i] = helper; - } - - //---------------------------------------------------------------------------- - // Wait forr the clients to finish - //---------------------------------------------------------------------------- - std::vector::iterator it; - for( it = pClients.begin(); it != pClients.end(); ++it ) - { - //-------------------------------------------------------------------------- - // Have we managed to start properly? - //-------------------------------------------------------------------------- - if( *it == 0 ) - { - log->Debug( 1, "Skipping client that falied to start" ); - continue; - } - - //-------------------------------------------------------------------------- - // Join the client thread - //-------------------------------------------------------------------------- - if( pthread_join( (*it)->thread, 0 ) < 0 ) - log->Error( 1, "Unable to join the clint thread for: %s", - (*it)->name.c_str() ); - - pSent[(*it)->name] = std::make_pair( - (*it)->handler->GetSentBytes(), - (*it)->handler->GetSentChecksum() ); - pReceived[(*it)->name] = std::make_pair( - (*it)->handler->GetReceivedBytes(), - (*it)->handler->GetReceivedChecksum() ); - delete (*it)->handler; - delete *it; - } - return 0; -} - -} diff --git a/tests/common/Server.hh b/tests/common/Server.hh deleted file mode 100644 index 5acd8ccc896..00000000000 --- a/tests/common/Server.hh +++ /dev/null @@ -1,201 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#ifndef SERVER_HH -#define SERVER_HH - -#include -#include -#include -#include -#include -#include - -namespace XrdClTests { - -//------------------------------------------------------------------------------ -//! Interface for the client handler -//------------------------------------------------------------------------------ -class ClientHandler -{ - public: - //-------------------------------------------------------------------------- - //! Constructor - //-------------------------------------------------------------------------- - ClientHandler(); - - //-------------------------------------------------------------------------- - //! Destructor - //-------------------------------------------------------------------------- - virtual ~ClientHandler(); - - //-------------------------------------------------------------------------- - //! Handle connection - //! - //! @param socket the connection socket - needs to be closed when not needed - //-------------------------------------------------------------------------- - virtual void HandleConnection( int socket ) = 0; - - //-------------------------------------------------------------------------- - //! Update statistics of the received data - //-------------------------------------------------------------------------- - void UpdateSentData( char *buffer, uint32_t size ); - - //-------------------------------------------------------------------------- - //! Update statistics of the sent data - //-------------------------------------------------------------------------- - void UpdateReceivedData( char *buffer, uint32_t size ); - - //-------------------------------------------------------------------------- - //! Get sent bytes count - //-------------------------------------------------------------------------- - uint64_t GetSentBytes() const - { - return pSentBytes; - } - - //-------------------------------------------------------------------------- - //! Get the checksum of the sent data buffers - //-------------------------------------------------------------------------- - uint32_t GetSentChecksum() const - { - return pSentChecksum; - } - - //-------------------------------------------------------------------------- - //! Get the received bytes count - //-------------------------------------------------------------------------- - uint64_t GetReceivedBytes() const - { - return pReceivedBytes; - } - - //-------------------------------------------------------------------------- - //! Get the checksum of the received data buffers - //-------------------------------------------------------------------------- - uint32_t GetReceivedChecksum() const - { - return pReceivedChecksum; - } - - - private: - uint64_t pSentBytes; - uint64_t pReceivedBytes; - uint32_t pSentChecksum; - uint32_t pReceivedChecksum; -}; - -//------------------------------------------------------------------------------ -//! Client hander factory interface -//------------------------------------------------------------------------------ -class ClientHandlerFactory -{ - public: - //-------------------------------------------------------------------------- - //! Destructor - //-------------------------------------------------------------------------- - virtual ~ClientHandlerFactory() {}; - - //-------------------------------------------------------------------------- - //! Create a client handler - //-------------------------------------------------------------------------- - virtual ClientHandler *CreateHandler() = 0; -}; - -//------------------------------------------------------------------------------ -// Forward declaration -//------------------------------------------------------------------------------ -struct ClientHelper; - -//------------------------------------------------------------------------------ -//! Server emulator -//------------------------------------------------------------------------------ -class Server -{ - public: - enum ProtocolFamily - { - Inet4, - Inet6, - Both - }; - - typedef std::map > TransferMap; - - //-------------------------------------------------------------------------- - //! Constructor - //-------------------------------------------------------------------------- - Server( ProtocolFamily family ); - - //-------------------------------------------------------------------------- - //! Destructor - //-------------------------------------------------------------------------- - ~Server(); - - //-------------------------------------------------------------------------- - //! Listen for incomming connections and handle clients - //! - //! @param port port to listen on - //! @param accept number of clients to accept - //! @param factory client handler factory, the server takes ownership - //! of this object - //-------------------------------------------------------------------------- - bool Setup( int port, int accept, ClientHandlerFactory *factory ); - - //-------------------------------------------------------------------------- - //! Start the server - //-------------------------------------------------------------------------- - bool Start(); - - //-------------------------------------------------------------------------- - //! Wait for the server to finish - it blocks until all the clients - //! have been handled - //-------------------------------------------------------------------------- - bool Stop(); - - //-------------------------------------------------------------------------- - //! Get the statisctics of the sent data - //-------------------------------------------------------------------------- - std::pair GetSentStats( const std::string host ) const; - - //-------------------------------------------------------------------------- - //! Get the stats of the received data - //-------------------------------------------------------------------------- - std::pair GetReceivedStats( const std::string host ) const; - - //-------------------------------------------------------------------------- - //! Handle clients - //-------------------------------------------------------------------------- - int HandleConnections(); - - private: - - TransferMap pSent; - TransferMap pReceived; - pthread_t pServerThread; - std::vector pClients; - int pListenSocket; - ClientHandlerFactory *pHandlerFactory; - ProtocolFamily pProtocolFamily; - -}; - -} - -#endif // SERVER_HH diff --git a/tests/common/TestEnv.cc b/tests/common/TestEnv.cc deleted file mode 100644 index 7f570399563..00000000000 --- a/tests/common/TestEnv.cc +++ /dev/null @@ -1,117 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#include "TestEnv.hh" -#include "XrdCl/XrdClOptimizers.hh" - -namespace XrdClTests { - -XrdSysMutex TestEnv::sEnvMutex; -XrdCl::Env *TestEnv::sEnv = 0; -XrdCl::Log *TestEnv::sLog = 0; - -//------------------------------------------------------------------------------ -// Constructor -//------------------------------------------------------------------------------ -TestEnv::TestEnv() -{ - PutString( "MainServerURL", "localhost:1094" ); - PutString( "Manager1URL", "localhost:1094" ); - PutString( "Manager2URL", "localhost:1094" ); - PutString( "DiskServerURL", "localhost:1094" ); - PutString( "DataPath", "/data" ); - PutString( "RemoteFile", "/data/cb4aacf1-6f28-42f2-b68a-90a73460f424.dat" ); - PutString( "LocalFile", "/data/testFile.dat" ); - PutString( "MultiIPServerURL", "multiip:1099" ); - - ImportString( "MainServerURL", "XRDTEST_MAINSERVERURL" ); - ImportString( "DiskServerURL", "XRDTEST_DISKSERVERURL" ); - ImportString( "Manager1URL", "XRDTEST_MANAGER1URL" ); - ImportString( "Manager2URL", "XRDTEST_MANAGER2URL" ); - ImportString( "DataPath", "XRDTEST_DATAPATH" ); - ImportString( "LocalFile", "XRDTEST_LOCALFILE" ); - ImportString( "RemoteFile", "XRDTEST_REMOTEFILE" ); - ImportString( "MultiIPServerURL", "XRDTEST_MULTIIPSERVERURL" ); -} - -//------------------------------------------------------------------------------ -// Get default client environment -//------------------------------------------------------------------------------ -XrdCl::Env *TestEnv::GetEnv() -{ - if( !sEnv ) - { - XrdSysMutexHelper scopedLock( sEnvMutex ); - if( sEnv ) - return sEnv; - sEnv = new TestEnv(); - } - return sEnv; -} - -XrdCl::Log *TestEnv::GetLog() -{ - //---------------------------------------------------------------------------- - // This is actually thread safe because it is first called from - // a static initializer in a thread safe context - //---------------------------------------------------------------------------- - if( unlikely( !sLog ) ) - sLog = new XrdCl::Log(); - return sLog; -} - -//------------------------------------------------------------------------------ -// Release the environment -//------------------------------------------------------------------------------ -void TestEnv::Release() -{ - delete sEnv; - sEnv = 0; -// delete sLog; -// sLog = 0; -} -} - -//------------------------------------------------------------------------------ -// Finalizer -//------------------------------------------------------------------------------ -namespace -{ - static struct EnvInitializer - { - //-------------------------------------------------------------------------- - // Initializer - //-------------------------------------------------------------------------- - EnvInitializer() - { - using namespace XrdCl; - Log *log = XrdClTests::TestEnv::GetLog(); - char *level = getenv( "XRDTEST_LOGLEVEL" ); - if( level ) - log->SetLevel( level ); - } - - //-------------------------------------------------------------------------- - // Finalizer - //-------------------------------------------------------------------------- - ~EnvInitializer() - { - XrdClTests::TestEnv::Release(); - } - } initializer; -} diff --git a/tests/common/TestEnv.hh b/tests/common/TestEnv.hh deleted file mode 100644 index 7980419f1c3..00000000000 --- a/tests/common/TestEnv.hh +++ /dev/null @@ -1,62 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#ifndef __TEST_ENV_HH__ -#define __TEST_ENV_HH__ - -#include "XrdSys/XrdSysPthread.hh" -#include "XrdCl/XrdClEnv.hh" -#include "XrdCl/XrdClLog.hh" - -namespace XrdClTests { - -//------------------------------------------------------------------------------ -//! Envornment holding the variables for tests -//------------------------------------------------------------------------------ -class TestEnv: public XrdCl::Env -{ - public: - //-------------------------------------------------------------------------- - //! Constructor - //-------------------------------------------------------------------------- - TestEnv(); - - //-------------------------------------------------------------------------- - //! Get default test environment - //-------------------------------------------------------------------------- - static XrdCl::Env *GetEnv(); - - //-------------------------------------------------------------------------- - //! Get default test environment - //-------------------------------------------------------------------------- - static XrdCl::Log *GetLog(); - - //-------------------------------------------------------------------------- - //! Release the environment - //-------------------------------------------------------------------------- - static void Release(); - - private: - static XrdSysMutex sEnvMutex; - static XrdCl::Env *sEnv; - static XrdCl::Log *sLog; -}; - -} - -#endif // __TEST_ENV_HH__ diff --git a/tests/common/TextRunner.cc b/tests/common/TextRunner.cc deleted file mode 100644 index 77d8f92d8b7..00000000000 --- a/tests/common/TextRunner.cc +++ /dev/null @@ -1,154 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#include -#include -#include -#include -#include "PathProcessor.hh" - -//------------------------------------------------------------------------------ -// Print all the tests present in the test suite -//------------------------------------------------------------------------------ -void printTests( const CppUnit::Test *t, std::string prefix = "" ) -{ - if( t == 0 ) - return; - - const CppUnit::TestSuite *suite = dynamic_cast( t ); - std::cerr << prefix << t->getName(); - if( suite ) - { - std::cerr << "/" << std::endl; - std::string prefix1 = " "; prefix1 += prefix; - prefix1 += t->getName(); prefix1 += "/"; - const std::vector &tests = suite->getTests(); - std::vector::const_iterator it; - for( it = tests.begin(); it != tests.end(); ++it ) - printTests( *it, prefix1 ); - } - else - std::cerr << std::endl; -} - -//------------------------------------------------------------------------------ -// Find a test -//------------------------------------------------------------------------------ -CppUnit::Test *findTest( CppUnit::Test *t, const std::string &test ) -{ - //---------------------------------------------------------------------------- - // Check the suit and the path - //---------------------------------------------------------------------------- - std::vector elements; - eos::PathProcessor::splitPath( elements, test ); - - if( t == 0 ) - return 0; - - if( elements.empty() ) - return 0; - - if( t->getName() != elements[0] ) - return 0; - - //---------------------------------------------------------------------------- - // Look for the requested test - //---------------------------------------------------------------------------- - CppUnit::Test *ret = t; - for( size_t i = 1; i < elements.size(); ++i ) - { - CppUnit::TestSuite *suite = dynamic_cast( ret ); - CppUnit::Test *next = 0; - const std::vector &tests = suite->getTests(); - std::vector::const_iterator it; - for( it = tests.begin(); it != tests.end(); ++it ) - if( (*it)->getName() == elements[i] ) - next = *it; - if( !next ) - return 0; - ret = next; - } - - return ret; -} - -//------------------------------------------------------------------------------ -// Start the show -//------------------------------------------------------------------------------ -int main( int argc, char **argv) -{ - //---------------------------------------------------------------------------- - // Load the test library - //---------------------------------------------------------------------------- - if( argc < 2 ) - { - std::cerr << "Usage: " << argv[0] << " libname.so testname" << std::endl; - return 1; - } - void *libHandle = dlopen( argv[1], RTLD_LAZY ); - if( libHandle == 0 ) - { - std::cerr << "Unable to load the test library: " << dlerror() << std::endl; - return 1; - } - - //---------------------------------------------------------------------------- - // Print help - //---------------------------------------------------------------------------- - CppUnit::Test *all = CppUnit::TestFactoryRegistry::getRegistry().makeTest(); - if( argc == 2 ) - { - std::cerr << "Select your tests:" << std::endl << std::endl; - printTests( all ); - std::cerr << std::endl; - return 1; - } - - //---------------------------------------------------------------------------- - // Build the test suite - //---------------------------------------------------------------------------- - CppUnit::TestSuite *selected = new CppUnit::TestSuite( "Selected tests" ); - for( int i = 2; i < argc; ++i ) - { - CppUnit::Test *t = findTest( all, std::string( argv[i]) ); - if( !t ) - { - std::cerr << "Unable to find: " << argv[i] << std::endl; - return 2; - } - selected->addTest( t ); - } - - std::cerr << "You have selected: " << std::endl << std::endl; - printTests( selected ); - std::cerr << std::endl; - - //---------------------------------------------------------------------------- - // Run the tests - //---------------------------------------------------------------------------- - std::cerr << "Running:" << std::endl << std::endl; - CppUnit::TextUi::TestRunner runner; - runner.addTest( selected ); - - runner.setOutputter( - new CppUnit::CompilerOutputter( &runner.result(), std::cerr ) ); - - bool wasSuccessful = runner.run(); - dlclose( libHandle ); - return wasSuccessful ? 0 : 1; -} diff --git a/tests/common/Utils.cc b/tests/common/Utils.cc deleted file mode 100644 index cb6447405a6..00000000000 --- a/tests/common/Utils.cc +++ /dev/null @@ -1,69 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#include "Utils.hh" -#include "XrdCl/XrdClUtils.hh" -#include -#include -#include -#include -#include - -namespace XrdClTests { - -//------------------------------------------------------------------------------ -// Convert text CRC32 to int -//------------------------------------------------------------------------------ -bool Utils::CRC32TextToInt( uint32_t &result, const std::string &text ) -{ - std::vector res; - XrdCl::Utils::splitString( res, text, " " ); - if( res.size() != 2 ) - return false; - - char *cnvCursor; - result = ::strtoll( res[1].c_str(), &cnvCursor, 0 ); - if( *cnvCursor != 0 ) - return false; - - return true; -} - -//------------------------------------------------------------------------------ -// Fill the buffer with random data -//------------------------------------------------------------------------------ -ssize_t Utils::GetRandomBytes( char *buffer, size_t size ) -{ - int fileFD = open( "/dev/urandom", O_RDONLY ); - if( fileFD < 0 ) - return -1; - - char *current = buffer; - ssize_t toRead = size; - ssize_t currRead = 0; - while( toRead > 0 && (currRead = read( fileFD, current, toRead )) > 0 ) - { - toRead -= currRead; - current += currRead; - } - close( fileFD ); - - return size-toRead;; -} - -} diff --git a/tests/common/Utils.hh b/tests/common/Utils.hh deleted file mode 100644 index 05e15b74fc9..00000000000 --- a/tests/common/Utils.hh +++ /dev/null @@ -1,98 +0,0 @@ -//------------------------------------------------------------------------------ -// Copyright (c) 2011-2012 by European Organization for Nuclear Research (CERN) -// Author: Lukasz Janyst -//------------------------------------------------------------------------------ -// XRootD is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// XRootD is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with XRootD. If not, see . -//------------------------------------------------------------------------------ - -#ifndef UTILS_HH -#define UTILS_HH - -#include -#include -#include -#include - -namespace XrdClTests { - -//------------------------------------------------------------------------------ -//! A bunch of useful functions -//------------------------------------------------------------------------------ -class Utils -{ - public: - //-------------------------------------------------------------------------- - //! Convert string representation of a crc checksum to int - //! - //! @param result the resulting integer - //! @param text input sting - //! @return status of the conversion - //-------------------------------------------------------------------------- - static bool CRC32TextToInt( uint32_t &result, const std::string &text ); - - //-------------------------------------------------------------------------- - //! Fill the buffer with random data - //! - //! @param buffer the buffer to be filled - //! @param size size of the buffer - //! @return number of ranom bytes actually generated, -1 on error - //-------------------------------------------------------------------------- - static ssize_t GetRandomBytes( char *buffer, size_t size ); - - //-------------------------------------------------------------------------- - //! Get initial CRC32 value - //-------------------------------------------------------------------------- - static uint32_t GetInitialCRC32() - { - return crc32( 0L, Z_NULL, 0 ); - } - - //-------------------------------------------------------------------------- - //! Compute crc32 checksum out of a buffer - //! - //! @param buffer data buffer - //! @param len size of the data buffer - //-------------------------------------------------------------------------- - static uint32_t ComputeCRC32( const void *buffer, uint64_t len ) - { - return crc32( GetInitialCRC32(), (const Bytef*)buffer, len ); - } - - //-------------------------------------------------------------------------- - //! Update a crc32 checksum - //! - //! @param crc old checksum - //! @param buffer data buffer - //! @param len size of the data buffer - //-------------------------------------------------------------------------- - static uint32_t UpdateCRC32( uint32_t crc, const void *buffer, uint64_t len ) - { - return crc32( crc, (const Bytef*)buffer, len ); - } - - //-------------------------------------------------------------------------- - //! Combine two crc32 checksums - //! - //! @param crc1 checksum of the first data block - //! @param crc2 checksum of the second data block - //! @param len2 size of the second data block - //-------------------------------------------------------------------------- - static uint32_t CombineCRC32( uint32_t crc1, uint32_t crc2, uint64_t len2 ) - { - return crc32_combine( crc1, crc2, len2 ); - } -}; -}; - -#endif // UTILS_HH diff --git a/ups/eupspkg.cfg.sh b/ups/eupspkg.cfg.sh deleted file mode 100644 index 60c2468f705..00000000000 --- a/ups/eupspkg.cfg.sh +++ /dev/null @@ -1,37 +0,0 @@ -# EupsPkg config file. Sourced by 'eupspkg' - -# Breaks on Darwin w/o this -export LANG=C - -PKGDIR=$PWD -BUILDDIR=$PWD/../xrootd-build - -config() -{ - rm -rf ${BUILDDIR} - mkdir ${BUILDDIR} - cd ${BUILDDIR} - cmake ${PKGDIR} -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=${PREFIX} -DENABLE_PERL=FALSE -} - -build() -{ - cd ${BUILDDIR} - default_build -} - -install() -{ - cd ${BUILDDIR} - make -j$NJOBS install - - ARCH=`arch` - case "${ARCH}" in - x86_64) mkdir -p ${PREFIX}/lib && cd ${PREFIX}/lib && ln -s ../lib64/* . ;; - *) echo "Default behaviour for managing lib(64)/ directory" ;; - esac - - - cd ${PKGDIR} - install_ups -} diff --git a/ups/xrootd.table b/ups/xrootd.table deleted file mode 100644 index 5bc243d1c2a..00000000000 --- a/ups/xrootd.table +++ /dev/null @@ -1,5 +0,0 @@ -envPrepend(PATH, ${PRODUCT_DIR}/bin) -envPrepend(LD_LIBRARY_PATH, ${PRODUCT_DIR}/lib) -envPrepend(DYLD_LIBRARY_PATH, ${PRODUCT_DIR}/lib) -envPrepend(LSST_LIBRARY_PATH, ${PRODUCT_DIR}/lib) -envPrepend(MANPATH, ${PRODUCT_DIR}/share/man:) diff --git a/utils/XrdCmsNotify.pm b/utils/XrdCmsNotify.pm deleted file mode 100755 index e166f4affba..00000000000 --- a/utils/XrdCmsNotify.pm +++ /dev/null @@ -1,198 +0,0 @@ -#!/usr/bin/env perl - -#****************************************************************************** -# * -# X r d C m s N o t i f y . p m * -# * -# (c) 2018 by the Board of Trustees of the Leland Stanford, Jr., University * -# All Rights Reserved * -# Produced by Andrew Hanushevsky for Stanford University under contract * -# DE-AC03-76-SFO0515 with the Department of Energy * -#****************************************************************************** - -package XrdCmsNotify; -require Exporter; -@ISA = qw(Exporter); -@EXPORT= qw(setDebug FileGone FileHere SendMsg); -use Socket; -{1} - -#****************************************************************************** -#* F i l e G o n e * -#****************************************************************************** - -#Call: FileGone([iname,]paths); - -#Input: iname - is the instance name. If the instance name starts with a -# slash, it is treated as a path and the cmsd is unnamed. -# @paths - an array of paths that are no longer on the server. -# -#Processing: -# The message is sent to the local cmsd so that the manager cmsd's are -# informed that each file is no longer available on this server. -# -#Output: None. - -sub FileGone {my(@paths) = @_; - my($file, $iname); - -# Get the instance name -# -$iname = shift(@paths) if substr($paths[0],0,1) ne '/'; - -# Send a message for each path in the path list -# -foreach $file (@paths) {&SendMsg($iname, "gone $file");} -} - -#****************************************************************************** -#* F i l e H e r e * -#****************************************************************************** - -#Call: FileHere([iname,]paths); - -#Input: iname - is the instance name. If the instance name starts with a -# slash, it is treated as a path and the cmsd is unnamed. -# @paths - an array of paths that are available on the server. -# -#Processing: -# The message is sent to the local Ocmsd so that the manager cmsd's are -# informed that each file is now available on this server. -# -#Output: None. - -sub FileHere {my(@paths) = @_; - my($file, $iname); - -# Get the instance name -# -$iname = shift(@paths) if substr($paths[0],0,1) ne '/'; - -# Send a message for each path in the path list -# -foreach $file (@paths) {&SendMsg($iname, "have $file");} -} - -#****************************************************************************** -#* S e n d M s g * -#****************************************************************************** - -#Input: $iname - instance name, if any -# $msg - message to be sent -# -#Processing: -# The message is sent to the cmsd udp path indicated in the cmsd.pid file -# -#Output: 0 - Message sent -# 1 - Message not sent, could not find the pid file or cmsd not running -# -#Notes: 1. If an absolute path is given, we check whether the in the -# file is still alive. If it is not, then no messages are sent. - -sub SendMsg {my($iname,$msg) = @_; - -# Allocate a socket if we do not have one -# - return 1 if !fileno(CMSSOCK) && !getSock($iname); - -# Get the target if we don't have it -# - if (!kill(0, $CMSPID)) - {close(CMSSOCK); - return 1 if !getSock($iname); - } - -# Send the message -# - print STDERR "CmsNotify: Sending message '$msg'\n" if $DEBUG; - chomp($msg); - return 0 if send(CMSSOCK, "$msg\n", 0, $CMSADDR); - print STDERR "CmsNotify: Unable to send to cmsd $CMSPID; $!\n" if $DEBUG; - return 1; -} - -#****************************************************************************** -#* s e t D e b u g * -#****************************************************************************** - -#Input: $dbg - True if debug is to be turned off; false otherwise. -# -#Processing: -# The global debug flag is set. -# -#Output: Previous debug setting. - -sub setDebug {my($dbg) = @_; - my($olddbg) = $DEBUG; - $DEBUG = $dbg; - return $olddbg; -} - -#****************************************************************************** -#* P r i v a t e F u n c t i o n s * -#****************************************************************************** -#****************************************************************************** -#* g e t S o c k * -#****************************************************************************** - -sub getSock {my($iname) = @_; - my($path); - -# Get the path we are to use -# - return 0 if !($path = getConfig($iname)); - -# Create the path we are to use -# - $path = "$path/olbd.notes"; - $path =~ tr:/:/:s; - $CMSADDR = sockaddr_un($path); - -# Create a socket -# - if (!socket(CMSSOCK, PF_UNIX, SOCK_DGRAM, 0)) - {print STDERR "CmsNotify: Unable to create socket; $!\n"; - return 0; - } - return 1; -} - -#****************************************************************************** -#* g e t C o n f i g * -#****************************************************************************** - -sub getConfig {my($iname) = @_; - my($fn, @phval, $path, $line, $pp1, $pp2); - -# Construct possible pid paths -# - if ($iname eq '') - {$pp1 = '/tmp/cmsd.pid'; $pp2 = '/var/run/cmsd/cmsd.pid';} - else - {$pp1 = "/tmp/$iname/cmsd.pid"; $pp2 = "/var/run/cmsd/$iname/cmsd.pid";} - -# We will look for the pid file in one of two locations -# - if (-r $pp1) {$fn = $pp1;} - elsif (-r $pp2) {$fn = $pp2;} - else {print STDERR "CmsNotify: Unable to find cmsd pid file\n" if $DEBUG; - print STDERR "as $pp1 or $pp2\n" if $DEBUG; - return ''; - } - - if (!open(INFD, $fn)) - {print STDERR "CmsNotify: Unable to open $fn; $!\n" if $DEBUG; return '';} - - @phval = ; - close(INFD); - chomp(@phval); - $CMSPID = shift(@phval); - undef($path); - if (kill(0, $CMSPID)) - {foreach $line (@phval) - {($path) = $line =~ m/^&ap=(.*)$/; last if $path;} - if (!$path) - {print STDERR "CmsNotify: Can't find cmsd admin path\n" if $DEBUG;} - } else {print STDERR "CmsNotify: cmsd process $CMSPID dead\n" if $DEBUG;} - return $path; -} diff --git a/utils/XrdOlbMonPerf b/utils/XrdOlbMonPerf deleted file mode 100755 index af62ae2b520..00000000000 --- a/utils/XrdOlbMonPerf +++ /dev/null @@ -1,283 +0,0 @@ -#!/usr/bin/env perl - -## $Id$ -## this script collects the informations on the server activity like: -## CPU, network I/O, memory usage ... -## modified to get rid of rperf. -## J-Y Nief - CCIN2P3 - 24/05/04 -## modified to accept additional options and drop multi-home uf's. -# Andy Hanushevsky - SLAC - 15/07/04 -# -## rewrite the network part -## get rid of the netstat daemon, causing zombies proliferation -## fix the netstat parameters positions for both linux and sunos -## added debug mode -d -# Fabrizio Furano - CERN - 20/05/09 -# -# Usage: XrdOlbMonPerf [-I netintf] [-n netspeed] [-m ] [-d] [] -# WARNING: if you are running this script on a Linux RH7.x server, -# the network metric is disabled as the netstat output is screwed, -# this info is useless. no problem for RH9 and RHEL3. - -# get the name of the server + platform -undef($shmid); -$shmkey = ''; -$rhost = `/bin/uname -n`; chomp($rhost); -$enBps = 131072000; -$systype= `/bin/uname`; chomp($systype); -$repval = 0; -$netif = ''; -$netmax = 0; # bits / s -$monint = 60; -$debug = 0; -$precsumio = -1; -$| = 1; # unbuffer STDOUT - -# Get options -# -while(($argval = shift) && (substr($argval,0,1) eq '-')) { - if ($argval eq '-I') { - Log("Network interface not specified.") if !($netif=shift); - } - elsif ($argval eq '-n') { - Log("Network speed not specified.") if !($netmax=shift); - } - elsif ($argval eq '-m') { - Log("Shared segment not specified.") if !($shmkey=shift); - $shmid = shmget($shmkey, 4096, 0744) if (shmkey ne '.'); - Log("Unable to attach shared memory; $!") if !defined($shmid); - } - elsif ($argval eq '-d') { - Debug("Debug mode enabled."); - $debug = 1; - } - else { - Log("Unknown option, $argval, ignored", 1); - } -} - - -Debug("Output is ") if ($debug == 1); - -# Get the interval -# -if ($argval) - {$monint = $argval; - Log("Invalid interval, '$monint'") if !($monint =~ /^\d+$/); - } - -Debug("Monitoring interval: '$monint'") if ($debug == 1); - -# get the number of CPUs on this server -if ( $systype eq 'Linux' ) { - $numCPU = `grep -c cpu /proc/stat` - 1; -} - -if ( $systype eq 'SunOS' ) { - $resp = `uname -X | grep NumCPU`; - chomp($resp); - @answer = split('= ', $resp); - $numCPU = $answer[1]; -} - -Debug("Number of CPUs: '$numCPU'") if ($debug == 1); - -# get the total memory of the server -if ($systype eq 'Linux') { - $resp = `grep MemTotal /proc/meminfo`; - @answer = split('\s+', $resp); - $TotMem = $answer[1]*1024; -} - -if ($systype eq 'SunOS') { - $resp = `/etc/prtconf | grep Memory`; - ($TotMem) = $resp =~ /.+:\s*(\d+)/s; - $TotMem *= 1024*1024; -} - -Debug("Total memory: '$TotMem'") if ($debug == 1); - -# if it is a Linux, retrieve the distrib version. -# necessary for netstat which does not give the same output on RH 7.x, RH 9, -# RHEL... -# netstat usable on RH 9 and RHEL 3 ==> distrib = "RH_OK", else "RH_WRONG" -if ($systype eq 'Linux') { - $distrib = "RH_OK"; - - if ( -r '/etc/redhat-release' ) { - $resp = `cat /etc/redhat-release`; - } else { - if ( -r '/etc/debian-version' ) { - $resp = "debian".`cat /etc/debian-version`; - } - else { - $resp = ""; - } - } - - if ( $resp =~ /release 7./ ) { $distrib = "RH_WRONG"; } -} - -Debug("Distro: '$distrib'") if ($debug == 1); - -# determine the column where the idle time and the free mem -# can be found in the 'vmstat' output. -# depends on the platform and on the 'vmstat' version, sigh... -$ind_idle = 0; -$ind_free = 0; -@output = `vmstat`; -chomp($output[1]); -@answer = split('\s+', $output[1]); -foreach $type(@answer) { - last if ( $type eq 'id' ); - $ind_idle++; -} -foreach $type(@answer) { - last if ( $type eq 'free' ); - $ind_free++; -} - -Debug("vmstat output. Idle is col: '$ind_idle'. Free mem is col: '$ind_free'") if ($debug == 1); - -# in the netstat output, the number of input and output packets is not in the -# same columns whether it is a Linux or SunOS platform -if ( $systype eq 'SunOS' ) { - $ind_mtu = 1; - $ind_net1 = 4; - $ind_net2 = 6; -} else { - $ind_mtu = 1; - $ind_net1 = 3; - $ind_net2 = 7; -} -$indx_net = 0; - -Debug("netstat output. Input pkt is col: '$ind_net1'. Output pkt is col: '$ind_net2'") if ($debug == 1); - -# check the number of network interface on the server and their -# capability (ethernet or gigabit) -$mtu = 1500; -if (!$netmax) - {@com = `netstat -i$netif`; - foreach $line(@com) { - if ( $line !~ /Kernel Interface/ && $line !~ /MTU/ ) { - ( $line =~ /eth/ ) && ( $netmax = 1e7 ); - ( $line =~ /ge/ || $line =~ /ce/ ) && ( $netmax = 1e8 ); - } - } - if (!$netmax) {$netmax = 1e8;} - - # 10/100 cards are rare nowadays. And there is always the possibility of declaring the right value - # with the -n switch - $netmax = 1e8 if ($systype eq 'Linux'); - } - -Debug("netmax is $netmax") if ($debug == 1); - -# Discard the first three lines of output -# -for ($i = 0; $i < 3; $i++) {$resp = ;} - -$ipio_old = 0; -while(1) { - - # what's the CPU utilization ? - @respCPU = `vmstat 1 2`; - chomp $respCPU[3]; - @resp_vmstat = split('\s+',$respCPU[3]); - if ( ($resp_vmstat[$ind_idle] > 100) || ($resp_vmstat[$ind_idle] < 0) ) { - $cpu = 0; - } else { - $cpu = $resp_vmstat[$ind_idle-2] + $resp_vmstat[$ind_idle-1]; - } - - # what's the runq load ? - chomp($respLoad = `uptime`); - @respSplit = split(',',$respLoad); - Debug("uptime reports load5:$respSplit[5]") if ($debug == 1); - - $load = int($respSplit[5]*100/$numCPU); - $load = 100 if $load > 100; - - # what's the network I/O activity? - @resp = `netstat -i`; - - if ($distrib ne 'RH_WRONG') { - $sumio = 0; - foreach $line (@resp) { - Debug("$line") if ($debug == 1); - - if ( $line !~ /Iface/ && $line !~ /Kernel/ && $line !~ /Name/) { - @respSplit = split(' ',$line); - - $mtu = $respSplit[$ind_mtu]; - $mtu = 1500 if $mtu == 0; - if ($respSplit[$0] !~ /lo/) { - Debug("$respSplit[$0] - Input pkts: $respSplit[$ind_net1] Output pkts: $respSplit[$ind_net2] Mtu: $mtu") if ($debug == 1); - $sumio += ($respSplit[$ind_net1] + $respSplit[$ind_net2]) * $mtu; - } - } - } - - Debug("Summed network traffic: $sumio") if ($debug == 1); - - $precsumio = $sumio if ($precsumio < 0); - $net_activity = ($sumio - $precsumio) / $monint; - Debug("net_activity: $net_activity") if ($debug == 1); - - $precsumio = $sumio; - } - - - # This is to support 1G, 10G cards adaptively, or a number of cards together - # In practice, we guess the max throughput of the network system - # in order to calculate a decent percent utilization estimation - # in the case the initial guess is completely wrong - $netmax = $netmax * 2 if ( $net_activity > ($netmax * 2.5) ); - - # We have to divide by two, but I really do not understand why - # ... this was also in the previous implementation and it works - $ipio = int ( $net_activity / $netmax * 100 / 2 ); - - - Debug("Calculated ipio: $ipio") if ($debug == 1); - $ipio = 100 if ($ipio > 100); - - - # what's the memory load ? - $used = $TotMem - $resp_vmstat[$ind_free]*1024; - $mem = int($used/$TotMem*100); - $mem = 100 if $mem > 100; - # - $repval++; - - # what's the paging I/O activity ? - # useless as this metric is highly correlated with some of the others above. - # being kept for backward compatibility with the load balancer. - $pgio = 0; - - if (!defined($shmid)) { - print "$load $cpu $mem $pgio $ipio\n"; - } - else { - $resp = pack('LLLLL',$load,$cpu,$mem,$pgio,$ipio); - Log("Unable to write into shared memory; $!") - if !shmwrite($shmid, $resp, 0, length($resp)); - } - - sleep($monint-1); -} - - -sub Log { - my($msg, $ret) = @_; - print STDERR "XrdOlbMonPerf: $msg\n"; - system('/bin/logger', '-p', 'daemon.notice', "XrdOlbMonPerf: $msg"); - return 0 if $ret; - exit(1); -} - -sub Debug { - my($msg) = @_; - print STDERR "XrdOlbMonPerf: $msg\n"; -} diff --git a/utils/hpsscp b/utils/hpsscp deleted file mode 100755 index 9325d4122d2..00000000000 --- a/utils/hpsscp +++ /dev/null @@ -1,820 +0,0 @@ -#!/usr/bin/env perl - -# $Id$ - -# (C) 2009 by the Board of Trustees of the Leland Stanford, Jr., University -# All Rights Reserved -# Produced by Andrew Hanushevsky for Stanford University under contract -# DE-AC02-76-SFO0515 with the Deprtment of Energy - -# This command stages in a file from a remote site. The syntax is: - -# {frm_xfr.HPSS | hpsscp} [options] <[usr@]host>: | -# <[usr@]host>: - -# options: - -# [-A []] [-c ] [-d] [-f] [-F {hide|none|oute|outo|}] - -# [-k ] [-m] [-M offs] [-n] [-p ] [-P ] [-r] [-s ] - -# [-t ] [-x ] [-w ] [-z] - -# options (valid only under hpsscp): - -# [-C] [-N ] [-Q ] [-W ] - -# -A send the output filename via a udp alert to the receiving host: -# If port is not specified, the default port is used. - -# -c supplies an option configuration file. The options in the file are -# added in front of whatever options remain. The file is structured -# as if you specified the options on the command line either in a single -# line or multiple lines separated by linends. - -# -d turn on debugging. - -# -k is the location of the keytab which should contain a single password -# to be used when logging in (default is /var/adm/xrootd/pftp/keyfile). - -# -f force file retrieval even if the file exists (i.e., replace). -# This is the default for zero length files. - -# -F specified the '.fail' file handling. The default is to create ".fail" -# when a transfer fails. The file contains the pftp session along with any -# pftp responses. You can change this as: -# hide Creates a dot file instead: "tpath>/..fail" -# none Does not create anything and stays silent about it. -# oute Print what would have been written to ".fail" to stderr. -# This is the default for hpsscp! -# outo Print what would have been written to ".fail" to stdout. -# Creates the file in the directory as "/.fail". - -# -m exit with non-zero status if the file exists and has a non-zero length. - -# -M specified he offset into the target filename where directories are to -# start being created. See notes on auto-path creation. - -# -n do not redirect standard error when invoking the copy function - -# -o the onwership information in the form of : where either one -# or both may be specified. The default comes from the data source. -# Ownership can only be maintained by root proceses or pftp logins. - -# -P The MSS port number to use (default is 2021). - -# -p set the protection mode of the file. Specify 1 to 4 octal digits, the -# letter 'r' for 0440, or the letter 'w' for 640. - -# -s specifies the storage class; where is a number. This has only -# meaning if is remote. - -# -t number of times to retry a failing copy that is retriable (default is 2). - -# -x The pftp command to use (default is /opt/xrootd/utils/pftp_client). - -# -w maximum number of seconds to wait between retries. Default is 240. - -# -z do not reset the process group id. - -# hpsscp options: -# -C continue execution even when a transfer slot cannot be obtained. By -# default, the transfer does not occur without a transfer slot if a queue -# directory exists. -# -# -N the location of the name space directory. If not specified, no check is -# made whether or not a directory path exists in hpss. This causes mkdir's -# to be executed for each copy, which is ineffecient and slow. With -N, a -# shadow directory name space is maintained to avoid duplicate mkdir's. - -# -Q the location of execution queue directory. This directory must contain -# at least one file and may contain any number. The number of files -# represents the maximum number of simultaneous requests allowed. Requests -# over the limit wait until a execution slot frees up. The default is -# "/var/hpss/xfrq", if it exists. - -# -W the location of the wait41 program that implements the transfer queue -# limits. The default is "/opt/xrootd/utils/wait41". - -# indicates that the file is in the Mass Storage System. The -# is used as the login name and is the location of the MSS. -# Only one of or may have this designation but -# one must have it. If user if omitted, the current user is used. - -# Auto-path creation is supported in three ways. Primarily, if a -# space appears in the hpss target path, then directories to -# left of the space are assumed to exist and directories to the -# are created prior to the pput of the file. If you do not want to -# use space notation, use the -M option to specify where the space -# would have occurred. This location must have a slash ('/'). -# For hpsscp, the -N option can be used to provide a reference name -# space that will determine which directories will be created. - -# Upon success, a zero status code is returned. Failure causes a message to -# be issued to stderr and a non-zero exit status to be returned. - -use IPC::Open2; -use Socket; - -# Global variables: -# -($CMD) = "/$0" =~ m|^.*/([^.]*).*$|g; -$isCPY = $CMD =~ m/^hpsscp/; -$MsgPfx = "$CMD: "; -$ER_SFX = '.fail'; # Suffix for fail file when error occurrs -$OKFile = 1; # If file already present, all is good. -$Replace = undef; # Replace existing file, if any -$Mode = undef; - -$Debug = 0; -$ErrLog = ''; # Command stream and response -$ErrDisp = ($isCPY ? 'oute' : ''); -$ErrFD = 0; -$ErrFile = ''; # Absolute name of where error data resides - -$newPGRP = 1; # Set new process group -$DMode = 0775; # Directory create mode -$doGet = 1; # We assume we will be doing gets -$UDPort = 0; # Alert udp port -$UDPdef = 8686; # Alert udp port default - -# hpsscp specific items -# -$nsDir = ''; -$nsMkd = ''; -$xqDir = (-d '/var/hpss/xfrq' ? '/var/hpss/xfrq' : ''); -$WT41 = (-x '/opt/xrootd/utils/wait41' ? 'opt/xrootd/utils/wait41' : ''); -$PFOK = 0; - -# Transfer specific items -# -$XfrCmd = '/opt/xrootd/utils/pftp_client'; -$XfrArgs = '-n -v'; -$XfrOut = '2>&1'; # The redirection of stderr to stdout -$XfrHost = ''; # MSS host name -$XfrPort = '2021'; # MSS port number -$XfrUser = ''; # MSS user name -$XfrPswd = ''; # MSS user password -$XfrKey = '/var/adm/xrootd/pftp/keyfile'; -$XfrGet = 'open %xfrhost %xfrport;user %xfruser %xfrpswd;binary;' . - 'setpblocksize 2097152;pget %xfrsfn %xfrtfn;quit;'; -$XfrPut1 = 'open %xfrhost %xfrport;user %xfruser %xfrpswd;delete %xfrtfn'; -$XfrPut2 = ';binary;setpblocksize 2097152;pput %xfrsfn %xfrtfn'; - -$XfrGID = ''; # The group ID (usually from source file) -$XfrUID = ''; # The user ID (usually from source file) -$XfrCOS = -1; # Class of Service -$XfrApnd = ''; # Value of '-a' -$XfrBase = ''; # Target prior to appending with XfrApnd -$XfrMaxTR= 2; # Maximum Number of tries. -$XfrMaxWT= 240; # Maximum Retry Wait Time. - -$XfrSrcFN= ''; # The source file -$XfrTrgFN= ''; # The target file -$XfrResp = ''; # The transfer response -$XfrSess = ''; # The transfer session -$XfrRC = 0; # The final return code -$XfrMoff = -1; - -# Get the options -# - $CmdLine = join(' ',@ARGV); - while (substr($ARGV[0], 0, 1) eq '-') { - $op = shift(@ARGV); - if ($op eq '-A') {if (&IsNum($ARGV[0])) {$UDPort= &GetVal('port', 'N');} - else {$UDPort = $UDPdef;} - } - elsif ($op eq '-c') {$CFile = &GetVal('config file'); AddOpts($CFile);} - elsif ($op eq '-d') {$Debug = 1;} - elsif ($op eq '-k') {$XfrKey = &GetVal('key file');} - elsif ($op eq '-f') {$Replace = 1;} - elsif ($op eq '-F') {$ErrDisp = &GetVal('fail handing'); - &Emsg("Invalid fail file disposition '$ErrDisp'.") - if !($ErrDisp =~ /^(hide|none|outo|oute|\/.*)$/); - } - elsif ($op eq '-m') {$OKFile = 0;} - elsif ($op eq '-M') {$XfrMoff = &GetVal('offset', 'N', 0);} - elsif ($op eq '-n') {$XfrOut = ''} - elsif ($op eq '-o') {&GetOwners(&GetVal('ownership'));} - elsif ($op eq '-P') {$XfrPort = &GetVal('port', 'N');} - elsif ($op eq '-p') {$Mode = &GetVal('mode'); - if ($Mode eq 'r') {$Mode = oct('0440');} - elsif ($Mode eq 'w') {$Mode = oct('0640');} - else {&Emsg("Invalid mode '$Mode'.") - if !IsOct($Mode) || $Mode > 7777; - $Mode = oct($Mode); - $Mode = undef if $Mode == 0; - } - } - elsif ($op eq '-s') {$XfrCOS = &GetVal('storage class', 'N');} - elsif ($op eq '-t') {$XfrMaxTR = &GetVal('max tries', 'N', 1);} - elsif ($op eq '-w') {$XfrMaxWT = &GetVal('max wait', 'N', 1);} - elsif ($op eq '-x') {$XfrCmd = &GetVal('transfer command');} - elsif ($op eq '-z') {$newPGRP = 0;} - elsif ($op eq '-C' && $isCPY) - {$PFOK = 1;} - elsif ($op eq '-N' && $isCPY) - {$nsDir = &GetDir('name space directory');} - elsif ($op eq '-Q' && $isCPY) - {$xqDir = &GetDir('xfer queue directory');} - elsif ($op eq '-W' && $isCPY) - {$WT41 = &GetVal('wait41 command'); - &Emsg(ET("Invalid -W command target")) if !-x $WT41; - } - else {&Emsg("Invalid option '$op'.",-1);} - } - &SayDebug("cmd = $CmdLine"); - -# Get the source and target filenames -# - &Emsg('Source file not specified.',-1) if (!($XfrSrcFN = $ARGV[0])); - &Emsg('Target file not specified.',-1) if (!($XfrTrgFN = $ARGV[1])); - &Emsg("Extraneous parameter, $ARGV[2].") if ($ARGV[2]); - -# See if the source is a remote location -# - ($mss_uhs, $XfrSrcFN) = split(':', $XfrSrcFN, 2); - if (!$XfrSrcFN) {$XfrSrcFN = $mss_uhs, $mss_uhs = '';} - else {$uhLoc = 'source'; $doGet = 1;} - ($mss_uht, $XfrTrgFN) = split(':', $XfrTrgFN, 2); - if (!$XfrTrgFN) {$XfrTrgFN = $mss_uht, $mss_uht = '';} - else {$uhLoc = 'target'; $doGet = 0;} - -# Make sure we have something but not too much of it -# - &Emsg("Source and target files may not be both remote files!") - if ($mss_uhs ne '' && $mss_uht ne ''); - &Emsg("Source and target files may not be both local files!") - if ($mss_uhs eq '' && $mss_uht eq ''); - -# Extract out the user and host -# - $mss_uh = ($mss_uhs ? $mss_uhs : $mss_uht); - if (index($mss_uh, '@') >= 0) {($XfrUser, $XfrHost) = split('@', $mss_uh);} - else {$XfrUser = (getpwuid($>))[0]; $XfrHost = $mss_uh;} - &Emsg("User missing in $uhLoc file specification.") if $XfrUser eq ''; - &Emsg("Host missing in $uhLoc file specification.") if $XfrHost eq ''; - -# Cleanup nsDir option -# - if ($nsDir ne '') - {while ($nsDir =~ s|//|/|g) {} - $nsDir .= $sfx if ($sfx = chop($nsDir)) ne '/'; - } - -# If we need to append something, do so now -# - if (!$doGet) - {if (index($XfrTrgFN, ' ') >= 0) - {($XfrBase, $XfrApnd) = split(/ /, $XfrTrgFN, 2); - $XfrTrgFN = $XfrBase.'/'.$XfrApnd; - } elsif ($XfrMoff >= 0) - {&Emsg("-M offset does not refer to a slash.") - if substr($XfrTrgFN, $XfrMoff, 1) ne '/'; - $XfrBase = substr($XfrTrgFN, 0, $XfrMoff); - $XfrApnd = substr($XfrTrgFN, $XfrMoff+1); - } - elsif ($nsDir ne '') {($XfrBase, $XfrApnd) = Get_Base($XfrTrgFN);} - } - -# Remove double slashes -# - while ($XfrSrcFN =~ s|//|/|g) {} - while ($XfrTrgFN =~ s|//|/|g) {} - &SayDebug("Base=$XfrBase Append=$XfrApnd Target=$XfrTrgFN") - if $Debug && ($XfrBase ne '' || $XfrApnd ne ''); - -# Handle error file disposition -# - if ($ErrDisp eq '') - {$ErrFile = ($doGet ? $XfrTrgFN : $XfrSrcFN).$ER_SFX;} - elsif ($ErrDisp eq 'hide') - {my($edir, $efn) = $XfrTrgFN =~ /^(.*)\/(.*)$/g; - $ErrFile = $edir.'/.'.$efn.$ER_SFX; - } - elsif ($ErrDisp eq 'oute') {$ErrFD = STDERR; $ErrDisp = 'prnt';} - elsif ($ErrDisp eq 'outo') {$ErrFD = STDOUT; $ErrDisp = 'prnt';} - elsif (substr($ErrDisp,0,1) eq '/') - {$ErrFile = $ErrDisp.$XfrTrgFN.$Er_SFX; - while ($ErrFile =~ s|//|/|g) {} - } - unlink($ErrFile) if $ErrFile ne ''; - -# If this is a put, make sure the source file exists before we do anything -# - if (!$doGet) - {@Svec = stat($XfrSrcFN); - &CleanUp(&Emsg(ET("Unable to put $XfrSrcFN"), 0)) if $Svec[7] eq ''; - $XfrUID = $Svec[4] if $XfrUID eq ''; - $XfrGID = $Svec[5] if $XfrGID eq ''; - &MakePath($nsMkd) if !($rc = TransPace(0,$Svec[7])) && $nsMkd ne ''; - &CleanUp($rc); - } - -# Check if the file already exists in the cache. If it doesn't create the -# directory path to the file. -# - @Svec = stat($XfrTrgFN); - if ($Svec[7]) - {if (!$Replace) - {utime(time(), $Svec[9], $XfrTrgFN); - &CleanUp(&Emsg("File $XfrTrgFN already exists.", $OKFile)); - } else {truncate($XfrTrgFN, 0);} - } else {&MakePath($XfrTrgFN);} - -# At this point we need to bring the file into this cache. Issue the -# appropriate command to do this and set the access mode if it succeeded. -# - &CleanUp($rc) if ($rc = TransPace(1,-1)); - &Emsg(ET("Unable to set mode for '$XfrTrgFN'"), 1) - if (defined($Mode) && !chmod($Mode, $XfrTrgFN)); - -# All done. -# - &CleanUp(0); - -#****************************************************************************** -#* A d d O p t s * -#****************************************************************************** - -sub AddOpts {my($cfn) = @_; my(@clines); - - &Emsg(ET("Unable to open config file '$cfn'")) if !open(CFD, $cfn); - - @clines = ; - chomp(@clines); - @clines = split(/ /,join(' ', @clines)); - unshift(@ARGV, @clines); - close(CFD); -} - -#****************************************************************************** -#* C l e a n u p * -#****************************************************************************** - -sub CleanUp {my($rc) = @_; my($eFN); - if ($rc && ($ErrLog || $XfrSess)) - {$XfrSess =~ s/\;/\n/g; - if ($ErrFile) - {$eFN = (-e $ErrFile ? '>>' : '>').$ErrFile; - if (!MakePath($ErrFile) || !open(ERFD,$eFN)) {$ErrFD=STDERR;} - else {print(ERFD $XfrSess,$ErrLog); close(ERFD);} - } - print($ErrFD $XfrSess,$ErrLog) if $ErrFD; - } - exit(($rc > 255 ? 255 : $rc)); -} - -#****************************************************************************** -#* G e t _ B a s e * -#****************************************************************************** - -sub Get_Base {my($Path) = @_; my($dir, $apnd, $Base); - - my(@dirs) = split('/', $Path); - my($fn) = pop(@dirs); - shift(@dirs) if ($dirs[0] eq ''); - while(($dir = shift(@dirs)) ne '') - {last if !-d $nsDir.$Base.'/'.$dir; $Base .= '/'.$dir;} - if ($dir ne '') - {while(($apnd = shift(@dirs)) ne '') {$dir .= '/'.$apnd;} - $nsMkd = $nsDir.$Path; - $dir .= '/'.$fn; - return ($Base, $dir); - } - return ('', ''); -} - -#****************************************************************************** -#* G e t _ F i l e * -#****************************************************************************** - -sub Get_File {my($cmd, $resp, $GetTries); - - # Construct the command stream - # - $cmd = &TransCmd($XfrGet); - - # Execute the command to get the file (as many times as wanted/needed) - # - $GetTries = 0; - do {return 0 if &Transfer($cmd); - $GetTries += &TransErr($GetTries+1); - } while($GetTries < $XfrMaxTR); - return $XfrRC; -} - -#****************************************************************************** -#* P u t _ A l e r t * -#****************************************************************************** - -sub Put_Alert {my($ip_addr); - -# if there is no need to send an alert, simply return -# - return 0 if !$UDPort; - -# Get a udp socket -# - if (!socket(MYSOCK, PF_INET, SOCK_DGRAM, getprotobyname("udp"))) - {&SayDebug("Unable to get socket for alert; $!", 1); return 0;} - -# Convert destination to a sockaddr -# - if (substr($XfrHost,0,1) =~ m/\d/) {$ip_addr = inet_aton($XfrHost);} - else {$ip_addr = gethostbyname($XfrHost);} - my($ip_dest) = pack_sockaddr_in($UDPort, $ip_addr); - -# Send the message -# - &SayDebug("Unable to send alert; $!", 1) - if !send(MYSOCK, "$XfrUser $XfrTrgFN\n", 0, $ip_dest); - - return 0; -} - -#****************************************************************************** -#* P u t _ F i l e * -#****************************************************************************** - -sub Put_File {my($Fsz) = @_; my($cmd, $dir, $resp, $sfx, $PutTries); - - # Establish command stream preamble - # - $cmd = &TransCmd($XfrPut1); - - # Check if we need to insert mkdir commands - # - if ($XfrApnd ne '') - {while ($XfrApnd =~ s|//|/|g) {} - while ($XfrBase =~ s|//|/|g) {} - $XfrBase .= $sfx if ($sfx = chop($XfrBase)) ne '/'; - my(@dirs) = split('/', $XfrApnd); - pop(@dirs); - shift(@dirs) if ($dirs[0] eq ''); - while(($dir = shift(@dirs)) ne '') - {$XfrBase .= '/'.$dir; $cmd .= ";mkdir $XfrBase";} - } - - # Set the cos if we need to - # - $cmd .= ";site setcos $XfrCos" if $XfrCOS >= 0; - - # Construct the penultimate command stream - # - $cmd .= TransCmd($XfrPut2); - - # Now add the uid/gid settings, as needed, followed by a quit. - # - if ($XfrUser eq 'root') - {$cmd .= ";site chuid $XfrUID $XfrTrgFN" if $XfrUID ne ''; - $cmd .= ";site chgid $XfrGID $XfrTrgFN" if $XfrGID ne ''; - } - $cmd .= ';quit'; - - # Execute the command to put the file (as many times as wanted/needed) - # - $PutTries = 0; - do {return &Put_Alert() if &Transfer($cmd, 1, $Fsz); - $PutTries += &TransErr($PutTries+1, 1); - } while($PutTries < $XfrMaxTR); - return $XfrRC; -} - -#****************************************************************************** -#* T r a n s C m d * -#****************************************************************************** - -sub TransCmd {my($cmd, $nofn) = @_; - -# Do command substitution (same for get or put) -# - $cmd =~ s/%xfrhost/$XfrHost/; - $cmd =~ s/%xfrport/$XfrPort/; - $cmd =~ s/%xfruser/$XfrUser/; - $cmd =~ s/%xfrsfn/$XfrSrcFN/; - $cmd =~ s/%xfrtfn/$XfrTrgFN/; - return $cmd; -} - -#****************************************************************************** -#* T r a n s E r r * -#****************************************************************************** - -sub TransErr {my($Try, $isput) = @_; - -# Errors that cannot be retried because nothing will work. Return a count -# that will cause the counter to decrease to zero. -# -if ($XfrResp =~ /FileToNet: select error = 145/) { # pftp timeout - Logit("pftp timeout for $XfrTrgFN"); - return $XfrMaxTR+1; - } - -if ($XfrResp =~ /cannot be opened - HPSS Error: -2/) { # ENOENT type of error - if ($isput) {Logit("mkdir error; path not found for $XfrTrgFN");} - else {Logit("get failed; '$XfrTrgFN' not found.");} - $XfrRC = 2; - return $XfrMaxTR+1; - } - -if (!$isput && $resp =~ /NetToFile:file write failure\(5\)/) { # Disk is full - Logit("pftp failed for $XfrTrgFN; disk is full."); - return $XfrMaxTR+1; - } - -# Errors which should be retried *forever* because problem is with HPSS -# not the particular file being transferred. Return non-increasing value. -# -if ($XfrResp =~ /Bad Data Transfer.\(error = -28,moved = 0\)/) { - Logit("No space in storage class for $XfrTrgFN"); - sleep Min(240,$XfrMaxWT); - return 0; - } -if ($XfrResp =~ /Login failed./) { - Logit("pftp login failed for $XfrTrgFN"); - sleep Min(240,$XfrMaxWT); - return 0; - } -if ($XfrResp =~ /Service not available, remote server has closed connection/) { - Logit("pftp login failed for $XfrTrgFN"); - sleep Min(240,$XfrMaxWT); - return 0; - } -if ($XfrResp =~ /connect: Connection timed out/) { - Logit("pftp login failed for $XfrTrgFN"); - sleep Min(240,$XfrMaxWT); - return 0; - } -if ($XfrResp =~ /connect: Connection refused/) { - Logit("pftp login failed for $XfrTrgFN"); - sleep Min(240,$XfrMaxWT); - return 0; - } - -# Retriable errors that are likely file related. These have a maximum limit and -# we wait between tries for an arbitrary but limited amount of time. -# -if ($XfrResp =~ /Bad Data Transfer.\(error = -5,moved = 0\)/) { - Logit("No devices available to pftp $XfrTrgFN"); - sleep Min(120 * $Try, $XfrMaxWT); - return 1; - } -if ($XfrResp =~ /cannot be opened - HPSS Error: -5/) { - Logit("Open error for $XfrTrgFN"); - sleep Min(60,$XfrMaxWT); - return 1; - } - -# The following error seems to correlate to BFS end session error BFSR0096 -# -if ($XfrResp =~ /Bad Data Transfer.\(error = -52,moved = 0\)/) { - Logit("BFS error = -52 for $XfrTrgFN"); - sleep Min(60,$XfrMaxWT); - return 1; - } - -# Retriable error conditions that require notification should go here. -# -if ($XfrResp =~ /HPSS Error: -5/) { # I/O error in HPSS? - Emsg("I/O error for $XfrTrgFN, rc=$XfrRC, try=$Try"); - sleep Min(60,$XfrMaxWT); # wait a bit and retry - return 1; - } - -# We don't know why we were called but we don't want to try again -# -$resp = 'transfer failed for unknown reasons.' if !($resp = &LastLine($resp)); -return $XfrMaxTR+2; -} - -#****************************************************************************** -#* T r a n s f e r * -#****************************************************************************** - -sub Transfer {my($Cmds, $isWrite, $Fsz) = @_; - my($i, $n, $pid, @Resp); - my($xdir) = ' bytes '.($isWrite ? 'sent ' : 'received '); - local(*Reader, *Writer); - -# Det things up -# - $XfrRC = 0; - $XfrSess .= $Cmds.'===================================;'; - -# Do some debugging here to avoid spilling the password -# - if ($Debug) - {&SayDebug("Executing '$XfrCmd $XfrArgs'"); - my($Sess) = $XfrSess; $Sess =~ s/\;/\n/g; - &SayDebug("Command stream:\n$Sess"); - } - -# We now insert the password prior to transfer to avoid revealing it -# - &GetPswd($XfrKey); - $Cmds =~ s/%xfrpswd/$XfrPswd/; - -# Establish a signal handler and create a process group -# - $SIG{PIPE} = 'PHandler'; - $SIG{TERM} = 'THandler'; - setpgrp() if $newPGRP; - -# execute the command feeding it the input -# - &Emsg(ET("Unable to exec '$XfrCmd'")) - if !($pid = open2(\*Reader, \*Writer, "$XfrCmd $XfrArgs $XfrOut")); - $Cmds =~ s/\;/\n/g; - &Emsg(ET("Unable to send commands to '$XfrCmd'")) if !print Writer $Cmds; - close(Writer); - -# Get the command output and clean-up the command -# - @Resp = ; - waitpid($pid,0); - $XfrRC = ($? != 2 ? $? : 22); - $XfrResp = join('',@Resp); - $XfrSess .= $XfrResp.'==================================='."\n"; - -# If we ended with an error, bail out -# - if ($XfrRC != 0) - {Logit("!!! Transfer ended with non-zero status ($XfrRC)."); return 0;} - -# Verify that the transfer fully completed. Note that the "transfer complete" -# message comes in two flavors and may be repeated. We want the last instance -# which cannot be neither on the last nor first lines of the response. -# - $i = $#Resp; $XfrRC = 8; - while($i--) - {last if ($Resp[$i] =~ m/226 Transfer Complete\./) - || ($Resp[$i] =~ m/226 Transfer complete\./); - } - -# Make sure we found the transfer complete message -# - if ($i < 0) - {Logit("!!! '226 Transfer complete' msg not found."); return 0;} - -# Make sure we have the number of bytes sent/received -# - if (($n = index( $Resp[$i+1], $xdir)) < 0) - {Logit("!!! '226 Transfer bytes' msg not found."); return 0;} - $XfrBytes = substr($Resp[$i+1], 0, $n); - -# For writes, make sure all the bytes were transferred -# - if ($isWrite && $XfrBytes != $Fsz) - {Logit("Transfer bytes ($XfrBytes) != file bytes ($Fsz)."); return 0;} - -# All done -# - $XfrRC = 0; - return 1; -} - -#****************************************************************************** -#* T r a n s P a c e * -#****************************************************************************** - -sub TransPace {my($doGet, $Fsz) = @_; my($resp, $pid); - local(*xfrRDR, *xfrWRT); - - - my($okPace) = 0; - if ($WT41 ne '' && $xqDir ne '') - {&SayDebug("Obtaining xfer slot in $xqDir"); - my($xsT) = time(); - if (!($pid = open2(\*xfrRDR, \*xfrWRT, "$WT41 $xqDir"))) - {&Emsg(ET("Unable to exec '$WT41'")); - return 1 if !$PFOK; - } else { - $resp = ; chomp($resp); $okPace = 1; - if ($resp ne 'OK') - {Emsg("Unable to obtain transfer slot."); - return 1 if !$PFOK; - } else { - $xsT = time() - $xsT; - SayDebug("Xfer slot obtained in $xsT seconds.");} - } - } - $rc = ($doGet ? Get_File($Fsz) : Put_File($Fsz)); - - if ($okPace) - {close(xfrRDR); close(xfrWRT); waitpid($pid,0);} - - return $rc; -} - -#****************************************************************************** -#* U s a g e * -#****************************************************************************** - -sub Usage {my($rc) = @_; -$CMD .= '.hpss' if !$isCPY; -print "Usage: $CMD [options] {<[usr@]host>: | <[usr@]host>:}\n"; -print "\n"; -print "options: [-c ] [-d] [-f] [-F {hide|none|oute|outo|}]\n"; -print " [-k ] [-m] [-M offs] [-n] [-p ] [-P ] [-r]\n"; -print " [-s ] [-t ] [-x ] [-w ] [-z]\n"; -if ($isCPY) { -print " [-C] [-N ] [-Q ] [-W ]\n"; -} -exit($rc); -} - -#****************************************************************************** -#* u t i l i t i e s * -#****************************************************************************** - -sub MakePath {my($path) = @_; my(@dirs, $mkpath, $dname); - @dirs = split('/', $path); pop(@dirs); - $mkpath = shift(@dirs); # start with either "" or "." - while(($dname = shift(@dirs)) ne "") - {$mkpath .= "/$dname"; - if (!-d $mkpath) - {return &Emsg(ET("mkdir($mkpath) failed for '$path'"),1) - if !mkdir($mkpath, $DMode); - } - } - return 1; -} - -sub Min {my($V1, $V2) = @_; return ($V1 < $V2 ? $V1 : $V2);} - -sub SayDebug {my($msg) = @_; - print STDERR $MsgPfx, $msg, "\n" if $Debug; -} - -sub LastLine {my($resp) = @_; - my(@rr, $i); - @rr = split("\n", $resp); - for ($i = $#rr; $i >= 0; $i--) {return $rr[$i] if $rr[$i];} - return ''; -} - -sub GetDir {my($item) = @_; my($v); - $v = shift(@ARGV); - &Emsg(ucfirst($item).' not specified.') if $v eq ''; - &Emsg(ET("Invalid $item, '$v'")) if !-d $v; - return $v; -} - -sub GetOwners {my($ug) = @_; - my($usr, $grp) = split(/:/, $ug, 2); - if ($usr ne '') - {if (IsNum($usr)) {$XfrUID = $usr;} - else {$XfrUID = getpwnam($usr); - &Emsg("User '$usr' is not valid.") if !defined($XfrUID); - } - } - if ($grp ne '') - {if (IsNum($grp)) {$XfrGID = $grp;} - else {$XfrGID = getgrnam($grp); - &Emsg("Group '$grp' is not valid.") if !defined($XfrGID); - } - } -} - -sub GetPswd {my($fn) = @_; - &Emsg(ET("Unable to open keyfile '$fn'")) if !open(KTFD, $fn); - $XfrPswd = ; close(KTFD); -} - -sub GetVal {my($item, $type, $minv) = @_; my($v, $q); - $v = shift(@ARGV); $q = 0; - &Emsg(ucfirst($item).' not specified.') if $v eq ''; - if ($type eq 'Q') { $q = uc(chop($v)); - if ($q eq 'K') {$q = 1024;} - elsif ($q eq 'M') {$q = 1024*1024;} - elsif ($q eq 'G') {$q = 1024*1024*1024;} - else {$v .= $q; $q = 0;} - } - &Emsg("Invalid $item, '$v'.") - if ($type eq 'N' || $type eq 'Q') && !&IsNum($v); - &Emsg("$item, '$v', is too small.") - if ($type eq 'N' && $v < $minv); - $v = $v*$q if $q; - return $v; - } - -sub IsNum {my($v) = @_; return ($v =~ m/^[0-9]+$/);} - -sub IsOct {my($v) = @_; return ($v =~ m/^[0-7]+$/);} - -sub Emsg {my($msg,$ret,$rc) = @_; - print STDERR $MsgPfx,$msg,"\n"; - Logit($msg) if !$isCPY; - return 0 if $ret > 0; - Usage(4) if $ret < 0; - $rc = 4 if $rc eq ""; - &CleanUp($rc); - } - -sub ET {my($etxt) = @_; return $etxt.'; '.lcfirst($!).'.';} - -sub Logit {my($msg) = @_; $ErrLog .= $MsgPfx.$msg."\n";} - -sub PHandler {$SIG{PIPE} = 'DEFAULT';} -sub THandler {$SIG{TERM} = 'DEFAULT'; kill(-15, getpgrp(0));} diff --git a/utils/netchk b/utils/netchk deleted file mode 100755 index a9e0ceb5b72..00000000000 --- a/utils/netchk +++ /dev/null @@ -1,124 +0,0 @@ -#!/usr/bin/env perl -use Cwd qw(realpath); -use IO::Socket; -use IPC::Open2; - -$SSH = 'ssh -a -x -oFallBackToRsh=no'; -$Xeq = "/tmp/netchk.$$"; -$Ppath = realpath($0); -chomp($myHost=`hostname`); -$| = 1; - -# 1st token is what to do -# - $Op = shift(@ARGV); - $Initial = ($Op eq 'ssh' || $Op eq 'tcp'); - $Inter = ($Op eq 'SSH' || $Op eq 'TCP'); - Usage("Operation (ssh or tcp) not specified.") if !$Op; - Usage("Operation '$Op' is neither 'ssh' nor 'tcp'.") if !($Initial||$Inter); - $Op = uc($Op); $doTCP = ($Op eq 'TCP'); - -# Get port to listen on and validate it -# - if ($doTCP && $Inter) - {($Src, $Lport) = split(/:/, shift(@ARGV), 2); - ValPort('listen', $myHost, $Lport, 1); - } - -# Get the first hop and rest of the hops and construct command line -# - $Hop = shift(@ARGV); ($Hop, $Port) = split(/:/, $Hop, 2); - if ($Hop) - {($Usr, $Hop) = split(/@/, $Hop, 2); - if (!$Hop) {$Hop = $Usr;} - else {$Lgn = "-l $Usr";} - } else {Usage("Target host not specified.") if $Initial;} - ValPort('tcp', $Hop, $Port) if $doTCP && $Hop; - while($HPval = shift(@ARGV)) - {($Hval, $Pval) = split(/:/, $HPval, 2); - if ($doTCP) {ValPort('tcp', $Hval, $Pval);} - else {$HPval = $Hval;} - $Rest .= $HPval.' '; - } - chop($Rest); $Op .= " $myHost:$Port" if $doTCP; - -# Tell user where we are -# - if ($Inter) - {if ($doTCP) {Accept($Src, $Lport);} - else {print "Reached $myHost via ssh!\n";} - } - -# Send program if we have a hop -# - if ($Hop) - {$Cmd = "$SSH $Lgn $Hop 'cat > $Xeq; chmod +x $Xeq; $Xeq $Op $Rest; exit'"; - Usage("Cannot open $Ppath; $!",1) if !open(PGM, $Ppath); - @Prog = ; close(PGM); - $myProg = join("", @Prog); - print "Moving on to $Hop. . .\n"; - Usage("Unable to launch $CMD; $!",1) if !open2(*README, *WRITEME, $Cmd); - print WRITEME "$myProg"; close(WRITEME); - Connect($Hop, $Port) if $doTCP; - while($Output = ) {print $Output;} - } - -unlink($Xeq); -exit(0); - -sub Accept {my($Src, $Port) = @_; - my $sock = new IO::Socket::INET (LocalHost => $myHost, LocalPort => $Port, - Proto => 'tcp', Listen => 1, Reuse => 1,); - -# If socket created, tell waiter to connect, and accept the connection -# - Usage("Could not create socket on $myHost; $!",1) unless $sock; - print "$myHost is listening on port $Lport for $Src\nOK\n"; - my $new_sock = $sock->accept(); - Usage("Accept failed on $myHost; $!") unless $new_sock; - $Src = $new_sock->peerhost() if !$Src; - $new_sock->autoflush(1); - $new_sock->recv($Ack, 256); - if ($Ack) {$new_sock->send("$Ack");} - else {print "$Src connect to $myHost but did not send ack.\n";} - close($sock); close($new_sock); -} - -sub Connect {my($Host, $Port) = @_; my($Output, $Resp, $Msg); - -# Wait until dest sends an OK to connect -# - while(($Output = ) && ($Output ne "OK\n")) {print $Output;} - Usage("Unable to connect to $Host:$Port; lost ssh connection.",1) if !$Output; - -# Now connect and tell the acceptor who we are -# - my $sock = new IO::Socket::INET (PeerAddr => $Host, PeerPort => $Port, - Proto => 'tcp',); - Usage("Could not create socket on $myHost; $!",1) unless $sock; - $sock->autoflush(1); - $Msg = getppid().".$$"; - $sock->send($Msg); - $sock->recv($Resp, 256); - if (!$Resp) {print "Connected to $Host:$Port but no ack received.\n";} - {if ($Resp eq $Msg) {print "$myHost can communicate with $Host\n";} - else {print "Connected to $Host:$Port but message exhange failed.";} - } - close($sock); -} - -sub ValPort {my($What, $Host, $Port, $Snuff) = @_; - Usage(ucfirst($What)." port not specified for $Host", $Snuff) if !$Port; - Usage("'$Port' is an invalid $What port for $Host.", $Snuff) - if !($Port =~ m/^\d+$/); -} - -sub Usage {my($txt, $kll) = @_; - print "netchk: $txt\n"; - if (!$kll) - {print "Usage: netchk {ssh|tcp} [@]: [[@]: [...]]\n"; - print " ssh only checks if you can ssh through the nodes and is ignored.\n"; - print " tcp checks for end-to-end message transitivety and is required.\n"; - } - exit(8); -} diff --git a/utils/xrootd-config b/utils/xrootd-config deleted file mode 100755 index 3cce14c09a4..00000000000 --- a/utils/xrootd-config +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/env bash -#------------------------------------------------------------------------------- -# Copyright (c) 2014 by European Organization for Nuclear Research (CERN) -# Author: Lukasz Janyst -#------------------------------------------------------------------------------- -# This file is part of the XRootD software suite. -# -# XRootD is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# XRootD is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with XRootD. If not, see . -# -# In applying this licence, CERN does not waive the privileges and immunities -# granted to it by virtue of its status as an Intergovernmental Organization -# or submit itself to any jurisdiction. -#------------------------------------------------------------------------------- - -version=__VERSION__ -prefix=__PREFIX__ -includedir=${prefix}/__INCLUDEDIR__/xrootd -plugin_version=__PLUGIN_VERSION__ - -usage() -{ - cat <&2 -fi - -while test $# -gt 0; do - case $1 in - --prefix) - echo $prefix - ;; - --version) - echo $version - ;; - --cflags) - if test "$includedir" != "/usr/include" ; then - echo "-I${includedir}" - fi - ;; - --plugin-version) - echo ${plugin_version} - ;; - --help) - usage 0 - ;; - *) - usage 1 1>&2 - ;; - esac - shift -done From f2320f172a3b6cfa079f8521a8f1d18adb8d99bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B3zsef=20Makai?= Date: Wed, 14 Mar 2018 18:13:35 +0100 Subject: [PATCH 002/442] Shrinking and fixing the cc7 ceph build --- .gitlab-ci.yml | 560 +------------------------------------------------ 1 file changed, 5 insertions(+), 555 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 39fee63e809..4c4a7bd2986 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,360 +1,18 @@ stages: - build:rpm - - build:dockerimage - - test - - publish - - clean -.template:deb_ubuntu_artful: &deb_ubuntu_artful_def - stage: build:rpm - image: ubuntu:artful - script: - - apt-get update - - apt-get install -y git cmake g++ debhelper devscripts equivs gdebi-core - - cp -R packaging/debian/ . - - mk-build-deps --build-dep debian/control - - gdebi -n xrootd-build-deps-depends*.deb - - version=`./genversion.sh --print-only` - - dch --create -v `echo $version | sed 's/^v\(.*\)/\1/'` --package xrootd --urgency low --distribution artful -M "This package is built and released automatically. For important notices and releases subscribe to our maling lists or visit our website." - - dpkg-buildpackage -b -us -uc -tc --buildinfo-option="-udeb_packages" --changes-option="-udeb_packages" - - mkdir artful - - cp deb_packages/*.deb artful - - cp deb_packages/*.ddeb artful - artifacts: - expire_in: 1 day - paths: - - artful/ - tags: - - docker-ubuntu - -.template:deb_ubuntu_xenial: &deb_ubuntu_xenial_def - stage: build:rpm - image: ubuntu:xenial - script: - - apt-get update - - apt-get install -y git cmake g++ debhelper devscripts equivs gdebi-core # pkg-create-dbgsym - - cp -R packaging/debian/ . - - mk-build-deps --build-dep debian/control - - gdebi -n xrootd-build-deps-depends*.deb - - version=`./genversion.sh --print-only` - - dch --create -v `echo $version | sed 's/^v\(.*\)/\1/'` --package xrootd --urgency low --distribution artful -M "This package is built and released automatically. For important notices and releases subscribe to our maling lists or visit our website." - - dpkg-buildpackage -b -us -uc -tc --changes-option="-udeb_packages" - - mkdir xenial - - cp deb_packages/*.deb xenial -# - cp ../*.ddeb xenial - artifacts: - expire_in: 1 day - paths: - - xenial/ - tags: - - docker-ubuntu - -build:cc7: +release:cc7:ceph: stage: build:rpm image: gitlab-registry.cern.ch/linuxsupport/cc7-base script: - - yum install --nogpg -y gcc-c++ rpm-build which git + - yum install --nogpg -y cmake3 make gcc-c++ rpm-build which git yum-plugin-priorities sssd-client sudo createrepo - cd packaging/ - ./makesrpm.sh - - yum-builddep --nogpgcheck -y *.src.rpm - - mkdir RPMS - - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" *.src.rpm - - cd .. - - mkdir cc-7/ - - cp packaging/*.src.rpm cc-7 - - cp packaging/RPMS/* cc-7 - artifacts: - expire_in: 1 day - paths: - - cc-7/ - tags: - - docker-cc7 - only: - - master - - /^stable-.*$/ - except: - - tags - - schedules - -build:cc7:ceph: - stage: build:rpm - image: gitlab-registry.cern.ch/linuxsupport/cc7-base - script: - - yum install --nogpg -y gcc-c++ rpm-build which git yum-plugin-priorities sssd-client sudo createrepo - - cd packaging/ - - ./makesrpm.sh --define '_with_ceph11 1' - echo -e '[ceph]\nname=ceph\nbaseurl=http://linuxsoft.cern.ch/mirror/download.ceph.com/rpm-luminous/el7/x86_64/\npriority=4\ngpgcheck=0\nenabled=1\n' >> /etc/yum.repos.d/ceph.repo - - yum-builddep --nogpgcheck -y *.src.rpm - - rpmbuild --rebuild --define="_with_ceph11 1" --define "_rpmdir RPMS/" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" *.src.rpm - - repo=/eos/project/s/storage-ci/www/xrootd/ceph/cc-7/x86_64/$(date +'%Y%m%d') - - sudo -u stci -H mkdir -p $repo - - sudo -u stci -H find $repo -type f -name '*.rpm' -delete - - sudo -u stci -H cp *.src.rpm $repo - - sudo -u stci -H cp RPMS/* $repo - - sudo -u stci -H createrepo --update -q $repo - tags: - - docker-slc6 - only: - - master - except: - - tags - - schedules - -build:slc6: - stage: build:rpm - image: gitlab-registry.cern.ch/linuxsupport/slc6-base - script: - - yum install --nogpg -y gcc-c++ rpm-build which tar git - - cd packaging/ - - ./makesrpm.sh - - yum-builddep --nogpgcheck -y *.src.rpm - - mkdir RPMS + - echo -e '[xrootd-testing]\nname=XRootD Testing repository\nbaseurl=http://xrootd.org/binaries/testing/slc/7/$basearch http://xrootd.cern.ch/sw/repos/testing/slc/7/$basearch\ngpgcheck=1\nenabled=1\nprotect=0\ngpgkey=http://xrootd.cern.ch/sw/releases/RPM-GPG-KEY.txt\n' >> /etc/yum.repos.d/xrootd-testing.repo + - echo -e '[xrootd-stable]\nname=XRootD Stable repository\nbaseurl=http://xrootd.org/binaries/stable/slc/7/$basearch http://xrootd.cern.ch/sw/repos/stable/slc/7/$basearch\ngpgcheck=1\nenabled=1\nprotect=0\ngpgkey=http://xrootd.cern.ch/sw/releases/RPM-GPG-KEY.txt\n' >> /etc/yum.repos.d/xrootd-stable.repo + - yum-builddep --setopt=cern*.exclude=xrootd* --nogpgcheck -y *.src.rpm - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" *.src.rpm - - cd .. - - mkdir slc-6 - - cp packaging/*.src.rpm slc-6 - - cp packaging/RPMS/* slc-6 - artifacts: - expire_in: 1 day - paths: - - slc-6/ - tags: - - docker-slc6 - only: - - master - - /^stable-.*$/ - except: - - tags - - schedules - -build:fedora-rawhide: - stage: build:rpm - image: fedora:rawhide - script: - - dnf install --nogpg -y gcc-c++ rpm-build which tar dnf-plugins-core git - - cd packaging/ - - ./makesrpm.sh - - dnf builddep --nogpgcheck -y *.src.rpm - - mkdir RPMS - - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" *.src.rpm - - cd .. - - mkdir fc-rawhide - - cp packaging/*.src.rpm fc-rawhide - - cp packaging/RPMS/* fc-rawhide - artifacts: - expire_in: 1 day - paths: - - fc-rawhide/ - tags: - - docker-fc_rawhide - only: - - master - - /^stable-.*$/ - except: - - tags - - schedules - -build:fedora-26: - stage: build:rpm - image: fedora:26 - script: - - dnf install --nogpg -y gcc-c++ rpm-build which tar dnf-plugins-core git - - cd packaging/ - - ./makesrpm.sh - - dnf builddep --nogpgcheck -y *.src.rpm - - mkdir RPMS - - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" *.src.rpm - - cd .. - - mkdir fc-26/ - - cp packaging/*.src.rpm fc-26 - - cp packaging/RPMS/* fc-26 - artifacts: - expire_in: 1 day - paths: - - fc-26/ - tags: - - docker-fc_rawhide - only: - - master - - /^stable-.*$/ - except: - - tags - - schedules - -build:fedora-25: - stage: build:rpm - image: fedora:25 - script: - - dnf install --nogpg -y gcc-c++ rpm-build which tar dnf-plugins-core git - - cd packaging/ - - ./makesrpm.sh - - dnf builddep --nogpgcheck -y *.src.rpm - - mkdir RPMS - - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" *.src.rpm - - cd .. - - mkdir fc-25/ - - cp packaging/*.src.rpm fc-25 - - cp packaging/RPMS/* fc-25 - artifacts: - expire_in: 1 day - paths: - - fc-25/ - tags: - - docker-fc25 - only: - - master - - /^stable-.*$/ - except: - - tags - - schedules - -build:fedora-24: - stage: build:rpm - image: fedora:24 - script: - - dnf install --nogpg -y gcc-c++ rpm-build which tar dnf-plugins-core git - - cd packaging/ - - ./makesrpm.sh - - dnf builddep --nogpgcheck -y *.src.rpm - - mkdir RPMS - - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" *.src.rpm - - cd .. - - mkdir fc-24/ - - cp packaging/*.src.rpm fc-24 - - cp packaging/RPMS/* fc-24 - artifacts: - expire_in: 1 day - paths: - - fc-24/ - tags: - - docker-fc25 - only: - - master - - /^stable-.*$/ - except: - - tags - - schedules - -build:deb_ubuntu_artful: - <<: *deb_ubuntu_artful_def - only: - - master - - /^stable-.*$/ - except: - - tags - - schedules - -build:deb_ubuntu_xenial: - <<: *deb_ubuntu_xenial_def - only: - - master - - /^stable-.*$/ - except: - - tags - - schedules - -build:macosx: - stage: build:rpm - script: - - mkdir build - - mkdir -p tarball/xrootd - - cd build - - /usr/local/bin/cmake -D ZLIB_INCLUDE_DIR=/usr/local/Cellar/zlib/1.2.8/include/ -D OPENSSL_INCLUDE_DIR=/usr/local/Cellar/openssl/1.0.2g/include/ -DCMAKE_INSTALL_PREFIX=../tarball/xrootd .. - - make - - make install - - cd ../tarball - - tar -zcf xrootd.tar.gz xrootd - - cd .. - - mkdir osx - - cp tarball/xrootd.tar.gz osx - artifacts: - expire_in: 1 day - paths: - - osx/ - tags: - - macosx-shell - only: - - master - - /^stable-.*$/ - except: - - tags - - schedules - -release:cc7-x86_64: - stage: build:rpm - script: - - mkdir cc-7-x86_64 - - git archive --prefix=xrootd-${CI_COMMIT_TAG#"v"}/ --format=tar tags/$CI_COMMIT_TAG | gzip -9fn > xrootd-${CI_COMMIT_TAG#"v"}.tar.gz - - mv xrootd-${CI_COMMIT_TAG#"v"}.tar.gz cc-7-x86_64 - - cd packaging/ - - ./makesrpm.sh - - /bin/mock --init -r epel-7-x86_64 --rebuild xrootd-*.src.rpm --resultdir ./build/ --define="_with_tests 1" --define="_without_xrootd_user 1" -D "dist .el7" - - cd .. - - cp packaging/build/*.rpm cc-7-x86_64 - artifacts: - expire_in: 1 day - paths: - - cc-7-x86_64/ - tags: - - xrootd-shell - only: - - web - except: - - branches - - /^(?!v[0-9]+).*/ - -release:slc6-x86_64: - stage: build:rpm - script: - - cd packaging/ - - ./makesrpm.sh - - /bin/mock --init -r epel-6-x86_64 --rebuild xrootd-*.src.rpm --resultdir ./build/ --define="_with_tests 1" --define="_without_xrootd_user 1" -D "dist .el6" - - cd .. - - mkdir slc-6-x86_64 - - cp packaging/build/*.rpm slc-6-x86_64 - artifacts: - expire_in: 1 day - paths: - - slc-6-x86_64/ - tags: - - xrootd-shell - only: - - web - except: - - branches - - /^(?!v[0-9]+).*/ - -release:slc6-i386: - stage: build:rpm - script: - - cd packaging/ - - ./makesrpm.sh - - /bin/mock --init -r epel-6-i386 --rebuild xrootd-*.src.rpm --resultdir ./build/ --define="_with_tests 1" --define="_without_xrootd_user 1" -D "dist .el6" - - cd .. - - mkdir slc-6-i386 - - cp packaging/build/*.rpm slc-6-i386 - artifacts: - expire_in: 1 day - paths: - - slc-6-i386/ - tags: - - xrootd-shell - only: - - web - except: - - branches - - /^(?!v[0-9]+).*/ - -release:cc7:ceph: - stage: build:rpm - image: gitlab-registry.cern.ch/linuxsupport/cc7-base - script: - - yum install --nogpg -y gcc-c++ rpm-build which git yum-plugin-priorities sssd-client sudo createrepo - - cd packaging/ - - ./makesrpm.sh --define '_with_ceph11 1' - - echo -e '[ceph]\nname=ceph\nbaseurl=http://linuxsoft.cern.ch/mirror/download.ceph.com/rpm-luminous/el7/x86_64/\npriority=4\ngpgcheck=0\nenabled=1\n' >> /etc/yum.repos.d/ceph.repo - - yum-builddep --nogpgcheck -y *.src.rpm - - rpmbuild --rebuild --define="_with_ceph11 1" --define "_rpmdir RPMS/" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" *.src.rpm - repo=/eos/project/s/storage-ci/www/xrootd/ceph-release/cc-7/x86_64/ - sudo -u stci -H mkdir -p $repo - sudo -u stci -H cp *.src.rpm $repo @@ -364,211 +22,3 @@ release:cc7:ceph: - docker-cc7 only: - tags - when: manual - -release:deb_ubuntu_artful: - <<: *deb_ubuntu_artful_def - only: - - web - except: - - branches - - /^(?!v[0-9]+).*/ - -release:deb_ubuntu_xenial: - <<: *deb_ubuntu_xenial_def - only: - - web - except: - - branches - - /^(?!v[0-9]+).*/ - -biweekly:cc7: - stage: build:rpm - image: gitlab-registry.cern.ch/linuxsupport/cc7-base - script: - - yum install --nogpg -y gcc-c++ rpm-build which git cppunit-devel - - xrootd_version=$(git tag | grep '^v' | grep -v 'rc.*$' | grep -v 'CERN$' | sed -e '$!d') - - xrootd_version=${xrootd_version:1} - - short_hash=$(git rev-parse --verify HEAD | awk '{print substr($0, 0, 8)}') - - a=( ${xrootd_version//./ } ) - - ((a[2]++)) || true - - experimental_version="${a[0]}.${a[1]}.${a[2]}-0.experimental."${CI_PIPELINE_ID}.$short_hash - - cd packaging/ - - ./makesrpm.sh --version $experimental_version - - yum-builddep --nogpgcheck -y *.src.rpm - - mkdir RPMS - - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_with_tests 1" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" *.src.rpm - - cd .. - - mkdir epel-7/ - - cp packaging/*.src.rpm epel-7 - - cp packaging/RPMS/* epel-7 - artifacts: - expire_in: 1 day - paths: - - epel-7/ - tags: - - docker-cc7 - only: - - schedules - except: - - tags - -biweekly:slc6: - stage: build:rpm - image: gitlab-registry.cern.ch/linuxsupport/slc6-base - script: - - yum install --nogpg -y gcc-c++ rpm-build which tar git cppunit-devel - - xrootd_version=$(git tag | grep '^v' | grep -v 'rc.*$' | grep -v 'CERN$' | sed -e '$!d') - - xrootd_version=${xrootd_version:1} - - short_hash=$(git rev-parse --verify HEAD | awk '{print substr($0, 0, 8)}') - - a=( ${xrootd_version//./ } ) - - ((a[2]++)) || true - - experimental_version="${a[0]}.${a[1]}.${a[2]}-0.experimental."${CI_PIPELINE_ID}.$short_hash - - cd packaging/ - - ./makesrpm.sh --version $experimental_version - - yum-builddep --nogpgcheck -y *.src.rpm - - mkdir RPMS - - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_with_tests 1" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" *.src.rpm - - cd .. - - mkdir epel-6/ - - cp packaging/*.src.rpm epel-6 - - cp packaging/RPMS/* epel-6 - artifacts: - expire_in: 1 day - paths: - - epel-6/ - tags: - - docker-slc6 - only: - - schedules - except: - - tags - -publish:rhel: - stage: publish - image: gitlab-registry.cern.ch/linuxsupport/cc7-base - script: - - yum install --nogpg -y sssd-client sudo createrepo - - prefix=/eos/project/s/storage-ci/www/xrootd - - "for platform in cc-7 slc-6 fc-rawhide fc-26 fc-25 fc-24; do - repo=$prefix/${CI_COMMIT_REF_NAME}/$platform/x86_64 - path=$repo/$(date +'%Y%m%d'); - sudo -u stci -H mkdir -p $path; - sudo -u stci -H find ${path} -type f -name '*.rpm' -delete; - sudo -u stci -H cp $platform/* $path; - sudo -u stci -H createrepo --update -q $path; - sudo -u stci -H createrepo --update -q $repo; - done" - tags: - - docker-slc6 - only: - - master - - /^stable-.*$/ - except: - - tags - - schedules - -publish:debian: - stage: publish - image: ubuntu:artful - script: - - apt-get update - - apt-get install -y sudo apt-utils sssd - - mkdir /home/stci - - chown -R stci:def-cg /home/stci - - chmod -R 700 /home/stci - - sudo -u stci -H gpg --homedir /home/stci/ --allow-secret-key-import --import /keys/stci-debian-repo.sec - - sudo -u stci -H ./packaging/debian_scripts/publish_debian_cern.sh ${CI_COMMIT_REF_NAME} - tags: - - docker-ubuntu - dependencies: - - build:deb_ubuntu_artful - - build:deb_ubuntu_xenial - only: - - master - - /^stable-.*$/ - except: - - tags - - schedules - -publish:rhel:release: - stage: publish - image: gitlab-registry.cern.ch/linuxsupport/cc7-base - script: - - yum install --nogpg -y sssd-client sudo createrepo - - prefix=/eos/project/s/storage-ci/www/xrootd - - tarball=cc-7-x86_64/xrootd-*.tar.gz - - "for platform in cc-7-x86_64 slc-6-x86_64 slc-6-i386; do - path=$prefix/release/$platform/$CI_COMMIT_TAG/; - sudo -u stci -H mkdir -p $path/{source,tarball}; - sudo -u stci -H cp $platform/*.rpm $path; - sudo -u stci -H find ${path} -type f -name '*.src.rpm' -delete; - sudo -u stci -H cp $platform/*.src.rpm $path/source; - sudo -u stci -H cp $tarball $path/tarball; - sudo -u stci -H createrepo --update -q $path; - sudo -u stci -H createrepo --update -q $prefix/release/$platform; - done" - tags: - - docker-slc6 - only: - - web - except: - - branches - - /^(?!v[0-9]+).*/ - -publish:debian:release: - stage: publish - image: ubuntu:artful - script: - - apt-get update - - apt-get install -y sudo apt-utils sssd - - mkdir /home/stci - - chown -R stci:def-cg /home/stci - - chmod -R 700 /home/stci - - sudo -u stci -H gpg --homedir /home/stci/ --allow-secret-key-import --import /keys/stci-debian-repo.sec - - sudo -u stci -H ./packaging/debian_scripts/publish_debian_cern.sh release - tags: - - docker-ubuntu - dependencies: - - release:deb_ubuntu_artful - - release:deb_ubuntu_xenial - only: - - web - except: - - branches - - /^(?!v[0-9]+).*/ - -publish:biweekly: - stage: publish - image: gitlab-registry.cern.ch/linuxsupport/cc7-base - script: - - yum install --nogpg -y sssd-client sudo createrepo - - prefix=/eos/project/s/storage-ci/www/xrootd - - "for platform in epel-7 epel-6; do - path=$prefix/experimental/$platform/x86_64/; - sudo -u stci -H mkdir -p $path; - sudo -u stci -H cp $platform/* $path; - sudo -u stci -H createrepo --update -q $path; - done" - tags: - - docker-slc6 - dependencies: - - biweekly:cc7 - - biweekly:slc6 - only: - - schedules - except: - - tags - -clean:artifacts: - stage: clean - image: gitlab-registry.cern.ch/linuxsupport/cc7-base - script: - - yum install --nogpg -y sssd-client sudo createrepo - - sudo -u stci -H bash -c 'for commit_dir in /eos/project/s/storage-ci/www/xrootd/stable-*/*/*/; do find ${commit_dir} -type f -name '"'"'*.rpm'"'"' -mtime +30 -delete; createrepo --update -q ${commit_dir}; done' - - sudo -u stci -H bash -c 'for commit_dir in /eos/project/s/storage-ci/www/xrootd/experimental/*/x86_64/; do find ${commit_dir} -type f -name '"'"'*.rpm'"'"' -mtime +30 -delete; createrepo --update -q ${commit_dir}; done' - tags: - - docker-slc6 - allow_failure: true - only: - - schedules From fff60e6947661d19059fa1dd64267d0307edcde9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B3zsef=20Makai?= Date: Sun, 18 Mar 2018 20:41:09 +0100 Subject: [PATCH 003/442] [CI] weekly scheduled builds based on xrootd experimental --- .gitlab-ci.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4c4a7bd2986..4fdc5c0db42 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -22,3 +22,21 @@ release:cc7:ceph: - docker-cc7 only: - tags + except: + - schedules + +weekly:cc7:ceph: + stage: build:rpm + image: gitlab-registry.cern.ch/linuxsupport/cc7-base + script: + - yum install --nogpg -y cmake3 make gcc-c++ rpm-build which git yum-plugin-priorities sssd-client sudo createrepo + - cd packaging/ + - ./makesrpm.sh + - echo -e '[ceph]\nname=ceph\nbaseurl=http://linuxsoft.cern.ch/mirror/download.ceph.com/rpm-luminous/el7/x86_64/\npriority=4\ngpgcheck=0\nenabled=1\n' >> /etc/yum.repos.d/ceph.repo + - echo -e '[xrootd-experimental]\nname=XRootD Experimental repository\nbaseurl=http://storage-ci.web.cern.ch/storage-ci/xrootd/experimental/epel-7/$basearch\ngpgcheck=1\nenabled=1\nprotect=0\n' >> /etc/yum.repos.d/xrootd-experimental.repo + - yum-builddep --setopt=cern*.exclude=xrootd* --nogpgcheck -y *.src.rpm + - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" *.src.rpm + tags: + - docker-cc7 + only: + - schedules From fb49edd084e33ac24262e10344da851a86d6f460 Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Tue, 17 Apr 2018 12:34:17 +0200 Subject: [PATCH 004/442] Sync with main xrootd repo. --- src/XrdCeph/XrdCephOss.cc | 8 ++++---- src/XrdCeph/XrdCephOssDir.cc | 2 +- src/XrdCeph/XrdCephOssFile.cc | 2 +- src/XrdCeph/XrdCephXAttr.cc | 10 +++++----- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/XrdCeph/XrdCephOss.cc b/src/XrdCeph/XrdCephOss.cc index ba5a4ab0e20..ec157163d90 100644 --- a/src/XrdCeph/XrdCephOss.cc +++ b/src/XrdCeph/XrdCephOss.cc @@ -70,7 +70,7 @@ extern "C" // set parameters try { ceph_posix_set_defaults(parms); - } catch (std::exception e) { + } catch (std::exception &e) { XrdCephEroute.Say("CephOss loading failed with exception. Check the syntax of parameters : ", parms); return 0; } @@ -193,7 +193,7 @@ int XrdCephOss::Stat(const char* path, } else { return ceph_posix_stat(env, path, buff); } - } catch (std::exception e) { + } catch (std::exception &e) { XrdCephEroute.Say("stat : invalid syntax in file parameters"); return -EINVAL; } @@ -228,7 +228,7 @@ int XrdCephOss::Truncate (const char* path, XrdOucEnv* env) { try { return ceph_posix_truncate(env, path, size); - } catch (std::exception e) { + } catch (std::exception &e) { XrdCephEroute.Say("truncate : invalid syntax in file parameters"); return -EINVAL; } @@ -237,7 +237,7 @@ int XrdCephOss::Truncate (const char* path, int XrdCephOss::Unlink(const char *path, int Opts, XrdOucEnv *env) { try { return ceph_posix_unlink(env, path); - } catch (std::exception e) { + } catch (std::exception &e) { XrdCephEroute.Say("unlink : invalid syntax in file parameters"); return -EINVAL; } diff --git a/src/XrdCeph/XrdCephOssDir.cc b/src/XrdCeph/XrdCephOssDir.cc index b3ddacf8472..6743edc5e47 100644 --- a/src/XrdCeph/XrdCephOssDir.cc +++ b/src/XrdCeph/XrdCephOssDir.cc @@ -38,7 +38,7 @@ int XrdCephOssDir::Opendir(const char *path, XrdOucEnv &env) { return -errno; } return XrdOssOK; - } catch (std::exception e) { + } catch (std::exception &e) { XrdCephEroute.Say("opendir : invalid syntax in file parameters"); return -EINVAL; } diff --git a/src/XrdCeph/XrdCephOssFile.cc b/src/XrdCeph/XrdCephOssFile.cc index 5ebef097f2a..3a0a63f47c7 100644 --- a/src/XrdCeph/XrdCephOssFile.cc +++ b/src/XrdCeph/XrdCephOssFile.cc @@ -44,7 +44,7 @@ int XrdCephOssFile::Open(const char *path, int flags, mode_t mode, XrdOucEnv &en if (rc < 0) return rc; m_fd = rc; return XrdOssOK; - } catch (std::exception e) { + } catch (std::exception &e) { XrdCephEroute.Say("open : invalid syntax in file parameters"); return -EINVAL; } diff --git a/src/XrdCeph/XrdCephXAttr.cc b/src/XrdCeph/XrdCephXAttr.cc index ec7af75a657..4c2f60a0f96 100644 --- a/src/XrdCeph/XrdCephXAttr.cc +++ b/src/XrdCeph/XrdCephXAttr.cc @@ -45,7 +45,7 @@ extern "C" // set parameters try { ceph_posix_set_defaults(parms); - } catch (std::exception e) { + } catch (std::exception &e) { XrdCephXattrEroute.Say("CephXattr loading failed with exception. Check the syntax of parameters : ", parms); return 0; } @@ -60,7 +60,7 @@ XrdCephXAttr::~XrdCephXAttr() {} int XrdCephXAttr::Del(const char *Aname, const char *Path, int fd) { try { return ceph_posix_removexattr(0, Path, Aname); - } catch (std::exception e) { + } catch (std::exception &e) { XrdCephXattrEroute.Say("Del : invalid syntax in file parameters", Path); return -EINVAL; } @@ -77,7 +77,7 @@ int XrdCephXAttr::Get(const char *Aname, void *Aval, int Avsz, } else { try { return ceph_posix_getxattr(0, Path, Aname, Aval, Avsz); - } catch (std::exception e) { + } catch (std::exception &e) { XrdCephXattrEroute.Say("Get : invalid syntax in file parameters", Path); return -EINVAL; } @@ -90,7 +90,7 @@ int XrdCephXAttr::List(AList **aPL, const char *Path, int fd, int getSz) { } else { try { return ceph_posix_listxattrs(0, Path, aPL, getSz); - } catch (std::exception e) { + } catch (std::exception &e) { XrdCephXattrEroute.Say("List : invalid syntax in file parameters", Path); return -EINVAL; } @@ -104,7 +104,7 @@ int XrdCephXAttr::Set(const char *Aname, const void *Aval, int Avsz, } else { try { return ceph_posix_setxattr(0, Path, Aname, Aval, Avsz, 0); - } catch (std::exception e) { + } catch (std::exception &e) { XrdCephXattrEroute.Say("Set : invalid syntax in file parameters", Path); return -EINVAL; } From 34ba84781f7b444195b285cdac6fa0ffdb8a95fc Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Wed, 8 May 2019 15:46:19 +0200 Subject: [PATCH 005/442] Update XrdCeph so it can be used as a submodule in xrootd code. --- CMakeLists.txt | 54 ++++++++++++++++++++++----------------- cmake/FindXRootD.cmake | 38 +++++++++++++++------------ src/CMakeLists.txt | 4 +++ src/XrdCeph/XrdCephOss.cc | 4 +++ 4 files changed, 59 insertions(+), 41 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 965fdc8bf8b..8fefeb2fc77 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,9 +3,11 @@ #------------------------------------------------------------------------------- cmake_minimum_required( VERSION 2.6 ) -IF(CMAKE_VERSION VERSION_GREATER "2.8.12") - CMAKE_POLICY(SET CMP0022 OLD) -ENDIF() +if( NOT XRDCEPH_SUBMODULE ) + IF(CMAKE_VERSION VERSION_GREATER "2.8.12") + CMAKE_POLICY(SET CMP0022 OLD) + ENDIF() +endif() project( xrootd-ceph ) @@ -13,8 +15,10 @@ set( CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/src ${PROJECT_SOURCE_DIR}/cmake ) -if(NOT (CMAKE_VERSION VERSION_LESS "3.1")) - cmake_policy(SET CMP0054 OLD) +if( NOT XRDCEPH_SUBMODULE ) + if(NOT (CMAKE_VERSION VERSION_LESS "3.1")) + cmake_policy(SET CMP0054 OLD) + endif() endif() include( XRootDUtils ) @@ -29,25 +33,27 @@ add_definitions( -DXRDPLUGIN_SOVERSION="${PLUGIN_VERSION}" ) #------------------------------------------------------------------------------- # Generate the version header #------------------------------------------------------------------------------- -execute_process( - COMMAND ${CMAKE_SOURCE_DIR}/genversion.sh --print-only ${CMAKE_SOURCE_DIR} - OUTPUT_VARIABLE XROOTD_VERSION - OUTPUT_STRIP_TRAILING_WHITESPACE ) - -add_custom_target( - XrdVersion.hh - ${CMAKE_SOURCE_DIR}/genversion.sh ${CMAKE_SOURCE_DIR} ) - -# sigh, yet another ugly hack :( -macro( add_library _target ) - _add_library( ${_target} ${ARGN} ) - add_dependencies( ${_target} XrdVersion.hh ) -endmacro() - -macro( add_executable _target ) - _add_executable( ${_target} ${ARGN} ) - add_dependencies( ${_target} XrdVersion.hh ) -endmacro() +if( NOT XRDCEPH_SUBMODULE ) + execute_process( + COMMAND ${CMAKE_SOURCE_DIR}/genversion.sh --print-only ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE XROOTD_VERSION + OUTPUT_STRIP_TRAILING_WHITESPACE ) + + add_custom_target( + XrdVersion.hh + ${CMAKE_SOURCE_DIR}/genversion.sh ${CMAKE_SOURCE_DIR} ) + + # sigh, yet another ugly hack :( + macro( add_library _target ) + _add_library( ${_target} ${ARGN} ) + add_dependencies( ${_target} XrdVersion.hh ) + endmacro() + + macro( add_executable _target ) + _add_executable( ${_target} ${ARGN} ) + add_dependencies( ${_target} XrdVersion.hh ) + endmacro() +endif() #------------------------------------------------------------------------------- # Build in subdirectories diff --git a/cmake/FindXRootD.cmake b/cmake/FindXRootD.cmake index 5b4208dcec0..66d9a90b1ce 100644 --- a/cmake/FindXRootD.cmake +++ b/cmake/FindXRootD.cmake @@ -5,27 +5,31 @@ # XROOTD_INCLUDE_DIRS - the xrootd include directories # XROOTD_LIBRARIES - xrootd libraries directories -find_path( XROOTD_INCLUDE_DIRS XrdSfs/XrdSfsAio.hh - HINTS - ${XROOTD_DIR} - $ENV{XROOTD_DIR} - /usr - /opt - PATH_SUFFIXES include/xrootd -) +if( XRDCEPH_SUBMODULE ) + set( XROOTD_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/src ) + set( XROOTD_LIBRARIES ${CMAKE_BINARY_DIR}/src/libXrdUtils.so ) +else() + find_path( XROOTD_INCLUDE_DIRS XrdSfs/XrdSfsAio.hh + HINTS + ${XROOTD_DIR} + $ENV{XROOTD_DIR} + /usr + /opt + PATH_SUFFIXES include/xrootd + ) -find_library( XROOTD_LIBRARIES XrdUtils - HINTS - ${XROOTD_DIR} - $ENV{XROOTD_DIR} - /usr - /opt - PATH_SUFFIXES lib -) + find_library( XROOTD_LIBRARIES XrdUtils + HINTS + ${XROOTD_DIR} + $ENV{XROOTD_DIR} + /usr + /opt + PATH_SUFFIXES lib + ) +endif() set(XROOTD_INCLUDE_DIR ${XROOTD_INCLUDE_DIRS}) set(XROOTD_LIBRARY ${XROOTD_LIBRARIES}) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(xrootd DEFAULT_MSG XROOTD_INCLUDE_DIRS XROOTD_LIBRARIES) - diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9960e8273e4..b044ee25fec 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,5 +2,9 @@ #------------------------------------------------------------------------------- # Include the subcomponents #------------------------------------------------------------------------------- +include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ) +if( XRDCEPH_SUBMODULE ) + add_compile_definitions( XRDCEPH_SUBMODULE ) +endif() include( XrdCeph ) diff --git a/src/XrdCeph/XrdCephOss.cc b/src/XrdCeph/XrdCephOss.cc index ec157163d90..ff6d4af2474 100644 --- a/src/XrdCeph/XrdCephOss.cc +++ b/src/XrdCeph/XrdCephOss.cc @@ -32,7 +32,11 @@ #include "XrdOuc/XrdOucTrace.hh" #include "XrdOuc/XrdOucStream.hh" #include "XrdOuc/XrdOucName2Name.hh" +#ifdef XRDCEPH_SUBMODULE +#include "XrdOuc/XrdOucN2NLoader.hh" +#else #include "private/XrdOuc/XrdOucN2NLoader.hh" +#endif #include "XrdVersion.hh" #include "XrdCeph/XrdCephOss.hh" #include "XrdCeph/XrdCephOssDir.hh" From 4b6e43f45b32a147df07720d3aedcb557f8ffb3b Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Tue, 8 Oct 2019 09:56:56 +0200 Subject: [PATCH 006/442] [CI] Build against ceph nautilus. --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4fdc5c0db42..e3a1e5b100f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -8,7 +8,7 @@ release:cc7:ceph: - yum install --nogpg -y cmake3 make gcc-c++ rpm-build which git yum-plugin-priorities sssd-client sudo createrepo - cd packaging/ - ./makesrpm.sh - - echo -e '[ceph]\nname=ceph\nbaseurl=http://linuxsoft.cern.ch/mirror/download.ceph.com/rpm-luminous/el7/x86_64/\npriority=4\ngpgcheck=0\nenabled=1\n' >> /etc/yum.repos.d/ceph.repo + - echo -e '[ceph]\nname=ceph\nbaseurl=http://linuxsoft.cern.ch/mirror/download.ceph.com/rpm-nautilus/el7/x86_64/\npriority=4\ngpgcheck=0\nenabled=1\n' >> /etc/yum.repos.d/ceph.repo - echo -e '[xrootd-testing]\nname=XRootD Testing repository\nbaseurl=http://xrootd.org/binaries/testing/slc/7/$basearch http://xrootd.cern.ch/sw/repos/testing/slc/7/$basearch\ngpgcheck=1\nenabled=1\nprotect=0\ngpgkey=http://xrootd.cern.ch/sw/releases/RPM-GPG-KEY.txt\n' >> /etc/yum.repos.d/xrootd-testing.repo - echo -e '[xrootd-stable]\nname=XRootD Stable repository\nbaseurl=http://xrootd.org/binaries/stable/slc/7/$basearch http://xrootd.cern.ch/sw/repos/stable/slc/7/$basearch\ngpgcheck=1\nenabled=1\nprotect=0\ngpgkey=http://xrootd.cern.ch/sw/releases/RPM-GPG-KEY.txt\n' >> /etc/yum.repos.d/xrootd-stable.repo - yum-builddep --setopt=cern*.exclude=xrootd* --nogpgcheck -y *.src.rpm @@ -32,7 +32,7 @@ weekly:cc7:ceph: - yum install --nogpg -y cmake3 make gcc-c++ rpm-build which git yum-plugin-priorities sssd-client sudo createrepo - cd packaging/ - ./makesrpm.sh - - echo -e '[ceph]\nname=ceph\nbaseurl=http://linuxsoft.cern.ch/mirror/download.ceph.com/rpm-luminous/el7/x86_64/\npriority=4\ngpgcheck=0\nenabled=1\n' >> /etc/yum.repos.d/ceph.repo + - echo -e '[ceph]\nname=ceph\nbaseurl=http://linuxsoft.cern.ch/mirror/download.ceph.com/rpm-nautilus/el7/x86_64/\npriority=4\ngpgcheck=0\nenabled=1\n' >> /etc/yum.repos.d/ceph.repo - echo -e '[xrootd-experimental]\nname=XRootD Experimental repository\nbaseurl=http://storage-ci.web.cern.ch/storage-ci/xrootd/experimental/epel-7/$basearch\ngpgcheck=1\nenabled=1\nprotect=0\n' >> /etc/yum.repos.d/xrootd-experimental.repo - yum-builddep --setopt=cern*.exclude=xrootd* --nogpgcheck -y *.src.rpm - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" *.src.rpm From 278327b2e5c6041ed0077736246e695bf495819b Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Tue, 8 Oct 2019 10:23:28 +0200 Subject: [PATCH 007/442] [CI] Add trigger for branches. --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e3a1e5b100f..82abfdae81a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -22,6 +22,7 @@ release:cc7:ceph: - docker-cc7 only: - tags + - branches except: - schedules From c079f3fa335f25679a6420ec830d2b5bb7b5b387 Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Tue, 8 Oct 2019 11:13:33 +0200 Subject: [PATCH 008/442] [CMake] Clean up gcc std flags. --- cmake/XRootDOSDefs.cmake | 7 +------ src/XrdCeph.cmake | 8 -------- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/cmake/XRootDOSDefs.cmake b/cmake/XRootDOSDefs.cmake index e5ffaf94abb..eadc24952b7 100644 --- a/cmake/XRootDOSDefs.cmake +++ b/cmake/XRootDOSDefs.cmake @@ -7,16 +7,11 @@ include( CheckCXXSourceRuns ) add_definitions( -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 ) set( LIBRARY_PATH_PREFIX "lib" ) -#------------------------------------------------------------------------------- -# Enable c++0x / c++11 -#------------------------------------------------------------------------------- -set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x" ) - #------------------------------------------------------------------------------- # GCC #------------------------------------------------------------------------------- if( CMAKE_COMPILER_IS_GNUCXX ) - set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x" ) + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11" ) set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Werror" ) set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-parameter" ) # gcc 4.1 is retarded diff --git a/src/XrdCeph.cmake b/src/XrdCeph.cmake index 8ef049554c7..1a68a7f8296 100644 --- a/src/XrdCeph.cmake +++ b/src/XrdCeph.cmake @@ -22,14 +22,6 @@ add_library( set_property(SOURCE XrdCeph/XrdCephPosix.cc PROPERTY COMPILE_FLAGS " -Wno-deprecated-declarations") -# needed for librados when as C++11 is enabled -include(CheckCXXCompilerFlag) -CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11) -IF (COMPILER_SUPPORTS_CXX11) - set_property(SOURCE XrdCeph/XrdCephPosix.cc APPEND_STRING - PROPERTY COMPILE_FLAGS " -std=c++11") -ENDIF() - target_link_libraries( XrdCephPosix ${XROOTD_LIBRARIES} From dafcdd9a233cba7d70c8699182f1e568d53ad419 Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Tue, 8 Oct 2019 11:13:53 +0200 Subject: [PATCH 009/442] [XrdCeph] Add include as it is missing in rados headers. rados/buffer.h is using std::unique_ptr but is missing the respective include --- src/XrdCeph/XrdCephPosix.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/XrdCeph/XrdCephPosix.cc b/src/XrdCeph/XrdCephPosix.cc index bd39816dade..9b3e209c35c 100644 --- a/src/XrdCeph/XrdCephPosix.cc +++ b/src/XrdCeph/XrdCephPosix.cc @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include From 7c597e3dbdaefe884d4c09a63f47ddba5829e2e2 Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Tue, 8 Oct 2019 16:04:35 +0200 Subject: [PATCH 010/442] [CI] Trigger build on master. --- .gitlab-ci.yml | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 82abfdae81a..134b26ac903 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -19,10 +19,9 @@ release:cc7:ceph: - sudo -u stci -H cp RPMS/* $repo - sudo -u stci -H createrepo --update -q $repo tags: - - docker-cc7 + - docker_node only: - tags - - branches except: - schedules @@ -38,6 +37,31 @@ weekly:cc7:ceph: - yum-builddep --setopt=cern*.exclude=xrootd* --nogpgcheck -y *.src.rpm - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" *.src.rpm tags: - - docker-cc7 + - docker_node only: - schedules + +build:cc7:ceph: + stage: build:rpm + image: gitlab-registry.cern.ch/linuxsupport/cc7-base + script: + - yum install --nogpg -y cmake3 make gcc-c++ rpm-build which git yum-plugin-priorities sssd-client sudo createrepo + - cd packaging/ + - ./makesrpm.sh + - echo -e '[ceph]\nname=ceph\nbaseurl=http://linuxsoft.cern.ch/mirror/download.ceph.com/rpm-nautilus/el7/x86_64/\npriority=4\ngpgcheck=0\nenabled=1\n' >> /etc/yum.repos.d/ceph.repo + - echo -e '[xrootd-testing]\nname=XRootD Testing repository\nbaseurl=http://xrootd.org/binaries/testing/slc/7/$basearch http://xrootd.cern.ch/sw/repos/testing/slc/7/$basearch\ngpgcheck=1\nenabled=1\nprotect=0\ngpgkey=http://xrootd.cern.ch/sw/releases/RPM-GPG-KEY.txt\n' >> /etc/yum.repos.d/xrootd-testing.repo + - echo -e '[xrootd-stable]\nname=XRootD Stable repository\nbaseurl=http://xrootd.org/binaries/stable/slc/7/$basearch http://xrootd.cern.ch/sw/repos/stable/slc/7/$basearch\ngpgcheck=1\nenabled=1\nprotect=0\ngpgkey=http://xrootd.cern.ch/sw/releases/RPM-GPG-KEY.txt\n' >> /etc/yum.repos.d/xrootd-stable.repo + - yum-builddep --setopt=cern*.exclude=xrootd* --nogpgcheck -y *.src.rpm + - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" *.src.rpm + - path=/eos/project/s/storage-ci/www/xrootd/ceph/cc-7/x86_64/$(date +'%Y%m%d') + - sudo -u stci -H mkdir -p $path; + - sudo -u stci -H find ${path} -type f -name '*.rpm' -delete; + - sudo -u stci -H cp RPMS/* $path; + - sudo -u stci -H createrepo --update -q $path; + tags: + - docker_node + only: + - master + except: + - tags + From a6a9bd64778e2f0b2e153163550bf40560029007 Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Tue, 8 Oct 2019 16:10:44 +0200 Subject: [PATCH 011/442] [CI] Correct runner tag. --- .gitlab-ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 134b26ac903..2562f01092b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -19,7 +19,7 @@ release:cc7:ceph: - sudo -u stci -H cp RPMS/* $repo - sudo -u stci -H createrepo --update -q $repo tags: - - docker_node + - docker-cc7 only: - tags except: @@ -37,7 +37,7 @@ weekly:cc7:ceph: - yum-builddep --setopt=cern*.exclude=xrootd* --nogpgcheck -y *.src.rpm - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" *.src.rpm tags: - - docker_node + - docker-cc7 only: - schedules @@ -59,7 +59,7 @@ build:cc7:ceph: - sudo -u stci -H cp RPMS/* $path; - sudo -u stci -H createrepo --update -q $path; tags: - - docker_node + - docker-cc7 only: - master except: From 5a19de8d822984202673d9912c15db5171b63c4b Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Wed, 16 Oct 2019 12:03:09 +0200 Subject: [PATCH 012/442] [XrdCeph] If built as a submodule use parent project's PLUGIN_VERSION. --- cmake/XRootDDefaults.cmake | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmake/XRootDDefaults.cmake b/cmake/XRootDDefaults.cmake index 7df08ab6f2f..597bb496c7e 100644 --- a/cmake/XRootDDefaults.cmake +++ b/cmake/XRootDDefaults.cmake @@ -9,6 +9,9 @@ if( "${CMAKE_BUILD_TYPE}" STREQUAL "" ) endif() endif() -define_default( PLUGIN_VERSION 4 ) +if( NOT XRDCEPH_SUBMODULE ) + define_default( PLUGIN_VERSION 4 ) +endif() + define_default( ENABLE_TESTS FALSE ) define_default( ENABLE_CEPH TRUE ) From 228cc6ad87e0133b80e77135b64393fba1cbef4c Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Fri, 17 Jan 2020 14:25:31 +0100 Subject: [PATCH 013/442] [CMake] Require cmake 3. --- CMakeLists.txt | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8fefeb2fc77..bce584b15a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,13 +1,7 @@ #------------------------------------------------------------------------------- # Project description #------------------------------------------------------------------------------- -cmake_minimum_required( VERSION 2.6 ) - -if( NOT XRDCEPH_SUBMODULE ) - IF(CMAKE_VERSION VERSION_GREATER "2.8.12") - CMAKE_POLICY(SET CMP0022 OLD) - ENDIF() -endif() +cmake_minimum_required( VERSION 3.1 ) project( xrootd-ceph ) From 712091caf7cbc1082826f9fbb0655b0d897bf76e Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Fri, 17 Jan 2020 15:16:34 +0100 Subject: [PATCH 014/442] [RPM] Update spec build dependency on cmake. --- packaging/rhel/xrootd-ceph.spec.in | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packaging/rhel/xrootd-ceph.spec.in b/packaging/rhel/xrootd-ceph.spec.in index f080da293a3..32e326e97e1 100644 --- a/packaging/rhel/xrootd-ceph.spec.in +++ b/packaging/rhel/xrootd-ceph.spec.in @@ -25,6 +25,16 @@ %define _with_ceph 1 %endif +%if %{?rhel:1}%{!?rhel:0} + %if %{rhel} > 7 + %define use_cmake3 0 + %else + %define use_cmake3 1 + %endif +%else + %define use_cmake3 0 +%endif + #------------------------------------------------------------------------------- # Package definitions #------------------------------------------------------------------------------- @@ -44,7 +54,11 @@ Source0: xrootd-ceph.tar.gz BuildRoot: %{_tmppath}/%{name}-root +%if %{use_cmake3} +BuildRequires: cmake3 +%else BuildRequires: cmake +%endif %if %{?_with_tests:1}%{!?_with_tests:0} BuildRequires: cppunit-devel From 34ea80a4d6e2968cfd4ea3051917b29208ae179e Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Fri, 17 Jan 2020 15:21:15 +0100 Subject: [PATCH 015/442] [RPM] Update cmake binary in build script. --- packaging/rhel/xrootd-ceph.spec.in | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packaging/rhel/xrootd-ceph.spec.in b/packaging/rhel/xrootd-ceph.spec.in index 32e326e97e1..4f3d2fa5ac8 100644 --- a/packaging/rhel/xrootd-ceph.spec.in +++ b/packaging/rhel/xrootd-ceph.spec.in @@ -106,11 +106,17 @@ export CXX=clang++ mkdir build pushd build -cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + +%if %{use_cmake3} +cmake3 \ +%else +cmake \ +%endif + -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=RelWithDebInfo \ %if %{?_with_tests:1}%{!?_with_tests:0} - -DENABLE_TESTS=TRUE \ + -DENABLE_TESTS=TRUE \ %else - -DENABLE_TESTS=FALSE \ + -DENABLE_TESTS=FALSE \ %endif ../ From 3e3b1cd16c6e1443ae93b7a666838aa6775ce454 Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Thu, 27 Feb 2020 11:12:31 +0100 Subject: [PATCH 016/442] [RPM] Set hard requirement on xrootd version. --- packaging/rhel/xrootd-ceph.spec.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packaging/rhel/xrootd-ceph.spec.in b/packaging/rhel/xrootd-ceph.spec.in index 4f3d2fa5ac8..99a3cec88ff 100644 --- a/packaging/rhel/xrootd-ceph.spec.in +++ b/packaging/rhel/xrootd-ceph.spec.in @@ -71,8 +71,8 @@ BuildRequires: libradosstriper-devel >= 11.0 BuildRequires: clang %endif -BuildRequires: xrootd-server-devel >= %{version}-%{release} -BuildRequires: xrootd-private-devel >= %{version}-%{release} +BuildRequires: xrootd-server-devel = %{version}-%{release} +BuildRequires: xrootd-private-devel = %{version}-%{release} %description The xrootd-ceph is an OSS layer plug-in for the XRootD server for interfacing From c06162c4b3fe75e4837a4ef5dda544773fc46e6e Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Thu, 27 Feb 2020 12:46:34 +0100 Subject: [PATCH 017/442] [RPM] Update the build requirement on xrootd devel. --- packaging/rhel/xrootd-ceph.spec.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packaging/rhel/xrootd-ceph.spec.in b/packaging/rhel/xrootd-ceph.spec.in index 99a3cec88ff..db9800dbb80 100644 --- a/packaging/rhel/xrootd-ceph.spec.in +++ b/packaging/rhel/xrootd-ceph.spec.in @@ -71,8 +71,8 @@ BuildRequires: libradosstriper-devel >= 11.0 BuildRequires: clang %endif -BuildRequires: xrootd-server-devel = %{version}-%{release} -BuildRequires: xrootd-private-devel = %{version}-%{release} +BuildRequires: xrootd-server-devel%{?_isa} = %{epoch}:%{version}-%{release} +BuildRequires: xrootd-private-devel%{?_isa} = %{epoch}:%{version}-%{release} %description The xrootd-ceph is an OSS layer plug-in for the XRootD server for interfacing From f5a72a5aa43dfaba7dc524d89717a63070aeb5e8 Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Thu, 27 Feb 2020 12:49:58 +0100 Subject: [PATCH 018/442] [CI] Update build script. --- .gitlab-ci.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2562f01092b..13861da70f8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -47,10 +47,13 @@ build:cc7:ceph: script: - yum install --nogpg -y cmake3 make gcc-c++ rpm-build which git yum-plugin-priorities sssd-client sudo createrepo - cd packaging/ - - ./makesrpm.sh - echo -e '[ceph]\nname=ceph\nbaseurl=http://linuxsoft.cern.ch/mirror/download.ceph.com/rpm-nautilus/el7/x86_64/\npriority=4\ngpgcheck=0\nenabled=1\n' >> /etc/yum.repos.d/ceph.repo - - echo -e '[xrootd-testing]\nname=XRootD Testing repository\nbaseurl=http://xrootd.org/binaries/testing/slc/7/$basearch http://xrootd.cern.ch/sw/repos/testing/slc/7/$basearch\ngpgcheck=1\nenabled=1\nprotect=0\ngpgkey=http://xrootd.cern.ch/sw/releases/RPM-GPG-KEY.txt\n' >> /etc/yum.repos.d/xrootd-testing.repo - - echo -e '[xrootd-stable]\nname=XRootD Stable repository\nbaseurl=http://xrootd.org/binaries/stable/slc/7/$basearch http://xrootd.cern.ch/sw/repos/stable/slc/7/$basearch\ngpgcheck=1\nenabled=1\nprotect=0\ngpgkey=http://xrootd.cern.ch/sw/releases/RPM-GPG-KEY.txt\n' >> /etc/yum.repos.d/xrootd-stable.repo + - echo -e '[xrootd-experimental]\nname=XRootD Experimental repository\nbaseurl=http://storage-ci.web.cern.ch/storage-ci/xrootd/experimental/epel-7/x86_64/\ngpgcheck=1\nenabled=1\nprotect=0\ngpgkey=http://xrootd.cern.ch/sw/releases/RPM-GPG-KEY.txt\n' >> /etc/yum.repos.d/xrootd-experimental.repo + - yum clean all + - version=$(yum info xrootd-devel | grep Version | cut -d':' -f2 | tr -d "[:blank:]") + - release=$(yum info xrootd-devel | grep Release | cut -d':' -f2 | tr -d "[:blank:]") + - release=${release%.el7.cern} + - ./makesrpm.sh --version $version-$release - yum-builddep --setopt=cern*.exclude=xrootd* --nogpgcheck -y *.src.rpm - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" *.src.rpm - path=/eos/project/s/storage-ci/www/xrootd/ceph/cc-7/x86_64/$(date +'%Y%m%d') From 89ead57fb66b60b9d6690d67d490a2be0272d2f9 Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Thu, 27 Feb 2020 12:55:40 +0100 Subject: [PATCH 019/442] [CI] Update nightly build script. --- .gitlab-ci.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 13861da70f8..a91b72306a5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -31,9 +31,13 @@ weekly:cc7:ceph: script: - yum install --nogpg -y cmake3 make gcc-c++ rpm-build which git yum-plugin-priorities sssd-client sudo createrepo - cd packaging/ - - ./makesrpm.sh - echo -e '[ceph]\nname=ceph\nbaseurl=http://linuxsoft.cern.ch/mirror/download.ceph.com/rpm-nautilus/el7/x86_64/\npriority=4\ngpgcheck=0\nenabled=1\n' >> /etc/yum.repos.d/ceph.repo - echo -e '[xrootd-experimental]\nname=XRootD Experimental repository\nbaseurl=http://storage-ci.web.cern.ch/storage-ci/xrootd/experimental/epel-7/$basearch\ngpgcheck=1\nenabled=1\nprotect=0\n' >> /etc/yum.repos.d/xrootd-experimental.repo + - yum clean all + - version=$(yum info xrootd-devel | grep Version | cut -d':' -f2 | tr -d "[:blank:]") + - release=$(yum info xrootd-devel | grep Release | cut -d':' -f2 | tr -d "[:blank:]") + - release=${release%.el7.cern} + - ./makesrpm.sh --version "$version-$release" - yum-builddep --setopt=cern*.exclude=xrootd* --nogpgcheck -y *.src.rpm - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" *.src.rpm tags: @@ -48,12 +52,12 @@ build:cc7:ceph: - yum install --nogpg -y cmake3 make gcc-c++ rpm-build which git yum-plugin-priorities sssd-client sudo createrepo - cd packaging/ - echo -e '[ceph]\nname=ceph\nbaseurl=http://linuxsoft.cern.ch/mirror/download.ceph.com/rpm-nautilus/el7/x86_64/\npriority=4\ngpgcheck=0\nenabled=1\n' >> /etc/yum.repos.d/ceph.repo - - echo -e '[xrootd-experimental]\nname=XRootD Experimental repository\nbaseurl=http://storage-ci.web.cern.ch/storage-ci/xrootd/experimental/epel-7/x86_64/\ngpgcheck=1\nenabled=1\nprotect=0\ngpgkey=http://xrootd.cern.ch/sw/releases/RPM-GPG-KEY.txt\n' >> /etc/yum.repos.d/xrootd-experimental.repo + - echo -e '[xrootd-experimental]\nname=XRootD Experimental repository\nbaseurl=http://storage-ci.web.cern.ch/storage-ci/xrootd/experimental/epel-7/$basearch\ngpgcheck=1\nenabled=1\nprotect=0\n' >> /etc/yum.repos.d/xrootd-experimental.repo - yum clean all - version=$(yum info xrootd-devel | grep Version | cut -d':' -f2 | tr -d "[:blank:]") - release=$(yum info xrootd-devel | grep Release | cut -d':' -f2 | tr -d "[:blank:]") - release=${release%.el7.cern} - - ./makesrpm.sh --version $version-$release + - ./makesrpm.sh --version "$version-$release" - yum-builddep --setopt=cern*.exclude=xrootd* --nogpgcheck -y *.src.rpm - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" *.src.rpm - path=/eos/project/s/storage-ci/www/xrootd/ceph/cc-7/x86_64/$(date +'%Y%m%d') From 851f756233b1eb891e228bcecfd4f30b57400ad3 Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Thu, 27 Feb 2020 14:38:21 +0100 Subject: [PATCH 020/442] [RPM] Update spec. --- packaging/rhel/xrootd-ceph.spec.in | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packaging/rhel/xrootd-ceph.spec.in b/packaging/rhel/xrootd-ceph.spec.in index db9800dbb80..ab991115364 100644 --- a/packaging/rhel/xrootd-ceph.spec.in +++ b/packaging/rhel/xrootd-ceph.spec.in @@ -73,6 +73,9 @@ BuildRequires: clang BuildRequires: xrootd-server-devel%{?_isa} = %{epoch}:%{version}-%{release} BuildRequires: xrootd-private-devel%{?_isa} = %{epoch}:%{version}-%{release} +BuildRequires: xrootd-libs%{?_isa} = %{epoch}:%{version}-%{release} +BuildRequires: xrootd-server-libs%{?_isa} = %{epoch}:%{version}-%{release} +BuildRequires: xrootd-client-libs%{?_isa} = %{epoch}:%{version}-%{release} %description The xrootd-ceph is an OSS layer plug-in for the XRootD server for interfacing From 1518a4cca727edfaa72b28a2e9cadf45267a2a37 Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Thu, 27 Feb 2020 15:06:49 +0100 Subject: [PATCH 021/442] [CI] Update release script. --- .gitlab-ci.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a91b72306a5..639b2b3655b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -6,13 +6,14 @@ release:cc7:ceph: image: gitlab-registry.cern.ch/linuxsupport/cc7-base script: - yum install --nogpg -y cmake3 make gcc-c++ rpm-build which git yum-plugin-priorities sssd-client sudo createrepo + - git checkout tags/${CI_COMMIT_TAG} - cd packaging/ - - ./makesrpm.sh + - ./makesrpm.sh --define "dist .el7" - echo -e '[ceph]\nname=ceph\nbaseurl=http://linuxsoft.cern.ch/mirror/download.ceph.com/rpm-nautilus/el7/x86_64/\npriority=4\ngpgcheck=0\nenabled=1\n' >> /etc/yum.repos.d/ceph.repo - echo -e '[xrootd-testing]\nname=XRootD Testing repository\nbaseurl=http://xrootd.org/binaries/testing/slc/7/$basearch http://xrootd.cern.ch/sw/repos/testing/slc/7/$basearch\ngpgcheck=1\nenabled=1\nprotect=0\ngpgkey=http://xrootd.cern.ch/sw/releases/RPM-GPG-KEY.txt\n' >> /etc/yum.repos.d/xrootd-testing.repo - echo -e '[xrootd-stable]\nname=XRootD Stable repository\nbaseurl=http://xrootd.org/binaries/stable/slc/7/$basearch http://xrootd.cern.ch/sw/repos/stable/slc/7/$basearch\ngpgcheck=1\nenabled=1\nprotect=0\ngpgkey=http://xrootd.cern.ch/sw/releases/RPM-GPG-KEY.txt\n' >> /etc/yum.repos.d/xrootd-stable.repo - yum-builddep --setopt=cern*.exclude=xrootd* --nogpgcheck -y *.src.rpm - - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" *.src.rpm + - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" -D "dist .el7" *.src.rpm - repo=/eos/project/s/storage-ci/www/xrootd/ceph-release/cc-7/x86_64/ - sudo -u stci -H mkdir -p $repo - sudo -u stci -H cp *.src.rpm $repo From a53b7587459954d7f7ad36cbf31b89f45ef4b924 Mon Sep 17 00:00:00 2001 From: Giuseppe Lo Presti Date: Mon, 2 Mar 2020 11:18:53 +0100 Subject: [PATCH 022/442] [RPM] Added hard runtime dependency as well and removed non-existent test package --- packaging/rhel/xrootd-ceph.spec.in | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/packaging/rhel/xrootd-ceph.spec.in b/packaging/rhel/xrootd-ceph.spec.in index ab991115364..50662aa69af 100644 --- a/packaging/rhel/xrootd-ceph.spec.in +++ b/packaging/rhel/xrootd-ceph.spec.in @@ -77,22 +77,12 @@ BuildRequires: xrootd-libs%{?_isa} = %{epoch}:%{version}-%{release} BuildRequires: xrootd-server-libs%{?_isa} = %{epoch}:%{version}-%{release} BuildRequires: xrootd-client-libs%{?_isa} = %{epoch}:%{version}-%{release} +Requires: xrootd-server-libs%{?_isa} = %{epoch}:%{version}-%{release} + %description The xrootd-ceph is an OSS layer plug-in for the XRootD server for interfacing with the Ceph storage platform. -#------------------------------------------------------------------------------- -# tests -#------------------------------------------------------------------------------- -%if %{?_with_tests:1}%{!?_with_tests:0} -%package tests -Summary: CPPUnit tests -Group: Development/Tools -Requires: %{name}-client = %{epoch}:%{version}-%{release} -%description tests -This package contains a set of CPPUnit tests for xrootd. -%endif - #------------------------------------------------------------------------------- # Build instructions #------------------------------------------------------------------------------- @@ -166,5 +156,7 @@ rm -rf $RPM_BUILD_ROOT # Changelog #------------------------------------------------------------------------------- %changelog +* Mon Mar 02 2020 Michal Simon +- fixed RPM dependencies * Thu Mar 08 2018 Michal Simon - initial release From d098606c6f4abcade416036270bfbe9122d04a31 Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Mon, 2 Mar 2020 16:05:49 +0100 Subject: [PATCH 023/442] Extend hard runtime dependencies. --- packaging/rhel/xrootd-ceph.spec.in | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packaging/rhel/xrootd-ceph.spec.in b/packaging/rhel/xrootd-ceph.spec.in index 50662aa69af..08cbd5effd4 100644 --- a/packaging/rhel/xrootd-ceph.spec.in +++ b/packaging/rhel/xrootd-ceph.spec.in @@ -78,6 +78,8 @@ BuildRequires: xrootd-server-libs%{?_isa} = %{epoch}:%{version}-%{release} BuildRequires: xrootd-client-libs%{?_isa} = %{epoch}:%{version}-%{release} Requires: xrootd-server-libs%{?_isa} = %{epoch}:%{version}-%{release} +Requires: xrootd-client-libs%{?_isa} = %{epoch}:%{version}-%{release} +Requires: xrootd-libs%{?_isa} = %{epoch}:%{version}-%{release} %description The xrootd-ceph is an OSS layer plug-in for the XRootD server for interfacing From a81178de28ebb0bc7af399bfd266c624b393f127 Mon Sep 17 00:00:00 2001 From: Mattias Ellert Date: Thu, 19 Mar 2020 21:30:00 +0100 Subject: [PATCH 024/442] Adapt to changes in ceph 15.1.1 buffer::list::copy was removed. --- src/XrdCeph/XrdCephPosix.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/XrdCeph/XrdCephPosix.cc b/src/XrdCeph/XrdCephPosix.cc index 9b3e209c35c..d46f6726bab 100644 --- a/src/XrdCeph/XrdCephPosix.cc +++ b/src/XrdCeph/XrdCephPosix.cc @@ -795,7 +795,7 @@ ssize_t ceph_posix_read(int fd, void *buf, size_t count) { ceph::bufferlist bl; int rc = striper->read(fr->name, &bl, count, fr->offset); if (rc < 0) return rc; - bl.copy(0, rc, (char*)buf); + bl.begin().copy(rc, (char*)buf); fr->offset += rc; fr->rdcount++; return rc; @@ -819,7 +819,7 @@ ssize_t ceph_posix_pread(int fd, void *buf, size_t count, off64_t offset) { ceph::bufferlist bl; int rc = striper->read(fr->name, &bl, count, offset); if (rc < 0) return rc; - bl.copy(0, rc, (char*)buf); + bl.begin().copy(rc, (char*)buf); fr->rdcount++; return rc; } else { @@ -832,7 +832,7 @@ static void ceph_aio_read_complete(rados_completion_t c, void *arg) { size_t rc = rados_aio_get_return_value(c); if (awa->bl) { if (rc > 0) { - awa->bl->copy(0, rc, (char*)awa->aiop->sfsAio.aio_buf); + awa->bl->begin().copy(rc, (char*)awa->aiop->sfsAio.aio_buf); } delete awa->bl; awa->bl = 0; @@ -969,7 +969,7 @@ static ssize_t ceph_posix_internal_getxattr(const CephFile &file, const char* na int rc = striper->getxattr(file.name, name, bl); if (rc < 0) return rc; size_t returned_size = (size_t)rc Date: Tue, 18 Aug 2020 12:21:21 +0200 Subject: [PATCH 025/442] [RPM] Pin xrootd-ceph to 14.2.11. --- packaging/rhel/xrootd-ceph.spec.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packaging/rhel/xrootd-ceph.spec.in b/packaging/rhel/xrootd-ceph.spec.in index 08cbd5effd4..03a64277e66 100644 --- a/packaging/rhel/xrootd-ceph.spec.in +++ b/packaging/rhel/xrootd-ceph.spec.in @@ -64,8 +64,8 @@ BuildRequires: cmake BuildRequires: cppunit-devel %endif -BuildRequires: librados-devel >= 11.0 -BuildRequires: libradosstriper-devel >= 11.0 +BuildRequires: librados-devel = 14.2.11 +BuildRequires: libradosstriper-devel = 14.2.11 %if %{?_with_clang:1}%{!?_with_clang:0} BuildRequires: clang From f65aba05400481acb137cef0f647147b306c7627 Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Tue, 18 Aug 2020 12:46:55 +0200 Subject: [PATCH 026/442] [RPM] Pin xrootd-ceph to 2:14.2.11. --- packaging/rhel/xrootd-ceph.spec.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packaging/rhel/xrootd-ceph.spec.in b/packaging/rhel/xrootd-ceph.spec.in index 03a64277e66..59d3039ef4d 100644 --- a/packaging/rhel/xrootd-ceph.spec.in +++ b/packaging/rhel/xrootd-ceph.spec.in @@ -64,8 +64,8 @@ BuildRequires: cmake BuildRequires: cppunit-devel %endif -BuildRequires: librados-devel = 14.2.11 -BuildRequires: libradosstriper-devel = 14.2.11 +BuildRequires: librados-devel = 2:14.2.11 +BuildRequires: libradosstriper-devel = 2:14.2.11 %if %{?_with_clang:1}%{!?_with_clang:0} BuildRequires: clang From bf2bc65fdf6390eeb9c0cd53dc1974ce94b5e1b5 Mon Sep 17 00:00:00 2001 From: Eric Cano Date: Wed, 26 Aug 2020 17:40:48 +0200 Subject: [PATCH 027/442] Added additional statistics to ceph access. Especially added them for async accesses. --- src/XrdCeph/XrdCephPosix.cc | 81 +++++++++++++++++++++++++++++++++---- 1 file changed, 74 insertions(+), 7 deletions(-) diff --git a/src/XrdCeph/XrdCephPosix.cc b/src/XrdCeph/XrdCephPosix.cc index d46f6726bab..24985aa95a3 100644 --- a/src/XrdCeph/XrdCephPosix.cc +++ b/src/XrdCeph/XrdCephPosix.cc @@ -63,9 +63,19 @@ struct CephFile { struct CephFileRef : CephFile { int flags; mode_t mode; - unsigned long long offset; + uint64_t offset; + // This mutex protects against parallel updates of the stats. + XrdSysMutex statsMutex; + uint64_t maxOffsetWritten; + uint64_t bytesAsyncWritePending; + uint64_t bytesWritten; unsigned rdcount; unsigned wrcount; + unsigned asyncRdStartCount; + unsigned asyncRdCompletionCount; + unsigned asyncWrStartCount; + unsigned asyncWrCompletionCount; + ::timeval lastAsyncSubmission; }; /// small struct for directory listing @@ -76,11 +86,12 @@ struct DirIterator { /// small struct for aio API callbacks struct AioArgs { - AioArgs(XrdSfsAio* a, AioCB *b, size_t n, ceph::bufferlist *_bl=0) : - aiop(a), callback(b), nbBytes(n), bl(_bl) {} + AioArgs(XrdSfsAio* a, AioCB *b, size_t n, int _fd, ceph::bufferlist *_bl=0) : + aiop(a), callback(b), nbBytes(n), fd(_fd), bl(_bl) {} XrdSfsAio* aiop; AioCB *callback; size_t nbBytes; + int fd; ceph::bufferlist *bl; }; @@ -152,6 +163,10 @@ CephFileRef* getFileRef(int fd) { XrdSysMutexHelper lock(g_fd_mutex); std::map::iterator it = g_fds.find(fd); if (it != g_fds.end()) { + // We will release the lock upon exiting this function. + // The structure here is not protected from deletion, but we trust xrootd to + // ensure close (which does the deletion) will not be called before all previous + // calls are complete (including the async ones). return &(it->second); } else { return 0; @@ -424,8 +439,17 @@ static CephFileRef getCephFileRef(const char *path, XrdOucEnv *env, int flags, fr.flags = flags; fr.mode = mode; fr.offset = 0; + fr.maxOffsetWritten = 0; + fr.bytesAsyncWritePending = 0; + fr.bytesWritten = 0; fr.rdcount = 0; fr.wrcount = 0; + fr.asyncRdStartCount = 0; + fr.asyncRdCompletionCount = 0; + fr.asyncWrStartCount = 0; + fr.asyncWrCompletionCount = 0; + fr.lastAsyncSubmission.tv_sec = 0; + fr.lastAsyncSubmission.tv_usec = 0; return fr; } @@ -644,8 +668,19 @@ int ceph_posix_open(XrdOucEnv* env, const char *pathname, int flags, mode_t mode int ceph_posix_close(int fd) { CephFileRef* fr = getFileRef(fd); if (fr) { - logwrapper((char*)"ceph_close: closed fd %d for file %s, read ops count %d, write ops count %d", - fd, fr->name.c_str(), fr->rdcount, fr->wrcount); + ::timeval now; + ::gettimeofday(&now, nullptr); + XrdSysMutexHelper lock(fr->statsMutex); + double lastAsyncAge = 1.0 * (now.tv_sec - fr->lastAsyncSubmission.tv_sec) + + 0.000001 * (now.tv_usec - fr->lastAsyncSubmission.tv_usec); + logwrapper((char*)"ceph_close: closed fd %d for file %s, read ops count %d, write ops count %d, " + "async write ops %d/%d, async pending write bytes %ld, " + "async read ops %d/%d, bytes written/max offset %ld/%ld, " + "last async op age %f", + fd, fr->name.c_str(), fr->rdcount, fr->wrcount, + fr->asyncWrCompletionCount, fr->asyncWrStartCount, fr->bytesAsyncWritePending, + fr->asyncWrCompletionCount, fr->asyncWrStartCount, fr->bytesWritten, fr->maxOffsetWritten, + lastAsyncAge); deleteFileRef(fd, *fr); return 0; } else { @@ -703,7 +738,10 @@ ssize_t ceph_posix_write(int fd, const void *buf, size_t count) { int rc = striper->write(fr->name, bl, count, fr->offset); if (rc) return rc; fr->offset += count; + XrdSysMutexHelper lock(fr->statsMutex); fr->wrcount++; + fr->bytesWritten+=count; + if (fr->offset) fr->maxOffsetWritten = std::max(fr->offset - 1, fr->maxOffsetWritten); return count; } else { return -EBADF; @@ -726,7 +764,10 @@ ssize_t ceph_posix_pwrite(int fd, const void *buf, size_t count, off64_t offset) bl.append((const char*)buf, count); int rc = striper->write(fr->name, bl, count, offset); if (rc) return rc; + XrdSysMutexHelper lock(fr->statsMutex); fr->wrcount++; + fr->bytesWritten+=count; + if (offset + count) fr->maxOffsetWritten = std::max(offset + count - 1, fr->maxOffsetWritten); return count; } else { return -EBADF; @@ -736,6 +777,17 @@ ssize_t ceph_posix_pwrite(int fd, const void *buf, size_t count, off64_t offset) static void ceph_aio_write_complete(rados_completion_t c, void *arg) { AioArgs *awa = reinterpret_cast(arg); size_t rc = rados_aio_get_return_value(c); + // Compute statistics before reportng to xrootd, so that a close cannot happen + // in the meantime. + CephFileRef* fr = getFileRef(awa->fd); + if (fr) { + XrdSysMutexHelper lock(fr->statsMutex); + fr->asyncWrCompletionCount++; + fr->bytesAsyncWritePending -= awa->nbBytes; + if (awa->aiop->sfsAio.aio_nbytes) + fr->maxOffsetWritten = std::max(fr->maxOffsetWritten, awa->aiop->sfsAio.aio_offset + awa->aiop->sfsAio.aio_nbytes - 1); + ::gettimeofday(&fr->lastAsyncSubmission, nullptr); + } awa->callback(awa->aiop, rc == 0 ? awa->nbBytes : rc); delete(awa); } @@ -768,12 +820,15 @@ ssize_t ceph_aio_write(int fd, XrdSfsAio *aiop, AioCB *cb) { return -EINVAL; } // prepare a ceph AioCompletion object and do async call - AioArgs *args = new AioArgs(aiop, cb, count); + AioArgs *args = new AioArgs(aiop, cb, count, fd); librados::AioCompletion *completion = cluster->aio_create_completion(args, ceph_aio_write_complete, NULL); // do the write int rc = striper->aio_write(fr->name, completion, bl, count, offset); completion->release(); + XrdSysMutexHelper lock(fr->statsMutex); + fr->asyncWrStartCount++; + fr->bytesAsyncWritePending+=count; return rc; } else { return -EBADF; @@ -796,6 +851,7 @@ ssize_t ceph_posix_read(int fd, void *buf, size_t count) { int rc = striper->read(fr->name, &bl, count, fr->offset); if (rc < 0) return rc; bl.begin().copy(rc, (char*)buf); + XrdSysMutexHelper lock(fr->statsMutex); fr->offset += rc; fr->rdcount++; return rc; @@ -820,6 +876,7 @@ ssize_t ceph_posix_pread(int fd, void *buf, size_t count, off64_t offset) { int rc = striper->read(fr->name, &bl, count, offset); if (rc < 0) return rc; bl.begin().copy(rc, (char*)buf); + XrdSysMutexHelper lock(fr->statsMutex); fr->rdcount++; return rc; } else { @@ -837,6 +894,13 @@ static void ceph_aio_read_complete(rados_completion_t c, void *arg) { delete awa->bl; awa->bl = 0; } + // Compute statistics before reportng to xrootd, so that a close cannot happen + // in the meantime. + CephFileRef* fr = getFileRef(awa->fd); + if (fr) { + XrdSysMutexHelper lock(fr->statsMutex); + fr->asyncRdCompletionCount++; + } awa->callback(awa->aiop, rc == 0 ? awa->nbBytes : rc); delete(awa); } @@ -867,12 +931,14 @@ ssize_t ceph_aio_read(int fd, XrdSfsAio *aiop, AioCB *cb) { return -EINVAL; } // prepare a ceph AioCompletion object and do async call - AioArgs *args = new AioArgs(aiop, cb, count, bl); + AioArgs *args = new AioArgs(aiop, cb, count, fd, bl); librados::AioCompletion *completion = cluster->aio_create_completion(args, ceph_aio_read_complete, NULL); // do the read int rc = striper->aio_read(fr->name, completion, bl, count, offset); completion->release(); + XrdSysMutexHelper lock(fr->statsMutex); + fr->asyncRdStartCount++; return rc; } else { return -EBADF; @@ -936,6 +1002,7 @@ int ceph_posix_stat(XrdOucEnv* env, const char *pathname, struct stat *buf) { int ceph_posix_fsync(int fd) { CephFileRef* fr = getFileRef(fd); if (fr) { + // no locking of fr as it is not used. logwrapper((char*)"ceph_sync: fd %d", fd); return 0; } else { From 1984b531a1fbd96d2ac4c09e081e48cbb15a7ff9 Mon Sep 17 00:00:00 2001 From: Eric Cano Date: Tue, 15 Sep 2020 14:50:39 +0200 Subject: [PATCH 028/442] Fixed statistics. --- src/XrdCeph/XrdCephPosix.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/XrdCeph/XrdCephPosix.cc b/src/XrdCeph/XrdCephPosix.cc index 24985aa95a3..d108a54c7af 100644 --- a/src/XrdCeph/XrdCephPosix.cc +++ b/src/XrdCeph/XrdCephPosix.cc @@ -679,7 +679,7 @@ int ceph_posix_close(int fd) { "last async op age %f", fd, fr->name.c_str(), fr->rdcount, fr->wrcount, fr->asyncWrCompletionCount, fr->asyncWrStartCount, fr->bytesAsyncWritePending, - fr->asyncWrCompletionCount, fr->asyncWrStartCount, fr->bytesWritten, fr->maxOffsetWritten, + fr->asyncRdCompletionCount, fr->asyncRdStartCount, fr->bytesWritten, fr->maxOffsetWritten, lastAsyncAge); deleteFileRef(fd, *fr); return 0; @@ -784,6 +784,7 @@ static void ceph_aio_write_complete(rados_completion_t c, void *arg) { XrdSysMutexHelper lock(fr->statsMutex); fr->asyncWrCompletionCount++; fr->bytesAsyncWritePending -= awa->nbBytes; + fr->bytesWritten += awa->nbBytes; if (awa->aiop->sfsAio.aio_nbytes) fr->maxOffsetWritten = std::max(fr->maxOffsetWritten, awa->aiop->sfsAio.aio_offset + awa->aiop->sfsAio.aio_nbytes - 1); ::gettimeofday(&fr->lastAsyncSubmission, nullptr); From b78df3d319e8b530db83d8d987adb4d912c0070d Mon Sep 17 00:00:00 2001 From: Eric Cano Date: Mon, 21 Sep 2020 17:33:19 +0200 Subject: [PATCH 029/442] Added tracking of max async length. --- src/XrdCeph/XrdCephPosix.cc | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/XrdCeph/XrdCephPosix.cc b/src/XrdCeph/XrdCephPosix.cc index d108a54c7af..c0668fb8543 100644 --- a/src/XrdCeph/XrdCephPosix.cc +++ b/src/XrdCeph/XrdCephPosix.cc @@ -76,6 +76,7 @@ struct CephFileRef : CephFile { unsigned asyncWrStartCount; unsigned asyncWrCompletionCount; ::timeval lastAsyncSubmission; + double longestAsyncWriteTime; }; /// small struct for directory listing @@ -87,11 +88,12 @@ struct DirIterator { /// small struct for aio API callbacks struct AioArgs { AioArgs(XrdSfsAio* a, AioCB *b, size_t n, int _fd, ceph::bufferlist *_bl=0) : - aiop(a), callback(b), nbBytes(n), fd(_fd), bl(_bl) {} + aiop(a), callback(b), nbBytes(n), fd(_fd), bl(_bl) { ::gettimeofday(&startTime, nullptr); } XrdSfsAio* aiop; AioCB *callback; size_t nbBytes; int fd; + ::timeval startTime; ceph::bufferlist *bl; }; @@ -450,6 +452,7 @@ static CephFileRef getCephFileRef(const char *path, XrdOucEnv *env, int flags, fr.asyncWrCompletionCount = 0; fr.lastAsyncSubmission.tv_sec = 0; fr.lastAsyncSubmission.tv_usec = 0; + fr.longestAsyncWriteTime = 0.0l; return fr; } @@ -676,11 +679,11 @@ int ceph_posix_close(int fd) { logwrapper((char*)"ceph_close: closed fd %d for file %s, read ops count %d, write ops count %d, " "async write ops %d/%d, async pending write bytes %ld, " "async read ops %d/%d, bytes written/max offset %ld/%ld, " - "last async op age %f", + "longest async write %f, last async op age %f", fd, fr->name.c_str(), fr->rdcount, fr->wrcount, fr->asyncWrCompletionCount, fr->asyncWrStartCount, fr->bytesAsyncWritePending, fr->asyncRdCompletionCount, fr->asyncRdStartCount, fr->bytesWritten, fr->maxOffsetWritten, - lastAsyncAge); + fr->longestAsyncWriteTime, lastAsyncAge); deleteFileRef(fd, *fr); return 0; } else { @@ -787,7 +790,10 @@ static void ceph_aio_write_complete(rados_completion_t c, void *arg) { fr->bytesWritten += awa->nbBytes; if (awa->aiop->sfsAio.aio_nbytes) fr->maxOffsetWritten = std::max(fr->maxOffsetWritten, awa->aiop->sfsAio.aio_offset + awa->aiop->sfsAio.aio_nbytes - 1); - ::gettimeofday(&fr->lastAsyncSubmission, nullptr); + ::timeval now; + ::gettimeofday(&now, nullptr); + double writeTime = 0.000001 * (now.tv_usec - awa->startTime.tv_usec) + 1.0 * (now.tv_sec - awa->startTime.tv_sec); + fr->longestAsyncWriteTime = std::max(fr->longestAsyncWriteTime, writeTime); } awa->callback(awa->aiop, rc == 0 ? awa->nbBytes : rc); delete(awa); @@ -829,6 +835,7 @@ ssize_t ceph_aio_write(int fd, XrdSfsAio *aiop, AioCB *cb) { completion->release(); XrdSysMutexHelper lock(fr->statsMutex); fr->asyncWrStartCount++; + ::gettimeofday(&fr->lastAsyncSubmission, nullptr); fr->bytesAsyncWritePending+=count; return rc; } else { From 6dd13f2cc630ce468124271a94dcae615049e811 Mon Sep 17 00:00:00 2001 From: Eric Cano Date: Tue, 22 Sep 2020 16:10:04 +0200 Subject: [PATCH 030/442] Added measurement of longest xrootd callback invocation for async writes. --- src/XrdCeph/XrdCephPosix.cc | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/XrdCeph/XrdCephPosix.cc b/src/XrdCeph/XrdCephPosix.cc index c0668fb8543..c1b2d5bfb1f 100644 --- a/src/XrdCeph/XrdCephPosix.cc +++ b/src/XrdCeph/XrdCephPosix.cc @@ -77,6 +77,7 @@ struct CephFileRef : CephFile { unsigned asyncWrCompletionCount; ::timeval lastAsyncSubmission; double longestAsyncWriteTime; + double longestCallbackInvocation; }; /// small struct for directory listing @@ -453,6 +454,7 @@ static CephFileRef getCephFileRef(const char *path, XrdOucEnv *env, int flags, fr.lastAsyncSubmission.tv_sec = 0; fr.lastAsyncSubmission.tv_usec = 0; fr.longestAsyncWriteTime = 0.0l; + fr.longestCallbackInvocation = 0.0l; return fr; } @@ -679,11 +681,11 @@ int ceph_posix_close(int fd) { logwrapper((char*)"ceph_close: closed fd %d for file %s, read ops count %d, write ops count %d, " "async write ops %d/%d, async pending write bytes %ld, " "async read ops %d/%d, bytes written/max offset %ld/%ld, " - "longest async write %f, last async op age %f", + "longest async write %f, longest callback invocation %f, last async op age %f", fd, fr->name.c_str(), fr->rdcount, fr->wrcount, fr->asyncWrCompletionCount, fr->asyncWrStartCount, fr->bytesAsyncWritePending, fr->asyncRdCompletionCount, fr->asyncRdStartCount, fr->bytesWritten, fr->maxOffsetWritten, - fr->longestAsyncWriteTime, lastAsyncAge); + fr->longestAsyncWriteTime, fr->longestCallbackInvocation, lastAsyncAge); deleteFileRef(fd, *fr); return 0; } else { @@ -795,7 +797,15 @@ static void ceph_aio_write_complete(rados_completion_t c, void *arg) { double writeTime = 0.000001 * (now.tv_usec - awa->startTime.tv_usec) + 1.0 * (now.tv_sec - awa->startTime.tv_sec); fr->longestAsyncWriteTime = std::max(fr->longestAsyncWriteTime, writeTime); } + ::timeval before, after; + if (fr) ::gettimeofday(&before, nullptr); awa->callback(awa->aiop, rc == 0 ? awa->nbBytes : rc); + if (fr) { + ::gettimeofday(&after, nullptr); + double callbackInvocationTime = 0.000001 * (after.tv_usec - before.tv_usec) + 1.0 * (after.tv_sec - before.tv_sec); + XrdSysMutexHelper lock(fr->statsMutex); + fr->longestCallbackInvocation = std::max(fr->longestCallbackInvocation, callbackInvocationTime); + } delete(awa); } From f93989b606b30cff7661b5a705ea258d3a16c5fc Mon Sep 17 00:00:00 2001 From: Michal Simon Date: Fri, 25 Sep 2020 14:44:01 +0200 Subject: [PATCH 031/442] [CI] Update runner tag. --- .gitlab-ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 639b2b3655b..3b097867e8e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -20,7 +20,7 @@ release:cc7:ceph: - sudo -u stci -H cp RPMS/* $repo - sudo -u stci -H createrepo --update -q $repo tags: - - docker-cc7 + - docker_node only: - tags except: @@ -42,7 +42,7 @@ weekly:cc7:ceph: - yum-builddep --setopt=cern*.exclude=xrootd* --nogpgcheck -y *.src.rpm - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" *.src.rpm tags: - - docker-cc7 + - docker_node only: - schedules @@ -67,7 +67,7 @@ build:cc7:ceph: - sudo -u stci -H cp RPMS/* $path; - sudo -u stci -H createrepo --update -q $path; tags: - - docker-cc7 + - docker_node only: - master except: From 8ceb9981805f284625d6b105b1ebbe8bc451df17 Mon Sep 17 00:00:00 2001 From: Eric Cano Date: Fri, 2 Oct 2020 16:39:31 +0200 Subject: [PATCH 032/442] Fixed logs for read case. --- src/XrdCeph/XrdCephPosix.cc | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/XrdCeph/XrdCephPosix.cc b/src/XrdCeph/XrdCephPosix.cc index c1b2d5bfb1f..ec326ec73b7 100644 --- a/src/XrdCeph/XrdCephPosix.cc +++ b/src/XrdCeph/XrdCephPosix.cc @@ -676,8 +676,12 @@ int ceph_posix_close(int fd) { ::timeval now; ::gettimeofday(&now, nullptr); XrdSysMutexHelper lock(fr->statsMutex); - double lastAsyncAge = 1.0 * (now.tv_sec - fr->lastAsyncSubmission.tv_sec) - + 0.000001 * (now.tv_usec - fr->lastAsyncSubmission.tv_usec); + double lastAsyncAge = 0.0; + // Only compute an age if the starting point was set. + if (fr->lastAsyncSubmission.tv_sec && fr->lastAsyncSubmission.tv_usec) { + lastAsyncAge = 1.0 * (now.tv_sec - fr->lastAsyncSubmission.tv_sec) + + 0.000001 * (now.tv_usec - fr->lastAsyncSubmission.tv_usec); + } logwrapper((char*)"ceph_close: closed fd %d for file %s, read ops count %d, write ops count %d, " "async write ops %d/%d, async pending write bytes %ld, " "async read ops %d/%d, bytes written/max offset %ld/%ld, " @@ -685,7 +689,7 @@ int ceph_posix_close(int fd) { fd, fr->name.c_str(), fr->rdcount, fr->wrcount, fr->asyncWrCompletionCount, fr->asyncWrStartCount, fr->bytesAsyncWritePending, fr->asyncRdCompletionCount, fr->asyncRdStartCount, fr->bytesWritten, fr->maxOffsetWritten, - fr->longestAsyncWriteTime, fr->longestCallbackInvocation, lastAsyncAge); + fr->longestAsyncWriteTime, fr->longestCallbackInvocation, (lastAsyncAge)); deleteFileRef(fd, *fr); return 0; } else { From 9e856d490c805928b3ace33150ea2256a5e00f82 Mon Sep 17 00:00:00 2001 From: George Patargias Date: Mon, 12 Oct 2020 18:25:19 +0100 Subject: [PATCH 033/442] Fix the file overwrite bug in ceph_posix_open function --- src/XrdCeph/XrdCephPosix.cc | 76 ++++++++++++++++++++++--------------- 1 file changed, 46 insertions(+), 30 deletions(-) diff --git a/src/XrdCeph/XrdCephPosix.cc b/src/XrdCeph/XrdCephPosix.cc index ec326ec73b7..28d842d39ba 100644 --- a/src/XrdCeph/XrdCephPosix.cc +++ b/src/XrdCeph/XrdCephPosix.cc @@ -633,41 +633,57 @@ void ceph_posix_set_logfunc(void (*logfunc) (char *, va_list argp)) { static int ceph_posix_internal_truncate(const CephFile &file, unsigned long long size); -int ceph_posix_open(XrdOucEnv* env, const char *pathname, int flags, mode_t mode) { +/** + * * brief ceph_posix_open function opens a file for read or write + * * details This function either: + * * Opens a file for reading. If the file doesn't exist, this is an error. + * * Opens a file for writing. If the file already exists, check whether overwrite has been requested. If overwrite + * * hasn't been requested for an existing file, this is an error. + * * param env XrdOucEnv* Unused + * * param pathname const char* Specify the file to open. + * * param flags int Indicates whether reading or writing, and whether to overwrite an existing file. + * * param mode mode_t Unused + * * return int This is a file descriptor (non-negative) if the operation is successful, + * * or an error code (negative value) if the operation fails + * */ + +int ceph_posix_open(XrdOucEnv* env, const char *pathname, int flags, mode_t mode){ + CephFileRef fr = getCephFileRef(pathname, env, flags, mode, 0); - int fd = insertFileRef(fr); - logwrapper((char*)"ceph_open: fd %d associated to %s", fd, pathname); - // in case of O_CREAT and O_EXCL, we should complain if the file exists - // in case of O_READ, the file has to exist - if (((flags & O_CREAT) && (flags & O_EXCL)) || ((flags&O_ACCMODE) == O_RDONLY)) { - libradosstriper::RadosStriper *striper = getRadosStriper(fr); - if (0 == striper) { - deleteFileRef(fd, fr); - return -EINVAL; + + struct stat buf; + libradosstriper::RadosStriper *striper = getRadosStriper(fr); //Get a handle to the RADOS striper API + int rc = striper->stat(fr.name, (uint64_t*)&(buf.st_size), &(buf.st_atime)); //Get details about a file + bool fileExists = (rc != -ENOENT); //Make clear what condition we are testing + + if ((flags&O_ACCMODE) == O_RDONLY) { // Access mode is READ + + if (fileExists) { + int fd = insertFileRef(fr); + logwrapper((char*)"File descriptor %d associated to file %s opened in read mode", fd, pathname); + return fd; + } else { + return -ENOENT; } - struct stat buf; - int rc = striper->stat(fr.name, (uint64_t*)&(buf.st_size), &(buf.st_atime)); - if ((flags&O_ACCMODE) == O_RDONLY) { - if (rc) { - deleteFileRef(fd, fr); - return rc; + + } else { // Access mode is WRITE + if (fileExists) { + if (flags & O_TRUNC) { + int rc = ceph_posix_unlink(env, pathname); + if (rc < 0 && rc != -ENOENT) { + return rc; + } + } else { + return -EEXIST; } - } else if (rc != -ENOENT) { - deleteFileRef(fd, fr); - if (0 == rc) return -EEXIST; - return rc; - } - } - // in case of O_TRUNC, we should truncate the file - if (flags & O_TRUNC) { - int rc = ceph_posix_internal_truncate(fr, 0); - // fail only if file exists and cannot be truncated - if (rc < 0 && rc != -ENOENT) { - deleteFileRef(fd, fr); - return rc; } + // At this point, we know either the target file didn't exist, or the ceph_posix_unlink above removed it + int fd = insertFileRef(fr); + logwrapper((char*)"File descriptor %d associated to file %s opened in write mode", fd, pathname); + return fd; + } - return fd; + } int ceph_posix_close(int fd) { From 498e31efacf04d214a5804535b5aeecf6f1563a4 Mon Sep 17 00:00:00 2001 From: aoanla Date: Tue, 10 Nov 2020 17:16:22 +0000 Subject: [PATCH 034/442] GFAL2/mkdir fix (lie to posix-assuming clients about directory operations) --- src/XrdCeph/XrdCephOss.cc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/XrdCeph/XrdCephOss.cc b/src/XrdCeph/XrdCephOss.cc index ff6d4af2474..76bed8e744f 100644 --- a/src/XrdCeph/XrdCephOss.cc +++ b/src/XrdCeph/XrdCephOss.cc @@ -168,12 +168,14 @@ int XrdCephOss::Create(const char *tident, const char *path, mode_t access_mode, int XrdCephOss::Init(XrdSysLogger *logger, const char* configFn) { return 0; } +//SCS - lie to posix-assuming clients about directories [fixes brittleness in GFAL2] int XrdCephOss::Mkdir(const char *path, mode_t mode, int mkpath, XrdOucEnv *envP) { - return -ENOTSUP; + return 0; } +//SCS - lie to posix-assuming clients about directories [fixes brittleness in GFAL2] int XrdCephOss::Remdir(const char *path, int Opts, XrdOucEnv *eP) { - return -ENOTSUP; + return 0; } int XrdCephOss::Rename(const char *from, From cec2eb26396507b180bc61e3cb79085e6bc0216b Mon Sep 17 00:00:00 2001 From: Ian Johnson Date: Tue, 1 Dec 2020 10:22:21 +0000 Subject: [PATCH 035/442] Check the return value from getstriper in ceph_posix_open --- src/XrdCeph/XrdCephPosix.cc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/XrdCeph/XrdCephPosix.cc b/src/XrdCeph/XrdCephPosix.cc index 28d842d39ba..3131a9c8f05 100644 --- a/src/XrdCeph/XrdCephPosix.cc +++ b/src/XrdCeph/XrdCephPosix.cc @@ -653,7 +653,15 @@ int ceph_posix_open(XrdOucEnv* env, const char *pathname, int flags, mode_t mode struct stat buf; libradosstriper::RadosStriper *striper = getRadosStriper(fr); //Get a handle to the RADOS striper API + + if (NULL == striper) { + logwrapper((char*)"Cannot create striper"); + return -EINVAL; + } + int rc = striper->stat(fr.name, (uint64_t*)&(buf.st_size), &(buf.st_atime)); //Get details about a file + + bool fileExists = (rc != -ENOENT); //Make clear what condition we are testing if ((flags&O_ACCMODE) == O_RDONLY) { // Access mode is READ From 63d36261dfe4eeaf2d9ab77d2436ba17b919a5e3 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 4 Dec 2020 14:18:04 +0000 Subject: [PATCH 036/442] Set the plugin version to the required number --- README | 15 +++++++++------ cmake/XRootDDefaults.cmake | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/README b/README index 3654eabb484..e2bf17c3df6 100644 --- a/README +++ b/README @@ -26,9 +26,7 @@ 2.1 Build system - xrootd-ceph uses CMake to handle the build process. It should build fine - with cmake 2.6, however, on some platforms, this version of cmake has problems - handling the perl libraries, therefore version 2.8 or newer is recommended. + xrootd-ceph uses CMake to handle the build process. Please use CMake version 3 or greater (e.g. cmake3). 2.2 Build steps @@ -37,15 +35,20 @@ mkdir build cd build + * Ensure that the correct plugin version number is set in cmake/XRootDDefaults.cmake: + + if( NOT XRDCEPH_SUBMODULE ) + define_default( PLUGIN_VERSION 5 ) + endif() + * Generate the build system files using cmake, ie: - cmake /path/to/the/xrootd/source -DCMAKE_INSTALL_PREFIX=/opt/xrootd \ - -DENABLE_PERL=FALSE + cmake /path/to/the/xrootd-ceph/source -DCMAKE_INSTALL_PREFIX=/opt/xrootd * Build the source: make - * Install the source: + * Install the shared libraries: make install diff --git a/cmake/XRootDDefaults.cmake b/cmake/XRootDDefaults.cmake index 597bb496c7e..15416a3046d 100644 --- a/cmake/XRootDDefaults.cmake +++ b/cmake/XRootDDefaults.cmake @@ -10,7 +10,7 @@ if( "${CMAKE_BUILD_TYPE}" STREQUAL "" ) endif() if( NOT XRDCEPH_SUBMODULE ) - define_default( PLUGIN_VERSION 4 ) + define_default( PLUGIN_VERSION 5 ) endif() define_default( ENABLE_TESTS FALSE ) From 366ef4052aef2c841eb132182d1b431cf6065516 Mon Sep 17 00:00:00 2001 From: George Patargias Date: Wed, 16 Dec 2020 18:31:12 +0000 Subject: [PATCH 037/442] Changes in xrootd-ceph.spec.in --- packaging/rhel/xrootd-ceph.spec.in | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packaging/rhel/xrootd-ceph.spec.in b/packaging/rhel/xrootd-ceph.spec.in index 59d3039ef4d..03b1caccebd 100644 --- a/packaging/rhel/xrootd-ceph.spec.in +++ b/packaging/rhel/xrootd-ceph.spec.in @@ -64,8 +64,8 @@ BuildRequires: cmake BuildRequires: cppunit-devel %endif -BuildRequires: librados-devel = 2:14.2.11 -BuildRequires: libradosstriper-devel = 2:14.2.11 +BuildRequires: librados-devel = 2:14.2.15 +BuildRequires: libradosstriper-devel = 2:14.2.15 %if %{?_with_clang:1}%{!?_with_clang:0} BuildRequires: clang @@ -144,8 +144,8 @@ rm -rf $RPM_BUILD_ROOT #------------------------------------------------------------------------------- %files %defattr(-,root,root,-) -%{_libdir}/libXrdCeph-4.so -%{_libdir}/libXrdCephXattr-4.so +%{_libdir}/libXrdCeph-5.so +%{_libdir}/libXrdCephXattr-5.so %{_libdir}/libXrdCephPosix.so* %if %{?_with_tests:1}%{!?_with_tests:0} @@ -158,6 +158,9 @@ rm -rf $RPM_BUILD_ROOT # Changelog #------------------------------------------------------------------------------- %changelog +* Wed Dec 16 2020 George Patargias +- updated version for librados-devel and libradosstriper-devel to 14.2.15 following the recent upgrade on external Echo gateways +- fixed version in xrootd-ceph shared libraries * Mon Mar 02 2020 Michal Simon - fixed RPM dependencies * Thu Mar 08 2018 Michal Simon From 534d9c051b8aaf486ff6ee380f1d29bc42721960 Mon Sep 17 00:00:00 2001 From: Mattias Ellert Date: Thu, 25 Feb 2021 04:47:57 +0100 Subject: [PATCH 038/442] When building as a submodule, dependencies on libraries in the main package should use target names rather than paths. If paths are used, cmake does not handle dependencies between targets and may attempt to build them in the wrong order: gmake[2]: *** No rule to make target 'src/libXrdUtils.so', needed by 'src/XrdCeph/src/libXrdCephPosix.so.0.0.1'. Stop. --- cmake/FindXRootD.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/FindXRootD.cmake b/cmake/FindXRootD.cmake index 66d9a90b1ce..48fe966b858 100644 --- a/cmake/FindXRootD.cmake +++ b/cmake/FindXRootD.cmake @@ -7,7 +7,7 @@ if( XRDCEPH_SUBMODULE ) set( XROOTD_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/src ) - set( XROOTD_LIBRARIES ${CMAKE_BINARY_DIR}/src/libXrdUtils.so ) + set( XROOTD_LIBRARIES XrdUtils ) else() find_path( XROOTD_INCLUDE_DIRS XrdSfs/XrdSfsAio.hh HINTS From dd49fff147276bc6f2b0ca6795454c8922c2d070 Mon Sep 17 00:00:00 2001 From: snafus Date: Fri, 20 Aug 2021 12:57:49 +0100 Subject: [PATCH 039/442] Fix AIO read logic affecting end of file Fix the logic when doing AIO reads. rc returns the number of bytes read; which would be 0 for end of file, or, less than nbytes if a partial read was required. Previously, the number of bytes actually read was always returned, except if 0 bytes were read, in which case the requested number of bytes was returned. Now, the number of bytes actually read (including 0) is always returned into the callback. There remains a final 0 byte read, but this should stop excess data being returned. --- src/XrdCeph/XrdCephPosix.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdCeph/XrdCephPosix.cc b/src/XrdCeph/XrdCephPosix.cc index 3131a9c8f05..e451f279007 100644 --- a/src/XrdCeph/XrdCephPosix.cc +++ b/src/XrdCeph/XrdCephPosix.cc @@ -947,7 +947,7 @@ static void ceph_aio_read_complete(rados_completion_t c, void *arg) { XrdSysMutexHelper lock(fr->statsMutex); fr->asyncRdCompletionCount++; } - awa->callback(awa->aiop, rc == 0 ? awa->nbBytes : rc); + awa->callback(awa->aiop, rc ); delete(awa); } From bfe3ef6f9c1c1c1869701b0f3af0208a3ee5eff9 Mon Sep 17 00:00:00 2001 From: Mattias Ellert Date: Fri, 27 Aug 2021 15:01:03 +0200 Subject: [PATCH 040/442] Fixing spelling errors Mostly found by lintian (Debian packaging debugging tool). Reported as "spelling-error-in-binary" and "typo-in-manual-page" --- docs/ReleaseNotes.txt | 16 ++++++++-------- src/XrdCeph/XrdCephOss.cc | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/ReleaseNotes.txt b/docs/ReleaseNotes.txt index 39e020df605..c8a8ceb19dc 100644 --- a/docs/ReleaseNotes.txt +++ b/docs/ReleaseNotes.txt @@ -309,7 +309,7 @@ Version 4.4.0 + **Major bug fixes** * **[Posix]** Remove double unlock of a mutex. * **[Client]** Serialize security protocol manager to allow MT loads. - * **[Authentication/sss]** Fix dynamic id incompatability introduced in 4.0. + * **[Authentication/sss]** Fix dynamic id incompatibility introduced in 4.0. * **[XtdHttp]** Removed the deprecated cipher SSLv3, in favor of TLS1.2 * **[XrdCl]** Close file on open timeout. * **[XrdCl]** Differentiate between a handshake and an xrootd request/response @@ -474,7 +474,7 @@ Version 4.2.2 * **[XrdCl]** xrdfs correctly handles quotations (fixes the problem with ALICE token) + **Miscellaneous** - * Fixes #245 Provide compatability when cmake version is > 3.0. + * Fixes #245 Provide compatibility when cmake version is > 3.0. * Use atomics to manipulate unlocked variable pollNum. * Bugfix: release lock when a file is closed before the prefetch thread is started. Observed with xrdcp ran without -f option and an existing local file. Fixes #239. @@ -496,7 +496,7 @@ Version 4.2.1 + **Miscellaneous** * **[Client/Cl]** Make sure kXR_mkpath is set for classic copy jobs when the - destination is xrootd (backward compatability fix). + destination is xrootd (backward compatibility fix). ------------- Version 4.2.0 @@ -964,7 +964,7 @@ Version 3.3.2 + **Major bug fixes** * Fix the opaque information setting in xrdcp using -OD (issue #1) * Fix compilation on Solaris 11 (issue #7) - * Fix issues with semaphore locking during thread cancelation on + * Fix issues with semaphore locking during thread cancellation on MaxOSX (issue #10) * Solve locking problems in the built-in poller (issue #4) * Solve performance issues in the new client. Note: this actually @@ -1218,7 +1218,7 @@ Version 3.2.0 path (as opposed to a control path) in the monitoring record. + **Major bug fixes** - * Provide compatability for sprintf() implementations that check output + * Provide compatibility for sprintf() implementations that check output buffer length. This currently only affects gentoo and Ubuntu Linux. We place it in the "major" section as it causes run-time errors there. * Reinsert buffer size calculation that was mistakenly deleted. @@ -1271,7 +1271,7 @@ Version 3.1.1 + **Major bug fixes** * Fix various client threading issues. - * [bug #87880] Properly unpack the incomming vector read data. + * [bug #87880] Properly unpack the incoming vector read data. * Rework the handshake when making a parallel connection. Previous method caused a deadlock when parallel connections were requested (e.g. xrdcp). * Add HAVE_SENDFILE definition to cmake config. All post-cmake version of @@ -1371,7 +1371,7 @@ Version 3.1.0 meta-manager the minimum number of responses needed to satisfy a hold delay (i.e. fast redirect). * Accept XrdSecSSSKT envar as documented but also continue to support - XrdSecsssKT for backward compatability. + XrdSecsssKT for backward compatibility. * Allow servers to specify to the meta-manager what share of requests they are willing to handle. Add the 'cms.sched gsdflt' and 'cms.sched gshr' configuration directives to specify this. @@ -1389,7 +1389,7 @@ Version 3.1.0 * Finally implement adding authentication information to the user monitoring record (requested by Matevz Tadel, CMS). This adds a new generic option, auth, to the xrootd.monitor directive. It needs to be specified for the - authentication information to be added. This keeps backward compatability. + authentication information to be added. This keeps backward compatibility. * Add a new method, chksum, to the standard filesystem interface. * Integrate checksums into the logical filesystem layer implementation. See the ofs.ckslib directive on how to do non-default configuration. diff --git a/src/XrdCeph/XrdCephOss.cc b/src/XrdCeph/XrdCephOss.cc index 76bed8e744f..5a9ada4f51b 100644 --- a/src/XrdCeph/XrdCephOss.cc +++ b/src/XrdCeph/XrdCephOss.cc @@ -146,7 +146,7 @@ int XrdCephOss::Configure(const char *configfn, XrdSysError &Eroute) { } } - // Now check if any errors occured during file i/o + // Now check if any errors occurred during file i/o int retc = Config.LastError(); if (retc) { NoGo = Eroute.Emsg("Config", -retc, "read config file", From cacb941f1dcc981538b7e15d2276f9d980588753 Mon Sep 17 00:00:00 2001 From: Mattias Ellert Date: Sat, 4 Sep 2021 08:23:14 +0200 Subject: [PATCH 041/442] Fix compilation failure on 32 bit architectures: armel, armhf, i386, mipsel, m68k, sh4, x32 .../xrootd-5.3.1/src/XrdCeph/src/XrdCeph/XrdCephPosix.cc: In function 'ssize_t ceph_posix_pwrite(int, const void*, size_t, off64_t)': .../xrootd-5.3.1/src/XrdCeph/src/XrdCeph/XrdCephPosix.cc:803:97: error: no matching function for call to 'max(off64_t, uint64_t&)' 803 | if (offset + count) fr->maxOffsetWritten = std::max(offset + count - 1, fr->maxOffsetWritten); | ^ In file included from /usr/include/c++/10/memory:63, from .../xrootd-5.3.1/src/XrdCeph/src/XrdCeph/XrdCephPosix.cc:36: /usr/include/c++/10/bits/stl_algobase.h:254:5: note: candidate: 'template constexpr const _Tp& std::max(const _Tp&, const _Tp&)' 254 | max(const _Tp& __a, const _Tp& __b) | ^~~ /usr/include/c++/10/bits/stl_algobase.h:254:5: note: template argument deduction/substitution failed: .../xrootd-5.3.1/src/XrdCeph/src/XrdCeph/XrdCephPosix.cc:803:97: note: deduced conflicting types for parameter 'const _Tp' ('long long int' and 'uint64_t' {aka 'long long unsigned int'}) 803 | if (offset + count) fr->maxOffsetWritten = std::max(offset + count - 1, fr->maxOffsetWritten); | ^ In file included from /usr/include/c++/10/memory:63, from .../xrootd-5.3.1/src/XrdCeph/src/XrdCeph/XrdCephPosix.cc:36: /usr/include/c++/10/bits/stl_algobase.h:300:5: note: candidate: 'template constexpr const _Tp& std::max(const _Tp&, const _Tp&, _Compare)' 300 | max(const _Tp& __a, const _Tp& __b, _Compare __comp) | ^~~ /usr/include/c++/10/bits/stl_algobase.h:300:5: note: template argument deduction/substitution failed: .../xrootd-5.3.1/src/XrdCeph/src/XrdCeph/XrdCephPosix.cc:803:97: note: deduced conflicting types for parameter 'const _Tp' ('long long int' and 'uint64_t' {aka 'long long unsigned int'}) 803 | if (offset + count) fr->maxOffsetWritten = std::max(offset + count - 1, fr->maxOffsetWritten); | ^ .../xrootd-5.3.1/src/XrdCeph/src/XrdCeph/XrdCephPosix.cc: In function 'void ceph_aio_write_complete(rados_completion_t, void*)': .../xrootd-5.3.1/src/XrdCeph/src/XrdCeph/XrdCephPosix.cc:822:124: error: no matching function for call to 'max(uint64_t&, __off64_t)' 822 | fr->maxOffsetWritten = std::max(fr->maxOffsetWritten, awa->aiop->sfsAio.aio_offset + awa->aiop->sfsAio.aio_nbytes - 1); | ^ In file included from /usr/include/c++/10/memory:63, from .../xrootd-5.3.1/src/XrdCeph/src/XrdCeph/XrdCephPosix.cc:36: /usr/include/c++/10/bits/stl_algobase.h:254:5: note: candidate: 'template constexpr const _Tp& std::max(const _Tp&, const _Tp&)' 254 | max(const _Tp& __a, const _Tp& __b) | ^~~ /usr/include/c++/10/bits/stl_algobase.h:254:5: note: template argument deduction/substitution failed: .../xrootd-5.3.1/src/XrdCeph/src/XrdCeph/XrdCephPosix.cc:822:124: note: deduced conflicting types for parameter 'const _Tp' ('long long unsigned int' and '__off64_t' {aka 'long long int'}) 822 | fr->maxOffsetWritten = std::max(fr->maxOffsetWritten, awa->aiop->sfsAio.aio_offset + awa->aiop->sfsAio.aio_nbytes - 1); | ^ In file included from /usr/include/c++/10/memory:63, from .../xrootd-5.3.1/src/XrdCeph/src/XrdCeph/XrdCephPosix.cc:36: /usr/include/c++/10/bits/stl_algobase.h:300:5: note: candidate: 'template constexpr const _Tp& std::max(const _Tp&, const _Tp&, _Compare)' 300 | max(const _Tp& __a, const _Tp& __b, _Compare __comp) | ^~~ /usr/include/c++/10/bits/stl_algobase.h:300:5: note: template argument deduction/substitution failed: .../xrootd-5.3.1/src/XrdCeph/src/XrdCeph/XrdCephPosix.cc:822:124: note: deduced conflicting types for parameter 'const _Tp' ('long long unsigned int' and '__off64_t' {aka 'long long int'}) 822 | fr->maxOffsetWritten = std::max(fr->maxOffsetWritten, awa->aiop->sfsAio.aio_offset + awa->aiop->sfsAio.aio_nbytes - 1); | ^ --- src/XrdCeph/XrdCephPosix.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/XrdCeph/XrdCephPosix.cc b/src/XrdCeph/XrdCephPosix.cc index 3131a9c8f05..8fab4580ad2 100644 --- a/src/XrdCeph/XrdCephPosix.cc +++ b/src/XrdCeph/XrdCephPosix.cc @@ -800,7 +800,7 @@ ssize_t ceph_posix_pwrite(int fd, const void *buf, size_t count, off64_t offset) XrdSysMutexHelper lock(fr->statsMutex); fr->wrcount++; fr->bytesWritten+=count; - if (offset + count) fr->maxOffsetWritten = std::max(offset + count - 1, fr->maxOffsetWritten); + if (offset + count) fr->maxOffsetWritten = std::max(uint64_t(offset + count - 1), fr->maxOffsetWritten); return count; } else { return -EBADF; @@ -819,7 +819,7 @@ static void ceph_aio_write_complete(rados_completion_t c, void *arg) { fr->bytesAsyncWritePending -= awa->nbBytes; fr->bytesWritten += awa->nbBytes; if (awa->aiop->sfsAio.aio_nbytes) - fr->maxOffsetWritten = std::max(fr->maxOffsetWritten, awa->aiop->sfsAio.aio_offset + awa->aiop->sfsAio.aio_nbytes - 1); + fr->maxOffsetWritten = std::max(fr->maxOffsetWritten, uint64_t(awa->aiop->sfsAio.aio_offset + awa->aiop->sfsAio.aio_nbytes - 1)); ::timeval now; ::gettimeofday(&now, nullptr); double writeTime = 0.000001 * (now.tv_usec - awa->startTime.tv_usec) + 1.0 * (now.tv_sec - awa->startTime.tv_sec); From 73fa7967ea8f03f54dd9eb4ac690fb28a791ca16 Mon Sep 17 00:00:00 2001 From: Mattias Ellert Date: Sun, 28 Aug 2022 12:08:41 +0200 Subject: [PATCH 042/442] Address some warnings from Doxygen --- src/XrdCeph/XrdCephOssFile.hh | 2 +- src/XrdCeph/XrdCephXAttr.hh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/XrdCeph/XrdCephOssFile.hh b/src/XrdCeph/XrdCephOssFile.hh index 094ed84c6f7..6b0425ea951 100644 --- a/src/XrdCeph/XrdCephOssFile.hh +++ b/src/XrdCeph/XrdCephOssFile.hh @@ -59,7 +59,7 @@ public: virtual int Close(long long *retsz=0); virtual ssize_t Read(off_t offset, size_t blen); virtual ssize_t Read(void *buff, off_t offset, size_t blen); - virtual int Read(XrdSfsAio *aoip); + virtual int Read(XrdSfsAio *aiop); virtual ssize_t ReadRaw(void *, off_t, size_t); virtual int Fstat(struct stat *buff); virtual ssize_t Write(const void *buff, off_t offset, size_t blen); diff --git a/src/XrdCeph/XrdCephXAttr.hh b/src/XrdCeph/XrdCephXAttr.hh index 0dfb9f5ec74..ed6f4d39f21 100644 --- a/src/XrdCeph/XrdCephXAttr.hh +++ b/src/XrdCeph/XrdCephXAttr.hh @@ -136,7 +136,7 @@ public: //! attribute value which may contain binary data. //! @param Path -> Path of the file whose attribute is to be set. //! @param fd -> If >=0 is the file descriptor of the opened subject file. - //! @param isnew When !0 then the attribute must not exist (i.e. new). + //! @param isNew When !0 then the attribute must not exist (i.e. new). //! Otherwise, if it does exist, the value is replaced. In //! either case, if it does not exist it should be created. //! From 9d52adea782d083e3b26d347f2467efe7005d9b6 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 20 Mar 2023 14:22:28 +0100 Subject: [PATCH 043/442] [CI] Fix cmake configuration command on Alpine build The new FindPython.cmake module expects Python_EXECUTABLE variable instead of PYTHON_EXECUTABLE. More information at https://cmake.org/cmake/help/latest/module/FindPython.html --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index abd2a4ba256..2706aa4e3d7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -236,7 +236,7 @@ jobs: -DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DCMAKE_INSTALL_PREFIX=/usr \ -DCMAKE_INSTALL_LIBDIR=lib \ - -DPYTHON_EXECUTABLE=$(command -v python3) \ + -DPython_EXECUTABLE=$(command -v python3) \ -DFORCE_ENABLED=ON \ -DENABLE_HTTP=OFF \ -DENABLE_TESTS=ON \ From 2572e832bb10cceb0faaaa4ceca813c5b7316499 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Mon, 3 Apr 2023 18:16:04 -0700 Subject: [PATCH 044/442] [HTTP] Initialize SecEntity.addrInfo to vaoid SEGV; Fixes #1986 --- src/XrdHttp/XrdHttpProtocol.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/XrdHttp/XrdHttpProtocol.cc b/src/XrdHttp/XrdHttpProtocol.cc index feef9d6c447..8dde9de87ad 100644 --- a/src/XrdHttp/XrdHttpProtocol.cc +++ b/src/XrdHttp/XrdHttpProtocol.cc @@ -280,6 +280,7 @@ XrdProtocol *XrdHttpProtocol::Match(XrdLink *lp) { // that is is https without invoking TLS on the actual link. Eventually, // we should just use the link's TLS native implementation. // + SecEntity.addrInfo = lp->AddrInfo(); XrdNetAddr *netP = const_cast(lp->NetAddr()); netP->SetDialect("https"); netP->SetTLS(true); From 79a870b360dbc9c8e84dd354a3603a5632da6dd4 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Mon, 3 Apr 2023 18:18:25 -0700 Subject: [PATCH 045/442] Update notes on http SecEntity fix. --- docs/PreReleaseNotes.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/PreReleaseNotes.txt b/docs/PreReleaseNotes.txt index 33572e5648a..cf34fb294a2 100644 --- a/docs/PreReleaseNotes.txt +++ b/docs/PreReleaseNotes.txt @@ -14,6 +14,8 @@ Prerelease Notes **Commit: 8a6d7c0 + **Major bug fixes** + **[HTTP]** Initialize SecEntity.addrInfo to vaoid SEGV; Fixes #1986 + **Commit: 2572e83 + **Minor bug fixes** From 4626bc2b7630d192e16061a3c43bca1324a66e60 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Tue, 4 Apr 2023 17:22:50 -0700 Subject: [PATCH 046/442] [Server] Use correct format to print size_t; fixes #1989 --- src/XrdXrootd/XrdXrootdTpcMon.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdXrootd/XrdXrootdTpcMon.cc b/src/XrdXrootd/XrdXrootdTpcMon.cc index fe41583fc89..e95ec709795 100644 --- a/src/XrdXrootd/XrdXrootdTpcMon.cc +++ b/src/XrdXrootd/XrdXrootdTpcMon.cc @@ -44,7 +44,7 @@ namespace const char *json_fmt = "{\"TPC\":\"%s\",\"Client\":\"%s\"," "\"Xeq\":{\"Beg\":\"%s\",\"End\":\"%s\",\"RC\":%d,\"Strm\":%u,\"Type\":\"%s\"," "\"IPv\":%c}," -"\"Src\":\"%s\",\"Dst\":\"%s\",\"Size\":%d}"; +"\"Src\":\"%s\",\"Dst\":\"%s\",\"Size\":%zu}"; const char *urlFMT = ""; From f94b12334cfb90aaee08f2738ddb12de0f05f547 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Tue, 4 Apr 2023 17:24:59 -0700 Subject: [PATCH 047/442] Update notes on tpc g-stream format patch. --- docs/PreReleaseNotes.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/PreReleaseNotes.txt b/docs/PreReleaseNotes.txt index cf34fb294a2..c1f882e1d38 100644 --- a/docs/PreReleaseNotes.txt +++ b/docs/PreReleaseNotes.txt @@ -18,5 +18,7 @@ Prerelease Notes **Commit: 2572e83 + **Minor bug fixes** + **[Server]** Use correct format to print size_t; fixes #1989 + **Commit: 4626bc2 + **Miscellaneous** From b4c894d6087ca001dc6b5676f8ff56630b7ae053 Mon Sep 17 00:00:00 2001 From: Brian Bockelman Date: Sat, 25 Mar 2023 06:53:24 -0500 Subject: [PATCH 048/442] Implement ability to have the token username as a separate claim Some token issuers, such as LIGOs, put the desired username in a claim besides the `sub` claim. This provides flexibility so they can use the `uid` claim for mapping purposes instead of `sub`. --- src/XrdSciTokens/README.md | 8 ++++ src/XrdSciTokens/XrdSciTokensAccess.cc | 60 +++++++++++++++++--------- 2 files changed, 48 insertions(+), 20 deletions(-) diff --git a/src/XrdSciTokens/README.md b/src/XrdSciTokens/README.md index 87d4948ac51..b76a8a699cb 100644 --- a/src/XrdSciTokens/README.md +++ b/src/XrdSciTokens/README.md @@ -104,6 +104,9 @@ are: - `default_user` (optional): If set, then all authorized operations will be done under the provided username when interacting with the filesystem. This is useful in the case where the administrator desires that all files owned by an issuer should be mapped to a particular Unix user account at the site. + - `username_claim` (optional): Not all issuers put the desired username in the `sub` claim (sometimes the subject is + set to a de-identified value). To use an alternate claim as the username, such as `uid`, set this to the desired + claim name. If set, it overrides `map_subject` and `default_user`. - `name_mapfile` (options): If set, then the referenced file is parsed as a JSON object and the specified mappings are applied to the username inside the XRootD framework. See below for more information on the mapfile. @@ -124,12 +127,14 @@ Mapfile format The file specified by the `name_mapfile` attribute can be used to perform identity mapping for a given issuer. It must parse as valid JSON and may look like this: +``` [ {"sub": "bbockelm", "path": "/home/bbockelm", "result": "bbockelm"}, {"group": "/cms/prod", "path": "/cms", "result": "cmsprod" comment="Added 1 Sept 2020"}, {"group": "/cms", "result": "cmsuser"}, {"group": "/cms", "result": "atlas" ignore="Only for testing"} ] +``` That is, we have a JSON list of objects; each object is interpreted as a rule. For an incoming request to match a rule, each present attribute must evaluate to true. In this case, the value of the `result` key is populated as the username @@ -137,6 +142,9 @@ in the XRootD internal credential. The enumerated keys are: - `sub`: True if the `sub` claim in the token matches the value in the mapfile (case-sensitive comparison). + - `username`: True if the username in the token (the claim specifying the username is configurable, controlled by the + `username_claim` variable in the issuer config; default is `sub`) matches the value in the mapfile (case-sensitive + comparison). - `path`: True iff the value of the attribute matches (case-sensitive) the prefix of the (normalized) requested path. For example, if the issuer's base path is `/home`, the operation is accessing `/home/bbockelm/foo`, and the path in the rule is `/bbockelm`, then this attribute evaluates to `true`. Note the path value and the requested path must diff --git a/src/XrdSciTokens/XrdSciTokensAccess.cc b/src/XrdSciTokens/XrdSciTokensAccess.cc index 22699bd67d3..097c10b097c 100644 --- a/src/XrdSciTokens/XrdSciTokensAccess.cc +++ b/src/XrdSciTokens/XrdSciTokensAccess.cc @@ -218,23 +218,28 @@ void ParseCanonicalPaths(const std::string &path, std::vector &resu struct MapRule { MapRule(const std::string &sub, + const std::string &username, const std::string &path_prefix, const std::string &group, - const std::string &name) + const std::string &result) : m_sub(sub), + m_username(username), m_path_prefix(path_prefix), m_group(group), - m_name(name) + m_result(result) { - //std::cerr << "Making a rule {sub=" << sub << ", path=" << path_prefix << ", group=" << group << ", result=" << name << "}" << std::endl; + //std::cerr << "Making a rule {sub=" << sub << ", username=" << username << ", path=" << path_prefix << ", group=" << group << ", result=" << name << "}" << std::endl; } - const std::string match(const std::string sub, - const std::string req_path, - const std::vector groups) const + const std::string match(const std::string &sub, + const std::string &username, + const std::string &req_path, + const std::vector &groups) const { if (!m_sub.empty() && sub != m_sub) {return "";} + if (!m_username.empty() && username != username) {return "";} + if (!m_path_prefix.empty() && strncmp(req_path.c_str(), m_path_prefix.c_str(), m_path_prefix.size())) { @@ -244,17 +249,18 @@ struct MapRule if (!m_group.empty()) { for (const auto &group : groups) { if (group == m_group) - return m_name; + return m_result; } return ""; } - return m_name; + return m_result; } std::string m_sub; + std::string m_username; std::string m_path_prefix; std::string m_group; - std::string m_name; + std::string m_result; }; struct IssuerConfig @@ -265,11 +271,13 @@ struct IssuerConfig const std::vector &restricted_paths, bool map_subject, const std::string &default_user, + const std::string &username_claim, const std::vector rules) - : m_map_subject(map_subject), + : m_map_subject(map_subject || !username_claim.empty()), m_name(issuer_name), m_url(issuer_url), m_default_user(default_user), + m_username_claim(username_claim), m_base_paths(base_paths), m_restricted_paths(restricted_paths), m_map_rules(rules) @@ -279,6 +287,7 @@ struct IssuerConfig const std::string m_name; const std::string m_url; const std::string m_default_user; + const std::string m_username_claim; const std::vector m_base_paths; const std::vector m_restricted_paths; const std::vector m_map_rules; @@ -358,7 +367,7 @@ class XrdAccRules std::string get_username(const std::string &req_path) const { for (const auto &rule : m_map_rules) { - std::string name = rule.match(m_token_subject, req_path, m_groups); + std::string name = rule.match(m_token_subject, m_username, req_path, m_groups); if (!name.empty()) { return name; } @@ -614,7 +623,7 @@ class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper } virtual bool Validate(const char *token, std::string &emsg, long long *expT, - XrdSecEntity *Entity) + XrdSecEntity *Entity) override { // Just check if the token is valid, no scope checking @@ -811,10 +820,18 @@ class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper token_subject = std::string(value); free(value); - std::string tmp_username; - if (config.m_map_subject) { - tmp_username = token_subject; - } else { + auto tmp_username = token_subject; + if (!config.m_username_claim.empty()) { + if (scitoken_get_claim_string(token, config.m_username_claim.c_str(), &value, &err_msg)) { + pthread_rwlock_unlock(&m_config_lock); + m_log.Log(LogMask::Warning, "GenerateAcls", "Failed to get token username:", err_msg); + free(err_msg); + scitoken_destroy(token); + return false; + } + tmp_username = std::string(value); + free(value); + } else if (!config.m_map_subject) { tmp_username = config.m_default_user; } @@ -972,6 +989,7 @@ class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper std::string path; std::string group; std::string sub; + std::string username; std::string result; bool ignore = false; for (const auto &entry : rule.get()) { @@ -989,8 +1007,9 @@ class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper } else if (entry.first == "sub") { sub = entry.second.get(); - } - else if (entry.first == "path") { + } else if (entry.first == "username") { + username = entry.second.get(); + } else if (entry.first == "path") { std::string norm_path; if (!MakeCanonical(entry.second.get(), norm_path)) { ss << "In mapfile " << filename << " encountered a path " << entry.second.get() @@ -1011,7 +1030,7 @@ class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper m_log.Log(LogMask::Error, "ParseMapfile", ss.str().c_str()); return false; } - rules.emplace_back(sub, path, group, result); + rules.emplace_back(sub, username, path, group, result); } return true; @@ -1158,11 +1177,12 @@ class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper auto default_user = reader.Get(section, "default_user", ""); auto map_subject = reader.GetBoolean(section, "map_subject", false); + auto username_claim = reader.Get(section, "username_claim", ""); issuers.emplace(std::piecewise_construct, std::forward_as_tuple(issuer), std::forward_as_tuple(name, issuer, base_paths, restricted_paths, - map_subject, default_user, rules)); + map_subject, default_user, username_claim, rules)); } if (issuers.empty()) { From 64679785b7fcf86fd1571ff1dce7f4f053c6225e Mon Sep 17 00:00:00 2001 From: Brian Bockelman Date: Sat, 25 Mar 2023 06:51:41 -0500 Subject: [PATCH 049/442] Add notes in README about the scitokens.trace option --- src/XrdSciTokens/README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/XrdSciTokens/README.md b/src/XrdSciTokens/README.md index b76a8a699cb..e26199b2c91 100644 --- a/src/XrdSciTokens/README.md +++ b/src/XrdSciTokens/README.md @@ -35,6 +35,14 @@ ofs.authlib [++] libXrdAccSciTokens.so config=/path/to/config/file If not given, it defaults to `/etc/xrootd/scitokens.cfg`. Restart the service for new settings to take effect. +The SciTokens plugin has multiple levels of logging output. To manage these, set: + +``` +scitokens.trace LEVEL_NAME +``` + +Valid levels include `error`, `warning`, `info`, `debug`, and `all`. + SciTokens Configuration File ---------------------------- From b5d69d9deec32abfbb9a4221bdabc4aa83202626 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 23 Mar 2023 14:59:42 +0100 Subject: [PATCH 050/442] Add scripts for running tests based on docker containers --- .gitignore | 2 + docker/.dockerignore | 1 + docker/build/Dockerfile.alma8 | 46 +++++++++ docker/build/Dockerfile.alma9 | 46 +++++++++ docker/build/Dockerfile.centos7 | 43 ++++++++ docker/builds/DockerfileCentos7 | 14 --- docker/builds/DockerfileCentos8 | 16 --- docker/builds/centos7_buildenv.sh | 4 - docker/builds/centos8_buildenv.sh | 4 - docker/config/xrootd-clustered.cfg | 59 +++++++++++ docker/config/xrootd-srv2.cfg | 4 + docker/scripts/setup.sh | 68 ++++++++++++ docker/xrd-docker | 160 +++++++++++++++++++++++++++++ 13 files changed, 429 insertions(+), 38 deletions(-) create mode 100644 docker/.dockerignore create mode 100644 docker/build/Dockerfile.alma8 create mode 100644 docker/build/Dockerfile.alma9 create mode 100644 docker/build/Dockerfile.centos7 delete mode 100644 docker/builds/DockerfileCentos7 delete mode 100644 docker/builds/DockerfileCentos8 delete mode 100755 docker/builds/centos7_buildenv.sh delete mode 100755 docker/builds/centos8_buildenv.sh create mode 100644 docker/config/xrootd-clustered.cfg create mode 100644 docker/config/xrootd-srv2.cfg create mode 100755 docker/scripts/setup.sh create mode 100755 docker/xrd-docker diff --git a/.gitignore b/.gitignore index 15dff770410..6959934d9b6 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,8 @@ config.status config.sub configure depcomp +docker/data +docker/xrootd.tar.gz install-sh lib/ libtool diff --git a/docker/.dockerignore b/docker/.dockerignore new file mode 100644 index 00000000000..8fce603003c --- /dev/null +++ b/docker/.dockerignore @@ -0,0 +1 @@ +data/ diff --git a/docker/build/Dockerfile.alma8 b/docker/build/Dockerfile.alma8 new file mode 100644 index 00000000000..83ece6b5b8b --- /dev/null +++ b/docker/build/Dockerfile.alma8 @@ -0,0 +1,46 @@ +FROM almalinux:8 + +# Install tools necessary for RPM development +RUN dnf install -y rpm-build rpmdevtools dnf-plugins-core epel-release \ + && dnf config-manager --set-enabled powertools + +# Create directory tree for building RPMs +RUN rpmdev-setuptree + +WORKDIR /root + +# XRootD source tarball must be created in the +# current directory in order to build this image +COPY xrootd.tar.gz rpmbuild/SOURCES + +# Extract spec file to build RPMs +RUN tar xzf rpmbuild/SOURCES/xrootd.tar.gz --strip-components=1 xrootd/xrootd.spec + +# Install build dependencies with dnf +RUN dnf builddep -y -D 'dist .el8' --define '_with_isal 1' --define '_with_python3 1' \ + --define '_with_scitokens 1' --define '_with_tests 1' --define '_with_xrdclhttp 1' \ + --define '_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm' xrootd.spec + +# Build RPMS for XRootD +RUN rpmbuild -bb -D 'dist .el8' --define '_with_isal 1' --define '_with_python3 1' \ + --define '_with_scitokens 1' --define '_with_tests 1' --define '_with_xrdclhttp 1' \ + --define '_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm' xrootd.spec + +# Second stage, build test image +FROM almalinux:8 + +COPY --from=0 /root/rpmbuild/RPMS/* /tmp/ + +# Install RPMS for XRootD and cleanup unneeded files +RUN rm /tmp/*-{devel,doc}* \ + && dnf install -y dnf-plugins-core epel-release \ + && dnf config-manager --set-enabled powertools \ + && dnf install -y /tmp/*.rpm \ + && dnf autoremove -y \ + && rm -rf /tmp/* + +# Copy configuration files +COPY config/*.cfg /etc/xrootd/ +COPY scripts/setup.sh /bin/setup.sh + +CMD [ "/sbin/init" ] diff --git a/docker/build/Dockerfile.alma9 b/docker/build/Dockerfile.alma9 new file mode 100644 index 00000000000..ec7ea70494c --- /dev/null +++ b/docker/build/Dockerfile.alma9 @@ -0,0 +1,46 @@ +FROM almalinux:9 + +# Install tools necessary for RPM development +RUN dnf install -y rpm-build rpmdevtools dnf-plugins-core epel-release \ + && dnf config-manager --set-enabled crb + +# Create directory tree for building RPMs +RUN rpmdev-setuptree + +WORKDIR /root + +# XRootD source tarball must be created in the +# current directory in order to build this image +COPY xrootd.tar.gz rpmbuild/SOURCES + +# Extract spec file to build RPMs +RUN tar xzf rpmbuild/SOURCES/xrootd.tar.gz --strip-components=1 xrootd/xrootd.spec + +# Install build dependencies with dnf +RUN dnf builddep -y -D 'dist .el9' --define '_with_isal 1' --define '_with_python3 1' \ + --define '_with_scitokens 1' --define '_with_tests 1' --define '_with_xrdclhttp 1' \ + --define '_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm' xrootd.spec + +# Build RPMS for XRootD +RUN rpmbuild -bb -D 'dist .el9' --define '_with_isal 1' --define '_with_python3 1' \ + --define '_with_scitokens 1' --define '_with_tests 1' --define '_with_xrdclhttp 1' \ + --define '_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm' xrootd.spec + +# Second stage, build test image +FROM almalinux:9 + +COPY --from=0 /root/rpmbuild/RPMS/* /tmp/ + +# Install RPMS for XRootD and cleanup unneeded files +RUN rm /tmp/*-{devel,doc}* \ + && dnf install -y dnf-plugins-core epel-release \ + && dnf config-manager --set-enabled crb \ + && dnf install -y /tmp/*.rpm \ + && dnf autoremove -y \ + && rm -rf /tmp/* + +# Copy configuration files +COPY config/*.cfg /etc/xrootd/ +COPY scripts/setup.sh /bin/setup.sh + +CMD [ "/sbin/init" ] diff --git a/docker/build/Dockerfile.centos7 b/docker/build/Dockerfile.centos7 new file mode 100644 index 00000000000..f0c4f01dbe1 --- /dev/null +++ b/docker/build/Dockerfile.centos7 @@ -0,0 +1,43 @@ +FROM cern/cc7-base + +# Install tools necessary for RPM development +RUN yum install -y rpm-build rpmdevtools yum-utils + +# Create directory tree for building RPMs +RUN rpmdev-setuptree + +WORKDIR /root + +# XRootD source tarball must be created in the +# current directory in order to build this image +COPY xrootd.tar.gz rpmbuild/SOURCES + +# Extract spec file to build RPMs +RUN tar xzf rpmbuild/SOURCES/xrootd.tar.gz --strip-components=1 xrootd/xrootd.spec + +# Install build dependencies with yum +RUN yum-builddep -y --define '_with_isal 1' --define '_with_python3 1' \ + --define '_with_scitokens 1' --define '_with_tests 1' --define '_with_xrdclhttp 1' \ + --define '_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm' xrootd.spec + +# Build RPMS for XRootD +RUN rpmbuild -bb --define '_with_isal 1' --define '_with_python3 1' \ + --define '_with_scitokens 1' --define '_with_tests 1' --define '_with_xrdclhttp 1' \ + --define '_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm' xrootd.spec + +# Second stage, build test image +FROM cern/cc7-base + +COPY --from=0 /root/rpmbuild/RPMS/* /tmp/ + +# Install RPMS for XRootD and cleanup unneeded files +RUN rm /tmp/*-{devel,doc}* \ + && yum install -y wget /tmp/*.rpm \ + && yum autoremove -y \ + && rm -rf /tmp/* + +# Copy configuration files +COPY config/*.cfg /etc/xrootd/ +COPY scripts/setup.sh /bin/setup.sh + +CMD [ "/sbin/init" ] diff --git a/docker/builds/DockerfileCentos7 b/docker/builds/DockerfileCentos7 deleted file mode 100644 index af530bd9bde..00000000000 --- a/docker/builds/DockerfileCentos7 +++ /dev/null @@ -1,14 +0,0 @@ -FROM centos:7 -MAINTAINER Michal Simon , CERN, 2015 - -USER root -RUN yum install -y epel-release -RUN yum install -y gcc-c++ rpm-build which git python-srpm-macros centos-release-scl vim -RUN git clone https://github.com/xrootd/xrootd -WORKDIR /xrootd/packaging -RUN ./makesrpm.sh --define "_with_python3 1" --define "_with_tests 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" --define "_with_isal 1" -RUN yum-builddep -y *.src.rpm -RUN rm -f *src.rpm -WORKDIR /xrootd/build -ENTRYPOINT scl enable devtoolset-7 bash - diff --git a/docker/builds/DockerfileCentos8 b/docker/builds/DockerfileCentos8 deleted file mode 100644 index c5b03534987..00000000000 --- a/docker/builds/DockerfileCentos8 +++ /dev/null @@ -1,16 +0,0 @@ -FROM centos:8 -MAINTAINER Michal Simon , CERN, 2015 - -USER root -RUN dnf install -y epel-release -RUN dnf install -y gcc-c++ rpm-build which git python-srpm-macros vim dnf-plugins-core -RUN dnf config-manager --set-enabled powertools -RUN git clone https://github.com/xrootd/xrootd -WORKDIR /xrootd/packaging -RUN ./makesrpm.sh --define "_with_python3 1" --define "_with_tests 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" --define "_with_isal 1" -RUN dnf builddep -y *.src.rpm -RUN dnf -y update libarchive -RUN rm -f *src.rpm -WORKDIR /xrootd/build -ENTRYPOINT bash - diff --git a/docker/builds/centos7_buildenv.sh b/docker/builds/centos7_buildenv.sh deleted file mode 100755 index 79d5705c667..00000000000 --- a/docker/builds/centos7_buildenv.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -docker build -t xrootd/centos7/build - < DockerfileCentos7 -docker run -it --rm xrootd/centos7/build diff --git a/docker/builds/centos8_buildenv.sh b/docker/builds/centos8_buildenv.sh deleted file mode 100755 index 28dc84d8370..00000000000 --- a/docker/builds/centos8_buildenv.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -docker build -t xrootd/centos8/build - < DockerfileCentos8 -docker run -it --rm xrootd/centos8/build diff --git a/docker/config/xrootd-clustered.cfg b/docker/config/xrootd-clustered.cfg new file mode 100644 index 00000000000..f6228b28a99 --- /dev/null +++ b/docker/config/xrootd-clustered.cfg @@ -0,0 +1,59 @@ +all.export /data +cms.delay startup 10 +cms.space linger 0 recalc 15 min 2% 1g 5% 2g + +xrootd.diglib * /etc/xrootd/dbgauth + +ofs.ckslib zcrc32 /usr/lib64/libXrdCksCalczcrc32.so +xrootd.chksum zcrc32 + +all.adminpath /var/spool/xrootd +all.pidpath /run/xrootd + +xrd.port 1094 if exec xrootd +xrd.port 2094 if exec cmsd + +if metaman+ +all.role meta manager +all.manager meta metaman 2094 + +else if man1+ +all.role manager +all.manager meta metaman 2094 +all.manager man1 2094 + +else if man2+ +all.role manager +all.manager meta metaman 2094 +all.manager man2 2094 + +else if srv1+ +all.role server +all.manager man1 2094 +ofs.tpc ttl 60 60 xfr 9 pgm /usr/bin/xrdcp --server +ofs.chkpnt enable + +else if srv2+ +all.role server +all.manager man1 2094 +ofs.tpc ttl 60 60 xfr 9 pgm /usr/bin/xrdcp --server +ofs.chkpnt enable + +else if srv3+ +all.role server +all.manager man2 2094 +ofs.tpc ttl 60 60 xfr 9 pgm /usr/bin/xrdcp --server +ofs.chkpnt enable + +else if srv4+ +all.role server +all.manager man2 2094 +ofs.tpc ttl 60 60 xfr 9 pgm /usr/bin/xrdcp --server +ofs.chkpnt enable +fi + +if defined ?~TEST_SIGNING +xrootd.seclib /usr/lib64/libXrdSec-4.so +sec.protocol unix +sec.level compatible force +fi diff --git a/docker/config/xrootd-srv2.cfg b/docker/config/xrootd-srv2.cfg new file mode 100644 index 00000000000..57c022faa20 --- /dev/null +++ b/docker/config/xrootd-srv2.cfg @@ -0,0 +1,4 @@ +all.export /data +all.adminpath /var/spool/xrootd +all.pidpath /var/run/xrootd +xrd.port 1099 diff --git a/docker/scripts/setup.sh b/docker/scripts/setup.sh new file mode 100755 index 00000000000..0da39070d3d --- /dev/null +++ b/docker/scripts/setup.sh @@ -0,0 +1,68 @@ +#!/bin/bash + +# set the TEST_SIGNING +export TEST_SIGNING=1 + +if [[ ${HOSTNAME} = 'metaman' ]] ; then + # download the a test file for upload tests + mkdir -p /data + cp /downloads/a048e67f-4397-4bb8-85eb-8d7e40d90763.dat /data/testFile.dat + chown -R xrootd:xrootd /data +fi + +# the data nodes +datanodes=('srv1' 'srv2' 'srv3' 'srv4') + +# create 'bigdir' in each of the data nodes +if [[ ${datanodes[*]} =~ ${HOSTNAME} ]] ; then + mkdir -p /data/bigdir + cd /data/bigdir + for i in `seq 10000`; do touch `uuidgen`.dat; done + cd - >/dev/null +fi + +# download the test files for 'srv1' +if [[ ${HOSTNAME} = 'srv1' ]] ; then + cp /downloads/a048e67f-4397-4bb8-85eb-8d7e40d90763.dat /data + cp /downloads/b3d40b3f-1d15-4ad3-8cb5-a7516acb2bab.dat /data + cp /downloads/b74d025e-06d6-43e8-91e1-a862feb03c84.dat /data + cp /downloads/cb4aacf1-6f28-42f2-b68a-90a73460f424.dat /data + cp /downloads/cef4d954-936f-4945-ae49-60ec715b986e.dat /data + mkdir /data/metalink + cp /downloads/input*.meta* /data/metalink/ + cp /downloads/ml*.meta* /data/metalink/ +fi + +# download the test files for 'srv2' and add another instance on 1099 +if [[ ${HOSTNAME} = 'srv2' ]] ; then + cp /downloads/1db882c8-8cd6-4df1-941f-ce669bad3458.dat /data + cp /downloads/3c9a9dd8-bc75-422c-b12c-f00604486cc1.dat /data + cp /downloads/7235b5d1-cede-4700-a8f9-596506b4cc38.dat /data + cp /downloads/7e480547-fe1a-4eaf-a210-0f3927751a43.dat /data + cp /downloads/89120cec-5244-444c-9313-703e4bee72de.dat /data +fi + +# download the test files for 'srv3' +if [[ ${HOSTNAME} = 'srv3' ]] ; then + cp /downloads/1db882c8-8cd6-4df1-941f-ce669bad3458.dat /data + cp /downloads/3c9a9dd8-bc75-422c-b12c-f00604486cc1.dat /data + cp /downloads/89120cec-5244-444c-9313-703e4bee72de.dat /data + cp /downloads/b74d025e-06d6-43e8-91e1-a862feb03c84.dat /data + cp /downloads/cef4d954-936f-4945-ae49-60ec715b986e.dat /data +fi + +# download the test files for 'srv4' +if [[ ${HOSTNAME} = 'srv4' ]] ; then + cp /downloads/1db882c8-8cd6-4df1-941f-ce669bad3458.dat /data + cp /downloads/7e480547-fe1a-4eaf-a210-0f3927751a43.dat /data + cp /downloads/89120cec-5244-444c-9313-703e4bee72de.dat /data + cp /downloads/b74d025e-06d6-43e8-91e1-a862feb03c84.dat /data + cp /downloads/cef4d954-936f-4945-ae49-60ec715b986e.dat /data + cp /downloads/data.zip /data + cp /downloads/large.zip /data +fi + +# make sure the test files and directories are owned by 'xrootd' user +if [[ ${datanodes[*]} =~ ${HOSTNAME} ]] ; then + chown -R xrootd:xrootd /data +fi diff --git a/docker/xrd-docker b/docker/xrd-docker new file mode 100755 index 00000000000..a6c1b0c6d2f --- /dev/null +++ b/docker/xrd-docker @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +trap 'exit 1' TERM KILL INT QUIT ABRT + +build() { + OS=${1:-centos7} + [[ -f xrootd.tar.gz ]] || package + [[ -f build/Dockerfile.${OS} ]] || die "unknwon OS: $OS" + docker build -f build/Dockerfile.${OS} -t xrootd -t xrootd:${OS} . +} + +clean() { + rm -f xrootd.tar.gz + docker rm -f metaman man1 man2 srv1 srv2 srv3 srv4 + docker system prune -f +} + +die() { + echo "$(basename $BASH_ARGV0): error: $@" + exit 1 +} + +fetch() { + [[ -d data/tmp ]] || mkdir -p data/tmp + + pushd data >/dev/null + + if ! command -v curl >/dev/null; then + die "curl command not availble, cannot download data" + fi + + TEST_FILES=( + {data,large}.zip + input{1..5}.meta{4,link} + mlFileTest{1..4}.meta4 + mlTpcTest.meta4 + mlZipTest.meta4 + tmp/{E.coli,bible.txt,world192.txt} + 1db882c8-8cd6-4df1-941f-ce669bad3458.dat + 3c9a9dd8-bc75-422c-b12c-f00604486cc1.dat + 7e480547-fe1a-4eaf-a210-0f3927751a43.dat + 7235b5d1-cede-4700-a8f9-596506b4cc38.dat + 89120cec-5244-444c-9313-703e4bee72de.dat + a048e67f-4397-4bb8-85eb-8d7e40d90763.dat + b3d40b3f-1d15-4ad3-8cb5-a7516acb2bab.dat + b74d025e-06d6-43e8-91e1-a862feb03c84.dat + cb4aacf1-6f28-42f2-b68a-90a73460f424.dat + cef4d954-936f-4945-ae49-60ec715b986e.dat + ) + + for FILE in ${TEST_FILES[@]}; do + if [[ ! -f ${FILE} ]]; then + echo ">>> Downloading ${FILE}" + curl -L http://xrootd.cern.ch/tests/test-files/${FILE} -o ${FILE} + fi + done +} + +package() { + REPODIR=$(git rev-parse --show-toplevel) + VERSION=$(git describe ${1:-HEAD} | tr '-' '.' | cut -d. -f-4) + + pushd ${REPODIR} >/dev/null + echo Creating tarball for XRootD ${VERSION} + genversion.sh --version ${VERSION} + sed -e "s/__VERSION__/${VERSION}/" -e 's/__RELEASE__/1/' \ + packaging/rhel/xrootd.spec.in >| xrootd.spec + git archive --prefix=xrootd/src/ --add-file src/XrdVersion.hh \ + --prefix=xrootd/ --add-file xrootd.spec -o xrootd.tar.gz ${1:-HEAD} + rm xrootd.spec + popd >/dev/null + mv ${REPODIR}/xrootd.tar.gz . +} + +run() { + TEST_SUITE=( + Utils + Socket + Poller + PostMaster + FileSystem + File + FileCopy + Threading + LocalFileHandler + Workflow + ) + + for T in ${@:-${TEST_SUITE[@]}}; do + docker exec -it metaman test-runner /usr/lib64/libXrdClTests.so "All Tests/${T}Test" + done + + # XrdEc tests are not working + # docker exec -it metaman test-runner /usr/lib64/libXrdEcTests.so 'All Tests' +} + +search() { + for container in metaman srv{1..4}; do + echo ${container}: + docker exec ${container} find /data $@ + echo + done +} + +setup() { + docker network create xrootd + + for container in metaman man{1,2} srv{1..4}; do + echo ">>> Start ${container} container" + [[ ${container} =~ ^man* ]] && ALIAS=--net-alias=manalias || ALIAS='' + docker run -d --privileged -e "container=docker" \ + -v ${PWD}/data:/downloads:z --name ${container} -h ${container} \ + --net=xrootd --net-alias=multiip ${ALIAS} --net-alias=${container} \ + xrootd:${1:-latest} /sbin/init + echo ">>> Setup ${container}" + docker exec ${container} setup.sh + echo ">>> Start xrootd and cmsd in ${container}" + docker exec ${container} systemctl start xrootd@clustered + docker exec ${container} systemctl start cmsd@clustered + echo + done + + echo ">>> Start xrootd@srv2 in srv2" + docker exec srv2 systemctl start xrootd@srv2 + + docker ps +} + +shell() { + docker exec -it ${1:-metaman} /bin/bash +} + +usage() { + echo $(basename $BASH_ARGV0) [COMMAND] [ARGS] + echo + echo COMMANDS: + echo + echo " fetch -- create data/ directory and download all data for running tests" + echo " package [VERSION] -- create xrootd.tar.gz tarball (VERSION=HEAD by default)" + echo " build [OS] -- build docker image based on OS: centos7 (default), alma8, alma9" + echo " setup [OS] -- setup and launch all containers required to run the test suite" + echo " run [TEST] -- run [TEST] test within metaman (runs all tests if called without arguments)" + echo " clean -- clean up tarball, remove and stop all docker containers and networks" + echo " shell [CONTAINER] -- start a bash shell inside container CONTAINER (default: metaman)" + echo + echo " A complete run of the test suite requires running the following commands:" + echo "" + echo " fetch, package, build, setup, run, clean" + echo "" + echo " The fetch command needs to be run only once to download testing data." + echo +} + +[[ $# == 0 ]] && usage && exit 0 + +CMD=$1 +shift +[[ $(type -t ${CMD}) == "function" ]] || die "unknown command: ${CMD}" +cd $(dirname "${BASH_SOURCE[0]}") || die "cannot change directory" +$CMD $@ From 2bc8b5e820a96f53934292fb4c5537c84e4844d2 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 23 Mar 2023 10:54:03 +0100 Subject: [PATCH 051/442] Stop relying on 'using namespace std;' This is in preparation for removing 'using namespace std;' from our headers, which is a necessary step in supporting C++17. --- src/Xrd/XrdConfig.cc | 8 +- src/XrdAcc/XrdAccGroups.cc | 6 +- src/XrdApps/XrdAccTest.cc | 28 +++---- src/XrdApps/XrdAppsCconfig.cc | 4 +- src/XrdApps/XrdCpConfig.cc | 14 ++-- src/XrdApps/XrdCrc32c.cc | 16 ++-- src/XrdApps/XrdMapCluster.cc | 24 +++--- src/XrdApps/XrdMpxStats.cc | 2 +- src/XrdApps/XrdMpxXml.cc | 6 +- src/XrdApps/XrdQStats.cc | 28 +++---- src/XrdApps/XrdWait41.cc | 22 +++--- src/XrdBwm/XrdBwmTrace.hh | 2 +- src/XrdCms/XrdCmsBaseFS.cc | 2 +- src/XrdCms/XrdCmsConfig.cc | 2 +- src/XrdCms/XrdCmsRedirLocal.cc | 2 +- src/XrdCrypto/XrdCryptoAux.hh | 2 +- src/XrdCrypto/XrdCryptoTrace.hh | 2 +- src/XrdCrypto/XrdCryptoX509Chain.cc | 2 +- src/XrdCrypto/XrdCryptosslCipher.cc | 2 +- src/XrdCrypto/XrdCryptosslRSA.cc | 6 +- src/XrdCrypto/XrdCryptosslTrace.hh | 2 +- src/XrdCrypto/XrdCryptotest.cc | 6 +- src/XrdFrc/XrdFrcReqAgent.cc | 4 +- src/XrdFrc/XrdFrcTrace.hh | 6 +- src/XrdFrc/XrdFrcUtils.cc | 4 +- src/XrdFrm/XrdFrmAdminMain.cc | 2 +- src/XrdFrm/XrdFrmConfig.cc | 2 +- src/XrdFrm/XrdFrmMonitor.cc | 2 +- src/XrdFrm/XrdFrmTSort.cc | 8 +- src/XrdFrm/XrdFrmXfrAgent.cc | 2 +- src/XrdHttp/XrdHttpProtocol.cc | 8 +- src/XrdHttp/XrdHttpReq.cc | 56 ++++++------- src/XrdHttp/XrdHttpUtils.cc | 4 +- src/XrdNet/XrdNetIF.cc | 2 +- src/XrdOfs/XrdOfsTPC.cc | 2 +- src/XrdOssCsi/XrdOssCsiTrace.hh | 4 +- src/XrdOuc/XrdOucCache.hh | 2 +- src/XrdOuc/XrdOucCacheCM.hh | 2 +- src/XrdOuc/XrdOucGMap.cc | 4 +- src/XrdOuc/XrdOucNSWalk.cc | 6 +- src/XrdOuc/XrdOucNSWalk.hh | 2 +- src/XrdOuc/XrdOucPup.cc | 4 +- src/XrdOuc/XrdOucStream.cc | 2 +- src/XrdOuc/XrdOucString.cc | 2 +- src/XrdOuc/XrdOucString.hh | 2 +- src/XrdPfc/XrdPfcFile.cc | 2 +- src/XrdPosix/XrdPosixLinkage.cc | 6 +- src/XrdPosix/XrdPosixMap.cc | 2 +- src/XrdRmc/XrdRmcData.cc | 32 ++++---- src/XrdRmc/XrdRmcReal.cc | 36 ++++----- src/XrdSciTokens/vendor/picojson/README.mkdn | 2 +- .../vendor/picojson/examples/github-issues.cc | 8 +- src/XrdSec/XrdSecClient.cc | 6 +- src/XrdSec/XrdSecPManager.cc | 4 +- src/XrdSec/XrdSecTLayer.cc | 2 +- src/XrdSec/XrdSecTrace.hh | 4 +- src/XrdSec/XrdSectestClient.cc | 18 ++--- src/XrdSec/XrdSectestServer.cc | 22 +++--- src/XrdSecgsi/XrdSecProtocolgsi.cc | 8 +- src/XrdSecgsi/XrdSecgsiGMAPFunDN.cc | 2 +- src/XrdSecgsi/XrdSecgsiProxy.cc | 2 +- src/XrdSecgsi/XrdSecgsiTrace.hh | 2 +- src/XrdSecgsi/XrdSecgsitest.cc | 2 +- src/XrdSeckrb5/XrdSecProtocolkrb5.cc | 16 ++-- src/XrdSecpwd/XrdSecProtocolpwd.cc | 6 +- src/XrdSecpwd/XrdSecProtocolpwd.hh | 2 +- src/XrdSecpwd/XrdSecpwdSrvAdmin.cc | 2 +- src/XrdSecpwd/XrdSecpwdTrace.hh | 2 +- src/XrdSecunix/XrdSecProtocolunix.cc | 4 +- src/XrdSsi/XrdSsiLogger.cc | 2 +- src/XrdSsi/XrdSsiLogger.hh | 8 +- src/XrdSsi/XrdSsiLogging.cc | 18 ++--- src/XrdSsi/XrdSsiShMam.cc | 10 +-- src/XrdSut/XrdSutAux.cc | 6 +- src/XrdSut/XrdSutBuffer.cc | 4 +- src/XrdSut/XrdSutTrace.hh | 2 +- src/XrdSys/XrdSysError.cc | 10 +-- src/XrdSys/XrdSysError.hh | 2 +- src/XrdSys/XrdSysIOEvents.cc | 10 +-- src/XrdSys/XrdSysIOEventsPollE.icc | 2 +- src/XrdSys/XrdSysIOEventsPollKQ.icc | 2 +- src/XrdSys/XrdSysIOEventsPollPoll.icc | 2 +- src/XrdSys/XrdSysIOEventsPollPort.icc | 2 +- src/XrdSys/XrdSysLogger.cc | 2 +- src/XrdSys/XrdSysPlugin.cc | 2 +- src/XrdSys/XrdSysPriv.cc | 14 ++-- src/XrdSys/XrdSysXSLock.cc | 6 +- src/XrdThrottle/XrdThrottleManager.cc | 2 +- src/XrdThrottle/XrdThrottleTrace.hh | 4 +- src/XrdVoms/XrdVomsFun.cc | 2 +- src/XrdVoms/XrdVomsMapfile.cc | 2 +- src/XrdVoms/XrdVomsTrace.hh | 4 +- tests/XrdSsiTests/XrdShMap.cc | 78 +++++++++---------- 93 files changed, 354 insertions(+), 354 deletions(-) diff --git a/src/Xrd/XrdConfig.cc b/src/Xrd/XrdConfig.cc index f838f214046..be95219132e 100644 --- a/src/Xrd/XrdConfig.cc +++ b/src/Xrd/XrdConfig.cc @@ -462,7 +462,7 @@ int XrdConfig::Configure(int argc, char **argv) Log.Emsg("Config", buff, "parameter not specified."); Usage(1); break; - case 'v': cerr <] [-d] [-h] [-H] [-I {v4|v6}]\n" + std::cerr <<"\nUsage: " <] [-d] [-h] [-H] [-I {v4|v6}]\n" "[-k {n|sz|sig}] [-l [=]] [-n name] [-p ] [-P ] [-L ]\n" - "[-R] [-s pidfile] [-S site] [-v] [-z] []" <]" < 0 ? rc : 0); } diff --git a/src/XrdAcc/XrdAccGroups.cc b/src/XrdAcc/XrdAccGroups.cc index 94b8edbc75e..c16c2644fa6 100644 --- a/src/XrdAcc/XrdAccGroups.cc +++ b/src/XrdAcc/XrdAccGroups.cc @@ -117,7 +117,7 @@ char *XrdAccGroups::AddName(const XrdAccGroupType gtype, const char *name) if (!(np = hp->Find(name))) {hp->Add(name, 0, 0, Hash_data_is_key); if (!(np = hp->Find(name))) - cerr <<"XrdAccGroups: Unable to add group " <= NGROUPS_MAX) {if (gtabi == NGROUPS_MAX) - cerr <<"XrdAccGroups: More than " <gtabi >= NGROUPS_MAX) {if (grp->gtabi == NGROUPS_MAX) - cerr <<"XrdAccGroups: More than " <gtabi <<"netgroups for " <user <gtabi <<"netgroups for " <user <] [ | ] \n\n"; - cerr <<": -a -g -h -o -r -u \n"; - cerr <<": [ [...]]\n"; - cerr <<": cr - create mv - rename st - status lk - lock\n"; - cerr <<" rd - read wr - write ls - readdir rm - remove\n"; - cerr <<" ec - excl create ei - excl rename\n"; - cerr <<" * - zap args ? - display privs\n"; - cerr <] [ | ] \n\n"; + std::cerr <<": -a -g -h -o -r -u \n"; + std::cerr <<": [ [...]]\n"; + std::cerr <<": cr - create mv - rename st - status lk - lock\n"; + std::cerr <<" rd - read wr - write ls - readdir rm - remove\n"; + std::cerr <<" ec - excl create ei - excl rename\n"; + std::cerr <<" * - zap args ? - display privs\n"; + std::cerr << std::flush; exit(msg ? 1 : 0); } @@ -212,7 +212,7 @@ bool singleshot=false; // Obtain the authorization object // if (!(Authorize = XrdAccDefaultAuthorizeObject(&myLogger, ConfigFN, 0, myVer))) - {cerr << "testaccess: Initialization failed." < [-h ] [-n ] [-x ] []" - "\n: [[pfx]*] | [*[sfx]] []" < [-h ] [-n ] [-x ] []" + "\n: [[pfx]*] | [*[sfx]] []" < ] [-DS ] [-np]\n" " [-md5] [-OD] [-OS] [-version] [-x]"; - cerr <<(Opts & opt1Src ? Syntax1 : Syntax) < | -}\n" + std::cerr <<"\nUsage: xrdcrc32c [opts] { | -}\n" "\n the path to the file whose checksum if to be computed." "\n- compute checksum from data presented at standard in;" "\n example: xrdcp - | xrdcrc32c -\n" @@ -81,7 +81,7 @@ void Usage(int rc) "\n-n do not end output with a newline character." "\n-s do not include file path in output result." "\n-x do not print leading zeroes in the checksum, if any." - <= argc) - {cerr <hasfile; pfxbuff[2] = clnow->verfile; } - cout <<' ' <name <state <name <state <nextSrv; } } @@ -410,7 +410,7 @@ void PrintMap(clMap *clmP, int lvl) if (lvl) pfxbuff[2] = ' '; while(clnow) {if (lvl) pfxbuff[1] = clnow->hasfile; - cout <name <state <name <state <valid && clnow->nextLvl) PrintMap(clnow->nextLvl,lvl+1); clnow = clnow->nextMan; } @@ -451,18 +451,18 @@ namespace void Usage(const char *emsg) { if (emsg) EMSG(emsg); - cerr <<"Usage: xrdmapc [] : []\n" - <<": [--help] [--list {all|m|s}] [--quiet] [--refresh] [--verify]" <] : []\n" + <<": [--help] [--list {all|m|s}] [--quiet] [--refresh] [--verify]" < existence status at each server.\n" " when specified, uses : to determine the locations\n" " of path and does optional verification." - <name <state <name <state <name - <<" referred to the following unconnected node:" <name + <<" referred to the following unconnected node:" <name <name <nextSrv; } } diff --git a/src/XrdApps/XrdMpxStats.cc b/src/XrdApps/XrdMpxStats.cc index 8268a652853..e9e53498390 100644 --- a/src/XrdApps/XrdMpxStats.cc +++ b/src/XrdApps/XrdMpxStats.cc @@ -198,7 +198,7 @@ void *mainOutput(void *parg) void Usage(int rc) { - cerr <<"\nUsage: mpxstats [-f {cgi|flat|xml}] -p [-s]" < [-s]" <[:]\n" + std::cerr <<"\nUsage: xrdqstats [opts] [:]\n" "\nopts: -f {cgi|flat|xml} -h -i -n -s what -z\n" "\n-f specify display format (default is wordy text format)." "\n-i number of seconds to wait before between redisplays, default 10." @@ -77,7 +77,7 @@ void Usage(int rc) "\na - All (default) b - Buffer usage d - Device polling" "\ni - Identification c - Connections p - Protocols" "\ns - Scheduling u - Usage data z - Synchronized info" - <= argc) - {cerr <GetBuffer() <GetBuffer() <Format(0, theStats->GetBuffer(), obuff); char *bP = obuff; while(wLen > 0) @@ -198,7 +198,7 @@ int main(int argc, char *argv[]) } delete theStats; if (WTime) sleep(WTime); - if (Count) cout <<"\n"; + if (Count) std::cout <<"\n"; } // All done diff --git a/src/XrdApps/XrdWait41.cc b/src/XrdApps/XrdWait41.cc index 3b5b48d164e..80cce27484f 100644 --- a/src/XrdApps/XrdWait41.cc +++ b/src/XrdApps/XrdWait41.cc @@ -140,7 +140,7 @@ int main(int argc, char *argv[]) for (i = 1; i < argc; i++) {if (stat(argv[i], &Stat)) {eText = XrdSysE2T(errno); - cerr <<"wait41: " <d_name); if (stat(buff, &Stat)) {eText = XrdSysE2T(errno); - cerr <<"wait41: " <text <text <val = open(gfP->text, O_CREAT|O_RDWR, AMode)) < 0) {eTxt = XrdSysE2T(errno); - cerr <<"Wait41: " <text <text <text <text <val); } else Num++; gfP = gfP->next; diff --git a/src/XrdBwm/XrdBwmTrace.hh b/src/XrdBwm/XrdBwmTrace.hh index d321e814d5e..d994089c0cf 100644 --- a/src/XrdBwm/XrdBwmTrace.hh +++ b/src/XrdBwm/XrdBwmTrace.hh @@ -40,7 +40,7 @@ extern XrdOucTrace BwmTrace; #define GTRACE(act) BwmTrace.What & TRACE_ ## act #define TRACES(x) \ - {BwmTrace.Beg(epname,tident); cerr <= 450) {theQ.rLeft = theQ.rAgain; Window.Reset(); - cerr <<"BYPASS " <" <" <Locate(Resp, newPath.c_str(), flags, EnvInfo); // set new error message to full url:port//newPath - const std::string errText { std::string(Resp.getErrText()) + ':' + to_string(Resp.getErrInfo()) + newPath}; + const std::string errText { std::string(Resp.getErrText()) + ':' + std::to_string(Resp.getErrInfo()) + newPath}; Resp.setErrInfo(0, errText.c_str()); // now have normal redirection to dataserver at url:port return rcode; diff --git a/src/XrdCrypto/XrdCryptoAux.hh b/src/XrdCrypto/XrdCryptoAux.hh index b9555ad5749..cbc810ff7ff 100644 --- a/src/XrdCrypto/XrdCryptoAux.hh +++ b/src/XrdCrypto/XrdCryptoAux.hh @@ -38,7 +38,7 @@ /******************************************************************************/ /* M i s c e l l a n e o u s D e f i n e s */ /******************************************************************************/ -#define ABSTRACTMETHOD(x) {cerr <<"Method "<What & cryptoTRACE_ ## act)) #define PRINT(y) {if (cryptoTrace) {cryptoTrace->Beg(epname); \ - cerr <End();}} + std::cerr <End();}} #define TRACE(act,x) if (QTRACE(act)) PRINT(x) #define DEBUG(y) TRACE(Debug,y) #define EPNAME(x) static const char *epname = x; diff --git a/src/XrdCrypto/XrdCryptoX509Chain.cc b/src/XrdCrypto/XrdCryptoX509Chain.cc index ad618fb7527..d1adc0bc39f 100644 --- a/src/XrdCrypto/XrdCryptoX509Chain.cc +++ b/src/XrdCrypto/XrdCryptoX509Chain.cc @@ -40,7 +40,7 @@ // ---------------------------------------------------------------------------// // For test dumps, to avoid interfering with the trace mutex -#define LOCDUMP(y) { cerr << epname << ":" << y << endl; } +#define LOCDUMP(y) { std::cerr << epname << ":" << y << std::endl; } // Description of errors static const char *X509ChainErrStr[] = { diff --git a/src/XrdCrypto/XrdCryptosslCipher.cc b/src/XrdCrypto/XrdCryptosslCipher.cc index 2e0e67e7443..e0bb5f5a67a 100644 --- a/src/XrdCrypto/XrdCryptosslCipher.cc +++ b/src/XrdCrypto/XrdCryptosslCipher.cc @@ -1036,7 +1036,7 @@ void XrdCryptosslCipher::PrintPublic(BIGNUM *pub) char *bpub = new char[lpub]; if (bpub) { BIO_read(biop,(void *)bpub,lpub); - cerr << bpub << endl; + std::cerr << bpub << std::endl; delete[] bpub; } EVP_PKEY_free(dsa); diff --git a/src/XrdCrypto/XrdCryptosslRSA.cc b/src/XrdCrypto/XrdCryptosslRSA.cc index dca76f8acaf..13a48ccb933 100644 --- a/src/XrdCrypto/XrdCryptosslRSA.cc +++ b/src/XrdCrypto/XrdCryptosslRSA.cc @@ -354,7 +354,7 @@ void XrdCryptosslRSA::Dump() char *btmp = new char[GetPublen()+1]; if (btmp) { ExportPublic(btmp,GetPublen()+1); - DEBUG("export pub key:"<What & cryptoTRACE_ ## act)) #define PRINT(y) {if (sslTrace) {sslTrace->Beg(epname); \ - cerr <End();}} + std::cerr <End();}} #define TRACE(act,x) if (QTRACE(act)) PRINT(x) #define DEBUG(y) TRACE(Debug,y) #define EPNAME(x) static const char *epname = x; diff --git a/src/XrdCrypto/XrdCryptotest.cc b/src/XrdCrypto/XrdCryptotest.cc index 7c8f43ecfe3..fcdc4d0ee77 100644 --- a/src/XrdCrypto/XrdCryptotest.cc +++ b/src/XrdCrypto/XrdCryptotest.cc @@ -49,7 +49,7 @@ // // Globals -#define PRINT(x) {cerr <ExportPublic(RSApubexp,4096); - PRINT(outname<<": public export:"<GetPublen()); PRINT(outname<<": --------------------------------------------------- "); char RSApriexp[4096]; TestRSA_1->ExportPrivate(RSApriexp,4096); - PRINT(outname<<": private export:"<GetPrilen()); PRINT(outname<<": --------------------------------------------------- "); diff --git a/src/XrdFrc/XrdFrcReqAgent.cc b/src/XrdFrc/XrdFrcReqAgent.cc index 7434fc0a2b7..dd1267a7d63 100644 --- a/src/XrdFrc/XrdFrcReqAgent.cc +++ b/src/XrdFrc/XrdFrcReqAgent.cc @@ -125,7 +125,7 @@ int XrdFrcReqAgent::List(XrdFrcRequest::Item *Items, int Num) for (i = 0; i <= XrdFrcRequest::maxPrty; i++) {Offs = 0; while(rQueue[i]->List(myLfn, sizeof(myLfn), Offs, Items, Num)) - {cout <List(myLfn, sizeof(myLfn), Offs, Items, Num)) - {cout <Next = FSTab[0][n]; FSTab[0][n] = fsp; if (n > DYent) DYent = n; -//cerr <<"Add " <Age <<' ' <basePath() <Age <<' ' <basePath() <Next = FSTab[j][k]; else fsq = Insert(fsq, FSTab[j][k]); FSTab[j][k] = fsq; -//cerr <<"Bin " <Age <<' ' <basePath() <Age <<' ' <basePath() <Next)) SCent--; numEnt--; -//cerr <<"Oldest " <Age <<' ' <basePath() <Age <<' ' <basePath() < 0) - sslavail = min(maxread, SSL_pending(ssl)); + sslavail = std::min(maxread, SSL_pending(ssl)); } if (sslavail < 0) { @@ -1472,10 +1472,10 @@ int XrdHttpProtocol::BuffgetData(int blen, char **data, bool wait) { // And now make available the data taken from the buffer. Note that the buffer // may be empty... if (myBuffStart <= myBuffEnd) { - rlen = min( (long) blen, (long)(myBuffEnd - myBuffStart) ); + rlen = std::min( (long) blen, (long)(myBuffEnd - myBuffStart) ); } else - rlen = min( (long) blen, (long)(myBuff->buff + myBuff->bsize - myBuffStart) ); + rlen = std::min( (long) blen, (long)(myBuff->buff + myBuff->bsize - myBuffStart) ); *data = myBuffStart; BuffConsume(rlen); diff --git a/src/XrdHttp/XrdHttpReq.cc b/src/XrdHttp/XrdHttpReq.cc index 7b7c2b18b4c..51c37736c34 100644 --- a/src/XrdHttp/XrdHttpReq.cc +++ b/src/XrdHttp/XrdHttpReq.cc @@ -86,7 +86,7 @@ std::string ISOdatetime(time_t t) { gmtime_r(&t, &t1); strftime(datebuf, 127, "%a, %d %b %Y %H:%M:%S GMT", &t1); - return (string) datebuf; + return (std::string) datebuf; } @@ -192,9 +192,9 @@ int XrdHttpReq::parseLine(char *line, int len) { m_status_trailer = true; } else { // Some headers need to be translated into "local" cgi info. - std::map< std:: string, std:: string > ::iterator it = prot->hdr2cgimap.find(key); + std::map< std::string, std::string > ::iterator it = prot->hdr2cgimap.find(key); if (it != prot->hdr2cgimap.end() && (opaque ? (0 == opaque->Get(it->second.c_str())) : true)) { - std:: string s; + std::string s; s.assign(val, line+len-val); trim(s); @@ -281,13 +281,13 @@ int XrdHttpReq::parseRWOp(char *str) { kXR_int32 newlen = sz; if (filesize > 0) - newlen = (kXR_int32) min(filesize - o1.bytestart, sz); + newlen = (kXR_int32) std::min(filesize - o1.bytestart, sz); rwOps.push_back(o1); while (len_ok < newlen) { ReadWriteOp nfo; - int len = min(newlen - len_ok, READV_MAXCHUNKSIZE); + int len = std::min(newlen - len_ok, READV_MAXCHUNKSIZE); nfo.bytestart = o1.bytestart + len_ok; nfo.byteend = nfo.bytestart + len - 1; @@ -479,7 +479,7 @@ int XrdHttpReq::ReqReadV() { } std::string XrdHttpReq::buildPartialHdr(long long bytestart, long long byteend, long long fsz, char *token) { - ostringstream s; + std::ostringstream s; s << "\r\n--" << token << "\r\n"; s << "Content-type: text/plain; charset=UTF-8\r\n"; @@ -489,7 +489,7 @@ std::string XrdHttpReq::buildPartialHdr(long long bytestart, long long byteend, } std::string XrdHttpReq::buildPartialHdrEnd(char *token) { - ostringstream s; + std::ostringstream s; s << "\r\n--" << token << "--\r\n"; @@ -1149,7 +1149,7 @@ int XrdHttpReq::ProcessHTTPReq() { } - string res; + std::string res; res = resourceplusopaque.c_str(); //res += "?xrd.dirstat=1"; @@ -1278,12 +1278,12 @@ int XrdHttpReq::ProcessHTTPReq() { xrdreq.read.dlen = 0; if (rwOps.size() == 0) { - l = (long)min(filesize-writtenbytes, (long long)1024*1024); + l = (long)std::min(filesize-writtenbytes, (long long)1024*1024); offs = writtenbytes; xrdreq.read.offset = htonll(writtenbytes); xrdreq.read.rlen = htonl(l); } else { - l = min(rwOps[0].byteend - rwOps[0].bytestart + 1 - writtenbytes, (long long)1024*1024); + l = std::min(rwOps[0].byteend - rwOps[0].bytestart + 1 - writtenbytes, (long long)1024*1024); offs = rwOps[0].bytestart + writtenbytes; xrdreq.read.offset = htonll(offs); xrdreq.read.rlen = htonl(l); @@ -1447,7 +1447,7 @@ int XrdHttpReq::ProcessHTTPReq() { memcpy(xrdreq.write.fhandle, fhandle, 4); long long chunk_bytes_remaining = m_current_chunk_size - m_current_chunk_offset; - long long bytes_to_write = min(static_cast(prot->BuffUsed()), + long long bytes_to_write = std::min(static_cast(prot->BuffUsed()), chunk_bytes_remaining); xrdreq.write.offset = htonll(writtenbytes); @@ -1470,7 +1470,7 @@ int XrdHttpReq::ProcessHTTPReq() { xrdreq.write.requestid = htons(kXR_write); memcpy(xrdreq.write.fhandle, fhandle, 4); - long long bytes_to_read = min(static_cast(prot->BuffUsed()), + long long bytes_to_read = std::min(static_cast(prot->BuffUsed()), length - writtenbytes); xrdreq.write.offset = htonll(writtenbytes); @@ -1534,7 +1534,7 @@ int XrdHttpReq::ProcessHTTPReq() { // --------- STAT is always the first step memset(&xrdreq, 0, sizeof (ClientRequest)); xrdreq.stat.requestid = htons(kXR_stat); - string s = resourceplusopaque.c_str(); + std::string s = resourceplusopaque.c_str(); l = resourceplusopaque.length() + 1; @@ -1555,7 +1555,7 @@ int XrdHttpReq::ProcessHTTPReq() { memset(&xrdreq, 0, sizeof (ClientRequest)); xrdreq.rmdir.requestid = htons(kXR_rmdir); - string s = resourceplusopaque.c_str(); + std::string s = resourceplusopaque.c_str(); l = s.length() + 1; xrdreq.rmdir.dlen = htonl(l); @@ -1569,7 +1569,7 @@ int XrdHttpReq::ProcessHTTPReq() { memset(&xrdreq, 0, sizeof (ClientRequest)); xrdreq.rm.requestid = htons(kXR_rm); - string s = resourceplusopaque.c_str(); + std::string s = resourceplusopaque.c_str(); l = s.length() + 1; xrdreq.rm.dlen = htonl(l); @@ -1628,7 +1628,7 @@ int XrdHttpReq::ProcessHTTPReq() { // --------- STAT is always the first step memset(&xrdreq, 0, sizeof (ClientRequest)); xrdreq.stat.requestid = htons(kXR_stat); - string s = resourceplusopaque.c_str(); + std::string s = resourceplusopaque.c_str(); l = resourceplusopaque.length() + 1; @@ -1659,7 +1659,7 @@ int XrdHttpReq::ProcessHTTPReq() { memset(&xrdreq, 0, sizeof (ClientRequest)); xrdreq.dirlist.requestid = htons(kXR_dirlist); - string s = resourceplusopaque.c_str(); + std::string s = resourceplusopaque.c_str(); xrdreq.dirlist.options[0] = kXR_dstat; //s += "?xrd.dirstat=1"; @@ -1686,7 +1686,7 @@ int XrdHttpReq::ProcessHTTPReq() { memset(&xrdreq, 0, sizeof (ClientRequest)); xrdreq.mkdir.requestid = htons(kXR_mkdir); - string s = resourceplusopaque.c_str(); + std::string s = resourceplusopaque.c_str(); xrdreq.mkdir.options[0] = (kXR_char) kXR_mkdirpath; l = s.length() + 1; @@ -1707,7 +1707,7 @@ int XrdHttpReq::ProcessHTTPReq() { memset(&xrdreq, 0, sizeof (ClientRequest)); xrdreq.mv.requestid = htons(kXR_mv); - string s = resourceplusopaque.c_str(); + std::string s = resourceplusopaque.c_str(); s += " "; char buf[256]; @@ -1953,7 +1953,7 @@ int XrdHttpReq::PostProcessHTTPReq(bool final_) { if (e.path.length() && (e.path != ".") && (e.path != "..")) { // The entry is filled. file1.txt - string p = "" + std::string p = "" ""; if (e.flags & kXR_isDir) p += "d"; @@ -2340,7 +2340,7 @@ int XrdHttpReq::PostProcessHTTPReq(bool final_) { // Now we have a chunk coming from the server. This may be a partial chunk if (rwOpPartialDone == 0) { - string s = buildPartialHdr(rwOps[rwOpDone].bytestart, + std::string s = buildPartialHdr(rwOps[rwOpDone].bytestart, rwOps[rwOpDone].byteend, filesize, (char *) "123456"); @@ -2367,7 +2367,7 @@ int XrdHttpReq::PostProcessHTTPReq(bool final_) { } if (rwOpDone == rwOps.size()) { - string s = buildPartialHdrEnd((char *) "123456"); + std::string s = buildPartialHdrEnd((char *) "123456"); if (prot->SendData((char *) s.c_str(), s.size())) return -1; } @@ -2419,7 +2419,7 @@ int XrdHttpReq::PostProcessHTTPReq(bool final_) { fopened = true; // We try to completely fill up our buffer before flushing - prot->ResumeBytes = min(length - writtenbytes, (long long) prot->BuffAvailable()); + prot->ResumeBytes = std::min(length - writtenbytes, (long long) prot->BuffAvailable()); if (sendcontinue) { prot->SendSimpleResp(100, NULL, NULL, 0, 0, keepalive); @@ -2446,7 +2446,7 @@ int XrdHttpReq::PostProcessHTTPReq(bool final_) { } // We try to completely fill up our buffer before flushing - prot->ResumeBytes = min(length - writtenbytes, (long long) prot->BuffAvailable()); + prot->ResumeBytes = std::min(length - writtenbytes, (long long) prot->BuffAvailable()); return 0; } @@ -2557,7 +2557,7 @@ int XrdHttpReq::PostProcessHTTPReq(bool final_) { /* The entry is filled. */ - string p; + std::string p; stringresp += "\n"; char *estr = escapeXML(e.path.c_str()); @@ -2611,7 +2611,7 @@ int XrdHttpReq::PostProcessHTTPReq(bool final_) { // If this was the last bunch of entries, send the buffer and empty it immediately if ((depth == 0) || !(e.flags & kXR_isDir)) { - string s = "\n\n"; + std::string s = "\n\n"; stringresp.insert(0, s); stringresp += "\n"; prot->SendSimpleResp(207, (char *) "Multi-Status", (char *) "Content-Type: text/xml; charset=\"utf-8\"", @@ -2673,7 +2673,7 @@ int XrdHttpReq::PostProcessHTTPReq(bool final_) { */ - string p = resource.c_str(); + std::string p = resource.c_str(); if (*p.rbegin() != '/') p += "/"; p += e.path; @@ -2735,7 +2735,7 @@ int XrdHttpReq::PostProcessHTTPReq(bool final_) { // If this was the last bunch of entries, send the buffer and empty it immediately if (final_) { - string s = "\n\n"; + std::string s = "\n\n"; stringresp.insert(0, s); stringresp += "\n"; prot->SendSimpleResp(207, (char *) "Multi-Status", (char *) "Content-Type: text/xml; charset=\"utf-8\"", diff --git a/src/XrdHttp/XrdHttpUtils.cc b/src/XrdHttp/XrdHttpUtils.cc index 78fa4f26ab8..34932988827 100644 --- a/src/XrdHttp/XrdHttpUtils.cc +++ b/src/XrdHttp/XrdHttpUtils.cc @@ -93,14 +93,14 @@ int parseURL(char *url, char *host, int &port, char **path) { *path = p2; char buf[256]; - int l = min((int)(p2 - p), (int)sizeof (buf)); + int l = std::min((int)(p2 - p), (int)sizeof (buf)); strncpy(buf, p, l); buf[l] = '\0'; // Now look for : p = strchr(buf, ':'); if (p) { - int l = min((int)(p - buf), (int)sizeof (buf)); + int l = std::min((int)(p - buf), (int)sizeof (buf)); strncpy(host, buf, l); host[l] = '\0'; diff --git a/src/XrdNet/XrdNetIF.cc b/src/XrdNet/XrdNetIF.cc index 8d004c32198..e7c0a51fbe5 100644 --- a/src/XrdNet/XrdNetIF.cc +++ b/src/XrdNet/XrdNetIF.cc @@ -409,7 +409,7 @@ int XrdNetIF::GetDest(char *dest, int dlen, ifType ifT, bool prefn) /* G e t I F */ /******************************************************************************/ -#define prtaddr(x) cerr <<"Addr!!! " << *x < rpInst = {0}; } /******************************************************************************/ diff --git a/src/XrdOssCsi/XrdOssCsiTrace.hh b/src/XrdOssCsi/XrdOssCsiTrace.hh index dc708615cf8..7abf4aaf180 100644 --- a/src/XrdOssCsi/XrdOssCsiTrace.hh +++ b/src/XrdOssCsi/XrdOssCsiTrace.hh @@ -48,13 +48,13 @@ #define TRACE(act, x) \ if (QTRACE(act)) \ - {OssCsiTrace.Beg(epname,tident); cerr < The message routing object to be used in conjunction //! with an XrdSysError object for error messages. When -//! nil, you should use cerr. +//! nil, you should use std::cerr. //! @param Config -> The name of the config file. When nil there was no //! configuration file. //! @param Parms -> Any parameters specified after the path on the diff --git a/src/XrdOuc/XrdOucGMap.cc b/src/XrdOuc/XrdOucGMap.cc index f576a55cc67..eaa11df5af0 100644 --- a/src/XrdOuc/XrdOucGMap.cc +++ b/src/XrdOuc/XrdOucGMap.cc @@ -57,8 +57,8 @@ enum XrdOucGMap_Match {kFull = 0, kContains = 4 }; -#define PRINT(t,n,y) {if (t) {t->Beg(n); cerr <End();}} -#define DEBUG(d,t,n,y) {if (d && t) {t->Beg(n); cerr <End();}} +#define PRINT(t,n,y) {if (t) {t->Beg(n); std::cerr <End();}} +#define DEBUG(d,t,n,y) {if (d && t) {t->Beg(n); std::cerr <End();}} //__________________________________________________________________________ static int FindMatchingCondition(const char *, XrdSecGMapEntry_t *mc, void *xmp) diff --git a/src/XrdOuc/XrdOucNSWalk.cc b/src/XrdOuc/XrdOucNSWalk.cc index d93fa7aeb1d..34ca4accdd9 100644 --- a/src/XrdOuc/XrdOucNSWalk.cc +++ b/src/XrdOuc/XrdOucNSWalk.cc @@ -247,9 +247,9 @@ int XrdOucNSWalk::Emsg(const char *pfx, int rc, const char *txt1, if (eDest) eDest->Emsg(pfx, rc, txt1, txt2); else if (mPfx) {const char *etxt = XrdSysE2T(rc); - cerr <:") for use by command line commands. // void setMsgOn(const char *pfx) {mPfx = pfx;} diff --git a/src/XrdOuc/XrdOucPup.cc b/src/XrdOuc/XrdOucPup.cc index d60395f9b58..cef05b6bdec 100644 --- a/src/XrdOuc/XrdOucPup.cc +++ b/src/XrdOuc/XrdOucPup.cc @@ -157,8 +157,8 @@ int XrdOucPup::Pack(struct iovec *iovP, struct iovec *iovE, XrdOucPupArgs *pup, Dtype = pP->Dtype; do {Base.B08 = (char **)(base + pP->Doffs); - //cerr <<"arg " <NList[pP->Name] ? Names->NList[pP->Name] : "?") <NList[pP->Name] ? Names->NList[pP->Name] : "?") <iov_base = Nil; vP->iov_len = 2; diff --git a/src/XrdOuc/XrdOucStream.cc b/src/XrdOuc/XrdOucStream.cc index 59f6802d349..d12e915d452 100644 --- a/src/XrdOuc/XrdOucStream.cc +++ b/src/XrdOuc/XrdOucStream.cc @@ -83,7 +83,7 @@ // The following is used by child processes prior to exec() to avoid deadlocks // -#define Erx(p, a, b) cerr <<#p <<": " <What <<"()'" <What <<"()'" <Next; } } diff --git a/src/XrdPosix/XrdPosixMap.cc b/src/XrdPosix/XrdPosixMap.cc index a5a06230a06..7796c926e50 100644 --- a/src/XrdPosix/XrdPosixMap.cc +++ b/src/XrdPosix/XrdPosixMap.cc @@ -169,7 +169,7 @@ int XrdPosixMap::Result(const XrdCl::XRootDStatus &Status, bool retneg1) // make this messae useful like the opteration and path). // // if (eNum != ENOENT && !eText.empty() && Debug) -// cerr <<"XrdPosix: " <Path()); - cerr <= prMax) prNext = 0; if (oVal == prSKIP) continue; prActive = prRun; - if (Debug > 1) cerr <<"prD: beg " <<(VNum >>XrdRmcReal::Shift) <<' ' + if (Debug > 1) std::cerr <<"prD: beg " <<(VNum >>XrdRmcReal::Shift) <<' ' <<(segEnd-segBeg+1)*SegSize <<'@' <<(segBeg*SegSize) - <<" f=" <Path() <>XrdRmcReal::Shift) - <<' ' < 1) std::cerr <<"PrD: end " <<(VNum >>XrdRmcReal::Shift) + <<' ' < prBeg[i] && segEnd <= prEnd[i])) {if (prHow == prSKIP) - {if (Debug) cerr <<"pDQ: " <Path() <Path() <= 0) {if ( crPerf < Apr.minPerf && prPerf < Apr.minPerf && (crPerf <= prPerf || crPerf <= prPerf*2)) - {if (Debug) cerr <<"PrD: Disabled for " <Path() <Path() <PreRead(&prReq);} } @@ -376,7 +376,7 @@ int XrdRmcData::Read(char *Buff, long long Offs, int rLen) DMutex.UnLock(); } } - if (Debug > 1) cerr <<"Rdr: " < 1) std::cerr <<"Rdr: ret " <<(cBuff ? Dest-Buff : rGot) <<" hits " + < rLen) rAmt = rLen; - if (Debug > 1) cerr <<"Rdr: " < 1) std::cerr <<"Rdr: ret " <<(Dest-Buff) <<" hits " <Path() <Path() <Path() <Path() < 1) std::cerr <<"Cache: Wait slot " <Contents != lAddr) {rAmt = -EIO; return 0;} } else { @@ -335,8 +335,8 @@ char *XrdRmcReal::Get(XrdOucCacheIO *ioP, long long lAddr, int &rAmt, int &noIO) rAmt = (sP->Count < 0 ? sP->Count & XrdRmcSlot::lenMask : SegSize); if (sP->Count & XrdRmcSlot::isNew) {noIO = -1; sP->Count &= ~XrdRmcSlot::isNew;} - if (Dbg > 2) cerr <<"Cache: Hit slot " <Status.inUse < 2) std::cerr <<"Cache: Hit slot " <Status.inUse <(Slot)*SegSize); } @@ -384,8 +384,8 @@ char *XrdRmcReal::Get(XrdOucCacheIO *ioP, long long lAddr, int &rAmt, int &noIO) Slots[Fnum].Owner(Slots, sP); sP->Count = (rAmt == SegSize ? SegFull : rAmt|XrdRmcSlot::isShort); sP->Status.inUse = nUse; - if (Dbg > 2) cerr <<"Cache: Miss slot " <Count & XrdRmcSlot::lenMask) < 2) std::cerr <<"Cache: Miss slot " <Count & XrdRmcSlot::lenMask) <Path(), "reading", (lAddr & Strip) << SegShft, SegSize, rAmt); cBuff = 0; @@ -459,7 +459,7 @@ void XrdRmcReal::PreRead() // Simply wait and dispatch elements // - if (Dbg) cerr <<"Cache: preread thread started; now " < 0) prReady.Post(); else prStop->Post(); - if (Dbg) cerr <<"Cache: preread thread exited; left " < 2) cerr <<"Cache: Ref " <Contents < 2) std::cerr <<"Cache: Ref " <Contents <>SegShft) <<" sz " <<(sP->Count & XrdRmcSlot::lenMask) - <<" uc " <Status.inUse <Status.inUse <Path() <Path() < 2) cerr <<"Cache: Upd " <Contents < 2) std::cerr <<"Cache: Upd " <Contents <>SegShft) <<" sz " <<(sP->Count & XrdRmcSlot::lenMask) - <<" uc " <Status.inUse <Status.inUse < picojson::value v; diff --git a/src/XrdSciTokens/vendor/picojson/examples/github-issues.cc b/src/XrdSciTokens/vendor/picojson/examples/github-issues.cc index 0235b965783..4bfb15b12ac 100644 --- a/src/XrdSciTokens/vendor/picojson/examples/github-issues.cc +++ b/src/XrdSciTokens/vendor/picojson/examples/github-issues.cc @@ -82,7 +82,7 @@ int main(int argc, char *argv[]) { curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, memfwrite); curl_easy_setopt(curl, CURLOPT_WRITEDATA, mf); if (curl_easy_perform(curl) != CURLE_OK) { - cerr << error << endl; + std::cerr << error << std::endl; } else { value v; string err; @@ -92,11 +92,11 @@ int main(int argc, char *argv[]) { array::iterator it; for (it = arr.begin(); it != arr.end(); it++) { object obj = it->get(); - cout << "#" << obj["number"].to_str() << ": " << obj["title"].to_str() << endl; - cout << " " << obj["html_url"].to_str() << endl << endl; + std::cout << "#" << obj["number"].to_str() << ": " << obj["title"].to_str() << std::endl; + std::cout << " " << obj["html_url"].to_str() << std::endl << endl; } } else { - cerr << err << endl; + std::cerr << err << std::endl; } } curl_easy_cleanup(curl); diff --git a/src/XrdSec/XrdSecClient.cc b/src/XrdSec/XrdSecClient.cc index 12337306482..82bfc61ed7c 100644 --- a/src/XrdSec/XrdSecClient.cc +++ b/src/XrdSec/XrdSecClient.cc @@ -50,7 +50,7 @@ /* M i s c e l l a n e o u s D e f i n e s */ /******************************************************************************/ -#define DEBUG(x) {if (DebugON) cerr <<"sec_Client: " <setErrInfo(ENOPROTOOPT, noperr); - else cerr <getErrInfo() != ENOENT) cerr <getErrText() <getErrInfo() != ENOENT) std::cerr <getErrText() <setErrInfo(rc, tlist, n); - else {for (i = 0; i < n; i++) cerr <Beg(epname,tident); cerr <End();} + {SecTrace->Beg(epname,tident); std::cerr <End();} #define DEBUG(y) if (QTRACE(Debug)) \ - {SecTrace->Beg(epname); cerr <End();} + {SecTrace->Beg(epname); std::cerr <End();} #define EPNAME(x) static const char *epname = x; #else diff --git a/src/XrdSec/XrdSectestClient.cc b/src/XrdSec/XrdSectestClient.cc index 7748ffd061f..fc6d0b83e19 100644 --- a/src/XrdSec/XrdSectestClient.cc +++ b/src/XrdSec/XrdSectestClient.cc @@ -107,14 +107,14 @@ void help(int); /*Make sure no more parameters exist. */ if (optind < argc) - {cerr <<"testClient: Extraneous parameter, '" <addrInfo = &theAddr; cred = pp->getCredentials(); if (!cred) - {cerr << "Unable to get credentials," <buffer, cred->size, 1, stdout) != (size_t) cred->size) - {cerr << "Unable to write credentials" <getParms(i, opts.host); - if (!sect) cerr <<"testServer: No security token for " <Authenticate(&cred, &parmp, &einfo) < 0) {rc = einfo.getErrInfo(); - cerr << "testServer: Authenticate error " <Entity.name ? pp->Entity.name : "?") + std::cout <<(pp->Entity.name ? pp->Entity.name : "?") <<"@" <<(pp->Entity.host ? pp->Entity.host : "?") - <<" prot=" <Entity.prot <Beg(epname); cerr <End();}} -#define POPTS(t,y) {if (t) {cerr <<"Secgsi" <Beg(epname); std::cerr <End();}} +#define POPTS(t,y) {if (t) {std::cerr <<"Secgsi" <setErrInfo(ENOMEM, msg); else - cerr <Beg(epname); cerr <End();}} +#define PRINT(y) {if (dnTrace) {dnTrace->Beg(epname); std::cerr <End();}} #define DEBUG(y) if (dnTrace && (dnTrace->What & TRACE_Authen)) PRINT(y) diff --git a/src/XrdSecgsi/XrdSecgsiProxy.cc b/src/XrdSecgsi/XrdSecgsiProxy.cc index 04879af73af..171645fb537 100644 --- a/src/XrdSecgsi/XrdSecgsiProxy.cc +++ b/src/XrdSecgsi/XrdSecgsiProxy.cc @@ -61,7 +61,7 @@ #include "XrdSecgsi/XrdSecgsiTrace.hh" -#define PRT(x) {cerr <What & TRACE_ ## act)) #define PRINT(y) {if (gsiTrace) {gsiTrace->Beg(epname); \ - cerr <End();}} + std::cerr <End();}} #define TRACE(act,x) if (QTRACE(act)) PRINT(x) #define NOTIFY(y) TRACE(Debug,y) #define DEBUG(y) TRACE(Authen,y) diff --git a/src/XrdSecgsi/XrdSecgsitest.cc b/src/XrdSecgsi/XrdSecgsitest.cc index b721079b721..ea134193685 100644 --- a/src/XrdSecgsi/XrdSecgsitest.cc +++ b/src/XrdSecgsi/XrdSecgsitest.cc @@ -62,7 +62,7 @@ // // Globals -// #define PRINT(x) {cerr <setErrInfo(rc, msgv, i); - else {for (k = 0; k < i; k++) cerr <setErrInfo(EINVAL, msg); - else cerr <setErrInfo(EINVAL, msg); - else cerr <setErrInfo(EINVAL, msg); - else cerr <setErrInfo(ENOMEM, msg); - else cerr <Beg(epname); cerr <End();}} +#define POPTS(t,y) {if (t) {t->Beg(epname); std::cerr <End();}} #else #define POPTS(t,y) #endif @@ -1916,13 +1916,13 @@ XrdSecProtocol *XrdSecProtocolpwdObject(const char mode, if (erp) erp->setErrInfo(ENOMEM, msg); else - cerr <Beg(epname); cerr <End();}} +#define PRINT(y) {{SecTrace->Beg(epname); std::cerr <End();}} #else #define PRINT(y) { } #endif diff --git a/src/XrdSecpwd/XrdSecpwdSrvAdmin.cc b/src/XrdSecpwd/XrdSecpwdSrvAdmin.cc index fe839aaa3fa..b0f7751b7a6 100644 --- a/src/XrdSecpwd/XrdSecpwdSrvAdmin.cc +++ b/src/XrdSecpwd/XrdSecpwdSrvAdmin.cc @@ -212,7 +212,7 @@ bool GetEntry(XrdSutPFile *ff, XrdOucString tag, bool AskConfirm(const char *msg1, bool defact, const char *msg2 = 0); int LocateFactoryIndex(char *tag, int &id); -#define PRT(x) {cerr <What & TRACE_ ## act)) #define PRINT(y) {if (pwdTrace) {pwdTrace->Beg(epname); \ - cerr <End();}} + std::cerr <End();}} #define TRACE(act,x) if (QTRACE(act)) PRINT(x) #define NOTIFY(y) TRACE(Debug,y) #define DEBUG(y) TRACE(Authen,y) diff --git a/src/XrdSecunix/XrdSecProtocolunix.cc b/src/XrdSecunix/XrdSecProtocolunix.cc index d9a1c8bea30..6abf1018bc6 100644 --- a/src/XrdSecunix/XrdSecProtocolunix.cc +++ b/src/XrdSecunix/XrdSecProtocolunix.cc @@ -147,7 +147,7 @@ int XrdSecProtocolunix::Authenticate(XrdSecCredentials *cred, "Secunix: Authentication protocol id mismatch (unix != %.4s).", cred->buffer); if (erp) erp->setErrInfo(EINVAL, msg); - else cerr <setErrInfo(ENOMEM, msg); - else cerr <traceBeg();} void XrdSsiLogger::TEnd() { - cerr <traceEnd(); } diff --git a/src/XrdSsi/XrdSsiLogger.hh b/src/XrdSsi/XrdSsiLogger.hh index 93032116188..1b3774a7500 100644 --- a/src/XrdSsi/XrdSsiLogger.hh +++ b/src/XrdSsi/XrdSsiLogger.hh @@ -118,14 +118,14 @@ enum mcbType {mcbAll=0, mcbClient, mcbServer}; static bool SetMCB(MCB_t &mcbP, mcbType mcbt=mcbAll); //----------------------------------------------------------------------------- -//! Define helper functions to allow ostream cerr output to appear in the log. +//! Define helper functions to allow std::ostream std::cerr output to appear in the log. //! The following two functions are used with the macros below. //! The SSI_LOG macro preceedes the message with a time stamp; SSI_SAY does not. -//! The endl ostream output item is automatically added to all output! +//! The std::endl std::ostream output item is automatically added to all output! //----------------------------------------------------------------------------- -#define SSI_LOG(x) {cerr <getPlugin("XrdSsiLoggerMCB")); - if (!msgCB && !theCB) cerr <<"Config " <Persist(); } @@ -153,8 +153,8 @@ extern "C" XrdSysLogPI_t XrdSysLogPInit(const char *cfgfn, char **argv, int argc) {if (cfgfn && *cfgfn) ConfigLog(cfgfn); if (!msgCB) - cerr <<"Config '-l@' requires a logmsg callback function " - <<"but it was found!" <size > 0 && bp->buffer) { if (pripre) { XrdOucString premsg(prepose); - cerr << premsg << endl; + std::cerr << premsg << std::endl; pripre = 0; } XrdOucString msg(bp->buffer,bp->size); - cerr << msg << endl; + std::cerr << msg << std::endl; } } // Get next diff --git a/src/XrdSut/XrdSutTrace.hh b/src/XrdSut/XrdSutTrace.hh index 388823a2a18..04e863560fc 100644 --- a/src/XrdSut/XrdSutTrace.hh +++ b/src/XrdSut/XrdSutTrace.hh @@ -41,7 +41,7 @@ #define QTRACE(act) (sutTrace && (sutTrace->What & sutTRACE_ ## act)) #define PRINT(y) {if (sutTrace) {sutTrace->Beg(epname); \ - cerr <End();}} + std::cerr <End();}} #define TRACE(act,x) if (QTRACE(act)) PRINT(x) #define DEBUG(y) TRACE(Debug,y) #define EPNAME(x) static const char *epname = x; diff --git a/src/XrdSys/XrdSysError.cc b/src/XrdSys/XrdSysError.cc index 4bfee80eceb..3812cad77a4 100644 --- a/src/XrdSys/XrdSysError.cc +++ b/src/XrdSys/XrdSysError.cc @@ -160,14 +160,14 @@ void XrdSysError::Say(const char *txt1, const char *txt2, const char *txt3, void XrdSysError::TBeg(const char *txt1, const char *txt2, const char *txt3) { - cerr <traceBeg(); - if (txt1) cerr <traceBeg(); + if (txt1) std::cerr <traceEnd();} +void XrdSysError::TEnd() {std::cerr <traceEnd();} diff --git a/src/XrdSys/XrdSysError.hh b/src/XrdSys/XrdSysError.hh index 0d970db522a..0a08d45f792 100644 --- a/src/XrdSys/XrdSysError.hh +++ b/src/XrdSys/XrdSysError.hh @@ -163,7 +163,7 @@ inline const char *SetPrefix(const char *prefix) return oldpfx; } -// TBeg() is used to start a trace on ostream cerr. The TEnd() ends the trace. +// TBeg() is used to start a trace on std::ostream std::cerr. The TEnd() ends the trace. // void TBeg(const char *txt1=0, const char *txt2=0, const char *txt3=0); void TEnd(); diff --git a/src/XrdSys/XrdSysIOEvents.cc b/src/XrdSys/XrdSysIOEvents.cc index 8d4b08a83c1..e47445a2321 100644 --- a/src/XrdSys/XrdSysIOEvents.cc +++ b/src/XrdSys/XrdSysIOEvents.cc @@ -85,7 +85,7 @@ namespace #define DO_TRACE(x,fd,y) \ {PollerInit::traceMTX.Lock(); \ - cerr <<"IOE fd "<chFD,"chan="<chFD,"chan="<< std::hex<<(void*)cP<< std::dec <<" inTOQ="<inTOQ)<<" status="<chFD,"chan="<chFD,"chan="<< std::hex<<(void*)cP<< std::dec <<" inTOQ="<inTOQ)<<" status="<ID); cerr <ID); std::cerr < &ent std::string -XrdVomsMapfile::Map(const std::vector &fqan) +XrdVomsMapfile::Map(const std::vector &fqan) { decltype(m_entries) entries = m_entries; if (!entries) {return "";} diff --git a/src/XrdVoms/XrdVomsTrace.hh b/src/XrdVoms/XrdVomsTrace.hh index 803c73f929b..15f0e426c89 100644 --- a/src/XrdVoms/XrdVomsTrace.hh +++ b/src/XrdVoms/XrdVomsTrace.hh @@ -32,8 +32,8 @@ #ifndef NODEBUG -#define PRINT(y) if (gDebug) {cerr <traceBeg() <<" XrdVoms"\ - <traceBeg() <<" XrdVoms"\ + <traceEnd();} #define DEBUG(y) if (gDebug > 1) {PRINT(y)} #define EPNAME(x) static const char *epname = x; diff --git a/tests/XrdSsiTests/XrdShMap.cc b/tests/XrdSsiTests/XrdShMap.cc index a63ef77c17d..14fe81e4faa 100644 --- a/tests/XrdSsiTests/XrdShMap.cc +++ b/tests/XrdSsiTests/XrdShMap.cc @@ -74,10 +74,10 @@ namespace /* D e f i n e s */ /******************************************************************************/ -#define FMSG(x) xRC|=1,cerr <] [-p ] [-t ]\n\n"; +std::cerr <<"Usage: xrdshmap [options] [command [command [...]]]\n\n"; +std::cerr <<"options: [-e] [-h {a32|c32|x32}] [-i ] [-p ] [-t ]\n\n"; if (terse) return rc; if (!uLine) Usage(); - cerr <= n) - {cerr < i) cerr <<'\n' < i) std::cerr <<'\n' <(adler); -// cerr <<"Z a32 sz=" <= 0) std::cout < Date: Thu, 6 Apr 2023 15:45:41 +0200 Subject: [PATCH 052/442] [XrdApps] Let XrdClProxyPlugin work with PgRead, adding some missing methods --- .../XrdClProxyPlugin/ProxyPrefixFile.hh | 101 +++++++++++++++--- 1 file changed, 87 insertions(+), 14 deletions(-) diff --git a/src/XrdApps/XrdClProxyPlugin/ProxyPrefixFile.hh b/src/XrdApps/XrdClProxyPlugin/ProxyPrefixFile.hh index 730d5c038c9..b0b381a09bb 100644 --- a/src/XrdApps/XrdClProxyPlugin/ProxyPrefixFile.hh +++ b/src/XrdApps/XrdClProxyPlugin/ProxyPrefixFile.hh @@ -26,6 +26,8 @@ #include "XrdCl/XrdClDefaultEnv.hh" #include "XrdCl/XrdClPlugInInterface.hh" +#include + using namespace XrdCl; namespace xrdcl_proxy @@ -45,7 +47,7 @@ public: //---------------------------------------------------------------------------- //! Destructor //---------------------------------------------------------------------------- - virtual ~ProxyPrefixFile(); + virtual ~ProxyPrefixFile() override; //---------------------------------------------------------------------------- //! Open @@ -54,13 +56,13 @@ public: OpenFlags::Flags flags, Access::Mode mode, ResponseHandler* handler, - uint16_t timeout); + uint16_t timeout) override; //---------------------------------------------------------------------------- //! Close //---------------------------------------------------------------------------- virtual XRootDStatus Close(ResponseHandler* handler, - uint16_t timeout) + uint16_t timeout) override { return pFile->Close(handler, timeout); } @@ -70,7 +72,7 @@ public: //---------------------------------------------------------------------------- virtual XRootDStatus Stat(bool force, ResponseHandler* handler, - uint16_t timeout) + uint16_t timeout) override { return pFile->Stat(force, handler, timeout); } @@ -83,11 +85,23 @@ public: uint32_t size, void* buffer, ResponseHandler* handler, - uint16_t timeout) + uint16_t timeout) override { return pFile->Read(offset, size, buffer, handler, timeout); } + //------------------------------------------------------------------------ + //! PgRead + //------------------------------------------------------------------------ + virtual XRootDStatus PgRead( uint64_t offset, + uint32_t size, + void *buffer, + ResponseHandler *handler, + uint16_t timeout ) override + { + return pFile->PgRead(offset, size, buffer, handler, timeout); + } + //---------------------------------------------------------------------------- //! Write //---------------------------------------------------------------------------- @@ -95,16 +109,53 @@ public: uint32_t size, const void* buffer, ResponseHandler* handler, - uint16_t timeout) + uint16_t timeout) override { return pFile->Write(offset, size, buffer, handler, timeout); } + //------------------------------------------------------------------------ + //! Write + //------------------------------------------------------------------------ + virtual XRootDStatus Write( uint64_t offset, + Buffer &&buffer, + ResponseHandler *handler, + uint16_t timeout = 0 ) override + { + return pFile->Write(offset, std::move(buffer), handler, timeout); + } + + //------------------------------------------------------------------------ + //! Write + //------------------------------------------------------------------------ + virtual XRootDStatus Write( uint64_t offset, + uint32_t size, + Optional fdoff, + int fd, + ResponseHandler *handler, + uint16_t timeout = 0 ) override + { + return pFile->Write(offset, size, fdoff, fd, handler, timeout); + } + + //------------------------------------------------------------------------ + //! PgWrite + //------------------------------------------------------------------------ + virtual XRootDStatus PgWrite( uint64_t offset, + uint32_t nbpgs, + const void *buffer, + std::vector &cksums, + ResponseHandler *handler, + uint16_t timeout ) override + { + return pFile->PgWrite(offset, nbpgs, buffer, cksums, handler, timeout); + } + //---------------------------------------------------------------------------- //! Sync //---------------------------------------------------------------------------- virtual XRootDStatus Sync(ResponseHandler* handler, - uint16_t timeout) + uint16_t timeout) override { return pFile->Sync(handler, timeout); } @@ -114,7 +165,7 @@ public: //---------------------------------------------------------------------------- virtual XRootDStatus Truncate(uint64_t size, ResponseHandler* handler, - uint16_t timeout) + uint16_t timeout) override { return pFile->Truncate(size, handler, timeout); } @@ -125,17 +176,39 @@ public: virtual XRootDStatus VectorRead(const ChunkList& chunks, void* buffer, ResponseHandler* handler, - uint16_t timeout) + uint16_t timeout) override { return pFile->VectorRead(chunks, buffer, handler, timeout); } + //------------------------------------------------------------------------ + //! VectorWrite + //------------------------------------------------------------------------ + virtual XRootDStatus VectorWrite( const ChunkList &chunks, + ResponseHandler *handler, + uint16_t timeout = 0 ) override + { + return pFile->VectorWrite(chunks, handler, timeout); + } + + //------------------------------------------------------------------------ + //! @see XrdCl::File::WriteV + //------------------------------------------------------------------------ + virtual XRootDStatus WriteV( uint64_t offset, + const struct iovec *iov, + int iovcnt, + ResponseHandler *handler, + uint16_t timeout = 0 ) override + { + return pFile->WriteV(offset, iov, iovcnt, handler, timeout); + } + //---------------------------------------------------------------------------- //! Fcntl //---------------------------------------------------------------------------- virtual XRootDStatus Fcntl(const Buffer& arg, ResponseHandler* handler, - uint16_t timeout) + uint16_t timeout) override { return pFile->Fcntl(arg, handler, timeout); } @@ -144,7 +217,7 @@ public: //! Visa //---------------------------------------------------------------------------- virtual XRootDStatus Visa(ResponseHandler* handler, - uint16_t timeout) + uint16_t timeout) override { return pFile->Visa(handler, timeout); } @@ -152,7 +225,7 @@ public: //---------------------------------------------------------------------------- //! IsOpen //---------------------------------------------------------------------------- - virtual bool IsOpen() const + virtual bool IsOpen() const override { return pFile->IsOpen(); } @@ -161,7 +234,7 @@ public: //! SetProperty //---------------------------------------------------------------------------- virtual bool SetProperty(const std::string& name, - const std::string& value) + const std::string& value) override { return pFile->SetProperty(name, value); } @@ -170,7 +243,7 @@ public: //! GetProperty //---------------------------------------------------------------------------- virtual bool GetProperty(const std::string& name, - std::string& value) const + std::string& value) const override { return pFile->GetProperty(name, value); } From 60db11374693bae16435c3247b7e647bb04e4af6 Mon Sep 17 00:00:00 2001 From: David Smith Date: Tue, 11 Apr 2023 17:38:38 +0200 Subject: [PATCH 053/442] [Server] Allow XrdXrootdFile::Serialize() to used to wait more than once --- src/XrdXrootd/XrdXrootdFile.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/XrdXrootd/XrdXrootdFile.cc b/src/XrdXrootd/XrdXrootdFile.cc index f7fb1a62465..ad895fdadac 100644 --- a/src/XrdXrootd/XrdXrootdFile.cc +++ b/src/XrdXrootd/XrdXrootdFile.cc @@ -170,7 +170,10 @@ void XrdXrootdFile::Ref(int num) fileMutex.Lock(); refCount += num; TRACEI(FSAIO,"File::Ref="<Post(); + if (num < 0 && syncWait && refCount <= 0) + {syncWait->Post(); + syncWait = nullptr; + } fileMutex.UnLock(); } From 081f2334bbb39dba92eb55b9a46d0105c15dccbd Mon Sep 17 00:00:00 2001 From: David Smith Date: Tue, 7 Mar 2023 17:28:39 +0100 Subject: [PATCH 054/442] [XrdCl] Avoid possibility of Channel unregistering the wrong task --- src/XrdCl/XrdClChannel.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/src/XrdCl/XrdClChannel.cc b/src/XrdCl/XrdClChannel.cc index 5f6b0efa3b9..c968040cfe4 100644 --- a/src/XrdCl/XrdClChannel.cc +++ b/src/XrdCl/XrdClChannel.cc @@ -132,7 +132,6 @@ namespace XrdCl Channel::~Channel() { pTickGenerator->Invalidate(); - pTaskManager->UnregisterTask( pTickGenerator ); delete pStream; pTransport->FinalizeChannel( pChannelData ); } From 51c0f5be07544bafabb56d89228c4c668f99aea2 Mon Sep 17 00:00:00 2001 From: David Smith Date: Fri, 14 Apr 2023 17:00:20 +0200 Subject: [PATCH 055/442] [Server] Correct the fh returned when reusing a handle from external table --- src/XrdXrootd/XrdXrootdFile.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/XrdXrootd/XrdXrootdFile.cc b/src/XrdXrootd/XrdXrootdFile.cc index ad895fdadac..deab025b376 100644 --- a/src/XrdXrootd/XrdXrootdFile.cc +++ b/src/XrdXrootd/XrdXrootdFile.cc @@ -218,6 +218,7 @@ int XrdXrootdFileTable::Add(XrdXrootdFile *fp) else {i -= XRD_FTABSIZE; if (XTab && i < XTnum) fP = &XTab[i]; else fP = 0; + i += XRD_FTABSIZE; } if (fP && *fP == heldSpotP) {*fP = fp; From 6a493d7881d1e8df40d6f94bc93a668f92d8de9e Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 12 Apr 2023 16:16:36 +0200 Subject: [PATCH 056/442] Fix Clang -Winconsistent-missing-override warnings Example warning: In file included from .../XrdXrootdAioTask.cc:42: src/XrdXrootd/XrdXrootdAioBuff.hh:51:25: warning: 'Recycle' overrides a member function but is not marked 'override' [-Winconsistent-missing-override] virtual void Recycle(); ^ src/XrdSfs/XrdSfsAio.hh:79:14: note: overridden virtual function is here virtual void Recycle() = 0; --- src/XrdMacaroons/XrdMacaroonsAuthz.hh | 2 +- src/XrdPosix/XrdPosixFile.hh | 2 +- src/XrdPss/XrdPss.hh | 34 +++++++++++++-------------- src/XrdXrootd/XrdXrootdAioBuff.hh | 2 +- src/XrdXrootd/XrdXrootdProtocol.hh | 20 ++++++++-------- 5 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/XrdMacaroons/XrdMacaroonsAuthz.hh b/src/XrdMacaroons/XrdMacaroonsAuthz.hh index acf88bc9c73..5ab52df1576 100644 --- a/src/XrdMacaroons/XrdMacaroonsAuthz.hh +++ b/src/XrdMacaroons/XrdMacaroonsAuthz.hh @@ -43,7 +43,7 @@ public: // Macaroons don't have a concept off an "issuers"; return an empty // list. - virtual Issuers IssuerList() {return Issuers();} + virtual Issuers IssuerList() override {return Issuers();} private: XrdAccPrivs OnMissing(const XrdSecEntity *Entity, diff --git a/src/XrdPosix/XrdPosixFile.hh b/src/XrdPosix/XrdPosixFile.hh index 4ca9679f233..6d99cf46489 100644 --- a/src/XrdPosix/XrdPosixFile.hh +++ b/src/XrdPosix/XrdPosixFile.hh @@ -166,7 +166,7 @@ inline void UpdtSize(size_t newsz) using XrdPosixObject::Who; -inline bool Who(XrdPosixFile **fileP) +inline bool Who(XrdPosixFile **fileP) override {*fileP = this; return true;} int Write(char *Buff, long long Offs, int Len) override; diff --git a/src/XrdPss/XrdPss.hh b/src/XrdPss/XrdPss.hh index 798312a727e..631013ea6f5 100644 --- a/src/XrdPss/XrdPss.hh +++ b/src/XrdPss/XrdPss.hh @@ -144,34 +144,34 @@ struct XrdVersionInfo; class XrdPssSys : public XrdOss { public: -virtual XrdOssDF *newDir(const char *tident) +virtual XrdOssDF *newDir(const char *tident) override {return (XrdOssDF *)new XrdPssDir(tident);} -virtual XrdOssDF *newFile(const char *tident) +virtual XrdOssDF *newFile(const char *tident) override {return (XrdOssDF *)new XrdPssFile(tident);} -virtual void Connect(XrdOucEnv &); +virtual void Connect(XrdOucEnv &) override; -virtual void Disc(XrdOucEnv &); +virtual void Disc(XrdOucEnv &) override; -int Chmod(const char *, mode_t mode, XrdOucEnv *eP=0); +int Chmod(const char *, mode_t mode, XrdOucEnv *eP=0) override; bool ConfigMapID(); virtual -int Create(const char *, const char *, mode_t, XrdOucEnv &, int opts=0); -void EnvInfo(XrdOucEnv *envP); -uint64_t Features() {return myFeatures;} +int Create(const char *, const char *, mode_t, XrdOucEnv &, int opts=0) override; +void EnvInfo(XrdOucEnv *envP) override; +uint64_t Features() override {return myFeatures;} int Init(XrdSysLogger *, const char *) override {return -ENOTSUP;} int Init(XrdSysLogger *, const char *, XrdOucEnv *envP) override; -int Lfn2Pfn(const char *Path, char *buff, int blen); +int Lfn2Pfn(const char *Path, char *buff, int blen) override; const -char *Lfn2Pfn(const char *Path, char *buff, int blen, int &rc); -int Mkdir(const char *, mode_t mode, int mkpath=0, XrdOucEnv *eP=0); -int Remdir(const char *, int Opts=0, XrdOucEnv *eP=0); +char *Lfn2Pfn(const char *Path, char *buff, int blen, int &rc) override; +int Mkdir(const char *, mode_t mode, int mkpath=0, XrdOucEnv *eP=0) override; +int Remdir(const char *, int Opts=0, XrdOucEnv *eP=0) override; int Rename(const char *, const char *, - XrdOucEnv *eP1=0, XrdOucEnv *eP2=0); -int Stat(const char *, struct stat *, int opts=0, XrdOucEnv *eP=0); -int Stats(char *bp, int bl); -int Truncate(const char *, unsigned long long, XrdOucEnv *eP=0); -int Unlink(const char *, int Opts=0, XrdOucEnv *eP=0); + XrdOucEnv *eP1=0, XrdOucEnv *eP2=0) override; +int Stat(const char *, struct stat *, int opts=0, XrdOucEnv *eP=0) override; +int Stats(char *bp, int bl) override; +int Truncate(const char *, unsigned long long, XrdOucEnv *eP=0) override; +int Unlink(const char *, int Opts=0, XrdOucEnv *eP=0) override; static const int PolNum = 2; enum PolAct {PolPath = 0, PolObj = 1}; diff --git a/src/XrdXrootd/XrdXrootdAioBuff.hh b/src/XrdXrootd/XrdXrootdAioBuff.hh index 2d2209741b4..174433dd1e3 100644 --- a/src/XrdXrootd/XrdXrootdAioBuff.hh +++ b/src/XrdXrootd/XrdXrootdAioBuff.hh @@ -48,7 +48,7 @@ XrdXrootdAioBuff* Alloc(XrdXrootdAioTask *arp); void doneWrite() override; -virtual void Recycle(); +virtual void Recycle() override; XrdXrootdAioBuff* next; diff --git a/src/XrdXrootd/XrdXrootdProtocol.hh b/src/XrdXrootd/XrdXrootdProtocol.hh index 162a30e5925..2ae6e5b343f 100644 --- a/src/XrdXrootd/XrdXrootdProtocol.hh +++ b/src/XrdXrootd/XrdXrootdProtocol.hh @@ -163,11 +163,11 @@ public: static char *Buffer(XrdSfsXioHandle h, int *bsz); // XrdSfsXio -XrdSfsXioHandle Claim(const char *buff, int datasz, int minasz=0);// XrdSfsXio +XrdSfsXioHandle Claim(const char *buff, int datasz, int minasz=0) override;// XrdSfsXio static int Configure(char *parms, XrdProtocol_Config *pi); - void DoIt() {(*this.*Resume)();} + void DoIt() override {(*this.*Resume)();} int do_WriteSpan(); @@ -181,29 +181,29 @@ static int Configure(char *parms, XrdProtocol_Config *pi); int getPathID() {return PathID;} - XrdProtocol *Match(XrdLink *lp); + XrdProtocol *Match(XrdLink *lp) override; - int Process(XrdLink *lp); // Sync: Job->Link.DoIt->Process + int Process(XrdLink *lp) override; // Sync: Job->Link.DoIt->Process int Process2(); int ProcSig(); - void Recycle(XrdLink *lp, int consec, const char *reason); + void Recycle(XrdLink *lp, int consec, const char *reason) override; static void Reclaim(XrdSfsXioHandle h); // XrdSfsXio - int SendFile(int fildes); // XrdSfsDio + int SendFile(int fildes) override; // XrdSfsDio - int SendFile(XrdOucSFVec *sfvec, int sfvnum); // XrdSfsDio + int SendFile(XrdOucSFVec *sfvec, int sfvnum) override; // XrdSfsDio - void SetFD(int fildes); // XrdSfsDio + void SetFD(int fildes) override; // XrdSfsDio - int Stats(char *buff, int blen, int do_sync=0); + int Stats(char *buff, int blen, int do_sync=0) override; void StreamNOP(); -XrdSfsXioHandle Swap(const char *buff, XrdSfsXioHandle h=0); // XrdSfsXio +XrdSfsXioHandle Swap(const char *buff, XrdSfsXioHandle h=0) override; // XrdSfsXio XrdXrootdProtocol *VerifyStream(int &rc, int pID, bool lok=true); From 422b1230b72ac89ed63b490fb63dbb245363766b Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 12 Apr 2023 16:29:44 +0200 Subject: [PATCH 057/442] Fix Clang -Wbraced-scalar-init warning src/XrdXrootd/XrdXrootdProtocol.cc:1504:25: warning: braces around scalar initializer [-Wbraced-scalar-init] linkAioReq = {0}; --- src/XrdXrootd/XrdXrootdProtocol.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdXrootd/XrdXrootdProtocol.cc b/src/XrdXrootd/XrdXrootdProtocol.cc index ddc872fd7e0..2e027b393c1 100644 --- a/src/XrdXrootd/XrdXrootdProtocol.cc +++ b/src/XrdXrootd/XrdXrootdProtocol.cc @@ -1501,7 +1501,7 @@ void XrdXrootdProtocol::Reset() doTLS = tlsNot; // Assume client is not capable. This will be ableTLS = false; // resolved during the kXR_protocol interchange. isTLS = false; // Made true when link converted to TLS - linkAioReq = {0}; + linkAioReq = 0; pioFree = pioFirst = pioLast = 0; isActive = isLinkWT= isNOP = isDead = false; sigNeed = sigHere = sigRead = false; From d5bc44972c6d2dbfd865be11ea5be572fe6d78f7 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 21 Mar 2023 11:31:44 +0100 Subject: [PATCH 058/442] [XrdPosix] Fix Clang -Wtautological-constant-out-of-range-compare warning src/XrdPosix/XrdPosixAdmin.cc:71:10: warning: result of comparison of constant 32940614417338485 with expression of type 'unsigned int' is always false [-Wtautological-constant-out-of-range-compare] if (i > std::numeric_limits::max() / sizeof(XrdCl::URL)) ~ ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Here std::numeric_limits::max() / sizeof(XrdCl::URL) == 32940614417338485, which is bigger than the largest unsigned int, therefore the comparison will always be false. We need i to be able to hold large enough values. Fixes 0dc292fbf63d25c6a9e000a2e66d7e3e0b8db735. --- src/XrdPosix/XrdPosixAdmin.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdPosix/XrdPosixAdmin.cc b/src/XrdPosix/XrdPosixAdmin.cc index 3b7e9810353..5e37e72a267 100644 --- a/src/XrdPosix/XrdPosixAdmin.cc +++ b/src/XrdPosix/XrdPosixAdmin.cc @@ -51,7 +51,7 @@ XrdCl::URL *XrdPosixAdmin::FanOut(int &num) XrdCl::URL *uVec; XrdNetAddr netLoc; const char *hName; - unsigned int i; + unsigned long i; // Make sure admin is ok // From 0a8ce19a9f03994ddc2ab3769e32e75e525df637 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 21 Mar 2023 11:31:21 +0100 Subject: [PATCH 059/442] [XrdSecgsi] Fix Clang -Wfortify-source warning src/XrdSecgsi/XrdSecgsiGMAPFunDN.cc:207:40: warning: 'sscanf' may overflow; destination buffer in argument 3 has size 4096, but the corresponding specifier may require size 4097 [-Wfortify-source] if (sscanf(l, "%4096s %256s", val, usr) >= 2) { ^ --- src/XrdSecgsi/XrdSecgsiGMAPFunDN.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdSecgsi/XrdSecgsiGMAPFunDN.cc b/src/XrdSecgsi/XrdSecgsiGMAPFunDN.cc index c89d3bc4bda..99bd139093d 100644 --- a/src/XrdSecgsi/XrdSecgsiGMAPFunDN.cc +++ b/src/XrdSecgsi/XrdSecgsiGMAPFunDN.cc @@ -204,7 +204,7 @@ int XrdSecgsiGMAPInit(const char *parms) if (len < 2) continue; if (l[0] == '#') continue; if (l[len-1] == '\n') l[len-1] = '\0'; - if (sscanf(l, "%4096s %256s", val, usr) >= 2) { + if (sscanf(l, "%4095s %255s", val, usr) >= 2) { XrdOucString stype = "matching"; char *p = &val[0]; int type = kFull; From 590a9fdac768888a45576478031e048b0bf97f46 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 12 Apr 2023 16:39:47 +0200 Subject: [PATCH 060/442] [Tests] Fix typo in FileCopy test Caught by a warning in Clang: tests/XrdClTests/FileCopyTest.cc:400:62: warning: variable 'st' is uninitialized when used within its own initialization [-Wuninitialized] CPPUNIT_ASSERT_XRDST( status.status == XrdCl::stError && st.code == XrdCl::errNotFound ); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ tests/XrdClTests/../common/CppUnitXrdHelpers.hh:36:28: note: expanded from macro 'CPPUNIT_ASSERT_XRDST' XrdCl::XRootDStatus st = x; --- tests/XrdClTests/FileCopyTest.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/XrdClTests/FileCopyTest.cc b/tests/XrdClTests/FileCopyTest.cc index 5c9c8de4d57..90e6845e795 100644 --- a/tests/XrdClTests/FileCopyTest.cc +++ b/tests/XrdClTests/FileCopyTest.cc @@ -397,7 +397,7 @@ void FileCopyTest::CopyTestFunc( bool thirdParty ) CPPUNIT_ASSERT_XRDST_NOTOK( process12.Run(0), XrdCl::errCheckSumError ); XrdCl::StatInfo *info = 0; XrdCl::XRootDStatus status = fs.Stat( targetPath, info ); - CPPUNIT_ASSERT_XRDST( status.status == XrdCl::stError && st.code == XrdCl::errNotFound ); + CPPUNIT_ASSERT_XRDST( status.status == XrdCl::stError && status.code == XrdCl::errNotFound ); properties.Clear(); //-------------------------------------------------------------------------- From a5941254a10264cbd7a35dd31306873d92bdaf82 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 12 Apr 2023 16:40:11 +0200 Subject: [PATCH 061/442] [Tests] Change name of macro local variable to avoid clashes The name st is used extensively throughout the code, so using the same name here can cause shadowing problems or hide typos like the one fixed in the previous commit. --- tests/common/CppUnitXrdHelpers.hh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/common/CppUnitXrdHelpers.hh b/tests/common/CppUnitXrdHelpers.hh index 99925bb2726..b4634433133 100644 --- a/tests/common/CppUnitXrdHelpers.hh +++ b/tests/common/CppUnitXrdHelpers.hh @@ -25,18 +25,18 @@ #define CPPUNIT_ASSERT_XRDST_NOTOK( x, err ) \ { \ - XrdCl::XRootDStatus st = x; \ + XrdCl::XRootDStatus _st = x; \ std::string msg = "["; msg += #x; msg += "]: "; \ - msg += st.ToStr(); \ - CPPUNIT_ASSERT_MESSAGE( msg, !st.IsOK() && st.code == err ); \ + msg += _st.ToStr(); \ + CPPUNIT_ASSERT_MESSAGE( msg, !_st.IsOK() && _st.code == err ); \ } #define CPPUNIT_ASSERT_XRDST( x ) \ { \ - XrdCl::XRootDStatus st = x; \ + XrdCl::XRootDStatus _st = x; \ std::string msg = "["; msg += #x; msg += "]: "; \ - msg += st.ToStr(); \ - CPPUNIT_ASSERT_MESSAGE( msg, st.IsOK() ); \ + msg += _st.ToStr(); \ + CPPUNIT_ASSERT_MESSAGE( msg, _st.IsOK() ); \ } #define CPPUNIT_ASSERT_ERRNO( x ) \ From 0f9aa1ef888bb3589ee5f9cb297098d19954762c Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 13 Apr 2023 17:57:40 +0200 Subject: [PATCH 062/442] [XrdPosix] Fix build with Clang and _FORTIFY_SOURCE enabled Some of the checks added by defining _FORTIFY_SOURCE break the build with clang with errors like the one shown below. See the manual page for feature_test_macros(7) for more information. xrootd/src/XrdPosix/XrdPosixPreload32.cc:375:9: error: redefinition of a 'extern inline' function 'pread' is not supported in C++ ssize_t pread(int fildes, void *buf, size_t nbyte, off_t offset) ^ /usr/include/bits/unistd.h:72:1: note: previous definition is here pread (int __fd, void *__buf, size_t __nbytes, __off_t __offset) ^ Since distributions enable _FORTIFY_SOURCE by default, it's better to disable it for the XrdPosix plugin when using clang. Builds with GCC are not affected by this problem. Fixes #1975. --- src/XrdPosix/XrdPosixPreload.cc | 4 ++++ src/XrdPosix/XrdPosixPreload32.cc | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/XrdPosix/XrdPosixPreload.cc b/src/XrdPosix/XrdPosixPreload.cc index e01bc8a2301..8db1400d485 100644 --- a/src/XrdPosix/XrdPosixPreload.cc +++ b/src/XrdPosix/XrdPosixPreload.cc @@ -28,6 +28,10 @@ /* specific prior written permission of the institution or contributor. */ /******************************************************************************/ +#if defined(__clang__) && defined(_FORTIFY_SOURCE) +#undef _FORTIFY_SOURCE +#endif + #include #include #include diff --git a/src/XrdPosix/XrdPosixPreload32.cc b/src/XrdPosix/XrdPosixPreload32.cc index 7280dd2ec13..436e23dc2bb 100644 --- a/src/XrdPosix/XrdPosixPreload32.cc +++ b/src/XrdPosix/XrdPosixPreload32.cc @@ -28,6 +28,10 @@ /* specific prior written permission of the institution or contributor. */ /******************************************************************************/ +#if defined(__clang__) && defined(_FORTIFY_SOURCE) +#undef _FORTIFY_SOURCE +#endif + #ifdef _LARGEFILE_SOURCE #undef _LARGEFILE_SOURCE #endif From 3c6e6b397d6a080457a29d7c45d26d9b20d174d6 Mon Sep 17 00:00:00 2001 From: Gerardo Ganis Date: Mon, 17 Apr 2023 18:30:54 +0200 Subject: [PATCH 063/442] Remove unused header --- src/XrdCrypto/XrdCryptosslgsiAux.hh | 102 ---------------------------- src/XrdHeaders.cmake | 1 - src/XrdUtils.cmake | 2 +- 3 files changed, 1 insertion(+), 104 deletions(-) delete mode 100644 src/XrdCrypto/XrdCryptosslgsiAux.hh diff --git a/src/XrdCrypto/XrdCryptosslgsiAux.hh b/src/XrdCrypto/XrdCryptosslgsiAux.hh deleted file mode 100644 index aac394370e3..00000000000 --- a/src/XrdCrypto/XrdCryptosslgsiAux.hh +++ /dev/null @@ -1,102 +0,0 @@ -#ifndef __CRYPTO_SSLGSIAUX_H__ -#define __CRYPTO_SSLGSIAUX_H__ -/******************************************************************************/ -/* */ -/* X r d C r y p t o s s l g s i A u x . h h */ -/* */ -/* (c) 2005, G. Ganis / CERN */ -/* */ -/* This file is part of the XRootD software suite. */ -/* */ -/* XRootD is free software: you can redistribute it and/or modify it under */ -/* the terms of the GNU Lesser General Public License as published by the */ -/* Free Software Foundation, either version 3 of the License, or (at your */ -/* option) any later version. */ -/* */ -/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ -/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ -/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ -/* License for more details. */ -/* */ -/* You should have received a copy of the GNU Lesser General Public License */ -/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ -/* COPYING (GPL license). If not, see . */ -/* */ -/* The copyright holder's institutional names and contributor's names may not */ -/* be used to endorse or promote products derived from this software without */ -/* specific prior written permission of the institution or contributor. */ -/* */ -/******************************************************************************/ - -/* ************************************************************************** */ -/* */ -/* GSI utility functions */ -/* */ -/* ************************************************************************** */ -#include "XrdCrypto/XrdCryptosslgsiX509Chain.hh" -#include "XrdCrypto/XrdCryptoX509Req.hh" -#include "XrdCrypto/XrdCryptoRSA.hh" -#include "XrdOuc/XrdOucString.hh" - -// The OID of the extension -#define gsiProxyCertInfo_OLD_OID "1.3.6.1.4.1.3536.1.222" -#define gsiProxyCertInfo_OID "1.3.6.1.5.5.7.1.14" - -// -// Function to check presence of a proxyCertInfo and retrieve the path length -// constraint. Written following RFC3820 and examples in openssl-/crypto -// source code. Extracts the policy field but ignores it contents. -bool XrdSslgsiProxyCertInfo(const void *ext, int &pathlen, bool *haspolicy = 0); -void XrdSslgsiSetPathLenConstraint(void *ext, int pathlen); - -// -// Proxies -// -typedef struct { - int bits; // Number of bits in the RSA key [512] - int valid; // Duration validity in secs [43200 (12 hours)] - int depthlen; // Maximum depth of the path of proxy certificates - // that can signed by this proxy certificates - // [-1 (== unlimited)] -} XrdProxyOpt_t; -// -// Create proxy certificates -int XrdSslgsiX509CreateProxy(const char *, const char *, XrdProxyOpt_t *, - XrdCryptosslgsiX509Chain *, XrdCryptoRSA **, const char *); -// -// Create a proxy certificate request -int XrdSslgsiX509CreateProxyReq(XrdCryptoX509 *, - XrdCryptoX509Req **, XrdCryptoRSA **); -// -// Sign a proxy certificate request -int XrdSslgsiX509SignProxyReq(XrdCryptoX509 *, XrdCryptoRSA *, - XrdCryptoX509Req *, XrdCryptoX509 **); -// -// Dump extensions -int XrdSslgsiX509DumpExtensions(XrdCryptoX509 *); -// -// Get VOMS attributes, if any -int XrdSslgsiX509GetVOMSAttr(XrdCryptoX509 *, XrdOucString &); -// -// Check GSI 3 proxy info extension -int XrdSslgsiX509CheckProxy3(XrdCryptoX509 *, XrdOucString &); - -/******************************************************************************/ -/* E r r o r s i n P r o x y M a n i p u l a t i o n s */ -/******************************************************************************/ -#define kErrPX_Error 1 // Generic error condition -#define kErrPX_BadEECfile 2 // Absent or bad EEC cert or key file -#define kErrPX_BadEECkey 3 // Inconsistent EEC key -#define kErrPX_ExpiredEEC 4 // EEC is expired -#define kErrPX_NoResources 5 // Unable to create new objects -#define kErrPX_SetAttribute 6 // Unable to set a certificate attribute -#define kErrPX_SetPathDepth 7 // Unable to set path depth -#define kErrPX_Signing 8 // Problems signing -#define kErrPX_GenerateKey 9 // Problem generating the RSA key -#define kErrPX_ProxyFile 10 // Problem creating / updating proxy file -#define kErrPX_BadNames 11 // Names in certificates are bad -#define kErrPX_BadSerial 12 // Problems resolving serial number -#define kErrPX_BadExtension 13 // Problems with the extensions - -#endif - diff --git a/src/XrdHeaders.cmake b/src/XrdHeaders.cmake index ee1db07411a..0570d07b7b2 100644 --- a/src/XrdHeaders.cmake +++ b/src/XrdHeaders.cmake @@ -175,7 +175,6 @@ if( NOT XRDCL_ONLY ) XrdCrypto/XrdCryptoX509Crl.hh XrdCrypto/XrdCryptoX509Req.hh XrdCrypto/XrdCryptoRSA.hh - XrdCrypto/XrdCryptosslgsiAux.hh XrdSut/XrdSutAux.hh XrdSut/XrdSutBucket.hh diff --git a/src/XrdUtils.cmake b/src/XrdUtils.cmake index 05b4146d92b..025b800f295 100644 --- a/src/XrdUtils.cmake +++ b/src/XrdUtils.cmake @@ -83,7 +83,7 @@ set ( XrdCryptoSources XrdCrypto/XrdCryptoX509Chain.cc XrdCrypto/XrdCryptoX509Chain.hh XrdCrypto/XrdCryptosslRSA.cc XrdCrypto/XrdCryptosslRSA.hh XrdCrypto/XrdCryptoRSA.cc XrdCrypto/XrdCryptoRSA.hh - XrdCrypto/XrdCryptosslgsiAux.cc XrdCrypto/XrdCryptosslgsiAux.hh + XrdCrypto/XrdCryptosslgsiAux.cc XrdCrypto/XrdCryptosslX509Req.cc XrdCrypto/XrdCryptosslX509Req.hh XrdCrypto/XrdCryptoX509Req.cc XrdCrypto/XrdCryptoX509Req.hh XrdCrypto/XrdCryptoAux.cc XrdCrypto/XrdCryptoAux.hh From d402227505c1fc407c2c4e2f23c0afbc335fe68b Mon Sep 17 00:00:00 2001 From: Gerardo Ganis Date: Mon, 17 Apr 2023 18:33:45 +0200 Subject: [PATCH 064/442] Use consistently SHA-256 for signatures (fix for issue #1992) --- src/XrdCrypto/XrdCryptosslgsiAux.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/XrdCrypto/XrdCryptosslgsiAux.cc b/src/XrdCrypto/XrdCryptosslgsiAux.cc index 09edd44229d..cef86cb1f0f 100644 --- a/src/XrdCrypto/XrdCryptosslgsiAux.cc +++ b/src/XrdCrypto/XrdCryptosslgsiAux.cc @@ -455,7 +455,7 @@ int XrdCryptosslX509CreateProxy(const char *fnc, const char *fnk, } // // Sign the request - if (!(X509_REQ_sign(preq, ekPX, EVP_sha1()))) { + if (!(X509_REQ_sign(preq, ekPX, EVP_sha256()))) { PRINT("problems signing the request"); return -kErrPX_Signing; } @@ -549,7 +549,7 @@ int XrdCryptosslX509CreateProxy(const char *fnc, const char *fnk, // // Sign the certificate - if (!(X509_sign(xPX, ekEEC, EVP_sha1()))) { + if (!(X509_sign(xPX, ekEEC, EVP_sha256()))) { PRINT("problems signing the certificate"); return -kErrPX_Signing; } @@ -851,7 +851,7 @@ int XrdCryptosslX509CreateProxyReq(XrdCryptoX509 *xcpi, } // // Sign the request - if (!(X509_REQ_sign(xro, ekro, EVP_sha1()))) { + if (!(X509_REQ_sign(xro, ekro, EVP_sha256()))) { PRINT("problems signing the request"); return -kErrPX_Signing; } @@ -1155,7 +1155,7 @@ int XrdCryptosslX509SignProxyReq(XrdCryptoX509 *xcpi, XrdCryptoRSA *kcpi, // // Sign the certificate - if (!(X509_sign(xpo, ekpi, EVP_sha1()))) { + if (!(X509_sign(xpo, ekpi, EVP_sha256()))) { PRINT("problems signing the certificate"); return -kErrPX_Signing; } From 952bd9ad6c52557a85e6ebeb48ae1383709c24a6 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Tue, 25 Apr 2023 22:03:53 -0700 Subject: [PATCH 065/442] [Xcache] Implement a file evict function. --- src/XrdOfs/XrdOfs.cc | 1 + src/XrdOfs/XrdOfs.hh | 1 + src/XrdOfs/XrdOfsConfig.cc | 22 +++++ src/XrdOfs/XrdOfsFSctl.cc | 16 +++- src/XrdPfc.cmake | 1 + src/XrdPfc/XrdPfc.cc | 4 + src/XrdPfc/XrdPfcFSctl.cc | 138 +++++++++++++++++++++++++++++ src/XrdPfc/XrdPfcFSctl.hh | 87 ++++++++++++++++++ src/XrdPss/XrdPss.hh | 2 + src/XrdSfs/XrdSfsInterface.hh | 10 ++- src/XrdXrootd/XrdXrootdProtocol.hh | 1 + src/XrdXrootd/XrdXrootdXeq.cc | 45 ++++++++++ 12 files changed, 323 insertions(+), 5 deletions(-) create mode 100644 src/XrdPfc/XrdPfcFSctl.cc create mode 100644 src/XrdPfc/XrdPfcFSctl.hh diff --git a/src/XrdOfs/XrdOfs.cc b/src/XrdOfs/XrdOfs.cc index 03c644442b1..6d829977344 100644 --- a/src/XrdOfs/XrdOfs.cc +++ b/src/XrdOfs/XrdOfs.cc @@ -173,6 +173,7 @@ XrdOfs::XrdOfs() : dMask{0000,0775}, fMask{0000,0775}, // Legacy // Establish defaults // ofsConfig = 0; + FSctl_PC = 0; FSctl_PI = 0; Authorization = 0; Finder = 0; diff --git a/src/XrdOfs/XrdOfs.hh b/src/XrdOfs/XrdOfs.hh index 5e9fecde0e0..eeb065ed374 100644 --- a/src/XrdOfs/XrdOfs.hh +++ b/src/XrdOfs/XrdOfs.hh @@ -446,6 +446,7 @@ const char *Split(const char *Args, const char **Opq, char *Path, int Plen); private: char *myRole; +XrdOfsFSctl_PI *FSctl_PC; // ->FSctl plugin (cache specific) XrdOfsFSctl_PI *FSctl_PI; // ->FSctl plugin XrdAccAuthorize *Authorization; // ->Authorization Service XrdCmsClient *Balancer; // ->Cluster Local Interface diff --git a/src/XrdOfs/XrdOfsConfig.cc b/src/XrdOfs/XrdOfsConfig.cc index 79b5bb7b149..6c06ce93916 100644 --- a/src/XrdOfs/XrdOfsConfig.cc +++ b/src/XrdOfs/XrdOfsConfig.cc @@ -53,6 +53,7 @@ #include "XrdOfs/XrdOfsConfigCP.hh" #include "XrdOfs/XrdOfsConfigPI.hh" #include "XrdOfs/XrdOfsEvs.hh" +#include "XrdOfs/XrdOfsFSctl_PI.hh" #include "XrdOfs/XrdOfsPoscq.hh" #include "XrdOfs/XrdOfsStats.hh" #include "XrdOfs/XrdOfsTPC.hh" @@ -85,6 +86,8 @@ extern XrdOfsStats OfsStats; extern XrdSysTrace OfsTrace; + +extern XrdOfs* XrdOfsFS; class XrdOss; extern XrdOss *XrdOfsOss; @@ -292,6 +295,12 @@ int XrdOfs::Configure(XrdSysError &Eroute, XrdOucEnv *EnvInfo) { } } +// If a cache has been configured then that cache may want to interact with +// the cache-specific FSctl() operation. We check if a plugin was provided. +// + if (ossFeatures & XRDOSS_HASCACH) + FSctl_PC = (XrdOfsFSctl_PI*)EnvInfo->GetPtr("XrdFSCtl_PC*"); + // Configure third party copy phase 2, but only if we are not a manager. // if ((Options & ThirdPC) && !(Options & isManager)) NoGo |= ConfigTPC(Eroute); @@ -318,6 +327,19 @@ int XrdOfs::Configure(XrdSysError &Eroute, XrdOucEnv *EnvInfo) { NoGo = 1; } +// Initialize the cache FSctl handler if we have one. The same deferal applies. +// + if (FSctl_PC) + {struct XrdOfsFSctl_PI::Plugins thePI = {Authorization, Finder, + XrdOfsOss, XrdOfsFS}; + XrdOucEnv pcEnv; + pcEnv.PutPtr("XrdOfsHandle*", dummyHandle); + if (!FSctl_PC->Configure(ConfigFN, 0, &pcEnv, thePI)) + {Eroute.Emsg("Config", "Unable to configure cache FSctl handler."); + NoGo = 1; + } + } + // Initialize th Evr object if we are an actual server // if (!(Options & isManager) && !evrObject.Init(Balancer)) NoGo = 1; diff --git a/src/XrdOfs/XrdOfsFSctl.cc b/src/XrdOfs/XrdOfsFSctl.cc index 6a1bf8e6dab..527eb9eeb80 100644 --- a/src/XrdOfs/XrdOfsFSctl.cc +++ b/src/XrdOfs/XrdOfsFSctl.cc @@ -265,9 +265,21 @@ int XrdOfs::FSctl(const int cmd, XrdOucErrInfo &eInfo, const XrdSecEntity *client) { -// If we have a plugin to handle this, use it. + EPNAME("FSctl"); + +// If this is the cache-specfic we need to do a lot more work. Otherwise this +// is a simple case of wheter we have a plug-in for this or not. // - if (FSctl_PI) return FSctl_PI->FSctl(cmd, args, eInfo, client); + if (cmd == SFS_FSCTL_PLUGXC) + {if (FSctl_PC) + {if (args.Arg2Len == -2) + {XrdOucEnv pc_Env(args.ArgP[1] ? args.ArgP[1] : 0, 0, client); + AUTHORIZE(client,&pc_Env,AOP_Read,"FSctl",args.ArgP[0],eInfo); + } + return FSctl_PC->FSctl(cmd, args, eInfo, client); + } + } + else if (FSctl_PI) return FSctl_PI->FSctl(cmd, args, eInfo, client); // Operation is not supported // diff --git a/src/XrdPfc.cmake b/src/XrdPfc.cmake index 52fca1839c0..c09bc0efcb4 100644 --- a/src/XrdPfc.cmake +++ b/src/XrdPfc.cmake @@ -23,6 +23,7 @@ add_library( XrdPfc/XrdPfcPurge.cc XrdPfc/XrdPfcCommand.cc XrdPfc/XrdPfcFile.cc XrdPfc/XrdPfcFile.hh + XrdPfc/XrdPfcFSctl.cc XrdPfc/XrdPfcFSctl.hh XrdPfc/XrdPfcStats.hh XrdPfc/XrdPfcInfo.cc XrdPfc/XrdPfcInfo.hh XrdPfc/XrdPfcIO.cc XrdPfc/XrdPfcIO.hh diff --git a/src/XrdPfc/XrdPfc.cc b/src/XrdPfc/XrdPfc.cc index d6b7e261790..ab0fbcbf114 100644 --- a/src/XrdPfc/XrdPfc.cc +++ b/src/XrdPfc/XrdPfc.cc @@ -37,6 +37,7 @@ #include "XrdPfc.hh" #include "XrdPfcTrace.hh" +#include "XrdPfcFSctl.hh" #include "XrdPfcInfo.hh" #include "XrdPfcIOFile.hh" #include "XrdPfcIOFileBlock.hh" @@ -118,6 +119,9 @@ XrdOucCache *XrdOucGetCache(XrdSysLogger *logger, XrdSysThread::Run(&tid, PurgeThread, 0, 0, "XrdPfc Purge"); } + XrdPfcFSctl* pfcFSctl = new XrdPfcFSctl(instance, logger); + env->PutPtr("XrdFSCtl_PC*", pfcFSctl); + return &instance; } } diff --git a/src/XrdPfc/XrdPfcFSctl.cc b/src/XrdPfc/XrdPfcFSctl.cc new file mode 100644 index 00000000000..1a2f1f29952 --- /dev/null +++ b/src/XrdPfc/XrdPfcFSctl.cc @@ -0,0 +1,138 @@ +/******************************************************************************/ +/* */ +/* X r d P f c F S c t l . c c */ +/* */ +/* (c) 2023 by the Board of Trustees of the Leland Stanford, Jr., University */ +/* All Rights Reserved */ +/* Produced by Andrew Hanushevsky for Stanford University under contract */ +/* DE-AC02-76-SFO0515 with the Department of Energy */ +/* */ +/* This file is part of the XRootD software suite. */ +/* */ +/* XRootD is free software: you can redistribute it and/or modify it under */ +/* the terms of the GNU Lesser General Public License as published by the */ +/* Free Software Foundation, either version 3 of the License, or (at your */ +/* option) any later version. */ +/* */ +/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ +/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ +/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ +/* License for more details. */ +/* */ +/* You should have received a copy of the GNU Lesser General Public License */ +/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ +/* COPYING (GPL license). If not, see . */ +/* */ +/* The copyright holder's institutional names and contributor's names may not */ +/* be used to endorse or promote products derived from this software without */ +/* specific prior written permission of the institution or contributor. */ +/******************************************************************************/ + +#include +#include +#include + +#include "XrdOfs/XrdOfsHandle.hh" +#include "XrdOuc/XrdOucEnv.hh" +#include "XrdOuc/XrdOucErrInfo.hh" +#include "XrdPfc/XrdPfc.hh" +#include "XrdPfc/XrdPfcFSctl.hh" +#include "XrdPfc/XrdPfcTrace.hh" +#include "XrdSfs/XrdSfsInterface.hh" +#include "XrdSys/XrdSysTrace.hh" + +/******************************************************************************/ +/* C o n s t r u c t o r */ +/******************************************************************************/ + +XrdPfcFSctl::XrdPfcFSctl(XrdPfc::Cache &cInst, XrdSysLogger *logP) + : myCache(cInst), hProc(0), Log(logP, "PfcFsctl"), + sysTrace(cInst.GetTrace()), m_traceID("PfcFSctl") {} + +/******************************************************************************/ +/* C o n f i g u r e */ +/******************************************************************************/ + +bool XrdPfcFSctl::Configure(const char *CfgFN, + const char *Parms, + XrdOucEnv *envP, + const Plugins &plugs) +{ +// All we are interested in is getting the file handle handler pointer +// + hProc = (XrdOfsHandle*)envP->GetPtr("XrdOfsHandle*"); + return hProc != 0; +} + +/******************************************************************************/ +/* F S c t l [ F i l e ] */ +/******************************************************************************/ + +int XrdPfcFSctl::FSctl(const int cmd, + int alen, + const char *args, + XrdSfsFile &file, + XrdOucErrInfo &eInfo, + const XrdSecEntity *client) +{ + eInfo.setErrInfo(ENOTSUP, "File based fstcl not supported for a cache."); + return SFS_ERROR; +} + +/******************************************************************************/ +/* F S c t l [ B a s e ] */ +/******************************************************************************/ + +int XrdPfcFSctl::FSctl(const int cmd, + XrdSfsFSctl &args, + XrdOucErrInfo &eInfo, + const XrdSecEntity *client) +{ + const char *msg = "", *xeq = args.Arg1; + int ec, rc; + +// Verify command +// + if (cmd != SFS_FSCTL_PLUGXC) + {eInfo.setErrInfo(EIDRM, "None-cache command issued to a cache."); + return SFS_ERROR; + } + +// Very that we have a command +// + if (!xeq || args.Arg1Len < 1) + {eInfo.setErrInfo(EINVAL, "Missing cache command or argument."); + return SFS_ERROR; + } + +// Process command +// + if ((!strcmp(xeq, "evict") || !strcmp(xeq, "fevict")) && args.Arg2Len == -2) + {std::string path = args.ArgP[0]; + ec = myCache.UnlinkFile(path, *xeq != 'f'); + switch(ec) + {case 0: if (hProc) hProc->Hide(path.c_str()); + [[fallthrough]]; + case -ENOENT: rc = SFS_OK; + break; + case -EBUSY: ec = ENOTTY; + rc = SFS_ERROR; + msg = "file is in use"; + break; + case -EAGAIN: rc = 5; + break; + default: rc = SFS_ERROR; + msg = "unlink failed"; + break; + } + TRACE(Info,"Cache "< + +int XrdXrootdProtocol::do_Set_Cache(XrdOucTokenizer &setargs) +{ + XrdOucErrInfo myError(Link->ID, Monitor.Did, clientPV); + XrdSfsFSctl myData; + char *cmd, *cargs, *opaque; + const char *myArgs[2]; + +// This set is valid only if we implement a cache +// + if ((fsFeatures & XrdSfs::hasCACH) == 0) + return Response.Send(kXR_ArgInvalid, "invalid set parameter"); + +// Get the command and argument +// + if (!(cmd = setargs.GetToken(&cargs))) + return Response.Send(kXR_ArgMissing,"set cache argument not specified."); + +// Prescreen the path if the next token starts with a slash +// + if (cargs && *cargs == '/') + {if (rpCheck(cargs, &opaque)) return rpEmsg("Setting", cargs); + if (!Squash(cargs)) return vpEmsg("Setting", cargs); + myData.ArgP = myArgs; myData.Arg2Len = -2; + myArgs[0] = cargs; + myArgs[1] = opaque; + } else { + myData.Arg2 = opaque; myData.Arg2Len = (opaque ? strlen(opaque) : 0); + } + myData.Arg1 = cmd; myData.Arg1Len = strlen(cmd); + +// Preform the actual function using the supplied arguments +// + int rc = osFS->FSctl(SFS_FSCTL_PLUGXC, myData, myError, CRED); + TRACEP(FS, "rc=" < Date: Tue, 25 Apr 2023 22:08:07 -0700 Subject: [PATCH 066/442] Update notes on Xcache support for file eviction. --- docs/PreReleaseNotes.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/PreReleaseNotes.txt b/docs/PreReleaseNotes.txt index c1f882e1d38..b0226dd1164 100644 --- a/docs/PreReleaseNotes.txt +++ b/docs/PreReleaseNotes.txt @@ -6,6 +6,8 @@ Prerelease Notes ================ + **New Features** + **[Xcache]** Implement a file evict function. + **Commit: 952bd9a **[PSS]** Allow origin to be a directory of a locally mounted file system. **Commit: 850a14f bb550ea **[Server]** Add gsi option to display DN when it differs from entity name. From 39f9e0ae6744c4e068905daf0a10270f443b8619 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Tue, 25 Apr 2023 22:08:33 -0700 Subject: [PATCH 067/442] [Client] Add xrdfs cache subcommand to allow for cache evictions. --- src/XrdCl/XrdClFS.cc | 68 ++++++++++++++++++++++++++++++++++ src/XrdCl/XrdClFileSystem.cc | 71 ++++++++++++++++++++++++++++-------- src/XrdCl/XrdClFileSystem.hh | 44 ++++++++++++++++++++++ 3 files changed, 167 insertions(+), 16 deletions(-) diff --git a/src/XrdCl/XrdClFS.cc b/src/XrdCl/XrdClFS.cc index a3660d078ea..98b1fef55c6 100644 --- a/src/XrdCl/XrdClFS.cc +++ b/src/XrdCl/XrdClFS.cc @@ -158,6 +158,68 @@ XRootDStatus ConvertMode( Access::Mode &mode, const std::string &modeStr ) return XRootDStatus(); } +//------------------------------------------------------------------------------ +// Perform a cache operation +//------------------------------------------------------------------------------ +XRootDStatus DoCache( FileSystem *fs, + Env *env, + const FSExecutor::CommandParams &args ) +{ + //---------------------------------------------------------------------------- + // Check up the args + //---------------------------------------------------------------------------- + Log *log = DefaultEnv::GetLog(); + uint32_t argc = args.size(); + + if( argc != 3 ) + { + log->Error( AppMsg, "Wrong number of arguments." ); + return XRootDStatus( stError, errInvalidArgs, 0, + "Wrong number of arguments." ); + } + + if( args[1] != "evict" && args[1] != "fevict") + { + log->Error( AppMsg, "Invalid cache operation." ); + return XRootDStatus( stError, errInvalidArgs, 0, "Invalid cache operation." ); + } + + std::string fullPath; + if( !BuildPath( fullPath, env, args[2] ).IsOK() ) + { + log->Error( AppMsg, "Invalid cache path." ); + return XRootDStatus( stError, errInvalidArgs, 0, "Invalid cache path." ); + } + + //---------------------------------------------------------------------------- + // Create the command + //---------------------------------------------------------------------------- + std::string cmd = args[1]; + cmd.append(" "); + cmd.append(fullPath); + + //---------------------------------------------------------------------------- + // Run the operation + //---------------------------------------------------------------------------- + Buffer *response = 0; + XRootDStatus st = fs->SendCache( cmd, response ); + if( !st.IsOK() ) + { + log->Error( AppMsg, "Unable set cache %s: %s", + fullPath.c_str(), + st.ToStr().c_str() ); + return st; + } + + if( response ) + { + std::cout << response->ToString() << '\n'; + } + + delete response; + + return XRootDStatus(); +} //------------------------------------------------------------------------------ // Change current working directory //------------------------------------------------------------------------------ @@ -1875,6 +1937,11 @@ XRootDStatus PrintHelp( FileSystem *, Env *, printf( " help\n" ); printf( " This help screen.\n\n" ); + printf( " cache {evict | fevict} \n" ); + printf( " Evict a file from a cache if not in use; while fevict\n" ); + printf( " focibly evicts the file causing any current uses of the\n" ); + printf( " file to get read failures on a subsequent read\n\n" ); + printf( " cd \n" ); printf( " Change the current working directory\n\n" ); @@ -2008,6 +2075,7 @@ FSExecutor *CreateExecutor( const URL &url ) Env *env = new Env(); env->PutString( "CWD", "/" ); FSExecutor *executor = new FSExecutor( url, env ); + executor->AddCommand( "cache", DoCache ); executor->AddCommand( "cd", DoCD ); executor->AddCommand( "chmod", DoChMod ); executor->AddCommand( "ls", DoLS ); diff --git a/src/XrdCl/XrdClFileSystem.cc b/src/XrdCl/XrdClFileSystem.cc index 2d8002cdd3e..fe94656961a 100644 --- a/src/XrdCl/XrdClFileSystem.cc +++ b/src/XrdCl/XrdClFileSystem.cc @@ -1869,6 +1869,35 @@ namespace XrdCl return XRootDStatus(); } + //---------------------------------------------------------------------------- + // Send cache info to the server - async + //---------------------------------------------------------------------------- + XRootDStatus FileSystem::SendCache( const std::string &info, + ResponseHandler *handler, + uint16_t timeout ) + { + // Note: adding SendCache() to the FileSystemPlugin class breaks ABI! + // So, the class is missing this until we do a major release. TODO + //if( pPlugIn ) + // return pPlugIn->SendCache( info, handler, timeout ); + return SendSet("cache ", info, handler, timeout ); + } + + //---------------------------------------------------------------------------- + //! Send cache info to the server - sync + //---------------------------------------------------------------------------- + XRootDStatus FileSystem::SendCache( const std::string &info, + Buffer *&response, + uint16_t timeout ) + { + SyncResponseHandler handler; + Status st = SendCache( info, &handler, timeout ); + if( !st.IsOK() ) + return st; + + return MessageUtils::WaitForResponse( &handler, response ); + } + //---------------------------------------------------------------------------- // Send info to the server - async //---------------------------------------------------------------------------- @@ -1878,22 +1907,7 @@ namespace XrdCl { if( pPlugIn ) return pPlugIn->SendInfo( info, handler, timeout ); - - Message *msg; - ClientSetRequest *req; - const char *prefix = "monitor info "; - size_t prefixLen = strlen( prefix ); - MessageUtils::CreateRequest( msg, req, info.length()+prefixLen ); - - req->requestid = kXR_set; - req->dlen = info.length()+prefixLen; - msg->Append( prefix, prefixLen, 24 ); - msg->Append( info.c_str(), info.length(), 24+prefixLen ); - MessageSendParams params; params.timeout = timeout; - MessageUtils::ProcessSendParams( params ); - XRootDTransport::SetDescription( msg ); - - return FileSystemData::Send( pImpl->fsdata, msg, handler, params ); + return SendSet("monitor info ", info, handler, timeout ); } //---------------------------------------------------------------------------- @@ -1911,6 +1925,31 @@ namespace XrdCl return MessageUtils::WaitForResponse( &handler, response ); } + //---------------------------------------------------------------------------- + // Send set request to the server - async + //---------------------------------------------------------------------------- + XRootDStatus FileSystem::SendSet( const char *prefix, + const std::string &info, + ResponseHandler *handler, + uint16_t timeout ) + { + + Message *msg; + ClientSetRequest *req; + size_t prefixLen = strlen( prefix ); + MessageUtils::CreateRequest( msg, req, info.length()+prefixLen ); + + req->requestid = kXR_set; + req->dlen = info.length()+prefixLen; + msg->Append( prefix, prefixLen, 24 ); + msg->Append( info.c_str(), info.length(), 24+prefixLen ); + MessageSendParams params; params.timeout = timeout; + MessageUtils::ProcessSendParams( params ); + XRootDTransport::SetDescription( msg ); + + return FileSystemData::Send( pImpl->fsdata, msg, handler, params ); + } + //---------------------------------------------------------------------------- // Prepare one or more files for access - async //---------------------------------------------------------------------------- diff --git a/src/XrdCl/XrdClFileSystem.hh b/src/XrdCl/XrdClFileSystem.hh index dabdaab0932..8229a234c10 100644 --- a/src/XrdCl/XrdClFileSystem.hh +++ b/src/XrdCl/XrdClFileSystem.hh @@ -640,6 +640,36 @@ namespace XrdCl uint16_t timeout = 0 ) XRD_WARN_UNUSED_RESULT; + //------------------------------------------------------------------------ + //! Send cache into the server - async + //! + //! @param info the info string to be sent + //! @param handler handler to be notified when the response arrives, + //! the response parameter will hold a Buffer object + //! if the procedure is successful + //! @param timeout timeout value, if 0 the environment default will + //! be used + //! @return status of the operation + //------------------------------------------------------------------------ + XRootDStatus SendCache( const std::string &info, + ResponseHandler *handler, + uint16_t timeout = 0 ) + XRD_WARN_UNUSED_RESULT; + + //------------------------------------------------------------------------ + //! Send cache into the server - sync + //! + //! @param info the info string to be sent + //! @param response the response (to be deleted by the user) + //! @param timeout timeout value, if 0 the environment default will + //! be used + //! @return status of the operation + //------------------------------------------------------------------------ + XRootDStatus SendCache( const std::string &info, + Buffer *&response, + uint16_t timeout = 0 ) + XRD_WARN_UNUSED_RESULT; + //------------------------------------------------------------------------ //! Send info to the server (up to 1024 characters)- async //! @@ -861,6 +891,20 @@ namespace XrdCl //------------------------------------------------------------------------ void UnLock(); + //------------------------------------------------------------------------ + //! Generic implementation of SendCache and SendInfo + //! + //! @param info : the info string to be sent + //! @param handler : handler to be notified when the response arrives. + //! @param timeout : timeout value or 0 for default. + //! @return status of the operation + //------------------------------------------------------------------------ + XRootDStatus SendSet( const char *prefix, + const std::string &info, + ResponseHandler *handler, + uint16_t timeout = 0 ) + XRD_WARN_UNUSED_RESULT; + //------------------------------------------------------------------------ //! Generic implementation of xattr operation //! From 25f83842def6df191ea9b7e1bea6319c75c87d91 Mon Sep 17 00:00:00 2001 From: David Smith Date: Thu, 27 Apr 2023 14:58:22 +0200 Subject: [PATCH 068/442] [Client] Add return codes for error checking in the network Event handler --- src/XrdCl/XrdClAsyncSocketHandler.cc | 202 +++++++++++++++++---------- src/XrdCl/XrdClAsyncSocketHandler.hh | 38 +++-- src/XrdCl/XrdClStream.cc | 11 +- src/XrdCl/XrdClStream.hh | 5 +- 4 files changed, 166 insertions(+), 90 deletions(-) diff --git a/src/XrdCl/XrdClAsyncSocketHandler.cc b/src/XrdCl/XrdClAsyncSocketHandler.cc index c416cebb367..76f1ba355c5 100644 --- a/src/XrdCl/XrdClAsyncSocketHandler.cc +++ b/src/XrdCl/XrdClAsyncSocketHandler.cc @@ -136,6 +136,9 @@ namespace XrdCl } pHandShakeDone = false; + pTlsHandShakeOngoing = false; + pHSWaitStarted = 0; + pHSWaitSeconds = 0; //-------------------------------------------------------------------------- // Initiate async connection to the address @@ -213,6 +216,23 @@ namespace XrdCl //-------------------------------------------------------------------------- type = pSocket->MapEvent( type ); + //-------------------------------------------------------------------------- + // Handle any read or write events. If any of the handlers indicate an error + // we will have been disconnected. A disconnection may cause the current + // object to be asynchronously reused or deleted, so we return immediately. + //-------------------------------------------------------------------------- + if( !EventRead( type ) ) + return; + + if( !EventWrite( type ) ) + return; + } + + //---------------------------------------------------------------------------- + // Handler for read related socket events + //---------------------------------------------------------------------------- + bool AsyncSocketHandler::EventRead( uint8_t type ) + { //-------------------------------------------------------------------------- // Read event //-------------------------------------------------------------------------- @@ -220,11 +240,12 @@ namespace XrdCl { pLastActivity = time(0); if( unlikely( pTlsHandShakeOngoing ) ) - OnTLSHandShake(); - else if( likely( pHandShakeDone ) ) - OnRead(); - else - OnReadWhileHandshaking(); + return OnTLSHandShake(); + + if( likely( pHandShakeDone ) ) + return OnRead(); + + return OnReadWhileHandshaking(); } //-------------------------------------------------------------------------- @@ -233,14 +254,25 @@ namespace XrdCl else if( type & ReadTimeOut ) { if( pHSWaitSeconds ) - CheckHSWait(); + { + if( !CheckHSWait() ) + return false; + } if( likely( pHandShakeDone ) ) - OnReadTimeout(); - else - OnTimeoutWhileHandshaking(); + return OnReadTimeout(); + + return OnTimeoutWhileHandshaking(); } + return true; + } + + //---------------------------------------------------------------------------- + // Handler for write related socket events + //---------------------------------------------------------------------------- + bool AsyncSocketHandler::EventWrite( uint8_t type ) + { //-------------------------------------------------------------------------- // Write event //-------------------------------------------------------------------------- @@ -248,19 +280,21 @@ namespace XrdCl { pLastActivity = time(0); if( unlikely( pSocket->GetStatus() == Socket::Connecting ) ) - OnConnectionReturn(); + return OnConnectionReturn(); + //------------------------------------------------------------------------ // Make sure we are not writing anything if we have been told to wait. //------------------------------------------------------------------------ - else if( pHSWaitSeconds == 0 ) - { - if( unlikely( pTlsHandShakeOngoing ) ) - OnTLSHandShake(); - else if( likely( pHandShakeDone ) ) - OnWrite(); - else - OnWriteWhileHandshaking(); - } + if( pHSWaitSeconds != 0 ) + return true; + + if( unlikely( pTlsHandShakeOngoing ) ) + return OnTLSHandShake(); + + if( likely( pHandShakeDone ) ) + return OnWrite(); + + return OnWriteWhileHandshaking(); } //-------------------------------------------------------------------------- @@ -269,16 +303,18 @@ namespace XrdCl else if( type & WriteTimeOut ) { if( likely( pHandShakeDone ) ) - OnWriteTimeout(); - else - OnTimeoutWhileHandshaking(); + return OnWriteTimeout(); + + return OnTimeoutWhileHandshaking(); } + + return true; } //---------------------------------------------------------------------------- // Connect returned //---------------------------------------------------------------------------- - void AsyncSocketHandler::OnConnectionReturn() + bool AsyncSocketHandler::OnConnectionReturn() { //-------------------------------------------------------------------------- // Check whether we were able to connect @@ -303,7 +339,7 @@ namespace XrdCl XrdSysE2T( errno ) ); pStream->OnConnectError( pSubStreamNum, XRootDStatus( stFatal, errSocketOptError, errno ) ); - return; + return false; } //-------------------------------------------------------------------------- @@ -315,7 +351,7 @@ namespace XrdCl pStreamName.c_str(), XrdSysE2T( errorCode ) ); pStream->OnConnectError( pSubStreamNum, XRootDStatus( stError, errConnectionError ) ); - return; + return false; } pSocket->SetStatus( Socket::Connected ); @@ -326,7 +362,7 @@ namespace XrdCl if( !st.IsOK() ) { pStream->OnConnectError( pSubStreamNum, st ); - return; + return false; } //-------------------------------------------------------------------------- @@ -344,7 +380,7 @@ namespace XrdCl log->Error( AsyncSockMsg, "[%s] Connection negotiation failed", pStreamName.c_str() ); pStream->OnConnectError( pSubStreamNum, st ); - return; + return false; } if( st.code != suRetry ) @@ -372,19 +408,20 @@ namespace XrdCl { pStream->OnConnectError( pSubStreamNum, XRootDStatus( stFatal, errPollerError ) ); - return; + return false; } + return true; } //---------------------------------------------------------------------------- // Got a write readiness event //---------------------------------------------------------------------------- - void AsyncSocketHandler::OnWrite() + bool AsyncSocketHandler::OnWrite() { if( !reqwriter ) { OnFault( XRootDStatus( stError, errInternal, 0, "Request writer is null." ) ); - return; + return false; } //-------------------------------------------------------------------------- // Let's do the writing ... @@ -396,30 +433,34 @@ namespace XrdCl // We failed //------------------------------------------------------------------------ OnFault( st ); - return; + return false; } //-------------------------------------------------------------------------- // We are not done yet //-------------------------------------------------------------------------- - if( st.code == suRetry) return; + if( st.code == suRetry) return true; //-------------------------------------------------------------------------- // Disable the respective substream if empty //-------------------------------------------------------------------------- reqwriter->Reset(); pStream->DisableIfEmpty( pSubStreamNum ); + return true; } //---------------------------------------------------------------------------- // Got a write readiness event while handshaking //---------------------------------------------------------------------------- - void AsyncSocketHandler::OnWriteWhileHandshaking() + bool AsyncSocketHandler::OnWriteWhileHandshaking() { XRootDStatus st; if( !hswriter || !hswriter->HasMsg() ) { if( !(st = DisableUplink()).IsOK() ) + { OnFaultWhileHandshaking( st ); - return; + return false; + } + return true; } //-------------------------------------------------------------------------- // Let's do the writing ... @@ -431,25 +472,29 @@ namespace XrdCl // We failed //------------------------------------------------------------------------ OnFaultWhileHandshaking( st ); - return; + return false; } //-------------------------------------------------------------------------- // We are not done yet //-------------------------------------------------------------------------- - if( st.code == suRetry ) return; + if( st.code == suRetry ) return true; //-------------------------------------------------------------------------- // Disable the uplink // Note: at this point we don't deallocate the HS message as we might need // to re-send it in case of a kXR_wait response //-------------------------------------------------------------------------- if( !(st = DisableUplink()).IsOK() ) + { OnFaultWhileHandshaking( st ); + return false; + } + return true; } //---------------------------------------------------------------------------- // Got a read readiness event //---------------------------------------------------------------------------- - void AsyncSocketHandler::OnRead() + bool AsyncSocketHandler::OnRead() { //-------------------------------------------------------------------------- // Make sure the response reader object exists @@ -457,7 +502,7 @@ namespace XrdCl if( !rspreader ) { OnFault( XRootDStatus( stError, errInternal, 0, "Response reader is null." ) ); - return; + return false; } //-------------------------------------------------------------------------- @@ -471,7 +516,7 @@ namespace XrdCl if( !st.IsOK() && st.code == errCorruptedHeader ) { OnHeaderCorruption(); - return; + return false; } //-------------------------------------------------------------------------- @@ -480,24 +525,25 @@ namespace XrdCl if( !st.IsOK() ) { OnFault( st ); - return; + return false; } //-------------------------------------------------------------------------- // We are not done yet //-------------------------------------------------------------------------- - if( st.code == suRetry ) return; + if( st.code == suRetry ) return true; //-------------------------------------------------------------------------- // We are done, reset the response reader so we can read out next message //-------------------------------------------------------------------------- rspreader->Reset(); + return true; } //---------------------------------------------------------------------------- // Got a read readiness event while handshaking //---------------------------------------------------------------------------- - void AsyncSocketHandler::OnReadWhileHandshaking() + bool AsyncSocketHandler::OnReadWhileHandshaking() { //-------------------------------------------------------------------------- // Make sure the response reader object exists @@ -505,7 +551,7 @@ namespace XrdCl if( !hsreader ) { OnFault( XRootDStatus( stError, errInternal, 0, "Hand-shake reader is null." ) ); - return; + return false; } //-------------------------------------------------------------------------- @@ -516,19 +562,19 @@ namespace XrdCl if( !st.IsOK() ) { OnFaultWhileHandshaking( st ); - return; + return false; } if( st.code != suDone ) - return; + return true; - HandleHandShake( hsreader->ReleaseMsg() ); + return HandleHandShake( hsreader->ReleaseMsg() ); } //------------------------------------------------------------------------ // Handle the handshake message //------------------------------------------------------------------------ - void AsyncSocketHandler::HandleHandShake( std::unique_ptr msg ) + bool AsyncSocketHandler::HandleHandShake( std::unique_ptr msg ) { //-------------------------------------------------------------------------- // OK, we have a new message, let's deal with it; @@ -547,7 +593,7 @@ namespace XrdCl if( !st.IsOK() ) { OnFaultWhileHandshaking( st ); - return; + return false; } if( st.code == suRetry ) @@ -568,6 +614,7 @@ namespace XrdCl pStreamName.c_str() ); OnFaultWhileHandshaking( XRootDStatus( stError, errSocketTimeout ) ); + return false; } else { @@ -581,15 +628,14 @@ namespace XrdCl pHSWaitStarted = time( 0 ); pHSWaitSeconds = waitSeconds; } - return; + return true; } //------------------------------------------------------------------------ // We are re-sending a protocol request //------------------------------------------------------------------------ else if( pHandShakeData->out ) { - SendHSMsg(); - return; + return SendHSMsg(); } } @@ -600,19 +646,22 @@ namespace XrdCl pTransport->NeedEncryption( pHandShakeData.get(), *pChannelData ) ) { XRootDStatus st = DoTlsHandShake(); - if( !st.IsOK() || st.code == suRetry ) return; + if( !st.IsOK() ) + return false; + if ( st.code == suRetry ) + return true; } //-------------------------------------------------------------------------- // Now prepare the next step of the hand-shake procedure //-------------------------------------------------------------------------- - HandShakeNextStep( st.IsOK() && st.code == suDone ); + return HandShakeNextStep( st.IsOK() && st.code == suDone ); } //------------------------------------------------------------------------ // Prepare the next step of the hand-shake procedure //------------------------------------------------------------------------ - void AsyncSocketHandler::HandShakeNextStep( bool done ) + bool AsyncSocketHandler::HandShakeNextStep( bool done ) { //-------------------------------------------------------------------------- // We successfully proceeded to the next step @@ -636,7 +685,7 @@ namespace XrdCl if( !(st = EnableUplink()).IsOK() ) { OnFaultWhileHandshaking( st ); - return; + return false; } pHandShakeDone = true; pStream->OnConnect( pSubStreamNum ); @@ -646,8 +695,9 @@ namespace XrdCl //-------------------------------------------------------------------------- else if( pHandShakeData->out ) { - SendHSMsg(); + return SendHSMsg(); } + return true; } //---------------------------------------------------------------------------- @@ -677,27 +727,31 @@ namespace XrdCl //---------------------------------------------------------------------------- // Handle write timeout //---------------------------------------------------------------------------- - void AsyncSocketHandler::OnWriteTimeout() + bool AsyncSocketHandler::OnWriteTimeout() { - pStream->OnWriteTimeout( pSubStreamNum ); + return pStream->OnWriteTimeout( pSubStreamNum ); } //---------------------------------------------------------------------------- // Handler read timeout //---------------------------------------------------------------------------- - void AsyncSocketHandler::OnReadTimeout() + bool AsyncSocketHandler::OnReadTimeout() { - pStream->OnReadTimeout( pSubStreamNum ); + return pStream->OnReadTimeout( pSubStreamNum ); } //---------------------------------------------------------------------------- // Handle timeout while handshaking //---------------------------------------------------------------------------- - void AsyncSocketHandler::OnTimeoutWhileHandshaking() + bool AsyncSocketHandler::OnTimeoutWhileHandshaking() { time_t now = time(0); if( now > pConnectionStarted+pConnectionTimeout ) + { OnFaultWhileHandshaking( XRootDStatus( stError, errSocketTimeout ) ); + return false; + } + return true; } //---------------------------------------------------------------------------- @@ -723,8 +777,8 @@ namespace XrdCl XRootDStatus st; if( !( st = pSocket->TlsHandShake( this, pUrl.GetHostName() ) ).IsOK() ) { - OnFaultWhileHandshaking( st ); pTlsHandShakeOngoing = false; + OnFaultWhileHandshaking( st ); return st; } @@ -743,25 +797,28 @@ namespace XrdCl //---------------------------------------------------------------------------- // Handle read/write event if we are in the middle of a TLS hand-shake //---------------------------------------------------------------------------- - inline void AsyncSocketHandler::OnTLSHandShake() + inline bool AsyncSocketHandler::OnTLSHandShake() { XRootDStatus st = DoTlsHandShake(); - if( !st.IsOK() || st.code == suRetry ) return; + if( !st.IsOK() ) + return false; + if ( st.code == suRetry ) + return true; - HandShakeNextStep( pTransport->HandShakeDone( pHandShakeData.get(), - *pChannelData ) ); + return HandShakeNextStep( pTransport->HandShakeDone( pHandShakeData.get(), + *pChannelData ) ); } //---------------------------------------------------------------------------- // Prepare a HS writer for sending and enable uplink //---------------------------------------------------------------------------- - void AsyncSocketHandler::SendHSMsg() + bool AsyncSocketHandler::SendHSMsg() { if( !hswriter ) { OnFaultWhileHandshaking( XRootDStatus( stError, errInternal, 0, "HS writer object missing!" ) ); - return; + return false; } //-------------------------------------------------------------------------- // We only set a new HS message if this is not a replay due to kXR_wait @@ -783,8 +840,9 @@ namespace XrdCl if( !(st = EnableUplink()).IsOK() ) { OnFaultWhileHandshaking( st ); - return; + return false; } + return true; } kXR_int32 AsyncSocketHandler::HandleWaitRsp( Message *msg ) @@ -801,7 +859,7 @@ namespace XrdCl //---------------------------------------------------------------------------- // Check if HS wait time elapsed //---------------------------------------------------------------------------- - void AsyncSocketHandler::CheckHSWait() + bool AsyncSocketHandler::CheckHSWait() { time_t now = time( 0 ); if( now - pHSWaitStarted >= pHSWaitSeconds ) @@ -809,13 +867,15 @@ namespace XrdCl Log *log = DefaultEnv::GetLog(); log->Debug( AsyncSockMsg, "[%s] The hand-shake wait time elapsed, will " "replay the endsess request.", pStreamName.c_str() ); - SendHSMsg(); + if( !SendHSMsg() ) + return false; //------------------------------------------------------------------------ // Make sure the wait state is reset //------------------------------------------------------------------------ pHSWaitSeconds = 0; pHSWaitStarted = 0; } + return true; } //------------------------------------------------------------------------ diff --git a/src/XrdCl/XrdClAsyncSocketHandler.hh b/src/XrdCl/XrdClAsyncSocketHandler.hh index f696e754af9..8589715a846 100644 --- a/src/XrdCl/XrdClAsyncSocketHandler.hh +++ b/src/XrdCl/XrdClAsyncSocketHandler.hh @@ -30,6 +30,7 @@ #include "XrdCl/XrdClAsyncHSReader.hh" #include "XrdCl/XrdClAsyncMsgWriter.hh" #include "XrdCl/XrdClAsyncHSWriter.hh" +#include "XrdOuc/XrdOucCompiler.hh" namespace XrdCl { @@ -149,37 +150,38 @@ namespace XrdCl //------------------------------------------------------------------------ // Connect returned //------------------------------------------------------------------------ - virtual void OnConnectionReturn(); + virtual bool OnConnectionReturn() XRD_WARN_UNUSED_RESULT; //------------------------------------------------------------------------ // Got a write readiness event //------------------------------------------------------------------------ - void OnWrite(); + bool OnWrite() XRD_WARN_UNUSED_RESULT; //------------------------------------------------------------------------ // Got a write readiness event while handshaking //------------------------------------------------------------------------ - void OnWriteWhileHandshaking(); + bool OnWriteWhileHandshaking() XRD_WARN_UNUSED_RESULT; //------------------------------------------------------------------------ // Got a read readiness event //------------------------------------------------------------------------ - void OnRead(); + bool OnRead() XRD_WARN_UNUSED_RESULT; //------------------------------------------------------------------------ // Got a read readiness event while handshaking //------------------------------------------------------------------------ - void OnReadWhileHandshaking(); + bool OnReadWhileHandshaking() XRD_WARN_UNUSED_RESULT; //------------------------------------------------------------------------ // Handle the handshake message //------------------------------------------------------------------------ - void HandleHandShake( std::unique_ptr msg ); + bool HandleHandShake( std::unique_ptr msg ) + XRD_WARN_UNUSED_RESULT; //------------------------------------------------------------------------ // Prepare the next step of the hand-shake procedure //------------------------------------------------------------------------ - void HandShakeNextStep( bool done ); + bool HandShakeNextStep( bool done ) XRD_WARN_UNUSED_RESULT; //------------------------------------------------------------------------ // Handle fault @@ -194,17 +196,17 @@ namespace XrdCl //------------------------------------------------------------------------ // Handle write timeout event //------------------------------------------------------------------------ - void OnWriteTimeout(); + bool OnWriteTimeout() XRD_WARN_UNUSED_RESULT; //------------------------------------------------------------------------ // Handle read timeout event //------------------------------------------------------------------------ - void OnReadTimeout(); + bool OnReadTimeout() XRD_WARN_UNUSED_RESULT; //------------------------------------------------------------------------ // Handle timeout event while handshaking //------------------------------------------------------------------------ - void OnTimeoutWhileHandshaking(); + bool OnTimeoutWhileHandshaking() XRD_WARN_UNUSED_RESULT; //------------------------------------------------------------------------ // Handle header corruption in case of kXR_status response @@ -229,12 +231,12 @@ namespace XrdCl // Handle read/write event if we are in the middle of a TLS hand-shake //------------------------------------------------------------------------ // Handle read/write event if we are in the middle of a TLS hand-shake - void OnTLSHandShake(); + bool OnTLSHandShake() XRD_WARN_UNUSED_RESULT; //------------------------------------------------------------------------ // Prepare a HS writer for sending and enable uplink //------------------------------------------------------------------------ - void SendHSMsg(); + bool SendHSMsg() XRD_WARN_UNUSED_RESULT; //------------------------------------------------------------------------ // Extract the value of a wait response @@ -248,7 +250,17 @@ namespace XrdCl //------------------------------------------------------------------------ // Check if HS wait time elapsed //------------------------------------------------------------------------ - void CheckHSWait(); + bool CheckHSWait() XRD_WARN_UNUSED_RESULT; + + //------------------------------------------------------------------------ + // Handler for read related socket events + //------------------------------------------------------------------------ + inline bool EventRead( uint8_t type ) XRD_WARN_UNUSED_RESULT; + + //------------------------------------------------------------------------ + // Handler for write related socket events + //------------------------------------------------------------------------ + inline bool EventWrite( uint8_t type ) XRD_WARN_UNUSED_RESULT; //------------------------------------------------------------------------ // Data members diff --git a/src/XrdCl/XrdClStream.cc b/src/XrdCl/XrdClStream.cc index b3c4466da35..d9e2681fe0c 100644 --- a/src/XrdCl/XrdClStream.cc +++ b/src/XrdCl/XrdClStream.cc @@ -1024,13 +1024,13 @@ namespace XrdCl //---------------------------------------------------------------------------- // Call back when a message has been reconstructed //---------------------------------------------------------------------------- - void Stream::OnReadTimeout( uint16_t substream ) + bool Stream::OnReadTimeout( uint16_t substream ) { //-------------------------------------------------------------------------- // We only take the main stream into account //-------------------------------------------------------------------------- if( substream != 0 ) - return; + return true; //-------------------------------------------------------------------------- // Check if there is no outgoing messages and if the stream TTL is elapesed. @@ -1070,7 +1070,7 @@ namespace XrdCl // object that aggregates this Stream. //---------------------------------------------------------------------- DefaultEnv::GetPostMaster()->ForceDisconnect( *pUrl ); - return; + return false; } } @@ -1083,14 +1083,17 @@ namespace XrdCl { scopedLock.UnLock(); OnError( substream, st ); + return false; } + return true; } //---------------------------------------------------------------------------- // Call back when a message has been reconstru //---------------------------------------------------------------------------- - void Stream::OnWriteTimeout( uint16_t /*substream*/ ) + bool Stream::OnWriteTimeout( uint16_t /*substream*/ ) { + return true; } //---------------------------------------------------------------------------- diff --git a/src/XrdCl/XrdClStream.hh b/src/XrdCl/XrdClStream.hh index 19522d37de9..5ded1166762 100644 --- a/src/XrdCl/XrdClStream.hh +++ b/src/XrdCl/XrdClStream.hh @@ -31,6 +31,7 @@ #include "XrdSys/XrdSysPthread.hh" #include "XrdSys/XrdSysRAtomic.hh" #include "XrdNet/XrdNetAddr.hh" +#include "XrdOuc/XrdOucCompiler.hh" #include #include #include @@ -219,12 +220,12 @@ namespace XrdCl //------------------------------------------------------------------------ //! On read timeout //------------------------------------------------------------------------ - void OnReadTimeout( uint16_t subStream ); + bool OnReadTimeout( uint16_t subStream ) XRD_WARN_UNUSED_RESULT; //------------------------------------------------------------------------ //! On write timeout //------------------------------------------------------------------------ - void OnWriteTimeout( uint16_t subStream ); + bool OnWriteTimeout( uint16_t subStream ) XRD_WARN_UNUSED_RESULT; //------------------------------------------------------------------------ //! Register channel event handler From 409e33095174dc11c17d579cbb1e71fdc8c8ad08 Mon Sep 17 00:00:00 2001 From: Elvin Sindrilaru Date: Mon, 27 Mar 2023 15:17:59 +0200 Subject: [PATCH 069/442] [XrdSys] Avoid memory leak report in asan when overwriting the default message for EBADE. --- src/XrdSys/XrdSysE2T.cc | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/XrdSys/XrdSysE2T.cc b/src/XrdSys/XrdSysE2T.cc index 9cb3621baa6..83b62e317f8 100644 --- a/src/XrdSys/XrdSysE2T.cc +++ b/src/XrdSys/XrdSysE2T.cc @@ -54,7 +54,7 @@ int initErrTable() // Premap all known error codes. // - for(int i = 1; i Date: Tue, 18 Apr 2023 14:21:41 +0200 Subject: [PATCH 070/442] [CI] Do not update pip, setuptools, and wheel for sdist build The build no longer works with the latest versions of pip, setuptools, and wheel due to usage of deprecated tools. This needs to be addressed by modernizing our Python packaging (see issue #1844). --- .github/workflows/build.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2706aa4e3d7..c5d1403070f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -954,9 +954,6 @@ jobs: pkg-config \ tree sudo apt-get autoclean -y - # Remove packages with invalid versions which cause sdist build to fail - sudo apt-get remove python3-debian python3-distro-info - python3 -m pip --no-cache-dir install --upgrade pip setuptools wheel python3 -m pip list - name: Clone repository From ad8c4eb3de364c5af454ed13cc791b91292da582 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 4 May 2023 13:40:54 +0200 Subject: [PATCH 071/442] [Python] Fix warning when building version string xrootd/bindings/python/src/PyXRootDEnv.hh:137:66: warning: adding 'int' to a string does not append to the string [-Wstring-plus-int] static std::string verstr( XrdVERSION[0] == 'v' ? XrdVERSION + 1 : XrdVERSION ); ~~~~~~~~~~~^~~ --- bindings/python/src/PyXRootDEnv.hh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/python/src/PyXRootDEnv.hh b/bindings/python/src/PyXRootDEnv.hh index 5642a86a0e9..3c1357f6620 100644 --- a/bindings/python/src/PyXRootDEnv.hh +++ b/bindings/python/src/PyXRootDEnv.hh @@ -134,7 +134,7 @@ namespace PyXRootD //---------------------------------------------------------------------------- PyObject* XrdVersion_cpp( PyObject *self, PyObject *args ) { - static std::string verstr( XrdVERSION[0] == 'v' ? XrdVERSION + 1 : XrdVERSION ); + static std::string verstr( XrdVERSION[0] == 'v' ? &XrdVERSION[1] : XrdVERSION ); return Py_BuildValue( "s", verstr.c_str() ); } From c2b0cdb849a5fd20aba856dc38cd086279dcc7e5 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 3 May 2023 17:00:59 +0200 Subject: [PATCH 072/442] Add git mailmap file This file is used to map author and committer names and email addresses to canonical real names and email addresses. This is useful when using git blame, git shortlog, etc. For more information, see gitmailmap(5). --- .mailmap | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 .mailmap diff --git a/.mailmap b/.mailmap new file mode 100644 index 00000000000..a6c99ac7d09 --- /dev/null +++ b/.mailmap @@ -0,0 +1,73 @@ +Alja Mrak-Tadel Alja MRak-Tadel +Alja Mrak-Tadel alja +Alja Mrak-Tadel alja +Alja Mrak-Tadel alja +Andreas Joachim Peters Andreas Peters +Andreas Joachim Peters Andreas Peters +Andreas Joachim Peters Andreas-Joachim Peters +Andrew Hanushevsky +Artem Harutyunyan +Brian Bockelman Brian P Bockelman +Brian Bockelman Brian P Bockelman +Cedric Caffy ccaffy <85744538+ccaffy@users.noreply.github.com> +Chris Burr Chris Burr +Chris Green +David Smith +David Smith David Smith +Edgar Fajardo efajardo +Edgar Fajardo efajardo +Elvin Sindrilaru +Fabrizio Furano Fabrizio Furano +Fabrizio Furano Fabrizio Furano +Fabrizio Furano Fabrizio Furano +Fabrizio Furano Fabrizio Furano +Fabrizio Furano ffurano +Fabrizio Furano furano +Fritz Mueller Fritz Mueller +Gerardo Ganis Gerardo GANIS +Gerardo Ganis Gerri Ganis +Gerardo Ganis Gerri Ganis +Gerardo Ganis Gerri Ganis +Gerardo Ganis ganis +Gerardo Ganis ganis +Gerardo Ganis gganis +Jacek Becla +Jan Iven +Jozsef Makai +Jozsef Makai Jozsef Makai +Kian-Tat Lim ktlim +Lukasz Janyst Lukasz Janyst +Lukasz Janyst Lukasz Janyst +Lukasz Janyst Lukasz Janyst +Lukasz Janyst Lukasz Janyst +Matevž Tadel Matevz Tadel +Matevž Tadel Matevž Tadel +Michal Simon Michal Simon +Michal Simon Michal Simon +Michal Simon simonmichal +Nikola Hardi +Nikola Hardi +Paul-Niklas Kramp Paul Kramp +Paul-Niklas Kramp niklas +Paul-Niklas Kramp pkramp +Sebastien Ponce +Wei Yang Wei Yang +Wei Yang Wei Yang +Wei Yang Wei Yang +Wei Yang Wei Yang +Wei Yang Wei Yang +Wei Yang Wei Yang +Wei Yang Wei Yang +Wei Yang Wei Yang +Wei Yang Wei Yang +Wei Yang Wei Yang + +Unknown bbrqa <> +Unknown fwinkl <> +Unknown mommsen <> +Unknown otron +Unknown root +Unknown root +Unknown root +Unknown root +Unknown xrootd From be37e134314508479d6a5d9bde9b8461e9dc77ac Mon Sep 17 00:00:00 2001 From: David Smith Date: Thu, 4 May 2023 17:14:53 +0200 Subject: [PATCH 073/442] [Client] Make sure Final operation is executed if pipeline fails part way --- src/XrdCl/XrdClOperations.cc | 26 ++++++++++++++++++++++++++ src/XrdCl/XrdClOperations.hh | 9 +++++++++ 2 files changed, 35 insertions(+) diff --git a/src/XrdCl/XrdClOperations.cc b/src/XrdCl/XrdClOperations.cc index 611c72df727..6bba0abd0b9 100644 --- a/src/XrdCl/XrdClOperations.cc +++ b/src/XrdCl/XrdClOperations.cc @@ -210,6 +210,32 @@ namespace XrdCl final = std::move( f ); } + //------------------------------------------------------------------------ + // Called by a pipeline on the handler of its first operation before Run + //------------------------------------------------------------------------ + void PipelineHandler::PreparePipelineStart() + { + // Move any final-function from the handler of the last operaiton to the + // first. It will be moved along the pipeline of handlers while the + // pipeline is run. + + if( final || !nextOperation ) return; + PipelineHandler *last = nextOperation->handler.get(); + while( last ) + { + Operation *nextop = last->nextOperation.get(); + if( !nextop ) break; + last = nextop->handler.get(); + } + if( last ) + { + // swap-then-move rather than only move as we need to guarantee that + // last->final is left without target. + std::function f; + f.swap( last->final ); + Assign( std::move( f ) ); + } + } //------------------------------------------------------------------------ // Stop the current pipeline diff --git a/src/XrdCl/XrdClOperations.hh b/src/XrdCl/XrdClOperations.hh index 24aa66a09c7..e49bb4e84cf 100644 --- a/src/XrdCl/XrdClOperations.hh +++ b/src/XrdCl/XrdClOperations.hh @@ -123,6 +123,11 @@ namespace XrdCl //------------------------------------------------------------------------ void Assign( std::function final ); + //------------------------------------------------------------------------ + //! Called by a pipeline on the handler of its first operation before Run + //------------------------------------------------------------------------ + void PreparePipelineStart(); + private: //------------------------------------------------------------------------ @@ -487,6 +492,10 @@ namespace XrdCl if( !operation ) std::logic_error( "Empty pipeline!" ); Operation *opr = operation.release(); + PipelineHandler *h = opr->handler.get(); + if( h ) + h->PreparePipelineStart(); + opr->Run( timeout, std::move( prms ), std::move( final ) ); } From ba9a39510b59dc45554aad8812745ffb37ebd366 Mon Sep 17 00:00:00 2001 From: David Smith Date: Thu, 4 May 2023 17:16:52 +0200 Subject: [PATCH 074/442] [Client] Avoid ZipArchive issuing VectorWrite which is too large during CloseArchive --- src/XrdCl/XrdClUtils.cc | 50 ++++++++++++++++++++++++++++++++++++ src/XrdCl/XrdClUtils.hh | 12 +++++++++ src/XrdCl/XrdClZipArchive.cc | 13 ++++++++-- 3 files changed, 73 insertions(+), 2 deletions(-) diff --git a/src/XrdCl/XrdClUtils.cc b/src/XrdCl/XrdClUtils.cc index 38a02f771a1..2a8d2b4943d 100644 --- a/src/XrdCl/XrdClUtils.cc +++ b/src/XrdCl/XrdClUtils.cc @@ -865,4 +865,54 @@ namespace XrdCl if( dst_supported.count( *itr ) ) return *itr; return std::string(); } + + //---------------------------------------------------------------------------- + //! Split chunks in a ChunkList into one or more ChunkLists + //---------------------------------------------------------------------------- + void Utils::SplitChunks( std::vector &listsvec, + const ChunkList &chunks, + const uint32_t maxcs, + const size_t maxc ) + { + listsvec.clear(); + if( !chunks.size() ) return; + + listsvec.emplace_back(); + ChunkList *c = &listsvec.back(); + const size_t cs = chunks.size(); + size_t idx = 0; + size_t nc = 0; + ChunkInfo tmpc; + + c->reserve( cs ); + + while( idx < cs ) + { + if( maxc && nc >= maxc ) + { + listsvec.emplace_back(); + c = &listsvec.back(); + c->reserve( cs - idx ); + nc = 0; + } + + if( tmpc.length == 0 ) + tmpc = chunks[idx]; + + if( maxcs && tmpc.length > maxcs ) + { + c->emplace_back( tmpc.offset, maxcs, tmpc.buffer ); + tmpc.offset += maxcs; + tmpc.length -= maxcs; + tmpc.buffer = static_cast( tmpc.buffer ) + maxcs; + } + else + { + c->emplace_back( tmpc.offset, tmpc.length, tmpc.buffer ); + tmpc.length = 0; + ++idx; + } + ++nc; + } + } } diff --git a/src/XrdCl/XrdClUtils.hh b/src/XrdCl/XrdClUtils.hh index 6691196f66b..e3c6a502827 100644 --- a/src/XrdCl/XrdClUtils.hh +++ b/src/XrdCl/XrdClUtils.hh @@ -272,6 +272,18 @@ namespace XrdCl if( !st.IsOK() ) return false; return protver >= kXR_PROTPGRWVERSION; } + + //------------------------------------------------------------------------ + //! Split chunks in a ChunkList into one or more ChunkLists + //! @param listsvec : output vector of ChunkLists + //! @param chunks : input ChunkLisits + //! @param maxcs : maximum size of a ChunkInfo in output + //! @param maxc : maximum number of ChunkInfo in each ChunkList + //------------------------------------------------------------------------ + static void SplitChunks( std::vector &listsvec, + const ChunkList &chunks, + const uint32_t maxcs, + const size_t maxc ); }; //---------------------------------------------------------------------------- diff --git a/src/XrdCl/XrdClZipArchive.cc b/src/XrdCl/XrdClZipArchive.cc index cba1e464d2f..41d14b53af9 100644 --- a/src/XrdCl/XrdClZipArchive.cc +++ b/src/XrdCl/XrdClZipArchive.cc @@ -28,6 +28,7 @@ #include "XrdCl/XrdClLog.hh" #include "XrdCl/XrdClDefaultEnv.hh" #include "XrdCl/XrdClConstants.hh" +#include "XrdCl/XrdClUtils.hh" #include "XrdZip/XrdZipZIP64EOCDL.hh" #include @@ -626,10 +627,18 @@ namespace XrdCl } auto wrtbuff = std::make_shared( GetCD() ); - chunks.emplace_back( cdoff, wrtbuff->size(), wrtbuff->data() ); + Pipeline p = XrdCl::Write( archive, cdoff, + wrtbuff->size(), + wrtbuff->data() ); wrtbufs.emplace_back( std::move( wrtbuff ) ); - Pipeline p = XrdCl::VectorWrite( archive, chunks ); + std::vector listsvec; + XrdCl::Utils::SplitChunks( listsvec, chunks, 262144, 1024 ); + + for(auto itr = listsvec.rbegin(); itr != listsvec.rend(); ++itr) + { + p = XrdCl::VectorWrite( archive, *itr ) | p; + } if( ckpinit ) p |= XrdCl::Checkpoint( archive, ChkPtCode::COMMIT ); p |= Close( archive ) >> From 5e96161d6f3209a03822198955f85afe32cf1459 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Mon, 3 Apr 2023 18:16:04 -0700 Subject: [PATCH 075/442] [HTTP] Initialize SecEntity.addrInfo to vaoid SEGV; Fixes #1986 --- src/XrdHttp/XrdHttpProtocol.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/XrdHttp/XrdHttpProtocol.cc b/src/XrdHttp/XrdHttpProtocol.cc index 8c976c9022e..a2938616876 100644 --- a/src/XrdHttp/XrdHttpProtocol.cc +++ b/src/XrdHttp/XrdHttpProtocol.cc @@ -280,6 +280,7 @@ XrdProtocol *XrdHttpProtocol::Match(XrdLink *lp) { // that is is https without invoking TLS on the actual link. Eventually, // we should just use the link's TLS native implementation. // + SecEntity.addrInfo = lp->AddrInfo(); XrdNetAddr *netP = const_cast(lp->NetAddr()); netP->SetDialect("https"); netP->SetTLS(true); From 6e79fc5d62f5c3c4cdf844ab4a2318c144dc055f Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Tue, 4 Apr 2023 17:22:50 -0700 Subject: [PATCH 076/442] [Server] Use correct format to print size_t; fixes #1989 --- src/XrdXrootd/XrdXrootdTpcMon.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdXrootd/XrdXrootdTpcMon.cc b/src/XrdXrootd/XrdXrootdTpcMon.cc index fe41583fc89..e95ec709795 100644 --- a/src/XrdXrootd/XrdXrootdTpcMon.cc +++ b/src/XrdXrootd/XrdXrootdTpcMon.cc @@ -44,7 +44,7 @@ namespace const char *json_fmt = "{\"TPC\":\"%s\",\"Client\":\"%s\"," "\"Xeq\":{\"Beg\":\"%s\",\"End\":\"%s\",\"RC\":%d,\"Strm\":%u,\"Type\":\"%s\"," "\"IPv\":%c}," -"\"Src\":\"%s\",\"Dst\":\"%s\",\"Size\":%d}"; +"\"Src\":\"%s\",\"Dst\":\"%s\",\"Size\":%zu}"; const char *urlFMT = ""; From 5663ee27f6a3d25291ce19751aa0bb344370d72b Mon Sep 17 00:00:00 2001 From: David Smith Date: Thu, 6 Apr 2023 15:45:41 +0200 Subject: [PATCH 077/442] [XrdApps] Let XrdClProxyPlugin work with PgRead, adding some missing methods --- .../XrdClProxyPlugin/ProxyPrefixFile.hh | 101 +++++++++++++++--- 1 file changed, 87 insertions(+), 14 deletions(-) diff --git a/src/XrdApps/XrdClProxyPlugin/ProxyPrefixFile.hh b/src/XrdApps/XrdClProxyPlugin/ProxyPrefixFile.hh index 730d5c038c9..b0b381a09bb 100644 --- a/src/XrdApps/XrdClProxyPlugin/ProxyPrefixFile.hh +++ b/src/XrdApps/XrdClProxyPlugin/ProxyPrefixFile.hh @@ -26,6 +26,8 @@ #include "XrdCl/XrdClDefaultEnv.hh" #include "XrdCl/XrdClPlugInInterface.hh" +#include + using namespace XrdCl; namespace xrdcl_proxy @@ -45,7 +47,7 @@ public: //---------------------------------------------------------------------------- //! Destructor //---------------------------------------------------------------------------- - virtual ~ProxyPrefixFile(); + virtual ~ProxyPrefixFile() override; //---------------------------------------------------------------------------- //! Open @@ -54,13 +56,13 @@ public: OpenFlags::Flags flags, Access::Mode mode, ResponseHandler* handler, - uint16_t timeout); + uint16_t timeout) override; //---------------------------------------------------------------------------- //! Close //---------------------------------------------------------------------------- virtual XRootDStatus Close(ResponseHandler* handler, - uint16_t timeout) + uint16_t timeout) override { return pFile->Close(handler, timeout); } @@ -70,7 +72,7 @@ public: //---------------------------------------------------------------------------- virtual XRootDStatus Stat(bool force, ResponseHandler* handler, - uint16_t timeout) + uint16_t timeout) override { return pFile->Stat(force, handler, timeout); } @@ -83,11 +85,23 @@ public: uint32_t size, void* buffer, ResponseHandler* handler, - uint16_t timeout) + uint16_t timeout) override { return pFile->Read(offset, size, buffer, handler, timeout); } + //------------------------------------------------------------------------ + //! PgRead + //------------------------------------------------------------------------ + virtual XRootDStatus PgRead( uint64_t offset, + uint32_t size, + void *buffer, + ResponseHandler *handler, + uint16_t timeout ) override + { + return pFile->PgRead(offset, size, buffer, handler, timeout); + } + //---------------------------------------------------------------------------- //! Write //---------------------------------------------------------------------------- @@ -95,16 +109,53 @@ public: uint32_t size, const void* buffer, ResponseHandler* handler, - uint16_t timeout) + uint16_t timeout) override { return pFile->Write(offset, size, buffer, handler, timeout); } + //------------------------------------------------------------------------ + //! Write + //------------------------------------------------------------------------ + virtual XRootDStatus Write( uint64_t offset, + Buffer &&buffer, + ResponseHandler *handler, + uint16_t timeout = 0 ) override + { + return pFile->Write(offset, std::move(buffer), handler, timeout); + } + + //------------------------------------------------------------------------ + //! Write + //------------------------------------------------------------------------ + virtual XRootDStatus Write( uint64_t offset, + uint32_t size, + Optional fdoff, + int fd, + ResponseHandler *handler, + uint16_t timeout = 0 ) override + { + return pFile->Write(offset, size, fdoff, fd, handler, timeout); + } + + //------------------------------------------------------------------------ + //! PgWrite + //------------------------------------------------------------------------ + virtual XRootDStatus PgWrite( uint64_t offset, + uint32_t nbpgs, + const void *buffer, + std::vector &cksums, + ResponseHandler *handler, + uint16_t timeout ) override + { + return pFile->PgWrite(offset, nbpgs, buffer, cksums, handler, timeout); + } + //---------------------------------------------------------------------------- //! Sync //---------------------------------------------------------------------------- virtual XRootDStatus Sync(ResponseHandler* handler, - uint16_t timeout) + uint16_t timeout) override { return pFile->Sync(handler, timeout); } @@ -114,7 +165,7 @@ public: //---------------------------------------------------------------------------- virtual XRootDStatus Truncate(uint64_t size, ResponseHandler* handler, - uint16_t timeout) + uint16_t timeout) override { return pFile->Truncate(size, handler, timeout); } @@ -125,17 +176,39 @@ public: virtual XRootDStatus VectorRead(const ChunkList& chunks, void* buffer, ResponseHandler* handler, - uint16_t timeout) + uint16_t timeout) override { return pFile->VectorRead(chunks, buffer, handler, timeout); } + //------------------------------------------------------------------------ + //! VectorWrite + //------------------------------------------------------------------------ + virtual XRootDStatus VectorWrite( const ChunkList &chunks, + ResponseHandler *handler, + uint16_t timeout = 0 ) override + { + return pFile->VectorWrite(chunks, handler, timeout); + } + + //------------------------------------------------------------------------ + //! @see XrdCl::File::WriteV + //------------------------------------------------------------------------ + virtual XRootDStatus WriteV( uint64_t offset, + const struct iovec *iov, + int iovcnt, + ResponseHandler *handler, + uint16_t timeout = 0 ) override + { + return pFile->WriteV(offset, iov, iovcnt, handler, timeout); + } + //---------------------------------------------------------------------------- //! Fcntl //---------------------------------------------------------------------------- virtual XRootDStatus Fcntl(const Buffer& arg, ResponseHandler* handler, - uint16_t timeout) + uint16_t timeout) override { return pFile->Fcntl(arg, handler, timeout); } @@ -144,7 +217,7 @@ public: //! Visa //---------------------------------------------------------------------------- virtual XRootDStatus Visa(ResponseHandler* handler, - uint16_t timeout) + uint16_t timeout) override { return pFile->Visa(handler, timeout); } @@ -152,7 +225,7 @@ public: //---------------------------------------------------------------------------- //! IsOpen //---------------------------------------------------------------------------- - virtual bool IsOpen() const + virtual bool IsOpen() const override { return pFile->IsOpen(); } @@ -161,7 +234,7 @@ public: //! SetProperty //---------------------------------------------------------------------------- virtual bool SetProperty(const std::string& name, - const std::string& value) + const std::string& value) override { return pFile->SetProperty(name, value); } @@ -170,7 +243,7 @@ public: //! GetProperty //---------------------------------------------------------------------------- virtual bool GetProperty(const std::string& name, - std::string& value) const + std::string& value) const override { return pFile->GetProperty(name, value); } From 11273fdac41ef9598e62a75b0a594d5ad6690dd7 Mon Sep 17 00:00:00 2001 From: David Smith Date: Tue, 11 Apr 2023 17:38:38 +0200 Subject: [PATCH 078/442] [Server] Allow XrdXrootdFile::Serialize() to used to wait more than once --- src/XrdXrootd/XrdXrootdFile.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/XrdXrootd/XrdXrootdFile.cc b/src/XrdXrootd/XrdXrootdFile.cc index f7fb1a62465..ad895fdadac 100644 --- a/src/XrdXrootd/XrdXrootdFile.cc +++ b/src/XrdXrootd/XrdXrootdFile.cc @@ -170,7 +170,10 @@ void XrdXrootdFile::Ref(int num) fileMutex.Lock(); refCount += num; TRACEI(FSAIO,"File::Ref="<Post(); + if (num < 0 && syncWait && refCount <= 0) + {syncWait->Post(); + syncWait = nullptr; + } fileMutex.UnLock(); } From 390e55e8ab4a7a4f222b1ab705f8a26f5c98fca1 Mon Sep 17 00:00:00 2001 From: David Smith Date: Tue, 7 Mar 2023 17:28:39 +0100 Subject: [PATCH 079/442] [XrdCl] Avoid possibility of Channel unregistering the wrong task --- src/XrdCl/XrdClChannel.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/src/XrdCl/XrdClChannel.cc b/src/XrdCl/XrdClChannel.cc index d2be5cd704f..6fba214d16e 100644 --- a/src/XrdCl/XrdClChannel.cc +++ b/src/XrdCl/XrdClChannel.cc @@ -132,7 +132,6 @@ namespace XrdCl Channel::~Channel() { pTickGenerator->Invalidate(); - pTaskManager->UnregisterTask( pTickGenerator ); delete pStream; pTransport->FinalizeChannel( pChannelData ); } From e15120b1ae8ee5bc988aa7f9e5292dcfad7f8c18 Mon Sep 17 00:00:00 2001 From: David Smith Date: Fri, 14 Apr 2023 17:00:20 +0200 Subject: [PATCH 080/442] [Server] Correct the fh returned when reusing a handle from external table --- src/XrdXrootd/XrdXrootdFile.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/XrdXrootd/XrdXrootdFile.cc b/src/XrdXrootd/XrdXrootdFile.cc index ad895fdadac..deab025b376 100644 --- a/src/XrdXrootd/XrdXrootdFile.cc +++ b/src/XrdXrootd/XrdXrootdFile.cc @@ -218,6 +218,7 @@ int XrdXrootdFileTable::Add(XrdXrootdFile *fp) else {i -= XRD_FTABSIZE; if (XTab && i < XTnum) fP = &XTab[i]; else fP = 0; + i += XRD_FTABSIZE; } if (fP && *fP == heldSpotP) {*fP = fp; From 2810d2d9c00dda39006facbd39d65c0482e66952 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 12 Apr 2023 16:16:36 +0200 Subject: [PATCH 081/442] Fix Clang -Winconsistent-missing-override warnings Example warning: In file included from .../XrdXrootdAioTask.cc:42: src/XrdXrootd/XrdXrootdAioBuff.hh:51:25: warning: 'Recycle' overrides a member function but is not marked 'override' [-Winconsistent-missing-override] virtual void Recycle(); ^ src/XrdSfs/XrdSfsAio.hh:79:14: note: overridden virtual function is here virtual void Recycle() = 0; --- src/XrdMacaroons/XrdMacaroonsAuthz.hh | 2 +- src/XrdPosix/XrdPosixFile.hh | 2 +- src/XrdPss/XrdPss.hh | 34 +++++++++++++-------------- src/XrdXrootd/XrdXrootdAioBuff.hh | 2 +- src/XrdXrootd/XrdXrootdProtocol.hh | 20 ++++++++-------- 5 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/XrdMacaroons/XrdMacaroonsAuthz.hh b/src/XrdMacaroons/XrdMacaroonsAuthz.hh index acf88bc9c73..5ab52df1576 100644 --- a/src/XrdMacaroons/XrdMacaroonsAuthz.hh +++ b/src/XrdMacaroons/XrdMacaroonsAuthz.hh @@ -43,7 +43,7 @@ public: // Macaroons don't have a concept off an "issuers"; return an empty // list. - virtual Issuers IssuerList() {return Issuers();} + virtual Issuers IssuerList() override {return Issuers();} private: XrdAccPrivs OnMissing(const XrdSecEntity *Entity, diff --git a/src/XrdPosix/XrdPosixFile.hh b/src/XrdPosix/XrdPosixFile.hh index e5cc6b1bda5..4d77b88d30c 100644 --- a/src/XrdPosix/XrdPosixFile.hh +++ b/src/XrdPosix/XrdPosixFile.hh @@ -165,7 +165,7 @@ inline void UpdtSize(size_t newsz) using XrdPosixObject::Who; -inline bool Who(XrdPosixFile **fileP) +inline bool Who(XrdPosixFile **fileP) override {*fileP = this; return true;} int Write(char *Buff, long long Offs, int Len) override; diff --git a/src/XrdPss/XrdPss.hh b/src/XrdPss/XrdPss.hh index e864a0983cf..4613c8ae48a 100644 --- a/src/XrdPss/XrdPss.hh +++ b/src/XrdPss/XrdPss.hh @@ -144,34 +144,34 @@ struct XrdVersionInfo; class XrdPssSys : public XrdOss { public: -virtual XrdOssDF *newDir(const char *tident) +virtual XrdOssDF *newDir(const char *tident) override {return (XrdOssDF *)new XrdPssDir(tident);} -virtual XrdOssDF *newFile(const char *tident) +virtual XrdOssDF *newFile(const char *tident) override {return (XrdOssDF *)new XrdPssFile(tident);} -virtual void Connect(XrdOucEnv &); +virtual void Connect(XrdOucEnv &) override; -virtual void Disc(XrdOucEnv &); +virtual void Disc(XrdOucEnv &) override; -int Chmod(const char *, mode_t mode, XrdOucEnv *eP=0); +int Chmod(const char *, mode_t mode, XrdOucEnv *eP=0) override; bool ConfigMapID(); virtual -int Create(const char *, const char *, mode_t, XrdOucEnv &, int opts=0); -void EnvInfo(XrdOucEnv *envP); -uint64_t Features() {return myFeatures;} +int Create(const char *, const char *, mode_t, XrdOucEnv &, int opts=0) override; +void EnvInfo(XrdOucEnv *envP) override; +uint64_t Features() override {return myFeatures;} int Init(XrdSysLogger *, const char *) override {return -ENOTSUP;} int Init(XrdSysLogger *, const char *, XrdOucEnv *envP) override; -int Lfn2Pfn(const char *Path, char *buff, int blen); +int Lfn2Pfn(const char *Path, char *buff, int blen) override; const -char *Lfn2Pfn(const char *Path, char *buff, int blen, int &rc); -int Mkdir(const char *, mode_t mode, int mkpath=0, XrdOucEnv *eP=0); -int Remdir(const char *, int Opts=0, XrdOucEnv *eP=0); +char *Lfn2Pfn(const char *Path, char *buff, int blen, int &rc) override; +int Mkdir(const char *, mode_t mode, int mkpath=0, XrdOucEnv *eP=0) override; +int Remdir(const char *, int Opts=0, XrdOucEnv *eP=0) override; int Rename(const char *, const char *, - XrdOucEnv *eP1=0, XrdOucEnv *eP2=0); -int Stat(const char *, struct stat *, int opts=0, XrdOucEnv *eP=0); -int Stats(char *bp, int bl); -int Truncate(const char *, unsigned long long, XrdOucEnv *eP=0); -int Unlink(const char *, int Opts=0, XrdOucEnv *eP=0); + XrdOucEnv *eP1=0, XrdOucEnv *eP2=0) override; +int Stat(const char *, struct stat *, int opts=0, XrdOucEnv *eP=0) override; +int Stats(char *bp, int bl) override; +int Truncate(const char *, unsigned long long, XrdOucEnv *eP=0) override; +int Unlink(const char *, int Opts=0, XrdOucEnv *eP=0) override; static const int PolNum = 2; enum PolAct {PolPath = 0, PolObj = 1}; diff --git a/src/XrdXrootd/XrdXrootdAioBuff.hh b/src/XrdXrootd/XrdXrootdAioBuff.hh index 2d2209741b4..174433dd1e3 100644 --- a/src/XrdXrootd/XrdXrootdAioBuff.hh +++ b/src/XrdXrootd/XrdXrootdAioBuff.hh @@ -48,7 +48,7 @@ XrdXrootdAioBuff* Alloc(XrdXrootdAioTask *arp); void doneWrite() override; -virtual void Recycle(); +virtual void Recycle() override; XrdXrootdAioBuff* next; diff --git a/src/XrdXrootd/XrdXrootdProtocol.hh b/src/XrdXrootd/XrdXrootdProtocol.hh index 162a30e5925..2ae6e5b343f 100644 --- a/src/XrdXrootd/XrdXrootdProtocol.hh +++ b/src/XrdXrootd/XrdXrootdProtocol.hh @@ -163,11 +163,11 @@ public: static char *Buffer(XrdSfsXioHandle h, int *bsz); // XrdSfsXio -XrdSfsXioHandle Claim(const char *buff, int datasz, int minasz=0);// XrdSfsXio +XrdSfsXioHandle Claim(const char *buff, int datasz, int minasz=0) override;// XrdSfsXio static int Configure(char *parms, XrdProtocol_Config *pi); - void DoIt() {(*this.*Resume)();} + void DoIt() override {(*this.*Resume)();} int do_WriteSpan(); @@ -181,29 +181,29 @@ static int Configure(char *parms, XrdProtocol_Config *pi); int getPathID() {return PathID;} - XrdProtocol *Match(XrdLink *lp); + XrdProtocol *Match(XrdLink *lp) override; - int Process(XrdLink *lp); // Sync: Job->Link.DoIt->Process + int Process(XrdLink *lp) override; // Sync: Job->Link.DoIt->Process int Process2(); int ProcSig(); - void Recycle(XrdLink *lp, int consec, const char *reason); + void Recycle(XrdLink *lp, int consec, const char *reason) override; static void Reclaim(XrdSfsXioHandle h); // XrdSfsXio - int SendFile(int fildes); // XrdSfsDio + int SendFile(int fildes) override; // XrdSfsDio - int SendFile(XrdOucSFVec *sfvec, int sfvnum); // XrdSfsDio + int SendFile(XrdOucSFVec *sfvec, int sfvnum) override; // XrdSfsDio - void SetFD(int fildes); // XrdSfsDio + void SetFD(int fildes) override; // XrdSfsDio - int Stats(char *buff, int blen, int do_sync=0); + int Stats(char *buff, int blen, int do_sync=0) override; void StreamNOP(); -XrdSfsXioHandle Swap(const char *buff, XrdSfsXioHandle h=0); // XrdSfsXio +XrdSfsXioHandle Swap(const char *buff, XrdSfsXioHandle h=0) override; // XrdSfsXio XrdXrootdProtocol *VerifyStream(int &rc, int pID, bool lok=true); From 9adf6bc4f98d3c32ca4791b6957dc0f8259b92e7 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 12 Apr 2023 16:29:44 +0200 Subject: [PATCH 082/442] Fix Clang -Wbraced-scalar-init warning src/XrdXrootd/XrdXrootdProtocol.cc:1504:25: warning: braces around scalar initializer [-Wbraced-scalar-init] linkAioReq = {0}; --- src/XrdXrootd/XrdXrootdProtocol.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdXrootd/XrdXrootdProtocol.cc b/src/XrdXrootd/XrdXrootdProtocol.cc index ddc872fd7e0..2e027b393c1 100644 --- a/src/XrdXrootd/XrdXrootdProtocol.cc +++ b/src/XrdXrootd/XrdXrootdProtocol.cc @@ -1501,7 +1501,7 @@ void XrdXrootdProtocol::Reset() doTLS = tlsNot; // Assume client is not capable. This will be ableTLS = false; // resolved during the kXR_protocol interchange. isTLS = false; // Made true when link converted to TLS - linkAioReq = {0}; + linkAioReq = 0; pioFree = pioFirst = pioLast = 0; isActive = isLinkWT= isNOP = isDead = false; sigNeed = sigHere = sigRead = false; From b184d45892bc982efe6cbe3a99f5fbf641b54c40 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 21 Mar 2023 11:31:44 +0100 Subject: [PATCH 083/442] [XrdPosix] Fix Clang -Wtautological-constant-out-of-range-compare warning src/XrdPosix/XrdPosixAdmin.cc:71:10: warning: result of comparison of constant 32940614417338485 with expression of type 'unsigned int' is always false [-Wtautological-constant-out-of-range-compare] if (i > std::numeric_limits::max() / sizeof(XrdCl::URL)) ~ ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Here std::numeric_limits::max() / sizeof(XrdCl::URL) == 32940614417338485, which is bigger than the largest unsigned int, therefore the comparison will always be false. We need i to be able to hold large enough values. Fixes 0dc292fbf63d25c6a9e000a2e66d7e3e0b8db735. --- src/XrdPosix/XrdPosixAdmin.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdPosix/XrdPosixAdmin.cc b/src/XrdPosix/XrdPosixAdmin.cc index 3b7e9810353..5e37e72a267 100644 --- a/src/XrdPosix/XrdPosixAdmin.cc +++ b/src/XrdPosix/XrdPosixAdmin.cc @@ -51,7 +51,7 @@ XrdCl::URL *XrdPosixAdmin::FanOut(int &num) XrdCl::URL *uVec; XrdNetAddr netLoc; const char *hName; - unsigned int i; + unsigned long i; // Make sure admin is ok // From 6f61cb3afe37d5150cd9ceafbf4f720268e80097 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 21 Mar 2023 11:31:21 +0100 Subject: [PATCH 084/442] [XrdSecgsi] Fix Clang -Wfortify-source warning src/XrdSecgsi/XrdSecgsiGMAPFunDN.cc:207:40: warning: 'sscanf' may overflow; destination buffer in argument 3 has size 4096, but the corresponding specifier may require size 4097 [-Wfortify-source] if (sscanf(l, "%4096s %256s", val, usr) >= 2) { ^ --- src/XrdSecgsi/XrdSecgsiGMAPFunDN.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdSecgsi/XrdSecgsiGMAPFunDN.cc b/src/XrdSecgsi/XrdSecgsiGMAPFunDN.cc index 38835f0e316..9ecb045a4e2 100644 --- a/src/XrdSecgsi/XrdSecgsiGMAPFunDN.cc +++ b/src/XrdSecgsi/XrdSecgsiGMAPFunDN.cc @@ -204,7 +204,7 @@ int XrdSecgsiGMAPInit(const char *parms) if (len < 2) continue; if (l[0] == '#') continue; if (l[len-1] == '\n') l[len-1] = '\0'; - if (sscanf(l, "%4096s %256s", val, usr) >= 2) { + if (sscanf(l, "%4095s %255s", val, usr) >= 2) { XrdOucString stype = "matching"; char *p = &val[0]; int type = kFull; From 78a77d7c9d6724a3df46d4fc26f11f117bbb9e75 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 12 Apr 2023 16:39:47 +0200 Subject: [PATCH 085/442] [Tests] Fix typo in FileCopy test Caught by a warning in Clang: tests/XrdClTests/FileCopyTest.cc:400:62: warning: variable 'st' is uninitialized when used within its own initialization [-Wuninitialized] CPPUNIT_ASSERT_XRDST( status.status == XrdCl::stError && st.code == XrdCl::errNotFound ); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ tests/XrdClTests/../common/CppUnitXrdHelpers.hh:36:28: note: expanded from macro 'CPPUNIT_ASSERT_XRDST' XrdCl::XRootDStatus st = x; --- tests/XrdClTests/FileCopyTest.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/XrdClTests/FileCopyTest.cc b/tests/XrdClTests/FileCopyTest.cc index 5c9c8de4d57..90e6845e795 100644 --- a/tests/XrdClTests/FileCopyTest.cc +++ b/tests/XrdClTests/FileCopyTest.cc @@ -397,7 +397,7 @@ void FileCopyTest::CopyTestFunc( bool thirdParty ) CPPUNIT_ASSERT_XRDST_NOTOK( process12.Run(0), XrdCl::errCheckSumError ); XrdCl::StatInfo *info = 0; XrdCl::XRootDStatus status = fs.Stat( targetPath, info ); - CPPUNIT_ASSERT_XRDST( status.status == XrdCl::stError && st.code == XrdCl::errNotFound ); + CPPUNIT_ASSERT_XRDST( status.status == XrdCl::stError && status.code == XrdCl::errNotFound ); properties.Clear(); //-------------------------------------------------------------------------- From 60097be0806b7c27979ae125439fda094c8eac15 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 12 Apr 2023 16:40:11 +0200 Subject: [PATCH 086/442] [Tests] Change name of macro local variable to avoid clashes The name st is used extensively throughout the code, so using the same name here can cause shadowing problems or hide typos like the one fixed in the previous commit. --- tests/common/CppUnitXrdHelpers.hh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/common/CppUnitXrdHelpers.hh b/tests/common/CppUnitXrdHelpers.hh index 99925bb2726..b4634433133 100644 --- a/tests/common/CppUnitXrdHelpers.hh +++ b/tests/common/CppUnitXrdHelpers.hh @@ -25,18 +25,18 @@ #define CPPUNIT_ASSERT_XRDST_NOTOK( x, err ) \ { \ - XrdCl::XRootDStatus st = x; \ + XrdCl::XRootDStatus _st = x; \ std::string msg = "["; msg += #x; msg += "]: "; \ - msg += st.ToStr(); \ - CPPUNIT_ASSERT_MESSAGE( msg, !st.IsOK() && st.code == err ); \ + msg += _st.ToStr(); \ + CPPUNIT_ASSERT_MESSAGE( msg, !_st.IsOK() && _st.code == err ); \ } #define CPPUNIT_ASSERT_XRDST( x ) \ { \ - XrdCl::XRootDStatus st = x; \ + XrdCl::XRootDStatus _st = x; \ std::string msg = "["; msg += #x; msg += "]: "; \ - msg += st.ToStr(); \ - CPPUNIT_ASSERT_MESSAGE( msg, st.IsOK() ); \ + msg += _st.ToStr(); \ + CPPUNIT_ASSERT_MESSAGE( msg, _st.IsOK() ); \ } #define CPPUNIT_ASSERT_ERRNO( x ) \ From c1d12a3f58138483fd60e29709f3848a9cf1c1ae Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 13 Apr 2023 17:57:40 +0200 Subject: [PATCH 087/442] [XrdPosix] Fix build with Clang and _FORTIFY_SOURCE enabled Some of the checks added by defining _FORTIFY_SOURCE break the build with clang with errors like the one shown below. See the manual page for feature_test_macros(7) for more information. xrootd/src/XrdPosix/XrdPosixPreload32.cc:375:9: error: redefinition of a 'extern inline' function 'pread' is not supported in C++ ssize_t pread(int fildes, void *buf, size_t nbyte, off_t offset) ^ /usr/include/bits/unistd.h:72:1: note: previous definition is here pread (int __fd, void *__buf, size_t __nbytes, __off_t __offset) ^ Since distributions enable _FORTIFY_SOURCE by default, it's better to disable it for the XrdPosix plugin when using clang. Builds with GCC are not affected by this problem. Fixes #1975. --- src/XrdPosix/XrdPosixPreload.cc | 4 ++++ src/XrdPosix/XrdPosixPreload32.cc | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/XrdPosix/XrdPosixPreload.cc b/src/XrdPosix/XrdPosixPreload.cc index 867b7347985..a62f0c658fe 100644 --- a/src/XrdPosix/XrdPosixPreload.cc +++ b/src/XrdPosix/XrdPosixPreload.cc @@ -28,6 +28,10 @@ /* specific prior written permission of the institution or contributor. */ /******************************************************************************/ +#if defined(__clang__) && defined(_FORTIFY_SOURCE) +#undef _FORTIFY_SOURCE +#endif + #include #include #include diff --git a/src/XrdPosix/XrdPosixPreload32.cc b/src/XrdPosix/XrdPosixPreload32.cc index 7280dd2ec13..436e23dc2bb 100644 --- a/src/XrdPosix/XrdPosixPreload32.cc +++ b/src/XrdPosix/XrdPosixPreload32.cc @@ -28,6 +28,10 @@ /* specific prior written permission of the institution or contributor. */ /******************************************************************************/ +#if defined(__clang__) && defined(_FORTIFY_SOURCE) +#undef _FORTIFY_SOURCE +#endif + #ifdef _LARGEFILE_SOURCE #undef _LARGEFILE_SOURCE #endif From 404bac4ef802124a1b13e9552993efa6a774f682 Mon Sep 17 00:00:00 2001 From: David Smith Date: Thu, 27 Apr 2023 14:58:22 +0200 Subject: [PATCH 088/442] [Client] Add return codes for error checking in the network Event handler --- src/XrdCl/XrdClAsyncSocketHandler.cc | 202 +++++++++++++++++---------- src/XrdCl/XrdClAsyncSocketHandler.hh | 38 +++-- src/XrdCl/XrdClStream.cc | 11 +- src/XrdCl/XrdClStream.hh | 5 +- 4 files changed, 166 insertions(+), 90 deletions(-) diff --git a/src/XrdCl/XrdClAsyncSocketHandler.cc b/src/XrdCl/XrdClAsyncSocketHandler.cc index c416cebb367..76f1ba355c5 100644 --- a/src/XrdCl/XrdClAsyncSocketHandler.cc +++ b/src/XrdCl/XrdClAsyncSocketHandler.cc @@ -136,6 +136,9 @@ namespace XrdCl } pHandShakeDone = false; + pTlsHandShakeOngoing = false; + pHSWaitStarted = 0; + pHSWaitSeconds = 0; //-------------------------------------------------------------------------- // Initiate async connection to the address @@ -213,6 +216,23 @@ namespace XrdCl //-------------------------------------------------------------------------- type = pSocket->MapEvent( type ); + //-------------------------------------------------------------------------- + // Handle any read or write events. If any of the handlers indicate an error + // we will have been disconnected. A disconnection may cause the current + // object to be asynchronously reused or deleted, so we return immediately. + //-------------------------------------------------------------------------- + if( !EventRead( type ) ) + return; + + if( !EventWrite( type ) ) + return; + } + + //---------------------------------------------------------------------------- + // Handler for read related socket events + //---------------------------------------------------------------------------- + bool AsyncSocketHandler::EventRead( uint8_t type ) + { //-------------------------------------------------------------------------- // Read event //-------------------------------------------------------------------------- @@ -220,11 +240,12 @@ namespace XrdCl { pLastActivity = time(0); if( unlikely( pTlsHandShakeOngoing ) ) - OnTLSHandShake(); - else if( likely( pHandShakeDone ) ) - OnRead(); - else - OnReadWhileHandshaking(); + return OnTLSHandShake(); + + if( likely( pHandShakeDone ) ) + return OnRead(); + + return OnReadWhileHandshaking(); } //-------------------------------------------------------------------------- @@ -233,14 +254,25 @@ namespace XrdCl else if( type & ReadTimeOut ) { if( pHSWaitSeconds ) - CheckHSWait(); + { + if( !CheckHSWait() ) + return false; + } if( likely( pHandShakeDone ) ) - OnReadTimeout(); - else - OnTimeoutWhileHandshaking(); + return OnReadTimeout(); + + return OnTimeoutWhileHandshaking(); } + return true; + } + + //---------------------------------------------------------------------------- + // Handler for write related socket events + //---------------------------------------------------------------------------- + bool AsyncSocketHandler::EventWrite( uint8_t type ) + { //-------------------------------------------------------------------------- // Write event //-------------------------------------------------------------------------- @@ -248,19 +280,21 @@ namespace XrdCl { pLastActivity = time(0); if( unlikely( pSocket->GetStatus() == Socket::Connecting ) ) - OnConnectionReturn(); + return OnConnectionReturn(); + //------------------------------------------------------------------------ // Make sure we are not writing anything if we have been told to wait. //------------------------------------------------------------------------ - else if( pHSWaitSeconds == 0 ) - { - if( unlikely( pTlsHandShakeOngoing ) ) - OnTLSHandShake(); - else if( likely( pHandShakeDone ) ) - OnWrite(); - else - OnWriteWhileHandshaking(); - } + if( pHSWaitSeconds != 0 ) + return true; + + if( unlikely( pTlsHandShakeOngoing ) ) + return OnTLSHandShake(); + + if( likely( pHandShakeDone ) ) + return OnWrite(); + + return OnWriteWhileHandshaking(); } //-------------------------------------------------------------------------- @@ -269,16 +303,18 @@ namespace XrdCl else if( type & WriteTimeOut ) { if( likely( pHandShakeDone ) ) - OnWriteTimeout(); - else - OnTimeoutWhileHandshaking(); + return OnWriteTimeout(); + + return OnTimeoutWhileHandshaking(); } + + return true; } //---------------------------------------------------------------------------- // Connect returned //---------------------------------------------------------------------------- - void AsyncSocketHandler::OnConnectionReturn() + bool AsyncSocketHandler::OnConnectionReturn() { //-------------------------------------------------------------------------- // Check whether we were able to connect @@ -303,7 +339,7 @@ namespace XrdCl XrdSysE2T( errno ) ); pStream->OnConnectError( pSubStreamNum, XRootDStatus( stFatal, errSocketOptError, errno ) ); - return; + return false; } //-------------------------------------------------------------------------- @@ -315,7 +351,7 @@ namespace XrdCl pStreamName.c_str(), XrdSysE2T( errorCode ) ); pStream->OnConnectError( pSubStreamNum, XRootDStatus( stError, errConnectionError ) ); - return; + return false; } pSocket->SetStatus( Socket::Connected ); @@ -326,7 +362,7 @@ namespace XrdCl if( !st.IsOK() ) { pStream->OnConnectError( pSubStreamNum, st ); - return; + return false; } //-------------------------------------------------------------------------- @@ -344,7 +380,7 @@ namespace XrdCl log->Error( AsyncSockMsg, "[%s] Connection negotiation failed", pStreamName.c_str() ); pStream->OnConnectError( pSubStreamNum, st ); - return; + return false; } if( st.code != suRetry ) @@ -372,19 +408,20 @@ namespace XrdCl { pStream->OnConnectError( pSubStreamNum, XRootDStatus( stFatal, errPollerError ) ); - return; + return false; } + return true; } //---------------------------------------------------------------------------- // Got a write readiness event //---------------------------------------------------------------------------- - void AsyncSocketHandler::OnWrite() + bool AsyncSocketHandler::OnWrite() { if( !reqwriter ) { OnFault( XRootDStatus( stError, errInternal, 0, "Request writer is null." ) ); - return; + return false; } //-------------------------------------------------------------------------- // Let's do the writing ... @@ -396,30 +433,34 @@ namespace XrdCl // We failed //------------------------------------------------------------------------ OnFault( st ); - return; + return false; } //-------------------------------------------------------------------------- // We are not done yet //-------------------------------------------------------------------------- - if( st.code == suRetry) return; + if( st.code == suRetry) return true; //-------------------------------------------------------------------------- // Disable the respective substream if empty //-------------------------------------------------------------------------- reqwriter->Reset(); pStream->DisableIfEmpty( pSubStreamNum ); + return true; } //---------------------------------------------------------------------------- // Got a write readiness event while handshaking //---------------------------------------------------------------------------- - void AsyncSocketHandler::OnWriteWhileHandshaking() + bool AsyncSocketHandler::OnWriteWhileHandshaking() { XRootDStatus st; if( !hswriter || !hswriter->HasMsg() ) { if( !(st = DisableUplink()).IsOK() ) + { OnFaultWhileHandshaking( st ); - return; + return false; + } + return true; } //-------------------------------------------------------------------------- // Let's do the writing ... @@ -431,25 +472,29 @@ namespace XrdCl // We failed //------------------------------------------------------------------------ OnFaultWhileHandshaking( st ); - return; + return false; } //-------------------------------------------------------------------------- // We are not done yet //-------------------------------------------------------------------------- - if( st.code == suRetry ) return; + if( st.code == suRetry ) return true; //-------------------------------------------------------------------------- // Disable the uplink // Note: at this point we don't deallocate the HS message as we might need // to re-send it in case of a kXR_wait response //-------------------------------------------------------------------------- if( !(st = DisableUplink()).IsOK() ) + { OnFaultWhileHandshaking( st ); + return false; + } + return true; } //---------------------------------------------------------------------------- // Got a read readiness event //---------------------------------------------------------------------------- - void AsyncSocketHandler::OnRead() + bool AsyncSocketHandler::OnRead() { //-------------------------------------------------------------------------- // Make sure the response reader object exists @@ -457,7 +502,7 @@ namespace XrdCl if( !rspreader ) { OnFault( XRootDStatus( stError, errInternal, 0, "Response reader is null." ) ); - return; + return false; } //-------------------------------------------------------------------------- @@ -471,7 +516,7 @@ namespace XrdCl if( !st.IsOK() && st.code == errCorruptedHeader ) { OnHeaderCorruption(); - return; + return false; } //-------------------------------------------------------------------------- @@ -480,24 +525,25 @@ namespace XrdCl if( !st.IsOK() ) { OnFault( st ); - return; + return false; } //-------------------------------------------------------------------------- // We are not done yet //-------------------------------------------------------------------------- - if( st.code == suRetry ) return; + if( st.code == suRetry ) return true; //-------------------------------------------------------------------------- // We are done, reset the response reader so we can read out next message //-------------------------------------------------------------------------- rspreader->Reset(); + return true; } //---------------------------------------------------------------------------- // Got a read readiness event while handshaking //---------------------------------------------------------------------------- - void AsyncSocketHandler::OnReadWhileHandshaking() + bool AsyncSocketHandler::OnReadWhileHandshaking() { //-------------------------------------------------------------------------- // Make sure the response reader object exists @@ -505,7 +551,7 @@ namespace XrdCl if( !hsreader ) { OnFault( XRootDStatus( stError, errInternal, 0, "Hand-shake reader is null." ) ); - return; + return false; } //-------------------------------------------------------------------------- @@ -516,19 +562,19 @@ namespace XrdCl if( !st.IsOK() ) { OnFaultWhileHandshaking( st ); - return; + return false; } if( st.code != suDone ) - return; + return true; - HandleHandShake( hsreader->ReleaseMsg() ); + return HandleHandShake( hsreader->ReleaseMsg() ); } //------------------------------------------------------------------------ // Handle the handshake message //------------------------------------------------------------------------ - void AsyncSocketHandler::HandleHandShake( std::unique_ptr msg ) + bool AsyncSocketHandler::HandleHandShake( std::unique_ptr msg ) { //-------------------------------------------------------------------------- // OK, we have a new message, let's deal with it; @@ -547,7 +593,7 @@ namespace XrdCl if( !st.IsOK() ) { OnFaultWhileHandshaking( st ); - return; + return false; } if( st.code == suRetry ) @@ -568,6 +614,7 @@ namespace XrdCl pStreamName.c_str() ); OnFaultWhileHandshaking( XRootDStatus( stError, errSocketTimeout ) ); + return false; } else { @@ -581,15 +628,14 @@ namespace XrdCl pHSWaitStarted = time( 0 ); pHSWaitSeconds = waitSeconds; } - return; + return true; } //------------------------------------------------------------------------ // We are re-sending a protocol request //------------------------------------------------------------------------ else if( pHandShakeData->out ) { - SendHSMsg(); - return; + return SendHSMsg(); } } @@ -600,19 +646,22 @@ namespace XrdCl pTransport->NeedEncryption( pHandShakeData.get(), *pChannelData ) ) { XRootDStatus st = DoTlsHandShake(); - if( !st.IsOK() || st.code == suRetry ) return; + if( !st.IsOK() ) + return false; + if ( st.code == suRetry ) + return true; } //-------------------------------------------------------------------------- // Now prepare the next step of the hand-shake procedure //-------------------------------------------------------------------------- - HandShakeNextStep( st.IsOK() && st.code == suDone ); + return HandShakeNextStep( st.IsOK() && st.code == suDone ); } //------------------------------------------------------------------------ // Prepare the next step of the hand-shake procedure //------------------------------------------------------------------------ - void AsyncSocketHandler::HandShakeNextStep( bool done ) + bool AsyncSocketHandler::HandShakeNextStep( bool done ) { //-------------------------------------------------------------------------- // We successfully proceeded to the next step @@ -636,7 +685,7 @@ namespace XrdCl if( !(st = EnableUplink()).IsOK() ) { OnFaultWhileHandshaking( st ); - return; + return false; } pHandShakeDone = true; pStream->OnConnect( pSubStreamNum ); @@ -646,8 +695,9 @@ namespace XrdCl //-------------------------------------------------------------------------- else if( pHandShakeData->out ) { - SendHSMsg(); + return SendHSMsg(); } + return true; } //---------------------------------------------------------------------------- @@ -677,27 +727,31 @@ namespace XrdCl //---------------------------------------------------------------------------- // Handle write timeout //---------------------------------------------------------------------------- - void AsyncSocketHandler::OnWriteTimeout() + bool AsyncSocketHandler::OnWriteTimeout() { - pStream->OnWriteTimeout( pSubStreamNum ); + return pStream->OnWriteTimeout( pSubStreamNum ); } //---------------------------------------------------------------------------- // Handler read timeout //---------------------------------------------------------------------------- - void AsyncSocketHandler::OnReadTimeout() + bool AsyncSocketHandler::OnReadTimeout() { - pStream->OnReadTimeout( pSubStreamNum ); + return pStream->OnReadTimeout( pSubStreamNum ); } //---------------------------------------------------------------------------- // Handle timeout while handshaking //---------------------------------------------------------------------------- - void AsyncSocketHandler::OnTimeoutWhileHandshaking() + bool AsyncSocketHandler::OnTimeoutWhileHandshaking() { time_t now = time(0); if( now > pConnectionStarted+pConnectionTimeout ) + { OnFaultWhileHandshaking( XRootDStatus( stError, errSocketTimeout ) ); + return false; + } + return true; } //---------------------------------------------------------------------------- @@ -723,8 +777,8 @@ namespace XrdCl XRootDStatus st; if( !( st = pSocket->TlsHandShake( this, pUrl.GetHostName() ) ).IsOK() ) { - OnFaultWhileHandshaking( st ); pTlsHandShakeOngoing = false; + OnFaultWhileHandshaking( st ); return st; } @@ -743,25 +797,28 @@ namespace XrdCl //---------------------------------------------------------------------------- // Handle read/write event if we are in the middle of a TLS hand-shake //---------------------------------------------------------------------------- - inline void AsyncSocketHandler::OnTLSHandShake() + inline bool AsyncSocketHandler::OnTLSHandShake() { XRootDStatus st = DoTlsHandShake(); - if( !st.IsOK() || st.code == suRetry ) return; + if( !st.IsOK() ) + return false; + if ( st.code == suRetry ) + return true; - HandShakeNextStep( pTransport->HandShakeDone( pHandShakeData.get(), - *pChannelData ) ); + return HandShakeNextStep( pTransport->HandShakeDone( pHandShakeData.get(), + *pChannelData ) ); } //---------------------------------------------------------------------------- // Prepare a HS writer for sending and enable uplink //---------------------------------------------------------------------------- - void AsyncSocketHandler::SendHSMsg() + bool AsyncSocketHandler::SendHSMsg() { if( !hswriter ) { OnFaultWhileHandshaking( XRootDStatus( stError, errInternal, 0, "HS writer object missing!" ) ); - return; + return false; } //-------------------------------------------------------------------------- // We only set a new HS message if this is not a replay due to kXR_wait @@ -783,8 +840,9 @@ namespace XrdCl if( !(st = EnableUplink()).IsOK() ) { OnFaultWhileHandshaking( st ); - return; + return false; } + return true; } kXR_int32 AsyncSocketHandler::HandleWaitRsp( Message *msg ) @@ -801,7 +859,7 @@ namespace XrdCl //---------------------------------------------------------------------------- // Check if HS wait time elapsed //---------------------------------------------------------------------------- - void AsyncSocketHandler::CheckHSWait() + bool AsyncSocketHandler::CheckHSWait() { time_t now = time( 0 ); if( now - pHSWaitStarted >= pHSWaitSeconds ) @@ -809,13 +867,15 @@ namespace XrdCl Log *log = DefaultEnv::GetLog(); log->Debug( AsyncSockMsg, "[%s] The hand-shake wait time elapsed, will " "replay the endsess request.", pStreamName.c_str() ); - SendHSMsg(); + if( !SendHSMsg() ) + return false; //------------------------------------------------------------------------ // Make sure the wait state is reset //------------------------------------------------------------------------ pHSWaitSeconds = 0; pHSWaitStarted = 0; } + return true; } //------------------------------------------------------------------------ diff --git a/src/XrdCl/XrdClAsyncSocketHandler.hh b/src/XrdCl/XrdClAsyncSocketHandler.hh index f696e754af9..8589715a846 100644 --- a/src/XrdCl/XrdClAsyncSocketHandler.hh +++ b/src/XrdCl/XrdClAsyncSocketHandler.hh @@ -30,6 +30,7 @@ #include "XrdCl/XrdClAsyncHSReader.hh" #include "XrdCl/XrdClAsyncMsgWriter.hh" #include "XrdCl/XrdClAsyncHSWriter.hh" +#include "XrdOuc/XrdOucCompiler.hh" namespace XrdCl { @@ -149,37 +150,38 @@ namespace XrdCl //------------------------------------------------------------------------ // Connect returned //------------------------------------------------------------------------ - virtual void OnConnectionReturn(); + virtual bool OnConnectionReturn() XRD_WARN_UNUSED_RESULT; //------------------------------------------------------------------------ // Got a write readiness event //------------------------------------------------------------------------ - void OnWrite(); + bool OnWrite() XRD_WARN_UNUSED_RESULT; //------------------------------------------------------------------------ // Got a write readiness event while handshaking //------------------------------------------------------------------------ - void OnWriteWhileHandshaking(); + bool OnWriteWhileHandshaking() XRD_WARN_UNUSED_RESULT; //------------------------------------------------------------------------ // Got a read readiness event //------------------------------------------------------------------------ - void OnRead(); + bool OnRead() XRD_WARN_UNUSED_RESULT; //------------------------------------------------------------------------ // Got a read readiness event while handshaking //------------------------------------------------------------------------ - void OnReadWhileHandshaking(); + bool OnReadWhileHandshaking() XRD_WARN_UNUSED_RESULT; //------------------------------------------------------------------------ // Handle the handshake message //------------------------------------------------------------------------ - void HandleHandShake( std::unique_ptr msg ); + bool HandleHandShake( std::unique_ptr msg ) + XRD_WARN_UNUSED_RESULT; //------------------------------------------------------------------------ // Prepare the next step of the hand-shake procedure //------------------------------------------------------------------------ - void HandShakeNextStep( bool done ); + bool HandShakeNextStep( bool done ) XRD_WARN_UNUSED_RESULT; //------------------------------------------------------------------------ // Handle fault @@ -194,17 +196,17 @@ namespace XrdCl //------------------------------------------------------------------------ // Handle write timeout event //------------------------------------------------------------------------ - void OnWriteTimeout(); + bool OnWriteTimeout() XRD_WARN_UNUSED_RESULT; //------------------------------------------------------------------------ // Handle read timeout event //------------------------------------------------------------------------ - void OnReadTimeout(); + bool OnReadTimeout() XRD_WARN_UNUSED_RESULT; //------------------------------------------------------------------------ // Handle timeout event while handshaking //------------------------------------------------------------------------ - void OnTimeoutWhileHandshaking(); + bool OnTimeoutWhileHandshaking() XRD_WARN_UNUSED_RESULT; //------------------------------------------------------------------------ // Handle header corruption in case of kXR_status response @@ -229,12 +231,12 @@ namespace XrdCl // Handle read/write event if we are in the middle of a TLS hand-shake //------------------------------------------------------------------------ // Handle read/write event if we are in the middle of a TLS hand-shake - void OnTLSHandShake(); + bool OnTLSHandShake() XRD_WARN_UNUSED_RESULT; //------------------------------------------------------------------------ // Prepare a HS writer for sending and enable uplink //------------------------------------------------------------------------ - void SendHSMsg(); + bool SendHSMsg() XRD_WARN_UNUSED_RESULT; //------------------------------------------------------------------------ // Extract the value of a wait response @@ -248,7 +250,17 @@ namespace XrdCl //------------------------------------------------------------------------ // Check if HS wait time elapsed //------------------------------------------------------------------------ - void CheckHSWait(); + bool CheckHSWait() XRD_WARN_UNUSED_RESULT; + + //------------------------------------------------------------------------ + // Handler for read related socket events + //------------------------------------------------------------------------ + inline bool EventRead( uint8_t type ) XRD_WARN_UNUSED_RESULT; + + //------------------------------------------------------------------------ + // Handler for write related socket events + //------------------------------------------------------------------------ + inline bool EventWrite( uint8_t type ) XRD_WARN_UNUSED_RESULT; //------------------------------------------------------------------------ // Data members diff --git a/src/XrdCl/XrdClStream.cc b/src/XrdCl/XrdClStream.cc index cb64cbb6bc6..097ecff2f72 100644 --- a/src/XrdCl/XrdClStream.cc +++ b/src/XrdCl/XrdClStream.cc @@ -1021,13 +1021,13 @@ namespace XrdCl //---------------------------------------------------------------------------- // Call back when a message has been reconstructed //---------------------------------------------------------------------------- - void Stream::OnReadTimeout( uint16_t substream ) + bool Stream::OnReadTimeout( uint16_t substream ) { //-------------------------------------------------------------------------- // We only take the main stream into account //-------------------------------------------------------------------------- if( substream != 0 ) - return; + return true; //-------------------------------------------------------------------------- // Check if there is no outgoing messages and if the stream TTL is elapesed. @@ -1067,7 +1067,7 @@ namespace XrdCl // object that aggregates this Stream. //---------------------------------------------------------------------- DefaultEnv::GetPostMaster()->ForceDisconnect( *pUrl ); - return; + return false; } } @@ -1080,14 +1080,17 @@ namespace XrdCl { scopedLock.UnLock(); OnError( substream, st ); + return false; } + return true; } //---------------------------------------------------------------------------- // Call back when a message has been reconstru //---------------------------------------------------------------------------- - void Stream::OnWriteTimeout( uint16_t /*substream*/ ) + bool Stream::OnWriteTimeout( uint16_t /*substream*/ ) { + return true; } //---------------------------------------------------------------------------- diff --git a/src/XrdCl/XrdClStream.hh b/src/XrdCl/XrdClStream.hh index 19522d37de9..5ded1166762 100644 --- a/src/XrdCl/XrdClStream.hh +++ b/src/XrdCl/XrdClStream.hh @@ -31,6 +31,7 @@ #include "XrdSys/XrdSysPthread.hh" #include "XrdSys/XrdSysRAtomic.hh" #include "XrdNet/XrdNetAddr.hh" +#include "XrdOuc/XrdOucCompiler.hh" #include #include #include @@ -219,12 +220,12 @@ namespace XrdCl //------------------------------------------------------------------------ //! On read timeout //------------------------------------------------------------------------ - void OnReadTimeout( uint16_t subStream ); + bool OnReadTimeout( uint16_t subStream ) XRD_WARN_UNUSED_RESULT; //------------------------------------------------------------------------ //! On write timeout //------------------------------------------------------------------------ - void OnWriteTimeout( uint16_t subStream ); + bool OnWriteTimeout( uint16_t subStream ) XRD_WARN_UNUSED_RESULT; //------------------------------------------------------------------------ //! Register channel event handler From 21ddb013e42f2861e17c3dcf167e53e91e483e23 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 4 May 2023 13:40:54 +0200 Subject: [PATCH 089/442] [Python] Fix warning when building version string xrootd/bindings/python/src/PyXRootDEnv.hh:137:66: warning: adding 'int' to a string does not append to the string [-Wstring-plus-int] static std::string verstr( XrdVERSION[0] == 'v' ? XrdVERSION + 1 : XrdVERSION ); ~~~~~~~~~~~^~~ --- bindings/python/src/PyXRootDEnv.hh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/python/src/PyXRootDEnv.hh b/bindings/python/src/PyXRootDEnv.hh index 5642a86a0e9..3c1357f6620 100644 --- a/bindings/python/src/PyXRootDEnv.hh +++ b/bindings/python/src/PyXRootDEnv.hh @@ -134,7 +134,7 @@ namespace PyXRootD //---------------------------------------------------------------------------- PyObject* XrdVersion_cpp( PyObject *self, PyObject *args ) { - static std::string verstr( XrdVERSION[0] == 'v' ? XrdVERSION + 1 : XrdVERSION ); + static std::string verstr( XrdVERSION[0] == 'v' ? &XrdVERSION[1] : XrdVERSION ); return Py_BuildValue( "s", verstr.c_str() ); } From 6c6977e4c81f6ef70313b8b149866fa19ac869ba Mon Sep 17 00:00:00 2001 From: David Smith Date: Thu, 4 May 2023 17:16:52 +0200 Subject: [PATCH 090/442] [Client] Avoid ZipArchive issuing VectorWrite which is too large during CloseArchive --- src/XrdCl/XrdClUtils.cc | 50 ++++++++++++++++++++++++++++++++++++ src/XrdCl/XrdClUtils.hh | 12 +++++++++ src/XrdCl/XrdClZipArchive.cc | 13 ++++++++-- 3 files changed, 73 insertions(+), 2 deletions(-) diff --git a/src/XrdCl/XrdClUtils.cc b/src/XrdCl/XrdClUtils.cc index 38a02f771a1..2a8d2b4943d 100644 --- a/src/XrdCl/XrdClUtils.cc +++ b/src/XrdCl/XrdClUtils.cc @@ -865,4 +865,54 @@ namespace XrdCl if( dst_supported.count( *itr ) ) return *itr; return std::string(); } + + //---------------------------------------------------------------------------- + //! Split chunks in a ChunkList into one or more ChunkLists + //---------------------------------------------------------------------------- + void Utils::SplitChunks( std::vector &listsvec, + const ChunkList &chunks, + const uint32_t maxcs, + const size_t maxc ) + { + listsvec.clear(); + if( !chunks.size() ) return; + + listsvec.emplace_back(); + ChunkList *c = &listsvec.back(); + const size_t cs = chunks.size(); + size_t idx = 0; + size_t nc = 0; + ChunkInfo tmpc; + + c->reserve( cs ); + + while( idx < cs ) + { + if( maxc && nc >= maxc ) + { + listsvec.emplace_back(); + c = &listsvec.back(); + c->reserve( cs - idx ); + nc = 0; + } + + if( tmpc.length == 0 ) + tmpc = chunks[idx]; + + if( maxcs && tmpc.length > maxcs ) + { + c->emplace_back( tmpc.offset, maxcs, tmpc.buffer ); + tmpc.offset += maxcs; + tmpc.length -= maxcs; + tmpc.buffer = static_cast( tmpc.buffer ) + maxcs; + } + else + { + c->emplace_back( tmpc.offset, tmpc.length, tmpc.buffer ); + tmpc.length = 0; + ++idx; + } + ++nc; + } + } } diff --git a/src/XrdCl/XrdClUtils.hh b/src/XrdCl/XrdClUtils.hh index 6691196f66b..e3c6a502827 100644 --- a/src/XrdCl/XrdClUtils.hh +++ b/src/XrdCl/XrdClUtils.hh @@ -272,6 +272,18 @@ namespace XrdCl if( !st.IsOK() ) return false; return protver >= kXR_PROTPGRWVERSION; } + + //------------------------------------------------------------------------ + //! Split chunks in a ChunkList into one or more ChunkLists + //! @param listsvec : output vector of ChunkLists + //! @param chunks : input ChunkLisits + //! @param maxcs : maximum size of a ChunkInfo in output + //! @param maxc : maximum number of ChunkInfo in each ChunkList + //------------------------------------------------------------------------ + static void SplitChunks( std::vector &listsvec, + const ChunkList &chunks, + const uint32_t maxcs, + const size_t maxc ); }; //---------------------------------------------------------------------------- diff --git a/src/XrdCl/XrdClZipArchive.cc b/src/XrdCl/XrdClZipArchive.cc index cba1e464d2f..41d14b53af9 100644 --- a/src/XrdCl/XrdClZipArchive.cc +++ b/src/XrdCl/XrdClZipArchive.cc @@ -28,6 +28,7 @@ #include "XrdCl/XrdClLog.hh" #include "XrdCl/XrdClDefaultEnv.hh" #include "XrdCl/XrdClConstants.hh" +#include "XrdCl/XrdClUtils.hh" #include "XrdZip/XrdZipZIP64EOCDL.hh" #include @@ -626,10 +627,18 @@ namespace XrdCl } auto wrtbuff = std::make_shared( GetCD() ); - chunks.emplace_back( cdoff, wrtbuff->size(), wrtbuff->data() ); + Pipeline p = XrdCl::Write( archive, cdoff, + wrtbuff->size(), + wrtbuff->data() ); wrtbufs.emplace_back( std::move( wrtbuff ) ); - Pipeline p = XrdCl::VectorWrite( archive, chunks ); + std::vector listsvec; + XrdCl::Utils::SplitChunks( listsvec, chunks, 262144, 1024 ); + + for(auto itr = listsvec.rbegin(); itr != listsvec.rend(); ++itr) + { + p = XrdCl::VectorWrite( archive, *itr ) | p; + } if( ckpinit ) p |= XrdCl::Checkpoint( archive, ChkPtCode::COMMIT ); p |= Close( archive ) >> From eeb85d2cb4631e8c96e58b3d404d013306827cf9 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 18 Apr 2023 14:21:41 +0200 Subject: [PATCH 091/442] [CI] Do not update pip, setuptools, and wheel for sdist build The build no longer works with the latest versions of pip, setuptools, and wheel due to usage of deprecated tools. This needs to be addressed by modernizing our Python packaging (see issue #1844). --- .github/workflows/build.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f3932993d3a..7596a6d08ee 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -871,9 +871,6 @@ jobs: pkg-config \ tree sudo apt-get autoclean -y - # Remove packages with invalid versions which cause sdist build to fail - sudo apt-get remove python3-debian python3-distro-info - python3 -m pip --no-cache-dir install --upgrade pip setuptools wheel python3 -m pip list - name: Clone repository From d994dc9b02552c9958d1a77690f622040902e541 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 5 May 2023 14:11:50 +0200 Subject: [PATCH 092/442] Update release notes for v5.5.5 --- docs/ReleaseNotes.txt | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/ReleaseNotes.txt b/docs/ReleaseNotes.txt index e393202c798..36b47e7f48e 100644 --- a/docs/ReleaseNotes.txt +++ b/docs/ReleaseNotes.txt @@ -5,6 +5,28 @@ XRootD Release Notes ============= +------------- +Version 5.5.5 +------------- + ++ **Major bug fixes** + **[HTTP]** Initialize SecEntity.addrInfo to avoid SEGV (issue 1986) + **[Server]** Allow XrdXrootdFile::Serialize() to be used to wait more than once (issue 1995) + **[Server]** Correct file handle returned when reusing it from external table + **[XrdCl]** Fix client crash on early closed connection (issue 1934) + ++ **Minor bug fixes** + **[Server]** Use correct format to print size_t (issue 1989) + **[XrdApps]** Let XrdClProxyPlugin work with PgRead (issue 1993) + **[XrdCl]** Avoid possibility of Channel unregistering the wrong task (issue 1883) + **[XrdCl]** Fix ZipArchive issuing VectorWrite which is too large during CloseArchive (issue 2004) + **[XrdSys]** Avoid memory leak when overwriting the default message for EBADE + ++ **Miscellaneous** + **[CMake]** Adjust build rules to not depend on scitokenscpp to build libXrdSecztn + **[Server,XrdPosix,XrdSecgsi]** Fix compile warnings with Clang compiler + **[XrdPosix]** Fix build with Clang and _FORTIFY_SOURCE enabled (issue 1975) + ------------- Version 5.5.4 ------------- From ffcaa36388fae6898270a0a3efef7cc1e45f3133 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Tue, 25 Apr 2023 22:10:27 -0700 Subject: [PATCH 093/442] Update notes on client support for cache eviction. --- docs/PreReleaseNotes.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/PreReleaseNotes.txt b/docs/PreReleaseNotes.txt index b3527cccdbe..be8ad34ae3a 100644 --- a/docs/PreReleaseNotes.txt +++ b/docs/PreReleaseNotes.txt @@ -6,6 +6,8 @@ Prerelease Notes ================ + **New Features** + **[Client]** Add xrdfs cache subcommand to allow for cache evictions. + **Commit: 39f9e0a **[Xcache]** Implement a file evict function. **Commit: 952bd9a **[PSS]** Allow origin to be a directory of a locally mounted file system. From 7846b1cc8cb2b163d3ee6ddc07b983529d232f64 Mon Sep 17 00:00:00 2001 From: David Smith Date: Fri, 12 May 2023 10:57:04 +0200 Subject: [PATCH 094/442] [HttpExtHandler] Avoid SEGV incase request has object for opaque data but no content --- src/XrdHttp/XrdHttpExtHandler.cc | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/XrdHttp/XrdHttpExtHandler.cc b/src/XrdHttp/XrdHttpExtHandler.cc index 7448331bfca..b4cfe771bd5 100644 --- a/src/XrdHttp/XrdHttpExtHandler.cc +++ b/src/XrdHttp/XrdHttpExtHandler.cc @@ -89,9 +89,12 @@ verb(req->requestverb), headers(req->allheaders) { resource = req->resource.c_str(); int envlen = 0; - headers["xrd-http-query"] = req->opaque?req->opaque->Env(envlen):""; - const char * resourcePlusOpaque = req->resourceplusopaque.c_str(); - headers["xrd-http-fullresource"] = resourcePlusOpaque != nullptr ? resourcePlusOpaque:""; + const char *p = nullptr; + if (req->opaque) + p = req->opaque->Env(envlen); + headers["xrd-http-query"] = p ? p:""; + p = req->resourceplusopaque.c_str(); + headers["xrd-http-fullresource"] = p ? p:""; headers["xrd-http-prot"] = prot->isHTTPS()?"https":"http"; // These fields usually identify the client that connected From bf176f3579c56bb1a0421096e10749c957bb65f2 Mon Sep 17 00:00:00 2001 From: Yuxiao Qu Date: Tue, 16 May 2023 13:20:12 -0500 Subject: [PATCH 095/442] Update HTTP Header Handling for Chunked Encoding and Status Trailer This commit updates the handling of HTTP headers to conform to HTTP standards and improve support for chunked transfer encoding and status trailers. Previously, the server incorrectly expected to receive the "Transfer-Encoding: chunked" header in HTTP requests. However, it has been realized that the "Transfer-Encoding" header is reserved for HTTP responses and should not be anticipated in an HTTP request. We've now corrected this behavior by removing the check for the "Transfer-Encoding" header in the incoming request. Instead, we now set the internal variable `m_transfer_encoding_chunked` when the client includes the custom header "X-Transfer-Status: true". This change aligns with the original intention, indicating that the client is prepared to receive a response using chunked transfer encoding and a status trailer. The `m_trailer_headers` variable is still set when the client includes the "TE: trailers" header, indicating its acceptance of trailer fields in the response. --- src/XrdHttp/XrdHttpReq.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/XrdHttp/XrdHttpReq.cc b/src/XrdHttp/XrdHttpReq.cc index 51c37736c34..69eb816fc0c 100644 --- a/src/XrdHttp/XrdHttpReq.cc +++ b/src/XrdHttp/XrdHttpReq.cc @@ -184,11 +184,10 @@ int XrdHttpReq::parseLine(char *line, int len) { } else if (!strcmp(key, "Expect") && strstr(val, "100-continue")) { sendcontinue = true; - } else if (!strcasecmp(key, "Transfer-Encoding") && strstr(val, "chunked")) { - m_transfer_encoding_chunked = true; } else if (!strcasecmp(key, "TE") && strstr(val, "trailers")) { m_trailer_headers = true; } else if (!strcasecmp(key, "X-Transfer-Status") && strstr(val, "true")) { + m_transfer_encoding_chunked = true; m_status_trailer = true; } else { // Some headers need to be translated into "local" cgi info. From cd970d55b281ecb30d9311b05ab181a5fe253b7c Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Mon, 22 May 2023 16:33:29 -0700 Subject: [PATCH 096/442] [Server] Also check for IPv6 ULA's to determine if an address is private. --- src/XrdNet/XrdNetAddrInfo.cc | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/XrdNet/XrdNetAddrInfo.cc b/src/XrdNet/XrdNetAddrInfo.cc index cfbe3b54a9d..12424dd29dd 100644 --- a/src/XrdNet/XrdNetAddrInfo.cc +++ b/src/XrdNet/XrdNetAddrInfo.cc @@ -53,6 +53,16 @@ #endif #endif +// The following tests for Unique Local Addresses (ULA) which Linux does not +// provide. The SITELOCAL macro only tests for the now deprecated non-routable +// addresses (RFC 3879). So, we need to implement the ULA test ourselves. +// Technically, only addresses starting with prefix 0xfd are ULA useable but +// RFC 4193 doesn't explicitly prohibit ULA's that start with 0xfc which may +// be used for registered ULA's in the future. So we test for both. +// +#define IN6_IS_ADDR_UNIQLOCAL(a) \ + ( ((const uint8_t *)(a))[0] == 0xfc || ((const uint8_t *)(a))[0] == 0xfd ) + /******************************************************************************/ /* S t a t i c M e m b e r s */ /******************************************************************************/ @@ -198,6 +208,7 @@ bool XrdNetAddrInfo::isPrivate() ipV4 = (unsigned char *)&IP.v6.sin6_addr.s6_addr32[3]; else {if ((IN6_IS_ADDR_LINKLOCAL(&IP.v6.sin6_addr)) || (IN6_IS_ADDR_SITELOCAL(&IP.v6.sin6_addr)) + || (IN6_IS_ADDR_UNIQLOCAL(&IP.v6.sin6_addr)) || (IN6_IS_ADDR_LOOPBACK (&IP.v6.sin6_addr))) return true; return false; } From e4093c56edef82b0eda357f59982196784f92edd Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Mon, 22 May 2023 16:37:06 -0700 Subject: [PATCH 097/442] Update notes on IPv6 ULA test. --- docs/PreReleaseNotes.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/PreReleaseNotes.txt b/docs/PreReleaseNotes.txt index be8ad34ae3a..b74a43ec39e 100644 --- a/docs/PreReleaseNotes.txt +++ b/docs/PreReleaseNotes.txt @@ -22,3 +22,5 @@ Prerelease Notes + **Minor bug fixes** + **Miscellaneous** + **[Server]** Also check for IPv6 ULA's to determine if an address is private. + **Commit: cd970d5 From 1c5e3d63aab1aa673ebdcec8eb282e835e09e0dc Mon Sep 17 00:00:00 2001 From: Mattias Ellert Date: Sun, 21 May 2023 09:16:14 +0200 Subject: [PATCH 098/442] The latest updates to glibc headers adds __nonnull to the fclose() Declaration in old version: extern int fclose (FILE *__stream); Declaration in new version: extern int fclose (FILE *__stream) __nonnull ((1)); This change causes a compilation error in xrootd: /builddir/build/BUILD/xrootd-5.5.5/src/XrdTls/XrdTlsTempCA.cc:54:48: error: ignoring attributes on template argument 'int (*)(FILE*)' [-Werror=ignored-attributes] 54 | typedef std::unique_ptr file_smart_ptr; | ^ This commit rewrites the code so the error is not triggered. --- src/XrdTls/XrdTlsTempCA.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/XrdTls/XrdTlsTempCA.cc b/src/XrdTls/XrdTlsTempCA.cc index 37431dee9f3..0c1dd52f528 100644 --- a/src/XrdTls/XrdTlsTempCA.cc +++ b/src/XrdTls/XrdTlsTempCA.cc @@ -50,8 +50,8 @@ #include namespace { - -typedef std::unique_ptr file_smart_ptr; + +typedef std::unique_ptr file_smart_ptr; static uint64_t monotonic_time_s() { From d0c6f60cfd31a2bb55b00a6ca275606a450dfb8b Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 12 May 2023 16:49:22 +0200 Subject: [PATCH 099/442] Remove git submodule for XrdCeph --- .gitmodules | 3 --- src/XrdCeph | 1 - 2 files changed, 4 deletions(-) delete mode 100644 .gitmodules delete mode 160000 src/XrdCeph diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 6a3084b7891..00000000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "src/XrdCeph"] - path = src/XrdCeph - url = https://github.com/xrootd/xrootd-ceph diff --git a/src/XrdCeph b/src/XrdCeph deleted file mode 160000 index 6ba517522a7..00000000000 --- a/src/XrdCeph +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6ba517522a7a15e08ad061b96996e8ee14328014 From d41ac573d9f76e7de1d69e9906acf045d62e3ab7 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 12 May 2023 17:07:06 +0200 Subject: [PATCH 100/442] [CMake] Remove option to update git submodules This is no longer necessary as there are no git submodules left in the repository. --- CMakeLists.txt | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ec47ab07053..410c20bc4cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,23 +54,6 @@ macro( add_executable _target ) add_dependencies( ${_target} XrdVersion.hh ) endmacro() -#------------------------------------------------------------------------------- -# Checkout the vomsxrd submodule -#------------------------------------------------------------------------------- -find_package(Git QUIET) -if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git") - option(GIT_SUBMODULES "Check submodules during build" ON) - if(GIT_SUBMODULES) - message(STATUS "Submodule update") - execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - RESULT_VARIABLE GIT_SUBMOD_RESULT) - if(NOT GIT_SUBMOD_RESULT EQUAL "0") - message(FATAL_ERROR "git submodule update --init failed with ${GIT_SUBMOD_RESULT}, please checkout submodules") - endif() - endif() -endif() - #------------------------------------------------------------------------------- # Build in subdirectories #------------------------------------------------------------------------------- From fa7cdfc949e1bc55d409c0aa838deac38b605394 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 12 May 2023 17:10:12 +0200 Subject: [PATCH 101/442] Update gen-tarball.sh to not add git submodules --- gen-tarball.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/gen-tarball.sh b/gen-tarball.sh index aa1605e6ed5..588c372f7f2 100755 --- a/gen-tarball.sh +++ b/gen-tarball.sh @@ -29,8 +29,6 @@ if [ $? -ne 0 ] ; then exit 1 fi git archive --prefix xrootd-${fver}/ ${ver} -o ${tdir}/xrootd-${fver}.tar -git submodule update --init -git submodule foreach --recursive "git archive --prefix xrootd-${fver}/\$path/ \$sha1 -o ${tdir}/\$sha1.tar ; tar -A -f ${tdir}/xrootd-${fver}.tar ${tdir}/\$sha1.tar ; rm ${tdir}/\$sha1.tar" cd ${tdir} gzip xrootd-${fver}.tar mv xrootd-${fver}.tar.gz ${curdir} From 7ee4547cdae56f287d5dc0f7437393761add85f4 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 12 May 2023 17:13:32 +0200 Subject: [PATCH 102/442] Update makesrpm.sh to not add git submodules --- packaging/makesrpm.sh | 33 --------------------------------- 1 file changed, 33 deletions(-) diff --git a/packaging/makesrpm.sh b/packaging/makesrpm.sh index 817ffdd2f1f..6dbfa6479e3 100755 --- a/packaging/makesrpm.sh +++ b/packaging/makesrpm.sh @@ -239,39 +239,6 @@ if test $? -ne 0; then exit 6 fi -#------------------------------------------------------------------------------- -# Make sure submodules are in place -#------------------------------------------------------------------------------- -git submodule init -git submodule update -- src/XrdClHttp -git submodule update -- src/XrdCeph -#git submodule foreach git pull origin master - -#------------------------------------------------------------------------------- -# Add XrdCeph sub-module to our tarball -#------------------------------------------------------------------------------- -cd src/XrdCeph - -if [ -z ${TAG+x} ]; then - COMMIT=`git log --pretty=format:"%H" -1` -else - COMMIT=$TAG -fi - -git archive --prefix=xrootd/src/XrdCeph/ --format=tar $COMMIT > $RPMSOURCES/xrootd-ceph.tar -if test $? -ne 0; then - echo "[!] Unable to create the xrootd-ceph source tarball" 1>&2 - exit 6 -fi - -tar --concatenate --file $RPMSOURCES/xrootd.tar $RPMSOURCES/xrootd-ceph.tar -if test $? -ne 0; then - echo "[!] Unable to add xrootd-ceph to xrootd tarball" 1>&2 - exit 6 -fi - -cd - > /dev/null - #------------------------------------------------------------------------------- # gzip the tarball #------------------------------------------------------------------------------- From 7f8b96622a1ded4d2ec01a2ed661a97bb436a3fa Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 12 May 2023 14:03:31 +0200 Subject: [PATCH 103/442] [CI] Set USER_VERSION not to confuse genversion.sh When we clone xrootd in GitHub Actions, the clone is shallow, so it doesn't have any tags and genversion.sh generates broken versions. --- .github/workflows/build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c5d1403070f..7b75431f2d0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,6 +17,9 @@ concurrency: group: build-${{ github.ref }} cancel-in-progress: true +env: + USER_VERSION: v5.6-rc1 + jobs: cmake-almalinux8: From f41e83c2c39f7b97026461afd4ab4022981ffcab Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 23 May 2023 13:23:53 +0200 Subject: [PATCH 104/442] Update docker scripts to work with podman --- docker/build/Dockerfile.centos7 | 11 ++++---- docker/xrd-docker | 45 ++++++++++++++++++--------------- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/docker/build/Dockerfile.centos7 b/docker/build/Dockerfile.centos7 index f0c4f01dbe1..ac3df9a45b9 100644 --- a/docker/build/Dockerfile.centos7 +++ b/docker/build/Dockerfile.centos7 @@ -1,13 +1,13 @@ -FROM cern/cc7-base +FROM centos:7 # Install tools necessary for RPM development -RUN yum install -y rpm-build rpmdevtools yum-utils +RUN yum install -y centos-release-scl epel-release rpm-build rpmdevtools yum-utils + +WORKDIR /root # Create directory tree for building RPMs RUN rpmdev-setuptree -WORKDIR /root - # XRootD source tarball must be created in the # current directory in order to build this image COPY xrootd.tar.gz rpmbuild/SOURCES @@ -26,12 +26,13 @@ RUN rpmbuild -bb --define '_with_isal 1' --define '_with_python3 1' \ --define '_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm' xrootd.spec # Second stage, build test image -FROM cern/cc7-base +FROM centos:7 COPY --from=0 /root/rpmbuild/RPMS/* /tmp/ # Install RPMS for XRootD and cleanup unneeded files RUN rm /tmp/*-{devel,doc}* \ + && yum install -y epel-release \ && yum install -y wget /tmp/*.rpm \ && yum autoremove -y \ && rm -rf /tmp/* diff --git a/docker/xrd-docker b/docker/xrd-docker index a6c1b0c6d2f..ccbffe7b7c1 100755 --- a/docker/xrd-docker +++ b/docker/xrd-docker @@ -2,17 +2,19 @@ trap 'exit 1' TERM KILL INT QUIT ABRT +: ${DOCKER:=$(command -v docker || command -v podman)} + build() { OS=${1:-centos7} [[ -f xrootd.tar.gz ]] || package [[ -f build/Dockerfile.${OS} ]] || die "unknwon OS: $OS" - docker build -f build/Dockerfile.${OS} -t xrootd -t xrootd:${OS} . + ${DOCKER} build -f build/Dockerfile.${OS} -t xrootd -t xrootd:${OS} . } clean() { rm -f xrootd.tar.gz - docker rm -f metaman man1 man2 srv1 srv2 srv3 srv4 - docker system prune -f + ${DOCKER} rm -f metaman man1 man2 srv1 srv2 srv3 srv4 + ${DOCKER} system prune -f } die() { @@ -67,7 +69,7 @@ package() { packaging/rhel/xrootd.spec.in >| xrootd.spec git archive --prefix=xrootd/src/ --add-file src/XrdVersion.hh \ --prefix=xrootd/ --add-file xrootd.spec -o xrootd.tar.gz ${1:-HEAD} - rm xrootd.spec + rm ${REPODIR}/src/XrdVersion.hh xrootd.spec popd >/dev/null mv ${REPODIR}/xrootd.tar.gz . } @@ -87,47 +89,50 @@ run() { ) for T in ${@:-${TEST_SUITE[@]}}; do - docker exec -it metaman test-runner /usr/lib64/libXrdClTests.so "All Tests/${T}Test" + ${DOCKER} exec -it metaman test-runner /usr/lib64/libXrdClTests.so "All Tests/${T}Test" done # XrdEc tests are not working - # docker exec -it metaman test-runner /usr/lib64/libXrdEcTests.so 'All Tests' + # ${DOCKER} exec -it metaman test-runner /usr/lib64/libXrdEcTests.so 'All Tests' } search() { for container in metaman srv{1..4}; do echo ${container}: - docker exec ${container} find /data $@ + ${DOCKER} exec ${container} find /data $@ echo done } setup() { - docker network create xrootd + ${DOCKER} network create xrootd + if [[ ${DOCKER} =~ podman ]]; then + EXTRA_OPTS="--group-add keep-groups" + else + EXTRA_OPTS="--privileged" + fi for container in metaman man{1,2} srv{1..4}; do echo ">>> Start ${container} container" - [[ ${container} =~ ^man* ]] && ALIAS=--net-alias=manalias || ALIAS='' - docker run -d --privileged -e "container=docker" \ - -v ${PWD}/data:/downloads:z --name ${container} -h ${container} \ - --net=xrootd --net-alias=multiip ${ALIAS} --net-alias=${container} \ - xrootd:${1:-latest} /sbin/init + [[ ${container} =~ ^man* ]] && ALIAS=--network-alias=manalias || ALIAS='' + ${DOCKER} run -d ${EXTRA_OPTS} --name ${container} -h ${container} \ + -v ${PWD}/data:/downloads:z --net=xrootd --network-alias=multiip ${ALIAS} \ + --network-alias=${container} xrootd:${1:-latest} /sbin/init echo ">>> Setup ${container}" - docker exec ${container} setup.sh + ${DOCKER} exec ${container} setup.sh echo ">>> Start xrootd and cmsd in ${container}" - docker exec ${container} systemctl start xrootd@clustered - docker exec ${container} systemctl start cmsd@clustered + ${DOCKER} exec ${container} systemctl start xrootd@clustered + ${DOCKER} exec ${container} systemctl start cmsd@clustered echo done echo ">>> Start xrootd@srv2 in srv2" - docker exec srv2 systemctl start xrootd@srv2 - - docker ps + ${DOCKER} exec srv2 systemctl start xrootd@srv2 + ${DOCKER} ps } shell() { - docker exec -it ${1:-metaman} /bin/bash + ${DOCKER} exec -it ${1:-metaman} /bin/bash } usage() { From 7c033c02f07428d4944762daf0cc8a10c2e52d0f Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 11 May 2023 13:09:11 +0200 Subject: [PATCH 105/442] [Tests] Add CppUnit include directories to test targets --- tests/common/CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/common/CMakeLists.txt b/tests/common/CMakeLists.txt index c40c234e990..daac25555f7 100644 --- a/tests/common/CMakeLists.txt +++ b/tests/common/CMakeLists.txt @@ -16,6 +16,9 @@ target_link_libraries( XrdCl XrdUtils) +target_include_directories( + XrdClTestsHelper PUBLIC ${CPPUNIT_INCLUDE_DIRS}) + add_executable( test-runner TextRunner.cc @@ -27,6 +30,9 @@ target_link_libraries( ${CPPUNIT_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) +target_include_directories( + test-runner PRIVATE ${CPPUNIT_INCLUDE_DIRS}) + #------------------------------------------------------------------------------- # Install #------------------------------------------------------------------------------- From a55144c42384f428bed0d75ce00b5581704d1d2e Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 11 May 2023 11:46:03 +0200 Subject: [PATCH 106/442] [CMake] Remove XRootDCommon.cmake This module is hacky, so remove it. In particular, the call to include_directories(../ ./ ...) adds the top directory of the project to the list of includes, and on macOS where there is no case-sensitivity, "#include " somewhere includes the newly added VERSION file with git information and causes compilation to fail with the error below: /Users/runner/work/xrootd/xrootd/src/../version:1:1: error: unknown type name '$Format' $Format:%(describe)$ ^ /Users/runner/work/xrootd/xrootd/src/../version:1:8: error: expected unqualified-id $Format:%(describe)$ ^ --- CMakeLists.txt | 3 +++ cmake/XRootDCommon.cmake | 7 ------- src/XrdApps.cmake | 1 - src/XrdCl/CMakeLists.txt | 1 - src/XrdCrypto.cmake | 1 - src/XrdDaemons.cmake | 1 - src/XrdEc/CMakeLists.txt | 1 - src/XrdFfs.cmake | 1 - src/XrdFrm.cmake | 1 - src/XrdHttp.cmake | 1 - src/XrdMacaroons.cmake | 1 - src/XrdPfc.cmake | 1 - src/XrdPlugins.cmake | 1 - src/XrdPosix.cmake | 1 - src/XrdSciTokens.cmake | 1 - src/XrdSec.cmake | 1 - src/XrdSecgsi.cmake | 1 - src/XrdSeckrb5.cmake | 1 - src/XrdSecztn.cmake | 1 - src/XrdServer.cmake | 1 - src/XrdSsi.cmake | 1 - src/XrdTpc.cmake | 1 - src/XrdUtils.cmake | 1 - src/XrdXml.cmake | 1 - tests/XrdCephTests/CMakeLists.txt | 1 - tests/XrdClTests/CMakeLists.txt | 1 - tests/XrdClTests/tls/CMakeLists.txt | 1 - tests/XrdEcTests/CMakeLists.txt | 1 - tests/XrdSsiTests/CMakeLists.txt | 1 - tests/common/CMakeLists.txt | 1 - 30 files changed, 3 insertions(+), 35 deletions(-) delete mode 100644 cmake/XRootDCommon.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 410c20bc4cd..c3129ebcf09 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,6 +57,9 @@ endmacro() #------------------------------------------------------------------------------- # Build in subdirectories #------------------------------------------------------------------------------- + +include_directories(BEFORE ${CMAKE_SOURCE_DIR}/src ${CMAKE_BINARY_DIR}/src) + add_subdirectory( src ) add_subdirectory( bindings ) diff --git a/cmake/XRootDCommon.cmake b/cmake/XRootDCommon.cmake deleted file mode 100644 index ec342c31657..00000000000 --- a/cmake/XRootDCommon.cmake +++ /dev/null @@ -1,7 +0,0 @@ - -if( READLINE_FOUND ) - include_directories( ${READLINE_INCLUDE_DIR} ) -endif() - -include_directories( ../ ./ ${ZLIB_INCLUDE_DIRS} ${UUID_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/src ${CMAKE_BINARY_DIR}/src - /usr/local/include ) diff --git a/src/XrdApps.cmake b/src/XrdApps.cmake index b12138f0d20..a4b91f77ba0 100644 --- a/src/XrdApps.cmake +++ b/src/XrdApps.cmake @@ -1,5 +1,4 @@ -include( XRootDCommon ) #------------------------------------------------------------------------------- # Modules diff --git a/src/XrdCl/CMakeLists.txt b/src/XrdCl/CMakeLists.txt index 2179019af8c..1abe0d4b9cb 100644 --- a/src/XrdCl/CMakeLists.txt +++ b/src/XrdCl/CMakeLists.txt @@ -1,5 +1,4 @@ -include( XRootDCommon ) #------------------------------------------------------------------------------- # Shared library version diff --git a/src/XrdCrypto.cmake b/src/XrdCrypto.cmake index e820e433e4f..337f18de779 100644 --- a/src/XrdCrypto.cmake +++ b/src/XrdCrypto.cmake @@ -1,4 +1,3 @@ -include( XRootDCommon ) #------------------------------------------------------------------------------- # Modules diff --git a/src/XrdDaemons.cmake b/src/XrdDaemons.cmake index dcde58d0bd3..ca6bf904a8f 100644 --- a/src/XrdDaemons.cmake +++ b/src/XrdDaemons.cmake @@ -1,5 +1,4 @@ -include( XRootDCommon ) #------------------------------------------------------------------------------- # xrootd diff --git a/src/XrdEc/CMakeLists.txt b/src/XrdEc/CMakeLists.txt index 53754340c2c..eedaaf9fbbb 100644 --- a/src/XrdEc/CMakeLists.txt +++ b/src/XrdEc/CMakeLists.txt @@ -1,4 +1,3 @@ -include( XRootDCommon ) include( ExternalProject ) #------------------------------------------------------------------------------- diff --git a/src/XrdFfs.cmake b/src/XrdFfs.cmake index fea80acb392..70551add9ee 100644 --- a/src/XrdFfs.cmake +++ b/src/XrdFfs.cmake @@ -1,5 +1,4 @@ -include( XRootDCommon ) #------------------------------------------------------------------------------- # Shared library version diff --git a/src/XrdFrm.cmake b/src/XrdFrm.cmake index 72b7c81cef3..1ed4432d72f 100644 --- a/src/XrdFrm.cmake +++ b/src/XrdFrm.cmake @@ -1,5 +1,4 @@ -include( XRootDCommon ) #------------------------------------------------------------------------------- # The XrdFrm library diff --git a/src/XrdHttp.cmake b/src/XrdHttp.cmake index 9a0fcc60ba7..1a7ed1e9d0a 100644 --- a/src/XrdHttp.cmake +++ b/src/XrdHttp.cmake @@ -1,4 +1,3 @@ -include( XRootDCommon ) #------------------------------------------------------------------------------- # Modules diff --git a/src/XrdMacaroons.cmake b/src/XrdMacaroons.cmake index 837bfe22e16..c7acc93ed37 100644 --- a/src/XrdMacaroons.cmake +++ b/src/XrdMacaroons.cmake @@ -1,4 +1,3 @@ -include( XRootDCommon ) #------------------------------------------------------------------------------- # Modules diff --git a/src/XrdPfc.cmake b/src/XrdPfc.cmake index c09bc0efcb4..719542ebb64 100644 --- a/src/XrdPfc.cmake +++ b/src/XrdPfc.cmake @@ -1,4 +1,3 @@ -include( XRootDCommon ) #------------------------------------------------------------------------------- # Modules diff --git a/src/XrdPlugins.cmake b/src/XrdPlugins.cmake index a057079a578..a7fa1bf0c48 100644 --- a/src/XrdPlugins.cmake +++ b/src/XrdPlugins.cmake @@ -1,5 +1,4 @@ -include( XRootDCommon ) #------------------------------------------------------------------------------- # Modules diff --git a/src/XrdPosix.cmake b/src/XrdPosix.cmake index cf32478185a..d67d05e7578 100644 --- a/src/XrdPosix.cmake +++ b/src/XrdPosix.cmake @@ -1,5 +1,4 @@ -include( XRootDCommon ) #------------------------------------------------------------------------------- # Shared library version diff --git a/src/XrdSciTokens.cmake b/src/XrdSciTokens.cmake index 5f400461761..244232e2d18 100644 --- a/src/XrdSciTokens.cmake +++ b/src/XrdSciTokens.cmake @@ -1,4 +1,3 @@ -include( XRootDCommon ) find_package( SciTokensCpp REQUIRED ) diff --git a/src/XrdSec.cmake b/src/XrdSec.cmake index 0b902f9401d..5647d52fa46 100644 --- a/src/XrdSec.cmake +++ b/src/XrdSec.cmake @@ -1,5 +1,4 @@ -include( XRootDCommon ) #------------------------------------------------------------------------------- # Modules diff --git a/src/XrdSecgsi.cmake b/src/XrdSecgsi.cmake index a6127918d9e..8559ffe920d 100644 --- a/src/XrdSecgsi.cmake +++ b/src/XrdSecgsi.cmake @@ -1,5 +1,4 @@ -include( XRootDCommon ) #------------------------------------------------------------------------------- # Shared library version diff --git a/src/XrdSeckrb5.cmake b/src/XrdSeckrb5.cmake index 30dceff0e8e..5f3fbbdb965 100644 --- a/src/XrdSeckrb5.cmake +++ b/src/XrdSeckrb5.cmake @@ -1,5 +1,4 @@ include_directories( ${KERBEROS5_INCLUDE_DIR} ) -include( XRootDCommon ) #------------------------------------------------------------------------------- # The XrdSeckrb5 module diff --git a/src/XrdSecztn.cmake b/src/XrdSecztn.cmake index 23403bcf859..3274140cfde 100644 --- a/src/XrdSecztn.cmake +++ b/src/XrdSecztn.cmake @@ -1,5 +1,4 @@ #include_directories( ${KERBEROS5_INCLUDE_DIR} ) -include( XRootDCommon ) #------------------------------------------------------------------------------- # The XrdSecztn module diff --git a/src/XrdServer.cmake b/src/XrdServer.cmake index d5d2e453347..a2ab25c5ee9 100644 --- a/src/XrdServer.cmake +++ b/src/XrdServer.cmake @@ -1,5 +1,4 @@ -include( XRootDCommon ) #------------------------------------------------------------------------------- # Plugin version (this protocol loaded eithr as a plugin or as builtin). diff --git a/src/XrdSsi.cmake b/src/XrdSsi.cmake index da7f36c94fc..352b291d6a8 100644 --- a/src/XrdSsi.cmake +++ b/src/XrdSsi.cmake @@ -1,5 +1,4 @@ -include( XRootDCommon ) #------------------------------------------------------------------------------- # Modules diff --git a/src/XrdTpc.cmake b/src/XrdTpc.cmake index 03e356ea12e..d61711f6d49 100644 --- a/src/XrdTpc.cmake +++ b/src/XrdTpc.cmake @@ -1,4 +1,3 @@ -include( XRootDCommon ) #------------------------------------------------------------------------------- # Modules diff --git a/src/XrdUtils.cmake b/src/XrdUtils.cmake index 025b800f295..d96338298f0 100644 --- a/src/XrdUtils.cmake +++ b/src/XrdUtils.cmake @@ -1,5 +1,4 @@ -include( XRootDCommon ) #------------------------------------------------------------------------------- # Shared library version diff --git a/src/XrdXml.cmake b/src/XrdXml.cmake index 6d87414c18d..c122ccbd366 100644 --- a/src/XrdXml.cmake +++ b/src/XrdXml.cmake @@ -1,5 +1,4 @@ -include( XRootDCommon ) if ( TINYXML_FOUND ) set( TINYXML_FILES "" ) diff --git a/tests/XrdCephTests/CMakeLists.txt b/tests/XrdCephTests/CMakeLists.txt index 383a6fa70f5..43bbdc072df 100644 --- a/tests/XrdCephTests/CMakeLists.txt +++ b/tests/XrdCephTests/CMakeLists.txt @@ -1,4 +1,3 @@ -include( XRootDCommon ) include_directories( ${CPPUNIT_INCLUDE_DIRS} ) add_library( diff --git a/tests/XrdClTests/CMakeLists.txt b/tests/XrdClTests/CMakeLists.txt index b1603333c5e..ae2ce0ddf3b 100644 --- a/tests/XrdClTests/CMakeLists.txt +++ b/tests/XrdClTests/CMakeLists.txt @@ -1,5 +1,4 @@ -include( XRootDCommon ) include_directories( ${CPPUNIT_INCLUDE_DIRS} ../common) add_subdirectory( tls ) diff --git a/tests/XrdClTests/tls/CMakeLists.txt b/tests/XrdClTests/tls/CMakeLists.txt index 7ab152723c0..93b3e145a10 100644 --- a/tests/XrdClTests/tls/CMakeLists.txt +++ b/tests/XrdClTests/tls/CMakeLists.txt @@ -1,5 +1,4 @@ -include( XRootDCommon ) #------------------------------------------------------------------------------- # xrdcopy diff --git a/tests/XrdEcTests/CMakeLists.txt b/tests/XrdEcTests/CMakeLists.txt index 7b6ede61b83..df549a0ca5b 100644 --- a/tests/XrdEcTests/CMakeLists.txt +++ b/tests/XrdEcTests/CMakeLists.txt @@ -1,5 +1,4 @@ -include( XRootDCommon ) include_directories( ${CPPUNIT_INCLUDE_DIRS} ../common ) link_directories( ${ISAL_LIBDIR} ) diff --git a/tests/XrdSsiTests/CMakeLists.txt b/tests/XrdSsiTests/CMakeLists.txt index 3bbf4d40a1e..e274d935b2b 100644 --- a/tests/XrdSsiTests/CMakeLists.txt +++ b/tests/XrdSsiTests/CMakeLists.txt @@ -1,5 +1,4 @@ -include( XRootDCommon ) add_executable( xrdshmap diff --git a/tests/common/CMakeLists.txt b/tests/common/CMakeLists.txt index daac25555f7..5497a7116dc 100644 --- a/tests/common/CMakeLists.txt +++ b/tests/common/CMakeLists.txt @@ -1,5 +1,4 @@ -include(XRootDCommon) add_library( XrdClTestsHelper SHARED From 3df27a31f374e07a838a916492c1a520e8f2195d Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 23 May 2023 16:34:32 +0200 Subject: [PATCH 107/442] [CMake] Update FindDavix.cmake module --- cmake/FindDavix.cmake | 84 ++++++++++++++++++++++++++++-------- src/XrdClHttp/CMakeLists.txt | 4 +- 2 files changed, 66 insertions(+), 22 deletions(-) diff --git a/cmake/FindDavix.cmake b/cmake/FindDavix.cmake index 8013d610d77..bd141c18478 100644 --- a/cmake/FindDavix.cmake +++ b/cmake/FindDavix.cmake @@ -1,23 +1,69 @@ -find_path( - Davix_INCLUDE_DIRS - NAMES davix/davix.hpp - HINTS ${Davix_INCLUDE_DIRS} -) +#.rst: +# FindDavix +# ------- +# +# Find Davix library for file management over HTTP-based protocols. +# +# Imported Targets +# ^^^^^^^^^^^^^^^^ +# +# This module defines :prop_tgt:`IMPORTED` target: +# +# ``Davix::Davix`` +# The libdavix library, if found. +# +# Result Variables +# ^^^^^^^^^^^^^^^^ +# +# This module will set the following variables in your project: +# +# ``DAVIX_FOUND`` +# True if Davix has been found. +# ``DAVIX_INCLUDE_DIRS`` +# Where to find davix.hpp, etc. +# ``DAVIX_LIBRARIES`` +# The libraries to link against to use Davix. +# ``DAVIX_VERSION`` +# The version of the Davix library found (e.g. 0.6.4) +# +# Obsolete variables +# ^^^^^^^^^^^^^^^^^^ +# +# The following variables may also be set, for backwards compatibility: +# +# ``DAVIX_LIBRARY`` +# where to find the DAVIX library. +# ``DAVIX_INCLUDE_DIR`` +# where to find the DAVIX headers (same as DAVIX_INCLUDE_DIRS) +# -find_library( - Davix_LIBRARIES - NAMES davix - HINTS ${Davix_LIBRARY_DIRS} -) +foreach(var FOUND INCLUDE_DIR INCLUDE_DIRS LIBRARY LIBRARIES) + unset(DAVIX_${var} CACHE) +endforeach() -include(FindPackageHandleStandardArgs) -FIND_PACKAGE_HANDLE_STANDARD_ARGS( - Davix - DEFAULT_MSG - Davix_LIBRARIES - Davix_INCLUDE_DIRS -) +find_package(PkgConfig) -if(Davix_FOUND) - mark_as_advanced(Davix_LIBRARIES Davix_INCLUDE_DIRS) +if(PKG_CONFIG_FOUND) + if(${Davix_FIND_REQUIRED}) + set(Davix_REQUIRED REQUIRED) + endif() + + if(NOT DEFINED Davix_FIND_VERSION) + pkg_check_modules(DAVIX ${Davix_REQUIRED} davix) + else() + pkg_check_modules(DAVIX ${Davix_REQUIRED} davix>=${Davix_FIND_VERSION}) + endif() + + set(DAVIX_LIBRARIES ${DAVIX_LDFLAGS}) + set(DAVIX_LIBRARY ${DAVIX_LIBRARIES}) + set(DAVIX_INCLUDE_DIRS ${DAVIX_INCLUDE_DIRS}) + set(DAVIX_INCLUDE_DIR ${DAVIX_INCLUDE_DIRS}) endif() + +if(DAVIX_FOUND AND NOT TARGET Davix::Davix) + add_library(Davix::Davix INTERFACE IMPORTED) + set_property(TARGET Davix::Davix PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${DAVIX_INCLUDE_DIRS}") + set_property(TARGET Davix::Davix PROPERTY INTERFACE_LINK_LIBRARIES "${DAVIX_LIBRARIES}") +endif() + +mark_as_advanced(DAVIX_INCLUDE_DIR DAVIX_LIBRARY) diff --git a/src/XrdClHttp/CMakeLists.txt b/src/XrdClHttp/CMakeLists.txt index 4af924bfd0c..f70d7aea5df 100644 --- a/src/XrdClHttp/CMakeLists.txt +++ b/src/XrdClHttp/CMakeLists.txt @@ -1,5 +1,3 @@ -include_directories(${Davix_INCLUDE_DIRS}/davix) - set(libXrdClHttp_sources XrdClHttpPlugInFactory.cc XrdClHttpPlugInUtil.cc @@ -11,6 +9,6 @@ set(PLUGIN_NAME "XrdClHttp-${PLUGIN_VERSION}") add_library(${PLUGIN_NAME} MODULE ${libXrdClHttp_sources}) -target_link_libraries(${PLUGIN_NAME} ${Davix_LIBRARIES} XrdCl XrdUtils) +target_link_libraries(${PLUGIN_NAME} Davix::Davix XrdCl XrdUtils) install(TARGETS ${PLUGIN_NAME} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) From f2a058d510e3cfaaf462fac82c16cc9bc2c9799a Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 25 May 2023 17:06:00 +0200 Subject: [PATCH 108/442] [CMake] Add scripts to build/test XRootD with CTest The test.cmake script is meant to automate some of the standard configure, build, test, install cycle during development, but also for continuous integration. The script can be generically called as ctest -VV -S test.cmake from the top directory of the repository. There are several options to customize the build, the main ones are: -DCOVERAGE=1 Enables test coverage analysis with gcov -DMEMCHECK=1 Enables memory checking with valgrind -DSTATIC_ANALYSIS=1 Enables static analysis with clang-tidy -DINSTALL=1 Enables an extra step to call make install When enabling coverage, a report is generated by default in the html/ directory inside the build directory. The results can be viewed by opening the file html/coverage_details.html. This step can be disabled by passing -DGCOVR=0 to ctest. It is recommended to use a debug build to generate the coverage analysis. The configuration can also be specified directly on the command line via the -C option. For example, to run a coverage build in debug mode, with less verbose output, and showing test output when a test failure happens, one can run: ctest -V --output-on-failure -C Debug -DCOVERAGE=1 -S test.cmake Some environment variables can also influence the behavior of the script, like CC, CXX, CMAKE_GENERATOR, CTEST_CONFIGURATION_TYPE, CMAKE_BUILD_PARALLEL_LEVEL, CTEST_PARALLEL_LEVEL, and CMAKE_ARGS. These are mostly self-explanatory and can be used to override the provided defaults. For example, to build with the clang compiler and use Ninja as CMake generator, one can run: env CC=clang CXX=clang++ CMAKE_GENERATOR=Ninja ctest -V -S test.cmake Finally, the script tries to load configuration files from the .ci subdirectory in the source directory. The default configuration is used if no specific configuration is found for the detected OS. For example, on Ubuntu, a file named ubuntu.cmake will be used if present instead of config.cmake. The script also tries to detect a version, so, for example, on Alma, one could use almalinux8.cmake which would have higher precedence than almalinux.cmake. The default config.cmake tries to enable as many options as possible without failing if the dependencies are not installed. --- .ci/config.cmake | 17 +++++ CTestConfig.cmake | 2 + test.cmake | 176 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 195 insertions(+) create mode 100644 .ci/config.cmake create mode 100644 CTestConfig.cmake create mode 100644 test.cmake diff --git a/.ci/config.cmake b/.ci/config.cmake new file mode 100644 index 00000000000..1da3b58ada5 --- /dev/null +++ b/.ci/config.cmake @@ -0,0 +1,17 @@ +set(FORCE_ENABLED 0 CACHE BOOL "") +set(ENABLE_ASAN 0 CACHE BOOL "") +set(ENABLE_TSAN 0 CACHE BOOL "") +set(ENABLE_FUSE 1 CACHE BOOL "") +set(ENABLE_HTTP 1 CACHE BOOL "") +set(ENABLE_KRB5 1 CACHE BOOL "") +set(ENABLE_MACAROONS 1 CACHE BOOL "") +set(ENABLE_PYTHON 1 CACHE BOOL "") +set(ENABLE_READLINE 1 CACHE BOOL "") +set(ENABLE_SCITOKENS 1 CACHE BOOL "") +set(ENABLE_TESTS 1 CACHE BOOL "") +set(ENABLE_VOMS 1 CACHE BOOL "") +set(ENABLE_XRDCL 1 CACHE BOOL "") +set(ENABLE_XRDCLHTTP 1 CACHE BOOL "") +set(ENABLE_XRDEC 1 CACHE BOOL "") +set(XRDCL_LIB_ONLY 0 CACHE BOOL "") +set(XRDCL_ONLY 0 CACHE BOOL "") diff --git a/CTestConfig.cmake b/CTestConfig.cmake new file mode 100644 index 00000000000..a2b1e4739ec --- /dev/null +++ b/CTestConfig.cmake @@ -0,0 +1,2 @@ +set(CTEST_PROJECT_NAME "XRootD") +set(CTEST_NIGHTLY_START_TIME "00:00:00 UTC") diff --git a/test.cmake b/test.cmake new file mode 100644 index 00000000000..ca12a1f90a2 --- /dev/null +++ b/test.cmake @@ -0,0 +1,176 @@ +cmake_minimum_required(VERSION 3.16) + +set(ENV{LANG} "C") +set(ENV{LC_ALL} "C") + +site_name(CTEST_SITE) + +if(EXISTS "/etc/os-release") + file(STRINGS "/etc/os-release" OS_NAME REGEX "^ID=.*$") + string(REGEX REPLACE "ID=[\"']?([^\"']*)[\"']?$" "\\1" OS_NAME "${OS_NAME}") + file(STRINGS "/etc/os-release" OS_VERSION REGEX "^VERSION_ID=.*$") + string(REGEX REPLACE "VERSION_ID=[\"']?([^\"'.]*).*$" "\\1" OS_VERSION "${OS_VERSION}") + file(STRINGS "/etc/os-release" OS_FULL_NAME REGEX "^PRETTY_NAME=.*$") + string(REGEX REPLACE "PRETTY_NAME=[\"']?([^\"']*)[\"']?$" "\\1" OS_FULL_NAME "${OS_FULL_NAME}") +elseif(APPLE) + set(OS_NAME "macOS") + execute_process(COMMAND sw_vers -productVersion + OUTPUT_VARIABLE OS_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE) + set(OS_FULL_NAME "${OS_NAME} ${OS_VERSION}") +else() + cmake_host_system_information(RESULT OS_NAME QUERY OS_NAME) + cmake_host_system_information(RESULT OS_VERSION QUERY OS_VERSION) + set(OS_FULL_NAME "${OS_NAME} ${OS_VERSION}") +endif() + +string(APPEND CTEST_SITE " (${OS_FULL_NAME} - ${CMAKE_SYSTEM_PROCESSOR})") + +cmake_host_system_information(RESULT + NCORES QUERY NUMBER_OF_PHYSICAL_CORES) +cmake_host_system_information(RESULT + NTHREADS QUERY NUMBER_OF_LOGICAL_CORES) + +if(NOT DEFINED ENV{CMAKE_BUILD_PARALLEL_LEVEL}) + set(ENV{CMAKE_BUILD_PARALLEL_LEVEL} ${NTHREADS}) +endif() + +if(NOT DEFINED ENV{CTEST_PARALLEL_LEVEL}) + set(ENV{CTEST_PARALLEL_LEVEL} ${NCORES}) +endif() + +if(NOT DEFINED CTEST_CONFIGURATION_TYPE) + if(DEFINED ENV{CMAKE_BUILD_TYPE}) + set(CTEST_CONFIGURATION_TYPE $ENV{CMAKE_BUILD_TYPE}) + else() + set(CTEST_CONFIGURATION_TYPE RelWithDebInfo) + endif() +endif() + +set(CTEST_BUILD_NAME "${CMAKE_SYSTEM_NAME}") + +execute_process(COMMAND ${CMAKE_COMMAND} --system-information + OUTPUT_VARIABLE CMAKE_SYSTEM_INFORMATION ERROR_VARIABLE ERROR) + +if(ERROR) + message(FATAL_ERROR "Cannot detect system information") +endif() + +string(REGEX REPLACE ".+CMAKE_CXX_COMPILER_ID \"([-0-9A-Za-z ]+)\".*$" "\\1" + COMPILER_ID "${CMAKE_SYSTEM_INFORMATION}") +string(REPLACE "GNU" "GCC" COMPILER_ID "${COMPILER_ID}") + +string(REGEX REPLACE ".+CMAKE_CXX_COMPILER_VERSION \"([^\"]+)\".*$" "\\1" + COMPILER_VERSION "${CMAKE_SYSTEM_INFORMATION}") + +string(APPEND CTEST_BUILD_NAME " ${COMPILER_ID} ${COMPILER_VERSION}") +string(APPEND CTEST_BUILD_NAME " ${CTEST_CONFIGURATION_TYPE}") + +if(DEFINED ENV{CMAKE_GENERATOR}) + set(CTEST_CMAKE_GENERATOR $ENV{CMAKE_GENERATOR}) +else() + string(REGEX REPLACE ".+CMAKE_GENERATOR \"([-0-9A-Za-z ]+)\".*$" "\\1" + CTEST_CMAKE_GENERATOR "${CMAKE_SYSTEM_INFORMATION}") +endif() + +if(NOT CTEST_CMAKE_GENERATOR MATCHES "Makefile") + string(APPEND CTEST_BUILD_NAME " ${CTEST_CMAKE_GENERATOR}") +endif() + +if(NOT DEFINED CTEST_SOURCE_DIRECTORY) + set(CTEST_SOURCE_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}") +endif() + +if(NOT DEFINED CTEST_BINARY_DIRECTORY) + get_filename_component(CTEST_BINARY_DIRECTORY "$ENV{PWD}/build" REALPATH) +endif() + +find_program(CTEST_GIT_COMMAND NAMES git) + +if(EXISTS ${CTEST_GIT_COMMAND}) + if(DEFINED ENV{GIT_PREVIOUS_COMMIT} AND DEFINED ENV{GIT_COMMIT}) + set(CTEST_CHECKOUT_COMMAND + "${CTEST_GIT_COMMAND} -C ${CTEST_SOURCE_DIRECTORY} checkout -f $ENV{GIT_PREVIOUS_COMMIT}") + set(CTEST_GIT_UPDATE_CUSTOM + ${CTEST_GIT_COMMAND} -C ${CTEST_SOURCE_DIRECTORY} checkout -f $ENV{GIT_COMMIT}) + elseif(DEFINED ENV{GIT_COMMIT}) + set(CTEST_CHECKOUT_COMMAND + "${CTEST_GIT_COMMAND} -C ${CTEST_SOURCE_DIRECTORY} checkout -f $ENV{GIT_COMMIT}") + set(CTEST_GIT_UPDATE_CUSTOM + ${CTEST_GIT_COMMAND} -C ${CTEST_SOURCE_DIRECTORY} checkout -f $ENV{GIT_COMMIT}) + else() + set(CTEST_GIT_UPDATE_CUSTOM ${CTEST_GIT_COMMAND} -C ${CTEST_SOURCE_DIRECTORY} diff HEAD) + endif() +endif() + +set(CMAKE_ARGS $ENV{CMAKE_ARGS} ${CMAKE_ARGS}) + +if(COVERAGE) + find_program(CTEST_COVERAGE_COMMAND NAMES gcov) + list(PREPEND CMAKE_ARGS "-DCMAKE_CXX_FLAGS=--coverage") +endif() + +if(MEMCHECK) + find_program(CTEST_MEMORYCHECK_COMMAND NAMES valgrind) +endif() + +if(STATIC_ANALYSIS) + find_program(CMAKE_CXX_CLANG_TIDY NAMES clang-tidy) + list(PREPEND CMAKE_ARGS "-DCMAKE_CXX_CLANG_TIDY=${CMAKE_CXX_CLANG_TIDY}") +endif() + +foreach(FILENAME ${OS_NAME}${OS_VERSION}.cmake ${OS_NAME}.cmake config.cmake) + if(EXISTS "${CTEST_SOURCE_DIRECTORY}/.ci/${FILENAME}") + message(STATUS "Using CMake cache file ${FILENAME}") + list(PREPEND CMAKE_ARGS -C ${CTEST_SOURCE_DIRECTORY}/.ci/${FILENAME}) + list(APPEND CTEST_NOTES_FILES ${CTEST_SOURCE_DIRECTORY}/.ci/${FILENAME}) + break() + endif() +endforeach() + +if(NOT DEFINED MODEL) + if(DEFINED CTEST_SCRIPT_ARG) + set(MODEL ${CTEST_SCRIPT_ARG}) + else() + set(MODEL Experimental) + endif() +endif() + +if(IS_DIRECTORY "${CTEST_BINARY_DIRECTORY}") + ctest_empty_binary_directory("${CTEST_BINARY_DIRECTORY}") +endif() + +ctest_read_custom_files("${CTEST_SOURCE_DIRECTORY}") + +ctest_start(${MODEL}) +ctest_update() + +ctest_configure(OPTIONS "${CMAKE_ARGS}") + +ctest_read_custom_files("${CTEST_BINARY_DIRECTORY}") +list(APPEND CTEST_NOTES_FILES ${CTEST_BINARY_DIRECTORY}/CMakeCache.txt) + +ctest_build() + +if(INSTALL) + set(ENV{DESTDIR} "${CTEST_BINARY_DIRECTORY}/install") + ctest_build(TARGET install) +endif() + +ctest_test() + +if(DEFINED CTEST_COVERAGE_COMMAND) + find_program(GCOVR NAMES gcovr) + if(EXISTS ${GCOVR}) + execute_process(COMMAND + ${GCOVR} -r ${CTEST_SOURCE_DIRECTORY}/src ${CTEST_BINARY_DIRECTORY} + --html-details ${CTEST_BINARY_DIRECTORY}/html/ ERROR_VARIABLE ERROR) + if(ERROR) + message(FATAL_ERROR "Failed to generate coverage report") + endif() + endif() + ctest_coverage() +endif() + +if(DEFINED CTEST_MEMORYCHECK_COMMAND) + ctest_memcheck() +endif() From 7f7caa28c3987070103706b3ab34ea7285290da8 Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Fri, 26 May 2023 15:10:50 +0200 Subject: [PATCH 109/442] XrdHttp: Corrected regression where no performance markers were sent to the client during a HTTP TPC transfer --- src/XrdHttp/XrdHttpProtocol.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/XrdHttp/XrdHttpProtocol.cc b/src/XrdHttp/XrdHttpProtocol.cc index b722b11145a..8d3b2ae764f 100644 --- a/src/XrdHttp/XrdHttpProtocol.cc +++ b/src/XrdHttp/XrdHttpProtocol.cc @@ -1579,10 +1579,11 @@ int XrdHttpProtocol::StartChunkedResp(int code, const char *desc, const char *he /******************************************************************************/ int XrdHttpProtocol::ChunkResp(const char *body, long long bodylen) { - if (ChunkRespHeader((bodylen <= 0) ? (body ? strlen(body) : 0) : bodylen)) + long long content_length = (bodylen <= 0) ? (body ? strlen(body) : 0) : bodylen; + if (ChunkRespHeader(content_length)) return -1; - if (body && SendData(body, bodylen)) + if (body && SendData(body, content_length)) return -1; return ChunkRespFooter(); From 3715a81a5807a2fd2aee5dbbcd2aac1a74d5acc6 Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Thu, 25 May 2023 14:12:25 +0200 Subject: [PATCH 110/442] XrdHttp: The deletion of a non-empty directory returns a 405 error code instead of 500 The mapping between errno error codes and XRoot protocol error code has also been changed. ENOTEMPTY is mapped to kXR_ItExists --- src/XProtocol/XProtocol.hh | 4 ++++ src/XrdHttp/XrdHttpReq.cc | 9 ++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/XProtocol/XProtocol.hh b/src/XProtocol/XProtocol.hh index eb9af2c7953..7b13d3916a3 100644 --- a/src/XProtocol/XProtocol.hh +++ b/src/XProtocol/XProtocol.hh @@ -1391,6 +1391,10 @@ static int mapError(int rc) case ETIMEDOUT: return kXR_ReqTimedOut; case EBADF: return kXR_FileNotOpen; case ECANCELED: return kXR_Cancelled; + // In the case one tries to delete a non-empty directory + // we have decided that until the next major release + // the kXR_ItExists flag will be returned + case ENOTEMPTY: return kXR_ItExists; default: return kXR_FSError; } } diff --git a/src/XrdHttp/XrdHttpReq.cc b/src/XrdHttp/XrdHttpReq.cc index 69eb816fc0c..732ca2e4254 100644 --- a/src/XrdHttp/XrdHttpReq.cc +++ b/src/XrdHttp/XrdHttpReq.cc @@ -926,7 +926,14 @@ void XrdHttpReq::mapXrdErrorToHttpStatus() { httpStatusCode = 409; httpStatusText = "Resource is a directory"; break; case kXR_ItExists: - httpStatusCode = 409; httpStatusText = "File already exists"; + if(request != ReqType::rtDELETE) { + httpStatusCode = 409; httpStatusText = "File already exists"; + } else { + // In the case the XRootD layer returns a kXR_ItExists after a deletion + // was submitted, we return a 405 status code with the error message set by + // the XRootD layer + httpStatusCode = 405; + } break; case kXR_InvalidRequest: httpStatusCode = 405; httpStatusText = "Method is not allowed"; From 6f591adbbe9108650b9f7fec2e1776c4152c1d49 Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Tue, 30 May 2023 09:21:55 +0200 Subject: [PATCH 111/442] [XProtocol.hh] Added fallthrough statement for ENOTEMPTY errno code mapping --- src/XProtocol/XProtocol.hh | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/XProtocol/XProtocol.hh b/src/XProtocol/XProtocol.hh index 7b13d3916a3..561161d9913 100644 --- a/src/XProtocol/XProtocol.hh +++ b/src/XProtocol/XProtocol.hh @@ -1370,7 +1370,12 @@ static int mapError(int rc) case ENOTBLK: return kXR_NotFile; case ENOTSUP: return kXR_Unsupported; case EISDIR: return kXR_isDirectory; - case EEXIST: return kXR_ItExists; + case ENOTEMPTY: [[fallthrough]]; + // In the case one tries to delete a non-empty directory + // we have decided that until the next major release + // the kXR_ItExists flag will be returned + case EEXIST: + return kXR_ItExists; case EBADRQC: return kXR_InvalidRequest; case ETXTBSY: return kXR_inProgress; case ENODEV: return kXR_FSError; @@ -1391,10 +1396,6 @@ static int mapError(int rc) case ETIMEDOUT: return kXR_ReqTimedOut; case EBADF: return kXR_FileNotOpen; case ECANCELED: return kXR_Cancelled; - // In the case one tries to delete a non-empty directory - // we have decided that until the next major release - // the kXR_ItExists flag will be returned - case ENOTEMPTY: return kXR_ItExists; default: return kXR_FSError; } } From 937b5eeef9419a53d5770f3047aa98de2cb51994 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Tue, 30 May 2023 17:32:38 -0700 Subject: [PATCH 112/442] [Server] Allow maxfd to be configurable and set the defalt to 256K. --- src/Xrd/XrdConfig.cc | 33 ++++++++++++++++++++++++++++++++- src/Xrd/XrdConfig.hh | 3 +++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/Xrd/XrdConfig.cc b/src/Xrd/XrdConfig.cc index be95219132e..37bcaa6b5c0 100644 --- a/src/Xrd/XrdConfig.cc +++ b/src/Xrd/XrdConfig.cc @@ -274,6 +274,7 @@ XrdConfig::XrdConfig() NetADM = 0; coreV = 1; Specs = 0; + maxFD = 256*1024; // 256K default Firstcp = Lastcp = 0; @@ -805,6 +806,7 @@ int XrdConfig::ConfigXeq(char *var, XrdOucStream &Config, XrdSysError *eDest) TS_Xeq("adminpath", xapath); TS_Xeq("allow", xallow); TS_Xeq("homepath", xhpath); + TS_Xeq("maxfd", xmaxfd); TS_Xeq("pidpath", xpidf); TS_Xeq("port", xport); TS_Xeq("protocol", xprot); @@ -1176,7 +1178,6 @@ void XrdConfig::setCFG(bool start) int XrdConfig::setFDL() { - static const int maxFD = 65535; // was 1048576 see XrdNetAddrInfo::sockNum struct rlimit rlim; char buff[100]; @@ -1649,6 +1650,36 @@ int XrdConfig::xbuf(XrdSysError *eDest, XrdOucStream &Config) return 0; } + +/******************************************************************************/ +/* x m a x f d */ +/******************************************************************************/ + +/* Function: xmaxfd + + Purpose: To parse the directive: maxfd + + maximum number of fs that can be established. + Specify a value optionally suffixed with 'k'. + + Output: 0 upon success or !0 upon failure. +*/ +int XrdConfig::xmaxfd(XrdSysError *eDest, XrdOucStream &Config) +{ + long long minFD = 1024, maxFD = 1024LL*1024LL; // between 1k and 1m + long long fdVal; + char *val; + + if (!(val = Config.GetWord())) + {eDest->Emsg("Config", "file descriptor limit not specified"); return 1;} + + if (XrdOuca2x::a2sz(*eDest,"maxfd value",val,&fdVal,minFD,maxFD)) return 1; + + maxFD = static_cast(fdVal); + + return 0; +} + /******************************************************************************/ /* x n e t */ /******************************************************************************/ diff --git a/src/Xrd/XrdConfig.hh b/src/Xrd/XrdConfig.hh index 2d1403a5e10..89f104462f7 100644 --- a/src/Xrd/XrdConfig.hh +++ b/src/Xrd/XrdConfig.hh @@ -76,6 +76,7 @@ int xallow(XrdSysError *edest, XrdOucStream &Config); int xapath(XrdSysError *edest, XrdOucStream &Config); int xhpath(XrdSysError *edest, XrdOucStream &Config); int xbuf(XrdSysError *edest, XrdOucStream &Config); +int xmaxfd(XrdSysError *edest, XrdOucStream &Config); int xnet(XrdSysError *edest, XrdOucStream &Config); int xnkap(XrdSysError *edest, char *val); int xlog(XrdSysError *edest, XrdOucStream &Config); @@ -134,5 +135,7 @@ char ppNet; signed char coreV; char Specs; static const int hpSpec = 0x01; + +unsigned int maxFD; }; #endif From 946cb968fdd0b1f7a35792f3510e8fdbf39b23f8 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Tue, 30 May 2023 17:56:34 -0700 Subject: [PATCH 113/442] Update notes on maxfd directive. --- docs/PreReleaseNotes.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/PreReleaseNotes.txt b/docs/PreReleaseNotes.txt index b74a43ec39e..e6a44528487 100644 --- a/docs/PreReleaseNotes.txt +++ b/docs/PreReleaseNotes.txt @@ -6,6 +6,8 @@ Prerelease Notes ================ + **New Features** + **[Server]** Make maxfd be configurable (default is 256k). + **Commit: 937b5ee **[Client]** Add xrdfs cache subcommand to allow for cache evictions. **Commit: 39f9e0a **[Xcache]** Implement a file evict function. From 27d65c0ac5de7ea30756fadfc2610e7bbe1b5d56 Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Tue, 30 May 2023 16:47:07 +0200 Subject: [PATCH 114/442] [XrdHttp] A GET with 'Want-Digest' header request issued on a non-existing file will return a 404 error instead of 500 --- src/XrdHttp/XrdHttpReq.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdHttp/XrdHttpReq.cc b/src/XrdHttp/XrdHttpReq.cc index 732ca2e4254..c3d052dfb60 100644 --- a/src/XrdHttp/XrdHttpReq.cc +++ b/src/XrdHttp/XrdHttpReq.cc @@ -1805,7 +1805,7 @@ XrdHttpReq::PostProcessChecksum(std::string &digest_header) { if (convert_to_base64) {free(digest_value);} return 0; } else { - prot->SendSimpleResp(500, NULL, NULL, "Underlying filesystem failed to calculate checksum.", 0, false); + prot->SendSimpleResp(httpStatusCode, NULL, NULL, httpStatusText.c_str(), httpStatusText.length(), false); return -1; } } From e1ba7a2e766fc972eaf64f3bcb79973c2b1c8775 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Wed, 31 May 2023 22:29:36 -0700 Subject: [PATCH 115/442] [Server] Version 2 of maxfd implementation to satisfy both camps. --- src/Xrd/XrdConfig.cc | 22 +++++++++++++++++----- src/Xrd/XrdConfig.hh | 1 + 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/Xrd/XrdConfig.cc b/src/Xrd/XrdConfig.cc index 37bcaa6b5c0..2a68f226fab 100644 --- a/src/Xrd/XrdConfig.cc +++ b/src/Xrd/XrdConfig.cc @@ -274,6 +274,7 @@ XrdConfig::XrdConfig() NetADM = 0; coreV = 1; Specs = 0; + isStrict = false; maxFD = 256*1024; // 256K default Firstcp = Lastcp = 0; @@ -1188,7 +1189,8 @@ int XrdConfig::setFDL() // Set the limit to the maximum allowed // - if (rlim.rlim_max == RLIM_INFINITY || rlim.rlim_max > maxFD) rlim.rlim_cur = maxFD; + if (rlim.rlim_max == RLIM_INFINITY || (isStrict && rlim.rlim_max > maxFD)) + rlim.rlim_cur = maxFD; else rlim.rlim_cur = rlim.rlim_max; #if (defined(__APPLE__) && defined(MAC_OS_X_VERSION_10_5)) if (rlim.rlim_cur > OPEN_MAX) rlim.rlim_max = rlim.rlim_cur = OPEN_MAX; @@ -1657,8 +1659,10 @@ int XrdConfig::xbuf(XrdSysError *eDest, XrdOucStream &Config) /* Function: xmaxfd - Purpose: To parse the directive: maxfd + Purpose: To parse the directive: maxfd [strict] + strict when specified, the limits is always applied. Otherwise, + it is only applied when rlimit is infinite. maximum number of fs that can be established. Specify a value optionally suffixed with 'k'. @@ -1666,14 +1670,22 @@ int XrdConfig::xbuf(XrdSysError *eDest, XrdOucStream &Config) */ int XrdConfig::xmaxfd(XrdSysError *eDest, XrdOucStream &Config) { - long long minFD = 1024, maxFD = 1024LL*1024LL; // between 1k and 1m + long long minV = 1024, maxV = 1024LL*1024LL; // between 1k and 1m long long fdVal; char *val; - if (!(val = Config.GetWord())) + if ((val = Config.GetWord())) + {if (!strcmp(val, "strict")) + {isStrict = true; + val = Config.GetWord(); + } else isStrict = false; + } + + if (!val) {eDest->Emsg("Config", "file descriptor limit not specified"); return 1;} - if (XrdOuca2x::a2sz(*eDest,"maxfd value",val,&fdVal,minFD,maxFD)) return 1; + + if (XrdOuca2x::a2sz(*eDest,"maxfd value",val,&fdVal,minV,maxV)) return 1; maxFD = static_cast(fdVal); diff --git a/src/Xrd/XrdConfig.hh b/src/Xrd/XrdConfig.hh index 89f104462f7..f29dc6005b2 100644 --- a/src/Xrd/XrdConfig.hh +++ b/src/Xrd/XrdConfig.hh @@ -136,6 +136,7 @@ signed char coreV; char Specs; static const int hpSpec = 0x01; +bool isStrict; unsigned int maxFD; }; #endif From d9a4c5ec81700086f62dd6a121912ff47e4197a1 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Wed, 31 May 2023 22:31:21 -0700 Subject: [PATCH 116/442] Update notes on additional commit for maxfd config. --- docs/PreReleaseNotes.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/PreReleaseNotes.txt b/docs/PreReleaseNotes.txt index e6a44528487..2ba30dc3a78 100644 --- a/docs/PreReleaseNotes.txt +++ b/docs/PreReleaseNotes.txt @@ -7,7 +7,7 @@ Prerelease Notes + **New Features** **[Server]** Make maxfd be configurable (default is 256k). - **Commit: 937b5ee + **Commit: 937b5ee e1ba7a2 **[Client]** Add xrdfs cache subcommand to allow for cache evictions. **Commit: 39f9e0a **[Xcache]** Implement a file evict function. From c6928f09e4f2b9970416f4ab11f637d99015673a Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Thu, 1 Jun 2023 23:18:37 -0700 Subject: [PATCH 117/442] [TLS] Make sure context is marked invalid if not properly constructed. --- src/XrdTls/XrdTlsContext.cc | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/XrdTls/XrdTlsContext.cc b/src/XrdTls/XrdTlsContext.cc index d31ae3d6709..323c6ae129c 100644 --- a/src/XrdTls/XrdTlsContext.cc +++ b/src/XrdTls/XrdTlsContext.cc @@ -538,8 +538,11 @@ int VerCB(int aOK, X509_STORE_CTX *x509P) /* C o n s t r u c t o r */ /******************************************************************************/ -#define FATAL(msg) {Fatal(eMsg, msg); return;} -#define FATAL_SSL(msg) {Fatal(eMsg, msg, true); return;} +#define KILL_CTX(x) if (x) {SSL_CTX_free(x); x = 0;} + +#define FATAL(msg) {Fatal(eMsg, msg); KILL_CTX(pImpl->ctx); return;} + +#define FATAL_SSL(msg) {Fatal(eMsg, msg, true); KILL_CTX(pImpl->ctx); return;} XrdTlsContext::XrdTlsContext(const char *cert, const char *key, const char *caDir, const char *caFile, @@ -1102,4 +1105,4 @@ bool XrdTlsContext::newHostCertificateDetected() { } } return false; -} \ No newline at end of file +} From c7a7f67fc822e78e68c5b1987a9989ab142fe802 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Thu, 1 Jun 2023 23:21:02 -0700 Subject: [PATCH 118/442] Update notes on invalidating TLS context. --- docs/PreReleaseNotes.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/PreReleaseNotes.txt b/docs/PreReleaseNotes.txt index 2ba30dc3a78..21053d0a9fb 100644 --- a/docs/PreReleaseNotes.txt +++ b/docs/PreReleaseNotes.txt @@ -22,6 +22,8 @@ Prerelease Notes + **Major bug fixes** + **Minor bug fixes** + **[TLS]** Make sure context is marked invalid if not properly constructed. + **Commit: c6928f0 + **Miscellaneous** **[Server]** Also check for IPv6 ULA's to determine if an address is private. From 3843e78b1eb1fd5ea7fc0e82c871aaf31cae3551 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 30 May 2023 16:36:11 +0200 Subject: [PATCH 119/442] [CMake] Use an interface library for bundled isa-l --- src/XrdIsal.cmake | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/XrdIsal.cmake b/src/XrdIsal.cmake index f110df69dc3..aced6b38d42 100644 --- a/src/XrdIsal.cmake +++ b/src/XrdIsal.cmake @@ -31,16 +31,14 @@ ExternalProject_add(isa-l BUILD_BYPRODUCTS ${ISAL_LIBRARY} ${ISAL_INCLUDE_DIRS} ) -add_library(isal STATIC IMPORTED) - -set(ISAL_LIBRARIES isal) +add_library(isal INTERFACE) add_dependencies(isal isa-l) -set_target_properties(isal - PROPERTIES - IMPORTED_LOCATION "${ISAL_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "$" -) +target_link_libraries(isal INTERFACE $) +target_include_directories(isal INTERFACE $) + +set(ISAL_LIBRARIES isal CACHE INTERNAL "" FORCE) +set(ISAL_INCLUDE_DIRS ${ISAL_INCLUDE_DIRS} CACHE INTERNAL "" FORCE) # Emulate what happens when find_package(isal) succeeds find_package_handle_standard_args(isal From c13a21825d01de13674d314d50e5884beec62e0d Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 30 May 2023 09:56:07 +0200 Subject: [PATCH 120/442] [CMake] Replace setting blank link properties with private linking Originally introduced in 220e15eb9cd75143ac1c67f4204759f575ef092d to prevent overlinking, setting an empty INTERFACE_LINK_LIBRARIES can now be replaced with target_link_libraries with PRIVATE linking. --- src/XrdApps.cmake | 17 ++-------- src/XrdCeph/src/XrdCeph.cmake | 19 +++-------- src/XrdCl/CMakeLists.txt | 4 ++- src/XrdClHttp/CMakeLists.txt | 2 +- src/XrdCrypto.cmake | 17 +++------- src/XrdEc/CMakeLists.txt | 6 ++-- src/XrdFfs.cmake | 5 ++- src/XrdHttp.cmake | 3 ++ src/XrdMacaroons.cmake | 8 ++--- src/XrdOssCsi.cmake | 7 +---- src/XrdPfc.cmake | 14 ++------- src/XrdPlugins.cmake | 56 +++++---------------------------- src/XrdPosix.cmake | 10 +++--- src/XrdSciTokens.cmake | 7 +---- src/XrdSec.cmake | 35 +++------------------ src/XrdSecgsi.cmake | 21 ++----------- src/XrdSeckrb5.cmake | 7 +---- src/XrdSecztn.cmake | 7 +---- src/XrdServer.cmake | 12 ++----- src/XrdSsi.cmake | 22 ++++--------- src/XrdTpc.cmake | 3 +- src/XrdUtils.cmake | 7 ++--- src/XrdVoms.cmake | 7 +---- src/XrdXml.cmake | 5 ++- tests/XrdEcTests/CMakeLists.txt | 9 +++--- 25 files changed, 73 insertions(+), 237 deletions(-) diff --git a/src/XrdApps.cmake b/src/XrdApps.cmake index a4b91f77ba0..01bf1b68e5b 100644 --- a/src/XrdApps.cmake +++ b/src/XrdApps.cmake @@ -162,6 +162,7 @@ add_library( target_link_libraries( XrdAppUtils + PRIVATE XrdUtils ) set_target_properties( @@ -179,13 +180,7 @@ add_library( XrdApps/XrdClProxyPlugin/ProxyPrefixPlugin.cc XrdApps/XrdClProxyPlugin/ProxyPrefixFile.cc) -target_link_libraries(${LIB_XRDCL_PROXY_PLUGIN} XrdCl) - -set_target_properties( - ${LIB_XRDCL_PROXY_PLUGIN} - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) +target_link_libraries(${LIB_XRDCL_PROXY_PLUGIN} PRIVATE XrdCl) #------------------------------------------------------------------------------- # XrdClRecorder library @@ -195,13 +190,7 @@ add_library( MODULE XrdApps/XrdClRecordPlugin/XrdClRecorderPlugin.cc ) -target_link_libraries(${LIB_XRDCL_RECORDER_PLUGIN} XrdCl) - -set_target_properties( - ${LIB_XRDCL_RECORDER_PLUGIN} - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) +target_link_libraries(${LIB_XRDCL_RECORDER_PLUGIN} PRIVATE XrdCl) add_executable( xrdreplay diff --git a/src/XrdCeph/src/XrdCeph.cmake b/src/XrdCeph/src/XrdCeph.cmake index 1a68a7f8296..60fb1da2099 100644 --- a/src/XrdCeph/src/XrdCeph.cmake +++ b/src/XrdCeph/src/XrdCeph.cmake @@ -24,6 +24,7 @@ set_property(SOURCE XrdCeph/XrdCephPosix.cc target_link_libraries( XrdCephPosix + PRIVATE ${XROOTD_LIBRARIES} ${RADOS_LIBS} ) @@ -31,9 +32,7 @@ set_target_properties( XrdCephPosix PROPERTIES VERSION ${XRD_CEPH_POSIX_VERSION} - SOVERSION ${XRD_CEPH_POSIX_SOVERSION} - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) + SOVERSION ${XRD_CEPH_POSIX_SOVERSION} ) #------------------------------------------------------------------------------- # The XrdCeph module @@ -49,15 +48,10 @@ add_library( target_link_libraries( ${LIB_XRD_CEPH} + PRIVATE ${XROOTD_LIBRARIES} XrdCephPosix ) -set_target_properties( - ${LIB_XRD_CEPH} - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) - #------------------------------------------------------------------------------- # The XrdCephXattr module #------------------------------------------------------------------------------- @@ -70,15 +64,10 @@ add_library( target_link_libraries( ${LIB_XRD_CEPH_XATTR} + PRIVATE ${XROOTD_LIBRARIES} XrdCephPosix ) -set_target_properties( - ${LIB_XRD_CEPH_XATTR} - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) - #------------------------------------------------------------------------------- # Install #------------------------------------------------------------------------------- diff --git a/src/XrdCl/CMakeLists.txt b/src/XrdCl/CMakeLists.txt index 1abe0d4b9cb..bac9e51bde1 100644 --- a/src/XrdCl/CMakeLists.txt +++ b/src/XrdCl/CMakeLists.txt @@ -108,6 +108,7 @@ add_library( target_link_libraries( XrdCl + PRIVATE XrdXml XrdUtils uuid::uuid @@ -125,7 +126,7 @@ set_target_properties( if( BUILD_XRDEC ) target_include_directories(XrdCl PUBLIC ${ISAL_INCLUDE_DIRS}) - target_link_libraries(XrdCl ${ISAL_LIBRARIES}) + target_link_libraries(XrdCl PRIVATE ${ISAL_LIBRARIES}) endif() #------------------------------------------------------------------------------- @@ -157,6 +158,7 @@ add_executable( target_link_libraries( xrdcp XrdCl + XrdUtils XrdAppUtils ) endif() diff --git a/src/XrdClHttp/CMakeLists.txt b/src/XrdClHttp/CMakeLists.txt index f70d7aea5df..5e4ceb0ad2e 100644 --- a/src/XrdClHttp/CMakeLists.txt +++ b/src/XrdClHttp/CMakeLists.txt @@ -9,6 +9,6 @@ set(PLUGIN_NAME "XrdClHttp-${PLUGIN_VERSION}") add_library(${PLUGIN_NAME} MODULE ${libXrdClHttp_sources}) -target_link_libraries(${PLUGIN_NAME} Davix::Davix XrdCl XrdUtils) +target_link_libraries(${PLUGIN_NAME} PRIVATE Davix::Davix XrdCl XrdUtils) install(TARGETS ${PLUGIN_NAME} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) diff --git a/src/XrdCrypto.cmake b/src/XrdCrypto.cmake index 337f18de779..fecb569c1cd 100644 --- a/src/XrdCrypto.cmake +++ b/src/XrdCrypto.cmake @@ -38,6 +38,7 @@ add_library( target_link_libraries( XrdCrypto + PRIVATE XrdUtils ${CMAKE_DL_LIBS} ) @@ -45,9 +46,7 @@ set_target_properties( XrdCrypto PROPERTIES VERSION ${XRD_CRYPTO_VERSION} - SOVERSION ${XRD_CRYPTO_SOVERSION} - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) + SOVERSION ${XRD_CRYPTO_SOVERSION} ) #------------------------------------------------------------------------------- # The XrdCryptoLite library @@ -64,6 +63,7 @@ add_library( target_link_libraries( XrdCryptoLite + PRIVATE XrdUtils OpenSSL::Crypto ) @@ -71,9 +71,7 @@ set_target_properties( XrdCryptoLite PROPERTIES VERSION ${XRD_CRYPTO_LITE_VERSION} - SOVERSION ${XRD_CRYPTO_LITE_SOVERSION} - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) + SOVERSION ${XRD_CRYPTO_LITE_SOVERSION} ) #------------------------------------------------------------------------------- # The XrdCryptossl module @@ -98,17 +96,12 @@ add_library( target_link_libraries( ${LIB_XRD_CRYPTOSSL} + PRIVATE XrdCrypto XrdUtils ${CMAKE_THREAD_LIBS_INIT} OpenSSL::SSL ) -set_target_properties( - ${LIB_XRD_CRYPTOSSL} - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) - #------------------------------------------------------------------------------- # Install #------------------------------------------------------------------------------- diff --git a/src/XrdEc/CMakeLists.txt b/src/XrdEc/CMakeLists.txt index eedaaf9fbbb..9e030880e37 100644 --- a/src/XrdEc/CMakeLists.txt +++ b/src/XrdEc/CMakeLists.txt @@ -20,10 +20,8 @@ add_library( XrdEcReader.hh XrdEcReader.cc ) -target_link_libraries( - XrdEc - XrdCl -) +target_link_libraries(XrdEc PRIVATE XrdCl ${ISAL_LIBRARIES}) +target_include_directories(XrdEc PRIVATE ${ISAL_INCLUDE_DIRS}) set_target_properties( XrdEc diff --git a/src/XrdFfs.cmake b/src/XrdFfs.cmake index 70551add9ee..a19410d1d56 100644 --- a/src/XrdFfs.cmake +++ b/src/XrdFfs.cmake @@ -21,6 +21,7 @@ add_library( target_link_libraries( XrdFfs + PRIVATE XrdCl XrdPosix XrdUtils @@ -30,9 +31,7 @@ set_target_properties( XrdFfs PROPERTIES VERSION ${XRD_FFS_VERSION} - SOVERSION ${XRD_FFS_SOVERSION} - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) + SOVERSION ${XRD_FFS_SOVERSION} ) #------------------------------------------------------------------------------- # xrootdfs diff --git a/src/XrdHttp.cmake b/src/XrdHttp.cmake index 1a7ed1e9d0a..391e290b4ce 100644 --- a/src/XrdHttp.cmake +++ b/src/XrdHttp.cmake @@ -42,16 +42,19 @@ if( BUILD_HTTP ) target_link_libraries( ${LIB_XRD_HTTP_UTILS} + PRIVATE XrdServer XrdUtils XrdCrypto ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT} + PUBLIC OpenSSL::SSL OpenSSL::Crypto ) target_link_libraries( ${MOD_XRD_HTTP} + PRIVATE XrdUtils ${LIB_XRD_HTTP_UTILS} ) diff --git a/src/XrdMacaroons.cmake b/src/XrdMacaroons.cmake index c7acc93ed37..bc2e55ae8f5 100644 --- a/src/XrdMacaroons.cmake +++ b/src/XrdMacaroons.cmake @@ -20,7 +20,8 @@ if( BUILD_MACAROONS ) XrdMacaroons/XrdMacaroonsConfigure.cc) target_link_libraries( - ${LIB_XRD_MACAROONS} ${CMAKE_DL_LIBS} + ${LIB_XRD_MACAROONS} + PRIVATE XrdHttpUtils XrdUtils XrdServer @@ -28,7 +29,8 @@ if( BUILD_MACAROONS ) ${MACAROONS_LIB} ${JSON_LIBRARIES} ${XROOTD_HTTP_LIB} - OpenSSL::Crypto) + OpenSSL::Crypto + ${CMAKE_DL_LIBS}) if( MacOSX ) SET( MACAROONS_LINK_FLAGS "-Wl") @@ -39,8 +41,6 @@ if( BUILD_MACAROONS ) set_target_properties( ${LIB_XRD_MACAROONS} PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" LINK_FLAGS "${MACAROONS_LINK_FLAGS}") #----------------------------------------------------------------------------- diff --git a/src/XrdOssCsi.cmake b/src/XrdOssCsi.cmake index 3ef4526f95d..30adcf1f013 100644 --- a/src/XrdOssCsi.cmake +++ b/src/XrdOssCsi.cmake @@ -29,16 +29,11 @@ add_library( target_link_libraries( ${LIB_XRD_OSSCSI} + PRIVATE XrdUtils XrdServer ${CMAKE_THREAD_LIBS_INIT} ) -set_target_properties( - ${LIB_XRD_OSSCSI} - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) - #------------------------------------------------------------------------------- # Install #------------------------------------------------------------------------------- diff --git a/src/XrdPfc.cmake b/src/XrdPfc.cmake index 719542ebb64..21ad24f6f8c 100644 --- a/src/XrdPfc.cmake +++ b/src/XrdPfc.cmake @@ -32,18 +32,13 @@ add_library( target_link_libraries( ${LIB_XRD_FILECACHE} + PRIVATE # XrdPosix XrdCl XrdUtils XrdServer ${CMAKE_THREAD_LIBS_INIT} ) -set_target_properties( - ${LIB_XRD_FILECACHE} - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) - #------------------------------------------------------------------------------- # The XrdBlacklistDecision library #------------------------------------------------------------------------------- @@ -54,15 +49,10 @@ add_library( target_link_libraries( ${LIB_XRD_BLACKLIST} + PRIVATE XrdUtils ) -set_target_properties( - ${LIB_XRD_BLACKLIST} - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) - #------------------------------------------------------------------------------- # xrdpfc_print #------------------------------------------------------------------------------- diff --git a/src/XrdPlugins.cmake b/src/XrdPlugins.cmake index a7fa1bf0c48..adfda36e43e 100644 --- a/src/XrdPlugins.cmake +++ b/src/XrdPlugins.cmake @@ -33,16 +33,11 @@ add_library( target_link_libraries( ${LIB_XRD_PSS} + PRIVATE XrdPosix XrdUtils XrdServer ) -set_target_properties( - ${LIB_XRD_PSS} - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) - #------------------------------------------------------------------------------- # The XrdBwm module #------------------------------------------------------------------------------- @@ -59,16 +54,11 @@ add_library( target_link_libraries( ${LIB_XRD_BWM} + PRIVATE XrdServer XrdUtils ${CMAKE_THREAD_LIBS_INIT} ) -set_target_properties( - ${LIB_XRD_BWM} - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) - #------------------------------------------------------------------------------- # N2No2p plugin library #------------------------------------------------------------------------------- @@ -79,14 +69,9 @@ add_library( target_link_libraries( ${LIB_XRD_N2NO2P} + PRIVATE XrdUtils ) -set_target_properties( - ${LIB_XRD_N2NO2P} - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) - #------------------------------------------------------------------------------- # GPFS stat() plugin library #------------------------------------------------------------------------------- @@ -97,14 +82,9 @@ add_library( target_link_libraries( ${LIB_XRD_GPFS} + PRIVATE XrdUtils ) -set_target_properties( - ${LIB_XRD_GPFS} - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) - #------------------------------------------------------------------------------- # Ofs Generic Prepare plugin library #------------------------------------------------------------------------------- @@ -115,14 +95,9 @@ add_library( target_link_libraries( ${LIB_XRD_GPI} + PRIVATE XrdUtils ) -set_target_properties( - ${LIB_XRD_GPI} - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) - #------------------------------------------------------------------------------- # libz compatible CRC32 plugin #------------------------------------------------------------------------------- @@ -134,15 +109,10 @@ add_library( target_link_libraries( ${LIB_XRD_ZCRC32} + PRIVATE XrdUtils ZLIB::ZLIB) -set_target_properties( - ${LIB_XRD_ZCRC32} - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) - #------------------------------------------------------------------------------- # The XrdThrottle lib #------------------------------------------------------------------------------- @@ -159,15 +129,10 @@ add_library( target_link_libraries( ${LIB_XRD_THROTTLE} + PRIVATE XrdServer XrdUtils ) -set_target_properties( - ${LIB_XRD_THROTTLE} - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) - #------------------------------------------------------------------------------- # The XrdCmsRedirLocal module #------------------------------------------------------------------------------- @@ -178,15 +143,10 @@ add_library( target_link_libraries( ${LIB_XRD_CMSREDIRL} + PRIVATE XrdServer XrdUtils ) -set_target_properties( - ${LIB_XRD_CMSREDIRL} - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) - #------------------------------------------------------------------------------- # Install #------------------------------------------------------------------------------- diff --git a/src/XrdPosix.cmake b/src/XrdPosix.cmake index d67d05e7578..df769110d45 100644 --- a/src/XrdPosix.cmake +++ b/src/XrdPosix.cmake @@ -34,6 +34,7 @@ add_library( target_link_libraries( XrdPosix + PRIVATE XrdCl XrdUtils ${CMAKE_THREAD_LIBS_INIT} ) @@ -42,9 +43,7 @@ set_target_properties( XrdPosix PROPERTIES VERSION ${XRD_POSIX_VERSION} - SOVERSION ${XRD_POSIX_SOVERSION} - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) + SOVERSION ${XRD_POSIX_SOVERSION} ) #------------------------------------------------------------------------------- # The XrdPosixPreload library @@ -61,6 +60,7 @@ add_library( target_link_libraries( XrdPosixPreload + PRIVATE XrdPosix ${CMAKE_DL_LIBS} ) @@ -68,9 +68,7 @@ set_target_properties( XrdPosixPreload PROPERTIES VERSION ${XRD_POSIX_PRELOAD_VERSION} - SOVERSION ${XRD_POSIX_PRELOAD_SOVERSION} - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) + SOVERSION ${XRD_POSIX_PRELOAD_SOVERSION} ) #------------------------------------------------------------------------------- # Install diff --git a/src/XrdSciTokens.cmake b/src/XrdSciTokens.cmake index 244232e2d18..7a079e76854 100644 --- a/src/XrdSciTokens.cmake +++ b/src/XrdSciTokens.cmake @@ -21,18 +21,13 @@ add_library( XrdSciTokens/XrdSciTokensHelper.hh ) target_link_libraries( ${LIB_XRD_SCITOKENS} + PRIVATE ${SCITOKENS_CPP_LIBRARIES} XrdUtils XrdServer ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT} ) -set_target_properties( - ${LIB_XRD_SCITOKENS} - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) - #------------------------------------------------------------------------------- # Install #------------------------------------------------------------------------------- diff --git a/src/XrdSec.cmake b/src/XrdSec.cmake index 5647d52fa46..6f878e5b736 100644 --- a/src/XrdSec.cmake +++ b/src/XrdSec.cmake @@ -33,16 +33,11 @@ add_library( target_link_libraries( ${LIB_XRD_SEC} + PRIVATE XrdUtils ${CMAKE_THREAD_LIBS_INIT} ${CMAKE_DL_LIBS} ) -set_target_properties( - ${LIB_XRD_SEC} - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) - #------------------------------------------------------------------------------- # The XrdSecpwd module #------------------------------------------------------------------------------- @@ -57,16 +52,11 @@ add_library( target_link_libraries( ${LIB_XRD_SEC_PROT} + PRIVATE XrdUtils ${CMAKE_THREAD_LIBS_INIT} OpenSSL::Crypto ) -set_target_properties( - ${LIB_XRD_SEC_PROT} - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) - #------------------------------------------------------------------------------- # The XrdSecpwd module #------------------------------------------------------------------------------- @@ -78,17 +68,12 @@ add_library( target_link_libraries( ${LIB_XRD_SEC_PWD} + PRIVATE XrdCrypto XrdUtils ${CMAKE_THREAD_LIBS_INIT} ${CRYPT_LIBRARY} ) -set_target_properties( - ${LIB_XRD_SEC_PWD} - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) - if( NOT XRDCL_LIB_ONLY ) #------------------------------------------------------------------------------- # xrdpwdadmin @@ -114,15 +99,10 @@ add_library( target_link_libraries( ${LIB_XRD_SEC_SSS} + PRIVATE XrdCryptoLite XrdUtils ) -set_target_properties( - ${LIB_XRD_SEC_SSS} - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) - if( NOT XRDCL_LIB_ONLY ) #------------------------------------------------------------------------------- # xrdsssadmin @@ -146,14 +126,9 @@ add_library( target_link_libraries( ${LIB_XRD_SEC_UNIX} + PRIVATE XrdUtils ) -set_target_properties( - ${LIB_XRD_SEC_UNIX} - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) - #------------------------------------------------------------------------------- # Install #------------------------------------------------------------------------------- diff --git a/src/XrdSecgsi.cmake b/src/XrdSecgsi.cmake index 8559ffe920d..45f058f206a 100644 --- a/src/XrdSecgsi.cmake +++ b/src/XrdSecgsi.cmake @@ -23,16 +23,11 @@ add_library( target_link_libraries( ${LIB_XRD_SEC_GSI} + PRIVATE XrdCrypto XrdUtils ${CMAKE_THREAD_LIBS_INIT} ) -set_target_properties( - ${LIB_XRD_SEC_GSI} - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) - #------------------------------------------------------------------------------- # The XrdSecgsiAuthzVO module #------------------------------------------------------------------------------- @@ -43,14 +38,9 @@ add_library( target_link_libraries( ${LIB_XRD_SEC_GSI_AUTHZVO} + PRIVATE XrdUtils ) -set_target_properties( - ${LIB_XRD_SEC_GSI_AUTHZVO} - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) - #------------------------------------------------------------------------------- # The XrdSecgsiGMAPDN module #------------------------------------------------------------------------------- @@ -61,14 +51,9 @@ add_library( target_link_libraries( ${LIB_XRD_SEC_GSI_GMAPDN} + PRIVATE XrdUtils ) -set_target_properties( - ${LIB_XRD_SEC_GSI_GMAPDN} - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) - if( NOT XRDCL_LIB_ONLY ) #------------------------------------------------------------------------------- # xrdgsiproxy diff --git a/src/XrdSeckrb5.cmake b/src/XrdSeckrb5.cmake index 5f3fbbdb965..e04b9b6ecd6 100644 --- a/src/XrdSeckrb5.cmake +++ b/src/XrdSeckrb5.cmake @@ -12,15 +12,10 @@ add_library( target_link_libraries( ${LIB_XRD_SEC_KRB5} + PRIVATE XrdUtils ${KERBEROS5_LIBRARIES} ) -set_target_properties( - ${LIB_XRD_SEC_KRB5} - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) - #------------------------------------------------------------------------------- # Install #------------------------------------------------------------------------------- diff --git a/src/XrdSecztn.cmake b/src/XrdSecztn.cmake index 3274140cfde..e02abc18f45 100644 --- a/src/XrdSecztn.cmake +++ b/src/XrdSecztn.cmake @@ -13,15 +13,10 @@ add_library( target_link_libraries( ${LIB_XRD_SEC_ZTN} + PRIVATE XrdUtils ) -set_target_properties( - ${LIB_XRD_SEC_ZTN} - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) - #------------------------------------------------------------------------------- # Install #------------------------------------------------------------------------------- diff --git a/src/XrdServer.cmake b/src/XrdServer.cmake index a2ab25c5ee9..b826eb2b43f 100644 --- a/src/XrdServer.cmake +++ b/src/XrdServer.cmake @@ -187,6 +187,7 @@ add_library( target_link_libraries( XrdServer + PRIVATE XrdUtils ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT} @@ -198,9 +199,7 @@ set_target_properties( XrdServer PROPERTIES VERSION ${XRD_SERVER_VERSION} - SOVERSION ${XRD_SERVER_SOVERSION} - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) + SOVERSION ${XRD_SERVER_SOVERSION} ) #------------------------------------------------------------------------------- # The XRootD protocol plugin @@ -212,16 +211,11 @@ add_library( target_link_libraries( ${LIB_XRD_PROTOCOL} + PRIVATE XrdServer XrdUtils ${EXTRA_LIBS} ) -set_target_properties( - ${LIB_XRD_PROTOCOL} - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) - #------------------------------------------------------------------------------- # Install #------------------------------------------------------------------------------- diff --git a/src/XrdSsi.cmake b/src/XrdSsi.cmake index 352b291d6a8..a5f00d92994 100644 --- a/src/XrdSsi.cmake +++ b/src/XrdSsi.cmake @@ -49,6 +49,7 @@ XrdSsi/XrdSsiUtils.cc XrdSsi/XrdSsiUtils.hh) target_link_libraries( XrdSsiLib + PRIVATE XrdCl XrdUtils ${CMAKE_THREAD_LIBS_INIT} ) @@ -57,8 +58,7 @@ set_target_properties( XrdSsiLib PROPERTIES VERSION ${XRD_SSI_LIB_VERSION} - SOVERSION ${XRD_SSI_LIB_SOVERSION} - LINK_INTERFACE_LIBRARIES "" ) + SOVERSION ${XRD_SSI_LIB_SOVERSION} ) #------------------------------------------------------------------------------- # The XrdSsiShMap library @@ -72,6 +72,7 @@ XrdSsi/XrdSsiShMat.cc XrdSsi/XrdSsiShMat.hh) target_link_libraries( XrdSsiShMap + PRIVATE XrdUtils ZLIB::ZLIB ${CMAKE_THREAD_LIBS_INIT} ) @@ -80,8 +81,7 @@ set_target_properties( XrdSsiShMap PROPERTIES VERSION ${XRD_SSI_SHMAP_VERSION} - SOVERSION ${XRD_SSI_SHMAP_SOVERSION} - LINK_INTERFACE_LIBRARIES "" ) + SOVERSION ${XRD_SSI_SHMAP_SOVERSION} ) #------------------------------------------------------------------------------- # The XrdSsi plugin @@ -100,16 +100,11 @@ add_library( target_link_libraries( ${LIB_XRD_SSI} + PRIVATE XrdSsiLib XrdUtils XrdServer ) -set_target_properties( - ${LIB_XRD_SSI} - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) - #------------------------------------------------------------------------------- # The XrdSsiLog plugin #------------------------------------------------------------------------------- @@ -121,16 +116,11 @@ add_library( target_link_libraries( ${LIB_XRD_SSILOG} + PRIVATE XrdSsiLib XrdUtils XrdServer ) -set_target_properties( - ${LIB_XRD_SSILOG} - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) - #------------------------------------------------------------------------------- # Install #------------------------------------------------------------------------------- diff --git a/src/XrdTpc.cmake b/src/XrdTpc.cmake index d61711f6d49..a83fcbcd526 100644 --- a/src/XrdTpc.cmake +++ b/src/XrdTpc.cmake @@ -44,6 +44,7 @@ if( BUILD_TPC ) target_link_libraries( ${LIB_XRD_TPC} + PRIVATE XrdServer XrdUtils XrdHttpUtils @@ -60,8 +61,6 @@ if( BUILD_TPC ) set_target_properties( ${LIB_XRD_TPC} PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" LINK_FLAGS "${TPC_LINK_FLAGS}" COMPILE_DEFINITIONS "${XRD_COMPILE_DEFS}") diff --git a/src/XrdUtils.cmake b/src/XrdUtils.cmake index d96338298f0..8c41a1cb3ff 100644 --- a/src/XrdUtils.cmake +++ b/src/XrdUtils.cmake @@ -290,6 +290,7 @@ add_library( target_link_libraries( XrdUtils + PRIVATE OpenSSL::SSL ${CMAKE_THREAD_LIBS_INIT} ${CMAKE_DL_LIBS} @@ -299,7 +300,7 @@ target_link_libraries( if ( SYSTEMD_FOUND ) target_link_libraries( - XrdUtils + XrdUtils PRIVATE ${SYSTEMD_LIBRARIES} ) endif() @@ -309,9 +310,7 @@ set_target_properties( PROPERTIES BUILD_RPATH ${CMAKE_CURRENT_BINARY_DIR} VERSION ${XRD_UTILS_VERSION} - SOVERSION ${XRD_UTILS_SOVERSION} - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) + SOVERSION ${XRD_UTILS_SOVERSION} ) #------------------------------------------------------------------------------- # Install diff --git a/src/XrdVoms.cmake b/src/XrdVoms.cmake index 1951a376eb0..beb4e2e0936 100644 --- a/src/XrdVoms.cmake +++ b/src/XrdVoms.cmake @@ -19,15 +19,10 @@ add_library( target_link_libraries( ${LIB_XRD_VOMS} + PRIVATE XrdCrypto ${VOMS_LIBRARIES} ) -set_target_properties( - ${LIB_XRD_VOMS} - PROPERTIES - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) - #------------------------------------------------------------------------------- # Install #------------------------------------------------------------------------------- diff --git a/src/XrdXml.cmake b/src/XrdXml.cmake index c122ccbd366..d4de72675dd 100644 --- a/src/XrdXml.cmake +++ b/src/XrdXml.cmake @@ -44,6 +44,7 @@ add_library( target_link_libraries( XrdXml + PRIVATE XrdUtils ${TINYXML_LIBRARIES} ${XRDXML2_LIBRARIES} @@ -53,9 +54,7 @@ set_target_properties( XrdXml PROPERTIES VERSION ${XRD_XML_VERSION} - SOVERSION ${XRD_XML_SOVERSION} - INTERFACE_LINK_LIBRARIES "" - LINK_INTERFACE_LIBRARIES "" ) + SOVERSION ${XRD_XML_SOVERSION} ) if ( TINYXML_FOUND ) target_include_directories( XrdXml PRIVATE ${TINYXML_INCLUDE_DIR} ) diff --git a/tests/XrdEcTests/CMakeLists.txt b/tests/XrdEcTests/CMakeLists.txt index df549a0ca5b..eaa8317ddda 100644 --- a/tests/XrdEcTests/CMakeLists.txt +++ b/tests/XrdEcTests/CMakeLists.txt @@ -1,9 +1,4 @@ -include_directories( ${CPPUNIT_INCLUDE_DIRS} ../common ) - -link_directories( ${ISAL_LIBDIR} ) -include_directories( ${ISAL_INCDIR} ) - add_library( XrdEcTests MODULE MicroTest.cc @@ -11,8 +6,12 @@ add_library( target_link_libraries( XrdEcTests + PRIVATE XrdEc ) +target_link_libraries(XrdEcTests PRIVATE ${ISAL_LIBRARIES}) +target_include_directories(XrdEcTests PRIVATE ../common ${CPPUNIT_UNCLUDE_DIRS} ${ISAL_INCLUDE_DIRS}) + #------------------------------------------------------------------------------- # Install #------------------------------------------------------------------------------- From d10748ee3d25a477fe306e3af7031e09628dad58 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 31 May 2023 10:44:54 +0200 Subject: [PATCH 121/442] [CMake] Replace include_directories with target_include_directories --- src/XrdMacaroons.cmake | 5 +++-- src/XrdSciTokens.cmake | 12 +++++++----- src/XrdSeckrb5.cmake | 4 ++-- src/XrdSecztn.cmake | 2 -- src/XrdTpc.cmake | 4 ++-- src/XrdVoms.cmake | 4 ++-- tests/XrdCephTests/CMakeLists.txt | 4 ++-- tests/XrdClTests/CMakeLists.txt | 6 ++++-- 8 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/XrdMacaroons.cmake b/src/XrdMacaroons.cmake index bc2e55ae8f5..edf50395e2c 100644 --- a/src/XrdMacaroons.cmake +++ b/src/XrdMacaroons.cmake @@ -9,8 +9,6 @@ set( LIB_XRD_MACAROONS XrdMacaroons-${PLUGIN_VERSION} ) #------------------------------------------------------------------------------- if( BUILD_MACAROONS ) - include_directories(${MACAROONS_INCLUDES} ${JSON_INCLUDE_DIRS}) - add_library( ${LIB_XRD_MACAROONS} MODULE @@ -32,6 +30,9 @@ if( BUILD_MACAROONS ) OpenSSL::Crypto ${CMAKE_DL_LIBS}) + target_include_directories(${LIB_XRD_MACAROONS} + PRIVATE ${MACAROONS_INCLUDES} ${JSON_INCLUDE_DIRS}) + if( MacOSX ) SET( MACAROONS_LINK_FLAGS "-Wl") else() diff --git a/src/XrdSciTokens.cmake b/src/XrdSciTokens.cmake index 7a079e76854..bdbe124a547 100644 --- a/src/XrdSciTokens.cmake +++ b/src/XrdSciTokens.cmake @@ -6,11 +6,6 @@ find_package( SciTokensCpp REQUIRED ) #------------------------------------------------------------------------------- set( LIB_XRD_SCITOKENS XrdAccSciTokens-${PLUGIN_VERSION} ) -include_directories( - ${SCITOKENS_CPP_INCLUDE_DIR} - XrdSciTokens/vendor/picojson - XrdSciTokens/vendor/inih ) - #------------------------------------------------------------------------------- # The XrdPfc library #------------------------------------------------------------------------------- @@ -28,6 +23,13 @@ target_link_libraries( ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT} ) +target_include_directories( + ${LIB_XRD_SCITOKENS} + PRIVATE + ${SCITOKENS_CPP_INCLUDE_DIR} + XrdSciTokens/vendor/picojson + XrdSciTokens/vendor/inih ) + #------------------------------------------------------------------------------- # Install #------------------------------------------------------------------------------- diff --git a/src/XrdSeckrb5.cmake b/src/XrdSeckrb5.cmake index e04b9b6ecd6..0597c8a6077 100644 --- a/src/XrdSeckrb5.cmake +++ b/src/XrdSeckrb5.cmake @@ -1,5 +1,3 @@ -include_directories( ${KERBEROS5_INCLUDE_DIR} ) - #------------------------------------------------------------------------------- # The XrdSeckrb5 module #------------------------------------------------------------------------------- @@ -16,6 +14,8 @@ target_link_libraries( XrdUtils ${KERBEROS5_LIBRARIES} ) +target_include_directories( ${LIB_XRD_SEC_KRB5} PRIVATE ${KERBEROS5_INCLUDE_DIR} ) + #------------------------------------------------------------------------------- # Install #------------------------------------------------------------------------------- diff --git a/src/XrdSecztn.cmake b/src/XrdSecztn.cmake index e02abc18f45..6da794fcd9b 100644 --- a/src/XrdSecztn.cmake +++ b/src/XrdSecztn.cmake @@ -1,5 +1,3 @@ -#include_directories( ${KERBEROS5_INCLUDE_DIR} ) - #------------------------------------------------------------------------------- # The XrdSecztn module #------------------------------------------------------------------------------- diff --git a/src/XrdTpc.cmake b/src/XrdTpc.cmake index a83fcbcd526..16c1106f06a 100644 --- a/src/XrdTpc.cmake +++ b/src/XrdTpc.cmake @@ -12,8 +12,6 @@ if( BUILD_TPC ) #----------------------------------------------------------------------------- # The XrdHttp library #----------------------------------------------------------------------------- - include_directories( ${CURL_INCLUDE_DIRS} ) - # On newer versions of libcurl, we can use pipelining of requests. include (CheckCSourceCompiles) SET( CMAKE_REQUIRED_INCLUDES "${CURL_INCLUDE_DIRS}" ) @@ -52,6 +50,8 @@ if( BUILD_TPC ) ${CMAKE_THREAD_LIBS_INIT} ${CURL_LIBRARIES} ) + target_include_directories( ${LIB_XRD_TPC} PRIVATE ${CURL_INCLUDE_DIRS} ) + if( MacOSX ) set( TPC_LINK_FLAGS, "-Wl" ) else() diff --git a/src/XrdVoms.cmake b/src/XrdVoms.cmake index beb4e2e0936..822abc7bcb0 100644 --- a/src/XrdVoms.cmake +++ b/src/XrdVoms.cmake @@ -3,8 +3,6 @@ # The XrdSecgsiVOMS library #------------------------------------------------------------------------------- -include_directories( ${VOMS_INCLUDE_DIR} ) - set( LIB_XRD_VOMS XrdVoms-${PLUGIN_VERSION} ) set( LIB_XRD_SEC_GSI_VOMS XrdSecgsiVOMS-${PLUGIN_VERSION} ) set( LIB_XRD_HTTP_VOMS XrdHttpVOMS-${PLUGIN_VERSION} ) @@ -23,6 +21,8 @@ target_link_libraries( XrdCrypto ${VOMS_LIBRARIES} ) +target_include_directories( ${LIB_XRD_VOMS} PRIVATE ${VOMS_INCLUDE_DIR} ) + #------------------------------------------------------------------------------- # Install #------------------------------------------------------------------------------- diff --git a/tests/XrdCephTests/CMakeLists.txt b/tests/XrdCephTests/CMakeLists.txt index 43bbdc072df..ed05bc465d0 100644 --- a/tests/XrdCephTests/CMakeLists.txt +++ b/tests/XrdCephTests/CMakeLists.txt @@ -1,5 +1,3 @@ -include_directories( ${CPPUNIT_INCLUDE_DIRS} ) - add_library( XrdCephTests MODULE CephParsingTest.cc @@ -12,6 +10,8 @@ target_link_libraries( ZLIB::ZLIB XrdCephPosix ) +target_include_directories( XrdCephTests PRIVATE ${CPPUNIT_INCLUDE_DIRS} ) + #------------------------------------------------------------------------------- # Install #------------------------------------------------------------------------------- diff --git a/tests/XrdClTests/CMakeLists.txt b/tests/XrdClTests/CMakeLists.txt index ae2ce0ddf3b..d8094c7cbcf 100644 --- a/tests/XrdClTests/CMakeLists.txt +++ b/tests/XrdClTests/CMakeLists.txt @@ -1,6 +1,4 @@ -include_directories( ${CPPUNIT_INCLUDE_DIRS} ../common) - add_subdirectory( tls ) set( LIB_XRD_CL_TEST_MONITOR XrdClTestMonitor-${PLUGIN_VERSION} ) @@ -34,6 +32,8 @@ target_link_libraries( ZLIB::ZLIB XrdCl ) +target_include_directories( XrdClTests PRIVATE ../common ${CPPUNIT_INCLUDE_DIRS} ) + add_library( ${LIB_XRD_CL_TEST_MONITOR} MODULE MonitorTestLib.cc @@ -44,6 +44,8 @@ target_link_libraries( XrdClTestsHelper XrdCl ) +target_include_directories( ${LIB_XRD_CL_TEST_MONITOR} PRIVATE ../common ) + foreach(TEST_SUITE # File # FileCopy From cafe86dcb26f0287182ef48d04e30e988a607f66 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 11 May 2023 15:14:37 +0200 Subject: [PATCH 122/442] [CI] Use DEBUG log level during CMake configuration --- .github/workflows/build.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7b75431f2d0..2c0d4c6e985 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -70,6 +70,7 @@ jobs: run: | cd .. cmake \ + --log-level=DEBUG \ -DCMAKE_CXX_STANDARD=17 \ -DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DCMAKE_INSTALL_PREFIX=/usr \ @@ -156,6 +157,7 @@ jobs: run: | cd .. cmake \ + --log-level=DEBUG \ -DCMAKE_CXX_STANDARD=17 \ -DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DCMAKE_INSTALL_PREFIX=/usr \ @@ -235,6 +237,7 @@ jobs: # need to fix ownership not to confuse git chown -R -v "$( id -u; ):$( id -g; )" xrootd cmake \ + --log-level=DEBUG \ -DCMAKE_CXX_STANDARD=17 \ -DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DCMAKE_INSTALL_PREFIX=/usr \ @@ -314,6 +317,7 @@ jobs: . /opt/rh/devtoolset-7/enable cd .. cmake3 \ + --log-level=DEBUG \ -DCMAKE_INSTALL_PREFIX=/usr/local/ \ -DPython_EXECUTABLE=$(command -v python3) \ -DENABLE_TESTS=ON \ @@ -387,6 +391,7 @@ jobs: . /opt/rh/devtoolset-7/enable cd .. cmake3 \ + --log-level=DEBUG \ -DCMAKE_INSTALL_PREFIX=/usr/local/ \ -DPython_EXECUTABLE=$(command -v python3) \ -DENABLE_TESTS=ON \ @@ -457,6 +462,7 @@ jobs: . /opt/rh/devtoolset-7/enable cd .. cmake3 \ + --log-level=DEBUG \ -DCMAKE_INSTALL_PREFIX=/usr/ \ -DPython_EXECUTABLE=$(command -v python2) \ -DENABLE_TESTS=ON \ @@ -521,6 +527,7 @@ jobs: run: | cd .. cmake \ + --log-level=DEBUG \ -DCMAKE_INSTALL_PREFIX=/usr \ -DPython_EXECUTABLE=$(command -v python3) \ -DENABLE_TESTS=ON \ @@ -586,6 +593,7 @@ jobs: sudo sed -i -e "s/localhost/localhost $(hostname)/g" /etc/hosts cd .. cmake \ + --log-level=DEBUG \ -DCMAKE_C_COMPILER=clang \ -DCMAKE_CXX_COMPILER=clang++ \ -DCMAKE_INSTALL_PREFIX=/usr/local/ \ From eec2004d50ec83463274853a0e2a5d8646b57588 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 24 May 2023 14:23:09 +0200 Subject: [PATCH 123/442] [RPM] Update makesrpm.sh to not use genversion.sh --- packaging/makesrpm.sh | 71 +++++++++---------------------------------- 1 file changed, 15 insertions(+), 56 deletions(-) diff --git a/packaging/makesrpm.sh b/packaging/makesrpm.sh index 6dbfa6479e3..13ece04aea6 100755 --- a/packaging/makesrpm.sh +++ b/packaging/makesrpm.sh @@ -4,22 +4,6 @@ # Author: Lukasz Janyst (10.03.2011) #------------------------------------------------------------------------------- -RCEXP='^[0-9]+\.[0-9]+\.[0-9]+\-rc.*$' -CERNEXP='^[0-9]+\.[0-9]+\.[0-9]+\-[0-9]+\.CERN.*$' - -#------------------------------------------------------------------------------- -# Find a program -#------------------------------------------------------------------------------- -function findProg() -{ - for prog in $@; do - if test -x "$(command -v $prog 2>/dev/null)"; then - echo $prog - break - fi - done -} - #------------------------------------------------------------------------------- # Print help #------------------------------------------------------------------------------- @@ -39,7 +23,7 @@ function printHelp() #------------------------------------------------------------------------------- # Parse the commandline, if only we could use getopt... :( #------------------------------------------------------------------------------- -SOURCEPATH="../" +SOURCEPATH=$(realpath $(dirname $0)/..) OUTPUTPATH="." PRINTHELP=0 @@ -65,7 +49,7 @@ while test ${#} -ne 0; do echo "--version parameter needs an argument" 1>&2 exit 1 fi - USER_VERSION="--version ${2}" + VERSION="${2}" shift elif test x${1} = x--define; then if test ${#} -lt 2; then @@ -108,12 +92,12 @@ fi #------------------------------------------------------------------------------- # Check if we have all the necassary components #------------------------------------------------------------------------------- -if test x`findProg rpmbuild` = x; then +if ! command -v rpmbuild 2>/dev/null; then echo "[!] Unable to find rpmbuild, aborting..." 1>&2 exit 1 fi -if test x`findProg git` = x; then +if ! command -v git 2>/dev/null; then echo "[!] Unable to find git, aborting..." 1>&2 exit 1 fi @@ -126,15 +110,9 @@ if test ! -d $SOURCEPATH/.git; then exit 2 fi -#------------------------------------------------------------------------------- -# Check the version number -#------------------------------------------------------------------------------- -if test ! -x $SOURCEPATH/genversion.sh; then - echo "[!] Unable to find the genversion script" 1>&2 - exit 3 -fi +: ${VERSION:=$(git --git-dir=$SOURCEPATH/.git describe)} +: ${RELEASE:=1} -VERSION=`$SOURCEPATH/genversion.sh --print-only $USER_VERSION $SOURCEPATH 2>/dev/null` if test $? -ne 0; then echo "[!] Unable to figure out the version number" 1>&2 exit 4 @@ -142,39 +120,20 @@ fi echo "[i] Working with version: $VERSION" -if test x${VERSION:0:1} = x"v"; then - VERSION=${VERSION:1} -fi - #------------------------------------------------------------------------------- -# Deal with release candidates +# Sanitize version to work with RPMs +# https://docs.fedoraproject.org/en-US/packaging-guidelines/Versioning/ #------------------------------------------------------------------------------- -RELEASE=1 -if test x`echo $VERSION | grep -E $RCEXP` != x; then - RELEASE=0.`echo $VERSION | sed 's/.*-rc/rc/'` - VERSION=`echo $VERSION | sed 's/-rc.*//'` -fi -#------------------------------------------------------------------------------- -# Deal with CERN releases -#------------------------------------------------------------------------------- -if test x`echo $VERSION | grep -E $CERNEXP` != x; then - RELEASE=`echo $VERSION | sed 's/.*-//'` - VERSION=`echo $VERSION | sed 's/-.*\.CERN//'` -fi +VERSION=${VERSION#v} # remove "v" prefix +VERSION=${VERSION/-rc/~rc} # release candidates use ~ in RPMs +VERSION=${VERSION/-g/^$(date +%Y%m%d)git} # snapshots use a caret +VERSION=${VERSION/-/.post} # handle git describe for post releases +VERSION=${VERSION//-/.} # replace remaining dashes with dots -#------------------------------------------------------------------------------- -# In case of user version check if the release number has been provided -#------------------------------------------------------------------------------- -if test x"$USER_VERSION" != x; then - TMP=`echo $VERSION | sed 's#.*-##g'` - if test $TMP != $VERSION; then - RELEASE=$TMP - VERSION=`echo $VERSION | sed 's#-[^-]*$##'` - fi -fi +# CentOS 7 cannot handle snapshot versions, filter out +[[ `rpm -E '%{dist}'` =~ el7 ]] && VERSION=${VERSION/^*/} -VERSION=`echo $VERSION | sed 's/-/./g'` echo "[i] RPM compliant version: $VERSION-$RELEASE" #------------------------------------------------------------------------------- From 552a19d184e453aaa01c17179eb6ab228c5434e2 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 31 May 2023 08:50:17 +0200 Subject: [PATCH 124/442] [RPM] Update compat version in spec file --- packaging/rhel/xrootd.spec.in | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packaging/rhel/xrootd.spec.in b/packaging/rhel/xrootd.spec.in index 3ddc5b01fe4..86757efb1ae 100644 --- a/packaging/rhel/xrootd.spec.in +++ b/packaging/rhel/xrootd.spec.in @@ -68,15 +68,17 @@ Group: System Environment/Daemons License: LGPLv3+ URL: http://xrootd.org/ -%define compat_version 4.12.3 +%define compat_version 4.12.9 # git clone http://xrootd.org/repo/xrootd.git xrootd # cd xrootd # git-archive master | gzip -9 > ~/rpmbuild/SOURCES/xrootd.tgz Source0: xrootd.tar.gz +# Need to keep in sync with the compat_version above +# Cannot use variable, as makesrpm.sh cannot expand it %if 0%{?_with_compat} -Source1: xrootd-%{compat_version}.tar.gz +Source1: xrootd-4.12.9.tar.gz %endif BuildRoot: %{_tmppath}/%{name}-root From 408e4cffc97ee80f6741d3298b08819d1e9ab683 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 11 May 2023 10:57:22 +0200 Subject: [PATCH 125/442] Use CMake to detect/set XRootD version and generate XrdVersion.hh --- .gitattributes | 2 +- CMakeLists.txt | 25 +-- VERSION | 1 + VERSION_INFO | 1 - bindings/python/.gitattributes | 1 - bindings/python/.gitignore | 1 - bindings/python/MANIFEST.in | 1 - bindings/python/setup.py.in | 53 ++---- cmake/XRootDVersion.cmake | 77 +++++++++ docker/xrd-docker | 15 +- genversion.sh | 291 +++++++-------------------------- packaging/wheel/MANIFEST.in | 2 +- packaging/wheel/publish.sh | 8 +- packaging/wheel/setup.py | 44 +---- src/CMakeLists.txt | 4 +- src/XrdCeph/genversion.sh | 239 --------------------------- src/XrdCl/CMakeLists.txt | 2 +- src/XrdVersion.hh.in | 7 +- 18 files changed, 172 insertions(+), 602 deletions(-) create mode 100644 VERSION delete mode 100644 VERSION_INFO delete mode 100644 bindings/python/.gitattributes create mode 100644 cmake/XRootDVersion.cmake delete mode 100755 src/XrdCeph/genversion.sh diff --git a/.gitattributes b/.gitattributes index 0893fe6afee..5f72683f8fc 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1 @@ -VERSION_INFO export-subst +VERSION export-subst diff --git a/CMakeLists.txt b/CMakeLists.txt index c3129ebcf09..01f5ca9d5a4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,8 @@ set( CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/src ${PROJECT_SOURCE_DIR}/cmake ) +include(XRootDVersion) + #------------------------------------------------------------------------------- # A 'plugins' phony target to simplify building build-tree binaries. # Plugins are responsible for adding themselves to this target, where @@ -30,29 +32,8 @@ add_definitions( -DXRDPLUGIN_SOVERSION="${PLUGIN_VERSION}" ) #------------------------------------------------------------------------------- # Generate the version header #------------------------------------------------------------------------------- -if (USER_VERSION) - set(XROOTD_VERSION "${USER_VERSION}") -else () -execute_process( - COMMAND ${CMAKE_SOURCE_DIR}/genversion.sh --print-only ${CMAKE_SOURCE_DIR} - OUTPUT_VARIABLE XROOTD_VERSION - OUTPUT_STRIP_TRAILING_WHITESPACE ) -endif() - -add_custom_target( - XrdVersion.hh - ${CMAKE_SOURCE_DIR}/genversion.sh --version ${XROOTD_VERSION} ${CMAKE_SOURCE_DIR}) - -# sigh, yet another ugly hack :( -macro( add_library _target ) - _add_library( ${_target} ${ARGN} ) - add_dependencies( ${_target} XrdVersion.hh ) -endmacro() -macro( add_executable _target ) - _add_executable( ${_target} ${ARGN} ) - add_dependencies( ${_target} XrdVersion.hh ) -endmacro() +configure_file(src/XrdVersion.hh.in src/XrdVersion.hh) #------------------------------------------------------------------------------- # Build in subdirectories diff --git a/VERSION b/VERSION new file mode 100644 index 00000000000..f4034a5d279 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +$Format:%(describe)$ diff --git a/VERSION_INFO b/VERSION_INFO deleted file mode 100644 index 5ac45999939..00000000000 --- a/VERSION_INFO +++ /dev/null @@ -1 +0,0 @@ -$Format:RefNames: %d%nShortHash: %h%nDate: %ai%n$ \ No newline at end of file diff --git a/bindings/python/.gitattributes b/bindings/python/.gitattributes deleted file mode 100644 index 0893fe6afee..00000000000 --- a/bindings/python/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -VERSION_INFO export-subst diff --git a/bindings/python/.gitignore b/bindings/python/.gitignore index 22eb52622ed..a4103cedce0 100755 --- a/bindings/python/.gitignore +++ b/bindings/python/.gitignore @@ -4,4 +4,3 @@ build .cproject *.py[co] MANIFEST -VERSION_INFO diff --git a/bindings/python/MANIFEST.in b/bindings/python/MANIFEST.in index 2110b2157d3..e9c2fda97b8 100644 --- a/bindings/python/MANIFEST.in +++ b/bindings/python/MANIFEST.in @@ -1,5 +1,4 @@ include README.rst -include VERSION_INFO recursive-include tests * recursive-include examples *.py recursive-include docs * diff --git a/bindings/python/setup.py.in b/bindings/python/setup.py.in index 7fb8c53f63d..7d02d07278e 100644 --- a/bindings/python/setup.py.in +++ b/bindings/python/setup.py.in @@ -51,49 +51,16 @@ xrdcllibdir = "${XRDCL_LIBDIR}" xrdlibdir = "${XRD_LIBDIR}" xrdsrcincdir = "${XRD_SRCINCDIR}" xrdbinincdir = "${XRD_BININCDIR}" -version = "${XROOTD_VERSION}" - -if version.startswith('unknown'): - try: - import os - version_file_path = os.path.join('${CMAKE_CURRENT_SOURCE_DIR}', 'VERSION') - print('Version file path: {}'.format(version_file_path)) - with open(version_file_path, 'r') as f: - version = f.read().split('/n')[0] - print('Version from file: {}'.format(version)) - except Exception as e: - print('{} \nCannot open VERSION_INFO file. {} will be used'.format(e, version)) - -# Sanitize in keeping with PEP 440 -# c.f. https://www.python.org/dev/peps/pep-0440/ -# c.f. https://github.com/pypa/pip/issues/8368 -# version needs to pass pip._vendor.packaging.version.Version() -version = version.replace("-", ".") - -if version.startswith("v"): - version = version[1:] - -version_parts = version.split(".") - -# Ensure release candidates sanitized to ..rc -if version_parts[-1].startswith("rc"): - version = ".".join(version_parts[:-1]) + version_parts[-1] - version_parts = version.split(".") - -if len(version_parts[0]) == 8: - # CalVer - date = version_parts[0] - year = date[:4] - incremental = date[4:] - if incremental.startswith("0"): - incremental = incremental[1:] - - version = year + "." + incremental - - if len(version_parts) > 1: - # https://github.com/pypa/pip/issues/9188#issuecomment-736025963 - hash = version_parts[1] - version = version + "+" + hash + +version = "${XRootD_VERSION_STRING}" + +# Sanitize version to conform to PEP 440 +# https://www.python.org/dev/peps/pep-0440 + +version = version.replace('-rc', 'rc') +version = version.replace('-g', '+git.') +version = version.replace('-', '.post', 1) +version = version.replace('-', '.') print('XRootD library dir: ', xrdlibdir) print('XRootD src include dir:', xrdsrcincdir) diff --git a/cmake/XRootDVersion.cmake b/cmake/XRootDVersion.cmake new file mode 100644 index 00000000000..41772f91c54 --- /dev/null +++ b/cmake/XRootDVersion.cmake @@ -0,0 +1,77 @@ +#.rst: +# +# XRootDVersion +# ------------- +# +# This module sets the version of XRootD. +# +# The version is determined in the following order: +# * If a version is set with -DXRootD_VERSION_STRING=x.y.z during configuration, it is used. +# * The version is read from the 'VERSION' file at the top directory of the repository. +# * If the 'VERSION' file has not been expanded, a version is set using git describe. +# * If none of the above worked, a fallback version is set using the current date. +# + +if(NOT DEFINED XRootD_VERSION_STRING) + file(READ "${PROJECT_SOURCE_DIR}/VERSION" XRootD_VERSION_STRING) + string(STRIP ${XRootD_VERSION_STRING} XRootD_VERSION_STRING) +endif() + +if(XRootD_VERSION_STRING MATCHES "Format:" AND IS_DIRECTORY ${PROJECT_SOURCE_DIR}/.git) + find_package(Git QUIET) + if(Git_FOUND) + message(VERBOSE "Determining version with git") + execute_process(COMMAND + ${GIT_EXECUTABLE} --git-dir ${PROJECT_SOURCE_DIR}/.git describe + OUTPUT_VARIABLE XRootD_VERSION_STRING ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + set(XRootD_VERSION_STRING "${XRootD_VERSION_STRING}" CACHE INTERNAL "XRootD Version") + endif() +endif() + +if(XRootD_VERSION_STRING MATCHES "^v?([0-9]+)[.]*([0-9]*)[.]*([0-9]*)[.]*([0-9]*)") + set(XRootD_VERSION_MAJOR ${CMAKE_MATCH_1}) + if(${CMAKE_MATCH_COUNT} GREATER 1) + set(XRootD_VERSION_MINOR ${CMAKE_MATCH_2}) + else() + set(XRootD_VERSION_MINOR 0) + endif() + if(${CMAKE_MATCH_COUNT} GREATER 2) + set(XRootD_VERSION_PATCH ${CMAKE_MATCH_3}) + else() + set(XRootD_VERSION_PATCH 0) + endif() + if(${CMAKE_MATCH_COUNT} GREATER 3) + set(XRootD_VERSION_TWEAK ${CMAKE_MATCH_4}) + else() + set(XRootD_VERSION_TWEAK 0) + endif() + math(EXPR XRootD_VERSION_NUMBER + "10000 * ${XRootD_VERSION_MAJOR} + 100 * ${XRootD_VERSION_MINOR} + ${XRootD_VERSION_PATCH}" + OUTPUT_FORMAT DECIMAL) +else() + message(WARNING "Failed to determine XRootD version, using a timestamp as fallback." + "You can override this by setting -DXRootD_VERSION_STRING=x.y.z during configuration.") + set(XRootD_VERSION_MAJOR 5) + set(XRootD_VERSION_MINOR 6) + set(XRootD_VERSION_PATCH 0) + set(XRootD_VERSION_TWEAK 0) + set(XRootD_VERSION_NUMBER 1000000) + string(TIMESTAMP XRootD_VERSION_STRING + "v${XRootD_VERSION_MAJOR}.${XRootD_VERSION_MINOR}-rc%Y%m%d" UTC) +endif() + +if(XRootD_VERSION_STRING MATCHES "[_-](.*)$") + set(XRootD_VERSION_SUFFIX ${CMAKE_MATCH_1}) +endif() + +string(REGEX MATCH "[0-9]+[.]*[0-9]*[.]*[0-9]*[.]*[0-9]*(-rc)?[0-9]*" + XRootD_VERSION ${XRootD_VERSION_STRING}) + +message(DEBUG "XRootD_VERSION_STRING = '${XRootD_VERSION_STRING}'") +message(DEBUG "XRootD_VERSION_NUMBER = '${XRootD_VERSION_NUMBER}'") +message(DEBUG "XRootD_VERSION_MAJOR = '${XRootD_VERSION_MAJOR}'") +message(DEBUG "XRootD_VERSION_MINOR = '${XRootD_VERSION_MINOR}'") +message(DEBUG "XRootD_VERSION_PATCH = '${XRootD_VERSION_PATCH}'") +message(DEBUG "XRootD_VERSION_TWEAK = '${XRootD_VERSION_TWEAK}'") +message(DEBUG "XRootD_VERSION_SUFFIX = '${XRootD_VERSION_SUFFIX}'") +message(DEBUG "XRootD_VERSION = '${XRootD_VERSION}'") diff --git a/docker/xrd-docker b/docker/xrd-docker index ccbffe7b7c1..0108bcf1343 100755 --- a/docker/xrd-docker +++ b/docker/xrd-docker @@ -60,16 +60,21 @@ fetch() { package() { REPODIR=$(git rev-parse --show-toplevel) - VERSION=$(git describe ${1:-HEAD} | tr '-' '.' | cut -d. -f-4) + VERSION=$(git describe ${1:-HEAD}) + + # sanitize version name to work with RPMs + VERSION=${VERSION#v} # remove "v" prefix + VERSION=${VERSION/-rc/~rc} # release candidates use ~ in RPMs + VERSION=${VERSION/-g/^$(date +%Y%m%d)git} # snapshots use a caret + VERSION=${VERSION/-/.post} # handle git describe for post releases + VERSION=${VERSION//-/.} # replace remaining dashes with dots pushd ${REPODIR} >/dev/null echo Creating tarball for XRootD ${VERSION} - genversion.sh --version ${VERSION} sed -e "s/__VERSION__/${VERSION}/" -e 's/__RELEASE__/1/' \ packaging/rhel/xrootd.spec.in >| xrootd.spec - git archive --prefix=xrootd/src/ --add-file src/XrdVersion.hh \ - --prefix=xrootd/ --add-file xrootd.spec -o xrootd.tar.gz ${1:-HEAD} - rm ${REPODIR}/src/XrdVersion.hh xrootd.spec + git archive --prefix=xrootd/ --add-file xrootd.spec -o xrootd.tar.gz ${1:-HEAD} + rm xrootd.spec popd >/dev/null mv ${REPODIR}/xrootd.tar.gz . } diff --git a/genversion.sh b/genversion.sh index e0c6f7f7200..ce9acb80861 100755 --- a/genversion.sh +++ b/genversion.sh @@ -1,244 +1,63 @@ #!/usr/bin/env bash -#------------------------------------------------------------------------------- -# Process the git decoration expansion and try to derive version number -#------------------------------------------------------------------------------- -EXP1='^v[12][0-9][0-9][0-9][01][0-9][0-3][0-9]-[0-2][0-9][0-5][0-9]$' -EXP2='^v[0-9]+\.[0-9]+\.[0-9]+$' -EXP3='^v[0-9]+\.[0-9]+\.[0-9]+\-rc.*$' - -#------------------------------------------------------------------------------- -# Get the numeric version -#------------------------------------------------------------------------------- -function getNumericVersion() -{ - VERSION=$1 - if test x`echo $VERSION | grep -E $EXP2` == x; then - echo "1000000"; - return; - fi - VERSION=${VERSION/v/} - VERSION=${VERSION//./ } - VERSION=($VERSION) - printf "%d%02d%02d\n" ${VERSION[0]} ${VERSION[1]} ${VERSION[2]} -} - -#------------------------------------------------------------------------------- -# Extract version number from git references -#------------------------------------------------------------------------------- -function getVersionFromRefs() -{ - REFS=${1/RefNames:/} - REFS=${REFS//,/} - REFS=${REFS/(/} - REFS=${REFS/)/} - REFS=($REFS) - - VERSION="unknown" - - for i in ${REFS[@]}; do - if test x`echo $i | grep -E $EXP2` != x; then - echo "$i" - return 0 - fi - - if test x`echo $i | grep -E $EXP1` != x; then - VERSION="$i" - fi - - if test x`echo $i | grep -E $EXP3` != x; then - VERSION="$i" - fi - - done - echo $VERSION - return 0 -} - -#------------------------------------------------------------------------------- -# Generate the version string from the date and the hash -#------------------------------------------------------------------------------- -function getVersionFromLog() -{ - AWK=gawk - EX="$(command -v gawk)" - if test x"${EX}" == x -o ! -x "${EX}"; then - AWK=awk - fi - - VERSION="`echo $@ | $AWK '{ gsub("-","",$1); print $1"-"$4; }'`" - if test $? -ne 0; then - echo "unknown"; - return 1 - fi - echo v$VERSION -} - -#------------------------------------------------------------------------------- -# Print help -#------------------------------------------------------------------------------- -function printHelp() +# This script no longer generates XrdVersion.hh, but just +# prints the version using the same strategy as in the module +# XRootDVersion.cmake. The script will first try to use a custom +# version set with the option --version or via the environment +# variable XRDVERSION, then read the VERSION file and if that is +# not expanded by git, git describe is used. If a bad version is +# set for any reason, a fallback version is used based on a date. + +function usage() { - echo "Usage:" 1>&2 - echo "${0} [--help|--print-only|--version] [SOURCEPATH]" 1>&2 - echo " --help prints this message" 1>&2 - echo " --print-only prints the version to stdout and quits" 1>&2 - echo " --version VERSION sets the version manually" 1>&2 + cat 1>&2 <<-EOF + Usage: + $(basename $0) [--help|--version] + + --help prints this message + --print-only ignored, used for backward compatibility + --version VERSION sets a custom version + EOF } -#------------------------------------------------------------------------------- -# Check the parameters -#------------------------------------------------------------------------------- -while test ${#} -ne 0; do - if test x${1} = x--help; then - PRINTHELP=1 - elif test x${1} = x--print-only; then - PRINTONLY=1 - elif test x${1} = x--version; then - if test ${#} -lt 2; then - echo "--version parameter needs an argument" 1>&2 - exit 1 - fi - USER_VERSION=${2} - shift - else - SOURCEPATH=${1} - fi - shift -done +SRC=$(dirname $0) +VF=${SRC}/VERSION -if test x$PRINTHELP != x; then - printHelp ${0} - exit 0 -fi - -if test x$SOURCEPATH != x; then - SOURCEPATH=${SOURCEPATH}/ - if test ! -d $SOURCEPATH; then - echo "The given source path does not exist: ${SOURCEPATH}" 1>&2 - exit 1 - fi -fi - -VERSION="unknown" - -#------------------------------------------------------------------------------- -# We're not inside a git repo -#------------------------------------------------------------------------------- -if test ! -d ${SOURCEPATH}.git; then - #----------------------------------------------------------------------------- - # We cannot figure out what version we are - #---------------------------------------------------------------------------- - echo "[I] No git repository info found. Trying to interpret VERSION_INFO" 1>&2 - if test -f src/XrdVersion.hh; then - echo "[I] The XrdVersion.hh file already exists" 1>&2 - exit 0 - elif test ! -r ${SOURCEPATH}VERSION_INFO; then - echo "[!] VERSION_INFO file absent. Unable to determine the version. Using \"unknown\"" 1>&2 - elif test x"`grep Format ${SOURCEPATH}VERSION_INFO`" != x; then - echo "[!] VERSION_INFO file invalid. Unable to determine the version. Using \"unknown\"" 1>&2 - elif test x$USER_VERSION != x; then - echo "[I] Using the user supplied version: ${USER_VERSION}" 1>&2 - VERSION=${USER_VERSION} - #----------------------------------------------------------------------------- - # The version file exists and seems to be valid so we know the version - #---------------------------------------------------------------------------- - else - REFNAMES="`grep RefNames ${SOURCEPATH}VERSION_INFO`" - VERSION="`getVersionFromRefs "$REFNAMES"`" - if test x$VERSION == xunknown; then - SHORTHASH="`grep ShortHash ${SOURCEPATH}VERSION_INFO`" - SHORTHASH=${SHORTHASH/ShortHash:/} - SHORTHASH=${SHORTHASH// /} - DATE="`grep Date ${SOURCEPATH}VERSION_INFO`" - DATE=${DATE/Date:/} - VERSION="`getVersionFromLog $DATE $SHORTHASH`" - fi - fi - -#------------------------------------------------------------------------------- -# Check if the version has been specified by the user -#------------------------------------------------------------------------------- -elif test x$USER_VERSION != x; then - VERSION=$USER_VERSION - -#------------------------------------------------------------------------------- -# We're in a git repo so we can try to determine the version using that -#------------------------------------------------------------------------------- +if [[ -n "${XRDVERSION}" ]]; then + VERSION=${XRDVERSION}; +elif [[ -r "${VF}" ]] && grep -vq "Format:" "${VF}"; then + VERSION=$(sed -e 's/-g/+g/' "${VF}") +elif git -C ${SRC} describe >/dev/null 2>&1; then + VERSION=$(git -C ${SRC} describe | sed -e 's/-g/+g/') else - echo "[I] Determining version from git" 1>&2 - EX="$(command -v git)" - if test x"${EX}" == x -o ! -x "${EX}"; then - echo "[!] Unable to find git in the path: setting the version tag to unknown" 1>&2 - else - #--------------------------------------------------------------------------- - # Sanity check - #--------------------------------------------------------------------------- - CURRENTDIR=$PWD - if [ x${SOURCEPATH} != x ]; then - cd ${SOURCEPATH} - fi - git log -1 >/dev/null 2>&1 - if test $? -ne 0; then - echo "[!] Error while generating src/XrdVersion.hh, the git repository may be corrupted" 1>&2 - echo "[!] Setting the version tag to unknown" 1>&2 - else - #------------------------------------------------------------------------- - # Can we match the exact annotated tag? - #------------------------------------------------------------------------- - git describe --abbrev=0 --exact-match >/dev/null 2>&1 - - if test ${?} -eq 0; then - VERSION=$(git describe --abbrev=0 --exact-match) - else - VERSION=$(git describe --abbrev=0) - # Append .postN with N equal to number of commits since last tag - VERSION="${VERSION}.post$(git rev-list ${VERSION}.. | wc -l)" - fi - fi - cd $CURRENTDIR - fi -fi - -#------------------------------------------------------------------------------- -# Make sure the version string is not longer than 25 characters -#------------------------------------------------------------------------------- -if [ ${#VERSION} -gt 25 ] && [ x$USER_VERSION == x ] ; then - VERSION="${VERSION:0:19}...${VERSION: -3}" -fi - -#------------------------------------------------------------------------------- -# Print the version info and exit if necassary -#------------------------------------------------------------------------------- -if test x$PRINTONLY != x; then - echo $VERSION - exit 0 -fi - -#------------------------------------------------------------------------------- -# Create XrdVersion.hh -#------------------------------------------------------------------------------- -NUMVERSION=`getNumericVersion $VERSION` - -if test ! -r ${SOURCEPATH}src/XrdVersion.hh.in; then - echo "[!] Unable to find src/XrdVersion.hh.in" 1>&2 - exit 1 -fi - -sed -e "s/#define XrdVERSION \"unknown\"/#define XrdVERSION \"$VERSION\"/" ${SOURCEPATH}src/XrdVersion.hh.in | \ -sed -e "s/#define XrdVNUMBER 1000000/#define XrdVNUMBER $NUMVERSION/" \ -> src/XrdVersion.hh.new - -if test $? -ne 0; then - echo "[!] Error while generating src/XrdVersion.hh from the input template" 1>&2 - exit 1 -fi + VERSION="v5.6-rc$(date +%Y%m%d)" +fi + +while [[ $# -gt 0 ]]; do + case $1 in + --help) + usage + exit 0 + ;; + + --print-only) + shift + ;; + + --version) + shift + if [[ $# == 0 ]]; then + echo "error: --version parameter needs an argument" 1>&2 + fi + VERSION=$1 + shift + ;; + + *) + echo "warning: unknown option: $1" 1>&2 + shift + ;; + esac +done -if test ! -e src/XrdVersion.hh; then - mv src/XrdVersion.hh.new src/XrdVersion.hh -elif test x"`diff src/XrdVersion.hh.new src/XrdVersion.hh`" != x; then - mv src/XrdVersion.hh.new src/XrdVersion.hh -else - rm src/XrdVersion.hh.new -fi -echo "[I] src/XrdVersion.hh successfully generated" 1>&2 +printf "${VERSION}" diff --git a/packaging/wheel/MANIFEST.in b/packaging/wheel/MANIFEST.in index d89d3e218a1..c8c539facbd 100644 --- a/packaging/wheel/MANIFEST.in +++ b/packaging/wheel/MANIFEST.in @@ -1,5 +1,5 @@ include *.sh *.py *.in -include CMakeLists.txt VERSION_INFO README COPYING* LICENSE +include CMakeLists.txt VERSION README COPYING* LICENSE recursive-include bindings * recursive-include cmake * diff --git a/packaging/wheel/publish.sh b/packaging/wheel/publish.sh index ddcb335157d..72c26260bee 100755 --- a/packaging/wheel/publish.sh +++ b/packaging/wheel/publish.sh @@ -1,10 +1,8 @@ #!/bin/bash -./genversion.sh -version=$(./genversion.sh --print-only) -version=${version#v} -echo $version > bindings/python/VERSION -rm -r dist +./genversion.sh >| VERSION + +[[ -d dist ]] && rm -rf dist # Determine if wheel.bdist_wheel is available for wheel.bdist_wheel in setup.py python3 -c 'import wheel' &> /dev/null diff --git a/packaging/wheel/setup.py b/packaging/wheel/setup.py index 179b57a9aec..29be651cee2 100644 --- a/packaging/wheel/setup.py +++ b/packaging/wheel/setup.py @@ -14,45 +14,13 @@ import sys def get_version(): - version = subprocess.check_output(['./genversion.sh', '--print-only']).decode() - - # Sanitize in keeping with PEP 440 - # c.f. https://www.python.org/dev/peps/pep-0440/ - # c.f. https://github.com/pypa/pip/issues/8368 - # version needs to pass pip._vendor.packaging.version.Version() - version = version.replace("-", ".") - - if version.startswith("v"): - version = version[1:] - - version_parts = version.split(".") - - # Ensure release candidates sanitized to ..rc - if version_parts[-1].startswith("rc"): - version = ".".join(version_parts[:-1]) + version_parts[-1] - version_parts = version.split(".") - - # Assume SemVer as default case - if len(version_parts[0]) == 8: - # CalVer - date = version_parts[0] - year = date[:4] - incremental = date[4:] - if incremental.startswith("0"): - incremental = incremental[1:] - - version = year + "." + incremental - - return version - -def get_version_from_file(): try: - with open('./bindings/python/VERSION') as f: - version = f.read().split('/n')[0] - return version + version = open('VERSION').read().strip() except: - print('Failed to get version from file. Using unknown') - return 'unknown' + from datetime import date + version = 'v5.6-rc' + date.today().strftime("%Y%m%d") + + return version def binary_exists(name): """Check whether `name` is on PATH.""" @@ -199,7 +167,7 @@ class CustomWheelGen(bdist_wheel): def run(self): pass -version = get_version_from_file() +version = get_version() setup_requires=[ 'pkgconfig' ] setup( diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fd1cde70dd6..53bc84bee8c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -108,7 +108,7 @@ install( CODE " EXECUTE_PROCESS( COMMAND cat ${CMAKE_SOURCE_DIR}/utils/xrootd-config - COMMAND sed -e \"s/__VERSION__/${XROOTD_VERSION}/\" + COMMAND sed -e \"s/__VERSION__/${XRootD_VERSION}/\" COMMAND sed -e \"s|__INCLUDEDIR__|${CMAKE_INSTALL_INCLUDEDIR}|\" COMMAND sed -e \"s/__PLUGIN_VERSION__/${PLUGIN_VERSION}/\" COMMAND sed -e \"s|__PREFIX__|${CMAKE_INSTALL_PREFIX}|\" @@ -128,7 +128,7 @@ install( MESSAGE( \"-- Processing: \" \${MANPAGE} ) EXECUTE_PROCESS( COMMAND cat \${MANPAGE} - COMMAND sed -e \"s/__VERSION__/${XROOTD_VERSION}/\" + COMMAND sed -e \"s/__VERSION__/${XRootD_VERSION}/\" OUTPUT_FILE \${MANPAGE}.new ) EXECUTE_PROCESS( COMMAND mv -f \${MANPAGE}.new \${MANPAGE} ) diff --git a/src/XrdCeph/genversion.sh b/src/XrdCeph/genversion.sh deleted file mode 100755 index 81008e77822..00000000000 --- a/src/XrdCeph/genversion.sh +++ /dev/null @@ -1,239 +0,0 @@ -#!/bin/bash - -#------------------------------------------------------------------------------- -# Process the git decoration expansion and try to derive version number -#------------------------------------------------------------------------------- -EXP1='^v[12][0-9][0-9][0-9][01][0-9][0-3][0-9]-[0-2][0-9][0-5][0-9]$' -EXP2='^v[0-9]+\.[0-9]+\.[0-9]+$' -EXP3='^v[0-9]+\.[0-9]+\.[0-9]+\-rc.*$' - -#------------------------------------------------------------------------------- -# Get the numeric version -#------------------------------------------------------------------------------- -function getNumericVersion() -{ - VERSION=$1 - if test x`echo $VERSION | egrep $EXP2` == x; then - echo "1000000"; - return; - fi - VERSION=${VERSION/v/} - VERSION=${VERSION//./ } - VERSION=($VERSION) - printf "%d%02d%02d\n" ${VERSION[0]} ${VERSION[1]} ${VERSION[2]} -} - -#------------------------------------------------------------------------------- -# Extract version number from git references -#------------------------------------------------------------------------------- -function getVersionFromRefs() -{ - REFS=${1/RefNames:/} - REFS=${REFS//,/} - REFS=${REFS/(/} - REFS=${REFS/)/} - REFS=($REFS) - - VERSION="unknown" - - for i in ${REFS[@]}; do - if test x`echo $i | egrep $EXP2` != x; then - echo "$i" - return 0 - fi - - if test x`echo $i | egrep $EXP1` != x; then - VERSION="$i" - fi - - if test x`echo $i | egrep $EXP3` != x; then - VERSION="$i" - fi - - done - echo $VERSION - return 0 -} - -#------------------------------------------------------------------------------- -# Generate the version string from the date and the hash -#------------------------------------------------------------------------------- -function getVersionFromLog() -{ - AWK=gawk - EX="`which gawk`" - if test x"${EX}" == x -o ! -x "${EX}"; then - AWK=awk - fi - - VERSION="`echo $@ | $AWK '{ gsub("-","",$1); print $1"-"$4; }'`" - if test $? -ne 0; then - echo "unknown"; - return 1 - fi - echo v$VERSION -} - -#------------------------------------------------------------------------------- -# Print help -#------------------------------------------------------------------------------- -function printHelp() -{ - echo "Usage:" 1>&2 - echo "${0} [--help|--print-only|--version] [SOURCEPATH]" 1>&2 - echo " --help prints this message" 1>&2 - echo " --print-only prints the version to stdout and quits" 1>&2 - echo " --version VERSION sets the version manually" 1>&2 -} - -#------------------------------------------------------------------------------- -# Check the parameters -#------------------------------------------------------------------------------- -while test ${#} -ne 0; do - if test x${1} = x--help; then - PRINTHELP=1 - elif test x${1} = x--print-only; then - PRINTONLY=1 - elif test x${1} = x--version; then - if test ${#} -lt 2; then - echo "--version parameter needs an argument" 1>&2 - exit 1 - fi - USER_VERSION=${2} - shift - else - SOURCEPATH=${1} - fi - shift -done - -if test x$PRINTHELP != x; then - printHelp ${0} - exit 0 -fi - -if test x$SOURCEPATH != x; then - SOURCEPATH=${SOURCEPATH}/ - if test ! -d $SOURCEPATH; then - echo "The given source path does not exist: ${SOURCEPATH}" 1>&2 - exit 1 - fi -fi - -VERSION="unknown" - -#------------------------------------------------------------------------------- -# We're not inside a git repo -#------------------------------------------------------------------------------- -if test ! -d ${SOURCEPATH}.git; then - #----------------------------------------------------------------------------- - # We cannot figure out what version we are - #---------------------------------------------------------------------------- - echo "[I] No git repository info found. Trying to interpret VERSION_INFO" 1>&2 - if test ! -r ${SOURCEPATH}VERSION_INFO; then - echo "[!] VERSION_INFO file absent. Unable to determine the version. Using \"unknown\"" 1>&2 - elif test x"`grep Format ${SOURCEPATH}VERSION_INFO`" != x; then - echo "[!] VERSION_INFO file invalid. Unable to determine the version. Using \"unknown\"" 1>&2 - - #----------------------------------------------------------------------------- - # The version file exists and seems to be valid so we know the version - #---------------------------------------------------------------------------- - else - REFNAMES="`grep RefNames ${SOURCEPATH}VERSION_INFO`" - VERSION="`getVersionFromRefs "$REFNAMES"`" - if test x$VERSION == xunknown; then - SHORTHASH="`grep ShortHash ${SOURCEPATH}VERSION_INFO`" - SHORTHASH=${SHORTHASH/ShortHash:/} - SHORTHASH=${SHORTHASH// /} - DATE="`grep Date ${SOURCEPATH}VERSION_INFO`" - DATE=${DATE/Date:/} - VERSION="`getVersionFromLog $DATE $SHORTHASH`" - fi - fi - -#------------------------------------------------------------------------------- -# Check if the version has been specified by the user -#------------------------------------------------------------------------------- -elif test x$USER_VERSION != x; then - VERSION=$USER_VERSION - -#------------------------------------------------------------------------------- -# We're in a git repo so we can try to determine the version using that -#------------------------------------------------------------------------------- -else - echo "[I] Determining version from git" 1>&2 - EX="`which git`" - if test x"${EX}" == x -o ! -x "${EX}"; then - echo "[!] Unable to find git in the path: setting the version tag to unknown" 1>&2 - else - #--------------------------------------------------------------------------- - # Sanity check - #--------------------------------------------------------------------------- - CURRENTDIR=$PWD - if [ x${SOURCEPATH} != x ]; then - cd ${SOURCEPATH} - fi - git log -1 >/dev/null 2>&1 - if test $? -ne 0; then - echo "[!] Error while generating src/XrdVersion.hh, the git repository may be corrupted" 1>&2 - echo "[!] Setting the version tag to unknown" 1>&2 - else - #------------------------------------------------------------------------- - # Can we match the exact tag? - #------------------------------------------------------------------------- - git describe --tags --abbrev=0 --exact-match >/dev/null 2>&1 - if test ${?} -eq 0; then - VERSION="`git describe --tags --abbrev=0 --exact-match`" - else - LOGINFO="`git log -1 --format='%ai %h'`" - if test ${?} -eq 0; then - VERSION="`getVersionFromLog $LOGINFO`" - fi - fi - fi - cd $CURRENTDIR - fi -fi - -#------------------------------------------------------------------------------- -# Make sure the version string is not longer than 25 characters -#------------------------------------------------------------------------------- -if [ ${#VERSION} -gt 25 ] && [ x$USER_VERSION == x ] ; then - VERSION="${VERSION:0:19}...${VERSION: -3}" -fi - -#------------------------------------------------------------------------------- -# Print the version info and exit if necassary -#------------------------------------------------------------------------------- -if test x$PRINTONLY != x; then - echo $VERSION - exit 0 -fi - -#------------------------------------------------------------------------------- -# Create XrdVersion.hh -#------------------------------------------------------------------------------- -NUMVERSION=`getNumericVersion $VERSION` - -if test ! -r ${SOURCEPATH}src/XrdVersion.hh.in; then - echo "[!] Unable to find src/XrdVersion.hh.in" 1>&2 - exit 1 -fi - -sed -e "s/#define XrdVERSION \"unknown\"/#define XrdVERSION \"$VERSION\"/" ${SOURCEPATH}src/XrdVersion.hh.in | \ -sed -e "s/#define XrdVNUMBER 1000000/#define XrdVNUMBER $NUMVERSION/" \ -> src/XrdVersion.hh.new - -if test $? -ne 0; then - echo "[!] Error while generating src/XrdVersion.hh from the input template" 1>&2 - exit 1 -fi - -if test ! -e src/XrdVersion.hh; then - mv src/XrdVersion.hh.new src/XrdVersion.hh -elif test x"`diff src/XrdVersion.hh.new src/XrdVersion.hh`" != x; then - mv src/XrdVersion.hh.new src/XrdVersion.hh -else - rm src/XrdVersion.hh.new -fi -echo "[I] src/XrdVersion.hh successfully generated" 1>&2 diff --git a/src/XrdCl/CMakeLists.txt b/src/XrdCl/CMakeLists.txt index bac9e51bde1..2a711d19333 100644 --- a/src/XrdCl/CMakeLists.txt +++ b/src/XrdCl/CMakeLists.txt @@ -251,7 +251,7 @@ install( MESSAGE( \"-- Processing: \" \$ENV{DESTDIR}/${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_MANDIR}/man1/\${MANPAGE} ) EXECUTE_PROCESS( COMMAND cat \${MANPAGE} - COMMAND sed -e \"s/__VERSION__/${XROOTD_VERSION}/\" + COMMAND sed -e \"s/__VERSION__/${XRootD_VERSION}/\" OUTPUT_FILE \${MANPAGE}.new WORKING_DIRECTORY \$ENV{DESTDIR}/${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_MANDIR}/man1 ) EXECUTE_PROCESS( diff --git a/src/XrdVersion.hh.in b/src/XrdVersion.hh.in index b764bdbbf9d..5bd0236443f 100644 --- a/src/XrdVersion.hh.in +++ b/src/XrdVersion.hh.in @@ -25,20 +25,17 @@ /* specific prior written permission of the institution or contributor. */ /******************************************************************************/ -// this file is automatically updated by the genversion.sh script -// if you touch anything make sure that it still works - #ifndef __XRD_VERSION_H__ #define __XRD_VERSION_H__ -#define XrdVERSION "unknown" +#define XrdVERSION "@XRootD_VERSION_STRING@" // Numeric representation of the version tag // The format for the released code is: xyyzz, where: x is the major version, // y is the minor version and zz is the bugfix revision number // For the non-released code the value is 1000000 #define XrdVNUMUNK 1000000 -#define XrdVNUMBER 1000000 +#define XrdVNUMBER @XRootD_VERSION_NUMBER@ #if XrdDEBUG #define XrdVSTRING XrdVERSION "_dbg" From 69370d640fca5bad0269f0a4be25c52f54712db4 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 5 Jun 2023 11:13:41 +0200 Subject: [PATCH 126/442] [CI] Enable devtoolset-7 on CentOS 7 --- .github/workflows/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2c0d4c6e985..d8980ac8bcd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -864,6 +864,7 @@ jobs: - name: Build sdist using publishing workflow run: | + . /opt/rh/devtoolset-7/enable cp packaging/wheel/* . ./publish.sh python3 -m pip --verbose install --upgrade ./dist/xrootd-*.tar.gz @@ -921,6 +922,7 @@ jobs: - name: Build sdist using publishing workflow run: | + . /opt/rh/devtoolset-7/enable cp packaging/wheel/* . ./publish.sh python3 -m pip --verbose install --upgrade ./dist/xrootd-*.tar.gz From 4df2c7343d597a51f2623cce8b9c11a72a0070a7 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 5 Jun 2023 11:09:45 +0200 Subject: [PATCH 127/442] [CMake] Enforce C++14 being available --- cmake/XRootDOSDefs.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/cmake/XRootDOSDefs.cmake b/cmake/XRootDOSDefs.cmake index 3d4ae4abc73..fdd0461e939 100644 --- a/cmake/XRootDOSDefs.cmake +++ b/cmake/XRootDOSDefs.cmake @@ -19,6 +19,7 @@ define_default( LIBRARY_PATH_PREFIX "lib" ) # Enable c++14 #------------------------------------------------------------------------------- set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED TRUE) if( ENABLE_ASAN ) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address") From 2401509d451306a14926159c8c06287fea946bce Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 1 Jun 2023 16:16:29 +0200 Subject: [PATCH 128/442] [Python] Modernize build system This is a rewrite of the packaging of the Python bindings. The new packaging supports building the Python bindings both as part of a standard CMake build, as well as against a previously installed version of XRootD without the Python bindings. A new setup.py at the top level has been created to replace the old one from packaging/wheel. It can be used to drive the main CMake build using pip to create source and binary distributions of XRootD. Closes: #1768, #1807 #1833, #1844, #2001, #2002. --- .gitignore | 1 - packaging/wheel/MANIFEST.in => MANIFEST.in | 6 +- bindings/python/CMakeLists.txt | 114 ++----------- bindings/python/MANIFEST.in | 2 +- bindings/python/README | 6 + bindings/python/README.rst | 16 -- bindings/python/VERSION | 1 + bindings/python/pyproject.toml | 1 + bindings/python/setup.py | 147 ++++++++++++++++ bindings/python/setup.py.in | 93 ---------- bindings/python/src/CMakeLists.txt | 58 +++++++ cmake/XRootDDefaults.cmake | 2 - packaging/debian/rules | 6 +- packaging/rhel/xrootd.spec.in | 3 +- packaging/wheel/TestCXX14.txt | 95 ----------- packaging/wheel/has_c++14.sh | 12 -- packaging/wheel/install.sh | 61 ------- packaging/wheel/setup.py | 188 --------------------- pyproject.toml | 3 + setup.py | 120 +++++++++++++ 20 files changed, 362 insertions(+), 573 deletions(-) rename packaging/wheel/MANIFEST.in => MANIFEST.in (76%) create mode 100644 bindings/python/README delete mode 100644 bindings/python/README.rst create mode 120000 bindings/python/VERSION create mode 120000 bindings/python/pyproject.toml create mode 100644 bindings/python/setup.py delete mode 100644 bindings/python/setup.py.in create mode 100644 bindings/python/src/CMakeLists.txt delete mode 100644 packaging/wheel/TestCXX14.txt delete mode 100755 packaging/wheel/has_c++14.sh delete mode 100755 packaging/wheel/install.sh delete mode 100644 packaging/wheel/setup.py create mode 100644 pyproject.toml create mode 100644 setup.py diff --git a/.gitignore b/.gitignore index 6959934d9b6..adfe0558ac7 100644 --- a/.gitignore +++ b/.gitignore @@ -59,4 +59,3 @@ test/testconfig.sh xrootd.spec dist *.egg-info -bindings/python/VERSION diff --git a/packaging/wheel/MANIFEST.in b/MANIFEST.in similarity index 76% rename from packaging/wheel/MANIFEST.in rename to MANIFEST.in index c8c539facbd..c39b67a5b56 100644 --- a/packaging/wheel/MANIFEST.in +++ b/MANIFEST.in @@ -1,11 +1,13 @@ include *.sh *.py *.in -include CMakeLists.txt VERSION README COPYING* LICENSE +include CMakeLists.txt +include COPYING* LICENSE +include VERSION README recursive-include bindings * recursive-include cmake * +recursive-include docs * recursive-include packaging * recursive-include src * recursive-include tests * recursive-include ups * recursive-include utils * -recursive-include docs * diff --git a/bindings/python/CMakeLists.txt b/bindings/python/CMakeLists.txt index 2c5168475f4..d29b35cc9f6 100644 --- a/bindings/python/CMakeLists.txt +++ b/bindings/python/CMakeLists.txt @@ -1,109 +1,27 @@ +cmake_minimum_required(VERSION 3.16...3.25) -set(SETUP_PY_IN "${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in") -set(SETUP_PY "${CMAKE_CURRENT_BINARY_DIR}/setup.py") -set(DEPS "${CMAKE_CURRENT_SOURCE_DIR}/libs/__init__.py") -set(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/python_bindings") -set(XRD_SRCINCDIR "${CMAKE_SOURCE_DIR}/src") -set(XRD_BININCDIR "${CMAKE_BINARY_DIR}/src") -set(XRDCL_LIBDIR "${CMAKE_BINARY_DIR}/src/XrdCl") -set(XRD_LIBDIR "${CMAKE_BINARY_DIR}/src") -set(XRDCL_INSTALL "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") +project(PyXRootD LANGUAGES CXX) -if( PYPI_BUILD ) - set(XRDCL_RPATH "$ORIGIN/${CMAKE_INSTALL_LIBDIR}") -else() - set(XRDCL_RPATH "$ORIGIN/../../..") -endif() - -if( "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" ) - if( CMAKE_CXX_COMPILER_VERSION VERSION_LESS 3.7 ) - message( "clang 3.5" ) - set( CLANG_PROHIBITED ", '-Wp,-D_FORTIFY_SOURCE=2', '-fstack-protector-strong'" ) - endif() - if( ( CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 6.0 ) OR ( CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.0 ) ) - message( "clang 6.0" ) - set( CLANG_PROHIBITED ", '-fcf-protection'" ) - endif() - if( CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 7.0 ) - message( "clang > 7.0" ) - set( CLANG_PROHIBITED ", '-fstack-clash-protection'" ) - endif() -endif() - -configure_file(${SETUP_PY_IN} ${SETUP_PY}) +find_package(Python REQUIRED COMPONENTS Interpreter Development) -string(FIND "${PIP_OPTIONS}" "--prefix" PIP_OPTIONS_PREFIX_POSITION) -if( "${PIP_OPTIONS_PREFIX_POSITION}" EQUAL "-1" ) - string(APPEND PIP_OPTIONS " --prefix \$ENV{DESTDIR}/${CMAKE_INSTALL_PREFIX}") +if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR OR PYPI_BUILD) + add_subdirectory(src) else() - message(WARNING - " The pip option --prefix has been set in '${PIP_OPTIONS}' which will change" - " it from its default value of '--prefix \$ENV{DESTDIR}/${CMAKE_INSTALL_PREFIX}'." - " Make sure this is intentional and that you understand the effects." - ) -endif() - -# Check it the Python interpreter has a valid version of pip -execute_process( - COMMAND ${Python_EXECUTABLE} -m pip --version - RESULT_VARIABLE VALID_PIP_EXIT_CODE - OUTPUT_QUIET -) - -if ( NOT ${VALID_PIP_EXIT_CODE} EQUAL 0 ) - # Attempt to still install with deprecated invocation of setup.py - message(WARNING - " ${Python_EXECUTABLE} does not have a valid pip associated with it." - " It is recommended that you install a version of pip to install Python" - " packages and bindings. If you are unable to install a version of pip" - " through a package manager or with your Python build try using the PyPA's" - " get-pip.py bootstrapping script ( https://github.com/pypa/get-pip ).\n" - " The installation of the Python bindings will attempt to continue using" - " the deprecated method of `${Python_EXECUTABLE} setup.py install`." - ) - - # https://docs.python.org/3/install/#splitting-the-job-up - add_custom_command(OUTPUT ${OUTPUT} - COMMAND ${Python_EXECUTABLE} ${SETUP_PY} --verbose build - DEPENDS ${DEPS}) + configure_file(setup.py setup.py) + file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/VERSION "${XRootD_VERSION_STRING}") - add_custom_target(python_target ALL DEPENDS ${OUTPUT} XrdCl) + option(INSTALL_PYTHON_BINDINGS "Install Python bindings" TRUE) - # Get the distribution name on Debian families - execute_process( COMMAND grep -i ^NAME= /etc/os-release - OUTPUT_VARIABLE DEB_DISTRO ) - STRING(REGEX REPLACE "^NAME=\"" "" DEB_DISTRO "${DEB_DISTRO}") - STRING(REGEX REPLACE "\".*" "" DEB_DISTRO "${DEB_DISTRO}") + if(INSTALL_PYTHON_BINDINGS) + set(PIP_OPTIONS "" CACHE STRING "Install options for pip") - if( DEB_DISTRO STREQUAL "Debian" OR DEB_DISTRO STREQUAL "Ubuntu" ) - set(PYTHON_LAYOUT "unix" CACHE STRING "Python installation layout (deb or unix)") - set(DEB_INSTALL_ARGS "--install-layout ${PYTHON_LAYOUT}") - endif() - - install( - CODE - "EXECUTE_PROCESS( - RESULT_VARIABLE INSTALL_STATUS - COMMAND /usr/bin/env ${XROOTD_PYBUILD_ENV} ${Python_EXECUTABLE} ${SETUP_PY} install \ - --verbose \ - --prefix \$ENV{DESTDIR}/${CMAKE_INSTALL_PREFIX} \ - ${DEB_INSTALL_ARGS} - ) - if(NOT INSTALL_STATUS EQUAL 0) - message(FATAL_ERROR \"Failed to install Python bindings\") - endif() - ") -else() - install( - CODE - "EXECUTE_PROCESS( - RESULT_VARIABLE INSTALL_STATUS - COMMAND /usr/bin/env ${XROOTD_PYBUILD_ENV} ${Python_EXECUTABLE} -m pip install \ - ${PIP_OPTIONS} \ - ${CMAKE_CURRENT_BINARY_DIR} - ) + install(CODE " + execute_process(COMMAND ${Python_EXECUTABLE} -m pip install ${PIP_OPTIONS} + --prefix \$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX} ${CMAKE_CURRENT_BINARY_DIR} + RESULT_VARIABLE INSTALL_STATUS) if(NOT INSTALL_STATUS EQUAL 0) message(FATAL_ERROR \"Failed to install Python bindings\") endif() - ") + ") + endif() endif() diff --git a/bindings/python/MANIFEST.in b/bindings/python/MANIFEST.in index e9c2fda97b8..2dd8b3ebfb0 100644 --- a/bindings/python/MANIFEST.in +++ b/bindings/python/MANIFEST.in @@ -1,4 +1,4 @@ -include README.rst +include CMakeLists.txt recursive-include tests * recursive-include examples *.py recursive-include docs * diff --git a/bindings/python/README b/bindings/python/README new file mode 100644 index 00000000000..059782ec5f0 --- /dev/null +++ b/bindings/python/README @@ -0,0 +1,6 @@ +# XRootD Python Bindings + +This is a set of simple but pythonic bindings for XRootD. It is designed to make +it easy to interface with the XRootD client, by writing Python instead of having +to write C++. + diff --git a/bindings/python/README.rst b/bindings/python/README.rst deleted file mode 100644 index cf5c2bd0129..00000000000 --- a/bindings/python/README.rst +++ /dev/null @@ -1,16 +0,0 @@ -Prerequisites (incomplete): xrootd - -# Installing on OSX -Setup should succeed if: - - xrootd is installed on your system - - xrootd was installed via homebrew - - you're installing the bindings package with the same version number as the - xrootd installation (`xrootd -v`). - -If you have xrootd installed and the installation still fails, do -`XRD_LIBDIR=XYZ; XRD_INCDIR=ZYX; pip install xrootd` -where XYZ and ZYX are the paths to the XRootD library and include directories on your system. - -## How to find the lib and inc directories -To find the library directory, search your system for "libXrd*" files. -The include directory should contain a file named "XrdVersion.hh", so search for that. diff --git a/bindings/python/VERSION b/bindings/python/VERSION new file mode 120000 index 00000000000..558194c5a5a --- /dev/null +++ b/bindings/python/VERSION @@ -0,0 +1 @@ +../../VERSION \ No newline at end of file diff --git a/bindings/python/pyproject.toml b/bindings/python/pyproject.toml new file mode 120000 index 00000000000..00c904eb842 --- /dev/null +++ b/bindings/python/pyproject.toml @@ -0,0 +1 @@ +../../pyproject.toml \ No newline at end of file diff --git a/bindings/python/setup.py b/bindings/python/setup.py new file mode 100644 index 00000000000..226607c2756 --- /dev/null +++ b/bindings/python/setup.py @@ -0,0 +1,147 @@ +import os +import platform +import subprocess +import sys + +from setuptools import setup, Extension +from setuptools.command.build_ext import build_ext +from subprocess import check_call, check_output + +try: + from shutil import which +except ImportError: + from distutils.spawn import find_executable as which + +srcdir = '${CMAKE_CURRENT_SOURCE_DIR}' + +cmdline_args = [] + +# Check for unexpanded srcdir to determine if this is part +# of a regular CMake build or a Python build using setup.py. + +if not srcdir.startswith('$'): + # When building the Python bindings as part of a standard CMake build, + # propagate down which cmake command to use, and the build type, C++ + # compiler, build flags, and how to link libXrdCl from the main build. + + cmake = '${CMAKE_COMMAND}' + + cmdline_args += [ + '-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}', + '-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}', + '-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}', + '-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}', + '-DXRootD_CLIENT_LIBRARY=${CMAKE_BINARY_DIR}/src/XrdCl/libXrdCl${CMAKE_SHARED_LIBRARY_SUFFIX}', + '-DXRootD_INCLUDE_DIR=${CMAKE_SOURCE_DIR}/src;${CMAKE_BINARY_DIR}/src', + ] +else: + srcdir = '.' + + cmake = which("cmake3") or which("cmake") + + for arg in sys.argv: + if arg.startswith('-D'): + cmdline_args.append(arg) + + for arg in cmdline_args: + sys.argv.remove(arg) + +def get_version(): + version = '${XRootD_VERSION_STRING}' + + if version.startswith('$'): + try: + version = open('VERSION').read().strip() + + if version.startswith('$'): + output = check_output(['git', 'describe']) + version = output.decode().strip() + except: + version = None + pass + + if version is None: + from datetime import date + version = '5.6-rc' + date.today().strftime("%Y%m%d") + + if version.startswith('v'): + version = version[1:] + + # Sanitize version to conform to PEP 440 + # https://www.python.org/dev/peps/pep-0440 + version = version.replace('-rc', 'rc') + version = version.replace('-g', '+git.') + version = version.replace('-', '.post', 1) + version = version.replace('-', '.') + + return version + +class CMakeExtension(Extension): + def __init__(self, name, src=srcdir, sources=[], **kwa): + Extension.__init__(self, name, sources=sources, **kwa) + self.src = os.path.abspath(src) + +class CMakeBuild(build_ext): + def build_extensions(self): + if cmake is None: + raise RuntimeError('Cannot find CMake executable') + + for ext in self.extensions: + extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name))) + + # Use relative RPATHs to ensure the correct libraries are picked up. + # The RPATH below covers most cases where a non-standard path is + # used for installation. It allows to find libXrdCl with a relative + # path from the site-packages directory. Build with install RPATH + # because libraries are installed by Python/pip not CMake, so CMake + # cannot fix the install RPATH later on. + + cmake_args = [ + '-DPython_EXECUTABLE={}'.format(sys.executable), + '-DCMAKE_BUILD_WITH_INSTALL_RPATH=TRUE', + '-DCMAKE_INSTALL_RPATH=$ORIGIN/../../../../$LIB', + '-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY={}/{}'.format(self.build_temp, ext.name), + '-DCMAKE_LIBRARY_OUTPUT_DIRECTORY={}/{}'.format(extdir, ext.name), + ] + + cmake_args += cmdline_args + + if not os.path.exists(self.build_temp): + os.makedirs(self.build_temp) + + check_call([cmake, ext.src, '-B', self.build_temp] + cmake_args) + check_call([cmake, '--build', self.build_temp, '--parallel']) + +version = get_version() + +setup(name='xrootd', + version=version, + description='XRootD Python bindings', + author='XRootD Developers', + author_email='xrootd-dev@slac.stanford.edu', + url='http://xrootd.org', + download_url='https://github.com/xrootd/xrootd/archive/v%s.tar.gz' % version, + keywords=['XRootD', 'network filesystem'], + license='LGPLv3+', + long_description=open(srcdir + '/README').read(), + long_description_content_type='text/plain', + packages = ['XRootD', 'XRootD.client', 'pyxrootd'], + package_dir = { + 'pyxrootd' : srcdir + '/src', + 'XRootD' : srcdir + '/libs', + 'XRootD/client': srcdir + '/libs/client', + }, + ext_modules= [ CMakeExtension('pyxrootd') ], + cmdclass={ 'build_ext': CMakeBuild }, + zip_safe=False, + classifiers=[ + "Intended Audience :: Information Technology", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)", + "Operating System :: MacOS", + "Operating System :: POSIX :: Linux", + "Operating System :: Unix", + "Programming Language :: C++", + "Programming Language :: Python", + ] + ) diff --git a/bindings/python/setup.py.in b/bindings/python/setup.py.in deleted file mode 100644 index 7d02d07278e..00000000000 --- a/bindings/python/setup.py.in +++ /dev/null @@ -1,93 +0,0 @@ -from __future__ import print_function - -from setuptools import setup, Extension -# sysconfig with setuptools v48.0.0+ is incompatible for Python 3.6 only, so fall back to distutils. -# FIXME: When support for Python 3.6 is dropped simplify this -import sys - -if sys.version_info < (3, 7): - from distutils import sysconfig -else: - import sysconfig - -from os import getenv, walk, path -import subprocess - -# Remove the "-Wstrict-prototypes" compiler option, which isn't valid for C++. -cfg_vars = sysconfig.get_config_vars() -opt = cfg_vars["OPT"] -cfg_vars["OPT"] = " ".join( flag for flag in opt.split() if flag not in ['-Wstrict-prototypes' ${CLANG_PROHIBITED} ] ) + " --std=c++14" - -cflags = cfg_vars["CFLAGS"] -cfg_vars["CFLAGS"] = " ".join( flag for flag in cflags.split() if flag not in ['-Wstrict-prototypes' ${CLANG_PROHIBITED} ] ) + " --std=c++14" - -# pypy doesn't define PY_CFLAGS so skip it if it's missing -if "PY_CFLAGS" in cfg_vars: - py_cflags = cfg_vars["PY_CFLAGS"] - cfg_vars["PY_CFLAGS"] = " ".join( flag for flag in py_cflags.split() if flag not in ['-Wstrict-prototypes' ${CLANG_PROHIBITED} ] ) + " --std=c++14" - -ccl=cfg_vars["CC"].split() -ccl[0]="${CMAKE_C_COMPILER}" -cfg_vars["CC"] = " ".join(ccl) -cxxl=cfg_vars["CXX"].split() -cxxl[0]="${CMAKE_CXX_COMPILER}" -cfg_vars["CXX"] = " ".join(cxxl) -cfg_vars["PY_CXXFLAGS"] = "${CMAKE_CXX_FLAGS}" - -# Make the RPATH relative to the python module -cfg_vars['LDSHARED'] = cfg_vars['LDSHARED'] + " -Wl,-rpath,${XRDCL_RPATH}" - -sources = list() -depends = list() - -for dirname, dirnames, filenames in walk('${CMAKE_CURRENT_SOURCE_DIR}/src'): - for filename in filenames: - if filename.endswith('.cc'): - sources.append(path.join(dirname, filename)) - elif filename.endswith('.hh'): - depends.append(path.join(dirname, filename)) - -xrdcllibdir = "${XRDCL_LIBDIR}" -xrdlibdir = "${XRD_LIBDIR}" -xrdsrcincdir = "${XRD_SRCINCDIR}" -xrdbinincdir = "${XRD_BININCDIR}" - -version = "${XRootD_VERSION_STRING}" - -# Sanitize version to conform to PEP 440 -# https://www.python.org/dev/peps/pep-0440 - -version = version.replace('-rc', 'rc') -version = version.replace('-g', '+git.') -version = version.replace('-', '.post', 1) -version = version.replace('-', '.') - -print('XRootD library dir: ', xrdlibdir) -print('XRootD src include dir:', xrdsrcincdir) -print('XRootD bin include dir:', xrdbinincdir) -print('Version: ', version) - -setup( name = 'xrootd', - version = version, - author = 'XRootD Developers', - author_email = 'xrootd-dev@slac.stanford.edu', - url = 'http://xrootd.org', - license = 'LGPLv3+', - description = "XRootD Python bindings", - long_description = "XRootD Python bindings", - packages = ['pyxrootd', 'XRootD', 'XRootD.client'], - package_dir = {'pyxrootd' : '${CMAKE_CURRENT_SOURCE_DIR}/src', - 'XRootD' : '${CMAKE_CURRENT_SOURCE_DIR}/libs', - 'XRootD.client': '${CMAKE_CURRENT_SOURCE_DIR}/libs/client'}, - ext_modules = [ - Extension( - 'pyxrootd.client', - sources = sources, - depends = depends, - libraries = ['XrdCl', 'XrdUtils', 'dl'], - extra_compile_args = ['-g'], - include_dirs = [xrdsrcincdir, xrdbinincdir], - library_dirs = [xrdlibdir, xrdcllibdir] - ) - ] - ) diff --git a/bindings/python/src/CMakeLists.txt b/bindings/python/src/CMakeLists.txt new file mode 100644 index 00000000000..29e58312e90 --- /dev/null +++ b/bindings/python/src/CMakeLists.txt @@ -0,0 +1,58 @@ +Python_add_library(client MODULE WITH_SOABI + # headers + AsyncResponseHandler.hh + ChunkIterator.hh + Conversions.hh + PyXRootD.hh + PyXRootDCopyProcess.hh + PyXRootDCopyProgressHandler.hh + PyXRootDEnv.hh + PyXRootDFile.hh + PyXRootDFileSystem.hh + PyXRootDFinalize.hh + PyXRootDURL.hh + Utils.hh + # sources + PyXRootDCopyProcess.cc + PyXRootDCopyProgressHandler.cc + PyXRootDFile.cc + PyXRootDFileSystem.cc + PyXRootDModule.cc + PyXRootDURL.cc + Utils.cc +) + +target_compile_options(client PRIVATE -w) # TODO: fix build warnings + +if(APPLE) + set(CMAKE_MACOSX_RPATH TRUE) + set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) + set_target_properties(client PROPERTIES + INSTALL_NAME_DIR "@rpath" INSTALL_RPATH "@loader_path") +endif() + +# Avoid a call to find_package(XRootD) in order to be able to override +# variables when building the module as part of a standard CMake build. + +if(TARGET XrdCl) + target_link_libraries(client PRIVATE XrdCl) +else() + find_library(XRootD_CLIENT_LIBRARY NAMES XrdCl) + + if(NOT XRootD_CLIENT_LIBRARY) + message(FATAL_ERROR "Could not find XRootD client library") + endif() + + find_path(XRootD_INCLUDE_DIR XrdVersion.hh PATH_SUFFIXES include/xrootd) + + if(NOT XRootD_INCLUDE_DIR) + message(FATAL_ERROR "Could not find XRootD client include directory") + endif() + + # The client library makes use of private XRootD headers, so add the + # extra include for it to allow building the Python bindings against + # a pre-installed XRootD. + + target_link_libraries(client PRIVATE ${XRootD_CLIENT_LIBRARY}) + target_include_directories(client PRIVATE ${XRootD_INCLUDE_DIR} ${XRootD_INCLUDE_DIR}/private) +endif() diff --git a/cmake/XRootDDefaults.cmake b/cmake/XRootDDefaults.cmake index f9bccf6549d..f7223ceadd2 100644 --- a/cmake/XRootDDefaults.cmake +++ b/cmake/XRootDDefaults.cmake @@ -19,8 +19,6 @@ option( ENABLE_XRDCL "Enable XRootD client." option( ENABLE_TESTS "Enable unit tests." FALSE ) option( ENABLE_HTTP "Enable HTTP component." TRUE ) option( ENABLE_PYTHON "Enable python bindings." TRUE ) -# As PIP_OPTIONS uses the cache, make sure to clean cache if rebuilding (e.g. cmake --build --clean-first) -SET(PIP_OPTIONS "" CACHE STRING "pip options used during the Python bindings install.") option( XRDCL_ONLY "Build only the client and necessary dependencies" FALSE ) option( XRDCL_LIB_ONLY "Build only the client libraries and necessary dependencies" FALSE ) option( PYPI_BUILD "The project is being built for PyPI release" FALSE ) diff --git a/packaging/debian/rules b/packaging/debian/rules index 44203c03974..df662e1c07c 100755 --- a/packaging/debian/rules +++ b/packaging/debian/rules @@ -1,12 +1,12 @@ #!/usr/bin/make -f +export DH_VERBOSE=1 export PYBUILD_NAME=xrootd -# --install-layout deb %: - dh $@ --builddirectory=build --destdir=deb_packages --with python3 + dh $@ --builddirectory=build --destdir=deb_packages --with python3 --buildsystem=cmake override_dh_auto_configure: - dh_auto_configure -- -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_LIBDIR=lib/$(shell dpkg-architecture -qDEB_HOST_MULTIARCH) -DPython_EXECUTABLE=/usr/bin/python3 -DCMAKE_SKIP_INSTALL_RPATH=ON -DENABLE_XRDCLHTTP=TRUE + dh_auto_configure -- -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_LIBDIR=lib/$(shell dpkg-architecture -qDEB_HOST_MULTIARCH) -DPython_EXECUTABLE=/usr/bin/python3 -DCMAKE_SKIP_INSTALL_RPATH=ON -DENABLE_XRDCLHTTP=TRUE -DINSTALL_PYTHON_BINDINGS=TRUE -DPIP_OPTIONS='--no-deps --disable-pip-version-check --verbose' override_dh_install: install -D -m 644 packaging/common/client.conf deb_packages/etc/xrootd/client.conf diff --git a/packaging/rhel/xrootd.spec.in b/packaging/rhel/xrootd.spec.in index 86757efb1ae..7a229600311 100644 --- a/packaging/rhel/xrootd.spec.in +++ b/packaging/rhel/xrootd.spec.in @@ -532,7 +532,8 @@ cmake \ %if %{?_with_isal:1}%{!?_with_isal:0} -DENABLE_XRDEC=TRUE \ %endif - -DUSER_VERSION=v%{version} \ + -DXRootD_VERSION_STRING=v%{version} \ + -DINSTALL_PYTHON_BINDINGS=FALSE \ ../ make -i VERBOSE=1 %{?_smp_mflags} diff --git a/packaging/wheel/TestCXX14.txt b/packaging/wheel/TestCXX14.txt deleted file mode 100644 index ef1e98e0bbf..00000000000 --- a/packaging/wheel/TestCXX14.txt +++ /dev/null @@ -1,95 +0,0 @@ -############################################################################### -# Test with cmake if the compiler supports all features of C++14 -############################################################################### - -cmake_minimum_required(VERSION 3.16...3.25 FATAL_ERROR) -project(TestCXX14 CXX) - -message("Your C++ compiler supports these C++14 features:") - -foreach( feature ${CMAKE_CXX_COMPILE_FEATURES} ) - - if( feature STREQUAL "cxx_std_14" ) - message( "${feature}" ) - set( has_cxx_std_14 TRUE ) - endif() - - if( feature STREQUAL "cxx_aggregate_default_initializers" ) - message( "${feature}" ) - set( has_cxx_aggregate_default_initializers TRUE ) - endif() - - if( feature STREQUAL "cxx_attribute_deprecated" ) - message( "${feature}" ) - set( has_cxx_attribute_deprecated TRUE ) - endif() - - if( feature STREQUAL "cxx_binary_literals" ) - message( "${feature}" ) - set( has_cxx_binary_literals TRUE ) - endif() - - if( feature STREQUAL "cxx_contextual_conversions" ) - message( "${feature}" ) - set( has_cxx_contextual_conversions TRUE ) - endif() - - if( feature STREQUAL "cxx_decltype_auto" ) - message( "${feature}" ) - set( has_cxx_decltype_auto TRUE ) - endif() - - if( feature STREQUAL "cxx_digit_separators" ) - message( "${feature}" ) - set( has_cxx_digit_separators TRUE ) - endif() - - if( feature STREQUAL "cxx_generic_lambdas" ) - message( "${feature}" ) - set( has_cxx_generic_lambdas TRUE ) - endif() - - if( feature STREQUAL "cxx_lambda_init_captures" ) - message( "${feature}" ) - set( has_cxx_lambda_init_captures TRUE ) - endif() - - if( feature STREQUAL "cxx_relaxed_constexpr" ) - message( "${feature}" ) - set( has_cxx_relaxed_constexpr TRUE ) - endif() - - if( feature STREQUAL "cxx_return_type_deduction" ) - message( "${feature}" ) - set( has_cxx_return_type_deduction TRUE ) - endif() - - if( feature STREQUAL "cxx_variable_templates" ) - message( "${feature}" ) - set( has_cxx_variable_templates TRUE ) - endif() - -endforeach() - -if( has_cxx_std_14 AND - has_cxx_aggregate_default_initializers AND - has_cxx_attribute_deprecated AND - has_cxx_binary_literals AND - has_cxx_contextual_conversions AND - has_cxx_decltype_auto AND - has_cxx_digit_separators AND - has_cxx_generic_lambdas AND - has_cxx_lambda_init_captures AND - has_cxx_relaxed_constexpr AND - has_cxx_return_type_deduction AND - has_cxx_variable_templates ) - - message( "We have full C++14 support." ) - set( has_full_cxx14 TRUE ) - -endif() - -if( NOT has_full_cxx14 ) - message( FATAL_ERROR "The compiler DOES NOT support all features of C++14!." ) -endif() - \ No newline at end of file diff --git a/packaging/wheel/has_c++14.sh b/packaging/wheel/has_c++14.sh deleted file mode 100755 index 25c883fc988..00000000000 --- a/packaging/wheel/has_c++14.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -mkdir has_c++14.tmp -cp packaging/wheel/TestCXX14.txt has_c++14.tmp/CMakeLists.txt -cd has_c++14.tmp -mkdir build -cd build -cmake3 .. -has_cxx14=$? -cd ../.. -rm -rf has_c++14.tmp -exit $has_cxx14 \ No newline at end of file diff --git a/packaging/wheel/install.sh b/packaging/wheel/install.sh deleted file mode 100755 index 897b21cee13..00000000000 --- a/packaging/wheel/install.sh +++ /dev/null @@ -1,61 +0,0 @@ -#!/bin/bash - -startdir="$(pwd)" -mkdir xrootdbuild -cd xrootdbuild - -# build only client -# build python bindings -# set install prefix -# set the respective version of python -# replace the default BINDIR with a custom one that can be easily removed afterwards -# (for the python bindings we don't want to install the binaries) -CMAKE_ARGS="-DPYPI_BUILD=TRUE -DXRDCL_LIB_ONLY=TRUE -DENABLE_PYTHON=TRUE -DCMAKE_INSTALL_PREFIX=$1/pyxrootd -DXRD_PYTHON_REQ_VERSION=$2 -DCMAKE_INSTALL_BINDIR=$startdir/xrootdbuild/bin" - -if [ "$5" = "true" ]; then - source /opt/rh/devtoolset-7/enable -fi - -cmake_path=$4 -$cmake_path .. $CMAKE_ARGS - -res=$? -if [ "$res" -ne "0" ]; then - exit 1 -fi - -make -j8 -res=$? -if [ "$res" -ne "0" ]; then - exit 1 -fi - -cd src -make install -res=$? -if [ "$res" -ne "0" ]; then - exit 1 -fi - -cd ../bindings/python - -# Determine if shutil.which is available for a modern Python package install -# (shutil.which was added in Python 3.3, so any version of Python 3 now will have it) -# TODO: Drop support for Python 3.3 and simplify to pip approach -${6} -c 'from shutil import which' &> /dev/null # $6 holds the python sys.executable -shutil_which_available=$? -if [ "${shutil_which_available}" -ne "0" ]; then - ${6} setup.py install ${3} - res=$? -else - ${6} -m pip install ${3} . - res=$? -fi -unset shutil_which_available - -if [ "$res" -ne "0" ]; then - exit 1 -fi - -cd $startdir -rm -r xrootdbuild diff --git a/packaging/wheel/setup.py b/packaging/wheel/setup.py deleted file mode 100644 index 29be651cee2..00000000000 --- a/packaging/wheel/setup.py +++ /dev/null @@ -1,188 +0,0 @@ -from setuptools import setup -from setuptools.command.install import install -from setuptools.command.sdist import sdist -from wheel.bdist_wheel import bdist_wheel - -# shutil.which was added in Python 3.3 -# c.f. https://docs.python.org/3/library/shutil.html#shutil.which -try: - from shutil import which -except ImportError: - from distutils.spawn import find_executable as which - -import subprocess -import sys - -def get_version(): - try: - version = open('VERSION').read().strip() - except: - from datetime import date - version = 'v5.6-rc' + date.today().strftime("%Y%m%d") - - return version - -def binary_exists(name): - """Check whether `name` is on PATH.""" - return which(name) is not None - -def check_cmake3(path): - args = (path, "--version") - popen = subprocess.Popen(args, stdout=subprocess.PIPE) - popen.wait() - output = popen.stdout.read().decode("utf-8") - prefix_len = len( "cmake version " ) - version = output[prefix_len:].split( '.' ) - return int( version[0] ) >= 3 - -def cmake_exists(): - """Check whether CMAKE is on PATH.""" - path = which('cmake') - if path is not None: - if check_cmake3(path): return True, path - path = which('cmake3') - return path is not None, path - -def is_rhel7(): - """check if we are running on rhel7 platform""" - try: - f = open( '/etc/redhat-release', "r" ) - txt = f.read().split() - i = txt.index( 'release' ) + 1 - return txt[i][0] == '7' - except IOError: - return False - except ValueError: - return False - -def has_devtoolset(): - """check if devtoolset-7 is installed""" - import subprocess - args = ( "/usr/bin/rpm", "-q", "devtoolset-7-gcc-c++" ) - popen = subprocess.Popen(args, stdout=subprocess.PIPE) - rc = popen.wait() - return rc == 0 - -def has_cxx14(): - """check if C++ compiler supports C++14""" - import subprocess - popen = subprocess.Popen("./has_c++14.sh", stdout=subprocess.PIPE) - rc = popen.wait() - return rc == 0 - -# def python_dependency_name( py_version_short, py_version_nodot ): -# """ find the name of python dependency """ -# # this is the path to default python -# path = which( 'python' ) -# from os.path import realpath -# # this is the real default python after resolving symlinks -# real = realpath(path) -# index = real.find( 'python' ) + len( 'python' ) -# # this is the version of default python -# defaultpy = real[index:] -# if defaultpy != py_version_short: -# return 'python' + py_version_nodot -# return 'python' - -class CustomInstall(install): - def run(self): - - py_version_short = self.config_vars['py_version_short'] - py_version_nodot = self.config_vars['py_version_nodot'] - - cmake_bin, cmake_path = cmake_exists() - make_bin = binary_exists( 'make' ) - comp_bin = binary_exists( 'c++' ) or binary_exists( 'g++' ) or binary_exists( 'clang' ) - - import pkgconfig - zlib_dev = pkgconfig.exists( 'zlib' ) - openssl_dev = pkgconfig.exists( 'openssl' ) - uuid_dev = pkgconfig.exists( 'uuid' ) - - if is_rhel7(): - if has_cxx14(): - devtoolset7 = True # we only care about devtoolset7 if the compiler does not support C++14 - need_devtoolset = "false" - else: - devtoolset7 = has_devtoolset() - need_devtoolset = "true" - else: - devtoolset7 = True # we only care about devtoolset7 on rhel7 - need_devtoolset = "false" - - pyname = None - if py_version_nodot[0] == '3': - python_dev = pkgconfig.exists( 'python3' ) or pkgconfig.exists( 'python' + py_version_nodot ); - pyname = 'python3' - else: - python_dev = pkgconfig.exists( 'python' ); - pyname = 'python' - - missing_dep = not ( cmake_bin and make_bin and comp_bin and zlib_dev and openssl_dev and python_dev and uuid_dev and devtoolset7 ) - - if missing_dep: - print( 'Some dependencies are missing:') - if not cmake_bin: print('\tcmake (version 3) is missing!') - if not make_bin: print('\tmake is missing!') - if not comp_bin: print('\tC++ compiler is missing (g++, c++, clang, etc.)!') - if not zlib_dev: print('\tzlib development package is missing!') - if not openssl_dev: print('\topenssl development package is missing!') - if not python_dev: print('\t{} development package is missing!'.format(pyname) ) - if not uuid_dev: print('\tuuid development package is missing') - if not devtoolset7: print('\tdevtoolset-7-gcc-c++ package is missing') - raise Exception( 'Dependencies missing!' ) - - useropt = '' - command = ['./install.sh'] - if self.user: - prefix = self.install_usersite - useropt = '--user' - else: - prefix = self.install_platlib - command.append(prefix) - command.append( py_version_short ) - command.append( useropt ) - command.append( cmake_path ) - command.append( need_devtoolset ) - command.append( sys.executable ) - rc = subprocess.call(command) - if rc: - raise Exception( 'Install step failed!' ) - - -class CustomDist(sdist): - def write_version_to_file(self): - - version = get_version() - with open('bindings/python/VERSION', 'w') as vi: - vi.write(version) - - def run(self): - self.write_version_to_file() - sdist.run(self) - - -class CustomWheelGen(bdist_wheel): - # Do not generate wheel - def run(self): - pass - -version = get_version() -setup_requires=[ 'pkgconfig' ] - -setup( - name = 'xrootd', - version = version, - author = 'XRootD Developers', - author_email = 'xrootd-dev@slac.stanford.edu', - url = 'http://xrootd.org', - license = 'LGPLv3+', - description = "XRootD with Python bindings", - long_description = "XRootD with Python bindings", - setup_requires = setup_requires, - cmdclass = { - 'install': CustomInstall, - 'sdist': CustomDist, - 'bdist_wheel': CustomWheelGen - } -) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000000..b0f076532a0 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools>=42"] +build-backend = "setuptools.build_meta" diff --git a/setup.py b/setup.py new file mode 100644 index 00000000000..dac6967face --- /dev/null +++ b/setup.py @@ -0,0 +1,120 @@ +import os +import platform +import subprocess +import sys + +from setuptools import setup, Extension +from setuptools.command.build_ext import build_ext +from subprocess import check_call, check_output + +try: + from shutil import which +except ImportError: + from distutils.spawn import find_executable as which + +cmdline_args = [] + +for arg in sys.argv: + if arg.startswith('-D'): + cmdline_args.append(arg) + +for arg in cmdline_args: + sys.argv.remove(arg) + +cmake = which("cmake3") or which("cmake") + +def get_version(): + try: + version = open('VERSION').read().strip() + + if version.startswith('$'): + output = check_output(['git', 'describe']) + version = output.decode().strip() + except: + version = None + pass + + if version is None: + from datetime import date + version = '5.6-rc' + date.today().strftime("%Y%m%d") + + if version.startswith('v'): + version = version[1:] + + # Sanitize version to conform to PEP 440 + # https://www.python.org/dev/peps/pep-0440 + version = version.replace('-rc', 'rc') + version = version.replace('-g', '+git.') + version = version.replace('-', '.post', 1) + version = version.replace('-', '.') + + return version + +class CMakeExtension(Extension): + def __init__(self, name, src='.', sources=[], **kwa): + Extension.__init__(self, name, sources=sources, **kwa) + self.src = os.path.abspath(src) + +class CMakeBuild(build_ext): + def build_extensions(self): + if cmake is None: + raise RuntimeError('Cannot find CMake executable') + + for ext in self.extensions: + extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name))) + + # Use $ORIGIN RPATH to ensure that the client library can load + # libXrdCl which will be installed in the same directory. Build + # with install RPATH because libraries are installed by Python/pip + # not CMake, so CMake cannot fix the install RPATH later on. + + cmake_args = [ + '-DPython_EXECUTABLE={}'.format(sys.executable), + '-DCMAKE_INSTALL_RPATH=$ORIGIN', + '-DCMAKE_BUILD_WITH_INSTALL_RPATH=TRUE', + '-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY={}/{}'.format(self.build_temp, ext.name), + '-DCMAKE_LIBRARY_OUTPUT_DIRECTORY={}/{}'.format(extdir, ext.name), + '-DENABLE_PYTHON=1', '-DENABLE_XRDCL=1', '-DXRDCL_LIB_ONLY=1', '-DPYPI_BUILD=1' + ] + + cmake_args += cmdline_args + + if not os.path.exists(self.build_temp): + os.makedirs(self.build_temp) + + check_call([cmake, ext.src, '-B', self.build_temp] + cmake_args) + check_call([cmake, '--build', self.build_temp, '--parallel']) + +version = get_version() + +setup(name='xrootd', + version=version, + description='eXtended ROOT daemon', + author='XRootD Developers', + author_email='xrootd-dev@slac.stanford.edu', + url='http://xrootd.org', + download_url='https://github.com/xrootd/xrootd/archive/v%s.tar.gz' % version, + keywords=['XRootD', 'network filesystem'], + license='LGPLv3+', + long_description=open('README').read(), + long_description_content_type='text/plain', + packages = ['XRootD', 'XRootD.client', 'pyxrootd'], + package_dir = { + 'pyxrootd' : 'bindings/python/src', + 'XRootD' : 'bindings/python/libs', + 'XRootD/client': 'bindings/python/libs/client', + }, + ext_modules= [ CMakeExtension('pyxrootd') ], + cmdclass={ 'build_ext': CMakeBuild }, + zip_safe=False, + classifiers=[ + "Intended Audience :: Information Technology", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)", + "Operating System :: MacOS", + "Operating System :: POSIX :: Linux", + "Operating System :: Unix", + "Programming Language :: C++", + "Programming Language :: Python", + ] + ) From 5559323264bcdbaa0eb034b7b336823d74f3f035 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 5 Jun 2023 17:09:02 +0200 Subject: [PATCH 129/442] Revert "[CI] Do not update pip, setuptools, and wheel for sdist build" This reverts commit eeb85d2cb4631e8c96e58b3d404d013306827cf9. This should not be necessary anymore now that the build system has been updated. --- .github/workflows/build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d8980ac8bcd..015bf2dde61 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -967,6 +967,9 @@ jobs: pkg-config \ tree sudo apt-get autoclean -y + # Remove packages with invalid versions which cause sdist build to fail + sudo apt-get remove python3-debian python3-distro-info + python3 -m pip --no-cache-dir install --upgrade pip setuptools wheel python3 -m pip list - name: Clone repository From a1cd57f08845dd71556219966af9381eace19be8 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 6 Jun 2023 11:38:06 +0200 Subject: [PATCH 130/442] [Python] Use PyUnicode rather than PyBytes for strings Fixes: #2011. --- bindings/python/src/ChunkIterator.hh | 2 +- bindings/python/src/Conversions.hh | 6 +++--- bindings/python/src/PyXRootD.hh | 19 +++++++----------- bindings/python/src/PyXRootDFile.cc | 24 +++++++++++------------ bindings/python/src/PyXRootDFile.hh | 2 +- bindings/python/src/PyXRootDFileSystem.cc | 18 ++++++++--------- bindings/python/src/PyXRootDURL.cc | 20 +++++++++---------- 7 files changed, 43 insertions(+), 48 deletions(-) diff --git a/bindings/python/src/ChunkIterator.hh b/bindings/python/src/ChunkIterator.hh index 7fbe931bffd..3770bb616ac 100644 --- a/bindings/python/src/ChunkIterator.hh +++ b/bindings/python/src/ChunkIterator.hh @@ -98,7 +98,7 @@ namespace PyXRootD else { self->currentOffset += self->chunksize; - pychunk = PyBytes_FromStringAndSize( (const char*) chunk->GetBuffer(), + pychunk = PyUnicode_FromStringAndSize( (const char*) chunk->GetBuffer(), chunk->GetSize() ); } diff --git a/bindings/python/src/Conversions.hh b/bindings/python/src/Conversions.hh index 6c7d7353e0f..a644a6eb23b 100644 --- a/bindings/python/src/Conversions.hh +++ b/bindings/python/src/Conversions.hh @@ -241,7 +241,7 @@ namespace PyXRootD { static PyObject* Convert( XrdCl::Buffer *buffer ) { - return PyBytes_FromStringAndSize( buffer->GetBuffer(), buffer->GetSize() ); + return PyUnicode_FromStringAndSize( buffer->GetBuffer(), buffer->GetSize() ); } }; @@ -249,7 +249,7 @@ namespace PyXRootD { static PyObject* Convert( XrdCl::ChunkInfo *chunk ) { - PyObject *o = PyBytes_FromStringAndSize( (const char*)chunk->buffer, + PyObject *o = PyUnicode_FromStringAndSize( (const char*)chunk->buffer, chunk->length ); delete[] (char*) chunk->buffer; return o; @@ -268,7 +268,7 @@ namespace PyXRootD for ( uint32_t i = 0; i < chunks.size(); ++i ) { XrdCl::ChunkInfo chunk = chunks.at( i ); - PyObject *buffer = PyBytes_FromStringAndSize( (const char *) chunk.buffer, + PyObject *buffer = PyUnicode_FromStringAndSize( (const char *) chunk.buffer, chunk.length ); delete[] (char*) chunk.buffer; diff --git a/bindings/python/src/PyXRootD.hh b/bindings/python/src/PyXRootD.hh index a979186865a..054f9852676 100644 --- a/bindings/python/src/PyXRootD.hh +++ b/bindings/python/src/PyXRootD.hh @@ -32,6 +32,13 @@ #if PY_MAJOR_VERSION >= 3 #define IS_PY3K +#define Py_TPFLAGS_HAVE_ITER 0 +#else +#define PyUnicode_AsUTF8 PyString_AsString +#define PyUnicode_Check PyString_Check +#define PyUnicode_FromString PyString_FromString +#define PyUnicode_FromStringAndSize PyString_FromStringAndSize +#define PyUnicode_GET_LENGTH PyString_Size #endif #define async( func ) \ @@ -39,16 +46,4 @@ func; \ Py_END_ALLOW_THREADS \ -#ifdef IS_PY3K -#define Py_TPFLAGS_HAVE_ITER 0 -#else -#if PY_MINOR_VERSION <= 5 -#define PyUnicode_FromString PyString_FromString -#endif -#define PyBytes_Size PyString_Size -#define PyBytes_Check PyString_Check -#define PyBytes_FromString PyString_FromString -#define PyBytes_FromStringAndSize PyString_FromStringAndSize -#endif - #endif /* PYXROOTD_HH_ */ diff --git a/bindings/python/src/PyXRootDFile.cc b/bindings/python/src/PyXRootDFile.cc index 619100990dd..f6d9608b898 100644 --- a/bindings/python/src/PyXRootDFile.cc +++ b/bindings/python/src/PyXRootDFile.cc @@ -195,7 +195,7 @@ namespace PyXRootD else { uint32_t bytesRead = 0; async( status = self->file->Read( offset, size, buffer, bytesRead, timeout ) ); - pyresponse = PyBytes_FromStringAndSize( buffer, bytesRead ); + pyresponse = PyUnicode_FromStringAndSize( buffer, bytesRead ); delete[] buffer; } @@ -295,10 +295,10 @@ namespace PyXRootD if ( off_init == 0 ) self->currentOffset += line->GetSize(); - pyline = PyBytes_FromStringAndSize( line->GetBuffer(), line->GetSize() ); + pyline = PyUnicode_FromStringAndSize( line->GetBuffer(), line->GetSize() ); } else - pyline = PyBytes_FromString( "" ); + pyline = PyUnicode_FromString( "" ); delete line; delete chunk; @@ -346,7 +346,7 @@ namespace PyXRootD { line = self->ReadLine( self, args, kwds ); - if ( !line || PyBytes_Size( line ) == 0 ) + if ( !line || PyUnicode_GET_LENGTH( line ) == 0 ) break; PyList_Append( lines, line ); @@ -800,14 +800,14 @@ namespace PyXRootD return NULL; // extract the attribute name from the tuple PyObject *py_name = PyTuple_GetItem( item, 0 ); - if( !PyBytes_Check( py_name ) ) + if( !PyUnicode_Check( py_name ) ) return NULL; - std::string name = PyBytes_AsString( py_name ); + std::string name = PyUnicode_AsUTF8( py_name ); // extract the attribute value from the tuple PyObject *py_value = PyTuple_GetItem( item, 1 ); - if( !PyBytes_Check( py_value ) ) + if( !PyUnicode_Check( py_value ) ) return NULL; - std::string value = PyBytes_AsString( py_value ); + std::string value = PyUnicode_AsUTF8( py_value ); // update the C++ list of xattrs attrs.push_back( XrdCl::xattr_t( name, value ) ); } @@ -864,9 +864,9 @@ namespace PyXRootD // get the item at respective index PyObject *item = PyList_GetItem( pyattrs, i ); // make sure the item is a string - if( !item || !PyBytes_Check( item ) ) + if( !item || !PyUnicode_Check( item ) ) return NULL; - std::string name = PyBytes_AsString( item ); + std::string name = PyUnicode_AsUTF8( item ); // update the C++ list of xattrs attrs.push_back( name ); } @@ -923,9 +923,9 @@ namespace PyXRootD // get the item at respective index PyObject *item = PyList_GetItem( pyattrs, i ); // make sure the item is a string - if( !item || !PyBytes_Check( item ) ) + if( !item || !PyUnicode_Check( item ) ) return NULL; - std::string name = PyBytes_AsString( item ); + std::string name = PyUnicode_AsUTF8( item ); // update the C++ list of xattrs attrs.push_back( name ); } diff --git a/bindings/python/src/PyXRootDFile.hh b/bindings/python/src/PyXRootDFile.hh index 8ce054b3d3c..bdc7568b823 100644 --- a/bindings/python/src/PyXRootDFile.hh +++ b/bindings/python/src/PyXRootDFile.hh @@ -124,7 +124,7 @@ namespace PyXRootD //-------------------------------------------------------------------------- // Raise StopIteration if the line we just read is empty //-------------------------------------------------------------------------- - if ( PyBytes_Size( line ) == 0 ) { + if ( PyUnicode_GET_LENGTH( line ) == 0 ) { PyErr_SetNone( PyExc_StopIteration ); return NULL; } diff --git a/bindings/python/src/PyXRootDFileSystem.cc b/bindings/python/src/PyXRootDFileSystem.cc index ceb2c01f7a6..ef85995d5bb 100644 --- a/bindings/python/src/PyXRootDFileSystem.cc +++ b/bindings/python/src/PyXRootDFileSystem.cc @@ -632,7 +632,7 @@ namespace PyXRootD for ( int i = 0; i < PyList_Size( pyfiles ); ++i ) { pyfile = PyList_GetItem( pyfiles, i ); if ( !pyfile ) return NULL; - file = PyBytes_AsString( pyfile ); + file = PyUnicode_AsUTF8( pyfile ); files.push_back( std::string( file ) ); } @@ -769,14 +769,14 @@ namespace PyXRootD return NULL; // extract the attribute name from the tuple PyObject *py_name = PyTuple_GetItem( item, 0 ); - if( !PyBytes_Check( py_name ) ) + if( !PyUnicode_Check( py_name ) ) return NULL; - std::string name = PyBytes_AsString( py_name ); + std::string name = PyUnicode_AsUTF8( py_name ); // extract the attribute value from the tuple PyObject *py_value = PyTuple_GetItem( item, 1 ); - if( !PyBytes_Check( py_value ) ) + if( !PyUnicode_Check( py_value ) ) return NULL; - std::string value = PyBytes_AsString( py_value ); + std::string value = PyUnicode_AsUTF8( py_value ); // update the C++ list of xattrs attrs.push_back( XrdCl::xattr_t( name, value ) ); } @@ -831,9 +831,9 @@ namespace PyXRootD // get the item at respective index PyObject *item = PyList_GetItem( pyattrs, i ); // make sure the item is a string - if( !item || !PyBytes_Check( item ) ) + if( !item || !PyUnicode_Check( item ) ) return NULL; - std::string name = PyBytes_AsString( item ); + std::string name = PyUnicode_AsUTF8( item ); // update the C++ list of xattrs attrs.push_back( name ); } @@ -888,9 +888,9 @@ namespace PyXRootD // get the item at respective index PyObject *item = PyList_GetItem( pyattrs, i ); // make sure the item is a string - if( !item || !PyBytes_Check( item ) ) + if( !item || !PyUnicode_Check( item ) ) return NULL; - std::string name = PyBytes_AsString( item ); + std::string name = PyUnicode_AsUTF8( item ); // update the C++ list of xattrs attrs.push_back( name ); } diff --git a/bindings/python/src/PyXRootDURL.cc b/bindings/python/src/PyXRootDURL.cc index ee957258087..3552605999c 100644 --- a/bindings/python/src/PyXRootDURL.cc +++ b/bindings/python/src/PyXRootDURL.cc @@ -56,12 +56,12 @@ namespace PyXRootD //---------------------------------------------------------------------------- int URL::SetProtocol( URL *self, PyObject *protocol, void *closure ) { - if ( !PyBytes_Check( protocol ) ) { + if ( !PyUnicode_Check( protocol ) ) { PyErr_SetString( PyExc_TypeError, "protocol must be string" ); return -1; } - self->url->SetProtocol( std::string ( PyBytes_AsString( protocol ) ) ); + self->url->SetProtocol( std::string ( PyUnicode_AsUTF8( protocol ) ) ); return 0; } @@ -78,12 +78,12 @@ namespace PyXRootD //---------------------------------------------------------------------------- int URL::SetUserName( URL *self, PyObject *username, void *closure ) { - if ( !PyBytes_Check( username ) ) { + if ( !PyUnicode_Check( username ) ) { PyErr_SetString( PyExc_TypeError, "username must be string" ); return -1; } - self->url->SetUserName( std::string( PyBytes_AsString( username ) ) ); + self->url->SetUserName( std::string( PyUnicode_AsUTF8( username ) ) ); return 0; } @@ -100,12 +100,12 @@ namespace PyXRootD //---------------------------------------------------------------------------- int URL::SetPassword( URL *self, PyObject *password, void *closure ) { - if ( !PyBytes_Check( password ) ) { + if ( !PyUnicode_Check( password ) ) { PyErr_SetString( PyExc_TypeError, "password must be string" ); return -1; } - self->url->SetPassword( std::string( PyBytes_AsString( password ) ) ); + self->url->SetPassword( std::string( PyUnicode_AsUTF8( password ) ) ); return 0; } @@ -122,12 +122,12 @@ namespace PyXRootD //---------------------------------------------------------------------------- int URL::SetHostName( URL *self, PyObject *hostname, void *closure ) { - if ( !PyBytes_Check( hostname ) ) { + if ( !PyUnicode_Check( hostname ) ) { PyErr_SetString( PyExc_TypeError, "hostname must be string" ); return -1; } - self->url->SetHostName( std::string( PyBytes_AsString( hostname ) ) ); + self->url->SetHostName( std::string( PyUnicode_AsUTF8( hostname ) ) ); return 0; } @@ -178,12 +178,12 @@ namespace PyXRootD //---------------------------------------------------------------------------- int URL::SetPath( URL *self, PyObject *path, void *closure ) { - if ( !PyBytes_Check( path ) ) { + if ( !PyUnicode_Check( path ) ) { PyErr_SetString( PyExc_TypeError, "path must be string" ); return -1; } - self->url->SetPath( std::string( PyBytes_AsString( path ) ) ); + self->url->SetPath( std::string( PyUnicode_AsUTF8( path ) ) ); return 0; } From d080e15b7b5d49fcbc08ee4800de7d6e4e5ace12 Mon Sep 17 00:00:00 2001 From: Brian Bockelman Date: Tue, 6 Jun 2023 07:59:28 -0500 Subject: [PATCH 131/442] Switch to a fixed set of DH parameters compatible with older OpenSSL. OpenSSL 3 started generating DH parameters that are not considered valid by `DH_check` for older OpenSSL 1.0.2. Since we can't change clients in the wild, I generated a set of DH params (`openssl dhparam 2048`) on an older OpenSSL 1.0.2 which appears to be considered acceptable by both versions of OpenSSL. This fixes the set of DH parameters (instead of generating them each time), which is fairly typical, and also increases the size from 512 (insecure) to 2048. --- src/XrdCrypto/XrdCryptosslCipher.cc | 49 +++++++++++++++++++++++++++++ src/XrdCrypto/XrdCryptosslCipher.hh | 7 ++++- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/XrdCrypto/XrdCryptosslCipher.cc b/src/XrdCrypto/XrdCryptosslCipher.cc index e0bb5f5a67a..a0f5de82f8b 100644 --- a/src/XrdCrypto/XrdCryptosslCipher.cc +++ b/src/XrdCrypto/XrdCryptosslCipher.cc @@ -47,6 +47,26 @@ #include #endif +// Hardcoded DH parameters that are acceptable to both OpenSSL 3.0 (RHEL9) +// and 1.0.2 (RHEL7). OpenSSL 3.0 reworked the DH parameter generation algorithm +// and now produces DH params that don't pass OpenSSL 1.0.2's parameter verification +// function (`DH_check`). Accordingly, since these are safe to reuse, we generated +// a single set of parameters for the server to always utilize. +static const char dh_param_enc[] = +R"( +-----BEGIN DH PARAMETERS----- +MIIBiAKCAYEAzcEAf3ZCkm0FxJLgKd1YoT16Hietl7QV8VgJNc5CYKmRu/gKylxT +MVZJqtUmoh2IvFHCfbTGEmZM5LdVaZfMLQf7yXjecg0nSGklYZeQQ3P0qshFLbI9 +u3z1XhEeCbEZPq84WWwXacSAAxwwRRrN5nshgAavqvyDiGNi+GqYpqGPb9JE38R3 +GJ51FTPutZlvQvEycjCbjyajhpItBB+XvIjWj2GQyvi+cqB0WrPQAsxCOPrBTCZL +OjM0NfJ7PQfllw3RDQev2u1Q+Rt8QyScJQCFUj/SWoxpw2ydpWdgAkrqTmdVYrev +x5AoXE52cVIC8wfOxaaJ4cBpnJui3Y0jZcOQj0FtC0wf4WcBpHnLLBzKSOQwbxts +WE8LkskPnwwrup/HqWimFFg40bC9F5Lm3CTDCb45mtlBxi3DydIbRLFhGAjlKzV3 +s9G3opHwwfgXpFf3+zg7NPV3g1//HLgWCvooOvMqaO+X7+lXczJJLMafEaarcAya +Kyo8PGKIAORrAgEF +-----END DH PARAMETERS----- +)"; + // ---------------------------------------------------------------------------// // // Cipher interface @@ -507,12 +527,41 @@ XrdCryptosslCipher::XrdCryptosslCipher(bool padded, int bits, char *pub, static EVP_PKEY *dhparms = [] { DEBUG("generate DH parameters"); EVP_PKEY *dhParam = 0; + +// +// Important historical context: +// - We used to generate DH params on every server startup (commented +// out below). This was prohibitively costly to do on startup for +// DH parameters large enough to be considered secure. +// - OpenSSL 3.0 improved the DH parameter generation to avoid leaking +// the first bit of the session key (see https://github.com/openssl/openssl/issues/9792 +// for more information). However, a side-effect is that the new +// parameters are not recognized as valid in OpenSSL 1.0.2. +// - Since we can't control old client versions and new servers can't +// generate compatible DH parameters, we switch to a fixed, much stronger +// set of DH parameters (3072 bits). +// +// The impact is that we continue leaking the first bit of the session key +// (meaning it's effectively 127 bits not 128 bits -- still plenty secure) +// but upgrade the DH parameters to something more modern (3072; previously, +// it was 512 bits which was not considered secure). The downside +// of fixed DH parameters is that if a nation-state attacked our selected +// parameters (using technology not currently available), we would have +// to upgrade all servers with a new set of DH parameters. +// + +/* EVP_PKEY_CTX *pkctx = EVP_PKEY_CTX_new_id(EVP_PKEY_DH, 0); EVP_PKEY_paramgen_init(pkctx); EVP_PKEY_CTX_set_dh_paramgen_prime_len(pkctx, kDHMINBITS); EVP_PKEY_CTX_set_dh_paramgen_generator(pkctx, 5); EVP_PKEY_paramgen(pkctx, &dhParam); EVP_PKEY_CTX_free(pkctx); +*/ + BIO *biop = BIO_new(BIO_s_mem()); + BIO_write(biop, dh_param_enc, strlen(dh_param_enc)); + PEM_read_bio_Parameters(biop, &dhParam); + BIO_free(biop); DEBUG("generate DH parameters done"); return dhParam; }(); diff --git a/src/XrdCrypto/XrdCryptosslCipher.hh b/src/XrdCrypto/XrdCryptosslCipher.hh index f24fc01508b..853716a6e99 100644 --- a/src/XrdCrypto/XrdCryptosslCipher.hh +++ b/src/XrdCrypto/XrdCryptosslCipher.hh @@ -39,7 +39,12 @@ #include #include -#define kDHMINBITS 512 +// This is not used as we no longer dynamically generate the DH parameters; +// see the comments in XrdCryptosslCipher.cc for more context. +// Purposely keeping it around to help make the issue visible to future readers +// of the code. +// +// #define kDHMINBITS 512 // ---------------------------------------------------------------------------// // From fc079d6eb76528ca1c42cb0939875195fe214fb9 Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Thu, 1 Jun 2023 13:24:42 +0200 Subject: [PATCH 132/442] [XrdHttp] A bad request error is returned in the case a client provides too many range requests --- src/XrdHttp/XrdHttpProtocol.cc | 1 + src/XrdHttp/XrdHttpReq.cc | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/src/XrdHttp/XrdHttpProtocol.cc b/src/XrdHttp/XrdHttpProtocol.cc index 8d3b2ae764f..7492193bb20 100644 --- a/src/XrdHttp/XrdHttpProtocol.cc +++ b/src/XrdHttp/XrdHttpProtocol.cc @@ -1531,6 +1531,7 @@ int XrdHttpProtocol::StartSimpleResp(int code, const char *desc, const char *hea else if (code == 404) ss << "Not Found"; else if (code == 405) ss << "Method Not Allowed"; else if (code == 500) ss << "Internal Server Error"; + else if (code == 400) ss << "Bad Request"; else ss << "Unknown"; } ss << crlf; diff --git a/src/XrdHttp/XrdHttpReq.cc b/src/XrdHttp/XrdHttpReq.cc index c3d052dfb60..e119181718d 100644 --- a/src/XrdHttp/XrdHttpReq.cc +++ b/src/XrdHttp/XrdHttpReq.cc @@ -2218,6 +2218,12 @@ int XrdHttpReq::PostProcessHTTPReq(bool final_) { return 0; } else if (rwOps.size() > 1) { + // First, check that the amount of range request can be handled by the vector read of the XRoot layer + if(rwOps.size() > XrdProto::maxRvecsz) { + std::string errMsg = "Too many range requests provided. Maximum range requests supported is " + std::to_string(XrdProto::maxRvecsz); + prot->SendSimpleResp(400, NULL, NULL,errMsg.c_str(), errMsg.size(), false); + return -1; + } // Multiple reads to perform, compose and send the header int cnt = 0; for (size_t i = 0; i < rwOps.size(); i++) { From 5634ffd8eac8db1a54cbceb4d4a487552264707d Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 7 Jun 2023 13:30:49 +0200 Subject: [PATCH 133/442] [Python] Only use WITH_SOABI with CMake 3.17 or greater --- bindings/python/src/CMakeLists.txt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/bindings/python/src/CMakeLists.txt b/bindings/python/src/CMakeLists.txt index 29e58312e90..5aed566ee19 100644 --- a/bindings/python/src/CMakeLists.txt +++ b/bindings/python/src/CMakeLists.txt @@ -1,4 +1,10 @@ -Python_add_library(client MODULE WITH_SOABI +# WITH_SOABI was introduced in CMake 3.17 +# https://cmake.org/cmake/help/latest/module/FindPython.html#commands +if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.17) + set(SOABI WITH_SOABI) +endif() + +Python_add_library(client MODULE ${SOABI} # headers AsyncResponseHandler.hh ChunkIterator.hh From 7f79a1130b2b069754003e3e2e20e5969fa1e3b1 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 7 Jun 2023 15:05:53 +0200 Subject: [PATCH 134/442] [CI] Update Fedora builds on GitLab CI --- .gitlab-ci.yml | 72 ++++++++++++++++---------------------------------- 1 file changed, 23 insertions(+), 49 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 25244c47127..ea654c51e81 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -148,52 +148,24 @@ build:cc7: - schedules - web -#build:fedora-36: -# stage: build:rpm -# image: fedora:36 -# script: -# - dnf install --nogpg -y gcc-c++ rpm-build tar dnf-plugins-core git -# - cd packaging/ -# - ./makesrpm.sh --define "_with_python3 1" --define "_with_xrdclhttp 1" --define "_with_ceph11 1" -# - dnf builddep --nogpgcheck -y *.src.rpm -# - mkdir RPMS -# - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_with_python3 1" --define "_with_xrdclhttp 1" --define "_with_ceph11 1" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" *.src.rpm -# - cd .. -# - mkdir fc-rawhide -# - cp packaging/*.src.rpm fc-rawhide -# - cp packaging/RPMS/* fc-rawhide -# artifacts: -# expire_in: 1 day -# paths: -# - fc-rawhide/ -# tags: -# - docker_node -# only: -# - master -# - /^stable-.*$/ -# except: -# - tags -# - schedules -# - web - -build:fedora-35: +build:fedora-37: stage: build:rpm - image: fedora:35 + image: fedora:37 script: - dnf install --nogpg -y gcc-c++ rpm-build tar dnf-plugins-core git - cd packaging/ - - ./makesrpm.sh --define "_with_python3 1" --define "_with_ceph11 1" + - ./makesrpm.sh --define "_with_python3 1" --define "_with_xrdclhttp 1" --define "_with_ceph11 1" - dnf builddep --nogpgcheck -y *.src.rpm - mkdir RPMS - - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_with_python3 1" --define "_with_ceph11 1" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" *.src.rpm + - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_with_python3 1" --define "_with_xrdclhttp 1" --define "_with_ceph11 1" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" *.src.rpm - cd .. - - mkdir fc-35/ - - cp packaging/*.src.rpm fc-35 - - cp packaging/RPMS/* fc-35 + - mkdir fc-37/ + - cp packaging/*.src.rpm fc-37 + - cp packaging/RPMS/* fc-37 artifacts: expire_in: 1 day paths: - - fc-35/ + - fc-37/ tags: - docker_node only: @@ -203,10 +175,11 @@ build:fedora-35: - tags - schedules - web + allow_failure: true -build:fedora-34: +build:fedora-38: stage: build:rpm - image: fedora:34 + image: fedora:38 script: - dnf install --nogpg -y gcc-c++ rpm-build tar dnf-plugins-core git - cd packaging/ @@ -215,13 +188,13 @@ build:fedora-34: - mkdir RPMS - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_with_python3 1" --define "_with_xrdclhttp 1" --define "_with_ceph11 1" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" *.src.rpm - cd .. - - mkdir fc-34/ - - cp packaging/*.src.rpm fc-34 - - cp packaging/RPMS/* fc-34 + - mkdir fc-38/ + - cp packaging/*.src.rpm fc-38 + - cp packaging/RPMS/* fc-38 artifacts: expire_in: 1 day paths: - - fc-34/ + - fc-38/ tags: - docker_node only: @@ -231,10 +204,11 @@ build:fedora-34: - tags - schedules - web + allow_failure: true -build:fedora-36: +build:fedora-39: stage: build:rpm - image: fedora:36 + image: fedora:39 script: - dnf install --nogpg -y gcc-c++ rpm-build tar dnf-plugins-core git - cd packaging/ @@ -243,13 +217,13 @@ build:fedora-36: - mkdir RPMS - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_with_python3 1" --define "_with_xrdclhttp 1" --define "_with_ceph11 1" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" *.src.rpm - cd .. - - mkdir fc-36/ - - cp packaging/*.src.rpm fc-36 - - cp packaging/RPMS/* fc-36 + - mkdir fc-39/ + - cp packaging/*.src.rpm fc-39 + - cp packaging/RPMS/* fc-39 artifacts: expire_in: 1 day paths: - - fc-36/ + - fc-39/ tags: - docker_node only: @@ -651,7 +625,7 @@ publish:rhel: script: - yum install --nogpg -y sssd-client sudo createrepo - prefix=/eos/project/s/storage-ci/www/xrootd - - "for platform in cs-8 cc-7 fc-35 fc-34; do + - "for platform in cs-8 cc-7 fc-{37..39}; do repo=$prefix/${CI_COMMIT_REF_NAME}/$platform/x86_64 path=$repo/$(date +'%Y%m%d'); sudo -u stci -H mkdir -p $path; From d90303c4e7306bf329cb3f1f16cc254bcea33678 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 7 Jun 2023 15:42:41 +0200 Subject: [PATCH 135/442] [CI] Remove macOS build from GitLab CI The build node which was running this job is no longer available. --- .gitlab-ci.yml | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ea654c51e81..abd73cda362 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -262,36 +262,6 @@ build:deb_ubuntu_jammy: - web allow_failure: true -build:macosx: - stage: build:rpm - script: - - mkdir build - - mkdir -p tarball/xrootd - - cd build - - cmake -DCMAKE_PREFIX_PATH='/usr/local/opt/zlib;/usr/local/opt/openssl' -DCMAKE_INSTALL_PREFIX=../tarball/xrootd .. - - cd src/XrdCl/ - - make -j4 - - make install - - cd ../../../tarball - - tar -zcf xrootd.tar.gz xrootd - - cd .. - - mkdir osx - - cp tarball/xrootd.tar.gz osx - artifacts: - expire_in: 1 day - paths: - - osx/ - tags: - - macosx-shell - only: - - master - - /^stable-.*$/ - except: - - tags - - schedules - - web - allow_failure: true - release:cs8-x86_64: stage: build:rpm image: gitlab-registry.cern.ch/linuxsupport/cs8-base From a3ee80593c0c6457a7715c4bafef83dbee10c30e Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 7 Jun 2023 15:49:46 +0200 Subject: [PATCH 136/442] [RPM] Dot not use git snapshot versions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fedora packaging guidelines mentions snapshot versions¹, but these are not well supported yet, so disable for now. 1. https://docs.fedoraproject.org/en-US/packaging-guidelines/Versioning/#_snapshots --- docker/xrd-docker | 2 +- packaging/makesrpm.sh | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/docker/xrd-docker b/docker/xrd-docker index 0108bcf1343..41563620b24 100755 --- a/docker/xrd-docker +++ b/docker/xrd-docker @@ -65,7 +65,7 @@ package() { # sanitize version name to work with RPMs VERSION=${VERSION#v} # remove "v" prefix VERSION=${VERSION/-rc/~rc} # release candidates use ~ in RPMs - VERSION=${VERSION/-g/^$(date +%Y%m%d)git} # snapshots use a caret + VERSION=${VERSION/-g*/} # snapshots not supported well, filter out VERSION=${VERSION/-/.post} # handle git describe for post releases VERSION=${VERSION//-/.} # replace remaining dashes with dots diff --git a/packaging/makesrpm.sh b/packaging/makesrpm.sh index 13ece04aea6..f3af058f0e1 100755 --- a/packaging/makesrpm.sh +++ b/packaging/makesrpm.sh @@ -127,13 +127,10 @@ echo "[i] Working with version: $VERSION" VERSION=${VERSION#v} # remove "v" prefix VERSION=${VERSION/-rc/~rc} # release candidates use ~ in RPMs -VERSION=${VERSION/-g/^$(date +%Y%m%d)git} # snapshots use a caret +VERSION=${VERSION/-g*/} # snapshots versions not supported well, filter out VERSION=${VERSION/-/.post} # handle git describe for post releases VERSION=${VERSION//-/.} # replace remaining dashes with dots -# CentOS 7 cannot handle snapshot versions, filter out -[[ `rpm -E '%{dist}'` =~ el7 ]] && VERSION=${VERSION/^*/} - echo "[i] RPM compliant version: $VERSION-$RELEASE" #------------------------------------------------------------------------------- From 34010513bebca90d4b8c4b945a3807a78e7158b8 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 7 Jun 2023 16:09:28 +0200 Subject: [PATCH 137/442] [RPM] Unset hardened build flags for Python bindings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is needed to avoid adding some hardening flags¹ to the compilation of Python modules which end up breaking CMake². 1. https://fedoraproject.org/wiki/Changes/Python_Extension_Flags#The_Problem 2. https://gitlab.kitware.com/cmake/cmake/-/issues/24980 --- packaging/rhel/xrootd.spec.in | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packaging/rhel/xrootd.spec.in b/packaging/rhel/xrootd.spec.in index 7a229600311..449de06a38c 100644 --- a/packaging/rhel/xrootd.spec.in +++ b/packaging/rhel/xrootd.spec.in @@ -574,6 +574,8 @@ popd popd %endif +%undefine _hardened_build + pushd build/bindings/python # build python3 bindings %if %{_with_python2} From 564f0b2b21933585b9c36e3e87f880e336cb660a Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Thu, 8 Jun 2023 18:06:01 -0700 Subject: [PATCH 138/442] [Server] Allow generic prepare plug-in to handle large responses, fixes #2023 --- src/XrdOfs/XrdOfsPrepGPI.cc | 45 +++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/src/XrdOfs/XrdOfsPrepGPI.cc b/src/XrdOfs/XrdOfsPrepGPI.cc index 2b9b9a1d251..c40f7feede2 100644 --- a/src/XrdOfs/XrdOfsPrepGPI.cc +++ b/src/XrdOfs/XrdOfsPrepGPI.cc @@ -13,6 +13,7 @@ #include "XrdOss/XrdOss.hh" #include "XrdOuc/XrdOuca2x.hh" +#include "XrdOuc/XrdOucBuffer.hh" #include "XrdOuc/XrdOucEnv.hh" #include "XrdOuc/XrdOucErrInfo.hh" #include "XrdOuc/XrdOucGatherConf.hh" @@ -51,6 +52,8 @@ namespace XrdOfsPrepGPIReal { XrdSysMutex gpiMutex; +XrdOucBuffPool *bPool = 0;; + XrdOss *ossP = 0; XrdScheduler *schedP = 0; XrdSysError *eLog = 0; @@ -62,6 +65,7 @@ int qryWait = 0; // Ditto static const int qryMaxWT = 33; // Maximum wait time int maxFiles = 48; +int maxResp = XrdOucEI::Max_Error_Len; bool addCGI = false; bool Debug = false; @@ -619,6 +623,10 @@ int PrepGPI::query( XrdSfsPrep &pargs, const XrdSecEntity *client) { EPNAME("Query"); + struct OucBuffer {XrdOucBuffer *pBuff; + OucBuffer() : pBuff(0) {} + ~OucBuffer() {if (pBuff) pBuff->Recycle();} + } OucBuff; const char *tid = (client ? client->tident : "anon"); int rc, bL; char *bP = eInfo.getMsgBuff(bL); @@ -629,14 +637,24 @@ int PrepGPI::query( XrdSfsPrep &pargs, if (!(okReq & okQuery)) {PrepRequest *rPP, *rP; if (reqFind(pargs.reqid, rPP, rP)) - {bL = snprintf(bP, bL, "Request %s queued.", pargs.reqid); + {bL = snprintf(bP, bL, "Request %s queued.", pargs.reqid)+1; } else { - bL = snprintf(bP, bL, "Request %s not queued.", pargs.reqid); + bL = snprintf(bP, bL, "Request %s not queued.", pargs.reqid)+1; } eInfo.setErrCode(bL); return SFS_DATA; } +// Allocate a buffer if need be +// + if (bPool) + {OucBuff.pBuff = bPool->Alloc(maxResp); + if (OucBuff.pBuff) + {bP = OucBuff.pBuff->Buffer(); + bL = maxResp; + } + } + // Get a request request object // PrepRequest *rP = Assemble(rc, tid, "query", pargs, ""); @@ -679,7 +697,11 @@ int PrepGPI::query( XrdSfsPrep &pargs, // Return response // - eInfo.setErrCode(rc); + if (!OucBuff.pBuff) eInfo.setErrCode(rc); + else {OucBuff.pBuff->SetLen(rc); + eInfo.setErrInfo(rc, OucBuff.pBuff); + OucBuff.pBuff = 0; + } return SFS_DATA; } } @@ -789,7 +811,7 @@ int PrepGPI::Xeq(PrepRequest *rP) /******************************************************************************/ // Parameters: -admit [-cgi] [-maxfiles [-maxreq ] -// [-maxquery ] [-pfn] -run +// [-maxquery ] [-maxresp ] [-pfn] -run // // : cancel | evict | prep | query | stage // : [,] @@ -878,6 +900,16 @@ XrdOfsPrepare *XrdOfsgetPrepare(XrdOfsgetPrepareArguments) if (XrdOuca2x::a2i(*eLog, "PrepPGI -maxreq", tokP, &maxReq, 1, 64)) return 0; } + else if (Token == "-maxresp") + {if (!(tokP = gpiConf.GetToken()) || *tokP == '-') + {eLog->Emsg("PrepGPI", "-maxresp argument not specified."); + return 0; + } + long long rspsz; + if (XrdOuca2x::a2sz(*eLog, "PrepPGI -maxresp", tokP, + &rspsz, 2048, 16777216)) return 0; + maxResp = static_cast(rspsz); + } else if (Token == "-pfn") usePFN = true; else if (Token == "-run") {if (!(tokP = gpiConf.GetToken()) || *tokP == '-') @@ -905,6 +937,11 @@ XrdOfsPrepare *XrdOfsgetPrepare(XrdOfsgetPrepareArguments) return 0; } +// Create a buffer pool for query responses if we need to +// + if (maxResp > (int)XrdOucEI::Max_Error_Len) + bPool = new XrdOucBuffPool(maxResp, maxResp); + // Set final debug flags // if (!Debug) Debug = getenv("XRDDEBUG") != 0; From 2b42eba419be1b1d427ae60167776a43c260fe05 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Thu, 8 Jun 2023 18:08:38 -0700 Subject: [PATCH 139/442] Update notes on generic prepare plugin enhancement. --- docs/PreReleaseNotes.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/PreReleaseNotes.txt b/docs/PreReleaseNotes.txt index 21053d0a9fb..707404c6df2 100644 --- a/docs/PreReleaseNotes.txt +++ b/docs/PreReleaseNotes.txt @@ -1,4 +1,6 @@ ====== + + XRootD ====== @@ -6,6 +8,8 @@ Prerelease Notes ================ + **New Features** + **[Server]** Allow generic prepare plug-in to handle large responses, fixes #2023 + **Commit: 564f0b2 **[Server]** Make maxfd be configurable (default is 256k). **Commit: 937b5ee e1ba7a2 **[Client]** Add xrdfs cache subcommand to allow for cache evictions. From f0d13be865e1950d5cca90519988bdcb2008a74d Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 12 Jun 2023 14:06:09 +0200 Subject: [PATCH 140/442] [XrdSecGsi] Use SHA256 as default message digest algorithm --- src/XrdSecgsi/XrdSecProtocolgsi.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdSecgsi/XrdSecProtocolgsi.cc b/src/XrdSecgsi/XrdSecProtocolgsi.cc index 52d80a660b2..b87929401af 100644 --- a/src/XrdSecgsi/XrdSecProtocolgsi.cc +++ b/src/XrdSecgsi/XrdSecProtocolgsi.cc @@ -158,7 +158,7 @@ int XrdSecProtocolgsi::GMAPOpt = 1; bool XrdSecProtocolgsi::GMAPuseDNname = 0; String XrdSecProtocolgsi::DefCrypto= "ssl"; String XrdSecProtocolgsi::DefCipher= "aes-128-cbc:bf-cbc:des-ede3-cbc"; -String XrdSecProtocolgsi::DefMD = "sha1:md5"; +String XrdSecProtocolgsi::DefMD = "sha256"; String XrdSecProtocolgsi::DefError = "invalid credentials "; int XrdSecProtocolgsi::PxyReqOpts = 0; int XrdSecProtocolgsi::AuthzPxyWhat = -1; From db6c96453fa46d74a9525c7ba3e17489a82e97f6 Mon Sep 17 00:00:00 2001 From: David Smith Date: Wed, 14 Jun 2023 14:46:41 +0200 Subject: [PATCH 141/442] [XrdClTests] Change PostMaster test to use the postmaster instance in the DefaultEnv --- tests/XrdClTests/PostMasterTest.cc | 90 ++++++++++++------------------ 1 file changed, 36 insertions(+), 54 deletions(-) diff --git a/tests/XrdClTests/PostMasterTest.cc b/tests/XrdClTests/PostMasterTest.cc index e8e4b02c337..e54a696c841 100644 --- a/tests/XrdClTests/PostMasterTest.cc +++ b/tests/XrdClTests/PostMasterTest.cc @@ -56,23 +56,22 @@ CPPUNIT_TEST_SUITE_REGISTRATION( PostMasterTest ); //------------------------------------------------------------------------------ namespace { - class PostMasterFinalizer + class PostMasterFetch { public: - PostMasterFinalizer( XrdCl::PostMaster *pm = 0 ): pPostMaster(pm) {} - ~PostMasterFinalizer() - { - if( pPostMaster ) - { - pPostMaster->Stop(); - pPostMaster->Finalize(); - } + PostMasterFetch() { } + ~PostMasterFetch() { } + XrdCl::PostMaster *Get() { + return XrdCl::DefaultEnv::GetPostMaster(); + } + XrdCl::PostMaster *Reset() { + XrdCl::PostMaster *pm = Get(); + pm->Stop(); + pm->Finalize(); + CPPUNIT_ASSERT( pm->Initialize() != 0 ); + CPPUNIT_ASSERT( pm->Start() != 0 ); + return pm; } - void Set( XrdCl::PostMaster *pm ) { pPostMaster = pm; } - XrdCl::PostMaster *Get() { return pPostMaster; } - - private: - XrdCl::PostMaster *pPostMaster; }; } @@ -285,17 +284,15 @@ void *TestThreadFunc( void *arg ) void PostMasterTest::ThreadingTest() { using namespace XrdCl; - PostMaster postMaster; - PostMasterFinalizer finalizer( &postMaster ); - postMaster.Initialize(); - postMaster.Start(); + PostMasterFetch pmfetch; + PostMaster *postMaster = pmfetch.Get(); pthread_t thread[100]; ArgHelper helper[100]; for( int i = 0; i < 100; ++i ) { - helper[i].pm = &postMaster; + helper[i].pm = postMaster; helper[i].index = i; pthread_create( &thread[i], 0, TestThreadFunc, &helper[i] ); } @@ -319,10 +316,8 @@ void PostMasterTest::FunctionalTest() env->PutInt( "TimeoutResolution", 1 ); env->PutInt( "ConnectionWindow", 15 ); - PostMaster postMaster; - PostMasterFinalizer finalizer( &postMaster ); - postMaster.Initialize(); - postMaster.Start(); + PostMasterFetch pmfetch; + PostMaster *postMaster = pmfetch.Get(); std::string address; CPPUNIT_ASSERT( testEnv->GetString( "MainServerURL", address ) ); @@ -348,7 +343,7 @@ void PostMasterTest::FunctionalTest() request->dlen = 0; XRootDTransport::MarshallRequest( &m1 ); - CPPUNIT_ASSERT_XRDST( postMaster.Send( host, &m1, &msgHandler1, false, expires ) ); + CPPUNIT_ASSERT_XRDST( postMaster->Send( host, &m1, &msgHandler1, false, expires ) ); CPPUNIT_ASSERT_XRDST( msgHandler1.WaitFor( m2 ) ); ServerResponse *resp = (ServerResponse *)m2.GetBuffer(); @@ -367,14 +362,14 @@ void PostMasterTest::FunctionalTest() msgHandler2.SetFilter( 1, 2 ); time_t shortexp = ::time(0) + 3; msgHandler2.SetExpiration( shortexp ); - CPPUNIT_ASSERT_XRDST( postMaster.Send( localhost1, &m1, &msgHandler2, false, + CPPUNIT_ASSERT_XRDST( postMaster->Send( localhost1, &m1, &msgHandler2, false, shortexp ) ); CPPUNIT_ASSERT_XRDST_NOTOK( msgHandler2.WaitFor( m2 ), errOperationExpired ); SyncMsgHandler msgHandler3; msgHandler3.SetFilter( 1, 2 ); msgHandler3.SetExpiration( expires ); - CPPUNIT_ASSERT_XRDST( postMaster.Send( localhost1, &m1, &msgHandler3, false, + CPPUNIT_ASSERT_XRDST( postMaster->Send( localhost1, &m1, &msgHandler3, false, expires ) ); CPPUNIT_ASSERT_XRDST_NOTOK( msgHandler3.WaitFor( m2 ), errConnectionError ); @@ -385,7 +380,7 @@ void PostMasterTest::FunctionalTest() Status st1, st2; const char *name = 0; - CPPUNIT_ASSERT_XRDST( postMaster.QueryTransport( host, + CPPUNIT_ASSERT_XRDST( postMaster->QueryTransport( host, TransportQuery::Name, nameObj ) ); nameObj.Get( name ); @@ -393,15 +388,11 @@ void PostMasterTest::FunctionalTest() CPPUNIT_ASSERT( name ); CPPUNIT_ASSERT( !::strcmp( name, "XRootD" ) ); - postMaster.Stop(); - postMaster.Finalize(); - //---------------------------------------------------------------------------- // Reinitialize and try to do something //---------------------------------------------------------------------------- env->PutInt( "LoadBalancerTTL", 5 ); - postMaster.Initialize(); - postMaster.Start(); + postMaster = pmfetch.Reset(); m2.Free(); m1.Zero(); @@ -416,7 +407,7 @@ void PostMasterTest::FunctionalTest() SyncMsgHandler msgHandler4; msgHandler4.SetFilter( 1, 2 ); msgHandler4.SetExpiration( expires ); - CPPUNIT_ASSERT_XRDST( postMaster.Send( host, &m1, &msgHandler4, false, expires ) ); + CPPUNIT_ASSERT_XRDST( postMaster->Send( host, &m1, &msgHandler4, false, expires ) ); CPPUNIT_ASSERT_XRDST( msgHandler4.WaitFor( m2 ) ); resp = (ServerResponse *)m2.GetBuffer(); @@ -432,7 +423,7 @@ void PostMasterTest::FunctionalTest() SyncMsgHandler msgHandler5; msgHandler5.SetFilter( 1, 2 ); msgHandler5.SetExpiration( expires ); - CPPUNIT_ASSERT_XRDST( postMaster.Send( host, &m1, &msgHandler5, false, expires ) ); + CPPUNIT_ASSERT_XRDST( postMaster->Send( host, &m1, &msgHandler5, false, expires ) ); CPPUNIT_ASSERT_XRDST( msgHandler5.WaitFor( m2 ) ); resp = (ServerResponse *)m2.GetBuffer(); @@ -452,9 +443,8 @@ void PostMasterTest::PingIPv6() //---------------------------------------------------------------------------- // Initialize the stuff //---------------------------------------------------------------------------- - PostMaster postMaster; - postMaster.Initialize(); - postMaster.Start(); + PostMasterFetch pmfetch; + PostMaster *postMaster = pmfetch.Get(); //---------------------------------------------------------------------------- // Build the message @@ -479,10 +469,10 @@ void PostMasterTest::PingIPv6() //---------------------------------------------------------------------------- // Send the message - localhost1 //---------------------------------------------------------------------------- - sc = postMaster.Send( localhost1, &m1, false, 1200 ); + sc = postMaster->Send( localhost1, &m1, false, 1200 ); CPPUNIT_ASSERT( sc.IsOK() ); - sc = postMaster.Receive( localhost1, m2, &f1, false, 1200 ); + sc = postMaster->Receive( localhost1, m2, &f1, false, 1200 ); CPPUNIT_ASSERT( sc.IsOK() ); ServerResponse *resp = (ServerResponse *)m2->GetBuffer(); CPPUNIT_ASSERT( resp != 0 ); @@ -492,21 +482,15 @@ void PostMasterTest::PingIPv6() //---------------------------------------------------------------------------- // Send the message - localhost2 //---------------------------------------------------------------------------- - sc = postMaster.Send( localhost2, &m1, false, 1200 ); + sc = postMaster->Send( localhost2, &m1, false, 1200 ); CPPUNIT_ASSERT( sc.IsOK() ); - sc = postMaster.Receive( localhost2, m2, &f1, 1200 ); + sc = postMaster->Receive( localhost2, m2, &f1, 1200 ); CPPUNIT_ASSERT( sc.IsOK() ); resp = (ServerResponse *)m2->GetBuffer(); CPPUNIT_ASSERT( resp != 0 ); CPPUNIT_ASSERT( resp->hdr.status == kXR_ok ); CPPUNIT_ASSERT( m2->GetSize() == 8 ); - - //---------------------------------------------------------------------------- - // Clean up - //---------------------------------------------------------------------------- - postMaster.Stop(); - postMaster.Finalize(); #endif } @@ -547,10 +531,8 @@ void PostMasterTest::MultiIPConnectionTest() env->PutInt( "TimeoutResolution", 1 ); env->PutInt( "ConnectionWindow", 5 ); - PostMaster postMaster; - PostMasterFinalizer finalizer( &postMaster ); - postMaster.Initialize(); - postMaster.Start(); + PostMasterFetch pmfetch; + PostMaster *postMaster = pmfetch.Get(); std::string address; CPPUNIT_ASSERT( testEnv->GetString( "MultiIPServerURL", address ) ); @@ -569,7 +551,7 @@ void PostMasterTest::MultiIPConnectionTest() msgHandler1.SetFilter( 1, 2 ); msgHandler1.SetExpiration( expires ); Message *m = CreatePing( 1, 2 ); - CPPUNIT_ASSERT_XRDST_NOTOK( postMaster.Send( url1, m, &msgHandler1, false, expires ), + CPPUNIT_ASSERT_XRDST_NOTOK( postMaster->Send( url1, m, &msgHandler1, false, expires ), errInvalidAddr ); //---------------------------------------------------------------------------- @@ -580,7 +562,7 @@ void PostMasterTest::MultiIPConnectionTest() msgHandler2.SetExpiration( expires ); Message m2; - CPPUNIT_ASSERT_XRDST( postMaster.Send( url2, m, &msgHandler2, false, expires ) ); + CPPUNIT_ASSERT_XRDST( postMaster->Send( url2, m, &msgHandler2, false, expires ) ); CPPUNIT_ASSERT_XRDST_NOTOK( msgHandler2.WaitFor( m2 ), errConnectionError ); //---------------------------------------------------------------------------- @@ -590,7 +572,7 @@ void PostMasterTest::MultiIPConnectionTest() msgHandler3.SetFilter( 1, 2 ); msgHandler3.SetExpiration( expires ); - CPPUNIT_ASSERT_XRDST( postMaster.Send( url3, m, &msgHandler3, false, expires ) ); + CPPUNIT_ASSERT_XRDST( postMaster->Send( url3, m, &msgHandler3, false, expires ) ); CPPUNIT_ASSERT_XRDST( msgHandler3.WaitFor( m2 ) ); ServerResponse *resp = (ServerResponse *)m2.GetBuffer(); CPPUNIT_ASSERT( resp != 0 ); From ff21f9b3f0ef44583d825a56da7a605d7f837635 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 14 Jun 2023 17:42:34 +0200 Subject: [PATCH 142/442] [Python] Fix regression introduced when fixing issue #2010 Fixes: #2038, a1cd57f08845dd71556219966af9381eace19be8 --- bindings/python/src/ChunkIterator.hh | 2 +- bindings/python/src/Conversions.hh | 6 +++--- bindings/python/src/PyXRootDFile.cc | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bindings/python/src/ChunkIterator.hh b/bindings/python/src/ChunkIterator.hh index 3770bb616ac..7fbe931bffd 100644 --- a/bindings/python/src/ChunkIterator.hh +++ b/bindings/python/src/ChunkIterator.hh @@ -98,7 +98,7 @@ namespace PyXRootD else { self->currentOffset += self->chunksize; - pychunk = PyUnicode_FromStringAndSize( (const char*) chunk->GetBuffer(), + pychunk = PyBytes_FromStringAndSize( (const char*) chunk->GetBuffer(), chunk->GetSize() ); } diff --git a/bindings/python/src/Conversions.hh b/bindings/python/src/Conversions.hh index a644a6eb23b..6c7d7353e0f 100644 --- a/bindings/python/src/Conversions.hh +++ b/bindings/python/src/Conversions.hh @@ -241,7 +241,7 @@ namespace PyXRootD { static PyObject* Convert( XrdCl::Buffer *buffer ) { - return PyUnicode_FromStringAndSize( buffer->GetBuffer(), buffer->GetSize() ); + return PyBytes_FromStringAndSize( buffer->GetBuffer(), buffer->GetSize() ); } }; @@ -249,7 +249,7 @@ namespace PyXRootD { static PyObject* Convert( XrdCl::ChunkInfo *chunk ) { - PyObject *o = PyUnicode_FromStringAndSize( (const char*)chunk->buffer, + PyObject *o = PyBytes_FromStringAndSize( (const char*)chunk->buffer, chunk->length ); delete[] (char*) chunk->buffer; return o; @@ -268,7 +268,7 @@ namespace PyXRootD for ( uint32_t i = 0; i < chunks.size(); ++i ) { XrdCl::ChunkInfo chunk = chunks.at( i ); - PyObject *buffer = PyUnicode_FromStringAndSize( (const char *) chunk.buffer, + PyObject *buffer = PyBytes_FromStringAndSize( (const char *) chunk.buffer, chunk.length ); delete[] (char*) chunk.buffer; diff --git a/bindings/python/src/PyXRootDFile.cc b/bindings/python/src/PyXRootDFile.cc index f6d9608b898..c366e7b1c30 100644 --- a/bindings/python/src/PyXRootDFile.cc +++ b/bindings/python/src/PyXRootDFile.cc @@ -195,7 +195,7 @@ namespace PyXRootD else { uint32_t bytesRead = 0; async( status = self->file->Read( offset, size, buffer, bytesRead, timeout ) ); - pyresponse = PyUnicode_FromStringAndSize( buffer, bytesRead ); + pyresponse = PyBytes_FromStringAndSize( buffer, bytesRead ); delete[] buffer; } From d80fc70f47a498fb34f5607cb2cde5763c78f6bc Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 12 Jun 2023 16:23:11 +0200 Subject: [PATCH 143/442] [XrdPosix] Do not compile XrdPosixPreload with link-time optimizations Fixes: #2032 --- src/XrdPosix.cmake | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/XrdPosix.cmake b/src/XrdPosix.cmake index df769110d45..e8db4bc2f99 100644 --- a/src/XrdPosix.cmake +++ b/src/XrdPosix.cmake @@ -70,6 +70,12 @@ set_target_properties( VERSION ${XRD_POSIX_PRELOAD_VERSION} SOVERSION ${XRD_POSIX_PRELOAD_SOVERSION} ) +# This is a special library meant to be loaded with LD_PRELOAD. +# It is meant to replace symbols from the system and as such +# must not be compiled with link-time optimizations. + +target_compile_options(XrdPosixPreload PRIVATE -fno-lto) + #------------------------------------------------------------------------------- # Install #------------------------------------------------------------------------------- From 8577e1fef61607b98ed15f78be6d165f64af20c9 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 13 Jun 2023 15:49:36 +0200 Subject: [PATCH 144/442] [XrdCl] Do not enforce TLS when --notlsok option is used --- src/XrdCl/XrdClXRootDTransport.cc | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/XrdCl/XrdClXRootDTransport.cc b/src/XrdCl/XrdClXRootDTransport.cc index f869327aa33..2fcaeb923db 100644 --- a/src/XrdCl/XrdClXRootDTransport.cc +++ b/src/XrdCl/XrdClXRootDTransport.cc @@ -1758,6 +1758,13 @@ namespace XrdCl XRootDChannelInfo *info = 0; channelData.Get( info ); + XrdCl::Env *env = XrdCl::DefaultEnv::GetEnv(); + int notlsok = DefaultNoTlsOK; + env->GetInt( "NoTlsOK", notlsok ); + + if( notlsok ) + return info->encrypted; + // Did the server instructed us to switch to TLS right away? if( info->serverFlags & kXR_gotoTLS ) { @@ -1894,8 +1901,10 @@ namespace XrdCl request->requestid = htons(kXR_protocol); request->clientpv = htonl(kXR_PROTOCOLVERSION); request->flags = ClientProtocolRequest::kXR_secreqs | - ClientProtocolRequest::kXR_bifreqs | - ClientProtocolRequest::kXR_ableTLS; + ClientProtocolRequest::kXR_bifreqs; + + if (info->encrypted) + request->flags |= ClientProtocolRequest::kXR_ableTLS; bool nodata = false; if( expect & ClientProtocolRequest::kXR_ExpBind ) From 96edcc1aa42495e1266664b05233384bfe16b364 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 13 Jun 2023 15:52:34 +0200 Subject: [PATCH 145/442] [XrdClTls] Improve error message on TLS context initialization failure --- src/XrdCl/XrdClTls.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdCl/XrdClTls.cc b/src/XrdCl/XrdClTls.cc index 115ee0a4d7d..3b66ff9f334 100644 --- a/src/XrdCl/XrdClTls.cc +++ b/src/XrdCl/XrdClTls.cc @@ -112,7 +112,7 @@ namespace XrdCl //---------------------------------------------------------------------- // we only need one instance of TLS //---------------------------------------------------------------------- - std::string emsg; + std::string emsg = "Failed to initialize TLS context"; static XrdTlsContext tlsContext( 0, 0, GetCaDir(), 0, 0, &emsg ); //---------------------------------------------------------------------- From 2c31fdbc9d45029c92c4091b863bc60c86b10a8d Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Fri, 16 Jun 2023 06:56:17 -0700 Subject: [PATCH 146/442] [Server] Use correct value for testing vector size. --- src/XrdXrootd/XrdXrootdXeq.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdXrootd/XrdXrootdXeq.cc b/src/XrdXrootd/XrdXrootdXeq.cc index f96d4d3ad9b..de82cd0cca2 100644 --- a/src/XrdXrootd/XrdXrootdXeq.cc +++ b/src/XrdXrootd/XrdXrootdXeq.cc @@ -2551,7 +2551,7 @@ int XrdXrootdProtocol::do_ReadV() // partial elements. // rdVecNum = rdVecLen / sizeof(readahead_list); - if ( (rdVecLen <= 0) || (rdVecNum*hdrSZ != rdVecLen) ) + if ( (rdVecNum <= 0) || (rdVecNum*hdrSZ != rdVecLen) ) return Response.Send(kXR_ArgInvalid, "Read vector is invalid"); // Make sure that we can copy the read vector to our local stack. We must impose From 971061c4f0774bae0cfeec72c40d54de3d12743c Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Fri, 16 Jun 2023 06:58:39 -0700 Subject: [PATCH 147/442] Update notes on readv vector size test correction. --- docs/PreReleaseNotes.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/PreReleaseNotes.txt b/docs/PreReleaseNotes.txt index 707404c6df2..f1a3e9e894e 100644 --- a/docs/PreReleaseNotes.txt +++ b/docs/PreReleaseNotes.txt @@ -26,6 +26,8 @@ Prerelease Notes + **Major bug fixes** + **Minor bug fixes** + **[Server]** Use correct value for testing vector size. + **Commit: 2c31fdb **[TLS]** Make sure context is marked invalid if not properly constructed. **Commit: c6928f0 From 14f7aca2ddb7247402a9d758d85a0e166759f41e Mon Sep 17 00:00:00 2001 From: David Smith Date: Fri, 16 Jun 2023 16:34:36 +0200 Subject: [PATCH 148/442] [XrdCl] Failure count detection fix in declarative API for Parallel() operation --- src/XrdCl/XrdClParallelOperation.hh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdCl/XrdClParallelOperation.hh b/src/XrdCl/XrdClParallelOperation.hh index 6736fefbaf0..dd38c56d46b 100644 --- a/src/XrdCl/XrdClParallelOperation.hh +++ b/src/XrdCl/XrdClParallelOperation.hh @@ -313,7 +313,7 @@ namespace XrdCl // although we might have the minimum to succeed we wait for the rest if( status.IsOK() ) return ( pending == 0 ); size_t nb = failed_cnt.fetch_add( 1, std::memory_order_relaxed ); - if( nb + 1 == failed_threshold ) res = status; // we dropped below the threshold + if( nb == failed_threshold ) res = status; // we dropped below the threshold // if we still have to wait for pending operations return false, // otherwise all is done, return true return ( pending == 0 ); From c19f3c10c97bfec7eac17634a8d081b6d7f0a01b Mon Sep 17 00:00:00 2001 From: Atri Bhattacharya Date: Tue, 13 Jun 2023 23:09:00 +0530 Subject: [PATCH 149/442] Add sandboxing settings to systemd service files. This is a first attempt to implement stronger security settings in systemd service files. The sandboxing settings added with this commit are commented out until they can receive more testing, as discussed in issue #2033. --- packaging/common/cmsd@.service | 8 ++++++++ packaging/common/frm_purged@.service | 8 ++++++++ packaging/common/frm_xfrd@.service | 8 ++++++++ packaging/common/xrootd@.service | 8 ++++++++ 4 files changed, 32 insertions(+) diff --git a/packaging/common/cmsd@.service b/packaging/common/cmsd@.service index 76d2d6b679b..f69e0e54d5d 100644 --- a/packaging/common/cmsd@.service +++ b/packaging/common/cmsd@.service @@ -6,6 +6,14 @@ Requires=network-online.target After=network-online.target [Service] +#PrivateDevices=true +#ProtectHostname=true +#ProtectClock=true +#ProtectKernelTunables=true +#ProtectKernelModules=true +#ProtectKernelLogs=true +#ProtectControlGroups=true +#RestrictRealtime=true ExecStart=/usr/bin/cmsd -l /var/log/xrootd/cmsd.log -c /etc/xrootd/xrootd-%i.cfg -k fifo -s /run/xrootd/cmsd-%i.pid -n %i User=xrootd Group=xrootd diff --git a/packaging/common/frm_purged@.service b/packaging/common/frm_purged@.service index 375e41aff3a..942dbf5adb2 100644 --- a/packaging/common/frm_purged@.service +++ b/packaging/common/frm_purged@.service @@ -6,6 +6,14 @@ Requires=network-online.target After=network-online.target [Service] +#PrivateDevices=true +#ProtectHostname=true +#ProtectClock=true +#ProtectKernelTunables=true +#ProtectKernelModules=true +#ProtectKernelLogs=true +#ProtectControlGroups=true +#RestrictRealtime=true ExecStart=/usr/bin/frm_purged -l /var/log/xrootd/frm_purged.log -c /etc/xrootd/xrootd-%i.cfg -k fifo -s /run/xrootd/frm_purged-%i.pid -n %i User=xrootd Group=xrootd diff --git a/packaging/common/frm_xfrd@.service b/packaging/common/frm_xfrd@.service index 0106ac061b5..cfc580db2f2 100644 --- a/packaging/common/frm_xfrd@.service +++ b/packaging/common/frm_xfrd@.service @@ -6,6 +6,14 @@ Requires=network-online.target After=network-online.target [Service] +#PrivateDevices=true +#ProtectHostname=true +#ProtectClock=true +#ProtectKernelTunables=true +#ProtectKernelModules=true +#ProtectKernelLogs=true +#ProtectControlGroups=true +#RestrictRealtime=true ExecStart=/usr/bin/frm_xfrd -l /var/log/xrootd/frm_xfrd.log -c /etc/xrootd/xrootd-%i.cfg -k fifo -s /run/xrootd/frm_xfrd-%i.pid -n %i User=xrootd Group=xrootd diff --git a/packaging/common/xrootd@.service b/packaging/common/xrootd@.service index c8c1279893e..1c8284c9c89 100644 --- a/packaging/common/xrootd@.service +++ b/packaging/common/xrootd@.service @@ -6,6 +6,14 @@ Requires=network-online.target After=network-online.target [Service] +#PrivateDevices=true +#ProtectHostname=true +#ProtectClock=true +#ProtectKernelTunables=true +#ProtectKernelModules=true +#ProtectKernelLogs=true +#ProtectControlGroups=true +#RestrictRealtime=true ExecStart=/usr/bin/xrootd -l /var/log/xrootd/xrootd.log -c /etc/xrootd/xrootd-%i.cfg -k fifo -s /run/xrootd/xrootd-%i.pid -n %i User=xrootd Group=xrootd From a06c6357caf5cb7ade152794ffa16185c9d8c66a Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Mon, 19 Jun 2023 07:21:44 -0700 Subject: [PATCH 150/442] [Apps] Make xrdcrc32c consistent with xrdadler32. Fixes #2045 --- src/XrdApps/XrdCrc32c.cc | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/XrdApps/XrdCrc32c.cc b/src/XrdApps/XrdCrc32c.cc index 2c5fabb111b..0b5f26de86d 100644 --- a/src/XrdApps/XrdCrc32c.cc +++ b/src/XrdApps/XrdCrc32c.cc @@ -71,7 +71,7 @@ void Fatal(const char *op, const char *target) void Usage(int rc) { - std::cerr <<"\nUsage: xrdcrc32c [opts] { | -}\n" + std::cerr <<"\nUsage: xrdcrc32c [opts] [ | -]\n" "\n the path to the file whose checksum if to be computed." "\n- compute checksum from data presented at standard in;" "\n example: xrdcp - | xrdcrc32c -\n" @@ -123,14 +123,9 @@ int main(int argc, char *argv[]) } } -// Make sure a path has been specified -// - if (optind >= argc) - {std::cerr < Date: Mon, 19 Jun 2023 07:24:46 -0700 Subject: [PATCH 151/442] Update notes on xrdcrc32c compability patch. --- docs/PreReleaseNotes.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/PreReleaseNotes.txt b/docs/PreReleaseNotes.txt index f1a3e9e894e..e6895293391 100644 --- a/docs/PreReleaseNotes.txt +++ b/docs/PreReleaseNotes.txt @@ -32,5 +32,7 @@ Prerelease Notes **Commit: c6928f0 + **Miscellaneous** + **[Apps]** Make xrdcrc32c consistent with xrdadler32. Fixes #204 + **Commit: a06c635 **[Server]** Also check for IPv6 ULA's to determine if an address is private. **Commit: cd970d5 From 294e7219c3d72b577965a86d67c7285706e03ed6 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 15 Jun 2023 11:27:46 +0200 Subject: [PATCH 152/442] [Tests] CppUnit tests fail if run in parallel This is because some of them may find a port unavailable as port numbers are fixed in the tests to 9999. --- tests/XrdClTests/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/XrdClTests/CMakeLists.txt b/tests/XrdClTests/CMakeLists.txt index d8094c7cbcf..238e2509899 100644 --- a/tests/XrdClTests/CMakeLists.txt +++ b/tests/XrdClTests/CMakeLists.txt @@ -60,6 +60,7 @@ foreach(TEST_SUITE ) add_test(NAME XrdCl::${TEST_SUITE} COMMAND $ $ "All Tests/${TEST_SUITE}Test") + set_tests_properties(XrdCl::${TEST_SUITE} PROPERTIES RUN_SERIAL TRUE) endforeach() #------------------------------------------------------------------------------- From 196d7c2151ffb492fff8fcfbf8491362a6ee686b Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 15 Jun 2023 14:58:53 +0200 Subject: [PATCH 153/442] [XrdEcTests] Use different directory name for each test This is necessary to avoid test failures when running tests in parallel, as they'd be trying to overwrite each other's data in that case, and also failing tests do not clean up after themselves, so the following tests all fail if a given test fails. --- tests/XrdEcTests/MicroTest.cc | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tests/XrdEcTests/MicroTest.cc b/tests/XrdEcTests/MicroTest.cc index 7668e4c6a66..d02743351b9 100644 --- a/tests/XrdEcTests/MicroTest.cc +++ b/tests/XrdEcTests/MicroTest.cc @@ -280,13 +280,10 @@ void MicroTest::Init( bool usecrc32c ) objcfg.reset( new ObjCfg( "test.txt", nbdata, nbparity, chsize, usecrc32c, true ) ); rawdata.clear(); - char cwdbuff[1024]; - char *cwdptr = getcwd( cwdbuff, sizeof( cwdbuff ) ); - CPPUNIT_ASSERT( cwdptr ); - std::string cwd = cwdptr; + char tmpdir[32] = "/tmp/xrootd-xrdec-XXXXXX"; // create the data directory - datadir = cwd + "/data"; - CPPUNIT_ASSERT( mkdir( datadir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH ) == 0 ); + CPPUNIT_ASSERT( mkdtemp(tmpdir) ); + datadir = tmpdir; // create a directory for each stripe size_t nbstrps = objcfg->nbdata + 2 * objcfg->nbparity; for( size_t i = 0; i < nbstrps; ++i ) From ecfa1bb5eac975257773a1388d339425e7fb941e Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 15 Jun 2023 14:20:25 +0200 Subject: [PATCH 154/442] [Tests] Add XrdEc tests to main test target --- tests/XrdEcTests/CMakeLists.txt | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/XrdEcTests/CMakeLists.txt b/tests/XrdEcTests/CMakeLists.txt index eaa8317ddda..753c30bc730 100644 --- a/tests/XrdEcTests/CMakeLists.txt +++ b/tests/XrdEcTests/CMakeLists.txt @@ -12,6 +12,25 @@ target_link_libraries( target_link_libraries(XrdEcTests PRIVATE ${ISAL_LIBRARIES}) target_include_directories(XrdEcTests PRIVATE ../common ${CPPUNIT_UNCLUDE_DIRS} ${ISAL_INCLUDE_DIRS}) +foreach(TEST + AlignedWriteTest + SmallWriteTest + BigWriteTest + VectorReadTest + IllegalVectorReadTest + AlignedWrite1MissingTest + AlignedWrite2MissingTest + AlignedWriteTestIsalCrcNoMt + SmallWriteTestIsalCrcNoMt + BigWriteTestIsalCrcNoMt + AlignedWrite1MissingTestIsalCrcNoMt + AlignedWrite2MissingTestIsalCrcNoMt) + add_test(NAME XrdEc::${TEST} + COMMAND $ $ + "All Tests/MicroTest/MicroTest::${TEST}" + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) +endforeach() + #------------------------------------------------------------------------------- # Install #------------------------------------------------------------------------------- From 6a4e19acb9ed264e96398f284adc64dcda2878fd Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 16 Jun 2023 16:10:23 +0200 Subject: [PATCH 155/442] [CMake] Add -fprofile-update=atomic to coverage flags This is to avoid corruption when running multi-threaded tests. See also https://gcc.gnu.org/bugzilla/show_bug.cgi?id=68080. --- test.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test.cmake b/test.cmake index ca12a1f90a2..b35d4ed8694 100644 --- a/test.cmake +++ b/test.cmake @@ -106,7 +106,7 @@ set(CMAKE_ARGS $ENV{CMAKE_ARGS} ${CMAKE_ARGS}) if(COVERAGE) find_program(CTEST_COVERAGE_COMMAND NAMES gcov) - list(PREPEND CMAKE_ARGS "-DCMAKE_CXX_FLAGS=--coverage") + list(PREPEND CMAKE_ARGS "-DCMAKE_CXX_FLAGS=--coverage -fprofile-update=atomic") endif() if(MEMCHECK) From cc0c87544a4d80078e09082ed65d7085352dfddd Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 16 Jun 2023 17:39:08 +0200 Subject: [PATCH 156/442] [Tests] Add a simple set of smoke tests for server+client --- tests/CMakeLists.txt | 2 + tests/XRootD/CMakeLists.txt | 33 ++++++++++++ tests/XRootD/smoke.sh | 103 ++++++++++++++++++++++++++++++++++++ tests/XRootD/xrootd.cfg | 8 +++ 4 files changed, 146 insertions(+) create mode 100644 tests/XRootD/CMakeLists.txt create mode 100755 tests/XRootD/smoke.sh create mode 100644 tests/XRootD/xrootd.cfg diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d26a902851f..880c0f1a964 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -13,3 +13,5 @@ endif() if( BUILD_CEPH ) add_subdirectory( XrdCephTests ) endif() + +add_subdirectory(XRootD) diff --git a/tests/XRootD/CMakeLists.txt b/tests/XRootD/CMakeLists.txt new file mode 100644 index 00000000000..f25d24211d1 --- /dev/null +++ b/tests/XRootD/CMakeLists.txt @@ -0,0 +1,33 @@ +if(XRDCL_ONLY) + return() +endif() + +execute_process(COMMAND id -u OUTPUT_VARIABLE UID OUTPUT_STRIP_TRAILING_WHITESPACE) + +if (UID EQUAL 0) + return() +endif() + +set(XRD_TEST_PORT "10940" CACHE STRING "Port for XRootD Test Server") + +list(APPEND XRDENV "XRDCP=$") +list(APPEND XRDENV "XRDFS=$") +list(APPEND XRDENV "CRC32C=$") +list(APPEND XRDENV "ADLER32=$") +list(APPEND XRDENV "HOST=root://localhost:${XRD_TEST_PORT}") + +configure_file(xrootd.cfg xrootd.cfg @ONLY) + +add_test(NAME XRootD::start + COMMAND sh -c "mkdir -p data && \ + $ -b -k fifo -l xrootd.log -s xrootd.pid -c xrootd.cfg") +set_tests_properties(XRootD::start PROPERTIES FIXTURES_SETUP XRootD) + +add_test(NAME XRootD::stop COMMAND sh -c "sleep 1 && rm -rf data && kill $(< xrootd.pid)") +set_tests_properties(XRootD::stop PROPERTIES FIXTURES_CLEANUP XRootD) + +add_test(NAME XRootD::smoke-test + COMMAND sh -c "${CMAKE_CURRENT_SOURCE_DIR}/smoke.sh") + +set_tests_properties(XRootD::smoke-test PROPERTIES + ENVIRONMENT "${XRDENV}" FIXTURES_REQUIRED XRootD) diff --git a/tests/XRootD/smoke.sh b/tests/XRootD/smoke.sh new file mode 100755 index 00000000000..ed2f3bfba33 --- /dev/null +++ b/tests/XRootD/smoke.sh @@ -0,0 +1,103 @@ +#!/usr/bin/env bash + +: ${ADLER32:=$(command -v xrdadler32)} +: ${CRC32C:=$(command -v xrdcrc32c)} +: ${XRDCP:=$(command -v xrdcp)} +: ${XRDFS:=$(command -v xrdfs)} +: ${OPENSSL:=$(command -v openssl)} +: ${HOST:=root://localhost:${PORT:-1094}} + +for PROG in ${ADLER32} ${CRC32C} ${XRDCP} ${XRDFS} ${OPENSSL}; do + if [[ ! -x "${PROG}" ]]; then + echo 1>&2 "$(basename $0): error: '${PROG}': command not found" + exit 1 + fi +done + +# This script assumes that ${HOST} exports an empty / as read/write. +# It also assumes that any authentication required is already setup. + +set -e + +${XRDCP} --version +${XRDFS} ${HOST} query config version + +# query some common server configurations + +CONFIG_PARAMS=( version role sitename ) + +for PARAM in ${CONFIG_PARAMS[@]}; do + ${XRDFS} ${HOST} query config ${PARAM} +done + +# some extra query commands that don't make any changes + +${XRDFS} ${HOST} stat / +${XRDFS} ${HOST} statvfs / +${XRDFS} ${HOST} spaceinfo / + +# create local temporary directory +TMPDIR=$(mktemp -d /tmp/xrdfs-test-XXXXXX) + +# cleanup after ourselves if something fails +trap "rm -rf ${TMPDIR}" EXIT + +# create remote temporary directory +# this will get cleaned up by CMake upon fixture tear down +${XRDFS} ${HOST} mkdir -p ${TMPDIR} + +# create local files with random contents using OpenSSL + +FILES=$(seq -w 1 ${NFILES:-10}) + +for i in $FILES; do + ${OPENSSL} rand -out "${TMPDIR}/${i}.ref" $((1024 * $RANDOM)) +done + +# upload local files to the server in parallel + +for i in $FILES; do + ${XRDCP} ${TMPDIR}/${i}.ref ${HOST}/${TMPDIR}/${i}.ref +done + +# list uploaded files, then download them to check for corruption + +${XRDFS} ${HOST} ls -l ${TMPDIR} + +for i in $FILES; do + ${XRDCP} ${HOST}/${TMPDIR}/${i}.ref ${TMPDIR}/${i}.dat +done + +# check that all checksums for downloaded files match + +for i in $FILES; do + REF32C=$(${CRC32C} < ${TMPDIR}/${i}.ref | cut -d' ' -f1) + NEW32C=$(${CRC32C} < ${TMPDIR}/${i}.dat | cut -d' ' -f1) + SRV32C=$(${XRDFS} ${HOST} query checksum ${TMPDIR}/${i}.ref?cks.type=crc32c | cut -d' ' -f2) + + REFA32=$(${ADLER32} < ${TMPDIR}/${i}.ref | cut -d' ' -f1) + NEWA32=$(${ADLER32} < ${TMPDIR}/${i}.dat | cut -d' ' -f1) + SRVA32=$(${XRDFS} ${HOST} query checksum ${TMPDIR}/${i}.ref?cks.type=adler32 | cut -d' ' -f2) + echo "${i}: crc32c: reference: ${REF32C}, server: ${SRV32C}, downloaded: ${REFA32}" + echo "${i}: adler32: reference: ${NEW32C}, server: ${SRVA32}, downloaded: ${NEWA32}" + + if [[ "${NEWA32}" != "${REFA32}" || "${SRVA32}" != "${REFA32}" ]]; then + echo 1>&2 "$(basename $0): error: adler32 checksum check failed for file: ${i}.dat" + exit 1 + fi + if [[ "${NEW32C}" != "${REF32C}" || "${SRV32C}" != "${REF32C}" ]]; then + echo 1>&2 "$(basename $0): error: crc32 checksum check failed for file: ${i}.dat" + exit 1 + fi +done + +for i in $FILES; do + ${XRDFS} ${HOST} rm ${TMPDIR}/${i}.ref & +done + +wait + +${XRDFS} ${HOST} rmdir ${TMPDIR} + +echo "ALL TESTS PASSED" +exit 0 diff --git a/tests/XRootD/xrootd.cfg b/tests/XRootD/xrootd.cfg new file mode 100644 index 00000000000..922179d5219 --- /dev/null +++ b/tests/XRootD/xrootd.cfg @@ -0,0 +1,8 @@ +# This minimal configuration file starts a standalone server +# that exports the data directory as / without authentication. + +all.export / +all.sitename XRootD +oss.localroot @CMAKE_CURRENT_BINARY_DIR@/data +xrd.port @XRD_TEST_PORT@ +xrootd.chksum chkcgi adler32 crc32c From 555cbf07a02b5687c7dfaf02857806e04c3df731 Mon Sep 17 00:00:00 2001 From: David Smith Date: Thu, 8 Jun 2023 16:19:40 +0200 Subject: [PATCH 157/442] [gsi] Fix some memory leaks when using Secgsi --- src/XrdCrypto/XrdCryptosslCipher.cc | 1 + src/XrdCrypto/XrdCryptosslRSA.cc | 7 +- src/XrdCrypto/XrdCryptosslgsiAux.cc | 193 ++++++++++++++++++---------- src/XrdSecgsi/XrdSecProtocolgsi.cc | 28 +++- src/XrdSecgsi/XrdSecProtocolgsi.hh | 14 +- 5 files changed, 165 insertions(+), 78 deletions(-) diff --git a/src/XrdCrypto/XrdCryptosslCipher.cc b/src/XrdCrypto/XrdCryptosslCipher.cc index a0f5de82f8b..cb282c7eaeb 100644 --- a/src/XrdCrypto/XrdCryptosslCipher.cc +++ b/src/XrdCrypto/XrdCryptosslCipher.cc @@ -902,6 +902,7 @@ bool XrdCryptosslCipher::Finalize(bool padded, EVP_PKEY_derive_set_peer(pkctx, peer); EVP_PKEY_derive(pkctx, (unsigned char *)ktmp, <mp); EVP_PKEY_CTX_free(pkctx); + EVP_PKEY_free(peer); if (ltmp > 0) { #if OPENSSL_VERSION_NUMBER < 0x10101000L if (padded) { diff --git a/src/XrdCrypto/XrdCryptosslRSA.cc b/src/XrdCrypto/XrdCryptosslRSA.cc index 13a48ccb933..54818139ac3 100644 --- a/src/XrdCrypto/XrdCryptosslRSA.cc +++ b/src/XrdCrypto/XrdCryptosslRSA.cc @@ -322,6 +322,8 @@ int XrdCryptosslRSA::ImportPrivate(const char *pri, int lpri) if (!fEVP) return -1; + + int rc = -1; prilen = -1; // Bio for exporting the pub key @@ -337,9 +339,10 @@ int XrdCryptosslRSA::ImportPrivate(const char *pri, int lpri) if (PEM_read_bio_PrivateKey(bpri, &fEVP, 0, 0)) { // Update status status = kComplete; - return 0; + rc = 0; } - return -1; + BIO_free(bpri); + return rc; } //_____________________________________________________________________________ diff --git a/src/XrdCrypto/XrdCryptosslgsiAux.cc b/src/XrdCrypto/XrdCryptosslgsiAux.cc index cef86cb1f0f..a88dbd900b8 100644 --- a/src/XrdCrypto/XrdCryptosslgsiAux.cc +++ b/src/XrdCrypto/XrdCryptosslgsiAux.cc @@ -42,6 +42,7 @@ #include #include #include +#include #include "XrdSut/XrdSutRndm.hh" #include "XrdCrypto/XrdCryptogsiX509Chain.hh" @@ -51,6 +52,26 @@ #include "XrdCrypto/XrdCryptosslX509.hh" #include "XrdCrypto/XrdCryptosslX509Req.hh" +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++// +// // +// type aliases to ease use of smart pointers with common ssl structures // +// // +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++// +static void stackOfX509ExtensionDelete(STACK_OF(X509_EXTENSION) *ske) { +#if OPENSSL_VERSION_NUMBER >= 0x10000000L + sk_X509_EXTENSION_pop_free(ske, X509_EXTENSION_free); +#else /* OPENSSL */ + sk_pop_free(ske, X509_EXTENSION_free); +#endif /* OPENSSL */ +} +using EVP_PKEY_ptr = std::unique_ptr; +using X509_ptr = std::unique_ptr; +using X509_NAME_ptr = std::unique_ptr; +using X509_REQ_ptr = std::unique_ptr; +using X509_EXTENSION_ptr = std::unique_ptr; +using PROXY_CERT_INFO_EXTENSION_ptr = std::unique_ptr; +using STACK_OF_X509_EXTENSION_ptr = std::unique_ptr; + //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++// // // // Extensions OID relevant for proxies // @@ -656,9 +677,20 @@ int XrdCryptosslX509CreateProxyReq(XrdCryptoX509 *xcpi, PRINT("EEC certificate has expired"); return -kErrPX_ExpiredEEC; } + + // These will be assigned dynamically allocated ssl structures later. + // They use type aliases for unique_ptr, to ease use of a smart pointer. + // + EVP_PKEY_ptr ekro(nullptr, &EVP_PKEY_free); + X509_EXTENSION_ptr ext(nullptr, &X509_EXTENSION_free); + X509_NAME_ptr psubj(nullptr, &X509_NAME_free); + X509_REQ_ptr xro(nullptr, &X509_REQ_free); + PROXY_CERT_INFO_EXTENSION_ptr pci(nullptr, &PROXY_CERT_INFO_EXTENSION_free); + STACK_OF_X509_EXTENSION_ptr esk(nullptr, &stackOfX509ExtensionDelete); + // // Create a new request - X509_REQ *xro = X509_REQ_new(); + xro.reset(X509_REQ_new()); if (!xro) { PRINT("cannot to create cert request"); return -kErrPX_NoResources; @@ -666,7 +698,10 @@ int XrdCryptosslX509CreateProxyReq(XrdCryptoX509 *xcpi, // // Use same num of bits as the signing certificate, but // less than 512 - int bits = EVP_PKEY_bits(X509_get_pubkey(xpi)); + ekro.reset(X509_get_pubkey(xpi)); + int bits = EVP_PKEY_bits(ekro.get()); + ekro = nullptr; + bits = (bits < 512) ? 512 : bits; // // Create the new PKI for the proxy (exponent 65537) @@ -676,7 +711,6 @@ int XrdCryptosslX509CreateProxyReq(XrdCryptoX509 *xcpi, return -kErrPX_GenerateKey; } BN_set_word(e, 0x10001); - EVP_PKEY *ekro = 0; EVP_PKEY_CTX *pkctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, 0); EVP_PKEY_keygen_init(pkctx); EVP_PKEY_CTX_set_rsa_keygen_bits(pkctx, bits); @@ -686,7 +720,11 @@ int XrdCryptosslX509CreateProxyReq(XrdCryptoX509 *xcpi, #else EVP_PKEY_CTX_set_rsa_keygen_pubexp(pkctx, e); #endif - EVP_PKEY_keygen(pkctx, &ekro); + { + EVP_PKEY *tmppk = nullptr; + EVP_PKEY_keygen(pkctx, &tmppk); + ekro.reset(tmppk); + } EVP_PKEY_CTX_free(pkctx); // // Set the key into the request @@ -694,7 +732,7 @@ int XrdCryptosslX509CreateProxyReq(XrdCryptoX509 *xcpi, PRINT("proxy key could not be generated - return"); return -kErrPX_GenerateKey; } - X509_REQ_set_pubkey(xro, ekro); + X509_REQ_set_pubkey(xro.get(), ekro.get()); // // Generate a serial number. Specification says that this *should* // unique, so we just draw an unsigned random integer @@ -704,16 +742,16 @@ int XrdCryptosslX509CreateProxyReq(XrdCryptoX509 *xcpi, // with is a random unsigned int used also as serial // number. // Duplicate user subject name - X509_NAME *psubj = X509_NAME_dup(X509_get_subject_name(xpi)); + psubj.reset(X509_NAME_dup(X509_get_subject_name(xpi))); if (xcro && *xcro && *((int *)(*xcro)) <= 10100) { // Delete existing proxy CN addition; for backward compatibility #if OPENSSL_VERSION_NUMBER >= 0x10000000L - int ne = X509_NAME_entry_count(psubj); + int ne = X509_NAME_entry_count(psubj.get()); #else /* OPENSSL */ int ne = psubj->entries->num; #endif /* OPENSSL */ if (ne >= 0) { - X509_NAME_ENTRY *cne = X509_NAME_delete_entry(psubj, ne-1); + X509_NAME_ENTRY *cne = X509_NAME_delete_entry(psubj.get(), ne-1); if (cne) { X509_NAME_ENTRY_free(cne); } else { @@ -725,21 +763,21 @@ int XrdCryptosslX509CreateProxyReq(XrdCryptoX509 *xcpi, // Create an entry with the common name unsigned char sn[20] = {0}; sprintf((char *)sn, "%d", serial); - if (!X509_NAME_add_entry_by_txt(psubj, (char *)"CN", MBSTRING_ASC, + if (!X509_NAME_add_entry_by_txt(psubj.get(), (char *)"CN", MBSTRING_ASC, sn, -1, -1, 0)) { PRINT("could not add CN - (serial: "<proxyPolicy->policyLanguage = OBJ_txt2obj("1.3.6.1.5.5.7.21.1", 1); // // Create a stack - STACK_OF(X509_EXTENSION) *esk = sk_X509_EXTENSION_new_null(); + esk.reset(sk_X509_EXTENSION_new_null()); if (!esk) { PRINT("could not create stack for extensions"); return -kErrPX_NoResources; @@ -780,11 +818,13 @@ int XrdCryptosslX509CreateProxyReq(XrdCryptoX509 *xcpi, inpci->pcPathLengthConstraint) indepthlen = ASN1_INTEGER_get(inpci->pcPathLengthConstraint); DEBUG("IN depth length: "<length = i2d_PROXY_CERT_INFO_EXTENSION(pci, 0); - if (!(X509_EXTENSION_get_data(ext)->data = (unsigned char *)malloc(X509_EXTENSION_get_data(ext)->length+1))) { + X509_EXTENSION_get_data(ext.get())->length = i2d_PROXY_CERT_INFO_EXTENSION(pci.get(), 0); + if (!(X509_EXTENSION_get_data(ext.get())->data = (unsigned char *)malloc(X509_EXTENSION_get_data(ext.get())->length+1))) { PRINT("could not allocate data field for extension"); return -kErrPX_NoResources; } - unsigned char *pp = X509_EXTENSION_get_data(ext)->data; - if ((i2d_PROXY_CERT_INFO_EXTENSION(pci, &pp)) <= 0) { + unsigned char *pp = X509_EXTENSION_get_data(ext.get())->data; + if ((i2d_PROXY_CERT_INFO_EXTENSION(pci.get(), &pp)) <= 0) { PRINT("problem converting data for extension"); return -kErrPX_Error; } + pci = nullptr; + // Set extension name. ASN1_OBJECT *obj = OBJ_txt2obj(gsiProxyCertInfo_OID, 1); - if (!obj || X509_EXTENSION_set_object(ext, obj) != 1) { + if (!obj || X509_EXTENSION_set_object(ext.get(), obj) != 1) { PRINT("could not set extension name"); + ASN1_OBJECT_free(obj); return -kErrPX_SetAttribute; } + ASN1_OBJECT_free(obj); + obj = 0; + // flag as critical - if (X509_EXTENSION_set_critical(ext, 1) != 1) { + if (X509_EXTENSION_set_critical(ext.get(), 1) != 1) { PRINT("could not set extension critical flag"); return -kErrPX_SetAttribute; } - if (sk_X509_EXTENSION_push(esk, ext) == 0) { + if (sk_X509_EXTENSION_push(esk.get(), ext.get()) == 0) { PRINT("could not push the extension in the stack"); return -kErrPX_Error; } + // ext resource now owned by esk + ext.release(); + // Add extensions - if (!(X509_REQ_add_extensions(xro, esk))) { + if (!(X509_REQ_add_extensions(xro.get(), esk.get()))) { PRINT("problem adding extension"); return -kErrPX_SetAttribute; } // // Sign the request - if (!(X509_REQ_sign(xro, ekro, EVP_sha256()))) { + if (!(X509_REQ_sign(xro.get(), ekro.get(), EVP_sha256()))) { PRINT("problems signing the request"); return -kErrPX_Signing; } // Prepare output - *xcro = new XrdCryptosslX509Req(xro); - *kcro = new XrdCryptosslRSA(ekro); + *xcro = new XrdCryptosslX509Req(xro.get()); + *kcro = new XrdCryptosslRSA(ekro.get()); - // Cleanup -#if OPENSSL_VERSION_NUMBER >= 0x10000000L - sk_X509_EXTENSION_pop_free(esk, X509_EXTENSION_free); -#else /* OPENSSL */ - sk_free(esk); -#endif /* OPENSSL */ + // xro, ekro resoruce now owned by *xcro and *kcro + xro.release(); + ekro.release(); // We are done return 0; @@ -900,9 +946,19 @@ int XrdCryptosslX509SignProxyReq(XrdCryptoX509 *xcpi, XrdCryptoRSA *kcpi, PRINT("inconsistent key loaded"); return -kErrPX_BadEECkey; } + + // These will be assigned dynamically allocated ssl structures later. + // They use type aliases for unique_ptr, to ease use of a smart pointer. + // + EVP_PKEY_ptr ekpi(nullptr, &EVP_PKEY_free); + X509_ptr xpo(nullptr, &X509_free); + X509_EXTENSION_ptr ext(nullptr, &X509_EXTENSION_free); + PROXY_CERT_INFO_EXTENSION_ptr pci(nullptr, &PROXY_CERT_INFO_EXTENSION_free); + STACK_OF_X509_EXTENSION_ptr xrisk(nullptr, &stackOfX509ExtensionDelete); + // Point to the cerificate #if OPENSSL_VERSION_NUMBER >= 0x30000000L - EVP_PKEY *ekpi = EVP_PKEY_dup((EVP_PKEY *)(kcpi->Opaque())); + ekpi.reset(EVP_PKEY_dup((EVP_PKEY *)(kcpi->Opaque()))); if (!ekpi) { PRINT("could not create a EVP_PKEY * instance - return"); return -kErrPX_NoResources; @@ -911,12 +967,12 @@ int XrdCryptosslX509SignProxyReq(XrdCryptoX509 *xcpi, XrdCryptoRSA *kcpi, RSA *kpi = EVP_PKEY_get0_RSA((EVP_PKEY *)(kcpi->Opaque())); // // Set the key into the request - EVP_PKEY *ekpi = EVP_PKEY_new(); + ekpi.reset(EVP_PKEY_new()); if (!ekpi) { PRINT("could not create a EVP_PKEY * instance - return"); return -kErrPX_NoResources; } - EVP_PKEY_set1_RSA(ekpi, kpi); + EVP_PKEY_set1_RSA(ekpi.get(), kpi); #endif // Get request in raw form @@ -960,50 +1016,50 @@ int XrdCryptosslX509SignProxyReq(XrdCryptoX509 *xcpi, XrdCryptoRSA *kcpi, unsigned int serial = (unsigned int)(strtol(sserial.c_str(), 0, 10)); // // Create new proxy cert - X509 *xpo = X509_new(); + xpo.reset(X509_new()); if (!xpo) { PRINT("could not create certificate object for proxies"); return -kErrPX_NoResources; } // Set version number - if (X509_set_version(xpo, 2L) != 1) { + if (X509_set_version(xpo.get(), 2L) != 1) { PRINT("could not set version"); return -kErrPX_SetAttribute; } // Set serial number - if (ASN1_INTEGER_set(X509_get_serialNumber(xpo), serial) != 1) { + if (ASN1_INTEGER_set(X509_get_serialNumber(xpo.get()), serial) != 1) { PRINT("could not set serial number"); return -kErrPX_SetAttribute; } // Set subject name - if (X509_set_subject_name(xpo, X509_REQ_get_subject_name(xri)) != 1) { + if (X509_set_subject_name(xpo.get(), X509_REQ_get_subject_name(xri)) != 1) { PRINT("could not set subject name"); return -kErrPX_SetAttribute; } // Set issuer name - if (X509_set_issuer_name(xpo, X509_get_subject_name(xpi)) != 1) { + if (X509_set_issuer_name(xpo.get(), X509_get_subject_name(xpi)) != 1) { PRINT("could not set issuer name"); return -kErrPX_SetAttribute; } // Set public key - if (X509_set_pubkey(xpo, X509_REQ_get_pubkey(xri)) != 1) { + if (X509_set_pubkey(xpo.get(), X509_REQ_get_pubkey(xri)) != 1) { PRINT("could not set public key"); return -kErrPX_SetAttribute; } // Set proxy validity: notBefore now - if (!X509_gmtime_adj(X509_get_notBefore(xpo), 0)) { + if (!X509_gmtime_adj(X509_get_notBefore(xpo.get()), 0)) { PRINT("could not set notBefore"); return -kErrPX_SetAttribute; } // Set proxy validity: notAfter timeleft from now - if (!X509_gmtime_adj(X509_get_notAfter(xpo), timeleft)) { + if (!X509_gmtime_adj(X509_get_notAfter(xpo.get()), timeleft)) { PRINT("could not set notAfter"); return -kErrPX_SetAttribute; } @@ -1033,6 +1089,7 @@ int XrdCryptosslX509SignProxyReq(XrdCryptoX509 *xcpi, XrdCryptoRSA *kcpi, inpci->pcPathLengthConstraint) indepthlen = ASN1_INTEGER_get(inpci->pcPathLengthConstraint); DEBUG("IN depth length: "<= 0x10000000L - int nriext = sk_X509_EXTENSION_num(xrisk); + int nriext = sk_X509_EXTENSION_num(xrisk.get()); #else /* OPENSSL */ - int nriext = sk_num(xrisk); + int nriext = sk_num(xrisk.get()); #endif /* OPENSSL */ if (nriext == 0 || !haskeyusage) { PRINT("wrong extensions in request: "<< nriext<<", "<pcPathLengthConstraint) reqdepthlen = ASN1_INTEGER_get(reqpci->pcPathLengthConstraint); + PROXY_CERT_INFO_EXTENSION_free(reqpci); } DEBUG("REQ depth length: "<length = i2d_PROXY_CERT_INFO_EXTENSION(pci, 0); - if (!(X509_EXTENSION_get_data(ext)->data = (unsigned char *)malloc(X509_EXTENSION_get_data(ext)->length+1))) { + X509_EXTENSION_get_data(ext.get())->length = i2d_PROXY_CERT_INFO_EXTENSION(pci.get(), 0); + if (!(X509_EXTENSION_get_data(ext.get())->data = (unsigned char *)malloc(X509_EXTENSION_get_data(ext.get())->length+1))) { PRINT("could not allocate data field for extension"); return -kErrPX_NoResources; } - unsigned char *pp = X509_EXTENSION_get_data(ext)->data; - if ((i2d_PROXY_CERT_INFO_EXTENSION(pci, &pp)) <= 0) { + unsigned char *pp = X509_EXTENSION_get_data(ext.get())->data; + if ((i2d_PROXY_CERT_INFO_EXTENSION(pci.get(), &pp)) <= 0) { PRINT("problem converting data for extension"); return -kErrPX_Error; } - PROXY_CERT_INFO_EXTENSION_free( pci ); + pci = nullptr; // Set extension name. ASN1_OBJECT *obj = OBJ_txt2obj(gsiProxyCertInfo_OID, 1); - if (!obj || X509_EXTENSION_set_object(ext, obj) != 1) { + if (!obj || X509_EXTENSION_set_object(ext.get(), obj) != 1) { PRINT("could not set extension name"); + ASN1_OBJECT_free( obj ); return -kErrPX_SetAttribute; } ASN1_OBJECT_free( obj ); + obj = 0; // flag as critical - if (X509_EXTENSION_set_critical(ext, 1) != 1) { + if (X509_EXTENSION_set_critical(ext.get(), 1) != 1) { PRINT("could not set extension critical flag"); return -kErrPX_SetAttribute; } - // Add the extension - if (X509_add_ext(xpo, ext, -1) == 0) { + // Add the extension (adds a copy of the extension) + if (X509_add_ext(xpo.get(), ext.get(), -1) == 0) { PRINT("could not add extension"); return -kErrPX_SetAttribute; } // // Sign the certificate - if (!(X509_sign(xpo, ekpi, EVP_sha256()))) { + if (!(X509_sign(xpo.get(), ekpi.get(), EVP_sha256()))) { PRINT("problems signing the certificate"); return -kErrPX_Signing; } - EVP_PKEY_free( ekpi ); // decrement reference counter - X509_EXTENSION_free( ext ); + ekpi = nullptr; + ext = nullptr; // Prepare outputs - *xcpo = new XrdCryptosslX509(xpo); + *xcpo = new XrdCryptosslX509(xpo.get()); - // Cleanup -#if OPENSSL_VERSION_NUMBER >= 0x10000000L - sk_X509_EXTENSION_free(xrisk); -#else /* OPENSSL */ - sk_free(xrisk); -#endif /* OPENSSL */ + // xpo resource is now owned by the *xcpo + xpo.release(); // We are done return 0; diff --git a/src/XrdSecgsi/XrdSecProtocolgsi.cc b/src/XrdSecgsi/XrdSecProtocolgsi.cc index b87929401af..562543d4523 100644 --- a/src/XrdSecgsi/XrdSecProtocolgsi.cc +++ b/src/XrdSecgsi/XrdSecProtocolgsi.cc @@ -1080,7 +1080,7 @@ void XrdSecProtocolgsi::Delete() SafeDelete(sessionMD); // Message Digest instance SafeDelete(sessionKsig); // RSA key to sign SafeDelete(sessionKver); // RSA key to verify - if (proxyChain) proxyChain->Cleanup(1); + if (proxyChain) proxyChain->Cleanup(); SafeDelete(proxyChain); // Chain with delegated proxies SafeFree(expectedHost); @@ -3902,6 +3902,9 @@ int XrdSecProtocolgsi::ServerDoCert(XrdSutBuffer *br, XrdSutBuffer **bm, if (needReq || (hs->Options & kOptsFwdPxy)) { // Create a new proxy chain hs->PxyChain = new X509Chain(); + // The new chain must be deleted if still in the handshake info + // when the info is destroyed + hs->Options |= kOptsDelPxy; // Add the current proxy if ((*ParseBucket)(bck, hs->PxyChain) > 1) { // Reorder it @@ -3912,21 +3915,34 @@ int XrdSecProtocolgsi::ServerDoCert(XrdSutBuffer *br, XrdSutBuffer **bm, XrdCryptoRSA *krPXp = 0; if ((*X509CreateProxyReq)(hs->PxyChain->End(), &rPXp, &krPXp) == 0) { // Save key in the cache - hs->Cref->buf4.buf = (char *)krPXp; + hs->Cref->buf4.len = krPXp->GetPrilen() + 1; + hs->Cref->buf4.buf = new char[hs->Cref->buf4.len]; + if (krPXp->ExportPrivate(hs->Cref->buf4.buf, hs->Cref->buf4.len) != 0) { + delete krPXp; + delete rPXp; + if (hs->PxyChain) hs->PxyChain->Cleanup(); + SafeDelete(hs->PxyChain); + cmsg = "cannot export private key of the proxy request!"; + return -1; + } // Prepare export bucket for request XrdSutBucket *bckr = rPXp->Export(); // Add it to the main list if ((*bm)->AddBucket(bckr) != 0) { + if (hs->PxyChain) hs->PxyChain->Cleanup(); SafeDelete(hs->PxyChain); NOTIFY("WARNING: proxy req: problem adding bucket to main buffer"); } + delete krPXp; delete rPXp; } else { + if (hs->PxyChain) hs->PxyChain->Cleanup(); SafeDelete(hs->PxyChain); NOTIFY("WARNING: proxy req: problem creating request"); } } } else { + if (hs->PxyChain) hs->PxyChain->Cleanup(); SafeDelete(hs->PxyChain); NOTIFY("WARNING: proxy req: wrong number of certificates"); } @@ -4037,8 +4053,12 @@ int XrdSecProtocolgsi::ServerDoSigpxy(XrdSutBuffer *br, XrdSutBuffer **bm, return 0; } // Set full PKI - XrdCryptoRSA *knpx = (XrdCryptoRSA *)(hs->Cref->buf4.buf); - npx->SetPKI((XrdCryptoX509data)(knpx->Opaque())); + XrdCryptoRSA *const knpx = npx->PKI(); + if (!knpx || knpx->ImportPrivate(hs->Cref->buf4.buf, hs->Cref->buf4.len) != 0) { + delete npx; + cmsg = "could not import private key into signed request"; + return 0; + } // Add the new proxy ecert to the chain pxyc->PushBack(npx); } diff --git a/src/XrdSecgsi/XrdSecProtocolgsi.hh b/src/XrdSecgsi/XrdSecProtocolgsi.hh index 5c5ccfa0cd6..2c79f5aebe8 100644 --- a/src/XrdSecgsi/XrdSecProtocolgsi.hh +++ b/src/XrdSecgsi/XrdSecProtocolgsi.hh @@ -112,7 +112,8 @@ enum kgsiHandshakeOpts { kOptsPxFile = 16, // 0x0010: Save delegated proxies in file kOptsDelChn = 32, // 0x0020: Delete chain kOptsPxCred = 64, // 0x0040: Save delegated proxies as credentials - kOptsCreatePxy = 128 // 0x0080: Request a client proxy + kOptsCreatePxy = 128, // 0x0080: Request a client proxy + kOptsDelPxy = 256 // 0x0100: Delete the proxy PxyChain }; // Error codes @@ -540,9 +541,14 @@ public: XrdSecProtocolgsi::stackCRL->Del(Crl); Crl = 0; } - // The proxy chain is owned by the proxy cache; invalid proxies are - // detected (and eventually removed) by QueryProxy - PxyChain = 0; + if (Options & kOptsDelPxy) { + if (PxyChain) PxyChain->Cleanup(); + SafeDelete(PxyChain); + } else { + // The proxy chain is owned by the proxy cache; invalid proxies + // are detected (and eventually removed) by QueryProxy + PxyChain = 0; + } SafeDelete(Parms); } void Dump(XrdSecProtocolgsi *p = 0); }; From 0d173ebd78641dae1f5c16bc9c8677db4ae34a2f Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 20 Jun 2023 09:12:23 +0200 Subject: [PATCH 158/442] Increase default number of parallel event loops to 10 This value has been chosen as default based on benchmarks that demonstrated that this is the minimum required number of threads to be able to saturate a link of 100Gbps. --- docs/man/xrdcp.1 | 2 +- src/XrdCl/XrdClConstants.hh | 2 +- src/XrdOuc/XrdOucPsx.cc | 2 +- src/XrdPss/XrdPssConfig.cc | 2 +- src/XrdSsi/XrdSsiClient.cc | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/man/xrdcp.1 b/docs/man/xrdcp.1 index 1e2f0699b3c..c0aeafa3f26 100644 --- a/docs/man/xrdcp.1 +++ b/docs/man/xrdcp.1 @@ -332,7 +332,7 @@ The default value is \fIOFF\fR. XRD_PARALLELEVTLOOP .RS 5 -The number of event loops. +The number of event loops (i.e. the number of threads handling requests). Default number is 10. .RE XRD_READRECOVERY diff --git a/src/XrdCl/XrdClConstants.hh b/src/XrdCl/XrdClConstants.hh index d553168f48e..11a1eca30b6 100644 --- a/src/XrdCl/XrdClConstants.hh +++ b/src/XrdCl/XrdClConstants.hh @@ -70,7 +70,7 @@ namespace XrdCl const int DefaultTCPKeepAliveInterval = 75; const int DefaultTCPKeepAliveProbes = 9; const int DefaultMultiProtocol = 0; - const int DefaultParallelEvtLoop = 1; + const int DefaultParallelEvtLoop = 10; const int DefaultMetalinkProcessing = 1; const int DefaultLocalMetalinkFile = 0; const int DefaultXRateThreshold = 0; diff --git a/src/XrdOuc/XrdOucPsx.cc b/src/XrdOuc/XrdOucPsx.cc index 4d4b59bf7e3..1af710ddb20 100644 --- a/src/XrdOuc/XrdOucPsx.cc +++ b/src/XrdOuc/XrdOucPsx.cc @@ -715,7 +715,7 @@ bool XrdOucPsx::ParseSet(XrdSysError *Eroute, XrdOucStream &Config) {"DataServerTTL", "DataServerTTL",1}, // Default 300 {"LBServerConn_ttl", "LoadBalancerTTL",1}, // Default 1200 {"LoadBalancerTTL", "LoadBalancerTTL",1}, // Default 1200 - {"ParallelEvtLoop", "ParallelEvtLoop",0}, // Default 3 + {"ParallelEvtLoop", "ParallelEvtLoop",0}, // Default 10 {"ParStreamsPerPhyConn", "SubStreamsPerChannel",0},// Default 1 {"ReadAheadSize", 0,0}, {"ReadAheadStrategy", 0,0}, diff --git a/src/XrdPss/XrdPssConfig.cc b/src/XrdPss/XrdPssConfig.cc index 5c6fc12338e..8a3ff5e8610 100644 --- a/src/XrdPss/XrdPssConfig.cc +++ b/src/XrdPss/XrdPssConfig.cc @@ -199,7 +199,7 @@ int XrdPssSys::Configure(const char *cfn, XrdOucEnv *envP) // Set default number of event loops // - XrdPosixConfig::SetEnv("ParallelEvtLoop", 3); + XrdPosixConfig::SetEnv("ParallelEvtLoop", 10); // Turn off the fork handler as we always exec after forking. // diff --git a/src/XrdSsi/XrdSsiClient.cc b/src/XrdSsi/XrdSsiClient.cc index c386206e43e..049443ab493 100644 --- a/src/XrdSsi/XrdSsiClient.cc +++ b/src/XrdSsi/XrdSsiClient.cc @@ -76,7 +76,7 @@ extern XrdSsiLogger::MCB_t *msgCBCl; Atomic(int) contactN(1); short maxTCB = 300; short maxCLW = 30; - short maxPEL = 3; + short maxPEL = 10; Atomic(bool) initDone(false); bool dsTTLSet = false; bool reqTOSet = false; From d7f4b6136be7f367ee7296ee6fbbca02b70cae02 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Wed, 21 Jun 2023 07:59:56 -0700 Subject: [PATCH 159/442] [Server] Include token information in the monitoring stream (phase 1). --- src/XrdSciTokens.cmake | 3 +- src/XrdSciTokens/XrdSciTokensAccess.cc | 11 ++++- src/XrdSciTokens/XrdSciTokensHelper.hh | 3 ++ src/XrdSciTokens/XrdSciTokensMon.cc | 31 ++++++++++++++ src/XrdSciTokens/XrdSciTokensMon.hh | 28 +++++++++++++ src/XrdSec/XrdSecEntity.cc | 1 + src/XrdSec/XrdSecEntity.hh | 4 +- src/XrdSec/XrdSecMonitor.hh | 56 ++++++++++++++++++++++++++ src/XrdUtils.cmake | 1 + src/XrdXrootd/XrdXrootdMonData.hh | 1 + src/XrdXrootd/XrdXrootdMonitor.cc | 18 +++++++++ src/XrdXrootd/XrdXrootdMonitor.hh | 5 ++- src/XrdXrootd/XrdXrootdXeq.cc | 5 ++- 13 files changed, 162 insertions(+), 5 deletions(-) create mode 100644 src/XrdSciTokens/XrdSciTokensMon.cc create mode 100644 src/XrdSciTokens/XrdSciTokensMon.hh create mode 100644 src/XrdSec/XrdSecMonitor.hh diff --git a/src/XrdSciTokens.cmake b/src/XrdSciTokens.cmake index bdbe124a547..784827d1ec1 100644 --- a/src/XrdSciTokens.cmake +++ b/src/XrdSciTokens.cmake @@ -13,7 +13,8 @@ add_library( ${LIB_XRD_SCITOKENS} MODULE XrdSciTokens/XrdSciTokensAccess.cc - XrdSciTokens/XrdSciTokensHelper.hh ) + XrdSciTokens/XrdSciTokensHelper.hh + XrdSciTokens/XrdSciTokensMon.cc XrdSciTokens/XrdSciTokensMon.hh ) target_link_libraries( ${LIB_XRD_SCITOKENS} PRIVATE diff --git a/src/XrdSciTokens/XrdSciTokensAccess.cc b/src/XrdSciTokens/XrdSciTokensAccess.cc index 097c10b097c..74c2797bf9c 100644 --- a/src/XrdSciTokens/XrdSciTokensAccess.cc +++ b/src/XrdSciTokens/XrdSciTokensAccess.cc @@ -24,6 +24,7 @@ #include "scitokens/scitokens.h" #include "XrdSciTokens/XrdSciTokensHelper.hh" +#include "XrdSciTokens/XrdSciTokensMon.hh" // The status-quo to retrieve the default object is to copy/paste the // linker definition and invoke directly. @@ -420,7 +421,8 @@ class XrdAccSciTokens; XrdAccSciTokens *accSciTokens = nullptr; XrdSciTokensHelper *SciTokensHelper = nullptr; -class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper +class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper, + public XrdSciTokensMon { enum class AuthzBehavior { @@ -528,6 +530,7 @@ class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper new_secentity.vorg = nullptr; new_secentity.grps = nullptr; new_secentity.role = nullptr; + new_secentity.secMon = Entity->secMon; const auto &issuer = access_rules->get_issuer(); if (!issuer.empty()) { new_secentity.vorg = strdup(issuer.c_str()); @@ -593,6 +596,12 @@ class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper // When the scope authorized this access, allow immediately. Otherwise, chain XrdAccPrivs returned_op = scope_success ? AddPriv(oper, XrdAccPriv_None) : OnMissing(&new_secentity, path, oper, env); + // Since we are doing an early return, insert token info into the + // monitoring stream if monitoring is in effect and access granted + // + if (Entity->secMon && scope_success && returned_op && Mon_isIO(oper)) + Mon_Report(new_secentity, token_subject, username); + // Cleanup the new_secentry if (new_secentity.vorg != nullptr) free(new_secentity.vorg); if (new_secentity.grps != nullptr) free(new_secentity.grps); diff --git a/src/XrdSciTokens/XrdSciTokensHelper.hh b/src/XrdSciTokens/XrdSciTokensHelper.hh index bab032d9599..aeac5ddb5ca 100644 --- a/src/XrdSciTokens/XrdSciTokensHelper.hh +++ b/src/XrdSciTokens/XrdSciTokensHelper.hh @@ -1,3 +1,5 @@ +#ifndef __XrdSciTokensHelper_hh__ +#define __XrdSciTokensHelper_hh__ /******************************************************************************/ /* */ /* X r d S c i T o k e n s H e l p e r . h h */ @@ -65,3 +67,4 @@ virtual bool Validate(const char *token, XrdSciTokensHelper() {} virtual ~XrdSciTokensHelper() {} }; +#endif diff --git a/src/XrdSciTokens/XrdSciTokensMon.cc b/src/XrdSciTokens/XrdSciTokensMon.cc new file mode 100644 index 00000000000..df408b07e3e --- /dev/null +++ b/src/XrdSciTokens/XrdSciTokensMon.cc @@ -0,0 +1,31 @@ +/******************************************************************************/ +/* */ +/* X r d S c i T o k e n s M o n . c c */ +/* */ +/******************************************************************************/ + +#include "XrdSciTokens/XrdSciTokensMon.hh" +#include "XrdSec/XrdSecEntity.hh" +#include "XrdSec/XrdSecMonitor.hh" + +/******************************************************************************/ +/* R e p o r t */ +/******************************************************************************/ + +void XrdSciTokensMon::Mon_Report(const XrdSecEntity& Entity, + const std::string& subject, + const std::string& username) +{ +// Create record +// + if (Entity.secMon) + {char buff[2048]; + snprintf(buff, sizeof(buff), + "s=%s&n=%s&o=%s&r=%s&g=%.1024s", + subject.c_str(),username.c_str(), + (Entity.vorg ? Entity.vorg : ""), + (Entity.role ? Entity.role : ""), + (Entity.grps ? Entity.grps : "")); + Entity.secMon->Report(XrdSecMonitor::TokenInfo, buff); + } +} diff --git a/src/XrdSciTokens/XrdSciTokensMon.hh b/src/XrdSciTokens/XrdSciTokensMon.hh new file mode 100644 index 00000000000..6f41787e302 --- /dev/null +++ b/src/XrdSciTokens/XrdSciTokensMon.hh @@ -0,0 +1,28 @@ +#ifndef __XrdSciTokensMon_hh__ +#define __XrdSciTokensMon_hh__ +/******************************************************************************/ +/* */ +/* X r d S c o T o k e n s M o n . h h */ +/* */ +/******************************************************************************/ + +#include + +#include "XrdAcc/XrdAccAuthorize.hh" + +class XrdSciTokensMon +{ +public: + +bool Mon_isIO(const Access_Operation oper) + {return oper == AOP_Read || oper == AOP_Update + || oper == AOP_Create || oper == AOP_Excl_Create; + } + +void Mon_Report(const XrdSecEntity& Entity, const std::string& subject, + const std::string& username); + + XrdSciTokensMon() {} + ~XrdSciTokensMon() {} +}; +#endif diff --git a/src/XrdSec/XrdSecEntity.cc b/src/XrdSec/XrdSecEntity.cc index 8262e545eb4..3f0c808a9be 100644 --- a/src/XrdSec/XrdSecEntity.cc +++ b/src/XrdSec/XrdSecEntity.cc @@ -130,6 +130,7 @@ void XrdSecEntity::Init(const char *spV) sessvar = 0; uid = 0; gid = 0; + secMon = 0; memset(future, 0, sizeof(future)); } diff --git a/src/XrdSec/XrdSecEntity.hh b/src/XrdSec/XrdSecEntity.hh index 138db32b4c0..9e057215dee 100644 --- a/src/XrdSec/XrdSecEntity.hh +++ b/src/XrdSec/XrdSecEntity.hh @@ -48,6 +48,7 @@ class XrdNetAddrInfo; class XrdSecEntityAttr; +class XrdSecMonitor; class XrdSysError; /******************************************************************************/ @@ -85,7 +86,8 @@ const char *pident; //!< Trace identifier (originator) uid_t uid; //!< Unix uid or 0 if none gid_t gid; //!< Unix gid or 0 if none - void *future[3]; //!< Reserved for future expansion +XrdSecMonitor *secMon; //!< If !0 security monitoring enabled + void *future[2]; //!< Reserved for future expansion XrdSecEntityAttr *eaAPI; //!< non-const API to attributes diff --git a/src/XrdSec/XrdSecMonitor.hh b/src/XrdSec/XrdSecMonitor.hh new file mode 100644 index 00000000000..a8a7fe85c77 --- /dev/null +++ b/src/XrdSec/XrdSecMonitor.hh @@ -0,0 +1,56 @@ +#ifndef __XRDSECMONITOR__ +#define __XRDSECMONITOR__ +/******************************************************************************/ +/* */ +/* X r d S e c M o n i t o r . h h */ +/* */ +/* (c) 2023 by the Board of Trustees of the Leland Stanford, Jr., University */ +/* All Rights Reserved */ +/* Produced by Andrew Hanushevsky for Stanford University under contract */ +/* DE-AC02-76-SFO0515 with the Department of Energy */ +/* */ +/* This file is part of the XRootD software suite. */ +/* */ +/* XRootD is free software: you can redistribute it and/or modify it under */ +/* the terms of the GNU Lesser General Public License as published by the */ +/* Free Software Foundation, either version 3 of the License, or (at your */ +/* option) any later version. */ +/* */ +/* XRootD is distributed in the hope that it will be useful, but WITHOUT */ +/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ +/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ +/* License for more details. */ +/* */ +/* You should have received a copy of the GNU Lesser General Public License */ +/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ +/* COPYING (GPL license). If not, see . */ +/* */ +/* The copyright holder's institutional names and contributor's names may not */ +/* be used to endorse or promote products derived from this software without */ +/* specific prior written permission of the institution or contributor. */ +/******************************************************************************/ + +class XrdSecMonitor +{ +public: + +enum WhatInfo {TokenInfo = 0}; + +//------------------------------------------------------------------------------ +//! Include extra information in the monitoring stream to be associated with +//! the current mapped user. This object is pointed to via the XrdSecEntity +//! secMon member. +//! +//! @param infoT - the enum describing what information is being reported +//! @param info - a null terminate string with the information in cgi format +//! +//! @return true - Information reported. +//! @return false - Invalid infoT code or not enabled, call has been ignored. +//------------------------------------------------------------------------------ + +virtual bool Report(WhatInfo infoT, const char *info) = 0; + + XrdSecMonitor() {} +virtual ~XrdSecMonitor() {} +}; +#endif diff --git a/src/XrdUtils.cmake b/src/XrdUtils.cmake index 8c41a1cb3ff..8621c3b974f 100644 --- a/src/XrdUtils.cmake +++ b/src/XrdUtils.cmake @@ -265,6 +265,7 @@ set ( XrdSecSources XrdSec/XrdSecEntityAttr.cc XrdSec/XrdSecEntityAttr.hh XrdSec/XrdSecEntityXtra.cc XrdSec/XrdSecEntityXtra.hh XrdSec/XrdSecLoadSecurity.cc XrdSec/XrdSecLoadSecurity.hh + XrdSec/XrdSecMonitor.hh XrdSecsss/XrdSecsssCon.cc XrdSecsss/XrdSecsssCon.hh XrdSecsss/XrdSecsssEnt.cc XrdSecsss/XrdSecsssEnt.hh XrdSecsss/XrdSecsssID.cc XrdSecsss/XrdSecsssID.hh diff --git a/src/XrdXrootd/XrdXrootdMonData.hh b/src/XrdXrootd/XrdXrootdMonData.hh index 29dfda85645..1c98a6b4707 100644 --- a/src/XrdXrootd/XrdXrootdMonData.hh +++ b/src/XrdXrootd/XrdXrootdMonData.hh @@ -110,6 +110,7 @@ const kXR_char XROOTD_MON_MAPPURG = 'p'; const kXR_char XROOTD_MON_MAPREDR = 'r'; const kXR_char XROOTD_MON_MAPSTAG = 's'; // Internal use only! const kXR_char XROOTD_MON_MAPTRCE = 't'; +const kXR_char XROOTD_MON_MAPTOKN = 'T'; const kXR_char XROOTD_MON_MAPUSER = 'u'; const kXR_char XROOTD_MON_MAPUEAC = 'U'; // User experiment/activity const kXR_char XROOTD_MON_MAPXFER = 'x'; diff --git a/src/XrdXrootd/XrdXrootdMonitor.cc b/src/XrdXrootd/XrdXrootdMonitor.cc index d0f11739296..e06a8ecfd3a 100644 --- a/src/XrdXrootd/XrdXrootdMonitor.cc +++ b/src/XrdXrootd/XrdXrootdMonitor.cc @@ -334,6 +334,23 @@ void XrdXrootdMonitor::User::Report(int eCode, int aCode) XrdXrootdMonitor::Map(XROOTD_MON_MAPUEAC,*this,buff); } +/******************************************************************************/ + +bool XrdXrootdMonitor::User::Report(WhatInfo infoT, const char *info) +{ + char buff[4096]; + +// Currently we support only the token external report +// + if (infoT != TokenInfo) return false; + + snprintf(buff, sizeof(buff), "&Uc=%d%s%s", ntohl(Did), + (*info == '&' ? "" : "&"), info); + + XrdXrootdMonitor::Map(XROOTD_MON_MAPTOKN,*this,buff); + + return true; +} /******************************************************************************/ /* C o n s t r u c t o r */ /******************************************************************************/ @@ -836,6 +853,7 @@ kXR_unt32 XrdXrootdMonitor::Map(char code, XrdXrootdMonitor::User &uInfo, // if (code == XROOTD_MON_MAPPATH) montype = XROOTD_MON_PATH; else if (code == XROOTD_MON_MAPUSER + || code == XROOTD_MON_MAPTOKN || code == XROOTD_MON_MAPUEAC) montype = XROOTD_MON_USER; else montype = XROOTD_MON_INFO; Send(montype, (void *)&map, size); diff --git a/src/XrdXrootd/XrdXrootdMonitor.hh b/src/XrdXrootd/XrdXrootdMonitor.hh index 8f1b7cde992..4f313cf83c0 100644 --- a/src/XrdXrootd/XrdXrootdMonitor.hh +++ b/src/XrdXrootd/XrdXrootdMonitor.hh @@ -36,6 +36,7 @@ #include #include +#include "XrdSec/XrdSecMonitor.hh" #include "XrdSys/XrdSysPthread.hh" #include "XrdXrootd/XrdXrootdMonData.hh" #include "XProtocol/XPtypes.hh" @@ -167,7 +168,7 @@ static Hello *First; /******************************************************************************/ -class User +class User : public XrdSecMonitor { public: @@ -217,6 +218,8 @@ inline kXR_unt32 MapPath(const char *Path) void Report(int eCode, int aCode); + bool Report(WhatInfo infoT, const char *info) override; + inline int Ready() {return XrdXrootdMonitor::monACTIVE;} User() : Agent(0), Did(0), Iops(0), Fops(0), Len(0), Name(0) {} diff --git a/src/XrdXrootd/XrdXrootdXeq.cc b/src/XrdXrootd/XrdXrootdXeq.cc index de82cd0cca2..6c9a1e87fff 100644 --- a/src/XrdXrootd/XrdXrootdXeq.cc +++ b/src/XrdXrootd/XrdXrootdXeq.cc @@ -1113,6 +1113,7 @@ int XrdXrootdProtocol::do_Login() if (Monitor.Logins() && (!Monitor.Auths() || !(Status & XRD_NEED_AUTH))) {Monitor.Report(Entity.moninfo); if (Entity.moninfo) {free(Entity.moninfo); Entity.moninfo = 0;} + Entity.secMon = &Monitor; } } @@ -4079,7 +4080,7 @@ void XrdXrootdProtocol::MonAuth() const char *bP = Buff; if (Client == &Entity) bP = Entity.moninfo; - else snprintf(Buff,sizeof(Buff), + else {snprintf(Buff,sizeof(Buff), "&p=%s&n=%s&h=%s&o=%s&r=%s&g=%s&m=%s%s&I=%c", Client->prot, (Client->name ? Client->name : ""), @@ -4091,6 +4092,8 @@ void XrdXrootdProtocol::MonAuth() (Entity.moninfo ? Entity.moninfo : ""), (clientPV & XrdOucEI::uIPv4 ? '4' : '6') ); + Client->secMon = &Monitor; + } Monitor.Report(bP); if (Entity.moninfo) {free(Entity.moninfo); Entity.moninfo = 0;} From 78f8d9d19f94e3737743586785ec3b16235c665d Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Wed, 21 Jun 2023 08:05:16 -0700 Subject: [PATCH 160/442] Update notes on including token info in the monitoring stream. --- docs/PreReleaseNotes.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/PreReleaseNotes.txt b/docs/PreReleaseNotes.txt index e6895293391..7839c8f0d2b 100644 --- a/docs/PreReleaseNotes.txt +++ b/docs/PreReleaseNotes.txt @@ -8,6 +8,8 @@ Prerelease Notes ================ + **New Features** + **[Server]** Include token information in the monitoring stream (phase 1). + **Commit: d7f4b61 **[Server]** Allow generic prepare plug-in to handle large responses, fixes #2023 **Commit: 564f0b2 **[Server]** Make maxfd be configurable (default is 256k). From b7be027baa3575e5915ded928a2d23f6ea00680b Mon Sep 17 00:00:00 2001 From: David Smith Date: Fri, 16 Jun 2023 15:43:00 +0200 Subject: [PATCH 161/442] [XrdCl] Fix potential stream timeout when a new request is sent to an idle stream --- src/XrdCl/XrdClSIDManager.cc | 18 ++++++++++++++++++ src/XrdCl/XrdClSIDManager.hh | 6 ++++++ src/XrdCl/XrdClXRootDTransport.cc | 12 +++++++----- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/XrdCl/XrdClSIDManager.cc b/src/XrdCl/XrdClSIDManager.cc index 2a40c935067..8718d0eabac 100644 --- a/src/XrdCl/XrdClSIDManager.cc +++ b/src/XrdCl/XrdClSIDManager.cc @@ -18,6 +18,8 @@ #include "XrdCl/XrdClSIDManager.hh" +#include + namespace XrdCl { //---------------------------------------------------------------------------- @@ -48,6 +50,7 @@ namespace XrdCl } memcpy( sid, &allocSID, 2 ); + pAllocTime[allocSID] = time(0); return Status(); } @@ -60,6 +63,7 @@ namespace XrdCl uint16_t relSID = 0; memcpy( &relSID, sid, 2 ); pFreeSIDs.push_back( relSID ); + pAllocTime.erase( relSID ); } //---------------------------------------------------------------------------- @@ -71,6 +75,20 @@ namespace XrdCl uint16_t tiSID = 0; memcpy( &tiSID, sid, 2 ); pTimeOutSIDs.insert( tiSID ); + pAllocTime.erase( tiSID ); + } + + //---------------------------------------------------------------------------- + // Check if any SID was allocated at or before a given time + //---------------------------------------------------------------------------- + bool SIDManager::IsAnySIDOldAs( const time_t tlim ) const + { + XrdSysMutexHelper scopedLock( pMutex ); + return std::any_of( pAllocTime.begin(), pAllocTime.end(), + [tlim](const auto& p) + { + return p.second <= tlim; + } ); } //---------------------------------------------------------------------------- diff --git a/src/XrdCl/XrdClSIDManager.hh b/src/XrdCl/XrdClSIDManager.hh index 0fcdb11f5ce..0eccf4b7098 100644 --- a/src/XrdCl/XrdClSIDManager.hh +++ b/src/XrdCl/XrdClSIDManager.hh @@ -83,6 +83,11 @@ namespace XrdCl //------------------------------------------------------------------------ void TimeOutSID( uint8_t sid[2] ); + //---------------------------------------------------------------------------- + //! Check if any SID was allocated at or before a given time + //---------------------------------------------------------------------------- + bool IsAnySIDOldAs( const time_t tlim ) const; + //------------------------------------------------------------------------ //! Check if a SID is timed out //------------------------------------------------------------------------ @@ -113,6 +118,7 @@ namespace XrdCl uint16_t GetNumberOfAllocatedSIDs() const; private: + std::unordered_map pAllocTime; std::list pFreeSIDs; std::set pTimeOutSIDs; uint16_t pSIDCeiling; diff --git a/src/XrdCl/XrdClXRootDTransport.cc b/src/XrdCl/XrdClXRootDTransport.cc index 2fcaeb923db..0caad99a00f 100644 --- a/src/XrdCl/XrdClXRootDTransport.cc +++ b/src/XrdCl/XrdClXRootDTransport.cc @@ -795,20 +795,22 @@ namespace XrdCl XrdSysMutexHelper scopedLock( info->mutex ); - uint16_t allocatedSIDs = info->sidManager->GetNumberOfAllocatedSIDs(); + const time_t now = time(0); + const bool anySID = + info->sidManager->IsAnySIDOldAs( now - streamTimeout ); log->Dump( XRootDTransportMsg, "[%s] Stream inactive since %d seconds, " - "stream timeout: %d, allocated SIDs: %d, wait barrier: %s", + "stream timeout: %d, any SID: %d, wait barrier: %s", info->streamName.c_str(), inactiveTime, streamTimeout, - allocatedSIDs, Utils::TimeToString(info->waitBarrier).c_str() ); + anySID, Utils::TimeToString(info->waitBarrier).c_str() ); if( inactiveTime < streamTimeout ) return Status(); - if( time(0) < info->waitBarrier ) + if( now < info->waitBarrier ) return Status(); - if( !allocatedSIDs ) + if( !anySID ) return Status(); return Status( stError, errSocketTimeout ); From 965c15daa6f5cbdfad3ddc40f97889bb4ba43457 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 20 Jun 2023 16:22:00 +0200 Subject: [PATCH 162/442] Revert "[CI] Set USER_VERSION not to confuse genversion.sh" This reverts commit 7f8b96622a1ded4d2ec01a2ed661a97bb436a3fa. This workaround is no longer necessary, as now the version is detected/generated with CMake and will fallback to using a date if no tags are found with git or in the version file. --- .github/workflows/build.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 015bf2dde61..1bd0fd92a1e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,9 +17,6 @@ concurrency: group: build-${{ github.ref }} cancel-in-progress: true -env: - USER_VERSION: v5.6-rc1 - jobs: cmake-almalinux8: From a295a93a43f9a1c4c4e275e60d46b1742ac1f8d9 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 22 Jun 2023 15:54:17 +0200 Subject: [PATCH 163/442] [CMake] Add missing include directories to xrdfs, xrootdfs, frm_admin These are usually found in /usr/include, so they went unnoticed until now. --- src/XrdCl/CMakeLists.txt | 4 ++++ src/XrdFfs.cmake | 2 ++ src/XrdFrm.cmake | 4 ++++ 3 files changed, 10 insertions(+) diff --git a/src/XrdCl/CMakeLists.txt b/src/XrdCl/CMakeLists.txt index 2a711d19333..4f8826302c7 100644 --- a/src/XrdCl/CMakeLists.txt +++ b/src/XrdCl/CMakeLists.txt @@ -145,6 +145,10 @@ target_link_libraries( XrdUtils ${READLINE_LIBRARY} ${NCURSES_LIBRARY} ) + +if( READLINE_FOUND ) + target_include_directories(xrdfs PRIVATE ${READLINE_INCLUDE_DIR}) +endif() endif() #------------------------------------------------------------------------------- diff --git a/src/XrdFfs.cmake b/src/XrdFfs.cmake index a19410d1d56..0eac1046cdb 100644 --- a/src/XrdFfs.cmake +++ b/src/XrdFfs.cmake @@ -47,6 +47,8 @@ if( BUILD_FUSE ) XrdPosix ${CMAKE_THREAD_LIBS_INIT} ${FUSE_LIBRARIES} ) + +target_include_directories(xrootdfs PRIVATE ${FUSE_INCLUDE_DIR}) endif() #------------------------------------------------------------------------------- diff --git a/src/XrdFrm.cmake b/src/XrdFrm.cmake index 1ed4432d72f..557ea843ce2 100644 --- a/src/XrdFrm.cmake +++ b/src/XrdFrm.cmake @@ -45,6 +45,10 @@ target_link_libraries( ${EXTRA_LIBS} ${SOCKET_LIBRARY} ) +if( READLINE_FOUND ) + target_include_directories(frm_admin PRIVATE ${READLINE_INCLUDE_DIR}) +endif() + #------------------------------------------------------------------------------- # frm_purged #------------------------------------------------------------------------------- From 68bf3275e3a70e02ac07594c07fe711ad64bfb7d Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 22 Jun 2023 16:35:42 +0200 Subject: [PATCH 164/442] [CMake] Do not require isa-l dependencies if not building it --- cmake/XRootDFindLibs.cmake | 41 +++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/cmake/XRootDFindLibs.cmake b/cmake/XRootDFindLibs.cmake index 7e98653b97f..a79d68a2839 100644 --- a/cmake/XRootDFindLibs.cmake +++ b/cmake/XRootDFindLibs.cmake @@ -141,21 +141,34 @@ if( ENABLE_SCITOKENS ) endif() if( ENABLE_XRDEC ) - if( FORCE_ENABLED ) - find_package( Yasm REQUIRED ) - find_package( LibTool REQUIRED ) - find_package( AutoMake REQUIRED ) - find_package( AutoConf REQUIRED ) - else() - find_package( Yasm ) - find_package( LibTool ) - find_package( AutoMake ) - find_package( AutoConf ) - endif() - if( YASM_FOUND AND LIBTOOL_FOUND AND AUTOMAKE_FOUND AND AUTOCONF_FOUND ) - set( BUILD_XRDEC TRUE ) + if( USE_SYSTEM_ISAL ) + if( FORCE_ENABLED ) + find_package(isal REQUIRED) + else() + find_package(isal) + endif() + if( ISAL_FOUND ) + set(BUILD_XRDEC TRUE) + else() + set(BUILD_XRDEC FALSE) + endif() else() - set( BUILD_XRDEC FALSE ) + if( FORCE_ENABLED ) + find_package( Yasm REQUIRED ) + find_package( LibTool REQUIRED ) + find_package( AutoMake REQUIRED ) + find_package( AutoConf REQUIRED ) + else() + find_package( Yasm ) + find_package( LibTool ) + find_package( AutoMake ) + find_package( AutoConf ) + endif() + if( YASM_FOUND AND LIBTOOL_FOUND AND AUTOMAKE_FOUND AND AUTOCONF_FOUND ) + set( BUILD_XRDEC TRUE ) + else() + set( BUILD_XRDEC FALSE ) + endif() endif() endif() From 7d6a3e80307be64f437a67b1e9e67ea81f259d94 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 22 Jun 2023 15:38:50 +0200 Subject: [PATCH 165/442] [CMake] Use pkg_check_modules in Findlibuuid.cmake The call to pkg_check_modules(UUID uuid) was overriding variables set by find_package(libuuid REQUIRED), which could sometimes set values that caused compilation to fail, or hide problems with Findlibuuid.cmake. Since libuuid is a required dependency of XRootD, we can remove calls to pkg_check_modules(UUID uuid) from our CMake files and use pkg-config to find libuuid from within the Findlibuuid.cmake module. This fixes an issue observed when libuuid is not installed in a standard location, since without a search CMake would just try to use -luuid to link and lead to missing symbols in a few places. Performing an actual search for the library using pkg-config and using all flags required to link, including any -L flag returned by pkg-config, resolves the issue. --- cmake/Findlibuuid.cmake | 40 +++++++++++++++++++++++++------------- cmake/XRootDFindLibs.cmake | 4 +--- 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/cmake/Findlibuuid.cmake b/cmake/Findlibuuid.cmake index 7f07c4b53e0..239ae22d579 100644 --- a/cmake/Findlibuuid.cmake +++ b/cmake/Findlibuuid.cmake @@ -17,7 +17,7 @@ # # This module will set the following variables in your project: # -# ``UUID_FOUND`` +# ``LIBUUID_FOUND`` # True if libuuid has been found. # ``UUID_INCLUDE_DIRS`` # Where to find uuid/uuid.h. @@ -34,9 +34,9 @@ # ``UUID_INCLUDE_DIR`` # where to find the uuid/uuid.h header (same as UUID_INCLUDE_DIRS). -include(CheckCXXSymbolExists) -include(CheckLibraryExists) -include(FindPackageHandleStandardArgs) +foreach(var FOUND INCLUDE_DIR INCLUDE_DIRS LIBRARY LIBRARIES) + unset(UUID_${var} CACHE) +endforeach() if(NOT UUID_INCLUDE_DIR) find_path(UUID_INCLUDE_DIR uuid/uuid.h) @@ -46,25 +46,37 @@ if(EXISTS UUID_INCLUDE_DIR) set(UUID_INCLUDE_DIRS ${UUID_INCLUDE_DIR}) set(CMAKE_REQUIRED_INCLUDES ${UUID_INCLUDE_DIRS}) check_cxx_symbol_exists("uuid_generate_random" "uuid/uuid.h" _uuid_header_only) + unset(CMAKE_REQUIRED_INCLUDES) endif() if(NOT _uuid_header_only AND NOT UUID_LIBRARY) - check_library_exists("uuid" "uuid_generate_random" "" _have_libuuid) - if(_have_libuuid) - set(UUID_LIBRARY "uuid") - set(UUID_LIBRARIES ${UUID_LIBRARY}) + find_package(PkgConfig) + + if(PKG_CONFIG_FOUND) + if(${libuuid_FIND_REQUIRED}) + set(libuuid_REQUIRED REQUIRED) + endif() + + pkg_check_modules(UUID ${libuuid_REQUIRED} uuid) + + set(UUID_LIBRARIES ${UUID_LDFLAGS}) + set(UUID_LIBRARY ${UUID_LIBRARIES}) + set(UUID_INCLUDE_DIRS ${UUID_INCLUDE_DIRS}) + set(UUID_INCLUDE_DIR ${UUID_INCLUDE_DIRS}) endif() endif() -unset(CMAKE_REQUIRED_INCLUDES) -unset(_uuid_header_only) -unset(_have_libuuid) - -if(NOT TARGET uuid::uuid) +if(UUID_FOUND AND NOT TARGET uuid::uuid) add_library(uuid::uuid INTERFACE IMPORTED) set_property(TARGET uuid::uuid PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${UUID_INCLUDE_DIRS}") set_property(TARGET uuid::uuid PROPERTY INTERFACE_LINK_LIBRARIES "${UUID_LIBRARIES}") endif() -find_package_handle_standard_args(libuuid DEFAULT_MSG UUID_INCLUDE_DIR) +if(_uuid_header_only) + find_package_handle_standard_args(libuuid DEFAULT_MSG UUID_INCLUDE_DIR) +else() + find_package_handle_standard_args(libuuid DEFAULT_MSG UUID_INCLUDE_DIR UUID_LIBRARY) +endif() + +unset(_uuid_header_only) mark_as_advanced(UUID_INCLUDE_DIR UUID_LIBRARY) diff --git a/cmake/XRootDFindLibs.cmake b/cmake/XRootDFindLibs.cmake index a79d68a2839..0436b05cf3d 100644 --- a/cmake/XRootDFindLibs.cmake +++ b/cmake/XRootDFindLibs.cmake @@ -113,13 +113,11 @@ if( ENABLE_MACAROONS ) include(FindPkgConfig REQUIRED) if( FORCE_ENABLED ) pkg_check_modules(JSON REQUIRED json-c) - pkg_check_modules(UUID REQUIRED uuid) else() pkg_check_modules(JSON json-c) - pkg_check_modules(UUID uuid) endif() endif() - if( MACAROONS_FOUND AND JSON_FOUND AND UUID_FOUND ) + if( MACAROONS_FOUND AND JSON_FOUND ) set( BUILD_MACAROONS TRUE ) else() set( BUILD_MACAROONS FALSE ) From b4c7d82873fd677cdde5082c46564a368f0c64c5 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 22 Jun 2023 19:37:31 +0200 Subject: [PATCH 166/442] [CMake] Update XRootDSummary.cmake - Remove old message about cryto, as it's now always enabled. - Remove UUID_FOUND from prerequisites for Macaroons, as that's a required dependency of XRootD. - Do not lie about XrdEc being enabled when it was disabled due unsatisfied dependencies. --- cmake/XRootDSummary.cmake | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/cmake/XRootDSummary.cmake b/cmake/XRootDSummary.cmake index a64fcac3e98..cf3548f1aa2 100644 --- a/cmake/XRootDSummary.cmake +++ b/cmake/XRootDSummary.cmake @@ -2,20 +2,19 @@ # Print the configuration summary #------------------------------------------------------------------------------- set( TRUE_VAR TRUE ) -component_status( READLINE ENABLE_READLINE READLINE_FOUND ) component_status( FUSE BUILD_FUSE FUSE_FOUND ) +component_status( HTTP BUILD_HTTP OPENSSL_FOUND ) component_status( KRB5 BUILD_KRB5 KERBEROS5_FOUND ) -component_status( XRDCL ENABLE_XRDCL TRUE_VAR ) -component_status( XRDCLHTTP ENABLE_XRDCLHTTP DAVIX_FOUND ) +component_status( MACAROONS BUILD_MACAROONS MACAROONS_FOUND AND JSON_FOUND AND BUILD_HTTP ) +component_status( PYTHON BUILD_PYTHON Python_Interpreter_FOUND AND Python_Development_FOUND ) +component_status( READLINE ENABLE_READLINE READLINE_FOUND ) +component_status( SCITOKENS BUILD_SCITOKENS SCITOKENSCPP_FOUND ) component_status( TESTS BUILD_TESTS CPPUNIT_FOUND AND GTEST_FOUND ) -component_status( HTTP BUILD_HTTP OPENSSL_FOUND ) component_status( TPC BUILD_TPC CURL_FOUND ) -component_status( MACAROONS BUILD_MACAROONS MACAROONS_FOUND ) -component_status( PYTHON BUILD_PYTHON Python_Interpreter_FOUND AND Python_Development_FOUND ) component_status( VOMSXRD BUILD_VOMS VOMS_FOUND ) -component_status( XRDEC ENABLE_XRDEC TRUE_VAR ) -component_status( MACAROONS BUILD_MACAROONS MACAROONS_FOUND AND JSON_FOUND AND UUID_FOUND ) -component_status( SCITOKENS BUILD_SCITOKENS SCITOKENSCPP_FOUND ) +component_status( XRDCL ENABLE_XRDCL TRUE_VAR ) +component_status( XRDCLHTTP ENABLE_XRDCLHTTP DAVIX_FOUND ) +component_status( XRDEC BUILD_XRDEC TRUE_VAR ) message( STATUS "----------------------------------------" ) message( STATUS "Installation path: " ${CMAKE_INSTALL_PREFIX} ) @@ -25,18 +24,24 @@ message( STATUS "Build type: " ${CMAKE_BUILD_TYPE} ) message( STATUS "Plug-in version: " ${PLUGIN_VERSION} ) message( STATUS "" ) message( STATUS "Readline support: " ${STATUS_READLINE} ) -message( STATUS "Fuse support: " ${STATUS_FUSE} ) -message( STATUS "Crypto support: " ${STATUS_CRYPTO} ) +message( STATUS "FUSE support: " ${STATUS_FUSE} ) message( STATUS "Kerberos5 support: " ${STATUS_KRB5} ) message( STATUS "XrdCl: " ${STATUS_XRDCL} ) message( STATUS "XrdClHttp: " ${STATUS_XRDCLHTTP} ) -message( STATUS "Tests: " ${STATUS_TESTS} ) message( STATUS "HTTP support: " ${STATUS_HTTP} ) message( STATUS "HTTP TPC support: " ${STATUS_TPC} ) -message( STATUS "Macaroons support: " ${STATUS_MACAROONS} ) message( STATUS "VOMS support: " ${STATUS_VOMSXRD} ) message( STATUS "Python support: " ${STATUS_PYTHON} ) -message( STATUS "XrdEc: " ${STATUS_XRDEC} ) +message( STATUS "Erasure coding: " ${STATUS_XRDEC} ) message( STATUS "Macaroons: " ${STATUS_MACAROONS} ) message( STATUS "SciTokens: " ${STATUS_SCITOKENS} ) +message( STATUS "Tests: " ${STATUS_TESTS} ) message( STATUS "----------------------------------------" ) + +if( FORCE_ENABLED ) + foreach(FEATURE FUSE HTTP KRB5 MACAROONS PYTHON READLINE SCITOKENS TESTS VOMSXRD XRDCL XRDCLHTTP) + if(ENABLE_${FEATURE} AND NOT STATUS_${FEATURE} STREQUAL "yes") + message(SEND_ERROR "Could not enable feature: ${FEATURE}") + endif() + endforeach() +endif() From 2339ba70451eb25f950425a1e5df2348333d8eea Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 22 Jun 2023 16:01:39 +0200 Subject: [PATCH 167/442] [Tests] Update command used to stop the XRootD server Some shells, like dash, do not support the syntax $(< xrootd.pid). Also, use TERM signal explicitly, to ensure a graceful shutdown. --- tests/XRootD/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/XRootD/CMakeLists.txt b/tests/XRootD/CMakeLists.txt index f25d24211d1..5593ab4e739 100644 --- a/tests/XRootD/CMakeLists.txt +++ b/tests/XRootD/CMakeLists.txt @@ -23,7 +23,8 @@ add_test(NAME XRootD::start $ -b -k fifo -l xrootd.log -s xrootd.pid -c xrootd.cfg") set_tests_properties(XRootD::start PROPERTIES FIXTURES_SETUP XRootD) -add_test(NAME XRootD::stop COMMAND sh -c "sleep 1 && rm -rf data && kill $(< xrootd.pid)") +add_test(NAME XRootD::stop + COMMAND sh -c "sleep 1 && rm -rf data && kill -s TERM $(cat xrootd.pid)") set_tests_properties(XRootD::stop PROPERTIES FIXTURES_CLEANUP XRootD) add_test(NAME XRootD::smoke-test From 7a01bc6639e2d8571ce9ef91e964aa8ad238494e Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 30 Jun 2023 15:10:36 +0200 Subject: [PATCH 168/442] [Tests] Add all.adminpath within build directory to server tests When tests are run on the same system as different users, XRootD would use /tmp/chkpnt and /tmp/ofsEvents for more than one user, and fail to start the server due to permission denied errors as the destinations would exist, but be created by someone else. --- tests/XRootD/xrootd.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/XRootD/xrootd.cfg b/tests/XRootD/xrootd.cfg index 922179d5219..a763a184f8b 100644 --- a/tests/XRootD/xrootd.cfg +++ b/tests/XRootD/xrootd.cfg @@ -3,6 +3,7 @@ all.export / all.sitename XRootD +all.adminpath @CMAKE_CURRENT_BINARY_DIR@/adm oss.localroot @CMAKE_CURRENT_BINARY_DIR@/data xrd.port @XRD_TEST_PORT@ xrootd.chksum chkcgi adler32 crc32c From 365f2fbcf1f29f1cd5ed00d56280b0d84d0368fc Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 22 Jun 2023 15:52:29 +0200 Subject: [PATCH 169/442] [CMake] Fix typo in include directories for XrdEcTests target --- tests/XrdEcTests/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/XrdEcTests/CMakeLists.txt b/tests/XrdEcTests/CMakeLists.txt index 753c30bc730..c6a6014702f 100644 --- a/tests/XrdEcTests/CMakeLists.txt +++ b/tests/XrdEcTests/CMakeLists.txt @@ -10,7 +10,7 @@ target_link_libraries( XrdEc ) target_link_libraries(XrdEcTests PRIVATE ${ISAL_LIBRARIES}) -target_include_directories(XrdEcTests PRIVATE ../common ${CPPUNIT_UNCLUDE_DIRS} ${ISAL_INCLUDE_DIRS}) +target_include_directories(XrdEcTests PRIVATE ../common ${CPPUNIT_INCLUDE_DIRS} ${ISAL_INCLUDE_DIRS}) foreach(TEST AlignedWriteTest From 2d07e3a55e5b77cd758dc3b34bf4ad4d96d3fbe1 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 27 Jun 2023 10:59:26 +0200 Subject: [PATCH 170/442] [CMake] Fix underlinking in XrdEc and XrdEcTests libraries --- src/XrdEc/CMakeLists.txt | 2 +- tests/XrdEcTests/CMakeLists.txt | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/XrdEc/CMakeLists.txt b/src/XrdEc/CMakeLists.txt index 9e030880e37..71c95e5adc7 100644 --- a/src/XrdEc/CMakeLists.txt +++ b/src/XrdEc/CMakeLists.txt @@ -20,7 +20,7 @@ add_library( XrdEcReader.hh XrdEcReader.cc ) -target_link_libraries(XrdEc PRIVATE XrdCl ${ISAL_LIBRARIES}) +target_link_libraries(XrdEc PRIVATE XrdCl XrdUtils ${ISAL_LIBRARIES}) target_include_directories(XrdEc PRIVATE ${ISAL_INCLUDE_DIRS}) set_target_properties( diff --git a/tests/XrdEcTests/CMakeLists.txt b/tests/XrdEcTests/CMakeLists.txt index c6a6014702f..e656ab4b585 100644 --- a/tests/XrdEcTests/CMakeLists.txt +++ b/tests/XrdEcTests/CMakeLists.txt @@ -7,9 +7,12 @@ add_library( target_link_libraries( XrdEcTests PRIVATE - XrdEc ) + XrdEc + XrdCl + XrdUtils + ${ISAL_LIBRARIES} + ${CPPUNIT_LIBRARIES}) -target_link_libraries(XrdEcTests PRIVATE ${ISAL_LIBRARIES}) target_include_directories(XrdEcTests PRIVATE ../common ${CPPUNIT_INCLUDE_DIRS} ${ISAL_INCLUDE_DIRS}) foreach(TEST From 6478b2c82df1f9db282fec73541407f63b102cf2 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 27 Jun 2023 10:12:51 +0200 Subject: [PATCH 171/442] [XrdEc] Mark operator< for XrdCl::FreeSpace as const When compiling with clang on macOS, operator<() must be const to be usable with std::sort. --- src/XrdCl/XrdClEcHandler.hh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/XrdCl/XrdClEcHandler.hh b/src/XrdCl/XrdClEcHandler.hh index e374d78ff45..4839c5378b4 100644 --- a/src/XrdCl/XrdClEcHandler.hh +++ b/src/XrdCl/XrdClEcHandler.hh @@ -32,11 +32,11 @@ namespace XrdCl std::string address; uint64_t freeSpace; FreeSpace() {}; - bool operator<(const FreeSpace &a) + bool operator<(const FreeSpace &a) const { return ((freeSpace > a.freeSpace) ? true : false); } - void Dump() + void Dump() const { std::cout << address << " : " << freeSpace << std::endl; } From 2452c00512f7cca98b5bfaff3015387ec97413bd Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 29 Jun 2023 17:32:09 +0200 Subject: [PATCH 172/442] [CMake] Only build XrdMacaroons if HTTP is enabled XrdMacaroons needs to link against XrdHttpUtils. --- cmake/XRootDFindLibs.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/XRootDFindLibs.cmake b/cmake/XRootDFindLibs.cmake index 0436b05cf3d..68080e2bade 100644 --- a/cmake/XRootDFindLibs.cmake +++ b/cmake/XRootDFindLibs.cmake @@ -117,7 +117,7 @@ if( ENABLE_MACAROONS ) pkg_check_modules(JSON json-c) endif() endif() - if( MACAROONS_FOUND AND JSON_FOUND ) + if( MACAROONS_FOUND AND JSON_FOUND AND BUILD_HTTP ) set( BUILD_MACAROONS TRUE ) else() set( BUILD_MACAROONS FALSE ) From 0a4b76bcc951335bc230ae1bad94b533af145c15 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 30 Jun 2023 12:52:49 +0200 Subject: [PATCH 173/442] [CMake] Print error if XrdMacaroons is enabled but HTTP is not --- cmake/XRootDFindLibs.cmake | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmake/XRootDFindLibs.cmake b/cmake/XRootDFindLibs.cmake index 68080e2bade..2d227df5d0d 100644 --- a/cmake/XRootDFindLibs.cmake +++ b/cmake/XRootDFindLibs.cmake @@ -105,6 +105,9 @@ endif() if( ENABLE_MACAROONS ) if( FORCE_ENABLED ) + if( NOT BUILD_HTTP ) + message(SEND_ERROR "Cannot enable XrdMacaroons without HTTP support") + endif() find_package( Macaroons REQUIRED ) else() find_package( Macaroons ) From 11fa0184120d5449be192ff63610026aaabea3d1 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 30 Jun 2023 12:54:09 +0200 Subject: [PATCH 174/442] [CMake] Disable XrdSsiTests if server is not enabled --- tests/XrdSsiTests/CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/XrdSsiTests/CMakeLists.txt b/tests/XrdSsiTests/CMakeLists.txt index e274d935b2b..a5e019e568f 100644 --- a/tests/XrdSsiTests/CMakeLists.txt +++ b/tests/XrdSsiTests/CMakeLists.txt @@ -1,4 +1,6 @@ - +if ( XRDCL_ONLY ) + return() +endif() add_executable( xrdshmap From 18ae3a257a5e9d6736da3027a1e64452939bf4f7 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 30 Jun 2023 13:04:23 +0200 Subject: [PATCH 175/442] [XrdCeph] Fix include directories of XrdCephTests target CMAKE_SOURCE_DIR refers to XRootD' source directory, so we need to use PROJECT_SOURCE_DIR within XrdCeph, since it is its own project. --- src/XrdCeph/tests/XrdCephTests/CMakeLists.txt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/XrdCeph/tests/XrdCephTests/CMakeLists.txt b/src/XrdCeph/tests/XrdCephTests/CMakeLists.txt index 35d3c465b0a..2011390d85b 100644 --- a/src/XrdCeph/tests/XrdCephTests/CMakeLists.txt +++ b/src/XrdCeph/tests/XrdCephTests/CMakeLists.txt @@ -1,8 +1,3 @@ -include_directories( ${CPPUNIT_INCLUDE_DIRS} ) -include_directories( ${XROOTD_INCLUDE_DIR} ) -include_directories( ${RADOS_INCLUDE_DIR} ) -include_directories( ${CMAKE_SOURCE_DIR}/src ) - message( "XROOTD_INCLUDE_DIR : ${XROOTD_INCLUDE_DIR}" ) add_library( @@ -17,6 +12,12 @@ target_link_libraries( ${ZLIB_LIBRARY} XrdCephPosix ) +target_include_directories(XrdCephTests PRIVATE + ${CPPUNIT_INCLUDE_DIRS} + ${RADOS_INCLUDE_DIR} + ${XROOTD_INCLUDE_DIR} + ${PROJECT_SOURCE_DIR}/src) + #------------------------------------------------------------------------------- # Install #------------------------------------------------------------------------------- From d02bcf00e8e7ce6e1adc7844180e0c05e1a7d98a Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 30 Jun 2023 13:07:34 +0200 Subject: [PATCH 176/442] [XrdCeph] Print summary status messages only when built standalone --- src/XrdCeph/cmake/XRootDSummary.cmake | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/XrdCeph/cmake/XRootDSummary.cmake b/src/XrdCeph/cmake/XRootDSummary.cmake index 0fa0a49500c..d8294d37c2f 100644 --- a/src/XrdCeph/cmake/XRootDSummary.cmake +++ b/src/XrdCeph/cmake/XRootDSummary.cmake @@ -6,6 +6,7 @@ component_status( CEPH TRUE_VAR CEPH_FOUND ) component_status( XROOTD TRUE_VAR XROOTD_FOUND ) component_status( TESTS BUILD_TESTS CPPUNIT_FOUND ) +if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) message( STATUS "----------------------------------------" ) message( STATUS "Installation path: " ${CMAKE_INSTALL_PREFIX} ) message( STATUS "C Compiler: " ${CMAKE_C_COMPILER} ) @@ -17,3 +18,4 @@ message( STATUS "CEPH: " ${STATUS_CEPH} ) message( STATUS "XRootD: " ${STATUS_XROOTD} ) message( STATUS "Tests: " ${STATUS_TESTS} ) message( STATUS "----------------------------------------" ) +endif() From 201b581ccee9d49debeb81e087b5a9f6d48a25ce Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 30 Jun 2023 13:11:46 +0200 Subject: [PATCH 177/442] [XrdCeph] Fix CMake warnings about mismatched names in find_package --- src/XrdCeph/cmake/{FindCPPUnit.cmake => FindCppUnit.cmake} | 2 +- src/XrdCeph/cmake/FindXRootD.cmake | 2 +- src/XrdCeph/cmake/XRootDFindLibs.cmake | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename src/XrdCeph/cmake/{FindCPPUnit.cmake => FindCppUnit.cmake} (90%) diff --git a/src/XrdCeph/cmake/FindCPPUnit.cmake b/src/XrdCeph/cmake/FindCppUnit.cmake similarity index 90% rename from src/XrdCeph/cmake/FindCPPUnit.cmake rename to src/XrdCeph/cmake/FindCppUnit.cmake index 9b015db1b96..cc6e7400191 100644 --- a/src/XrdCeph/cmake/FindCPPUnit.cmake +++ b/src/XrdCeph/cmake/FindCppUnit.cmake @@ -27,4 +27,4 @@ set(CPPUNIT_INCLUDE_DIRS ${CPPUNIT_INCLUDE_DIR}) set(CPPUNIT_LIBRARIES ${CPPUNIT_LIBRARY}) include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(cppunit DEFAULT_MSG CPPUNIT_INCLUDE_DIRS CPPUNIT_LIBRARIES) +find_package_handle_standard_args(CppUnit DEFAULT_MSG CPPUNIT_INCLUDE_DIRS CPPUNIT_LIBRARIES) diff --git a/src/XrdCeph/cmake/FindXRootD.cmake b/src/XrdCeph/cmake/FindXRootD.cmake index 48fe966b858..a3596ebc338 100644 --- a/src/XrdCeph/cmake/FindXRootD.cmake +++ b/src/XrdCeph/cmake/FindXRootD.cmake @@ -32,4 +32,4 @@ set(XROOTD_INCLUDE_DIR ${XROOTD_INCLUDE_DIRS}) set(XROOTD_LIBRARY ${XROOTD_LIBRARIES}) include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(xrootd DEFAULT_MSG XROOTD_INCLUDE_DIRS XROOTD_LIBRARIES) +find_package_handle_standard_args(XRootD DEFAULT_MSG XROOTD_INCLUDE_DIRS XROOTD_LIBRARIES) diff --git a/src/XrdCeph/cmake/XRootDFindLibs.cmake b/src/XrdCeph/cmake/XRootDFindLibs.cmake index 93b4a8fbf32..78570502ca1 100644 --- a/src/XrdCeph/cmake/XRootDFindLibs.cmake +++ b/src/XrdCeph/cmake/XRootDFindLibs.cmake @@ -7,7 +7,7 @@ find_package( XRootD REQUIRED ) find_package( ceph REQUIRED ) if( ENABLE_TESTS ) - find_package( CPPUnit ) + find_package( CppUnit ) if( CPPUNIT_FOUND ) set( BUILD_TESTS TRUE ) else() From 8bb0a1708ead2d29fd4c51076c5627bf4a208a71 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 30 Jun 2023 13:32:27 +0200 Subject: [PATCH 178/442] [CMake] Show status message about Ceph in XRootDSummary.cmake --- cmake/XRootDSummary.cmake | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmake/XRootDSummary.cmake b/cmake/XRootDSummary.cmake index cf3548f1aa2..7df55dda4e5 100644 --- a/cmake/XRootDSummary.cmake +++ b/cmake/XRootDSummary.cmake @@ -2,6 +2,7 @@ # Print the configuration summary #------------------------------------------------------------------------------- set( TRUE_VAR TRUE ) +component_status( CEPH XRDCEPH_SUBMODULE TRUE_VAR) component_status( FUSE BUILD_FUSE FUSE_FOUND ) component_status( HTTP BUILD_HTTP OPENSSL_FOUND ) component_status( KRB5 BUILD_KRB5 KERBEROS5_FOUND ) @@ -23,6 +24,7 @@ message( STATUS "C++ Compiler: " ${CMAKE_CXX_COMPILER} ) message( STATUS "Build type: " ${CMAKE_BUILD_TYPE} ) message( STATUS "Plug-in version: " ${PLUGIN_VERSION} ) message( STATUS "" ) +message( STATUS "Ceph support: " ${STATUS_CEPH} ) message( STATUS "Readline support: " ${STATUS_READLINE} ) message( STATUS "FUSE support: " ${STATUS_FUSE} ) message( STATUS "Kerberos5 support: " ${STATUS_KRB5} ) @@ -39,7 +41,7 @@ message( STATUS "Tests: " ${STATUS_TESTS} ) message( STATUS "----------------------------------------" ) if( FORCE_ENABLED ) - foreach(FEATURE FUSE HTTP KRB5 MACAROONS PYTHON READLINE SCITOKENS TESTS VOMSXRD XRDCL XRDCLHTTP) + foreach(FEATURE CEPH FUSE HTTP KRB5 MACAROONS PYTHON READLINE SCITOKENS TESTS VOMSXRD XRDCL XRDCLHTTP) if(ENABLE_${FEATURE} AND NOT STATUS_${FEATURE} STREQUAL "yes") message(SEND_ERROR "Could not enable feature: ${FEATURE}") endif() From 815d7a955891558a5fd10ef8a8ff52a1495d350f Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 22 Jun 2023 19:41:31 +0200 Subject: [PATCH 179/442] [docs] Add docs/INSTALL.md explaining how to build XRootD from source --- docs/INSTALL.md | 198 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 docs/INSTALL.md diff --git a/docs/INSTALL.md b/docs/INSTALL.md new file mode 100644 index 00000000000..adfd144a410 --- /dev/null +++ b/docs/INSTALL.md @@ -0,0 +1,198 @@ +## Building and Installing XRootD from Source Code + +### XRootD Required and Optional Build Dependencies + +The required build-time dependencies are: bash, cmake, make, a C++ compiler with +support for C++14, kernel headers from the OS, and development packages for +libuuid, OpenSSL, and zlib. The optional features are shown in the table below +together with the dependencies required to enable them. + +| Feature | Dependencies | +|:----------------------|:-----------------------------------------| +| FUSE support | fuse-devel | +| HTTP support (client) | davix-devel | +| HTTP support (server) | libcurl-devel | +| Erasure coding | isa-l / autoconf automake libtool yasm | +| Kerberos5 | krb5-devel | +| Macaroons | json-c-devel libmacaroons-devel | +| Python bindings | python3-devel python3-setuptools | +| readline | readline-devel | +| SciTokens | scitokens-cpp-devel | +| systemd support | systemd-devel | +| VOMS | voms-devel | +| Testing | cppunit-devel gtest-devel | + +Other optional dependencies: tinyxml-devel, libxml2-devel. These have bundled +copies which are used if not found in the system. In the following sections, we +show how to install all available dependencies in most of the supported operating +systems. + +#### RPM-based distributions: RedHat, Fedora, CentOS, Alma, Rocky + +On RedHat Enterprise Linux and derivatives, all dependencies are available, +except for Intel's [isa-l](https://github.com/intel/isa-l) library. You may +build and install isa-l from source, but alternatively, you can simple install +isa-l dependencies (i.e. `autoconf`, `automake`, `libtool`, and `yasm`) and let +the bundled isa-l be built along XRootD. On Fedora, it's not necessary to +install the `epel-release` package, but on most others it is required, as some +dependencies are only available in [EPEL](https://docs.fedoraproject.org/en-US/epel/). +It may also be necessary to enable other repositories manually if they are not +already enabled by default, like the `PowerTools`, `crb`, or `scl` repositories. +On CentOS 7, this can be done by installing `centos-release-scl` in addition to +`epel-release`. The command to install all dependencies is shown below. You may, +however, need to replace `dnf` with `yum` or prepend `sudo` to this command, +depending on the distribution: + +```sh +dnf install \ + cmake \ + cppunit-devel \ + curl-devel \ + davix-devel \ + diffutils \ + file \ + fuse-devel \ + gcc-c++ \ + git \ + gtest-devel \ + json-c-devel \ + krb5-devel \ + libmacaroons-devel \ + libtool \ + libuuid-devel \ + libxml2-devel \ + make \ + openssl-devel \ + python3-devel \ + python3-setuptools \ + readline-devel \ + scitokens-cpp-devel \ + systemd-devel \ + tinyxml-devel \ + voms-devel \ + yasm \ + zlib-devel +``` + +On CentOS 7, the default compiler is too old, so `devtoolset-11` should be used +instead. It can be enabled afterwards with `source /opt/rh/devtoolset-11/enable`. +Moreover, `cmake` installs CMake 2.x on CentOS 7, so `cmake3` needs to be installed +instead and `cmake3`/`ctest3` used in the command line. + +#### DEB-based distrubutions: Debian 11, Ubuntu 22.04 + +On Debian 11 and Ubuntu 22.04, all necessary dependencies are available in the +system. In earlier versions, some of XRootD's optional features may have to be +disabled if their dependencies are not available to be installed via apt. + +```sh +apt install \ + cmake \ + davix-dev \ + g++ \ + libcppunit-dev \ + libcurl4-openssl-dev \ + libfuse-dev \ + libgtest-dev \ + libisal-dev \ + libjson-c-dev \ + libkrb5-dev \ + libmacaroons-dev \ + libreadline-dev \ + libscitokens-dev \ + libssl-dev \ + libsystemd-dev \ + libtinyxml-dev \ + libxml2-dev \ + make \ + pkg-config \ + python3-dev \ + python3-setuptools \ + uuid-dev \ + voms-dev \ + zlib1g-dev +``` + +### Homebrew on macOS + +On macOS, XRootD is available to install via Homebrew. We recommend using it to +install dependencies as well when building XRootD from source: + +```sh +brew install \ + cmake \ + cppunit \ + curl \ + davix \ + gcc \ + googletest \ + isa-l \ + krb5 \ + libxml2 \ + libxcrypt \ + make \ + openssl@1.1 \ + ossp-uuid \ + pkg-config \ + python@3.11 \ + readline \ + zlib \ +``` + +Homebrew is also available on Linux. The dependency `ossp-uuid` is not required +in this case, and `libfuse@2` can be installed to enable FUSE support. + +## Building from Source Code with CMake + +XRootD uses [CMake](https://cmake.org) as its build generator. CMake +is used during configuration to generate the actual build system that +is used to build the project with a build tool like `make` or `ninja`. +If you are new to CMake, we recommend reading the official +[tutorial](https://cmake.org/cmake/help/latest/guide/tutorial/index.html) +which provides a step-by-step guide on how to get started with CMake. +For the anxious user, assuming the repository is cloned into the `xrootd` +directory under the current working directory, the basic workflow is + +```sh +cmake -S xrootd -B xrootd_build +cmake --build xrootd_build --parallel +cmake --install xrootd_build +``` + +If you'd like to install somewhere other than the default of `/usr/local`, +then you need to pass the option `-DCMAKE_INSTALL_PREFIX=` to +the first command, with the location where you'd like to install as argument. + +The table below documents the main build options that can be used to customize +the build: + +| CMake Option | Default | Description +|:-------------------|:--------|:-------------------------------------------------------------- +| `ENABLE_FUSE` | TRUE | Enable FUSE filesystem driver +| `ENABLE_HTTP` | TRUE | Enable HTTP component (XrdHttp) +| `ENABLE_KRB5` | TRUE | Enable Kerberos 5 authentication +| `ENABLE_MACAROONS` | TRUE | Enable Macaroons plugin (server only) +| `ENABLE_PYTHON` | TRUE | Enable building of the Python bindings +| `ENABLE_READLINE` | TRUE | Enable readline support in the commandline utilities +| `ENABLE_SCITOKENS` | TRUE | Enable SciTokens plugin (server only) +| `ENABLE_VOMS` | TRUE | Enable VOMS plug-in +| `ENABLE_XRDCLHTTP` | TRUE | Enable xrdcl-http plugin +| `ENABLE_XRDCL` | TRUE | Enable XRootD client +| `ENABLE_XRDEC` | FALSE | Enable support for erasure coding +| `ENABLE_ASAN` | FALSE | Build with adress sanitizer enabled +| `ENABLE_TSAN` | FALSE | Build with thread sanitizer enabled +| `ENABLE_TESTS` | FALSE | Enable unit tests +| `FORCE_ENABLED` | FALSE | Fail CMake configuration if enabled components cannot be built +| `XRDCL_LIB_ONLY` | FALSE | Build only the client libraries and necessary dependencies +| `XRDCL_ONLY` | FALSE | Build only the client and necessary dependencies +| `USE_SYSTEM_ISAL` | FALSE | Use isa-l library installed in the system + +### Running XRootD Tests + +After you've built the project, you should run the unit and integration tests +with CTest to ensure that they all pass. This can be done simply by running +`ctest` from the build directory. However, we also provide a CMake script to +automate more advanced testing, including enabling a coverage report, memory checking with +`valgrind`, and static analysis with `clang-tidy`. The script is named [test.cmake](../test.cmake) +and can be found in the top directory of the repository. Its usage is documented in the file +[TESTING.md](TESTING.md). From 513e784d6d5d6ab18849c9aaa4a04af831dff5b4 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 22 Jun 2023 10:56:30 +0200 Subject: [PATCH 180/442] [docs] Add docs/TESTING.md explaining how to use test.cmake script --- docs/TESTING.md | 408 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 408 insertions(+) create mode 100644 docs/TESTING.md diff --git a/docs/TESTING.md b/docs/TESTING.md new file mode 100644 index 00000000000..6a63b9c9fd3 --- /dev/null +++ b/docs/TESTING.md @@ -0,0 +1,408 @@ +## Configuring and Running XRootD tests with CTest + +XRootD tests are divided into two main categories: unit and integration +tests that can be run directly with CTest, and containerized tests that +are required to be run from within a container built with docker or podman. +This document describes how to run the former, that is, the tests that are +run just with CTest. This document assumes you are already familiar with +how to build XRootD from source. If you need instructions on how to do that, +please see the [INSTALL.md](INSTALL.md) file. There you will also find a full +list of optional features and which dependencies are required to enable them. + +### Enabling tests during CMake configuration + +XRootD unit and integration tests are enabled via the CMake configuration +option `-DENABLE_TESTS=ON`. Unit and integration tests may depend on CppUnit +or GoogleTest (a migration from CppUnit to GoogleTest is ongoing). Therefore, +the development packages for CppUnit and GoogleTest (i.e. `cppunit-devel` and +`gtest-devel` on RPM-based distributions) are needed in order to enable all +available tests. Here we discuss how to use the [test.cmake](../test.cmake) +CTest script to run all steps to configure and build the project, then run all +tests using CTest. The script [test.cmake](../test.cmake) can be generically +called from the top directory of the repository as shown below + +```sh +xrootd $ ctest -V -S test.cmake +-- Using CMake cache file config.cmake +Run dashboard with model Experimental + Source directory: xrootd + Build directory: xrootd/build + Reading ctest configuration file: xrootd/CTestConfig.cmake + Site: example.cern.ch (Linux - x86_64) + Build name: Linux GCC 12.3.1 RelWithDebInfo + Use Experimental tag: 20230622-0712 + Updating the repository: xrootd + Use GIT repository type + Old revision of repository is: 6fce466a5f9b369f45ef2592c2ae246de1f13103 + New revision of repository is: 6fce466a5f9b369f45ef2592c2ae246de1f13103 + Gathering version information (one . per revision): + +Configure project + Each . represents 1024 bytes of output + ..... Size of output: 4K +Build project + Each symbol represents 1024 bytes of output. + '!' represents an error and '*' a warning. + .................................................. Size: 49K + .. Size of output: 52K + 0 Compiler errors + 0 Compiler warnings +Test project xrootd/build + Start 1: XrdCl::URLTest.LocalURLs + 1/23 Test #1: XrdCl::URLTest.LocalURLs ..................... Passed 0.01 sec + Start 2: XrdCl::URLTest.RemoteURLs + 2/23 Test #2: XrdCl::URLTest.RemoteURLs .................... Passed 0.12 sec + Start 3: XrdCl::URLTest.InvalidURLs + 3/23 Test #3: XrdCl::URLTest.InvalidURLs ................... Passed 0.01 sec + Start 4: XrdHttpTests.checksumHandlerTests + 4/23 Test #4: XrdHttpTests.checksumHandlerTests ............ Passed 0.01 sec + Start 5: XrdHttpTests.checksumHandlerSelectionTest + 5/23 Test #5: XrdHttpTests.checksumHandlerSelectionTest .... Passed 0.01 sec + Start 6: XrdCl::Poller + 6/23 Test #6: XrdCl::Poller ................................ Passed 5.01 sec + Start 7: XrdCl::Socket + 7/23 Test #7: XrdCl::Socket ................................ Passed 0.02 sec + Start 8: XrdCl::Utils + 8/23 Test #8: XrdCl::Utils ................................. Passed 8.01 sec + Start 9: XrdEc::AlignedWriteTest + 9/23 Test #9: XrdEc::AlignedWriteTest ...................... Passed 0.06 sec + Start 10: XrdEc::SmallWriteTest +10/23 Test #10: XrdEc::SmallWriteTest ........................ Passed 0.06 sec + Start 11: XrdEc::BigWriteTest +11/23 Test #11: XrdEc::BigWriteTest .......................... Passed 0.05 sec + Start 12: XrdEc::VectorReadTest +12/23 Test #12: XrdEc::VectorReadTest ........................ Passed 0.06 sec + Start 13: XrdEc::IllegalVectorReadTest +13/23 Test #13: XrdEc::IllegalVectorReadTest ................. Passed 0.06 sec + Start 14: XrdEc::AlignedWrite1MissingTest +14/23 Test #14: XrdEc::AlignedWrite1MissingTest .............. Passed 0.06 sec + Start 15: XrdEc::AlignedWrite2MissingTest +15/23 Test #15: XrdEc::AlignedWrite2MissingTest .............. Passed 0.05 sec + Start 16: XrdEc::AlignedWriteTestIsalCrcNoMt +16/23 Test #16: XrdEc::AlignedWriteTestIsalCrcNoMt ........... Passed 0.06 sec + Start 17: XrdEc::SmallWriteTestIsalCrcNoMt +17/23 Test #17: XrdEc::SmallWriteTestIsalCrcNoMt ............. Passed 0.06 sec + Start 18: XrdEc::BigWriteTestIsalCrcNoMt +18/23 Test #18: XrdEc::BigWriteTestIsalCrcNoMt ............... Passed 0.06 sec + Start 19: XrdEc::AlignedWrite1MissingTestIsalCrcNoMt +19/23 Test #19: XrdEc::AlignedWrite1MissingTestIsalCrcNoMt ... Passed 0.06 sec + Start 20: XrdEc::AlignedWrite2MissingTestIsalCrcNoMt +20/23 Test #20: XrdEc::AlignedWrite2MissingTestIsalCrcNoMt ... Passed 0.06 sec + Start 21: XRootD::start +21/23 Test #21: XRootD::start ................................ Passed 0.01 sec + Start 23: XRootD::smoke-test +22/23 Test #23: XRootD::smoke-test ........................... Passed 1.63 sec + Start 22: XRootD::stop +23/23 Test #22: XRootD::stop ................................. Passed 1.00 sec + +100% tests passed, 0 tests failed out of 23 + +Total Test time (real) = 16.55 sec +``` + +For full verbose output, use `-VV` instead of `-V`. We recommend using at least `-V` +to add some verbosity. The output is too terse to be useful otherwise. + +### Customizing the Build + +#### Selecting a build type, compile flags, optional features, etc + +Since the script is targeted for usage with continuous integration, it tries to +load a configuration file from the `.ci` subdirectory in the source directory. +The default configuration is in the `config.cmake` file. This file is used to +pre-load the CMake cache. If found, it is passed to CMake during configuration +via the `-C` option. This file is a CMake script that should only contain CMake +`set()` commands using the `CACHE` option to populate the cache. Some effort is +made to detect and use a more specific configuration file than the generic +`config.cmake` that is used by default. For example, on Ubuntu, a file named +`ubuntu.cmake` will be used if present. The script also tries to detect the +version of the OS and use a more specific file if found for that version. For +example, on Alma Linux 8, one could use `almalinux8.cmake` which would have +higher precedence than `almalinux.cmake`. The default `config.cmake` file will +enable as many options as possible without failing if the dependencies are not +installed, so it should be sufficient in most cases. + +The behavior of the [test.cmake](../test.cmake) script can also be influenced +by environment variables like `CC`, `CXX`, `CXXFLAGS`, `CMAKE_ARGS`, `CMAKE_GENERATOR`, +`CMAKE_BUILD_PARALLEL_LEVEL`, `CTEST_PARALLEL_LEVEL`, and `CTEST_CONFIGURATION_TYPE`. +These are mostly self-explanatory and can be used to override the provided defaults. +For example, to build with `clang` and use `ninja` as CMake generator, one can run: + +```sh +xrootd $ env CC=clang CXX=clang++ CMAKE_GENERATOR=Ninja ctest -V -S test.cmake +``` + +For performance analysis and profiling with `perf`, we recommend building with + +```sh +xrootd $ CXXFLAGS='-fno-omit-frame-pointer' ctest -V -C RelWithDebInfo -S test.cmake +``` + +For enabling link-time optimizations (LTO), we recommend using +``` +CXXFLAGS='-flto -Werror=odr -Werror=lto-type-mismatch -Werror=strict-aliasing' +``` + +This turns some important warnings into errors to avoid potential runtime issues +with LTO. Please see GCC's manual page for descriptions of each of the warnings +above. XRootD also support using address and thread sanitizers, via the options +`-DENABLE_ASAN=ON` and `-DENABLE_TSAN=ON`, respectively. These should be enabled +using `CMAKE_ARGS`, as shown below + +```sh +$ env CMAKE_ARGS="-DENABLE_TSAN=1" ctest -V -S test.cmake +``` + +Note that options passed by setting `CMAKE_ARGS` in the environment have higher +precedence than what is in the pre-loaded cache file, so this method can be used +to override the defaults without having to edit the pre-loaded cache file. + +#### Enabling coverage, memory checking, and static analysis + +The [test.cmake](../test.cmake) has are several options that allow the developer +to customize the build being tested. The main options are shown in the table +below: + +| Option | Description | +|:------------------------:|:-------------------------------------------| +| **-DCOVERAGE=ON** | Enables test coverage analysis with gcov | +| **-DMEMCHECK=ON** | Enables memory checking with valgrind | +| **-DSTATIC_ANALYSIS=ON** | Enables static analysis with clang-tidy | +| **-DINSTALL=ON** | Enables an extra step to call make install | + +When enabling coverage, a report is generated by default in the `html/` +directory inside the build directory. The results can then be viewed by +opening the file `html/coverage_details.html` in a web browser. The report +generation step can be disabled by passing the option `-DGCOVR=0` to the +script. If `gcovr` is not found, the step will be skipped automatically. +It is recommended to use a debug build to generate the coverage analysis. + +The CMake build type can be specified directly on the command line with the +`-C` option of `ctest`. For example, to run a coverage build in debug mode, +showing test output when a test failure happens, one can run: + +```sh +xrootd $ ctest -V --output-on-failure -C Debug -DCOVERAGE=ON -S test.cmake +``` + +Memory checking is performed with `valgrind` when it is enabled. In this case, +the tests are run twice, once as usual, and once with `valgrind`. The output +logs from running the tests with `valgrind` can be found in the build directory +at `build/Testing/Temporary/MemoryChecker.<#>.log` where `<#>` corresponds to +the test number as shown when running `ctest`. + +Static analysis requires `clang-tidy` and is enabled by setting `CMAKE_CXX_CLANG_TIDY` +for the build. If `clang-tidy` is not in the standard `PATH`, then it may be +necessary to set it manually instead of using the option `-DSTATIC_ANALYSIS=ON`. +For the moment XRootD does not provide yet its own configuration file for +`clang-tidy`, so the defaults will be used for the build. Warnings and errors +coming from static analysis will be shown as part of the regular build, so it is +important to enable full verbosity when enabling static analysis to be able to +see the output from `clang-tidy`. + +The option `-DINSTALL=ON` will enable a step to perform a so-called staged +installation inside the build directory. It can be used to check if the +installation procedure is working as expected, by inspecting the contents of the +`install/` directory inside the build directory after installation: + +```sh +xrootd $ ctest -DINSTALL=1 -S test.cmake + Each . represents 1024 bytes of output + ..... Size of output: 4K + Each symbol represents 1024 bytes of output. + '!' represents an error and '*' a warning. + .................................................. Size: 49K + .. Size of output: 52K + Each symbol represents 1024 bytes of output. + '!' represents an error and '*' a warning. + ............................................*!.... Size: 49K + . Size of output: 50K +Error(s) when building project +xrootd $ tree -L 3 -d build/install +build/install +└── usr + ├── bin + ├── include + │   └── xrootd + ├── lib + │   └── python3.11 + ├── lib64 + └── share + ├── man + └── xrootd + +11 directories +``` + +Note that, as shown above, `CTest` erroneously shows build errors when +installing XRootD with this command. This is because of a deprecation +warning emitted by `pip` while installing the Python bindings and can be +safely ignored. + +### Dependencies Required for Coverage, Memory Checking, and Static Analysis + +#### RPM-based distributions: RedHat, Fedora, CentOS, Alma, Rocky + +The [test.cmake](../test.cmake) script may also need some extra dependencies for +some of its features. For memory checking, `valgrind` is needed, and for static +analysis, `clang-tidy` is needed: + +```sh +dnf install \ + clang \ + clang-tools-extra \ + valgrind +``` + +For coverage, you need to install `gcovr`. It is not available via `yum`/`dnf`, +but can be installed with `pip`. See https://gcovr.com/en/stable/installation.html +for more information. + +Dependencies to run containerized tests with `podman` on RHEL 8/9 and derivatives +can be installed with `dnf groupinstall 'Container Management'`. On CentOS 7 and +RHEL 7, one can use `docker` by installing it with `yum install docker`. In this +case, you will need to ensure that your user is in the `docker` group so that +you can run docker without using `sudo`, and that the system daemon for Docker +is running. A quick test to check if everything is correctly setup is to try to +start a busybox image: `docker run --rm -it busybox`. + +#### DEB-based distributions: Debian 11, Ubuntu 22.04 + +On Debian, Ubuntu, and derivatives, The extra dependencies to use with the [test.cmake](../test.cmake) script can be +installed with: + +```sh +apt install clang clang-tidy valgrind gcovr +``` + +Dependencies to run containerized tests can be installed with +```sh +apt install podman +``` + +## Running XRootD Containerized Tests with Docker and/or Podman + +Some XRootD tests implemented using [CppUnit](http://cppunit.sourceforge.net/) +require a containerized environment to run. This file explains how to use the +recently added `xrd-docker` script to setup and run these containerized tests. +The steps needed are described below. + +1. Creating an XRootD tarball to build a container image + +The first thing that needs to be done is packaging a special tarball with the +right version of XRootD to be used to build the container image for testing. The +command `xrd-docker package` by default creates a tarball named `xrootd.tar.gz` +in the current directory. We recommend changing directory to the `docker/` +directory in the XRootD git repository in order to run these commands. Suppose +we would like to run the tests for release v5.6.0. Then, we would run +```sh +$ xrd-docker package v5.6.0 +``` +to create the tarball that will be used to build the container image. The +tarball created by this command is *not* a standard tarball. It prepares and +adds a spec file with the version already replaced in that the `Dockerfile`s use +during the build, so it is important to use `xrd-docker package` instead of +renaming a standard tarball and placing it in the current directory. + +1. Building a container image for testing + +The next step is to build the container image that will be used for testing. +The container image can be built with either `docker` or `podman`, and the +currently supported OSs are CentOS 7, AlmaLinux 8, and AlmaLinux 9. The command +to build the image is +```sh +$ xrd-docker build +``` +where `` is one of `centos7` (default), `alma8`, or `alma9`. The name +simply chooses which `Dockerfile` is used from the `build/` directory, as they +are named `Dockerfile.` for each suported OS. It is possible to add new +`Dockerfile`s following this same naming scheme to support custom setups and +still use `xrd-docker build` command to build an image. The images built with +`xrd-docker build` are named simply `xrootd` (latest being a default tag added +by docker), and an extra `xrootd:` tag is added to allow having it built for +multiple OSs at the same time. The current `Dockerfile`s use the spec file and +build the image using the RPM packaging workflow, installing dependencies as +declared in the spec file, in the first stage, building the RPMs in a second +stage, then, in a third stage starting from a fresh image, the RPMs built in +stage 2 are copied over and installed with `yum` or `dnf`. + +**Switching between `docker` and `podman` if both are installed + +The `xrd-docker` script takes either `docker` or `podman` if available, in this +order. If you have only one of the two installed, everything should work without +any extra setup, but if you have both installed and would like to use `podman` +instead of `docker` for building the images, it can be done by exporting an +environment variable: +```sh +$ export DOCKER=$(command -v podman) +$ xrd-docker build # uses podman from now on... +``` + +1. Downloading required test data + +Before setting up the containers and running the tests, we must ensure that all +data required to run the tests is present in the `data/` directory. This can be +done with a call to `xrd-docker fetch`. The `data/` directory is mounted into +the container images during setup, and each container in the small cluster +copies part of the data into the root directory to be served via XRootD. + +1. Setting up the cluster of containers + +Now that we have a container image and test data available, it's time to start +the cluster of containers that will be used for testing. The setup will create +a docker or podman network and add each container to it. The cluster structure +is as follows: + +```mermaid +graph TD; + metaman --> man1; + metaman --> man2; + man1 --> srv1; + man1 --> srv2; + man2 --> srv3; + man2 --> srv4; +``` + +The `metaman` container runs `cmsd` and acts as redirector for `man1` and +`man2`, which themselves are also redirectors for `srv{1..4}`, which in turn +serve data files. The container cluster can be setup with the command +```sh +$ xrd-docker setup +``` +where `` is optional and if not given, the `xrootd:latest` image will be +used. + +1. Running the tests + +Now that everything is setup, we can run the tests with + +```sh +$ xrd-docker run +``` + +If something goes wrong, it is possible to enter each container with the command +```sh +$ xrd-docker shell +``` +where `` is one of `metaman` (default), `man{1,2}` or `srv{1..4}`. + +### Appendix + +#### Setting up `podman` + +Unlike `docker`, `podman` may not work out of the box after installing it. If it +doesn't, make sure that you have subuids and subgids setup for your user by +running, for example, the two commands below: + +```sh +$ sudo usermod --add-subuids 1000000-1000999999 $(id -un) +$ sudo usermod --add-subgids 1000000-1000999999 $(id -un) +``` + +You may also have to ensure that container registries in +`/etc/containers/registries.conf`. Usually a usable configuration can be renamed +from `/etc/containers/registries.conf.example`. + +Finally, you may want to try container runtimes other than the default. If you +still have problems getting started, `podman`'s documentation can be found at +`https://podman.io/docs`. From de5f8fbafc0936b49d8cc8f7029ac65932a802ca Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 26 Jun 2023 13:05:42 +0200 Subject: [PATCH 181/442] [docs] Add docs/CONTRIBUTING.md Closes: #1991. Co-authored-by: Andrew Hanushevsky --- docs/CONTRIBUTING.md | 234 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 234 insertions(+) create mode 100644 docs/CONTRIBUTING.md diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md new file mode 100644 index 00000000000..82c1a211d79 --- /dev/null +++ b/docs/CONTRIBUTING.md @@ -0,0 +1,234 @@ +## XRootD Development Model and How to Contribute + +### Versioning + +XRootD software releases are organized into major, minor and patch versions, +with the intent that installing minor version releases with the same major +version do not take long to perform, cause significant downtime, or break +dependent software. New major versions can be more disruptive, and may +substantially change or remove software components. Releases are assigned +version numbers, such as "5.6.0", where: + +* The first number designates a major version. Major versions may introduce + binary incompatibility with previous major versions and may require code + dependent on libraries in the new major version to be recompiled. Generally, + such requirements are limited to code that enhances XRootD functionality (e.g. + plug-ins). User application code that only uses public APIs should continue + to work unchanged. Consequently, major versions are infrequent and are + introduced approximately every 5 years. + +* The second number increments within the major version and designates a minor + version. Minor versions introduce new features within a major version. They + are binary compatible with all versions within the major version and occur as + often as needed to address community needs. On average, there are a few minor + versions per year. + +* The last digit increments whenever changes are applied to a minor version to + fix problems. These occur at random frequency, as often as necessary to fix + problems. Since patch versions represent the minimum change necessary to fix + a problem they provide the forward path for problem resolution. + +* When the first number increments, the second and third numbers are reset to + zero and when the second number increments the third number is reset to zero. + +* A fourth number may be added by EOS as indication that the version of XRootD + used by EOS has been patched after the official release. Patches introduced + in an intermediate release for EOS will be likely included into the following + patch release, unless it is a temporary fix affecting only EOS. + +#### Library versions + +When a library evolves compatibly: existing interfaces are preserved, but new +ones are added the library’s minor version number must be incremented. Since +nothing has been done that would break applications constructed earlier, it is +OK for older applications to be linked with the newer library at run-time. + +If the interfaces in a library shared object change incompatibly, then the major +revision number associated with the library must be incremented. Doing so will +cause run-time linking errors for the applictions constructed with the older +versions of the library and thus will prevent them from running, as opposed to +crashing in an uncontrollable way. + +More information on library versioning is available +[here](https://www.usenix.org/legacy/publications/library/proceedings/als00/2000papers/papers/full_papers/browndavid/browndavid_html/) +and +[here](https://www.akkadia.org/drepper/dsohowto.pdf). + +The project policy is that a change to public interfaces (as defined in the +installed headers) requires a major release - bumping the major version number. + +### Releases and Release Procedure + +Feature releases with current developments will normally be built a few times +per year. Each `master` release is preceded by one or more release candidates +that get tested for bugs and deployment issues in a possibly wide range of +environments. When the release candidates are deemed sufficiently stable, then +the final release is built. + +In addition to the `master` or "feature" releases, "bug fix" releases may be built +whenever needed. These are for bug fixes only, so they normally should not need +release candidates (due to the reduced need for additional testing). + +RPM packages are built for each release, including release candidates. All the +packages are pushed to the testing yum repository. Additionally, all the bug fix +releases and all the final `master` releases are pushed to the stable repository. +See the [download](https://xrootd.slac.stanford.edu/dload.html) page for details. + +### Stable and Develoment Branches + +Beginning with XRootD 5.6.0, the development model is based on two long-term +branches: `master`, and `devel`. + +The `master` branch is the stable branch. It contains released versions of +XRootD and may also contain unreleased bug fixes which do not require a new +minor release. Each patch release for a given major+minor series is created from +the `master` branch by adding any required bug fixes from the `devel` branch to +the `master` branch and tagging a new release, such that all XRootD releases may +be found linearly in git history. + +The `devel` branch is the development branch where all new features and other +developments happen. Each new feature release is created by rebasing, then +(perharps partially) merging the `devel` branch into the `master` branch, then +tagging the relase on `master`. The `devel` branch will be kept current with the +`master` branch by rebasing it after each patch release, to ensure that all bug +fixes are always included in both `master` and `devel`. + +### Guidelines for Contributors + +This section provides guidelines for people who want to contribute +code to the XRootD project. It is adapted from git's own guidelines +for contributors, which can be found at https://github.com/git/git/Documentation/SubmittingPatches. + +#### Deciding what to base your work on + +In general, always base your work on the oldest branch that your +change is relevant to. + +* A bug fix should be based on the latest release tag in general. If + the bug is not present there, then base it on `master`. Otherwise, + if it is only present on `devel`, or a feature branch, then base it + on the tip of `devel` or the relevant feature branch. + +* A new feature should be based on `devel` in general. If the new + feature depends on topics which are not yet merged, fork a branch + from the tip of `devel`, merge these topics to the branch, and work + on that branch. You can get an idea of how the branches relate to + each other with `git log --first-parent master..` or with + `git log --all --decorate --graph --oneline`. + +* Corrections and enhancements to a topic not yet merged into `devel` + should be based on the tip of that topic. Before merging, we recommend + cleaning up the history by squashing commits that are fixups for + earlier commits in the same branch rather than committing a bad change + and the fix for it in separate commits. This is important to preserve + the ability to use git bisect to find which commit introduced a bug. + +#### Make separate commits for logically separate changes + +In your commits, you should give an explanation for the change(s) that +is detailed enough so that a code reviewer can judge if it is a good +thing to do or not without reading the actual patch text to determine +how well the code actually does it. + +If your description is too long, that's probably a sign that the commit +should be split into finer grained pieces. That being said, patches which +plainly describe the things that help reviewers checking the patch and +future maintainers understand the code are very welcome. + +If you are fixing a bug, it would be immensely useful to include a test +demonstrating the problem being fixed, so that not only the problem is +avoided in the future, but also reviewers can more easily verify that the +proposed fix works as expected. Similarly, new features which come with +accompanying tests are much more likely to be reviewed and merged in a +timely fashion. + +When developing XRootD on your own fork, please make sure that the +existing test suite is not broken by any of your changes by pushing to +a branch in your own fork and checking the result of the GitHub Actions +runs. + +#### Describe your changes well + +The log message that explains your changes is just as important as the +changes themselves. The commit messages are the base for creating the +release notes for each release. Hence, each commit message should clearly +state whether it is a bug fix or a new feature whenever that is not +immediately obvious from the nature of the change itself. Moreover, it is +very important to explain not only _what_ your code does, but also _why_ +it does it. + +The first line of the commit message should be a short description of up +to about 50 characters (soft limit, hard limit at 80 characters), and +should skip the full stop. It is encouraged, although not necessary, to +include a tag which identifies the general area of code being modified, +for example "[Server]", "[XrdHttp]", etc. If in doubt, please check the +git log for similar files to see what are the current conventions. + +After the title sentence, you should include a blank line and then the +body of the commit message, which should be a meaningful description that + +* explains the problem the change tries to solve, i.e. what is wrong + with the current code without the change. + +* justifies the way the change solves the problem, i.e. why the + result with the change is better. + +* alternate solutions considered but discarded, if any. + +You should use the imperative to describe your changes, for example: +``` +Change default value of foo to 1024 +``` +instead of +``` +This commit changes the default value of foo to 1024 +``` +or +``` +Changed default default value of foo to 1024 +``` + +Examples of good commit messages: + +``` +Author: Andrew Hanushevsky +Date: Thu Jun 8 18:06:01 2023 -0700 + + [Server] Allow generic prepare plug-in to handle large responses, fixes #2023 +``` + +``` +Author: Brian Bockelman +Date: Sat Feb 18 13:15:49 2023 -0600 + + Map authentication failure to HTTP 401 + + The authentication failure error message was previously mapped to + HTTP 500 (internal server error). 401 Unauthorized (despite its name) + is what HTTP servers typically utilize for authentication problems. +``` + +#### References to other commits, issues, pull requests, etc + +Sometimes, it may be useful to refer to the pull request on GitHub, an +open issue which a commit fixes/closes, or simply an older commit which +may have introduced a regression fixed by the current change. When referring +to older commits, try to use the same format as produced by +``` +git show -s --pretty=reference +``` + +For issues, add a "Closes: #nnnn" or "Fixes: #nnnn" tag to the body of the +commit message (or, even better, to the pull request description). When +linking a change to a specific issue or pull request, please verify in the +GitHub website that the association actually worked. Depending on how you +phrase your message, this may not happen automatically. In that case, it is +also possible to use the "Development" side panel on the right to manually +create the connection between pull requests and issues. If you intend to +have your changes be part of a particular release which is not the next +release being planned, you may also mark your pull request for inclusion +in the desired release by using the "Milestone" side panel on the right. +This can be used as an alternative method of marking a change as "bug fix" +or "feature", depending on if it will only be included in the next patch +release or feature release. Any changes which require a major release must +be marked with the appropriate milestone. From 04fb351b69a2f1789e16b543c7fc8f6f117ffb9b Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 23 Jun 2023 17:20:46 +0200 Subject: [PATCH 182/442] Update README files and convert to Markdown Co-authored-by: Andrew Hanushevsky --- MANIFEST.in | 2 +- README | 83 --------- README.md | 99 ++++++++++ bindings/python/README | 6 - bindings/python/README.md | 236 ++++++++++++++++++++++++ bindings/python/docs/source/install.rst | 2 +- bindings/python/setup.py | 2 +- setup.py | 2 +- 8 files changed, 339 insertions(+), 93 deletions(-) delete mode 100644 README create mode 100644 README.md delete mode 100644 bindings/python/README create mode 100644 bindings/python/README.md diff --git a/MANIFEST.in b/MANIFEST.in index c39b67a5b56..e348ad7b7e9 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,7 +1,7 @@ include *.sh *.py *.in include CMakeLists.txt include COPYING* LICENSE -include VERSION README +include VERSION README.md recursive-include bindings * recursive-include cmake * diff --git a/README b/README deleted file mode 100644 index 4df5d8ff7b2..00000000000 --- a/README +++ /dev/null @@ -1,83 +0,0 @@ - --------------------------------------------------------------------------------- - _ _ ______ _____ - \ \ / (_____ \ _ (____ \ - \ \/ / _____) ) ___ ___ | |_ _ \ \ - ) ( (_____ ( / _ \ / _ \| _)| | | | - / /\ \ | | |_| | |_| | |__| |__/ / - /_/ \_\ |_|\___/ \___/ \___)_____/ - --------------------------------------------------------------------------------- - -1. S U P P O R T E D O P E R A T I N G S Y S T E M S - - XRootD is supported on the following platforms: - - * RedHat Enterprise Linux 7 or greater and their derivatives (Scientific Linux) - compiled with gcc - * MacOSX 10.6 or greater compiled with gcc or clang - -2. B U I L D I N S T R U C T I O N S - -2.0 Build dependecies - - XRootD requires at minimum following packages (RHEL distro): - - * gcc-c++ cmake(3) krb5-devel libuuid-devel libxml2-devel openssl-devel systemd-devel zlib-devel - * devtoolset-7 (only RHEL7) - -2.1 Build system - - XRootD uses CMake to handle the build process. It should build fine with -cmake 3.6 or greater. It may be also possible to use version 2.8 but this -is neither recommended nor supported. - -2.2 Build parameters - - The build process supports the following parameters: - - * CMAKE_INSTALL_PREFIX - indicates where the XRootD files should be installed, - (default: /usr) - * CMAKE_BUILD_TYPE - type of the build: Release/Debug/RelWithDebInfo - * FORCE_32BITS - Force building 32 bit binaries when on Solaris AMD64 - (default: FALSE) - * ENABLE_PERL - enable the perl bindings if possible (default: TRUE) - * ENABLE_FUSE - enable the fuse filesystem driver if possible - (default: TRUE) - * ENABLE_KRB5 - enable the Kerberos 5 authentication if possible - (default: TRUE) - * ENABLE_READLINE - enable the lib readline support in the commandline - utilities (default: TRUE) - * OPENSSL_ROOT_DIR - path to the root of the openssl installation if it - cannot be detected in a standard location - * KERBEROS5_ROOT_DIR - path to the root of the kerberos installation if it - cannot be detected in a standard location - * READLINE_ROOT_DIR - path to the root of the readline installation if it - cannot be detected in a standard location - * CMAKE_C_COMPILER - path to the c compiler that should be used - * CMAKE_CXX_COMPILER - path to the c++ compiler that should be used - -2.3 Build steps - - * on RHEL7 only: scl enable devtoolset-7 /bin/bash - - * Create an empty build directory: - - mkdir build - cd build - - * Generate the build system files using cmake, ie: - - cmake /path/to/the/xrootd/source -DCMAKE_INSTALL_PREFIX=/opt/xrootd \ - -DENABLE_PERL=FALSE - - * Build the source: - - make - - * Install the source: - - make install - -3. P L A T F O R M N O T E S - * None. diff --git a/README.md b/README.md new file mode 100644 index 00000000000..a79193927f0 --- /dev/null +++ b/README.md @@ -0,0 +1,99 @@ +

+ +

+ +## XRootD: eXtended ROOT Daemon + +The [XRootD](http://xrootd.org) project provides a high-performance, +fault-tolerant, and secure solution for handling massive amounts of data +distributed across multiple storage resources, such as disk servers, tape +libraries, and remote sites. It enables efficient data access and movement in a +transparent and uniform manner, regardless of the underlying storage technology +or location. It was initially developed by the High Energy Physics (HEP) +community to meet the data storage and access requirements of the BaBar +experiment at SLAC and later extended to meet the needs of experiments at the +Large Hadron Collider (LHC) at CERN. XRootD is the core technology powering the +[EOS](https://eos-web.web.cern.ch/) distributed filesystem, which is the storage +solution used by LHC experiments and the storage backend for +[CERNBox](https://cernbox.web.cern.ch/). XRootD is also used as the core +technology for global CDN deployments across multiple science domains. + +XRootD is based on a scalable architecture that supports multi-protocol +communications. XRootD provides a set of plugins and tools that allows the user +to configure it freely to deploy data access clusters of any size, and which can +include sophisticated features such as erasure coded files, various methods of +authentication and authorization, as well as integration with other storage +systems like [ceph](https://ceph.io). + +## Documentation + +Genral documentation such as configuration reference guides, and user manuals +can be found on the XRootD website at http://xrootd.org/docs.html. + +## Supported Operating Systems + +XRootD is officially supported on the following platforms: + + * RedHat Enterprise Linux 7 or later and their derivatives + * Debian 11 and Ubuntu 22.04 or later + * macOS 11 (Big Sur) or later + +Support for other operating systems is provided by the community. + +## Installation Instructions + +XRootD is available via official channels in most operating systems. +Installation via the system package manager should be preferred if possible. + +In RPM-based distributions, like CentOS, Alma, Rocky, Fedora, etc, one can +search and install XRootD packages with + +```sh +$ yum search xrootd +$ sudo yum install xrootd* python3-xrootd +``` +or +```sh +$ dnf search xrootd +$ sudo dnf install xrootd* python3-xrootd +``` + +In some distributions, it may be necessary to first install the EPEL release +repository with `yum install epel-release` or `dnf install epel-release`. + +On Debian 11 or later, and Ubuntu 22.04 or later, XRootD can be installed via apt + +```sh +$ apt search xrootd +$ sudo apt install xrootd* python3-xrootd +``` + +On macOS, XRootD is available via Homebrew +```sh +$ brew install xrootd +``` + +Finally, it is also possible to install the XRootD python bindings from PyPI +using pip: +``` +$ pip install xrootd +``` + +For detailed instructions on how to build and install XRootD from source code, +please see [docs/INSTALL.md](docs/INSTALL.md) in this repository. + +## User Support and Bug Reports + +Bugs should be reported using [GitHub issues](https://github.com/xrootd/xrootd/issues). +You can open a new ticket by clicking [here](https://github.com/xrootd/xrootd/issues/new). + +For general questions about XRootD, you can send a message to our user mailing +list at xrootd-l@slac.stanford.edu. Please check XRootD's contact page at +http://xrootd.org/contact.html for further information. + +## Contributing + +User contributions can be submitted via pull request on GitHub. We recommend +that you create your own fork of XRootD on GitHub and use it to submit your +patches. For more detailed instructions on how to contribute, please refer to +the file [docs/CONTRIBUTING.md](docs/CONTRIBUTING.md). diff --git a/bindings/python/README b/bindings/python/README deleted file mode 100644 index 059782ec5f0..00000000000 --- a/bindings/python/README +++ /dev/null @@ -1,6 +0,0 @@ -# XRootD Python Bindings - -This is a set of simple but pythonic bindings for XRootD. It is designed to make -it easy to interface with the XRootD client, by writing Python instead of having -to write C++. - diff --git a/bindings/python/README.md b/bindings/python/README.md new file mode 100644 index 00000000000..17c08f144b1 --- /dev/null +++ b/bindings/python/README.md @@ -0,0 +1,236 @@ +## XRootD Python Bindings + +This is a set of simple but pythonic bindings for XRootD. It is designed to make +it easy to interface with the XRootD client, by writing Python instead of having +to write C++. + +## Installation + +For general instructions on how to use `pip` to install Python packages, please +take a look at https://packaging.python.org/en/latest/tutorials/installing-packages/. +The installation of XRootD and its Python bindings follows for the most part the +same procedure. However, there are some important things that are specific to +XRootD, which we discuss here. Since XRootD 5.6, it is possible to use `pip` to +install only the Python bindings, building it against a pre-installed version of +XRootD. In this case, we recommend using the same version of XRootD for both +parts, even if the newer Python bindings should be usable with older versions of +XRootD 5.x. Suppose that XRootD is installed already into `/usr`. Then, one can +build and install the Python bindings as shown below. + +```sh +xrootd $ cd bindings/python +python $ python3 -m pip install --target install/ . +Processing xrootd/bindings/python + Installing build dependencies ... done + Getting requirements to build wheel ... done + Installing backend dependencies ... done + Preparing metadata (pyproject.toml) ... done +Building wheels for collected packages: xrootd + Building wheel for xrootd (pyproject.toml) ... done + Created wheel for xrootd: filename=xrootd-5.6-cp311-cp311-linux_x86_64.whl size=203460 sha256=8bbd9168... + Stored in directory: /tmp/pip-ephem-wheel-cache-rc_kb_nx/wheels/af/1b/42/bb953908... +Successfully built xrootd +Installing collected packages: xrootd +``` + +The command above installs the Python bindings into the `install/` directory in +the current working directory. The structure is as shown below + +```sh +install/ +|-- XRootD +| |-- __init__.py +| |-- __pycache__ +| | |-- __init__.cpython-311.pyc +| |-- client +| |-- __init__.py +| |-- __pycache__ +| | |-- __init__.cpython-311.pyc +| | |-- _version.cpython-311.pyc +| | |-- copyprocess.cpython-311.pyc +| | |-- env.cpython-311.pyc +| | |-- file.cpython-311.pyc +| | |-- filesystem.cpython-311.pyc +| | |-- finalize.cpython-311.pyc +| | |-- flags.cpython-311.pyc +| | |-- glob_funcs.cpython-311.pyc +| | |-- responses.cpython-311.pyc +| | |-- url.cpython-311.pyc +| | |-- utils.cpython-311.pyc +| |-- _version.py +| |-- copyprocess.py +| |-- env.py +| |-- file.py +| |-- filesystem.py +| |-- finalize.py +| |-- flags.py +| |-- glob_funcs.py +| |-- responses.py +| |-- url.py +| |-- utils.py +|-- pyxrootd +| |-- __init__.py +| |-- __pycache__ +| | |-- __init__.cpython-311.pyc +| |-- client.cpython-311-x86_64-linux-gnu.so +|-- xrootd-5.6.dist-info + |-- INSTALLER + |-- METADATA + |-- RECORD + |-- REQUESTED + |-- WHEEL + |-- direct_url.json + |-- top_level.txt + +8 directories, 36 files +``` + +If you would like to install it for your own user, then use `pip install --user` +instead of `--target`. + +If XRootD is not already installed into the system, then you will want to +install both the client libraries and the Python bindings together using `pip`. +This is possible by using the `setup.py` at the top level of the project, rather +than the one in the `bindings/python` subdirectory. + +```sh +xrootd $ python3 -m pip install --target install/ . +Processing xrootd + Installing build dependencies ... done + Getting requirements to build wheel ... done + Installing backend dependencies ... done + Preparing metadata (pyproject.toml) ... done +Building wheels for collected packages: xrootd + Building wheel for xrootd (pyproject.toml) ... done + Created wheel for xrootd: filename=xrootd-5.6-cp311-cp311-linux_x86_64.whl size=65315683 sha256=a2e7ff52... + Stored in directory: /tmp/pip-ephem-wheel-cache-9g6ovy4q/wheels/47/93/fc/a23666d3... +Successfully built xrootd +Installing collected packages: xrootd +Successfully installed xrootd-5.6 +``` + +In this case, the structure is a bit different than before: + +```sh +xrootd $ tree install/ +install/ +|-- XRootD +| |-- __init__.py +| |-- __pycache__ +| | |-- __init__.cpython-311.pyc +| |-- client +| |-- __init__.py +| |-- __pycache__ +| | |-- __init__.cpython-311.pyc +| | |-- _version.cpython-311.pyc +| | |-- copyprocess.cpython-311.pyc +| | |-- env.cpython-311.pyc +| | |-- file.cpython-311.pyc +| | |-- filesystem.cpython-311.pyc +| | |-- finalize.cpython-311.pyc +| | |-- flags.cpython-311.pyc +| | |-- glob_funcs.cpython-311.pyc +| | |-- responses.cpython-311.pyc +| | |-- url.cpython-311.pyc +| | |-- utils.cpython-311.pyc +| |-- _version.py +| |-- copyprocess.py +| |-- env.py +| |-- file.py +| |-- filesystem.py +| |-- finalize.py +| |-- flags.py +| |-- glob_funcs.py +| |-- responses.py +| |-- url.py +| |-- utils.py +|-- pyxrootd +| |-- __init__.py +| |-- __pycache__ +| | |-- __init__.cpython-311.pyc +| |-- client.cpython-311-x86_64-linux-gnu.so +| |-- libXrdAppUtils.so +| |-- libXrdAppUtils.so.2 +| |-- libXrdAppUtils.so.2.0.0 +| |-- libXrdCl.so +| |-- libXrdCl.so.3 +| |-- libXrdCl.so.3.0.0 +| |-- libXrdClHttp-5.so +| |-- libXrdClProxyPlugin-5.so +| |-- libXrdClRecorder-5.so +| |-- libXrdCrypto.so +| |-- libXrdCrypto.so.2 +| |-- libXrdCrypto.so.2.0.0 +| |-- libXrdCryptoLite.so +| |-- libXrdCryptoLite.so.2 +| |-- libXrdCryptoLite.so.2.0.0 +| |-- libXrdCryptossl-5.so +| |-- libXrdPosix.so +| |-- libXrdPosix.so.3 +| |-- libXrdPosix.so.3.0.0 +| |-- libXrdPosixPreload.so +| |-- libXrdPosixPreload.so.2 +| |-- libXrdPosixPreload.so.2.0.0 +| |-- libXrdSec-5.so +| |-- libXrdSecProt-5.so +| |-- libXrdSecgsi-5.so +| |-- libXrdSecgsiAUTHZVO-5.so +| |-- libXrdSecgsiGMAPDN-5.so +| |-- libXrdSeckrb5-5.so +| |-- libXrdSecpwd-5.so +| |-- libXrdSecsss-5.so +| |-- libXrdSecunix-5.so +| |-- libXrdSecztn-5.so +| |-- libXrdUtils.so +| |-- libXrdUtils.so.3 +| |-- libXrdUtils.so.3.0.0 +| |-- libXrdXml.so +| |-- libXrdXml.so.3 +| |-- libXrdXml.so.3.0.0 +|-- xrootd-5.6.dist-info + |-- COPYING + |-- COPYING.BSD + |-- COPYING.LGPL + |-- INSTALLER + |-- LICENSE + |-- METADATA + |-- RECORD + |-- REQUESTED + |-- WHEEL + |-- direct_url.json + |-- top_level.txt + +8 directories, 78 files +``` + +As can be seen above, now all client libraries have been installed alongside the +C++ Python bindings library (`client.cpython-311-x86_64-linux-gnu.so`). When +installing via `pip` by simply calling `pip install xrootd`, the package that +gets installed is in this mode which includes the libraries. However, command +line tools are not included. + +Binary wheels are supported as well. They can be built using the `wheel` +subcommand instead of `install`: + +```sh +xrootd $ python3.12 -m pip wheel . +Processing xrootd + Installing build dependencies ... done + Getting requirements to build wheel ... done + Installing backend dependencies ... done + Preparing metadata (pyproject.toml) ... done +Building wheels for collected packages: xrootd + Building wheel for xrootd (pyproject.toml) ... done + Created wheel for xrootd: filename=xrootd-5.6-cp312-cp312-linux_x86_64.whl size=65318541 sha256=6c4ed389... + Stored in directory: /tmp/pip-ephem-wheel-cache-etujwyx1/wheels/cf/67/3c/514b21dd... +Successfully built xrootd +``` + +If you want to have everything installed, that is, server, client, command line +tools, etc, then it is recommended to use CMake to build the project, and use +the options `-DENABLE_PYTHON=ON -DINSTALL_PYTHON_BINDINGS=ON` so that CMake +takes care of calling `pip` to install the Python bindings compiled together +with the other components in the end. The option `-DPIP_OPTIONS` can be used to +pass on options to pip, but it should never be used to change the installation +prefix, as that is handled by CMake. Please see [INSTALL.md](../../docs/INSTALL.md) +for full instructions on how to build XRootD from source using CMake. diff --git a/bindings/python/docs/source/install.rst b/bindings/python/docs/source/install.rst index b382321edde..abcdd555839 100644 --- a/bindings/python/docs/source/install.rst +++ b/bindings/python/docs/source/install.rst @@ -2,5 +2,5 @@ **Installing** ``pyxrootd`` =========================== -.. include:: ../../README.rst +.. include:: ../../README.md :start-line: 8 diff --git a/bindings/python/setup.py b/bindings/python/setup.py index 226607c2756..33e4689a6dd 100644 --- a/bindings/python/setup.py +++ b/bindings/python/setup.py @@ -123,7 +123,7 @@ def build_extensions(self): download_url='https://github.com/xrootd/xrootd/archive/v%s.tar.gz' % version, keywords=['XRootD', 'network filesystem'], license='LGPLv3+', - long_description=open(srcdir + '/README').read(), + long_description=open(srcdir + '/README.md').read(), long_description_content_type='text/plain', packages = ['XRootD', 'XRootD.client', 'pyxrootd'], package_dir = { diff --git a/setup.py b/setup.py index dac6967face..16deb9457a3 100644 --- a/setup.py +++ b/setup.py @@ -96,7 +96,7 @@ def build_extensions(self): download_url='https://github.com/xrootd/xrootd/archive/v%s.tar.gz' % version, keywords=['XRootD', 'network filesystem'], license='LGPLv3+', - long_description=open('README').read(), + long_description=open('README.md').read(), long_description_content_type='text/plain', packages = ['XRootD', 'XRootD.client', 'pyxrootd'], package_dir = { From ffd573ee65b4b6470d097307625d8b4f7176ceda Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 14 Jun 2023 15:00:55 +0200 Subject: [PATCH 183/442] XRootD 5.6.0 --- bindings/python/setup.py | 2 +- docs/PreReleaseNotes.txt | 32 -------------------- docs/ReleaseNotes.txt | 64 ++++++++++++++++++++++++++++++++++++++++ genversion.sh | 2 +- setup.py | 2 +- 5 files changed, 67 insertions(+), 35 deletions(-) diff --git a/bindings/python/setup.py b/bindings/python/setup.py index 33e4689a6dd..fabf9c2c966 100644 --- a/bindings/python/setup.py +++ b/bindings/python/setup.py @@ -62,7 +62,7 @@ def get_version(): if version is None: from datetime import date - version = '5.6-rc' + date.today().strftime("%Y%m%d") + version = '5.7-rc' + date.today().strftime("%Y%m%d") if version.startswith('v'): version = version[1:] diff --git a/docs/PreReleaseNotes.txt b/docs/PreReleaseNotes.txt index 7839c8f0d2b..2f867923942 100644 --- a/docs/PreReleaseNotes.txt +++ b/docs/PreReleaseNotes.txt @@ -6,35 +6,3 @@ XRootD Prerelease Notes ================ - -+ **New Features** - **[Server]** Include token information in the monitoring stream (phase 1). - **Commit: d7f4b61 - **[Server]** Allow generic prepare plug-in to handle large responses, fixes #2023 - **Commit: 564f0b2 - **[Server]** Make maxfd be configurable (default is 256k). - **Commit: 937b5ee e1ba7a2 - **[Client]** Add xrdfs cache subcommand to allow for cache evictions. - **Commit: 39f9e0a - **[Xcache]** Implement a file evict function. - **Commit: 952bd9a - **[PSS]** Allow origin to be a directory of a locally mounted file system. - **Commit: 850a14f bb550ea - **[Server]** Add gsi option to display DN when it differs from entity name. - **Commit: 2630fe1 - **[Server]** Allow specfication of minimum and maximum creation mode. - **Commit: 8a6d7c0 - -+ **Major bug fixes** - -+ **Minor bug fixes** - **[Server]** Use correct value for testing vector size. - **Commit: 2c31fdb - **[TLS]** Make sure context is marked invalid if not properly constructed. - **Commit: c6928f0 - -+ **Miscellaneous** - **[Apps]** Make xrdcrc32c consistent with xrdadler32. Fixes #204 - **Commit: a06c635 - **[Server]** Also check for IPv6 ULA's to determine if an address is private. - **Commit: cd970d5 diff --git a/docs/ReleaseNotes.txt b/docs/ReleaseNotes.txt index 36b47e7f48e..b8066aebbb0 100644 --- a/docs/ReleaseNotes.txt +++ b/docs/ReleaseNotes.txt @@ -5,6 +5,70 @@ XRootD Release Notes ============= +------------- +Version 5.6.0 +------------- + ++ **New Features** + **[CMake]** Modernization of build system, now requires CMake 3.16 + **[Client]** Add xrdfs cache subcommand to allow for cache evictions + **[Misc]** Add support for building with musl libc (issue #1645) + **[Python]** Modernization of build system, better support for creating binary wheels, + properly propagating CXXFLAGS (issues #1768, #1807, #1833, #1844, #2001, #2002) + **[Python]** Better handling of unicode strings in the API (issue #2011) + **[Server]** Add gsi option to display DN when it differs from entity name + **[Server]** Allow generic prepare plug-in to handle large responses (issue #2023) + **[Server]** Allow specfication of minimum and maximum creation mode (issue #649) + **[Server]** Make maxfd be configurable (default is 256k) (issue #2010) + **[Server]** Include token information in the monitoring stream (phase 1). + **[Xcache]** Implement a file evict function + **[Xcache,XrdCl]** Increase default number of parallel event loops to 10 (#2047) + **[XrdCl]** xrdcp: number of parallel copy jobs increased from 4 to 128 + **[XrdHttp]** Allow XRootD to return trailers indicating failure (#1912) + **[XrdHttp]** Denote Accept-Ranges in HEAD response (issue #1889) + **[XrdHttp]** Report cache object age for caching proxy mode (#1919) + **[XrdPss]** Allow origin to be a directory of a locally mounted file system + **[XrdSciTokens]** Implement ability to have the token username as a separate claim (#1978) + **[XrdSecgsi]** Use SHA-256 for signatures, and message digest algorithm (issues #1992, #2030) + **[XrdSecztn]** Allow option '-tokenlib none' to disable token validation (issue #1895) + **[XrdSecztn]** Allow to point to a token file using CGI '?xrd.ztn=tokenfile' (#1926) + ++ **Major bug fixes** + **[XrdHttp]** Fix SEGV in case request has object for opaque data but no content (#2007) + **[XrdSecgsi]** Fix memory leaks in GSI authentication (issue #2021) + ++ **Minor bug fixes** + **[Server]** Use correct value for testing vector size + **[XrdCl]** Fix off by one error in failure recovery check in parallel operation (issue #2040) + **[XrdCl]** Fix potential stream timeout when a new request is sent to an idle stream (issue #2042) + **[XrdCl]** Do not enforce TLS when --notlsok option is used in combination with root:// URL. + This allows falling back to e.g. Kerberos authentication on a server with ZTN plugin + enabled if the client has no certificates, hence not able to use TLS (issue #2020) + **[XrdEc]** Fix compilation issues and underlinking on macOS + **[XrdHttp]** Fix error returned when a client provides too many range requests (issue #2003) + **[XrdHttp]** Fix regression where performance markers were missing during an HTTP TPC transfer (#2017) + **[XrdHttp]** Return 404 instead of 500 error code on GET request on non-existent file (issue #2018) + **[XrdHttp]** Return 405 instead of 500 error code on deletion of non-empty directory (issue #1896) + **[XrdHttp]** Update HTTP header handling for chunked encoding and status trailer (#2009) + **[XrdTls]** Make sure TLS context is marked invalid if not properly constructed (issue #2020) + **[XrdTls]** Fix build failure with latest glibc (#2012) + ++ **Miscellaneous** + **[Apps]** Make xrdcrc32c consistent with xrdadler32 (issue #2045) + **[CMake]** Build option ENABLE_CRYPTO has been removed. OpenSSL is always required with XRootD 5 (issue #1827) + **[CMake]** New test.cmake script added to automate configure/build/test cycle + **[CMake]** Fix build with link-time optimizations on 32bit systems (issue #2032) + **[docs]** Update READMEs, contribution, installation, and testing documentation + **[Misc]** Fix warnings from Clang compiler (#1997) + **[Misc]** Add sandboxing settings to systemd service files (initially commented out) (issue #2033) + **[Server]** Also check for IPv6 ULA's to determine if an address is private + **[Tests]** New script xrd-docker added to automate running of dockerized tests (#1974) + **[XProtocol]** Add fallthrough statement for ENOTEMPTY errno code mapping + **[XRootD] ** Update code to no longer rely on using namespace std; (needed to support C++17) + **[XrdCeph]** Submodule merged back into main repository (#2008) + **[XrdCeph]** Minor build system updates and integration with main repository + **[XrdCrypto]** Switch to a fixed set of DH parameters compatible with older OpenSSL (issue #2014) + ------------- Version 5.5.5 ------------- diff --git a/genversion.sh b/genversion.sh index ce9acb80861..39723b89dc8 100755 --- a/genversion.sh +++ b/genversion.sh @@ -30,7 +30,7 @@ elif [[ -r "${VF}" ]] && grep -vq "Format:" "${VF}"; then elif git -C ${SRC} describe >/dev/null 2>&1; then VERSION=$(git -C ${SRC} describe | sed -e 's/-g/+g/') else - VERSION="v5.6-rc$(date +%Y%m%d)" + VERSION="v5.7-rc$(date +%Y%m%d)" fi while [[ $# -gt 0 ]]; do diff --git a/setup.py b/setup.py index 16deb9457a3..7a17a0fa5b9 100644 --- a/setup.py +++ b/setup.py @@ -36,7 +36,7 @@ def get_version(): if version is None: from datetime import date - version = '5.6-rc' + date.today().strftime("%Y%m%d") + version = '5.7-rc' + date.today().strftime("%Y%m%d") if version.startswith('v'): version = version[1:] From b6c02a567748a021d86d6a59eb62b5179030cb31 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 3 Jul 2023 15:19:09 +0200 Subject: [PATCH 184/442] [Tests] Fix typos in echo commands in server test --- tests/XRootD/smoke.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/XRootD/smoke.sh b/tests/XRootD/smoke.sh index ed2f3bfba33..a5f76cc924f 100755 --- a/tests/XRootD/smoke.sh +++ b/tests/XRootD/smoke.sh @@ -78,8 +78,8 @@ for i in $FILES; do REFA32=$(${ADLER32} < ${TMPDIR}/${i}.ref | cut -d' ' -f1) NEWA32=$(${ADLER32} < ${TMPDIR}/${i}.dat | cut -d' ' -f1) SRVA32=$(${XRDFS} ${HOST} query checksum ${TMPDIR}/${i}.ref?cks.type=adler32 | cut -d' ' -f2) - echo "${i}: crc32c: reference: ${REF32C}, server: ${SRV32C}, downloaded: ${REFA32}" - echo "${i}: adler32: reference: ${NEW32C}, server: ${SRVA32}, downloaded: ${NEWA32}" + echo "${i}: crc32c: reference: ${REF32C}, server: ${SRV32C}, downloaded: ${REF32C}" + echo "${i}: adler32: reference: ${NEWA32}, server: ${SRVA32}, downloaded: ${NEWA32}" if [[ "${NEWA32}" != "${REFA32}" || "${SRVA32}" != "${REFA32}" ]]; then echo 1>&2 "$(basename $0): error: adler32 checksum check failed for file: ${i}.dat" From 496a044c3a490e0108cc925d748a2ae569b1a88c Mon Sep 17 00:00:00 2001 From: David Smith Date: Mon, 10 Jul 2023 13:56:44 +0200 Subject: [PATCH 185/442] [XrdCl] Avoid race in postmaster QueryTransport --- src/XrdCl/XrdClPostMaster.cc | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/XrdCl/XrdClPostMaster.cc b/src/XrdCl/XrdClPostMaster.cc index 7b860c3c18c..1389571393d 100644 --- a/src/XrdCl/XrdClPostMaster.cc +++ b/src/XrdCl/XrdClPostMaster.cc @@ -245,11 +245,15 @@ namespace XrdCl AnyObject &result ) { XrdSysRWLockHelper scopedLock( pImpl->pDisconnectLock ); - PostMasterImpl::ChannelMap::iterator it = - pImpl->pChannelMap.find( url.GetChannelId() ); - if( it == pImpl->pChannelMap.end() ) - return Status( stError, errInvalidOp ); - Channel *channel = it->second; + Channel *channel = 0; + { + XrdSysMutexHelper scopedLock2( pImpl->pChannelMapMutex ); + PostMasterImpl::ChannelMap::iterator it = + pImpl->pChannelMap.find( url.GetChannelId() ); + if( it == pImpl->pChannelMap.end() ) + return Status( stError, errInvalidOp ); + channel = it->second; + } if( !channel ) return Status( stError, errNotSupported ); From 2f2206c2c87735726be71d40d52d79bf8530e6da Mon Sep 17 00:00:00 2001 From: David Smith Date: Mon, 10 Jul 2023 14:19:51 +0200 Subject: [PATCH 186/442] [XrdCrypto] Avoid race in GetCryptoFactory --- src/XrdCrypto/XrdCryptoFactory.cc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/XrdCrypto/XrdCryptoFactory.cc b/src/XrdCrypto/XrdCryptoFactory.cc index 8e2a4f8c0c1..61b1112bfb3 100644 --- a/src/XrdCrypto/XrdCryptoFactory.cc +++ b/src/XrdCrypto/XrdCryptoFactory.cc @@ -43,6 +43,7 @@ #include "XrdOuc/XrdOucHash.hh" #include "XrdOuc/XrdOucPinLoader.hh" #include "XrdSys/XrdSysPlatform.hh" +#include "XrdSys/XrdSysPthread.hh" #include "XrdVersion.hh" @@ -418,6 +419,7 @@ XrdCryptoFactory *XrdCryptoFactory::GetCryptoFactory(const char *factoryid) // Static method to load/locate the crypto factory named factoryid static XrdVERSIONINFODEF(myVer,cryptoloader,XrdVNUMBER,XrdVERSION); + static XrdSysMutex fMutex; static FactoryEntry *factorylist = 0; static int factorynum = 0; static XrdOucHash plugins; @@ -426,6 +428,10 @@ XrdCryptoFactory *XrdCryptoFactory::GetCryptoFactory(const char *factoryid) char factobjname[80], libfn[80]; EPNAME("Factory::GetCryptoFactory"); + // Factory entries are tracked in a static list. + // Make sure only one thread may be using or modifying the list at a time. + XrdSysMutexHelper mHelp(fMutex); + // // The id must be defined if (!factoryid) { From e4aeda0f0258876133bad9a7379b929739e2dbae Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 6 Jul 2023 10:30:52 +0200 Subject: [PATCH 187/442] [XrdCl] Add missing argument in call to debug log message --- src/XrdCl/XrdClAsyncSocketHandler.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdCl/XrdClAsyncSocketHandler.cc b/src/XrdCl/XrdClAsyncSocketHandler.cc index 76f1ba355c5..1d380a8ecb7 100644 --- a/src/XrdCl/XrdClAsyncSocketHandler.cc +++ b/src/XrdCl/XrdClAsyncSocketHandler.cc @@ -624,7 +624,7 @@ namespace XrdCl Log *log = DefaultEnv::GetLog(); log->Debug( AsyncSockMsg, "[%s] Received a wait response to endsess request, " "will wait for %d seconds before replaying the endsess request", - waitSeconds ); + pStreamName.c_str(), waitSeconds ); pHSWaitStarted = time( 0 ); pHSWaitSeconds = waitSeconds; } From 5f88beb05ce62363fa10a61ef84db2196a303b82 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Sat, 8 Jul 2023 10:37:48 +0200 Subject: [PATCH 188/442] [CMake] Fix Findlibuuid.cmake to use kernel provided uuid on macOS Fixes: #2052, 7d6a3e80307be64f437a67b1e9e67ea81f259d94. --- cmake/Findlibuuid.cmake | 58 +++++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/cmake/Findlibuuid.cmake b/cmake/Findlibuuid.cmake index 239ae22d579..d203b777f97 100644 --- a/cmake/Findlibuuid.cmake +++ b/cmake/Findlibuuid.cmake @@ -42,41 +42,55 @@ if(NOT UUID_INCLUDE_DIR) find_path(UUID_INCLUDE_DIR uuid/uuid.h) endif() -if(EXISTS UUID_INCLUDE_DIR) +if(IS_DIRECTORY "${UUID_INCLUDE_DIR}") + include(CheckCXXSymbolExists) set(UUID_INCLUDE_DIRS ${UUID_INCLUDE_DIR}) set(CMAKE_REQUIRED_INCLUDES ${UUID_INCLUDE_DIRS}) check_cxx_symbol_exists("uuid_generate_random" "uuid/uuid.h" _uuid_header_only) unset(CMAKE_REQUIRED_INCLUDES) endif() -if(NOT _uuid_header_only AND NOT UUID_LIBRARY) - find_package(PkgConfig) +if(_uuid_header_only) + find_package_handle_standard_args(libuuid DEFAULT_MSG UUID_INCLUDE_DIR) +else() + if(NOT UUID_LIBRARY) + include(CheckLibraryExists) + check_library_exists("uuid" "uuid_generate_random" "" _have_libuuid) - if(PKG_CONFIG_FOUND) - if(${libuuid_FIND_REQUIRED}) - set(libuuid_REQUIRED REQUIRED) + if(_have_libuuid) + set(UUID_LIBRARY "uuid") + set(UUID_LIBRARIES ${UUID_LIBRARY}) + else() + find_package(PkgConfig) + if(PKG_CONFIG_FOUND) + if(${libuuid_FIND_REQUIRED}) + set(libuuid_REQUIRED REQUIRED) + endif() + pkg_check_modules(UUID ${libuuid_REQUIRED} uuid) + set(UUID_LIBRARIES ${UUID_LDFLAGS}) + set(UUID_LIBRARY ${UUID_LIBRARIES}) + set(UUID_INCLUDE_DIRS ${UUID_INCLUDE_DIRS}) + set(UUID_INCLUDE_DIR ${UUID_INCLUDE_DIRS}) + endif() endif() - - pkg_check_modules(UUID ${libuuid_REQUIRED} uuid) - - set(UUID_LIBRARIES ${UUID_LDFLAGS}) - set(UUID_LIBRARY ${UUID_LIBRARIES}) - set(UUID_INCLUDE_DIRS ${UUID_INCLUDE_DIRS}) - set(UUID_INCLUDE_DIR ${UUID_INCLUDE_DIRS}) + unset(_have_libuuid) endif() + find_package_handle_standard_args(libuuid DEFAULT_MSG UUID_INCLUDE_DIR UUID_LIBRARY) endif() -if(UUID_FOUND AND NOT TARGET uuid::uuid) +if(LIBUUID_FOUND AND NOT TARGET uuid::uuid) add_library(uuid::uuid INTERFACE IMPORTED) - set_property(TARGET uuid::uuid PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${UUID_INCLUDE_DIRS}") + set_property(TARGET uuid::uuid PROPERTY INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${UUID_INCLUDE_DIRS}") set_property(TARGET uuid::uuid PROPERTY INTERFACE_LINK_LIBRARIES "${UUID_LIBRARIES}") endif() -if(_uuid_header_only) - find_package_handle_standard_args(libuuid DEFAULT_MSG UUID_INCLUDE_DIR) -else() - find_package_handle_standard_args(libuuid DEFAULT_MSG UUID_INCLUDE_DIR UUID_LIBRARY) -endif() - -unset(_uuid_header_only) mark_as_advanced(UUID_INCLUDE_DIR UUID_LIBRARY) + +if(NOT "${libuuid_FIND_QUIET}") + message(DEBUG "UUID_FOUND = ${LIBUUID_FOUND}") + message(DEBUG "UUID_HEADER_ONLY = ${_uuid_header_only}") + message(DEBUG "UUID_INCLUDE_DIR = ${UUID_INCLUDE_DIR}") + message(DEBUG "UUID_INCLUDE_DIRS = ${UUID_INCLUDE_DIRS}") + message(DEBUG "UUID_LIBRARY = ${UUID_LIBRARY}") + message(DEBUG "UUID_LIBRARIES = ${UUID_LIBRARIES}") +endif() From 94f0125c1d1213604bc975e073d88bff18151e3d Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Sat, 8 Jul 2023 10:44:40 +0200 Subject: [PATCH 189/442] Update INSTALL.md to reflect uuid change --- docs/INSTALL.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/INSTALL.md b/docs/INSTALL.md index adfd144a410..b594ebcee2c 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -132,15 +132,15 @@ brew install \ libxcrypt \ make \ openssl@1.1 \ - ossp-uuid \ pkg-config \ python@3.11 \ readline \ zlib \ ``` -Homebrew is also available on Linux. The dependency `ossp-uuid` is not required -in this case, and `libfuse@2` can be installed to enable FUSE support. +Homebrew is also available on Linux, where `utils-linux` is required as +an extra dependency since uuid symbols are not provided by the kernel like +on macOS. On Linux, `libfuse@2` may be installed to enable FUSE support. ## Building from Source Code with CMake From 00a7df40027e9493b02f1616511681eb82863a3b Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 10 Jul 2023 15:32:14 +0200 Subject: [PATCH 190/442] [CMake,Python] Handle RPATH for Python bindings also on macOS --- bindings/python/setup.py | 6 +++++- bindings/python/src/CMakeLists.txt | 3 +-- setup.py | 6 +++++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/bindings/python/setup.py b/bindings/python/setup.py index fabf9c2c966..ede38f1a2bc 100644 --- a/bindings/python/setup.py +++ b/bindings/python/setup.py @@ -99,11 +99,15 @@ def build_extensions(self): cmake_args = [ '-DPython_EXECUTABLE={}'.format(sys.executable), '-DCMAKE_BUILD_WITH_INSTALL_RPATH=TRUE', - '-DCMAKE_INSTALL_RPATH=$ORIGIN/../../../../$LIB', '-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY={}/{}'.format(self.build_temp, ext.name), '-DCMAKE_LIBRARY_OUTPUT_DIRECTORY={}/{}'.format(extdir, ext.name), ] + if sys.platform == 'darwin': + cmake_args += [ '-DCMAKE_INSTALL_RPATH=@loader_path/../../..' ] + else: + cmake_args += [ '-DCMAKE_INSTALL_RPATH=$ORIGIN/../../../../$LIB' ] + cmake_args += cmdline_args if not os.path.exists(self.build_temp): diff --git a/bindings/python/src/CMakeLists.txt b/bindings/python/src/CMakeLists.txt index 5aed566ee19..5ea0e703445 100644 --- a/bindings/python/src/CMakeLists.txt +++ b/bindings/python/src/CMakeLists.txt @@ -33,8 +33,7 @@ target_compile_options(client PRIVATE -w) # TODO: fix build warnings if(APPLE) set(CMAKE_MACOSX_RPATH TRUE) set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) - set_target_properties(client PROPERTIES - INSTALL_NAME_DIR "@rpath" INSTALL_RPATH "@loader_path") + set_target_properties(client PROPERTIES INSTALL_NAME_DIR "@rpath") endif() # Avoid a call to find_package(XRootD) in order to be able to override diff --git a/setup.py b/setup.py index 7a17a0fa5b9..fcb7b7b6811 100644 --- a/setup.py +++ b/setup.py @@ -70,13 +70,17 @@ def build_extensions(self): cmake_args = [ '-DPython_EXECUTABLE={}'.format(sys.executable), - '-DCMAKE_INSTALL_RPATH=$ORIGIN', '-DCMAKE_BUILD_WITH_INSTALL_RPATH=TRUE', '-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY={}/{}'.format(self.build_temp, ext.name), '-DCMAKE_LIBRARY_OUTPUT_DIRECTORY={}/{}'.format(extdir, ext.name), '-DENABLE_PYTHON=1', '-DENABLE_XRDCL=1', '-DXRDCL_LIB_ONLY=1', '-DPYPI_BUILD=1' ] + if sys.platform == 'darwin': + cmake_args += [ '-DCMAKE_INSTALL_RPATH=@loader_path' ] + else: + cmake_args += [ '-DCMAKE_INSTALL_RPATH=$ORIGIN' ] + cmake_args += cmdline_args if not os.path.exists(self.build_temp): From e6136c9ebaf56b5f17eda2f53ccafcbd930ac3b1 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 10 Jul 2023 15:39:08 +0200 Subject: [PATCH 191/442] [CMake] Set RPATH that works for binaries and libraries on macOS --- cmake/XRootDOSDefs.cmake | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmake/XRootDOSDefs.cmake b/cmake/XRootDOSDefs.cmake index fdd0461e939..475fcfe33da 100644 --- a/cmake/XRootDOSDefs.cmake +++ b/cmake/XRootDOSDefs.cmake @@ -108,9 +108,9 @@ if( APPLE ) set( MacOSX TRUE ) set( XrdClPipelines TRUE ) - if( NOT DEFINED CMAKE_MACOSX_RPATH ) - set( CMAKE_MACOSX_RPATH 1 ) - endif() + set(CMAKE_MACOSX_RPATH TRUE) + set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) + set(CMAKE_INSTALL_RPATH "@loader_path/../lib") # this is here because of Apple deprecating openssl and krb5 set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated-declarations" ) From b2a3ae8a27794e890eb02e79ab72cc95893abe9e Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 10 Jul 2023 11:02:04 +0200 Subject: [PATCH 192/442] [CI] Update macOS build in GitHub Actions --- .github/workflows/build.yml | 74 ++++++++++++------------------------- 1 file changed, 23 insertions(+), 51 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1bd0fd92a1e..892c44284c2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -557,76 +557,48 @@ jobs: runs-on: macos-latest + env: + CC: clang + CXX: clang++ + CMAKE_ARGS: "-DUSE_SYSTEM_ISAL=TRUE;-DINSTALL_PYTHON_BINDINGS=FALSE" + CMAKE_PREFIX_PATH: /usr/local/opt/openssl@3 + steps: - - name: Install external dependencies with homebrew + - name: Install dependencies with Homebrew run: | - brew install \ - cmake \ - cppunit \ - make \ - gcc \ - googletest \ - zlib \ - krb5 \ - ossp-uuid \ - libxml2 \ - openssl@3 + brew install cmake cppunit davix googletest isa-l openssl@3 python@3.11 - - name: Install necessary Python libraries + - name: Install Python dependencies with pip run: | + python3 --version --version python3 -m pip install --upgrade pip setuptools wheel python3 -m pip list + python3 -m site - name: Clone repository uses: actions/checkout@v3 - with: - fetch-depth: 0 - # Given how homebrew installs things, openssl needs to be have its locations - # be given explicitly. - - name: Build with cmake + - name: Build and Run Tests with CTest run: | # workaround for issue #1772, should be removed when that's fixed sudo sed -i -e "s/localhost/localhost $(hostname)/g" /etc/hosts - cd .. - cmake \ - --log-level=DEBUG \ - -DCMAKE_C_COMPILER=clang \ - -DCMAKE_CXX_COMPILER=clang++ \ - -DCMAKE_INSTALL_PREFIX=/usr/local/ \ - -DCMAKE_PREFIX_PATH=/usr/local/opt/openssl@3 \ - -DPython_EXECUTABLE=$(command -v python3) \ - -DENABLE_TESTS=ON \ - -DPIP_OPTIONS="--verbose" \ - -S xrootd \ - -B build - cmake build -LH - cmake \ - --build build \ - --clean-first \ - --parallel $(($(sysctl -n hw.ncpu) - 1)) - cmake --build build --target install - python3 -m pip install --user build/bindings/python - python3 -m pip list + ctest -VV -S test.cmake - - name: Run tests with CTest - run: | - ctest --output-on-failure --test-dir ../build + - name: Install with CMake + run: cmake --install build - - name: Verify install - run: | - command -v xrootd - command -v xrdcp + - name: Install Python Bindings + run: python3 -m pip install --verbose --use-pep517 --user build/bindings/python - - name: Verify Python bindings + - name: Run Post-install Tests run: | export DYLD_LIBRARY_PATH=/usr/local/lib - python3 --version --version - python3 -m pip list + xrdcp --version + python3 -c 'import XRootD; print(XRootD);' + python3 -c 'from pyxrootd import client; print(client);' + python3 -c 'from pyxrootd import client; print(client.XrdVersion_cpp())' + python3 -c 'from XRootD import client; print(client.FileSystem("root://localhost:1094"))' python3 -m pip show xrootd - python3 -c 'import XRootD; print(XRootD)' - python3 -c 'import pyxrootd; print(pyxrootd)' - python3 -c 'from XRootD import client; print(client.FileSystem("root://someserver:1094"))' rpm-centos7: From d0dbce1ef60861322cdf8cecc046571e4a73f394 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 11 Jul 2023 08:53:27 +0200 Subject: [PATCH 193/442] [CMake] Make sure Python is required in PyPI build --- cmake/XRootDFindLibs.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/XRootDFindLibs.cmake b/cmake/XRootDFindLibs.cmake index 2d227df5d0d..e74dacfa0a2 100644 --- a/cmake/XRootDFindLibs.cmake +++ b/cmake/XRootDFindLibs.cmake @@ -173,8 +173,8 @@ if( ENABLE_XRDEC ) endif() endif() -if( ENABLE_PYTHON AND (LINUX OR KFREEBSD OR Hurd OR MacOSX) ) - if( FORCE_ENABLED ) +if( ENABLE_PYTHON ) + if( FORCE_ENABLED OR PYPI_BUILD ) find_package( Python ${XRD_PYTHON_REQ_VERSION} COMPONENTS Interpreter Development REQUIRED ) else() find_package( Python ${XRD_PYTHON_REQ_VERSION} COMPONENTS Interpreter Development ) From 790db260b414687aa5281d75c4390555203c961e Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 11 Jul 2023 09:01:07 +0200 Subject: [PATCH 194/442] [Python] Use PEP517 by default when building Python bindings --- bindings/python/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/python/CMakeLists.txt b/bindings/python/CMakeLists.txt index d29b35cc9f6..85f75aad04e 100644 --- a/bindings/python/CMakeLists.txt +++ b/bindings/python/CMakeLists.txt @@ -13,7 +13,7 @@ else() option(INSTALL_PYTHON_BINDINGS "Install Python bindings" TRUE) if(INSTALL_PYTHON_BINDINGS) - set(PIP_OPTIONS "" CACHE STRING "Install options for pip") + set(PIP_OPTIONS "--use-pep517" CACHE STRING "Install options for pip") install(CODE " execute_process(COMMAND ${Python_EXECUTABLE} -m pip install ${PIP_OPTIONS} From b49d43916794f3da18662f4bbbb6cfce9e2ffc27 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 11 Jul 2023 10:10:34 +0200 Subject: [PATCH 195/442] [CI] Switch Rocky8 RPM builds to Alma8 --- .gitlab-ci.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index abd73cda362..001c83b74d8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -294,18 +294,18 @@ release:cs8-x86_64: except: - branches -release:rocky8-x86_64: +release:alma8-x86_64: stage: build:rpm - image: rockylinux:8 + image: almalinux:8 script: - dnf install -y epel-release - dnf install --nogpg -y gcc-c++ rpm-build tar dnf-plugins-core git python-macros - dnf config-manager --set-enabled powertools - dnf install -y cppunit-devel gtest-devel - dnf -y update libarchive - - mkdir rocky-8-x86_64 + - mkdir alma-8-x86_64 - ./gen-tarball.sh $CI_COMMIT_TAG - - mv xrootd-${CI_COMMIT_TAG#"v"}.tar.gz cs-8-x86_64 + - mv xrootd-${CI_COMMIT_TAG#"v"}.tar.gz alma-8-x86_64 - cd packaging/ - git checkout tags/${CI_COMMIT_TAG} - ./makesrpm.sh --define "_with_python3 1" --define "_with_tests 1" --define "_without_xrootd_user 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" --define "_with_isal 1" -D "dist .el8" @@ -313,12 +313,12 @@ release:rocky8-x86_64: - mkdir RPMS - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_with_python3 1" --define "_with_tests 1" --define "_without_xrootd_user 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" --define "_with_isal 1" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" -D "dist .el8" *.src.rpm - cd .. - - cp packaging/RPMS/*.rpm rocky-8-x86_64 - - cp packaging/*src.rpm rocky-8-x86_64 + - cp packaging/RPMS/*.rpm alma-8-x86_64 + - cp packaging/*src.rpm alma-8-x86_64 artifacts: expire_in: 1 day paths: - - rocky-8-x86_64/ + - alma-8-x86_64/ tags: - docker_node only: From 1a43b4b9d82b51f97c6e98fc6faf5ef1fadd0756 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 11 Jul 2023 10:13:02 +0200 Subject: [PATCH 196/442] [CI] Add RPM release builds for Alma9 --- .gitlab-ci.yml | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 001c83b74d8..dc968a92e37 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -326,6 +326,37 @@ release:alma8-x86_64: except: - branches +release:alma9-x86_64: + stage: build:rpm + image: almalinux:9 + script: + - dnf install -y epel-release + - dnf install --nogpg -y rpm-build tar dnf-plugins-core git python-macros + - dnf config-manager --set-enabled crb + - dnf -y update libarchive + - mkdir alma-9-x86_64 + - ./gen-tarball.sh $CI_COMMIT_TAG + - mv xrootd-${CI_COMMIT_TAG#"v"}.tar.gz alma-9-x86_64 + - cd packaging/ + - git checkout tags/${CI_COMMIT_TAG} + - ./makesrpm.sh --define "_with_python3 1" --define "_with_tests 1" --define "_without_xrootd_user 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" --define "_with_isal 1" -D "dist .el8" + - dnf builddep --nogpgcheck -y *.src.rpm + - mkdir RPMS + - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_with_python3 1" --define "_with_tests 1" --define "_without_xrootd_user 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" --define "_with_isal 1" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" -D "dist .el8" *.src.rpm + - cd .. + - cp packaging/RPMS/*.rpm alma-9-x86_64 + - cp packaging/*src.rpm alma-9-x86_64 + artifacts: + expire_in: 1 day + paths: + - alma-9-x86_64/ + tags: + - docker_node + only: + - web + except: + - branches + release:cc7-x86_64: stage: build:rpm image: gitlab-registry.cern.ch/linuxsupport/cc7-base @@ -645,7 +676,7 @@ publish:rhel:release: - yum install --nogpg -y sssd-client sudo createrepo - prefix=/eos/project/s/storage-ci/www/xrootd - tarball=cc-7-x86_64/xrootd-*.tar.gz - - "for platform in rocky-8-x86_64 cs-8-x86_64 cc-7-x86_64; do + - "for platform in alma-8-x86_64 alma-9-x86_64 cs-8-x86_64 cc-7-x86_64; do path=$prefix/release/$platform/$CI_COMMIT_TAG/; sudo -u stci -H mkdir -p $path/{source,tarball}; sudo -u stci -H cp $platform/*.rpm $path; From 3115b4a3faf216a8934397dd86e1212d3595551b Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 11 Jul 2023 10:33:15 +0200 Subject: [PATCH 197/442] [CI] Do not skip yum/dnf gpg checks --- .gitlab-ci.yml | 66 +++++++++++++++++++++++++------------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index dc968a92e37..e23c6d4a02d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -63,11 +63,11 @@ build:cs9: image: gitlab-registry.cern.ch/linuxsupport/cs9-base script: - dnf install -y epel-release - - dnf install --nogpg -y gcc-c++ rpm-build tar dnf-plugins-core git + - dnf install -y gcc-c++ rpm-build tar dnf-plugins-core git - dnf install -y cppunit-devel gtest-devel - cd packaging/ - ./makesrpm.sh --define "_with_python3 1" --define "_with_tests 1" --define "_without_xrootd_user 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" -D "dist .el9" - - dnf builddep --nogpgcheck -y *.src.rpm + - dnf builddep -y *.src.rpm - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_with_python3 1" --define "_with_tests 1" --define "_without_xrootd_user 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" -D "dist .el9" *.src.rpm - cd .. - mkdir cs-9 @@ -93,11 +93,11 @@ build:cs8: image: gitlab-registry.cern.ch/linuxsupport/cs8-base script: - dnf install -y epel-release - - dnf install --nogpg -y gcc-c++ rpm-build tar dnf-plugins-core git + - dnf install -y gcc-c++ rpm-build tar dnf-plugins-core git - dnf config-manager --set-enabled powertools - cd packaging - ./makesrpm.sh --define "_with_python3 1" --define "_with_tests 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" - - dnf builddep --nogpgcheck -y *.src.rpm + - dnf builddep -y *.src.rpm - dnf -y update libarchive - mkdir RPMS - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_with_python3 1" --define "_with_tests 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" *.src.rpm @@ -124,10 +124,10 @@ build:cc7: image: gitlab-registry.cern.ch/linuxsupport/cc7-base script: - head -n -6 /etc/yum.repos.d/epel.repo > /tmp/epel.repo ; mv -f /tmp/epel.repo /etc/yum.repos.d/epel.repo - - yum install --nogpg -y gcc-c++ rpm-build git python-srpm-macros centos-release-scl + - yum install -y gcc-c++ rpm-build git python-srpm-macros centos-release-scl - cd packaging/ - ./makesrpm.sh --define "_with_python3 1" --define "_with_tests 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" --define "_with_isal 1" - - yum-builddep --nogpgcheck -y *.src.rpm + - yum-builddep -y *.src.rpm - mkdir RPMS - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_with_python3 1" --define "_with_tests 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" --define "_with_isal 1" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" *.src.rpm - cd .. @@ -152,10 +152,10 @@ build:fedora-37: stage: build:rpm image: fedora:37 script: - - dnf install --nogpg -y gcc-c++ rpm-build tar dnf-plugins-core git + - dnf install -y gcc-c++ rpm-build tar dnf-plugins-core git - cd packaging/ - ./makesrpm.sh --define "_with_python3 1" --define "_with_xrdclhttp 1" --define "_with_ceph11 1" - - dnf builddep --nogpgcheck -y *.src.rpm + - dnf builddep -y *.src.rpm - mkdir RPMS - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_with_python3 1" --define "_with_xrdclhttp 1" --define "_with_ceph11 1" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" *.src.rpm - cd .. @@ -181,10 +181,10 @@ build:fedora-38: stage: build:rpm image: fedora:38 script: - - dnf install --nogpg -y gcc-c++ rpm-build tar dnf-plugins-core git + - dnf install -y gcc-c++ rpm-build tar dnf-plugins-core git - cd packaging/ - ./makesrpm.sh --define "_with_python3 1" --define "_with_xrdclhttp 1" --define "_with_ceph11 1" - - dnf builddep --nogpgcheck -y *.src.rpm + - dnf builddep -y *.src.rpm - mkdir RPMS - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_with_python3 1" --define "_with_xrdclhttp 1" --define "_with_ceph11 1" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" *.src.rpm - cd .. @@ -210,10 +210,10 @@ build:fedora-39: stage: build:rpm image: fedora:39 script: - - dnf install --nogpg -y gcc-c++ rpm-build tar dnf-plugins-core git + - dnf install -y gcc-c++ rpm-build tar dnf-plugins-core git - cd packaging/ - ./makesrpm.sh --define "_with_python3 1" --define "_with_xrdclhttp 1" --define "_with_ceph11 1" - - dnf builddep --nogpgcheck -y *.src.rpm + - dnf builddep -y *.src.rpm - mkdir RPMS - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_with_python3 1" --define "_with_xrdclhttp 1" --define "_with_ceph11 1" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" *.src.rpm - cd .. @@ -267,7 +267,7 @@ release:cs8-x86_64: image: gitlab-registry.cern.ch/linuxsupport/cs8-base script: - dnf install -y epel-release - - dnf install --nogpg -y gcc-c++ rpm-build tar dnf-plugins-core git python-macros + - dnf install -y gcc-c++ rpm-build tar dnf-plugins-core git python-macros - dnf config-manager --set-enabled powertools - dnf install -y cppunit-devel gtest-devel - dnf -y update libarchive @@ -277,7 +277,7 @@ release:cs8-x86_64: - cd packaging/ - git checkout tags/${CI_COMMIT_TAG} - ./makesrpm.sh --define "_with_python3 1" --define "_with_tests 1" --define "_without_xrootd_user 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" --define "_with_isal 1" -D "dist .el8" - - dnf builddep --nogpgcheck -y *.src.rpm + - dnf builddep -y *.src.rpm - mkdir RPMS - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_with_python3 1" --define "_with_tests 1" --define "_without_xrootd_user 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" --define "_with_isal 1" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" -D "dist .el8" *.src.rpm - cd .. @@ -299,7 +299,7 @@ release:alma8-x86_64: image: almalinux:8 script: - dnf install -y epel-release - - dnf install --nogpg -y gcc-c++ rpm-build tar dnf-plugins-core git python-macros + - dnf install -y gcc-c++ rpm-build tar dnf-plugins-core git python-macros - dnf config-manager --set-enabled powertools - dnf install -y cppunit-devel gtest-devel - dnf -y update libarchive @@ -309,7 +309,7 @@ release:alma8-x86_64: - cd packaging/ - git checkout tags/${CI_COMMIT_TAG} - ./makesrpm.sh --define "_with_python3 1" --define "_with_tests 1" --define "_without_xrootd_user 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" --define "_with_isal 1" -D "dist .el8" - - dnf builddep --nogpgcheck -y *.src.rpm + - dnf builddep -y *.src.rpm - mkdir RPMS - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_with_python3 1" --define "_with_tests 1" --define "_without_xrootd_user 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" --define "_with_isal 1" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" -D "dist .el8" *.src.rpm - cd .. @@ -331,7 +331,7 @@ release:alma9-x86_64: image: almalinux:9 script: - dnf install -y epel-release - - dnf install --nogpg -y rpm-build tar dnf-plugins-core git python-macros + - dnf install -y rpm-build tar dnf-plugins-core git python-macros - dnf config-manager --set-enabled crb - dnf -y update libarchive - mkdir alma-9-x86_64 @@ -340,7 +340,7 @@ release:alma9-x86_64: - cd packaging/ - git checkout tags/${CI_COMMIT_TAG} - ./makesrpm.sh --define "_with_python3 1" --define "_with_tests 1" --define "_without_xrootd_user 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" --define "_with_isal 1" -D "dist .el8" - - dnf builddep --nogpgcheck -y *.src.rpm + - dnf builddep -y *.src.rpm - mkdir RPMS - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_with_python3 1" --define "_with_tests 1" --define "_without_xrootd_user 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" --define "_with_isal 1" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" -D "dist .el8" *.src.rpm - cd .. @@ -362,14 +362,14 @@ release:cc7-x86_64: image: gitlab-registry.cern.ch/linuxsupport/cc7-base script: - head -n -6 /etc/yum.repos.d/epel.repo > /tmp/epel.repo ; mv -f /tmp/epel.repo /etc/yum.repos.d/epel.repo - - yum install --nogpg -y gcc-c++ rpm-build git python-srpm-macros centos-release-scl + - yum install -y gcc-c++ rpm-build git python-srpm-macros centos-release-scl - mkdir cc-7-x86_64 - ./gen-tarball.sh $CI_COMMIT_TAG - mv xrootd-${CI_COMMIT_TAG#"v"}.tar.gz cc-7-x86_64 - cd packaging/ - git checkout tags/${CI_COMMIT_TAG} - ./makesrpm.sh --define "_with_python3 1" --define "_with_tests 1" --define "_without_xrootd_user 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" --define "_with_isal 1" -D "dist .el7" - - yum-builddep --nogpgcheck -y *.src.rpm + - yum-builddep -y *.src.rpm - mkdir RPMS - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_with_python3 1" --define "_with_tests 1" --define "_without_xrootd_user 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" --define "_with_isal 1" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" -D "dist .el7" *.src.rpm - cd .. @@ -412,7 +412,7 @@ release:pypi: stage: build:rpm image: gitlab-registry.cern.ch/linuxsupport/cc7-base script: - - yum install --nogpg -y git python3-pip + - yum install -y git python3-pip - cp packaging/wheel/* . - ./publish.sh artifacts: @@ -431,7 +431,7 @@ publish:pypi: stage: publish image: gitlab-registry.cern.ch/linuxsupport/cc7-base script: - - yum install --nogpg -y sssd-client sudo + - yum install -y sssd-client sudo - sudo -u stci -H mkdir -p /eos/project/s/storage-ci/www/xrootd/release/pypi-dist - sudo -u stci -H cp dist/*.tar.gz /eos/project/s/storage-ci/www/xrootd/release/pypi-dist/. tags: @@ -448,7 +448,7 @@ weekly:cs8: image: gitlab-registry.cern.ch/linuxsupport/cs8-base script: - dnf install -y epel-release - - dnf install --nogpg -y gcc-c++ rpm-build tar dnf-plugins-core git python-macros + - dnf install -y gcc-c++ rpm-build tar dnf-plugins-core git python-macros - dnf config-manager --set-enabled powertools - dnf install -y cppunit-devel gtest-devel - dnf -y update libarchive @@ -460,7 +460,7 @@ weekly:cs8: - experimental_version="${a[0]}.${a[1]}.${a[2]}-0.experimental."${CI_PIPELINE_ID}.$short_hash - cd packaging/ - ./makesrpm.sh --version $experimental_version --define "_with_python3 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" --define "_with_isal 1" - - dnf builddep --nogpgcheck -y *.src.rpm + - dnf builddep -y *.src.rpm - mkdir RPMS - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_with_tests 1" --define "_with_python3 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" --define "_with_isal 1" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" *.src.rpm - cd .. @@ -483,7 +483,7 @@ weekly:cc7: image: gitlab-registry.cern.ch/linuxsupport/cc7-base script: - head -n -6 /etc/yum.repos.d/epel.repo > /tmp/epel.repo ; mv -f /tmp/epel.repo /etc/yum.repos.d/epel.repo - - yum install --nogpg -y gcc-c++ rpm-build git cppunit-devel gtest-devel python-srpm-macros centos-release-scl + - yum install -y gcc-c++ rpm-build git cppunit-devel gtest-devel python-srpm-macros centos-release-scl - xrootd_version=$(git for-each-ref --sort=taggerdate --format '%(refname)' refs/tags | grep '^refs/tags/v5' | grep -v 'rc.*$' | grep -v 'osghotfix' | grep -v 'CERN$' | sed -e '$!d') - xrootd_version=${xrootd_version:11} - echo $xrootd_version @@ -495,7 +495,7 @@ weekly:cc7: - echo $experimental_version - cd packaging/ - ./makesrpm.sh --version $experimental_version --define "_with_python3 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" --define "_with_isal 1" - - yum-builddep --nogpgcheck -y *.src.rpm + - yum-builddep -y *.src.rpm - mkdir RPMS - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_with_tests 1" --define "_with_python3 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" --define "_with_isal 1" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" *.src.rpm - cd .. @@ -518,7 +518,7 @@ xrootd_docker_get: stage: build:dockerimage:prepare image: gitlab-registry.cern.ch/linuxsupport/cc7-base script: - - yum install --nogpg -y git + - yum install -y git - git clone https://gitlab.cern.ch/eos/xrootd-docker.git - if [ ! -d "epel-7" ]; then mkdir epel-7; cp cc-7/* epel-7; fi artifacts: @@ -624,7 +624,7 @@ publish:rhel: stage: publish image: gitlab-registry.cern.ch/linuxsupport/cc7-base script: - - yum install --nogpg -y sssd-client sudo createrepo + - yum install -y sssd-client sudo createrepo - prefix=/eos/project/s/storage-ci/www/xrootd - "for platform in cs-8 cc-7 fc-{37..39}; do repo=$prefix/${CI_COMMIT_REF_NAME}/$platform/x86_64 @@ -673,7 +673,7 @@ publish:rhel:release: stage: publish image: gitlab-registry.cern.ch/linuxsupport/cc7-base script: - - yum install --nogpg -y sssd-client sudo createrepo + - yum install -y sssd-client sudo createrepo - prefix=/eos/project/s/storage-ci/www/xrootd - tarball=cc-7-x86_64/xrootd-*.tar.gz - "for platform in alma-8-x86_64 alma-9-x86_64 cs-8-x86_64 cc-7-x86_64; do @@ -720,7 +720,7 @@ publish:weekly: stage: publish image: gitlab-registry.cern.ch/linuxsupport/cc7-base script: - - yum install --nogpg -y sssd-client sudo createrepo + - yum install -y sssd-client sudo createrepo - prefix=/eos/project/s/storage-ci/www/xrootd - "for platform in epel-8 epel-7 epel-6; do if [ -d $platform ] ; then @@ -747,7 +747,7 @@ publish:koji:cs8: stage: post:publish image: gitlab-registry.cern.ch/linuxsupport/rpmci/kojicli script: - - yum install --nogpg -y sssd-client + - yum install -y sssd-client - kinit stci@CERN.CH -k -t /stci.krb5/stci.keytab - path=/eos/project/s/storage-ci/www/xrootd/release/cs-8-x86_64/$CI_COMMIT_TAG/source/ - if [[ $CI_COMMIT_TAG != *rc* ]] ; then koji build eos8 $path/*.src.rpm ; else stat $path/*.src.rpm ; fi @@ -763,7 +763,7 @@ publish:koji:cc7: stage: post:publish image: gitlab-registry.cern.ch/linuxsupport/rpmci/kojicli script: - - yum install --nogpg -y sssd-client + - yum install -y sssd-client - kinit stci@CERN.CH -k -t /stci.krb5/stci.keytab - path=/eos/project/s/storage-ci/www/xrootd/release/cc-7-x86_64/$CI_COMMIT_TAG/source/ - if [[ $CI_COMMIT_TAG != *rc* ]] ; then koji build eos7 $path/*.src.rpm ; else stat $path/*.src.rpm ; fi @@ -779,7 +779,7 @@ clean:artifacts: stage: clean image: gitlab-registry.cern.ch/linuxsupport/cc7-base script: - - yum install --nogpg -y sssd-client sudo createrepo + - yum install -y sssd-client sudo createrepo - sudo -u stci -H bash -c 'for commit_dir in /eos/project/s/storage-ci/www/xrootd/master/*/*/; do find ${commit_dir} -mindepth 1 -maxdepth 1 -type d -ctime +10 | xargs rm -rf; createrepo --update -q ${commit_dir}; done' - sudo -u stci -H bash -c 'for commit_dir in /eos/project/s/storage-ci/www/xrootd/stable-*/*/*/; do find ${commit_dir} -type f -name '"'"'*.rpm'"'"' -mtime +30 -delete; createrepo --update -q ${commit_dir}; done' - sudo -u stci -H bash -c 'for commit_dir in /eos/project/s/storage-ci/www/xrootd/experimental/*/x86_64/; do find ${commit_dir} -type f -name '"'"'*.rpm'"'"' -mtime +30 -delete; createrepo --update -q ${commit_dir}; done' From 8f0a3ebf6b14c65c57667d07c0c624f3d4b215a8 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 11 Jul 2023 10:55:00 +0200 Subject: [PATCH 198/442] XRootD 5.6.1 --- docs/ReleaseNotes.txt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/ReleaseNotes.txt b/docs/ReleaseNotes.txt index b8066aebbb0..f7f11258539 100644 --- a/docs/ReleaseNotes.txt +++ b/docs/ReleaseNotes.txt @@ -5,6 +5,23 @@ XRootD Release Notes ============= +------------- +Version 5.6.1 +------------- + ++ **Minor bug fixes** + **[CMake]** Fix Findlibuuid.cmake to use kernel provided uuid on macOS + **[XrdCl]** Avoid race in postmaster QueryTransport + **[XrdCl]** Add missing argument in call to debug log message. + This fixes sporadic crashes seen in FTS when debug logging is enabled. + **[XrdCrypto]** Avoid race in GetCryptoFactory + ++ **Miscellaneous** + **[CMake]** Make sure Python is required in PyPI build + **[CMake]** Set RPATH that works for binaries and libraries on macOS + **[CMake,Python]** Handle RPATH for Python bindings on macOS + **[Python]** Use PEP517 by default when building Python bindings + ------------- Version 5.6.0 ------------- From e25442709e0aa96a137230980fe75bd003b4094b Mon Sep 17 00:00:00 2001 From: Brian Bockelman Date: Tue, 25 Jul 2023 12:13:25 -0500 Subject: [PATCH 199/442] Fix logic error in user mapping When setting up mapping rules, if the user feature was utilized, a logic error would cause the rule to be ignored. Fixes #2056 --- src/XrdSciTokens/XrdSciTokensAccess.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdSciTokens/XrdSciTokensAccess.cc b/src/XrdSciTokens/XrdSciTokensAccess.cc index 74c2797bf9c..25b3f9eccbc 100644 --- a/src/XrdSciTokens/XrdSciTokensAccess.cc +++ b/src/XrdSciTokens/XrdSciTokensAccess.cc @@ -239,7 +239,7 @@ struct MapRule { if (!m_sub.empty() && sub != m_sub) {return "";} - if (!m_username.empty() && username != username) {return "";} + if (!m_username.empty() && username != m_username) {return "";} if (!m_path_prefix.empty() && strncmp(req_path.c_str(), m_path_prefix.c_str(), m_path_prefix.size())) From 3aa7503c63adec79cbc2bb5743ba1f00113c0c70 Mon Sep 17 00:00:00 2001 From: Pablo Llopis Date: Mon, 24 Jul 2023 19:22:53 +0200 Subject: [PATCH 200/442] Implement Linux epoll maxfd limit --- src/Xrd/XrdConfig.cc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Xrd/XrdConfig.cc b/src/Xrd/XrdConfig.cc index 2a68f226fab..0979801c6ab 100644 --- a/src/Xrd/XrdConfig.cc +++ b/src/Xrd/XrdConfig.cc @@ -93,6 +93,9 @@ #if defined(__linux__) || defined(__GNU__) #include #endif +#if defined(__linux__) +#include +#endif #ifdef __APPLE__ #include #endif @@ -1194,6 +1197,11 @@ int XrdConfig::setFDL() else rlim.rlim_cur = rlim.rlim_max; #if (defined(__APPLE__) && defined(MAC_OS_X_VERSION_10_5)) if (rlim.rlim_cur > OPEN_MAX) rlim.rlim_max = rlim.rlim_cur = OPEN_MAX; +#endif +#if defined(__linux__) +// Setting a limit beyond this value on Linux is guaranteed to fail during epoll_wait() + unsigned int epoll_max_fd = (INT_MAX / sizeof(struct epoll_event)); + if (rlim.rlim_cur > (rlim_t)epoll_max_fd) rlim.rlim_max = rlim.rlim_cur = epoll_max_fd; #endif if (setrlimit(RLIMIT_NOFILE, &rlim) < 0) return Log.Emsg("Config", errno,"set FD limit"); From 01c6f07eb729ad9fa6f2e353a18b0f330ffb2982 Mon Sep 17 00:00:00 2001 From: Yuxiao Qu Date: Mon, 17 Jul 2023 10:25:28 -0500 Subject: [PATCH 201/442] [XrdHttp] Add back parsing of Transfer-Encoding header This commit reintroduces a header parser for Transfer-Encoding: chunked to maintain compatibility with the EGI Nagios probe. This issue was first mentioned in #2058. Fixes: d96b2b70487e159b47c24925abcfa00f975ec3d6, #2009 --- src/XrdHttp/XrdHttpReq.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/XrdHttp/XrdHttpReq.cc b/src/XrdHttp/XrdHttpReq.cc index e119181718d..c1d13495155 100644 --- a/src/XrdHttp/XrdHttpReq.cc +++ b/src/XrdHttp/XrdHttpReq.cc @@ -186,6 +186,8 @@ int XrdHttpReq::parseLine(char *line, int len) { sendcontinue = true; } else if (!strcasecmp(key, "TE") && strstr(val, "trailers")) { m_trailer_headers = true; + } else if (!strcasecmp(key, "Transfer-Encoding") && strstr(val, "chunked")) { + m_transfer_encoding_chunked = true; } else if (!strcasecmp(key, "X-Transfer-Status") && strstr(val, "true")) { m_transfer_encoding_chunked = true; m_status_trailer = true; From f5f783997f5866ac88257c63c95d784f1af26f1d Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Tue, 8 Aug 2023 16:29:00 -0700 Subject: [PATCH 202/442] [Server] Default ffdest as per current pmark specification. --- src/XrdNet/XrdNetPMarkCfg.cc | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/XrdNet/XrdNetPMarkCfg.cc b/src/XrdNet/XrdNetPMarkCfg.cc index 376e959f730..343fc8bd556 100644 --- a/src/XrdNet/XrdNetPMarkCfg.cc +++ b/src/XrdNet/XrdNetPMarkCfg.cc @@ -273,20 +273,16 @@ XrdNetPMark *XrdNetPMarkCfg::Config(XrdSysError *eLog, XrdScheduler *sched, // If firefly is enabled, make sure we have an ffdest // if (useFFly < 0) - {if (ffPortD || ffPortO) useFFly = true; - else {useFFly = false; - eLog->Say("Config warning: firefly disabled; " - "configuration incomplete!"); - return 0; - } - } else { - if (useFFly && !ffPortD && !ffPortO) - {eLog->Say("Config invalid: pmark 'use firefly' requires " - "specifying 'ffdest'!"); - fatal = true; + {if (ffPortD || ffPortO) + {useFFly = true; + if (!ffPortO) ffPortO = ffPORT; + } else { + useFFly = false; + eLog->Say("Config warning: firefly disabled; " + "configuration incomplete!"); return 0; } - } + } else if (useFFly && !ffPortO) ffPortO = ffPORT; // Resolve trace and debug settings // @@ -1042,6 +1038,9 @@ do{if (!strcmp("debug", val) || !strcmp("nodebug", val)) continue; } + // We accept 'origin' as a dest for backward compatibility. That is the + // enforced default should 'use firefly' be specified. + // if (!strcmp("ffdest", val)) {const char *addtxt = ""; char *colon, *comma; From a0e92975ef1a7fa0c58830c3e5ab6bbb3209a173 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Tue, 8 Aug 2023 16:34:32 -0700 Subject: [PATCH 203/442] Update notes on pmark realignment. --- docs/PreReleaseNotes.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/PreReleaseNotes.txt b/docs/PreReleaseNotes.txt index 2f867923942..cbe9c86170b 100644 --- a/docs/PreReleaseNotes.txt +++ b/docs/PreReleaseNotes.txt @@ -6,3 +6,14 @@ XRootD Prerelease Notes ================ + ++ **New Features** + ++ **Major bug fixes** + ++ **Minor bug fixes** + ++ **Miscellaneous** + **[Server]** Default ffdest as per current pmark specification. + **Commit: f5f7839 + From 478ad4b4c4ce305d072249d7f25c5aa134d44f69 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Wed, 16 Aug 2023 23:24:08 -0700 Subject: [PATCH 204/442] [Server] Align code with the actual documentation for auth idspec. This patch provides backward compatiblity for old auth files. --- docs/ReleaseNotes.txt | 11 +++++++++++ src/XrdAcc/XrdAccAuthFile.cc | 6 ++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/docs/ReleaseNotes.txt b/docs/ReleaseNotes.txt index f7f11258539..b33aac7c201 100644 --- a/docs/ReleaseNotes.txt +++ b/docs/ReleaseNotes.txt @@ -5,6 +5,17 @@ XRootD Release Notes ============= +------------- +Version 5.6.2 +------------- + ++ **Major bug fixes** + ++ **Minor bug fixes** + ++ **Miscellaneous** + **[Server]** Correct code that tried to fix auth id spec on 11/2020. + ------------- Version 5.6.1 ------------- diff --git a/src/XrdAcc/XrdAccAuthFile.cc b/src/XrdAcc/XrdAccAuthFile.cc index 1f9601af480..1f4c45477b6 100644 --- a/src/XrdAcc/XrdAccAuthFile.cc +++ b/src/XrdAcc/XrdAccAuthFile.cc @@ -157,9 +157,11 @@ char XrdAccAuthFile::getID(char **id) return 0; } -// Id's are of the form 'c:', make sure we have that (don't validate it) +// Id's are of the form 'c', but historically they were 'c:' so we accept a +// two character specification but only validate the first to be backward +// compatible. // - if (strlen(pp) != 2 || !index("ghoru", *pp)) + if (strlen(pp) <= 2 || !index("ghoru", *pp)) {Eroute->Emsg("AuthFile", "Invalid ID sprecifier -", pp); flags = (DBflags)(flags | dbError); return 0; From 409b3cc7647edf169c65dc9fe9459fd0a20d2995 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 11 Aug 2023 15:41:04 +0200 Subject: [PATCH 205/442] [CMake] Always compile XrdOssCsi --- src/CMakeLists.txt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 53bc84bee8c..d95715fed98 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -55,10 +55,7 @@ if( NOT XRDCL_ONLY ) include( XrdPlugins ) include( XrdSsi ) include( XrdPfc ) - - if( CMAKE_COMPILER_IS_GNUCXX ) - include( XrdOssCsi ) - endif() + include( XrdOssCsi ) if( BUILD_HTTP ) include( XrdHttp ) From 0bd2f2cb5755a678d306f9dbe4f950cf3eb5b31d Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 14 Aug 2023 16:11:04 +0200 Subject: [PATCH 206/442] Fix typo in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a79193927f0..c17a2c513bd 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ systems like [ceph](https://ceph.io). ## Documentation -Genral documentation such as configuration reference guides, and user manuals +General documentation such as configuration reference guides, and user manuals can be found on the XRootD website at http://xrootd.org/docs.html. ## Supported Operating Systems From 99f75ccb3eac8b612a565422049467afa75f2d91 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 18 Aug 2023 13:49:00 +0200 Subject: [PATCH 207/442] [CMake] Hide build output for isa-l to not confuse CTest CTest parses the build output and will return a non-zero value if the configure/build phase of isa-l appears in the output, which fails CI builds that run via CTest with the test.cmake script. --- src/XrdIsal.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/src/XrdIsal.cmake b/src/XrdIsal.cmake index aced6b38d42..496fd29dbc7 100644 --- a/src/XrdIsal.cmake +++ b/src/XrdIsal.cmake @@ -29,6 +29,7 @@ ExternalProject_add(isa-l BUILD_COMMAND make -j ${CMAKE_BUILD_PARALLEL_LEVEL} INSTALL_COMMAND ${CMAKE_COMMAND} -E copy_directory ${ISAL_ROOT}/include ${ISAL_ROOT}/isa-l BUILD_BYPRODUCTS ${ISAL_LIBRARY} ${ISAL_INCLUDE_DIRS} + LOG_DOWNLOAD 1 LOG_CONFIGURE 1 LOG_BUILD 1 LOG_INSTALL 1 ) add_library(isal INTERFACE) From d4cecec94235a9549f6ee4fc5f7084e71ccca463 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 18 Aug 2023 16:40:54 +0200 Subject: [PATCH 208/442] [Tests] Let OS cleanup test library When dlclose(libHandle) is called, some test threads may still be using the library being unloaded, so sometimes it causes the tests to crash. --- tests/common/TextRunner.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/common/TextRunner.cc b/tests/common/TextRunner.cc index 77d8f92d8b7..26f5ff86514 100644 --- a/tests/common/TextRunner.cc +++ b/tests/common/TextRunner.cc @@ -149,6 +149,5 @@ int main( int argc, char **argv) new CppUnit::CompilerOutputter( &runner.result(), std::cerr ) ); bool wasSuccessful = runner.run(); - dlclose( libHandle ); return wasSuccessful ? 0 : 1; } From fd5d2d8b23769971eb62bb111651a0fb693b9292 Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Mon, 21 Aug 2023 12:02:12 +0200 Subject: [PATCH 209/442] XrdTls: XrdTlsTempCA - CRLs containing critical extensions are inserted at the end of the bundled CRL file Solves issue #2065 --- src/XrdCrypto/XrdCryptosslX509Crl.cc | 6 ++++ src/XrdCrypto/XrdCryptosslX509Crl.hh | 3 ++ src/XrdTls/XrdTlsTempCA.cc | 47 ++++++++++++++++++++++++++-- 3 files changed, 53 insertions(+), 3 deletions(-) diff --git a/src/XrdCrypto/XrdCryptosslX509Crl.cc b/src/XrdCrypto/XrdCryptosslX509Crl.cc index 653aa38902c..6ece76af59d 100644 --- a/src/XrdCrypto/XrdCryptosslX509Crl.cc +++ b/src/XrdCrypto/XrdCryptosslX509Crl.cc @@ -376,6 +376,12 @@ int XrdCryptosslX509Crl::GetFileType(const char *crlfn) return rc; } +bool XrdCryptosslX509Crl::hasCriticalExtension() { + // If the X509_CRL_get_ext_by_critical() function returns -1, no critical extension + // has been found + return X509_CRL_get_ext_by_critical(crl,1,-1) != -1; +} + //_____________________________________________________________________________ int XrdCryptosslX509Crl::LoadCache() { diff --git a/src/XrdCrypto/XrdCryptosslX509Crl.hh b/src/XrdCrypto/XrdCryptosslX509Crl.hh index 26ca673ec6a..d2dc06b66e5 100644 --- a/src/XrdCrypto/XrdCryptosslX509Crl.hh +++ b/src/XrdCrypto/XrdCryptosslX509Crl.hh @@ -83,6 +83,9 @@ public: // Dump CRL object to a file. bool ToFile(FILE *fh); + //Returns true if the CRL certificate has critical extension, false otherwise + bool hasCriticalExtension(); + private: X509_CRL *crl{nullptr}; // The CRL object time_t lastupdate{-1}; // time of last update diff --git a/src/XrdTls/XrdTlsTempCA.cc b/src/XrdTls/XrdTlsTempCA.cc index 0c1dd52f528..8dabc355338 100644 --- a/src/XrdTls/XrdTlsTempCA.cc +++ b/src/XrdTls/XrdTlsTempCA.cc @@ -161,6 +161,13 @@ class CRLSet { * processFile(...) method, false otherwise */ bool atLeastOneValidCRLFound() const; + /** + * https://github.com/xrootd/xrootd/issues/2065 + * To mitigate that issue, we need to defer the insertion of the CRLs that contain + * critical extensions at the end of the bundled CRL file + * @return true on success. + */ + bool processCRLWithCriticalExt(); private: XrdSysError &m_log; @@ -171,6 +178,9 @@ class CRLSet { std::unordered_set m_known_crls; const int m_output_fd; std::atomic m_atLeastOneValidCRLFound; + //Store the CRLs containing critical extensions to defer their insertion + //at the end of the bundled CRL file. Issue https://github.com/xrootd/xrootd/issues/2065 + std::vector> m_crls_critical_extension; }; @@ -179,7 +189,7 @@ CRLSet::processFile(file_smart_ptr &fp, const std::string &fname) { file_smart_ptr outputfp(fdopen(dup(m_output_fd), "w"), &fclose); if (!outputfp.get()) { - m_log.Emsg("CAset", "Failed to reopen file for output", fname.c_str()); + m_log.Emsg("CRLSet", "Failed to reopen file for output", fname.c_str()); return false; } @@ -202,10 +212,17 @@ CRLSet::processFile(file_smart_ptr &fp, const std::string &fname) //m_log.Emsg("CRLset", "New CRL with hash", fname.c_str(), hash_ptr); m_known_crls.insert(hash_ptr); - if (!xrd_crl->ToFile(outputfp.get())) { + if(xrd_crl->hasCriticalExtension()) { + // Issue https://github.com/xrootd/xrootd/issues/2065 + // This CRL will be put at the end of the bundled file + m_crls_critical_extension.emplace_back(std::move(xrd_crl)); + } else { + // No critical extension found on that CRL, just insert it on the CRL bundled file + if (!xrd_crl->ToFile(outputfp.get())) { m_log.Emsg("CRLset", "Failed to write out CRL", fname.c_str()); fflush(outputfp.get()); return false; + } } } fflush(outputfp.get()); @@ -217,6 +234,26 @@ bool CRLSet::atLeastOneValidCRLFound() const { return m_atLeastOneValidCRLFound; } +bool CRLSet::processCRLWithCriticalExt() { + // Don't open the output file if not necessary + if(!m_crls_critical_extension.empty()) { + file_smart_ptr outputfp(fdopen(dup(m_output_fd), "w"), &fclose); + if (!outputfp.get()) { + m_log.Emsg("CRLSet", "Failed to reopen file for output critical CRLs with critical extension"); + return false; + } + for (const auto &crl: m_crls_critical_extension) { + if (!crl->ToFile(outputfp.get())) { + m_log.Emsg("CRLset", "Failed to write out CRL with critical extension", crl->ParentFile()); + fflush(outputfp.get()); + return false; + } + } + fflush(outputfp.get()); + } + return true; +} + } @@ -417,8 +454,12 @@ XrdTlsTempCA::Maintenance() } closedir(dirp); + if(!crl_builder.processCRLWithCriticalExt()){ + m_log.Emsg("Maintenance", "Failed to insert CRLs with critical extension for CRLs", result->d_name); + } + if (!new_file->commit()) { - m_log.Emsg("Mainteance", "Failed to finalize new CA / CRL files"); + m_log.Emsg("Maintenance", "Failed to finalize new CA / CRL files"); return false; } //m_log.Emsg("Maintenance", "Successfully created CA and CRL files", new_file->getCAFilename().c_str(), From 62654026caf43d6155de71694f943a7582c1cc74 Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Tue, 22 Aug 2023 08:57:21 +0200 Subject: [PATCH 210/442] XrdTls: XrdTlsTempCA - Replaced dup() by XrdSysFD_Dup() in output file open --- src/XrdTls/XrdTlsTempCA.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/XrdTls/XrdTlsTempCA.cc b/src/XrdTls/XrdTlsTempCA.cc index 8dabc355338..392ec914a69 100644 --- a/src/XrdTls/XrdTlsTempCA.cc +++ b/src/XrdTls/XrdTlsTempCA.cc @@ -187,7 +187,7 @@ class CRLSet { bool CRLSet::processFile(file_smart_ptr &fp, const std::string &fname) { - file_smart_ptr outputfp(fdopen(dup(m_output_fd), "w"), &fclose); + file_smart_ptr outputfp(fdopen(XrdSysFD_Dup(m_output_fd), "w"), &fclose); if (!outputfp.get()) { m_log.Emsg("CRLSet", "Failed to reopen file for output", fname.c_str()); return false; @@ -237,7 +237,7 @@ bool CRLSet::atLeastOneValidCRLFound() const { bool CRLSet::processCRLWithCriticalExt() { // Don't open the output file if not necessary if(!m_crls_critical_extension.empty()) { - file_smart_ptr outputfp(fdopen(dup(m_output_fd), "w"), &fclose); + file_smart_ptr outputfp(fdopen(XrdSysFD_Dup(m_output_fd), "w"), &fclose); if (!outputfp.get()) { m_log.Emsg("CRLSet", "Failed to reopen file for output critical CRLs with critical extension"); return false; From af465e23b4970a5eaa10056af5fd4db1c45fb2d5 Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Tue, 22 Aug 2023 11:41:51 +0200 Subject: [PATCH 211/442] XrdTls: XrdTlsTempCA - Refactored CASet and CRLSet to open the output file only once before the processing --- src/XrdTls/XrdTlsTempCA.cc | 94 ++++++++++++++++++++------------------ 1 file changed, 49 insertions(+), 45 deletions(-) diff --git a/src/XrdTls/XrdTlsTempCA.cc b/src/XrdTls/XrdTlsTempCA.cc index 392ec914a69..1944ad79819 100644 --- a/src/XrdTls/XrdTlsTempCA.cc +++ b/src/XrdTls/XrdTlsTempCA.cc @@ -60,13 +60,28 @@ static uint64_t monotonic_time_s() { return tp.tv_sec + (tp.tv_nsec >= 500000000); } +/** + * Class managing the CRL or CA output file pointer. It is a RAII-style class that opens the output + * file in the constructor and close the file when the instance is destroyed + */ +class Set { +public: + Set(int output_fd, XrdSysError & err) : m_log(err),m_output_fp(file_smart_ptr(fdopen(XrdSysFD_Dup(output_fd), "w"), &fclose)){ + if(!m_output_fp.get()) { + m_output_fp.reset(); + } + } + virtual ~Set() = default; +protected: + // Reference to the logging that can be used by the inheriting classes. + XrdSysError &m_log; + // Pointer to the CA or CRL output file + file_smart_ptr m_output_fp; +}; -class CASet { +class CASet : public Set { public: - CASet(int output_fd, XrdSysError &err) - : m_log(err), - m_output_fd(output_fd) - {} + CASet(int output_fd, XrdSysError &err):Set(output_fd,err){} /** * Given an open file descriptor pointing to @@ -82,13 +97,11 @@ class CASet { bool processFile(file_smart_ptr &fd, const std::string &fname); private: - XrdSysError &m_log; // Grid CA directories tend to keep everything in triplicate; // we keep a unique hash of all known CAs so we write out each // one only once. std::unordered_set m_known_cas; - const int m_output_fd; }; @@ -101,9 +114,8 @@ CASet::processFile(file_smart_ptr &fp, const std::string &fname) XrdCryptosslX509ParseFile(fp.get(), &chain, fname.c_str()); auto ca = chain.Begin(); - file_smart_ptr outputfp(fdopen(XrdSysFD_Dup(m_output_fd), "w"), &fclose); - if (!outputfp.get()) { - m_log.Emsg("CAset", "Failed to reopen file for output", fname.c_str()); + if (!m_output_fp.get()) { + m_log.Emsg("CAset", "No output file has been opened", fname.c_str()); chain.Cleanup(); return false; } @@ -121,28 +133,23 @@ CASet::processFile(file_smart_ptr &fp, const std::string &fname) //m_log.Emsg("CAset", "New CA with hash", fname.c_str(), hash_ptr); m_known_cas.insert(hash_ptr); - if (XrdCryptosslX509ToFile(ca, outputfp.get(), fname.c_str())) { + if (XrdCryptosslX509ToFile(ca, m_output_fp.get(), fname.c_str())) { m_log.Emsg("CAset", "Failed to write out CA", fname.c_str()); chain.Cleanup(); return false; } ca = chain.Next(); } - fflush(outputfp.get()); + fflush(m_output_fp.get()); chain.Cleanup(); return true; } -class CRLSet { +class CRLSet : public Set { public: - CRLSet(int output_fd, XrdSysError &err) - : m_log(err), - m_output_fd(output_fd), - m_atLeastOneValidCRLFound(false) - {} - + CRLSet(int output_fd, XrdSysError &err):Set(output_fd,err){} /** * Given an open file descriptor pointing to * a file potentially containing a CRL, process it @@ -170,13 +177,11 @@ class CRLSet { bool processCRLWithCriticalExt(); private: - XrdSysError &m_log; // Grid CA directories tend to keep everything in triplicate; // we keep a unique hash of all known CRLs so we write out each // one only once. std::unordered_set m_known_crls; - const int m_output_fd; std::atomic m_atLeastOneValidCRLFound; //Store the CRLs containing critical extensions to defer their insertion //at the end of the bundled CRL file. Issue https://github.com/xrootd/xrootd/issues/2065 @@ -187,12 +192,10 @@ class CRLSet { bool CRLSet::processFile(file_smart_ptr &fp, const std::string &fname) { - file_smart_ptr outputfp(fdopen(XrdSysFD_Dup(m_output_fd), "w"), &fclose); - if (!outputfp.get()) { - m_log.Emsg("CRLSet", "Failed to reopen file for output", fname.c_str()); + if (!m_output_fp.get()) { + m_log.Emsg("CRLSet", "No output file has been opened", fname.c_str()); return false; } - // Assume we can safely ignore a failure to parse; we load every file in // the directory and that will naturally include a number of non-CRL files. for (std::unique_ptr xrd_crl(new XrdCryptosslX509Crl(fp.get(), fname.c_str())); @@ -218,14 +221,14 @@ CRLSet::processFile(file_smart_ptr &fp, const std::string &fname) m_crls_critical_extension.emplace_back(std::move(xrd_crl)); } else { // No critical extension found on that CRL, just insert it on the CRL bundled file - if (!xrd_crl->ToFile(outputfp.get())) { + if (!xrd_crl->ToFile(m_output_fp.get())) { m_log.Emsg("CRLset", "Failed to write out CRL", fname.c_str()); - fflush(outputfp.get()); + fflush(m_output_fp.get()); return false; } } } - fflush(outputfp.get()); + fflush(m_output_fp.get()); return true; } @@ -235,21 +238,19 @@ bool CRLSet::atLeastOneValidCRLFound() const { } bool CRLSet::processCRLWithCriticalExt() { - // Don't open the output file if not necessary if(!m_crls_critical_extension.empty()) { - file_smart_ptr outputfp(fdopen(XrdSysFD_Dup(m_output_fd), "w"), &fclose); - if (!outputfp.get()) { - m_log.Emsg("CRLSet", "Failed to reopen file for output critical CRLs with critical extension"); + if (!m_output_fp.get()) { + m_log.Emsg("CRLSet", "No output file has been opened to add CRLs with critical extension"); return false; } for (const auto &crl: m_crls_critical_extension) { - if (!crl->ToFile(outputfp.get())) { + if (!crl->ToFile(m_output_fp.get())) { m_log.Emsg("CRLset", "Failed to write out CRL with critical extension", crl->ParentFile()); - fflush(outputfp.get()); + fflush(m_output_fp.get()); return false; } } - fflush(outputfp.get()); + fflush(m_output_fp.get()); } return true; } @@ -399,8 +400,6 @@ XrdTlsTempCA::Maintenance() m_log.Emsg("TempCA", "Failed to create a new temp CA / CRL file"); return false; } - CASet ca_builder(new_file->getCAFD(), m_log); - CRLSet crl_builder(new_file->getCRLFD(), m_log); int fddir = XrdSysFD_Open(m_ca_dir.c_str(), O_DIRECTORY); if (fddir < 0) { @@ -416,7 +415,10 @@ XrdTlsTempCA::Maintenance() struct dirent *result; errno = 0; - while ((result = readdir(dirp))) { + { + CASet ca_builder(new_file->getCAFD(), m_log); + CRLSet crl_builder(new_file->getCRLFD(), m_log); + while ((result = readdir(dirp))) { //m_log.Emsg("Will parse file for CA certificates", result->d_name); if (result->d_name[0] == '.') {continue;} if (result->d_type != DT_REG) @@ -446,16 +448,18 @@ XrdTlsTempCA::Maintenance() m_log.Emsg("Maintenance", "Failed to process file for CRLs", result->d_name); } errno = 0; - } - if (errno) { + } + if (errno) { m_log.Emsg("Maintenance", "Failure during readdir", strerror(errno)); closedir(dirp); return false; - } - closedir(dirp); + } + closedir(dirp); - if(!crl_builder.processCRLWithCriticalExt()){ - m_log.Emsg("Maintenance", "Failed to insert CRLs with critical extension for CRLs", result->d_name); + if (!crl_builder.processCRLWithCriticalExt()) { + m_log.Emsg("Maintenance", "Failed to insert CRLs with critical extension for CRLs", result->d_name); + } + m_atLeastOneCRLFound = crl_builder.atLeastOneValidCRLFound(); } if (!new_file->commit()) { @@ -466,7 +470,7 @@ XrdTlsTempCA::Maintenance() // new_file->getCRLFilename().c_str()); m_ca_file.reset(new std::string(new_file->getCAFilename())); m_crl_file.reset(new std::string(new_file->getCRLFilename())); - m_atLeastOneCRLFound = crl_builder.atLeastOneValidCRLFound(); + return true; } From e53d4c8dd48b497d1f2fcba7238077bde41331b2 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 23 Aug 2023 10:10:56 +0200 Subject: [PATCH 212/442] [CMake] Update Findlibuuid.cmake to use correct include paths We need to use target_include_directories() instead of set_property() on the uuid::uuid target, since setting the property directly does not make those directories used during compilation. This is needed for CMS-SW, as it uses a specific version of libuuid installed in a non-standard location. However, on macOS, using target_include_directories() causes kernel header paths to be added in the compilation ahead of the C++ compiler's own paths, which causes compilation failures with clang from Xcode. This is why we were using set_property() up to now. For target_include_directories() to work in both cases (macOS and Linux with non-standard install path), we need to set CMAKE_FIND_FRAMEWORKS to LAST before looking for the uuid.h header. This way the kernel framework header paths are skipped, and uuid.h is found in one of the regular macOS SDK header paths instead. We now also perform a search for the uuid library using find_library, to avoid unnecessary calls to pkg-config. We keep it only as a last resort to find the library, as in some cases pkg-config works where we'd have to set CMAKE_PREFIX_PATH for find_library to work. Nevertheless, when we do need to fallback to pkg-config, the include directory set by the uuid.pc file is "wrong", i.e. it reports /include/uuid, not just /include as we expect, so we need to fix it by stripping the last component of the include directory in that case. Finally, in order to let pkg-config work, we were clearing CMake cache variables in the beginning of the Findlibuuid module, which had the unfortunate side effect of preventing users from setting UUID_INCLUDE_DIR and UUID_LIBRARY in the CMake command line to pick up a specific version of libuuid. We moved this section within the block where pkg-config is used to correct this problem. --- cmake/Findlibuuid.cmake | 79 +++++++++++++++++++++++++---------------- 1 file changed, 48 insertions(+), 31 deletions(-) diff --git a/cmake/Findlibuuid.cmake b/cmake/Findlibuuid.cmake index d203b777f97..6f44d1bd7a0 100644 --- a/cmake/Findlibuuid.cmake +++ b/cmake/Findlibuuid.cmake @@ -34,54 +34,71 @@ # ``UUID_INCLUDE_DIR`` # where to find the uuid/uuid.h header (same as UUID_INCLUDE_DIRS). -foreach(var FOUND INCLUDE_DIR INCLUDE_DIRS LIBRARY LIBRARIES) - unset(UUID_${var} CACHE) -endforeach() +include(CheckCXXSymbolExists) if(NOT UUID_INCLUDE_DIR) + set(CMAKE_FIND_FRAMEWORK LAST) find_path(UUID_INCLUDE_DIR uuid/uuid.h) endif() if(IS_DIRECTORY "${UUID_INCLUDE_DIR}") - include(CheckCXXSymbolExists) - set(UUID_INCLUDE_DIRS ${UUID_INCLUDE_DIR}) - set(CMAKE_REQUIRED_INCLUDES ${UUID_INCLUDE_DIRS}) + set(CMAKE_REQUIRED_INCLUDES ${UUID_INCLUDE_DIR}) check_cxx_symbol_exists("uuid_generate_random" "uuid/uuid.h" _uuid_header_only) unset(CMAKE_REQUIRED_INCLUDES) endif() -if(_uuid_header_only) - find_package_handle_standard_args(libuuid DEFAULT_MSG UUID_INCLUDE_DIR) -else() - if(NOT UUID_LIBRARY) - include(CheckLibraryExists) - check_library_exists("uuid" "uuid_generate_random" "" _have_libuuid) +if(NOT UUID_LIBRARY AND NOT _uuid_header_only) + find_library(UUID_LIBRARY NAMES uuid) - if(_have_libuuid) - set(UUID_LIBRARY "uuid") - set(UUID_LIBRARIES ${UUID_LIBRARY}) - else() - find_package(PkgConfig) - if(PKG_CONFIG_FOUND) - if(${libuuid_FIND_REQUIRED}) - set(libuuid_REQUIRED REQUIRED) - endif() - pkg_check_modules(UUID ${libuuid_REQUIRED} uuid) - set(UUID_LIBRARIES ${UUID_LDFLAGS}) - set(UUID_LIBRARY ${UUID_LIBRARIES}) - set(UUID_INCLUDE_DIRS ${UUID_INCLUDE_DIRS}) - set(UUID_INCLUDE_DIR ${UUID_INCLUDE_DIRS}) + if(UUID_LIBRARY) + set(CMAKE_REQUIRED_INCLUDES ${UUID_INCLUDE_DIR}) + set(CMAKE_REQUIRED_LIBRARIES ${UUID_LIBRARY}) + check_cxx_symbol_exists("uuid_generate_random" "uuid/uuid.h" _have_libuuid) + unset(CMAKE_REQUIRED_INCLUDES) + unset(CMAKE_REQUIRED_LIBRARIES) + endif() + + if(NOT _have_libuuid) + find_package(PkgConfig QUIET) + if(PKG_CONFIG_FOUND) + # We need to clear cache variables set above, which pkg-config may set, + # otherwise the call to pkg_check_modules will have no effect, as it does + # not override cache variables. + foreach(var FOUND INCLUDE_DIR INCLUDE_DIRS LIBRARY LIBRARIES) + unset(UUID_${var} CACHE) + endforeach() + if(${libuuid_FIND_REQUIRED}) + set(libuuid_REQUIRED REQUIRED) endif() + pkg_check_modules(UUID ${libuuid_REQUIRED} uuid) + + # The include directory returned by pkg-config is /include/uuid, + # while we expect just /include, so strip the last component to + # allow #include to actually work. + get_filename_component(UUID_INCLUDE_DIR ${UUID_INCLUDE_DIRS} DIRECTORY) + + set(UUID_INCLUDE_DIR ${UUID_INCLUDE_DIR} CACHE PATH "") + set(UUID_LIBRARY ${UUID_LDFLAGS} CACHE STRING "") + unset(UUID_INCLUDE_DIRS CACHE) + unset(UUID_LIBRARIES CACHE) endif() - unset(_have_libuuid) endif() +endif() + +if(_uuid_header_only) + find_package_handle_standard_args(libuuid DEFAULT_MSG UUID_INCLUDE_DIR) +else() find_package_handle_standard_args(libuuid DEFAULT_MSG UUID_INCLUDE_DIR UUID_LIBRARY) endif() -if(LIBUUID_FOUND AND NOT TARGET uuid::uuid) - add_library(uuid::uuid INTERFACE IMPORTED) - set_property(TARGET uuid::uuid PROPERTY INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${UUID_INCLUDE_DIRS}") - set_property(TARGET uuid::uuid PROPERTY INTERFACE_LINK_LIBRARIES "${UUID_LIBRARIES}") +if(LIBUUID_FOUND) + set(UUID_INCLUDE_DIRS ${UUID_INCLUDE_DIR}) + set(UUID_LIBRARIES ${UUID_LIBRARY}) + if(NOT TARGET uuid::uuid) + add_library(uuid::uuid INTERFACE IMPORTED) + target_include_directories(uuid::uuid SYSTEM INTERFACE "${UUID_INCLUDE_DIRS}") + target_link_libraries(uuid::uuid INTERFACE "${UUID_LIBRARIES}") + endif() endif() mark_as_advanced(UUID_INCLUDE_DIR UUID_LIBRARY) From 2f28fd1319e350b62df1f18de2a1ae50ea5664e4 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 21 Aug 2023 17:44:24 +0200 Subject: [PATCH 213/442] [Python] Allow build customization via environment variable This lets users export the CMAKE_ARGS environment variable to pass extra options to CMake when configuring to allow users to customize the build. If possible, arguments are split with shlex so that quotes are preserved for options that may contain space characters. If shlex is not available, then arguments are split on blank space. For example, to set the CXXFLAGS to "-Wall -g", use: $ export CMAKE_ARGS="-DCMAKE_CXX_FLAGS='-Wall -g'" $ python3 -m pip wheel --verbose . Alternatively, one may set options in a CMake cache file: $ echo 'set(CMAKE_CXX_FLAGS "-Wall -g" CACHE STRING "" FORCE)' > cfg.cmake $ env CMAKE_ARGS='-C cfg.cmake' python3 -m pip wheel --verbose . Of course, some variables like CXXFLAGS are used automatically by CMake, so in this case you can use the variable directly rather than use the CMAKE_ARGS environment variable. For example, to compile the Python bindings with Clang, one can use directly the CC and CXX variables: env CC=clang CXX=clang++ python3 -m pip wheel --verbose . Closes: #2062 --- bindings/python/setup.py | 16 ++++++++++------ setup.py | 16 ++++++++-------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/bindings/python/setup.py b/bindings/python/setup.py index ede38f1a2bc..737f2d6bd53 100644 --- a/bindings/python/setup.py +++ b/bindings/python/setup.py @@ -12,6 +12,15 @@ except ImportError: from distutils.spawn import find_executable as which +def get_cmake_args(): + args = os.getenv('CMAKE_ARGS') + + if not args: + return [] + + from shlex import split + return split(args) + srcdir = '${CMAKE_CURRENT_SOURCE_DIR}' cmdline_args = [] @@ -39,12 +48,7 @@ cmake = which("cmake3") or which("cmake") - for arg in sys.argv: - if arg.startswith('-D'): - cmdline_args.append(arg) - - for arg in cmdline_args: - sys.argv.remove(arg) + cmdline_args += get_cmake_args() def get_version(): version = '${XRootD_VERSION_STRING}' diff --git a/setup.py b/setup.py index fcb7b7b6811..62019633d30 100644 --- a/setup.py +++ b/setup.py @@ -12,16 +12,16 @@ except ImportError: from distutils.spawn import find_executable as which -cmdline_args = [] +cmake = which("cmake3") or which("cmake") -for arg in sys.argv: - if arg.startswith('-D'): - cmdline_args.append(arg) +def get_cmake_args(): + args = os.getenv('CMAKE_ARGS') -for arg in cmdline_args: - sys.argv.remove(arg) + if not args: + return [] -cmake = which("cmake3") or which("cmake") + from shlex import split + return split(args) def get_version(): try: @@ -81,7 +81,7 @@ def build_extensions(self): else: cmake_args += [ '-DCMAKE_INSTALL_RPATH=$ORIGIN' ] - cmake_args += cmdline_args + cmake_args += get_cmake_args() if not os.path.exists(self.build_temp): os.makedirs(self.build_temp) From d2a5cb508123d434c80c7e91028d6a12aa0e0d3e Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 11 Jul 2023 08:56:00 +0200 Subject: [PATCH 214/442] [Python] Check for Development.Module with CMake 3.18 and above In order to build binary wheels with manylinux images, we need to not depend on Development.Embed, since on those images, libpython is not available and CMake will not consider Python found without this change. XRootD only needs the Python.h header to build the Python bindings. Issue: #1833. See also: https://github.com/pypa/manylinux/issues/484 --- bindings/python/CMakeLists.txt | 8 +++++++- cmake/XRootDFindLibs.cmake | 13 +++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/bindings/python/CMakeLists.txt b/bindings/python/CMakeLists.txt index 85f75aad04e..032a7337c90 100644 --- a/bindings/python/CMakeLists.txt +++ b/bindings/python/CMakeLists.txt @@ -2,7 +2,13 @@ cmake_minimum_required(VERSION 3.16...3.25) project(PyXRootD LANGUAGES CXX) -find_package(Python REQUIRED COMPONENTS Interpreter Development) +if( CMAKE_VERSION VERSION_LESS 3.18 ) + set(PYTHON_COMPONENTS Interpreter Development) +else() + set(PYTHON_COMPONENTS Interpreter Development.Module) +endif() + +find_package(Python REQUIRED COMPONENTS ${PYTHON_COMPONENTS}) if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR OR PYPI_BUILD) add_subdirectory(src) diff --git a/cmake/XRootDFindLibs.cmake b/cmake/XRootDFindLibs.cmake index e74dacfa0a2..30775cca90f 100644 --- a/cmake/XRootDFindLibs.cmake +++ b/cmake/XRootDFindLibs.cmake @@ -173,13 +173,18 @@ if( ENABLE_XRDEC ) endif() endif() -if( ENABLE_PYTHON ) +if( ENABLE_PYTHON OR PYPI_BUILD ) + if( CMAKE_VERSION VERSION_LESS 3.18 ) + set(PYTHON_COMPONENTS Interpreter Development) + else() + set(PYTHON_COMPONENTS Interpreter Development.Module) + endif() if( FORCE_ENABLED OR PYPI_BUILD ) - find_package( Python ${XRD_PYTHON_REQ_VERSION} COMPONENTS Interpreter Development REQUIRED ) + find_package( Python ${XRD_PYTHON_REQ_VERSION} REQUIRED COMPONENTS ${PYTHON_COMPONENTS} ) else() - find_package( Python ${XRD_PYTHON_REQ_VERSION} COMPONENTS Interpreter Development ) + find_package( Python ${XRD_PYTHON_REQ_VERSION} COMPONENTS ${PYTHON_COMPONENTS} ) endif() - if( Python_Interpreter_FOUND AND Python_Development_FOUND ) + if( Python_FOUND ) set( BUILD_PYTHON TRUE ) else() set( BUILD_PYTHON FALSE ) From 8bc70d3e9b9c6da5201dc55094ea5ab61ad25f15 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 29 Aug 2023 16:21:59 +0200 Subject: [PATCH 215/442] [CMake] Run tests in parallel and fail build when tests fail --- test.cmake | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test.cmake b/test.cmake index b35d4ed8694..c8d0415f54d 100644 --- a/test.cmake +++ b/test.cmake @@ -156,7 +156,11 @@ if(INSTALL) ctest_build(TARGET install) endif() -ctest_test() +ctest_test(PARALLEL_LEVEL $ENV{CTEST_PARALLEL_LEVEL} RETURN_VALUE TEST_RESULT) + +if(NOT ${TEST_RESULT} EQUAL 0) + message(FATAL_ERROR "Tests failed") +endif() if(DEFINED CTEST_COVERAGE_COMMAND) find_program(GCOVR NAMES gcovr) From 9d348abaa4db0de9970c4e843705eace300956f4 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 1 Sep 2023 16:53:29 +0200 Subject: [PATCH 216/442] [XrdSecztn] Fix template for default ZTN token location Co-Authored-by: Andreas Joachim Peters Fixes: #2080 --- src/XrdSecztn/XrdSecProtocolztn.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/XrdSecztn/XrdSecProtocolztn.cc b/src/XrdSecztn/XrdSecProtocolztn.cc index c6c3ac71fea..6312910dbc9 100644 --- a/src/XrdSecztn/XrdSecProtocolztn.cc +++ b/src/XrdSecztn/XrdSecProtocolztn.cc @@ -324,8 +324,7 @@ XrdSecCredentials *XrdSecProtocolztn::findToken(XrdOucErrInfo *erp, if (Vec[i].beginswith('/') == 1) {char tokPath[MAXPATHLEN+8]; - snprintf(tokPath, sizeof(tokPath), tokName, - Vec[i].length(), int(geteuid())); + snprintf(tokPath, sizeof(tokPath), tokName, int(geteuid())); resp = readToken(erp, tokPath, isbad); if (resp || isbad) return resp; continue; From 75e70d10e176df049702c12161e1ae29039e83b5 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 1 Sep 2023 17:06:07 +0200 Subject: [PATCH 217/442] [docs] Fix broken link to git's contribution guidelines --- docs/CONTRIBUTING.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 82c1a211d79..4f3b311a485 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -97,7 +97,8 @@ fixes are always included in both `master` and `devel`. This section provides guidelines for people who want to contribute code to the XRootD project. It is adapted from git's own guidelines -for contributors, which can be found at https://github.com/git/git/Documentation/SubmittingPatches. +for contributors, which can be found in their repository on GitHub at +https://github.com/git/git/blob/master/Documentation/SubmittingPatches. #### Deciding what to base your work on From c87bec48ca0162085942d70f6265975da8576a80 Mon Sep 17 00:00:00 2001 From: Fabio Andrijauskas Date: Thu, 31 Aug 2023 11:33:51 -0700 Subject: [PATCH 218/442] Updating maximum header size and maximum line length for any line in INI file This update is to fix this issue: https://github.com/xrootd/xrootd/issues/2074 --- src/XrdSciTokens/vendor/inih/INIReader.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/XrdSciTokens/vendor/inih/INIReader.h b/src/XrdSciTokens/vendor/inih/INIReader.h index 0c7ec07a248..961858e9ec1 100644 --- a/src/XrdSciTokens/vendor/inih/INIReader.h +++ b/src/XrdSciTokens/vendor/inih/INIReader.h @@ -89,7 +89,7 @@ int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, /* Maximum line length for any line in INI file. */ #ifndef INI_MAX_LINE -#define INI_MAX_LINE 200 +#define INI_MAX_LINE 1024 #endif #ifdef __cplusplus @@ -117,8 +117,8 @@ home page for more info: #include #endif -#define MAX_SECTION 50 -#define MAX_NAME 50 +#define MAX_SECTION 1024 +#define MAX_NAME 1024 /* Strip whitespace chars off end of given string, in place. Return s. */ inline static char* rstrip(char* s) From cd3cb05367b6adf73886f7ed2f7e46aea46ea5e7 Mon Sep 17 00:00:00 2001 From: David Smith Date: Tue, 5 Sep 2023 16:36:34 +0200 Subject: [PATCH 219/442] [Xrd] Add initialiser in one of the XrdScheduler constructors --- src/Xrd/XrdScheduler.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Xrd/XrdScheduler.cc b/src/Xrd/XrdScheduler.cc index 75925a9456d..087613e07c2 100644 --- a/src/Xrd/XrdScheduler.cc +++ b/src/Xrd/XrdScheduler.cc @@ -122,7 +122,7 @@ XrdScheduler::XrdScheduler(XrdSysError *eP, XrdOucTrace *tP, // XrdScheduler::XrdScheduler(int minw, int maxw, int maxi) : XrdJob("underused thread monitor"), - WorkAvail(0, "sched work") + XrdTraceOld(0), WorkAvail(0, "sched work") { XrdSysLogger *Logger; int eFD; From d85709e6e7bd69ef16ca54f380f60840a1835328 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Wed, 6 Sep 2023 21:58:47 -0700 Subject: [PATCH 220/442] [ClHttp] Add pgWrite() support to the http client plugin. --- src/XrdClHttp/XrdClHttpFilePlugIn.cc | 14 ++++++++++++++ src/XrdClHttp/XrdClHttpFilePlugIn.hh | 10 ++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/XrdClHttp/XrdClHttpFilePlugIn.cc b/src/XrdClHttp/XrdClHttpFilePlugIn.cc index c58e7f600b9..092d42f1eb3 100644 --- a/src/XrdClHttp/XrdClHttpFilePlugIn.cc +++ b/src/XrdClHttp/XrdClHttpFilePlugIn.cc @@ -375,6 +375,19 @@ XRootDStatus HttpFilePlugIn::Write(uint64_t offset, uint32_t size, return XRootDStatus(); } +//------------------------------------------------------------------------ +//! @see XrdCl::File::PgWrite +//------------------------------------------------------------------------ +XRootDStatus HttpFilePlugIn::PgWrite( uint64_t offset, + uint32_t size, + const void *buffer, + std::vector &cksums, + ResponseHandler *handler, + uint16_t timeout ) +{ (void)cksums; + return Write(offset, size, buffer, handler, timeout); +} + XRootDStatus HttpFilePlugIn::Sync(ResponseHandler *handler, uint16_t timeout) { (void)handler; (void)timeout; @@ -384,6 +397,7 @@ XRootDStatus HttpFilePlugIn::Sync(ResponseHandler *handler, uint16_t timeout) { return XRootDStatus(); } + XRootDStatus HttpFilePlugIn::VectorRead(const ChunkList &chunks, void *buffer, ResponseHandler *handler, uint16_t /*timeout*/) { diff --git a/src/XrdClHttp/XrdClHttpFilePlugIn.hh b/src/XrdClHttp/XrdClHttpFilePlugIn.hh index bde85da5da5..dbab10a603f 100644 --- a/src/XrdClHttp/XrdClHttpFilePlugIn.hh +++ b/src/XrdClHttp/XrdClHttpFilePlugIn.hh @@ -84,6 +84,16 @@ class HttpFilePlugIn : public FilePlugIn { ResponseHandler *handler, uint16_t timeout ) override; + //------------------------------------------------------------------------ + //! @see XrdCl::File::PgWrite - async + //------------------------------------------------------------------------ + virtual XRootDStatus PgWrite( uint64_t offset, + uint32_t size, + const void *buffer, + std::vector &cksums, + ResponseHandler *handler, + uint16_t timeout ) override; + //------------------------------------------------------------------------ //! @see XrdCl::File::Sync //------------------------------------------------------------------------ From e8615472a8e15952de13b4f412145e13d221a5c0 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Wed, 13 Sep 2023 18:00:07 -0700 Subject: [PATCH 221/442] [Server] Export readv comma separated limits via XRD_READV_LIMITS envar. --- src/XrdXrootd/XrdXrootdConfig.cc | 9 +++++++++ src/XrdXrootd/XrdXrootdProtocol.cc | 2 ++ src/XrdXrootd/XrdXrootdProtocol.hh | 1 + src/XrdXrootd/XrdXrootdXeq.cc | 4 ++-- 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/XrdXrootd/XrdXrootdConfig.cc b/src/XrdXrootd/XrdXrootdConfig.cc index 38e42458019..702e952bbfa 100644 --- a/src/XrdXrootd/XrdXrootdConfig.cc +++ b/src/XrdXrootd/XrdXrootdConfig.cc @@ -219,6 +219,15 @@ int XrdXrootdProtocol::Configure(char *parms, XrdProtocol_Config *pi) // n = (pi->theEnv ? pi->theEnv->GetInt("MaxBuffSize") : 0); maxTransz = maxBuffsz = (n ? n : BPool->MaxSize()); + maxReadv_ior = maxTransz-(int)sizeof(readahead_list); + +// Export the readv_ior_max and readv_iov_max values +// + {char buff[256]; + snprintf(buff, sizeof(buff), "%d,%d", maxReadv_ior, XrdProto::maxRvecsz); + XrdOucEnv::Export("XRD_READV_LIMITS", buff); + } + memset(Route, 0, sizeof(Route)); // Now process and configuration parameters diff --git a/src/XrdXrootd/XrdXrootdProtocol.cc b/src/XrdXrootd/XrdXrootdProtocol.cc index 2e027b393c1..e0e50e75d7d 100644 --- a/src/XrdXrootd/XrdXrootdProtocol.cc +++ b/src/XrdXrootd/XrdXrootdProtocol.cc @@ -113,6 +113,8 @@ XrdNetSocket *XrdXrootdProtocol::AdminSock= 0; int XrdXrootdProtocol::hcMax = 28657; // const for now int XrdXrootdProtocol::maxBuffsz; int XrdXrootdProtocol::maxTransz = 262144; // 256KB +int XrdXrootdProtocol::maxReadv_ior = + XrdXrootdProtocol::maxTransz-(int)sizeof(readahead_list); int XrdXrootdProtocol::as_maxperlnk = 8; // Max ops per link int XrdXrootdProtocol::as_maxperreq = 8; // Max ops per request int XrdXrootdProtocol::as_maxpersrv = 4096;// Max ops per server diff --git a/src/XrdXrootd/XrdXrootdProtocol.hh b/src/XrdXrootd/XrdXrootdProtocol.hh index 7a80775ebab..50a23046ca6 100644 --- a/src/XrdXrootd/XrdXrootdProtocol.hh +++ b/src/XrdXrootd/XrdXrootdProtocol.hh @@ -471,6 +471,7 @@ static char tlsNot; // TLS requirements for incapable clients // static int maxBuffsz; // Maximum buffer size we can have static int maxTransz; // Maximum transfer size we can have +static int maxReadv_ior; // Maximum readv element length // Statistical area // diff --git a/src/XrdXrootd/XrdXrootdXeq.cc b/src/XrdXrootd/XrdXrootdXeq.cc index 6c9a1e87fff..f7ef8fc22b9 100644 --- a/src/XrdXrootd/XrdXrootdXeq.cc +++ b/src/XrdXrootd/XrdXrootdXeq.cc @@ -2018,7 +2018,7 @@ int XrdXrootdProtocol::do_Qconf() bp += n; bleft -= n; } else if (!strcmp("readv_ior_max", val)) - {n = snprintf(bp,bleft,"%d\n",maxTransz-(int)sizeof(readahead_list)); + {n = snprintf(bp,bleft,"%d\n",maxReadv_ior); bp += n; bleft -= n; } else if (!strcmp("readv_iov_max", val)) @@ -2571,7 +2571,7 @@ int XrdXrootdProtocol::do_ReadV() // to copy the read ahead list to our readv vector for later processing. // raVec = (readahead_list *)argp->buff; - totSZ = rdVecLen; Quantum = maxTransz - hdrSZ; + totSZ = rdVecLen; Quantum = maxReadv_ior; for (i = 0; i < rdVecNum; i++) {totSZ += (rdVec[i].size = ntohl(raVec[i].rlen)); if (rdVec[i].size < 0) return Response.Send(kXR_ArgInvalid, From aa80b13268c4490bc84509b12f91e5a78a7e32f1 Mon Sep 17 00:00:00 2001 From: David Smith Date: Tue, 12 Sep 2023 17:13:10 +0200 Subject: [PATCH 222/442] [XrdTls] Change the thread-id returned to openssl 1.0 to improve performance --- src/XrdCrypto/XrdCryptoLite_bf32.cc | 16 +++++ src/XrdTls/XrdTlsContext.cc | 90 +++++++++++++++++++---------- src/XrdTls/XrdTlsContext.hh | 2 +- 3 files changed, 75 insertions(+), 33 deletions(-) diff --git a/src/XrdCrypto/XrdCryptoLite_bf32.cc b/src/XrdCrypto/XrdCryptoLite_bf32.cc index 9a738234462..72f05a99c8f 100644 --- a/src/XrdCrypto/XrdCryptoLite_bf32.cc +++ b/src/XrdCrypto/XrdCryptoLite_bf32.cc @@ -42,6 +42,9 @@ #include #include +#if OPENSSL_VERSION_NUMBER < 0x10100000L +#include "XrdTls/XrdTlsContext.hh" +#endif #if OPENSSL_VERSION_NUMBER >= 0x30000000L #include #endif @@ -178,6 +181,19 @@ int XrdCryptoLite_bf32::Encrypt(const char *key, XrdCryptoLite *XrdCryptoLite_New_bf32(const char Type) { #ifdef HAVE_SSL +#if OPENSSL_VERSION_NUMBER < 0x10100000L + // In case nothing has yet configured a libcrypto thread-id callback + // function we provide one via the XrdTlsContext Init method. Compared + // to the default the aim is to provide better properies when libcrypto + // uses the thread-id as hash-table keys for the per-thread error state. + static struct configThreadid { + configThreadid() {eText = XrdTlsContext::Init();} + const char *eText; + } ctid; + // Make sure all went well + // + if (ctid.eText) return (XrdCryptoLite *)0; +#endif #if OPENSSL_VERSION_NUMBER >= 0x30000000L // With openssl v3 the blowfish cipher is only available via the "legacy" // provider. Legacy is typically not enabled by default (but can be via diff --git a/src/XrdTls/XrdTlsContext.cc b/src/XrdTls/XrdTlsContext.cc index 323c6ae129c..6f7227a92ca 100644 --- a/src/XrdTls/XrdTlsContext.cc +++ b/src/XrdTls/XrdTlsContext.cc @@ -25,7 +25,7 @@ #include #include "XrdOuc/XrdOucUtils.hh" -#include "XrdSys/XrdSysAtomics.hh" +#include "XrdSys/XrdSysRAtomic.hh" #include "XrdSys/XrdSysError.hh" #include "XrdSys/XrdSysPthread.hh" #include "XrdSys/XrdSysTimer.hh" @@ -34,10 +34,6 @@ #include "XrdTls/XrdTlsContext.hh" #include "XrdTls/XrdTlsTrace.hh" -#if __cplusplus >= 201103L -#include -#endif - /******************************************************************************/ /* G l o b a l s */ /******************************************************************************/ @@ -317,9 +313,45 @@ namespace extern "C" { #endif +template +struct tlsmix; + +template<> +struct tlsmix { + static unsigned long mixer(unsigned long x) { + // mixer based on splitmix64 + x ^= x >> 30; + x *= 0xbf58476d1ce4e5b9UL; + x ^= x >> 27; + x *= 0x94d049bb133111ebUL; + x ^= x >> 31; + return x; + } +}; + +template<> +struct tlsmix { + static unsigned long mixer(unsigned long x) { + // mixer based on murmurhash3 + x ^= x >> 16; + x *= 0x85ebca6bU; + x ^= x >> 13; + x *= 0xc2b2ae35U; + x ^= x >> 16; + return x; + } +}; + unsigned long sslTLS_id_callback(void) { - return (unsigned long)XrdSysThread::ID(); + // base thread-id on the id given by XrdSysThread; + // but openssl 1.0 uses thread-id as a key for looking + // up per thread crypto ERR structures in a hash-table. + // So mix bits so that the table's hash function gives + // better distribution. + + unsigned long x = (unsigned long)XrdSysThread::ID(); + return tlsmix::mixer(x); } XrdSysMutex *MutexVector = 0; @@ -364,12 +396,9 @@ const char *sslCiphers = "ECDHE-ECDSA-AES128-GCM-SHA256:" const char *sslCiphers = "ALL:!LOW:!EXP:!MD5:!MD2"; #endif -XrdSysMutex ctxMutex; -#if __cplusplus >= 201103L -std::atomic initDone( false ); -#else -bool initDone = false; -#endif +XrdSysMutex dbgMutex, tlsMutex; +XrdSys::RAtomic initDbgDone{ false }; +bool initTlsDone{ false }; /******************************************************************************/ /* I n i t T L S */ @@ -377,13 +406,13 @@ bool initDone = false; void InitTLS() // This is strictly a one-time call! { - XrdSysMutexHelper ctxHelper(ctxMutex); + XrdSysMutexHelper tlsHelper(tlsMutex); // Make sure we are not trying to load the ssl library more than once. This can // happen when a server and a client instance happen to be both defined. // - if (initDone) return; - initDone = true; + if (initTlsDone) return; + initTlsDone = true; // SSL library initialisation // @@ -575,24 +604,21 @@ XrdTlsContext::XrdTlsContext(const char *cert, const char *key, // Verify that initialzation has occurred. This is not heavy weight as // there will usually be no more than two instances of this object. // - AtomicBeg(ctxMutex); -#if __cplusplus >= 201103L - bool done = initDone.load(); -#else - bool done = AtomicGet(initDone); -#endif - AtomicEnd(ctxMutex); - if (!done) - {const char *dbg; - if (!(opts & servr) && (dbg = getenv("XRDTLS_DEBUG"))) - {int dbgOpts = 0; - if (strstr(dbg, "ctx")) dbgOpts |= XrdTls::dbgCTX; - if (strstr(dbg, "sok")) dbgOpts |= XrdTls::dbgSOK; - if (strstr(dbg, "sio")) dbgOpts |= XrdTls::dbgSIO; - if (!dbgOpts) dbgOpts = XrdTls::dbgALL; - XrdTls::SetDebug(dbgOpts|XrdTls::dbgOUT); + if (!initDbgDone) + {XrdSysMutexHelper dbgHelper(dbgMutex); + if (!initDbgDone) + {const char *dbg; + if (!(opts & servr) && (dbg = getenv("XRDTLS_DEBUG"))) + {int dbgOpts = 0; + if (strstr(dbg, "ctx")) dbgOpts |= XrdTls::dbgCTX; + if (strstr(dbg, "sok")) dbgOpts |= XrdTls::dbgSOK; + if (strstr(dbg, "sio")) dbgOpts |= XrdTls::dbgSIO; + if (!dbgOpts) dbgOpts = XrdTls::dbgALL; + XrdTls::SetDebug(dbgOpts|XrdTls::dbgOUT); + } + if ((emsg = Init())) FATAL(emsg); + initDbgDone = true; } - if ((emsg = Init())) FATAL(emsg); } // If no CA cert information is specified and this is not a server context, diff --git a/src/XrdTls/XrdTlsContext.hh b/src/XrdTls/XrdTlsContext.hh index 2b510378fb8..e6b61b7b828 100644 --- a/src/XrdTls/XrdTlsContext.hh +++ b/src/XrdTls/XrdTlsContext.hh @@ -19,7 +19,7 @@ //------------------------------------------------------------------------------ #include -//#include +#include //---------------------------------------------------------------------------- // Forward declarations From 6e205bc48138f4dd7294212b694b6a43bef35cb4 Mon Sep 17 00:00:00 2001 From: David Smith Date: Thu, 17 Aug 2023 16:57:16 +0200 Subject: [PATCH 223/442] [XrdHttp] Refactoring of the request statemachine for GET This change was mostly extracted from a larger pull request that included other features, #1957. The current change aims to be a minimal refactor in preparation for other work in the same area of the code. Co-authored-by: Brian Bockelman --- src/XrdHttp/XrdHttpReq.cc | 156 ++++++++++++++++---------------------- 1 file changed, 66 insertions(+), 90 deletions(-) diff --git a/src/XrdHttp/XrdHttpReq.cc b/src/XrdHttp/XrdHttpReq.cc index c1d13495155..426d9ff1f4e 100644 --- a/src/XrdHttp/XrdHttpReq.cc +++ b/src/XrdHttp/XrdHttpReq.cc @@ -1115,6 +1115,11 @@ int XrdHttpReq::ProcessHTTPReq() { } + // The reqstate parameter basically moves us through a simple state machine. + // - 0: Perform a stat on the resource + // - 1: Perform a checksum request on the resource (only if requested in header; otherwise skipped) + // - 2: Perform an open request (dirlist as appropriate). + // - 3+: Reads from file; if at end, perform a close. switch (reqstate) { case 0: // Stat() @@ -1127,7 +1132,36 @@ int XrdHttpReq::ProcessHTTPReq() { } return 0; - case 1: // Open() or dirlist + case 1: // Checksum request + if (!(fileflags & kXR_isDir) && !m_req_digest.empty()) { + // In this case, the Want-Digest header was set. + bool has_opaque = strchr(resourceplusopaque.c_str(), '?'); + // Note that doChksum requires that the memory stays alive until the callback is invoked. + m_req_cksum = prot->cksumHandler.getChecksumToRun(m_req_digest); + if(!m_req_cksum) { + // No HTTP IANA checksums have been configured by the server admin, return a "METHOD_NOT_ALLOWED" error + prot->SendSimpleResp(403, NULL, NULL, (char *) "No HTTP-IANA compatible checksums have been configured.", 0, false); + return -1; + } + m_resource_with_digest = resourceplusopaque; + if (has_opaque) { + m_resource_with_digest += "&cks.type="; + m_resource_with_digest += m_req_cksum->getXRootDConfigDigestName().c_str(); + } else { + m_resource_with_digest += "?cks.type="; + m_resource_with_digest += m_req_cksum->getXRootDConfigDigestName().c_str(); + } + if (prot->doChksum(m_resource_with_digest) < 0) { + prot->SendSimpleResp(500, NULL, NULL, (char *) "Failed to start internal checksum request to satisfy Want-Digest header.", 0, false); + return -1; + } + return 0; + } else { + TRACEI(DEBUG, "No checksum requested; skipping to request state 2"); + reqstate += 1; + } + // fallthrough + case 2: // Open() or dirlist { if (!prot->Bridge) { @@ -1176,29 +1210,6 @@ int XrdHttpReq::ProcessHTTPReq() { // We don't want to be invoked again after this request is finished return 1; - } else if (!m_req_digest.empty()) { - // In this case, the Want-Digest header was set. - bool has_opaque = strchr(resourceplusopaque.c_str(), '?'); - // Note that doChksum requires that the memory stays alive until the callback is invoked. - m_req_cksum = prot->cksumHandler.getChecksumToRun(m_req_digest); - if(!m_req_cksum) { - // No HTTP IANA checksums have been configured by the server admin, return a "METHOD_NOT_ALLOWED" error - prot->SendSimpleResp(403, NULL, NULL, (char *) "No HTTP-IANA compatible checksums have been configured.", 0, false); - return -1; - } - m_resource_with_digest = resourceplusopaque; - if (has_opaque) { - m_resource_with_digest += "&cks.type="; - m_resource_with_digest += m_req_cksum->getXRootDConfigDigestName().c_str(); - } else { - m_resource_with_digest += "?cks.type="; - m_resource_with_digest += m_req_cksum->getXRootDConfigDigestName().c_str(); - } - if (prot->doChksum(m_resource_with_digest) < 0) { - prot->SendSimpleResp(500, NULL, NULL, (char *) "Failed to start internal checksum request to satisfy Want-Digest header.", 0, false); - return -1; - } - return 0; } else { @@ -1224,41 +1235,15 @@ int XrdHttpReq::ProcessHTTPReq() { } - } - case 2: // Open() in the case the user also requested a checksum. - { - if (!m_req_digest.empty()) { - // --------- OPEN - memset(&xrdreq, 0, sizeof (ClientRequest)); - xrdreq.open.requestid = htons(kXR_open); - l = resourceplusopaque.length() + 1; - xrdreq.open.dlen = htonl(l); - xrdreq.open.mode = 0; - xrdreq.open.options = htons(kXR_retstat | kXR_open_read); - - if (!prot->Bridge->Run((char *) &xrdreq, (char *) resourceplusopaque.c_str(), l)) { - prot->SendSimpleResp(404, NULL, NULL, (char *) "Could not run request.", 0, false); - return -1; - } - - // Prepare to chunk up the request - writtenbytes = 0; - - // We want to be invoked again after this request is finished - return 0; - } } // fallthrough - default: // Read() or Close() + default: // Read() or Close(); reqstate is 3+ { - if ( ((reqstate == 3 || (!m_req_digest.empty() && (reqstate == 4))) && (rwOps.size() > 1)) || - (writtenbytes >= length) ) { - - // Close() if this was a readv or we have finished, otherwise read the next chunk - - // --------- CLOSE - + // --------- CLOSE + if ( ((reqstate == 4) && (rwOps.size() > 1)) || // In this case, we performed a ReadV and it's done. + (writtenbytes >= length) ) // No ReadV but we have completed the request. + { memset(&xrdreq, 0, sizeof (ClientRequest)); xrdreq.close.requestid = htons(kXR_close); memcpy(xrdreq.close.fhandle, fhandle, 4); @@ -1272,6 +1257,7 @@ int XrdHttpReq::ProcessHTTPReq() { return 1; } + // --------- READ or READV if (rwOps.size() <= 1) { // No chunks or one chunk... Request the whole file or single read @@ -1332,7 +1318,7 @@ int XrdHttpReq::ProcessHTTPReq() { return -1; } } else { - // More than one chunk to read... use readv + // --------- READV length = ReqReadV(); @@ -1345,12 +1331,12 @@ int XrdHttpReq::ProcessHTTPReq() { // We want to be invoked again after this request is finished return 0; - } + } // case 3+ - } + } // switch (reqstate) - } + } // case XrdHttpReq::rtGET case XrdHttpReq::rtPUT: { @@ -2088,10 +2074,15 @@ int XrdHttpReq::PostProcessHTTPReq(bool final_) { } - } else { - - + } // end handling of dirlist + else + { // begin handling of open-read-close + // To duplicate the state diagram from the rtGET request state + // - 0: Perform a stat on the resource + // - 1: Perform a checksum request on the resource (only if requested in header; otherwise skipped) + // - 2: Perform an open request (dirlist as appropriate). + // - 3+: Reads from file; if at end, perform a close. switch (reqstate) { case 0: //stat { @@ -2137,21 +2128,14 @@ int XrdHttpReq::PostProcessHTTPReq(bool final_) { // We are here in the case of a negative response in a non-manager return 0; + } // end stat + case 1: // checksum was requested and now we have its response. + { + return PostProcessChecksum(m_digest_header); } - case 1: // open - case 2: // open when digest was requested + case 2: // open { - - if (reqstate == 1 && !m_req_digest.empty()) { // We requested a checksum and now have its response. - int response = PostProcessChecksum(m_digest_header); - if (-1 == response) { - return -1; - } - return 0; - } else if (((reqstate == 2 && !m_req_digest.empty()) || - (reqstate == 1 && m_req_digest.empty())) - && (xrdresp == kXR_ok)) { - + if (xrdresp == kXR_ok) { getfhandle(); @@ -2254,28 +2238,21 @@ int XrdHttpReq::PostProcessHTTPReq(bool final_) { - } else if (xrdresp != kXR_ok) { + } else { // xrdresp indicates an error occurred - // If it's a dir then we are in the wrong place and we did the wrong thing. - //if (xrderrcode == 3016) { - // fileflags &= kXR_isDir; - // reqstate--; - // return 0; - //} prot->SendSimpleResp(httpStatusCode, NULL, NULL, httpStatusText.c_str(), httpStatusText.length(), false); return -1; } - // Remaining case: reqstate == 2 and we didn't ask for a digest (should be a read). + // Case should not be reachable + return -1; } - // fallthrough default: //read or readv { - // If we are postprocessing a close, potentially send out informational trailers if ((ntohs(xrdreq.header.requestid) == kXR_close) || - ((reqstate == 3) && (ntohs(xrdreq.header.requestid) == kXR_readv))) + ((reqstate == 4) && (ntohs(xrdreq.header.requestid) == kXR_readv))) { if (m_transfer_encoding_chunked && m_trailer_headers) { @@ -2330,7 +2307,7 @@ int XrdHttpReq::PostProcessHTTPReq(bool final_) { // Prevent scenario where data is expected but none is actually read // E.g. Accessing files which return the results of a script if ((ntohs(xrdreq.header.requestid) == kXR_read) && - (reqstate > 2) && (iovN == 0)) { + (reqstate > 3) && (iovN == 0)) { TRACEI(REQ, "Stopping request because more data is expected " "but no data has been read."); return -1; @@ -2406,12 +2383,11 @@ int XrdHttpReq::PostProcessHTTPReq(bool final_) { this->iovN = 0; return 0; - } + } // end read or readv } // switch reqstate - - } + } // End handling of the open-read-close case break; From 147ceff0cd0692b0e4557f02d8cf8b0bed49cd8c Mon Sep 17 00:00:00 2001 From: David Smith Date: Thu, 14 Sep 2023 11:13:16 +0200 Subject: [PATCH 224/442] [XrdHttp] Refactor read issuing during GET and fix read vector too long (#1976) Co-authored-by: Cedric Caffy --- src/XrdHttp.cmake | 1 + src/XrdHttp/XrdHttpProtocol.cc | 10 +- src/XrdHttp/XrdHttpProtocol.hh | 4 + src/XrdHttp/XrdHttpReadRangeHandler.cc | 658 +++++++++++++++++++++++++ src/XrdHttp/XrdHttpReadRangeHandler.hh | 282 +++++++++++ src/XrdHttp/XrdHttpReq.cc | 510 ++++++++++--------- src/XrdHttp/XrdHttpReq.hh | 42 +- src/XrdHttp/XrdHttpUtils.hh | 5 + tests/XrdHttpTests/XrdHttpTests.cc | 413 +++++++++++++++- 9 files changed, 1656 insertions(+), 269 deletions(-) create mode 100644 src/XrdHttp/XrdHttpReadRangeHandler.cc create mode 100644 src/XrdHttp/XrdHttpReadRangeHandler.hh diff --git a/src/XrdHttp.cmake b/src/XrdHttp.cmake index 391e290b4ce..ac0ed07033e 100644 --- a/src/XrdHttp.cmake +++ b/src/XrdHttp.cmake @@ -25,6 +25,7 @@ if( BUILD_HTTP ) XrdHttp/XrdHttpStatic.hh XrdHttp/XrdHttpTrace.hh XrdHttp/XrdHttpUtils.cc XrdHttp/XrdHttpUtils.hh + XrdHttp/XrdHttpReadRangeHandler.cc XrdHttp/XrdHttpReadRangeHandler.hh XrdHttp/XrdHttpChecksumHandler.cc XrdHttp/XrdHttpChecksumHandler.hh XrdHttp/XrdHttpChecksum.cc XrdHttp/XrdHttpChecksum.hh) diff --git a/src/XrdHttp/XrdHttpProtocol.cc b/src/XrdHttp/XrdHttpProtocol.cc index 7492193bb20..b2efb2d20ee 100644 --- a/src/XrdHttp/XrdHttpProtocol.cc +++ b/src/XrdHttp/XrdHttpProtocol.cc @@ -110,6 +110,7 @@ int XrdHttpProtocol::m_bio_type = 0; // BIO type identifier for our custom BIO. BIO_METHOD *XrdHttpProtocol::m_bio_method = NULL; // BIO method constructor. char *XrdHttpProtocol::xrd_cslist = nullptr; XrdHttpChecksumHandler XrdHttpProtocol::cksumHandler = XrdHttpChecksumHandler(); +XrdHttpReadRangeHandler::Configuration XrdHttpProtocol::ReadRangeConfig; XrdSysTrace XrdHttpTrace("http"); @@ -185,7 +186,7 @@ int BIO_get_shutdown(BIO *bio) { XrdHttpProtocol::XrdHttpProtocol(bool imhttps) : XrdProtocol("HTTP protocol handler"), ProtLink(this), -SecEntity(""), CurrentReq(this) { +SecEntity(""), CurrentReq(this, ReadRangeConfig) { myBuff = 0; Addr_str = 0; Reset(); @@ -951,6 +952,10 @@ int XrdHttpProtocol::Config(const char *ConfigFN, XrdOucEnv *myEnv) { char *var; int cfgFD, GoNo, NoGo = 0, ismine; + var = nullptr; + XrdOucEnv::Import("XRD_READV_LIMITS", var); + XrdHttpReadRangeHandler::Configure(eDest, var, ReadRangeConfig); + cksumHandler.configure(xrd_cslist); auto nonIanaChecksums = cksumHandler.getNonIANAConfiguredCksums(); if(nonIanaChecksums.size()) { @@ -1527,11 +1532,12 @@ int XrdHttpProtocol::StartSimpleResp(int code, const char *desc, const char *hea else if (code == 206) ss << "Partial Content"; else if (code == 302) ss << "Redirect"; else if (code == 307) ss << "Temporary Redirect"; + else if (code == 400) ss << "Bad Request"; else if (code == 403) ss << "Forbidden"; else if (code == 404) ss << "Not Found"; else if (code == 405) ss << "Method Not Allowed"; + else if (code == 416) ss << "Range Not Satisfiable"; else if (code == 500) ss << "Internal Server Error"; - else if (code == 400) ss << "Bad Request"; else ss << "Unknown"; } ss << crlf; diff --git a/src/XrdHttp/XrdHttpProtocol.hh b/src/XrdHttp/XrdHttpProtocol.hh index 0663dfd70c7..8db563595e7 100644 --- a/src/XrdHttp/XrdHttpProtocol.hh +++ b/src/XrdHttp/XrdHttpProtocol.hh @@ -47,6 +47,7 @@ #include "Xrd/XrdProtocol.hh" #include "XrdOuc/XrdOucHash.hh" #include "XrdHttpChecksumHandler.hh" +#include "XrdHttpReadRangeHandler.hh" #include @@ -129,6 +130,9 @@ public: // XrdHttp checksum handling class static XrdHttpChecksumHandler cksumHandler; + /// configuration for the read range handler + static XrdHttpReadRangeHandler::Configuration ReadRangeConfig; + /// called via https bool isHTTPS() { return ishttps; } diff --git a/src/XrdHttp/XrdHttpReadRangeHandler.cc b/src/XrdHttp/XrdHttpReadRangeHandler.cc new file mode 100644 index 00000000000..f6157bb6644 --- /dev/null +++ b/src/XrdHttp/XrdHttpReadRangeHandler.cc @@ -0,0 +1,658 @@ +//------------------------------------------------------------------------------ +// This file is part of XrdHTTP: A pragmatic implementation of the +// HTTP/WebDAV protocol for the Xrootd framework +// +// Copyright (c) 2013 by European Organization for Nuclear Research (CERN) +// Authors: Cedric Caffy , David Smith +// File Date: Aug 2023 +//------------------------------------------------------------------------------ +// XRootD is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// XRootD is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with XRootD. If not, see . +//------------------------------------------------------------------------------ + +#include "XProtocol/XPtypes.hh" +#include "XrdHttpReadRangeHandler.hh" +#include "XrdOuc/XrdOuca2x.hh" +#include "XrdOuc/XrdOucTUtils.hh" +#include "XrdOuc/XrdOucUtils.hh" + +#include +#include +#include +#include +#include +#include +#include + +//------------------------------------------------------------------------------ +//! static, class method: initialise a configuraiton object. parms is currently +//! only content of environment variable XRD_READV_LIMITS, to get the specific +//! kXR_readv limits. +//------------------------------------------------------------------------------ +int XrdHttpReadRangeHandler::Configure +( + XrdSysError &Eroute, + const char *const parms, + Configuration &cfg) +{ + if( !parms ) return 0; + + std::vector splitArgs; + XrdOucTUtils::splitString( splitArgs, parms, "," ); + if( splitArgs.size() < 2 ) return 0; + + //---------------------------------------------------------------------------- + // params is expected to be "," + //---------------------------------------------------------------------------- + std::string iorstr = splitArgs[0]; + std::string iovstr = splitArgs[1]; + XrdOucUtils::trim( iorstr ); + XrdOucUtils::trim( iovstr ); + + int val; + if( XrdOuca2x::a2i( Eroute, "Error reading specific value of readv_ior_max", + iorstr.c_str(), &val, 1, -1 ) ) + { + return -1; + } + + cfg.readv_ior_max = val; + if( XrdOuca2x::a2i( Eroute, "Error reading specific value of readv_iov_max", + iovstr.c_str(), &val, 1, -1 ) ) + { + return -1; + } + + cfg.readv_iov_max = val; + cfg.reqs_max = RREQ_MAXSIZE; + cfg.haveSizes = true; + + return 0; +} + +//------------------------------------------------------------------------------ +//! return the Error object +//------------------------------------------------------------------------------ +const XrdHttpReadRangeHandler::Error & XrdHttpReadRangeHandler::getError() const +{ + return error_; +} + +//------------------------------------------------------------------------------ +//! indicates when there were no valid Range head ranges supplied +//------------------------------------------------------------------------------ +bool XrdHttpReadRangeHandler::isFullFile() +{ + return rawUserRanges_.empty(); +} + +//------------------------------------------------------------------------------ +//! indicates a single range (implied whole file, or single range) or empty file +//------------------------------------------------------------------------------ +bool XrdHttpReadRangeHandler::isSingleRange() +{ + if( !rangesResolved_ ) + resolveRanges(); + + return( resolvedUserRanges_.size() <= 1 ); +} + +//------------------------------------------------------------------------------ +//! return resolved (i.e. obsolute start and end) byte ranges desired +//------------------------------------------------------------------------------ +const XrdHttpReadRangeHandler::UserRangeList &XrdHttpReadRangeHandler::ListResolvedRanges() +{ + static const UserRangeList emptyList; + + if( !rangesResolved_ ) + resolveRanges(); + + if( error_ ) + return emptyList; + + return resolvedUserRanges_; +} + +//------------------------------------------------------------------------------ +//! return XrdHttpIOList for sending to read or readv +//------------------------------------------------------------------------------ +const XrdHttpIOList &XrdHttpReadRangeHandler::NextReadList() +{ + static const XrdHttpIOList emptyList; + + if( !rangesResolved_ ) + resolveRanges(); + + if( error_ ) + return emptyList; + + if( !splitRange_.empty() ) + { + if( currSplitRangeIdx_ == 0 && currSplitRangeOff_ == 0 ) + { + //------------------------------------------------------------------------ + // Nothing read: Prevent scenario where data is expected but none is + // actually read E.g. Accessing files which return the results of a script + //------------------------------------------------------------------------ + error_.set( 500, "Stopping request because more data is expected " + "but no data has been read." ); + return emptyList; + } + + //-------------------------------------------------------------------------- + // we may have some unacknowledged portion of the last range; maybe due to a + // short read. so remove what was received and potentially reissue. + //-------------------------------------------------------------------------- + + trimSplit(); + if( !splitRange_.empty() ) + return splitRange_; + } + + if( splitRangeIdx_ >= resolvedUserRanges_.size() ) + return emptyList; + + splitRanges(); + + return splitRange_; +} + +//------------------------------------------------------------------------------ +//! Force handler to enter error state +//------------------------------------------------------------------------------ +void XrdHttpReadRangeHandler::NotifyError() +{ + if( error_ ) + return; + + error_.set( 500, "An error occured." ); +} + +//------------------------------------------------------------------------------ +//! Advance internal counters concerning received bytes +//------------------------------------------------------------------------------ +int XrdHttpReadRangeHandler::NotifyReadResult +( + const ssize_t ret, + const UserRange** const urp, + bool &start, + bool &allend +) +{ + if( error_ ) + return -1; + + if( ret == 0 ) + return 0; + + if( ret < 0 ) + { + error_.set( 500, "Range handler read failure." ); + return -1; + } + + if( !rangesResolved_ ) + { + error_.set( 500, "Range handler ranges not yet resolved." ); + return -1; + } + + if( splitRange_.empty() ) + { + error_.set( 500, "No ranges being read." ); + return -1; + } + + start = false; + allend = false; + + if( currSplitRangeIdx_ >= splitRange_.size() || + resolvedRangeIdx_ >= resolvedUserRanges_.size() ) + { + error_.set( 500, "Range handler index invalid." ); + return -1; + } + + if( urp ) + *urp = &resolvedUserRanges_[resolvedRangeIdx_]; + + if( resolvedRangeOff_ == 0 ) + start = true; + + const int clen = splitRange_[currSplitRangeIdx_].size; + + const off_t ulen = resolvedUserRanges_[resolvedRangeIdx_].end + - resolvedUserRanges_[resolvedRangeIdx_].start + 1; + + currSplitRangeOff_ += ret; + resolvedRangeOff_ += ret; + + if( currSplitRangeOff_ > clen || resolvedRangeOff_ > ulen ) + { + error_.set( 500, "Range handler read crossing chunk boundary." ); + return -1; + } + + if( currSplitRangeOff_ == clen ) + { + currSplitRangeOff_ = 0; + currSplitRangeIdx_++; + + if( currSplitRangeIdx_ >= splitRange_.size() ) + { + currSplitRangeIdx_ = 0; + splitRange_.clear(); + } + } + + if( resolvedRangeOff_ == ulen ) + { + resolvedRangeIdx_++; + resolvedRangeOff_ = 0; + if( resolvedRangeIdx_ >= resolvedUserRanges_.size() ) + allend = true; + } + + return 0; +} + +//------------------------------------------------------------------------------ +//! parse the line after a "Range: " http request header +//------------------------------------------------------------------------------ +void XrdHttpReadRangeHandler::ParseContentRange(const char* const line) +{ + char *str1, *saveptr1, *token; + + std::unique_ptr< char, decltype(std::free)* > + line_copy { strdup( line ), std::free }; + + //---------------------------------------------------------------------------- + // line_copy is argument of the Range header. + // + // e.g. "bytes=15-17,20-25" + // We skip the unit prefix (upto first '='). We don't + // enforce this prefix nor check what it is (e.g. 'bytes') + //---------------------------------------------------------------------------- + + str1 = line_copy.get(); + token = strchr(str1,'='); + if (token) str1 = token + 1; + + //---------------------------------------------------------------------------- + // break up the ranges and process each + //---------------------------------------------------------------------------- + + for( ; ; str1 = NULL ) + { + token = strtok_r( str1, " ,\n\r", &saveptr1 ); + if( token == NULL ) + break; + + if( !strlen(token) ) continue; + + const int rc = parseOneRange( token ); + if( rc ) + { + //------------------------------------------------------------------------ + // on error we ignore the whole range header + //------------------------------------------------------------------------ + rawUserRanges_.clear(); + return; + } + } +} + +//------------------------------------------------------------------------------ +//! resets this handler +//------------------------------------------------------------------------------ +void XrdHttpReadRangeHandler::reset() +{ + error_.reset(); + rawUserRanges_.clear(); + rawUserRanges_.shrink_to_fit(); + resolvedUserRanges_.clear(); + resolvedUserRanges_.shrink_to_fit(); + splitRange_.clear(); + splitRange_.shrink_to_fit(); + rangesResolved_ = false; + splitRangeIdx_ = 0; + splitRangeOff_ = 0; + currSplitRangeIdx_ = 0; + currSplitRangeOff_ = 0; + resolvedRangeIdx_ = 0; + resolvedRangeOff_ = 0; + filesize_ = 0; +} + +//------------------------------------------------------------------------------ +//! sets the filesize, used during resolving and issuing range requests +//------------------------------------------------------------------------------ +int XrdHttpReadRangeHandler::SetFilesize(const off_t fs) +{ + if( error_ ) + return -1; + + if( rangesResolved_ ) + { + error_.set( 500, "Filesize notified after ranges resolved." ); + return -1; + } + + filesize_ = fs; + return 0; +} + +//------------------------------------------------------------------------------ +//! private method: paring a single range from the header +//------------------------------------------------------------------------------ +int XrdHttpReadRangeHandler::parseOneRange(char* const str) +{ + UserRange ur; + char *sep; + + //---------------------------------------------------------------------------- + // expected input is an individual range, e.g. + // 5-6 + // 5- + // -2 + //---------------------------------------------------------------------------- + + sep = strchr( str, '-' ); + if( !sep ) + { + //-------------------------------------------------------------------------- + // Unexpected range format + //-------------------------------------------------------------------------- + return -1; + } + + *sep = '\0'; + if( rangeFig( str, ur.start_set, ur.start )<0 ) + { + //-------------------------------------------------------------------------- + // Error in range start + //-------------------------------------------------------------------------- + *sep = '-'; + return -1; + } + *sep = '-'; + if( rangeFig( sep+1, ur.end_set, ur.end )<0 ) + { + //-------------------------------------------------------------------------- + // Error in range end + //-------------------------------------------------------------------------- + return -1; + } + + if( !ur.start_set && !ur.end_set ) + { + //-------------------------------------------------------------------------- + // Unexpected range format + //-------------------------------------------------------------------------- + return -1; + } + + if( ur.start_set && ur.end_set && ur.start > ur.end ) + { + //-------------------------------------------------------------------------- + // Range start is after range end + //-------------------------------------------------------------------------- + return -1; + } + + if( !ur.start_set && ur.end_set && ur.end == 0 ) + { + //-------------------------------------------------------------------------- + // Request to return last 0 bytes of file + //-------------------------------------------------------------------------- + return -1; + } + + rawUserRanges_.push_back(ur); + return 0; +} + +//------------------------------------------------------------------------------ +//! private method: decode a decimal value from range header +//------------------------------------------------------------------------------ +int XrdHttpReadRangeHandler::rangeFig(const char* const s, bool &set, off_t &val) +{ + char *endptr = (char*)s; + errno = 0; + long long int v = strtoll( s, &endptr, 10 ); + if( (errno == ERANGE && (v == LONG_MAX || v == LONG_MIN)) + || (errno != 0 && errno != EINVAL && v == 0) ) + { + return -1; + } + if( *endptr != '\0' ) + { + return -1; + } + if( endptr == s ) + { + set = false; + } + else + { + set = true; + val = v; + } + return 0; +} + +//------------------------------------------------------------------------------ +//! private method: turn user supplied range into absolute range using filesize +//------------------------------------------------------------------------------ +void XrdHttpReadRangeHandler::resolveRanges() +{ + if( error_ ) + return; + + resolvedUserRanges_.clear(); + + for( const auto &rr: rawUserRanges_ ) + { + off_t start = 0; + off_t end = 0; + + if( rr.end_set ) + { + if( rr.start_set ) + { + //---------------------------------------------------------------------- + // end and start set + // e.g. 5-6 + //---------------------------------------------------------------------- + start = rr.start; + end = rr.end; + + //---------------------------------------------------------------------- + // skip ranges outside the file + //---------------------------------------------------------------------- + if( start >= filesize_ ) + continue; + + if( end >= filesize_ ) + { + end = filesize_ - 1; + } + } + else // !start + { + //---------------------------------------------------------------------- + // end is set but not start + // e.g. -5 + //---------------------------------------------------------------------- + if( rr.end == 0 ) + continue; + end = filesize_ -1; + if( rr.end > filesize_ ) + { + start = 0; + } + else + { + start = filesize_ - rr.end; + } + } + } + else // !end + { + //------------------------------------------------------------------------ + // end is not set + // e.g. 5- + //------------------------------------------------------------------------ + if( !rr.start_set ) continue; + if( rr.start >= filesize_ ) + continue; + start = rr.start; + end = filesize_ - 1; + } + resolvedUserRanges_.emplace_back( start, end ); + } + + if( rawUserRanges_.empty() && filesize_>0 ) + { + //-------------------------------------------------------------------------- + // special case: no ranges: speficied, return whole file + //-------------------------------------------------------------------------- + resolvedUserRanges_.emplace_back( 0, filesize_ - 1 ); + } + + if( !rawUserRanges_.empty() && resolvedUserRanges_.empty() ) + { + error_.set( 416, "None of the range-specifier values in the Range " + "request-header field overlap the current extent of the selected resource." ); + } + + rangesResolved_ = true; +} + +//------------------------------------------------------------------------------ +//! private method: proceed through the resolved ranges, splitting into ranges +//! suitable for read or readv. This method is called repeatedly until we've +//! gone though all the resolved ranges. +//------------------------------------------------------------------------------ +void XrdHttpReadRangeHandler::splitRanges() +{ + splitRange_.clear(); + currSplitRangeIdx_ = 0; + currSplitRangeOff_ = 0; + resolvedRangeIdx_ = splitRangeIdx_; + resolvedRangeOff_ = splitRangeOff_; + + //---------------------------------------------------------------------------- + // If we make a list of just one range XrdHttpReq will issue kXR_read, + // otherwise kXR_readv. + // + // If this is a full file read, or single user range, we'll fetch only one + // range at a time, so it is sent as a series of kXR_read requests. + // + // For multi range requests we pack a number of suitably sized ranges, thereby + // using kXR_readv. However, if there's a long user range we can we try to + // proceed by issuing single range requests and thereby using kXR_read. + // + // We don't merge user ranges in a single chunk as we always expect to be + // able to notify at boundaries with the output bools of NotifyReadResult. + //---------------------------------------------------------------------------- + + size_t maxch = vectorReadMaxChunks_; + size_t maxchs = vectorReadMaxChunkSize_; + if( isSingleRange() ) + { + maxchs = rRequestMaxBytes_; + maxch = 1; + } + + splitRange_.reserve( maxch ); + + //---------------------------------------------------------------------------- + // Start/continue splitting the resolvedUserRanges_ into a XrdHttpIOList. + //---------------------------------------------------------------------------- + + const size_t cs = resolvedUserRanges_.size(); + size_t nc = 0; + size_t rsr = rRequestMaxBytes_; + UserRange tmpur; + + while( ( splitRangeIdx_ < cs ) && ( rsr > 0 ) ) + { + //-------------------------------------------------------------------------- + // Check if we've readed the maximum number of allowed chunks. + //-------------------------------------------------------------------------- + if( nc >= maxch ) + break; + + if( !tmpur.start_set ) + { + tmpur = resolvedUserRanges_[splitRangeIdx_]; + tmpur.start += splitRangeOff_; + } + + const off_t l = tmpur.end - tmpur.start + 1; + size_t maxsize = std::min( rsr, maxchs ); + + //-------------------------------------------------------------------------- + // If we're starting a new set of chunks and we have enough data available + // in the current user range we allow a kXR_read of the max request size. + //-------------------------------------------------------------------------- + if( nc == 0 && l >= (off_t)rRequestMaxBytes_ ) + maxsize = rRequestMaxBytes_; + + if( l > (off_t)maxsize ) + { + splitRange_.emplace_back( nullptr, tmpur.start, maxsize ); + tmpur.start += maxsize; + splitRangeOff_ += maxsize; + rsr -= maxsize; + } + else + { + splitRange_.emplace_back( nullptr, tmpur.start, l ); + rsr -= l; + tmpur = UserRange(); + splitRangeOff_ = 0; + splitRangeIdx_++; + } + nc++; + } +} + +//------------------------------------------------------------------------------ +//! private method: remove partially received request +//------------------------------------------------------------------------------ +void XrdHttpReadRangeHandler::trimSplit() +{ + if( currSplitRangeIdx_ < splitRange_.size() ) + { + splitRange_.erase( splitRange_.begin(), + splitRange_.begin() + currSplitRangeIdx_ ); + } + else + splitRange_.clear(); + + if( splitRange_.size() > 0 ) + { + if( currSplitRangeOff_ < splitRange_[0].size ) + { + splitRange_[0].offset += currSplitRangeOff_; + splitRange_[0].size -= currSplitRangeOff_; + } + else + splitRange_.clear(); + } + + currSplitRangeIdx_ = 0; + currSplitRangeOff_ = 0; +} diff --git a/src/XrdHttp/XrdHttpReadRangeHandler.hh b/src/XrdHttp/XrdHttpReadRangeHandler.hh new file mode 100644 index 00000000000..b0b21b9bf32 --- /dev/null +++ b/src/XrdHttp/XrdHttpReadRangeHandler.hh @@ -0,0 +1,282 @@ +//------------------------------------------------------------------------------ +// This file is part of XrdHTTP: A pragmatic implementation of the +// HTTP/WebDAV protocol for the Xrootd framework +// +// Copyright (c) 2013 by European Organization for Nuclear Research (CERN) +// Authors: Cedric Caffy , David Smith +// File Date: Aug 2023 +//------------------------------------------------------------------------------ +// XRootD is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// XRootD is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with XRootD. If not, see . +//------------------------------------------------------------------------------ + +#ifndef XROOTD_XRDHTTPREADRANGEHANDLER_HH +#define XROOTD_XRDHTTPREADRANGEHANDLER_HH + +#include "XrdHttpUtils.hh" +#include +#include + + +/** + * Class responsible for parsing the HTTP Content-Range header + * coming from the client, generating appropriate read ranges + * for read or readv and tracking the responses to the requests. + */ +class XrdHttpReadRangeHandler { +public: + + /** + * These are defaults for: + * READV_MAXCHUNKS Max length of the XrdHttpIOList vector. + * READV_MAXCHUNKSIZE Max length of a XrdOucIOVec2 element. + * RREQ_MAXSIZE Max bytes to issue in a whole readv/read. + */ + static constexpr size_t READV_MAXCHUNKS = 512; + static constexpr size_t READV_MAXCHUNKSIZE = 512*1024; + static constexpr size_t RREQ_MAXSIZE = 8*1024*1024; + + /** + * Configuration can give specific values for the max chunk + * size, number of chunks and maximum overall request size, + * to override the defaults. + */ + struct Configuration { + Configuration() : haveSizes(false) { } + + Configuration(const size_t vectorReadMaxChunkSize, + const size_t vectorReadMaxChunks, + const size_t rRequestMaxBytes) : + haveSizes(true), readv_ior_max(vectorReadMaxChunkSize), + readv_iov_max(vectorReadMaxChunks), reqs_max(rRequestMaxBytes) { } + + + bool haveSizes; + size_t readv_ior_max; // max chunk size + size_t readv_iov_max; // max number of chunks + size_t reqs_max; // max bytes in read or readv + }; + + /** + * Error structure for storing error codes and message. + * operator bool() can be used to query if a value is set. + */ + struct Error { + bool errSet{false}; + int httpRetCode{0}; + std::string errMsg; + + explicit operator bool() const { return errSet; } + + void set(int rc, const std::string &m) + { httpRetCode = rc; errMsg = m; errSet = true; } + + void reset() { httpRetCode = 0; errMsg.clear(); errSet = false; } + }; + + /** + * Structure for recording or reporting user ranges. The can specify an + * unbounded range where eiter the start or end offset is not specified. + */ + struct UserRange { + bool start_set; + bool end_set; + off_t start; + off_t end; + + UserRange() : start_set(false), end_set(false), start(0), end(0) { } + + UserRange(off_t st, off_t en) : start_set(true), end_set(true), start(st), + end(en) { } + }; + + typedef std::vector UserRangeList; + + /** + * Constructor. + * Supplied with an Configuration object. The supplied object remains owned + * by the caller, but should remain valid throughout the lifetime of the + * ReadRangeHandler. + * + * @param @conf Configuration object. + */ + XrdHttpReadRangeHandler(const Configuration &conf) + { + rRequestMaxBytes_ = RREQ_MAXSIZE; + vectorReadMaxChunkSize_ = READV_MAXCHUNKSIZE; + vectorReadMaxChunks_ = READV_MAXCHUNKS; + + if( conf.haveSizes ) + { + vectorReadMaxChunkSize_ = conf.readv_ior_max; + vectorReadMaxChunks_ = conf.readv_iov_max; + rRequestMaxBytes_ = conf.reqs_max; + } + reset(); + } + + /** + * Parses a configuration into a Configuration object. + * @param @Eroute Error reporting object + * @param @parms Configuration string. + * @param @cfg an output Configuration object + * @return 0 for success, otherwise failure. + */ + static int Configure(XrdSysError &Eroute, const char *const parms, + Configuration &cfg); + + /** + * getter for the Error object. The object can be inspected with its operator + * bool() method to indicate an error has happened. Error code and message are + * available in other members of Error. + */ + const Error& getError() const; + + /** + * Indicates no valid Range header was given and thus the implication is that + * whole file is required. A range or ranges may be given that cover the whole + * file but that situation is not detected. + */ + bool isFullFile(); + + /** + * Incidcates whether there is a single range, either given by a Range header + * with single range or implied by having no Range header. + * Also returns true for an empty file, although there is no range of bytes. + * @return true if there is a single range. + */ + bool isSingleRange(); + + /** + * Returns a reference of the list of ranges. These are resolved, meaning that + * if there was no Range header, or it was in the form -N or N-, the file size + * is used to compute the actual range of bytes that are needed. The list + * remains owned by the handler and may be invalidated on reset(). + * @return List of ranges in a UserRangeList object. + * The returned list may be empty, i.e. for an empty file or if there + * is an error. Use getError() to see if there is an error. + */ + const UserRangeList &ListResolvedRanges(); + + /** + * Requests a XrdHttpIOList (vector of XrdOucIOVec2) that describes the next + * bytes that need to be fetched from a file. If there is more than one chunk + * it is size appropriately for a readv request, if there is one request it + * should be sent as a read request. Therefore the chunks do not necessarily + * correspond to the ranges the user requested. The caller issue the requests + * in the order provided and call NotifyReadResult with the ordered results. + * @return a reference to a XrdHttpIOList. The object remains owned by the + * handler. It may be invalided by a new call to NextReadList() or + * reset(). The returned list may be empty, which implies no more + * reads are needed One can use getError() to see if there is an + * error. + */ + const XrdHttpIOList &NextReadList(); + + /** + * Force the handler to enter error state. Sets a generic error message + * if there was not already an error. + */ + void NotifyError(); + + /** + * Notifies the handler about the arrival of bytes from a read or readv + * request. The handler tracks the progress of the arriving bytes against + * the bytes ranges the user requested. + * @param ret the number of bytes received + * @param urp a pointer to a pointer of a UserRange object. If urp is not + * nullptr, the pointer to a UserRange is returned that describes + * the current range associated with the received bytes. The + * handler retains ownership of the returned object. reset() of + * the handler invalidates the UserRange object. + * @param start is an output bool parameter that indicates whether the + * received bytes mark the start of a UserRange. + * @param allend is an output bool parameter that indicates whether the + * received bytes mark the end of all the UserRanges + * @return 0 upon success, -1 if an error happened. + * One needs to call the getError() method to return the error. + */ + int NotifyReadResult(const ssize_t ret, + const UserRange** const urp, + bool &start, + bool &allend); + + /** + * Parses the Content-Range header value and sets the ranges within the + * object. + * @param line the line under the format "bytes=0-19, 25-30" + * In case the parsing fails any partial results are cleared. There is no + * error notification as the rest of the request processing should continue + * in any case. + */ + void ParseContentRange(const char* const line); + + /** + * Resets the object state, ready for handling a new request. + */ + void reset(); + + /** + * Notifies of the current file size. This information is required for + * processing range requests that imply reading to the end or a certain + * position before the end of a file. It is also used to determine when read + * or readv need no longer be issued when reading the whole file. + * Can be called once or more, after reset() but before isSingleRange(), + * ListResolvedRanges() or NextReadList() methods. + * @param sz the size of the file + * @return 0 upon success, -1 if an error happened. + * One needs to call the getError() method to return the error. + */ + int SetFilesize(const off_t sz); + +private: + int parseOneRange(char* const str); + int rangeFig(const char* const s, bool &set, off_t &start); + void resolveRanges(); + void splitRanges(); + void trimSplit(); + + Error error_; + + UserRangeList rawUserRanges_; + + bool rangesResolved_; + + UserRangeList resolvedUserRanges_; + + XrdHttpIOList splitRange_; + + // the position in resolvedUserRanges_ corresponding to all the + // bytes notified via the NotifyReadResult() method + size_t resolvedRangeIdx_; + off_t resolvedRangeOff_; + + // position of the method splitRanges() in within resolvedUserRanges_ + // from where it split ranges into chunks for sending to read/readv + size_t splitRangeIdx_; + off_t splitRangeOff_; + + // the position in splitRange_ corresponding to all the + // bytes notified via the NotifyReadResult() method + size_t currSplitRangeIdx_; + int currSplitRangeOff_; + + off_t filesize_; + + size_t vectorReadMaxChunkSize_; + size_t vectorReadMaxChunks_; + size_t rRequestMaxBytes_; +}; + + +#endif //XROOTD_XRDHTTPREADRANGEHANDLER_HH diff --git a/src/XrdHttp/XrdHttpReq.cc b/src/XrdHttp/XrdHttpReq.cc index 426d9ff1f4e..b39c5883a53 100644 --- a/src/XrdHttp/XrdHttpReq.cc +++ b/src/XrdHttp/XrdHttpReq.cc @@ -165,7 +165,10 @@ int XrdHttpReq::parseLine(char *line, int len) { } else if (!strcmp(key, "Host")) { parseHost(val); } else if (!strcmp(key, "Range")) { - parseContentRange(val); + // (rfc2616 14.35.1) says if Range header contains any range + // which is syntactically invalid the Range header should be ignored. + // Therefore no need for the range handler to report an error. + readRangeHandler.ParseContentRange(val); } else if (!strcmp(key, "Content-Length")) { length = atoll(val); @@ -221,89 +224,6 @@ int XrdHttpReq::parseHost(char *line) { return 0; } -int XrdHttpReq::parseContentRange(char *line) { - int j; - char *str1, *saveptr1, *token; - - - - for (j = 1, str1 = line;; j++, str1 = NULL) { - token = strtok_r(str1, " ,\n=", &saveptr1); - if (token == NULL) - break; - - //printf("%d: %s\n", j, token); - - if (!strlen(token)) continue; - - - parseRWOp(token); - - } - - return j; -} - -int XrdHttpReq::parseRWOp(char *str) { - ReadWriteOp o1; - int j; - char *saveptr2, *str2, *subtoken, *endptr; - bool ok = false; - - for (str2 = str, j = 0;; str2 = NULL, j++) { - subtoken = strtok_r(str2, "-", &saveptr2); - if (subtoken == NULL) - break; - - switch (j) { - case 0: - o1.bytestart = strtoll(subtoken, &endptr, 0); - if (!o1.bytestart && (endptr == subtoken)) o1.bytestart = -1; - break; - case 1: - o1.byteend = strtoll(subtoken, &endptr, 0); - if (!o1.byteend && (endptr == subtoken)) o1.byteend = -1; - ok = true; - break; - default: - // Malformed! - ok = false; - break; - } - - } - - - // This can be largely optimized - if (ok) { - - kXR_int32 len_ok = 0; - long long sz = o1.byteend - o1.bytestart + 1; - kXR_int32 newlen = sz; - - if (filesize > 0) - newlen = (kXR_int32) std::min(filesize - o1.bytestart, sz); - - rwOps.push_back(o1); - - while (len_ok < newlen) { - ReadWriteOp nfo; - int len = std::min(newlen - len_ok, READV_MAXCHUNKSIZE); - - nfo.bytestart = o1.bytestart + len_ok; - nfo.byteend = nfo.bytestart + len - 1; - len_ok += len; - rwOps_split.push_back(nfo); - } - length += len_ok; - - - } - - - return j; -} - int XrdHttpReq::parseFirstLine(char *line, int len) { char *key = line; @@ -436,29 +356,23 @@ void XrdHttpReq::clientUnMarshallReadAheadList(int nitems) { } } -int XrdHttpReq::ReqReadV() { +int XrdHttpReq::ReqReadV(const XrdHttpIOList &cl) { - kXR_int64 total_len = 0; - rwOpPartialDone = 0; // Now we build the protocol-ready read ahead list // and also put the correct placeholders inside the cache - int n = rwOps_split.size(); - if (!ralist) ralist = (readahead_list *) malloc(n * sizeof (readahead_list)); + int n = cl.size(); + ralist.clear(); + ralist.reserve(n); int j = 0; - for (int i = 0; i < n; i++) { - - // We can suppose that we know the length of the file - // Hence we can sort out requests that are out of boundary or trim them - if (rwOps_split[i].bytestart > filesize) continue; - if (rwOps_split[i].byteend > filesize - 1) rwOps_split[i].byteend = filesize - 1; - - memcpy(&(ralist[j].fhandle), this->fhandle, 4); + for (const auto &c: cl) { + ralist.emplace_back(); + auto &ra = ralist.back(); + memcpy(&ra.fhandle, this->fhandle, 4); - ralist[j].offset = rwOps_split[i].bytestart; - ralist[j].rlen = rwOps_split[i].byteend - rwOps_split[i].bytestart + 1; - total_len += ralist[j].rlen; + ra.offset = c.offset; + ra.rlen = c.size; j++; } @@ -523,11 +437,21 @@ int XrdHttpReq::File(XrdXrootd::Bridge::Context &info, //!< the result context int dlen //!< byte count ) { + // sendfile about to be sent by bridge for fetching data for GET: + // no https, no chunked+trailer, no multirange + //prot->SendSimpleResp(200, NULL, NULL, NULL, dlen); int rc = info.Send(0, 0, 0, 0); TRACE(REQ, " XrdHttpReq::File dlen:" << dlen << " send rc:" << rc); - if (rc) return false; - writtenbytes += dlen; + bool start, finish; + // short read will be classed as error + if (rc) { + readRangeHandler.NotifyError(); + return false; + } + + if (readRangeHandler.NotifyReadResult(dlen, nullptr, start, finish) < 0) + return false; return true; @@ -538,7 +462,8 @@ bool XrdHttpReq::Done(XrdXrootd::Bridge::Context & info) { TRACE(REQ, " XrdHttpReq::Done"); xrdresp = kXR_ok; - + + this->iovN = 0; int r = PostProcessHTTPReq(true); // Beware, we don't have to reset() if the result is 0 @@ -1240,10 +1165,14 @@ int XrdHttpReq::ProcessHTTPReq() { default: // Read() or Close(); reqstate is 3+ { + const XrdHttpIOList &readChunkList = readRangeHandler.NextReadList(); + + // Close() if we have finished, otherwise read the next chunk + // --------- CLOSE - if ( ((reqstate == 4) && (rwOps.size() > 1)) || // In this case, we performed a ReadV and it's done. - (writtenbytes >= length) ) // No ReadV but we have completed the request. + if ( readChunkList.empty() ) { + memset(&xrdreq, 0, sizeof (ClientRequest)); xrdreq.close.requestid = htons(kXR_close); memcpy(xrdreq.close.fhandle, fhandle, 4); @@ -1254,13 +1183,14 @@ int XrdHttpReq::ProcessHTTPReq() { } // We have finished + readClosing = true; return 1; } // --------- READ or READV - - if (rwOps.size() <= 1) { - // No chunks or one chunk... Request the whole file or single read + + if ( readChunkList.size() == 1 ) { + // Use a read request for single range long l; long long offs; @@ -1271,21 +1201,17 @@ int XrdHttpReq::ProcessHTTPReq() { memcpy(xrdreq.read.fhandle, fhandle, 4); xrdreq.read.dlen = 0; - if (rwOps.size() == 0) { - l = (long)std::min(filesize-writtenbytes, (long long)1024*1024); - offs = writtenbytes; - xrdreq.read.offset = htonll(writtenbytes); - xrdreq.read.rlen = htonl(l); - } else { - l = std::min(rwOps[0].byteend - rwOps[0].bytestart + 1 - writtenbytes, (long long)1024*1024); - offs = rwOps[0].bytestart + writtenbytes; - xrdreq.read.offset = htonll(offs); - xrdreq.read.rlen = htonl(l); - } + offs = readChunkList[0].offset; + l = readChunkList[0].size; + + xrdreq.read.offset = htonll(offs); + xrdreq.read.rlen = htonl(l); - // If we are using HTTPS or if the client requested trailers, disable sendfile - // (in the latter case, the chunked encoding prevents sendfile usage) - if (prot->ishttps || (m_transfer_encoding_chunked && m_trailer_headers)) { + // If we are using HTTPS or if the client requested trailers, or if the + // read concerns a multirange reponse, disable sendfile + // (in the latter two cases, the extra framing is only done in PostProcessHTTPReq) + if (prot->ishttps || (m_transfer_encoding_chunked && m_trailer_headers) || + !readRangeHandler.isSingleRange()) { if (!prot->Bridge->setSF((kXR_char *) fhandle, false)) { TRACE(REQ, " XrdBridge::SetSF(false) failed."); @@ -1320,9 +1246,9 @@ int XrdHttpReq::ProcessHTTPReq() { } else { // --------- READV - length = ReqReadV(); + length = ReqReadV(readChunkList); - if (!prot->Bridge->Run((char *) &xrdreq, (char *) ralist, length)) { + if (!prot->Bridge->Run((char *) &xrdreq, (char *) &ralist[0], length)) { prot->SendSimpleResp(404, NULL, NULL, (char *) "Could not run read request.", 0, false); return -1; } @@ -2105,6 +2031,8 @@ int XrdHttpReq::PostProcessHTTPReq(bool final_) { &fileflags, &filemodtime); + readRangeHandler.SetFilesize(filesize); + // We will default the response size specified by the headers; if that // wasn't given, use the file size. if (!length) { @@ -2152,6 +2080,8 @@ int XrdHttpReq::PostProcessHTTPReq(bool final_) { &fileflags, &filemodtime); + readRangeHandler.SetFilesize(filesize); + // As above: if the client specified a response size, we use that. // Otherwise, utilize the filesize if (!length) { @@ -2175,7 +2105,13 @@ int XrdHttpReq::PostProcessHTTPReq(bool final_) { responseHeader += std::string("Age: ") + std::to_string(object_age < 0 ? 0 : object_age); } - if (rwOps.size() == 0) { + const XrdHttpReadRangeHandler::UserRangeList &uranges = readRangeHandler.ListResolvedRanges(); + if (uranges.empty() && readRangeHandler.getError()) { + prot->SendSimpleResp(readRangeHandler.getError().httpRetCode, NULL, NULL, readRangeHandler.getError().errMsg.c_str(),0,false); + return -1; + } + + if (readRangeHandler.isFullFile()) { // Full file. if (m_transfer_encoding_chunked && m_trailer_headers) { @@ -2184,59 +2120,53 @@ int XrdHttpReq::PostProcessHTTPReq(bool final_) { prot->SendSimpleResp(200, NULL, responseHeader.empty() ? NULL : responseHeader.c_str(), NULL, filesize, keepalive); } return 0; - } else - if (rwOps.size() == 1) { - // Only one read to perform - if (rwOps[0].byteend < 0) // The requested range was along the lines of "Range: 1234-", meaning we need to fill in the end - rwOps[0].byteend = filesize - 1; - int cnt = (rwOps[0].byteend - rwOps[0].bytestart + 1); + } + + if (readRangeHandler.isSingleRange()) { + // Possibly with zero sized file but should have been included + // in the FullFile case above + if (uranges.size() != 1) + return -1; + + // Only one range to return to the user char buf[64]; - + const off_t cnt = uranges[0].end - uranges[0].start + 1; + XrdOucString s = "Content-Range: bytes "; - sprintf(buf, "%lld-%lld/%lld", rwOps[0].bytestart, rwOps[0].byteend, filesize); + sprintf(buf, "%lld-%lld/%lld", (long long int)uranges[0].start, (long long int)uranges[0].end, filesize); s += buf; if (!responseHeader.empty()) { s += "\r\n"; s += responseHeader.c_str(); } + prot->SendSimpleResp(206, NULL, (char *)s.c_str(), NULL, cnt, keepalive); return 0; - } else - if (rwOps.size() > 1) { - // First, check that the amount of range request can be handled by the vector read of the XRoot layer - if(rwOps.size() > XrdProto::maxRvecsz) { - std::string errMsg = "Too many range requests provided. Maximum range requests supported is " + std::to_string(XrdProto::maxRvecsz); - prot->SendSimpleResp(400, NULL, NULL,errMsg.c_str(), errMsg.size(), false); - return -1; - } - // Multiple reads to perform, compose and send the header - int cnt = 0; - for (size_t i = 0; i < rwOps.size(); i++) { + } - if (rwOps[i].bytestart > filesize) continue; - if (rwOps[i].byteend > filesize - 1) - rwOps[i].byteend = filesize - 1; + // Multiple reads to perform, compose and send the header + off_t cnt = 0; + for (auto &ur : uranges) { + cnt += ur.end - ur.start + 1; - cnt += (rwOps[i].byteend - rwOps[i].bytestart + 1); + cnt += buildPartialHdr(ur.start, + ur.end, + filesize, + (char *) "123456").size(); - cnt += buildPartialHdr(rwOps[i].bytestart, - rwOps[i].byteend, - filesize, - (char *) "123456").size(); - } - cnt += buildPartialHdrEnd((char *) "123456").size(); - std::string header = "Content-Type: multipart/byteranges; boundary=123456"; - if (!m_digest_header.empty()) { - header += "\n"; - header += m_digest_header; - } - - prot->SendSimpleResp(206, NULL, header.c_str(), NULL, cnt, keepalive); - return 0; + } + cnt += buildPartialHdrEnd((char *) "123456").size(); + std::string header = "Content-Type: multipart/byteranges; boundary=123456"; + if (!m_digest_header.empty()) { + header += "\n"; + header += m_digest_header; } + prot->SendSimpleResp(206, NULL, header.c_str(), NULL, cnt, keepalive); + return 0; + } else { // xrdresp indicates an error occurred @@ -2251,10 +2181,14 @@ int XrdHttpReq::PostProcessHTTPReq(bool final_) { default: //read or readv { // If we are postprocessing a close, potentially send out informational trailers - if ((ntohs(xrdreq.header.requestid) == kXR_close) || - ((reqstate == 4) && (ntohs(xrdreq.header.requestid) == kXR_readv))) + if ((ntohs(xrdreq.header.requestid) == kXR_close) || readClosing) { - + const XrdHttpReadRangeHandler::Error &rrerror = readRangeHandler.getError(); + if (rrerror) { + httpStatusCode = rrerror.httpRetCode; + httpStatusText = rrerror.errMsg; + } + if (m_transfer_encoding_chunked && m_trailer_headers) { if (prot->ChunkRespHeader(0)) return -1; @@ -2271,7 +2205,8 @@ int XrdHttpReq::PostProcessHTTPReq(bool final_) { return -1; } - return keepalive ? 1 : -1; + if (rrerror) return -1; + return keepalive ? 1 : -1; } // On error, we can only send out a message if trailers are enabled and the @@ -2304,84 +2239,24 @@ int XrdHttpReq::PostProcessHTTPReq(bool final_) { } } - // Prevent scenario where data is expected but none is actually read - // E.g. Accessing files which return the results of a script - if ((ntohs(xrdreq.header.requestid) == kXR_read) && - (reqstate > 3) && (iovN == 0)) { - TRACEI(REQ, "Stopping request because more data is expected " - "but no data has been read."); - return -1; - } - TRACEI(REQ, "Got data vectors to send:" << iovN); - if (ntohs(xrdreq.header.requestid) == kXR_readv) { - // Readv case, we must take out each individual header and format it according to the http rules - readahead_list *l; - char *p; - int len; - - // Cycle on all the data that is coming from the server - for (int i = 0; i < iovN; i++) { - - for (p = (char *) iovP[i].iov_base; p < (char *) iovP[i].iov_base + iovP[i].iov_len;) { - l = (readahead_list *) p; - len = ntohl(l->rlen); - - // Now we have a chunk coming from the server. This may be a partial chunk - if (rwOpPartialDone == 0) { - std::string s = buildPartialHdr(rwOps[rwOpDone].bytestart, - rwOps[rwOpDone].byteend, - filesize, - (char *) "123456"); - - TRACEI(REQ, "Sending multipart: " << rwOps[rwOpDone].bytestart << "-" << rwOps[rwOpDone].byteend); - if (prot->SendData((char *) s.c_str(), s.size())) return -1; - } - - // Send all the data we have - if (prot->SendData(p + sizeof (readahead_list), len)) return -1; - - // If we sent all the data relative to the current original chunk request - // then pass to the next chunk, otherwise wait for more data - rwOpPartialDone += len; - if (rwOpPartialDone >= rwOps[rwOpDone].byteend - rwOps[rwOpDone].bytestart + 1) { - rwOpDone++; - rwOpPartialDone = 0; - } - - p += sizeof (readahead_list); - p += len; - - } - } - - if (rwOpDone == rwOps.size()) { - std::string s = buildPartialHdrEnd((char *) "123456"); - if (prot->SendData((char *) s.c_str(), s.size())) return -1; - } + XrdHttpIOList received; + getReadResponse(received); + int rc; + if (readRangeHandler.isSingleRange()) { + rc = sendReadResponseSingleRange(received); } else { - // Send chunked encoding header - if (m_transfer_encoding_chunked && m_trailer_headers) { - int sum = 0; - for (int i = 0; i < iovN; i++) sum += iovP[i].iov_len; - prot->ChunkRespHeader(sum); - } - for (int i = 0; i < iovN; i++) { - if (prot->SendData((char *) iovP[i].iov_base, iovP[i].iov_len)) return -1; - writtenbytes += iovP[i].iov_len; - } - if (m_transfer_encoding_chunked && m_trailer_headers) { - prot->ChunkRespFooter(); - } + rc = sendReadResponsesMultiRanges(received); } - - // Let's make sure that we avoid sending the same data twice, - // in the case where PostProcessHTTPReq is invoked again - this->iovN = 0; - + if (rc) { + // make sure readRangeHandler will trigger close + // of file after next NextReadList(). + readRangeHandler.NotifyError(); + } + return 0; } // end read or readv @@ -2800,10 +2675,8 @@ void XrdHttpReq::reset() { TRACE(REQ, " XrdHttpReq request ended."); //if (xmlbody) xmlFreeDoc(xmlbody); - rwOps.clear(); - rwOps_split.clear(); - rwOpDone = 0; - rwOpPartialDone = 0; + readRangeHandler.reset(); + readClosing = false; writtenbytes = 0; etext.clear(); redirdest = ""; @@ -2819,8 +2692,8 @@ void XrdHttpReq::reset() { depth = 0; xrdresp = kXR_noResponsesYet; xrderrcode = kXR_noErrorYet; - if (ralist) free(ralist); - ralist = 0; + ralist.clear(); + ralist.shrink_to_fit(); request = rtUnset; resource = ""; @@ -2884,3 +2757,152 @@ void XrdHttpReq::getfhandle() { (int) fhandle[0] << ":" << (int) fhandle[1] << ":" << (int) fhandle[2] << ":" << (int) fhandle[3]); } + +void XrdHttpReq::getReadResponse(XrdHttpIOList &received) { + received.clear(); + + if (ntohs(xrdreq.header.requestid) == kXR_readv) { + readahead_list *l; + char *p; + kXR_int32 len; + + // Cycle on all the data that is coming from the server + for (int i = 0; i < iovN; i++) { + + for (p = (char *) iovP[i].iov_base; p < (char *) iovP[i].iov_base + iovP[i].iov_len;) { + l = (readahead_list *) p; + len = ntohl(l->rlen); + + received.emplace_back(p+sizeof(readahead_list), -1, len); + + p += sizeof (readahead_list); + p += len; + + } + } + return; + } + + // kXR_read result + for (int i = 0; i < iovN; i++) { + received.emplace_back((char*)iovP[i].iov_base, -1, iovP[i].iov_len); + } + +} + +int XrdHttpReq::sendReadResponsesMultiRanges(const XrdHttpIOList &received) { + + if (received.size() == 0) { + bool start, finish; + if (readRangeHandler.NotifyReadResult(0, nullptr, start, finish) < 0) { + return -1; + } + return 0; + } + + // user is expecting multiple ranges, we must be prepared to send an + // individual header for each and format it according to the http rules + + struct rinfo { + bool start; + bool finish; + const XrdOucIOVec2 *ci; + const XrdHttpReadRangeHandler::UserRange *ur; + std::string st_header; + std::string fin_header; + }; + + // report each received byte chunk to the range handler and record the details + // of original user range it related to and if starts a range or finishes all. + std::vector rvec; + + rvec.reserve(received.size()); + + for(const auto &rcv: received) { + rinfo rentry; + bool start, finish; + const XrdHttpReadRangeHandler::UserRange *ur; + + if (readRangeHandler.NotifyReadResult(rcv.size, &ur, start, finish) < 0) { + return -1; + } + rentry.ur = ur; + rentry.start = start; + rentry.finish = finish; + rentry.ci = &rcv; + + if (start) { + std::string s = buildPartialHdr(ur->start, + ur->end, + filesize, + (char *) "123456"); + + rentry.st_header = s; + } + + if (finish) { + std::string s = buildPartialHdrEnd((char *) "123456"); + rentry.fin_header = s; + } + + rvec.push_back(rentry); + } + + // send the user the headers / data + for(const auto &rentry: rvec) { + + if (rentry.start) { + TRACEI(REQ, "Sending multipart: " << rentry.ur->start << "-" << rentry.ur->end); + if (prot->SendData((char *) rentry.st_header.c_str(), rentry.st_header.size())) { + return -1; + } + } + + // Send all the data we have + if (prot->SendData((char *) rentry.ci->data, rentry.ci->size)) { + return -1; + } + + if (rentry.finish) { + if (prot->SendData((char *) rentry.fin_header.c_str(), rentry.fin_header.size())) { + return -1; + } + } + } + + return 0; +} + +int XrdHttpReq::sendReadResponseSingleRange(const XrdHttpIOList &received) { + // single range http transfer + + if (received.size() == 0) { + bool start, finish; + if (readRangeHandler.NotifyReadResult(0, nullptr, start, finish) < 0) { + return -1; + } + return 0; + } + + off_t sum = 0; + // notify the range handler and return if error + for(const auto &rcv: received) { + bool start, finish; + if (readRangeHandler.NotifyReadResult(rcv.size, nullptr, start, finish) < 0) { + return -1; + } + sum += rcv.size; + } + + // Send chunked encoding header + if (m_transfer_encoding_chunked && m_trailer_headers) { + prot->ChunkRespHeader(sum); + } + for(const auto &rcv: received) { + if (prot->SendData((char *) rcv.data, rcv.size)) return -1; + } + if (m_transfer_encoding_chunked && m_trailer_headers) { + prot->ChunkRespFooter(); + } + return 0; +} diff --git a/src/XrdHttp/XrdHttpReq.hh b/src/XrdHttp/XrdHttpReq.hh index 81e50683eea..48dbbcd003a 100644 --- a/src/XrdHttp/XrdHttpReq.hh +++ b/src/XrdHttp/XrdHttpReq.hh @@ -44,6 +44,7 @@ #include "XProtocol/XProtocol.hh" #include "XrdXrootd/XrdXrootdBridge.hh" #include "XrdHttpChecksumHandler.hh" +#include "XrdHttpReadRangeHandler.hh" #include #include @@ -54,14 +55,6 @@ -#define READV_MAXCHUNKS 512 -#define READV_MAXCHUNKSIZE (1024*128) - -struct ReadWriteOp { - // < 0 means "not specified" - long long bytestart; - long long byteend; -}; struct DirListInfo { std::string path; @@ -94,9 +87,7 @@ private: // after a response body has started bool m_status_trailer{false}; - int parseContentRange(char *); int parseHost(char *); - int parseRWOp(char *); //xmlDocPtr xmlbody; /* the resulting document tree */ XrdHttpProtocol *prot; @@ -126,6 +117,19 @@ private: // Sanitize the resource from http[s]://[host]/ questionable prefix void sanitizeResourcePfx(); + // parses the iovN data pointers elements as either a kXR_read or kXR_readv + // response and fills out a XrdHttpIOList with the corresponding length and + // buffer pointers. File offsets from kXR_readv responses are not recorded. + void getReadResponse(XrdHttpIOList &received); + + // notifies the range handler of receipt of bytes and sends the client + // the data. + int sendReadResponseSingleRange(const XrdHttpIOList &received); + + // notifies the range handler of receipt of bytes and sends the client + // the data and necessary headers, assuming multipart/byteranges content type. + int sendReadResponsesMultiRanges(const XrdHttpIOList &received); + /** * Extract a comma separated list of checksums+metadata into a vector * @param checksumList the list like "0:sha1, 1:adler32, 2:md5" @@ -142,13 +146,13 @@ private: static void determineXRootDChecksumFromUserDigest(const std::string & userDigest, std::vector & xrootdChecksums); public: - XrdHttpReq(XrdHttpProtocol *protinstance) : keepalive(true) { + XrdHttpReq(XrdHttpProtocol *protinstance, const XrdHttpReadRangeHandler::Configuration &rcfg) : + readRangeHandler(rcfg), keepalive(true) { prot = protinstance; length = 0; //xmlbody = 0; depth = 0; - ralist = 0; opaque = 0; writtenbytes = 0; fopened = false; @@ -169,8 +173,8 @@ public: int parseBody(char *body, long long len); /// Prepare the buffers for sending a readv request - int ReqReadV(); - readahead_list *ralist; + int ReqReadV(const XrdHttpIOList &cl); + std::vector ralist; /// Build a partial header for a multipart response std::string buildPartialHdr(long long bytestart, long long byteend, long long filesize, char *token); @@ -224,13 +228,9 @@ public: /// Tells if we have finished reading the header bool headerok; - - // This can be largely optimized... - /// The original list of multiple reads to perform - std::vector rwOps; - /// The new list got from chunking the original req respecting the xrootd - /// max sizes etc. - std::vector rwOps_split; + /// Tracking the next ranges of data to read during GET + XrdHttpReadRangeHandler readRangeHandler; + bool readClosing; bool keepalive; long long length; // Total size from client for PUT; total length of response TO client for GET. diff --git a/src/XrdHttp/XrdHttpUtils.hh b/src/XrdHttp/XrdHttpUtils.hh index 67d334bf11d..3b5ff6c2fe9 100644 --- a/src/XrdHttp/XrdHttpUtils.hh +++ b/src/XrdHttp/XrdHttpUtils.hh @@ -37,6 +37,9 @@ #include "XProtocol/XPtypes.hh" #include "XrdSec/XrdSecEntity.hh" +#include "XrdOuc/XrdOucIOVec.hh" +#include +#include #ifndef XRDHTTPUTILS_HH #define XRDHTTPUTILS_HH @@ -89,6 +92,8 @@ char *unquote(char *str); // Escape a string and return a new one char *escapeXML(const char *str); +typedef std::vector XrdHttpIOList; + #endif /* XRDHTTPUTILS_HH */ diff --git a/tests/XrdHttpTests/XrdHttpTests.cc b/tests/XrdHttpTests/XrdHttpTests.cc index 739b45b7f08..7b7ce778de3 100644 --- a/tests/XrdHttpTests/XrdHttpTests.cc +++ b/tests/XrdHttpTests/XrdHttpTests.cc @@ -3,10 +3,11 @@ #include "XrdHttp/XrdHttpReq.hh" #include "XrdHttp/XrdHttpProtocol.hh" #include "XrdHttp/XrdHttpChecksumHandler.hh" +#include "XrdHttp/XrdHttpReadRangeHandler.hh" #include #include #include - +#include using namespace testing; @@ -164,4 +165,412 @@ TEST(XrdHttpTests, checksumHandlerSelectionTest) { handler.configure(configChecksumList); ASSERT_EQ(nullptr, handler.getChecksumToRun(reqDigest)); } -} \ No newline at end of file +} + +TEST(XrdHttpTests, xrdHttpReadRangeHandlerTwoRangesOfSizeEqualToMaxChunkSize) { + long long filesize = 8; + int rangeBegin = 0; + int rangeEnd = 3; + int rangeBegin2 = 4; + int rangeEnd2 = 7; + int readvMaxChunkSize = 4; + int readvMaxChunks = 20; + int rReqMaxSize = 200; + std::stringstream ss; + ss << "bytes=" << rangeBegin << "-" << rangeEnd << ", " << rangeBegin2 << "-" << rangeEnd2; + std::string rs = ss.str(); + XrdHttpReadRangeHandler::Configuration cfg(readvMaxChunkSize, readvMaxChunks, rReqMaxSize); + XrdHttpReadRangeHandler h(cfg); + h.ParseContentRange(rs.c_str()); + h.SetFilesize(filesize); + const XrdHttpReadRangeHandler::UserRangeList &ul = h.ListResolvedRanges(); + const XrdHttpIOList &cl = h.NextReadList(); + ASSERT_EQ(2, cl.size()); + ASSERT_EQ(0, cl[0].offset); + ASSERT_EQ(4, cl[0].size); + ASSERT_EQ(4, cl[1].offset); + ASSERT_EQ(4, cl[1].size); + ASSERT_EQ(2, ul.size()); +} + +TEST(XrdHttpTests, xrdHttpReadRangeHandlerOneRangeSizeLessThanMaxChunkSize) { + long long filesize = 8; + int rangeBegin = 0; + int rangeEnd = 3; + int readvMaxChunkSize = 5; + int readvMaxChunks = 20; + int rReqMaxSize = 200; + std::stringstream ss; + ss << "bytes=" << rangeBegin << "-" << rangeEnd; + std::string rs = ss.str(); + XrdHttpReadRangeHandler::Configuration cfg(readvMaxChunkSize, readvMaxChunks, rReqMaxSize); + XrdHttpReadRangeHandler h(cfg); + h.ParseContentRange(rs.c_str()); + h.SetFilesize(filesize); + const XrdHttpReadRangeHandler::UserRangeList &ul = h.ListResolvedRanges(); + const XrdHttpIOList &cl = h.NextReadList(); + ASSERT_EQ(1, cl.size()); + ASSERT_EQ(0, cl[0].offset); + ASSERT_EQ(4, cl[0].size); + ASSERT_EQ(1, ul.size()); +} + +TEST(XrdHttpTests, xrdHttpReadRangeHandlerOneRangeSizeGreaterThanMaxChunkSize) { + long long filesize = 8; + int rangeBegin = 0; + int rangeEnd = 7; + int readvMaxChunkSize = 3; + int readvMaxChunks = 20; + int rReqMaxSize = 200; + std::stringstream ss; + ss << "bytes=" << rangeBegin << "-" << rangeEnd; + std::string rs = ss.str(); + XrdHttpReadRangeHandler::Configuration cfg(readvMaxChunkSize, readvMaxChunks, rReqMaxSize); + XrdHttpReadRangeHandler h(cfg); + h.ParseContentRange(rs.c_str()); + h.SetFilesize(filesize); + { + const XrdHttpReadRangeHandler::UserRangeList &ul = h.ListResolvedRanges(); + const XrdHttpIOList &cl = h.NextReadList(); + ASSERT_EQ(1, cl.size()); + ASSERT_EQ(0, cl[0].offset); + ASSERT_EQ(8, cl[0].size); + ASSERT_EQ(1, ul.size()); + } + ss.str(""); + ss << "bytes=0-0," << rangeBegin << "-" << rangeEnd; + rs = ss.str(); + h.reset(); + h.ParseContentRange(rs.c_str()); + h.SetFilesize(filesize); + { + const XrdHttpReadRangeHandler::UserRangeList &ul = h.ListResolvedRanges(); + const XrdHttpIOList &cl = h.NextReadList(); + ASSERT_EQ(4, cl.size()); + ASSERT_EQ(0, cl[0].offset); + ASSERT_EQ(1, cl[0].size); + ASSERT_EQ(0, cl[1].offset); + ASSERT_EQ(3, cl[1].size); + ASSERT_EQ(3, cl[2].offset); + ASSERT_EQ(3, cl[2].size); + ASSERT_EQ(6, cl[3].offset); + ASSERT_EQ(2, cl[3].size); + ASSERT_EQ(2, ul.size()); + } +} + +TEST(XrdHttpTests, xrdHttpReadRangeHandlerRange0ToEnd) { + long long filesize = 200; + int rangeBegin = 0; + int readvMaxChunkSize = 4; + int readvMaxChunks = 20; + int rReqMaxSize = 100; + bool start, finish; + std::stringstream ss; + ss << "bytes=" << rangeBegin << "-" << "\r"; + std::string rs = ss.str(); + XrdHttpReadRangeHandler::Configuration cfg(readvMaxChunkSize, readvMaxChunks, rReqMaxSize); + XrdHttpReadRangeHandler h(cfg); + h.ParseContentRange(rs.c_str()); + h.SetFilesize(filesize); + const XrdHttpReadRangeHandler::UserRangeList &ul = h.ListResolvedRanges(); + { + const XrdHttpIOList &cl1 = h.NextReadList(); + ASSERT_EQ(1, ul.size()); + ASSERT_EQ(1, cl1.size()); + ASSERT_EQ(0, cl1[0].offset); + ASSERT_EQ(100, cl1[0].size); + ASSERT_EQ(0, h.NotifyReadResult(100, nullptr, start, finish)); + } + { + const XrdHttpIOList &cl2 = h.NextReadList(); + ASSERT_EQ(1, cl2.size()); + ASSERT_EQ(100, cl2[0].offset); + ASSERT_EQ(100, cl2[0].size); + } +} + +TEST(XrdHttpTests, xrdHttpReadRangeHandlerRange5FromEnd) { + long long filesize = 200; + int rangeEnd = 5; + int readvMaxChunkSize = 4; + int readvMaxChunks = 20; + int rReqMaxSize = 100; + bool start, finish; + std::stringstream ss; + ss << "bytes=-" << rangeEnd << "\r"; + std::string rs = ss.str(); + XrdHttpReadRangeHandler::Configuration cfg(readvMaxChunkSize, readvMaxChunks, rReqMaxSize); + XrdHttpReadRangeHandler h(cfg); + h.ParseContentRange(rs.c_str()); + h.SetFilesize(filesize); + const XrdHttpReadRangeHandler::UserRangeList &ul = h.ListResolvedRanges(); + { + const XrdHttpIOList &cl1 = h.NextReadList(); + ASSERT_EQ(1, ul.size()); + ASSERT_EQ(1, cl1.size()); + ASSERT_EQ(195, cl1[0].offset); + ASSERT_EQ(5, cl1[0].size); + ASSERT_EQ(0, h.NotifyReadResult(5, nullptr, start, finish)); + } + { + const XrdHttpIOList &cl2 = h.NextReadList(); + ASSERT_EQ(0, cl2.size()); + const XrdHttpReadRangeHandler::Error &error = h.getError(); + ASSERT_EQ(false, static_cast(error)); + } +} + +TEST(XrdHttpTests, xrdHttpReadRangeHandlerRange0To0) { + long long filesize = 8; + int rangeBegin = 0; + int rangeEnd = 0; + int readvMaxChunkSize = 4; + int readvMaxChunks = 20; + int rReqMaxSize = 100; + std::stringstream ss; + ss << "bytes=" << rangeBegin << "-" << rangeEnd; + std::string rs = ss.str(); + XrdHttpReadRangeHandler::Configuration cfg(readvMaxChunkSize, readvMaxChunks, rReqMaxSize); + XrdHttpReadRangeHandler h(cfg); + h.ParseContentRange(rs.c_str()); + h.SetFilesize(filesize); + const XrdHttpReadRangeHandler::UserRangeList &ul = h.ListResolvedRanges(); + const XrdHttpIOList &cl = h.NextReadList(); + ASSERT_EQ(1, cl.size()); + ASSERT_EQ(1, ul.size()); + ASSERT_EQ(0, ul[0].start); + ASSERT_EQ(0, ul[0].end); + ASSERT_EQ(0, cl[0].offset); + ASSERT_EQ(1, cl[0].size); +} + +TEST(XrdHttpTests, xrdHttpReadRangeHandlerEndByteGreaterThanFileSize) { + long long filesize = 2; + int rangeBegin = 0; + int rangeEnd = 4; + int readvMaxChunkSize = 10; + int readvMaxChunks = 20; + int rReqMaxSize = 100; + std::stringstream ss; + ss << "bytes=" << rangeBegin << "-" << rangeEnd; + std::string rs = ss.str(); + XrdHttpReadRangeHandler::Configuration cfg(readvMaxChunkSize, readvMaxChunks, rReqMaxSize); + XrdHttpReadRangeHandler h(cfg); + h.ParseContentRange(rs.c_str()); + h.SetFilesize(filesize); + const XrdHttpReadRangeHandler::UserRangeList &ul = h.ListResolvedRanges(); + const XrdHttpIOList &cl = h.NextReadList(); + ASSERT_EQ(1, cl.size()); + ASSERT_EQ(0, cl[0].offset); + ASSERT_EQ(2, cl[0].size); + ASSERT_EQ(1, ul.size()); + ASSERT_EQ(0, ul[0].start); + ASSERT_EQ(1, ul[0].end); +} + +TEST(XrdHttpTests, xrdHttpReadRangeHandlerRangeBeginGreaterThanFileSize) { + long long filesize = 2; + int rangeBegin = 4; + int rangeEnd = 6; + int readvMaxChunkSize = 10; + int readvMaxChunks = 20; + int rReqMaxSize = 100; + std::stringstream ss; + ss << "bytes=" << rangeBegin << "-" << rangeEnd; + std::string rs = ss.str(); + XrdHttpReadRangeHandler::Configuration cfg(readvMaxChunkSize, readvMaxChunks, rReqMaxSize); + XrdHttpReadRangeHandler h(cfg); + h.ParseContentRange(rs.c_str()); + h.SetFilesize(filesize); + const XrdHttpReadRangeHandler::UserRangeList &ul = h.ListResolvedRanges(); + const XrdHttpIOList &cl = h.NextReadList(); + ASSERT_EQ(0, ul.size()); + ASSERT_EQ(0, cl.size()); + const XrdHttpReadRangeHandler::Error &error = h.getError(); + ASSERT_EQ(true, static_cast(error)); + ASSERT_EQ(416, error.httpRetCode); +} + +TEST(XrdHttpTests, xrdHttpReadRangeHandlerTwoRangesOneOutsideFileExtent) { + long long filesize = 20; + int rangeBegin1 = 22; + int rangeEnd1 = 30; + int rangeBegin2 = 4; + int rangeEnd2 = 6; + int readvMaxChunkSize = 10; + int readvMaxChunks = 20; + int rReqMaxSize = 100; + std::stringstream ss; + ss << "bytes=" << rangeBegin1 << "-" << rangeEnd1 << "," << rangeBegin2 << "-" << rangeEnd2; + std::string rs = ss.str(); + XrdHttpReadRangeHandler::Configuration cfg(readvMaxChunkSize, readvMaxChunks, rReqMaxSize); + XrdHttpReadRangeHandler h(cfg); + h.ParseContentRange(rs.c_str()); + h.SetFilesize(filesize); + const XrdHttpReadRangeHandler::UserRangeList &ul = h.ListResolvedRanges(); + const XrdHttpIOList &cl = h.NextReadList(); + ASSERT_EQ(1, ul.size()); + ASSERT_EQ(4, ul[0].start); + ASSERT_EQ(6, ul[0].end); + ASSERT_EQ(1, cl.size()); + ASSERT_EQ(4, cl[0].offset); + ASSERT_EQ(3, cl[0].size); +} + +TEST(XrdHttpTests, xrdHttpReadRangeHandlerMultiChunksSingleRange) { + long long filesize = 20; + int rangeBegin = 0; + int rangeEnd = 15; + int readvMaxChunkSize = 3; + int readvMaxChunks = 20; + int rReqMaxSize = 5; + bool start, finish; + std::stringstream ss; + ss << "bytes=" << rangeBegin << "-" << rangeEnd; + std::string rs = ss.str(); + XrdHttpReadRangeHandler::Configuration cfg(readvMaxChunkSize, readvMaxChunks, rReqMaxSize); + XrdHttpReadRangeHandler h(cfg); + h.ParseContentRange(rs.c_str()); + h.SetFilesize(filesize); + const XrdHttpReadRangeHandler::UserRangeList &ul = h.ListResolvedRanges(); + ASSERT_EQ(1, ul.size()); + ASSERT_EQ(0, ul[0].start); + ASSERT_EQ(15, ul[0].end); + { + const XrdHttpIOList &cl = h.NextReadList(); + ASSERT_EQ(1, cl.size()); + ASSERT_EQ(0, cl[0].offset); + ASSERT_EQ(5, cl[0].size); + ASSERT_EQ(0, h.NotifyReadResult(5, nullptr, start, finish)); + ASSERT_EQ(true, start); + ASSERT_EQ(false, finish); + } + { + const XrdHttpIOList &cl = h.NextReadList(); + ASSERT_EQ(1, cl.size()); + ASSERT_EQ(5, cl[0].offset); + ASSERT_EQ(5, cl[0].size); + ASSERT_EQ(0, h.NotifyReadResult(5, nullptr, start, finish)); + ASSERT_EQ(false, start); + ASSERT_EQ(false, finish); + } + { + const XrdHttpIOList &cl = h.NextReadList(); + ASSERT_EQ(1, cl.size()); + ASSERT_EQ(10, cl[0].offset); + ASSERT_EQ(5, cl[0].size); + ASSERT_EQ(0, h.NotifyReadResult(5, nullptr, start, finish)); + ASSERT_EQ(false, start); + ASSERT_EQ(false, finish); + } + { + const XrdHttpIOList &cl = h.NextReadList(); + ASSERT_EQ(1, cl.size()); + ASSERT_EQ(15, cl[0].offset); + ASSERT_EQ(1, cl[0].size); + ASSERT_EQ(0, h.NotifyReadResult(1, nullptr, start, finish)); + ASSERT_EQ(false, start); + ASSERT_EQ(true, finish); + } + { + const XrdHttpIOList &cl = h.NextReadList(); + ASSERT_EQ(0, cl.size()); + const XrdHttpReadRangeHandler::Error &error = h.getError(); + ASSERT_EQ(false, static_cast(error)); + } +} + +TEST(XrdHttpTests, xrdHttpReadRangeHandlerMultiChunksTwoRanges) { + long long filesize = 22; + int rangeBegin1 = 0; + int rangeEnd1 = 1; + int rangeBegin2 = 5; + int rangeEnd2 = 21; + int readvMaxChunkSize = 3; + int readvMaxChunks = 2; + int rReqMaxSize = 5; + bool start, finish; + const XrdHttpReadRangeHandler::UserRange *ur; + std::stringstream ss; + ss << "bytes=" << rangeBegin1 << "-" << rangeEnd1 << "," << rangeBegin2 << "-" << rangeEnd2; + std::string rs = ss.str(); + XrdHttpReadRangeHandler::Configuration cfg(readvMaxChunkSize, readvMaxChunks, rReqMaxSize); + XrdHttpReadRangeHandler h(cfg); + h.ParseContentRange(rs.c_str()); + h.SetFilesize(filesize); + const XrdHttpReadRangeHandler::UserRangeList &ul = h.ListResolvedRanges(); + ASSERT_EQ(2, ul.size()); + ASSERT_EQ(0, ul[0].start); + ASSERT_EQ(1, ul[0].end); + ASSERT_EQ(5, ul[1].start); + ASSERT_EQ(21, ul[1].end); + { + // we get 0-1, 5-7 + const XrdHttpIOList &cl = h.NextReadList(); + ASSERT_EQ(2, cl.size()); + ASSERT_EQ(0, cl[0].offset); + ASSERT_EQ(2, cl[0].size); + ASSERT_EQ(5, cl[1].offset); + ASSERT_EQ(3, cl[1].size); + ASSERT_EQ(0, h.NotifyReadResult(2, &ur, start, finish)); + ASSERT_EQ(true, start); + ASSERT_EQ(false, finish); + ASSERT_EQ(0, ur->start); + ASSERT_EQ(1, ur->end); + ASSERT_EQ(0, h.NotifyReadResult(3, &ur, start, finish)); + ASSERT_EQ(true, start); + ASSERT_EQ(false, finish); + ASSERT_EQ(5, ur->start); + ASSERT_EQ(21, ur->end); + } + { + // we get 8-12 + const XrdHttpIOList &cl = h.NextReadList(); + ASSERT_EQ(1, cl.size()); + ASSERT_EQ(8, cl[0].offset); + ASSERT_EQ(5, cl[0].size); + ASSERT_EQ(0, h.NotifyReadResult(5, &ur, start, finish)); + ASSERT_EQ(false, start); + ASSERT_EQ(false, finish); + ASSERT_EQ(5, ur->start); + ASSERT_EQ(21, ur->end); + } + { + // we get 13-17 + const XrdHttpIOList &cl = h.NextReadList(); + ASSERT_EQ(1, cl.size()); + ASSERT_EQ(13, cl[0].offset); + ASSERT_EQ(5, cl[0].size); + ASSERT_EQ(0, h.NotifyReadResult(5, &ur, start, finish)); + ASSERT_EQ(false, start); + ASSERT_EQ(false, finish); + ASSERT_EQ(5, ur->start); + ASSERT_EQ(21, ur->end); + } + { + // we get 18-20, 21-21 + const XrdHttpIOList &cl = h.NextReadList(); + ASSERT_EQ(2, cl.size()); + ASSERT_EQ(18, cl[0].offset); + ASSERT_EQ(3, cl[0].size); + ASSERT_EQ(21, cl[1].offset); + ASSERT_EQ(1, cl[1].size); + ASSERT_EQ(0, h.NotifyReadResult(3, &ur, start, finish)); + ASSERT_EQ(false, start); + ASSERT_EQ(false, finish); + ASSERT_EQ(5, ur->start); + ASSERT_EQ(21, ur->end); + ASSERT_EQ(0, h.NotifyReadResult(1, &ur, start, finish)); + ASSERT_EQ(false, start); + ASSERT_EQ(true, finish); + ASSERT_EQ(5, ur->start); + ASSERT_EQ(21, ur->end); + } + { + const XrdHttpIOList &cl = h.NextReadList(); + ASSERT_EQ(0, cl.size()); + const XrdHttpReadRangeHandler::Error &error = h.getError(); + ASSERT_EQ(false, static_cast(error)); + } +} From 18d3bd33fe2fd5478ddbc687e94abc2065e157c0 Mon Sep 17 00:00:00 2001 From: David Smith Date: Thu, 14 Sep 2023 11:15:52 +0200 Subject: [PATCH 225/442] [XrdHttp] Correct chunked response for GET with a byte range #2076 Also suppress Content-Length header for transfer-encoding chunked response. --- src/XrdHttp/XrdHttpReq.cc | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/src/XrdHttp/XrdHttpReq.cc b/src/XrdHttp/XrdHttpReq.cc index b39c5883a53..5b9c185e4de 100644 --- a/src/XrdHttp/XrdHttpReq.cc +++ b/src/XrdHttp/XrdHttpReq.cc @@ -2115,7 +2115,7 @@ int XrdHttpReq::PostProcessHTTPReq(bool final_) { // Full file. if (m_transfer_encoding_chunked && m_trailer_headers) { - prot->StartChunkedResp(200, NULL, responseHeader.empty() ? NULL : responseHeader.c_str(), filesize, keepalive); + prot->StartChunkedResp(200, NULL, responseHeader.empty() ? NULL : responseHeader.c_str(), -1, keepalive); } else { prot->SendSimpleResp(200, NULL, responseHeader.empty() ? NULL : responseHeader.c_str(), NULL, filesize, keepalive); } @@ -2140,8 +2140,11 @@ int XrdHttpReq::PostProcessHTTPReq(bool final_) { s += responseHeader.c_str(); } - - prot->SendSimpleResp(206, NULL, (char *)s.c_str(), NULL, cnt, keepalive); + if (m_transfer_encoding_chunked && m_trailer_headers) { + prot->StartChunkedResp(206, NULL, (char *)s.c_str(), -1, keepalive); + } else { + prot->SendSimpleResp(206, NULL, (char *)s.c_str(), NULL, cnt, keepalive); + } return 0; } @@ -2163,8 +2166,11 @@ int XrdHttpReq::PostProcessHTTPReq(bool final_) { header += m_digest_header; } - - prot->SendSimpleResp(206, NULL, header.c_str(), NULL, cnt, keepalive); + if (m_transfer_encoding_chunked && m_trailer_headers) { + prot->StartChunkedResp(206, NULL, header.c_str(), -1, keepalive); + } else { + prot->SendSimpleResp(206, NULL, header.c_str(), NULL, cnt, keepalive); + } return 0; @@ -2814,7 +2820,10 @@ int XrdHttpReq::sendReadResponsesMultiRanges(const XrdHttpIOList &received) { // report each received byte chunk to the range handler and record the details // of original user range it related to and if starts a range or finishes all. + // also sum the total of the headers and data which need to be sent to the user, + // in case we need it for chunked transfer encoding std::vector rvec; + off_t sum_len = 0; rvec.reserve(received.size()); @@ -2838,16 +2847,26 @@ int XrdHttpReq::sendReadResponsesMultiRanges(const XrdHttpIOList &received) { (char *) "123456"); rentry.st_header = s; + sum_len += s.size(); } + sum_len += rcv.size; + if (finish) { std::string s = buildPartialHdrEnd((char *) "123456"); rentry.fin_header = s; + sum_len += s.size(); } rvec.push_back(rentry); } + + // Send chunked encoding header + if (m_transfer_encoding_chunked && m_trailer_headers) { + prot->ChunkRespHeader(sum_len); + } + // send the user the headers / data for(const auto &rentry: rvec) { @@ -2870,6 +2889,11 @@ int XrdHttpReq::sendReadResponsesMultiRanges(const XrdHttpIOList &received) { } } + // Send chunked encoding footer + if (m_transfer_encoding_chunked && m_trailer_headers) { + prot->ChunkRespFooter(); + } + return 0; } From 0ff51f08a9de24f10b1c0a353c5ce3d8626657ec Mon Sep 17 00:00:00 2001 From: Mattias Ellert Date: Fri, 14 Jul 2023 16:54:06 +0200 Subject: [PATCH 226/442] The include statement in rst includes the included file verbatim and therefore assumes that the included file is also in rst format. If it is a different format, like e.g. md, there are many syntax errors and warnings. Use mdinclude when including the README.md file during the documentation build. --- bindings/python/docs/source/conf.py | 10 ++++++++++ bindings/python/docs/source/install.rst | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/bindings/python/docs/source/conf.py b/bindings/python/docs/source/conf.py index 14aa9782860..db581b08ef1 100644 --- a/bindings/python/docs/source/conf.py +++ b/bindings/python/docs/source/conf.py @@ -29,6 +29,16 @@ 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.viewcode'] +try: + import sphinx_mdinclude + extensions.extend(['sphinx_mdinclude']) +except ImportError: + try: + import m2r + extensions.extend(['m2r']) + except ImportError: + pass + # Add any paths that contain templates here, relative to this directory. templates_path = ['.templates'] diff --git a/bindings/python/docs/source/install.rst b/bindings/python/docs/source/install.rst index abcdd555839..7e6ff75d3f4 100644 --- a/bindings/python/docs/source/install.rst +++ b/bindings/python/docs/source/install.rst @@ -2,5 +2,5 @@ **Installing** ``pyxrootd`` =========================== -.. include:: ../../README.md +.. mdinclude:: ../../README.md :start-line: 8 From aec4d6490e2202a3cfd3bdc4394bdbf34d68f29f Mon Sep 17 00:00:00 2001 From: Mattias Ellert Date: Fri, 14 Jul 2023 17:07:30 +0200 Subject: [PATCH 227/442] WARNING: The pre-Sphinx 1.0 'intersphinx_mapping' format is deprecated and will be removed in Sphinx 8. Update to the current format as described in the documentation. --- bindings/python/docs/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/python/docs/source/conf.py b/bindings/python/docs/source/conf.py index db581b08ef1..756b1b474e1 100644 --- a/bindings/python/docs/source/conf.py +++ b/bindings/python/docs/source/conf.py @@ -250,4 +250,4 @@ #texinfo_show_urls = 'footnote' # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'http://docs.python.org/': None} +intersphinx_mapping = { 'python': ('https://docs.python.org/3/', None) } From f923c0766b80fb705ff87f8a8bfb56465a0331bd Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 5 Sep 2023 09:30:36 +0200 Subject: [PATCH 228/442] [XrdSecgsi] Fix crash of xrdgsitest when proxy is not already set The test re-creates the proxy certificate, so if the initial loading of a pre-existing certificate fails, we use the recreated one for the remaining tests. --- src/XrdSecgsi/XrdSecgsitest.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/XrdSecgsi/XrdSecgsitest.cc b/src/XrdSecgsi/XrdSecgsitest.cc index ea134193685..4e10dd33635 100644 --- a/src/XrdSecgsi/XrdSecgsitest.cc +++ b/src/XrdSecgsi/XrdSecgsitest.cc @@ -256,6 +256,10 @@ int main( int argc, char **argv ) exit(1); } + // use recreated proxy certificate if it a proxy was not already set + if (!xPX) + xPX = xPXp; + // pline(""); pline("Load CA certificates"); From 47d64a7f325b8aabb059d24ef17a0583c31335b4 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 5 Sep 2023 21:50:59 +0200 Subject: [PATCH 229/442] [XrdCl] Fix promotion of root:// URLs to use TLS encryption Unless --notlsok is explicitly used, the client should try to promote root:// URLs to use TLS. Fixes: #2078, #2082, 8577e1fef61607b98ed15f78be6d165f64af20c9. --- src/XrdCl/XrdClXRootDTransport.cc | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/XrdCl/XrdClXRootDTransport.cc b/src/XrdCl/XrdClXRootDTransport.cc index 0caad99a00f..8604f107e5b 100644 --- a/src/XrdCl/XrdClXRootDTransport.cc +++ b/src/XrdCl/XrdClXRootDTransport.cc @@ -1905,13 +1905,17 @@ namespace XrdCl request->flags = ClientProtocolRequest::kXR_secreqs | ClientProtocolRequest::kXR_bifreqs; - if (info->encrypted) + XrdCl::Env *env = XrdCl::DefaultEnv::GetEnv(); + + int notlsok = DefaultNoTlsOK; + env->GetInt( "NoTlsOK", notlsok ); + + if (info->encrypted || !notlsok) request->flags |= ClientProtocolRequest::kXR_ableTLS; bool nodata = false; if( expect & ClientProtocolRequest::kXR_ExpBind ) { - XrdCl::Env *env = XrdCl::DefaultEnv::GetEnv(); int value = DefaultTlsNoData; env->GetInt( "TlsNoData", value ); nodata = bool( value ); From 3aa91c8eee42095f08150f310153b300b494d202 Mon Sep 17 00:00:00 2001 From: Angelo Galavotti Date: Thu, 31 Aug 2023 15:52:11 +0200 Subject: [PATCH 230/442] [XrdCl] Fix flag check for append in XrdClZipArchive --- src/XrdCl/XrdClZipArchive.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdCl/XrdClZipArchive.cc b/src/XrdCl/XrdClZipArchive.cc index 41d14b53af9..fb303d23668 100644 --- a/src/XrdCl/XrdClZipArchive.cc +++ b/src/XrdCl/XrdClZipArchive.cc @@ -500,7 +500,7 @@ namespace XrdCl { // the file does not exist in the archive so it only makes sense // if our user is opening for append - if( flags | OpenFlags::New ) + if( flags & OpenFlags::New ) { openfn = fn; lfh.reset( new LFH( fn, crc32, size, time( 0 ) ) ); From 961608070bbbda17622e34a592f461647d0962a0 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 25 Aug 2023 17:04:39 +0200 Subject: [PATCH 231/442] Merge PreReleaseNotes.txt into ReleaseNotes.txt --- docs/PreReleaseNotes.txt | 19 ------------------- docs/ReleaseNotes.txt | 1 + 2 files changed, 1 insertion(+), 19 deletions(-) delete mode 100644 docs/PreReleaseNotes.txt diff --git a/docs/PreReleaseNotes.txt b/docs/PreReleaseNotes.txt deleted file mode 100644 index cbe9c86170b..00000000000 --- a/docs/PreReleaseNotes.txt +++ /dev/null @@ -1,19 +0,0 @@ -====== - - -XRootD -====== - -Prerelease Notes -================ - -+ **New Features** - -+ **Major bug fixes** - -+ **Minor bug fixes** - -+ **Miscellaneous** - **[Server]** Default ffdest as per current pmark specification. - **Commit: f5f7839 - diff --git a/docs/ReleaseNotes.txt b/docs/ReleaseNotes.txt index b33aac7c201..c048173bae2 100644 --- a/docs/ReleaseNotes.txt +++ b/docs/ReleaseNotes.txt @@ -14,6 +14,7 @@ Version 5.6.2 + **Minor bug fixes** + **Miscellaneous** + **[Server]** Default ffdest as per current pmark specification. **[Server]** Correct code that tried to fix auth id spec on 11/2020. ------------- From b4fbc217cc1781f94ed509701c4c295d81a55593 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 15 Sep 2023 13:54:38 +0200 Subject: [PATCH 232/442] [CMake] Align XRootDVersion.cmake with genversion.sh --- cmake/XRootDVersion.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/XRootDVersion.cmake b/cmake/XRootDVersion.cmake index 41772f91c54..b5b26b0295a 100644 --- a/cmake/XRootDVersion.cmake +++ b/cmake/XRootDVersion.cmake @@ -52,7 +52,7 @@ else() message(WARNING "Failed to determine XRootD version, using a timestamp as fallback." "You can override this by setting -DXRootD_VERSION_STRING=x.y.z during configuration.") set(XRootD_VERSION_MAJOR 5) - set(XRootD_VERSION_MINOR 6) + set(XRootD_VERSION_MINOR 7) set(XRootD_VERSION_PATCH 0) set(XRootD_VERSION_TWEAK 0) set(XRootD_VERSION_NUMBER 1000000) From 190b44b31b3f7ee121ce22e44dbf46914477818d Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 15 Sep 2023 13:56:50 +0200 Subject: [PATCH 233/442] [Tests] Ensure smoke test fails when a command is not found --- tests/XRootD/smoke.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/XRootD/smoke.sh b/tests/XRootD/smoke.sh index a5f76cc924f..73d84f2a255 100755 --- a/tests/XRootD/smoke.sh +++ b/tests/XRootD/smoke.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash +set -e + : ${ADLER32:=$(command -v xrdadler32)} : ${CRC32C:=$(command -v xrdcrc32c)} : ${XRDCP:=$(command -v xrdcp)} @@ -17,9 +19,9 @@ done # This script assumes that ${HOST} exports an empty / as read/write. # It also assumes that any authentication required is already setup. -set -e +echo Using ${OPENSSL}: $(${OPENSSL} version) +echo Using ${XRDCP}: $(${XRDCP} --version) -${XRDCP} --version ${XRDFS} ${HOST} query config version # query some common server configurations From 5f862a22359343ccac003a2576d406a8d153a072 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 29 Aug 2023 16:37:11 +0200 Subject: [PATCH 234/442] [CMake] Use GitHub's syntax to group log lines per stage --- test.cmake | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test.cmake b/test.cmake index c8d0415f54d..8e5dfe63692 100644 --- a/test.cmake +++ b/test.cmake @@ -3,6 +3,18 @@ cmake_minimum_required(VERSION 3.16) set(ENV{LANG} "C") set(ENV{LC_ALL} "C") +macro(section title) + if (DEFINED ENV{CI}) + message("::group::${title}") + endif() +endmacro() + +macro(endsection) + if (DEFINED ENV{CI}) + message("::endgroup::") + endif() +endmacro() + site_name(CTEST_SITE) if(EXISTS "/etc/os-release") @@ -144,25 +156,32 @@ ctest_read_custom_files("${CTEST_SOURCE_DIRECTORY}") ctest_start(${MODEL}) ctest_update() +section("Configure") ctest_configure(OPTIONS "${CMAKE_ARGS}") ctest_read_custom_files("${CTEST_BINARY_DIRECTORY}") list(APPEND CTEST_NOTES_FILES ${CTEST_BINARY_DIRECTORY}/CMakeCache.txt) +endsection() +section("Build") ctest_build() if(INSTALL) set(ENV{DESTDIR} "${CTEST_BINARY_DIRECTORY}/install") ctest_build(TARGET install) endif() +endsection() +section("Test") ctest_test(PARALLEL_LEVEL $ENV{CTEST_PARALLEL_LEVEL} RETURN_VALUE TEST_RESULT) if(NOT ${TEST_RESULT} EQUAL 0) message(FATAL_ERROR "Tests failed") endif() +endsection() if(DEFINED CTEST_COVERAGE_COMMAND) + section("Coverage") find_program(GCOVR NAMES gcovr) if(EXISTS ${GCOVR}) execute_process(COMMAND @@ -173,8 +192,11 @@ if(DEFINED CTEST_COVERAGE_COMMAND) endif() endif() ctest_coverage() + endsection() endif() if(DEFINED CTEST_MEMORYCHECK_COMMAND) + section("Memcheck") ctest_memcheck() + endsection() endif() From afb8a9000a3c5d3e3b9d4ba9bd882f835019979c Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 29 Aug 2023 17:21:42 +0200 Subject: [PATCH 235/442] [Tests] Add post-install.sh test script --- tests/post-install.sh | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100755 tests/post-install.sh diff --git a/tests/post-install.sh b/tests/post-install.sh new file mode 100755 index 00000000000..1215bcf79d0 --- /dev/null +++ b/tests/post-install.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +# This script is meant as a basic test to be run post-installation +# to catch problems such as bad RPATHs, Python bindings not able to +# find XrdCl libraries post-installation, etc. Setting variables as +# LD_LIBRARY_PATH, PYTHONPATH, etc, may "fix" a problem caught by +# this script, but most likely the real fix would be to set correct +# relative RPATHs such that everything works without any extra steps +# on the part of the user. PYTHONPATH may be exceptionally set when +# the Python bindings are intentionally installed into a custom path. + +set -e + +: "${XRDCP:=$(command -v xrdcp)}" +: "${XRDFS:=$(command -v xrdfs)}" +: "${PYTHON:=$(command -v python3 || command -v python)}" + +for PROG in ${XRDCP} ${XRDFS} ${PYTHON}; do + if [[ ! -x ${PROG} ]]; then + echo 1>&2 "$(basename "$0"): error: '${PROG}': command not found" + exit 1 + fi +done + +V=$(xrdcp --version 2>&1) +echo "Using ${XRDCP} (${V#v})" +echo "Using ${PYTHON} ($(${PYTHON} --version))" +${PYTHON} -m pip show xrootd +${PYTHON} -c 'import XRootD; print(XRootD)' +${PYTHON} -c 'import pyxrootd; print(pyxrootd)' +${PYTHON} -c 'from XRootD import client; print(client)' +${PYTHON} -c 'from XRootD import client; print(client.FileSystem("root://localhost"))' From 0231985027108c01add328a9b2b19ba918d6f4b7 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 15 Sep 2023 14:00:39 +0200 Subject: [PATCH 236/442] XRootD 5.6.2 --- docs/ReleaseNotes.txt | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/docs/ReleaseNotes.txt b/docs/ReleaseNotes.txt index c048173bae2..ea39a26f149 100644 --- a/docs/ReleaseNotes.txt +++ b/docs/ReleaseNotes.txt @@ -10,12 +10,36 @@ Version 5.6.2 ------------- + **Major bug fixes** - -+ **Minor bug fixes** + **[XrdHttp]** Fix chunked PUT creating empty files (issue #2058) + ++ **Minor bug fixes** + **[CMake]** Update Findlibuuid.cmake to use correct include paths + **[Python]** Fix inclusion of markdown README file in documentation (#2057) + **[Server]** Align code with actual documentation for auth idspec (issue #2061) + **[XrdCl]** Fix flag check for append in XrdClZipArchive + **[XrdCl]** Fix promotion of root:// URLs to use TLS encryption (issue #2078) + **[XrdHttp]** Correct chunked response for GET with a byte range (issue #2076) + **[XrdHttp]** Refactor read issuing during GET and fix read vector too long (issue #1976) + **[XrdSciTokens]** Fix logic error in user mapping (issue #2056) + **[XrdSciTokens]** Update maximum header size and line length in INI files (issue #2074) + **[XrdSecgsi]** Fix crash of xrdgsitest when proxy is not already set + **[XrdSecztn]** Fix template for default ZTN token location (issue #2080) + **[XrdTls]** Change the thread-id returned to openssl 1.0 to improve performance (issue #2084) + **[XrdTls]** Insert CRLs containing critical extensions at the end of the bundle (issue #2065) + **Miscellaneous** - **[Server]** Default ffdest as per current pmark specification. - **[Server]** Correct code that tried to fix auth id spec on 11/2020. + **[CMake]** Always compile XrdOssCsi (compiled only with GCC before) + **[CMake]** Hide build output for isa-l to not confuse CTest + **[CMake]** Run tests in parallel and fail build when tests fail + **[Python]** Allow build customization via environment variable (issue #2062) + **[Python]** Check for Development.Module with CMake 3.18 and above + **[Server]** Add initialiser in one of the XrdScheduler constructors (#2081) + **[Server]** Default ffdest as per current pmark specification + **[Server]** Export readv comma separated limits via XRD_READV_LIMITS envar + **[Server]** Implement Linux epoll maxfd limit (#2063) + **[XrdClHttp] Add pgWrite support to the HTTP client plugin + **[XrdHttp]** Refactor request statemachine for HTTP GET requests (#2072) + **[XrdTls]** Refactor CASet and CRLSet to open the output file only once before the processing ------------- Version 5.6.1 From d07e0a21287841335b917e24b273c6b10b184c91 Mon Sep 17 00:00:00 2001 From: Mattias Ellert Date: Sat, 16 Sep 2023 13:19:23 +0200 Subject: [PATCH 237/442] Fix spelling errors reported by lintian --- src/XrdApps/XrdClRecordPlugin/README.md | 8 ++++---- src/XrdApps/XrdClRecordPlugin/XrdClActionMetrics.hh | 2 +- src/XrdApps/XrdClRecordPlugin/XrdClReplay.cc | 8 ++++---- src/XrdApps/XrdClRecordPlugin/XrdClReplayArgs.hh | 4 ++-- src/XrdFfs/XrdFfsPosix.cc | 2 +- src/XrdOuc/XrdOucProg.cc | 4 ++-- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/XrdApps/XrdClRecordPlugin/README.md b/src/XrdApps/XrdClRecordPlugin/README.md index 8ba35867fa0..5e878a5c0a3 100644 --- a/src/XrdApps/XrdClRecordPlugin/README.md +++ b/src/XrdApps/XrdClRecordPlugin/README.md @@ -32,7 +32,7 @@ _________________ The **xrdreplay** application provides the following operation modes: * print mode (-p) : display runtime and IO statistics for a record file -* verify mode (-v) : verify the existance of the required input files for a record file +* verify mode (-v) : verify the existence of the required input files for a record file * creation mode (-c,-t) : create the required input data using file creation and write the minimal required size (-c) or truncate files to the minimal required size (-t) * playback mode (default) : replay a given record file @@ -113,7 +113,7 @@ xrdreplay -v recording.cvs # size: 536.87 MB [ 0 B out of 536.87 MB ] ( 0.00% ) # ---> info: file exists and has sufficient size ``` -On success the shell returns 0, if there was a missing, too small or inaccesible file it returns -5 (251). +On success the shell returns 0, if there was a missing, too small or inaccessible file it returns -5 (251). ```bash Warning: xrdreplay considers a file only as an input file if it has no bytes written. @@ -250,12 +250,12 @@ usage: xrdreplay [-p|--print] [-c|--create-data] [t|--truncate-data] [-l|--long] -p | --print : print only mode - shows all the IO for the given replay file without actually running any IO -s | --summary : print summary - shows all the aggregated IO counter summed for all files -l | --long : print long - show all file IO counter for each individual file - -v | --verify : verify the existance of all input files + -v | --verify : verify the existence of all input files -x | --speed : change playback speed by factor [ > 0.0 ] -r | --replace := : replace in the argument list the string with - option is usable several times e.g. to change storage prefixes or filenames - [recordfilename] : if a file is given, it will be used as record input otherwhise STDIN is used to read records! + [recordfilename] : if a file is given, it will be used as record input otherwise STDIN is used to read records! example: ... --replace file:://localhost:=root://xrootd.eu/ : redirect local file to remote ``` diff --git a/src/XrdApps/XrdClRecordPlugin/XrdClActionMetrics.hh b/src/XrdApps/XrdClRecordPlugin/XrdClActionMetrics.hh index e5802b771db..6d7351257c8 100644 --- a/src/XrdApps/XrdClRecordPlugin/XrdClActionMetrics.hh +++ b/src/XrdApps/XrdClRecordPlugin/XrdClActionMetrics.hh @@ -65,7 +65,7 @@ struct ActionMetrics std::string cnt = i + "::n"; // IOPS std::string vol = i + "::b"; // number of bytes - std::string err = i + "::e"; // number of unsuccessfull IOs + std::string err = i + "::e"; // number of unsuccessful IOs std::string off = i + "::o"; // maximum offset seen ios[cnt] = 0; diff --git a/src/XrdApps/XrdClRecordPlugin/XrdClReplay.cc b/src/XrdApps/XrdClRecordPlugin/XrdClReplay.cc index b5abbd7b386..4e884dcb47d 100644 --- a/src/XrdApps/XrdClRecordPlugin/XrdClReplay.cc +++ b/src/XrdApps/XrdClRecordPlugin/XrdClReplay.cc @@ -247,7 +247,7 @@ bool AssureFile(const std::string& url, uint64_t size, bool viatruncate, bool ve if (verify) { - std::cerr << "Verify: file is missing or inaccesible: " << url << std::endl; + std::cerr << "Verify: file is missing or inaccessible: " << url << std::endl; return false; } @@ -1079,7 +1079,7 @@ void usage() << " -f | --suppress : force to run all IO with all successful result status - suppress all others" << std::endl; std::cerr - << " - by default the player won't run with an unsuccessfull recorded IO" + << " - by default the player won't run with an unsuccessfully recorded IO" << std::endl; std::cerr << std::endl; std::cerr @@ -1145,10 +1145,10 @@ int main(int argc, char** argv) if (sampling_error) { - std::cerr << "Warning: IO file contains unsuccessfull samples!" << std::endl; + std::cerr << "Warning: IO file contains unsuccessful samples!" << std::endl; if (!opt.suppress_error()) { - std::cerr << "... run with [-f] or [--suppress] option to suppress unsuccessfull IO events!" + std::cerr << "... run with [-f] or [--suppress] option to suppress unsuccessful IO events!" << std::endl; exit(-1); } diff --git a/src/XrdApps/XrdClRecordPlugin/XrdClReplayArgs.hh b/src/XrdApps/XrdClRecordPlugin/XrdClReplayArgs.hh index 04edb34fbe6..898386b93ee 100644 --- a/src/XrdApps/XrdClRecordPlugin/XrdClReplayArgs.hh +++ b/src/XrdApps/XrdClRecordPlugin/XrdClReplayArgs.hh @@ -163,7 +163,7 @@ class ReplayArgs std::cerr << " -l | --long : print long - show all file IO counter for each individual file" << std::endl; - std::cerr << " -v | --verify : verify the existance of all input files" + std::cerr << " -v | --verify : verify the existence of all input files" << std::endl; std::cerr << " -x | --speed : change playback speed by factor [ > 0.0 ]" @@ -176,7 +176,7 @@ class ReplayArgs << std::endl; std::cerr << std::endl; std::cerr - << " [recordfilename] : if a file is given, it will be used as record input otherwhise STDIN is used to read records!" + << " [recordfilename] : if a file is given, it will be used as record input otherwise STDIN is used to read records!" << std::endl; std::cerr << "example: ... --replace file:://localhost:=root://xrootd.eu/ : redirect local file to remote" diff --git a/src/XrdFfs/XrdFfsPosix.cc b/src/XrdFfs/XrdFfsPosix.cc index b6615db3693..20973446766 100644 --- a/src/XrdFfs/XrdFfsPosix.cc +++ b/src/XrdFfs/XrdFfsPosix.cc @@ -439,7 +439,7 @@ struct XrdFfsPosixX_readdirall_args { NULL in this case. Do we need some protection here? We are not in trouble so far - because FUSE's _getattr will test the existance of the dir + because FUSE's _getattr will test the existence of the dir so we know that at least one data server has the directory. */ void* XrdFfsPosix_x_readdirall(void* x) diff --git a/src/XrdOuc/XrdOucProg.cc b/src/XrdOuc/XrdOucProg.cc index 9a258d80457..39a552932ff 100644 --- a/src/XrdOuc/XrdOucProg.cc +++ b/src/XrdOuc/XrdOucProg.cc @@ -313,7 +313,7 @@ int XrdOucProg::Setup(const char *prog, XrdSysError *errP, if (rc <= 0) {if (errP) {if (!rc || !argV[0]) - {const char *pgm = (Proc ? "proceedure" : "program"); + {const char *pgm = (Proc ? "procedure" : "program"); errP->Emsg("Run", pgm, "name not specified."); } else errP->Emsg("Run", rc, "set up", argV[0]); } @@ -321,7 +321,7 @@ int XrdOucProg::Setup(const char *prog, XrdSysError *errP, } // Record the arguments including the phamtom null pointer. We must have -// atleast one, the program or proceedure name. +// atleast one, the program or procedure name. // numArgs = rc; Arg = new char*[rc+1]; From d5f600834da7fbbb47477a7267609eefc74f8033 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Mon, 18 Sep 2023 00:39:29 -0700 Subject: [PATCH 238/442] [Server] Fix incorrect patch for authfile that made 5.6.2 fail. Fixes: #2088, 478ad4b4. --- src/XrdAcc/XrdAccAuthFile.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdAcc/XrdAccAuthFile.cc b/src/XrdAcc/XrdAccAuthFile.cc index 1f4c45477b6..8b738baa733 100644 --- a/src/XrdAcc/XrdAccAuthFile.cc +++ b/src/XrdAcc/XrdAccAuthFile.cc @@ -161,7 +161,7 @@ char XrdAccAuthFile::getID(char **id) // two character specification but only validate the first to be backward // compatible. // - if (strlen(pp) <= 2 || !index("ghoru", *pp)) + if (strlen(pp) > 2 || !index("ghoru", *pp)) {Eroute->Emsg("AuthFile", "Invalid ID sprecifier -", pp); flags = (DBflags)(flags | dbError); return 0; From 5fbcf7a2ce4869132c65e8a22011314e605e6f82 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 18 Sep 2023 16:27:20 +0200 Subject: [PATCH 239/442] Revert "[Python] Use PEP517 by default when building Python bindings" This reverts commit 790db260b414687aa5281d75c4390555203c961e. Not supported on CentOS 7. --- bindings/python/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/python/CMakeLists.txt b/bindings/python/CMakeLists.txt index 032a7337c90..d872fc67c5f 100644 --- a/bindings/python/CMakeLists.txt +++ b/bindings/python/CMakeLists.txt @@ -19,7 +19,7 @@ else() option(INSTALL_PYTHON_BINDINGS "Install Python bindings" TRUE) if(INSTALL_PYTHON_BINDINGS) - set(PIP_OPTIONS "--use-pep517" CACHE STRING "Install options for pip") + set(PIP_OPTIONS "" CACHE STRING "Install options for pip") install(CODE " execute_process(COMMAND ${Python_EXECUTABLE} -m pip install ${PIP_OPTIONS} From 0321bc5bf793122afe5590577e3836eb2bcb51ea Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 18 Sep 2023 10:40:59 +0200 Subject: [PATCH 240/442] [CI] Fix GitLab release workflow on Alma 9 --- .gitlab-ci.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e23c6d4a02d..6440ea7f6cc 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -331,18 +331,17 @@ release:alma9-x86_64: image: almalinux:9 script: - dnf install -y epel-release - - dnf install -y rpm-build tar dnf-plugins-core git python-macros + - dnf install -y dnf-plugins-core rpmdevtools git - dnf config-manager --set-enabled crb - - dnf -y update libarchive - mkdir alma-9-x86_64 - ./gen-tarball.sh $CI_COMMIT_TAG - mv xrootd-${CI_COMMIT_TAG#"v"}.tar.gz alma-9-x86_64 - cd packaging/ - git checkout tags/${CI_COMMIT_TAG} - - ./makesrpm.sh --define "_with_python3 1" --define "_with_tests 1" --define "_without_xrootd_user 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" --define "_with_isal 1" -D "dist .el8" + - ./makesrpm.sh --define "_with_python3 1" --define "_with_tests 1" --define "_without_xrootd_user 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" --define "_with_isal 1" -D "dist .el9" - dnf builddep -y *.src.rpm - mkdir RPMS - - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_with_python3 1" --define "_with_tests 1" --define "_without_xrootd_user 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" --define "_with_isal 1" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" -D "dist .el8" *.src.rpm + - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_with_python3 1" --define "_with_tests 1" --define "_without_xrootd_user 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" --define "_with_isal 1" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" -D "dist .el9" *.src.rpm - cd .. - cp packaging/RPMS/*.rpm alma-9-x86_64 - cp packaging/*src.rpm alma-9-x86_64 From 59b8b3893183b9a1919d79bbab1d9e0b42fa6092 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 18 Sep 2023 17:11:32 +0200 Subject: [PATCH 241/442] [Python] Show install command being run This is ok now that we require CMake 3.16 (COMMAND_ECHO was added in CMake 3.15). This "reverts" commit 209f6a40. --- bindings/python/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/python/CMakeLists.txt b/bindings/python/CMakeLists.txt index d872fc67c5f..c6a77f5aa0d 100644 --- a/bindings/python/CMakeLists.txt +++ b/bindings/python/CMakeLists.txt @@ -24,7 +24,7 @@ else() install(CODE " execute_process(COMMAND ${Python_EXECUTABLE} -m pip install ${PIP_OPTIONS} --prefix \$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX} ${CMAKE_CURRENT_BINARY_DIR} - RESULT_VARIABLE INSTALL_STATUS) + RESULT_VARIABLE INSTALL_STATUS COMMAND_ECHO STDOUT) if(NOT INSTALL_STATUS EQUAL 0) message(FATAL_ERROR \"Failed to install Python bindings\") endif() From 8f343946a8a74344c8528e71c0fa16232d0ae959 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 18 Sep 2023 17:27:00 +0200 Subject: [PATCH 242/442] Revert "The include statement in rst includes the included file verbatim and" This reverts commit 0ff51f08a9de24f10b1c0a353c5ce3d8626657ec. Requires extra dependencies to work, not available in some distributions. --- bindings/python/docs/source/conf.py | 10 ---------- bindings/python/docs/source/install.rst | 2 +- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/bindings/python/docs/source/conf.py b/bindings/python/docs/source/conf.py index 756b1b474e1..1798e88aafa 100644 --- a/bindings/python/docs/source/conf.py +++ b/bindings/python/docs/source/conf.py @@ -29,16 +29,6 @@ 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.viewcode'] -try: - import sphinx_mdinclude - extensions.extend(['sphinx_mdinclude']) -except ImportError: - try: - import m2r - extensions.extend(['m2r']) - except ImportError: - pass - # Add any paths that contain templates here, relative to this directory. templates_path = ['.templates'] diff --git a/bindings/python/docs/source/install.rst b/bindings/python/docs/source/install.rst index 7e6ff75d3f4..abcdd555839 100644 --- a/bindings/python/docs/source/install.rst +++ b/bindings/python/docs/source/install.rst @@ -2,5 +2,5 @@ **Installing** ``pyxrootd`` =========================== -.. mdinclude:: ../../README.md +.. include:: ../../README.md :start-line: 8 From 196ce937d696a7618eca9d03c3a2063db09c98d1 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 18 Sep 2023 17:47:15 +0200 Subject: [PATCH 243/442] [Python] Convert pyxrootd installation instructions to rst This avoids extra dependencies on sphinx_mdinclude, which is not always available, by converting the markdown instructions into restructured text format. --- bindings/python/README.md | 230 ------------------------ bindings/python/docs/source/install.rst | 228 ++++++++++++++++++++++- 2 files changed, 223 insertions(+), 235 deletions(-) diff --git a/bindings/python/README.md b/bindings/python/README.md index 17c08f144b1..e46add4581c 100644 --- a/bindings/python/README.md +++ b/bindings/python/README.md @@ -4,233 +4,3 @@ This is a set of simple but pythonic bindings for XRootD. It is designed to make it easy to interface with the XRootD client, by writing Python instead of having to write C++. -## Installation - -For general instructions on how to use `pip` to install Python packages, please -take a look at https://packaging.python.org/en/latest/tutorials/installing-packages/. -The installation of XRootD and its Python bindings follows for the most part the -same procedure. However, there are some important things that are specific to -XRootD, which we discuss here. Since XRootD 5.6, it is possible to use `pip` to -install only the Python bindings, building it against a pre-installed version of -XRootD. In this case, we recommend using the same version of XRootD for both -parts, even if the newer Python bindings should be usable with older versions of -XRootD 5.x. Suppose that XRootD is installed already into `/usr`. Then, one can -build and install the Python bindings as shown below. - -```sh -xrootd $ cd bindings/python -python $ python3 -m pip install --target install/ . -Processing xrootd/bindings/python - Installing build dependencies ... done - Getting requirements to build wheel ... done - Installing backend dependencies ... done - Preparing metadata (pyproject.toml) ... done -Building wheels for collected packages: xrootd - Building wheel for xrootd (pyproject.toml) ... done - Created wheel for xrootd: filename=xrootd-5.6-cp311-cp311-linux_x86_64.whl size=203460 sha256=8bbd9168... - Stored in directory: /tmp/pip-ephem-wheel-cache-rc_kb_nx/wheels/af/1b/42/bb953908... -Successfully built xrootd -Installing collected packages: xrootd -``` - -The command above installs the Python bindings into the `install/` directory in -the current working directory. The structure is as shown below - -```sh -install/ -|-- XRootD -| |-- __init__.py -| |-- __pycache__ -| | |-- __init__.cpython-311.pyc -| |-- client -| |-- __init__.py -| |-- __pycache__ -| | |-- __init__.cpython-311.pyc -| | |-- _version.cpython-311.pyc -| | |-- copyprocess.cpython-311.pyc -| | |-- env.cpython-311.pyc -| | |-- file.cpython-311.pyc -| | |-- filesystem.cpython-311.pyc -| | |-- finalize.cpython-311.pyc -| | |-- flags.cpython-311.pyc -| | |-- glob_funcs.cpython-311.pyc -| | |-- responses.cpython-311.pyc -| | |-- url.cpython-311.pyc -| | |-- utils.cpython-311.pyc -| |-- _version.py -| |-- copyprocess.py -| |-- env.py -| |-- file.py -| |-- filesystem.py -| |-- finalize.py -| |-- flags.py -| |-- glob_funcs.py -| |-- responses.py -| |-- url.py -| |-- utils.py -|-- pyxrootd -| |-- __init__.py -| |-- __pycache__ -| | |-- __init__.cpython-311.pyc -| |-- client.cpython-311-x86_64-linux-gnu.so -|-- xrootd-5.6.dist-info - |-- INSTALLER - |-- METADATA - |-- RECORD - |-- REQUESTED - |-- WHEEL - |-- direct_url.json - |-- top_level.txt - -8 directories, 36 files -``` - -If you would like to install it for your own user, then use `pip install --user` -instead of `--target`. - -If XRootD is not already installed into the system, then you will want to -install both the client libraries and the Python bindings together using `pip`. -This is possible by using the `setup.py` at the top level of the project, rather -than the one in the `bindings/python` subdirectory. - -```sh -xrootd $ python3 -m pip install --target install/ . -Processing xrootd - Installing build dependencies ... done - Getting requirements to build wheel ... done - Installing backend dependencies ... done - Preparing metadata (pyproject.toml) ... done -Building wheels for collected packages: xrootd - Building wheel for xrootd (pyproject.toml) ... done - Created wheel for xrootd: filename=xrootd-5.6-cp311-cp311-linux_x86_64.whl size=65315683 sha256=a2e7ff52... - Stored in directory: /tmp/pip-ephem-wheel-cache-9g6ovy4q/wheels/47/93/fc/a23666d3... -Successfully built xrootd -Installing collected packages: xrootd -Successfully installed xrootd-5.6 -``` - -In this case, the structure is a bit different than before: - -```sh -xrootd $ tree install/ -install/ -|-- XRootD -| |-- __init__.py -| |-- __pycache__ -| | |-- __init__.cpython-311.pyc -| |-- client -| |-- __init__.py -| |-- __pycache__ -| | |-- __init__.cpython-311.pyc -| | |-- _version.cpython-311.pyc -| | |-- copyprocess.cpython-311.pyc -| | |-- env.cpython-311.pyc -| | |-- file.cpython-311.pyc -| | |-- filesystem.cpython-311.pyc -| | |-- finalize.cpython-311.pyc -| | |-- flags.cpython-311.pyc -| | |-- glob_funcs.cpython-311.pyc -| | |-- responses.cpython-311.pyc -| | |-- url.cpython-311.pyc -| | |-- utils.cpython-311.pyc -| |-- _version.py -| |-- copyprocess.py -| |-- env.py -| |-- file.py -| |-- filesystem.py -| |-- finalize.py -| |-- flags.py -| |-- glob_funcs.py -| |-- responses.py -| |-- url.py -| |-- utils.py -|-- pyxrootd -| |-- __init__.py -| |-- __pycache__ -| | |-- __init__.cpython-311.pyc -| |-- client.cpython-311-x86_64-linux-gnu.so -| |-- libXrdAppUtils.so -| |-- libXrdAppUtils.so.2 -| |-- libXrdAppUtils.so.2.0.0 -| |-- libXrdCl.so -| |-- libXrdCl.so.3 -| |-- libXrdCl.so.3.0.0 -| |-- libXrdClHttp-5.so -| |-- libXrdClProxyPlugin-5.so -| |-- libXrdClRecorder-5.so -| |-- libXrdCrypto.so -| |-- libXrdCrypto.so.2 -| |-- libXrdCrypto.so.2.0.0 -| |-- libXrdCryptoLite.so -| |-- libXrdCryptoLite.so.2 -| |-- libXrdCryptoLite.so.2.0.0 -| |-- libXrdCryptossl-5.so -| |-- libXrdPosix.so -| |-- libXrdPosix.so.3 -| |-- libXrdPosix.so.3.0.0 -| |-- libXrdPosixPreload.so -| |-- libXrdPosixPreload.so.2 -| |-- libXrdPosixPreload.so.2.0.0 -| |-- libXrdSec-5.so -| |-- libXrdSecProt-5.so -| |-- libXrdSecgsi-5.so -| |-- libXrdSecgsiAUTHZVO-5.so -| |-- libXrdSecgsiGMAPDN-5.so -| |-- libXrdSeckrb5-5.so -| |-- libXrdSecpwd-5.so -| |-- libXrdSecsss-5.so -| |-- libXrdSecunix-5.so -| |-- libXrdSecztn-5.so -| |-- libXrdUtils.so -| |-- libXrdUtils.so.3 -| |-- libXrdUtils.so.3.0.0 -| |-- libXrdXml.so -| |-- libXrdXml.so.3 -| |-- libXrdXml.so.3.0.0 -|-- xrootd-5.6.dist-info - |-- COPYING - |-- COPYING.BSD - |-- COPYING.LGPL - |-- INSTALLER - |-- LICENSE - |-- METADATA - |-- RECORD - |-- REQUESTED - |-- WHEEL - |-- direct_url.json - |-- top_level.txt - -8 directories, 78 files -``` - -As can be seen above, now all client libraries have been installed alongside the -C++ Python bindings library (`client.cpython-311-x86_64-linux-gnu.so`). When -installing via `pip` by simply calling `pip install xrootd`, the package that -gets installed is in this mode which includes the libraries. However, command -line tools are not included. - -Binary wheels are supported as well. They can be built using the `wheel` -subcommand instead of `install`: - -```sh -xrootd $ python3.12 -m pip wheel . -Processing xrootd - Installing build dependencies ... done - Getting requirements to build wheel ... done - Installing backend dependencies ... done - Preparing metadata (pyproject.toml) ... done -Building wheels for collected packages: xrootd - Building wheel for xrootd (pyproject.toml) ... done - Created wheel for xrootd: filename=xrootd-5.6-cp312-cp312-linux_x86_64.whl size=65318541 sha256=6c4ed389... - Stored in directory: /tmp/pip-ephem-wheel-cache-etujwyx1/wheels/cf/67/3c/514b21dd... -Successfully built xrootd -``` - -If you want to have everything installed, that is, server, client, command line -tools, etc, then it is recommended to use CMake to build the project, and use -the options `-DENABLE_PYTHON=ON -DINSTALL_PYTHON_BINDINGS=ON` so that CMake -takes care of calling `pip` to install the Python bindings compiled together -with the other components in the end. The option `-DPIP_OPTIONS` can be used to -pass on options to pip, but it should never be used to change the installation -prefix, as that is handled by CMake. Please see [INSTALL.md](../../docs/INSTALL.md) -for full instructions on how to build XRootD from source using CMake. diff --git a/bindings/python/docs/source/install.rst b/bindings/python/docs/source/install.rst index abcdd555839..54c5bab7101 100644 --- a/bindings/python/docs/source/install.rst +++ b/bindings/python/docs/source/install.rst @@ -1,6 +1,224 @@ -=========================== -**Installing** ``pyxrootd`` -=========================== +================================= +Installing XRootD Python Bindings +================================= -.. include:: ../../README.md - :start-line: 8 +For general instructions on how to use ``pip`` to install Python packages, please +take a look at https://packaging.python.org/en/latest/tutorials/installing-packages/. +The installation of XRootD and its Python bindings follows for the most part the +same procedure. However, there are some important things that are specific to +XRootD, which we discuss here. Since XRootD 5.6, it is possible to use ``pip`` to +install only the Python bindings, building it against a pre-installed version of +XRootD. In this case, we recommend using the same version of XRootD for both +parts, even if the newer Python bindings should be usable with older versions of +XRootD 5.x. Suppose that XRootD is installed already into ``/usr``. Then, one can +build and install the Python bindings as shown below:: + + xrootd $ cd bindings/python + python $ python3 -m pip install --target install/ . + Processing xrootd/bindings/python + Installing build dependencies ... done + Getting requirements to build wheel ... done + Installing backend dependencies ... done + Preparing metadata (pyproject.toml) ... done + Building wheels for collected packages: xrootd + Building wheel for xrootd (pyproject.toml) ... done + Created wheel for xrootd: filename=xrootd-5.6-cp311-cp311-linux_x86_64.whl size=203460 sha256=8bbd9168... + Stored in directory: /tmp/pip-ephem-wheel-cache-rc_kb_nx/wheels/af/1b/42/bb953908... + Successfully built xrootd + Installing collected packages: xrootd + +The command above installs the Python bindings into the ``install/`` directory in +the current working directory. The structure is as shown below:: + + install/ + |-- XRootD + | |-- __init__.py + | |-- __pycache__ + | | |-- __init__.cpython-311.pyc + | |-- client + | |-- __init__.py + | |-- __pycache__ + | | |-- __init__.cpython-311.pyc + | | |-- _version.cpython-311.pyc + | | |-- copyprocess.cpython-311.pyc + | | |-- env.cpython-311.pyc + | | |-- file.cpython-311.pyc + | | |-- filesystem.cpython-311.pyc + | | |-- finalize.cpython-311.pyc + | | |-- flags.cpython-311.pyc + | | |-- glob_funcs.cpython-311.pyc + | | |-- responses.cpython-311.pyc + | | |-- url.cpython-311.pyc + | | |-- utils.cpython-311.pyc + | |-- _version.py + | |-- copyprocess.py + | |-- env.py + | |-- file.py + | |-- filesystem.py + | |-- finalize.py + | |-- flags.py + | |-- glob_funcs.py + | |-- responses.py + | |-- url.py + | |-- utils.py + |-- pyxrootd + | |-- __init__.py + | |-- __pycache__ + | | |-- __init__.cpython-311.pyc + | |-- client.cpython-311-x86_64-linux-gnu.so + |-- xrootd-5.6.dist-info + |-- INSTALLER + |-- METADATA + |-- RECORD + |-- REQUESTED + |-- WHEEL + |-- direct_url.json + |-- top_level.txt + + 8 directories, 36 files + +If you would like to install it for your own user, then use +``pip install --user`` instead of ``--target``. + +If XRootD is not already installed into the system, then you will want to +install both the client libraries and the Python bindings together using ``pip``. +This is possible by using the ``setup.py`` at the top level of the project, rather +than the one in the ``bindings/python`` subdirectory:: + + xrootd $ python3 -m pip install --target install/ . + Processing xrootd + Installing build dependencies ... done + Getting requirements to build wheel ... done + Installing backend dependencies ... done + Preparing metadata (pyproject.toml) ... done + Building wheels for collected packages: xrootd + Building wheel for xrootd (pyproject.toml) ... done + Created wheel for xrootd: filename=xrootd-5.6-cp311-cp311-linux_x86_64.whl size=65315683 sha256=a2e7ff52... + Stored in directory: /tmp/pip-ephem-wheel-cache-9g6ovy4q/wheels/47/93/fc/a23666d3... + Successfully built xrootd + Installing collected packages: xrootd + Successfully installed xrootd-5.6 + +In this case, the structure is a bit different than before:: + + xrootd $ tree install/ + install/ + |-- XRootD + | |-- __init__.py + | |-- __pycache__ + | | |-- __init__.cpython-311.pyc + | |-- client + | |-- __init__.py + | |-- __pycache__ + | | |-- __init__.cpython-311.pyc + | | |-- _version.cpython-311.pyc + | | |-- copyprocess.cpython-311.pyc + | | |-- env.cpython-311.pyc + | | |-- file.cpython-311.pyc + | | |-- filesystem.cpython-311.pyc + | | |-- finalize.cpython-311.pyc + | | |-- flags.cpython-311.pyc + | | |-- glob_funcs.cpython-311.pyc + | | |-- responses.cpython-311.pyc + | | |-- url.cpython-311.pyc + | | |-- utils.cpython-311.pyc + | |-- _version.py + | |-- copyprocess.py + | |-- env.py + | |-- file.py + | |-- filesystem.py + | |-- finalize.py + | |-- flags.py + | |-- glob_funcs.py + | |-- responses.py + | |-- url.py + | |-- utils.py + |-- pyxrootd + | |-- __init__.py + | |-- __pycache__ + | | |-- __init__.cpython-311.pyc + | |-- client.cpython-311-x86_64-linux-gnu.so + | |-- libXrdAppUtils.so + | |-- libXrdAppUtils.so.2 + | |-- libXrdAppUtils.so.2.0.0 + | |-- libXrdCl.so + | |-- libXrdCl.so.3 + | |-- libXrdCl.so.3.0.0 + | |-- libXrdClHttp-5.so + | |-- libXrdClProxyPlugin-5.so + | |-- libXrdClRecorder-5.so + | |-- libXrdCrypto.so + | |-- libXrdCrypto.so.2 + | |-- libXrdCrypto.so.2.0.0 + | |-- libXrdCryptoLite.so + | |-- libXrdCryptoLite.so.2 + | |-- libXrdCryptoLite.so.2.0.0 + | |-- libXrdCryptossl-5.so + | |-- libXrdPosix.so + | |-- libXrdPosix.so.3 + | |-- libXrdPosix.so.3.0.0 + | |-- libXrdPosixPreload.so + | |-- libXrdPosixPreload.so.2 + | |-- libXrdPosixPreload.so.2.0.0 + | |-- libXrdSec-5.so + | |-- libXrdSecProt-5.so + | |-- libXrdSecgsi-5.so + | |-- libXrdSecgsiAUTHZVO-5.so + | |-- libXrdSecgsiGMAPDN-5.so + | |-- libXrdSeckrb5-5.so + | |-- libXrdSecpwd-5.so + | |-- libXrdSecsss-5.so + | |-- libXrdSecunix-5.so + | |-- libXrdSecztn-5.so + | |-- libXrdUtils.so + | |-- libXrdUtils.so.3 + | |-- libXrdUtils.so.3.0.0 + | |-- libXrdXml.so + | |-- libXrdXml.so.3 + | |-- libXrdXml.so.3.0.0 + |-- xrootd-5.6.dist-info + |-- COPYING + |-- COPYING.BSD + |-- COPYING.LGPL + |-- INSTALLER + |-- LICENSE + |-- METADATA + |-- RECORD + |-- REQUESTED + |-- WHEEL + |-- direct_url.json + |-- top_level.txt + + 8 directories, 78 files + +As can be seen above, now all client libraries have been installed alongside the +C++ Python bindings library (``client.cpython-311-x86_64-linux-gnu.so``). When +installing via ``pip`` by simply calling ``pip install xrootd``, the package that +gets installed is in this mode which includes the libraries. However, command +line tools are not included. + +Binary wheels are supported as well. They can be built using the ``wheel`` +subcommand instead of ``install``:: + + xrootd $ python3.12 -m pip wheel . + Processing xrootd + Installing build dependencies ... done + Getting requirements to build wheel ... done + Installing backend dependencies ... done + Preparing metadata (pyproject.toml) ... done + Building wheels for collected packages: xrootd + Building wheel for xrootd (pyproject.toml) ... done + Created wheel for xrootd: filename=xrootd-5.6-cp312-cp312-linux_x86_64.whl size=65318541 sha256=6c4ed389... + Stored in directory: /tmp/pip-ephem-wheel-cache-etujwyx1/wheels/cf/67/3c/514b21dd... + Successfully built xrootd + +If you want to have everything installed, that is, server, client, command line +tools, etc, then it is recommended to use CMake to build the project, and use +the options ``-DENABLE_PYTHON=ON -DINSTALL_PYTHON_BINDINGS=ON`` so that CMake +takes care of calling ``pip`` to install the Python bindings compiled together +with the other components in the end. The option ``-DPIP_OPTIONS`` can be used to +pass on options to pip, but it should never be used to change the installation +prefix, as that is handled by CMake. Please see INSTALL.md_ for instructions on +how to build XRootD from source using CMake. + +.. _INSTALL.md: https://github.com/xrootd/xrootd/blob/master/docs/INSTALL.md From fe268eb622e2192d54a4230cea54c41660bd5788 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 19 Sep 2023 09:01:15 +0200 Subject: [PATCH 244/442] [CMake] Move utils/ install commands into its own subdirectory Use configure_file() to expand CMake variables in xrootd-config script instead of using execute_process() with sed. Use install with PROGRAMS instead of FILES to set as executable with CMake. --- CMakeLists.txt | 2 ++ src/CMakeLists.txt | 32 -------------------------------- utils/CMakeLists.txt | 13 +++++++++++++ utils/xrootd-config | 10 +++++----- 4 files changed, 20 insertions(+), 37 deletions(-) create mode 100644 utils/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 01f5ca9d5a4..ebf87d93d26 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,6 +49,8 @@ if( BUILD_TESTS ) add_subdirectory( tests ) endif() +add_subdirectory(utils) + include( XRootDSummary ) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d95715fed98..dda0692aa01 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -80,39 +80,7 @@ if( NOT XRDCL_ONLY ) endif() -#------------------------------------------------------------------------------- -# Install the utility scripts -#------------------------------------------------------------------------------- -if( NOT XRDCL_ONLY ) -install( - FILES - ${CMAKE_SOURCE_DIR}/utils/XrdCmsNotify.pm - ${CMAKE_SOURCE_DIR}/utils/netchk - ${CMAKE_SOURCE_DIR}/utils/XrdOlbMonPerf - ${CMAKE_SOURCE_DIR}/utils/cms_monPerf - DESTINATION ${CMAKE_INSTALL_DATADIR}/xrootd/utils - PERMISSIONS - OWNER_EXECUTE OWNER_WRITE OWNER_READ - GROUP_EXECUTE GROUP_READ - WORLD_EXECUTE WORLD_READ ) -endif() - if( NOT XRDCL_LIB_ONLY ) -#------------------------------------------------------------------------------- -# Install xrootd-config -#------------------------------------------------------------------------------- -install( - CODE " - EXECUTE_PROCESS( - COMMAND cat ${CMAKE_SOURCE_DIR}/utils/xrootd-config - COMMAND sed -e \"s/__VERSION__/${XRootD_VERSION}/\" - COMMAND sed -e \"s|__INCLUDEDIR__|${CMAKE_INSTALL_INCLUDEDIR}|\" - COMMAND sed -e \"s/__PLUGIN_VERSION__/${PLUGIN_VERSION}/\" - COMMAND sed -e \"s|__PREFIX__|${CMAKE_INSTALL_PREFIX}|\" - OUTPUT_FILE \$ENV{DESTDIR}/${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/xrootd-config ) - EXECUTE_PROCESS( - COMMAND chmod 755 \$ENV{DESTDIR}/${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/xrootd-config )" -) #------------------------------------------------------------------------------- # Post process man pages diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt new file mode 100644 index 00000000000..e18493cd931 --- /dev/null +++ b/utils/CMakeLists.txt @@ -0,0 +1,13 @@ +if( NOT XRDCL_LIB_ONLY ) + configure_file(xrootd-config xrootd-config @ONLY) + install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/xrootd-config DESTINATION ${CMAKE_INSTALL_BINDIR}) +endif() + +if( NOT XRDCL_ONLY ) + install(PROGRAMS + ${CMAKE_SOURCE_DIR}/utils/XrdCmsNotify.pm + ${CMAKE_SOURCE_DIR}/utils/netchk + ${CMAKE_SOURCE_DIR}/utils/XrdOlbMonPerf + ${CMAKE_SOURCE_DIR}/utils/cms_monPerf + DESTINATION ${CMAKE_INSTALL_DATADIR}/xrootd/utils) +endif() diff --git a/utils/xrootd-config b/utils/xrootd-config index 685fdc8d3ce..2f00e163191 100755 --- a/utils/xrootd-config +++ b/utils/xrootd-config @@ -23,10 +23,10 @@ # or submit itself to any jurisdiction. #------------------------------------------------------------------------------- -version=__VERSION__ -prefix=__PREFIX__ -includedir=${prefix}/__INCLUDEDIR__/xrootd -plugin_version=__PLUGIN_VERSION__ +version=@XRootD_VERSION_STRING@ +prefix=@CMAKE_INSTALL_PREFIX@ +includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@/xrootd +plugin_version=@PLUGIN_VERSION@ usage() { @@ -58,7 +58,7 @@ while test $# -gt 0; do echo $prefix ;; --version) - echo $version + echo ${version#v} ;; --cflags) if test "$includedir" != "/usr/include" ; then From 3744b4a2a3998492fbe2b501a74c786cef7f5838 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 19 Sep 2023 09:58:11 +0200 Subject: [PATCH 245/442] [CMake] Move man page install commands into docs/man subdirectory This also fixes the substitution of the project version into the man pages. The post-install script was looping over all man pages in /usr/share/man{1,8} trying to replace __VERSION__ with the actual project version. Now we use configure_file() to replace it. --- CMakeLists.txt | 1 + docs/CMakeLists.txt | 3 +++ docs/man/CMakeLists.txt | 58 ++++++++++++++++++++++++++++++++++++++++ docs/man/cmsd.8 | 2 +- docs/man/frm_admin.8 | 2 +- docs/man/frm_purged.8 | 2 +- docs/man/frm_xfragent.8 | 2 +- docs/man/frm_xfrd.8 | 2 +- docs/man/libXrdVoms.1 | 2 +- docs/man/mpxstats.8 | 2 +- docs/man/xrdadler32.1 | 2 +- docs/man/xrdcp.1 | 2 +- docs/man/xrdfs.1 | 2 +- docs/man/xrdgsiproxy.1 | 2 +- docs/man/xrdgsitest.1 | 2 +- docs/man/xrdmapc.1 | 2 +- docs/man/xrdpfc_print.8 | 2 +- docs/man/xrdpwdadmin.8 | 2 +- docs/man/xrdsssadmin.8 | 2 +- docs/man/xrootd.8 | 2 +- docs/man/xrootdfs.1 | 2 +- src/CMakeLists.txt | 21 --------------- src/XrdApps.cmake | 10 ------- src/XrdCl/CMakeLists.txt | 29 -------------------- src/XrdDaemons.cmake | 6 ----- src/XrdFfs.cmake | 5 ---- src/XrdFrm.cmake | 8 ------ src/XrdPfc.cmake | 6 ----- src/XrdSec.cmake | 6 ----- src/XrdSecgsi.cmake | 7 ----- src/XrdVoms.cmake | 11 -------- 31 files changed, 80 insertions(+), 127 deletions(-) create mode 100644 docs/CMakeLists.txt create mode 100644 docs/man/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index ebf87d93d26..7d10f7aee9d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,6 +49,7 @@ if( BUILD_TESTS ) add_subdirectory( tests ) endif() +add_subdirectory(docs) add_subdirectory(utils) include( XRootDSummary ) diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt new file mode 100644 index 00000000000..d0ce545a9b8 --- /dev/null +++ b/docs/CMakeLists.txt @@ -0,0 +1,3 @@ + +add_subdirectory(man) + diff --git a/docs/man/CMakeLists.txt b/docs/man/CMakeLists.txt new file mode 100644 index 00000000000..bacd32217f2 --- /dev/null +++ b/docs/man/CMakeLists.txt @@ -0,0 +1,58 @@ +if( XRDCL_LIB_ONLY ) + return() +endif() + +set(MAN1PAGES + xrdcp.1 + xrdfs.1 + xrdmapc.1 +) + +set(MAN8PAGES + cmsd.8 + frm_admin.8 + frm_purged.8 + frm_xfragent.8 + frm_xfrd.8 + mpxstats.8 + xrdpfc_print.8 + xrdpwdadmin.8 + xrdsssadmin.8 + xrootd.8 +) + +if ( BUILD_FUSE ) + list(APPEND MAN1PAGES xrootdfs.1) +endif() + +if ( NOT XRDCL_ONLY ) + list(APPEND MAN1PAGES xrdadler32.1 xrdgsiproxy.1 xrdgsitest.1) + + foreach(MAN ${MAN8PAGES}) + configure_file(${MAN} man8/${MAN} @ONLY) + endforeach() + + install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/man8 + DESTINATION ${CMAKE_INSTALL_MANDIR}) + + if( BUILD_VOMS ) + list(APPEND MAN1PAGES libXrdVoms.1) + endif() +endif() + +foreach(MAN ${MAN1PAGES}) + configure_file(${MAN} man1/${MAN} @ONLY) +endforeach() + +install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/man1 + DESTINATION ${CMAKE_INSTALL_MANDIR}) + +install(CODE + "execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink xrdcp.1 xrdcopy.1 + WORKING_DIRECTORY \$ENV{DESTDIR}/${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_MANDIR}/man1)") + +if( BUILD_VOMS ) + install(CODE + "execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink libXrdVoms.1 libXrdSecgsiVOMS.1 + WORKING_DIRECTORY \$ENV{DESTDIR}/${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_MANDIR}/man1)") +endif() diff --git a/docs/man/cmsd.8 b/docs/man/cmsd.8 index e1bc1690fac..b271c5906ab 100644 --- a/docs/man/cmsd.8 +++ b/docs/man/cmsd.8 @@ -1,4 +1,4 @@ -.TH cmsd 8 "__VERSION__" +.TH cmsd 8 "@XRootD_VERSION_STRING@" .SH NAME cmsd - Cluster Management Services daemon .SH SYNOPSIS diff --git a/docs/man/frm_admin.8 b/docs/man/frm_admin.8 index ecdf1d165df..6b83cfc6c88 100644 --- a/docs/man/frm_admin.8 +++ b/docs/man/frm_admin.8 @@ -1,4 +1,4 @@ -.TH frm_admin 8 "__VERSION__" +.TH frm_admin 8 "@XRootD_VERSION_STRING@" .SH NAME frm_admin - administer file residency parameters .SH SYNOPSIS diff --git a/docs/man/frm_purged.8 b/docs/man/frm_purged.8 index 72885c00950..e4e020bb5f4 100644 --- a/docs/man/frm_purged.8 +++ b/docs/man/frm_purged.8 @@ -1,4 +1,4 @@ -.TH frm_purged 8 "__VERSION__" +.TH frm_purged 8 "@XRootD_VERSION_STRING@" .SH NAME frm_purged - File Residency Manager purge daemon .SH SYNOPSIS diff --git a/docs/man/frm_xfragent.8 b/docs/man/frm_xfragent.8 index 54fd1062315..c9e6857b25e 100644 --- a/docs/man/frm_xfragent.8 +++ b/docs/man/frm_xfragent.8 @@ -1,4 +1,4 @@ -.TH frm_xfragent 8 "__VERSION__" +.TH frm_xfragent 8 "@XRootD_VERSION_STRING@" .SH NAME frm_xfragent - File Residency Manager Transfer agent .SH SYNOPSIS diff --git a/docs/man/frm_xfrd.8 b/docs/man/frm_xfrd.8 index f550b627589..bc53f170f44 100644 --- a/docs/man/frm_xfrd.8 +++ b/docs/man/frm_xfrd.8 @@ -1,4 +1,4 @@ -.TH frm_xfrd 8 "__VERSION__" +.TH frm_xfrd 8 "@XRootD_VERSION_STRING@" .SH NAME frm_xfrd - File Residency Manager transfer daemon .SH SYNOPSIS diff --git a/docs/man/libXrdVoms.1 b/docs/man/libXrdVoms.1 index cec36f00039..651bb6cc35e 100644 --- a/docs/man/libXrdVoms.1 +++ b/docs/man/libXrdVoms.1 @@ -1,4 +1,4 @@ -.TH libXrdVoms 1 "__VERSION__" +.TH libXrdVoms 1 "@XRootD_VERSION_STRING@" .SH NAME libXrdVoms - XRootD plug-in to extract VOMS attributes .SH SYNOPSIS diff --git a/docs/man/mpxstats.8 b/docs/man/mpxstats.8 index 722fd1412d7..edd17a545a9 100644 --- a/docs/man/mpxstats.8 +++ b/docs/man/mpxstats.8 @@ -1,4 +1,4 @@ -.TH mpxstats 8 "__VERSION__" +.TH mpxstats 8 "@XRootD_VERSION_STRING@" .SH NAME mpxstats - Multiplexing Monitor Statistics daemon .SH SYNOPSIS diff --git a/docs/man/xrdadler32.1 b/docs/man/xrdadler32.1 index 4a778a3ac10..fe31c7e88b5 100644 --- a/docs/man/xrdadler32.1 +++ b/docs/man/xrdadler32.1 @@ -1,4 +1,4 @@ -.TH xrdadler32 1 "__VERSION__" +.TH xrdadler32 1 "@XRootD_VERSION_STRING@" .SH NAME xrdadler32 - compute and display an adler32 checksum .SH SYNOPSIS diff --git a/docs/man/xrdcp.1 b/docs/man/xrdcp.1 index c0aeafa3f26..126d8ea65ce 100644 --- a/docs/man/xrdcp.1 +++ b/docs/man/xrdcp.1 @@ -1,4 +1,4 @@ -.TH xrdcopy 1 "__VERSION__" +.TH xrdcopy 1 "@XRootD_VERSION_STRING@" .SH NAME xrdcp - copy files .SH SYNOPSIS diff --git a/docs/man/xrdfs.1 b/docs/man/xrdfs.1 index a84c7e17b09..fd5ddd291c5 100644 --- a/docs/man/xrdfs.1 +++ b/docs/man/xrdfs.1 @@ -1,4 +1,4 @@ -.TH xrdfs 1 "__VERSION__" +.TH xrdfs 1 "@XRootD_VERSION_STRING@" .SH NAME xrdfs - xrootd file and directory meta-data utility .SH SYNOPSIS diff --git a/docs/man/xrdgsiproxy.1 b/docs/man/xrdgsiproxy.1 index ade5272331c..c63517a69ab 100644 --- a/docs/man/xrdgsiproxy.1 +++ b/docs/man/xrdgsiproxy.1 @@ -1,4 +1,4 @@ -.TH xrdgsiproxy 1 "__VERSION__" +.TH xrdgsiproxy 1 "@XRootD_VERSION_STRING@" .SH NAME xrdgsiproxy - generate a proxy X.509 certificate .SH SYNOPSIS diff --git a/docs/man/xrdgsitest.1 b/docs/man/xrdgsitest.1 index ee59e006238..1eca0cfbb6b 100644 --- a/docs/man/xrdgsitest.1 +++ b/docs/man/xrdgsitest.1 @@ -1,4 +1,4 @@ -.TH xrdgsitest 1 "__VERSION__" +.TH xrdgsitest 1 "@XRootD_VERSION_STRING@" .SH NAME xrdgsitest - test crypto functionality relevant for the GSI implementation .SH SYNOPSIS diff --git a/docs/man/xrdmapc.1 b/docs/man/xrdmapc.1 index 9807a487812..7ed05ff9530 100644 --- a/docs/man/xrdmapc.1 +++ b/docs/man/xrdmapc.1 @@ -1,4 +1,4 @@ -.TH xrdmapc 1 "__VERSION__" +.TH xrdmapc 1 "@XRootD_VERSION_STRING@" .SH NAME xrdmapc - query XRootD redirector (status/subscribers/paths) .SH SYNOPSIS diff --git a/docs/man/xrdpfc_print.8 b/docs/man/xrdpfc_print.8 index 948283e3cd0..e8dbf846c3a 100644 --- a/docs/man/xrdpfc_print.8 +++ b/docs/man/xrdpfc_print.8 @@ -1,4 +1,4 @@ -.TH xrdpfc_print 8 "__VERSION__" +.TH xrdpfc_print 8 "@XRootD_VERSION_STRING@" .SH NAME xrdpfc_print - print content of XRootd ProxyFileCache meta data .SH SYNOPSIS diff --git a/docs/man/xrdpwdadmin.8 b/docs/man/xrdpwdadmin.8 index aff35737ab8..2d3903e7dd8 100644 --- a/docs/man/xrdpwdadmin.8 +++ b/docs/man/xrdpwdadmin.8 @@ -1,4 +1,4 @@ -.TH xrdpwdadmin 8 "__VERSION__" +.TH xrdpwdadmin 8 "@XRootD_VERSION_STRING@" .SH NAME xrdpwdadmin - administer pwd security protocol passwords .SH SYNOPSIS diff --git a/docs/man/xrdsssadmin.8 b/docs/man/xrdsssadmin.8 index 235d4ed5636..40bbc13efa5 100644 --- a/docs/man/xrdsssadmin.8 +++ b/docs/man/xrdsssadmin.8 @@ -1,4 +1,4 @@ -.TH xrdsssadmin 8 "__VERSION__" +.TH xrdsssadmin 8 "@XRootD_VERSION_STRING@" .SH NAME xrdsssadmin - administer simple shared secret keytables .SH SYNOPSIS diff --git a/docs/man/xrootd.8 b/docs/man/xrootd.8 index 022318ae98a..d954993b70c 100644 --- a/docs/man/xrootd.8 +++ b/docs/man/xrootd.8 @@ -1,4 +1,4 @@ -.TH xrootd 8 "__VERSION__" +.TH xrootd 8 "@XRootD_VERSION_STRING@" .SH NAME xrootd - eXtended ROOT daemon .SH SYNOPSIS diff --git a/docs/man/xrootdfs.1 b/docs/man/xrootdfs.1 index 92ae7adc69d..11e87865573 100644 --- a/docs/man/xrootdfs.1 +++ b/docs/man/xrootdfs.1 @@ -1,4 +1,4 @@ -.TH xrootdfs 1 "__VERSION__" +.TH xrootdfs 1 "@XRootD_VERSION_STRING@" .SH NAME xrootdfs - xrootd FUSE file system daemon .SH SYNOPSIS diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index dda0692aa01..69add8bd96f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -79,24 +79,3 @@ if( NOT XRDCL_ONLY ) endif() endif() - -if( NOT XRDCL_LIB_ONLY ) - -#------------------------------------------------------------------------------- -# Post process man pages -#------------------------------------------------------------------------------- -install( - CODE " - FILE(GLOB MANPAGES - \"\$ENV{DESTDIR}/${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_MANDIR}/man[1,8]/*.[1,8]\" ) - FOREACH(MANPAGE \${MANPAGES}) - MESSAGE( \"-- Processing: \" \${MANPAGE} ) - EXECUTE_PROCESS( - COMMAND cat \${MANPAGE} - COMMAND sed -e \"s/__VERSION__/${XRootD_VERSION}/\" - OUTPUT_FILE \${MANPAGE}.new ) - EXECUTE_PROCESS( - COMMAND mv -f \${MANPAGE}.new \${MANPAGE} ) - ENDFOREACH()" -) -endif() diff --git a/src/XrdApps.cmake b/src/XrdApps.cmake index 01bf1b68e5b..cb6704818cc 100644 --- a/src/XrdApps.cmake +++ b/src/XrdApps.cmake @@ -221,13 +221,3 @@ install( TARGETS xrdreplay RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) - -install( - FILES - ${PROJECT_SOURCE_DIR}/docs/man/xrdadler32.1 - DESTINATION ${CMAKE_INSTALL_MANDIR}/man1 ) - -install( - FILES - ${PROJECT_SOURCE_DIR}/docs/man/mpxstats.8 - DESTINATION ${CMAKE_INSTALL_MANDIR}/man8 ) diff --git a/src/XrdCl/CMakeLists.txt b/src/XrdCl/CMakeLists.txt index 4f8826302c7..22857187c72 100644 --- a/src/XrdCl/CMakeLists.txt +++ b/src/XrdCl/CMakeLists.txt @@ -228,13 +228,6 @@ install( DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/xrootd/private/XrdCl ) if( NOT XRDCL_LIB_ONLY ) -install( - FILES - ${PROJECT_SOURCE_DIR}/docs/man/xrdfs.1 - ${PROJECT_SOURCE_DIR}/docs/man/xrdcp.1 - ${PROJECT_SOURCE_DIR}/docs/man/xrdmapc.1 - DESTINATION ${CMAKE_INSTALL_MANDIR}/man1 ) - install( CODE " EXECUTE_PROCESS( @@ -242,26 +235,4 @@ install( WORKING_DIRECTORY \$ENV{DESTDIR}/${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR} )" ) -install( - CODE " - EXECUTE_PROCESS( - COMMAND ln -sf xrdcp.1 xrdcopy.1 - WORKING_DIRECTORY \$ENV{DESTDIR}/${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_MANDIR}/man1 )" -) - -install( - CODE " - FOREACH(MANPAGE xrdfs.1 xrdcp.1 xrdmapc.1) - MESSAGE( \"-- Processing: \" \$ENV{DESTDIR}/${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_MANDIR}/man1/\${MANPAGE} ) - EXECUTE_PROCESS( - COMMAND cat \${MANPAGE} - COMMAND sed -e \"s/__VERSION__/${XRootD_VERSION}/\" - OUTPUT_FILE \${MANPAGE}.new - WORKING_DIRECTORY \$ENV{DESTDIR}/${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_MANDIR}/man1 ) - EXECUTE_PROCESS( - COMMAND mv -f \${MANPAGE}.new \${MANPAGE} - WORKING_DIRECTORY \$ENV{DESTDIR}/${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_MANDIR}/man1 ) - ENDFOREACH()" -) - endif() diff --git a/src/XrdDaemons.cmake b/src/XrdDaemons.cmake index ca6bf904a8f..0bfb7c20876 100644 --- a/src/XrdDaemons.cmake +++ b/src/XrdDaemons.cmake @@ -72,9 +72,3 @@ install( TARGETS xrootd cmsd RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ) - -install( - FILES - ${PROJECT_SOURCE_DIR}/docs/man/cmsd.8 - ${PROJECT_SOURCE_DIR}/docs/man/xrootd.8 - DESTINATION ${CMAKE_INSTALL_MANDIR}/man8 ) diff --git a/src/XrdFfs.cmake b/src/XrdFfs.cmake index 0eac1046cdb..d7ce6665850 100644 --- a/src/XrdFfs.cmake +++ b/src/XrdFfs.cmake @@ -62,9 +62,4 @@ if( BUILD_FUSE ) install( TARGETS xrootdfs RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) - - install( - FILES - ${PROJECT_SOURCE_DIR}/docs/man/xrootdfs.1 - DESTINATION ${CMAKE_INSTALL_MANDIR}/man1 ) endif() diff --git a/src/XrdFrm.cmake b/src/XrdFrm.cmake index 557ea843ce2..bdceeee7a82 100644 --- a/src/XrdFrm.cmake +++ b/src/XrdFrm.cmake @@ -104,11 +104,3 @@ target_link_libraries( install( TARGETS frm_admin frm_purged frm_xfrd frm_xfragent RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) - -install( - FILES - ${PROJECT_SOURCE_DIR}/docs/man/frm_admin.8 - ${PROJECT_SOURCE_DIR}/docs/man/frm_purged.8 - ${PROJECT_SOURCE_DIR}/docs/man/frm_xfrd.8 - ${PROJECT_SOURCE_DIR}/docs/man/frm_xfragent.8 - DESTINATION ${CMAKE_INSTALL_MANDIR}/man8 ) diff --git a/src/XrdPfc.cmake b/src/XrdPfc.cmake index 21ad24f6f8c..34d73b692c2 100644 --- a/src/XrdPfc.cmake +++ b/src/XrdPfc.cmake @@ -88,9 +88,3 @@ install( install( TARGETS xrdpfc_print RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) - -install( - FILES - ${PROJECT_SOURCE_DIR}/docs/man/xrdpfc_print.8 - DESTINATION ${CMAKE_INSTALL_MANDIR}/man8 ) - diff --git a/src/XrdSec.cmake b/src/XrdSec.cmake index 6f878e5b736..2e3776a5ecb 100644 --- a/src/XrdSec.cmake +++ b/src/XrdSec.cmake @@ -144,10 +144,4 @@ install( xrdsssadmin xrdpwdadmin RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ) - -install( - FILES - ${PROJECT_SOURCE_DIR}/docs/man/xrdsssadmin.8 - ${PROJECT_SOURCE_DIR}/docs/man/xrdpwdadmin.8 - DESTINATION ${CMAKE_INSTALL_MANDIR}/man8 ) endif() diff --git a/src/XrdSecgsi.cmake b/src/XrdSecgsi.cmake index 45f058f206a..609e65ba6c3 100644 --- a/src/XrdSecgsi.cmake +++ b/src/XrdSecgsi.cmake @@ -100,12 +100,5 @@ install( xrdgsitest RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ) - -install( - FILES - ${PROJECT_SOURCE_DIR}/docs/man/xrdgsiproxy.1 - ${PROJECT_SOURCE_DIR}/docs/man/xrdgsitest.1 - DESTINATION ${CMAKE_INSTALL_MANDIR}/man1 ) - endif() diff --git a/src/XrdVoms.cmake b/src/XrdVoms.cmake index 822abc7bcb0..55cdf2a3b2d 100644 --- a/src/XrdVoms.cmake +++ b/src/XrdVoms.cmake @@ -31,11 +31,6 @@ install( RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ) -install( - FILES - ${PROJECT_SOURCE_DIR}/docs/man/libXrdVoms.1 - DESTINATION ${CMAKE_INSTALL_MANDIR}/man1 ) - install( CODE " EXECUTE_PROCESS( @@ -47,9 +42,3 @@ install( EXECUTE_PROCESS( COMMAND ln -sf lib${LIB_XRD_VOMS}.so lib${LIB_XRD_HTTP_VOMS}.so WORKING_DIRECTORY \$ENV{DESTDIR}/${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR} )" ) - -install( - CODE " - EXECUTE_PROCESS( - COMMAND ln -sf libXrdVoms.1 libXrdSecgsiVOMS.1 - WORKING_DIRECTORY \$ENV{DESTDIR}/${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_MANDIR}/man1 )" ) From 2556293deed42b60d80dd816a3707d093bc59025 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 19 Sep 2023 11:44:13 +0200 Subject: [PATCH 246/442] [XrdCeph] Align CMake requirement with main CMakeLists.txt --- src/XrdCeph/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdCeph/CMakeLists.txt b/src/XrdCeph/CMakeLists.txt index bce584b15a9..c8730143079 100644 --- a/src/XrdCeph/CMakeLists.txt +++ b/src/XrdCeph/CMakeLists.txt @@ -1,7 +1,7 @@ #------------------------------------------------------------------------------- # Project description #------------------------------------------------------------------------------- -cmake_minimum_required( VERSION 3.1 ) +cmake_minimum_required(VERSION 3.16...3.25) project( xrootd-ceph ) From 941025aff2efeb504728e62fc7cc94ea45ee4a44 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 25 Sep 2023 14:47:18 +0200 Subject: [PATCH 247/442] [XrdCms] Try to load blacklist even if some entries are invalid Fixes: #2092 --- src/XrdCms/XrdCmsBlackList.cc | 6 +++--- src/XrdCms/XrdCmsBlackList.hh | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/XrdCms/XrdCmsBlackList.cc b/src/XrdCms/XrdCmsBlackList.cc index ed5eda173c5..5de2490ee14 100644 --- a/src/XrdCms/XrdCmsBlackList.cc +++ b/src/XrdCms/XrdCmsBlackList.cc @@ -382,7 +382,7 @@ XrdOucTList *XrdCmsBlackList::Flatten(XrdOucTList *tList, int tPort) /******************************************************************************/ bool XrdCmsBlackList::GetBL(XrdOucTList *&bList, - XrdOucTList **&rList, int &rcnt) + XrdOucTList **&rList, int &rcnt, bool isInit) { static int msgCnt = 0; XrdOucEnv myEnv; @@ -455,7 +455,7 @@ bool XrdCmsBlackList::GetBL(XrdOucTList *&bList, // Return ending status // blFile.Close(); - bList = (aOK ? bAnchor.Export() : 0); + bList = ((aOK || isInit) ? bAnchor.Export() : nullptr); rList = rAnchor[1].Array(rcnt); return aOK; } @@ -494,7 +494,7 @@ void XrdCmsBlackList::Init(XrdScheduler *sP, XrdCmsCluster *cP, // if (!stat(blFN, &Stat)) {blTime = Stat.st_mtime; - GetBL(blReal, blRedr, blRcnt); + GetBL(blReal, blRedr, blRcnt, /* isInit = */ true); if (blReal) blMN.Ring(); } diff --git a/src/XrdCms/XrdCmsBlackList.hh b/src/XrdCms/XrdCmsBlackList.hh index 036e533019e..87b8cd6cede 100644 --- a/src/XrdCms/XrdCmsBlackList.hh +++ b/src/XrdCms/XrdCmsBlackList.hh @@ -97,6 +97,6 @@ static int AddRD(BL_Grip *rAnchor, char *rSpec, char *hSpec); static bool AddRD(XrdOucTList **rList, char *rSpec, char *hSpec); static XrdOucTList *Flatten(XrdOucTList *tList, int tPort); -static bool GetBL(XrdOucTList *&bList, XrdOucTList **&rList, int &rcnt); +static bool GetBL(XrdOucTList *&bList, XrdOucTList **&rList, int &rcnt, bool isInit = false); }; #endif From f39cf279b1a07d79a60e29b29334cfb9c095fa84 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 26 Sep 2023 09:52:15 +0200 Subject: [PATCH 248/442] Update .mailmap file --- .mailmap | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/.mailmap b/.mailmap index a6c99ac7d09..116e6e98944 100644 --- a/.mailmap +++ b/.mailmap @@ -14,6 +14,7 @@ Chris Burr Chris Burr David Smith David Smith David Smith +Ed J Edgar Fajardo efajardo Edgar Fajardo efajardo Elvin Sindrilaru @@ -23,6 +24,7 @@ Fabrizio Furano Fabrizio Furano Fabrizio Furano Fabrizio Furano Fabrizio Furano ffurano Fabrizio Furano furano +Frank Winklmeier fwinkl <> Fritz Mueller Fritz Mueller Gerardo Ganis Gerardo GANIS Gerardo Ganis Gerri Ganis @@ -32,6 +34,7 @@ Gerardo Ganis ganis Gerardo Ganis ganis Gerardo Ganis gganis Jacek Becla +James Walder snafus Jan Iven Jozsef Makai Jozsef Makai Jozsef Makai @@ -50,7 +53,9 @@ Nikola Hardi Paul-Niklas Kramp Paul Kramp Paul-Niklas Kramp niklas Paul-Niklas Kramp pkramp +Remigius Mommsen mommsen <> Sebastien Ponce +Thorsten Kollegger TKollegger Wei Yang Wei Yang Wei Yang Wei Yang Wei Yang Wei Yang @@ -63,11 +68,10 @@ Wei Yang Wei Yang Wei Yang Wei Yang Unknown bbrqa <> -Unknown fwinkl <> -Unknown mommsen <> Unknown otron -Unknown root -Unknown root -Unknown root -Unknown root -Unknown xrootd +Unknown root +Unknown root +Unknown root +Unknown root +Unknown xrootd +Unknown root From 5ffe9a110b524054c0fdf55ab8fe6117e399a592 Mon Sep 17 00:00:00 2001 From: David Smith Date: Fri, 6 Oct 2023 11:23:52 +0200 Subject: [PATCH 249/442] [XrdCl] Give error return code if xrdfs rm fails to delete any file. Closes #2097 --- src/XrdCl/XrdClFS.cc | 7 +++++-- tests/XRootD/smoke.sh | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/XrdCl/XrdClFS.cc b/src/XrdCl/XrdClFS.cc index 98b1fef55c6..e455d6ac1b4 100644 --- a/src/XrdCl/XrdClFS.cc +++ b/src/XrdCl/XrdClFS.cc @@ -701,9 +701,12 @@ XRootDStatus DoRm( FileSystem *fs, } //---------------------------------------------------------------------------- - // Run the query + // Run the query: + // Parallel() will take the vector of Pipeline by reference and empty the + // vector, so rms.size() will change after the call. //---------------------------------------------------------------------------- - XRootDStatus st = WaitFor( Parallel( rms ).AtLeast( rms.size() ) ); + const size_t rs = rms.size(); + XRootDStatus st = WaitFor( Parallel( rms ).AtLeast( rs ) ); if( !st.IsOK() ) return st; diff --git a/tests/XRootD/smoke.sh b/tests/XRootD/smoke.sh index 73d84f2a255..eb4c71e486c 100755 --- a/tests/XRootD/smoke.sh +++ b/tests/XRootD/smoke.sh @@ -99,6 +99,45 @@ done wait +# +# check return code for xrdfs rm +# create another 6 files, which should be deleted during the test +# +for i in $(seq -w 1 6) ; do + ${OPENSSL} rand -out "${TMPDIR}/${i}.exists.ref" $((1024 * $RANDOM)) + ${XRDCP} ${TMPDIR}/${i}.exists.ref ${HOST}/${TMPDIR}/${i}.exists.ref +done + +# remove 3 existing, should succeed not error +${XRDFS} ${HOST} rm ${TMPDIR}/1.exists.ref ${TMPDIR}/2.exists.ref ${TMPDIR}/3.exists.ref + +set +e + +# remove 3 not existing, should error +# +${XRDFS} ${HOST} rm ${TMPDIR}/not_exists_1.ref ${TMPDIR}/not_exists_2.ref ${TMPDIR}/not_exists_3.ref +rm_rc=$? +if [ $rm_rc -eq 0 ]; then + exit 1 +fi +# +# remove 2 existing, 1 not existing should error +# +${XRDFS} ${HOST} rm ${TMPDIR}/4.exists.ref ${TMPDIR}/5.exists.ref ${TMPDIR}/not_exists_4.ref +rm_rc=$? +if [ $rm_rc -eq 0 ]; then + exit 1 +fi +# +# remove 1 existing, 2 not existing should error +# +${XRDFS} ${HOST} rm ${TMPDIR}/6.exists.ref ${TMPDIR}/not_exists_5.ref ${TMPDIR}/not_exists_6.ref +rm_rc=$? +if [ $rm_rc -eq 0 ]; then + exit 1 +fi +set -e + ${XRDFS} ${HOST} rmdir ${TMPDIR} echo "ALL TESTS PASSED" From e02d0caf56aa434132b5c01615ee2021628c9235 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 9 Oct 2023 16:53:51 +0200 Subject: [PATCH 250/442] [CMake] Export project version in CMake config Fixes: #2094 --- CMakeLists.txt | 23 ++++++++++++++++++----- cmake/XRootDConfig.cmake.in | 24 +++++++++++++++++------- cmake/XRootDVersion.cmake | 2 +- 3 files changed, 36 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7d10f7aee9d..36a7f62b570 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,11 +59,24 @@ include( XRootDSummary ) # Install XRootDConfig.cmake module #------------------------------------------------------------------------------- -configure_file( "cmake/XRootDConfig.cmake.in" "cmake/XRootDConfig.cmake" @ONLY ) - -install( - FILES ${CMAKE_BINARY_DIR}/cmake/XRootDConfig.cmake - DESTINATION ${CMAKE_INSTALL_DATADIR}/xrootd/cmake ) +include(CMakePackageConfigHelpers) + +write_basic_package_version_file(cmake/${PROJECT_NAME}ConfigVersion.cmake + VERSION ${XRootD_VERSION} COMPATIBILITY SameMajorVersion) + +configure_package_config_file(cmake/${PROJECT_NAME}Config.cmake.in cmake/${PROJECT_NAME}Config.cmake + INSTALL_PREFIX + ${CMAKE_INSTALL_PREFIX} + INSTALL_DESTINATION + ${CMAKE_INSTALL_DATADIR}/xrootd/cmake + PATH_VARS + CMAKE_INSTALL_INCLUDEDIR + CMAKE_INSTALL_LIBDIR + CMAKE_INSTALL_DATADIR +) + +install(DIRECTORY ${PROJECT_BINARY_DIR}/cmake/ + DESTINATION ${CMAKE_INSTALL_DATADIR}/xrootd/cmake) #------------------------------------------------------------------------------- # Configure an 'uninstall' target diff --git a/cmake/XRootDConfig.cmake.in b/cmake/XRootDConfig.cmake.in index c70e94846ea..a1e00fdaa26 100644 --- a/cmake/XRootDConfig.cmake.in +++ b/cmake/XRootDConfig.cmake.in @@ -19,6 +19,20 @@ # List of components: CLIENT, UTILS, SERVER, POSIX, HTTP and SSI ################################################################################ +@PACKAGE_INIT@ + +set(XRootD_FOUND TRUE) + +set(XRootD_VERSION @XRootD_VERSION@) +set(XRootD_PLUGIN_VERSION @XRootD_VERSION_MAJOR@) + +set(XRootD_VERSION_MAJOR @XRootD_VERSION_MAJOR@) +set(XRootD_VERSION_MINOR @XRootD_VERSION_MINOR@) +set(XRootD_VERSION_PATCH @XRootD_VERSION_PATCH@) +set(XRootD_VERSION_TWEAK @XRootD_VERSION_TWEAK@) +set(XRootD_VERSION_NUMBER @XRootD_VERSION_NUMBER@) +set(XRootD_VERSION_STRING @XRootD_VERSION_STRING@) + ################################################################################ # Make sure all *_FOUND variables are intialized to FALSE ################################################################################ @@ -233,10 +247,6 @@ ENDIF() # Set up the XRootD find module ################################################################################ -IF( XRootD_FIND_REQUIRED ) - INCLUDE( FindPackageHandleStandardArgs ) - FIND_PACKAGE_HANDLE_STANDARD_ARGS( XRootD - REQUIRED_VARS XROOTD_INCLUDE_DIRS ${_XROOTD_MISSING_COMPONENTS} - ) -ENDIF() - +include(FindPackageHandleStandardArgs) +set(XRootD_CONFIG ${CMAKE_CURRENT_LIST_FILE}) +find_package_handle_standard_args(XRootD CONFIG_MODE) diff --git a/cmake/XRootDVersion.cmake b/cmake/XRootDVersion.cmake index b5b26b0295a..8435a05f1e5 100644 --- a/cmake/XRootDVersion.cmake +++ b/cmake/XRootDVersion.cmake @@ -64,7 +64,7 @@ if(XRootD_VERSION_STRING MATCHES "[_-](.*)$") set(XRootD_VERSION_SUFFIX ${CMAKE_MATCH_1}) endif() -string(REGEX MATCH "[0-9]+[.]*[0-9]*[.]*[0-9]*[.]*[0-9]*(-rc)?[0-9]*" +string(REGEX MATCH "[0-9]+[.]*[0-9]*[.]*[0-9]*[.]*[0-9]*(-rc)?[0-9].*" XRootD_VERSION ${XRootD_VERSION_STRING}) message(DEBUG "XRootD_VERSION_STRING = '${XRootD_VERSION_STRING}'") From c6e0e598ce37b258fff8922b08bf5c45515a8e57 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 9 Oct 2023 17:04:29 +0200 Subject: [PATCH 251/442] [CMake] Find only XRootD matching XRootDConfig.cmake installation path Without this change, a call to find_package(XRootD) might find it from the wrong location, e.g. cmake -DXRootD_DIR=/my/xrootd/share/xrootd/cmake ... may actually find XRootD installed in /usr instead of /my/xrootd, if it's installed there. --- cmake/XRootDConfig.cmake.in | 78 +++++++------------------------------ 1 file changed, 14 insertions(+), 64 deletions(-) diff --git a/cmake/XRootDConfig.cmake.in b/cmake/XRootDConfig.cmake.in index a1e00fdaa26..f28a38278c6 100644 --- a/cmake/XRootDConfig.cmake.in +++ b/cmake/XRootDConfig.cmake.in @@ -47,36 +47,21 @@ SET( XROOTD_SSI_FOUND FALSE ) ################################################################################ # Set XRootD include paths ################################################################################ -FIND_PATH( XROOTD_INCLUDE_DIRS XrdVersion.hh - HINTS - ${XROOTD_DIR} - $ENV{XROOTD_DIR} - /usr - /opt/xrootd - PATH_SUFFIXES include/xrootd - PATHS /opt/xrootd -) -IF( NOT "${XROOTD_INCLUDE_DIRS}" STREQUAL "XROOTD_INCLUDE_DIRS-NOTFOUND" ) - SET( XROOTD_FOUND TRUE ) - SET( XROOTD_INCLUDE_DIRS "${XROOTD_INCLUDE_DIRS};${XROOTD_INCLUDE_DIRS}/private" ) -ENDIF() +set_and_check(XRootD_CMAKE_DIR "${CMAKE_CURRENT_LIST_DIR}") +set_and_check(XRootD_DATA_DIR "@PACKAGE_CMAKE_INSTALL_DATADIR@") +set_and_check(XRootD_INCLUDE_DIR "@PACKAGE_CMAKE_INSTALL_INCLUDEDIR@") +set_and_check(XRootD_LIB_DIR "@PACKAGE_CMAKE_INSTALL_LIBDIR@") -IF( NOT XROOTD_FOUND ) - LIST( APPEND _XROOTD_MISSING_COMPONENTS XROOTD_FOUND ) -ENDIF() +set(XRootD_INCLUDE_DIRS "${XRootD_INCLUDE_DIR};${XRootD_INCLUDE_DIR}/private") +set(XROOTD_INCLUDE_DIRS "${XRootD_INCLUDE_DIRS}") # backward compatibility ################################################################################ # XRootD client libs # - libXrdCl ################################################################################ FIND_LIBRARY( XROOTD_CLIENT_LIBRARIES XrdCl - HINTS - ${XROOTD_DIR} - $ENV{XROOTD_DIR} - /usr - /opt/xrootd - PATH_SUFFIXES lib lib64 + PATHS ${XRootD_LIB_DIR} NO_DEFAULT_PATH ) IF( NOT "${XROOTD_CLIENT_LIBRARIES}" STREQUAL "XROOTD_CLIENT_LIBRARIES-NOTFOUND" ) @@ -95,12 +80,7 @@ ENDIF() # - libXrdUtils ################################################################################ FIND_LIBRARY( XROOTD_UTILS_LIBRARIES XrdUtils - HINTS - ${XROOTD_DIR} - $ENV{XROOTD_DIR} - /usr - /opt/xrootd - PATH_SUFFIXES lib lib64 + PATHS ${XRootD_LIB_DIR} NO_DEFAULT_PATH ) IF( NOT "${XROOTD_UTILS_LIBRARIES}" STREQUAL "XROOTD_UTILS_LIBRARIES-NOTFOUND" ) @@ -119,12 +99,7 @@ ENDIF() # - libXrdServer ################################################################################ FIND_LIBRARY( XROOTD_SERVER_LIBRARIES XrdServer - HINTS - ${XROOTD_DIR} - $ENV{XROOTD_DIR} - /usr - /opt/xrootd - PATH_SUFFIXES lib lib64 + PATHS ${XRootD_LIB_DIR} NO_DEFAULT_PATH ) IF( NOT "${XROOTD_SERVER_LIBRARIES}" STREQUAL "XROOTD_SERVER_LIBRARIES-NOTFOUND" ) @@ -144,21 +119,11 @@ ENDIF() # - libXrdPosixPreload ################################################################################ FIND_LIBRARY( XROOTD_POSIX_LIBRARY XrdPosix - HINTS - ${XROOTD_DIR} - $ENV{XROOTD_DIR} - /usr - /opt/xrootd - PATH_SUFFIXES lib lib64 + PATHS ${XRootD_LIB_DIR} NO_DEFAULT_PATH ) FIND_LIBRARY( XROOTD_POSIX_PRELOAD_LIBRARY XrdPosixPreload - HINTS - ${XROOTD_DIR} - $ENV{XROOTD_DIR} - /usr - /opt/xrootd - PATH_SUFFIXES lib lib64 + PATHS ${XRootD_LIB_DIR} NO_DEFAULT_PATH ) IF( NOT "${XROOTD_POSIX_LIBRARY}" STREQUAL "XROOTD_POSIX_LIBRARY-NOTFOUND" ) @@ -180,12 +145,7 @@ ENDIF() # - libXrdHtppUtils ################################################################################ FIND_LIBRARY( XROOTD_HTTP_LIBRARIES XrdHttpUtils - HINTS - ${XROOTD_DIR} - $ENV{XROOTD_DIR} - /usr - /opt/xrootd - PATH_SUFFIXES lib lib64 + PATHS ${XRootD_LIB_DIR} NO_DEFAULT_PATH ) IF( NOT "${XROOTD_HTTP_LIBRARIES}" STREQUAL "XROOTD_HTTP_LIBRARIES-NOTFOUND" ) @@ -205,21 +165,11 @@ ENDIF() # - XrdSsiShMap ################################################################################ FIND_LIBRARY( XROOTD_SSI_LIBRARY XrdSsiLib - HINTS - ${XROOTD_DIR} - $ENV{XROOTD_DIR} - /usr - /opt/xrootd - PATH_SUFFIXES lib lib64 + PATHS ${XRootD_LIB_DIR} NO_DEFAULT_PATH ) FIND_LIBRARY( XROOTD_SSI_SHMAP_LIBRARY XrdSsiShMap - HINTS - ${XROOTD_DIR} - $ENV{XROOTD_DIR} - /usr - /opt/xrootd - PATH_SUFFIXES lib lib64 + PATHS ${XRootD_LIB_DIR} NO_DEFAULT_PATH ) IF( NOT "${XROOTD_SSI_LIBRARY}" STREQUAL "XROOTD_SSI_LIBRARY-NOTFOUND" ) From 3b014ff2337a4072d95f73464b10dd7a3f7ab458 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 9 Oct 2023 17:06:44 +0200 Subject: [PATCH 252/442] [CMake] Handle components using more standard method --- cmake/XRootDConfig.cmake.in | 56 ++++++++----------------------------- 1 file changed, 12 insertions(+), 44 deletions(-) diff --git a/cmake/XRootDConfig.cmake.in b/cmake/XRootDConfig.cmake.in index f28a38278c6..773bfd2bad6 100644 --- a/cmake/XRootDConfig.cmake.in +++ b/cmake/XRootDConfig.cmake.in @@ -69,12 +69,6 @@ IF( NOT "${XROOTD_CLIENT_LIBRARIES}" STREQUAL "XROOTD_CLIENT_LIBRARIES-NOTFOUND" LIST( APPEND XROOTD_LIBRARIES ${XROOTD_CLIENT_LIBRARIES} ) ENDIF() -IF( XRootD_FIND_REQUIRED_CLIENT AND NOT XROOTD_CLIENT_FOUND ) - MESSAGE( "XRootD client required but not found!" ) - LIST( APPEND _XROOTD_MISSING_COMPONENTS XROOTD_CLIENT_FOUND ) - SET( XROOTD_FOUND FALSE ) -ENDIF() - ################################################################################ # XRootD utils libs # - libXrdUtils @@ -88,12 +82,6 @@ IF( NOT "${XROOTD_UTILS_LIBRARIES}" STREQUAL "XROOTD_UTILS_LIBRARIES-NOTFOUND" ) LIST( APPEND XROOTD_LIBRARIES ${XROOTD_UTILS_LIBRARIES} ) ENDIF() -IF( XRootD_FIND_REQUIRED_UTILS AND NOT XROOTD_UTILS_FOUND ) - MESSAGE( "XRootD utils required but not found!" ) - LIST( APPEND _XROOTD_MISSING_COMPONENTS XROOTD_UTILS_FOUND ) - SET( XROOTD_FOUND FALSE ) -ENDIF() - ################################################################################ # XRootD server libs # - libXrdServer @@ -107,12 +95,6 @@ IF( NOT "${XROOTD_SERVER_LIBRARIES}" STREQUAL "XROOTD_SERVER_LIBRARIES-NOTFOUND" LIST( APPEND XROOTD_LIBRARIES ${XROOTD_SERVER_LIBRARIES} ) ENDIF() -IF( XRootD_FIND_REQUIRED_SERVER AND NOT XROOTD_SERVER_FOUND ) - MESSAGE( "XRootD server required but not found!" ) - LIST( APPEND _XROOTD_MISSING_COMPONENTS XROOTD_SERVER_FOUND ) - SET( XROOTD_FOUND FALSE ) -ENDIF() - ################################################################################ # XRootD posix libs # - libXrdPosix @@ -134,12 +116,6 @@ IF( NOT "${XROOTD_POSIX_LIBRARY}" STREQUAL "XROOTD_POSIX_LIBRARY-NOTFOUND" ) ENDIF() ENDIF() -IF( XRootD_FIND_REQUIRED_POSIX AND NOT XROOTD_POSIX_FOUND ) - MESSAGE( "XRootD posix required but not found!" ) - LIST( APPEND _XROOTD_MISSING_COMPONENTS XROOTD_POSIX_FOUND ) - SET( XROOTD_FOUND FALSE ) -ENDIF() - ################################################################################ # XRootD HTTP (XrdHttp) libs # - libXrdHtppUtils @@ -153,12 +129,6 @@ IF( NOT "${XROOTD_HTTP_LIBRARIES}" STREQUAL "XROOTD_HTTP_LIBRARIES-NOTFOUND" ) LIST( APPEND XROOTD_LIBRARIES ${XROOTD_HTTP_LIBRARIES} ) ENDIF() -IF( XRootD_FIND_REQUIRED_HTTP AND NOT XROOTD_HTTP_FOUND ) - MESSAGE( "XRootD http required but not found!" ) - LIST( APPEND _XROOTD_MISSING_COMPONENTS XROOTD_HTTP_FOUND ) - SET( XROOTD_FOUND FALSE ) -ENDIF() - ################################################################################ # XRootD SSI libs # - XrdSsiLib @@ -180,23 +150,21 @@ IF( NOT "${XROOTD_SSI_LIBRARY}" STREQUAL "XROOTD_SSI_LIBRARY-NOTFOUND" ) ENDIF() ENDIF() -IF( XRootD_FIND_REQUIRED_SSI AND NOT XROOTD_SSI_FOUND ) - MESSAGE( "XRootD ssi required but not found!" ) - LIST (APPEND _XROOTD_MISSING_COMPONENTS XROOTD_SSI_FOUND ) - SET( XROOTD_FOUND FALSE ) -ENDIF() - -################################################################################ -# Utility variables for plug-in development -################################################################################ -IF( XROOTD_FOUND ) - SET( XROOTD_PLUGIN_VERSION @PLUGIN_VERSION@ ) -ENDIF() - ################################################################################ # Set up the XRootD find module ################################################################################ +foreach(COMPONENT UTILS CLIENT SERVER HTTP POSIX SSI) + # Set uppercase names to keep backward compatibility + set(XRootD_${COMPONENT}_FOUND ${XROOTD_${COMPONENT}_FOUND}) + set(XRootD_${COMPONENT}_LIBRARIES ${XROOTD_${COMPONENT}_LIBRARIES}) +endforeach() + +check_required_components(XRootD) + +set(XROOTD_FOUND ${XRootD_FOUND}) +set(XRootD_LIBRARIES ${XROOTD_LIBRARIES}) + include(FindPackageHandleStandardArgs) set(XRootD_CONFIG ${CMAKE_CURRENT_LIST_FILE}) -find_package_handle_standard_args(XRootD CONFIG_MODE) +find_package_handle_standard_args(XRootD CONFIG_MODE HANDLE_COMPONENTS) From e7cf81f82d177b768f740b9db28e669b09273ca6 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 9 Oct 2023 17:08:24 +0200 Subject: [PATCH 253/442] [CMake] Add extra debugging messages in XRootDConfig.cmake --- cmake/XRootDConfig.cmake.in | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/cmake/XRootDConfig.cmake.in b/cmake/XRootDConfig.cmake.in index 773bfd2bad6..971b415a704 100644 --- a/cmake/XRootDConfig.cmake.in +++ b/cmake/XRootDConfig.cmake.in @@ -165,6 +165,27 @@ check_required_components(XRootD) set(XROOTD_FOUND ${XRootD_FOUND}) set(XRootD_LIBRARIES ${XROOTD_LIBRARIES}) +message(DEBUG "XRootD_VERSION = '${XRootD_VERSION}'") +message(DEBUG "XRootD_VERSION_MAJOR = '${XRootD_VERSION_MAJOR}'") +message(DEBUG "XRootD_VERSION_MINOR = '${XRootD_VERSION_MINOR}'") +message(DEBUG "XRootD_VERSION_PATCH = '${XRootD_VERSION_PATCH}'") +message(TRACE "XRootD_VERSION_TWEAK = '${XRootD_VERSION_TWEAK}'") +message(TRACE "XRootD_VERSION_NUMBER = '${XRootD_VERSION_NUMBER}'") + +message(TRACE "XRootD_FOUND = '${XRootD_FOUND}'") +message(TRACE "XRootD_INSTALL_PREFIX = '@CMAKE_INSTALL_PREFIX@'") +message(TRACE "XRootD_INCLUDE_DIR = '@PACKAGE_CMAKE_INSTALL_INCLUDEDIR@'") +message(TRACE "XRootD_LIBRARY_DIR = '@PACKAGE_CMAKE_INSTALL_LIBDIR@'") +message(TRACE "XRootD_LIBRARIES = '${XRootD_LIBRARIES}'") + +foreach(COMPONENT UTILS CLIENT SERVER HTTP POSIX SSI) + message(TRACE "XRootD_${COMPONENT}_FOUND\t\t= '${XRootD_${COMPONENT}_FOUND}'") +endforeach() + +foreach(COMPONENT UTILS CLIENT SERVER HTTP POSIX SSI) + message(TRACE "XRootD_${COMPONENT}_LIBRARIES\t= '${XRootD_${COMPONENT}_LIBRARIES}'") +endforeach() + include(FindPackageHandleStandardArgs) set(XRootD_CONFIG ${CMAKE_CURRENT_LIST_FILE}) find_package_handle_standard_args(XRootD CONFIG_MODE HANDLE_COMPONENTS) From 335a2591f190319da00b22d63bb7cf510599d90c Mon Sep 17 00:00:00 2001 From: Derek Weitzel Date: Tue, 10 Oct 2023 11:25:04 -0500 Subject: [PATCH 254/442] [XrdHttp] Fix parsing of chunked PUT lengths Chunked encoded PUT are formatted with a length before each chunk. The bugfix change was from using `prot->BufAvailable()` to `prot->BufUsed()`. Additionally, add a sanity check on the size of a chunk in order to protect against malformed of malicious sized chunks. Also, added additional trace statements to help when the chunk is rejected. --- src/XrdHttp/XrdHttpReq.cc | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/XrdHttp/XrdHttpReq.cc b/src/XrdHttp/XrdHttpReq.cc index 5b9c185e4de..78cbcd2c761 100644 --- a/src/XrdHttp/XrdHttpReq.cc +++ b/src/XrdHttp/XrdHttpReq.cc @@ -1328,14 +1328,24 @@ int XrdHttpReq::ProcessHTTPReq() { // Parse out the next chunk size. long long idx = 0; bool found_newline = false; - for (; idx < prot->BuffAvailable(); idx++) { + // Set a maximum size of chunk we will allow + // Nginx sets this to "NGX_MAX_OFF_T_VALUE", which is 9223372036854775807 (a some crazy number) + // We set it to 1TB, which is 1099511627776 + // This is to prevent a malicious client from sending a very large chunk size + // or a malformed chunk request. + // 1TB in base-16 is 0x40000000000, so only allow 11 characters, plus the CRLF + long long max_chunk_size_chars = std::max(static_cast(prot->BuffUsed()), static_cast(13)); + for (; idx < max_chunk_size_chars; idx++) { if (prot->myBuffStart[idx] == '\n') { found_newline = true; break; } } - if ((idx == 0) || prot->myBuffStart[idx-1] != '\r') { + // If we found a new line, but it is the first character in the buffer (no chunk length) + // or if the previous character is not a CR. + if (found_newline && ((idx == 0) || prot->myBuffStart[idx-1] != '\r')) { prot->SendSimpleResp(400, NULL, NULL, (char *)"Invalid chunked encoding", 0, false); + TRACE(REQ, "XrdHTTP PUT: Sending invalid chunk encoding. Start of chunk should have had a length, followed by a CRLF."); return -1; } if (found_newline) { @@ -1345,6 +1355,7 @@ int XrdHttpReq::ProcessHTTPReq() { // Chunk sizes can be followed by trailer information or CRLF if (*endptr != ';' && *endptr != '\r') { prot->SendSimpleResp(400, NULL, NULL, (char *)"Invalid chunked encoding", 0, false); + TRACE(REQ, "XrdHTTP PUT: Sending invalid chunk encoding. Chunk size was not followed by a ';' or CR." << __LINE__); return -1; } m_current_chunk_size = chunk_contents; @@ -1373,7 +1384,7 @@ int XrdHttpReq::ProcessHTTPReq() { xrdreq.write.offset = htonll(writtenbytes); xrdreq.write.dlen = htonl(bytes_to_write); - TRACEI(REQ, "Writing chunk of size " << bytes_to_write << " starting with '" << *(prot->myBuffStart) << "'"); + TRACEI(REQ, "XrdHTTP PUT: Writing chunk of size " << bytes_to_write << " starting with '" << *(prot->myBuffStart) << "'" << " with " << chunk_bytes_remaining << " bytes remaining in the chunk"); if (!prot->Bridge->Run((char *) &xrdreq, prot->myBuffStart, bytes_to_write)) { prot->SendSimpleResp(500, NULL, NULL, (char *) "Could not run write request.", 0, false); return -1; From b290f5f6814e66c4bfb0f3df86cada0a85c2fbd7 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Wed, 11 Oct 2023 07:55:40 -0700 Subject: [PATCH 255/442] [Server] Correct value for the maximum exp/act combined value. --- src/XrdNet/XrdNetPMark.cc | 2 +- src/XrdNet/XrdNetPMark.hh | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/XrdNet/XrdNetPMark.cc b/src/XrdNet/XrdNetPMark.cc index 3eb52eaa0cf..d9e00d99fc1 100644 --- a/src/XrdNet/XrdNetPMark.cc +++ b/src/XrdNet/XrdNetPMark.cc @@ -47,7 +47,7 @@ bool XrdNetPMark::getEA(const char *cgi, int &ecode, int &acode) if (stP) {char *eol; int eacode = strtol(stP+12, &eol, 10); - if (eacode >= 0 && eacode <= XrdNetPMark::maxExpID + if (eacode >= 0 && eacode <= XrdNetPMark::maxTotID && (*eol == '&' || *eol ==0)) {ecode = eacode >> XrdNetPMark::btsActID; acode = eacode & XrdNetPMark::mskActID; diff --git a/src/XrdNet/XrdNetPMark.hh b/src/XrdNet/XrdNetPMark.hh index 565549f9fba..26e9154a2f4 100644 --- a/src/XrdNet/XrdNetPMark.hh +++ b/src/XrdNet/XrdNetPMark.hh @@ -80,6 +80,7 @@ static const int mskActID = 63; static const int maxActID = 63; static const int maxExpID = 511; +static const int maxTotID = 0x7fff; virtual ~XrdNetPMark() {} // This object cannot be deleted! }; From 76953534312e5e66f1524ccefda55871a54e85f5 Mon Sep 17 00:00:00 2001 From: Derek Weitzel Date: Wed, 11 Oct 2023 08:56:37 -0500 Subject: [PATCH 256/442] [XrdHttp] Fix max to min when reading chunk length Small change to using the minimum of either the used buffer size or the first 13 characters of a chunk to read the chunk length. --- src/XrdHttp/XrdHttpReq.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdHttp/XrdHttpReq.cc b/src/XrdHttp/XrdHttpReq.cc index 78cbcd2c761..2202ae7effc 100644 --- a/src/XrdHttp/XrdHttpReq.cc +++ b/src/XrdHttp/XrdHttpReq.cc @@ -1334,7 +1334,7 @@ int XrdHttpReq::ProcessHTTPReq() { // This is to prevent a malicious client from sending a very large chunk size // or a malformed chunk request. // 1TB in base-16 is 0x40000000000, so only allow 11 characters, plus the CRLF - long long max_chunk_size_chars = std::max(static_cast(prot->BuffUsed()), static_cast(13)); + long long max_chunk_size_chars = std::min(static_cast(prot->BuffUsed()), static_cast(13)); for (; idx < max_chunk_size_chars; idx++) { if (prot->myBuffStart[idx] == '\n') { found_newline = true; From 854f90bb84c8d31bacfbbfef0120fa1272f66e0b Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Tue, 10 Oct 2023 11:29:53 +0200 Subject: [PATCH 257/442] XrdHttp: Headers provided by the client are now parsed in a case-insensitive way when trying to match http header2cgi configured keys --- src/XrdHttp/XrdHttpReq.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/XrdHttp/XrdHttpReq.cc b/src/XrdHttp/XrdHttpReq.cc index 2202ae7effc..b79b0ccfa84 100644 --- a/src/XrdHttp/XrdHttpReq.cc +++ b/src/XrdHttp/XrdHttpReq.cc @@ -196,7 +196,9 @@ int XrdHttpReq::parseLine(char *line, int len) { m_status_trailer = true; } else { // Some headers need to be translated into "local" cgi info. - std::map< std::string, std::string > ::iterator it = prot->hdr2cgimap.find(key); + auto it = std::find_if(prot->hdr2cgimap.begin(), prot->hdr2cgimap.end(),[key](const auto & item) { + return !strcasecmp(key,item.first.c_str()); + }); if (it != prot->hdr2cgimap.end() && (opaque ? (0 == opaque->Get(it->second.c_str())) : true)) { std::string s; s.assign(val, line+len-val); From ccc8ab739813426041b10ffad0043ac3d7574e32 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 17 Oct 2023 11:46:30 +0200 Subject: [PATCH 258/442] [Xrd] Create environment file within adminpath Fixes: #2106 --- src/Xrd/XrdConfig.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Xrd/XrdConfig.cc b/src/Xrd/XrdConfig.cc index 0979801c6ab..33528ec235e 100644 --- a/src/Xrd/XrdConfig.cc +++ b/src/Xrd/XrdConfig.cc @@ -1057,7 +1057,7 @@ void XrdConfig::Manifest(const char *pidfn) // if (pidfn && (Slash = rindex(pidfn, '/'))) {strncpy(manBuff, pidfn, Slash-pidfn); pidP = manBuff+(Slash-pidfn);} - else {strcpy(manBuff, "/tmp"); pidP = manBuff+4;} + else {strcpy(manBuff, ProtInfo.AdmPath); pidP = manBuff+strlen(ProtInfo.AdmPath);} // Construct the pid file name for ourselves // From d0a30cce68bb29e8386b06b0663eb4acaf30c499 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 19 Oct 2023 09:15:46 +0200 Subject: [PATCH 259/442] [Xrd] Call tzset() early to ensure thread-safety of localtime_r() and mktime() Fixes: #2107 --- src/Xrd/XrdMain.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Xrd/XrdMain.cc b/src/Xrd/XrdMain.cc index 29446a5ca12..1254c76a147 100644 --- a/src/Xrd/XrdMain.cc +++ b/src/Xrd/XrdMain.cc @@ -165,6 +165,9 @@ int main(int argc, char *argv[]) char buff[128]; int i, retc; +// Call tzset() early to ensure thread-safety of localtime_r() and mktime(). + tzset(); + // Turn off sigpipe and host a variety of others before we start any threads // XrdSysUtils::SigBlock(); From f28949241b31e7867b95dab31cd3dd8fd62fc92d Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 23 Oct 2023 14:28:56 +0200 Subject: [PATCH 260/442] [XrdEc] Wait for pipeline including XrdCl::AppendFile() to finish Cannot allow parallel calls to XrdCl::ZipArchive::AppendFile() for the same zip archive object as, for instance, the local file header member is stored as a std::unique_ptr in the class and gets reset with each call to AppendFile(). Fixes: #2050 --- src/XrdEc/XrdEcStrmWriter.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdEc/XrdEcStrmWriter.cc b/src/XrdEc/XrdEcStrmWriter.cc index 18da18a4fc1..4953a52e27f 100644 --- a/src/XrdEc/XrdEcStrmWriter.cc +++ b/src/XrdEc/XrdEcStrmWriter.cc @@ -211,7 +211,7 @@ namespace XrdEc writes.emplace_back( std::move( p ) ); } - XrdCl::Async( XrdCl::Parallel( writes ) >> [=]( XrdCl::XRootDStatus &st ){ global_status.report_wrt( st, blksize ); } ); + XrdCl::WaitFor( XrdCl::Parallel( writes ) >> [=]( XrdCl::XRootDStatus &st ){ global_status.report_wrt( st, blksize ); } ); } //--------------------------------------------------------------------------- From 247bdf63f696b8caa545869577fd184812659784 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Tue, 3 Oct 2023 04:53:32 -0700 Subject: [PATCH 261/442] [Server] Export ptr to full TLS context into the Xrd env. --- src/Xrd/XrdConfig.cc | 3 ++- src/XrdAcc/XrdAccAuthorize.hh | 17 +++++++++++++++++ src/XrdOfs/XrdOfsConfigPI.cc | 19 +++++++++++++------ 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/Xrd/XrdConfig.cc b/src/Xrd/XrdConfig.cc index 33528ec235e..7f15a38b668 100644 --- a/src/Xrd/XrdConfig.cc +++ b/src/Xrd/XrdConfig.cc @@ -687,7 +687,8 @@ int XrdConfig::Configure(int argc, char **argv) else {Log.Say("++++++ ", myInstance, " TLS initialization started."); if (SetupTLS()) {Log.Say("------ ",myInstance," TLS initialization ended."); - ProtInfo.tlsCtx = XrdGlobal::tlsCtx; + if ((ProtInfo.tlsCtx = XrdGlobal::tlsCtx)) + theEnv.PutPtr("XrdTlsContext*", XrdGlobal::tlsCtx); } else { NoGo = 1; Log.Say("------ ",myInstance," TLS initialization failed."); diff --git a/src/XrdAcc/XrdAccAuthorize.hh b/src/XrdAcc/XrdAccAuthorize.hh index 54f34076332..ff69f0359d2 100644 --- a/src/XrdAcc/XrdAccAuthorize.hh +++ b/src/XrdAcc/XrdAccAuthorize.hh @@ -150,12 +150,15 @@ virtual ~XrdAccAuthorize() {} //! XrdAccAuthorizeObject() is an extern "C" function that is called to obtain //! an instance of the auth object that will be used for all subsequent //! authorization decisions. It must be defined in the plug-in shared library. +//! A second version which is used preferentially if it exists should be +//! used if accessto theenvironmental pointer s needed. //! All the following extern symbols must be defined at file level! //! //! @param lp -> XrdSysLogger to be tied to an XrdSysError object for messages //! @param cfn -> The name of the configuration file //! @param parm -> Parameters specified on the authlib directive. If none it //! is zero. +//! @param envP -> Pointer to environment only available for version 2. //! //! @return Success: A pointer to the authorization object. //! Failure: Null pointer which causes initialization to fail. @@ -170,6 +173,20 @@ typedef XrdAccAuthorize *(*XrdAccAuthorizeObject_t)(XrdSysLogger *lp, const char *cfn, const char *parm) {...} */ + +// Alternatively: + +typedef XrdAccAuthorize *(*XrdAccAuthorizeObject2_t)(XrdSysLogger *lp, + const char *cfn, + const char *parm, + XrdOucEnv *envP); + + +/*! extern "C" XrdAccAuthorize *XrdAccAuthorizeObject2(XrdSysLogger *lp, + const char *cfn, + const char *parm, + XrdOucEnv *envP) {...} +*/ //------------------------------------------------------------------------------ //! Add an authorization object as a wrapper to the existing object. diff --git a/src/XrdOfs/XrdOfsConfigPI.cc b/src/XrdOfs/XrdOfsConfigPI.cc index 2682bccfcb9..5e4c25edc62 100644 --- a/src/XrdOfs/XrdOfsConfigPI.cc +++ b/src/XrdOfs/XrdOfsConfigPI.cc @@ -828,7 +828,8 @@ bool XrdOfsConfigPI::SetupAuth(XrdOucEnv *envP) (XrdSysLogger *lp, const char *cfn, const char *parm, XrdVersionInfo &vInfo); - XrdAccAuthorizeObject_t ep; + XrdAccAuthorizeObject_t ep1; + XrdAccAuthorizeObject2_t ep2; char *AuthLib = LP[PIX(theAutLib)].lib; char *AuthParms = LP[PIX(theAutLib)].parms; @@ -841,18 +842,24 @@ bool XrdOfsConfigPI::SetupAuth(XrdOucEnv *envP) return AddLibAut(envP); } -// Create a plugin object +// Create a plugin object. It will be version 2 or version 1, in that order // {XrdOucPinLoader myLib(Eroute, urVer, "authlib", AuthLib); - ep = (XrdAccAuthorizeObject_t)(myLib.Resolve("XrdAccAuthorizeObject")); - if (!ep) return false; + ep2 = (XrdAccAuthorizeObject2_t)(myLib.Resolve("XrdAccAuthorizeObject2")); + if (!ep2) + {ep1 = (XrdAccAuthorizeObject_t)(myLib.Resolve("XrdAccAuthorizeObject")); + if (!ep1) return false; + if (!(autPI = ep1(Eroute->logger(), ConfigFN, AuthParms))) return false; + } else { + if (!(autPI = ep2(Eroute->logger(), ConfigFN, AuthParms, envP))) + return false; + } if (strcmp(AuthLib, myLib.Path())) {free(AuthLib); AuthLib = LP[PIX(theAutLib)].lib = strdup(myLib.Path());} } -// Get the Object now +// Process additional wrapper objects now // - if (!(autPI = ep(Eroute->logger(), ConfigFN, AuthParms))) return false; return AddLibAut(envP); } From da9e9ac37c63f34bce501911ff8558bff0dca39a Mon Sep 17 00:00:00 2001 From: Brian Bockelman Date: Sun, 1 Oct 2023 12:23:11 -0500 Subject: [PATCH 262/442] Pass through currently configured CA files to the SciTokens library --- src/XrdSciTokens/XrdSciTokensAccess.cc | 35 ++++++++++++++++++++------ 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/src/XrdSciTokens/XrdSciTokensAccess.cc b/src/XrdSciTokens/XrdSciTokensAccess.cc index 25b3f9eccbc..44b7e05d9a8 100644 --- a/src/XrdSciTokens/XrdSciTokensAccess.cc +++ b/src/XrdSciTokens/XrdSciTokensAccess.cc @@ -5,6 +5,7 @@ #include "XrdSec/XrdSecEntity.hh" #include "XrdSec/XrdSecEntityAttr.hh" #include "XrdSys/XrdSysLogger.hh" +#include "XrdTls/XrdTlsContext.hh" #include "XrdVersion.hh" #include @@ -432,7 +433,7 @@ class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper, }; public: - XrdAccSciTokens(XrdSysLogger *lp, const char *parms, XrdAccAuthorize* chain) : + XrdAccSciTokens(XrdSysLogger *lp, const char *parms, XrdAccAuthorize* chain, XrdOucEnv *envP) : m_chain(chain), m_parms(parms ? parms : ""), m_next_clean(monotonic_time() + m_expiry_secs), @@ -441,7 +442,7 @@ class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper, pthread_rwlock_init(&m_config_lock, nullptr); m_config_lock_initialized = true; m_log.Say("++++++ XrdAccSciTokens: Initialized SciTokens-based authorization."); - if (!Config()) { + if (!Config(envP)) { throw std::runtime_error("Failed to configure SciTokens authorization."); } } @@ -926,7 +927,7 @@ class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper, } - bool Config() { + bool Config(XrdOucEnv *envP) { // Set default mask for logging. m_log.setMsgMask(LogMask::Error | LogMask::Warning); @@ -962,6 +963,15 @@ class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper, } m_log.Emsg("Config", "Logging levels enabled -", LogMaskToString(m_log.getMsgMask()).c_str()); + auto xrdEnv = static_cast(envP ? envP->GetPtr("xrdEnv*") : nullptr); + auto tlsCtx = static_cast(xrdEnv ? xrdEnv->GetPtr("XrdTlsContext*") : nullptr); + if (tlsCtx) { + auto params = tlsCtx->GetParams(); + if (params && !params->cafile.empty()) { + scitoken_config_set_str("tls.ca_file", params->cafile.c_str(), nullptr); + } + } + return Reconfig(); } @@ -1259,10 +1269,10 @@ class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper, }; void InitAccSciTokens(XrdSysLogger *lp, const char *cfn, const char *parm, - XrdAccAuthorize *accP) + XrdAccAuthorize *accP, XrdOucEnv *envP) { try { - accSciTokens = new XrdAccSciTokens(lp, parm, accP); + accSciTokens = new XrdAccSciTokens(lp, parm, accP, envP); SciTokensHelper = accSciTokens; } catch (std::exception &) { } @@ -1273,7 +1283,7 @@ extern "C" { XrdAccAuthorize *XrdAccAuthorizeObjAdd(XrdSysLogger *lp, const char *cfn, const char *parm, - XrdOucEnv * /*not used*/, + XrdOucEnv *envP, XrdAccAuthorize *accP) { // Record the parent authorization plugin. There is no need to use @@ -1283,7 +1293,7 @@ XrdAccAuthorize *XrdAccAuthorizeObjAdd(XrdSysLogger *lp, // If we have been initialized by a previous load, them return that result. // Otherwise, it's the first time through, get a new SciTokens authorizer. // - if (!accSciTokens) InitAccSciTokens(lp, cfn, parm, accP); + if (!accSciTokens) InitAccSciTokens(lp, cfn, parm, accP, envP); return accSciTokens; } @@ -1291,7 +1301,16 @@ XrdAccAuthorize *XrdAccAuthorizeObject(XrdSysLogger *lp, const char *cfn, const char *parm) { - InitAccSciTokens(lp, cfn, parm, 0); + InitAccSciTokens(lp, cfn, parm, nullptr, nullptr); + return accSciTokens; +} + +XrdAccAuthorize *XrdAccAuthorizeObject2(XrdSysLogger *lp, + const char *cfn, + const char *parm, + XrdOucEnv *envP) +{ + InitAccSciTokens(lp, cfn, parm, nullptr, envP); return accSciTokens; } From 22e2a4ec1d2adbabf912cec5f7ebe8cfaedad2fc Mon Sep 17 00:00:00 2001 From: Brian Bockelman Date: Tue, 24 Oct 2023 07:48:35 -0500 Subject: [PATCH 263/442] Conditionally compile support for loading CA files. The `scitoken_config_set_str` function was only recently added to the SciTokens library; some supported platforms do not plan on taking the updated version. Accordingly, add a CMake-level test to see if the symbol exists and only then make the call to the library. --- cmake/FindSciTokensCpp.cmake | 8 ++++++++ src/XrdSciTokens.cmake | 4 ++++ src/XrdSciTokens/XrdSciTokensAccess.cc | 4 ++++ 3 files changed, 16 insertions(+) diff --git a/cmake/FindSciTokensCpp.cmake b/cmake/FindSciTokensCpp.cmake index 3c9aabc91b5..1e37d3c64ea 100644 --- a/cmake/FindSciTokensCpp.cmake +++ b/cmake/FindSciTokensCpp.cmake @@ -1,4 +1,6 @@ +include(CheckSymbolExists) + FIND_PATH(SCITOKENS_CPP_INCLUDE_DIR scitokens/scitokens.h HINTS ${SCITOKENS_CPP_DIR} @@ -18,3 +20,9 @@ FIND_LIBRARY(SCITOKENS_CPP_LIBRARIES SciTokens INCLUDE(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(SciTokensCpp DEFAULT_MSG SCITOKENS_CPP_LIBRARIES SCITOKENS_CPP_INCLUDE_DIR) +IF (SCITOKENS_CPP_INCLUDE_DIR) + SET( CMAKE_REQUIRED_INCLUDES ${SCITOKENS_CPP_INCLUDE_DIR} ) + SET( CMAKE_REQUIRED_LIBRARIES ${SCITOKENS_CPP_LIBRARIES} ) + CHECK_SYMBOL_EXISTS(scitoken_config_set_str "scitokens/scitokens.h" HAVE_SCITOKEN_CONFIG_SET_STR) + MARK_AS_ADVANCED(HAVE_SCITOKEN_CONFIG_SET_STR) +ENDIF () diff --git a/src/XrdSciTokens.cmake b/src/XrdSciTokens.cmake index 784827d1ec1..f2def352ef4 100644 --- a/src/XrdSciTokens.cmake +++ b/src/XrdSciTokens.cmake @@ -31,6 +31,10 @@ target_include_directories( XrdSciTokens/vendor/picojson XrdSciTokens/vendor/inih ) +if (HAVE_SCITOKEN_CONFIG_SET_STR) + target_compile_definitions(${LIB_XRD_SCITOKENS} PRIVATE HAVE_SCITOKEN_CONFIG_SET_STR) +endif() + #------------------------------------------------------------------------------- # Install #------------------------------------------------------------------------------- diff --git a/src/XrdSciTokens/XrdSciTokensAccess.cc b/src/XrdSciTokens/XrdSciTokensAccess.cc index 44b7e05d9a8..6ab82c39bf1 100644 --- a/src/XrdSciTokens/XrdSciTokensAccess.cc +++ b/src/XrdSciTokens/XrdSciTokensAccess.cc @@ -968,7 +968,11 @@ class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper, if (tlsCtx) { auto params = tlsCtx->GetParams(); if (params && !params->cafile.empty()) { +#ifdef HAVE_SCITOKEN_CONFIG_SET_STR scitoken_config_set_str("tls.ca_file", params->cafile.c_str(), nullptr); +#else + m_log.Log(LogMask::Warning, "Config", "tls.ca_file is set but the platform's libscitokens.so does not support setting config parameters"); +#endif } } From b14bf1464ce7599100836c1c3cf8e8bcbdbd9ec0 Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Wed, 11 Oct 2023 15:41:47 +0200 Subject: [PATCH 264/442] XrdHttp: Promote SciTag header if packet marking has been configured on the server --- src/XrdHttp/XrdHttpProtocol.cc | 3 +++ src/XrdHttp/XrdHttpProtocol.hh | 4 ++++ src/XrdHttp/XrdHttpReq.cc | 40 ++++++++++++++++++++++++++++------ src/XrdHttp/XrdHttpReq.hh | 4 ++++ src/XrdNet/XrdNetPMark.hh | 4 +++- 5 files changed, 47 insertions(+), 8 deletions(-) diff --git a/src/XrdHttp/XrdHttpProtocol.cc b/src/XrdHttp/XrdHttpProtocol.cc index b2efb2d20ee..fcb481add84 100644 --- a/src/XrdHttp/XrdHttpProtocol.cc +++ b/src/XrdHttp/XrdHttpProtocol.cc @@ -109,6 +109,7 @@ XrdSecService *XrdHttpProtocol::CIA = 0; // Authentication Server int XrdHttpProtocol::m_bio_type = 0; // BIO type identifier for our custom BIO. BIO_METHOD *XrdHttpProtocol::m_bio_method = NULL; // BIO method constructor. char *XrdHttpProtocol::xrd_cslist = nullptr; +XrdNetPMark * XrdHttpProtocol::pmarkHandle = nullptr; XrdHttpChecksumHandler XrdHttpProtocol::cksumHandler = XrdHttpChecksumHandler(); XrdHttpReadRangeHandler::Configuration XrdHttpProtocol::ReadRangeConfig; @@ -956,6 +957,8 @@ int XrdHttpProtocol::Config(const char *ConfigFN, XrdOucEnv *myEnv) { XrdOucEnv::Import("XRD_READV_LIMITS", var); XrdHttpReadRangeHandler::Configure(eDest, var, ReadRangeConfig); + pmarkHandle = (XrdNetPMark* ) myEnv->GetPtr("XrdNetPMark*"); + cksumHandler.configure(xrd_cslist); auto nonIanaChecksums = cksumHandler.getNonIANAConfiguredCksums(); if(nonIanaChecksums.size()) { diff --git a/src/XrdHttp/XrdHttpProtocol.hh b/src/XrdHttp/XrdHttpProtocol.hh index 8db563595e7..6083d547b24 100644 --- a/src/XrdHttp/XrdHttpProtocol.hh +++ b/src/XrdHttp/XrdHttpProtocol.hh @@ -48,6 +48,7 @@ #include "XrdOuc/XrdOucHash.hh" #include "XrdHttpChecksumHandler.hh" #include "XrdHttpReadRangeHandler.hh" +#include "XrdNet/XrdNetPMark.hh" #include @@ -433,5 +434,8 @@ protected: /// The list of checksums that were configured via the xrd.cksum parameter on the server config file static char * xrd_cslist; + + /// Packet marking handler pointer (assigned from the environment during the Config() call) + static XrdNetPMark * pmarkHandle; }; #endif diff --git a/src/XrdHttp/XrdHttpReq.cc b/src/XrdHttp/XrdHttpReq.cc index b79b0ccfa84..68147831e6b 100644 --- a/src/XrdHttp/XrdHttpReq.cc +++ b/src/XrdHttp/XrdHttpReq.cc @@ -194,6 +194,10 @@ int XrdHttpReq::parseLine(char *line, int len) { } else if (!strcasecmp(key, "X-Transfer-Status") && strstr(val, "true")) { m_transfer_encoding_chunked = true; m_status_trailer = true; + } else if (!strcasecmp(key, "SciTag")) { + if(prot->pmarkHandle != nullptr) { + parseScitag(val); + } } else { // Some headers need to be translated into "local" cgi info. auto it = std::find_if(prot->hdr2cgimap.begin(), prot->hdr2cgimap.end(),[key](const auto & item) { @@ -203,13 +207,7 @@ int XrdHttpReq::parseLine(char *line, int len) { std::string s; s.assign(val, line+len-val); trim(s); - - if (hdr2cgistr.length() > 0) { - hdr2cgistr.append("&"); - } - hdr2cgistr.append(it->second); - hdr2cgistr.append("="); - hdr2cgistr.append(s); + addCgi(it->second,s); } } @@ -226,6 +224,25 @@ int XrdHttpReq::parseHost(char *line) { return 0; } +void XrdHttpReq::parseScitag(const std::string & val) { + int scitag = 0; + std::string scitagS = val; + trim(scitagS); + if(scitagS.size()) { + if(scitagS[0] != '-') { + try { + scitag = std::stoi(scitagS.c_str(), nullptr, 10); + if (scitag > XrdNetPMark::maxTotID) { + scitag = 0; + } + } catch (...) { + //Nothing to do, scitag = 0 by default + } + } + } + addCgi("scitag.flow", std::to_string(scitag)); +} + int XrdHttpReq::parseFirstLine(char *line, int len) { char *key = line; @@ -753,6 +770,15 @@ void XrdHttpReq::sanitizeResourcePfx() { } } +void XrdHttpReq::addCgi(const std::string &key, const std::string &value) { + if (hdr2cgistr.length() > 0) { + hdr2cgistr.append("&"); + } + hdr2cgistr.append(key); + hdr2cgistr.append("="); + hdr2cgistr.append(value); +} + // Parse a resource line: // - sanitize diff --git a/src/XrdHttp/XrdHttpReq.hh b/src/XrdHttp/XrdHttpReq.hh index 48dbbcd003a..d99b42e14a9 100644 --- a/src/XrdHttp/XrdHttpReq.hh +++ b/src/XrdHttp/XrdHttpReq.hh @@ -89,6 +89,8 @@ private: int parseHost(char *); + void parseScitag(const std::string & val); + //xmlDocPtr xmlbody; /* the resulting document tree */ XrdHttpProtocol *prot; @@ -186,6 +188,8 @@ public: // NOTE: this function assumes that the strings are unquoted, and will quote them void appendOpaque(XrdOucString &s, XrdSecEntity *secent, char *hash, time_t tnow); + void addCgi(const std::string & key, const std::string & value); + // ---------------- // Description of the request. The header/body parsing // is supposed to populate these fields, for fast access while diff --git a/src/XrdNet/XrdNetPMark.hh b/src/XrdNet/XrdNetPMark.hh index 26e9154a2f4..e62ece457e5 100644 --- a/src/XrdNet/XrdNetPMark.hh +++ b/src/XrdNet/XrdNetPMark.hh @@ -71,6 +71,9 @@ virtual Handle *Begin(XrdNetAddrInfo &addr, Handle &handle, static bool getEA(const char *cgi, int &ecode, int &acode); XrdNetPMark() {} + +static const int maxTotID = 0x7fff; + protected: // ID limits and specifications @@ -80,7 +83,6 @@ static const int mskActID = 63; static const int maxActID = 63; static const int maxExpID = 511; -static const int maxTotID = 0x7fff; virtual ~XrdNetPMark() {} // This object cannot be deleted! }; From bf979ccfecd540bffd3694cbbf2c8b01727ef28b Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Mon, 9 Oct 2023 16:00:58 +0200 Subject: [PATCH 265/442] [XrdTpcTPC] Implemented packet marking for HTTP TPC PULL/PUSH single and multistream --- src/XrdHttp/XrdHttpExtHandler.cc | 6 +- src/XrdHttp/XrdHttpExtHandler.hh | 6 ++ src/XrdHttp/XrdHttpReq.cc | 12 ++-- src/XrdHttp/XrdHttpReq.hh | 3 + src/XrdTpc.cmake | 3 +- src/XrdTpc/PMarkManager.cc | 72 ++++++++++++++++++++ src/XrdTpc/PMarkManager.hh | 113 +++++++++++++++++++++++++++++++ src/XrdTpc/XrdTpcMultistream.cc | 6 ++ src/XrdTpc/XrdTpcTPC.cc | 50 ++++++++++---- src/XrdTpc/XrdTpcTPC.hh | 8 ++- 10 files changed, 257 insertions(+), 22 deletions(-) create mode 100644 src/XrdTpc/PMarkManager.cc create mode 100644 src/XrdTpc/PMarkManager.hh diff --git a/src/XrdHttp/XrdHttpExtHandler.cc b/src/XrdHttp/XrdHttpExtHandler.cc index b4cfe771bd5..674620acaae 100644 --- a/src/XrdHttp/XrdHttpExtHandler.cc +++ b/src/XrdHttp/XrdHttpExtHandler.cc @@ -112,6 +112,10 @@ verb(req->requestverb), headers(req->allheaders) { clientgroups = prot->SecEntity.vorg; trim(clientgroups); } - + + // Get the packet marking handle and the client scitag from the XrdHttp layer + pmark = prot->pmarkHandle; + mSciTag = req->mScitag; + length = req->length; } diff --git a/src/XrdHttp/XrdHttpExtHandler.hh b/src/XrdHttp/XrdHttpExtHandler.hh index 7ddb2106cf4..06fd468ac07 100644 --- a/src/XrdHttp/XrdHttpExtHandler.hh +++ b/src/XrdHttp/XrdHttpExtHandler.hh @@ -36,6 +36,8 @@ #include #include +#include "XrdNet/XrdNetPMark.hh" + class XrdLink; class XrdSecEntity; class XrdHttpReq; @@ -55,6 +57,10 @@ public: std::string clientdn, clienthost, clientgroups; long long length; + XrdNetPMark * pmark; + + int mSciTag; + // Get full client identifier void GetClientID(std::string &clid); diff --git a/src/XrdHttp/XrdHttpReq.cc b/src/XrdHttp/XrdHttpReq.cc index 68147831e6b..cd645ad7d40 100644 --- a/src/XrdHttp/XrdHttpReq.cc +++ b/src/XrdHttp/XrdHttpReq.cc @@ -225,22 +225,24 @@ int XrdHttpReq::parseHost(char *line) { } void XrdHttpReq::parseScitag(const std::string & val) { - int scitag = 0; + // The scitag header has been populated and the packet marking was configured, the scitag will either be equal to 0 + // or to the value passed by the client + mScitag = 0; std::string scitagS = val; trim(scitagS); if(scitagS.size()) { if(scitagS[0] != '-') { try { - scitag = std::stoi(scitagS.c_str(), nullptr, 10); - if (scitag > XrdNetPMark::maxTotID) { - scitag = 0; + mScitag = std::stoi(scitagS.c_str(), nullptr, 10); + if (mScitag > XrdNetPMark::maxTotID || mScitag < 0) { + mScitag = 0; } } catch (...) { //Nothing to do, scitag = 0 by default } } } - addCgi("scitag.flow", std::to_string(scitag)); + addCgi("scitag.flow", std::to_string(mScitag)); } int XrdHttpReq::parseFirstLine(char *line, int len) { diff --git a/src/XrdHttp/XrdHttpReq.hh b/src/XrdHttp/XrdHttpReq.hh index d99b42e14a9..b88d3ab4588 100644 --- a/src/XrdHttp/XrdHttpReq.hh +++ b/src/XrdHttp/XrdHttpReq.hh @@ -159,6 +159,7 @@ public: writtenbytes = 0; fopened = false; headerok = false; + mScitag = -1; }; virtual ~XrdHttpReq(); @@ -303,6 +304,8 @@ public: /// In a long write, we track where we have arrived long long writtenbytes; + int mScitag; + diff --git a/src/XrdTpc.cmake b/src/XrdTpc.cmake index 16c1106f06a..cbe22019b83 100644 --- a/src/XrdTpc.cmake +++ b/src/XrdTpc.cmake @@ -38,7 +38,8 @@ if( BUILD_TPC ) XrdTpc/XrdTpcCurlMulti.cc XrdTpc/XrdTpcCurlMulti.hh XrdTpc/XrdTpcState.cc XrdTpc/XrdTpcState.hh XrdTpc/XrdTpcStream.cc XrdTpc/XrdTpcStream.hh - XrdTpc/XrdTpcTPC.cc XrdTpc/XrdTpcTPC.hh) + XrdTpc/XrdTpcTPC.cc XrdTpc/XrdTpcTPC.hh + XrdTpc/PMarkManager.cc XrdTpc/PMarkManager.hh) target_link_libraries( ${LIB_XRD_TPC} diff --git a/src/XrdTpc/PMarkManager.cc b/src/XrdTpc/PMarkManager.cc new file mode 100644 index 00000000000..796033385bd --- /dev/null +++ b/src/XrdTpc/PMarkManager.cc @@ -0,0 +1,72 @@ +//------------------------------------------------------------------------------ +// This file is part of XrdTpcTPC +// +// Copyright (c) 2023 by European Organization for Nuclear Research (CERN) +// Author: Cedric Caffy +// File Date: Oct 2023 +//------------------------------------------------------------------------------ +// XRootD is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// XRootD is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with XRootD. If not, see . +//------------------------------------------------------------------------------ + + +#include +#include "PMarkManager.hh" + +PMarkManager::SocketInfo::SocketInfo(int fd, const struct sockaddr * sockP) { + netAddr.Set(sockP,fd); + client.addrInfo = static_cast(&netAddr); +} + +PMarkManager::PMarkManager(XrdNetPMark *pmark) : mPmark(pmark), mTransferWillStart(false) {} + +void PMarkManager::addFd(int fd, const struct sockaddr * sockP) { + if(mPmark && mTransferWillStart && mReq->mSciTag >= 0) { + // The transfer will start and the packet marking has been configured, this socket must be registered for future packet marking + mSocketInfos.emplace(fd, sockP); + } +} + +void PMarkManager::startTransfer(XrdHttpExtReq * req) { + mReq = req; + mTransferWillStart = true; +} + +void PMarkManager::beginPMarks() { + if(!mSocketInfos.empty() && mPmarkHandles.empty()) { + // Create the first pmark handle that will be used as a basis for the other handles + // if that handle cannot be created (mPmark->Begin() would return nullptr), then the packet marking will not work + // This base pmark handle will be placed at the beginning of the vector of pmark handles + std::stringstream ss; + ss << "scitag.flow=" << mReq->mSciTag; + auto sockInfo = mSocketInfos.front(); + mInitialFD = sockInfo.client.addrInfo->SockFD(); + mPmarkHandles.emplace(mInitialFD,mPmark->Begin(sockInfo.client, mReq->resource.c_str(), ss.str().c_str(), "http-tpc")); + mSocketInfos.pop(); + } else { + // The first pmark handle was created, or not. Create the other pmark handles from the other connected sockets + while(!mSocketInfos.empty()) { + auto & sockInfo = mSocketInfos.front(); + if(mPmarkHandles[mInitialFD]){ + mPmarkHandles.emplace(sockInfo.client.addrInfo->SockFD(),mPmark->Begin(*sockInfo.client.addrInfo, *mPmarkHandles[mInitialFD], nullptr)); + } + mSocketInfos.pop(); + } + } +} + +void PMarkManager::endPmark(int fd) { + // We need to delete the PMark handle associated to the fd passed in parameter + // we just look for it and reset the unique_ptr to nullptr to trigger the PMark handle deletion + mPmarkHandles.erase(fd); +} \ No newline at end of file diff --git a/src/XrdTpc/PMarkManager.hh b/src/XrdTpc/PMarkManager.hh new file mode 100644 index 00000000000..3c166f413d4 --- /dev/null +++ b/src/XrdTpc/PMarkManager.hh @@ -0,0 +1,113 @@ +//------------------------------------------------------------------------------ +// This file is part of XrdTpcTPC +// +// Copyright (c) 2023 by European Organization for Nuclear Research (CERN) +// Author: Cedric Caffy +// File Date: Oct 2023 +//------------------------------------------------------------------------------ +// XRootD is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// XRootD is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with XRootD. If not, see . +//------------------------------------------------------------------------------ +#ifndef XROOTD_PMARKMANAGER_HH +#define XROOTD_PMARKMANAGER_HH + +#include "XrdNet/XrdNetPMark.hh" +#include "XrdSec/XrdSecEntity.hh" +#include "XrdNet/XrdNetAddrInfo.hh" +#include "XrdNet/XrdNetAddr.hh" +#include "XrdHttp/XrdHttpExtHandler.hh" + +#include +#include +#include + +/** + * This class will manage packet marking handles for TPC transfers + * + * Each time a socket will be opened by curl (via the opensocket_callback), the manager + * will register the related information to the socket. + * + * Once the transfer will start we will start the packet marking by creating XrdNetPMark::Handle + * objects from the socket information previously registered. + * + * In the case of multi-stream HTTP TPC transfers, a packet marking handle will be created for each stream. + * The first one will be created as a basic one. The other will be created using the first packet marking handle as a basis. + */ +class PMarkManager { +public: + + /** + * This class allows to create and keep a XrdSecEntity object + * from the socket file descriptor and address + * Everything is done on the constructor + * + * These infos will be used later on when we create new PMark handles + */ + class SocketInfo { + public: + SocketInfo(int fd, const struct sockaddr * sockP); + XrdNetAddr netAddr; + XrdSecEntity client; + }; + + PMarkManager(XrdNetPMark * pmark); + /** + * Add the connected socket information that will be used for packet marking to this manager class + * Note: these info will only be added if startTransfer(...) has been called. It allows + * to ensure that the connection will be related to the data transfers and not for anything else. We only want + * to mark the traffic of the transfers. + * @param fd the socket file descriptor + * @param sockP the structure describing the address of the socket + */ + void addFd(int fd, const struct sockaddr * sockP); + + /** + * Calling this function will indicate that the connections that will happen will be related to the + * data transfer. The addFd(...) function will then register any socket that is created after this function + * will be called. + * @param req the request object that will be used later on to get some information about the transfer + */ + void startTransfer(XrdHttpExtReq * req); + /** + * Creates the different packet marking handles allowing to mark the transfer packets + * + * Call this after the curl_multi_perform() has been called. + */ + void beginPMarks(); + + /** + * This function deletes the PMark handle associated to the fd passed in parameter + * Use this before closing the associated socket! Otherwise the information contained in the firefly + * (e.g sent bytes or received bytes) will have values equal to 0. + * @param fd the fd of the socket to be closed + */ + void endPmark(int fd); + + virtual ~PMarkManager() = default; +private: + // The queue of socket information from which we will create the packet marking handles + std::queue mSocketInfos; + // The map of socket FD and packet marking handles + std::map> mPmarkHandles; + // The instance of the packet marking functionality + XrdNetPMark * mPmark; + // Is true when startTransfer(...) has been called + bool mTransferWillStart; + // The XrdHttpTPC request information + XrdHttpExtReq * mReq; + // The file descriptor used to create the first packet marking handle + int mInitialFD = -1; +}; + + +#endif //XROOTD_PMARKMANAGER_HH diff --git a/src/XrdTpc/XrdTpcMultistream.cc b/src/XrdTpc/XrdTpcMultistream.cc index 973eae88636..71cc990f99f 100644 --- a/src/XrdTpc/XrdTpcMultistream.cc +++ b/src/XrdTpc/XrdTpcMultistream.cc @@ -281,6 +281,9 @@ int TPCHandler::RunCurlWithStreamsImpl(XrdHttpExtReq &req, State &state, curl_handles.emplace_back(handles.back()->GetHandle()); } + // Notify the packet marking manager that the transfer will start after this point + rec.pmarkManager.startTransfer(&req); + // Create the multi-handle and add in the current transfer to it. MultiCurlHandler mch(handles, m_log); CURLM *multi_handle = mch.Get(); @@ -347,6 +350,9 @@ int TPCHandler::RunCurlWithStreamsImpl(XrdHttpExtReq &req, State &state, break; } + rec.pmarkManager.beginPMarks(); + + // Harvest any messages, looking for CURLMSG_DONE. CURLMsg *msg; do { diff --git a/src/XrdTpc/XrdTpcTPC.cc b/src/XrdTpc/XrdTpcTPC.cc index 6d327806bcd..0a2eb5db987 100644 --- a/src/XrdTpc/XrdTpcTPC.cc +++ b/src/XrdTpc/XrdTpcTPC.cc @@ -119,18 +119,34 @@ int TPCHandler::opensocket_callback(void *clientp, curlsocktype purpose, struct curl_sockaddr *aInfo) { -// See what kind of address will be used to connect -// -if (purpose == CURLSOCKTYPE_IPCXN && clientp) - {XrdNetAddr thePeer(&(aInfo->addr)); - ((TPCLogRecord *)clientp)->isIPv6 = (thePeer.isIPType(XrdNetAddrInfo::IPv6) - && !thePeer.isMapped()); - } + //Return a socket file descriptor (note the clo_exec flag will be set). + int fd = XrdSysFD_Socket(aInfo->family, aInfo->socktype, aInfo->protocol); + // See what kind of address will be used to connect + // + if(fd < 0) { + return CURL_SOCKET_BAD; + } + TPCLogRecord * rec = (TPCLogRecord *)clientp; + if (purpose == CURLSOCKTYPE_IPCXN && clientp) + {XrdNetAddr thePeer(&(aInfo->addr)); + rec->isIPv6 = (thePeer.isIPType(XrdNetAddrInfo::IPv6) + && !thePeer.isMapped()); + // Register the socket to the packet marking manager + rec->pmarkManager.addFd(fd,&aInfo->addr); + } + + return fd; +} -// Return a socket file descriptor (note the clo_exec flag will be set). -// - int fd = XrdSysFD_Socket(aInfo->family, aInfo->socktype, aInfo->protocol); - return (fd >= 0 ? fd : CURL_SOCKET_BAD); +int TPCHandler::closesocket_callback(void *clientp, curl_socket_t fd) { + TPCLogRecord * rec = (TPCLogRecord *)clientp; + + // Destroy the PMark handle associated to the file descriptor before closing it. + // Otherwise, we would lose the socket usage information if the socket is closed before + // the PMark handle is closed. + rec->pmarkManager.endPmark(fd); + + return close(fd); } /******************************************************************************/ @@ -644,6 +660,8 @@ int TPCHandler::RunCurlWithUpdates(CURL *curl, XrdHttpExtReq &req, State &state, } last_marker = now; } + // The transfer will start after this point, notify the packet marking manager + rec.pmarkManager.startTransfer(&req); mres = curl_multi_perform(multi_handle, &running_handles); if (mres == CURLM_CALL_MULTI_PERFORM) { // curl_multi_perform should be called again immediately. On newer @@ -654,6 +672,8 @@ int TPCHandler::RunCurlWithUpdates(CURL *curl, XrdHttpExtReq &req, State &state, } else if (running_handles == 0) { break; } + + rec.pmarkManager.beginPMarks(); //printf("There are %d running handles\n", running_handles); // Harvest any messages, looking for CURLMSG_DONE. @@ -828,7 +848,7 @@ int TPCHandler::RunCurlBasic(CURL *curl, XrdHttpExtReq &req, State &state, /******************************************************************************/ int TPCHandler::ProcessPushReq(const std::string & resource, XrdHttpExtReq &req) { - TPCLogRecord rec; + TPCLogRecord rec(req.pmark); rec.log_prefix = "PushRequest"; rec.local = req.resource; rec.remote = resource; @@ -849,6 +869,8 @@ int TPCHandler::ProcessPushReq(const std::string & resource, XrdHttpExtReq &req) // curl_easy_setopt(curl, CURLOPT_SOCKOPTFUNCTION, sockopt_setcloexec_callback); curl_easy_setopt(curl, CURLOPT_OPENSOCKETFUNCTION, opensocket_callback); curl_easy_setopt(curl, CURLOPT_OPENSOCKETDATA, &rec); + curl_easy_setopt(curl, CURLOPT_CLOSESOCKETFUNCTION, closesocket_callback); + curl_easy_setopt(curl, CURLOPT_CLOSESOCKETDATA, &rec); auto query_header = req.headers.find("xrd-http-fullresource"); std::string redirect_resource = req.resource; if (query_header != req.headers.end()) { @@ -908,7 +930,7 @@ int TPCHandler::ProcessPushReq(const std::string & resource, XrdHttpExtReq &req) /******************************************************************************/ int TPCHandler::ProcessPullReq(const std::string &resource, XrdHttpExtReq &req) { - TPCLogRecord rec; + TPCLogRecord rec(req.pmark); rec.log_prefix = "PullRequest"; rec.local = req.resource; rec.remote = resource; @@ -929,6 +951,8 @@ int TPCHandler::ProcessPullReq(const std::string &resource, XrdHttpExtReq &req) // curl_easy_setopt(curl,CURLOPT_SOCKOPTFUNCTION,sockopt_setcloexec_callback); curl_easy_setopt(curl, CURLOPT_OPENSOCKETFUNCTION, opensocket_callback); curl_easy_setopt(curl, CURLOPT_OPENSOCKETDATA, &rec); + curl_easy_setopt(curl, CURLOPT_CLOSESOCKETFUNCTION, closesocket_callback); + curl_easy_setopt(curl, CURLOPT_CLOSESOCKETDATA, &rec); std::unique_ptr fh(m_sfs->newFile(name, m_monid++)); if (!fh.get()) { char msg[] = "Failed to initialize internal transfer file handle"; diff --git a/src/XrdTpc/XrdTpcTPC.hh b/src/XrdTpc/XrdTpcTPC.hh index 5b8090d5bf9..4356299dba7 100644 --- a/src/XrdTpc/XrdTpcTPC.hh +++ b/src/XrdTpc/XrdTpcTPC.hh @@ -10,6 +10,7 @@ #include "XrdHttp/XrdHttpUtils.hh" #include "XrdTls/XrdTlsTempCA.hh" +#include "PMarkManager.hh" #include @@ -55,10 +56,12 @@ private: curlsocktype purpose, struct curl_sockaddr *address); + static int closesocket_callback(void *clientp, curl_socket_t fd); + struct TPCLogRecord { - TPCLogRecord() : bytes_transferred( -1 ), status( -1 ), - tpc_status(-1), streams( 1 ), isIPv6(false) + TPCLogRecord(XrdNetPMark * pmark) : bytes_transferred( -1 ), status( -1 ), + tpc_status(-1), streams( 1 ), isIPv6(false), pmarkManager(pmark) { gettimeofday(&begT, 0); // Set effective start time } @@ -76,6 +79,7 @@ private: int tpc_status; unsigned int streams; bool isIPv6; + PMarkManager pmarkManager; }; int ProcessOptionsReq(XrdHttpExtReq &req); From 062e58f012347f6c9e7eae9f637aa1ad34a8e0f3 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 6 Oct 2023 11:18:05 +0200 Subject: [PATCH 266/442] [XrdTpc] Differentiate error messages for push/pull TPC transfer modes Fixes: #2060 --- src/XrdTpc/XrdTpcMultistream.cc | 7 ++++++- src/XrdTpc/XrdTpcTPC.cc | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/XrdTpc/XrdTpcMultistream.cc b/src/XrdTpc/XrdTpcMultistream.cc index 71cc990f99f..aefc729383e 100644 --- a/src/XrdTpc/XrdTpcMultistream.cc +++ b/src/XrdTpc/XrdTpcMultistream.cc @@ -332,9 +332,14 @@ int TPCHandler::RunCurlWithStreamsImpl(XrdHttpExtReq &req, State &state, } int timeout = (transfer_start == last_advance_time) ? m_first_timeout : m_timeout; if (now > last_advance_time + timeout) { + const char *log_prefix = rec.log_prefix.c_str(); + bool tpc_pull = strncmp("Pull", log_prefix, 4) == 0; + mch.SetErrorCode(10); std::stringstream ss; - ss << "Transfer failed because no bytes have been received in " << timeout << " seconds."; + ss << "Transfer failed because no bytes have been " + << (tpc_pull ? "received from the source (pull mode) in " + : "transmitted to the destination (push mode) in ") << timeout << " seconds."; mch.SetErrorMessage(ss.str()); break; } diff --git a/src/XrdTpc/XrdTpcTPC.cc b/src/XrdTpc/XrdTpcTPC.cc index 0a2eb5db987..88434b0b38e 100644 --- a/src/XrdTpc/XrdTpcTPC.cc +++ b/src/XrdTpc/XrdTpcTPC.cc @@ -650,9 +650,14 @@ int TPCHandler::RunCurlWithUpdates(CURL *curl, XrdHttpExtReq &req, State &state, } int timeout = (transfer_start == last_advance_time) ? m_first_timeout : m_timeout; if (now > last_advance_time + timeout) { + const char *log_prefix = rec.log_prefix.c_str(); + bool tpc_pull = strncmp("Pull", log_prefix, 4) == 0; + state.SetErrorCode(10); std::stringstream ss; - ss << "Transfer failed because no bytes have been received in " << timeout << " seconds."; + ss << "Transfer failed because no bytes have been " + << (tpc_pull ? "received from the source (pull mode) in " + : "transmitted to the destination (push mode) in ") << timeout << " seconds."; state.SetErrorMessage(ss.str()); curl_multi_remove_handle(multi_handle, curl); curl_multi_cleanup(multi_handle); From 8eff8a4ccd2bfc5abb6a9fd215d43235eecd95f1 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 19 Oct 2023 09:18:08 +0200 Subject: [PATCH 267/442] [Tests] Skip xrdfs query checksum tests on unsupported filesystems Issue: #2096 Bug: https://bugs.gentoo.org/915073 --- tests/XRootD/smoke.sh | 43 +++++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/tests/XRootD/smoke.sh b/tests/XRootD/smoke.sh index eb4c71e486c..58e0f93f0f7 100755 --- a/tests/XRootD/smoke.sh +++ b/tests/XRootD/smoke.sh @@ -73,24 +73,31 @@ done # check that all checksums for downloaded files match for i in $FILES; do - REF32C=$(${CRC32C} < ${TMPDIR}/${i}.ref | cut -d' ' -f1) - NEW32C=$(${CRC32C} < ${TMPDIR}/${i}.dat | cut -d' ' -f1) - SRV32C=$(${XRDFS} ${HOST} query checksum ${TMPDIR}/${i}.ref?cks.type=crc32c | cut -d' ' -f2) - - REFA32=$(${ADLER32} < ${TMPDIR}/${i}.ref | cut -d' ' -f1) - NEWA32=$(${ADLER32} < ${TMPDIR}/${i}.dat | cut -d' ' -f1) - SRVA32=$(${XRDFS} ${HOST} query checksum ${TMPDIR}/${i}.ref?cks.type=adler32 | cut -d' ' -f2) - echo "${i}: crc32c: reference: ${REF32C}, server: ${SRV32C}, downloaded: ${REF32C}" - echo "${i}: adler32: reference: ${NEWA32}, server: ${SRVA32}, downloaded: ${NEWA32}" - - if [[ "${NEWA32}" != "${REFA32}" || "${SRVA32}" != "${REFA32}" ]]; then - echo 1>&2 "$(basename $0): error: adler32 checksum check failed for file: ${i}.dat" - exit 1 - fi - if [[ "${NEW32C}" != "${REF32C}" || "${SRV32C}" != "${REF32C}" ]]; then - echo 1>&2 "$(basename $0): error: crc32 checksum check failed for file: ${i}.dat" - exit 1 - fi + REF32C=$(${CRC32C} < ${TMPDIR}/${i}.ref | cut -d' ' -f1) + NEW32C=$(${CRC32C} < ${TMPDIR}/${i}.dat | cut -d' ' -f1) + + REFA32=$(${ADLER32} < ${TMPDIR}/${i}.ref | cut -d' ' -f1) + NEWA32=$(${ADLER32} < ${TMPDIR}/${i}.dat | cut -d' ' -f1) + + if setfattr -n user.checksum -v ${REF32C} ${TMPDIR}/${i}.ref; then + SRV32C=$(${XRDFS} ${HOST} query checksum ${TMPDIR}/${i}.ref?cks.type=crc32c | cut -d' ' -f2) + SRVA32=$(${XRDFS} ${HOST} query checksum ${TMPDIR}/${i}.ref?cks.type=adler32 | cut -d' ' -f2) + else + echo "Extended attributes not supported, using downloaded checksums for server checks" + SRV32C=${NEW32C} SRVA32=${NEWA32} # use downloaded file checksum if xattr not supported + fi + + if [[ "${NEWA32}" != "${REFA32}" || "${SRVA32}" != "${REFA32}" ]]; then + echo 1>&2 "$(basename $0): error: adler32 checksum check failed for file: ${i}.dat" + echo 1>&2 "${i}: adler32: reference: ${REFA32}, server: ${SRVA32}, downloaded: ${NEWA32}" + exit 1 + fi + + if [[ "${NEW32C}" != "${REF32C}" || "${SRV32C}" != "${REF32C}" ]]; then + echo 1>&2 "$(basename $0): error: crc32 checksum check failed for file: ${i}.dat" + echo 1>&2 "${i}: crc32c: reference: ${REF32C}, server: ${SRV32C}, downloaded: ${NEW32C}" + exit 1 + fi done for i in $FILES; do From bfb2280daa646ceab614ffebab630b57d756b531 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 27 Oct 2023 13:45:05 +0200 Subject: [PATCH 268/442] XRootD 5.6.3 --- docs/ReleaseNotes.txt | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/docs/ReleaseNotes.txt b/docs/ReleaseNotes.txt index ea39a26f149..5cebf989090 100644 --- a/docs/ReleaseNotes.txt +++ b/docs/ReleaseNotes.txt @@ -5,6 +5,37 @@ XRootD Release Notes ============= +------------- +Version 5.6.3 +------------- + ++ **Minor bug fixes** + **[CMake]** Export project version in CMake config (issue #2094) + **[CMake]** Find only XRootD matching XRootDConfig.cmake installation path + **[Python]** Do not use PEP517 by default, not supported on CentOS 7 + **[Server]** Call tzset() early to ensure thread-safety of localtime_r() and mktime() (issue #2107) + **[Server]** Correct maximum exp/act value in XrdNetPMark::getEA + **[Server]** Create environment file within adminpath (issue #2106) + **[Server]** Fix incorrect patch for authfile parsing (issue #2088) + **[Tests]** Skip server checksum query test on unsupported filesystems (issue #2096) + **[XrdCl]** Return an error if xrdfs rm fails to delete any file (issue #2097) + **[XrdCms]** Try to load blacklist even if some entries are invalid (issue #2092) + **[XrdEc]** Wait for pipeline including XrdCl::AppendFile() to finish (issue #2050) + **[XrdHttp]** Fix parsing of chunked PUT lengths (#2102, #2103) + ++ **Miscellaneous** + **[CMake]** Add extra debugging messages in XRootDConfig.cmake + **[CMake]** Handle components using more standard method + **[Misc]** Fix spelling errors reported by lintian (#2087) + **[Python]** Convert pyxrootd installation instructions to rst + **[Server]** Export ptr to full TLS context into the Xrd env + **[XrdCeph]** Align CMake requirement with main CMakeLists.txt + **[XrdHttp]** Implemented HTTP TPC Packet Marking (#2109) + **[XrdHttp]** Parse headers provided by the client in case-insensitive way when matching header2cgi keys (#2101) + **[XrdHttp]** Promote SciTag header if packet marking has been configured on the server (#2101) + **[XrdSciTokens]** Use configured CA path in SciTokens plugin if supported (#2095, #2112) + **[XrdTpc]** Differentiate error messages for push/pull TPC transfer modes (issue #2060) + ------------- Version 5.6.2 ------------- From f3b2e86b9b80bb35f97dd4ad30c4cd5904902a4c Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 27 Oct 2023 16:49:39 +0200 Subject: [PATCH 269/442] [XrdNet] Add XrdNetPMark.hh to set of public headers --- src/XrdHeaders.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/src/XrdHeaders.cmake b/src/XrdHeaders.cmake index 0570d07b7b2..5117772a2c2 100644 --- a/src/XrdHeaders.cmake +++ b/src/XrdHeaders.cmake @@ -21,6 +21,7 @@ set( XROOTD_PUBLIC_HEADERS XrdNet/XrdNetCmsNotify.hh XrdNet/XrdNetConnect.hh XrdNet/XrdNetOpts.hh + XrdNet/XrdNetPMark.hh XrdNet/XrdNetSockAddr.hh XrdNet/XrdNetSocket.hh XrdOuc/XrdOucBuffer.hh From 39b23f1ce3069e06dbb81ab60157e8f346c7629b Mon Sep 17 00:00:00 2001 From: Brian Bockelman Date: Sat, 25 Nov 2023 07:43:39 -0600 Subject: [PATCH 270/442] Use a monotonically-increasing counter for the link ID Instead of using the client-provided PID (which may not be unique), use the session ID counter. This allows the tuple of the (PID, server ID) to be unique in the produced monitoring packets (until the 32-bit unsigned integer rolls over). Adds a comment noting which `SID` is being used as there are three different definitions for the `S` (server, session, stream) in the codebase. --- src/XrdXrootd/XrdXrootdXeq.cc | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/XrdXrootd/XrdXrootdXeq.cc b/src/XrdXrootd/XrdXrootdXeq.cc index f7ef8fc22b9..2e32dd9a11a 100644 --- a/src/XrdXrootd/XrdXrootdXeq.cc +++ b/src/XrdXrootd/XrdXrootdXeq.cc @@ -948,7 +948,7 @@ int XrdXrootdProtocol::do_Login() { XrdXrootdSessID sessID; XrdNetAddrInfo *addrP; - int i, pid, rc, sendSID = 0; + int i, rc, sendSID = 0; char uname[sizeof(Request.login.username)+1]; // Keep Statistics @@ -966,9 +966,8 @@ int XrdXrootdProtocol::do_Login() return Response.Send(kXR_TLSRequired, emsg); } -// Unmarshall the pid and construct username using the POSIX.1-2008 standard +// Unmarshall and construct username using the POSIX.1-2008 standard // - pid = (int)ntohl(Request.login.pid); strncpy(uname, (const char *)Request.login.username, sizeof(uname)-1); uname[sizeof(uname)-1] = 0; XrdOucUtils::Sanitize(uname); @@ -979,8 +978,15 @@ int XrdXrootdProtocol::do_Login() "duplicate login; already logged in"); // Establish the ID for this link -// - Link->setID(uname, pid); +// Note: the 'SID' here is a session ID and different from the 'SID' (server ID) +// used in the monitoring and generated by the `genSID` function for `XrdOucUtils::Ident`. +// It's also different than the SID (stream ID) created in XrdCl. +// It was previously set to the `PID` value provided by the client. However, the monitoring +// originally was designed so the tuple (session ID, server ID) is unique so this was switched +// to a unique (until 32-bit rollover), server-controlled value. +// + mySID = getSID(); + Link->setID(uname, mySID); CapVer = Request.login.capver[0]; // Establish the session ID if the client can handle it (protocol version > 0) @@ -989,7 +995,6 @@ int XrdXrootdProtocol::do_Login() {sessID.FD = Link->FDnum(); sessID.Inst = Link->Inst(); sessID.Pid = myPID; - mySID = getSID(); sessID.Sid = mySID; sendSID = 1; if (!clientPV) From aea36b4f2b34185c4c1a45ec084a88c22fa45e16 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 28 Nov 2023 16:42:10 +0100 Subject: [PATCH 271/442] Revert "Use a monotonically-increasing counter for the link ID" This reverts commit 39b23f1ce3069e06dbb81ab60157e8f346c7629b. Breaks TPC transfers where the server calls the client to perform the actual transfer. --- src/XrdXrootd/XrdXrootdXeq.cc | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/XrdXrootd/XrdXrootdXeq.cc b/src/XrdXrootd/XrdXrootdXeq.cc index 2e32dd9a11a..f7ef8fc22b9 100644 --- a/src/XrdXrootd/XrdXrootdXeq.cc +++ b/src/XrdXrootd/XrdXrootdXeq.cc @@ -948,7 +948,7 @@ int XrdXrootdProtocol::do_Login() { XrdXrootdSessID sessID; XrdNetAddrInfo *addrP; - int i, rc, sendSID = 0; + int i, pid, rc, sendSID = 0; char uname[sizeof(Request.login.username)+1]; // Keep Statistics @@ -966,8 +966,9 @@ int XrdXrootdProtocol::do_Login() return Response.Send(kXR_TLSRequired, emsg); } -// Unmarshall and construct username using the POSIX.1-2008 standard +// Unmarshall the pid and construct username using the POSIX.1-2008 standard // + pid = (int)ntohl(Request.login.pid); strncpy(uname, (const char *)Request.login.username, sizeof(uname)-1); uname[sizeof(uname)-1] = 0; XrdOucUtils::Sanitize(uname); @@ -978,15 +979,8 @@ int XrdXrootdProtocol::do_Login() "duplicate login; already logged in"); // Establish the ID for this link -// Note: the 'SID' here is a session ID and different from the 'SID' (server ID) -// used in the monitoring and generated by the `genSID` function for `XrdOucUtils::Ident`. -// It's also different than the SID (stream ID) created in XrdCl. -// It was previously set to the `PID` value provided by the client. However, the monitoring -// originally was designed so the tuple (session ID, server ID) is unique so this was switched -// to a unique (until 32-bit rollover), server-controlled value. -// - mySID = getSID(); - Link->setID(uname, mySID); +// + Link->setID(uname, pid); CapVer = Request.login.capver[0]; // Establish the session ID if the client can handle it (protocol version > 0) @@ -995,6 +989,7 @@ int XrdXrootdProtocol::do_Login() {sessID.FD = Link->FDnum(); sessID.Inst = Link->Inst(); sessID.Pid = myPID; + mySID = getSID(); sessID.Sid = mySID; sendSID = 1; if (!clientPV) From fe2224d33b0c310e19d497e6e795f779c8e88001 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 30 Nov 2023 10:02:03 +0100 Subject: [PATCH 272/442] Reapply "[XrdCl] Make sure error message does not include a null-character." This reverts commit 5a832596a59d1055f59620d43fbb740955ce6787. Even though commit 9987b4b41990df69ae611a523acb76c69eeccbbb did fix an error message that had a second null termination character, reverting this change was a mistake, as this change is actually still needed to avoid the single terminating null byte in error messages from appearing in the output of the client. Closes: #2138 --- src/XrdCl/XrdClXRootDMsgHandler.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/XrdCl/XrdClXRootDMsgHandler.cc b/src/XrdCl/XrdClXRootDMsgHandler.cc index 1bd60a3e336..81e41d21bb1 100644 --- a/src/XrdCl/XrdClXRootDMsgHandler.cc +++ b/src/XrdCl/XrdClXRootDMsgHandler.cc @@ -1202,7 +1202,9 @@ namespace XrdCl if( pStatus.code == errErrorResponse ) { st->errNo = rsp->body.error.errnum; - std::string errmsg( rsp->body.error.errmsg, rsp->hdr.dlen-4 ); + // omit the last character as the string returned from the server + // (acording to protocol specs) should be null-terminated + std::string errmsg( rsp->body.error.errmsg, rsp->hdr.dlen-5 ); if( st->errNo == kXR_noReplicas && !pLastError.IsOK() ) errmsg += " Last seen error: " + pLastError.ToString(); st->SetErrorMessage( errmsg ); From 8a4e7a387c01041e13fa31e221f43777be99315d Mon Sep 17 00:00:00 2001 From: Angelo Galavotti Date: Wed, 5 Jul 2023 15:47:05 +0000 Subject: [PATCH 273/442] [Tests] Add converted PollerTest Converts the PollerTest from CPPUnit to GTest --- tests/XrdCl/CMakeLists.txt | 7 +- tests/XrdCl/GTestXrdHelpers.hh | 31 ++++ tests/XrdCl/XrdClPoller.cc | 263 +++++++++++++++++++++++++++++++++ 3 files changed, 300 insertions(+), 1 deletion(-) create mode 100644 tests/XrdCl/GTestXrdHelpers.hh create mode 100644 tests/XrdCl/XrdClPoller.cc diff --git a/tests/XrdCl/CMakeLists.txt b/tests/XrdCl/CMakeLists.txt index a114c4cb620..d65d8101dbe 100644 --- a/tests/XrdCl/CMakeLists.txt +++ b/tests/XrdCl/CMakeLists.txt @@ -1,18 +1,23 @@ add_executable(xrdcl-unit-tests XrdClURL.cc + XrdClPoller.cc + ../common/Server.cc + ../common/Utils.cc + ../common/TestEnv.cc ) target_link_libraries(xrdcl-unit-tests XrdCl XrdXml XrdUtils + ZLIB::ZLIB GTest::GTest GTest::Main ) target_include_directories(xrdcl-unit-tests - PRIVATE ${CMAKE_SOURCE_DIR}/src + PRIVATE ${CMAKE_SOURCE_DIR}/src ../common ) gtest_discover_tests(xrdcl-unit-tests TEST_PREFIX XrdCl::) diff --git a/tests/XrdCl/GTestXrdHelpers.hh b/tests/XrdCl/GTestXrdHelpers.hh new file mode 100644 index 00000000000..fb522967a2e --- /dev/null +++ b/tests/XrdCl/GTestXrdHelpers.hh @@ -0,0 +1,31 @@ +//------------------------------------------------------------------------------ +// Copyright (c) 2023 by European Organization for Nuclear Research (CERN) +// Author: Angelo Galavotti +//------------------------------------------------------------------------------ +// XRootD is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// XRootD is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with XRootD. If not, see . +//------------------------------------------------------------------------------ + +#ifndef __GTEST_XRD_HELPERS_HH__ +#define __GTEST_XRD_HELPERS_HH__ + +#include +#include +#include +#include + +#define GTEST_ASSERT_XRDST( x ) \ +{ \ + XrdCl::XRootDStatus _st = x; \ + EXPECT_TRUE(_st.IsOK()) << "[" << #x << "]: " << _st.ToStr() << std::endl; \ +} diff --git a/tests/XrdCl/XrdClPoller.cc b/tests/XrdCl/XrdClPoller.cc new file mode 100644 index 00000000000..fdd3ae5ddb8 --- /dev/null +++ b/tests/XrdCl/XrdClPoller.cc @@ -0,0 +1,263 @@ +//------------------------------------------------------------------------------ +// Copyright (c) 2023 by European Organization for Nuclear Research (CERN) +// Author: Angelo Galavotti +//------------------------------------------------------------------------------ +// XRootD is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// XRootD is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with XRootD. If not, see . +//------------------------------------------------------------------------------ + +#include "XrdCl/XrdClPoller.hh" +#include "GTestXrdHelpers.hh" +#include "Server.hh" +#include "Utils.hh" +#include "TestEnv.hh" +#include "XrdCl/XrdClURL.hh" +#include "XrdCl/XrdClUtils.hh" +#include "XrdCl/XrdClSocket.hh" +#include + +#include + + +#include "XrdCl/XrdClPollerBuiltIn.hh" + + +using namespace XrdClTests; +using namespace testing; +//------------------------------------------------------------------------------ +// Client handler +//------------------------------------------------------------------------------ +class RandomPumpHandler: public ClientHandler +{ + public: + //-------------------------------------------------------------------------- + // Pump some random data through the socket + //-------------------------------------------------------------------------- + virtual void HandleConnection( int socket ) + { + XrdCl::ScopedDescriptor scopetDesc( socket ); + XrdCl::Log *log = TestEnv::GetLog(); + + uint8_t packets = random() % 100; + uint16_t packetSize; + char buffer[50000]; + log->Debug( 1, "Sending %d packets to the client", packets ); + + for( int i = 0; i < packets; ++i ) + { + packetSize = random() % 50000; + log->Dump( 1, "Sending %d packet, %d bytes of data", i, packetSize ); + if( Utils::GetRandomBytes( buffer, packetSize ) != packetSize ) + { + log->Error( 1, "Unable to get %d bytes of random data", packetSize ); + return; + } + + if( ::write( socket, buffer, packetSize ) != packetSize ) + { + log->Error( 1, "Unable to send the %d bytes of random data", + packetSize ); + return; + } + UpdateSentData( buffer, packetSize ); + } + } +}; + +//------------------------------------------------------------------------------ +// Client handler factory +//------------------------------------------------------------------------------ +class RandomPumpHandlerFactory: public ClientHandlerFactory +{ + public: + virtual ClientHandler *CreateHandler() + { + return new RandomPumpHandler(); + } +}; + +//------------------------------------------------------------------------------ +// Socket listener +//------------------------------------------------------------------------------ +class SocketHandler: public XrdCl::SocketHandler +{ + public: + //-------------------------------------------------------------------------- + // Initializer + //-------------------------------------------------------------------------- + virtual void Initialize( XrdCl::Poller *poller ) + { + pPoller = poller; + } + + //-------------------------------------------------------------------------- + // Handle an event + //-------------------------------------------------------------------------- + virtual void Event( uint8_t type, + XrdCl::Socket *socket ) + { + //------------------------------------------------------------------------ + // Read event + //------------------------------------------------------------------------ + if( type & ReadyToRead ) + { + char buffer[50000]; + int desc = socket->GetFD(); + ssize_t ret = 0; + + while( 1 ) + { + char *current = buffer; + uint32_t spaceLeft = 50000; + while( (spaceLeft > 0) && + ((ret = ::read( desc, current, spaceLeft )) > 0) ) + { + current += ret; + spaceLeft -= ret; + } + + UpdateTransferMap( socket->GetSockName(), buffer, 50000-spaceLeft ); + + if( ret == 0 ) + { + pPoller->RemoveSocket( socket ); + return; + } + + if( ret < 0 ) + { + if( errno != EAGAIN && errno != EWOULDBLOCK ) + pPoller->EnableReadNotification( socket, false ); + return; + } + } + } + + //------------------------------------------------------------------------ + // Timeout + //------------------------------------------------------------------------ + if( type & ReadTimeOut ) + pPoller->RemoveSocket( socket ); + } + + //-------------------------------------------------------------------------- + // Update the checksums + //-------------------------------------------------------------------------- + void UpdateTransferMap( const std::string &sockName, + const void *buffer, + uint32_t size ) + { + //------------------------------------------------------------------------ + // Check if we have an entry in the map + //------------------------------------------------------------------------ + std::pair res; + Server::TransferMap::iterator it; + res = pMap.insert( std::make_pair( sockName, std::make_pair( 0, 0 ) ) ); + it = res.first; + if( res.second == true ) + { + it->second.first = 0; + it->second.second = Utils::ComputeCRC32( 0, 0 ); + } + + //------------------------------------------------------------------------ + // Update the entry + //------------------------------------------------------------------------ + it->second.first += size; + it->second.second = Utils::UpdateCRC32( it->second.second, buffer, size ); + } + + //-------------------------------------------------------------------------- + //! Get the stats of the received data + //-------------------------------------------------------------------------- + std::pair GetReceivedStats( + const std::string sockName ) const + { + Server::TransferMap::const_iterator it = pMap.find( sockName ); + if( it == pMap.end() ) + return std::make_pair( 0, 0 ); + return it->second; + } + + private: + Server::TransferMap pMap; + XrdCl::Poller *pPoller; +}; + +//------------------------------------------------------------------------------ +// Test the functionality the built-in poller +//------------------------------------------------------------------------------ + +class PollerTest : public ::testing::Test {}; + +TEST(PollerTest, FunctionTest) +{ + XrdCl::Poller *poller = new XrdCl::PollerBuiltIn(); // only uses built-in poller + + using XrdCl::Socket; + using XrdCl::URL; + + //---------------------------------------------------------------------------- + // Initialize + //---------------------------------------------------------------------------- + Server server( Server::Both ); + Socket s[3]; + EXPECT_TRUE( server.Setup( 9999, 3, new RandomPumpHandlerFactory() ) ); + EXPECT_TRUE( server.Start() ); + EXPECT_TRUE( poller->Initialize() ); + EXPECT_TRUE( poller->Start() ); + + //---------------------------------------------------------------------------- + // Connect the sockets + //---------------------------------------------------------------------------- + SocketHandler *handler = new SocketHandler(); + for( int i = 0; i < 3; ++i ) + { + GTEST_ASSERT_XRDST( s[i].Initialize() ); + GTEST_ASSERT_XRDST( s[i].Connect( "localhost", 9999 ) ); + EXPECT_TRUE( poller->AddSocket( &s[i], handler ) ); + EXPECT_TRUE( poller->EnableReadNotification( &s[i], true, 60 ) ); + EXPECT_TRUE( poller->IsRegistered( &s[i] ) ); + } + + //---------------------------------------------------------------------------- + // All the business happens elsewhere so we have nothing better to do + // here that wait, otherwise server->stop will hang. + //---------------------------------------------------------------------------- + ::sleep(5); + + //---------------------------------------------------------------------------- + // Cleanup + //---------------------------------------------------------------------------- + EXPECT_TRUE( poller->Stop() ); + EXPECT_TRUE( server.Stop() ); + EXPECT_TRUE( poller->Finalize() ); + + std::pair stats[3]; + std::pair statsServ[3]; + for( int i = 0; i < 3; ++i ) + { + EXPECT_TRUE( !poller->IsRegistered( &s[i] ) ); + stats[i] = handler->GetReceivedStats( s[i].GetSockName() ); + statsServ[i] = server.GetSentStats( s[i].GetSockName() ); + EXPECT_TRUE( stats[i].first == statsServ[i].first ); + EXPECT_TRUE( stats[i].second == statsServ[i].second ); + } + + for( int i = 0; i < 3; ++i ) + s[i].Close(); + + delete handler; + delete poller; +} + From 83b7d3d310049eff0faed9fd77ad33b984d97d6c Mon Sep 17 00:00:00 2001 From: Angelo Galavotti Date: Thu, 6 Jul 2023 12:18:12 +0000 Subject: [PATCH 274/442] [Tests] Add converted SocketTest Adds file with SocketTest converted from CPPUnit to GTest.. --- tests/XrdCl/CMakeLists.txt | 1 + tests/XrdCl/XrdClSocket.cc | 302 +++++++++++++++++++++++++++++++++++++ 2 files changed, 303 insertions(+) create mode 100644 tests/XrdCl/XrdClSocket.cc diff --git a/tests/XrdCl/CMakeLists.txt b/tests/XrdCl/CMakeLists.txt index d65d8101dbe..e89deaa84c5 100644 --- a/tests/XrdCl/CMakeLists.txt +++ b/tests/XrdCl/CMakeLists.txt @@ -2,6 +2,7 @@ add_executable(xrdcl-unit-tests XrdClURL.cc XrdClPoller.cc + XrdClSocket.cc ../common/Server.cc ../common/Utils.cc ../common/TestEnv.cc diff --git a/tests/XrdCl/XrdClSocket.cc b/tests/XrdCl/XrdClSocket.cc new file mode 100644 index 00000000000..981f3289ead --- /dev/null +++ b/tests/XrdCl/XrdClSocket.cc @@ -0,0 +1,302 @@ +//------------------------------------------------------------------------------ +// Copyright (c) 2023 by European Organization for Nuclear Research (CERN) +// Author: Angelo Galavotti +//------------------------------------------------------------------------------ +// XRootD is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// XRootD is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with XRootD. If not, see . +//------------------------------------------------------------------------------ + +#include +#include +#include +#include +#include +#include "Server.hh" +#include "Utils.hh" +#include "TestEnv.hh" +#include "XrdCl/XrdClSocket.hh" +#include "XrdCl/XrdClUtils.hh" + +using namespace XrdClTests; + +//------------------------------------------------------------------------------ +// Mock socket for testing +//------------------------------------------------------------------------------ +struct MockSocket : public XrdCl::Socket +{ + public: + + MockSocket() : size( sizeof( ServerResponseHeader ) + sizeof( ServerResponseBody_Protocol ) ), + buffer( reinterpret_cast( &response ) ), offset( 0 ), + random_engine( std::chrono::system_clock::now().time_since_epoch().count() ), + retrygen( 0, 9 ), + retry_threshold( retrygen( random_engine ) ) + { + response.hdr.status = kXR_ok; + response.hdr.streamid[0] = 1; + response.hdr.streamid[1] = 2; + response.hdr.dlen = htonl( sizeof( ServerResponseBody_Protocol ) ); + + response.body.protocol.flags = 123; + response.body.protocol.pval = 4567; + response.body.protocol.secreq.rsvd = 'A'; + response.body.protocol.secreq.seclvl = 'B'; + response.body.protocol.secreq.secopt = 'C'; + response.body.protocol.secreq.secver = 'D'; + response.body.protocol.secreq.secvsz = 'E'; + response.body.protocol.secreq.theTag = 'F'; + response.body.protocol.secreq.secvec.reqindx = 'G'; + response.body.protocol.secreq.secvec.reqsreq = 'H'; + } + + virtual XrdCl::XRootDStatus Read( char *outbuf, size_t rdsize, int &bytesRead ) + { + size_t btsleft = size - offset; + if( btsleft == 0 || nodata() ) + return XrdCl::XRootDStatus( XrdCl::stOK, XrdCl::suRetry ); + + if( rdsize > btsleft ) + rdsize = btsleft; + + std::uniform_int_distribution sizegen( 0, rdsize ); + rdsize = sizegen( random_engine ); + + if( rdsize == 0 ) + return XrdCl::XRootDStatus( XrdCl::stOK, XrdCl::suRetry ); + + memcpy( outbuf, buffer + offset, rdsize ); + offset += rdsize; + bytesRead = rdsize; + + return XrdCl::XRootDStatus(); + } + + virtual XrdCl::XRootDStatus Send( const char *buffer, size_t size, int &bytesWritten ) + { + return XrdCl::XRootDStatus( XrdCl::stError, XrdCl::errNotSupported ); + } + + inline bool IsEqual( XrdCl::Message &msg ) + { + response.hdr.dlen = ntohl( response.hdr.dlen ); + bool ok = ( memcmp( msg.GetBuffer(), &response, size ) == 0 ); + response.hdr.dlen = htonl( response.hdr.dlen ); + return ok; + } + + private: + + inline bool nodata() + { + size_t doretry = retrygen( random_engine ); + return doretry > retry_threshold; + } + + ServerResponse response; + const size_t size; + char *buffer; + size_t offset; + + std::default_random_engine random_engine; + std::uniform_int_distribution retrygen; + const size_t retry_threshold; +}; + +//------------------------------------------------------------------------------ +// Client handler +//------------------------------------------------------------------------------ +class RandomHandler: public ClientHandler +{ + public: + virtual void HandleConnection( int socket ) + { + XrdCl::ScopedDescriptor scopedDesc( socket ); + XrdCl::Log *log = TestEnv::GetLog(); + + //------------------------------------------------------------------------ + // Pump some data + //------------------------------------------------------------------------ + uint8_t packets = random() % 100; + uint16_t packetSize; + char buffer[50000]; + log->Debug( 1, "Sending %d packets to the client", packets ); + + if( ::Utils::Write( socket, &packets, 1 ) != 1 ) + { + log->Error( 1, "Unable to send the packet count" ); + return; + } + + for( int i = 0; i < packets; ++i ) + { + packetSize = random() % 50000; + log->Dump( 1, "Sending %d packet, %d bytes of data", i, packetSize ); + if( Utils::GetRandomBytes( buffer, packetSize ) != packetSize ) + { + log->Error( 1, "Unable to get %d bytes of random data", packetSize ); + return; + } + + if( ::Utils::Write( socket, &packetSize, 2 ) != 2 ) + { + log->Error( 1, "Unable to send the packet size" ); + return; + } + if( ::Utils::Write( socket, buffer, packetSize ) != packetSize ) + { + log->Error( 1, "Unable to send the %d bytes of random data", + packetSize ); + return; + } + UpdateSentData( buffer, packetSize ); + } + + //------------------------------------------------------------------------ + // Receive some data + //------------------------------------------------------------------------ + if( ::Utils::Read( socket, &packets, 1 ) != 1 ) + { + log->Error( 1, "Unable to receive the packet count" ); + return; + } + + log->Debug( 1, "Receivng %d packets from the client", packets ); + + for( int i = 0; i < packets; ++i ) + { + if( ::Utils::Read( socket, &packetSize, 2 ) != 2 ) + { + log->Error( 1, "Unable to receive the packet size" ); + return; + } + + if ( ::Utils::Read( socket, buffer, packetSize ) != packetSize ) + { + log->Error( 1, "Unable to receive the %d bytes of data", + packetSize ); + return; + } + UpdateReceivedData( buffer, packetSize ); + log->Dump( 1, "Received %d bytes from the client", packetSize ); + } + } +}; + +//------------------------------------------------------------------------------ +// Client handler factory +//------------------------------------------------------------------------------ +class RandomHandlerFactory: public ClientHandlerFactory +{ + public: + virtual ClientHandler *CreateHandler() + { + return new RandomHandler(); + } +}; + +//------------------------------------------------------------------------------ +// Declaration +//------------------------------------------------------------------------------ + +class SocketTest : public ::testing::Test {}; + +//------------------------------------------------------------------------------ +// Test the transfer +//------------------------------------------------------------------------------ +TEST(SocketTest, TransferTest) +{ + using namespace XrdCl; + srandom( time(0) ); + Server serv( Server::Both ); + Socket sock; + + //---------------------------------------------------------------------------- + // Start up the server and connect to it + //---------------------------------------------------------------------------- + uint16_t port = 9998; // was 9999, but we need to change ports from other + // tests so that we can run all of them in parallel. + // Will find another, better way to ensure this in the future + EXPECT_TRUE( serv.Setup( port, 1, new RandomHandlerFactory() ) ); + EXPECT_TRUE( serv.Start() ); + + EXPECT_TRUE( sock.GetStatus() == Socket::Disconnected ); + EXPECT_TRUE( sock.Initialize( AF_INET6 ).IsOK() ); + EXPECT_TRUE( sock.Connect( "localhost", port ).IsOK() ); + EXPECT_TRUE( sock.GetStatus() == Socket::Connected ); + + //---------------------------------------------------------------------------- + // Get the number of packets + //---------------------------------------------------------------------------- + uint8_t packets; + uint32_t bytesTransmitted; + uint16_t packetSize; + Status sc; + char buffer[50000]; + uint64_t sentCounter = 0; + uint32_t sentChecksum = ::Utils::ComputeCRC32( 0, 0 ); + uint64_t receivedCounter = 0; + uint32_t receivedChecksum = ::Utils::ComputeCRC32( 0, 0 ); + sc = sock.ReadRaw( &packets, 1, 60, bytesTransmitted ); + EXPECT_TRUE( sc.status == stOK ); + + //---------------------------------------------------------------------------- + // Read each packet + //---------------------------------------------------------------------------- + for( int i = 0; i < packets; ++i ) + { + sc = sock.ReadRaw( &packetSize, 2, 60, bytesTransmitted ); + EXPECT_TRUE( sc.status == stOK ); + sc = sock.ReadRaw( buffer, packetSize, 60, bytesTransmitted ); + EXPECT_TRUE( sc.status == stOK ); + receivedCounter += bytesTransmitted; + receivedChecksum = ::Utils::UpdateCRC32( receivedChecksum, buffer, + bytesTransmitted ); + } + + //---------------------------------------------------------------------------- + // Send the number of packets + //---------------------------------------------------------------------------- + packets = random() % 100; + + sc = sock.WriteRaw( &packets, 1, 60, bytesTransmitted ); + EXPECT_TRUE( (sc.status == stOK) && (bytesTransmitted == 1) ); + + for( int i = 0; i < packets; ++i ) + { + packetSize = random() % 50000; + EXPECT_TRUE( ::Utils::GetRandomBytes( buffer, packetSize ) == packetSize ); + + sc = sock.WriteRaw( (char *)&packetSize, 2, 60, bytesTransmitted ); + EXPECT_TRUE( (sc.status == stOK) && (bytesTransmitted == 2) ); + sc = sock.WriteRaw( buffer, packetSize, 60, bytesTransmitted ); + EXPECT_TRUE( (sc.status == stOK) && (bytesTransmitted == packetSize) ); + sentCounter += bytesTransmitted; + sentChecksum = ::Utils::UpdateCRC32( sentChecksum, buffer, + bytesTransmitted ); + } + + //---------------------------------------------------------------------------- + // Check the counters and the checksums + //---------------------------------------------------------------------------- + std::string socketName = sock.GetSockName(); + + sock.Close(); + EXPECT_TRUE( serv.Stop() ); + + std::pair sent = serv.GetSentStats( socketName ); + std::pair received = serv.GetReceivedStats( socketName ); + EXPECT_TRUE( sentCounter == received.first ); + EXPECT_TRUE( receivedCounter == sent.first ); + EXPECT_TRUE( sentChecksum == received.second ); + EXPECT_TRUE( receivedChecksum == sent.second ); +} From 033d7318736792fdfcebae59847b6151710f5028 Mon Sep 17 00:00:00 2001 From: Angelo Galavotti Date: Thu, 6 Jul 2023 13:18:39 +0000 Subject: [PATCH 275/442] [Tests] Add converted UtilsTest Adds converted UtilsTest from CPPUnit to GTest. Finally, it corrects a comment that was straight-up wrong in UtilsTest.cc. --- tests/XrdCl/CMakeLists.txt | 1 + tests/XrdCl/GTestXrdHelpers.hh | 11 +- tests/XrdCl/XrdClPoller.cc | 10 +- tests/XrdCl/XrdClSocket.cc | 3 +- tests/XrdCl/XrdClUtilsTest.cc | 252 +++++++++++++++++++++++++++++++++ tests/XrdClTests/UtilsTest.cc | 2 +- 6 files changed, 270 insertions(+), 9 deletions(-) create mode 100644 tests/XrdCl/XrdClUtilsTest.cc diff --git a/tests/XrdCl/CMakeLists.txt b/tests/XrdCl/CMakeLists.txt index e89deaa84c5..bca0c2a5aca 100644 --- a/tests/XrdCl/CMakeLists.txt +++ b/tests/XrdCl/CMakeLists.txt @@ -3,6 +3,7 @@ add_executable(xrdcl-unit-tests XrdClURL.cc XrdClPoller.cc XrdClSocket.cc + XrdClUtilsTest.cc ../common/Server.cc ../common/Utils.cc ../common/TestEnv.cc diff --git a/tests/XrdCl/GTestXrdHelpers.hh b/tests/XrdCl/GTestXrdHelpers.hh index fb522967a2e..5dbf7b76930 100644 --- a/tests/XrdCl/GTestXrdHelpers.hh +++ b/tests/XrdCl/GTestXrdHelpers.hh @@ -24,8 +24,13 @@ #include #include -#define GTEST_ASSERT_XRDST( x ) \ -{ \ - XrdCl::XRootDStatus _st = x; \ +/** @brief Equivalent of CPPUNIT_ASSERT_XRDST + * + * Shows the code that we are asserting and its value + * in the final evaluation. + */ +#define GTEST_ASSERT_XRDST( x ) \ +{ \ + XrdCl::XRootDStatus _st = x; \ EXPECT_TRUE(_st.IsOK()) << "[" << #x << "]: " << _st.ToStr() << std::endl; \ } diff --git a/tests/XrdCl/XrdClPoller.cc b/tests/XrdCl/XrdClPoller.cc index fdd3ae5ddb8..cb6b669c6a9 100644 --- a/tests/XrdCl/XrdClPoller.cc +++ b/tests/XrdCl/XrdClPoller.cc @@ -28,12 +28,11 @@ #include - #include "XrdCl/XrdClPollerBuiltIn.hh" - using namespace XrdClTests; using namespace testing; + //------------------------------------------------------------------------------ // Client handler //------------------------------------------------------------------------------ @@ -194,12 +193,17 @@ class SocketHandler: public XrdCl::SocketHandler XrdCl::Poller *pPoller; }; + //------------------------------------------------------------------------------ -// Test the functionality the built-in poller +// PollerTest class declaration //------------------------------------------------------------------------------ class PollerTest : public ::testing::Test {}; +//------------------------------------------------------------------------------ +// Test the functionality the built-in poller +//------------------------------------------------------------------------------ + TEST(PollerTest, FunctionTest) { XrdCl::Poller *poller = new XrdCl::PollerBuiltIn(); // only uses built-in poller diff --git a/tests/XrdCl/XrdClSocket.cc b/tests/XrdCl/XrdClSocket.cc index 981f3289ead..676d8c01aa3 100644 --- a/tests/XrdCl/XrdClSocket.cc +++ b/tests/XrdCl/XrdClSocket.cc @@ -205,9 +205,8 @@ class RandomHandlerFactory: public ClientHandlerFactory }; //------------------------------------------------------------------------------ -// Declaration +// SocketTest class declaration //------------------------------------------------------------------------------ - class SocketTest : public ::testing::Test {}; //------------------------------------------------------------------------------ diff --git a/tests/XrdCl/XrdClUtilsTest.cc b/tests/XrdCl/XrdClUtilsTest.cc new file mode 100644 index 00000000000..c27a48da640 --- /dev/null +++ b/tests/XrdCl/XrdClUtilsTest.cc @@ -0,0 +1,252 @@ +//------------------------------------------------------------------------------ +// Copyright (c) 2023 by European Organization for Nuclear Research (CERN) +// Author: Angelo Galavotti +//------------------------------------------------------------------------------ +// This file is part of the XRootD software suite. +// +// XRootD is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// XRootD is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with XRootD. If not, see . +// +// In applying this licence, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. +//------------------------------------------------------------------------------ + +#include "XrdCl/XrdClAnyObject.hh" +#include "GTestXrdHelpers.hh" +#include "XrdCl/XrdClTaskManager.hh" +#include "XrdCl/XrdClSIDManager.hh" +#include "XrdCl/XrdClPropertyList.hh" + +//------------------------------------------------------------------------------ +// Declaration +//------------------------------------------------------------------------------ + +class A +{ + public: + A( bool &st ): a(0.0), stat(st) {} + ~A() { stat = true; } + double a; + bool &stat; +}; + +class B +{ + public: + int b; +}; + +//------------------------------------------------------------------------------ +// UtilityTest class declaration +//------------------------------------------------------------------------------ +class UtilsTest : public ::testing::Test {}; + +//------------------------------------------------------------------------------ +// Any test +//------------------------------------------------------------------------------ +TEST(UtilsTest, AnyTest) +{ + bool destructorCalled1 = false; + bool destructorCalled2 = false; + bool destructorCalled3 = false; + A *a1 = new A( destructorCalled1 ); + A *a2 = new A( destructorCalled2 ); + A *a3 = new A( destructorCalled3 ); + A *a4 = 0; + B *b = 0; + + XrdCl::AnyObject *any1 = new XrdCl::AnyObject(); + XrdCl::AnyObject *any2 = new XrdCl::AnyObject(); + XrdCl::AnyObject *any3 = new XrdCl::AnyObject(); + XrdCl::AnyObject *any4 = new XrdCl::AnyObject(); + + any1->Set( a1 ); + any1->Get( b ); + any1->Get( a4 ); + EXPECT_TRUE( !b ); + EXPECT_TRUE( a4 ); + EXPECT_TRUE( any1->HasOwnership() ); + + delete any1; + EXPECT_TRUE( destructorCalled1 ); + + any2->Set( a2 ); + any2->Set( (int*)0 ); + delete any2; + EXPECT_TRUE( !destructorCalled2 ); + delete a2; + + any3->Set( a3, false ); + EXPECT_TRUE( !any3->HasOwnership() ); + delete any3; + EXPECT_TRUE( !destructorCalled3 ); + delete a3; + + // test destruction of an empty object + delete any4; +} + +//------------------------------------------------------------------------------ +// Some tasks that do something +//------------------------------------------------------------------------------ +class TestTask1: public XrdCl::Task +{ + public: + TestTask1( std::vector &runs ): pRuns( runs ) + { + SetName( "TestTask1" ); + } + virtual time_t Run( time_t now ) + { + pRuns.push_back( now ); + return 0; + } + private: + std::vector &pRuns; +}; + +class TestTask2: public XrdCl::Task +{ + public: + TestTask2( std::vector &runs ): pRuns( runs ) + { + SetName( "TestTask2" ); + } + + virtual time_t Run( time_t now ) + { + pRuns.push_back( now ); + if( pRuns.size() >= 5 ) + return 0; + return now+2; + } + private: + std::vector &pRuns; +}; + +//------------------------------------------------------------------------------ +// Task Manager test +//------------------------------------------------------------------------------ +TEST(UtilsTest, TaskManagerTest) +{ + using namespace XrdCl; + + std::vector runs1, runs2; + Task *tsk1 = new TestTask1( runs1 ); + Task *tsk2 = new TestTask2( runs2 ); + + TaskManager taskMan; + EXPECT_TRUE( taskMan.Start() ); + + time_t now = ::time(0); + taskMan.RegisterTask( tsk1, now+2 ); + taskMan.RegisterTask( tsk2, now+1 ); + + ::sleep( 6 ); + taskMan.UnregisterTask( tsk2 ); + + ::sleep( 2 ); + + EXPECT_TRUE( runs1.size() == 1 ); + EXPECT_TRUE( runs2.size() == 3 ); + EXPECT_TRUE( taskMan.Stop() ); +} + +//------------------------------------------------------------------------------ +// SID Manager test +//------------------------------------------------------------------------------ +TEST(UtilsTest, SIDManagerTest) +{ + using namespace XrdCl; + std::shared_ptr manager = SIDMgrPool::Instance().GetSIDMgr( "root://fake:1094//dir/file" ); + + uint8_t sid1[2]; + uint8_t sid2[2]; + uint8_t sid3[2]; + uint8_t sid4[2]; + uint8_t sid5[2]; + + GTEST_ASSERT_XRDST( manager->AllocateSID( sid1 ) ); + GTEST_ASSERT_XRDST( manager->AllocateSID( sid2 ) ); + manager->ReleaseSID( sid2 ); + GTEST_ASSERT_XRDST( manager->AllocateSID( sid3 ) ); + GTEST_ASSERT_XRDST( manager->AllocateSID( sid4 ) ); + GTEST_ASSERT_XRDST( manager->AllocateSID( sid5 ) ); + + EXPECT_TRUE( (sid1[0] != sid2[0]) || (sid1[1] != sid2[1]) ); + EXPECT_TRUE( manager->NumberOfTimedOutSIDs() == 0 ); + manager->TimeOutSID( sid4 ); + manager->TimeOutSID( sid5 ); + EXPECT_TRUE( manager->NumberOfTimedOutSIDs() == 2 ); + EXPECT_TRUE( manager->IsTimedOut( sid3 ) == false ); + EXPECT_TRUE( manager->IsTimedOut( sid1 ) == false ); + EXPECT_TRUE( manager->IsTimedOut( sid4 ) == true ); + EXPECT_TRUE( manager->IsTimedOut( sid5 ) == true ); + manager->ReleaseTimedOut( sid5 ); + EXPECT_TRUE( manager->IsTimedOut( sid5 ) == false ); + manager->ReleaseAllTimedOut(); + EXPECT_TRUE( manager->NumberOfTimedOutSIDs() == 0 ); +} + +//------------------------------------------------------------------------------ +// Property List test +//------------------------------------------------------------------------------ +TEST(UtilsTest, PropertyListTest) +{ + using namespace XrdCl; + PropertyList l; + l.Set( "s1", "test string 1" ); + l.Set( "i1", 123456789123ULL ); + + uint64_t i1; + std::string s1; + + EXPECT_TRUE( l.Get( "s1", s1 ) ); + EXPECT_TRUE( s1 == "test string 1" ); + EXPECT_TRUE( l.Get( "i1", i1 ) ); + EXPECT_TRUE( i1 == 123456789123ULL ); + EXPECT_TRUE( l.HasProperty( "s1" ) ); + EXPECT_TRUE( !l.HasProperty( "s2" ) ); + EXPECT_TRUE( l.HasProperty( "i1" ) ); + + for( int i = 0; i < 1000; ++i ) + l.Set( "vect_int", i, i+1000 ); + + int i; + int num; + for( i = 0; l.HasProperty( "vect_int", i ); ++i ) + { + EXPECT_TRUE( l.Get( "vect_int", i, num ) ); + EXPECT_TRUE( num = i+1000 ); + } + EXPECT_TRUE( i == 1000 ); + + XRootDStatus st1, st2; + st1.SetErrorMessage( "test error message" ); + l.Set( "status", st1 ); + EXPECT_TRUE( l.Get( "status", st2 ) ); + EXPECT_TRUE( st2.status == st1.status ); + EXPECT_TRUE( st2.code == st1.code ); + EXPECT_TRUE( st2.errNo == st1.errNo ); + EXPECT_TRUE( st2.GetErrorMessage() == st1.GetErrorMessage() ); + + std::vector v1, v2; + v1.push_back( "test string 1" ); + v1.push_back( "test string 2" ); + v1.push_back( "test string 3" ); + l.Set( "vector", v1 ); + EXPECT_TRUE( l.Get( "vector", v2 ) ); + for( size_t i = 0; i < v1.size(); ++i ) + EXPECT_TRUE( v1[i] == v2[i] ); +} diff --git a/tests/XrdClTests/UtilsTest.cc b/tests/XrdClTests/UtilsTest.cc index 86104bc57f7..7bec1130f41 100644 --- a/tests/XrdClTests/UtilsTest.cc +++ b/tests/XrdClTests/UtilsTest.cc @@ -212,7 +212,7 @@ void UtilsTest::SIDManagerTest() } //------------------------------------------------------------------------------ -// SID Manager test +// Property List test //------------------------------------------------------------------------------ void UtilsTest::PropertyListTest() { From 57d49220903fd3473b8e84eb49981d8f5b4e6712 Mon Sep 17 00:00:00 2001 From: Angelo Galavotti Date: Fri, 7 Jul 2023 15:07:28 +0000 Subject: [PATCH 276/442] [Tests] Add converted XrdEc test (MicroTest) Adds converted XrdEc tests from CPPUnit to GTest. In addition, it adds the new references to the XrdEcTests class in the Reader and Zip class (as friend classes). --- src/XrdCl/XrdClZipArchive.hh | 2 + src/XrdEc/XrdEcReader.hh | 2 + tests/CMakeLists.txt | 1 + tests/XrdCl/XrdClUtilsTest.cc | 1 + tests/XrdEc/CMakeLists.txt | 25 ++ tests/XrdEc/MicroTest.cc | 755 ++++++++++++++++++++++++++++++++++ 6 files changed, 786 insertions(+) create mode 100644 tests/XrdEc/CMakeLists.txt create mode 100644 tests/XrdEc/MicroTest.cc diff --git a/src/XrdCl/XrdClZipArchive.hh b/src/XrdCl/XrdClZipArchive.hh index d0ed61a2707..5c1acdb7625 100644 --- a/src/XrdCl/XrdClZipArchive.hh +++ b/src/XrdCl/XrdClZipArchive.hh @@ -44,6 +44,7 @@ //----------------------------------------------------------------------------- namespace XrdEc{ class StrmWriter; class Reader; template class OpenOnlyImpl; }; class MicroTest; +class XrdEcTests; namespace XrdCl { @@ -63,6 +64,7 @@ namespace XrdCl template friend class XrdEc::OpenOnlyImpl; friend class ::MicroTest; + friend class ::XrdEcTests; template friend XRootDStatus ReadFromImpl( ZipArchive&, const std::string&, uint64_t, uint32_t, void*, ResponseHandler*, uint16_t ); diff --git a/src/XrdEc/XrdEcReader.hh b/src/XrdEc/XrdEcReader.hh index 3cfb75b4201..7b2de06480e 100644 --- a/src/XrdEc/XrdEcReader.hh +++ b/src/XrdEc/XrdEcReader.hh @@ -35,6 +35,7 @@ #include class MicroTest; +class XrdEcTests; namespace XrdEc { @@ -57,6 +58,7 @@ namespace XrdEc class Reader { friend class ::MicroTest; + friend class ::XrdEcTests; friend struct block_t; public: diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 880c0f1a964..03afc193849 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -8,6 +8,7 @@ add_subdirectory( XrdSsiTests ) if( BUILD_XRDEC ) add_subdirectory( XrdEcTests ) + add_subdirectory( XrdEc ) # new tests with GTest endif() if( BUILD_CEPH ) diff --git a/tests/XrdCl/XrdClUtilsTest.cc b/tests/XrdCl/XrdClUtilsTest.cc index c27a48da640..fcbfeb8b4d1 100644 --- a/tests/XrdCl/XrdClUtilsTest.cc +++ b/tests/XrdCl/XrdClUtilsTest.cc @@ -22,6 +22,7 @@ // or submit itself to any jurisdiction. //------------------------------------------------------------------------------ +#include #include "XrdCl/XrdClAnyObject.hh" #include "GTestXrdHelpers.hh" #include "XrdCl/XrdClTaskManager.hh" diff --git a/tests/XrdEc/CMakeLists.txt b/tests/XrdEc/CMakeLists.txt new file mode 100644 index 00000000000..0531c35ec43 --- /dev/null +++ b/tests/XrdEc/CMakeLists.txt @@ -0,0 +1,25 @@ +add_executable(xrdec-unit-tests + MicroTest.cc + ../common/Server.cc + ../common/Utils.cc + ../common/TestEnv.cc +) + +target_link_libraries(xrdec-unit-tests + XrdEc + XrdCl + XrdXml + XrdUtils + ZLIB::ZLIB + GTest::GTest + GTest::Main + ${ISAL_LIBRARIES} +) + +target_include_directories(xrdec-unit-tests + PRIVATE ${CMAKE_SOURCE_DIR}/src + PRIVATE ../common + ${ISAL_INCLUDE_DIRS} +) + +gtest_discover_tests(xrdec-unit-tests TEST_PREFIX XrdCl::) diff --git a/tests/XrdEc/MicroTest.cc b/tests/XrdEc/MicroTest.cc new file mode 100644 index 00000000000..61727e8c0fc --- /dev/null +++ b/tests/XrdEc/MicroTest.cc @@ -0,0 +1,755 @@ +//------------------------------------------------------------------------------ +// Copyright (c) 2023 by European Organization for Nuclear Research (CERN) +// Author: Angelo Galavotti +//------------------------------------------------------------------------------ +// This file is part of the XRootD software suite. +// +// XRootD is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// XRootD is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with XRootD. If not, see . +// +// In applying this licence, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. +//------------------------------------------------------------------------------ + +#include "TestEnv.hh" +#include "../XrdCl/GTestXrdHelpers.hh" +#include + +#include "XrdEc/XrdEcStrmWriter.hh" +#include "XrdEc/XrdEcReader.hh" +#include "XrdEc/XrdEcObjCfg.hh" + +#include "XrdCl/XrdClMessageUtils.hh" + +#include "XrdZip/XrdZipCDFH.hh" + +#include +#include +#include + +#include +#include +#include +#include +#include + +using namespace XrdEc; + +//------------------------------------------------------------------------------ +// Declaration +//------------------------------------------------------------------------------ +class XrdEcTests : public ::testing::Test +{ + public: + void Init( bool usecrc32c ); + + inline void AlignedWriteTestImpl( bool usecrc32c ) + { + // create the data and stripe directories + Init( usecrc32c ); + // run the test + AlignedWriteRaw(); + // verify that we wrote the data correctly + Verify(); + // clean up the data directory + CleanUp(); + } + + inline void AlignedWriteTest() + { + AlignedWriteTestImpl( true ); + } + + inline void AlignedWriteTestIsalCrcNoMt() + { + AlignedWriteTestImpl( false ); + } + + inline void VectorReadTest(){ + Init(true); + + AlignedWriteRaw(); + + Verify(); + + uint32_t seed = std::chrono::system_clock::now().time_since_epoch().count(); + + VerifyVectorRead(seed); + + CleanUp(); + } + + inline void IllegalVectorReadTest(){ + Init(true); + + AlignedWriteRaw(); + + Verify(); + + uint32_t seed = + std::chrono::system_clock::now().time_since_epoch().count(); + + IllegalVectorRead(seed); + + CleanUp(); + } + + inline void AlignedWrite1MissingTestImpl( bool usecrc32c ) + { + // initialize directories + Init( usecrc32c ); + UrlNotReachable( 2 ); + // run the test + AlignedWriteRaw(); + // verify that we wrote the data correctly + Verify(); + // clean up + UrlReachable( 2 ); + CleanUp(); + } + + inline void AlignedWrite1MissingTest() + { + AlignedWrite1MissingTestImpl( true ); + } + + inline void AlignedWrite1MissingTestIsalCrcNoMt() + { + AlignedWrite1MissingTestImpl( false ); + } + + inline void AlignedWrite2MissingTestImpl( bool usecrc32c ) + { + // initialize directories + Init( usecrc32c ); + UrlNotReachable( 2 ); + UrlNotReachable( 3 ); + // run the test + AlignedWriteRaw(); + // verify that we wrote the data correctly + Verify(); + // clean up + UrlReachable( 2 ); + UrlReachable( 3 ); + CleanUp(); + } + + inline void AlignedWrite2MissingTest() + { + AlignedWrite2MissingTestImpl( true ); + } + + inline void AlignedWrite2MissingTestIsalCrcNoMt() + { + AlignedWrite2MissingTestImpl( false ); + } + + void VarlenWriteTest( uint32_t wrtlen, bool usecrc32c ); + + inline void SmallWriteTest() + { + VarlenWriteTest( 7, true ); + } + + inline void SmallWriteTestIsalCrcNoMt() + { + VarlenWriteTest( 7, false ); + } + + void BigWriteTest() + { + VarlenWriteTest( 77, true ); + } + + void BigWriteTestIsalCrcNoMt() + { + VarlenWriteTest( 77, false ); + } + + void Verify() + { + ReadVerifyAll(); + CorruptedReadVerify(); + } + + void VerifyVectorRead(uint32_t randomSeed); + + void IllegalVectorRead(uint32_t randomSeed); + + void CleanUp(); + + inline void ReadVerifyAll() + { + AlignedReadVerify(); + PastEndReadVerify(); + SmallChunkReadVerify(); + BigChunkReadVerify(); + + for( size_t i = 0; i < 10; ++i ) + RandomReadVerify(); + } + + void ReadVerify( uint32_t rdsize, uint64_t maxrd = std::numeric_limits::max() ); + + void RandomReadVerify(); + + void Corrupted1stBlkReadVerify(); + + inline void AlignedReadVerify() + { + ReadVerify( chsize, rawdata.size() ); + } + + inline void PastEndReadVerify() + { + ReadVerify( chsize ); + } + + inline void SmallChunkReadVerify() + { + ReadVerify( 5 ); + } + + inline void BigChunkReadVerify() + { + ReadVerify( 23 ); + } + + void CorruptedReadVerify(); + + void CorruptChunk( size_t blknb, size_t strpnb ); + + void UrlNotReachable( size_t index ); + void UrlReachable( size_t index ); + + private: + + void AlignedWriteRaw(); + + void copy_rawdata( char *buffer, size_t size ) + { + const char *begin = buffer; + const char *end = begin + size; + std::copy( begin, end, std::back_inserter( rawdata ) ); + } + + std::string datadir; + std::unique_ptr objcfg; + + static const size_t nbdata = 4; + static const size_t nbparity = 2; + static const size_t chsize = 16; + static const size_t nbiters = 16; + + static const size_t lfhsize = 30; + + std::vector rawdata; +}; + +TEST_F(XrdEcTests, AlignedWriteTest) +{ + AlignedWriteTest(); +} + +TEST_F(XrdEcTests, SmallWriteTest) +{ + SmallWriteTest(); +} + +TEST_F(XrdEcTests, BigWriteTest) +{ + BigWriteTest(); +} + +TEST_F(XrdEcTests, VectorReadTest) +{ + VectorReadTest(); +} + +TEST_F(XrdEcTests, IllegalVectorReadTest) +{ + IllegalVectorReadTest(); +} + +TEST_F(XrdEcTests, AlignedWrite1MissingTest) +{ + AlignedWrite1MissingTest(); +} + +TEST_F(XrdEcTests, AlignedWrite2MissingTest) +{ + AlignedWrite2MissingTest(); +} + +TEST_F(XrdEcTests, AlignedWriteTestIsalCrcNoMt) +{ + AlignedWriteTestIsalCrcNoMt(); +} + +TEST_F(XrdEcTests, SmallWriteTestIsalCrcNoMt) +{ + SmallWriteTestIsalCrcNoMt(); +} + +TEST_F(XrdEcTests, BigWriteTestIsalCrcNoMt) +{ + BigWriteTestIsalCrcNoMt(); +} + +TEST_F(XrdEcTests, AlignedWrite1MissingTestIsalCrcNoMt) +{ + AlignedWrite1MissingTestIsalCrcNoMt(); +} + +TEST_F(XrdEcTests, AlignedWrite2MissingTestIsalCrcNoMt) +{ + AlignedWrite2MissingTestIsalCrcNoMt(); +} + + +void XrdEcTests::Init( bool usecrc32c ) +{ + objcfg.reset( new ObjCfg( "test.txt", nbdata, nbparity, chsize, usecrc32c, true ) ); + rawdata.clear(); + + char tmpdir[32] = "/tmp/xrootd-xrdec-XXXXXX"; + // create the data directory + EXPECT_TRUE( mkdtemp(tmpdir) ); + datadir = tmpdir; + // create a directory for each stripe + size_t nbstrps = objcfg->nbdata + 2 * objcfg->nbparity; + for( size_t i = 0; i < nbstrps; ++i ) + { + std::stringstream ss; + ss << std::setfill('0') << std::setw( 2 ) << i; + std::string strp = datadir + '/' + ss.str() + '/'; + objcfg->plgr.emplace_back( strp ); + EXPECT_TRUE( mkdir( strp.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH ) == 0 ); + } +} + +void XrdEcTests::CorruptChunk( size_t blknb, size_t strpnb ) +{ + Reader reader( *objcfg ); + // open the data object + XrdCl::SyncResponseHandler handler1; + reader.Open( &handler1 ); + handler1.WaitForResponse(); + XrdCl::XRootDStatus *status = handler1.GetStatus(); + GTEST_ASSERT_XRDST( *status ); + delete status; + + // get the CD buffer + std::string fn = objcfg->GetFileName( blknb, strpnb ); + std::string url = reader.urlmap[fn]; + buffer_t cdbuff = reader.dataarchs[url]->GetCD(); + + // close the data object + XrdCl::SyncResponseHandler handler2; + reader.Close( &handler2 ); + handler2.WaitForResponse(); + status = handler2.GetStatus(); + GTEST_ASSERT_XRDST( *status ); + delete status; + + // parse the CD buffer + const char *buff = cdbuff.data(); + size_t size = cdbuff.size(); + XrdZip::cdvec_t cdvec; + XrdZip::cdmap_t cdmap; + std::tie(cdvec, cdmap ) = XrdZip::CDFH::Parse( buff, size ); + + // now corrupt the chunk (put wrong checksum) + XrdZip::CDFH &cdfh = *cdvec[cdmap[fn]]; + uint64_t offset = cdfh.offset + lfhsize + fn.size(); // offset of the data + XrdCl::File f; + XrdCl::XRootDStatus status2 = f.Open( url, XrdCl::OpenFlags::Write ); + GTEST_ASSERT_XRDST( status2 ); + std::string str = "XXXXXXXX"; + status2 = f.Write( offset, str.size(), str.c_str() ); + GTEST_ASSERT_XRDST( status2 ); + status2 = f.Close(); + GTEST_ASSERT_XRDST( status2 ); +} + +void XrdEcTests::UrlNotReachable( size_t index ) +{ + XrdCl::URL url( objcfg->plgr[index] ); + EXPECT_TRUE( chmod( url.GetPath().c_str(), 0 ) == 0 ); +} + +void XrdEcTests::UrlReachable( size_t index ) +{ + XrdCl::URL url( objcfg->plgr[index] ); + mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH | + S_IXUSR | S_IXGRP | S_IXOTH; + EXPECT_TRUE( chmod( url.GetPath().c_str(), mode ) == 0 ); +} + +void XrdEcTests::CorruptedReadVerify() +{ + UrlNotReachable( 0 ); + ReadVerifyAll(); + UrlNotReachable( 1 ); + ReadVerifyAll(); + UrlReachable( 0 ); + UrlReachable( 1 ); + + CorruptChunk( 0, 1 ); + ReadVerifyAll(); + + CorruptChunk( 0, 2 ); + ReadVerifyAll(); + +} + +void XrdEcTests::VerifyVectorRead(uint32_t seed){ + Reader reader( *objcfg ); + // open the data object + XrdCl::SyncResponseHandler handler1; + reader.Open( &handler1 ); + handler1.WaitForResponse(); + XrdCl::XRootDStatus *status = handler1.GetStatus(); + GTEST_ASSERT_XRDST( *status ); + delete status; + + std::default_random_engine random_engine(seed); + + std::vector> buffers(5); + std::vector expected; + XrdCl::ChunkList chunks; + for(int i = 0; i < 5; i++){ + std::uniform_int_distribution sizeGen(0, rawdata.size()/4); + uint32_t size = sizeGen(random_engine); + std::uniform_int_distribution offsetGen(0, rawdata.size() - size); + uint32_t offset = offsetGen(random_engine); + + buffers[i].resize(size); + chunks.push_back(XrdCl::ChunkInfo(offset, size, buffers[i].data())); + + std::string resultExp( rawdata.data() + offset, size ); + expected.push_back(resultExp); + } + + XrdCl::SyncResponseHandler h; + reader.VectorRead(chunks, nullptr, &h, 0); + h.WaitForResponse(); + status = h.GetStatus(); + GTEST_ASSERT_XRDST( *status ); + delete status; + for(int i = 0; i < 5; i++){ + std::string result(buffers[i].data(), expected[i].size()); + EXPECT_TRUE( result == expected[i] ); + } + + XrdCl::SyncResponseHandler handler2; + reader.Close( &handler2 ); + handler2.WaitForResponse(); + status = handler2.GetStatus(); + GTEST_ASSERT_XRDST( *status ); + delete status; +} + +void XrdEcTests::IllegalVectorRead(uint32_t seed){ + Reader reader(*objcfg); + // open the data object + XrdCl::SyncResponseHandler handler1; + reader.Open(&handler1); + handler1.WaitForResponse(); + XrdCl::XRootDStatus *status = handler1.GetStatus(); + GTEST_ASSERT_XRDST(*status); + delete status; + + std::default_random_engine random_engine(seed); + + std::vector> buffers(5); + XrdCl::ChunkList chunks; + for (int i = 0; i < 5; i++) + { + std::uniform_int_distribution sizeGen(1, rawdata.size() / 4); + uint32_t size = sizeGen(random_engine); + std::uniform_int_distribution offsetGen(0, + rawdata.size() - size); + uint32_t offset = offsetGen(random_engine); + if (i == 0) + offset = rawdata.size() - size / 2; + + buffers[i].resize(size); + + chunks.push_back(XrdCl::ChunkInfo(offset, size, buffers[i].data())); + + } + + XrdCl::SyncResponseHandler h; + reader.VectorRead(chunks, nullptr, &h, 0); + h.WaitForResponse(); + status = h.GetStatus(); + // the response should be negative since one of the reads was over the file end + if (status->IsOK()) + { + EXPECT_TRUE(false); + } + delete status; + + buffers.clear(); + buffers.resize(1025); + chunks.clear(); + for (int i = 0; i < 1025; i++) + { + std::uniform_int_distribution sizeGen(1, rawdata.size() / 4); + uint32_t size = sizeGen(random_engine); + std::uniform_int_distribution offsetGen(0, + rawdata.size() - size); + uint32_t offset = offsetGen(random_engine); + + buffers[i].resize(size); + + chunks.push_back(XrdCl::ChunkInfo(offset, size, buffers[i].data())); + + } + + XrdCl::SyncResponseHandler h2; + reader.VectorRead(chunks, nullptr, &h2, 0); + h2.WaitForResponse(); + status = h2.GetStatus(); + // the response should be negative since we requested too many reads + if (status->IsOK()) + { + EXPECT_TRUE(false); + } + delete status; + + XrdCl::SyncResponseHandler handler2; + reader.Close(&handler2); + handler2.WaitForResponse(); + status = handler2.GetStatus(); + GTEST_ASSERT_XRDST(*status); + delete status; +} + +void XrdEcTests::ReadVerify( uint32_t rdsize, uint64_t maxrd ) +{ + Reader reader( *objcfg ); + // open the data object + XrdCl::SyncResponseHandler handler1; + reader.Open( &handler1 ); + handler1.WaitForResponse(); + XrdCl::XRootDStatus *status = handler1.GetStatus(); + GTEST_ASSERT_XRDST( *status ); + delete status; + + uint64_t rdoff = 0; + char *rdbuff = new char[rdsize]; + uint32_t bytesrd = 0; + uint64_t total_bytesrd = 0; + do + { + XrdCl::SyncResponseHandler h; + reader.Read( rdoff, rdsize, rdbuff, &h, 0 ); + h.WaitForResponse(); + status = h.GetStatus(); + GTEST_ASSERT_XRDST( *status ); + // get the actual result + auto rsp = h.GetResponse(); + XrdCl::ChunkInfo *ch = nullptr; + rsp->Get( ch ); + bytesrd = ch->length; + std::string result( reinterpret_cast( ch->buffer ), bytesrd ); + // get the expected result + size_t rawoff = rdoff; + size_t rawsz = rdsize; + if( rawoff + rawsz > rawdata.size() ) rawsz = rawdata.size() - rawoff; + std::string expected( rawdata.data() + rawoff, rawsz ); + // make sure the expected and actual results are the same + EXPECT_TRUE( result == expected ); + delete status; + delete rsp; + rdoff += bytesrd; + total_bytesrd += bytesrd; + } + while( bytesrd == rdsize && total_bytesrd < maxrd ); + delete[] rdbuff; + + // close the data object + XrdCl::SyncResponseHandler handler2; + reader.Close( &handler2 ); + handler2.WaitForResponse(); + status = handler2.GetStatus(); + GTEST_ASSERT_XRDST( *status ); + delete status; +} + +void XrdEcTests::RandomReadVerify() +{ + size_t filesize = rawdata.size(); + static std::default_random_engine random_engine( std::chrono::system_clock::now().time_since_epoch().count() ); + std::uniform_int_distribution offdistr( 0, filesize ); + uint64_t rdoff = offdistr( random_engine ); + std::uniform_int_distribution lendistr( rdoff, filesize + 32 ); + uint32_t rdlen = lendistr( random_engine ); + + Reader reader( *objcfg ); + // open the data object + XrdCl::SyncResponseHandler handler1; + reader.Open( &handler1 ); + handler1.WaitForResponse(); + XrdCl::XRootDStatus *status = handler1.GetStatus(); + GTEST_ASSERT_XRDST( *status ); + delete status; + + // read the data + char *rdbuff = new char[rdlen]; + XrdCl::SyncResponseHandler h; + reader.Read( rdoff, rdlen, rdbuff, &h, 0 ); + h.WaitForResponse(); + status = h.GetStatus(); + GTEST_ASSERT_XRDST( *status ); + // get the actual result + auto rsp = h.GetResponse(); + XrdCl::ChunkInfo *ch = nullptr; + rsp->Get( ch ); + uint32_t bytesrd = ch->length; + std::string result( reinterpret_cast( ch->buffer ), bytesrd ); + // get the expected result + size_t rawoff = rdoff; + size_t rawlen = rdlen; + if( rawoff > rawdata.size() ) rawlen = 0; + else if( rawoff + rawlen > rawdata.size() ) rawlen = rawdata.size() - rawoff; + std::string expected( rawdata.data() + rawoff, rawlen ); + // make sure the expected and actual results are the same + EXPECT_TRUE( result == expected ); + delete status; + delete rsp; + delete[] rdbuff; + + // close the data object + XrdCl::SyncResponseHandler handler2; + reader.Close( &handler2 ); + handler2.WaitForResponse(); + status = handler2.GetStatus(); + GTEST_ASSERT_XRDST( *status ); + delete status; +} + +void XrdEcTests::Corrupted1stBlkReadVerify() +{ + uint64_t rdoff = 0; + uint32_t rdlen = objcfg->datasize; + + Reader reader( *objcfg ); + // open the data object + XrdCl::SyncResponseHandler handler1; + reader.Open( &handler1 ); + handler1.WaitForResponse(); + XrdCl::XRootDStatus *status = handler1.GetStatus(); + GTEST_ASSERT_XRDST( *status ); + delete status; + + // read the data + char *rdbuff = new char[rdlen]; + XrdCl::SyncResponseHandler h; + reader.Read( rdoff, rdlen, rdbuff, &h, 0 ); + h.WaitForResponse(); + status = h.GetStatus(); + EXPECT_TRUE( status->status == XrdCl::stError && + status->code == XrdCl::errDataError ); + delete status; + delete[] rdbuff; + + // close the data object + XrdCl::SyncResponseHandler handler2; + reader.Close( &handler2 ); + handler2.WaitForResponse(); + status = handler2.GetStatus(); + GTEST_ASSERT_XRDST( *status ); + delete status; +} + +int unlink_cb(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf) +{ + int rc = remove( fpath ); + EXPECT_TRUE( rc == 0 ); + return rc; +} + +void XrdEcTests::CleanUp() +{ + // delete the data directory + nftw( datadir.c_str(), unlink_cb, 64, FTW_DEPTH | FTW_PHYS ); +} + +void XrdEcTests::AlignedWriteRaw() +{ + char buffer[objcfg->chunksize]; + StrmWriter writer( *objcfg ); + // open the data object + XrdCl::SyncResponseHandler handler1; + writer.Open( &handler1 ); + handler1.WaitForResponse(); + XrdCl::XRootDStatus *status = handler1.GetStatus(); + GTEST_ASSERT_XRDST( *status ); + delete status; + // write to the data object + for( size_t i = 0; i < nbiters; ++i ) + { + memset( buffer, 'A' + i, objcfg->chunksize ); + writer.Write( objcfg->chunksize, buffer, nullptr ); + copy_rawdata( buffer, sizeof( buffer ) ); + } + XrdCl::SyncResponseHandler handler2; + writer.Close( &handler2 ); + handler2.WaitForResponse(); + status = handler2.GetStatus(); + GTEST_ASSERT_XRDST( *status ); + delete status; +} + +void XrdEcTests::VarlenWriteTest( uint32_t wrtlen, bool usecrc32c ) +{ + // create the data and stripe directories + Init( usecrc32c ); + // open the data object + StrmWriter writer( *objcfg ); + XrdCl::SyncResponseHandler handler1; + writer.Open( &handler1 ); + handler1.WaitForResponse(); + XrdCl::XRootDStatus *status = handler1.GetStatus(); + GTEST_ASSERT_XRDST( *status ); + delete status; + // write the data + char wrtbuff[wrtlen]; + size_t bytesleft = nbiters * objcfg->chunksize; + size_t i = 0; + while( bytesleft > 0 ) + { + if( wrtlen > bytesleft ) wrtlen = bytesleft; + memset( wrtbuff, 'A' + i, wrtlen ); + writer.Write( wrtlen, wrtbuff, nullptr ); + copy_rawdata( wrtbuff, wrtlen ); + bytesleft -= wrtlen; + ++i; + } + XrdCl::SyncResponseHandler handler2; + writer.Close( &handler2 ); + handler2.WaitForResponse(); + status = handler2.GetStatus(); + GTEST_ASSERT_XRDST( *status ); + delete status; + + // verify that we wrote the data correctly + Verify(); + // clean up the data directory + CleanUp(); +} + From 74ceaeafaf6649b83ed646fea7811807e8c551d1 Mon Sep 17 00:00:00 2001 From: Angelo Galavotti Date: Thu, 20 Jul 2023 14:58:12 +0000 Subject: [PATCH 277/442] [Tests] Convert all CPPUnitHelper functions to GTest equivalent --- tests/XrdCl/GTestXrdHelpers.hh | 42 ++++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/tests/XrdCl/GTestXrdHelpers.hh b/tests/XrdCl/GTestXrdHelpers.hh index 5dbf7b76930..d54ef29c95a 100644 --- a/tests/XrdCl/GTestXrdHelpers.hh +++ b/tests/XrdCl/GTestXrdHelpers.hh @@ -29,8 +29,42 @@ * Shows the code that we are asserting and its value * in the final evaluation. */ -#define GTEST_ASSERT_XRDST( x ) \ -{ \ - XrdCl::XRootDStatus _st = x; \ - EXPECT_TRUE(_st.IsOK()) << "[" << #x << "]: " << _st.ToStr() << std::endl; \ +#define GTEST_ASSERT_XRDST( x ) \ +{ \ + XrdCl::XRootDStatus _st = x; \ + EXPECT_TRUE(_st.IsOK()) << "[" << #x << "]: " << _st.ToStr() << std::endl; \ } + +/** @brief Equivalent of CPPUNIT_ASSERT_XRDST_NOTOK + * + * Shows the code that we are asserting and asserts that its + * execution is throwing an error. + */ +#define GTEST_ASSERT_XRDST_NOTOK( x, err ) \ +{ \ + XrdCl::XRootDStatus _st = x; \ + EXPECT_TRUE(!_st.IsOK() && _st.code == err) << "[" << #x << "]: " << _st.ToStr() << std::endl; \ +} + +/** @brief Equivalent of CPPUNIT_ASSERT_ERRNO + * + * Shows the code that we are asserting and its error + * number. + */ +#define GTEST_ASSERT_ERRNO( x ) \ +{ \ + EXPECT_TRUE(x) << "[" << #x << "]: " << strerror(errno) << std::endl; \ +} + +/** @brief Equivalent of GTEST_ASSERT_PTHREAD + * + * Shows the code that we are asserting and its error + * number, in a thread-safe manner. + */ +#define GTEST_ASSERT_PTHREAD( x ) \ +{ \ + errno = x; \ + EXPECT_TRUE(errno == 0) << "[" << #x << "]: " << strerror(errno) << std::endl; \ +} + +#endif // __GTEST_XRD_HELPERS_HH__ From cb9a7a91d7334dcf146ca9a1db8bb8bcb6826467 Mon Sep 17 00:00:00 2001 From: Angelo Galavotti Date: Thu, 20 Jul 2023 14:59:13 +0000 Subject: [PATCH 278/442] [Tests] Add converted tests in FileTest Adds converted tests in FileTest from CPPUnit to GTest --- tests/XrdCl/IdentityPlugIn.cc | 488 ++++++++++++++++++++ tests/XrdCl/IdentityPlugIn.hh | 55 +++ tests/XrdCl/XrdClFileTest.cc | 836 ++++++++++++++++++++++++++++++++++ 3 files changed, 1379 insertions(+) create mode 100644 tests/XrdCl/IdentityPlugIn.cc create mode 100644 tests/XrdCl/IdentityPlugIn.hh create mode 100644 tests/XrdCl/XrdClFileTest.cc diff --git a/tests/XrdCl/IdentityPlugIn.cc b/tests/XrdCl/IdentityPlugIn.cc new file mode 100644 index 00000000000..eea454e50c0 --- /dev/null +++ b/tests/XrdCl/IdentityPlugIn.cc @@ -0,0 +1,488 @@ +//------------------------------------------------------------------------------ +// Copyright (c) 2023 by European Organization for Nuclear Research (CERN) +// Author: Angelo Galavotti +//------------------------------------------------------------------------------ +// This file is part of the XRootD software suite. +// +// XRootD is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// XRootD is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with XRootD. If not, see . +// +// In applying this licence, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. +//------------------------------------------------------------------------------ + +#include "XrdCl/XrdClFile.hh" +#include "XrdCl/XrdClFileSystem.hh" +#include "XrdCl/XrdClPlugInInterface.hh" +#include "XrdCl/XrdClLog.hh" +#include "IdentityPlugIn.hh" +#include "TestEnv.hh" + +using namespace XrdCl; +using namespace XrdClTests; + +namespace +{ + //---------------------------------------------------------------------------- + // A plugin that forwards all the calls to XrdCl::File + //---------------------------------------------------------------------------- + class IdentityFile: public XrdCl::FilePlugIn + { + public: + //------------------------------------------------------------------------ + // Constructor + //------------------------------------------------------------------------ + IdentityFile() + { + XrdCl::Log *log = TestEnv::GetLog(); + log->Debug( 1, "Calling IdentityFile::IdentityFile" ); + pFile = new File( false ); + } + + //------------------------------------------------------------------------ + // Destructor + //------------------------------------------------------------------------ + virtual ~IdentityFile() + { + XrdCl::Log *log = TestEnv::GetLog(); + log->Debug( 1, "Calling IdentityFile::~IdentityFile" ); + delete pFile; + } + + //------------------------------------------------------------------------ + // Open + //------------------------------------------------------------------------ + virtual XRootDStatus Open( const std::string &url, + OpenFlags::Flags flags, + Access::Mode mode, + ResponseHandler *handler, + uint16_t timeout ) + { + XrdCl::Log *log = TestEnv::GetLog(); + log->Debug( 1, "Calling IdentityFile::Open" ); + return pFile->Open( url, flags, mode, handler, timeout ); + } + + //------------------------------------------------------------------------ + // Close + //------------------------------------------------------------------------ + virtual XRootDStatus Close( ResponseHandler *handler, + uint16_t timeout ) + { + XrdCl::Log *log = TestEnv::GetLog(); + log->Debug( 1, "Calling IdentityFile::Close" ); + return pFile->Close( handler, timeout ); + } + + //------------------------------------------------------------------------ + // Stat + //------------------------------------------------------------------------ + virtual XRootDStatus Stat( bool force, + ResponseHandler *handler, + uint16_t timeout ) + { + XrdCl::Log *log = TestEnv::GetLog(); + log->Debug( 1, "Calling IdentityFile::Stat" ); + return pFile->Stat( force, handler, timeout ); + } + + + //------------------------------------------------------------------------ + // Read + //------------------------------------------------------------------------ + virtual XRootDStatus Read( uint64_t offset, + uint32_t size, + void *buffer, + ResponseHandler *handler, + uint16_t timeout ) + { + XrdCl::Log *log = TestEnv::GetLog(); + log->Debug( 1, "Calling IdentityFile::Read" ); + return pFile->Read( offset, size, buffer, handler, timeout ); + } + + //------------------------------------------------------------------------ + // Write + //------------------------------------------------------------------------ + virtual XRootDStatus Write( uint64_t offset, + uint32_t size, + const void *buffer, + ResponseHandler *handler, + uint16_t timeout ) + { + XrdCl::Log *log = TestEnv::GetLog(); + log->Debug( 1, "Calling IdentityFile::Write" ); + return pFile->Write( offset, size, buffer, handler, timeout ); + } + + //------------------------------------------------------------------------ + // Sync + //------------------------------------------------------------------------ + virtual XRootDStatus Sync( ResponseHandler *handler, + uint16_t timeout ) + { + XrdCl::Log *log = TestEnv::GetLog(); + log->Debug( 1, "Calling IdentityFile::Sync" ); + return pFile->Sync( handler, timeout ); + } + + //------------------------------------------------------------------------ + // Truncate + //------------------------------------------------------------------------ + virtual XRootDStatus Truncate( uint64_t size, + ResponseHandler *handler, + uint16_t timeout ) + { + XrdCl::Log *log = TestEnv::GetLog(); + log->Debug( 1, "Calling IdentityFile::Truncate" ); + return pFile->Truncate( size, handler, timeout ); + } + + //------------------------------------------------------------------------ + // VectorRead + //------------------------------------------------------------------------ + virtual XRootDStatus VectorRead( const ChunkList &chunks, + void *buffer, + ResponseHandler *handler, + uint16_t timeout ) + { + XrdCl::Log *log = TestEnv::GetLog(); + log->Debug( 1, "Calling IdentityFile::VectorRead" ); + return pFile->VectorRead( chunks, buffer, handler, timeout ); + } + + //------------------------------------------------------------------------ + // Fcntl + //------------------------------------------------------------------------ + virtual XRootDStatus Fcntl( const Buffer &arg, + ResponseHandler *handler, + uint16_t timeout ) + { + XrdCl::Log *log = TestEnv::GetLog(); + log->Debug( 1, "Calling IdentityFile::Fcntl" ); + return pFile->Fcntl( arg, handler, timeout ); + } + + //------------------------------------------------------------------------ + // Visa + //------------------------------------------------------------------------ + virtual XRootDStatus Visa( ResponseHandler *handler, + uint16_t timeout ) + { + XrdCl::Log *log = TestEnv::GetLog(); + log->Debug( 1, "Calling IdentityFile::Visa" ); + return pFile->Visa( handler, timeout ); + } + + //------------------------------------------------------------------------ + // IsOpen + //------------------------------------------------------------------------ + virtual bool IsOpen() const + { + XrdCl::Log *log = TestEnv::GetLog(); + log->Debug( 1, "Calling IdentityFile::IsOpen" ); + return pFile->IsOpen(); + } + + //------------------------------------------------------------------------ + // SetProperty + //------------------------------------------------------------------------ + virtual bool SetProperty( const std::string &name, + const std::string &value ) + { + XrdCl::Log *log = TestEnv::GetLog(); + log->Debug( 1, "Calling IdentityFile::SetProperty" ); + return pFile->SetProperty( name, value ); + } + + //------------------------------------------------------------------------ + // GetProperty + //------------------------------------------------------------------------ + virtual bool GetProperty( const std::string &name, + std::string &value ) const + { + XrdCl::Log *log = TestEnv::GetLog(); + log->Debug( 1, "Calling IdentityFile::GetProperty" ); + return pFile->GetProperty( name, value ); + } + + private: + XrdCl::File *pFile; + }; + + //---------------------------------------------------------------------------- + // A plug-in that forwards all the calls to a XrdCl::FileSystem object + //---------------------------------------------------------------------------- + class IdentityFileSystem: public FileSystemPlugIn + { + public: + //------------------------------------------------------------------------ + // Constructor + //------------------------------------------------------------------------ + IdentityFileSystem( const std::string &url ) + { + XrdCl::Log *log = TestEnv::GetLog(); + log->Debug( 1, "Calling IdentityFileSystem::IdentityFileSystem" ); + pFileSystem = new XrdCl::FileSystem( URL(url), false ); + } + + //------------------------------------------------------------------------ + // Destructor + //------------------------------------------------------------------------ + virtual ~IdentityFileSystem() + { + XrdCl::Log *log = TestEnv::GetLog(); + log->Debug( 1, "Calling IdentityFileSystem::~IdentityFileSysytem" ); + delete pFileSystem; + } + + //------------------------------------------------------------------------ + // Locate + //------------------------------------------------------------------------ + virtual XRootDStatus Locate( const std::string &path, + OpenFlags::Flags flags, + ResponseHandler *handler, + uint16_t timeout ) + { + XrdCl::Log *log = TestEnv::GetLog(); + log->Debug( 1, "Calling IdentityFileSystem::Locate" ); + return pFileSystem->Locate( path, flags, handler, timeout ); + } + + //------------------------------------------------------------------------ + // Mv + //------------------------------------------------------------------------ + virtual XRootDStatus Mv( const std::string &source, + const std::string &dest, + ResponseHandler *handler, + uint16_t timeout ) + { + XrdCl::Log *log = TestEnv::GetLog(); + log->Debug( 1, "Calling IdentityFileSystem::Mv" ); + return pFileSystem->Mv( source, dest, handler, timeout ); + } + + //------------------------------------------------------------------------ + // Query + //------------------------------------------------------------------------ + virtual XRootDStatus Query( QueryCode::Code queryCode, + const Buffer &arg, + ResponseHandler *handler, + uint16_t timeout ) + { + XrdCl::Log *log = TestEnv::GetLog(); + log->Debug( 1, "Calling IdentityFileSystem::Query" ); + return pFileSystem->Query( queryCode, arg, handler, timeout ); + } + + //------------------------------------------------------------------------ + // Truncate + //------------------------------------------------------------------------ + virtual XRootDStatus Truncate( const std::string &path, + uint64_t size, + ResponseHandler *handler, + uint16_t timeout ) + { + XrdCl::Log *log = TestEnv::GetLog(); + log->Debug( 1, "Calling IdentityFileSystem::Truncate" ); + return pFileSystem->Truncate( path, size, handler, timeout ); + } + + //------------------------------------------------------------------------ + // Rm + //------------------------------------------------------------------------ + virtual XRootDStatus Rm( const std::string &path, + ResponseHandler *handler, + uint16_t timeout ) + { + XrdCl::Log *log = TestEnv::GetLog(); + log->Debug( 1, "Calling IdentityFileSystem::Rm" ); + return pFileSystem->Rm( path, handler, timeout ); + } + + //------------------------------------------------------------------------ + // MkDir + //------------------------------------------------------------------------ + virtual XRootDStatus MkDir( const std::string &path, + MkDirFlags::Flags flags, + Access::Mode mode, + ResponseHandler *handler, + uint16_t timeout ) + { + XrdCl::Log *log = TestEnv::GetLog(); + log->Debug( 1, "Calling IdentityFileSystem::MkDir" ); + return pFileSystem->MkDir( path, flags, mode, handler, timeout ); + } + + //------------------------------------------------------------------------ + // RmDir + //------------------------------------------------------------------------ + virtual XRootDStatus RmDir( const std::string &path, + ResponseHandler *handler, + uint16_t timeout ) + { + XrdCl::Log *log = TestEnv::GetLog(); + log->Debug( 1, "Calling IdentityFileSystem::RmDir" ); + return pFileSystem->RmDir( path, handler, timeout ); + } + + //------------------------------------------------------------------------ + // ChMod + //------------------------------------------------------------------------ + virtual XRootDStatus ChMod( const std::string &path, + Access::Mode mode, + ResponseHandler *handler, + uint16_t timeout ) + { + XrdCl::Log *log = TestEnv::GetLog(); + log->Debug( 1, "Calling IdentityFileSystem::ChMod" ); + return pFileSystem->ChMod( path, mode, handler, timeout ); + } + + //------------------------------------------------------------------------ + // Ping + //------------------------------------------------------------------------ + virtual XRootDStatus Ping( ResponseHandler *handler, + uint16_t timeout ) + { + XrdCl::Log *log = TestEnv::GetLog(); + log->Debug( 1, "Calling IdentityFileSystem::Ping" ); + return pFileSystem->Ping( handler, timeout ); + } + + //------------------------------------------------------------------------ + // Stat + //------------------------------------------------------------------------ + virtual XRootDStatus Stat( const std::string &path, + ResponseHandler *handler, + uint16_t timeout ) + { + XrdCl::Log *log = TestEnv::GetLog(); + log->Debug( 1, "Calling IdentityFileSystem::Stat" ); + return pFileSystem->Stat( path, handler, timeout ); + } + + //------------------------------------------------------------------------ + // StatVFS + //------------------------------------------------------------------------ + virtual XRootDStatus StatVFS( const std::string &path, + ResponseHandler *handler, + uint16_t timeout ) + { + XrdCl::Log *log = TestEnv::GetLog(); + log->Debug( 1, "Calling IdentityFileSystem::StatVFS" ); + return pFileSystem->StatVFS( path, handler, timeout ); + } + + //------------------------------------------------------------------------ + // Protocol + //------------------------------------------------------------------------ + virtual XRootDStatus Protocol( ResponseHandler *handler, + uint16_t timeout = 0 ) + { + XrdCl::Log *log = TestEnv::GetLog(); + log->Debug( 1, "Calling IdentityFileSystem::Protocol" ); + return pFileSystem->Protocol( handler, timeout ); + } + + //------------------------------------------------------------------------ + // DirlList + //------------------------------------------------------------------------ + virtual XRootDStatus DirList( const std::string &path, + DirListFlags::Flags flags, + ResponseHandler *handler, + uint16_t timeout ) + { + XrdCl::Log *log = TestEnv::GetLog(); + log->Debug( 1, "Calling IdentityFileSystem::DirList" ); + return pFileSystem->DirList( path, flags, handler, timeout ); + } + + //------------------------------------------------------------------------ + // SendInfo + //------------------------------------------------------------------------ + virtual XRootDStatus SendInfo( const std::string &info, + ResponseHandler *handler, + uint16_t timeout ) + { + XrdCl::Log *log = TestEnv::GetLog(); + log->Debug( 1, "Calling IdentityFileSystem::SendInfo" ); + return pFileSystem->SendInfo( info, handler, timeout ); + } + + //------------------------------------------------------------------------ + // Prepare + //------------------------------------------------------------------------ + virtual XRootDStatus Prepare( const std::vector &fileList, + PrepareFlags::Flags flags, + uint8_t priority, + ResponseHandler *handler, + uint16_t timeout ) + { + XrdCl::Log *log = TestEnv::GetLog(); + log->Debug( 1, "Calling IdentityFileSystem::Prepare" ); + return pFileSystem->Prepare( fileList, flags, priority, handler, + timeout ); + } + + //------------------------------------------------------------------------ + // SetProperty + //------------------------------------------------------------------------ + virtual bool SetProperty( const std::string &name, + const std::string &value ) + { + XrdCl::Log *log = TestEnv::GetLog(); + log->Debug( 1, "Calling IdentityFileSystem::SetProperty" ); + return pFileSystem->SetProperty( name, value ); + } + + //------------------------------------------------------------------------ + // GetProperty + //------------------------------------------------------------------------ + virtual bool GetProperty( const std::string &name, + std::string &value ) const + { + XrdCl::Log *log = TestEnv::GetLog(); + log->Debug( 1, "Calling IdentityFilesystem::GetProperty" ); + return pFileSystem->GetProperty( name, value ); + } + + private: + XrdCl::FileSystem *pFileSystem; + }; +} + +namespace XrdClTests +{ + //---------------------------------------------------------------------------- + // Create a file plug-in for the given URL + //---------------------------------------------------------------------------- + FilePlugIn *IdentityFactory::CreateFile( const std::string &url ) + { + XrdCl::Log *log = TestEnv::GetLog(); + log->Debug( 1, "Creating an identity file plug-in" ); + return new IdentityFile(); + } + + //---------------------------------------------------------------------------- + // Create a file system plug-in for the given URL + //---------------------------------------------------------------------------- + FileSystemPlugIn *IdentityFactory::CreateFileSystem( const std::string &url ) + { + XrdCl::Log *log = TestEnv::GetLog(); + log->Debug( 1, "Creating an identity file system plug-in" ); + return new IdentityFileSystem( url ); + } +} + diff --git a/tests/XrdCl/IdentityPlugIn.hh b/tests/XrdCl/IdentityPlugIn.hh new file mode 100644 index 00000000000..7f6fb0d5c90 --- /dev/null +++ b/tests/XrdCl/IdentityPlugIn.hh @@ -0,0 +1,55 @@ +//------------------------------------------------------------------------------ +// Copyright (c) 2023 by European Organization for Nuclear Research (CERN) +// Author: Angelo Galavotti +//------------------------------------------------------------------------------ +// This file is part of the XRootD software suite. +// +// XRootD is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// XRootD is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with XRootD. If not, see . +// +// In applying this licence, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. +//------------------------------------------------------------------------------ + +#ifndef __XRDCLTESTS_IDENTITY_PLUGIN_HH__ +#define __XRDCLTESTS_IDENTITY_PLUGIN_HH__ + +#include "XrdCl/XrdClPlugInInterface.hh" + +namespace XrdClTests +{ + //---------------------------------------------------------------------------- + // Plugin factory + //---------------------------------------------------------------------------- + class IdentityFactory: public XrdCl::PlugInFactory + { + public: + //------------------------------------------------------------------------ + // Destructor + //------------------------------------------------------------------------ + virtual ~IdentityFactory() {} + + //------------------------------------------------------------------------ + // Create a file plug-in for the given URL + //------------------------------------------------------------------------ + virtual XrdCl::FilePlugIn *CreateFile( const std::string &url ); + + //------------------------------------------------------------------------ + // Create a file system plug-in for the given URL + //------------------------------------------------------------------------ + virtual XrdCl::FileSystemPlugIn *CreateFileSystem( const std::string &url ); + }; +}; + +#endif // __XRDCLTESTS_IDENTITY_PLUGIN_HH__ diff --git a/tests/XrdCl/XrdClFileTest.cc b/tests/XrdCl/XrdClFileTest.cc new file mode 100644 index 00000000000..b0303621cd2 --- /dev/null +++ b/tests/XrdCl/XrdClFileTest.cc @@ -0,0 +1,836 @@ +//------------------------------------------------------------------------------ +// Copyright (c) 2023 by European Organization for Nuclear Research (CERN) +// Author: Angelo Galavotti +//------------------------------------------------------------------------------ +// XRootD is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// XRootD is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with XRootD. If not, see . +//------------------------------------------------------------------------------ + +#include "TestEnv.hh" +#include "Utils.hh" +#include "IdentityPlugIn.hh" + +#include "GTestXrdHelpers.hh" +#include "XrdCl/XrdClFile.hh" +#include "XrdCl/XrdClDefaultEnv.hh" +#include "XrdCl/XrdClPlugInManager.hh" +#include "XrdCl/XrdClMessage.hh" +#include "XrdCl/XrdClSIDManager.hh" +#include "XrdCl/XrdClPostMaster.hh" +#include "XrdCl/XrdClXRootDTransport.hh" +#include "XrdCl/XrdClMessageUtils.hh" +#include "XrdCl/XrdClXRootDMsgHandler.hh" +#include "XrdCl/XrdClCopyProcess.hh" +#include "XrdCl/XrdClZipArchive.hh" +#include "XrdCl/XrdClConstants.hh" +#include "XrdCl/XrdClZipOperations.hh" + +using namespace XrdClTests; + +//------------------------------------------------------------------------------ +// Class declaration +//------------------------------------------------------------------------------ +class FileTest: public ::testing::Test +{ + public: + void RedirectReturnTest(); + void ReadTest(); + void WriteTest(); + void WriteVTest(); + void VectorReadTest(); + void VectorWriteTest(); + void VirtualRedirectorTest(); + void XAttrTest(); +}; + +//------------------------------------------------------------------------------ +// Tests declaration +//------------------------------------------------------------------------------ +TEST_F(FileTest, RedirectReturnTest) +{ + RedirectReturnTest(); +} + +TEST_F(FileTest, ReadTest) +{ + ReadTest(); +} + +TEST_F(FileTest, WriteTest) +{ + WriteTest(); +} + +TEST_F(FileTest, WriteVTest) +{ + WriteVTest(); +} + +TEST_F(FileTest, VectorReadTest) +{ + VectorReadTest(); +} + +TEST_F(FileTest, VectorWriteTest) +{ + VectorWriteTest(); +} + +TEST_F(FileTest, VirtualRedirectorTest) +{ + VirtualRedirectorTest(); +} + +TEST_F(FileTest, XAttrTest) +{ + XAttrTest(); +} + +TEST_F(FileTest, PlugInTest) +{ + XrdCl::PlugInFactory *f = new IdentityFactory; + XrdCl::DefaultEnv::GetPlugInManager()->RegisterDefaultFactory(f); + RedirectReturnTest(); + ReadTest(); + WriteTest(); + VectorReadTest(); + XrdCl::DefaultEnv::GetPlugInManager()->RegisterDefaultFactory(0); +} + +//------------------------------------------------------------------------------ +// Redirect return test +//------------------------------------------------------------------------------ +void FileTest::RedirectReturnTest() +{ + using namespace XrdCl; + + //---------------------------------------------------------------------------- + // Initialize + //---------------------------------------------------------------------------- + Env *testEnv = TestEnv::GetEnv(); + + std::string address; + std::string dataPath; + + EXPECT_TRUE( testEnv->GetString( "MainServerURL", address ) ); + EXPECT_TRUE( testEnv->GetString( "DataPath", dataPath ) ); + + URL url( address ); + EXPECT_TRUE( url.IsValid() ); + + std::string path = dataPath + "/cb4aacf1-6f28-42f2-b68a-90a73460f424.dat"; + std::string fileUrl = address + "/" + path; + + //---------------------------------------------------------------------------- + // Build the open request + //---------------------------------------------------------------------------- + Message *msg; + ClientOpenRequest *req; + MessageUtils::CreateRequest( msg, req, path.length() ); + req->requestid = kXR_open; + req->options = kXR_open_read | kXR_retstat; + req->dlen = path.length(); + msg->Append( path.c_str(), path.length(), 24 ); + XRootDTransport::SetDescription( msg ); + + SyncResponseHandler *handler = new SyncResponseHandler(); + MessageSendParams params; params.followRedirects = false; + MessageUtils::ProcessSendParams( params ); + OpenInfo *response = 0; + GTEST_ASSERT_XRDST( MessageUtils::SendMessage( url, msg, handler, params, 0 ) ); + XRootDStatus st1 = MessageUtils::WaitForResponse( handler, response ); + delete handler; + GTEST_ASSERT_XRDST_NOTOK( st1, errRedirect ); + EXPECT_TRUE( !response ); + delete response; +} + +//------------------------------------------------------------------------------ +// Read test +//------------------------------------------------------------------------------ +void FileTest::ReadTest() +{ + using namespace XrdCl; + + //---------------------------------------------------------------------------- + // Initialize + //---------------------------------------------------------------------------- + Env *testEnv = TestEnv::GetEnv(); + + std::string address; + std::string dataPath; + + EXPECT_TRUE( testEnv->GetString( "MainServerURL", address ) ); + EXPECT_TRUE( testEnv->GetString( "DataPath", dataPath ) ); + + URL url( address ); + EXPECT_TRUE( url.IsValid() ); + + std::string filePath = dataPath + "/cb4aacf1-6f28-42f2-b68a-90a73460f424.dat"; + std::string fileUrl = address + "/"; + fileUrl += filePath; + + //---------------------------------------------------------------------------- + // Fetch some data and checksum + //---------------------------------------------------------------------------- + const uint32_t MB = 1024*1024; + char *buffer1 = new char[40*MB]; + char *buffer2 = new char[40*MB]; + uint32_t bytesRead1 = 0; + uint32_t bytesRead2 = 0; + File f; + StatInfo *stat; + + //---------------------------------------------------------------------------- + // Open the file + //---------------------------------------------------------------------------- + GTEST_ASSERT_XRDST( f.Open( fileUrl, OpenFlags::Read ) ); + + //---------------------------------------------------------------------------- + // Stat1 + //---------------------------------------------------------------------------- + GTEST_ASSERT_XRDST( f.Stat( false, stat ) ); + EXPECT_TRUE( stat ); + EXPECT_TRUE( stat->GetSize() == 1048576000 ); + EXPECT_TRUE( stat->TestFlags( StatInfo::IsReadable ) ); + delete stat; + stat = 0; + + //---------------------------------------------------------------------------- + // Stat2 + //---------------------------------------------------------------------------- + GTEST_ASSERT_XRDST( f.Stat( true, stat ) ); + EXPECT_TRUE( stat ); + EXPECT_TRUE( stat->GetSize() == 1048576000 ); + EXPECT_TRUE( stat->TestFlags( StatInfo::IsReadable ) ); + delete stat; + + //---------------------------------------------------------------------------- + // Read test + //---------------------------------------------------------------------------- + GTEST_ASSERT_XRDST( f.Read( 10*MB, 40*MB, buffer1, bytesRead1 ) ); + GTEST_ASSERT_XRDST( f.Read( 1008576000, 40*MB, buffer2, bytesRead2 ) ); + EXPECT_TRUE( bytesRead1 == 40*MB ); + EXPECT_TRUE( bytesRead2 == 40000000 ); + + uint32_t crc = XrdClTests::Utils::ComputeCRC32( buffer1, 40*MB ); + EXPECT_TRUE( crc == 3303853367UL ); + + crc = XrdClTests::Utils::ComputeCRC32( buffer2, 40000000 ); + EXPECT_TRUE( crc == 898701504UL ); + + delete [] buffer1; + delete [] buffer2; + + GTEST_ASSERT_XRDST( f.Close() ); + + //---------------------------------------------------------------------------- + // Read ZIP archive test (uncompressed) + //---------------------------------------------------------------------------- + std::string archiveUrl = address + "/" + dataPath + "/data.zip"; + + ZipArchive zip; + GTEST_ASSERT_XRDST( WaitFor( OpenArchive( zip, archiveUrl, OpenFlags::Read ) ) ); + + //---------------------------------------------------------------------------- + // There are 3 files in the data.zip archive: + // - athena.log + // - paper.txt + // - EastAsianWidth.txt + //---------------------------------------------------------------------------- + + struct + { + std::string file; // file name + uint64_t offset; // offset in the file + uint32_t size; // number of characters to be read + char buffer[100]; // the buffer + std::string expected; // expected result + } testset[] = + { + { "athena.log", 65530, 99, {0}, "D__Jet" }, // reads past the end of the file (there are just 6 characters to read not 99) + { "paper.txt", 1024, 65, {0}, "igh rate (the order of 100 kHz), the data are usually distributed" }, + { "EastAsianWidth.txt", 2048, 18, {0}, "8;Na # DIGIT EIGHT" } + }; + + for( int i = 0; i < 3; ++i ) + { + std::string result; + GTEST_ASSERT_XRDST( WaitFor( + ReadFrom( zip, testset[i].file, testset[i].offset, testset[i].size, testset[i].buffer ) >> + [&result]( auto& s, auto& c ) + { + if( s.IsOK() ) + result.assign( static_cast(c.buffer), c.length ); + } + ) ); + EXPECT_TRUE( testset[i].expected == result ); + } + + GTEST_ASSERT_XRDST( WaitFor( CloseArchive( zip ) ) ); +} + + +//------------------------------------------------------------------------------ +// Read test +//------------------------------------------------------------------------------ +void FileTest::WriteTest() +{ + using namespace XrdCl; + + //---------------------------------------------------------------------------- + // Initialize + //---------------------------------------------------------------------------- + Env *testEnv = TestEnv::GetEnv(); + + std::string address; + std::string dataPath; + + EXPECT_TRUE( testEnv->GetString( "MainServerURL", address ) ); + EXPECT_TRUE( testEnv->GetString( "DataPath", dataPath ) ); + + URL url( address ); + EXPECT_TRUE( url.IsValid() ); + + std::string filePath = dataPath + "/testFile.dat"; + std::string fileUrl = address + "/"; + fileUrl += filePath; + + //---------------------------------------------------------------------------- + // Fetch some data and checksum + //---------------------------------------------------------------------------- + const uint32_t MB = 1024*1024; + char *buffer1 = new char[4*MB]; + char *buffer2 = new char[4*MB]; + char *buffer3 = new char[4*MB]; + char *buffer4 = new char[4*MB]; + uint32_t bytesRead1 = 0; + uint32_t bytesRead2 = 0; + File f1, f2; + + EXPECT_TRUE( XrdClTests::Utils::GetRandomBytes( buffer1, 4*MB ) == 4*MB ); + EXPECT_TRUE( XrdClTests::Utils::GetRandomBytes( buffer2, 4*MB ) == 4*MB ); + uint32_t crc1 = XrdClTests::Utils::ComputeCRC32( buffer1, 4*MB ); + crc1 = XrdClTests::Utils::UpdateCRC32( crc1, buffer2, 4*MB ); + + //---------------------------------------------------------------------------- + // Write the data + //---------------------------------------------------------------------------- + EXPECT_TRUE( f1.Open( fileUrl, OpenFlags::Delete | OpenFlags::Update, + Access::UR | Access::UW ).IsOK() ); + EXPECT_TRUE( f1.Write( 0, 4*MB, buffer1 ).IsOK() ); + EXPECT_TRUE( f1.Write( 4*MB, 4*MB, buffer2 ).IsOK() ); + EXPECT_TRUE( f1.Sync().IsOK() ); + EXPECT_TRUE( f1.Close().IsOK() ); + + //---------------------------------------------------------------------------- + // Read the data and verify the checksums + //---------------------------------------------------------------------------- + StatInfo *stat = 0; + EXPECT_TRUE( f2.Open( fileUrl, OpenFlags::Read ).IsOK() ); + EXPECT_TRUE( f2.Stat( false, stat ).IsOK() ); + EXPECT_TRUE( stat ); + EXPECT_TRUE( stat->GetSize() == 8*MB ); + EXPECT_TRUE( f2.Read( 0, 4*MB, buffer3, bytesRead1 ).IsOK() ); + EXPECT_TRUE( f2.Read( 4*MB, 4*MB, buffer4, bytesRead2 ).IsOK() ); + EXPECT_TRUE( bytesRead1 == 4*MB ); + EXPECT_TRUE( bytesRead2 == 4*MB ); + uint32_t crc2 = XrdClTests::Utils::ComputeCRC32( buffer3, 4*MB ); + crc2 = XrdClTests::Utils::UpdateCRC32( crc2, buffer4, 4*MB ); + EXPECT_TRUE( f2.Close().IsOK() ); + EXPECT_TRUE( crc1 == crc2 ); + + //---------------------------------------------------------------------------- + // Truncate test + //---------------------------------------------------------------------------- + EXPECT_TRUE( f1.Open( fileUrl, OpenFlags::Delete | OpenFlags::Update, + Access::UR | Access::UW ).IsOK() ); + EXPECT_TRUE( f1.Truncate( 20*MB ).IsOK() ); + EXPECT_TRUE( f1.Close().IsOK() ); + FileSystem fs( url ); + StatInfo *response = 0; + EXPECT_TRUE( fs.Stat( filePath, response ).IsOK() ); + EXPECT_TRUE( response ); + EXPECT_TRUE( response->GetSize() == 20*MB ); + EXPECT_TRUE( fs.Rm( filePath ).IsOK() ); + delete [] buffer1; + delete [] buffer2; + delete [] buffer3; + delete [] buffer4; + delete response; + delete stat; +} + +//------------------------------------------------------------------------------ +// WriteV test +//------------------------------------------------------------------------------ +void FileTest::WriteVTest() +{ + using namespace XrdCl; + + //---------------------------------------------------------------------------- + // Initialize + //---------------------------------------------------------------------------- + Env *testEnv = TestEnv::GetEnv(); + + std::string address; + std::string dataPath; + + EXPECT_TRUE( testEnv->GetString( "MainServerURL", address ) ); + EXPECT_TRUE( testEnv->GetString( "DataPath", dataPath ) ); + + URL url( address ); + EXPECT_TRUE( url.IsValid() ); + + std::string filePath = dataPath + "/testFile.dat"; + std::string fileUrl = address + "/"; + fileUrl += filePath; + + //---------------------------------------------------------------------------- + // Fetch some data and checksum + //---------------------------------------------------------------------------- + const uint32_t MB = 1024*1024; + char *buffer1 = new char[4*MB]; + char *buffer2 = new char[4*MB]; + char *buffer3 = new char[8*MB]; + uint32_t bytesRead1 = 0; + File f1, f2; + + EXPECT_TRUE( XrdClTests::Utils::GetRandomBytes( buffer1, 4*MB ) == 4*MB ); + EXPECT_TRUE( XrdClTests::Utils::GetRandomBytes( buffer2, 4*MB ) == 4*MB ); + uint32_t crc1 = XrdClTests::Utils::ComputeCRC32( buffer1, 4*MB ); + crc1 = XrdClTests::Utils::UpdateCRC32( crc1, buffer2, 4*MB ); + + //---------------------------------------------------------------------------- + // Prepare IO vector + //---------------------------------------------------------------------------- + int iovcnt = 2; + iovec iov[iovcnt]; + iov[0].iov_base = buffer1; + iov[0].iov_len = 4*MB; + iov[1].iov_base = buffer2; + iov[1].iov_len = 4*MB; + + //---------------------------------------------------------------------------- + // Write the data + //---------------------------------------------------------------------------- + EXPECT_TRUE( f1.Open( fileUrl, OpenFlags::Delete | OpenFlags::Update, + Access::UR | Access::UW ).IsOK() ); + EXPECT_TRUE( f1.WriteV( 0, iov, iovcnt ).IsOK() ); + EXPECT_TRUE( f1.Sync().IsOK() ); + EXPECT_TRUE( f1.Close().IsOK() ); + + //---------------------------------------------------------------------------- + // Read the data and verify the checksums + //---------------------------------------------------------------------------- + StatInfo *stat = 0; + EXPECT_TRUE( f2.Open( fileUrl, OpenFlags::Read ).IsOK() ); + EXPECT_TRUE( f2.Stat( false, stat ).IsOK() ); + EXPECT_TRUE( stat ); + EXPECT_TRUE( stat->GetSize() == 8*MB ); + EXPECT_TRUE( f2.Read( 0, 8*MB, buffer3, bytesRead1 ).IsOK() ); + EXPECT_TRUE( bytesRead1 == 8*MB ); + + uint32_t crc2 = XrdClTests::Utils::ComputeCRC32( buffer3, 8*MB ); + EXPECT_TRUE( f2.Close().IsOK() ); + EXPECT_TRUE( crc1 == crc2 ); + + FileSystem fs( url ); + EXPECT_TRUE( fs.Rm( filePath ).IsOK() ); + delete [] buffer1; + delete [] buffer2; + delete [] buffer3; + delete stat; +} + +//------------------------------------------------------------------------------ +// Vector read test +//------------------------------------------------------------------------------ +void FileTest::VectorReadTest() +{ + using namespace XrdCl; + + //---------------------------------------------------------------------------- + // Initialize + //---------------------------------------------------------------------------- + Env *testEnv = TestEnv::GetEnv(); + + std::string address; + std::string dataPath; + + EXPECT_TRUE( testEnv->GetString( "MainServerURL", address ) ); + EXPECT_TRUE( testEnv->GetString( "DataPath", dataPath ) ); + + URL url( address ); + EXPECT_TRUE( url.IsValid() ); + + std::string filePath = dataPath + "/a048e67f-4397-4bb8-85eb-8d7e40d90763.dat"; + std::string fileUrl = address + "/"; + fileUrl += filePath; + + //---------------------------------------------------------------------------- + // Fetch some data and checksum + //---------------------------------------------------------------------------- + const uint32_t MB = 1024*1024; + char *buffer1 = new char[40*MB]; + char *buffer2 = new char[40*256000]; + File f; + + //---------------------------------------------------------------------------- + // Build the chunk list + //---------------------------------------------------------------------------- + ChunkList chunkList1; + ChunkList chunkList2; + for( int i = 0; i < 40; ++i ) + { + chunkList1.push_back( ChunkInfo( (i+1)*10*MB, 1*MB ) ); + chunkList2.push_back( ChunkInfo( (i+1)*10*MB, 256000 ) ); + } + + //---------------------------------------------------------------------------- + // Open the file + //---------------------------------------------------------------------------- + GTEST_ASSERT_XRDST( f.Open( fileUrl, OpenFlags::Read ) ); + VectorReadInfo *info = 0; + GTEST_ASSERT_XRDST( f.VectorRead( chunkList1, buffer1, info ) ); + EXPECT_TRUE( info->GetSize() == 40*MB ); + delete info; + uint32_t crc = 0; + crc = XrdClTests::Utils::ComputeCRC32( buffer1, 40*MB ); + EXPECT_TRUE( crc == 3695956670UL ); + + info = 0; + GTEST_ASSERT_XRDST( f.VectorRead( chunkList2, buffer2, info ) ); + EXPECT_TRUE( info->GetSize() == 40*256000 ); + delete info; + crc = XrdClTests::Utils::ComputeCRC32( buffer2, 40*256000 ); + EXPECT_TRUE( crc == 3492603530UL ); + + GTEST_ASSERT_XRDST( f.Close() ); + + delete [] buffer1; + delete [] buffer2; +} + +void gen_random_str(char *s, const int len) +{ + static const char alphanum[] = + "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; + + for (int i = 0; i < len - 1; ++i) + { + s[i] = alphanum[rand() % (sizeof(alphanum) - 1)]; + } + + s[len - 1] = 0; +} + +//------------------------------------------------------------------------------ +// Vector write test +//------------------------------------------------------------------------------ +void FileTest::VectorWriteTest() +{ + using namespace XrdCl; + + //---------------------------------------------------------------------------- + // Initialize + //---------------------------------------------------------------------------- + Env *testEnv = TestEnv::GetEnv(); + + std::string address; + std::string dataPath; + + EXPECT_TRUE( testEnv->GetString( "MainServerURL", address ) ); + EXPECT_TRUE( testEnv->GetString( "DataPath", dataPath ) ); + + URL url( address ); + EXPECT_TRUE( url.IsValid() ); + + std::string filePath = dataPath + "/a048e67f-4397-4bb8-85eb-8d7e40d90763.dat"; + std::string fileUrl = address + "/"; + fileUrl += filePath; + + //---------------------------------------------------------------------------- + // Build a random chunk list for vector write + //---------------------------------------------------------------------------- + + const uint32_t MB = 1024*1024; + const uint32_t GB = 1000*MB; // maybe that's not 100% precise but that's + // what we have in our testbed + + time_t seed = time( 0 ); + srand( seed ); + DefaultEnv::GetLog()->Info( UtilityMsg, + "Carrying out the VectorWrite test with seed: %d", seed ); + + // figure out how many chunks are we going to write/read + size_t nbChunks = rand() % 100 + 1; + + XrdCl::ChunkList chunks; + size_t min_offset = 0; + uint32_t expectedCrc32 = 0; + size_t totalSize = 0; + + for( size_t i = 0; i < nbChunks; ++i ) + { + // figure out the offset + size_t offset = min_offset + rand() % ( GB - min_offset + 1 ); + + // figure out the size + size_t size = MB + rand() % ( MB + 1 ); + if( offset + size >= GB ) + size = GB - offset; + + // generate random string of given size + char *buffer = new char[size]; + gen_random_str( buffer, size ); + + // calculate expected checksum + expectedCrc32 = XrdClTests::Utils::UpdateCRC32( expectedCrc32, buffer, size ); + totalSize += size; + chunks.push_back( XrdCl::ChunkInfo( offset, size, buffer ) ); + + min_offset = offset + size; + if( min_offset >= GB ) + break; + } + + //---------------------------------------------------------------------------- + // Open the file + //---------------------------------------------------------------------------- + File f; + GTEST_ASSERT_XRDST( f.Open( fileUrl, OpenFlags::Update ) ); + + //---------------------------------------------------------------------------- + // First do a VectorRead so we can revert to the original state + //---------------------------------------------------------------------------- + char *buffer1 = new char[totalSize]; + VectorReadInfo *info1 = 0; + GTEST_ASSERT_XRDST( f.VectorRead( chunks, buffer1, info1 ) ); + + //---------------------------------------------------------------------------- + // Then do the VectorWrite + //---------------------------------------------------------------------------- + GTEST_ASSERT_XRDST( f.VectorWrite( chunks ) ); + + //---------------------------------------------------------------------------- + // Now do a vector read and verify that the checksum is the same + //---------------------------------------------------------------------------- + char *buffer2 = new char[totalSize]; + VectorReadInfo *info2 = 0; + GTEST_ASSERT_XRDST( f.VectorRead( chunks, buffer2, info2 ) ); + + EXPECT_TRUE( info2->GetSize() == totalSize ); + uint32_t crc32 = XrdClTests::Utils::ComputeCRC32( buffer2, totalSize ); + EXPECT_TRUE( crc32 == expectedCrc32 ); + + //---------------------------------------------------------------------------- + // And finally revert to the original state + //---------------------------------------------------------------------------- + GTEST_ASSERT_XRDST( f.VectorWrite( info1->GetChunks() ) ); + GTEST_ASSERT_XRDST( f.Close() ); + + delete info1; + delete info2; + delete [] buffer1; + delete [] buffer2; + for( auto itr = chunks.begin(); itr != chunks.end(); ++itr ) + delete[] (char*)itr->buffer; +} + +void FileTest::VirtualRedirectorTest() +{ + using namespace XrdCl; + + //---------------------------------------------------------------------------- + // Initialize + //---------------------------------------------------------------------------- + Env *testEnv = TestEnv::GetEnv(); + + std::string address; + std::string dataPath; + + EXPECT_TRUE( testEnv->GetString( "MainServerURL", address ) ); + EXPECT_TRUE( testEnv->GetString( "DataPath", dataPath ) ); + + URL url( address ); + EXPECT_TRUE( url.IsValid() ); + + std::string mlUrl1 = address + "/" + dataPath + "/metalink/mlFileTest1.meta4"; + std::string mlUrl2 = address + "/" + dataPath + "/metalink/mlFileTest2.meta4"; + std::string mlUrl3 = address + "/" + dataPath + "/metalink/mlFileTest3.meta4"; + std::string mlUrl4 = address + "/" + dataPath + "/metalink/mlFileTest4.meta4"; + + File f1, f2, f3, f4; + + const std::string fileUrl = "root://srv1:1094//data/a048e67f-4397-4bb8-85eb-8d7e40d90763.dat"; + const std::string key = "LastURL"; + std::string value; + + //---------------------------------------------------------------------------- + // Open the 1st metalink file + // (the metalink contains just one file with a correct location) + //---------------------------------------------------------------------------- + GTEST_ASSERT_XRDST( f1.Open( mlUrl1, OpenFlags::Read ) ); + EXPECT_TRUE( f1.GetProperty( key, value ) ); + URL lastUrl( value ); + EXPECT_TRUE( lastUrl.GetLocation() == fileUrl ); + GTEST_ASSERT_XRDST( f1.Close() ); + + //---------------------------------------------------------------------------- + // Open the 2nd metalink file + // (the metalink contains 2 files, the one with higher priority does not exist) + //---------------------------------------------------------------------------- + GTEST_ASSERT_XRDST( f2.Open( mlUrl2, OpenFlags::Read ) ); + EXPECT_TRUE( f2.GetProperty( key, value ) ); + URL lastUrl2( value ); + EXPECT_TRUE( lastUrl2.GetLocation() == fileUrl ); + GTEST_ASSERT_XRDST( f2.Close() ); + + //---------------------------------------------------------------------------- + // Open the 3rd metalink file + // (the metalink contains 2 files, both don't exist) + //---------------------------------------------------------------------------- + GTEST_ASSERT_XRDST_NOTOK( f3.Open( mlUrl3, OpenFlags::Read ), errErrorResponse ); + + //---------------------------------------------------------------------------- + // Open the 4th metalink file + // (the metalink contains 2 files, both exist) + //---------------------------------------------------------------------------- + const std::string replica1 = "root://srv3:1094//data/3c9a9dd8-bc75-422c-b12c-f00604486cc1.dat"; + const std::string replica2 = "root://srv2:1094//data/3c9a9dd8-bc75-422c-b12c-f00604486cc1.dat"; + + GTEST_ASSERT_XRDST( f4.Open( mlUrl4, OpenFlags::Read ) ); + EXPECT_TRUE( f4.GetProperty( key, value ) ); + URL lastUrl3( value ); + EXPECT_TRUE( lastUrl3.GetLocation() == replica1 ); + GTEST_ASSERT_XRDST( f4.Close() ); + //---------------------------------------------------------------------------- + // Delete the replica that has been selected by the virtual redirector + //---------------------------------------------------------------------------- + FileSystem fs( replica1 ); + GTEST_ASSERT_XRDST( fs.Rm( "/data/3c9a9dd8-bc75-422c-b12c-f00604486cc1.dat" ) ); + //---------------------------------------------------------------------------- + // Now reopen the file + //---------------------------------------------------------------------------- + GTEST_ASSERT_XRDST( f4.Open( mlUrl4, OpenFlags::Read ) ); + EXPECT_TRUE( f4.GetProperty( key, value ) ); + URL lastUrl4( value ); + EXPECT_TRUE( lastUrl4.GetLocation() == replica2 ); + GTEST_ASSERT_XRDST( f4.Close() ); + //---------------------------------------------------------------------------- + // Recreate the deleted file + //---------------------------------------------------------------------------- + CopyProcess process; + PropertyList properties, results; + properties.Set( "source", replica2 ); + properties.Set( "target", replica1 ); + GTEST_ASSERT_XRDST( process.AddJob( properties, &results ) ); + GTEST_ASSERT_XRDST( process.Prepare() ); + GTEST_ASSERT_XRDST( process.Run(0) ); +} + +void FileTest::XAttrTest() +{ + using namespace XrdCl; + + //---------------------------------------------------------------------------- + // Get the environment variables + //---------------------------------------------------------------------------- + Env *testEnv = TestEnv::GetEnv(); + + std::string address; + std::string dataPath; + + EXPECT_TRUE( testEnv->GetString( "DiskServerURL", address ) ); + EXPECT_TRUE( testEnv->GetString( "DataPath", dataPath ) ); + + std::string filePath = dataPath + "/a048e67f-4397-4bb8-85eb-8d7e40d90763.dat"; + std::string fileUrl = address + "/" + filePath; + + URL url( address ); + EXPECT_TRUE( url.IsValid() ); + + + File file; + GTEST_ASSERT_XRDST( file.Open( fileUrl, OpenFlags::Update ) ); + + std::map attributes + { + std::make_pair( "version", "v1.2.3-45" ), + std::make_pair( "checksum", "2ccc0e85556a6cd193dd8d2b40aab50c" ), + std::make_pair( "index", "4" ) + }; + + //---------------------------------------------------------------------------- + // Test SetXAttr + //---------------------------------------------------------------------------- + std::vector attrs; + auto itr1 = attributes.begin(); + for( ; itr1 != attributes.end() ; ++itr1 ) + attrs.push_back( std::make_tuple( itr1->first, itr1->second ) ); + + std::vector result1; + GTEST_ASSERT_XRDST( file.SetXAttr( attrs, result1 ) ); + + auto itr2 = result1.begin(); + for( ; itr2 != result1.end() ; ++itr2 ) + GTEST_ASSERT_XRDST( itr2->status ); + + //---------------------------------------------------------------------------- + // Test GetXAttr + //---------------------------------------------------------------------------- + std::vector names; + itr1 = attributes.begin(); + for( ; itr1 != attributes.end() ; ++itr1 ) + names.push_back( itr1->first ); + + std::vector result2; + GTEST_ASSERT_XRDST( file.GetXAttr( names, result2 ) ); + + auto itr3 = result2.begin(); + for( ; itr3 != result2.end() ; ++itr3 ) + { + GTEST_ASSERT_XRDST( itr3->status ); + auto match = attributes.find( itr3->name ); + EXPECT_TRUE( match != attributes.end() ); + EXPECT_TRUE( match->second == itr3->value ); + } + + //---------------------------------------------------------------------------- + // Test ListXAttr + //---------------------------------------------------------------------------- + GTEST_ASSERT_XRDST( file.ListXAttr( result2 ) ); + + itr3 = result2.begin(); + for( ; itr3 != result2.end() ; ++itr3 ) + { + GTEST_ASSERT_XRDST( itr3->status ); + auto match = attributes.find( itr3->name ); + EXPECT_TRUE( match != attributes.end() ); + EXPECT_TRUE( match->second == itr3->value ); + } + + //---------------------------------------------------------------------------- + // Test DelXAttr + //---------------------------------------------------------------------------- + GTEST_ASSERT_XRDST( file.DelXAttr( names, result1 ) ); + + itr2 = result1.begin(); + for( ; itr2 != result1.end() ; ++itr2 ) + GTEST_ASSERT_XRDST( itr2->status ); + + GTEST_ASSERT_XRDST( file.Close() ); +} From d42f582c87a5a69755bfa6d3ce4275f922dbed7b Mon Sep 17 00:00:00 2001 From: Angelo Galavotti Date: Thu, 20 Jul 2023 17:05:08 +0200 Subject: [PATCH 279/442] [Tests] Add converted tests from FileCopyTest Converts tests in FileCopyTests from CPPUnit to GTest --- tests/XrdCl/XrdClFileCopyTest.cc | 635 +++++++++++++++++++++++++++++++ 1 file changed, 635 insertions(+) create mode 100644 tests/XrdCl/XrdClFileCopyTest.cc diff --git a/tests/XrdCl/XrdClFileCopyTest.cc b/tests/XrdCl/XrdClFileCopyTest.cc new file mode 100644 index 00000000000..175145f2c3b --- /dev/null +++ b/tests/XrdCl/XrdClFileCopyTest.cc @@ -0,0 +1,635 @@ +//------------------------------------------------------------------------------ +// Copyright (c) 2023 by European Organization for Nuclear Research (CERN) +// Author: Angelo Galavotti +//------------------------------------------------------------------------------ +// XRootD is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// XRootD is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with XRootD. If not, see . +//------------------------------------------------------------------------------ + +#include "TestEnv.hh" +#include "GTestXrdHelpers.hh" +#include "XrdCl/XrdClFile.hh" +#include "XrdCl/XrdClDefaultEnv.hh" +#include "XrdCl/XrdClMessage.hh" +#include "XrdCl/XrdClSIDManager.hh" +#include "XrdCl/XrdClPostMaster.hh" +#include "XrdCl/XrdClXRootDTransport.hh" +#include "XrdCl/XrdClMessageUtils.hh" +#include "XrdCl/XrdClXRootDMsgHandler.hh" +#include "XrdCl/XrdClUtils.hh" +#include "XrdCl/XrdClCheckSumManager.hh" +#include "XrdCl/XrdClCopyProcess.hh" + +#include "XrdCks/XrdCks.hh" +#include "XrdCks/XrdCksCalc.hh" +#include "XrdCks/XrdCksData.hh" + +#include +#include +#include + +using namespace XrdClTests; + +//------------------------------------------------------------------------------ +// Declaration +//------------------------------------------------------------------------------ +class FileCopyTest : public ::testing::Test +{ + public: + void DownloadTestFunc(); + void UploadTestFunc(); + void CopyTestFunc( bool thirdParty = true ); +}; + +//------------------------------------------------------------------------------ +// Download test +//------------------------------------------------------------------------------ +void FileCopyTest::DownloadTestFunc() +{ + using namespace XrdCl; + + //---------------------------------------------------------------------------- + // Initialize + //---------------------------------------------------------------------------- + Env *testEnv = TestEnv::GetEnv(); + + std::string address; + std::string remoteFile; + + EXPECT_TRUE( testEnv->GetString( "MainServerURL", address ) ); + EXPECT_TRUE( testEnv->GetString( "RemoteFile", remoteFile ) ); + + URL url( address ); + EXPECT_TRUE( url.IsValid() ); + + std::string fileUrl = address + "/" + remoteFile; + + const uint32_t MB = 1024*1024; + char *buffer = new char[4*MB]; + StatInfo *stat = 0; + File f; + + //---------------------------------------------------------------------------- + // Open and stat the file + //---------------------------------------------------------------------------- + GTEST_ASSERT_XRDST( f.Open( fileUrl, OpenFlags::Read ) ); + + GTEST_ASSERT_XRDST( f.Stat( false, stat ) ); + EXPECT_TRUE( stat ); + EXPECT_TRUE( stat->TestFlags( StatInfo::IsReadable ) ); + + //---------------------------------------------------------------------------- + // Fetch the data + //---------------------------------------------------------------------------- + uint64_t totalRead = 0; + uint32_t bytesRead = 0; + + CheckSumManager *man = DefaultEnv::GetCheckSumManager(); + EXPECT_TRUE( man ); + XrdCksCalc *crc32Sum = man->GetCalculator("zcrc32"); + EXPECT_TRUE( crc32Sum ); + + while( 1 ) + { + GTEST_ASSERT_XRDST( f.Read( totalRead, 4*MB, buffer, bytesRead ) ); + if( bytesRead == 0 ) + break; + totalRead += bytesRead; + crc32Sum->Update( buffer, bytesRead ); + } + + //---------------------------------------------------------------------------- + // Compare the checksums + //---------------------------------------------------------------------------- + char crcBuff[9]; + XrdCksData crc; crc.Set( (const void *)crc32Sum->Final(), 4 ); crc.Get( crcBuff, 9 ); + std::string transferSum = "zcrc32:"; transferSum += crcBuff; + + std::string remoteSum; + std::string lastUrl; + EXPECT_TRUE( f.GetProperty( "LastURL", lastUrl ) ); + GTEST_ASSERT_XRDST( Utils::GetRemoteCheckSum( remoteSum, "zcrc32", + URL( lastUrl ) ) ); + EXPECT_TRUE( remoteSum == transferSum ); + + delete stat; + delete crc32Sum; + delete[] buffer; +} + +//------------------------------------------------------------------------------ +// Upload test +//------------------------------------------------------------------------------ +void FileCopyTest::UploadTestFunc() +{ + using namespace XrdCl; + + //---------------------------------------------------------------------------- + // Initialize + //---------------------------------------------------------------------------- + Env *testEnv = TestEnv::GetEnv(); + + std::string address; + std::string dataPath; + std::string localFile; + + EXPECT_TRUE( testEnv->GetString( "MainServerURL", address ) ); + EXPECT_TRUE( testEnv->GetString( "DataPath", dataPath ) ); + EXPECT_TRUE( testEnv->GetString( "LocalFile", localFile ) ); + + URL url( address ); + EXPECT_TRUE( url.IsValid() ); + + std::string fileUrl = address + "/" + dataPath + "/testUpload.dat"; + std::string remoteFile = dataPath + "/testUpload.dat"; + + const uint32_t MB = 1024*1024; + char *buffer = new char[4*MB]; + File f; + + //---------------------------------------------------------------------------- + // Open + //---------------------------------------------------------------------------- + int fd = -1; + GTEST_ASSERT_ERRNO( (fd=open( localFile.c_str(), O_RDONLY )) > 0 ) + GTEST_ASSERT_XRDST( f.Open( fileUrl, + OpenFlags::Delete|OpenFlags::Update ) ); + + //---------------------------------------------------------------------------- + // Read the data + //---------------------------------------------------------------------------- + uint64_t offset = 0; + ssize_t bytesRead; + + CheckSumManager *man = DefaultEnv::GetCheckSumManager(); + XrdCksCalc *crc32Sum = man->GetCalculator("zcrc32"); + EXPECT_TRUE( crc32Sum ); + + while( (bytesRead = read( fd, buffer, 4*MB )) > 0 ) + { + crc32Sum->Update( buffer, bytesRead ); + GTEST_ASSERT_XRDST( f.Write( offset, bytesRead, buffer ) ); + offset += bytesRead; + } + + EXPECT_TRUE( bytesRead >= 0 ); + close( fd ); + GTEST_ASSERT_XRDST( f.Close() ); + delete [] buffer; + + //---------------------------------------------------------------------------- + // Find out which server has the file + //---------------------------------------------------------------------------- + FileSystem fs( url ); + LocationInfo *locations = 0; + GTEST_ASSERT_XRDST( fs.DeepLocate( remoteFile, OpenFlags::Refresh, locations ) ); + EXPECT_TRUE( locations ); + EXPECT_TRUE( locations->GetSize() != 0 ); + FileSystem fs1( locations->Begin()->GetAddress() ); + delete locations; + + //---------------------------------------------------------------------------- + // Verify the size + //---------------------------------------------------------------------------- + StatInfo *stat = 0; + GTEST_ASSERT_XRDST( fs1.Stat( remoteFile, stat ) ); + EXPECT_TRUE( stat ); + EXPECT_TRUE( stat->GetSize() == offset ); + + //---------------------------------------------------------------------------- + // Compare the checksums + //---------------------------------------------------------------------------- + char crcBuff[9]; + XrdCksData crc; crc.Set( (const void *)crc32Sum->Final(), 4 ); crc.Get( crcBuff, 9 ); + std::string transferSum = "zcrc32:"; transferSum += crcBuff; + + std::string remoteSum, lastUrl; + f.GetProperty( "LastURL", lastUrl ); + GTEST_ASSERT_XRDST( Utils::GetRemoteCheckSum( remoteSum, "zcrc32", + lastUrl ) ); + EXPECT_TRUE( remoteSum == transferSum ); + + //---------------------------------------------------------------------------- + // Delete the file + //---------------------------------------------------------------------------- + GTEST_ASSERT_XRDST( fs.Rm( dataPath + "/testUpload.dat" ) ); + + delete stat; + delete crc32Sum; +} + +//------------------------------------------------------------------------------ +// Upload test +//------------------------------------------------------------------------------ +TEST_F(FileCopyTest, UploadTest) +{ + UploadTestFunc(); +} + +TEST_F(FileCopyTest, MultiStreamUploadTest) +{ + XrdCl::Env *env = XrdCl::DefaultEnv::GetEnv(); + env->PutInt( "SubStreamsPerChannel", 4 ); + UploadTestFunc(); +} + +//------------------------------------------------------------------------------ +// Download test +//------------------------------------------------------------------------------ +TEST_F(FileCopyTest, DownloadTest) +{ + DownloadTestFunc(); +} + +TEST_F(FileCopyTest, MultiStreamDownloadTest) +{ + XrdCl::Env *env = XrdCl::DefaultEnv::GetEnv(); + env->PutInt( "SubStreamsPerChannel", 4 ); + DownloadTestFunc(); +} + +namespace +{ + //---------------------------------------------------------------------------- + // Abort handler + //---------------------------------------------------------------------------- + class CancelProgressHandler: public XrdCl::CopyProgressHandler + { + public: + //------------------------------------------------------------------------ + // Constructor/destructor + //------------------------------------------------------------------------ + CancelProgressHandler(): pCancel( false ) {} + virtual ~CancelProgressHandler() {}; + + //------------------------------------------------------------------------ + // Job progress + //------------------------------------------------------------------------ + virtual void JobProgress( uint16_t jobNum, + uint64_t bytesProcessed, + uint64_t bytesTotal ) + { + if( bytesProcessed > 128*1024*1024 ) + pCancel = true; + } + + //------------------------------------------------------------------------ + // Determine whether the job should be canceled + //------------------------------------------------------------------------ + virtual bool ShouldCancel( uint16_t jobNum ) { return pCancel; } + + private: + bool pCancel; + }; +} + +//------------------------------------------------------------------------------ +// Third party copy test +//------------------------------------------------------------------------------ +void FileCopyTest::CopyTestFunc( bool thirdParty ) +{ + using namespace XrdCl; + + //---------------------------------------------------------------------------- + // Initialize + //---------------------------------------------------------------------------- + Env *testEnv = TestEnv::GetEnv(); + + std::string metamanager; + std::string manager1; + std::string manager2; + std::string sourceFile; + std::string dataPath; + + EXPECT_TRUE( testEnv->GetString( "MainServerURL", metamanager ) ); + EXPECT_TRUE( testEnv->GetString( "Manager1URL", manager1 ) ); + EXPECT_TRUE( testEnv->GetString( "Manager2URL", manager2 ) ); + EXPECT_TRUE( testEnv->GetString( "RemoteFile", sourceFile ) ); + EXPECT_TRUE( testEnv->GetString( "DataPath", dataPath ) ); + + std::string sourceURL = manager1 + "/" + sourceFile; + std::string targetPath = dataPath + "/tpcFile"; + std::string targetURL = manager2 + "/" + targetPath; + std::string metalinkURL = metamanager + "/" + dataPath + "/metalink/mlTpcTest.meta4"; + std::string metalinkURL2 = metamanager + "/" + dataPath + "/metalink/mlZipTest.meta4"; + std::string zipURL = metamanager + "/" + dataPath + "/data.zip"; + std::string zipURL2 = metamanager + "/" + dataPath + "/large.zip"; + std::string fileInZip = "paper.txt"; + std::string fileInZip2 = "bible.txt"; + std::string xcpSourceURL = metamanager + "/" + dataPath + "/1db882c8-8cd6-4df1-941f-ce669bad3458.dat"; + std::string localFile = "/data/localfile.dat"; + + CopyProcess process1, process2, process3, process4, process5, process6, process7, process8, process9, + process10, process11, process12, process13, process14, process15, process16, process17; + PropertyList properties, results; + FileSystem fs( manager2 ); + + //---------------------------------------------------------------------------- + // Copy from a ZIP archive + //---------------------------------------------------------------------------- + if( !thirdParty ) + { + results.Clear(); + properties.Set( "source", zipURL ); + properties.Set( "target", targetURL ); + properties.Set( "zipArchive", true ); + properties.Set( "zipSource", fileInZip ); + GTEST_ASSERT_XRDST( process6.AddJob( properties, &results ) ); + GTEST_ASSERT_XRDST( process6.Prepare() ); + GTEST_ASSERT_XRDST( process6.Run(0) ); + GTEST_ASSERT_XRDST( fs.Rm( targetPath ) ); + properties.Clear(); + + //-------------------------------------------------------------------------- + // Copy from a ZIP archive (compressed) and validate the zcrc32 checksum + //-------------------------------------------------------------------------- + results.Clear(); + properties.Set( "source", zipURL2 ); + properties.Set( "target", targetURL ); + properties.Set( "checkSumMode", "end2end" ); + properties.Set( "checkSumType", "zcrc32" ); + properties.Set( "zipArchive", true ); + properties.Set( "zipSource", fileInZip2 ); + GTEST_ASSERT_XRDST( process10.AddJob( properties, &results ) ); + GTEST_ASSERT_XRDST( process10.Prepare() ); + GTEST_ASSERT_XRDST( process10.Run(0) ); + GTEST_ASSERT_XRDST( fs.Rm( targetPath ) ); + properties.Clear(); + + //-------------------------------------------------------------------------- + // Copy with `--rm-bad-cksum` + //-------------------------------------------------------------------------- + results.Clear(); + properties.Set( "source", sourceURL ); + properties.Set( "target", targetURL ); + properties.Set( "checkSumMode", "end2end" ); + properties.Set( "checkSumType", "auto" ); + properties.Set( "checkSumPreset", "bad-value" ); //< provide wrong checksum value, so the check fails and the file gets removed + properties.Set( "rmOnBadCksum", true ); + GTEST_ASSERT_XRDST( process12.AddJob( properties, &results ) ); + GTEST_ASSERT_XRDST( process12.Prepare() ); + GTEST_ASSERT_XRDST_NOTOK( process12.Run(0), XrdCl::errCheckSumError ); + XrdCl::StatInfo *info = 0; + XrdCl::XRootDStatus status = fs.Stat( targetPath, info ); + GTEST_ASSERT_XRDST( status.status == XrdCl::stError && status.code == XrdCl::errNotFound ); + properties.Clear(); + + //-------------------------------------------------------------------------- + // Copy with `--zip-mtln-cksum` + //-------------------------------------------------------------------------- + results.Clear(); + properties.Set( "source", metalinkURL2 ); + properties.Set( "target", targetURL ); + properties.Set( "checkSumMode", "end2end" ); + properties.Set( "checkSumType", "zcrc32" ); + XrdCl::Env *env = XrdCl::DefaultEnv::GetEnv(); + env->PutInt( "ZipMtlnCksum", 1 ); + GTEST_ASSERT_XRDST( process13.AddJob( properties, &results ) ); + GTEST_ASSERT_XRDST( process13.Prepare() ); + GTEST_ASSERT_XRDST_NOTOK( process13.Run(0), XrdCl::errCheckSumError ); + env->PutInt( "ZipMtlnCksum", 0 ); + GTEST_ASSERT_XRDST( fs.Rm( targetPath ) ); + + //-------------------------------------------------------------------------- + // Copy with + // `--xrate` + // `--xrate-threshold` + //-------------------------------------------------------------------------- + results.Clear(); + properties.Clear(); + properties.Set( "source", sourceURL ); + properties.Set( "target", targetURL ); + properties.Set( "xrate", 1024 * 1024 * 32 ); //< limit the transfer rate to 32MB/s + properties.Set( "xrateThreshold", 1024 * 1024 * 30 ); //< fail the job if the transfer rate drops under 30MB/s + GTEST_ASSERT_XRDST( process14.AddJob( properties, &results ) ); + GTEST_ASSERT_XRDST( process14.Prepare() ); + GTEST_ASSERT_XRDST( process14.Run(0) ); + GTEST_ASSERT_XRDST( fs.Rm( targetPath ) ); + + //-------------------------------------------------------------------------- + // Now test the cp-timeout + //-------------------------------------------------------------------------- + results.Clear(); + properties.Clear(); + properties.Set( "source", sourceURL ); + properties.Set( "target", targetURL ); + properties.Set( "xrate", 1024 * 1024 ); //< limit the transfer rate to 1MB/s (the file is 1GB big so the transfer will take 1024 seconds) + properties.Set( "cpTimeout", 10 ); //< timeout the job after 10 seconds + GTEST_ASSERT_XRDST( process15.AddJob( properties, &results ) ); + GTEST_ASSERT_XRDST( process15.Prepare() ); + GTEST_ASSERT_XRDST_NOTOK( process15.Run(0), XrdCl::errOperationExpired ); + GTEST_ASSERT_XRDST( fs.Rm( targetPath ) ); + + //-------------------------------------------------------------------------- + // Test posc for local files + //-------------------------------------------------------------------------- + results.Clear(); + properties.Clear(); + std::string localtrg = "file://localhost/data/tpcFile.dat"; + properties.Set( "source", sourceURL ); + properties.Set( "target", localtrg ); + properties.Set( "posc", true ); + CancelProgressHandler progress16; //> abort the copy after 100MB + GTEST_ASSERT_XRDST( process16.AddJob( properties, &results ) ); + GTEST_ASSERT_XRDST( process16.Prepare() ); + GTEST_ASSERT_XRDST_NOTOK( process16.Run( &progress16 ), errOperationInterrupted ); + XrdCl::FileSystem localfs( "file://localhost" ); + XrdCl::StatInfo *ptr = nullptr; + GTEST_ASSERT_XRDST_NOTOK( localfs.Stat( "/data/tpcFile.dat", ptr ), XrdCl::errLocalError ); + + //-------------------------------------------------------------------------- + // Test --retry and --retry-policy + //-------------------------------------------------------------------------- + results.Clear(); + properties.Clear(); + properties.Set( "xrate", 1024 * 1024 * 32 ); //< limit the transfer rate to 32MB/s + properties.Set( "cpTimeout", 20 ); //< timeout the job after 20 seconds + properties.Set( "source", sourceURL ); + properties.Set( "target", targetURL ); + env->PutInt( "CpRetry", 1 ); + env->PutString( "CpRetryPolicy", "continue" ); + GTEST_ASSERT_XRDST( process17.AddJob( properties, &results ) ); + GTEST_ASSERT_XRDST( process17.Prepare() ); + GTEST_ASSERT_XRDST( process17.Run(0) ); + GTEST_ASSERT_XRDST( fs.Rm( targetPath ) ); + env->PutInt( "CpRetry", XrdCl::DefaultCpRetry ); + env->PutString( "CpRetryPolicy", XrdCl::DefaultCpRetryPolicy ); + } + + //---------------------------------------------------------------------------- + // Copy from a Metalink + //---------------------------------------------------------------------------- + results.Clear(); + properties.Clear(); + properties.Set( "source", metalinkURL ); + properties.Set( "target", targetURL ); + properties.Set( "checkSumMode", "end2end" ); + properties.Set( "checkSumType", "zcrc32" ); + GTEST_ASSERT_XRDST( process5.AddJob( properties, &results ) ); + GTEST_ASSERT_XRDST( process5.Prepare() ); + GTEST_ASSERT_XRDST( process5.Run(0) ); + GTEST_ASSERT_XRDST( fs.Rm( targetPath ) ); + properties.Clear(); + + // XCp test + results.Clear(); + properties.Set( "source", xcpSourceURL ); + properties.Set( "target", targetURL ); + properties.Set( "checkSumMode", "end2end" ); + properties.Set( "checkSumType", "zcrc32" ); + properties.Set( "xcp", true ); + properties.Set( "nbXcpSources", 3 ); + GTEST_ASSERT_XRDST( process7.AddJob( properties, &results ) ); + GTEST_ASSERT_XRDST( process7.Prepare() ); + GTEST_ASSERT_XRDST( process7.Run(0) ); + GTEST_ASSERT_XRDST( fs.Rm( targetPath ) ); + properties.Clear(); + + //---------------------------------------------------------------------------- + // Copy to local fs + //---------------------------------------------------------------------------- + results.Clear(); + properties.Set( "source", sourceURL ); + properties.Set( "target", "file://localhost" + localFile ); + properties.Set( "checkSumMode", "end2end" ); + properties.Set( "checkSumType", "zcrc32" ); + GTEST_ASSERT_XRDST( process8.AddJob( properties, &results ) ); + GTEST_ASSERT_XRDST( process8.Prepare() ); + GTEST_ASSERT_XRDST( process8.Run(0) ); + properties.Clear(); + + //---------------------------------------------------------------------------- + // Copy from local fs with extended attributes + //---------------------------------------------------------------------------- + + // set extended attributes in the local source file + File lf; + GTEST_ASSERT_XRDST( lf.Open( "file://localhost" + localFile, OpenFlags::Write ) ); + std::vector attrs; attrs.push_back( xattr_t( "foo", "bar" ) ); + std::vector result; + GTEST_ASSERT_XRDST( lf.SetXAttr( attrs, result ) ); + EXPECT_TRUE( result.size() == 1 ); + GTEST_ASSERT_XRDST( result.front().status ); + GTEST_ASSERT_XRDST( lf.Close() ); + + results.Clear(); + properties.Set( "source", "file://localhost" + localFile ); + properties.Set( "target", targetURL ); + properties.Set( "checkSumMode", "end2end" ); + properties.Set( "checkSumType", "zcrc32" ); + properties.Set( "preserveXAttr", true ); + GTEST_ASSERT_XRDST( process9.AddJob( properties, &results ) ); + GTEST_ASSERT_XRDST( process9.Prepare() ); + GTEST_ASSERT_XRDST( process9.Run(0) ); + properties.Clear(); + + // now test if the xattrs were preserved + std::vector xattrs; + GTEST_ASSERT_XRDST( fs.ListXAttr( targetPath, xattrs ) ); + EXPECT_TRUE( xattrs.size() == 1 ); + XAttr &xattr = xattrs.front(); + GTEST_ASSERT_XRDST( xattr.status ); + EXPECT_TRUE( xattr.name == "foo" && xattr.value == "bar" ); + + //---------------------------------------------------------------------------- + // Cleanup + //---------------------------------------------------------------------------- + GTEST_ASSERT_XRDST( fs.Rm( targetPath ) ); + EXPECT_TRUE( remove( localFile.c_str() ) == 0 ); + + //---------------------------------------------------------------------------- + // Initialize and run the copy + //---------------------------------------------------------------------------- + properties.Set( "source", sourceURL ); + properties.Set( "target", targetURL ); + properties.Set( "checkSumMode", "end2end" ); + properties.Set( "checkSumType", "zcrc32" ); + if( thirdParty ) + properties.Set( "thirdParty", "only" ); + GTEST_ASSERT_XRDST( process1.AddJob( properties, &results ) ); + GTEST_ASSERT_XRDST( process1.Prepare() ); + GTEST_ASSERT_XRDST( process1.Run(0) ); + GTEST_ASSERT_XRDST( fs.Rm( targetPath ) ); + properties.Clear(); + + //---------------------------------------------------------------------------- + // Copy with `auto` checksum + //---------------------------------------------------------------------------- + results.Clear(); + properties.Set( "source", sourceURL ); + properties.Set( "target", targetURL ); + properties.Set( "checkSumMode", "end2end" ); + properties.Set( "checkSumType", "auto" ); + if( thirdParty ) + properties.Set( "thirdParty", "only" ); + GTEST_ASSERT_XRDST( process11.AddJob( properties, &results ) ); + GTEST_ASSERT_XRDST( process11.Prepare() ); + GTEST_ASSERT_XRDST( process11.Run(0) ); + GTEST_ASSERT_XRDST( fs.Rm( targetPath ) ); + properties.Clear(); + + // the further tests are only valid for third party copy for now + if( !thirdParty ) + return; + + //---------------------------------------------------------------------------- + // Abort the copy after 100MB + //---------------------------------------------------------------------------- +// CancelProgressHandler progress; +// GTEST_ASSERT_XRDST( process2.AddJob( properties, &results ) ); +// GTEST_ASSERT_XRDST( process2.Prepare() ); +// GTEST_ASSERT_XRDST_NOTOK( process2.Run(&progress), errErrorResponse ); +// GTEST_ASSERT_XRDST( fs.Rm( targetPath ) ); + + //---------------------------------------------------------------------------- + // Copy from a non-existent source + //---------------------------------------------------------------------------- + results.Clear(); + properties.Set( "source", "root://localhost:9997//test" ); // was 9999, this change allows for + properties.Set( "target", targetURL ); // parallel testing + properties.Set( "initTimeout", 10 ); + properties.Set( "thirdParty", "only" ); + GTEST_ASSERT_XRDST( process3.AddJob( properties, &results ) ); + GTEST_ASSERT_XRDST( process3.Prepare() ); + XrdCl::XRootDStatus status = process3.Run(0); + EXPECT_TRUE( !status.IsOK() && ( status.code == errOperationExpired || status.code == errConnectionError ) ); + + //---------------------------------------------------------------------------- + // Copy to a non-existent target + //---------------------------------------------------------------------------- + results.Clear(); + properties.Set( "source", sourceURL ); + properties.Set( "target", "root://localhost:9997//test" ); // was 9999, this change allows for + properties.Set( "initTimeout", 10 ); // parallel testing + properties.Set( "thirdParty", "only" ); + GTEST_ASSERT_XRDST( process4.AddJob( properties, &results ) ); + GTEST_ASSERT_XRDST( process4.Prepare() ); + status = process4.Run(0); + EXPECT_TRUE( !status.IsOK() && ( status.code == errOperationExpired || status.code == errConnectionError ) ); +} + +//------------------------------------------------------------------------------ +// Third party copy test +//------------------------------------------------------------------------------ +TEST_F(FileCopyTest, ThirdPartyCopyTest) +{ + CopyTestFunc( true ); +} + +//------------------------------------------------------------------------------ +// Cormal copy test +//------------------------------------------------------------------------------ +TEST_F (FileCopyTest, NormalCopyTest) +{ + CopyTestFunc( false ); +} From f1bcb2f59dded68cdf028c3d0ce9456b5f1c3d24 Mon Sep 17 00:00:00 2001 From: Angelo Galavotti Date: Thu, 20 Jul 2023 17:07:38 +0200 Subject: [PATCH 280/442] [Tests] Add converted tests from FileSystemTest Converts tests in FileSystemTests from CPPUnit to GTest --- tests/XrdCl/XrdClFileSystemTest.cc | 745 +++++++++++++++++++++++++++++ 1 file changed, 745 insertions(+) create mode 100644 tests/XrdCl/XrdClFileSystemTest.cc diff --git a/tests/XrdCl/XrdClFileSystemTest.cc b/tests/XrdCl/XrdClFileSystemTest.cc new file mode 100644 index 00000000000..17ca8cd7547 --- /dev/null +++ b/tests/XrdCl/XrdClFileSystemTest.cc @@ -0,0 +1,745 @@ +//------------------------------------------------------------------------------ +// Copyright (c) 2023 by European Organization for Nuclear Research (CERN) +// Author: Angelo Galavotti +//------------------------------------------------------------------------------ +// XRootD is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// XRootD is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with XRootD. If not, see . +//------------------------------------------------------------------------------ + +#include +#include +#include "XrdCl/XrdClDefaultEnv.hh" +#include "XrdCl/XrdClPlugInManager.hh" +#include "GTestXrdHelpers.hh" + +#include + +#include "TestEnv.hh" +#include "IdentityPlugIn.hh" + +using namespace XrdClTests; + +//------------------------------------------------------------------------------ +// Class declaration +//------------------------------------------------------------------------------ +class FileSystemTest: public ::testing::Test +{ + public: + void LocateTest(); + void MvTest(); + void ServerQueryTest(); + void TruncateRmTest(); + void MkdirRmdirTest(); + void ChmodTest(); + void PingTest(); + void StatTest(); + void StatVFSTest(); + void ProtocolTest(); + void DeepLocateTest(); + void DirListTest(); + void SendInfoTest(); + void PrepareTest(); + void XAttrTest(); +}; + +//------------------------------------------------------------------------------ +// Tests declaration +//------------------------------------------------------------------------------ +TEST_F(FileSystemTest, LocateTest) +{ + LocateTest(); +} + +TEST_F(FileSystemTest, MvTest) +{ + MvTest(); +} + +TEST_F(FileSystemTest, ServerQueryTest) +{ + ServerQueryTest(); +} + +TEST_F(FileSystemTest, TruncateRmTest) +{ + TruncateRmTest(); +} + +TEST_F(FileSystemTest, MkdirRmdirTest) +{ + MkdirRmdirTest(); +} + +TEST_F(FileSystemTest, ChmodTest) +{ + ChmodTest(); +} + +TEST_F(FileSystemTest, PingTest) +{ + PingTest(); +} + +TEST_F(FileSystemTest, StatTest) +{ + StatTest(); +} + +TEST_F(FileSystemTest, StatVFSTest) +{ + StatVFSTest(); +} + +TEST_F(FileSystemTest, ProtocolTest) +{ + ProtocolTest(); +} + +TEST_F(FileSystemTest, DeepLocateTest) +{ + DeepLocateTest(); +} + +TEST_F(FileSystemTest, DirListTest) +{ + DirListTest(); +} + +TEST_F(FileSystemTest, SendInfoTest) +{ + SendInfoTest(); +} + +TEST_F(FileSystemTest, PrepareTest) +{ + PrepareTest(); +} + +TEST_F(FileSystemTest, XAttrTest) +{ + XAttrTest(); +} + +TEST_F(FileSystemTest, PlugInTest) +{ + XrdCl::PlugInFactory *f = new IdentityFactory; + XrdCl::DefaultEnv::GetPlugInManager()->RegisterDefaultFactory(f); + LocateTest(); + MvTest(); + ServerQueryTest(); + TruncateRmTest(); + MkdirRmdirTest(); + ChmodTest(); + PingTest(); + StatTest(); + StatVFSTest(); + ProtocolTest(); + DeepLocateTest(); + DirListTest(); + SendInfoTest(); + PrepareTest(); + XrdCl::DefaultEnv::GetPlugInManager()->RegisterDefaultFactory(0); +} + + + +//------------------------------------------------------------------------------ +// Locate test +//------------------------------------------------------------------------------ +void FileSystemTest::LocateTest() +{ + using namespace XrdCl; + + //---------------------------------------------------------------------------- + // Get the environment variables + //---------------------------------------------------------------------------- + Env *testEnv = TestEnv::GetEnv(); + + std::string address; + std::string remoteFile; + + EXPECT_TRUE( testEnv->GetString( "MainServerURL", address ) ); + EXPECT_TRUE( testEnv->GetString( "RemoteFile", remoteFile ) ); + + URL url( address ); + EXPECT_TRUE( url.IsValid() ); + + //---------------------------------------------------------------------------- + // Query the server for all of the file locations + //---------------------------------------------------------------------------- + FileSystem fs( url ); + + LocationInfo *locations = 0; + GTEST_ASSERT_XRDST( fs.Locate( remoteFile, OpenFlags::Refresh, locations ) ); + EXPECT_TRUE( locations ); + EXPECT_TRUE( locations->GetSize() != 0 ); + delete locations; +} + +//------------------------------------------------------------------------------ +// Mv test +//------------------------------------------------------------------------------ +void FileSystemTest::MvTest() +{ + using namespace XrdCl; + + //---------------------------------------------------------------------------- + // Get the environment variables + //---------------------------------------------------------------------------- + Env *testEnv = TestEnv::GetEnv(); + + std::string address; + std::string dataPath; + std::string remoteFile; + + EXPECT_TRUE( testEnv->GetString( "DiskServerURL", address ) ); + EXPECT_TRUE( testEnv->GetString( "RemoteFile", remoteFile ) ); + + URL url( address ); + EXPECT_TRUE( url.IsValid() ); + + std::string filePath1 = remoteFile; + std::string filePath2 = remoteFile + "2"; + + + LocationInfo *info = 0; + FileSystem fs( url ); + + // move the file + GTEST_ASSERT_XRDST( fs.Mv( filePath1, filePath2 ) ); + // make sure it's not there + GTEST_ASSERT_XRDST_NOTOK( fs.Locate( filePath1, OpenFlags::Refresh, info ), + errErrorResponse ); + // make sure the destination is there + GTEST_ASSERT_XRDST( fs.Locate( filePath2, OpenFlags::Refresh, info ) ); + delete info; + // move it back + GTEST_ASSERT_XRDST( fs.Mv( filePath2, filePath1 ) ); + // make sure it's there + GTEST_ASSERT_XRDST( fs.Locate( filePath1, OpenFlags::Refresh, info ) ); + delete info; + // make sure the other one is gone + GTEST_ASSERT_XRDST_NOTOK( fs.Locate( filePath2, OpenFlags::Refresh, info ), + errErrorResponse ); +} + +//------------------------------------------------------------------------------ +// Query test +//------------------------------------------------------------------------------ +void FileSystemTest::ServerQueryTest() +{ + using namespace XrdCl; + + //---------------------------------------------------------------------------- + // Get the environment variables + //---------------------------------------------------------------------------- + Env *testEnv = TestEnv::GetEnv(); + + std::string address; + std::string remoteFile; + + EXPECT_TRUE( testEnv->GetString( "DiskServerURL", address ) ); + EXPECT_TRUE( testEnv->GetString( "RemoteFile", remoteFile ) ); + + URL url( address ); + EXPECT_TRUE( url.IsValid() ); + + FileSystem fs( url ); + Buffer *response = 0; + Buffer arg; + arg.FromString( remoteFile ); + GTEST_ASSERT_XRDST( fs.Query( QueryCode::Checksum, arg, response ) ); + EXPECT_TRUE( response ); + EXPECT_TRUE( response->GetSize() != 0 ); + delete response; +} + +//------------------------------------------------------------------------------ +// Truncate/Rm test +//------------------------------------------------------------------------------ +void FileSystemTest::TruncateRmTest() +{ + using namespace XrdCl; + + //---------------------------------------------------------------------------- + // Get the environment variables + //---------------------------------------------------------------------------- + Env *testEnv = TestEnv::GetEnv(); + + std::string address; + std::string dataPath; + + EXPECT_TRUE( testEnv->GetString( "MainServerURL", address ) ); + EXPECT_TRUE( testEnv->GetString( "DataPath", dataPath ) ); + + URL url( address ); + EXPECT_TRUE( url.IsValid() ); + + std::string filePath = dataPath + "/testfile"; + std::string fileUrl = address + "/"; + fileUrl += filePath; + + FileSystem fs( url ); + File f; + GTEST_ASSERT_XRDST( f.Open( fileUrl, OpenFlags::Update | OpenFlags::Delete, + Access::UR | Access::UW ) ); + GTEST_ASSERT_XRDST( fs.Truncate( filePath, 10000000 ) ); + GTEST_ASSERT_XRDST( fs.Rm( filePath ) ); +} + +//------------------------------------------------------------------------------ +// Mkdir/Rmdir test +//------------------------------------------------------------------------------ +void FileSystemTest::MkdirRmdirTest() +{ + using namespace XrdCl; + + //---------------------------------------------------------------------------- + // Get the environment variables + //---------------------------------------------------------------------------- + Env *testEnv = TestEnv::GetEnv(); + + std::string address; + std::string dataPath; + + EXPECT_TRUE( testEnv->GetString( "DiskServerURL", address ) ); + EXPECT_TRUE( testEnv->GetString( "DataPath", dataPath ) ); + + URL url( address ); + EXPECT_TRUE( url.IsValid() ); + + std::string dirPath1 = dataPath + "/testdir"; + std::string dirPath2 = dataPath + "/testdir/asdads"; + + FileSystem fs( url ); + + GTEST_ASSERT_XRDST( fs.MkDir( dirPath2, MkDirFlags::MakePath, + Access::UR | Access::UW | Access::UX ) ); + GTEST_ASSERT_XRDST( fs.RmDir( dirPath2 ) ); + GTEST_ASSERT_XRDST( fs.RmDir( dirPath1 ) ); +} + +//------------------------------------------------------------------------------ +// Chmod test +//------------------------------------------------------------------------------ +void FileSystemTest::ChmodTest() +{ + using namespace XrdCl; + + //---------------------------------------------------------------------------- + // Get the environment variables + //---------------------------------------------------------------------------- + Env *testEnv = TestEnv::GetEnv(); + + std::string address; + std::string dataPath; + + EXPECT_TRUE( testEnv->GetString( "DiskServerURL", address ) ); + EXPECT_TRUE( testEnv->GetString( "DataPath", dataPath ) ); + + URL url( address ); + EXPECT_TRUE( url.IsValid() ); + + std::string dirPath = dataPath + "/testdir"; + + FileSystem fs( url ); + + GTEST_ASSERT_XRDST( fs.MkDir( dirPath, MkDirFlags::MakePath, + Access::UR | Access::UW | Access::UX ) ); + GTEST_ASSERT_XRDST( fs.ChMod( dirPath, + Access::UR | Access::UW | Access::UX | + Access::GR | Access::GX ) ); + GTEST_ASSERT_XRDST( fs.RmDir( dirPath ) ); +} + +//------------------------------------------------------------------------------ +// Locate test +//------------------------------------------------------------------------------ +void FileSystemTest::PingTest() +{ + using namespace XrdCl; + + //---------------------------------------------------------------------------- + // Get the environment variables + //---------------------------------------------------------------------------- + Env *testEnv = TestEnv::GetEnv(); + + std::string address; + EXPECT_TRUE( testEnv->GetString( "MainServerURL", address ) ); + URL url( address ); + EXPECT_TRUE( url.IsValid() ); + + FileSystem fs( url ); + GTEST_ASSERT_XRDST( fs.Ping() ); +} + +//------------------------------------------------------------------------------ +// Stat test +//------------------------------------------------------------------------------ +void FileSystemTest::StatTest() +{ + using namespace XrdCl; + + Env *testEnv = TestEnv::GetEnv(); + + std::string address; + std::string remoteFile; + + EXPECT_TRUE( testEnv->GetString( "MainServerURL", address ) ); + EXPECT_TRUE( testEnv->GetString( "RemoteFile", remoteFile ) ); + + URL url( address ); + EXPECT_TRUE( url.IsValid() ); + + FileSystem fs( url ); + StatInfo *response = 0; + GTEST_ASSERT_XRDST( fs.Stat( remoteFile, response ) ); + EXPECT_TRUE( response ); + EXPECT_TRUE( response->GetSize() == 1048576000 ); + EXPECT_TRUE( response->TestFlags( StatInfo::IsReadable ) ); + EXPECT_TRUE( response->TestFlags( StatInfo::IsWritable ) ); + EXPECT_TRUE( !response->TestFlags( StatInfo::IsDir ) ); + delete response; +} + +//------------------------------------------------------------------------------ +// Stat VFS test +//------------------------------------------------------------------------------ +void FileSystemTest::StatVFSTest() +{ + using namespace XrdCl; + + Env *testEnv = TestEnv::GetEnv(); + + std::string address; + std::string dataPath; + + EXPECT_TRUE( testEnv->GetString( "MainServerURL", address ) ); + EXPECT_TRUE( testEnv->GetString( "DataPath", dataPath ) ); + + URL url( address ); + EXPECT_TRUE( url.IsValid() ); + + FileSystem fs( url ); + StatInfoVFS *response = 0; + GTEST_ASSERT_XRDST( fs.StatVFS( dataPath, response ) ); + EXPECT_TRUE( response ); + delete response; +} + +//------------------------------------------------------------------------------ +// Protocol test +//------------------------------------------------------------------------------ +void FileSystemTest::ProtocolTest() +{ + using namespace XrdCl; + + Env *testEnv = TestEnv::GetEnv(); + + std::string address; + EXPECT_TRUE( testEnv->GetString( "MainServerURL", address ) ); + URL url( address ); + EXPECT_TRUE( url.IsValid() ); + + FileSystem fs( url ); + ProtocolInfo *response = 0; + GTEST_ASSERT_XRDST( fs.Protocol( response ) ); + EXPECT_TRUE( response ); + delete response; +} + +//------------------------------------------------------------------------------ +// Deep locate test +//------------------------------------------------------------------------------ +void FileSystemTest::DeepLocateTest() +{ + using namespace XrdCl; + + //---------------------------------------------------------------------------- + // Get the environment variables + //---------------------------------------------------------------------------- + Env *testEnv = TestEnv::GetEnv(); + + std::string address; + std::string remoteFile; + + EXPECT_TRUE( testEnv->GetString( "MainServerURL", address ) ); + EXPECT_TRUE( testEnv->GetString( "RemoteFile", remoteFile ) ); + + URL url( address ); + EXPECT_TRUE( url.IsValid() ); + + //---------------------------------------------------------------------------- + // Query the server for all of the file locations + //---------------------------------------------------------------------------- + FileSystem fs( url ); + + LocationInfo *locations = 0; + GTEST_ASSERT_XRDST( fs.DeepLocate( remoteFile, OpenFlags::Refresh, locations ) ); + EXPECT_TRUE( locations ); + EXPECT_TRUE( locations->GetSize() != 0 ); + LocationInfo::Iterator it = locations->Begin(); + for( ; it != locations->End(); ++it ) + EXPECT_TRUE( it->IsServer() ); + delete locations; +} + +//------------------------------------------------------------------------------ +// Dir list +//------------------------------------------------------------------------------ +void FileSystemTest::DirListTest() +{ + using namespace XrdCl; + + //---------------------------------------------------------------------------- + // Get the environment variables + //---------------------------------------------------------------------------- + Env *testEnv = TestEnv::GetEnv(); + + std::string address; + std::string dataPath; + + EXPECT_TRUE( testEnv->GetString( "MainServerURL", address ) ); + EXPECT_TRUE( testEnv->GetString( "DataPath", dataPath ) ); + + URL url( address ); + EXPECT_TRUE( url.IsValid() ); + + std::string lsPath = dataPath + "/bigdir"; + + //---------------------------------------------------------------------------- + // Query the server for all of the file locations + //---------------------------------------------------------------------------- + FileSystem fs( url ); + + DirectoryList *list = 0; + GTEST_ASSERT_XRDST( fs.DirList( lsPath, DirListFlags::Stat | DirListFlags::Locate, list ) ); + EXPECT_TRUE( list ); + EXPECT_TRUE( list->GetSize() == 40000 ); + + std::set dirls1; + for( auto itr = list->Begin(); itr != list->End(); ++itr ) + { + DirectoryList::ListEntry *entry = *itr; + dirls1.insert( entry->GetName() ); + } + + delete list; + list = 0; + + //---------------------------------------------------------------------------- + // Now do a chunked query + //---------------------------------------------------------------------------- + std::set dirls2; + + LocationInfo *info = 0; + GTEST_ASSERT_XRDST( fs.DeepLocate( lsPath, OpenFlags::PrefName, info ) ); + EXPECT_TRUE( info ); + + for( auto itr = info->Begin(); itr != info->End(); ++itr ) + { + XrdSysSemaphore sem( 0 ); + auto handler = XrdCl::ResponseHandler::Wrap( [&]( auto &s, auto &r ) + { + GTEST_ASSERT_XRDST( s ); + auto &list = To( r ); + for( auto itr = list.Begin(); itr != list.End(); ++itr ) + dirls2.insert( ( *itr )->GetName() ); + if( s.code == XrdCl::suDone ) + sem.Post(); + } ); + + FileSystem fs1( std::string( itr->GetAddress() ) ); + GTEST_ASSERT_XRDST( fs1.DirList( lsPath, DirListFlags::Stat | DirListFlags::Chunked, handler ) ); + sem.Wait(); + } + delete info; + info = 0; + + EXPECT_TRUE( dirls1 == dirls2 ); + + //---------------------------------------------------------------------------- + // Now list an empty directory + //---------------------------------------------------------------------------- + GTEST_ASSERT_XRDST( fs.MkDir( "/data/empty", MkDirFlags::None, Access::None ) ); + GTEST_ASSERT_XRDST( fs.DeepLocate( "/data/empty", OpenFlags::PrefName, info ) ); + EXPECT_TRUE( info->GetSize() ); + FileSystem fs3( info->Begin()->GetAddress() ); + GTEST_ASSERT_XRDST( fs3.DirList( "/data/empty", DirListFlags::Stat, list ) ); + EXPECT_TRUE( list ); + EXPECT_TRUE( list->GetSize() == 0 ); + GTEST_ASSERT_XRDST( fs.RmDir( "/data/empty" ) ); + + delete list; + list = 0; + delete info; + info = 0; +} + + +//------------------------------------------------------------------------------ +// Set +//------------------------------------------------------------------------------ +void FileSystemTest::SendInfoTest() +{ + using namespace XrdCl; + + //---------------------------------------------------------------------------- + // Get the environment variables + //---------------------------------------------------------------------------- + Env *testEnv = TestEnv::GetEnv(); + + std::string address; + std::string dataPath; + + EXPECT_TRUE( testEnv->GetString( "MainServerURL", address ) ); + URL url( address ); + EXPECT_TRUE( url.IsValid() ); + + FileSystem fs( url ); + + Buffer *id = 0; + GTEST_ASSERT_XRDST( fs.SendInfo( "test stuff", id ) ); + EXPECT_TRUE( id ); + EXPECT_TRUE( id->GetSize() == 4 ); + delete id; +} + + +//------------------------------------------------------------------------------ +// Set +//------------------------------------------------------------------------------ +void FileSystemTest::PrepareTest() +{ + using namespace XrdCl; + + //---------------------------------------------------------------------------- + // Get the environment variables + //---------------------------------------------------------------------------- + Env *testEnv = TestEnv::GetEnv(); + + std::string address; + std::string dataPath; + + EXPECT_TRUE( testEnv->GetString( "MainServerURL", address ) ); + URL url( address ); + EXPECT_TRUE( url.IsValid() ); + + FileSystem fs( url ); + + Buffer *id = 0; + std::vector list; + list.push_back( "/data/1db882c8-8cd6-4df1-941f-ce669bad3458.dat" ); + list.push_back( "/data/1db882c8-8cd6-4df1-941f-ce669bad3458.dat" ); + + GTEST_ASSERT_XRDST( fs.Prepare( list, PrepareFlags::Stage, 1, id ) ); + EXPECT_TRUE( id ); + EXPECT_TRUE( id->GetSize() ); + delete id; +} + +//------------------------------------------------------------------------------ +// Extended attributes test +//------------------------------------------------------------------------------ +void FileSystemTest::XAttrTest() +{ + using namespace XrdCl; + + //---------------------------------------------------------------------------- + // Get the environment variables + //---------------------------------------------------------------------------- + Env *testEnv = TestEnv::GetEnv(); + + std::string address; + std::string remoteFile; + + EXPECT_TRUE( testEnv->GetString( "DiskServerURL", address ) ); + EXPECT_TRUE( testEnv->GetString( "RemoteFile", remoteFile ) ); + + URL url( address ); + EXPECT_TRUE( url.IsValid() ); + + FileSystem fs( url ); + + std::map attributes + { + std::make_pair( "version", "v1.2.3-45" ), + std::make_pair( "checksum", "2ccc0e85556a6cd193dd8d2b40aab50c" ), + std::make_pair( "index", "4" ) + }; + + //---------------------------------------------------------------------------- + // Test SetXAttr + //---------------------------------------------------------------------------- + std::vector attrs; + auto itr1 = attributes.begin(); + for( ; itr1 != attributes.end() ; ++itr1 ) + attrs.push_back( std::make_tuple( itr1->first, itr1->second ) ); + + std::vector result1; + GTEST_ASSERT_XRDST( fs.SetXAttr( remoteFile, attrs, result1 ) ); + + auto itr2 = result1.begin(); + for( ; itr2 != result1.end() ; ++itr2 ) + GTEST_ASSERT_XRDST( itr2->status ); + result1.clear(); + + //---------------------------------------------------------------------------- + // Test GetXAttr + //---------------------------------------------------------------------------- + std::vector names; + itr1 = attributes.begin(); + for( ; itr1 != attributes.end() ; ++itr1 ) + names.push_back( itr1->first ); + + std::vector result2; + GTEST_ASSERT_XRDST( fs.GetXAttr( remoteFile, names, result2 ) ); + + auto itr3 = result2.begin(); + for( ; itr3 != result2.end() ; ++itr3 ) + { + GTEST_ASSERT_XRDST( itr3->status ); + auto match = attributes.find( itr3->name ); + EXPECT_TRUE( match != attributes.end() ); + EXPECT_TRUE( match->second == itr3->value ); + } + result2.clear(); + + //---------------------------------------------------------------------------- + // Test ListXAttr + //---------------------------------------------------------------------------- + GTEST_ASSERT_XRDST( fs.ListXAttr( remoteFile, result2 ) ); + + itr3 = result2.begin(); + for( ; itr3 != result2.end() ; ++itr3 ) + { + GTEST_ASSERT_XRDST( itr3->status ); + auto match = attributes.find( itr3->name ); + EXPECT_TRUE( match != attributes.end() ); + EXPECT_TRUE( match->second == itr3->value ); + } + + result2.clear(); + + //---------------------------------------------------------------------------- + // Test DelXAttr + //---------------------------------------------------------------------------- + GTEST_ASSERT_XRDST( fs.DelXAttr( remoteFile, names, result1 ) ); + + itr2 = result1.begin(); + for( ; itr2 != result1.end() ; ++itr2 ) + GTEST_ASSERT_XRDST( itr2->status ); + + result1.clear(); +} + From a44b1afea7549153f2db8c6982b02089d2f19595 Mon Sep 17 00:00:00 2001 From: Angelo Galavotti Date: Thu, 20 Jul 2023 17:08:15 +0200 Subject: [PATCH 281/442] [Tests] Add converted tests from LocalFileHandlerTest Converts tests in LocalFileHandlerTests from CPPUnit to GTest --- tests/XrdCl/XrdClLocalFileHandlerTest.cc | 534 +++++++++++++++++++++++ 1 file changed, 534 insertions(+) create mode 100644 tests/XrdCl/XrdClLocalFileHandlerTest.cc diff --git a/tests/XrdCl/XrdClLocalFileHandlerTest.cc b/tests/XrdCl/XrdClLocalFileHandlerTest.cc new file mode 100644 index 00000000000..80979d6ab5a --- /dev/null +++ b/tests/XrdCl/XrdClLocalFileHandlerTest.cc @@ -0,0 +1,534 @@ +//------------------------------------------------------------------------------ +// Copyright (c) 2023 by European Organization for Nuclear Research (CERN) +// Author: Angelo Galavotti +//------------------------------------------------------------------------------ +// XRootD is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// XRootD is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with XRootD. If not, see . +//------------------------------------------------------------------------------ +#include "TestEnv.hh" +#include "GTestXrdHelpers.hh" +#include "XrdCl/XrdClFile.hh" + +#include +#include +#include + +using namespace XrdClTests; + +//------------------------------------------------------------------------------ +// Declaration +//------------------------------------------------------------------------------ +class LocalFileHandlerTest: public ::testing::Test +{ + public: + void CreateTestFileFunc( std::string url, std::string content = "GenericTestFile" ); + void OpenCloseTest(); + void ReadTest(); + void ReadWithOffsetTest(); + void WriteTest(); + void WriteWithOffsetTest(); + void WriteMkdirTest(); + void TruncateTest(); + void VectorReadTest(); + void VectorWriteTest(); + void SyncTest(); + void WriteVTest(); + void XAttrTest(); +}; + +//---------------------------------------------------------------------------- +// Create the file to be tested +//---------------------------------------------------------------------------- +void LocalFileHandlerTest::CreateTestFileFunc( std::string url, std::string content ){ + mode_t openmode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; + int fd = open( url.c_str(), O_RDWR | O_CREAT | O_TRUNC, openmode ); + int rc = write( fd, content.c_str(), content.size() ); + EXPECT_EQ( rc, int( content.size() ) ); + rc = close( fd ); + EXPECT_EQ( rc, 0 ); +} + +TEST_F(LocalFileHandlerTest, SyncTest){ + using namespace XrdCl; + std::string targetURL = "/tmp/lfilehandlertestfilesync"; + CreateTestFileFunc( targetURL ); + + //---------------------------------------------------------------------------- + // Open and Sync File + //---------------------------------------------------------------------------- + OpenFlags::Flags flags = OpenFlags::Update; + Access::Mode mode = Access::UR | Access::UW | Access::GR | Access::OR; + File *file = new File(); + GTEST_ASSERT_XRDST( file->Open( targetURL, flags, mode ) ); + GTEST_ASSERT_XRDST( file->Sync() ); + GTEST_ASSERT_XRDST( file->Close() ); + EXPECT_TRUE( remove( targetURL.c_str() ) == 0 ); + delete file; +} + +TEST_F(LocalFileHandlerTest, OpenCloseTest){ + using namespace XrdCl; + std::string targetURL = "/tmp/lfilehandlertestfileopenclose"; + CreateTestFileFunc( targetURL ); + + //---------------------------------------------------------------------------- + // Open existing file + //---------------------------------------------------------------------------- + OpenFlags::Flags flags = OpenFlags::Update; + Access::Mode mode = Access::UR|Access::UW|Access::GR|Access::OR; + File *file = new File(); + GTEST_ASSERT_XRDST( file->Open( targetURL, flags, mode ) ); + EXPECT_TRUE( file->IsOpen() ); + GTEST_ASSERT_XRDST( file->Close() ); + EXPECT_TRUE( remove( targetURL.c_str() ) == 0 ); + + //---------------------------------------------------------------------------- + // Try open non-existing file + //---------------------------------------------------------------------------- + EXPECT_TRUE( !file->Open( targetURL, flags, mode ).IsOK() ); + EXPECT_TRUE( !file->IsOpen() ); + + //---------------------------------------------------------------------------- + // Try close non-opened file, return has to be error + //---------------------------------------------------------------------------- + EXPECT_TRUE( file->Close().status == stError ); + delete file; +} + +TEST_F(LocalFileHandlerTest, WriteTest){ + using namespace XrdCl; + std::string targetURL = "/tmp/lfilehandlertestfilewrite"; + std::string toBeWritten = "tenBytes1\0"; + uint32_t writeSize = toBeWritten.size(); + CreateTestFileFunc( targetURL, "" ); + char *buffer = new char[writeSize]; + //---------------------------------------------------------------------------- + // Open and Write File + //---------------------------------------------------------------------------- + OpenFlags::Flags flags = OpenFlags::Update; + Access::Mode mode = Access::UR|Access::UW|Access::GR|Access::OR; + File *file = new File(); + GTEST_ASSERT_XRDST( file->Open( targetURL, flags, mode ) ); + EXPECT_TRUE( file->IsOpen() ); + GTEST_ASSERT_XRDST( file->Write( 0, writeSize, toBeWritten.c_str()) ); + GTEST_ASSERT_XRDST( file->Close() ); + + //---------------------------------------------------------------------------- + // Read file with POSIX calls to confirm correct write + //---------------------------------------------------------------------------- + int fd = open( targetURL.c_str(), flags ); + int rc = read( fd, buffer, int( writeSize ) ); + EXPECT_EQ( rc, int( writeSize ) ); + std::string read( (char *)buffer, writeSize ); + EXPECT_TRUE( toBeWritten == read ); + EXPECT_TRUE( remove( targetURL.c_str() ) == 0 ); + delete[] buffer; + delete file; +} + +TEST_F(LocalFileHandlerTest, WriteWithOffsetTest){ + using namespace XrdCl; + std::string targetURL = "/tmp/lfilehandlertestfilewriteoffset"; + std::string toBeWritten = "tenBytes10"; + std::string notToBeOverwritten = "front"; + uint32_t writeSize = toBeWritten.size(); + uint32_t offset = notToBeOverwritten.size(); + void *buffer = new char[offset]; + CreateTestFileFunc( targetURL, notToBeOverwritten ); + + //---------------------------------------------------------------------------- + // Open and Write File + //---------------------------------------------------------------------------- + OpenFlags::Flags flags = OpenFlags::Update; + Access::Mode mode = Access::UR|Access::UW|Access::GR|Access::OR; + File *file = new File(); + GTEST_ASSERT_XRDST( file->Open( targetURL, flags, mode ) ); + EXPECT_TRUE( file->IsOpen() ); + GTEST_ASSERT_XRDST( file->Write( offset, writeSize, toBeWritten.c_str()) ); + GTEST_ASSERT_XRDST( file->Close() ); + + //---------------------------------------------------------------------------- + // Read file with POSIX calls to confirm correct write + //---------------------------------------------------------------------------- + int fd = open( targetURL.c_str(), flags ); + int rc = read( fd, buffer, offset ); + EXPECT_EQ( rc, int( offset ) ); + std::string read( (char *)buffer, offset ); + EXPECT_TRUE( notToBeOverwritten == read ); + EXPECT_TRUE( remove( targetURL.c_str() ) == 0 ); + delete[] (char*)buffer; + delete file; +} + +TEST_F(LocalFileHandlerTest, WriteMkdirTest){ + using namespace XrdCl; + std::string targetURL = "/tmp/testdir/further/muchfurther/evenfurther/lfilehandlertestfilewrite"; + std::string toBeWritten = "tenBytes10"; + uint32_t writeSize = toBeWritten.size(); + char *buffer = new char[writeSize]; + + //---------------------------------------------------------------------------- + // Open and Write File + //---------------------------------------------------------------------------- + OpenFlags::Flags flags = OpenFlags::Update | OpenFlags::MakePath | OpenFlags::New; + Access::Mode mode = Access::UR|Access::UW|Access::UX; + File *file = new File(); + GTEST_ASSERT_XRDST( file->Open( targetURL, flags, mode ) ); + EXPECT_TRUE( file->IsOpen() ); + GTEST_ASSERT_XRDST( file->Write( 0, writeSize, toBeWritten.c_str()) ); + GTEST_ASSERT_XRDST( file->Close() ); + + //---------------------------------------------------------------------------- + // Read file with POSIX calls to confirm correct write + //---------------------------------------------------------------------------- + int fd = open( targetURL.c_str(), flags ); + int rc = read( fd, buffer, writeSize ); + EXPECT_EQ( rc, int( writeSize ) ); + std::string read( buffer, writeSize ); + EXPECT_TRUE( toBeWritten == read ); + EXPECT_TRUE( remove( targetURL.c_str() ) == 0 ); + delete[] buffer; + delete file; +} + +TEST_F(LocalFileHandlerTest, ReadTest){ + using namespace XrdCl; + std::string targetURL = "/tmp/lfilehandlertestfileread"; + std::string toBeWritten = "tenBytes10"; + uint32_t offset = 0; + uint32_t writeSize = toBeWritten.size(); + char *buffer = new char[writeSize]; + uint32_t bytesRead = 0; + + //---------------------------------------------------------------------------- + // Write file with POSIX calls to ensure correct write + //---------------------------------------------------------------------------- + CreateTestFileFunc( targetURL, toBeWritten ); + + //---------------------------------------------------------------------------- + // Open and Read File + //---------------------------------------------------------------------------- + OpenFlags::Flags flags = OpenFlags::Update; + Access::Mode mode = Access::UR|Access::UW|Access::GR|Access::OR; + File *file = new File(); + GTEST_ASSERT_XRDST( file->Open( targetURL, flags, mode ) ); + EXPECT_TRUE( file->IsOpen() ); + GTEST_ASSERT_XRDST( file->Read( offset, writeSize, buffer, bytesRead ) ); + GTEST_ASSERT_XRDST( file->Close() ); + + std::string read( (char*)buffer, writeSize ); + EXPECT_TRUE( toBeWritten == read ); + EXPECT_TRUE( remove( targetURL.c_str() ) == 0 ); + + delete[] buffer; + delete file; +} + +TEST_F(LocalFileHandlerTest, ReadWithOffsetTest){ + using namespace XrdCl; + std::string targetURL = "/tmp/lfilehandlertestfileread"; + std::string toBeWritten = "tenBytes10"; + uint32_t offset = 3; + std::string expectedRead = "Byte"; + uint32_t readsize = expectedRead.size(); + char *buffer = new char[readsize]; + uint32_t bytesRead = 0; + + //---------------------------------------------------------------------------- + // Write file with POSIX calls to ensure correct write + //---------------------------------------------------------------------------- + CreateTestFileFunc( targetURL, toBeWritten ); + + //---------------------------------------------------------------------------- + // Open and Read File + //---------------------------------------------------------------------------- + OpenFlags::Flags flags = OpenFlags::Update; + Access::Mode mode = Access::UR|Access::UW|Access::GR|Access::OR; + File *file = new File(); + GTEST_ASSERT_XRDST( file->Open( targetURL, flags, mode ) ); + EXPECT_TRUE( file->IsOpen() ); + GTEST_ASSERT_XRDST( file->Read( offset, readsize, buffer, bytesRead ) ); + GTEST_ASSERT_XRDST( file->Close() ); + + std::string read( buffer, readsize ); + EXPECT_TRUE( expectedRead == read ); + EXPECT_TRUE( remove( targetURL.c_str() ) == 0 ); + delete[] buffer; + delete file; +} + +TEST_F(LocalFileHandlerTest, TruncateTest){ + using namespace XrdCl; + //---------------------------------------------------------------------------- + // Initialize + //---------------------------------------------------------------------------- + std::string targetURL = "/tmp/lfilehandlertestfiletruncate"; + + CreateTestFileFunc(targetURL); + //---------------------------------------------------------------------------- + // Prepare truncate + //---------------------------------------------------------------------------- + OpenFlags::Flags flags = OpenFlags::Update | OpenFlags::Force; + Access::Mode mode = Access::UR|Access::UW|Access::GR|Access::OR; + File *file = new File(); + uint32_t bytesRead = 0; + uint32_t truncateSize = 5; + + //---------------------------------------------------------------------------- + // Read after truncate, but with greater length. bytesRead must still be + // truncate size if truncate works as intended + //---------------------------------------------------------------------------- + GTEST_ASSERT_XRDST( file->Open( targetURL, flags, mode ) ); + GTEST_ASSERT_XRDST( file->Truncate( truncateSize ) ); + char *buffer = new char[truncateSize + 3]; + GTEST_ASSERT_XRDST( file->Read( 0, truncateSize + 3, buffer, bytesRead ) ); + EXPECT_EQ( truncateSize, bytesRead ); + GTEST_ASSERT_XRDST( file->Close() ); + EXPECT_TRUE( remove( targetURL.c_str() ) == 0 ); + delete file; + delete[] buffer; +} + +TEST_F(LocalFileHandlerTest, VectorReadTest) +{ + using namespace XrdCl; + + //---------------------------------------------------------------------------- + // Initialize + //---------------------------------------------------------------------------- + std::string targetURL = "/tmp/lfilehandlertestfilevectorread"; + CreateTestFileFunc( targetURL ); + VectorReadInfo *info = 0; + ChunkList chunks; + + //---------------------------------------------------------------------------- + // Prepare VectorRead + //---------------------------------------------------------------------------- + OpenFlags::Flags flags = OpenFlags::Update; + Access::Mode mode = Access::UR|Access::UW|Access::GR|Access::OR; + File file; + GTEST_ASSERT_XRDST( file.Open( targetURL, flags, mode ) ); + + //---------------------------------------------------------------------------- + // VectorRead no cursor + //---------------------------------------------------------------------------- + + chunks.push_back( ChunkInfo( 0, 5, new char[5] ) ); + chunks.push_back( ChunkInfo( 10, 5, new char[5] ) ); + GTEST_ASSERT_XRDST( file.VectorRead( chunks, NULL, info ) ); + GTEST_ASSERT_XRDST( file.Close() ); + EXPECT_TRUE( info->GetSize() == 10 ); + EXPECT_EQ( 0, memcmp( "Gener", + info->GetChunks()[0].buffer, + info->GetChunks()[0].length ) ); + EXPECT_EQ( 0, memcmp( "tFile", + info->GetChunks()[1].buffer, + info->GetChunks()[1].length ) ); + delete[] (char*)chunks[0].buffer; + delete[] (char*)chunks[1].buffer; + delete info; + + //---------------------------------------------------------------------------- + // VectorRead cursor + //---------------------------------------------------------------------------- + char *buffer = new char[10]; + chunks.clear(); + chunks.push_back( ChunkInfo( 0, 5, 0 ) ); + chunks.push_back( ChunkInfo( 10, 5, 0 ) ); + info = 0; + + GTEST_ASSERT_XRDST( file.Open( targetURL, flags, mode ) ); + GTEST_ASSERT_XRDST( file.VectorRead( chunks, buffer, info ) ); + GTEST_ASSERT_XRDST( file.Close() ); + EXPECT_TRUE( info->GetSize() == 10 ); + EXPECT_EQ( 0, memcmp( "GenertFile", + info->GetChunks()[0].buffer, + info->GetChunks()[0].length ) ); + EXPECT_TRUE( remove( targetURL.c_str() ) == 0 ); + + delete[] buffer; + delete info; +} + +TEST_F(LocalFileHandlerTest, VectorWriteTest) +{ + using namespace XrdCl; + + //---------------------------------------------------------------------------- + // Initialize + //---------------------------------------------------------------------------- + std::string targetURL = "/tmp/lfilehandlertestfilevectorwrite"; + CreateTestFileFunc( targetURL ); + ChunkList chunks; + + //---------------------------------------------------------------------------- + // Prepare VectorWrite + //---------------------------------------------------------------------------- + OpenFlags::Flags flags = OpenFlags::Update; + Access::Mode mode = Access::UR|Access::UW|Access::GR|Access::OR; + File file; + GTEST_ASSERT_XRDST( file.Open( targetURL, flags, mode ) ); + + //---------------------------------------------------------------------------- + // VectorWrite + //---------------------------------------------------------------------------- + + ChunkInfo chunk( 0, 5, new char[5] ); + memset( chunk.buffer, 'A', chunk.length ); + chunks.push_back( chunk ); + chunk = ChunkInfo( 10, 5, new char[5] ); + memset( chunk.buffer, 'B', chunk.length ); + chunks.push_back( chunk ); + + GTEST_ASSERT_XRDST( file.VectorWrite( chunks ) ); + + //---------------------------------------------------------------------------- + // Verify with VectorRead + //---------------------------------------------------------------------------- + + VectorReadInfo *info = 0; + char *buffer = new char[10]; + GTEST_ASSERT_XRDST( file.VectorRead( chunks, buffer, info ) ); + + EXPECT_EQ( 0, memcmp( buffer, "AAAAABBBBB", 10 ) ); + + GTEST_ASSERT_XRDST( file.Close() ); + EXPECT_TRUE( info->GetSize() == 10 ); + + delete[] (char*)chunks[0].buffer; + delete[] (char*)chunks[1].buffer; + delete[] buffer; + delete info; + + EXPECT_TRUE( remove( targetURL.c_str() ) == 0 ); +} + +TEST_F(LocalFileHandlerTest, WriteVTest) +{ + using namespace XrdCl; + + //---------------------------------------------------------------------------- + // Initialize + //---------------------------------------------------------------------------- + std::string targetURL = "/tmp/lfilehandlertestfilewritev"; + CreateTestFileFunc( targetURL ); + + //---------------------------------------------------------------------------- + // Prepare WriteV + //---------------------------------------------------------------------------- + OpenFlags::Flags flags = OpenFlags::Update; + Access::Mode mode = Access::UR|Access::UW|Access::GR|Access::OR; + File file; + GTEST_ASSERT_XRDST( file.Open( targetURL, flags, mode ) ); + + char str[] = "WriteVTest"; + std::vector buffer( 10 ); + std::copy( str, str + sizeof( str ) - 1, buffer.begin() ); + int iovcnt = 2; + iovec iov[iovcnt]; + iov[0].iov_base = buffer.data(); + iov[0].iov_len = 6; + iov[1].iov_base = buffer.data() + 6; + iov[1].iov_len = 4; + GTEST_ASSERT_XRDST( file.WriteV( 7, iov, iovcnt ) ); + + uint32_t bytesRead = 0; + buffer.resize( 17 ); + GTEST_ASSERT_XRDST( file.Read( 0, 17, buffer.data(), bytesRead ) ); + EXPECT_TRUE( buffer.size() == 17 ); + std::string expected = "GenericWriteVTest"; + EXPECT_TRUE( std::string( buffer.data(), buffer.size() ) == expected ); + GTEST_ASSERT_XRDST( file.Close() ); + EXPECT_TRUE( remove( targetURL.c_str() ) == 0 ); +} + +TEST_F(LocalFileHandlerTest, XAttrTest) +{ + using namespace XrdCl; + + //---------------------------------------------------------------------------- + // Initialize + // (we do the test in /data as /tmp might be on tpmfs, + // which does not support xattrs) + //---------------------------------------------------------------------------- + std::string targetURL = "/data/lfilehandlertestfilexattr"; + CreateTestFileFunc( targetURL ); + + File f; + GTEST_ASSERT_XRDST( f.Open( targetURL, OpenFlags::Update ) ); + + //---------------------------------------------------------------------------- + // Test XAttr Set + //---------------------------------------------------------------------------- + std::vector attrs; + attrs.push_back( xattr_t( "version", "v3.3.3" ) ); + attrs.push_back( xattr_t( "description", "a very important file" ) ); + attrs.push_back( xattr_t( "checksum", "0x22334455" ) ); + + std::vector st_resp; + + GTEST_ASSERT_XRDST( f.SetXAttr( attrs, st_resp ) ); + + std::vector::iterator itr1; + for( itr1 = st_resp.begin(); itr1 != st_resp.end(); ++itr1 ) + GTEST_ASSERT_XRDST( itr1->status ); + + //---------------------------------------------------------------------------- + // Test XAttr Get + //---------------------------------------------------------------------------- + std::vector names; + names.push_back( "version" ); + names.push_back( "description" ); + std::vector resp; + GTEST_ASSERT_XRDST( f.GetXAttr( names, resp ) ); + + GTEST_ASSERT_XRDST( resp[0].status ); + GTEST_ASSERT_XRDST( resp[1].status ); + + EXPECT_TRUE( resp.size() == 2 ); + int vid = resp[0].name == "version" ? 0 : 1; + int did = vid == 0 ? 1 : 0; + EXPECT_TRUE( resp[vid].name == "version" && + resp[vid].value == "v3.3.3" ); + EXPECT_TRUE( resp[did].name == "description" && + resp[did].value == "a very important file" ); + + //---------------------------------------------------------------------------- + // Test XAttr Del + //---------------------------------------------------------------------------- + names.clear(); + names.push_back( "description" ); + st_resp.clear(); + GTEST_ASSERT_XRDST( f.DelXAttr( names, st_resp ) ); + EXPECT_TRUE( st_resp.size() == 1 ); + GTEST_ASSERT_XRDST( st_resp[0].status ); + + //---------------------------------------------------------------------------- + // Test XAttr List + //---------------------------------------------------------------------------- + resp.clear(); + GTEST_ASSERT_XRDST( f.ListXAttr( resp ) ); + EXPECT_TRUE( resp.size() == 2 ); + vid = resp[0].name == "version" ? 0 : 1; + int cid = vid == 0 ? 1 : 0; + EXPECT_TRUE( resp[vid].name == "version" && + resp[vid].value == "v3.3.3" ); + EXPECT_TRUE( resp[cid].name == "checksum" && + resp[cid].value == "0x22334455" ); + + //---------------------------------------------------------------------------- + // Cleanup + //---------------------------------------------------------------------------- + GTEST_ASSERT_XRDST( f.Close() ); + EXPECT_TRUE( remove( targetURL.c_str() ) == 0 ); +} From e7fb17abff886f9e718ac40f67f46bc81f03cc04 Mon Sep 17 00:00:00 2001 From: Angelo Galavotti Date: Thu, 20 Jul 2023 17:09:17 +0200 Subject: [PATCH 282/442] [Tests] Add converted tests from OperationWorkflowTest Converts tests in OperationWorkflowTest from CPPUnit to GTest --- tests/XrdCl/XrdClOperationsWorkflowTest.cc | 970 +++++++++++++++++++++ 1 file changed, 970 insertions(+) create mode 100644 tests/XrdCl/XrdClOperationsWorkflowTest.cc diff --git a/tests/XrdCl/XrdClOperationsWorkflowTest.cc b/tests/XrdCl/XrdClOperationsWorkflowTest.cc new file mode 100644 index 00000000000..b0ddd9f6ea5 --- /dev/null +++ b/tests/XrdCl/XrdClOperationsWorkflowTest.cc @@ -0,0 +1,970 @@ +//------------------------------------------------------------------------------ +// Copyright (c) 2023 by European Organization for Nuclear Research (CERN) +// Author: Angelo Galavotti +//------------------------------------------------------------------------------ +// This file is part of the XRootD software suite. +// +// XRootD is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// XRootD is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with XRootD. If not, see . +// +// In applying this licence, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. +//------------------------------------------------------------------------------ + +#include "TestEnv.hh" +#include "IdentityPlugIn.hh" +#include "GTestXrdHelpers.hh" +#include "XrdCl/XrdClURL.hh" +#include "XrdCl/XrdClOperations.hh" +#include "XrdCl/XrdClParallelOperation.hh" +#include "XrdCl/XrdClFileOperations.hh" +#include "XrdCl/XrdClFileSystemOperations.hh" +#include "XrdCl/XrdClCheckpointOperation.hh" +#include "XrdCl/XrdClFwd.hh" + +#include + +using namespace XrdClTests; + +//------------------------------------------------------------------------------ +// Declaration +//------------------------------------------------------------------------------ +class WorkflowTest: public ::testing::Test +{ + public: + void ReadingWorkflowTest(); + void WritingWorkflowTest(); + void MissingParameterTest(); + void OperationFailureTest(); + void DoubleRunningTest(); + void ParallelTest(); + void FileSystemWorkflowTest(); + void MixedWorkflowTest(); + void WorkflowWithFutureTest(); + void XAttrWorkflowTest(); + void MkDirAsyncTest(); + void CheckpointTest(); +}; + +namespace { + using namespace XrdCl; + + XrdCl::URL GetAddress(){ + Env *testEnv = TestEnv::GetEnv(); + std::string address; + EXPECT_TRUE( testEnv->GetString( "MainServerURL", address ) ); + return XrdCl::URL(address); + } + + std::string GetPath(const std::string &fileName){ + Env *testEnv = TestEnv::GetEnv(); + + std::string dataPath; + EXPECT_TRUE( testEnv->GetString( "DataPath", dataPath ) ); + + return dataPath + "/" + fileName; + } + + + std::string GetFileUrl(const std::string &fileName){ + Env *testEnv = TestEnv::GetEnv(); + + std::string address; + std::string dataPath; + + EXPECT_TRUE( testEnv->GetString( "MainServerURL", address ) ); + EXPECT_TRUE( testEnv->GetString( "DataPath", dataPath ) ); + + URL url( address ); + EXPECT_TRUE( url.IsValid() ); + + std::string path = dataPath + "/" + fileName; + std::string fileUrl = address + "/" + path; + + return fileUrl; + } + + class TestingHandler: public ResponseHandler { + public: + TestingHandler(){ + executed = false; + } + + void HandleResponseWithHosts(XrdCl::XRootDStatus *status, XrdCl::AnyObject *response, XrdCl::HostList *hostList) { + delete hostList; + HandleResponse(status, response); + } + + void HandleResponse(XrdCl::XRootDStatus *status, XrdCl::AnyObject *response) { + GTEST_ASSERT_XRDST(*status); + delete status; + delete response; + executed = true; + } + + bool Executed(){ + return executed; + } + + protected: + bool executed; + }; + + class ExpectErrorHandler: public ResponseHandler + { + public: + ExpectErrorHandler(){ + executed = false; + } + + void HandleResponseWithHosts(XrdCl::XRootDStatus *status, XrdCl::AnyObject *response, XrdCl::HostList *hostList) { + delete hostList; + HandleResponse(status, response); + } + + void HandleResponse(XrdCl::XRootDStatus *status, XrdCl::AnyObject *response) { + EXPECT_TRUE( !status->IsOK() ); + delete status; + delete response; + executed = true; + } + + bool Executed(){ + return executed; + } + + protected: + bool executed; + }; +} + + +TEST(WorkflowTest, ReadingWorkflowTest){ + using namespace XrdCl; + + //---------------------------------------------------------------------------- + // Initialize + //---------------------------------------------------------------------------- + std::string fileUrl = GetFileUrl("cb4aacf1-6f28-42f2-b68a-90a73460f424.dat"); + File f; + + //---------------------------------------------------------------------------- + // Create handlers + //---------------------------------------------------------------------------- + TestingHandler openHandler; + TestingHandler readHandler; + TestingHandler closeHandler; + + //---------------------------------------------------------------------------- + // Forward parameters between operations + //---------------------------------------------------------------------------- + Fwd size; + Fwd buffer; + + //---------------------------------------------------------------------------- + // Create and execute workflow + //---------------------------------------------------------------------------- + + const OpenFlags::Flags flags = OpenFlags::Read; + uint64_t offset = 0; + + auto &&pipe = Open( f, fileUrl, flags ) >> openHandler // by reference + | Stat( f, true) >> [size, buffer]( XRootDStatus &status, StatInfo &stat ) mutable + { + GTEST_ASSERT_XRDST( status ); + EXPECT_TRUE( stat.GetSize() == 1048576000 ); + size = stat.GetSize(); + buffer = new char[stat.GetSize()]; + } + | Read( f, offset, size, buffer ) >> &readHandler // by pointer + | Close( f ) >> closeHandler; // by reference + + XRootDStatus status = WaitFor( pipe ); + GTEST_ASSERT_XRDST( status ); + + EXPECT_TRUE( openHandler.Executed() ); + EXPECT_TRUE( readHandler.Executed() ); + EXPECT_TRUE( closeHandler.Executed() ); + + delete[] reinterpret_cast( *buffer ); +} + + +TEST(WorkflowTest, WritingWorkflowTest){ + using namespace XrdCl; + + //---------------------------------------------------------------------------- + // Initialize + //---------------------------------------------------------------------------- + std::string fileUrl = GetFileUrl("testFile.dat"); + auto flags = OpenFlags::Write | OpenFlags::Delete | OpenFlags::Update; + std::string texts[3] = {"First line\n", "Second line\n", "Third line\n"}; + File f; + + auto url = GetAddress(); + FileSystem fs(url); + auto relativePath = GetPath("testFile.dat"); + + auto createdFileSize = texts[0].size() + texts[1].size() + texts[2].size(); + + //---------------------------------------------------------------------------- + // Create handlers + //---------------------------------------------------------------------------- + std::packaged_task parser { + []( XRootDStatus& status, ChunkInfo &chunk ) + { + GTEST_ASSERT_XRDST( status ); + char* buffer = reinterpret_cast( chunk.buffer ); + std::string ret( buffer, chunk.length ); + delete[] buffer; + return ret; + } + }; + std::future rdresp = parser.get_future(); + + //---------------------------------------------------------------------------- + // Forward parameters between operations + //---------------------------------------------------------------------------- + Fwd> iov; + Fwd size; + Fwd buffer; + + //---------------------------------------------------------------------------- + // Create and execute workflow + //---------------------------------------------------------------------------- + Pipeline pipe = Open( f, fileUrl, flags ) >> [iov, texts]( XRootDStatus &status ) mutable + { + GTEST_ASSERT_XRDST( status ); + std::vector vec( 3 ); + vec[0].iov_base = strdup( texts[0].c_str() ); + vec[0].iov_len = texts[0].size(); + vec[1].iov_base = strdup( texts[1].c_str() ); + vec[1].iov_len = texts[1].size(); + vec[2].iov_base = strdup( texts[2].c_str() ); + vec[2].iov_len = texts[2].size(); + iov = std::move( vec ); + } + | WriteV( f, 0, iov ) + | Sync( f ) + | Stat( f, true ) >> [size, buffer, createdFileSize]( XRootDStatus &status, StatInfo &info ) mutable + { + GTEST_ASSERT_XRDST( status ); + EXPECT_TRUE( createdFileSize == info.GetSize() ); + size = info.GetSize(); + buffer = new char[info.GetSize()]; + } + | Read( f, 0, size, buffer ) >> parser + | Close( f ) + | Rm( fs, relativePath ); + + XRootDStatus status = WaitFor( std::move( pipe ) ); + GTEST_ASSERT_XRDST( status ); + EXPECT_TRUE( rdresp.get() == texts[0] + texts[1] + texts[2] ); + + free( (*iov)[0].iov_base ); + free( (*iov)[1].iov_base ); + free( (*iov)[2].iov_base ); +} + + +TEST(WorkflowTest, MissingParameterTest){ + using namespace XrdCl; + + //---------------------------------------------------------------------------- + // Initialize + //---------------------------------------------------------------------------- + std::string fileUrl = GetFileUrl("cb4aacf1-6f28-42f2-b68a-90a73460f424.dat"); + File f; + + //---------------------------------------------------------------------------- + // Create handlers + //---------------------------------------------------------------------------- + ExpectErrorHandler readHandler; + + //---------------------------------------------------------------------------- + // Bad forwards + //---------------------------------------------------------------------------- + Fwd size; + Fwd buffer; + + //---------------------------------------------------------------------------- + // Create and execute workflow + //---------------------------------------------------------------------------- + + bool error = false, closed = false; + const OpenFlags::Flags flags = OpenFlags::Read; + uint64_t offset = 0; + + Pipeline pipe = Open( f, fileUrl, flags ) + | Stat( f, true ) + | Read( f, offset, size, buffer ) >> readHandler // by reference + | Close( f ) >> [&]( XRootDStatus& st ) + { + closed = true; + } + | Final( [&]( const XRootDStatus& st ) + { + error = !st.IsOK(); + }); + + XRootDStatus status = WaitFor( std::move( pipe ) ); + EXPECT_TRUE( status.IsError() ); + //---------------------------------------------------------------------------- + // If there is an error, last handlers should not be executed + //---------------------------------------------------------------------------- + EXPECT_TRUE( readHandler.Executed() ); + EXPECT_TRUE( !closed & error ); +} + + + +TEST(WorkflowTest, OperationFailureTest){ + using namespace XrdCl; + + //---------------------------------------------------------------------------- + // Initialize + //---------------------------------------------------------------------------- + std::string fileUrl = GetFileUrl("noexisting.dat"); + File f; + + //---------------------------------------------------------------------------- + // Create handlers + //---------------------------------------------------------------------------- + ExpectErrorHandler openHandler; + std::future statresp; + std::future readresp; + std::future closeresp; + + //---------------------------------------------------------------------------- + // Create and execute workflow + //---------------------------------------------------------------------------- + + const OpenFlags::Flags flags = OpenFlags::Read; + auto &&pipe = Open( f, fileUrl, flags ) >> &openHandler // by pointer + | Stat( f, true ) >> statresp + | Read( f, 0, 0, nullptr ) >> readresp + | Close( f ) >> closeresp; + + XRootDStatus status = WaitFor( pipe ); // by obscure operation type + EXPECT_TRUE( status.IsError() ); + + //---------------------------------------------------------------------------- + // If there is an error, handlers should not be executed + //---------------------------------------------------------------------------- + EXPECT_TRUE(openHandler.Executed()); + + try + { + statresp.get(); + } + catch( PipelineException &ex ) + { + GTEST_ASSERT_XRDST_NOTOK( ex.GetError(), errPipelineFailed ); + } + + try + { + readresp.get(); + } + catch( PipelineException &ex ) + { + GTEST_ASSERT_XRDST_NOTOK( ex.GetError(), errPipelineFailed ); + } + + try + { + closeresp.get(); + } + catch( PipelineException &ex ) + { + GTEST_ASSERT_XRDST_NOTOK( ex.GetError(), errPipelineFailed ); + } +} + + +TEST(WorkflowTest, DoubleRunningTest){ + using namespace XrdCl; + + //---------------------------------------------------------------------------- + // Initialize + //---------------------------------------------------------------------------- + std::string fileUrl = GetFileUrl("cb4aacf1-6f28-42f2-b68a-90a73460f424.dat"); + File f; + + //---------------------------------------------------------------------------- + // Create and execute workflow + //---------------------------------------------------------------------------- + + const OpenFlags::Flags flags = OpenFlags::Read; + bool opened = false, closed = false; + + auto &&pipe = Open( f, fileUrl, flags ) >> [&]( XRootDStatus &status ){ opened = status.IsOK(); } + | Close( f ) >> [&]( XRootDStatus &status ){ closed = status.IsOK(); }; + + std::future ftr = Async( pipe ); + + //---------------------------------------------------------------------------- + // Running workflow again should fail + //---------------------------------------------------------------------------- + try + { + Async( pipe ); + EXPECT_TRUE( false ); + } + catch( std::logic_error &err ) + { + + } + + + XRootDStatus status = ftr.get(); + + //---------------------------------------------------------------------------- + // Running workflow again should fail + //---------------------------------------------------------------------------- + try + { + Async( pipe ); + EXPECT_TRUE( false ); + } + catch( std::logic_error &err ) + { + + } + + EXPECT_TRUE( status.IsOK() ); + + EXPECT_TRUE( opened ); + EXPECT_TRUE( closed ); +} + + +TEST(WorkflowTest, ParallelTest){ + using namespace XrdCl; + + //---------------------------------------------------------------------------- + // Initialize + //---------------------------------------------------------------------------- + File lockFile; + File firstFile; + File secondFile; + + std::string lockFileName = "lockfile.lock"; + std::string dataFileName = "testFile.dat"; + + std::string lockUrl = GetFileUrl(lockFileName); + std::string firstFileUrl = GetFileUrl("cb4aacf1-6f28-42f2-b68a-90a73460f424.dat"); + std::string secondFileUrl = GetFileUrl(dataFileName); + + const auto readFlags = OpenFlags::Read; + const auto createFlags = OpenFlags::Delete; + + // ---------------------------------------------------------------------------- + // Create lock file and new data file + // ---------------------------------------------------------------------------- + auto f = new File(); + auto dataF = new File(); + + std::vector pipes; pipes.reserve( 2 ); + pipes.emplace_back( Open( f, lockUrl, createFlags ) | Close( f ) ); + pipes.emplace_back( Open( dataF, secondFileUrl, createFlags ) | Close( dataF ) ); + GTEST_ASSERT_XRDST( WaitFor( Parallel( pipes ) >> []( XRootDStatus &status ){ GTEST_ASSERT_XRDST( status ); } ) ); + EXPECT_TRUE( pipes.empty() ); + + delete f; + delete dataF; + + //---------------------------------------------------------------------------- + // Create and execute workflow + //---------------------------------------------------------------------------- + uint64_t offset = 0; + uint32_t size = 50 ; + char* firstBuffer = new char[size](); + char* secondBuffer = new char[size](); + + Fwd url1, url2; + + bool lockHandlerExecuted = false; + auto lockOpenHandler = [&,url1, url2]( XRootDStatus &status ) mutable + { + url1 = GetFileUrl( "cb4aacf1-6f28-42f2-b68a-90a73460f424.dat" ); + url2 = GetFileUrl( dataFileName ); + lockHandlerExecuted = true; + }; + + std::future parallelresp, closeresp; + + Pipeline firstPipe = Open( firstFile, url1, readFlags ) + | Read( firstFile, offset, size, firstBuffer ) + | Close( firstFile ); + + Pipeline secondPipe = Open( secondFile, url2, readFlags ) + | Read( secondFile, offset, size, secondBuffer ) + | Close( secondFile ); + + Pipeline pipe = Open( lockFile, lockUrl, readFlags ) >> lockOpenHandler + | Parallel( firstPipe, secondPipe ) >> parallelresp + | Close( lockFile ) >> closeresp; + + XRootDStatus status = WaitFor( std::move( pipe ) ); + EXPECT_TRUE(status.IsOK()); + + EXPECT_TRUE(lockHandlerExecuted); + + try + { + parallelresp.get(); + closeresp.get(); + } + catch( std::exception &ex ) + { + EXPECT_TRUE( false ); + } + + delete[] firstBuffer; + delete[] secondBuffer; + + //---------------------------------------------------------------------------- + // Remove lock file and data file + //---------------------------------------------------------------------------- + f = new File(); + dataF = new File(); + + auto url = GetAddress(); + FileSystem fs( url ); + + auto lockRelativePath = GetPath(lockFileName); + auto dataRelativePath = GetPath(dataFileName); + + bool exec1 = false, exec2 = false; + Pipeline deletingPipe( Parallel( Rm( fs, lockRelativePath ) >> [&]( XRootDStatus &status ){ GTEST_ASSERT_XRDST( status ); exec1 = true; }, + Rm( fs, dataRelativePath ) >> [&]( XRootDStatus &status ){ GTEST_ASSERT_XRDST( status ); exec2 = true; } ) ); + GTEST_ASSERT_XRDST( WaitFor( std::move( deletingPipe ) ) ); + + EXPECT_TRUE( exec1 ); + EXPECT_TRUE( exec2 ); + + delete f; + delete dataF; + + //---------------------------------------------------------------------------- + // Test the policies + //---------------------------------------------------------------------------- + std::string url_exists = "/data/1db882c8-8cd6-4df1-941f-ce669bad3458.dat"; + std::string not_exists = "/data/blablabla.txt"; + GTEST_ASSERT_XRDST( WaitFor( Parallel( Stat( fs, url_exists ), Stat( fs, url_exists ) ).Any() ) ); + + std::string also_exists = "/data/3c9a9dd8-bc75-422c-b12c-f00604486cc1.dat"; + GTEST_ASSERT_XRDST( WaitFor( Parallel( Stat( fs, url_exists ), + Stat( fs, also_exists ), + Stat( fs, not_exists ) ).Some( 2 ) ) ); + std::atomic errcnt( 0 ); + std::atomic okcnt( 0 ); + + auto hndl = [&]( auto s, auto i ) + { + if( s.IsOK() ) ++okcnt; + else ++errcnt; + }; + + GTEST_ASSERT_XRDST( WaitFor( Parallel( Stat( fs, url_exists ) >> hndl, + Stat( fs, also_exists ) >> hndl, + Stat( fs, not_exists ) >> hndl ).AtLeast( 1 ) ) ); + EXPECT_TRUE( okcnt == 2 && errcnt == 1 ); +} + + +TEST(WorkflowTest, FileSystemWorkflowTest){ + using namespace XrdCl; + + TestingHandler mkDirHandler; + TestingHandler locateHandler; + TestingHandler moveHandler; + TestingHandler secondLocateHandler; + TestingHandler removeHandler; + + auto url = GetAddress(); + FileSystem fs( url ); + + std::string newDirUrl = GetPath("sourceDirectory"); + std::string destDirUrl = GetPath("destDirectory"); + + auto noneFlags = OpenFlags::None; + + Pipeline fsPipe = MkDir( fs, newDirUrl, MkDirFlags::None, Access::None ) >> mkDirHandler + | Locate( fs, newDirUrl, noneFlags ) >> locateHandler + | Mv( fs, newDirUrl, destDirUrl ) >> moveHandler + | Locate( fs, destDirUrl, OpenFlags::Refresh ) >> secondLocateHandler + | RmDir( fs, destDirUrl ) >> removeHandler; + + Pipeline pipe( std::move( fsPipe) ); + + XRootDStatus status = WaitFor( std::move( pipe ) ); + EXPECT_TRUE(status.IsOK()); + + EXPECT_TRUE(mkDirHandler.Executed()); + EXPECT_TRUE(locateHandler.Executed()); + EXPECT_TRUE(moveHandler.Executed()); + EXPECT_TRUE(secondLocateHandler.Executed()); + EXPECT_TRUE(removeHandler.Executed()); +} + + +TEST(WorkflowTest, MixedWorkflowTest){ + using namespace XrdCl; + + const size_t nbFiles = 2; + + FileSystem fs( GetAddress() ); + File file[nbFiles]; + + auto flags = OpenFlags::Write | OpenFlags::Delete | OpenFlags::Update; + + std::string dirName = "tempDir"; + std::string dirPath = GetPath( dirName ); + + std::string firstFileName = dirName + "/firstFile"; + std::string secondFileName = dirName + "/secondFile"; + std::string url[nbFiles] = { GetFileUrl(firstFileName), GetFileUrl(secondFileName) }; + + std::string path[nbFiles] = { GetPath(firstFileName), GetPath(secondFileName) }; + + std::string content[nbFiles] = { "First file content", "Second file content" }; + char* text[nbFiles] = { const_cast(content[0].c_str()), const_cast(content[1].c_str()) }; + size_t length[nbFiles] = { content[0].size(), content[1].size() }; + + + Fwd size[nbFiles]; + Fwd buffer[nbFiles]; + + std::future ftr[nbFiles]; + + bool cleaningHandlerExecuted = false; + auto cleaningHandler = [&](XRootDStatus &status, LocationInfo& info) + { + LocationInfo::Iterator it; + for( it = info.Begin(); it != info.End(); ++it ) + { + auto url = URL(it->GetAddress()); + FileSystem fs(url); + auto st = fs.RmDir(dirPath); + EXPECT_TRUE(st.IsOK()); + } + cleaningHandlerExecuted = true; + }; + + std::vector fileWorkflows; + for( size_t i = 0; i < nbFiles; ++i ) + { + auto &&operation = Open( file[i], url[i], flags ) + | Write( file[i], 0, length[i], text[i] ) + | Sync( file[i] ) + | Stat( file[i], true ) >> [size, buffer, i]( XRootDStatus &status, StatInfo &info ) mutable + { + GTEST_ASSERT_XRDST( status ); + size[i] = info.GetSize(); + buffer[i] = new char[*size[i]]; + } + | Read( file[i], 0, size[i], buffer[i] ) >> ftr[i] + | Close( file[i] ); + fileWorkflows.emplace_back( operation ); + } + + Pipeline pipe = MkDir( fs, dirPath, MkDirFlags::None, Access::None ) >> []( XRootDStatus &status ){ GTEST_ASSERT_XRDST( status ); } + | Parallel( fileWorkflows ) + | Rm( fs, path[0] ) + | Rm( fs, path[1] ) + | DeepLocate( fs, dirPath, OpenFlags::Refresh ) >> cleaningHandler; + + XRootDStatus status = WaitFor( std::move( pipe ) ); + GTEST_ASSERT_XRDST( status ); + + for( size_t i = 0; i < nbFiles; ++i ) + { + ChunkInfo chunk = ftr[i].get(); + char *buffer = reinterpret_cast( chunk.buffer ); + std::string result( buffer, chunk.length ); + delete[] buffer; + EXPECT_TRUE( result == content[i] ); + } + + EXPECT_TRUE(cleaningHandlerExecuted); +} + + +TEST(WorkflowTest, WorkflowWithFutureTest) +{ + using namespace XrdCl; + + //---------------------------------------------------------------------------- + // Initialize + //---------------------------------------------------------------------------- + Env *testEnv = TestEnv::GetEnv(); + + std::string address; + std::string dataPath; + + EXPECT_TRUE( testEnv->GetString( "MainServerURL", address ) ); + EXPECT_TRUE( testEnv->GetString( "DataPath", dataPath ) ); + + URL url( address ); + EXPECT_TRUE( url.IsValid() ); + + std::string filePath = dataPath + "/cb4aacf1-6f28-42f2-b68a-90a73460f424.dat"; + std::string fileUrl = address + "/"; + fileUrl += filePath; + + //---------------------------------------------------------------------------- + // Fetch some data and checksum + //---------------------------------------------------------------------------- + const uint32_t MB = 1024*1024; + char *expected = new char[40*MB]; + char *buffer = new char[40*MB]; + uint32_t bytesRead = 0; + File f; + + //---------------------------------------------------------------------------- + // Open and Read and Close in standard way + //---------------------------------------------------------------------------- + GTEST_ASSERT_XRDST( f.Open( fileUrl, OpenFlags::Read ) ); + GTEST_ASSERT_XRDST( f.Read( 10*MB, 40*MB, expected, bytesRead ) ); + EXPECT_TRUE( bytesRead == 40*MB ); + GTEST_ASSERT_XRDST( f.Close() ); + + //---------------------------------------------------------------------------- + // Now do the test + //---------------------------------------------------------------------------- + File file; + std::future ftr; + Pipeline pipeline = Open( file, fileUrl, OpenFlags::Read ) | Read( file, 10*MB, 40*MB, buffer ) >> ftr | Close( file ); + std::future status = Async( std::move( pipeline ) ); + + try + { + ChunkInfo result = ftr.get(); + EXPECT_TRUE( result.length = bytesRead ); + EXPECT_TRUE( strncmp( expected, (char*)result.buffer, bytesRead ) == 0 ); + } + catch( PipelineException &ex ) + { + EXPECT_TRUE( false ); + } + + GTEST_ASSERT_XRDST( status.get() ) + + delete[] expected; + delete[] buffer; +} + +TEST(WorkflowTest, XAttrWorkflowTest) +{ + using namespace XrdCl; + + //---------------------------------------------------------------------------- + // Initialize + //---------------------------------------------------------------------------- + Env *testEnv = TestEnv::GetEnv(); + + std::string address; + std::string dataPath; + + EXPECT_TRUE( testEnv->GetString( "MainServerURL", address ) ); + EXPECT_TRUE( testEnv->GetString( "DataPath", dataPath ) ); + + URL url( address ); + EXPECT_TRUE( url.IsValid() ); + + std::string filePath = dataPath + "/cb4aacf1-6f28-42f2-b68a-90a73460f424.dat"; + std::string fileUrl = address + "/"; + fileUrl += filePath; + + //---------------------------------------------------------------------------- + // Now do the test + //---------------------------------------------------------------------------- + std::string xattr_name = "xrd.name"; + std::string xattr_value = "ala ma kota"; + File file1, file2; + + // set extended attribute + Pipeline set = Open( file1, fileUrl, OpenFlags::Write ) + | SetXAttr( file1, xattr_name, xattr_value ) + | Close( file1 ); + GTEST_ASSERT_XRDST( WaitFor( std::move( set ) ) ); + + // read and delete the extended attribute + std::future rsp1; + + Pipeline get_del = Open( file2, fileUrl, OpenFlags::Update ) + | GetXAttr( file2, xattr_name ) >> rsp1 + | DelXAttr( file2, xattr_name ) + | Close( file2 ); + + GTEST_ASSERT_XRDST( WaitFor( std::move( get_del ) ) ); + + try + { + EXPECT_TRUE( xattr_value == rsp1.get() ); + } + catch( PipelineException &ex ) + { + EXPECT_TRUE( false ); + } + + //---------------------------------------------------------------------------- + // Test the bulk operations + //---------------------------------------------------------------------------- + std::vector names{ "xrd.name1", "xrd.name2" }; + std::vector attrs; + attrs.push_back( xattr_t( names[0], "ala ma kota" ) ); + attrs.push_back( xattr_t( names[1], "ela nic nie ma" ) ); + File file3, file4; + + // set extended attributes + Pipeline set_bulk = Open( file3, fileUrl, OpenFlags::Write ) + | SetXAttr( file3, attrs ) + | Close( file3 ); + GTEST_ASSERT_XRDST( WaitFor( std::move( set_bulk ) ) ); + + // read and delete the extended attribute + Pipeline get_del_bulk = Open( file4, fileUrl, OpenFlags::Update ) + | ListXAttr( file4 ) >> + [&]( XRootDStatus &status, std::vector &rsp ) + { + GTEST_ASSERT_XRDST( status ); + EXPECT_TRUE( rsp.size() == attrs.size() ); + for( size_t i = 0; i < rsp.size(); ++i ) + { + auto itr = std::find_if( attrs.begin(), attrs.end(), + [&]( xattr_t &a ){ return std::get<0>( a ) == rsp[i].name; } ); + EXPECT_TRUE( itr != attrs.end() ); + EXPECT_TRUE( std::get<1>( *itr ) == rsp[i].value ); + } + } + | DelXAttr( file4, names ) + | Close( file4 ); + + GTEST_ASSERT_XRDST( WaitFor( std::move( get_del_bulk ) ) ); + + //---------------------------------------------------------------------------- + // Test FileSystem xattr + //---------------------------------------------------------------------------- + FileSystem fs( fileUrl ); + std::future rsp2; + + Pipeline pipeline = SetXAttr( fs, filePath, xattr_name, xattr_value ) + | GetXAttr( fs, filePath, xattr_name ) >> rsp2 + | ListXAttr( fs, filePath ) >> + [&]( XRootDStatus &status, std::vector &rsp ) + { + GTEST_ASSERT_XRDST( status ); + EXPECT_TRUE( rsp.size() == 1 ); + EXPECT_TRUE( rsp[0].name == xattr_name ); + EXPECT_TRUE( rsp[0].value == xattr_value ); + } + | DelXAttr( fs, filePath, xattr_name ); + + GTEST_ASSERT_XRDST( WaitFor( std::move( pipeline ) ) ); + + try + { + EXPECT_TRUE( xattr_value == rsp2.get() ); + } + catch( PipelineException &ex ) + { + EXPECT_TRUE( false ); + } +} + +TEST(WorkflowTest, MkDirAsyncTest) { + using namespace XrdCl; + + FileSystem fs( GetAddress() ); + + std::packaged_task mkdirTask{ + []( XrdCl::XRootDStatus &st ) { + if (!st.IsOK()) + throw XrdCl::PipelineException( st ); + }}; + + XrdCl::Access::Mode access = XrdCl::Access::Mode::UR | XrdCl::Access::Mode::UW | + XrdCl::Access::Mode::UX | XrdCl::Access::Mode::GR | + XrdCl::Access::Mode::GW | XrdCl::Access::Mode::GX; + + auto &&t = Async( MkDir( fs, "/data/MkDirAsyncTest", XrdCl::MkDirFlags::None, access ) >> mkdirTask | + RmDir( fs, "/data/MkDirAsyncTest" ) + ); + + EXPECT_TRUE(t.get().status == stOK); +} + +TEST(WorkflowTest, CheckpointTest) { + using namespace XrdCl; + + File f1; + const char data[] = "Murzynek Bambo w Afryce mieszka,\n" + "czarna ma skore ten nasz kolezka\n" + "Uczy sie pilnie przez cale ranki\n" + "Ze swej murzynskiej pierwszej czytanki."; + std::string url = "root://localhost//data/chkpttest.txt"; + + GTEST_ASSERT_XRDST( WaitFor( Open( f1, url, OpenFlags::New | OpenFlags::Write ) | + Write( f1, 0, sizeof( data ), data ) | + Close( f1 ) ) ); + + //--------------------------------------------------------------------------- + // Update the file without commiting the checkpoint + //--------------------------------------------------------------------------- + File f2; + const char update[] = "Jan A Kowalski"; + + GTEST_ASSERT_XRDST( WaitFor( Open( f2, url, OpenFlags::Update ) | + Checkpoint( f2, ChkPtCode::BEGIN ) | + ChkptWrt( f2, 0, sizeof( update ), update ) | + Close( f2 ) ) ); + + File f3; + char readout[sizeof( data )]; + // readout the data to see if the update was succesful (it shouldn't be) + GTEST_ASSERT_XRDST( WaitFor( Open( f3, url, OpenFlags::Read ) | + Read( f3, 0, sizeof( readout ), readout ) | + Close( f3 ) ) ); + // we expect the data to be unchanged + EXPECT_TRUE( strncmp( readout, data, sizeof( data ) ) == 0 ); + + //--------------------------------------------------------------------------- + // Update the file and commit the changes + //--------------------------------------------------------------------------- + File f4; + GTEST_ASSERT_XRDST( WaitFor( Open( f4, url, OpenFlags::Update ) | + Checkpoint( f4, ChkPtCode::BEGIN ) | + ChkptWrt( f4, 0, sizeof( update ), update ) | + Checkpoint( f4, ChkPtCode::COMMIT ) | + Close( f4 ) ) ); + File f5; + // readout the data to see if the update was succesful (it shouldn't be) + GTEST_ASSERT_XRDST( WaitFor( Open( f5, url, OpenFlags::Read ) | + Read( f5, 0, sizeof( readout ), readout ) | + Close( f5 ) ) ); + // we expect the data to be unchanged + EXPECT_TRUE( strncmp( readout, update, sizeof( update ) ) == 0 ); + EXPECT_TRUE( strncmp( readout + sizeof( update ), data + sizeof( update ), + sizeof( data ) - sizeof( update ) ) == 0 ); + + //--------------------------------------------------------------------------- + // Now clean up + //--------------------------------------------------------------------------- + FileSystem fs( url ); + GTEST_ASSERT_XRDST( WaitFor( Rm( fs, "/data/chkpttest.txt" ) ) ); +} + From 03105e82273eb7cc8979fffeae9469d7eec418f8 Mon Sep 17 00:00:00 2001 From: Angelo Galavotti Date: Thu, 20 Jul 2023 17:09:36 +0200 Subject: [PATCH 283/442] [Tests] Add converted tests from PostMasterTest Converts tests in PostMasterTest from CPPUnit to GTest --- tests/XrdCl/XrdClPostMasterTest.cc | 572 +++++++++++++++++++++++++++++ 1 file changed, 572 insertions(+) create mode 100644 tests/XrdCl/XrdClPostMasterTest.cc diff --git a/tests/XrdCl/XrdClPostMasterTest.cc b/tests/XrdCl/XrdClPostMasterTest.cc new file mode 100644 index 00000000000..5677379b23c --- /dev/null +++ b/tests/XrdCl/XrdClPostMasterTest.cc @@ -0,0 +1,572 @@ +//------------------------------------------------------------------------------ +// Copyright (c) 2023 by European Organization for Nuclear Research (CERN) +// Author: Angelo Galavotti +//------------------------------------------------------------------------------ +// XRootD is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// XRootD is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with XRootD. If not, see . +//------------------------------------------------------------------------------ + +#include +#include +#include +#include +#include +#include + +#include + +#include "TestEnv.hh" +#include "GTestXrdHelpers.hh" + +using namespace XrdClTests; + +//------------------------------------------------------------------------------ +// Declaration +//------------------------------------------------------------------------------ +class PostMasterTest: public ::testing::Test +{ + public: + void FunctionalTest(); + void ThreadingTest(); + void PingIPv6(); + void MultiIPConnectionTest(); +}; + +//------------------------------------------------------------------------------ +// Tear down the post master +//------------------------------------------------------------------------------ +namespace +{ + class PostMasterFetch + { + public: + PostMasterFetch() { } + ~PostMasterFetch() { } + XrdCl::PostMaster *Get() { + return XrdCl::DefaultEnv::GetPostMaster(); + } + XrdCl::PostMaster *Reset() { + XrdCl::PostMaster *pm = Get(); + pm->Stop(); + pm->Finalize(); + EXPECT_TRUE( pm->Initialize() != 0 ); + EXPECT_TRUE( pm->Start() != 0 ); + return pm; + } + }; +} + +//------------------------------------------------------------------------------ +// Message filter +//------------------------------------------------------------------------------ +class XrdFilter +{ + friend class SyncMsgHandler; + + public: + XrdFilter( unsigned char id0 = 0, unsigned char id1 = 0 ) + { + streamId[0] = id0; + streamId[1] = id1; + } + + virtual bool Filter( const XrdCl::Message *msg ) + { + ServerResponse *resp = (ServerResponse *)msg->GetBuffer(); + if( resp->hdr.streamid[0] == streamId[0] && + resp->hdr.streamid[1] == streamId[1] ) + return true; + return false; + } + + virtual uint16_t GetSid() const + { + return (((uint16_t)streamId[1] << 8) | (uint16_t)streamId[0]); + } + + unsigned char streamId[2]; +}; + +//------------------------------------------------------------------------------ +// Synchronous Message Handler +//------------------------------------------------------------------------------ +class SyncMsgHandler : public XrdCl::MsgHandler +{ + public: + SyncMsgHandler() : + sem( 0 ), request( nullptr ), response( nullptr ), expiration( 0 ) + { + } + + private: + + XrdFilter filter; + XrdSysSemaphore sem; + XrdCl::XRootDStatus status; + const XrdCl::Message *request; + std::shared_ptr response; + time_t expiration; + + public: + + //------------------------------------------------------------------------ + // Examine an incoming message, and decide on the action to be taken + //------------------------------------------------------------------------ + virtual uint16_t Examine( std::shared_ptr &msg ) + { + if( filter.Filter( msg.get() ) ) + { + response = msg; + return RemoveHandler; + } + return Ignore; + } + + //------------------------------------------------------------------------ + // Reexamine the incoming message, and decide on the action to be taken + //------------------------------------------------------------------------ + virtual uint16_t InspectStatusRsp() + { + return XrdCl::MsgHandler::Action::None; + } + + //------------------------------------------------------------------------ + // Get handler sid + //------------------------------------------------------------------------ + virtual uint16_t GetSid() const + { + return filter.GetSid(); + } + + //------------------------------------------------------------------------ + // Process the message if it was "taken" by the examine action + //------------------------------------------------------------------------ + virtual void Process() + { + sem.Post(); + }; + + //------------------------------------------------------------------------ + // Handle an event other that a message arrival + //------------------------------------------------------------------------ + virtual uint8_t OnStreamEvent( StreamEvent event, + XrdCl::XRootDStatus status ) + { + if( event == Ready ) + return 0; + this->status = status; + sem.Post(); + return RemoveHandler; + }; + + //------------------------------------------------------------------------ + // The requested action has been performed and the status is available + //------------------------------------------------------------------------ + virtual void OnStatusReady( const XrdCl::Message *message, + XrdCl::XRootDStatus status ) + { + request = message; + this->status = status; + if( !status.IsOK() ) + sem.Post(); + } + + //------------------------------------------------------------------------ + // Get a timestamp after which we give up + //------------------------------------------------------------------------ + virtual time_t GetExpiration() + { + return expiration; + } + + void SetExpiration( time_t e ) + { + expiration = e; + } + + XrdCl::XRootDStatus WaitFor( XrdCl::Message &rsp ) + { + sem.Wait(); + if( response ) + rsp = std::move( *response ); + return status; + } + + void SetFilter( unsigned char id0 = 0, unsigned char id1 = 0 ) + { + filter.streamId[0] = id0; + filter.streamId[1] = id1; + } +}; + +//------------------------------------------------------------------------------ +// Thread argument passing helper +//------------------------------------------------------------------------------ +struct ArgHelper +{ + XrdCl::PostMaster *pm; + int index; +}; + +//------------------------------------------------------------------------------ +// Post master test thread +//------------------------------------------------------------------------------ +void *TestThreadFunc( void *arg ) +{ + using namespace XrdCl; + + std::string address; + Env *testEnv = TestEnv::GetEnv(); + EXPECT_TRUE( testEnv->GetString( "MainServerURL", address ) ); + + ArgHelper *a = (ArgHelper*)arg; + URL host( address ); + + //---------------------------------------------------------------------------- + // Send the ping messages + //---------------------------------------------------------------------------- + SyncMsgHandler msgHandlers[100]; + Message msgs[100]; + time_t expires = time(0)+1200; + for( int i = 0; i < 100; ++i ) + { + msgs[i].Allocate( sizeof( ClientPingRequest ) ); + msgs[i].Zero(); + msgs[i].SetDescription( "kXR_ping ()" ); + ClientPingRequest *request = (ClientPingRequest *)msgs[i].GetBuffer(); + request->streamid[0] = a->index; + request->requestid = kXR_ping; + request->dlen = 0; + XRootDTransport::MarshallRequest( &msgs[i] ); + request->streamid[1] = i; + msgHandlers[i].SetFilter( a->index, i ); + msgHandlers[i].SetExpiration( expires ); + GTEST_ASSERT_XRDST( a->pm->Send( host, &msgs[i], &msgHandlers[i], false, expires ) ); + } + + //---------------------------------------------------------------------------- + // Receive the answers + //---------------------------------------------------------------------------- + for( int i = 0; i < 100; ++i ) + { + XrdCl::Message msg; + GTEST_ASSERT_XRDST( msgHandlers[i].WaitFor( msg ) ); + ServerResponse *resp = (ServerResponse *)msg.GetBuffer(); + EXPECT_TRUE( resp != 0 ); + EXPECT_TRUE( resp->hdr.status == kXR_ok ); + EXPECT_TRUE( msg.GetSize() == 8 ); + } + return 0; +} + +//------------------------------------------------------------------------------ +// Threading test +//------------------------------------------------------------------------------ +TEST(PostMasterTest, ThreadingTest) +{ + using namespace XrdCl; + PostMasterFetch pmfetch; + PostMaster *postMaster = pmfetch.Get(); + + pthread_t thread[100]; + ArgHelper helper[100]; + + for( int i = 0; i < 100; ++i ) + { + helper[i].pm = postMaster; + helper[i].index = i; + pthread_create( &thread[i], 0, TestThreadFunc, &helper[i] ); + } + + for( int i = 0; i < 100; ++i ) + pthread_join( thread[i], 0 ); +} + +//------------------------------------------------------------------------------ +// Test the functionality of a poller +//------------------------------------------------------------------------------ +TEST(PostMasterTest, FunctionalTest) +{ + using namespace XrdCl; + + //---------------------------------------------------------------------------- + // Initialize the stuff + //---------------------------------------------------------------------------- + Env *env = DefaultEnv::GetEnv(); + Env *testEnv = TestEnv::GetEnv(); + env->PutInt( "TimeoutResolution", 1 ); + env->PutInt( "ConnectionWindow", 15 ); + + PostMasterFetch pmfetch; + PostMaster *postMaster = pmfetch.Get(); + + std::string address; + EXPECT_TRUE( testEnv->GetString( "MainServerURL", address ) ); + + //---------------------------------------------------------------------------- + // Send a message and wait for the answer + //---------------------------------------------------------------------------- + time_t expires = ::time(0)+1200; + Message m1, m2; + URL host( address ); + + SyncMsgHandler msgHandler1; + msgHandler1.SetFilter( 1, 2 ); + msgHandler1.SetExpiration( expires ); + + m1.Allocate( sizeof( ClientPingRequest ) ); + m1.Zero(); + + ClientPingRequest *request = (ClientPingRequest *)m1.GetBuffer(); + request->streamid[0] = 1; + request->streamid[1] = 2; + request->requestid = kXR_ping; + request->dlen = 0; + XRootDTransport::MarshallRequest( &m1 ); + + GTEST_ASSERT_XRDST( postMaster->Send( host, &m1, &msgHandler1, false, expires ) ); + + GTEST_ASSERT_XRDST( msgHandler1.WaitFor( m2 ) ); + ServerResponse *resp = (ServerResponse *)m2.GetBuffer(); + EXPECT_TRUE( resp != 0 ); + EXPECT_TRUE( resp->hdr.status == kXR_ok ); + EXPECT_TRUE( m2.GetSize() == 8 ); + + //---------------------------------------------------------------------------- + // Send out some stuff to a location where nothing listens + //---------------------------------------------------------------------------- + env->PutInt( "ConnectionWindow", 5 ); + env->PutInt( "ConnectionRetry", 3 ); + URL localhost1( "root://localhost:10101" ); + + SyncMsgHandler msgHandler2; + msgHandler2.SetFilter( 1, 2 ); + time_t shortexp = ::time(0) + 3; + msgHandler2.SetExpiration( shortexp ); + GTEST_ASSERT_XRDST( postMaster->Send( localhost1, &m1, &msgHandler2, false, + shortexp ) ); + GTEST_ASSERT_XRDST_NOTOK( msgHandler2.WaitFor( m2 ), errOperationExpired ); + + SyncMsgHandler msgHandler3; + msgHandler3.SetFilter( 1, 2 ); + msgHandler3.SetExpiration( expires ); + GTEST_ASSERT_XRDST( postMaster->Send( localhost1, &m1, &msgHandler3, false, + expires ) ); + GTEST_ASSERT_XRDST_NOTOK( msgHandler3.WaitFor( m2 ), errConnectionError ); + + //---------------------------------------------------------------------------- + // Test the transport queries + //---------------------------------------------------------------------------- + AnyObject nameObj, sidMgrObj; + Status st1, st2; + const char *name = 0; + + GTEST_ASSERT_XRDST( postMaster->QueryTransport( host, + TransportQuery::Name, + nameObj ) ); + nameObj.Get( name ); + + EXPECT_TRUE( name ); + EXPECT_TRUE( !::strcmp( name, "XRootD" ) ); + + //---------------------------------------------------------------------------- + // Reinitialize and try to do something + //---------------------------------------------------------------------------- + env->PutInt( "LoadBalancerTTL", 5 ); + postMaster = pmfetch.Reset(); + + m2.Free(); + m1.Zero(); + + request = (ClientPingRequest *)m1.GetBuffer(); + request->streamid[0] = 1; + request->streamid[1] = 2; + request->requestid = kXR_ping; + request->dlen = 0; + XRootDTransport::MarshallRequest( &m1 ); + + SyncMsgHandler msgHandler4; + msgHandler4.SetFilter( 1, 2 ); + msgHandler4.SetExpiration( expires ); + GTEST_ASSERT_XRDST( postMaster->Send( host, &m1, &msgHandler4, false, expires ) ); + + GTEST_ASSERT_XRDST( msgHandler4.WaitFor( m2 ) ); + resp = (ServerResponse *)m2.GetBuffer(); + EXPECT_TRUE( resp != 0 ); + EXPECT_TRUE( resp->hdr.status == kXR_ok ); + EXPECT_TRUE( m2.GetSize() == 8 ); + + //---------------------------------------------------------------------------- + // Sleep 10 secs waiting for iddle connection to be closed and see + // whether we can reconnect + //---------------------------------------------------------------------------- + sleep( 10 ); + SyncMsgHandler msgHandler5; + msgHandler5.SetFilter( 1, 2 ); + msgHandler5.SetExpiration( expires ); + GTEST_ASSERT_XRDST( postMaster->Send( host, &m1, &msgHandler5, false, expires ) ); + + GTEST_ASSERT_XRDST( msgHandler5.WaitFor( m2 ) ); + resp = (ServerResponse *)m2.GetBuffer(); + EXPECT_TRUE( resp != 0 ); + EXPECT_TRUE( resp->hdr.status == kXR_ok ); + EXPECT_TRUE( m2.GetSize() == 8 ); +} + + +//------------------------------------------------------------------------------ +// Test the functionality of a poller +//------------------------------------------------------------------------------ +TEST(PostMasterTest, PingIPv6) +{ + using namespace XrdCl; +#if 0 + //---------------------------------------------------------------------------- + // Initialize the stuff + //---------------------------------------------------------------------------- + PostMasterFetch pmfetch; + PostMaster *postMaster = pmfetch.Get(); + + //---------------------------------------------------------------------------- + // Build the message + //---------------------------------------------------------------------------- + Message m1, *m2 = 0; + XrdFilter f1( 1, 2 ); + URL localhost1( "root://[::1]" ); + URL localhost2( "root://[::127.0.0.1]" ); + + m1.Allocate( sizeof( ClientPingRequest ) ); + m1.Zero(); + + ClientPingRequest *request = (ClientPingRequest *)m1.GetBuffer(); + request->streamid[0] = 1; + request->streamid[1] = 2; + request->requestid = kXR_ping; + request->dlen = 0; + XRootDTransport::MarshallRequest( &m1 ); + + Status sc; + + //---------------------------------------------------------------------------- + // Send the message - localhost1 + //---------------------------------------------------------------------------- + sc = postMaster->Send( localhost1, &m1, false, 1200 ); + EXPECT_TRUE( sc.IsOK() ); + + sc = postMaster->Receive( localhost1, m2, &f1, false, 1200 ); + EXPECT_TRUE( sc.IsOK() ); + ServerResponse *resp = (ServerResponse *)m2->GetBuffer(); + EXPECT_TRUE( resp != 0 ); + EXPECT_TRUE( resp->hdr.status == kXR_ok ); + EXPECT_TRUE( m2->GetSize() == 8 ); + + //---------------------------------------------------------------------------- + // Send the message - localhost2 + //---------------------------------------------------------------------------- + sc = postMaster->Send( localhost2, &m1, false, 1200 ); + EXPECT_TRUE( sc.IsOK() ); + + sc = postMaster->Receive( localhost2, m2, &f1, 1200 ); + EXPECT_TRUE( sc.IsOK() ); + resp = (ServerResponse *)m2->GetBuffer(); + EXPECT_TRUE( resp != 0 ); + EXPECT_TRUE( resp->hdr.status == kXR_ok ); + EXPECT_TRUE( m2->GetSize() == 8 ); +#endif +} + +namespace +{ + //---------------------------------------------------------------------------- + // Create a ping message + //---------------------------------------------------------------------------- + XrdCl::Message *CreatePing( char streamID1, char streamID2 ) + { + using namespace XrdCl; + Message *m = new Message(); + m->Allocate( sizeof( ClientPingRequest ) ); + m->Zero(); + + ClientPingRequest *request = (ClientPingRequest *)m->GetBuffer(); + request->streamid[0] = streamID1; + request->streamid[1] = streamID2; + request->requestid = kXR_ping; + XRootDTransport::MarshallRequest( m ); + return m; + } +} + + +//------------------------------------------------------------------------------ +// Connection test +//------------------------------------------------------------------------------ +TEST(PostMasterTest, MultiIPConnectionTest) +{ + using namespace XrdCl; + + //---------------------------------------------------------------------------- + // Initialize the stuff + //---------------------------------------------------------------------------- + Env *env = DefaultEnv::GetEnv(); + Env *testEnv = TestEnv::GetEnv(); + env->PutInt( "TimeoutResolution", 1 ); + env->PutInt( "ConnectionWindow", 5 ); + + PostMasterFetch pmfetch; + PostMaster *postMaster = pmfetch.Get(); + + std::string address; + EXPECT_TRUE( testEnv->GetString( "MultiIPServerURL", address ) ); + + time_t expires = ::time(0)+1200; + URL url1( "nenexistent" ); + URL url2( address ); + URL url3( address ); + url2.SetPort( 1111 ); + url3.SetPort( 1099 ); + + //---------------------------------------------------------------------------- + // Sent ping to a nonexistent host + //---------------------------------------------------------------------------- + SyncMsgHandler msgHandler1; + msgHandler1.SetFilter( 1, 2 ); + msgHandler1.SetExpiration( expires ); + Message *m = CreatePing( 1, 2 ); + GTEST_ASSERT_XRDST_NOTOK( postMaster->Send( url1, m, &msgHandler1, false, expires ), + errInvalidAddr ); + + //---------------------------------------------------------------------------- + // Try on the wrong port + //---------------------------------------------------------------------------- + SyncMsgHandler msgHandler2; + msgHandler2.SetFilter( 1, 2 ); + msgHandler2.SetExpiration( expires ); + Message m2; + + GTEST_ASSERT_XRDST( postMaster->Send( url2, m, &msgHandler2, false, expires ) ); + GTEST_ASSERT_XRDST_NOTOK( msgHandler2.WaitFor( m2 ), errConnectionError ); + + //---------------------------------------------------------------------------- + // Try on a good one + //---------------------------------------------------------------------------- + SyncMsgHandler msgHandler3; + msgHandler3.SetFilter( 1, 2 ); + msgHandler3.SetExpiration( expires ); + + GTEST_ASSERT_XRDST( postMaster->Send( url3, m, &msgHandler3, false, expires ) ); + GTEST_ASSERT_XRDST( msgHandler3.WaitFor( m2 ) ); + ServerResponse *resp = (ServerResponse *)m2.GetBuffer(); + EXPECT_TRUE( resp != 0 ); + EXPECT_TRUE( resp->hdr.status == kXR_ok ); + EXPECT_TRUE( m2.GetSize() == 8 ); +} From 0a25467812186c43bdc97cc109d881cfc2018ef1 Mon Sep 17 00:00:00 2001 From: Angelo Galavotti Date: Thu, 20 Jul 2023 17:10:13 +0200 Subject: [PATCH 284/442] [Tests] Add converted tests from ThreadingTest Converts tests in ThreadingTest from CPPUnit to GTest --- tests/XrdCl/XrdClThreadingTest.cc | 337 ++++++++++++++++++++++++++++++ 1 file changed, 337 insertions(+) create mode 100644 tests/XrdCl/XrdClThreadingTest.cc diff --git a/tests/XrdCl/XrdClThreadingTest.cc b/tests/XrdCl/XrdClThreadingTest.cc new file mode 100644 index 00000000000..16d11ddb47a --- /dev/null +++ b/tests/XrdCl/XrdClThreadingTest.cc @@ -0,0 +1,337 @@ +//------------------------------------------------------------------------------ +// Copyright (c) 2023 by European Organization for Nuclear Research (CERN) +// Author: Angelo Galavotti +//------------------------------------------------------------------------------ +// XRootD is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// XRootD is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with XRootD. If not, see . +//------------------------------------------------------------------------------ + +#include "TestEnv.hh" +#include "Utils.hh" +#include "GTestXrdHelpers.hh" +#include "XrdCl/XrdClFile.hh" +#include "XrdCl/XrdClDefaultEnv.hh" +#include "XrdCl/XrdClUtils.hh" +#include +#include +#include +#include +#include +#include "XrdCks/XrdCksData.hh" + +//------------------------------------------------------------------------------ +// Thread helper struct +//------------------------------------------------------------------------------ +struct ThreadData +{ + ThreadData(): + file( 0 ), startOffset( 0 ), length( 0 ), checkSum( 0 ), + firstBlockChecksum(0) {} + XrdCl::File *file; + uint64_t startOffset; + uint64_t length; + uint32_t checkSum; + uint32_t firstBlockChecksum; +}; + +const uint32_t MB = 1024*1024; + +//------------------------------------------------------------------------------ +// Declaration +//------------------------------------------------------------------------------ +class ThreadingTest: public ::testing::Test +{ + public: + typedef void (*TransferCallback)( ThreadData *data ); + void ReadTestFunc( TransferCallback transferCallback ); + void ReadTest(); + void MultiStreamReadTest(); + void ReadForkTest(); + void MultiStreamReadForkTest(); + void MultiStreamReadMonitorTest(); +}; + +//------------------------------------------------------------------------------ +// Reader thread +//------------------------------------------------------------------------------ +void *DataReader( void *arg ) +{ + using namespace XrdClTests; + + ThreadData *td = (ThreadData*)arg; + + uint64_t offset = td->startOffset; + uint64_t dataLeft = td->length; + uint64_t chunkSize = 0; + uint32_t bytesRead = 0; + char *buffer = new char[4*MB]; + + while( 1 ) + { + chunkSize = 4*MB; + if( chunkSize > dataLeft ) + chunkSize = dataLeft; + + if( chunkSize == 0 ) + break; + + GTEST_ASSERT_XRDST( td->file->Read( offset, chunkSize, buffer, + bytesRead ) ); + + offset += bytesRead; + dataLeft -= bytesRead; + td->checkSum = Utils::UpdateCRC32( td->checkSum, buffer, bytesRead ); + } + + delete [] buffer; + + return 0; +} + +//------------------------------------------------------------------------------ +// Read test +//------------------------------------------------------------------------------ +void ThreadingTest::ReadTestFunc( TransferCallback transferCallback ) +{ + using namespace XrdCl; + + //---------------------------------------------------------------------------- + // Initialize + //---------------------------------------------------------------------------- + Env *testEnv = XrdClTests::TestEnv::GetEnv(); + + std::string address; + std::string dataPath; + + EXPECT_TRUE( testEnv->GetString( "MainServerURL", address ) ); + EXPECT_TRUE( testEnv->GetString( "DataPath", dataPath ) ); + + URL url( address ); + EXPECT_TRUE( url.IsValid() ); + + std::string fileUrl[5]; + std::string path[5]; + path[0] = dataPath + "/1db882c8-8cd6-4df1-941f-ce669bad3458.dat"; + path[1] = dataPath + "/3c9a9dd8-bc75-422c-b12c-f00604486cc1.dat"; + path[2] = dataPath + "/7235b5d1-cede-4700-a8f9-596506b4cc38.dat"; + path[3] = dataPath + "/7e480547-fe1a-4eaf-a210-0f3927751a43.dat"; + path[4] = dataPath + "/89120cec-5244-444c-9313-703e4bee72de.dat"; + + for( int i = 0; i < 5; ++i ) + fileUrl[i] = address + "/" + path[i]; + + //---------------------------------------------------------------------------- + // Open and stat the files + //---------------------------------------------------------------------------- + ThreadData threadData[20]; + + for( int i = 0; i < 5; ++i ) + { + File *f = new File(); + StatInfo *si = 0; + GTEST_ASSERT_XRDST( f->Open( fileUrl[i], OpenFlags::Read ) ); + GTEST_ASSERT_XRDST( f->Stat( false, si ) ); + EXPECT_TRUE( si ); + EXPECT_TRUE( si->TestFlags( StatInfo::IsReadable ) ); + + uint64_t step = si->GetSize()/4; + + for( int j = 0; j < 4; ++j ) + { + threadData[j*5+i].file = f; + threadData[j*5+i].startOffset = j*step; + threadData[j*5+i].length = step; + threadData[j*5+i].checkSum = XrdClTests::Utils::GetInitialCRC32(); + + + //------------------------------------------------------------------------ + // Get the checksum of the first 4MB block at the startOffser - this + // will be verified by the forking test + //------------------------------------------------------------------------ + uint64_t offset = threadData[j*5+i].startOffset; + char *buffer = new char[4*MB]; + uint32_t bytesRead = 0; + + GTEST_ASSERT_XRDST( f->Read( offset, 4*MB, buffer, bytesRead ) ); + EXPECT_TRUE( bytesRead == 4*MB ); + threadData[j*5+i].firstBlockChecksum = + XrdClTests::Utils::ComputeCRC32( buffer, 4*MB ); + delete [] buffer; + } + + threadData[15+i].length = si->GetSize() - threadData[15+i].startOffset; + delete si; + } + + //---------------------------------------------------------------------------- + // Spawn the threads and wait for them to finish + //---------------------------------------------------------------------------- + pthread_t thread[20]; + for( int i = 0; i < 20; ++i ) + GTEST_ASSERT_PTHREAD( pthread_create( &(thread[i]), 0, + ::DataReader, &(threadData[i]) ) ); + + if( transferCallback ) + (*transferCallback)( threadData ); + + for( int i = 0; i < 20; ++i ) + GTEST_ASSERT_PTHREAD( pthread_join( thread[i], 0 ) ); + + //---------------------------------------------------------------------------- + // Glue up and compare the checksums + //---------------------------------------------------------------------------- + uint32_t checkSums[5]; + for( int i = 0; i < 5; ++i ) + { + //-------------------------------------------------------------------------- + // Calculate the local check sum + //-------------------------------------------------------------------------- + checkSums[i] = threadData[i].checkSum; + for( int j = 1; j < 4; ++j ) + { + checkSums[i] = XrdClTests::Utils::CombineCRC32( checkSums[i], + threadData[j*5+i].checkSum, + threadData[j*5+i].length ); + } + + char crcBuff[9]; + XrdCksData crc; crc.Set( &checkSums[i], 4 ); crc.Get( crcBuff, 9 ); + std::string transferSum = "zcrc32:"; transferSum += crcBuff; + + //-------------------------------------------------------------------------- + // Get the checksum + //-------------------------------------------------------------------------- + std::string remoteSum, lastUrl; + threadData[i].file->GetProperty( "LastURL", lastUrl ); + GTEST_ASSERT_XRDST( Utils::GetRemoteCheckSum( + remoteSum, "zcrc32", lastUrl ) ); + EXPECT_TRUE( remoteSum == transferSum ); // TODO; same test repeated twice?? (check w amadio) + EXPECT_TRUE( remoteSum == transferSum ) << path[i]; + } + + //---------------------------------------------------------------------------- + // Close the files + //---------------------------------------------------------------------------- + for( int i = 0; i < 5; ++i ) + { + GTEST_ASSERT_XRDST( threadData[i].file->Close() ); + delete threadData[i].file; + } +} + + +//------------------------------------------------------------------------------ +// Read test +//------------------------------------------------------------------------------ +TEST_F(ThreadingTest, ReadTest) +{ + ReadTestFunc(0); +} + +//------------------------------------------------------------------------------ +// Multistream read test +//------------------------------------------------------------------------------ +TEST_F(ThreadingTest, MultiStreamReadTest) +{ + XrdCl::Env *env = XrdCl::DefaultEnv::GetEnv(); + env->PutInt( "SubStreamsPerChannel", 4 ); + ReadTestFunc(0); +} + +//------------------------------------------------------------------------------ +// Child - read some data from each of the open files and close them +//------------------------------------------------------------------------------ +int runChild( ThreadData *td ) +{ + XrdCl::Log *log = XrdClTests::TestEnv::GetLog(); + log->Debug( 1, "Running the child" ); + + for( int i = 0; i < 20; ++i ) + { + uint64_t offset = td[i].startOffset; + char *buffer = new char[4*MB]; + uint32_t bytesRead = 0; + + GTEST_ASSERT_XRDST( td[i].file->Read( offset, 4*MB, buffer, bytesRead ) ); + EXPECT_TRUE( bytesRead == 4*MB ); + EXPECT_TRUE( td[i].firstBlockChecksum == + XrdClTests::Utils::ComputeCRC32( buffer, 4*MB ) ); + delete [] buffer; + } + + for( int i = 0; i < 5; ++i ) + { + GTEST_ASSERT_XRDST( td[i].file->Close() ); + delete td[i].file; + } + + return 0; +} + +//------------------------------------------------------------------------------ +// Forking function +//------------------------------------------------------------------------------ +void forkAndRead( ThreadData *data ) +{ + XrdCl::Log *log = XrdClTests::TestEnv::GetLog(); + for( int chld = 0; chld < 5; ++chld ) + { + sleep(10); + pid_t pid; + log->Debug( 1, "About to fork" ); + GTEST_ASSERT_ERRNO( (pid=fork()) != -1 ); + + if( !pid ) _exit( runChild( data ) ); + + log->Debug( 1, "Forked successfully, pid of the child: %d", pid ); + int status; + log->Debug( 1, "Waiting for the child" ); + GTEST_ASSERT_ERRNO( waitpid( pid, &status, 0 ) != -1 ); + log->Debug( 1, "Wait done, status: %d", status ); + EXPECT_TRUE( WIFEXITED( status ) ); + EXPECT_TRUE( WEXITSTATUS( status ) == 0 ); + } +} + +//------------------------------------------------------------------------------ +// Read fork test +//------------------------------------------------------------------------------ +TEST_F(ThreadingTest, ReadForkTest) +{ + XrdCl::Env *env = XrdCl::DefaultEnv::GetEnv(); + env->PutInt( "RunForkHandler", 1 ); + ReadTestFunc(&forkAndRead); +} + +//------------------------------------------------------------------------------ +// Multistream read fork test +//------------------------------------------------------------------------------ +TEST_F(ThreadingTest, MultiStreamReadForkTest) +{ + XrdCl::Env *env = XrdCl::DefaultEnv::GetEnv(); + env->PutInt( "SubStreamsPerChannel", 4 ); + env->PutInt( "RunForkHandler", 1 ); + ReadTestFunc(&forkAndRead); +} + +//------------------------------------------------------------------------------ +// Multistream read monitor +//------------------------------------------------------------------------------ +TEST_F(ThreadingTest, MultiStreamReadMonitorTest) +{ + XrdCl::Env *env = XrdCl::DefaultEnv::GetEnv(); + env->PutString( "ClientMonitor", "./libXrdClTestMonitor.so" ); + env->PutString( "ClientMonitorParam", "TestParam" ); + env->PutInt( "SubStreamsPerChannel", 4 ); + ReadTestFunc(0); +} From 16c0a84e6b1c603d34ba585d043ff37e61d91064 Mon Sep 17 00:00:00 2001 From: Angelo Galavotti Date: Thu, 20 Jul 2023 17:10:40 +0200 Subject: [PATCH 285/442] [Tests] Add newly converted tests into the build system --- tests/XrdCl/CMakeLists.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/XrdCl/CMakeLists.txt b/tests/XrdCl/CMakeLists.txt index bca0c2a5aca..64748874963 100644 --- a/tests/XrdCl/CMakeLists.txt +++ b/tests/XrdCl/CMakeLists.txt @@ -4,11 +4,20 @@ add_executable(xrdcl-unit-tests XrdClPoller.cc XrdClSocket.cc XrdClUtilsTest.cc + IdentityPlugIn.cc + XrdClFileTest.cc + XrdClFileCopyTest.cc + XrdClFileSystemTest.cc + XrdClOperationsWorkflowTest.cc + XrdClLocalFileHandlerTest.cc + XrdClPostMasterTest.cc + XrdClThreadingTest.cc ../common/Server.cc ../common/Utils.cc ../common/TestEnv.cc ) + target_link_libraries(xrdcl-unit-tests XrdCl XrdXml From e17024f6b3c9064808c0d97e1635da112267b1a2 Mon Sep 17 00:00:00 2001 From: Angelo Galavotti Date: Thu, 31 Aug 2023 15:54:35 +0200 Subject: [PATCH 286/442] [Tests] Add tests for XrdZipArchive and XrdZipOperations --- tests/XrdCl/CMakeLists.txt | 1 + tests/XrdCl/XrdClZip.cc | 103 +++++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 tests/XrdCl/XrdClZip.cc diff --git a/tests/XrdCl/CMakeLists.txt b/tests/XrdCl/CMakeLists.txt index 64748874963..93ce4f630cf 100644 --- a/tests/XrdCl/CMakeLists.txt +++ b/tests/XrdCl/CMakeLists.txt @@ -3,6 +3,7 @@ add_executable(xrdcl-unit-tests XrdClURL.cc XrdClPoller.cc XrdClSocket.cc + XrdClZip.cc XrdClUtilsTest.cc IdentityPlugIn.cc XrdClFileTest.cc diff --git a/tests/XrdCl/XrdClZip.cc b/tests/XrdCl/XrdClZip.cc new file mode 100644 index 00000000000..3715da269e8 --- /dev/null +++ b/tests/XrdCl/XrdClZip.cc @@ -0,0 +1,103 @@ +//------------------------------------------------------------------------------ +// Copyright (c) 2023 by European Organization for Nuclear Research (CERN) +// Author: Angelo Galavotti +//------------------------------------------------------------------------------ +// XRootD is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// XRootD is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with XRootD. If not, see . +//------------------------------------------------------------------------------ + +#include "TestEnv.hh" +#include "Utils.hh" +#include "IdentityPlugIn.hh" + +#include "GTestXrdHelpers.hh" +#include "XrdZip/XrdZipUtils.hh" +#include "XrdCl/XrdClZipArchive.hh" +#include "XrdCl/XrdClZipListHandler.hh" +#include "XrdCl/XrdClZipOperations.hh" + +using namespace XrdClTests; +using namespace XrdCl; + +//------------------------------------------------------------------------------ +// Class declaration +//------------------------------------------------------------------------------ +class ZipTest: public ::testing::Test { + public: + void Init(); + std::string archiveUrl; + std::string testFileUrl; + ZipArchive zip_file; +}; + +void ZipTest::Init(){ + using namespace XrdCl; + Env *testEnv = TestEnv::GetEnv(); + + std::string address; + std::string dataPath; + + EXPECT_TRUE( testEnv->GetString( "MainServerURL", address ) ); + EXPECT_TRUE( testEnv->GetString( "DataPath", dataPath ) ); + + std::string path = dataPath + "/data.zip"; + archiveUrl = address + "/" + path; + std::string testFilePath = dataPath + "/san_martino.txt"; + testFileUrl = address + "/" + testFilePath; +} + +TEST_F(ZipTest, ExtractTest) { + Init(); + uint16_t timeout = 2; + GTEST_ASSERT_XRDST(zip_file.OpenArchive(archiveUrl, OpenFlags::Read, NULL, timeout)); + GTEST_ASSERT_XRDST(zip_file.CloseArchive(NULL, timeout)); +} + +TEST_F(ZipTest, OpenFileTest){ + Init(); + GTEST_ASSERT_XRDST(WaitFor(OpenArchive(zip_file, archiveUrl, OpenFlags::Read))) + GTEST_ASSERT_XRDST(zip_file.OpenFile("paper.txt", OpenFlags::Read)); + // get stat info for the given file + StatInfo* info_out; + GTEST_ASSERT_XRDST(zip_file.Stat("paper.txt", info_out)); + GTEST_ASSERT_XRDST(zip_file.CloseFile()); + GTEST_ASSERT_XRDST_NOTOK(zip_file.OpenFile("gibberish.txt", OpenFlags::Read), errNotFound); + GTEST_ASSERT_XRDST(WaitFor(CloseArchive(zip_file))); +} + +TEST_F(ZipTest, ListFileTest) { + Init(); + GTEST_ASSERT_XRDST(WaitFor(OpenArchive(zip_file, archiveUrl, OpenFlags::Read))); + DirectoryList* dummy_list; + GTEST_ASSERT_XRDST(zip_file.List(dummy_list)); + EXPECT_TRUE(dummy_list != NULL); + GTEST_ASSERT_XRDST(WaitFor(CloseArchive(zip_file))); +} + +TEST_F(ZipTest, GetterTests) { + Init(); + + // Get file + GTEST_ASSERT_XRDST(WaitFor(OpenArchive(zip_file, archiveUrl, OpenFlags::Read))); + File* file = NULL; + file = &(zip_file.GetFile()); + EXPECT_TRUE(file != NULL); + + // Get checksum + uint32_t cksum; + GTEST_ASSERT_XRDST(zip_file.GetCRC32("paper.txt", cksum)); + + // Get offset (i.e. byte position in the archive) + uint64_t offset; + GTEST_ASSERT_XRDST(zip_file.GetOffset("paper.txt", offset)); +} \ No newline at end of file From 2fd4e6773b5527b96ab28e5702c8f5f6185a9df4 Mon Sep 17 00:00:00 2001 From: Angelo Galavotti Date: Tue, 15 Aug 2023 14:20:11 +0200 Subject: [PATCH 287/442] [Tests] Refactor XRootD tests to work with a local cluster This adapts the tests to run with a cluster launched by CMake running only on the local host without the need to use docker. --- tests/CMakeLists.txt | 2 +- tests/XRootD/CMakeLists.txt | 2 + tests/XRootD/cluster/CMakeLists.txt | 40 ++++ tests/XRootD/cluster/configs/xrootd_man1.cfg | 24 ++ tests/XRootD/cluster/configs/xrootd_man2.cfg | 24 ++ .../XRootD/cluster/configs/xrootd_metaman.cfg | 23 ++ tests/XRootD/cluster/configs/xrootd_srv1.cfg | 25 +++ tests/XRootD/cluster/configs/xrootd_srv2.cfg | 25 +++ tests/XRootD/cluster/configs/xrootd_srv3.cfg | 25 +++ tests/XRootD/cluster/configs/xrootd_srv4.cfg | 25 +++ tests/XRootD/cluster/mvdata/data.zip | Bin 0 -> 791722 bytes tests/XRootD/cluster/mvdata/input1.meta4 | 8 + tests/XRootD/cluster/mvdata/input1.metalink | 11 + tests/XRootD/cluster/mvdata/input2.meta4 | 9 + tests/XRootD/cluster/mvdata/input2.metalink | 12 + tests/XRootD/cluster/mvdata/input3.meta4 | 9 + tests/XRootD/cluster/mvdata/input3.metalink | 14 ++ tests/XRootD/cluster/mvdata/input4.meta4 | 10 + tests/XRootD/cluster/mvdata/input4.metalink | 15 ++ tests/XRootD/cluster/mvdata/input5.meta4 | 11 + tests/XRootD/cluster/mvdata/input5.metalink | 16 ++ tests/XRootD/cluster/mvdata/large.zip | Bin 0 -> 3256280 bytes tests/XRootD/cluster/mvdata/mlFileTest1.meta4 | 7 + tests/XRootD/cluster/mvdata/mlFileTest2.meta4 | 8 + tests/XRootD/cluster/mvdata/mlFileTest3.meta4 | 8 + tests/XRootD/cluster/mvdata/mlFileTest4.meta4 | 8 + tests/XRootD/cluster/mvdata/mlTpcTest.meta4 | 9 + tests/XRootD/cluster/mvdata/mlZipTest.meta4 | 9 + tests/XRootD/cluster/setup.sh | 198 ++++++++++++++++ tests/XRootD/cluster/smoketest-clustered.sh | 140 ++++++++++++ tests/XrdCl/CMakeLists.txt | 43 +++- tests/XrdCl/XrdClFile.cc | 38 ++++ tests/XrdCl/XrdClFileCopyTest.cc | 65 ++++-- tests/XrdCl/XrdClFileSystemTest.cc | 31 ++- tests/XrdCl/XrdClFileTest.cc | 211 ++++++++++++------ tests/XrdCl/XrdClLocalFileHandlerTest.cc | 118 +++++----- tests/XrdCl/XrdClOperationsWorkflowTest.cc | 46 ++-- tests/XrdCl/XrdClPostMasterTest.cc | 31 +-- tests/common/TestEnv.cc | 18 +- 39 files changed, 1117 insertions(+), 201 deletions(-) create mode 100644 tests/XRootD/cluster/CMakeLists.txt create mode 100644 tests/XRootD/cluster/configs/xrootd_man1.cfg create mode 100644 tests/XRootD/cluster/configs/xrootd_man2.cfg create mode 100644 tests/XRootD/cluster/configs/xrootd_metaman.cfg create mode 100644 tests/XRootD/cluster/configs/xrootd_srv1.cfg create mode 100644 tests/XRootD/cluster/configs/xrootd_srv2.cfg create mode 100644 tests/XRootD/cluster/configs/xrootd_srv3.cfg create mode 100644 tests/XRootD/cluster/configs/xrootd_srv4.cfg create mode 100644 tests/XRootD/cluster/mvdata/data.zip create mode 100644 tests/XRootD/cluster/mvdata/input1.meta4 create mode 100644 tests/XRootD/cluster/mvdata/input1.metalink create mode 100644 tests/XRootD/cluster/mvdata/input2.meta4 create mode 100644 tests/XRootD/cluster/mvdata/input2.metalink create mode 100644 tests/XRootD/cluster/mvdata/input3.meta4 create mode 100644 tests/XRootD/cluster/mvdata/input3.metalink create mode 100644 tests/XRootD/cluster/mvdata/input4.meta4 create mode 100644 tests/XRootD/cluster/mvdata/input4.metalink create mode 100644 tests/XRootD/cluster/mvdata/input5.meta4 create mode 100644 tests/XRootD/cluster/mvdata/input5.metalink create mode 100644 tests/XRootD/cluster/mvdata/large.zip create mode 100644 tests/XRootD/cluster/mvdata/mlFileTest1.meta4 create mode 100644 tests/XRootD/cluster/mvdata/mlFileTest2.meta4 create mode 100644 tests/XRootD/cluster/mvdata/mlFileTest3.meta4 create mode 100644 tests/XRootD/cluster/mvdata/mlFileTest4.meta4 create mode 100644 tests/XRootD/cluster/mvdata/mlTpcTest.meta4 create mode 100644 tests/XRootD/cluster/mvdata/mlZipTest.meta4 create mode 100755 tests/XRootD/cluster/setup.sh create mode 100755 tests/XRootD/cluster/smoketest-clustered.sh create mode 100644 tests/XrdCl/XrdClFile.cc diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 03afc193849..c2712245cff 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -15,4 +15,4 @@ if( BUILD_CEPH ) add_subdirectory( XrdCephTests ) endif() -add_subdirectory(XRootD) +add_subdirectory( XRootD ) diff --git a/tests/XRootD/CMakeLists.txt b/tests/XRootD/CMakeLists.txt index 5593ab4e739..4439cee9f55 100644 --- a/tests/XRootD/CMakeLists.txt +++ b/tests/XRootD/CMakeLists.txt @@ -32,3 +32,5 @@ add_test(NAME XRootD::smoke-test set_tests_properties(XRootD::smoke-test PROPERTIES ENVIRONMENT "${XRDENV}" FIXTURES_REQUIRED XRootD) + +add_subdirectory(cluster) diff --git a/tests/XRootD/cluster/CMakeLists.txt b/tests/XRootD/cluster/CMakeLists.txt new file mode 100644 index 00000000000..aaa5fe3a4ed --- /dev/null +++ b/tests/XRootD/cluster/CMakeLists.txt @@ -0,0 +1,40 @@ +if(XRDCL_ONLY) + return() +endif() + +execute_process(COMMAND id -u OUTPUT_VARIABLE UID OUTPUT_STRIP_TRAILING_WHITESPACE) + +# ensure that we're not root +if (UID EQUAL 0) + return() +endif() + +# find executables +list(APPEND XRDENV "XRDCP=$") +list(APPEND XRDENV "XRDFS=$") +list(APPEND XRDENV "CRC32C=$") +list(APPEND XRDENV "ADLER32=$") +list(APPEND XRDENV "XROOTD=$") +list(APPEND XRDENV "CMSD=$") + +set(SRVNAMES "metaman" "man1" "man2" "srv1" "srv2" "srv3" "srv4") + +foreach(i ${SRVNAMES}) + configure_file("configs/xrootd_${i}.cfg" "configs/xrootd_${i}.cfg" @ONLY) +endforeach() + + + +# Start the smoke test for the cluster +add_test(NAME XRootD::start::cluster + COMMAND sh -c "cp -r ${CMAKE_CURRENT_SOURCE_DIR}/mvdata ${CMAKE_CURRENT_BINARY_DIR} && \ + ${CMAKE_CURRENT_SOURCE_DIR}/setup.sh start" ) +set_tests_properties(XRootD::start::cluster PROPERTIES ENVIRONMENT "${XRDENV}" FIXTURES_SETUP XRootD_Cluster) + +add_test(NAME XRootD::smoke-test-cluster + COMMAND sh -c "${CMAKE_CURRENT_SOURCE_DIR}/smoketest-clustered.sh" ) +set_tests_properties(XRootD::smoke-test-cluster PROPERTIES ENVIRONMENT "${XRDENV}" FIXTURES_REQUIRED XRootD_Cluster) + +add_test(NAME XRootD::stop::cluster + COMMAND sh -c "${CMAKE_CURRENT_SOURCE_DIR}/setup.sh stop" ) +set_tests_properties(XRootD::stop::cluster PROPERTIES ENVIRONMENT "${XRDENV}" FIXTURES_CLEANUP XRootD_Cluster) diff --git a/tests/XRootD/cluster/configs/xrootd_man1.cfg b/tests/XRootD/cluster/configs/xrootd_man1.cfg new file mode 100644 index 00000000000..bb3c6464e6f --- /dev/null +++ b/tests/XRootD/cluster/configs/xrootd_man1.cfg @@ -0,0 +1,24 @@ +# This minimal configuration file starts a standalone server +# that exports the data directory as / without authentication. +cms.delay startup 10 +cms.space linger 0 recalc 15 min 2% 1g 5% 2g + +xrd.port 10941 if exec xrootd +xrd.port 20941 if exec cmsd + +all.export / + +all.role manager +all.manager meta localhost:20940 +all.manager localhost:20941 + +oss.localroot @CMAKE_CURRENT_BINARY_DIR@/xrd-data/man1 +all.adminpath @CMAKE_CURRENT_BINARY_DIR@/xrd-adm/man1 + +all.sitename XRootDman1 +ofs.ckslib zcrc32 @CMAKE_BINARY_DIR@/src/libXrdCksCalczcrc32.so +xrootd.chksum zcrc32 chkcgi adler32 crc32c + +xrd.trace all + +xrd.maxfd strict 64k \ No newline at end of file diff --git a/tests/XRootD/cluster/configs/xrootd_man2.cfg b/tests/XRootD/cluster/configs/xrootd_man2.cfg new file mode 100644 index 00000000000..35852a54fbe --- /dev/null +++ b/tests/XRootD/cluster/configs/xrootd_man2.cfg @@ -0,0 +1,24 @@ +# This minimal configuration file starts a standalone server +# that exports the data directory as / without authentication. +cms.delay startup 10 +cms.space linger 0 recalc 15 min 2% 1g 5% 2g + +xrd.port 10942 if exec xrootd +xrd.port 20942 if exec cmsd + +all.export / + +all.role manager +all.manager meta localhost:20940 +all.manager localhost:20942 + +oss.localroot @CMAKE_CURRENT_BINARY_DIR@/xrd-data/man2 +all.adminpath @CMAKE_CURRENT_BINARY_DIR@/xrd-adm/man2 + +all.sitename XRootDman2 +ofs.ckslib zcrc32 @CMAKE_BINARY_DIR@/src/libXrdCksCalczcrc32.so +xrootd.chksum zcrc32 chkcgi adler32 crc32c + +xrd.trace all + +xrd.maxfd strict 64k \ No newline at end of file diff --git a/tests/XRootD/cluster/configs/xrootd_metaman.cfg b/tests/XRootD/cluster/configs/xrootd_metaman.cfg new file mode 100644 index 00000000000..00c2824e440 --- /dev/null +++ b/tests/XRootD/cluster/configs/xrootd_metaman.cfg @@ -0,0 +1,23 @@ +# This minimal configuration file starts a standalone server +# that exports the data directory as / without authentication. +cms.delay startup 10 +cms.space linger 0 recalc 15 min 2% 1g 5% 2g + +xrd.port 10940 if exec xrootd +xrd.port 20940 if exec cmsd + +all.export / + +all.role meta manager +all.manager meta localhost:20940 + +oss.localroot @CMAKE_CURRENT_BINARY_DIR@/xrd-data/metaman +all.adminpath @CMAKE_CURRENT_BINARY_DIR@/xrd-adm/metaman + +all.sitename XRootDmetaman +ofs.ckslib zcrc32 @CMAKE_BINARY_DIR@/src/libXrdCksCalczcrc32.so +xrootd.chksum zcrc32 chkcgi adler32 crc32c + +xrd.trace all + +xrd.maxfd strict 64k \ No newline at end of file diff --git a/tests/XRootD/cluster/configs/xrootd_srv1.cfg b/tests/XRootD/cluster/configs/xrootd_srv1.cfg new file mode 100644 index 00000000000..918c7850ba0 --- /dev/null +++ b/tests/XRootD/cluster/configs/xrootd_srv1.cfg @@ -0,0 +1,25 @@ +# This minimal configuration file starts a standalone server +# that exports the data directory as / without authentication. +cms.delay startup 10 +cms.space linger 0 recalc 15 min 2% 1g 5% 2g + +xrd.port 10943 + +all.export / + +all.role server +all.manager localhost:20941 + +oss.localroot @CMAKE_CURRENT_BINARY_DIR@/xrd-data/srv1 +all.adminpath @CMAKE_CURRENT_BINARY_DIR@/xrd-adm/srv1 + +all.sitename XRootDsrv1 +ofs.ckslib zcrc32 @CMAKE_BINARY_DIR@/src/libXrdCksCalczcrc32.so +xrootd.chksum zcrc32 chkcgi adler32 crc32c + +ofs.tpc ttl 60 60 xfr 9 pgm @CMAKE_BINARY_DIR@/src/XrdCl/xrdcp --server +ofs.chkpnt enable + +xrd.trace all + +xrd.maxfd strict 64k \ No newline at end of file diff --git a/tests/XRootD/cluster/configs/xrootd_srv2.cfg b/tests/XRootD/cluster/configs/xrootd_srv2.cfg new file mode 100644 index 00000000000..870b8800819 --- /dev/null +++ b/tests/XRootD/cluster/configs/xrootd_srv2.cfg @@ -0,0 +1,25 @@ +# This minimal configuration file starts a standalone server +# that exports the data directory as / without authentication. +cms.delay startup 10 +cms.space linger 0 recalc 15 min 2% 1g 5% 2g + +xrd.port 10944 + +all.export / + +all.role server +all.manager localhost:20941 + +oss.localroot @CMAKE_CURRENT_BINARY_DIR@/xrd-data/srv2 +all.adminpath @CMAKE_CURRENT_BINARY_DIR@/xrd-adm/srv2 + +all.sitename XRootDsrv2 +ofs.ckslib zcrc32 @CMAKE_BINARY_DIR@/src/libXrdCksCalczcrc32.so +xrootd.chksum zcrc32 chkcgi adler32 crc32c + +ofs.tpc ttl 60 60 xfr 9 pgm @CMAKE_BINARY_DIR@/src/XrdCl/xrdcp --server +ofs.chkpnt enable + +xrd.trace all + +xrd.maxfd strict 64k \ No newline at end of file diff --git a/tests/XRootD/cluster/configs/xrootd_srv3.cfg b/tests/XRootD/cluster/configs/xrootd_srv3.cfg new file mode 100644 index 00000000000..51ebddcd944 --- /dev/null +++ b/tests/XRootD/cluster/configs/xrootd_srv3.cfg @@ -0,0 +1,25 @@ +# This minimal configuration file starts a standalone server +# that exports the data directory as / without authentication. +cms.delay startup 10 +cms.space linger 0 recalc 15 min 2% 1g 5% 2g + +xrd.port 10945 + +all.export / + +all.role server +all.manager localhost:20942 + +oss.localroot @CMAKE_CURRENT_BINARY_DIR@/xrd-data/srv3 +all.adminpath @CMAKE_CURRENT_BINARY_DIR@/xrd-adm/srv3 + +all.sitename XRootDsrv3 +ofs.ckslib zcrc32 @CMAKE_BINARY_DIR@/src/libXrdCksCalczcrc32.so +xrootd.chksum zcrc32 chkcgi adler32 crc32c + +ofs.tpc ttl 60 60 xfr 9 pgm @CMAKE_BINARY_DIR@/src/XrdCl/xrdcp --server +ofs.chkpnt enable + +xrd.trace all + +xrd.maxfd strict 64k \ No newline at end of file diff --git a/tests/XRootD/cluster/configs/xrootd_srv4.cfg b/tests/XRootD/cluster/configs/xrootd_srv4.cfg new file mode 100644 index 00000000000..51142c889bf --- /dev/null +++ b/tests/XRootD/cluster/configs/xrootd_srv4.cfg @@ -0,0 +1,25 @@ +# This minimal configuration file starts a standalone server +# that exports the data directory as / without authentication. +cms.delay startup 10 +cms.space linger 0 recalc 15 min 2% 1g 5% 2g + +xrd.port 10946 + +all.export / + +all.role server +all.manager localhost:20942 + +oss.localroot @CMAKE_CURRENT_BINARY_DIR@/xrd-data/srv4 +all.adminpath @CMAKE_CURRENT_BINARY_DIR@/xrd-adm/srv4 + +all.sitename XRootDsrv4 +ofs.ckslib zcrc32 @CMAKE_BINARY_DIR@/src/libXrdCksCalczcrc32.so +xrootd.chksum zcrc32 chkcgi adler32 crc32c + +ofs.tpc ttl 60 60 xfr 9 pgm @CMAKE_BINARY_DIR@/src/XrdCl/xrdcp --server +ofs.chkpnt enable + +xrd.trace all + +xrd.maxfd strict 64k \ No newline at end of file diff --git a/tests/XRootD/cluster/mvdata/data.zip b/tests/XRootD/cluster/mvdata/data.zip new file mode 100644 index 0000000000000000000000000000000000000000..b5d2fb904f11f47ce4047922b614f480bcdd9dd0 GIT binary patch literal 791722 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�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�_(cQ_r_csR^64*SI;l&~oARm;N( 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 AMgRZ+ literal 0 HcmV?d00001 diff --git a/tests/XRootD/cluster/mvdata/input1.meta4 b/tests/XRootD/cluster/mvdata/input1.meta4 new file mode 100644 index 00000000000..1852892387d --- /dev/null +++ b/tests/XRootD/cluster/mvdata/input1.meta4 @@ -0,0 +1,8 @@ + + + + A_file:output.dat + root://localhost:10943//data/b74d025e-06d6-43e8-91e1-a862feb03c84.dat + + + diff --git a/tests/XRootD/cluster/mvdata/input1.metalink b/tests/XRootD/cluster/mvdata/input1.metalink new file mode 100644 index 00000000000..cd80ca4a127 --- /dev/null +++ b/tests/XRootD/cluster/mvdata/input1.metalink @@ -0,0 +1,11 @@ + + + + + + root://localhost:10943//data/b74d025e-06d6-43e8-91e1-a862feb03c84.dat + + + + + diff --git a/tests/XRootD/cluster/mvdata/input2.meta4 b/tests/XRootD/cluster/mvdata/input2.meta4 new file mode 100644 index 00000000000..1bacadecef5 --- /dev/null +++ b/tests/XRootD/cluster/mvdata/input2.meta4 @@ -0,0 +1,9 @@ + + + + A_file:output.dat + root://localhost:10944//data/b74d025e-06d6-43e8-91e1-a862feb03c84.dat + root://localhost:10943//data/b74d025e-06d6-43e8-91e1-a862feb03c84.dat + + + diff --git a/tests/XRootD/cluster/mvdata/input2.metalink b/tests/XRootD/cluster/mvdata/input2.metalink new file mode 100644 index 00000000000..d4e2dae0cf0 --- /dev/null +++ b/tests/XRootD/cluster/mvdata/input2.metalink @@ -0,0 +1,12 @@ + + + + + + root://localhost:10944//data/b74d025e-06d6-43e8-91e1-a862feb03c84.dat + root://localhost:10943//data/b74d025e-06d6-43e8-91e1-a862feb03c84.dat + + + + + diff --git a/tests/XRootD/cluster/mvdata/input3.meta4 b/tests/XRootD/cluster/mvdata/input3.meta4 new file mode 100644 index 00000000000..639489f3672 --- /dev/null +++ b/tests/XRootD/cluster/mvdata/input3.meta4 @@ -0,0 +1,9 @@ + + + + A_file:output.dat + 40e5fdb0 + root://localhost:10943//data/b74d025e-06d6-43e8-91e1-a862feb03c84.dat + + + diff --git a/tests/XRootD/cluster/mvdata/input3.metalink b/tests/XRootD/cluster/mvdata/input3.metalink new file mode 100644 index 00000000000..8067d41a24d --- /dev/null +++ b/tests/XRootD/cluster/mvdata/input3.metalink @@ -0,0 +1,14 @@ + + + + + + 40e5fdb0 + + + root://localhost:10943//data/b74d025e-06d6-43e8-91e1-a862feb03c84.dat + + + + + diff --git a/tests/XRootD/cluster/mvdata/input4.meta4 b/tests/XRootD/cluster/mvdata/input4.meta4 new file mode 100644 index 00000000000..68e068a520e --- /dev/null +++ b/tests/XRootD/cluster/mvdata/input4.meta4 @@ -0,0 +1,10 @@ + + + + A_file:output.dat + 40e5fdb0 + root://localhost:10944//data/1db882c8-8cd6-4df1-941f-ce669bad3458.dat + root://localhost:10943//data/b74d025e-06d6-43e8-91e1-a862feb03c84.dat + + + diff --git a/tests/XRootD/cluster/mvdata/input4.metalink b/tests/XRootD/cluster/mvdata/input4.metalink new file mode 100644 index 00000000000..e95cfbd1531 --- /dev/null +++ b/tests/XRootD/cluster/mvdata/input4.metalink @@ -0,0 +1,15 @@ + + + + + + 40e5fdb0 + + + root://localhost:10944//data/1db882c8-8cd6-4df1-941f-ce669bad3458.dat + root://localhost:10943//data/b74d025e-06d6-43e8-91e1-a862feb03c84.dat + + + + + diff --git a/tests/XRootD/cluster/mvdata/input5.meta4 b/tests/XRootD/cluster/mvdata/input5.meta4 new file mode 100644 index 00000000000..30438fd9521 --- /dev/null +++ b/tests/XRootD/cluster/mvdata/input5.meta4 @@ -0,0 +1,11 @@ + + + + A_file:output.dat + f024c8e5 + 851fef8c18d878b31366d1aabeefdead + 40e5fdb0 + root://localhost:10943//data/b74d025e-06d6-43e8-91e1-a862feb03c84.dat + + + diff --git a/tests/XRootD/cluster/mvdata/input5.metalink b/tests/XRootD/cluster/mvdata/input5.metalink new file mode 100644 index 00000000000..e745d2b64fe --- /dev/null +++ b/tests/XRootD/cluster/mvdata/input5.metalink @@ -0,0 +1,16 @@ + + + + + + f024c8e5 + 851fef8c18d878b31366d1aabeefdead + 40e5fdb0 + + + root://localhost:10943//data/b74d025e-06d6-43e8-91e1-a862feb03c84.dat + + + + + diff --git a/tests/XRootD/cluster/mvdata/large.zip b/tests/XRootD/cluster/mvdata/large.zip new file mode 100644 index 0000000000000000000000000000000000000000..c0612aa89c039b5a0b500ae2ae0e0bbb23a98374 GIT binary patch literal 3256280 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�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

`55@Op2x`dg4m1-;tbp2kC;T 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

-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%0AQI4|F5Ii78X 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

|3I_kA=#(=rfP>)#I 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ǾKf4M`#fWl?R2&>MZv5FiX|AmWDWOnmfYn=gQqtlb$*1+%W! z!8m5_xglTt9b24lEbeQB%SW!GttYQMuqd%q9ddU9hEl3eItuV2oG^n^K&wnz>F_Ca z9kCQ;nZ3%e5E95r7#5~EJ1SLsn&rh0#9q6%&BgP&>xTZ`hYF}Bfu;o#lBC$(J%}*b3qK?_8DnrKb;jH$5we z#jn=?%1n%cgvDnZxps^YGx6RV8C>r{$uy(Hccc(Mmjl%(4H)@S3DnoIj)XZ$CFI$u z-Pbfa(eg_z(f*iClYW&RQX}HjaV#Gn2^TZ}eBlF^SbA^MdjLJY8A7p}=9Thp&gYwq zKzX+=LF9w=BAR7R!3w~2wX!Lp4PX(}<4{3=_M+43FjQcl9LOGPV|HqO$Tzr3YYDJV zED?t#sz<|Ao6mx}B1Ti4RAM5n! zWGvD$$;0>@wxlM!*0S+0Z0fC#*kG}Jh{U>PEy0eqZ$6U(9m)TC?FC63kaFk6#xheXD}p=y<+0>&Lh zqspXev0J2Zw@}cX$Jc;i;XU(6KU6YOybDD;+_V6?(_lOEh&cF65D}Niy7}6xW%dC; zC(V%=sIPVhTIz8*5lhT@`-FPR!~MHZ27<{gyaofhIz;Qkc83V6N5hRM zwwB-r+x*=0oLshcMC{MO)b1iPa?=l5#>gD5vq8W5=Ggwrj8;LfM17N8SVb7Eu{DUgpiVMRvAdn0!Ta&G zJs%E;Gj|ls7?cORHr-<%cp2%=GPK!l`~G^5MD z$odyKImg1y6=nVu_w&dx6a~vE1IP_S;IG8a*gIvi+Ry=_X!;v#VBmKlxw?qpJKSW^o#)XM4J$#83=!OisIqtpdOt?5%2-GyzHZ zPwtgrzLvO7J8m#Pj~z)nZi)8?ep1x$$^~>+szqzYS;xmfj7jF#J25|e&MJbqEpiWa z$5cI%n_TIGsxmB^Fsp_w{FV8y&{~0AD%kDkdaqWyVY4VuCrb?6nZ~@iZQU%Xv*_q5 z8T;|(s`Ykj@Z+Ple0uCSBsAy7f-Di~6nev82{c_q$`jBUdm8t++&!ra6}q{|rY>H> z$@_46OCFhH@;(hzWdGPn(O4)WqCj_Is2u~S7ud6Hb_);n6l{}xKVgbo?^ZR#Cl1;+ zr~;pIXu5Ni-3XA2iCaBY+!WF&*94EOM!I@Or=i~4=9ef-Y-{}57A;WaD@;r{Jm?*z zh3UH7+SD>AK-zphTFNPDCqq`&#L=p@D0(;9A}VVj-b-5W)`BmHO*H85M`MTs%3w&0 zX6NQ*{>6Bzcg5eaX`A|vY~TO^Mb$o}(0@-P818Pq`9ujS2o@>p()o_LVh5cUsb)?{ zXJIk`|`4ugsLd^Hi&{ujm^xvScq`Y`|>m@IxCKp2cloHwX zs!B#UGe1t6#|cdulnw7Xs|I<4;nJ>?mMqP`(}>C&9*0I&B?b|CYlW9Gk`P{N$zmGw zl@2DG|49oIq2J|bsgqpC$ODhK;0+Cwm>fujXb6*o1U`bVR{X(@UqU!ELIIsZplsAh zIuXXN#3`vF%<6*5ZnjQWTiBC6p)Z_$C?|>m84nRo#lBQ4Z0ypokZiQT!5N|~x-tnkx+hs#_ zl5CKDoKOAkxErd2HU&=kh1DyhXjxNN(9(m_qT#!j`b(A7*DcIUQ6exN-q`Pei-7M} zr8f}6WmufjLwE;3O81vu4VuY2t*HiR!@a{-&ZJ{XaNRHIJX9jFi^AAwL}){FVSMU2?cGQyLu?SkUyTx8Fw7}+S^Li_q9*?1kywot0=H1ex(f@HJFK{de$089 zuB;tKUnJ&>>=!RnSy(ECB7p?nMEu+R5oRT)YO=#j!7R%}p;_e{_gWUTdWC8jNeD&4 z@NvrSB_*FiK&{ikBq^^M&|Xmnk(gyY;WPE~BKbLiNKrctU%|VFdb5KOL^x}m>L{pw z1yG!KB#_dtxjLmBWXJ=mS7J?EKk5EyXck9G`eFt%>S|xiR=O~gos3)mJN-WC$E&?Qzw^$R zf6fD|I4xiJliC@R%qNU{-nhiTdBILqA8O@J!Csv?ID#lt%?K+&wBrmnhLK*o_e`@g z3<4t$1S73-iM0&rUS!y)zTgDOR%Ev@WDUU2H$bHMTJ+B5pUGrNqKF$R%^$?Su+ZZ57OUlK{5Vhp+AQ21QL7ZXP zCEtengd)p{A!bWTh(a)3S^;_YKN>U7JO(?fuw-=E+0?I|%kFUQXj@I5D%n$@Dnak@|_zVo~T&+sp^Pz4m#m;g8yHQM@0z5(0 zXPQhF@TZg$&9l!x6s#yEnSyafa4QxhVibTmL%I}%M8($aJvASw#v+cSzgFiQYNW1h z4wGr*lX*%d;?XGUVsLwxQ+y{)V|ca|vOE+)MG*877G zBvm6d4bj}qenbEE<|qR4$EL_LLPafnJ4PPq3K`LZ2KpH8Ha>CX)tuV8JyX8F=v!pf zXsj(L5x{SsRmeW6Q#7ktvqKSfkj*QF61q#sy-LwT(Y?eEs&NyIJH%V{Q1L#}%qvx# zjSJBd+j{&dvEkc0N+qcct0kkmy;#r_Sva}ZfL{x5sv8Z<@&o5S zE=*{nyQQW zA}ZkfyqBZM!>g}%R-CP4(Uav`BSc_Yy=Q$~^|0@34?BcyUEV6bZn~pDb~uvv z%sLIoJMiS(AFXY@cdn*U%h%g|Ny+lD-g!mkaGP3W#YwjZLJ=rmhHw70}h@_@xcwpQ#S_!iNonFo;;vq)6cbZLn`v zZbThxIZF4y`8KZpM^s8&D%uQ8?L)6&UR=z1OMnYhtw(eemLA=w7nXyuMy9D{8ya+o-E zzfFr1G@r@P$377`M7Q&{5S6pO%*tO3VnKykx~#4 z(RXnW1O54eSM6RHLYsm`;BEotih&he`BQlK6Z%uM~SM&lXz-{0p%tX5sAo`m}J^62XqZ zrSS9-c1f53jkfVh^9rDm=$Jg=kAp1v@^HSt{%EU7Us1zpWhh^_r?484j0|51M0~$u z7ug`$r7EysYn1BzC~3C!ngtvaaRNIJ)nhBKI7_p+y|b*;FYm6UkV1Yy$C_ymEMi*) zQdPuxgG8BWYYk#(ch0Z07Uwf$Os=!_L!+MTZW0~ zZL*nb;8KoxGuC2rwT!#?eC*F$n~`;H9$dSeC~sW~k+je&r5SNFEYo_^oI-8KKnK@8 z%ZrndfB(kDfg`>`EH9(gxDI{z3i+g@20R~a>Er@?=Tvn35F)^?Rn_LHo46s84q}D5 z-uJ_`c~L?>N$+|}f0pf?G#ATrq$>$zH+xCMr87zT2x0U?HjMfg)iAR6u1}01nPqlh zX;#7h%P3}H=ddWBrFRId9}3{6$$^!uV&ahHQlEwN@?q0y|~l9 zW(c*@DFDfs!r$y%>FX7Jgktr*OKQF6wOcx)NLJ!c+H**cg2F2Xo0qs^-duitATLL$3h;C*3Q@pxdZ{ZPMRe-y&tHiYHo(O6{xD|?le z$pwtSpDVKL`V<1!V6Tsz68dbx;K^zhyh1)*u?<(Ag}?S))oB%oMpT%(3V-~R2yK** zc>*z5?Bx7`nDDp$inY=nDrXrJ;mt$H<$FfD(Uo^RFny&G9fy~a&5^Dp#DyMrKB3ZSs0B<-uB`J3_@(s zS_wBm5BM${dwBr)alIbznu6F3^_cJvPL=*alVi1-RPwwhGP9CVpx~p2R!}B8YwlX@ zFBV^n!aY;~(%!gTZK5T%K_KLU)T2TvhKU#(dw*CZ^A)uwOvoMWg&01G+sxP(zhbn@ zfdy|!J9!q@p!sB=4hu)|>-)UoE+U(#wY}t=rEh(q2~rvYYKXHwSyI#>8lI%^mY}eA z@tAP&J-Wh&b18!6E-=`wwisN@Aj>MZE~Ubcjt zIp=RoGmJ4d2L5b+1&cx3kuBT9*+%?|jtfN_9Ohs5+oCW^giy$h+WJ zU~BT0urt^8XHICDZ>ibFLqt7#vD(Mxekh-%X6ooR`(hBiR4J?^uHo6HM>ObpFKA=L9F`E2Iv8v@`= z#U-(|cCtn_t@ZQu&t7`^l+$W1bxgDKy({DE^kZfLFj!Sp=N6ocatz~b6n}ik8?^{L zJ`Z|$(e+F#7J%K?Y5O8VXo6Z=-JoA3aCT+byL+x|34pIbZNsBB%Lhi0Ums`b7}k^oyQ1Mo~_S ziX5uvZ@YkOWVDdEic{4nR*6X{cyG(+^f7T!Tzx#I?E9v!o~YhDFo5%A2#c-}uVIe( z=+Un_Yd3i8aW6i#EIrG$D|xxQMadn;B`d?jnk`MJLjMyhlNAb?UTf9sCZzsc^+bek zx^_2%;it6CwiUHw`tqe3Y}$>@0ihnNM9dGdI>h`AG$)s35#sEgD0|lFlaW0Wt#EU_ z3On4;1^y(QDV%2^V~Dn5c4PLWNpvgLhkQHx@dE&Ifp{C`83+-j^Uk1SjE zZkMa*_@2vDqGR!+WNsomNDH6r?S(=l|9Y z3bOXMw0FjYuaX@3CG1CFzt`%Lf2SVm>Y0AEKIN+pb&=a&t4uQcBKTd*jz_P*G#F>A zcoYV{A#D=^YDFzjxrS^%YZVof;=It}XSV}QFohTujg6Cu@>j*;1Ajln9hC;$o0> zy?eDYANyfa5rU>ML@P-nM|@{{L`wz|S|&>pC+1Y-qq48r ztre18LTBJGh{ET{Zqun&1`W6>awhB1h&5zOj-ff&3LH;*w*}5fTR+L+cyuD~_q4yT zoi87x_ z^M7jexFCUoFd9>%vqYeP`APhO>M`9KBikg+(!3I0=p)q;c}>r92Bd^*_~{VZu=JL6 zUA@);$KoML%I8i8H5Mzfpf8xDoVA-)hRhnGC=)}u{t~^ycd*EC#dW@uP|sKp+qSB( zOgK4hp=IQEKEzP5r48v#n)3EAgE2`_y}jri2Qv6g86qFJq>s|NTa1A*oD7RA={yrD zZ2t2XDjKQs>HM5M{Bv{AgLEbDXwqJ_Lp*(r?%LG-ju`>EN}8^5u!VT~5OyB{)TEwN z($hZS1B3$QC$JN3Oxg0F=Hkih4z1G+#Z>IKX*lh#m%OTWQ29kYpR#<#@q`+(Yj5gJ z*2vY&CZsf9^xseQAe2d1N|5TjF_2?YR8baZyX**SFV3R8EO1r0Nn1~>V9BK8%=$WE z_xjWCL(DYa){Y>UQyHW_S1H1Wq}cU4HS=&W<2N*Z29$$rM>&RTR6F%(SnPKRrv#2^&_rtsA_TsHr;{5d8&RkHX{0RpTS+jt z)?{kgrpt1MQK8;w5kkGvNf$f=T9%V3~bXCHNyt*3pff1`}Qyy8Tg0|AJFjz4L!IJ>s z18(N3+D%Nu{57!@JsUHZTPm)@%_Q1B4~$o3^rd))kVF&7>-W-ldt`TZO)d%Unx8fY zLDgg)>{Vx}St+N0u}PZ#hb#CgTaw3Z#jdC9w~q6LBcD|j2JpJQJGZte%pe}K+NU2yxVnHKRp$qJz z!roX|_>bBw&7-W?>?Np&@dN(EcWOq+!i9M?oV>X{t!^yhnW~@bDIO|!ve*_DbC;KE zUOLYYtX3G^BvkP%y*aDN(C(4$NuMvcxZZ%AObw8{B9g3cp_ZE+f#?J3V41Yjf61dS zVv*b!6_jMh?a?UruxmzN;Uf>5G4+<2j$-@2aY!lkj=I`BejXEW|b8 zE#!Z(B)^acPPTjrfphd5n#)Q~;-!DLyhGkqr@WL>VI84yZ~yilHE0Y5WQokgVB zkNV;DSNkn{FSN|%oLEK(KGw?5zN%H$gs!%PToBu7p%Kus$s%-sY8GZWKe{{bmi=`02Z?4$=#K*|lqiONvH`q6;6Zr*$ zE4KbuOy!ULz^3fi?M1?f(!swr-&daUL!AZCP`t>Mo%da~W1A$*Hg7#bgv?KA>hs}0 zZdR8=F1qjXVU<3-Yxli2vG-rv?Ap-p-rCE5uZQ3BGpd2yiNBIOst0uwzDsGq!!~cp_{(H4=f#P3L-XHpX^<0}-u-sRIdR;!iK&yX7$iPfu z)avq%QcJ&CK59zC^3%^%)ArrU)lX07?egOf*=U-o&B{}IsAeH}jqk%w2;8RHWDb=U z)Lq@Kyufeuv08iZcs{Lt@tGpK5SB|3o2BTuGS<9OD>wS3+S5cVPjxmrI;G+%hxC{Q z?D6W{t!&1}YP(rov6j{G{Q9n18_^H-eos4be*F*my;hd^pNY@$Qd6Wa^~%!!l%Dc_ zUb}sEQ`(jL{40OHHlJf!o1O2{NH4ooo#W~(f6Tr^SGQ}!JRQ$ty*8lls;BMwX>CHE z);CcW6Oc12cly$tr}bgg`}I4G^n{f=4NvR4am+r|%Dz659^b5K&M)=)cAV<`a%-xA zOXI$EbmNNH=%=+U_=ystLt2}GU$UdPUwhLZtEyQW;NNHlR`#22=Qq5x#oJEx${F~j z-|>JUO2}VW6NPOZViqQgJU00H>}|CFTU5}&SY=%%sW~l1n~+|U%Fxck5}#AkCd^m< zS3bXE1#@s9339*RYaIi0XW)q68N7EqT5{N6d!Zk}BvlHD2^}V3JGfYh+*LRGZ|dPM zwr0Ow{agh{;nK}C{C#KHk7bnQiY4+Pi`mr&{+)W+Ov{7zrEc=j?;^c@Wn?pS{T@ZT zN%=hG2l(oTn!4ZBN`ns0sfy7iTG*NAldyX{WfUwn=7pL9@Q?KfyoN9;_oKy)uJoA~ zgt@N@sp>YqqmNLoavS@@!E5O<$~+1a$SFIoY`$*mCmuz z1umK=f};98n0Uf4)}CAcai=eFZot>KYW|W>aHn^sSY9*E;t1iUd@FbcdDmViLP{7pYWj|DHjLOSY`UNL|GW$c3G3$eqBogl1Jh}l)9bT{c zdaY2L^N5R@+_wmsE_Mz4gP`Dy8H*<+_MLMEgZcM!5G6Y~M@DU0)6RA6DR&ITefCg) znX+TOwcu7t8b&;}RoQbx6N4s(#96}#73cn?+Eq!R1uX4%}URe2NldbnOKE0qN(mSve-b#e+KL`hjNco5Q)a-<{=VV%&0P@%7m zIy3UPdFY+BmXVkQ&JI1CSKeb@0}N3zzqaieLkS-@U%*U+IJV3qBDA4i6FB9C8MtlnVXfZroVO9T1I7)`++&5)Cd5E8Byf7|@8;#a@wr*C zO?7v!n%T3ZZKq5#wSsATt1Ipgp$K&1cODBl>DB|$;HMpx%@yI`z?F@uysP7CT`$DNzAbL{ zA$56W_66s?jcmGFp+490MyuUa!e{+8CwNn;qXj8bUa4wGjf`zLx2LpX^%UHVgbK}B;e3j*vAqb(2lyW75vr*k^Y(+eUv*D2Ay=T; zrG#)}{iL(wzPsF{ZhuBxLR6bFA4VhlB>lL2CL&wJ za?w@{@oH3QOVS#B#!IIhGoXtD)WH?YzZci2+tn21XpNHy-6T?qh)Ic3*p8bIRU0eX z_yRp1G51}7-m+Fi)lJi%_czadt=%~AB07l`zNOj!y}#Uw;awGtXgsL0uSa6sWPRP& zK3}*S{gH3tZg+7B@lysUzqa=?mWV(Z30_mem2${~)Tt=B?Tj-E=j? zMrs8L&S){^b?1>67p~EsmBal?rOTbypSrK-`lQs#s~ZN+%`7PK59)0-3ce20bLr1K ze=mt!%LJ-2>Opg}?|H#r=%1m1nKgYL1-|195Z~ts%X@OK4S^tN#`B=~G=mS3!JO|w z1D5kKe?v`(xhC|AziUuMbqwY&|KG{zn~!2RD1*Uybl9OGU71CfXM>wKy?_jQ?nE)s zD1Q6Of?_@~9W$JtnI!_iHFAUV_(l=(ORAm}%qURCg}V`nK=ZV38->=@9gmrD#`n;l zWYUsnlHaf){)a%!z`eN-dE^t>$Ra0B=i$^N94gXPx4=4BS-Z!~s~rB857?TOMxzp) zY}ei&tMN=GidkOS)~dTsejD4d4E-d*VcV>EakJ%^?Hh=~YlOP}Yt*drRDIL?VXFQ} z!ygoI!UMmn$0M@i4s_g5DlMgEbsw{}d8)b^whM+DZdy)?Q!)g8K5Ph7>#o($f1-Y* zWJPz6XKSRJuRUdznf9w!nDS_#@JNk#8$-!yn7lprxE#uk$ z7Pw$tsoD97;#KjEe@j{DxIIoE5qN?>u{d&+YhI}=D$vWfx}FH7DV=vBns)1+;hctP zDq}%#II6ZnPugOdjHq0>kquHl)OJ5IikOmz3JoET9A&%fPnX&L3sG-mVvLE~8%M2d zgu9#1q1^;N1`_7a4D35}$a(6hTFk^Sm#CR?{xQ910l1dMtDOP;nq^R>4pQk0ZA@6o z1X7tSpx!#C+iD;BF+tHtCrO;vEFfn@-M{y>%5JyL#@U+?i|ESRWZ5TH99q_H)uo7y zJ@G^tg0%gZaKLJH3CS8ep?U&;}9Gn?Ti_jqQrMZ^=C5Wjz;kkXq!W zhVqMOU8l3F+O6&L?#%Mzfv;^#pui zu>sSsNv84QqWjfdsQazli?aJ=Pj0v_08U&*bTz)k#7aZ7U8%c%-%*u_2 zcC{q3bX?Z@ch7uNH_v3(e~yKL{~K{^BTiWd#YjEf62eOvX3p)GEup12s!);qLjj`mqs=^3w4Po z5;8+rjg*GVg5bUCIeNfL3Yis+%90L6uRdXD)LJWAZwm=n!$>@UJ4c8~``+6|r%PbC z(8lXVZ|a>({6xA1;vqhhi)|2z3U5J^*fiLZ@S(9hsD!+%-==8ue8NTa6dil;>h!T< z^RBog|HCDhHXyG5&3#l?FEl2&c57GVA}3V(DzJi$RvowdPTI3RI`_2G?r#3IayJgf zpli-oxJ56C1+ev(v4T7ff~0D*iZWrJG&FD3{iJbMiKtU|P_+!WJ!mZfXKS|IG-f(5 zf6P-}CWP~mqE=q8ER0SJKeUVoVqPk&s*z}FUH}>yb+4|4K{0oHHvgSt3D_YG`aLXT ze(Yq>wNWVFvGL7lPCnhtJwWUD+9@?j7o(g`A9NmP#{`}zos_`V>Q$HKT9?va$N~Ts zAS!_MN_y|c=@INPaRz`>f7N5J+dxw(vt}CR7)Xo)JP#yvRE7~SuIRJ3*Cu8Al@Vht zK_9d!X>t}VBi}9r$HNxGz9St{-V`WMET_%=v z$VLbeSKo7uX~9`77IshYnj_fG>{n<>-5SNK;Fi}D$m`bClKOA-@@tq%AxKYN2Fw@q zH|3DBCk-l?!IQX<0M^zWXm-5vo5L~)6i^eLlv-qPZwf^u^FoE?$-^=pkCA|np$aXz zDeuI;b%sbsV`JPm5XUf#^d!-?z!R1y^7x?$^P@~2-73jKfPOwqf%UZB-38$KLO`dM*e^$+Wn(Ai7|YN7Yn>DQ$*%j_H!(k zKg4hOmHPwsUDhFhi_zqB3gXD+=6X@U#O}`9I>I$T(9eF)`lUoLz>X2Udv@*_K)W!z zXgz>)Byb$tV7JBS(;p}58ajH!AqZeOd)h0WkrY|8=#NRg7#JH0=2a%T>x!meCJEy7 z3X(aho07O;%dxlKy3*)nfym+Q`v>S>KkqDflGCt4lft@qCj1u8$1*HfxEvx{xvSUP zPDcg7IltpJacpR?Xgk`qe4|_6qah11m!~n^g%wtp$=yENrAv?Ss`Q*`H{aU2s|nM`a>xHZeeq8o~1fYBkA zh~KfM?y$*?G^2l=EO6f?P$mnSl}RJD@fL3fLddJmZ@1Fti(kFOh_0FVPz)5X&%2ud zPIag3pM=$1U?~Ojr!7iY*fUtkw_JH%8a4!MR&Y89>$+u-FHIgDZVFc3N(N@qUTO?> zb?HhE&AtpV*jCSkxbRaudoA`1D=%eeLMhUMpmO8{8`zc}G%Z!ATUi{X{Ng>B=S<@) zL@_4@3wyKF`S^?)Ch$~B3y8vH*e13PIP0O*W?T3u(ln9q-FGIGegAHKfO$t8Yis>h z#}@F$@5P~}%1Bq02AxwCtm(DK1-)2ED=Ez?GGlmaP(fkR#nVQsXiSCt;q5Z!XZ=b< z^+Bzj<<#GW9keI0%lv56{z)yRua4oY2-9g9v%AuPU~Z2wxQ*h9pQ|?Z69t5ypkaZzGo-75DuMy?4*67zefWEpxb@`{O zB?ZMbQatv?5eji|z-ZX$7PEf#a2g|Lz(*0YrwRo);c+2sd+Gwax6rns$QdNO~1IIAbjVOkk%nSBL}a2yGNS5y%9 zfwD5_VUv5;5}KQ(Fc+u9aQq1Ca?*YQu)HR^H#gsZ8Hxm-07*c$zfsnO&+flKM-`t* z|5o84u=HVIQ<2YptM>g#_5aoI1l50BMYeE5CdH%3;eKSmcxIW_4ZC+t=Lb(q+KyTBO4k$y@RL7}#m zg1TI(V%)f6v9jf{Jz<_(wvCqO{hD)T*zgWioq4xH0JhS03}y zz@RrIbAC)tksEi2%-q%c*zourj^%5t71H%!=5?=WwzM>G?AM}iq4)BLVt3KIOFf64 zZBoHnrD}H!EhWdPn$B7c^ZR-Ks0G~|SNSRc>KwTex+9wu<9#N_RDdcF@s%F2-s;yK zTOSPIartuUY3eKF=eZSRquH2f739?Vz~e2lvrlnIyNT1jSKwu{K#Ru26%DF~ zZ$=VYyw*y|63$Z#BwEwPkW^bEvDzz|fZb!MmYw=lZtus?QIfY>HMU|Q03b*kYz!XD z(#6LDh9U;&(bS>DI7W!q+lb8%gU$G5kw$?Df`cNdqpA0_P;7oC4ALf;pS0|aQ3YOZ z0k2r78Pp4y2R_`+q8t~OEWkNfWSoljAhY4j^6j9OZ$x)ddjC^ToSF%LpmSf`VrEh}a8~&{?S7Y?+c}*1MS; z5-Mo+Z}goGY(KSEdj8s*Xc^TT-16C#v%nO+z%xV&7X2GCr>CW$dwg5MF6`Po#2m}1@3kNMTIkiZhQ zQJ}UWGB32KHapK97|IIZlX=~?$)x2Wis1yQwZL1BVMqc2hrxMKh$6b0%MV)V`^?fv z^8k>RC|+JO3*74nhti=9YQfT^r$5pTwaKQk)Uuv~7NGd|2@ogRt66?k6eMjCi{?+V zIdiB-j}A&si@(gPI@Edl(GLib)Nt>+t=IMIeh}Mj6 zRGqC%9Gl`v26N`Dq3Liiyxym5E)LmtVo&#Tp!S3FEara9c*{4MWJ;yRpt z4~mF8a7v!XG-pn}F!JD}9uGU51 zjyez@`_yR>!#r?Qj3UO`@x0ew@-E)+M`}Q9_<6r!L6x_5OU3pPseK;oRsxF#yC;Tq zWw*53GF_zJ?%p@Kj*`k!?*QYkW=W>|e7k1(=E}(Y$~~QD^bVAv``Mj|8NFJ}0L9W7 z>T+d5f5a;Nyu9C!>9{hXVTI@mU~sq!>9v73eOphn73;_Pw6X`PYrPp>djwi8+^whe zq#eWTHA?#G*Y1tR40$p8BM}|PJAbI2o~miFJLYe&K0DN4-JfCI8ys*sD&&}w-}w}s z@-$Lu%a6&HCC_YIl~X7(Y%ytTd!D;L+}->u?@5`V->U8Qyr181ZFfGDba*bewP;VA z6)Isncj)y+70cO`@XC8`Dz!(MDGAlYCChYXDK&5_y5YG_F9l9G1S`wz%GvJQk!3nt zNwWWnQpX3_rFm}C7lEc^S&rGIf0nb$S4MF_pZu7&+xE2!ewzJlThu@LNYy?Kcn(a< z-SHyf5q*8_A=2aEOrV%CZ^XR^S|6;KQ~IODWHYP*N_PicsT?x^Wc{_`!>QVN;y?!) zkIlGPM{W1Rl~v-LENBcPeewE}7A&t@=YS*pw%Ozhf5ZK5dUJ7KirA}05MXpD;6HGu zRLWOaOyxq2eyom7ncJW(ReYe7GjcqK#3aOTk#DISmG~F6B&oJb;b5+ZE_oBkpToBlXSLuuka ztXczYKS&qIx@C^QY81$)?IRX6{efl6yf~yf9GZGJ1+p12Y>k^ogY1ZUwv-v-suDp$ zokQxDe$&*`L_u8xg=&hJz8ue6+B`dnxQb~^i`l}ojiChhZ^<2DM%H}HYTDx1AVj?@ z1}|D2o(mS4n{1%97cc#}+?G56du{7FD^=2xme0XH*W*!(S#E6x>D@Sn#%2C_n(`bm zgAan(R3B>FnVtFChiUi0&-2YjRNW%?@d-{mt*7jgZ4!oR9gV`XZe!vu??8g(ax~m& zeI8=Qn?V+uV_5BJ9**YhtWB2tj+uPZg%S*Yp#z)BN~l!Ec`Rb6e_7Ro&^f0?%y%x) z=ArIa+%@{Jae{^;k*yhMc^L|`V73&} z%)eLBB=Tb35wJhR4k_R{7E2<_n*fzQP;_M16Rpd}`$$$JZsY24NoiFfSlI;jWv0yw zi|5ynes&6>l~6GwJKth8)xg0eL=Gr6S4{}32r?z5lMIuqNiK<(jJIBJ>eqJrdhYiv z%CA*s&TP97Jn4GhOreSu$Dv}+_Jpu!XY zQ)&+b&1DgCxC%eoXbhsrhC2{oE>W~566tc{_+Ck)OE8&rU%1+cvxI_OYwEeqdVMYNDT-1vdkeH;Xcj_> zfb)K_cNAxcB@;NqHm<-SZ)KuTjZa2*InV{&GsnjXCTxdAHQ~3Mu&74>9ZMT0f0sSF zGZ>N#`l5gg#ygK!0MCI!{6L4no;mkBSE^K$@Wb$6QdNmM%4kgM5vAZ~w8qxq0t+v< zkmSn3xDNR9h8qjULt@$QVSUc>^k_mrl+9d(RhD)9wd$&>b4Iogd6WBJzvN~7^2PuD zF>TM0@U6|#)BpU~Q^sBY`u_2f_i+_LtM_d`2<5M!J*C2fDqDk*=510+c;y_HN!G;e zjV5Dx6`Kp1&IpR0Qt9OkT3&e0I*&QLB{b4zA-Q#-=_6v)86w>f>s_Vm%f{?TVADuz z{9r_=wY0z9Fhs7A6M5JUs&rD`>$AY|3xU{lm?Hib6P}L7lCW zb;LLU?MmXBZcck-XoMya~=^#YqB9u<8FyZZKr%Jc`W?X zTXR>l!<{`&&=*!2K!UK3SknE{an*Q@(8xjQ31E?-e!L&j;7Y#ucRJA4N0CUG_ zrL$5Q*xluh`5}8<*Od{=Z8rEk5M$AwCKK@t*ANTZ0#ys+O#CSc5!WFBaa2I6DaB4c zbnOhmlq5XsZapwOOabxwGP3C?5ycu%0tWfLcc9kar63KcEmkx~u_t0^0=Q`oQ7Lsj zfw0CrScd`-sD!WSh`>t910mg{*3wuZ=txpVxIw_F!)HuUP51H}$*6~{Xr6^%lrG)& z-M)I%@WzX@7@|)Rw4@q#eF|QZdqlzSN8qiz4R@5fny);EvXtN|*kUS{x|jhSv9Z$& zZRFComdPX7WzB}=K8<}-79X$7F)@~u^iV5!q>kV*KSxL2s=>)HoI7yMMJ>(2NUbC}d&1@x?job12hS^md3n&2gQna~f)LT* zC}@{JlFjrOa{~fb;|_9hKNtd+IGPC(dSeE~3z2p?Rld~5UXl`Fa?v{Ppj2rIaKDf9 zq}A^<88uUMRwOd>5NkvdZ*)ZFG3fJDy~Zm^m!?m z$-YlYO353|&vV{zr?XU-;b&6bJF*)q!WwTUIEd_Sed4J9 zix#?2p~%z9A)uQQ;VbmYfy&*c%etKgP(avu@3VE1A3+E|aQrclF5mERxHx$^b8F#* z?Qz7q@!aB#H_><;lI1DDu&L6SwhY8Gvw{7*S}$V&T*R7FPo!NU0|rVub&#Yi<@YN} zhGM-(#=<6n@Ms7qsf^VnO@8lPK0M?rOdN0ZpA?o^Jw}fv;M1#%mP6v3BZrwuDI#>g zv7Q2mFXeRy;H)NF`oK13h*T}AY?jt0`Tiveq`HCi$$PeF(AOWVlN~zPX^aa;X3^FF z?HF*YF^@+Ml0ys1umPvRSMrI{xf@(jO2}kUMTT_!Dni{BV{gInLJtMMhLBv{BVoXv z(<=h@7F(oKA$S9Ye@X%SKPO9pE3puq4fin+*yS#ph1kY84tn%SHxR)TMr@&N2$ho3~9( z-b0j{RyC1saiRb*tQjwBD0(_{9F6Y5?LWYRK`feTFpNA6K7}CqIxk`?s+Gj+UGx_U zo!IiOstOixp&;$&FDbS3p>%nCsRqlS;Q$wY&eclX`nw z2nFF;f|>=Vm{bRnltlF(>s3aDzf==xPIRH6+GMWnd)rggq|&OR7-L76e$FAb(#w;} z#YwWPF??bxd_~RYVr3W)5u*o&iI{hTitH5zjC9UN(%(int-;#YPt!c^0FPIB`=k!d zvXen`<%vIJi%12#N^1`%nQJl^w{Lmtb{>?gl4o(H@5`%w70anxPRqNShsomAS=f0A znaA?GY?hSKd|Yx@1aGzDO=Wa*1Zhm>cFYtOL=>&XOm~WGDS}U;u!gEus;C(%-P%KL zW?Ez_+O7CGNN?y3NDy`Qs4h}? zgYmMzDE2g~NK#v!WD*brC}of;7X$^t00)wffWZf zqHj1mr-Z8;?er|te-66oe~vMdrbxh%k7f1;7^-Oa};%VEQq7~+ukp`rVJNPB>T z<8&z@t1%Y1-N6=vS>_Uyph4dWq6QV|=el8gCB<&5LuFMq2TK2iM9C~EF?;Wawk7`m z4`d7~YEp%FAU$9-7g5UMuCD1bBj)_a)+HHTyrssJ8z#RDUO?bk?cx+)u6XvYy z&@W0-!ut4X^qwAYb(R(_mBDHOEtm_^cq+Ae!H*rQw9duq+PrHZ&Uyg*Ij3D z6Q8-fG_h0HS)iqQ!W6AR%+2Ps9R>zBEA+J#B`{|fB!xcPwzWy!(dq2`D?qneM zd)`Dp(hlC*8K`7R@=Ep7)lKh}75Dp2T+F{_Jwv~iRf#7d%*c1?OE#f(;uY_}i`!9b z;`Q@uLMtCG-Uz;R`~>5)P?5|xYTnOY9+wWhxIfPyl;=Zsj-Kc|dOA}ZUA+Pbk678p zh9A%C^6s)GRTk0tH}~yO=~1F7`Nrn%yR_%2qgidvM0xUmWClOle)28%O_*UHI1BbH zW6qM=`_6==PQ1z$jXmX~Z5ubD#&x&k2knksR-V{_SuTu6`NvS4>+D+W14BF43m#<2^!@0T7 zNOU6zz&|bq(EP;FUP388DAsqA>1q>rEm>!!o{lGfyQy z98mRbK?%AyLgrLNO%jNO=gUP8%@6R-?ONiGOP1O@4E6CC zT{(_TA@78&kwvf0G@Q1l>}6n!(_=m`M_OYq;ebBPCR)5m8_l}aI!3_a zwTr1yc|1z$zre7*yP<9J08P&%6G^kD2ttmPvw)+r18BEjEtISGnd$Xkj~9+XUbwfP zG!Y%EeMtOEY8sj1diV7HMhoV(+jnY+Pnj4EvW#Owd=MAByZOvImR{eot6BXIP}VJ}B8?D^Wr^4ffg1O)a4y!G`IVxB7#g&0bn6NL7u z2`qt$DWP%!iN{|slf+j;Nm{{9(fEIdK}NR2d(S&&@hoq{eVpD)&q1E4Fa}6vyr#iP zg+|=i-2$KfpI8Etr_7ue811c9UVxro;Vhbz!aeV}XZmMijON732@&_&7DjT2Ws9@> zu{ue$7}+8=Eyt%GN7w`BZ3Vs*;`_;9#OED>93H zF9*7I{r!taoJ07(tm9Q99Lt=jt2*A+vf3rRCM<pc}sq!A$_X4ZGLCLI(+B{faE3M*Kf_MRn<#> zD3pF9?o5AR(B70Ms7}x7K&C%-M2!Q2xPV(C@yrPvls!k5NveCQygqelIdg&6!?iji z(Yf;~BbH~3jL;66EQKE2|39Rcn=n6j4%MTwBf1cfYZk7uyZ|O~gD^&K58Au6?0u=X z^6}B#w%{k0=aFg4%PpVFjvT0Rinfux2Ott;Vp6R#tPia@r-Y|W-E&^kbdmCj;1=$O z>XG_Mr=}^)DC$apurGfaI1jkxb99jlr~fP5WGNh)D>6S6{6?Lfi|`(#K_+iOU6k3J z`o6h^CEy#2noUDMSW^w3OccUVh5e1&U0!YgLI&=uWE(_hHos*5v^p~~F2RjdJGlxH z#L3LoR)#m8_5BOVN;0TV)aHk*ju!^A$V7n^>V-GQo)~#_bJ1p`01kW#Y}#7;K1hep zt{>cN793Fctn0IfUw*_xDL+a&pL^+8M#5Xvn%{&FWb`8Nv*@kR)tIk~)K^nT)mf?_XE|icd8KTNfLeyVVfDROL9JIOgYKf;5*f$;94t-P5mkuVn-L;D5`nizh^J_ zlDI2*K}Yb!cvmc#fifOs3+nyjw*0(|ksXChQ#`{P>dQKrPxF$5mR7uFTv}mWY*APm zUDCYqJs+Tfbq@WrO;lY*Ns}3PA1|Qceco$SXe~G$#nBm`$91m%DKrt`g*;MNgO1Ui z?yqh)af1S-a1I3v`@Uz`L_OV=O{McnzOZrkF;&Oado3?Ta5`BFi7x#yIm$K;Q-jiJ z$Y)CIFI=rm2YT^u*#J8i6}b;BvXY9WIn?8kC9evqkmJf4ylGM~_H7 zuK7iDMbC)GTL?* z_Q$*ie9-ew=@AW?B!()7hJRp?=8yH4Q{h7s^we?WZ**UxSYdHWZIA?uC>zLssD@(| zng6I%e;FvEd`#0wLpG0Uk z&)S`(*#it8SNF}qqPL4ut1GwvG3^(3uZDA*ePpd_2DyPKF{62%AH-tng?+KSDG|tY zobWW9#O$%PXhGz-x8)Jhl>WvagA)d$@>Hi?u)<89L%?POtlAkR+m#_p(zhH?(^LQ3 zubyg4U2;TL>ZO$F8V2=D8sv?24h~7(F~Pd<#{5=wy`6c|0n>FA_-CcOfZT43Du$bU zDjpbCsBKobT{QBbx9qbZ76j@LC-I+zYT3@FP-pJDY!)1|f+Ur4N0axz{E}g~Yu?&? z^$QT>U;F0Re@VwHLuaYAGH6OP4zC$2|K3X3q8Rq6$G<<-O-~lWC7SP#*}Bzkg|>)e ze&s=o`o})B52P-E?Ed#W3{N;43M{a+bO zVA0y-$3K@uuphD&xUcqK_rNIT=41c^tNK`LOCejAi}eU%4DP4_B{&cU z;G*S76fOv)U8?Bp?Z3-PW>@SbhK*5kA>p z7W;kmo~6OT0Tzx6q4%tgREf)JyGi8V#F$8KDwT`PZ$}EH`ZWErG}9QfI`=q?KN3w? z5%c|-uz+t%Ulf_Kp3JW!_@5h!OU~bu@iO|Fww%*dw`#tGOJTzG)hlUCW{#i@)hB3{|lH!OsGs`u*Honbu&k_Ld>$dT7f5W3t2xz=hWF*ICDY8fpf1~z`NG^->caK0|K=z@K2^h#DeFTgh0)J+rUlmIjXyT{m399Md$4VFNqNQJ8LsHo;Z&wAdDq1 zE@$RwQ&j-NE~21&?L~e_iI8`6e~Exj?gT@|N!4Qf)@X*}hg@aNwJ)H>*;kkNJ;+Ov z*tA438=2nn#GNdyIGnfCLov3(K{Wn71B^oaSVP7tl8@?&(nIjJ-#=^RP3WaAfw`JU zHxKOVnbQR!O^c|)Bc}a$pTlMT)QeyBl5E?TrKr#Zv|Pk1c0oa<+G>(Q6 z(hgs9K4bpDwWFjL3U)SQbF+`exs0GweIrXV#VZzZjqm!lixf&2d0S@5j#s~!^=Rz5@42}NX1U1 zgT@gQ(=>V}qXGsF%Tw?=kc&)=LB!@7#CSW$xt0a*8rkAnX+ET%kI52EDg+qMhI!4W zKl{aiY3=j9E}>OAlS6RLDoc$!BQjYjUFDQr^lUn2+^Y8Z?MH+HeN30o3F!$4Ud@J% zK=aj1cqHf*)*3ZIOAQmM>7j-pfbu6#2S!1mXj(MF+gt;8`$kqxAhL{2MYcp+LE%p& z;GI%q^g}LSWvh#lP^73w+%zlfOR{EQj#pJCd8LYg$h!1rj*Cg!inhRm$tT!>a_en6 z)?HM65MG`6Z+0cYBp`3C=eJZTt2u`X&$e#rKv_tV%mD8A>y)>PA6IhB`xi%NG(AZ$ z{#w#pD~UVHQ3umQbJX#p&_++utltSilOh6jv)@vwL1OsPQ5%3-gQFz`UeH50w_}RYnu!E3LHvZHa^oBg!#H9X|IQxB?MWY@IzDqh6la_Y%4pRkp8;T0JzOo`tb;bfg(85Kf?bLRCf@dSbh9 zXH!~#hSQV<;hs_QFj!=Ju?<=0?}BQeo#uZXtGw8cDX)<;Xba|oAuW6`R2`Z(40*_h z>moZu?Z*(@4S5Q@?pCZLl8?qpX51A`mf`z2xy*dSDG>NQNabIh8)Kf*34&!sJjuil z#;_r6Rm~N#bJKi~;Z)>JIXiKb(T{}h6#I7?&h06g>)l$<#s9Sv z{8`0uaW0!`P$C>rC(a&Xtvz{R&@QkVcyo$-mPh#@pe}{dtLVHWuO`8p>VV``DPc0^ zjm`b%YB!OHPfPg%SZuqvIFHtSprO+K!MUsH-K5*VVxRjKkL2NFh?}&%)N0k*DR!c( z2_i{-or$HPeo8OED@athV&r9-qjqe@34Ai!ngIimW%mBjsAA@@ngT~PDY}$E{x^?j z2rw5`JQI3~n8q^Ck?QgX0Zqh;Hu=B-|5~FG!gTx`KBNI}<}5TZ)`DH4 zp)G+blV^jT`1_yEVm;CIa3&~(XgzNx1EDG3=x&%dV|F)NYvpHg!2-}BE~T7btu*12 z$Bv?qle)f{sT(EoxDDI-g&8);2Pfy-skr0%)1hHdtK~YYHnZ zG0onGgs_nc!cPX9pZMQ_*yNDK7WWt7stf+R=dI%#vmi_4OY0$U!Bs?m()8fYWCCYXIi>iURg0Pllu$ByH^b{U|nt_B}_djy9)PHnR+Sp!LDGpnY9ZY zEpEnpb;4NUTLp%Y8_sGUX+DOF>PfAH3?OO3iZ+vi``QbULTv;uKcwwhV=jd*nJJPL z7Nhh>wIej$dAJnu%7a8`L?MXQg6|}HhM572V!C!+zqQzlY#PID*=D^_N4(#A0*Gq8LSiaVyV-es&t)>IRg}0R^qc3Z4K24p zIDr6lN>}lqb)=J2<%AwR!0D0lqn&qQi<^P~Rr6%@EmFf|4I0YBZUGn%J54hK_&qg_ zX+)Vx94@K``M?hf%NJgK)v7EXIIle9V<}Z&Xy+^FQKm&@I&BgPufe5Ra2*h(Mnv2vTWRL+xBcAtBzQh~Kgdg+bvBo5@0~ zn~kkfF&PDTm-YnqFt*viFB@EA`Is9?=_ZjI!Lxr6r<+jl!hAKNHrEQYc2(2N(+3u5 zB|_KAf()%Y4X<+$=3Ud)V+xz}NOvQe$0~S|8-mS>UG~JCELac_v>%q0B^%^0=x1!B z$Z$?+$YQZg!Z963U|ZmfLL81*qGP~WG-_&3Q>~`-I!P8rdf)19&iky^G9pI9iNT?E z1tAocBoZ&@Zdf9~a@U?Y}wYT)R zuw(ZyKj>b^OdYv+d|M{GF9B{56H1FVgvy(eKu{v4tr!_Z8}pbDnOc~}Y^LvvZV;_J zpN|Qhy=?-hj@)0`zUfJtL~&=EJ8s~h4Y?>tqRue9Mp9=tmVo3Z-%9BtmYiPxVPP++ zgyrTS|4omm_)O$YHr3s^bUFBE=!!y4l3E5^7UC~ltHPYG8YM2=cABZcp&0xieZnl2 zYBn6|XeP|>T4!HrT7ZUX@vl`rJ#2z###_pXJeX9mBCJAO#J3EWFr5@-YoG9%? z<2V4Poy9mc_oi=PEBwKtr2d2Itm~a5tgKSAU`s*#6C0{E#nuiG+JJ9X?PhG> zQgIIMTdOv(o6_XY<3evm``2m;%(|19KrjW0NI%yrFH?FkxbI~`9OY+P5qv=%nEz7> z`(xjvPzSw$oLPVYH->i_t@mkcz4KSptSAj~Lg|jA3i3u_^WFim&sn!t zkIBl#0YpL8zU$xJh?KkB@LOmtT1y_*2+BUUS{E#JUO>Nn*DVRI30}Y8*!D{D=JeoTx zhexwC>&K-=bCwM~8o@23l$?TUJho&u(L?H@T3Qc-(DBf*g~PTb@-+k{Aj$On6URIb z#5jc$v0zOxg)jBov)b0+qOswjoDh(DZaQeo2OM`kU%YYCtf61%2*LTGitAXluLY|b z^1RuRVUBH^wonK7%2?@WIYs#aHoN9qDZ0&kvl&_* zN-Qof!U4p4#{H>5P7m(_EMA8u8E-((m2T)JOv|(p%1m5{ZtP6#-8&&V4;{~~ghi>T z5BZM5e#74QjS@X`py`3!m0ha2FgtGt%?+_iV^$L_(BZmP;3_0TX2Di7A{I>1u06$6#9JiomRvQOH4Xu8hkYxGLnLnrI${ROB*I!XMs@6 z0s}gzV;3|K+439=SLuy8q`o6#XnQF=Yt?jKl8xVd-<-MHkWFihC;MfgLjIMkwl^FzSzuhH?7HF6u<|7JSkB93igKRzQSppZD#F2@T9n{vk>xl z|5bB7a9eU3V0=#&K{S_E>>_wPLZ$(6HwUEXm-%lvu_nQL~9G%ze__-P$C$YYOovkztm?uYnFVeljs0O!Tc45u<~TyU9`| zFmbVad(rado)7-L8TUM|I%`wE+~F(ELLk<$jSuLOhJu;(qBO~UjfBM3{x;s*t|(KG zO{zAGvMD|={RZL<9Q}@z!v#9DC>&5uMvfh@_a2xaq|*dvi4J7n9nPfbMZg`9tAl`M z=q+uVtAXZ!IB?8KQXJw${X$N#37pn^JOf>J?6u2nHp-J2+uWDu!rln%lv0245VxEW z6MD>}xld4B^Mby_G}&|yI4Z^p;T?Ad zX7~_KFMt6MX5|Y&b)|W3%()c!Gsg$H1!S-p&xbO$8Mf<19&xH;cRI|SQ=kd`)9xFt)xa%)=F zuEV*z)(ZIMYq|<&L-nV$&62;`cj=4vSALR!@^{tt()29K%3s25r1#@iT~qAPAs7M) zP_BShXwSeq>-ipV-~B|DP#dnAJ|Ye-&7C(m)=uvpi%ez>c||jzTiNH?3USH8 z1}fbO4?|2XwoT60yj$KOE11&}wa2u=M4t2Yza=%aK}<}4Qy3ZV*2e2qr@DQ@*FtVj zzyA@}%-$=D3by}3$Pk|-Bj=5`lTKNF2fE#Is1#sW`85c?H?t>lM3?QY=d}8(9d-pb zsg481>=G5m+Rv1pxtL6Q7RIplD`sVmKMEG%wO`$Tc|`47oZE8g!%eQNeD){nCz{>j z+4hEPzNhEaWzoa29KhSZz2B3}THkV4R^(o!=7;npJz<}R4&&G6h)KaW*XQN>lAzk@ z>hG>>HmcR;;*oOCAQiH5y_WokR|&7*>R6^@la3-Ce@avKJF4;_G#sDx_)>C@d+KH) zH4$&kLwIcUrOjp({mgf_0KQx;#qZel*AB9sil7 z^HD-++v@7cKrkf!?qgP+Z8fgVe|K&4^qSJt<-Wc)Ju71*p~sdbF`aMTl$n10|4g0< z9YDD={Uuj^A!ID`-@GXU|3TjMhwK51OaF7%&sTAEF$%o|q85F$y>SOatdOepV1t*e z^6iRZ*%v$&j_nksU);CQd|?3#UOFmQD;DEz-AA26uCJfX>NVM?d=8~XABLQx7=3ZQZ1hWD#)3DSyN)-qb+Y>tpXNzp3<=V^mEm; zeYbM;)6;po{P;sQbf#*v^3+}$Yy@3auMONv=axhNtv*(3kIb`vTKncRm1lGP$b6;$ zo+aP7GR%7AHop)(%*s9fk!^!+bwH00jcYTHz7{L{4*f{xKlq{E@42h&?DK5n4J)_# zXR53uv;6!^y>k1X(o?^$R_^mdm0gCF9rCaI@yhfZg$rm!Q;)2v%a1>%b62-(}{eG{NuF!K9R{;;UkYeYv@FLOLvP*$svH@)2 zW+4_RxfpG}F}u!};cwdRYOz84?ds<$D%PDRpBxKmT(J^9WWl)lfWP-&mLKNh^@Y;% zwKYW`;8{r8J1#d~jjw*Fn^aNFB2bd`K?@m(B9}pOvB-+Z>)&ahW{h<`78TNjvISo- z%d3$k&Q3}N5iKMFLnTsNw$FxgQl~y^WJE|$k%EC|@!0o~!t`@ZvM3K=5wl#N`2@h_ z*$RsF^`H!vB!T4IhGcz8QFaSPL_}}?HAr`n&)HXNHRVJBre+4Lvrrw9C~<3CbAd~r z6rGxsCoRu=TXnPc3H)ij_amEMOxpnN(a-I|bO9yVHQ5xdmsr4VX`X*-tGp|4MR_{u&v}W8eN@L}wd@-F&d+n9bh~2aiR5p5kwd=Hi4GA@1sefq)?esQY-R}f5cP=ZKfdU7^dby6fq26+KMO(?-Y@w}q z;w0H9y>@)Jpg|Pk@Y?o@7TmAR8+QS>3^#rw~c$_#%p4#-zJN}eQb9eKfF}aKE?d*!b zxLi3}ZWcJmSrd3!rrR(W$|}YZ02VQB+S5Y}}5Z9-&)TJaJV_xT9Lw^w))jifoa% zT|}l*8kzX->uxtS+EPk8K8&f;dJ`XAyD~0%h*z9?hSK;73gesaIG4;W4;GUn)_G-; zTQ#X$@#PjHFd~;a-5@=m&2|$>*c%sP-$5fovi8|6d`aoVt+Eyp?ld{_z>E81R*Umxp0=qA+E^*B4 zg(2R}1|@x4)=d>e4S3U|(Rc5~!2~d;$+W+US+q|$ZR}T-R`J2J5f%i{HVPQQ?t0P zYTDUH`WL@yy?Y{;3sa|^rZ6ptZ`6S1%W4G2Bpd>vG2uI#eJkb#45nbs>#YL@MGe)V z@39SMwTA9E5NG8WKa`Iac`KuOQsab8SoJ5(YJKG*C2;=!bR4R>oyma0Snrk1%FZQo zSpkN#1_k0tu}LN|OGWqKv=T*`zhN;tqIItbs?jr5JY;_QPiLzutk}|59P^c9095|y zeY^yNj3r@#^w<#k2%Ugnr_fFF5rZvnroMX?wso6|KiQZ^5tV=GP?>>-6J>2z`4qBi zOz&*QE*tb!KuA6d+crS1GO5T z+74UOKYTJ)yCs<{-Dcw&|*+)onT7YC+dh04OA>4!Q?Sp?mKft~)Sz)ax|9)5_rjZQ1I@!muWU~%3JnwnxDRg?D$w-Dwc zD#*y#cboqW1%ZDlZE%CqIW9;;TmRKmRfj`U?^N4^_eSjGEDpbayl7u~ zxMj(Bi-^xf2)F+Uf4paG{|z3%oM&tAUH{?ZAPXWyt=`d1AtX{BR1c<2@}poR7qb&Dcs< zaP2E@*TA;mSeD}FTV=hzw}W0A0T(T1MTA)A{UsaVO?JNSq6um35i9wYUWK~wZQ?PZ zUVf3bt^yYj!miP`c1mjUrTz0X~mt~Q<%Vr;?-Q+n^ zsE7f`$Yl<~DAA=g z1HvR?ud{FuQFMsHot~0+qYFv(WufgL2vGYJcZm~p2of+zo#OneoFg(tv;NRI%!LwH z!3yyZw;K1g6pr2@9XQI?-}GBbZ(D;_lxo%8o^(Th5E;ti%8RD=GYVO*oxDCQvoKMn z-H>ecn(0b=_HrFJb5Zy510ip?^+IBj;1%o`FnMvU)Bu>q<52BjvOtfeVOJlh=5K7M zT1H5r4eH0v&r^O?a(=WjWEhEtRaZGrq7sONcw7}+T`Fzun+dfYO3|g{yxg^&X7C54 zhYC(}3e_V~tx)eSxr&Dfa_b%ZPpW)iZh$-y(oUSJFb)*)!V9Ny?Ile9fd7)-moXnM zx#0i6p52p6GRY27|F1QVFwbKn*`qdywa_iNxGzaW>(h&0D_TZGqpSP3g`*sQA%wb! z*gDk%_jx77Hk@}cBti0#h`#7&>?C8ty2{>+a{PG}j!U6g$O#h%x*r$?0BWA7L&x*T zTRBdT&H79yW_Rl;8PV9CS`zF@=I8$*tKeB@_T?ycF;ZNMzh9`d_!d-5wC`I!6U^K% zn|-dpTh^G|k05UlErJw*mNYTI$LCXh>^on)Xbx)q(ueHlY}YRO`7zWuQOS@&Gn_Y& z*0?=4Q+;acWYepqp_3#BqrwY<-rq(ayCFAompEU!kjN_CUG9kVU4iQ?1U;z9C01tR zhq)t7aF!DoT#fO(-JkJ-!FIxhvl2r8?cC&^Z(MzGO=@jog6fGeMYGh-?EYnC-o6iZ zMx~tFG#N3R0MJ)HfTR;*B$TQ=r|{N38&Y|gWptx4XgbjV`4L_HzJ7cp(|$&tIcJMI z3}-t36xPIyZ?m&g<+b)5OiWQ0vFA!iWDsQEB$UBeX`q;TT`kAi-ArZN_&*=}DzuRaugE+>1k@w0OeB!QK zIlQ9AB@z*INQ0|YSh^BfDwzpZRN_J^62C1K{P1XrN@BmT23KECEHwiqvN@CGwLBwWuqa^*Jrn#*x);K9|{0gP$tAVl8uK`E)9!d5m5C$;n08 zK9ddwH7A=wM8abJdD_JRwk0%f=t&PZRB7-`KaE^J@>U1|M- zJFf1!;=o*@YxJKI={g8&$-WO=SCE_$^{!}!EE==O=Xz|`5o%?w7{nUmRN&{4i~q@} z!%Qy>io9L6!ojUgF|GhjK(fEgCp%FWHHbuFsl1S_p~O@dW=f&g$#;)i(~gPn%;kAs z(HjeC$d;4JOxY9P=EL@+k_B&x#UfgS=EA(Ut;P>1OHRft|3H~|gA=f7?@QQq#_(fZ zL1?%b5&x581FjFX$$>}f&@_wUbs7%ShY3#rdUr@@MkE@=lE}QBkfMtLHYo=u6kXp+ zK8!fPg@aj?v;1`$C?VtlAoZy`uNT(s$OI={(q8a%9$!*m(R-=MCT^&4&`;t~7~`@| zPn?@#QqasfzPx0bAU*hNmlCTILN>yMPZ_n?VU?CQl6Ef8jmzh7L5XY(R>sbWP%^ex zfJTa49{l6EbrHm4P+dhH(^((X#)`JB{Hln_nU}_`E-=vG3i0eSr4OmPbcF9=eFX&R z?exClHM{Ow4BD_D8qi?)1b!i=8`J3685A~U;nvw+8AG0eTH`|bCKu{>K{0p`>Ez8p z$l{Qf;hilwblr)>odV?vs#@a^1^gth>8)`OUv!5?jSJ4{es7kpeVA>j3GiDD4(AgG zn6p}-c$54Byx@e28YBVR6@1XfdRomNw6w}1xgUks^Bmk+?8r_>fg!eIGG^t; zGsA6_ASw-#N#7>6lOqQNO=196O`enfQ0=mEw4~H;_5O*oRo{F{Lsb$;{77w2@vm>R z4D`O0@2q!s{!;pU7a6aQR1vx3Z&3iMJybV0KW~L*>YKmiiz%P>11b3lJ}Nha&EFZw zF+X%GfV%p3M%{h$v8uSb>$|#ntT-76Qa32aP)nrq7_z!nY6>v#GHah-U-FFHid3WC z1hncy7hcH*-r1dt?!93uEFq{cKtuZp`tKVmH{m(7aA@VinJuhmn6)ZHx&?#a8>Mwx8u&ai4c$wIIiVM~a>!$RQTLmz-g!7PcG z3jiy#$0e_w)q}Y&_K7B0?gE3wOtmo*k-=ulWe# z-o23e16$$c=DFTY=ZZ+d1;@czB&6T~r!>0OV))c6rS@h9<&QUXb8X~Csi}N3MKi}6 zF%K`V3?l$9Xql;o2zmraBA9pIK5Zw(Nm&Cz@_i}bG)jLX>27@gvFQWFP@hVkO zJfjY*>9VKW_FNfeL!B7OHI_FoiWuV;BPg5sNuwbVi6N8{x2*sTtwc$GYVA3Vvwb-) zA`z+OVI@%Srl)gZJj3e(v5bfMyHRCGm=!A=szd5|K88(TUX~bVi6mglkcpfv4ThEg z+ly>LdJy^?D!!>|CePHE7~rSPpql|mt6eZ7b^HH%q~F#FPQ3TlQM?1 z9P*MDSBHA*oQizwEhRwJ(Dv3%hthh!{#vt_wj0K*xv~NtLJVwm_lK{ql%d|Lb8AG* zA$WI9P{VZP){|vqk?T?;Y5MIIJC~hwhQpw8IeV`uJEFPE{2zDp>xDYb4L*^g+X>Je2-G{l5(eBbPNt=iHZG`4B zM5aq-n-1>%yPKb{{PgbTo=WUWR^fkrah@0XD3nV^0o5))n{%fq_j~q^oBU2Rfh!~Z zCGU+dVyvq}7=NxYjPTK4`{vkxN#%zhsxQ@Il>5a;KlXJV`SvSb@lzgTcF+T9i$*Ol zJlOnbGB*#Tc*@kDgQ|iF1X%b1c{FI+(j4#3ix4I{Otn$u80T74piv6W! z%Wp1#L{f_D^6mO*wmFaWL_rf}{VuqqW0lXlwV;@6zuHR8W{v@NVM$zCG8yuO-Y+L3 z4gGQUMidKbmLlLw{T0VZgPp}jYm9MRympUEtQG2AqapFvXep{x7sLCKPtt7X-XAL? z{0d7*kcf7^Ji_j`8jloQt*|-flZ^Ioudrj9NfvfA7XG(tyPu!0z#pU;#7*evmc|tD{Lc1f& zbA785tH6TsUgEmNLCcd@z9*9p>XAY=pgv_MAFSe!3dT5A-P8D3KTF>k>gA{shFRR~ z(JWKghc5Riw%vALM7#5Zwe7haGciZzkp7__PnGbE=>*wNCAMz|vRa<7nQG7bicF-^KI!VO zI9Il%H;X-OHkzt@*~P)bn7zRpM~FNyA@`zhr2}$^szylQ`f`-4*gj|1dGGv zwD4L_bqOUBR+%-IvX*IBF#>ijVcON2e+CHs- z1Rf}L3XoC3+*BIMZsQ4AaW!nKA{lM1*`}f`%5?L$+EAcxUHW5ss#{6S7w7?^O^IO&RNPO+Zy7K#@i>@wZ{?R|kWE%t72d`)8oN~XX56&utC^$efw@kUZ^re&33-g4|EUPM?y6%Bf77D*D6iWNnd$e zcM?OEk20M1oh2}2Jynatp?9{-Mbj@H3b1w;Qr) zKe~G|6O4lPe6B~ZW@vvK8@+p=D_*?@TIPYOqQS(hJwvHz2?b~T#e5jDHKbX0Pqq_T z18u>OYS(n4#&*g@#Gtjtqp?=sm z@Z>1}Pkw?q7V|)k^76S2^>lH0&3f-%?LKXeoKrrJ`Wr%hji1oon&Etz2RG26G z?5@$M0&Y+faymFQVco)_EB@7q`Ya4=S9b-@&#EraaYkK81|4jPsQ{DxM4Mdx!RL{` z`@W$}IIRRdSpNz?1heeDEZN>~N#{-5^ypvuCDP?GlKJe*5Tq$=NWQH3IJfN1$_D<( z|F{#6Og6J--0$Gl^42yV^sb1@{Fsj14~nn-n3{Tb-q`T(y_Ksk71yYYB5M)7z2SEi0mj^(kb_~>Jk}m&So`Ex4e!q|lrbQ-Z zUeZ9X??V~JEcFzb5b_H4UA+TtK-+ywk15~!+>0!TY@LVTKdd>Bx6ytMa>&Jeys1g7 zVAPo~T*!|NHz%bseMKNhAtH&c&7#p?VKQT8pC#Tx@aSIqP?A$yJc~Xdq3iOp@GJvp zD+G|#O%tz;g@*JoBP6a`w|Jb`#PpscfdtJHfDdOx!GV)R>Z!1q z^bU%Q;|E&jY0*eC&M~hd+hy)VlGxSG z5dL5>tmy)>W?{NYido6b^I#`lXI&*y14x2H6XcOY4y;uW(VO3|bCR%!6JD+!CMF_lFYOQ4_OBScwl z*FwJ z*BXZ|!KFk&>XDZeGmg>a>LHSIvwS6*tA}tZ{J0lqffLrVGY#ptO_yGNk&m4xVGie^ z1FLEIGFfa;sXC}}IK~9pDw~QKtC|Q7`Be^HDEo5^-q@zuv9QPVKO z@kn}(MMkA@4Q7r4g`{fqd`Rgq(1l#d>nw~U-rg2$>z%o=AnU6jhzA^sHr+17{vGT@ zcyOMMbWg$0cD}4nU_5By3 zUo%B7ig2v*u_1Z^8PAU?pK1+{hHQ{}$Hs1lHB;yWE1I#|k*c%Gs3HAM6kNiBqpiK2 zp+UYE3kSFOy}vM;1lVKtCUl)FhUNDS)t5?FT1LZ}q7}QZIsm43h_Xr0e$x5?gn_0& zJyEMLEj1z(QWTJu0Vt*)1$0>nvSc*VCbVg-jk9Ido)M{63s~8hV0&kNGdApCotVc$ zU$VuZGq-xZepUv%s{C&ihOqVG?$M=jM?i8l>~v`S$1&CAS7IBnq5GBojT!QSQ_=p? znpY@&0oGnnlcY|OzHFq1NoXRYlQ-k-7w}?q$zo~_^31b>PToHsMeO1Fio>#T+U5Bg zUzzZYr#A#Fgr7yJN+>e6KLhG_fgNzQd@sW<`bpt`Y8p5{PI6!ttIW^;BJ@0)obLFB(Rv~$UG^S5L`BStVt$*mu;}BT`R0U+9*-OfUj=z@^AsCk^y=lpj;+( z4@&Cy?v2jmT#oJ>)j;P4(hRE_GeatffTm&12AP$i8NvVCGg7d2udP1zJoZj0Y-sb;yMfOTskL*`rFCgwyrl?-zU9Gwau{$BT9$h;yP) z=GxPSeXuMeh|}Htj{4P zx9nO(2O5ums$ix^0QkrA9-1taYiRvTc&Byg4`HhlgZjffZoU$^u(*dU{6#6b&oHf( z<1~VP@@9lDP%-+NDzEFTOubM%gwEelwPu@emWdAY=bQGz8@zy8)E4PHalJns!B*JG z_9-ci|3hFo@DeH6BYNj(A@f*6a{ikKaOsX>(~a38f3TD@I~J2P$lw(x9$S zyOlV`6<(vj&skE%ktozsLgg?LsDa=UKVODqc_Zfg^rsiXM+P){+pf&EHYP>^)d!E=hjZqKV>ADH*z$f7^=;P>nbl zqU*!b)|A$5QtN|YcIkst7P=F&XI~9ZMNcc;^bgTOQXRuLbAZ=)Jc}6P0#3iQ=-0Tl z?nMYBdA*-J?N;_ntB2cYD@eFM+*AeDP#-Y_ze0`?J;@Kx%7Jn2IpU-_S|dg(oDI!A zi04u_^G(z5A7$$m`G=bxu2O!Jm)iBswZnLAhhF&pv9Dxe^X@)jEyeo5*kd+jc_E@U z`5Vif>Du+`!(Nt4^L6S2Qb}f$ybiJg7eNZRAC7iplC8@^CqTflV)!w=$QyG!Y!K@D z=OpmTWNIUgQ%bvIyd92?x5hoC${In@fWDE$F5XkWrhBF;-@Cvuu1QYWi11$bac8!D*6U?LM!q^VXYkzg2aO#at6Hot4;+p! zQs0V2(;hDWtz2MMMSoc@qc=7KGyz^KoyV0IFM?&hD}2J6@tw z)a5CVe{pBid@U)IECi_fpCbF{0(c`ake-H50u1x{f(h!Bp~+?khFb4Bh#hg#M2z~# z-o*GwhK@-mRbF{+Xxr1eX)g`l|L?VRl3C1}kydm{urrGY#Ib%nnzf618?Kn6=x83y zr>bLAAuj&V4~QPk+&OB*zGsSml5uEhY?6XEsmqF4pC~dQ)1huO(QCbjLzSHpl@)Hg zUsGD*kn-oz{lEv_{MVY0gw(G@S?BSAd5mub%&y37>rb#+Y`-j$FYV(H`o1b^>DtHM z7~y87#XLkxl`&`1hMSxJoP@vGJ`-~h2qPK_2ek6Gu0r1F`~0*1ROcPU=c8=z%bjj8 zKU^B4rJUpKUuYdFDce+W9x9x1=6o#FMX1+0jzKmujo5g_JTR1R`6(~)Q9P)?|o=eLKp1?=Y{LsCc1%8|3IE<5h0Xi9)XM7Af?b(_o zpeAYtWAMcY;e+$&`NN9nHu(vWs93Wft+YYR(-opw+~WB^V~mS8;=V?XlKUJ%IC4Xy z4by-Q{iSNy>y9MEE9oYp<|*Qm0Rw;$F-6X}W*fcS<+IZzXjRHflio$^Fb|Ew+6z+J zw;RV320JX$cx_?plCq7DX7Qa*2yGc*veQKE|H-Ox;!2?Zd!~3?vD;m+w+^D@nUeZU z_Q12z(LM5DOF5@WA7|(R>!3+RZe|na5)+qA<3X5qIBaoD!6Eoa;C9Mme^-A2mEDKD zMfwg1@JDMJdcUpskKYK_BDUdQt6^6uV-`-FL`(^dCdRg~JsP%vPBbT=Tzczhvk?BQ zP`$-W1|OE+Od|!mfjV$t8aI(q-@D$LaRAZET`sHhbdk++q1!> z4?so~dPT4oR8%6xl_7OF|VUX${F>}I8jdn*1#AX_l#sq;y3sxVN`*wJ z1HGH&fjR z-GHJ9@BTtkp0=mj+WX#PSH*f4XCLW2wLxj)fV`F*lGtZykwagxSOjGiH;0(vG8s48 zt$DHYXB&F6JS#RT=8y#meMlGO-AzC{IGl(81|1wpcCYB>sw~zt8)f}Xjr@f;Ql;IW zkCf-14-!MD%p{Nw=>^v}U%pS{sUFpV%$C6xp%Tjvq>HiP)RPnD3<*g+G$UE8hYQ5T zY;qtigq_?769#QsNV223`@}!88;P^XPi9rMuh%y-Inp=QF}SdY)GW~@;cX44hZD>) zlZ#oUVxjZsj|bfiCamw75EfyB_QW<5B9at3InZ=LrTK1SR%gD+BW1!_U&Rg1B`jzn;R zgg?JQV#9-HT$(W&V{@7~f>|LMFf(vl}|<}~CL zK!+>{W+wMyF7CfLxf|kBd^;{n=;zy>ZFw<=m&S0!nImQ+E4;rdP(0`T#FpNEX?sy4 zIR+X+Sl#j=hxE=nqDfqXTd!_sb7}B3n^Q*OvwTwCMG_eI+F1Xc=y;7d=ZW!%+QjJr zG1ok-on1RhRjT_<=0oVZs~gax;J%2j$LGR`Xx4R17K#d%8fO9Wk7i;BhVJQv1(Hs( z?8uv7QWaNCqJZ|0^5J8;2k%XfP#-<{vd=_~khL}Na-w9(x!bS?j(<#`b)}FDx@*$6AE`EN zH{($G5h3L8!Kb{HQhCkaImf6K1rs~pJr{~3L`FGGUr{$K=$U{tH3z`DORI)l`?UaV zv*x2Ly~d4|Qh!^y%a_V`as=O?^Ln`JJA+AuvXKwfz8XiaHGn?Z+W4JYqUgq< z|I+;fl66b`gTUh~uupN~WLXrdBmqnMNEA5tqCg)AiZb$<&~41eP7=^&r;Wz~cM7+^ zRthouDUnhenhA|e-J2>xf*5MCGl)=|0HV67_VvC{zG5EiB&~h4M(fz*YIoE`hK=r1 zf=a|WvbJfZ{@<7-6Vy9o+4MpwFmBJjR+Sn^QC*vv$FHmb8rf&X6gL{j*V+>sI?i%9 z)_=%Dc_$KD3OW2?P*BF~itM#gABjCTlwv3l{cxIXa~x_K!6k5dvI6~!g*nJGBhllt z8B1DNq0w@TJz98=aEC@fft_`n4F;NXF26)yis|}>U6&T`EJZ*E4Z)Wq*X>CniNDM~ z$&mNJ2-|Ja;Ee`5nL29C@>q(ZPsb{&^vCMUCXS`BJ|xhi1pt&uKW{0Qbn8xOJ$oN@ zoHmI2pMrhnt={VHCa4_Qi;O0#(6^0xSXM~Y*@lPBab_lC)pxw-SeGE?x2=zrl!ZeN za)#Zt73+RV4~yCorKC66tem156qaZH|DLf4q?sf))}a@He7`!M_K`VX8h*VG7nu%TH{Bj8;ida%Wt^wtY9H)Ltu#89gyH#j-> znL^S>;UMcR4`X6=-xHOAFko1SUKt4b60K!BE=E90pN!s>`-bNqm(p*dL|-j?QzmoL zxE^mi`IHMqO1v1XbBN)MrH!$GWw2|`wg;&iR6 zw^~kuEJnnMGNFlks)3JPI$ZJ%zKSqp9^hV$onTAN#3J>nB~a6csx9Hs`GYp?Df56& z?;op2ON3VYXUarIt36F}^hyu+d378S(3v)4JkE^mPnC=|& zj}0S|yqZ3Y@j65LuDfE&}C zo)i1U0w!+1{8W#$oK`ZGnnN&O=I#@w02M;NaD7C$Uqef>jW3OelC;+G{(V59;n`Df z1M7>&Umx!M9vw_+4RwR)v6+wcGs0_ijIPNf6WN@(kPZj&LPE83FZ%IJFg6^j*Aq=; z=BNz)ZjOvmu@P)((cYXgAt|*Ke&$Y;{A1N57Ftksf+D9;{X_%h)x(q&v7%F49DUiI5^%QM zVYHVGo4~UL1Jv~5+-GmmrU9H-CZdYpcuS5V>*Rm`?`N;>RN^7Z zzMd!|BTH+v*^uE@m32rJe9Id!>=RASG5vx7RZjgly_v6b<-qZ=S_zN{3!@n3ETcH8 zPxW|tqe^0uQ!|K@5TZ=F0Xst~X;pSBQP+`J9y5~k^y)5}$>bFmm}IiAr?y}jPZG<* zPDe*Aig{j?ytO~sBXkw{qUhAbsA}EKw*xJbClAfv^oE2ps2M;C)jl@S<@$aZ6R#3e z-kc}JgdP=vxGltV7^deALR8)cy^OMZ63Ozzs>}#e!j22Td3T7cKzdRFMsI}|PXu}V z13VVL7EY-bx7vwD&Jt-%yK11<1c0ic*r5@70qHwdyC;0kyFL5-#;_){Yx?ByPwI zt>X${+D7JHwTKq%>{?on!C#HgKujqgNcMLT5Bsckv3a}*Q6>_olrQ0`@H-1UL3k0H zlAJ`I+~A(XBab8?+&mk&0#~?05Wlbleq;Zc()987{^;Z+LS*9Ef*qf~9fe?C$T0gX$n z(94*uGqrU3GhR7s$n4>s=3yquye{(ubgq?Y>cF?jH_-?>|$e>0Bg8mR57Kye* zEM)2$I&!8uDeaXvOa%j8aFQPRh{X+@=~kPEhJiV}2)aVw!Qp>1f0Z^9?NxH7ba(Q7 zhRKpzIwKTMLH?GuPQ_)v6&JX_mnD(ow+-ZO$iTT|W*j*!(dOoxANtKz{FFy|s@2LH zw-cylna0(MkEF2zGBObGLKz9#n@PyN<9yh_j0O)*FxLhc2s!e&eIxK$(epij^{_4t z&DsZ6bfrQ>!qErjRDoxr|6)DqNCCAsUo;Z5Kt7ss3)vGt4IC9-tE&tC%bp#Zco#8g z9h#c8W?lsGt(bvFPD&6>YuJs=f(dg4+7V&}lNcZrrNEP=cy3vmvU2CgkhszW`Mrn! zDV*>!t5tYeq%>}{HF;5{wQ*Irl47-#;_Q8nN#$sfIw>WpYjwF31!UgccqTTxqc3?+ zyT_Q1Us8HaV>*?)tzs&Jj{jAfUb48@$UF0s-aD2A-s(m5_4@*~4#&fAf+is%f2K$a znSV{$O8G$E?*|Rya0F*|gi45}xq2*)3Sc^2wXM25NbyWX4bADnR;yGtH-ANlKq6gY zqQG}8wH(!3jef%5C)K<}Ys0KuY8GP)M^wjmJy@o5HWl~j4^lAoYgnXHnvgqtuX!$8 zsmH8Bq!KvYUSHf4c*s^(vACsGmFSdOQAo5fd3rzk*i^Wx0a#OM@xwvai_>~nFblph zL~t%cwTv=HpGtqGnVvG{@ccLS*y_gODUg*@3*UHsp%V5z2k3#fiN#c5(`b;gQ^!JF zA>~c{y}umJjiZz*kiK&v-L0TTid>JWi`AsFf*2sPk)}Fr1jG64=VPNeCC&WQSp+^Z zFM)~JU~B&rsRVc^LplsB7xVqd#L^AN6~Qzfmn6F%uP3bI9IPw-;0UHzdJX z0oI39O!jtmY|2s`i`cOSrb(27stPm$uN)arSj=}?SpTP`-ftL^Y1V6W| zSrnJv71?Yn*s6s+Gb;|r$0lFUzfi=$00oEoki888q~(Pmrijh#?U@b*2JqZni3wH7 z{Rq$iHaxMd!mfYp`S-+ak%oa1$FDEZHGDj_hT(b;&EAWd!!O2>UV67cy~>uY6TG-; zX;L~_>bm5jw1(yihv4omPI76;2us>sv<*zPUqef{)wRalS|E0ClnK$!_?M~xz`O;T z!54tX*~}FmJ+Pf^h&9E3^b`dp~v*QHCsskGmHxLVKNqloh_e`TTFzwR~1j zUSWd9M&}5R#W)cUh}P=+8?yOXwir6mq6ymJ^cAC>qDMFivs2Jx&-)AaUAZGg+pICP z&ITW+!>u07!FM3e+^C8pbDRWAe8E{dfG6S~Y0}sZy{!fv%_gIdu2@@mZYaOq3JsvW zK4!9rlqfWn!9bViYfwwhi^4cyPYAhT1bAkKDOXJ;BCb*NieT1ki zN{{N%#>Hxv9)(B-f+T70rYlEsmm%=BA1CHC@!HiglV1%3b5`@zWr2+WzCDBV%&l?D z(mT-jw$%;+2bx1RW>KscUFi5*F=$xS9V*U#T1+E6@zg)1&R&YN zl;abw4Vi2NTGq6mC17-K|BqJ8%iv#Gvp*?6=4lL$e>11=B6G(;J55fElE!gNwx-ua z=QAU>o-mM5xdR%E>aQ`iI48)fW~r4t8)VE2D}*aRz7rEctl9NJ0zVuAXw9j=LN&SM z?}e{|Csa4dHa(s(Q3o*Ygy1gd5Qao1s@I=-tvp>qoGHx(KMm_@=(@UzFjK-NPgR3; zRQTn_xDVGY6&TruLHe2t9YCXe*b#30rJ zO8-VgV*WFWI&9twR=g_%(e6H(w)FVKYw?E*#~+BQ?2Xt1#}Zmfh8&V(FPMmB5sqjb zV>5+-gp{klaPQd>_RmB4gN+#c7QxpKMy?d`UZF{7?SQzGi zOCst)S_L$C>Xbb<&^9$}%HV|o@e1w*Q#lXMT0$dRf1kJI;{w|oz(R}FSjU%}b$hgO zaoh2<$BpthhPzuiPdaq2!++sihwO8yerAra+lYQaSce+J%@V8{P{E#@)`0s0 zFE+IBWatcV2xr?Z4K3n1L1IQg&ti40xGc5-j3W;(6zel*6LA;bk86sdRSa{rO|lJo zJLP~FEM2a=dnhV*gagIZ<0=fe-7c1|wd?})=KGs_4%~rI!mvAncY`r`W~43QdRdF| zXoc~|R4PcTN=Qbj62_HHr0$QxAA@(b61`v(+5rj(bi_hcafD$$ajZQeVmAx#%IW~) z#-I6FA1bkSCGcTt>7o%49?;i1haOk_9N{)H-FzNS)efa4vnDh9nB;KjBJEP=uVWU3 zKvmae4m*jZ4K`b(yI5IdF^rdh&DY4;Dh8KWZ54-WBswY78A3*grEai=dWKXe>i)=u za<5#GS@YE_#2^q7V4{T$u-J#lt|mmaUw}1%1(FcHSu0Mm&r9I5#yJRS+#2D)k|P0x|jgWmRSmG zir*5sY(y;cf$sRSv5b@#H`835?*|w=^cYD%f|Lj3Q7c&Vo=Z4nNQ$D>Y0IMet1;ZR z2dSDY&Co=giRVjZOj~p8i?Z(YFE>OO_K?q6*3U^xUtmF4%a%Fr#VnooH*eengFElE zg;RUc^1%1kEspC(hFVTb_f;cNvcPQEg`bz_OE+_?vhdkL#HPGN7&>JeEu4BuWq(!K z>c0-u&gvqq5lUbgBd!UazfHX^bsNNlg~Qq-A6W@goLtGB&^@7^G; zBFPHbr(X%tuVPO=Ewceq;d+(Jr}~*zz$tf=Aje39qmz*|B_q)LojMqA1N&DnM&_?M zT2;{<3xq#5YnF$#q=6i_4>QP0WBhal*5VmqaiF{vc4A%vCMPuX+4@oLUGBImTUoCOj9*qt5F$Qj_GlvY0BpNqRL^U zY=-<`wa5WmD|UztyrZ@y7OxcFumDgc5z`yqAED+6@=z8bWif0g9gQCZqFTHnMPq2g}zu1zHv4PHmGLmRi` z=xZ*)qrZOdFA4<+(c6`pi7I|(N6QMnEct@Xy$GZ1sb zCOw=Ty1A9_(%ps_ePy3O52)b_yS0OPr5bhfdJ*1;*ZdQVUIvhpQ+(p?hv|>BJ9FeM zw576hDrc;Cw_|^jFgj3VEJL5I>%)IaBup#^ZOv(oMW>+}E}mNQWdgaQ-AKHgN;F3+ z>_U8b(RgmAC!A2$`T!Rco(uW9)TNtwI-J40#+Wz>mFFN>MnjvBIHU8%Dhk71+aqn* zA1)$i+yqywAh(%bHKep;!^zU!iOR0vDd^+7*q+0N>G}HkR{pQZ@#U3DtEh0@ga?og zs^EKVMQ#lsfH@5h`Xfp6Y~eMNe{1g#S1VwbkQ=+6a9fD1geT9OPz+0KqQ>}&khpUX z%8|t{mJ(&VKn&ebm#jP1Mcq0{ql~smFa9(NS!el7PLWu#PGR>8k#XlME2DS+Th*M7 zI=K7s#NY76{ovPSdD7JS?`+V;>#|xP>K*@f)C?f5lz;!9>i6_TenzKoLZqC#&Aj43 zZ%l|>q6pdWThlBuDqr&{u#~j_>_xFT=xo%@!&>x%?(AK1Z3VbOmcoZu=01|vtiy6l zB)k@N5Bl#}ANcx0UTcYA@sIhe-buVnYTYlKFjbs)8t2FqJ4NqryxQ!~0bnV?vdlK! z9L@w~TuD~Zhzb~*LR{aZ3$W(~FR2Ki#Ja5lG>Sq5^OxiVGUV9^o_SX3X&QI7+1*|Z zIn0;e{aJeDUXx4tm2ZQ@y=nvoAXp6ghOrpU`hu_{y#K4(PF{d|y96r%Q{+{8MK!gY zJsNb}n{n9TOp>R^HmxPr@beqtyh=luKcz zYT8Wg*D&j|`40XEbv|vkO(Y##Hz=nV-B^x!iqRnbx;|Waqlp{ZkJjeutKaBzWUAu# zk{dr$r=3TGBXK`e*;(iAMQi9@C|jg`@2o!6dG@^@V(E z`dnvS-3uGA{ZOFd8%Y*&f2j~2!n?{LxOSNRxnAEaK82A<@sCH=4*4_lIfWERO6Zv< z!`Ae6`TniMY+9pSEw-!O_w9`!t}xLDs)9T9eN1)X<n%(J+1=~x&rmSTa zXRVO77duI8fjcsR6J{ZGbM2irx&x#X(~y?vGGP3bnt4;V2Iho^v7A(|v?+cqS@^W< z*PE{8IisQoYrw1QF~6LdUqOD2h@7!`8upwZ3~4gArI*6p(777yVyHNtHEmVqK#3^{ z4SP{$-EM;8oga$&6*Rw^F0?H&f6kym)Yz{$@7dRq*JhS%EohDc^sN!;Zf|;a$Q4)UF8Jk zF)y=%toS(gnAg2JXM<}%P>%({p&nY^f&!xB9c*vsIS;5xh>>gR(py8knfaMl?Ul@T z+-yw`mQm!oQ-eGww((=frazc0=65&I6>f+LSj`v)4$--H<#}hA-b6%N zXKOsUTh>(Fz7#82sWiTfW2$CRvuZb4FA@$-ib#aCE8zyr?@?)n95OpLs4n_345=*h zm=ThyYzK$JSJ9`2oybo^Unv?J_gH#Ai?r!(p~>|wZgU&Z0%NO$L3tl$3FS!PMa|zf z>%Svv{_?(24hpJcXt?hUrZpsVPyhon2!~~IW@eWr){KPglG+!R?)R@Ri?$>`_S+!% z6yQeS*0h^sFATnrFuRV-4ozp@nmwm+$9$Rurtqz&Ekd_M&uNYDMnULXV=JaXkRI1m zgGjG*-cn$9Colt)%L`eiFc^+Us~_WA*f%k2T7-oz5{m5q(A;71HB}{&E2j&{EeCmp z5o0Crg;Q}`+*AQ#93&khuQ|MZ*#KnSs;E_a=0&pyuzl0s?F7^vr9B+Qpy{@hNvXSYe$++MOH(*%=kIjuAHtB zO~)}fcrQ|Akk=CNSF+hD_&dEh#1EBnACqrFNQVSiw^ZHHM7z2Kum({4c4<_C$GbqH<0r^4xRl89D*MP|Jx zcV_=6XT=M(_0J-6!yh^~m^9ApHA_n~_WmXmmo%I`7ysl*H3mXyo(yt1ijvITz_q+V zHAsYua+F&6PdRI(<3Jk4vIEYE|a za;mq8ihc}QSapkm16Rpw8ra>gVgP7hrPeABwux(EhY)4!Z#r~horrv`*-$vO@L0Sy$BNvfw@hzX&^vCSK3e2 zHc?Z_GS?WKOctvzq-idx+Ncd|c^5VYrBv1uec0$R&BuD`fmf|+h0toxY~0u(iFIUx z*G>gZN1l0fS`cGrQ_K#tB88xj&q}tIlM3G2K>EyImVfkRiv=f`H>4w*oL-+sRx|)}09bW!- zPa8pzSl|@Lj;*|W8;_2XRLfe*;WeD>o+M;TjqJPN;{{n>PqTGl?4Bdm=tNc0TkcQI zER+=~p@0@Q7TB!$CUX{O`S(^{IY-ZXGH#$sGYLxd)PeeRBKs&{&3~#WyoY&u(9#p4 zCDX}$>06Oe9j@eb<%5RzCI%MWx(cA`0Y7i}2*;9Z4OsH1dfW1ZYmhET>j#b2*CX40 zuhQ}h)}@+r?TGZ;Qm#UzVn zwS(A+$H{n+aifkXmeTzR;|(DPcy40E92zj1{4bht$7DvEj)L>sBh#GPm_PMwm+=&R zg>@DMFdcR#hemJfzHUbU@z^&OV9BRm*<^`giQt-{cL+BhASU5cKV=(WV8HWkAlgkm zqfETujVYDf+XFsI>->3EUtZLa|E>c3zZYbTMl9TL%-%9)OWXyGPLK2nXr+`Ps+n3*PH< z%7@8tyx(QbUWqw;JUhPkw?6-XBf~#cRa=qnN>+|?@sdV5*c@^eu(tLZ>y5DusZguN z*9c7hmbQCQ%dfqC8cOe~SxRQig(A!cU<3s-@YJOj+zx7P^;ge*t?7-IbeaM#28Kcz zPby3Xq9S>sVAB3PPBFPe7R0EPYiv!28cRDA_O3JQLLzsZF|t2F3?-Ho6aW_hR$S+* zXD3X)RR$l?1)fUyXmfawE_B7BjljIxv?1}PTqPUgJ`zupWx&Rz5k&cHbTnilNESzv z>JN-<%SNEvGE8<=YgcLwtf@t>y=(wrCB`?-5fY#JJAIZQ?g;u+4Nqiz-YF>wY?2{S z?J`VE&4C6hl>~sNpf%2JBq=es)X5QV2DJ$e!0MKYzCp*HXsL_tpQVsxuex|(cLVqH; z+A0;R7i6S;xo#cKfHbg#RKz(Z^0IN=;z#F0v`7aQgz+D=;Ye=53#5AJM7|ATH0D<2 zU%v+#KPe6V5{8XD^6^O85@PXz#eAsW-{x(Y6{Xx<5?ozMBluKEz_nbiN%zMODB`81 zDi4~-9nZTbN&0kXMT<|RtP*}PD{49JE15VFx zekFFo>#U??f3>99Ag>`J~FE z_9xU9^;&T?ww0`{00G7b2p`&TNqPWJEk-WsSm{0m3 z@TJBqLF`8EoKmBkT(&DIRFWqXq?EMILWxgjxn_w*Ftc8?_`c?=>ewpwAjeR;Jz$6M zU8R^Pb_kJJGbM4FY9AX5Qfc#{mf3h5^7fWfwtU-4*`ufrXoV||-uOGu8|f+dEeL;8 zv{}wt%Pckm_f6m}#FRczR7X&Z*-+^#N&ZlfGYd-L@PLbmT~kS_PyxNtH59M?z^hE; zNt~vvqXpT<@;LoL;84k1$gW2bIJ#PMCb*xtt;->iqo@*=Pd=Y`{pC5zS(=t>E+8SITNFzrjzvMZJj8VvktLZNls#@bd8DkP-GUibxSm z{sC47{R@bUy|$;kpft-ZKZ#1XDRiNuL2U^BDX=B5>Ixb^Q*# zK%vDfiB3=`k}n1k&9TjVS|k$A zoAbMi+IF1FrsIqoT1{_fowP{3I$WX4ntLFB+%Os%KNm?XC)=w0>d3`d$nZl=EwE-+ zOeyihJg7<+j0_6d4o=EPJ=E}>&M!q6$XFs;JpvpJ>EibtCl)U~)eF2(%@)7Hyvw9G zvX#mkpb~c3nCKRrgMtH`7l;7~{DfYY9Y}Um?#RlZ1#ZfAe4TFqllGtQ!cP9>`tThs`=M3)?Mfswwnw%-)Z&sAtho4^&iANgS5B zi8vy3C$WNh5YrH`Bx8Td=EcJ#f5DF0NJ|#y>Dt#Lfg>ecF(`KrNV;!oBI}CXmCUS+ zO}nxwlMKoCAVGvm{ogDrnKb1S0vK6ePE`ZNwN0!&e?N2mb4MFo!y;92$?2?GmQ8^t zs>=B$gu6HaNuagSm{m2Ka%Yh?f~qznkhBt%hUh-@*_O?7^&xMmmO^x3FX!;52uXIk z=*Dcr<=Ngf;$DnG6UU$`XWS+4T^E`*@h%VUmS06HRUaElPO!BwZvL(Vq~qMkUTqG`xJxsV>!+l%){+gt?ulYolli3#A#IO6s03 zU_8((@-}P`P=TFbGPwBl#K5hg?}ajdJd@F#fALL}$2%}QGg zkRp|&t6VD}dJ16yrXm<*u6m`a#q`6prOlML=5rF9_A!grY?l+B36#0=FvJl_n__)} zTSkb}E>sZ={g%A%@ddbSzK84tLnKgeKx6II;n38~pW{Za1cudBJx2p;Puok4Dnzax z<9ImmbjO_Vz`~6Ig(kn-XiJ3glpSjdh!ebkf?RC!v8R}luufY@RR5AI9$=8w02;xM zzEuN!-=ilid=BCA=iiT6R%KDJrAERnO?#dtl-1%I-S-^Az}TnpfI?L&2-4eQ7`FZ#;1DcDe@C5A`~;&3GIFYyy(}i`ZF>vgXj+A7?DW*wo*E>R}99V1qDcWPQ~}wIQ&Y1X&6=ZZ$F4bYY3FZz?6B|@x}67G^xttk5&FKBHeUv z_I?)2`{GM4X(kT^aAtF6E!$|%L^n?n9Qi0#StxeZt_e-#&=($dX;uD|>}dX(>sHTe zainvsyK?dHJS6b#p`d&YRooV>;1pi=#6$-f6u24}QyCuNZGA2rY(eoMce??l6+#_A zCyd9QT^7QdsCMLfd$u;K5%$7AxG;Oe47;#GNXej{8lt!6E=LHP)syCvFtyYkM0?X; zkPj3Q2B^tOMB-;@5Y%2Y6T>bld?4yDlCmhUx>S^5<*hkM;je=A^M@S3l)&D9cto~V zE+{%lsd}!t`KMqsf>_LN8D&+gQ3tqhVt$ZM5`sk)#jD4;*upPp7uM`J@iC_Wq_Ddl z7;YrS4281bA>E2+nr}K7a%9iSc5=W7tuHwJ6-EG88&)!_HRip_Zls|O1qX?}glK@6 zQV4PW!SpSh=g!6?IVOLtgtNg+?5qVr0?F#Tnu1k5=6#5}mc}7TbzLAZA0aQ>-K59I z_rgcPR*GY*&m=n!XX!&LP|*R~7vIx#Q#PgL9G&i+2k8Zk&Fak4p&-F+Ea|0{Zamm0 z_t4Pke@G;R!?3!0gc1P}3`cCVNh}@KK@D>kG(D_f(L$Ok#8dP6{tr#@YpQ^vdW^B- zl&+v9)owg8!!WnW;D9g919upZ3@!}M#I!Y`Un!D;Rpa1ZZ+!Ei&xh;hjzM-DZ>A}h zc-iKr%yN)nQ1;McN4ne3uRIMsCQ?|#-y;wf?krMX{-BavNPOKt76G6<6>nFKq_r1t z;BfmPp|lvqrK1D&%*mBWbS_;(rfQ6+i*u0Y;{@BrDgs9?IMGHPvggn72@y;?5O~U>Xov}Bzb&cF}%&r+f%Fy!*oy$zBT&*uyO?lz%x_` ztNTZ_^l6n?8Ijq9YLAzSe<-Ej6@j=U=kf?%4(q2yN-uzICeoq<8&WPt!|b zyqYnr@)h4C#9dD*QD;09Whs;oJZkxyp>if*Q744YeNRq(MPNB-K#JD#9C3%?#^Ab_ zgoIf|QUeQv!Lp2qWjwGOw#=879nTGC)+K;?rwKO(CGD+0_l0aGqFQbM()uUBt`_45 z_ic$~P~S}SgrH+#4Q!zPG`7#a=V)-t2i4E!-H|NS6@ITCbgRl+L#ufPm5iAb_WN)r zv;ywtwb&C=tKrRIv%c38qga`d&0@>LDINm>(jhgRNxFyGDhbtmqx?b?AkT|h4;D28 z>cr$}xB&|d3D+Z*#79d~3{>lK3nv(z0hxrPCqBxN=8b_KbTP?AjgnE8Ia_kA_~&dL zhPRhA->-~0$SW$cK#u*d-XHsc`(Mu?KU=PfdoL$34^10aPI+K#FFEEm;92Hq4Aw}j zOetE?5awcU)zYm137Vd50t$-VK~WQu6h)M7v(?MiRbc#aTVRS%OG)*L+HEEoSdtI6~?qbH%|Rg+Ui!Wu1iZ z$nn!we+em_^ai)$axpk=m}NRJ^?Ad-Ga?j*FCk4RYLKpDrHLj`F9d-ZdtgFo1dj;X z0njF^#12|y2S4d(VthhQw7%#RdLL~H2b_y`x#a{LFvbU7^p2lRDv3;I=q1{jG7jfF z9q;r@?u#de1F;y_Y=@bHVpoy|R;sqizzCYM6jPpNjt&-`;4G%VOPl3=lMJ*?PAt&_ z5bTt~jTBmErS{&A5RMI*R^~P<@R3%EJGSKC@mDa>@fnYgQYT8AP7m8vfPb_SB-oSL zrAh=)vs%8^c3F}=yOmg3@Kbh2GV+0P%O1s}C!ZPnHYg)<)#-*tC>Td5-N;;8603pk zZ?J>Vb0=}t1}lv=oHZ6k(Q2={VnSil;x($rj=*zyPrP?eXm%0%mK?pJ1h!&h-xnMj z#ZNdALOYESphfZRG+c_WTI8Q$3BWDzWC?P2)GK3Bd!2z`@%3*eZbvWfH=iZmjwD## zd!7U(8$!IN@^$a>F;TZ8JQkR6QfJtysbl=u(MkaW-GfhJVJ|RbHE%ODc%OaSwh59LMaT6 z(qdq7ky4lUFU9E#l!_G;3@y=nDA~R?9nVidcta+KBaVSK0OFM7@PCG8Vq11YFT_PQ z7~dNdp*fc7@{z?3A?ijc+iqe&-u^`s{E})W^l`N-P(E5dC&@d^hivWW5JQq$5REv2 zM~c1WQDt?n#!s842o^U_OH)1aC6=nD?Av>;fE*|B8)z*gTAL?7bzV`3XwP{PYg2j% zq#*#~K#&ckl>fKo>`$feNOjw-WGMgSF(Gc|$j0O4)bFTg$G1!;t$pL+w%=Ov*S{fG zh|xs`=_6SIh=a%NNt*8&4V<>o3~EvpIK9OD5kRBMGI*f3o_vAZN`6{3PAivl%N2f% zPGbZ#jLP1|#46e4CgY;-CvhY=WR4X;Tu$=ql#piy{W4T1R9$YhZ%^J2Crzzd4o};J zaro;UgupQsrC=6~Ix*qwBykx{Vs#gmV+v3Sfj`9!q<0aHSC5*jBXV_hgN!H9liqwh z_&L}!BQ|Ha83L+mQ9^h^ZhDO!%n}1JYPB52@9+RLV(a-xf7VuJHFlqB2$*QdIt5_g z1!X||9ivif&%7FyGhRJx!sz=Toa37sxEOHZ#KvucI}VmQf>me3CwEs98V@Xh_`ous zYsFkt4A?k2N?ZU!fDd78fDg$|5JQ_H3H#8BlN=Ft(*>WxtzMHnN0@7De`}Iknlv+( z42l!TbPmJb0z=3dU~KB-;LfGSMAI_XAZSaZ5Me5Cl-=D@??i773Tdih`Bp(Rq_!z8 zC=@b!tO(-7DCwH*dW*aAu9R&`+v-#jF`tNXbdbmdEhNBd$?m1(gBm!{lb)A~Jn@K+ zMlTk_ns7aZ-G@!n@k9j~Nk$~+{5jb>>$irVc&VP}99X{!h7PZIKkV{+5FOOdgPDfE zG^x(_36U;owC8t{ocvXw$cauT0kQ?za_5EnFFj&-Gn|MNw~)VzyKOJQD<&UWk`55M zK=mp6 zdXxQ7z4%s={M$gzJ2>>IkysB*;fZ6IQbJdP=PXELr%duV@cXdlmLWt~5}|lKr#KdU z%NXcK0K?M|P`7MWDeA{hvrgZO6PwO$$NW?>i4&`g2cht!B8_0t?p-vIu$w<@n;HUk z-^8ADBq5%X6-njC0RQ;TFc<{T#j>gUoW)IQPe7|ft`IF*9x5)mFeJmbmew^50!)h%(^ zEB3?r;z5u~7~kJg+>86iWCf}efMcH+dp3*(eI^ zv9X2|qc%uq@suvMrFrjQD#Z+sAuao^A$3E>HD^Yt3%a=j79+#1B@{K{6^7Cg#5QM= zN4jN%#*iyo3#Ns$K*iEwlv`bzjTXptdEvL@W<8K9f38rggKM)ldDQ$h+hFK(P#KAB zD`%fUTT&2~wE}qn0=*1)qf>LH?ouA-*w@qU7=ZG?^1lO z&DB>RC-vlda*)Nxmmctx#|pqXuNX$izIg zeFw|NPax9+z~`1SIT{^eu#VMvSJqH%(@05U3F-JsBN%Lt)Z~xyWrhMdnWC1;KfyL~kZjVuqxixTcw0g;TAs40nfUY9d!3@!T*>ekqVt%Pq1!YIkHW33vX z|B}Vj)y`J2T}l&8f|j<0VFwlO&7&DL9Yp@(W5=s+2!Tdgv!-P@2;Eav!QuZ&zVQ8Ne_BuT+E z4M4AUnk z#zzP#l3cT!z4e-AVc`-h4Z6`_5>@l0Q8kbUzqfmH_%KOX05KZDy5*=nVBd;yG|AKN zp5Wj*^nen&l4vn-*!zHP=elFO6JaF!UG{)FC_u{W1aJYYTbGZ*bwOC^UXFsJR)fJt zY&6D)RAeV(-vdh&!8{T>CrOPd>;wi*#_kNL-{U%{Nz$AO|NW1fzjp=7$;Oxds8Hr1 z`zLYqL;%)N6q6n%?E#bauT^J~;UXpmy4Thch(;uI=U3SmMe+}FT)b6uNuz2}-ifG7 zszwRQNoF|QaNoa20j#=x$1FaVSCOs~uyV_!2o_zW73e{)YH0FMM1>;X0%?wbABT2} zqzHIYs4gyVAnl`UO|>9J*8*hf#=!o{v;Pc4N9s5cf;58{W+UWqXHj@LVx7_IWjP>s z)Y~{@{skB%k);x_M*W*)ZxwEiKBmp|&a;`G+B5YpZ#?BaxYM|)n|ewMMUDvF#dgj% zl~iyW?t?m^kg>7^hB`pu#jWbKtoI0n4_)*uRnaS7okQGD{bVGzv4n-F);ZHumV%H= z75H$%kHn=%o+Ge(7zV0_g($^$TzIcQVB_9}P4QS{IEbJkY&WSVyNyp~mjvo}{u-5b zm{umC`x02Nh3J6%3K&|p>nY*4oErcpH3b&!Hkocsjl=mcE(}z10%PHIM~0ze{om

ToUcJqPQM!Z9Du9fDBRz5*dmOm=F?PW>!|ms&$g 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

    Hrs@W!@ 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�H& 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�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

    D*EENOuqD2UH)~r&2??#HU;^tc6i`OVN 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%0ATKCv{Afq+wq8}!tHUt z5B&zaSr8;ZUpeJ&|`<14-;wYx|I^fBtv-;^Ry&x0UpSt@-N>$nbMOjOGGi&M%u zG9@ShH6es_Z8zK79@A<{4lAWK7_3P@et?3ha$Qp8Sdf*2IZY_6IS_Cm4=mRwdP@;A zJ-T~{DzJJ`r}J>?*(Zvct+$xIrUHxFWz6EQJ4lbK56fE9l7*dI@G<#0ZFO1aDU+OG zZ;jBkko7Z=j;AZj)V+uI5vhV+8OiFW-35vL^(?c$zMa`u8Fnc;x)2F>)xZ>fOtD#i zDAP+pvw}n3Si#YNzS!PADu4*GXCVL=bZQJFRL~PyWJ?)onIyVkH`OIXyrNKOkVIa{ zLTNUl(=F1jVqssfox1@16M>P1g_*(HzVxAyL&?}CK7|>}RP)zeaymjpM$EDD!+=~sy+sCm6psTSiTV*U5zl+C?-y0oE)h7+&TjG` z5y8%2kKk@)tXa3k<<*;)4NhA`qq1ICUra8fw5bM@IccnR^R0WMwmex;1J(JHy)F4#wfnka zM>vWv`1}+FC~mpLtC>_$_Ss^UjGeiD50%n$jJ3RSt@U#B>8l|vLTnqPY>p}tEl$Y7 z?!-0bt1ttxj*r>OqP;O&T{+9XH|CtjTNXCM(58b2a#fNkl9yw1hgxT|e5?9+Fde?K zguB}8Pr1_m^i#7Hw8KyRkhX#Y(N@fN0u7XYsDHo7;5@td45@KP);%PW{_XM$B$AtA zyxLkEAOp^q^M=HX5iu?8Q>YXksnt7-oEQvtkP6+(N|;L1;Qu+zkL72zsltD$PE;k? z4_PTp0d^3kJS7i_-Pof+Im5>`8YUx98Vq(0F{O4(27^4dCmvuf`JU<%A`LuzXpX|2*&>4d08sS@5}wwDi~Ca?&oyJ!~|* zRfBSb`!2M3exg|N+t(&Ala7;z5u9nLK`A(yP9%yAK^I#aX znMK%}z!Dnio4hZmoCE()69sowG47=eR#H6v$p~9FJ*8dQ-FeXs`*fC^wd~SY$o?s1 zvJ{Gsx+7Xq(%awjFG9sG|APLFqFvf~#>FE-)vowPYIZ@n*6L!EcB5p?SR^%k+rR)W zOA^SU3+W7w)IzvVGVayL!mVFiBbv7ssUKr!eYDn5gqIcw^ZB%2iRTuFaFWC>GO24R zp@?`x+DtjW$)9E#6cC$0@t7_v9P?ACrZ*G2mC>0zYTA!nN#W7ppSmMPhI_nCnAW`s zg7ug`u-O5)1o@S*A%pNi0eCD>rP1XQyvKyDP)j%BeE#EW`H0)X;r&xGN>$-)bD~LR zg~;xYf=>KqZ(xZmEg-c7I<=Hem7P}8O^#g}$$Y$J$ocl60VF38Qy|KpE21sr-;%k| z_b@w804QmOh)Z#ExYE*2V4p>JwS5>d*8EmvwHU4%i!ZKw54znk?p->;)?VMhm&DIG9)i-FL2tO_SW;_WI8Qj?Xlsx~ zQe?tVf3>t7F!#ocfN2h7t<-UmCV2d8ib&vzIduXZsalKW?O@UA^ct?dx^FN57+xsk z{-FVI^iIIyP_u;O>o71(*T{UE^1R-#lFO#NMKZ;zC|QAr)<=Z0YT+_74L4`#)2-$9 z7Mhp;baCuoSsS@VOXS!%&W%$^prI#ohK5I7L>zK0tf2y2jdip(kjQ)hW;{2vAScZo)_(<{7nx%0wjJDc?zE4sYC3nE8QZjtidlVq=EWUfzW)%0N1e#BnTv14I5{ zqQ2xsE>Q>_A8*eNUr`T%N>nVIN1rHXH_#8A>ZhDAWfN;FsK7a?T9(b(cpsRMOH51x za1oR(?G#&Pyw+E+C@UGp)xl6Mf^{<)z4Yxu>W1~R6$&Zs3{z|{y#-9|ZxK`_1K~0Y z`FDWKEquPI#(Mb|p(!m6#Zp(A2(d8_N#X94@NHB|z{r3&{ZRkMUB5-^?o4&qN?bjO zT;@q7`_&`$Z-9X1i^7U^IchhD>5oqrg+|1C{gk0ObOfhGz2%$%)ZOV_Wu5BGzq(|K zh0R`EgV2YetbvrglMe8swV$f0jQG=;+`9S2+?&6LTZ3PVMN zzqVO?f39U~As$l}u9;UTkkc(p>py+>`QA;gSMQHCGXSct&WgG>>~A4#gU1|ORF<^c z*A7&zj2qzYTyq=Goh(cH$vX3K$~j$TGKF~@-F=spr~fo;gQv`!UxyS?KHOMIv!Q;> zh_GabaG78MoE&SZRVb|nUPa&TV%{Dg5)BiO@Gy4WywS~bAB6A*h0I0mWWNs#uj!9W zV>H~UyeqprGc^KiwpQY!+zqov;>9#d)Dg2o)5b(~(JuI29v4l+xxI3EkLBac6LZt>ilwwfGeUg1y5<3d z{}^WOqzL`Ys=r&k&jTJ`z84-;QnbHDV|0g%W2t9+9f@8RUyM+xWP4Ngdi2qjZZm9= z-}mg2S5ZsRAtoxH@mXon;hql#)jPpTCAxl|aO4BUV@QaCrsj>9tMdh(0K|=lh zvEXct$$&b%43k?4V!1t;1D0MvSo&Je^Ng+6B|ftGx6s^z>t1ay;HiNeP$o&mXfEKG zlh?XQGZXpYki`*f)W_u!qEqg2w^%I>_pXW?E8sYu86v|`u!Yr=t-@t$2M8nO;gWe6 zMZhO`2UT9X!HbjEh%)D+z-_Gago$I#sLD~y<(p(sH$%O&yZ)c|`|$L^`+KooKlB_QZXP~t#Mhwkw#v$dODUlS0YDapClQNBXJIlSHsk-^uVLPeLjvaP4vo^7(Bzx<2J(&bxS_&aY0)>d=$Ec!NlYAMS|SqL^R>u)D#+VN zM^x;bJvA%fOTX6?R8Qn32%u|ocb%VxP>$tUK@ZZ;Eqc8n)bL$afA)^U8(UkEOPgGg90J+f3+ zTzzVmBC7zz-(ykfZ}v^cY>5bh!ZFpbJqUQzGYbqF4SZzIZ?;$J zjfLUKI1n)YSd}=N3E*YVAUa4WKdSf&yi4^Zaqsb)868&?Lt40}j0R_;1-Lw2ZR=hs3E;c?$AvFv$)-SRCE|gxqE>+>|%hs12X8dy$I3URd#dZ zdM)z<+?#fFKf_Jaj4t!05C8B7+)iK(t4(x||JJuvqgA$8KnTbDR3#^>@%FnLrOvG0 zTr9P09)6)PrP15VmeE#Md8yWRHh=(BKyl)xdc|i#PbSd{NGsRoTI8?E(X&{<>z8gM z7g|4DP996&29Arj=@T02wX?jGd8&b0r0YgX^=Jh(bUg*;U9$s8a|CI|0DFUryJ5#CG|FtEu1Az z)57rB^ozgOhq_I+Zu)Q4eZHBcBCyCf6)>vOZ)fSdr40-{*3&G|C1?&sqBqt~i1_ER zy(R*}GsYcU@s)a{B|J<69;KRHm0d)GAps6-$lUCktv%;r@gn17Uuls5j7fCUM;>&Q zDXBbf-Uq;WK z2~nOb`Y^yx*dos3jirtPi5dvPQ1&G8wfQdx)Zzr>^G9B_P+8EmIbT(jO_nlO>f{R^ zdm%xc<)*{D`&ef)^SH1>-D{Bv<^3D$_%STOG!%AJ+1=*|9`>HKp21)U*$;9rInQ)H zd}*dr*z9iF9|POgMAUMKI4_vSN+n=;A}DiMAhgOwMv*|KJHV9?Rz~`Q_dZ5{W^!*BSymQ?y;|(SpHaq6qOx9rM=#Rx&~M+UuUb|? zvyFYG_lH!a_I%w8A#E!@mLdHD5wfA~Gu?v8YYL}+9I0R$`RZoL>Aa%H6}7Oe!tIAo zRVDdGD&%qXOV$=!h1Sr4y6^DX(qDVSg-AX@^H1+bB8*zu7EkI^DOkmH=7|qoLXlu+ z&jXxiQ4D(ra9~}B4MrM!Mio6|=8U#)U@yl7d3Ud1?-7S%^jO3TZsjR$$>eyDC`nt5 zUrAv~7-dMtjjFt$ws%oQx@Lot=7X@>nN+1B=>J-6`_0$s{S5ti0}$(Z%4Bb$qN`m; zjjDYv;^Wzd^DV9KIq-3IlYfK~OHmBGhQh3n*AH0qSMHjT7nX*Pn@tM!++}qCgCVH% zf|%Xqq%8A%dR8>%52b&N1DPm&eDZiKt3&MrfFQk@pF^a^!#=ltYDzN^=bvSDAiJt` zUL5FGpT!_?Drc|LoJI+9y=M6WHDEKu)B-F9-A%@(FQcoIAidbZJiHB+PZ|{m!bB6A zD_Yb*Qlr1l(t?$T{7c5J_vd(6aUM`PEh;Wx*PeE{5ox;~IbVd;pA0O$a5BCe&2}}4 z{V>$q$*lH~W$(ibq%*a(jd}9)`)9Tl&UJP_aZ7#M=Z2Man=S=y)uyfem0gen#A0Sy zMV(LF};8WQN{7H4Zg>s2bb zO(@3lf{|hZ!lg%nIpJ{Jw~T!xtCox=7&_|XHR`X>9i@SQyf+=P+74$D!=&_&vK}2% zz}HOYo%r$uvr$sr$54H}k`$4s(O79jK>J(vHZcf%S~^R$-?z<{CejoCKD~Nk90@Pw zEjRJ-+D*7nAghZy>xDZAnaMDWrs$h~?iraF>@k0+6j*{gr)S#H>#G?4|=a({xOuSML<~I+FlHqlRp+Ef;>w`zTcM=AU3ALpMR&m}zBox-YXVS*e|6rp3@;VyNNce{P0nCOuK7Jsijz23e|IkI+v1p=A3i zDh`hBc27=TYD_SSN?NkR7(lzwl@*qW1(UoyUmVKHg7bW=04rjQHV*ed3V+5ZKr(zs zva-Se!LH1^kb=xqAB-LlGozZeR9Y);W%b?ZkYWYhhqkox--DJ}TlA=R>r5);6lhU4 znv8f3K5=VvGg0snR)juNr3{!x3B&j)!HTBD__W8=d!6tSbv&dk0b2&{?!@t*-YAqN zf3_6UXZ8x6P%dLu=gIMIrmhltYaH*3S-{|KsVGZ%KR<4Pn>H_{3cRyzx}9zAZFQ8p zt6`6j{&8LDpVk3x-PvrcQBj2;#} zGE&OHHVY=4Ly6DL=K!9L+00}ESCN^28%win`9wQxLAR3A^!+Z<$$G&DLf-;)p+5~< z0D?0OXf)7EPK!65G25@_OlTqO9)c%F+)cUX$uq6qNnvCLy~f<)K$8{-(o{@dm4lby za`P@S)W5>_jyr&`hw|N$LB&mGYcVt+$?^kfN-~oj4|5v1U;AswY^3)G@yPmfXP^Q9?WO~QCVgOj%+iECF$^%R$|z4mnQ@V^xw1HASvx?GWL~5Y?9;i;HUD^PRyae9zns9dY>kJLN{Ji^n$`gg0waEy| z!2EcsL*LV!2*P&#s}J4mdRA2cBXzQp%Z6IhF(wA07V4oxcU zVjxHEiheGJaC2_4yw6>=-JZt@Mop{U&F>T}q6K2Uii zog}ScVjV~>P<`C+CdI@e(J?<#ZvR3(ekgx%NkZ0FbF{31P7hD2h@`?2jP_$KDc$5g zrfolYyf4X^ILHN5gy1fG0Ozylb4XZx5xQC&2Cu;?D1=xH9gGy(q-T`5gE3f&2iD^o18x&gW?ZLxgwlm&hp}92XMxRvHNkT_ietF_8 zFJ)K+xlt;*ng+~?@acNLrUl<9mN1f3CO@2FrJRsa6l@d&A-b&-nI8jv800FuaodmA ziuXT;juXkYT8ziLV=Ks!K#?NbDo?^odS_Scu4_-x1&x{tx3yF;hLmd_qG1i%25t3S>kctNc7?(VKiin{R6S|dTD@I}( z`R;*9?$g8|`Ewzc^2&is{iYWCJFb{pTWcQ$oQkfkuPIy}Nk?4IoxJ>_Up}#_7A@%MiWT%&&(8u-GKqk{J4vNmp`Af3A`6QYO z&KlNq*h;A(P9-k^r?H^7k;VwAh0J8%UrS}a&%aF$^7deG0H3Qg5{4QT>{|BaW)OKa zO*H;Zn_8URAzX+4-z4tci3T&Irs^zPg%nBc-`wSnpc{l!RpvZpIVe z%I)$Dqx0%oJ$F*BV`n*u~Y)fa^)G4c$|uS;BKL+!}XU^7w~Z&fJL9+EQCbS z>DQc_LBXWA0=jgE2Z9N7 zN5cEi!qsG;>m3U0v;F2vjPSHrr8FWlZU;1-chH_vyDIfO*D9fk#SqjOSd1~q81Yyk zdEeMxM2@9-OxRXk%r?}2XpFv3yPG9;Gsl+PXu8+$`zq7!V(H?~R7N(^cBMi#N)7p3 zbgsM{E^YEbA0nBN9)0|OYj%S$nD*gWEw9uQa35n4qK%qs*QN&5w?)I{qx(HlA%z=* zQn89y4YXfF5X16K+01=AWKuV}34Q43P491WGC6q?9)&DHo&7Lxi}3s-U!>?K_?m)2 z3JeKA`&@@kY);25wz$KCJ)CJ)EQ&NKY};?O$uEva3Hl&(hc{ZRA#i&TNH~qY;Y9;N zu#kFDwI8yyi*~vw3&=*G6nTqm*o$m?2V?$XYLiEWWG{D%+k|3~v*@bCO9k<20#N0` z6tyeL{ZyXM9vd|S@BO($jdrM=7)`m* z7BUKkP`jcb?hriBFe+Bx1&Qy_lak##<&)1ylWZBrg0%Cj#EPd`X|kn9vZd^7D_=m{ zor)(zTwIR&urV7m01G2Dpi1$|R@ zzfvdS!qYchnlhx9vaRj$eLr6qn@1TLP1RE7;2phn?k%dVKNa*l7KcGed7SETKIH4l{})d%h+T&50u!|f(SpZ*>ky#*;JyS zgzka!zWVC2pZ2HP3Fe*!?s@a_%1SYuw^@VK-|Ini4Ho(hwN*%`)y)-?brzK@Z#Jy_ z1jTFFhyzlLRR%On*650nZc1xnv_>~yCo&kS=VEr;NcY9?7D_xO)8SNwp!Dy}-RzexWi zh{m;YT~c!9xTQkXS*)j=@&(daCTUUfK77CZPbMmx%zBqQn&GX!KdgQv%k=%vfBwqk zupC}&Y6%+qbn45JyN-0oTnxcA5P-fIj&WmVWv2tL-acJruxc&$K>_blUnrt3;5_mS zX}l}z6&OgTC{2N}mgXUisjcX1X&rUq`?1$vu?%)-TIP(M`JQ_(=$}8aqzw`l3zH&* zvczMC(IAqIwE2vhtP)k6^f8t7bQ7}rALZ?6Cg;N07tx857t6Ag;A0fh3{UK3Hc@Eb zG@2>RrS z#gP4DW%6;QyJtF`dVfOmhRu?rMTg>OfP5ww52MeE60&;+BTYkPYrx|^O zH=btC&=2u$pN%NM7y3Gumv%sgju%w#ijFLM$83fxejox%80zWRvW zqVD&53GcVGovLS1wQ5DqgIK@aK*Uct-rHW-v_JP?K!Xp#)X_xfTQO<2>COv? z@hi>6(A{;UvrBddCL-i*9l->&gU5FZ7x7cc{q5on#xAGf#a)C$wa}_!Poq-@rFjtk zax4r~3NEv?IyE8zzLv}#if+%t)l+j;cfy5^jBAoie8%H9*dQcwY6YJ!4o{`DWQF-RvNGiLo=fbxE z;+nv5$bV+!h$0UF?@e~1K`hKqeOqr2S|oD;tEZ(X0^<<7qex5vkQC~(v=oCKe<>g#az`SV$OsGqXo7U9~9?w$*X!Q=2LGNJ&0fv=({SDp#cwXNjHvEr5P~XM}oF^ z?YzmZ6`aE{%W{x_GIx$XNQ4(m| z*`6!8p6-v4=!84N(%3Y@(W)DZBz?Sa%J&eTd(kRDm38b}suI#u)5;{gLO(DR>QUGo z-spe-WPn`CP0LLcuD&G`V?s{zyJ0rxAwe1VYbHHUWAawQmDqxAD#rlkJNN86i}0$( z9qxr=cx$?I!06s!XttWURQ9kGao?Z}X`N*l(vz;rIqrlz@d;VWq+;DG8zt@_s? zx5b@fS%QN=G&NWXrgwKZ6kgk0HGbi7$|9XR$JHVYTDq*vVdI5#*+Dg}5H?lpysF8h z!TIe1AsJs@vpB33S}Hp}}DVY%A9SR3cjlZQ3;ZWUK;=}n0j5Sel_Fcyq8PXTe@ldISTRdrf3aks06oo8+R zRN`7KpGu7_r@Ulo|v}ZC)Yj7Vt?Xr7D&4 zYFOL{)1Ghd+VQ}ZJza4Qv)Mx}9mbe1LOt|njS!lfO*ZcpG9H^$ ztwAVynMU?aSli%_FYLeSg)mABn4Ow;;8%AsN)p#a_$j_tHTdP$@p|j*f*!=Kmu>_A zAER!tcCRUZs=cu5{9}Zv>K+xMLmtu!s_-OQ}V z+ZxpD>2*dfPQF|I-ok5^DYe{#5Fli!0hH)cC4z;cEwr#sLghUzGxPre$hwQ4q3fgPj=txdh1Nob z^K7!l%xCeHlH_GQ15!y7e7E?Q9KrZ@`c2wDxc>|ImO7mgMwiM-)bF6oW?(@p*oy1l zn9%#z^X?fWPN=YErwMO$*3!Ty;VoQA=GKbm6He&Zd!w;^rPY1T@AHO+osr9ShhW_~ z*%g?LX?M$R@m{>h)Nc~O}DiZ|#wUrlrN|BITH1gr_!y5glgU`5VK3)k_w-hLqRg6dk9^fZwXfJ#~DC8Uj^rk=9$>S zAZNB}@4|P27U!4CM%9&p1>Z`^Ze(2>3z?BhOlA;V|DUL+M7bEiqvJ(3CWwhS!Y|gj z_++6TvI`3J>BxdPEJG?yhNHagM>EISOAIe}Es4iwq~s>(CS`tb3xEJ6oJ_wv6Plmq ztfnz}JM%D}(DRBTe+X%b$h>4w@cEPKPVP5LBqDN$q1)>X!R+$v_*VFAjC75j>|zKv zDZvM()SQ4El6A^rV^xFL&qyXD{pcXnJlyPBY56$K|0iC~?j@(4^u$fUk&UCs1JmG3 zuSf~_pOn{edEOT@4bMvlW%E_GseR-duTH0)2!odTECPfE4!6#4GvVohk>HE3n^_#` zH}e;-w4gCd$l1lKqC0RJZfzgVE!Z=32Iv*uZ-(knQCII+Ra>#wbk#4*onRx$7}0Eu zWw!CFUr3r1Ouhg?-Iud+m_G9S!||E}_ZKxH9PvVnqtuYrS+o!G>Ep~P(T{JsW_K*= zB6>_z4~97I=noqB2&fYA>a;}lV}brSAtwekxv0d4s`>*Oi>Kc z-zi?hpivnl953^lXLxQuKtP^PKt(S=&%?t!_W?|f{P(1SvCE_{%P;G3bt6WYkVX5H zj~?}6#&nZ)_eEVou+rC+0Z8=B8)pBI;kY`RGu&g!14_9yl70mRTG)`NB2I6--;wr$ zf${>u1m+bJZs^ZtzP~5#tzq|7BoIxWZ=MOkvHhZ1Nywu(kTZgB6tWK!-M7lBWba@myEcy(aclAD@*NWx9t9}<)-%OQ~` zUf+7+DWl4UGo@W)-Z!a8K)zaAi`Vts3nBs2N1NEL#&m*2v4}h#YF8^#Z^5yWTSfB!+k|T;Pt)x!}7`jQReM# z^6VLBs0~E5)RcV58eZ)B|a-uatUSZix03DM3-ib>XkBUN3MpMN z7tRxA8f$#a|7I zL?=J>ZF`Kur1m+BW&Pvfmp#4Fy=L^>1TH*;)()yxx zx!||{t6oA$*$h~IS&WWI+6{EKZI z+p~0zSM45?|56#~yuz()>giI87Y3RL50wxYma@#A&4}N`T1wD$4u?8uu<%>5?7k?% z-MTd$hx9a$L|cXdhcR{N&{7yh_NYr+N_`6;Jb6_^==Rf3+Gbxz^1)agwsa?>nk!ca zOrKm@yH!ppWK%B}dXF>chSLmVNogDz@W}c8-+kKK$KUYKW5phdn&%VmAuQlp%D*#m zaK3WQkjkyi1aG%FXTH=jXW0Krp)vZ$LV#SZ_@&69(n1hSB7 zyE)$n@yz;ZcYQw?j*;96Pa@bu+?h7XyGb?)N-gO-=WN}cPopADyoZJ7)SuC10NwUZ ziL7{e!oT@N)G4?bq3dxN2iSk@yK0v!hw`ng+~Ev3<8u%*{FhN=#nZYQO1#ir)PxTu z?4lra6{QEoYeOL*IjNL)<-eTFn>t1LGpI`INweTgrdDfP82!i~0B;&lD_C>gMKb%`SbXNDkZe$Kv_4y!uk{*#iQ=b@ zFOLczCV+A(z4e3ft|{h=Uc`BH%?i6Wl97@H@@=m{gtM*l_93NM8nC^v zx6nzD6DyIWjySt@=*R2OR79grg{xW>1*fb5zcDacYnnwYiQcM?+P}dH@M_qY$kQy=(G!aela6-Hiaq6^Z%tj zP3AkF$XK3qlPvbcpiLb;1DjLn)UQsHyH9g_#(Qr|0rQy^lAyU(mVEBH{AuZ8G^@kYF^V5>7d<7m)Am}g?jBA97h zWc{j25+1bQ;`gJE%o1M&Z4eWD*G;{$GS!SoxCAA$MHgFpIuECwL7owy{J3&Z2wgzp z{yY1Fb=9H+Y>}-qR~9YT;P~U2s`NbVpf_2nY;l z9uv(mH=kX#K)?9tf$Q8pmz_wyp`(=N&+&YF>9$K#?mgqcbPbv9Eu*5xi7sS10`2%eYv%5#Zk zRW6_Jceb0$9Jw`pAvbU%;8`l-N6(g8r_&Gzy84asfZcq_@m0QJo}2w2v(EDSt|2QCmlm0i z&pUx{{FLlORos)_=X&8r*D!}%XAi|wjL;op%0o-v(MsJhDY_UsnlACKwGf2S_3=K?SQ8aC|Fvi*HW{dm=}%0?9!zq+kvFMxm{x*vbn66v454?g1k_OR*2ExYpy z`2eTQj7ie2@)T)t^E&P9MToLhaA}wP4%wIRA|$CHb5^Y^zmuD`d`Uvr2bA7YTH@zj zlba|SoB7PB+j(aA+w*v6){#FgpjMIOS2di28dPkiE-1Q5I8*u|CZKwchY)__I{uo|6}+IOanxWJSg<$>)%ddD#(bhPoJSrDl3-!R zE!K8{>s-RiDgoh;|M4VKUj|B}v=xVOKje|)5=>3C@a&A%RSCauvhGmI*aTZ{+F@0b z7elkr`o1S6hMgk$ShXm)caN6*6H#PP+J_`?96#7SQ?KC>4*({Zcne%xytz|0U5mZs zriq&fR+1Zxg|$Q1XR=Vcn{3VZt3A=N*&RG6@Dt03*zMT%FWiP(>~|~nhH=V#8O&}b z>q(kzSz0>f2Fi(Afr#2#xD(}J{w|UO{4~zYc_F~~z&rHZ7qJrYz&+$u;CvF|goENh z)ip_mVA)sF95Dwhcy3z9Vv-4>Ly2Cp5-nAhM_}@|oTfI=>8b8sIxFV_wyvBk?1v5F z-GFyB-luBt>bS8%h4yGSo{#m;eYu{!ae*MEgndJ8dtHJ9oAT8+rn7IL(kzvdA_Kbg#U4SB!Qj^0cNk zmwm5*TU8>jVe@Ye{6xb#R0*M}1n8zYrqeJWMG$=w)SFVlc$2)W?j$(cc$A&0>du ztLoX1rK!*N;{^9tKEyW3sg@q=rfBRb%Tg+~F;D|(dv&HdkyTm+LKHXve7tt&29=I< zIU*6z_E=zAp5BloyfGY)v{_4EJLg;(TX3V@51lp-h8Iv^PG}rwp^mee@EyMmu~JjE zS#WQg8pok8ISE9No^XR5(JMQwpjP|BlzCtm z%}PPG&H>)A+}3PGftS0PR#QK>Et&JqJ~NSq_3lf-E&tM78iJ5=EzY$dWW9v#DchIf z?e2E9Zu?^hj!=t-Xjybxa}<7fG|5q*I}ukLCz^j0vGjAui`=omJzpssV=H6c8d zPxcclnG0kgPC*qRSw`1UW=PAURJQqy!fq26FC0P+*toLH%Fb%Fd|twH%@u7+fbyiZ3P>c!tY9SANh|A1 zi~37L`{h7;K2A|JqZ`|$It%6pRa=eVv0v3)vX=wqD{;4IQ86FVNU7)pi<*CX5=?L= zfZhwaw7R|`d@H^25?Y1fFZFT-TXBo-^>w)6tq#^d3wD8K6jkoH2PEIT%iEHeJCeYi z$C)yk^Tb)3y$YwG3~@AfK_GOCppw-B&+S!<1#D8S9g2383#;C;GPW)vVcC?17u@5} zAm1PAJCa8s2u~P8?~05=`AWP*Pq6$nK;v;Ux&l;}Q8wj#D=J4Y6xnk2)1(`U#g{p& z1~3Zg#ZbD{wUpsTiM2l>_faO<-gn;C5j2xDZ5wDi(Dy64s(8Uzguhntn7girh^ICM3>aYCNDyn>9`M`CG|yC~no6crX2Otf{WG*GYZj9N%#G zl>=Ki)ds*5KV*K#c?$*jN7N8%M)jBA$cFR*e2Jk~>Dc^)tQCzMAN}b&g(~cnseY=9 zPkHXX*l~n3@B(HMf)yCG zHP57jX4*Im`(jep$iuLJ^T^yn3dXd-C=pS|54@V&O-W)-Wc!stm?-FgCEDuUR(zuiwP%Y}r5| zoDv2#c2bvIO)^Zvy_V%au*{mh(DM;M;TkhC^s`%9v!9A5acYMwSsSm{ zP$L$ObGW}y6=Q?|J+E%=UgjohNG`D~=#HdTb?+mrJ5<+cLsxl(s`DsSav+y#h;K4` zjhJ53*_14c%S0S#z-21b< zbQHI{VLt-nB)w}CI^9!g04zk3<-I51^aduYp-=}x@^1$o&LgrpV?g7LXRpT{IXEyf zfbTZ-j&B%Wf0st6KQ&~vLQ|8pC@xEpI+h^N0hmvAI%k$YKEb2Eu?eni3bsNvr*zqq za}{oO9$d`the6#)e8<9@+Pj~M{T5#Q$P30LAdoDK%^=fEB4XLMk2C?FCC6IMB8w4V z!T3f^&HfDdQ=}G@YUjbcIGF^P@XQP~+u&kRqS&c#zxxb1z)&rOUg-#GDn2St*(JD5Zf(>w)r))72MFS8p#>Xnpsv#mq-J*srBtZ^P|B z?0Fn9qAuuQ8t$2omV13s?5D&1(x?QlQ~j#k#}pz(sliyvPCK@pZX}+elW8qoeNDV; zfih>l^J{mjELnPM_dVXCe{KrGC1nzKMnkqJ)h#kqx2qzHOSC#F2utqErD17iBZ1b< zq6Z0M+3az4gOdc7=5|~f4m$SR7wJekPYYR;JbTP1qs4f>b;>PU?M#D1Vx>E7c6uKr`@G(0 z>$DQKu;>n2{lXCIE+vx7UA{U|Wg?Aw%|O`wiP+(Wrlx2`n(@T^7;7FEcI(eo(-v8# z2Mpi7uK5W{_c+W1Xe$V*4P{hGSSxIzk<`0CD{SJ*O~3Y@Ohc!fm=9mFR3ZVPG7oA@ zeJ41bT|-;!bm33^k}=ARt}V}ELH<}^@yw84f*UcT2aNO7<6*%8c};#|#m>^rH0g?B zK~NPfeDr78ckvM;CTBX83lTUr`-X2-YDVgG-r4CMx%J|bA$hXXOXpy>Dj+YRjqZas zRxsnut-cBkR7#wcW%;rUpC>)o9NO~?>i4^d=dqe5uw0MI{)5Wkj@P6^BR2i6zT=BZQV{;EGMRa83N;{F zUiO$)6;i0->`>>QXRM?4ZQSBNm3yN?o;7LUHN1j(sJfgd_7>YCM$}5!=-s8|Be%>v1};UD<{>mjmq_X1C;dG?1(n}86p#CHTZiyJg3EW zNNf9dN_c#%j?YyxU7Mc%JtW^2apFI#%~+*Zy`KFbeRHgy*EuQVKq!}U9VW@0yM4X1 zI(KIbd^1)TS?^UC9#7Fl9>cw9#V;p2*l)Wkd$a5X7k@*N4M_vEBYGG)0C^Q+i)w!3 zMM;F#mvk$m&v%7*6em29qE7&7+D`U0PsY=E7|*mKmyjPyqp%h7|?fw;ZWjf-Y&8GFsYPtD`>R7Q-jHFe7*fO~QAyYrjQYf(& z)Utq-HLS-+l&czyc?8+-gLdlOIGZ)6_M0}ROtn(mfpfw1iu8J4^T{RGP6z`G5Qp^# zmcSrXVl||IS*p1CjLzO@M4`^JY8GYBx6!EwAqZn))^znBt zAPg(+!EveT6w-^T+KwHlFbkM{WIHvYP(-+thsxdHRM=y|_f;YVYPeIY) z>0IN6(xASL@K?)QNwnlQqZ!Ixe^#q@E8`W7`>on2S5qee3gBUvYK!j=%}bSai(21Y zAtm7!;m{vB`aCFIPzmu1Z69w^B-sik;-wQ`!`m+jb^Z6e$F~ z*LZppP57HkfO0t-!EZX-Ka+oOSO=Qm;EV`?H}ACRWW2}9im_r5&;^UzRtAS-1V9jV zy#2d#x43aHKGOn@t7xzMq$`DnpVmj&Qa@Rbeo&mHT2wFvo;*_5Od*~HcM6+oPBT^1 zldvh6TMA*q@HRE0L-SAgUFcSjR$Fs$CMyg1Ehz(YrhsmoYrXHWb}ydP`anC+C~0XU zp7IuUeyQP|=qN}d?K{dJH^^ACZgAivA#bbKp2=hGsI2i-*6`q4U|cJlDbXu5@!MA} zkcMNE!n=oA0uMd|5l6Z$dN9dsxf0dx@i@HYbji3h5`@^bpL=8!C+3y1qH?hK0PKPv zy;D*8gYvm015clbFZ{)um{wP3o(g6cN)^xKDuZrMHDVUU4w>;va}5iqcG!825yl=W zTVasG*kmt;2IOOZducvbf@u2OY^QS?1a>2m`*^8T9^K86ddU}V7Kk;Fd*jxxerkR{ zDHN5r^qUVF3|7qP3jBMf9w1L;0YI7=-ZPqaiILwa@jJFj*)N-cHDhzwz`_d^WaphH zwO1W(T8&0@nap!MDqkux%i>u{o>pSkxjW8h?IR_ywQMfhYG00z9ztGi>+%QE7bTZ=x>JH~)?W!^U%kREpVw-22O@fDWP@RsNeS&##2ritzAsA&nWF=C$wW1 zRd;3;7!^ZcjWlMmjvEQPaY`4d?{oR4Y@2VigF+J3%AdEUGz~Mf27cM`e57{F{cvuS zovc@vRI<`X14ex&_Vjj)MozynnWq4q)ifq)&|jLd&R4q^+H`6E)Dz<)jFuUa_s8^4 z=T%NeeYbOQ*6?F9A|%f_G~Ai}suh4)s$zVfk*qsS2Ni?V=pVmq;%S_gJvb-SYr~n+ zm!kEl`{rZ~+`FMM5_4VY3!!>g|3cfLQ-*_hxHLPVmM!1?iaSD5#GV1L#VCZQie}&y z%e~Ov%ZB@&xx&gWe9Axf+HUK;voDNpNmux$N+%h!cbrWf731ipyJipzs}&sd6B--t zyyr~{X=+jTIK~#vn?vTnF*_|ydOWs6DAWc~CG1$kBg){}!M(%HzHZp1p2nsJqt;9h z?0mpmlc4=j`_oHApUv1nG_Nzs-4oXn9JeT_)L#9z)zv5^B}rM2L>}_W7>APm?Ri+L zh4sNJohaR{j~}`_zSfG zb!|x7GefI+M&#{I8eSG#6T)l}8yZ9UrvAB}gqWItpmVqyD1v!?YSA}(lfKwp0 zV1urfN_Bj|(M};jX)MPR-T3{m)F%Xzn|-DvQw4xg-l@nXhu0007M6|yh(2Y)40!`& z13k8U=FV4$7Vp$n`Na1|RQ{O{h}`CS^^tZQfGH-n)ygd|K-+rET!%Gh9gRRD{k5rv zR+}@@pXR(GPuta3#LVoHq(kdqOLXJ*`=nVd(ib5Yb!sN#G}G}#3|Ix)*Qib7Nzy`O z3X4}0RQ*1zV*ub)SLpp%xp_027TaW#$CK8b**{J{i?qtU8L)4_yc)QQV$eBCEf!mV zo@X%%y;bEdqkh|1JXY7zpNSRZp5(34nx4j39Nv_D^)UVJx_iUO=`e8Q7{)?^A>{yI za2#(teeQ^62H)|_H9ZC9lgyaP?FqxFjawD$8^FJ$sj$k0p6%y+R}09*Ik6Y z)r}P_06tc9mjs|C?P|@Y?z?w4c=a3$QB0MP9?EWsa1sCgVV-P^@Wpdm2~ucj0_WCZ zVI9RSh|S4{m5z26Acmp8?+I)1=ibX^B%m@?$eHkPHQwp$RhJX^lati^*N4`P%2KgC z8q%RACAL8y<{ZK7-aZm^%wM z$qV2ia1N?j@r;WlQJJg=SiW>a-=-0SM7a@kq}BFK8icl1Ay*bRL7zHr_#&Wb8VP*? z%nq&ak*u;;@)INff6ctSKEyXY`rMn(^T(fF&mHHP%^Op7DFgXKKOUS@wpiO%%OP>d zDdTGw^OoXFiPbvDQxrLkQP-- zl<88nFCnHp81i3U%5_j7# zY){oB^&EWyIXC}=`BAy6--`5kHxKSrz9WG#>k~p{D@gnKX3ZR^5hL+QFS|k4(QxK` z-=50f`!n*+9q}`&z#~DDn?1CNSl@+FFmwDZ;xt9hnh0k8vIRC%cOra?Bs6noOi~Uc za6wOVk)~VrG22`(t^RHyEv%$tvs=ucfzfP)Wua@8T*-Mlxi=?tfh{ zWlF|MYNC6tOwi<+&^Whg6mo5(h;YB8`HQv9j1`G-^eyDz01wP^rK}{EAKaePk^&Fb ziTAn>$rPUC#$}Xe#D2gZ=u(BkP~caMIlQd=qhOgU8&^J>j8gGqyuA{h!?#u)tzLvo zHQkI;LiL8Rf#9%8A%u=7O&XIOKGwswQA9pYqAn3F7(6E(ay6iXW(m|JyOho3K)yU3z;mqEt_VnFZj#pdmfBGo-@xS*B-cwt##Qh{5&P6B)%{0~A-yU?M zWbvK7N}H_a@kBeaX%0&FbT14cF}aF6P-(XZ1BLLnPSLVtB8a1FR)2c@a_E^XpR~zQ z5urBiaCqBGejjQ=@CnEM*Iqbvsb`Sw)4hFe^2{uopLAtPO^~C?KLR}4^qdc&1XR2m z-SL&yHW^|+w2CS_vW=cj zE@rcv5832Uy+2A^!dA%$5{|~C2Wkz13UU{h+3{$Zmgr55e(nn=())3BYZF!vRYY-~0>fh?^AXG-! z`3S)~^+Xzbv>bG`+7iv5{*es*&{x~TV}8O>FZ%CR@+R*(pJ-`lSDCL$UPZPHW3oT+Fq-7P!ug%aSB6zjs%DwDZ% z+-Lslp$_HYW45*bJ+t6TZ!DUZKd|ZzbFWk!Dxph!&>(n+>5&ayY|dMF>jO^{B4}D9 zY<8^^Zh^Szw)~6OLkuQj#M1SbZv`sRMN&V_pcEZ^ns4Ny`1{{yqAYm`{zfM`E$Z_w zf_X~#_@|{>@WXr}n_{C1Q!yPjP3-UcVl~e;1nH>1sbrFNm`RA0pi`jWR9Yf?7VcGV z?i`pwZnVz_0XHP^2(}|Kzf79It{Xw})5kIYIA-`x>3IB4qLHP@W38)HiJKPs)~9|! zCA80Jm;b{X%Tn5NypJa&4Vi6amMki?_SP|-PeA3{MuucHJtewf!tn6&kj z{_e$9`2raFe#(p3aUAb}VmU#!l&+$0Zq4w}(4N;VGw&}X7+;$r5A4S%m-5x(*H6{q zRCNbWPQOt@nO>6pw(KF|5(T*sT7OhOh6ifqIqjEsC8LlnbLe3)&~-?0Cf(D zw1LMnx;lquXM9!SI-CGaVbz6el>+xF`MJn)EBdaWqct z#SC0og{h3~_rS;H$FS8Dh6DR!`B?J=G|7TL@=!btDD@(XUQJ{gGV29#WqpETs8k2# z7%lu%)+fCMS8|X8xqQlhY(SfGs7OXPNNY^~pqZ(u1t?aw6%{R|%tn3bO8=Mk!qlN+GRtgf zMT)hf7vwK&i_Lo;@HQN_oNKKk+ePkd--ZZHAWIk^afDbrr6@)<#Pqa%sdq?Mo7buQ zcd@#k_NCALi}F<gd_sL5Ce@Yd&Y=rS2T}gdq7sDttk^px~_q#rqCj) zmD5D;FPn56(ePN+_RUJ}ugSRooaTun=`qmNrcVJzwP~}W3BA!t+X1JU+6dpa$*4~% zY6n|IqoYeW8~?QpUe_B?3s2|6mN9p0u#X&qrxXb55`%x)JkqCZ5MwKKruG@u2UGEu zX@*McrWMmzP9hQ`)30W`HpP(olie-n>;5R`mp`>|6JI5iWnTh|Es%r`pjwjt`7@K0 zXmQ6xeUghvE*RWH-DF-`Oao=VH{dta$na~G?%~YQqp*l@uo@N#XX}Sbsno?~dcl|) z4eGcy<+K}b{I&(uSQgbDz9%gBwcQTKEA;%X!9YV1;P-YEN?AzZ6d9bXKhpHKEq_mn zXisTTVtBAOKliS{;KoItTGnwW?`OxTBUHA0Egp&G^I7e@rOD%xTK+8;9r>rXt={d1 z{X46KiNmi#U!HumGiQ?Ks^f0%Br!t-B9i4L`*C49CF-pu!NZGM6&Sgxn$AeAmwH@tS*;)(wAOl{s+ zE}CGIAsFwuW2CjAZ2pp24gSp^$<}jtTTL-#zc4>S&jcO!=IJ1>Qh3`2vE?zZL*ix7sbY4dPtC_R1=|I^!}kID5SR{){4EgT zpKV)&*h`k{io9>77?`?jdW@%=cl{h)68Y`LA zT)Tl3cO9J1y;u+?9u1_;xY3I&lWg~-Kd?{wcSgdv?B2b)b6L8G8$B+RHcv(aL6WvS zYl&fC%!i=8D{|jq^JRQTx)VBsF~pT4^`DQE4$`Fp0iOeCtS6%8{NWodbxeC)>T>zq zyI*m(@82%91_5hD96BUgPZ@HcYx-e4G^drRQorTd8(J3=UVB=(Ppg~c#$~Qp8xg`- zn*p#-77@TSCz!GXwFdx;v6{2Ux%Lqg_c9kQ{L)FuQJ}eZHvg~10=Q-?K)t?OB7vF* zh0l}5tcGZG%=H|NRI=eNNmOljPrftR@3+F}xS~#$nj3f4u?@kp0qS|~dcX1FIEgpe z$F|~s?wCIECITle-4{;T@@8Hp0`5BiI*lMxrnTjTAnu!j^CMpy`E&dsUXW?IY>|7x zjA(H;7@XGT2Zs;IfoiD6OB_y9@OPCL1;Cxt3E{d!bY_ozPbR5cSH;Sa_4LDSREjyn ztBBE`ZJ!TcoQ~4vh_sX~4fPA??UB5f?52u%8hA&OIk`W#(#hiAb9UY|dKF0~MCkQE zy2G$XpL=ig;|r!w>fQRkz6Cruo{jIqDV(I8KHZ7zQ2A}zw^=*{2+_5E+xxoh4#zl& zS3)`&cz;u{#2ytnE$f9mNQHn3E{)Pvi$nxFI%X+NT9&Hrl+LgmPUzGx+^{H*^&>sr z_dhFJJeGJC+g&Qsw)cko+gw@?nIjp?DEB9JkTQPT{;axCSkM~pSi$xvs_fNjJYwX| zV$v()XZCYvK6hYn%Wv?O(;uJrM#%a{Ba#;;J?@Q$S1K+O79Wp-YA}DTLcUDyZ&4uC zwYp3eZDE?_Q65F@iWvk{BBYS2$OK?iYnk*CZf`LKux!0%nFPoTNX_ohpALh!-W9Jx zOY|`1T{6l}Trn@=Yg;5q6R6yp3ouBGH(c;=}#&{{|qDpu=21SGr*DRy# zfz=SYu;t2cPHh&%(QV3*1z=I>>ZZngP8qC}!nqWDt_EF@A%CHO?m7~E|11xO$w63R zG{rKMwTDLo<-5yQ77jWvj3ut0ho(kIL7A|A82Sq&+?F^uf-f&so6%u@Hc`Lk(D(7&VRRo8zPBhXX2I2jn!tq|Ya7;cP}x zt_?E25a&{k4&FqvZh8f;cZlpbM0p zXGXF@H{9!}FTe(PY-Yv5xGtg-`F%~h0N6R-%g3693P-6-t{9cB-X8kgrWl`3HR?Vy zpmP1)nGDdx$b@gJfx7vd(UMKCu&y#2!+*HG(op?8>}vByHdL1ouxN@E(Ws5O%RrI0 z6qYvCG=c8(2V&P2o`XXQE^Fzmc_vYw6Y$XZ*dji*-<;=Z?gs>-p~8M&p&!K76@UPc zi8x`yqR+s`2X@VLwg>O%rguDW%RKdeE)Z3+E`lPjoBc%Ru7y%qCFiwAksJ@W6j@?; zoRH}cx#X$8sLEr9!Cjtb7_pv3J{obI8FWF^t3V3uTeS0tXTYt!Gyun*7;>2Eo=-cb z2$lCtlA`8QK&H%2g7}MUOnDksGqV#p{D20__v#*#s8T%2)+j(w3bN*^6p2nlf7PJh z2*xrL^TcTMGy>l;?W505x&BZ+*Z7c_jGKOvk45AwwvD=CI;+$f-|T3Y{;}Cy9nweJ zdn;}s7L~&ue!T9Q*?}D8$`PCXVgPVgXbD{V=3;xOR_XW2&Og^xXMWa!{j3U~v(hlT zIMt5cL`8I4E{GL<*j@d(S3S~5e>Re`>BRR8gz9b}$rP57&(VBJ=vl;D!qPnkeV(nN@Fo0J&)vr<*Z_t#Pg38D zyAK}}s!l=b2+_AP1I^q*+ZBz=Y8f1Oc%A>-rF?~rY#FwdZb6k-uHNIJ7Jh?h8vGCr zu?Zg&utrmO2D^EHJ4JcBkG!)~qn>b}hHDi(&T(9ELPu+jgJB~@+$a6_GeInYCnws$ z`o!w#PhyTy!2k@yd5dLODftIHsm6>y)Vw}0snT%MTOxZ?v6?1`dFDTMP@h#9HG(kUuWyY$5{fYYeMizl%Xxk^HnA$QBjp9ae zaLRaTl-A;|Z%AhTARv4~vdgq4BKfp;o_aXV%#ip&1k8MU{2N?o-gl6fXoA$7Tegpq zfEm*y*ia&F3>1S$G8L7{vZsT-C4igrQPW^s2g`vGhvWkBsy4%bl#8g1$|T(rb^@*m z9N<_LHvAy3T})JGha~Ju23=Xsg}&3xRK-3krtwRm202K{FBSz?>~`lZx-NO|enbZW zZ{s1w0P9Jk-Ga0|ouwI|-POfRiSY!{5xDTa0wiH`ZrgtQVpflA3!N#bUNLEJ?jn(g zr8Y+@(wYXB+(KL_zKaptRMl2T6^n6l=IIc0Yj`r*Pd+!JP$!6e?=3xKo#-_qIUg0=S*(x_`K?x(s0;Zu=|Y#jw`C)}c4txSrJs?9I!;Ro z5{liq0i=`NB+XF@1kh4y19cVW2}Vf};?F}`Vu!SNop}eZwL2f{fna1ZB(v=<%ZGu~ zg|0RpLMxVFYwP7~M^Wt1iT3I6wv=&0Q?|&!76!OZ}Rb}L8pT4HDMN65Ab#!!-<)F=s8 zKe}LIMSw1`ay9j{!|1oWg(!qdr+eeZJ#f>TLmF3qt+xF}%Pk_##_<##s4P`WQU6$} zkSBLt>=BXf-SYu)qC-=BS~Wun#l~G^IaIr)^pp@;!Kh3rBG9cdKpl>vA`;QBfVE)f{n&U(*DIwwPQm~}93@S}=T6Zww^Ad^u<2Wwk$)VT3db2WpX;Uk%dPbp73{ZZwaMA2W20xO+ zu!YG^X(E$bRgKm+U9`*EycR#BzYtpC{UndocnvmfiN{kQx;^y$ezc{ZqFSPjLyWrC z9BVRJ2l)l`MV=I@Ik+t1H4HHys}Gd@b7@ zTy1ux=`f?A3W)q~tJ7iXDLDG3Y8kVc2}95fratP!OHF|eeJ8gZq@Lo*$tR)cEBS&H zIDv7}<(Qc^Q!=RPb&m52VEn0exHI!>I(|ci%%+~6xIOS>E~)GvS0e7+Y##7;^7L-bt;14v9>=U>lPELIL4WKk*vkGaW(%AkD(^ z8=?wdeKA0k()Y*>r;WAe^nHWnjcHJ)F#2!J&=B)I$%2o;)3Mw@TH8N0<8!+D{CnN( zv})@XNb97hw4?Csrw2d=;fH!WY3DrEum4w z9Z)tGMSu`@0I6qAUS}b%rNIt8A(Dm0yvOC)nt(sk&Kr3D$hOh9nhWqcs{V$ ze3w@6xoVBV)tveBiFe@a$MCiLy*&fIlDb{Y#_*k_1M}zkOIsrv{VXWv;LD|3wtVFQ zy=9F;&f8s$1h_oST)PMkrWVt$g331>($%?9*YV{d&Qcj)cKp6g+fzRtd=NN?p2wd` z8w6dknlfvefmLGakPUjTIX*0yAyS0E^s)K%4KhvqAXCm+Y3{aWgC|7HJng^=9>7tp zCf6jokV&)FgmU0x09u5ydXzf3I1Ga&1tJ?1=gp#^KLUlmvZKv&gX+R8*EyDg=2#2zyVRoEongAkyt)kIn@>00S*gQm`9{$c?N zWdR=&{X-gvD{DhV0SvCB^7g&yX-F0EfOq2VCz80>h-5+93^odD-l34g2!~TcBoix= zca=7qW=HUI*^M=c`3rMON#>UeX7B-z;)-GVRl}1TVA=2ZIi9&sSG{O}Kd}=0&1@-j z1=Wb0)&L}|!znGUtt1V;I724^hUIc(gT!2r@}X~jC_L=PlJAy z^@HX^$ym~W?Q^-Y&;nfi;4e(@Ckd~S8 zO4rq+gBHDdKZhWMPWgW5@%E;GZbFHX{;tOpMcCtu18m8dx^>CmOcsR!Hz8pZ0mj2L zjF}{N#zgU%Mh5LNwjorpOJ-<|0o0BY*xwZ@ww_ zz*dLk$sfWeKh>v01x^i|9-qnEv}67bTe!8#I0R(wt5;CB*6IU;8T-mKqiVV)B4n znkBQb8CrAbH9;Po$X)+g&PeGVY`OH_M(b)B)8=4rneaJZj|{D%+Eg9ASx(S2yV~c$ z|2e=g*S~G+8ePXA()qvaAM|^K#bcZUL;qZL+pB+s6S_@2!2C59{}ZH{3h<=C;l>vI zr|VgoXTx!gCmSdZ?H>iLR>w3%djIu)PYJGO%Fwcs+JgO{_u}GG;uyfAKTk%5O9wo1 zpEB-L(}6%iT6vZ6qiN|vyAN-gG?D4UkIe?!o(fSV_Er-&^d4IYObAO;{m$i%D`T!k z-&PH@fZ`Kk!{lvj4;gPK1CI@dCYI{rLcWF7cwHQtR7>0ix+Fs8rX{5OtRtUwd|GTe-@JkPd$&>4( z2FI&%sB1v2M#`=~1ka3dE@a9m=ieNM<~R>NBHmwTFvpF_H-gJ=Hl}okZMz0Wz4lo2 zkX%Lq#OXqoBhf*D0}Tt0W_E7z_f(rP+ zH3;C$lR(M0P0NOvZ=dU~9x7znvnx?NF!iS~Uq40vkp!q(Wl-`CTB1Jam4<9ew2iom zmM(G!%VNkHsFT%n5Uu#Zl=*Yl>?`COG4h;@>B4 z&N8lP;;~WR{Wh1S*96Sp-E*a%P;k|=f*NM*LYySPa-zFRR}h!V?456B(QC#8OCw0~ z;!)(xkzQZRRyXR3;)Z`FrmRHR807oa?)G39(%kiWoLh_`aLO67t=(>2_ z=aLdv$ho;a493!aDDJRgzxIGyWA3?)md~6PSP=-_D%0*h4LvB%cSCs6hOhczWK2tUQ zoNN(tvgAMZ=xE76@^rGV_&r!*UlWdEfW78&Wi431K=7iJoyse`Ms{c4Nz04@*D=o- zxpb&Q0@I=uZR42$#{o5a`RY(sq^L&%dy5d|B!GGv?Gqgl6i65C&kq$uXAoeJ|B;^a z$&z9nfoPf`1sMlK(_}jhfAGR!p|sl5ME<@C*jE`*3k#5gLA>zvpXS4O9ui=srAV%a zm`O~s)AjyIrJ#e(2OEJx0bvuUo$%w6X9E0f`j*Q(gb{E_m6X4i0mMzDp>dU7CuZar zq8Y{|6N3%wXJ0Nf?cO(8Z3hL|YaMPaJs&^mdiC6}P=xGTx{E)`uFVgYqFE-Fg6Y)+ zNpduv9p`B;){^ddPBe>{UQ4$-Qk^nGjdfVaOy?XrCMG4exxCFE(qPa&JhlzuIeqA- zm19^W1xrTDD+<;@dFZ22!V5W4^5&bRevOKadDt7mE1DyXjaoBcVm5WaQx>$$8v{Vl z0qn8SUO9joNI=yEjziRr)0Sagtz>^EC3taQO#U#-HU=MPiJOvTv@90pVz3{qReY~b z`Iu&Yy7rh3v??KCRqOP4Pn@ z0D8w(WtZa6d5$~YRTxbCJ{iDfulX5TfgMc&36XPPf5UF1R8{MaP;)AhB^n2W zti_YCG@yKZ?o?iiu~ekYn}|0?sgl@MuHtC#g4Kc+=iB-{^3pRvxMf6|cz*M@ko z>>^wWLHh@XqE`W8v=o;+ZLDe9)Fup0%PTFI*o{ErvqST<0@wg!ebuq%p~XVA-BJ^4 zR0NauXPzRo+(0oil0DfGa|!1FCFMRSWr`x@J7BSJOW)0cUEit=TI+RnYoGwUnx|ZPQP)Ca0w4B<9m7r^7R*_Yrqvc7joN=Xu zF{Mrh2NuTaUe?U80hADc^=hgXv*=tOIvDtbr2}I ze1NBOL0uaxF5D1TX=M~djk5QlRf=ItoL@MQ+r_l`y&?6UhojNllMy@(7jmZi@)EYlIA8Tsg*>3KNyqp|tvPvqkq$yN9h zaAY2|0R!O~Xyr5zrfvZZS*cqTTUT=O9@LoI%U5FqQD}XaW8NTvpGzSN?SAOi<|f2B zkP>8lAc>kJAijoU)LwIWNCG4J`kxf>*VVlViU@(zP{BZ8X)b1BUzfp zxC^5K!VZN-cEUTK<|>Tx+}MXGD>Xn^!OybkCTbn%L$VA7dy#X#qMOdAvv@hAl_#}AKizpc z5H?xR+EY_n}8iH;O_ z*L$A>*p1%>Yz}TW%UZ+LVVX0V16~24&;IvJTP3F>Bga@ai+I{FVS^{maJr~>7c1es zh@xl>IaVYyKI-#?G=?;_28vzI6yq>X4;wj#%MKH7e55(@Zz8$TB%N;i`e8n`o$6(B zu_)qgZt7txobhv$6&IRv9tbhlUdLOD#~K=u*oPDZJUlp2AvaFoPUHp0b`d zk*Qmd2EYGYuj5Co4Zmi>29*rhh<^jlf3grfrwl|uGoo285#`BksmJ)7m1|q^FdQpv zdX`w?-U_T920LYs^4#lOV9QWQ$mKu;LYuOu3c&rpVbHvTSGd;M!d=k3q}Kl1vRUTW zo`kVXW?%|hhoB_$bR1=W(VZ1CloF(vd;r05u3D#a_OjPg?4hCEdhDpEXOf>5(=uw&MK{KGmD-9Ep+5(#9ayH;N zxU%*lF?lc>O?O`d^NSEo{89s7)Xk|auCFkzPP6pPujh1o+uA6{vLpLb7Qjy;9Pm#p zk+@o|I-WI5TN}&*v0`TS41;Nbb^2{JjbW3Mh+!c$$`K-LnxV8bIWV)qlr1MgPndP3 z;N}-ynAvRBQwJ8Fhpd|;(qIvEsV9+j*_X!? zOP|O@=hW7yIn5hJD&5pa?)(b89@*vX48lQPGbQnZP{Y z$A-B6)N@s0l|wb8H#G%LJGsx2CMZjzki`horw`bdb!p+yw>LXDsLqSWf~>qmO_Zpm zy_j2ETj$skFacN|xCM~9Y}}`(>i#%hho;i9lx&06JJh04lWyTHi4*ZGQTP=|)u;cO z&P`xJDKD-Ge6b318I*-5&hv&GfoT84v$+>z*s+VE_`c@MtFS21ci)hS3$<<%zgjT( zJb~fItiB_vwnoZOAZYueZf(;w|9h60DRXOKrq6;Pu?d$)(OvDVd!xw8=CV)M(nCBe zADh0P$zZq{onG!~|$qz9VG!;A&2J8&1%Dm${jYbpSaS_gYqjS%q0Mdt*Y?9*1#LAov z55)smS0@oe0}EugYo0qPEbj|$$gDwm#m}`V`JbCXAd%^8Xi` zR?~Xv9pZZYD&YC36xWO%2EUC@tdAt$)^@I+s?989O9A>bv|Wz-C3>C z;mbvZQBdkQTVT{=$l(2S{io%G)$RNg;ze;sAsMT5gI=m5H-c!5uxu5DPGmxo5}_7z z;Z#o3g%de_v@BLx{AazZ>mzsKG0x<1{t(c4k(ToZ_n#;XNG6H&K*;S8BZDm?=i>#} zIrz*1Wv7_UW<))G$vcpR&bu~z@^lZ@#uU;YOP+xpw|vrY5JI{rcx0bmXx=aiZ0Qc# zrW)pj(Opl$W>p-I)n{mk*^ffeV)^DQf$O0&_RWF&FNL z{gaU;c$S~Y;MhwKH1wN|JkCxw{P z5yUXH4QHSYc*iM-(EU0;q#5Z3G_4U*;9hv`FYUtjjj-Ir?1V$8C4pg8%*O(E#ynQ0 z3kZLOyYk>OQf}7%VyGOVFviK%m;VMx?$I0L7OSc`PHw5bIvMqhZY0_VmNgV*IYiA4 z#XO@lXVNuT+}3AX1>|P(%j_)MKE6J9=VkX`Yfgg=N?SoV(`M9mIM;2cv7wp!kOnqc z&Fxi_k?cvyenYLc)(?-y=wgxsCCV#77PTh~i1thqNoz$A(nw|?*Y#`@jzp+#IX^7~ z+tTx+9riJ73LSZ=tRG$NZEpM>29!`JVf8wbcO%fl`}l>TF&qyvZFf|67u-qG=r z|9=3J6fKJ{MM}!s|6^du#c=BX9a?f3EV)E6nVzwj-L}>ZQW-l5;c^OWy^OYBBlFBE zCiD8pEZeap_-lKve?JtbLpf06$p$ou-R@CDHFKsYedc}DbtPBSzEZqkSMWngdG{gG z%qdlM+Nd{eU2MoVa>;?_(EIVzy-@~MbtO1>yLe%yXS#Bo(5< z-hNJ4md1VLGz|fg*z1=#7!h(7Hd`QJfPY|AU`djxjrxLP)4d)-qSNZt;-fM|dkdSU zLm!kfIRuWtCrI}4ah9+NNeDdC*Q3Y(pw#DORU>7lVKaJHI37pkH&0W8K`zBH>E(3j z)i6vU+?xB6IOx<2jV%{;JLQj`QuHht z4XB;gpP}zE&bJ1n%Gfj!XMko_Un!iumMr^NPj;6ARPykY>veHq>s~9Wlu#jK*eb=<7IOMIXiT$WLv_lI zyQm_|C>73Yo{T!;?+PQ9I)Kh)b}S7EbY3SP*RF zKelwsC$iED(Jn0*0h(o1TgiF-VAL2KZHWQfFv;VohiJ4qZfT?{K-8pMmhQdX>9O2# z8*=cpO--c4u##40S$um@OBF1w^pU3Q*h`fg|Bk+h6- z;-ts>&h|Mt9OsFm92p8?0c)vu{!z>&VZ7AZth5%XUwUE$mKnY}S+!w&DC^u)k?@a`jytqv)>G^!Q535M$ljsx>+w-J z@x)M(@GnQ1ysL6k4YI0M*hW~{o63VwM_6EeJG=N!NGYi*b-ZecE`^|i(z7bjrIN(D z=@#U=k%oOL?o!M7_^(%AMf61BCYi*hYq)#T>J2qC>BHT-Gg&mgOAo z8x`7^hI5KSrX}2XbdoGEh}i0FR?UoYwEB|TaQ1$vhAPd3pQvd>IvU4A+Mv7Y_eV=6 zRXTlhz@!t-aXv9aajH3{;4wNQgkO{UFK^7WU3ApuUaugrbumqvIrbt|Evsw!JB_m8 zH}`4QN&4Eeu$r|W5AJ+X2PIjP4k=u`UDBnk?TB;6oL%%Lr4dOa^X5h@P^XFYIHc8142SN(4|UC5Y55}?OH*|9t;&tbz29?WsWgjUjAQ$BsF%{~ zLClUyEfB6D&7L$a64y1#97Er7wwct7Dpit5HaZ{(9i#|MCV+||PoD0+fbcDidJY4m z2uS0DEoti%pRr^kXtp>xsHFLNz{sq|_*qEQb2FYFCRr$C>uQ4CavXF;9*K<381AD& zl5@owL>k-AP2U3Sy%WTwC~dMZIYhOM(9agIx18tPe%M}9n8xk^Igu?$?{3tCW$)JW z{k2Y`RJBwxsIIS}ALj^1se@m&3XZ49v9Ai=%38w+5lxIVlt0vZk@ez?RGm)dHytDv zvV<(d)+-n(w*Y1Dq@kOgEsT?UBlF+iwszN8-m*On69S0Uta;{&*42@}`I@*r+taAP zNLakOn$>9#oExg6Y?KG-yKto7$3vRB)b9n;_Z7RX4kC1w9Yk~iL}2@7}^Z8DVC--qYLvU*`U8$#LXJ7e?>(6sfCTzZrKuK;}hR=`h`;C8=er zYyGlWcOV%|LP-J~0IY24>38_&#|Qw-tdeH!Z8kRQO5zZR@bK`%=bzg#QGiUsvOP-p zYLNXJ4ApH}w5irERQO7+DK0IKJI2aP?4T5){aiHr)3a;TQh6I{)r@0|vulZ#8g((R z%^yqa!b=0?yByVKji0HN?zAB4u+;$giSlOh` zUP21d>9RE^r4amV#{?kZ0rAh?hvZi3!ds2gzZQK7lvIs0C|TsfC&5}2L}+As3-EIq zu>Z8ES6_^;$_7C`qK44*LK9eoH;%lT-$kK16KrrzM8Q_g#@?Yy>q+JARYIFm5Su@` zlL%eDy6TV4)euZ>64OGGw&XfKTuDl}Jt!xsGMj1cw0kBYBr}FfAR#b zY92v<42e?3sXW7B=fT9ALoy>8ldI7*^5DO7v|;H}=2fWQY9zkDIn-NF;5=W{CZfvC z`3a(^h1k0Z{vh%AF;C1YQL$G571+8{1B{=Pyt=!2=uade%`Yiiw(_gkZ+}fgOT|dxPk(8eG2xH03G&-I!C)FkO7w9VDbdEjtPVeaV7^P4 z(Gg20x|I(P9AO|^T*idfWlZ8FGDhLRKSE^#tEh%*`Jdo#tH$yq zHU?k8Vbca2%-*tHDt*wYFod2d;YbeUMqAsq(r*c$S5#P2bc-o<+@2>36$D4*;3*+$ zQ|sJ#LAgmY4&*6V}!P!+wLVouSlr?=;e7$6Q?=zzu^)WH=|L*Yv`H9j5dgf zP@Xs2KqT+wKUtU1jo!@?6vy38Jq=rp?7Aj|$|4$~ zis8A`*Q0@zPNao%O}DWOaH8MOeMe?g@i)czk!bVJ7~30l!MjERhUU+3P2bHS$Tmp= z18x~uP#1!3uhc4+7XViiF7Z!C1I) zD9)Hb@F=Af4=oV&cQO_7AsSOPhRr}@d>Yrwk z%FAh`_OeOOVP$SsPZeIdYlcDE(G=u=vqVS#@V}^W;xUFM=mI7yed1sj!h_^PRxTOF zp=qxfwd6^XE2H2Z?CZDQt(LSRmu1n_$i%BaX{&}{Jjqz~6X=xgN5;`L2nYO?+bObrdU&^1bMcnlo~BeN zjQbjI!)li<%D*=I27lqKt8T6J>!?)gZR}Ybt-{kE?RBp*47D{c%86ay7Cpy6|0JP4(jUz7pz&4j^jqxJTLO!0;9zw*)oAWsIN*p!@T-YnW_kJ8)QC zS6JbC>!P`B#!z^N2ar#Z8hJ`H-g!u=@*yv!-cY&WaapNEAIzgKn5xr$0N^5jlMg2Q zW?v(N?ZenzsheONFO}(q3-Vl6Ot{K}>(c`juCjllh+#Y8G}+k6af4(8WuB+050wMh zZkJd~P>)1EDgK2jl!;^PWo5*;>Uu$aKw&rMp%XryR^quoSt7l@W^7Mif!>$@^6yy| zM)}4sVo``3ZQx>fsoN_NBB@}2e<}!=_a75M;sId;{nDTMGXl+fcJ0;#>9Ld%_j7gW zUIlT1jO2K-kWzfe=`3JKZasou-hj; zM6+AEdqEe8>FBKOU{^h~2opb&Nen9Yp7KvJW-3f;vR%y%i-=@1YM(fNLHPl86IY7Z z_&Cp-ee(%0Qk=~FIXw?ehX|8pLG%G*RN0#c$z)+19}w0WQ=Znn&KTPu8jFfgh5#VA zspixeiu(s)OT(EhE#li>%O?PzLv+IBp}0JL{X#sIa%A}<$ONpam~6K#2t2k!3}`W{ zE;q$uasNNmZM(C8mrO;tfR~>7s=lOj5t26;Jz0%Ea^pb`HBoQX%E+hS#2Mf^;3657?S-PIyP@6M z2q?;lAZb44M(abn;1cPh@`Q{#E_3XvGdWxxt&d_;6?y6-s)65SCdh-O?ic|rm^5Q}{YHCHl?pmD0q-K8fQete9+ zHH$2%&=Jv(^g8;=g@Dqd;yO%Ct{dA}kcCfzQ|1;Tt1;cKS#1uY4{+!}Tt|OxHJ~=F zzyJH~Ot}KHz?(~lN6tBq4m#F`XS#|1U?M#DoT`eGN8Jzo@LPEW@`Mzhso!F(%NzsC z%Brh*#?^hwC)V7l!WWb?xe_<}F%B7-2Y)s}L0(rTide|yo2fu|3Cc zDV!3{bsk#_;=P=)X$ZaQ>DlA68cy+i!^^lRMm#e8)habm`Nq+msYJ zE7|kiF{Kw;UA)o~<`kU@OqQLb7V>LJQYD@*7ay`}>6B)9pOA>*s>&?jqhco+UaOTq z2Jtf*`@8@W(HWUqx$NE<8~HYe!>QdleMb0+=a(WF@JbmHW_>Ic+N6!x)Li4;_3&-| z%Sg|q-F51R(c^Vo{_Sx{0z|Zv7TvvyTmiMYCsLH_&^hh`6B!yXl6a+aCon;OGbcuA zrkw9plE`RXN@W(DH~!iV&#lz&%EWj#GGznYQl4&*+CT!kMHXqzy1)+H#5E!D8!uom zp4Ca(yI2Oad3;3LF{4g)5TC(BiX((&PAH;4{=2x=R-Q~FQ6JP&O)>j|i8j``)La>x z%_Qw_>D9=g>!sR5_hb)3wMQ+bGrhQdCDACx?0|!55-Jg5mKXjA~mn}AYS;|Q(S-!i`%2ESZ7L^C1 z=tA8Tzk?H*Dc#0U{Q@0g`M4fej-jp%BMOA!GK_#ep6H&92Hbx60*os1?)fGmu9DvSgt_ zAXrHv!Xq4^fez-;!Nn|Z4aFyT(r-kuG4YP@MHE{dD-Z@l0kU!`KJ+zk5+2xZR5C*k zG=`a)DniGApfOAfMQ8@VgZMB^5D!)nkUJVCAwfT52Y!ZC`ov|Ji8{vVe^HGVv@t77 z5^Ep>3l;Cm@*yIXbT4DiTu)qpJ}AKQk^Wv78Q%CMKAZKb3OAhwTx$bi_EP&}vPEqy ztcR-d1*5o}O-GQ0AT7$$m86O?y77?Z?*rRlLCg6VVhQcY4n|%+LVD)IRk+E_zCB0T zLLIg+k*H>qvb_qsab#%anbRxNBjCYQ40NF;0H*6=7Rpa9EoIFAX*OxMFNAH);6u5J z2(M@l_COR@m2TdkSk_l9VY~w(MKu{Cr=-OXf2IgtnE*qDnj9w1sNUCo3BZm3NZE5{zke{kDV|v@NwAi5b+T2&7)u;n zz$}s}lCOqYL$=a6xl}fuZvIlW4Ha+}ov4rNVodW3wZqgS>Ux%eCER8>C+Q&mbF%ZHB_!=mEI-!mZkJ0?UFnM@kU7>v zlm~N}Hmls)aS^$s$y2hVPK;+V&UAHgbV)FQ6{>DS#nUf3Fi?NZ!|p^hz(Nxy4cH9I z=Vj_rP2mWlMkztW0qV7YK}y}-5rO$iXdI1`-n~Q$0`{ zN-By0x>*?!P#a5RF+~zczhO(polw=ub2@${V{9g>CY6|AF-$sRI`=HB9P!oNG{h6# z=oV?bd6$wVOI_FOJONTmKE`liUr*hY!(c zdRckH(YT{o5Q9KxK_QZz=+Rfg$q^sz!}C z4oILTrg~}w)3!G3i!}>|`z}`C%q$57Pci<)D=e3R61OH|4ogf9#pJV{G)TZ3Q zj*OM&Y>K&@eVWmb2Ie#!&fU$iz-~qL_FbrYM}L*;2N6q z1?@IuZC;t1G9F2wbh&d|OLxm0g?criqcz)Q0kjNCdv7g zrPClN-X8YX(eLC6@%>mvE(Yo3qV40D^)eqB*$|l6NHb%)0Gv^xMh-lGc6s24RUrdoq=l7%s8=4801#HDg76v z&+Mi^`m7edK8+=7Ch;Gwqs10!9AspWSBV_pj{rvOK+4NS5lroT^9|_H*{@*uLFTVN zanCG^&(WQsIE4F%XN8viDmR(i&f7_$Uel3N2S9c zu;|~VeAq23(!4}0Ah1r5XghktT)o$4|XA;kAxnuY=``O*MitEaW%5O#EIk_AkD)7C6ClOosnetKXPl6yrn={EeTO= zF(jo97sE4YByUG1G3_0SY;I7a%&HML$dlWZHu9ioO{`M|w(s#03>9I)qWNo6_hVGv zQBnrHOQmJIfGgNqXb;Ah$}uTDi4^{fxHE%{VYjLq(_iR5#v1S;F$)Tn83d?!%ZYfP zfi}g5hhL292gFyvQ1Gri3|A}P3ypNBeQQHaj=SfNfhdTr=wMWH-Rf2u3G`S~W7*~` z?}(2YQojbXoI-i0Ha!&9PIWrI9pMwQXsMb($m%If1dk0adqvjH@=r4#uYYMhDCFBu znKeV)$Rg5Wh|^^~(!z_9_pxJ=XhMft5nGRBwI8g#JBk!7q1;E;)< z8K)SwxDv_EzU(5<848)xE-+`GX#|j9>&x*|P_mp8qd5@oSOS2`e2+R^#%txFg`1B= zk&D-e%*a}2n}_D`IZfOFLcc{5N2nb_jP9lt3a}wRLoqTi1J9$)aiscqfqo&X-Q2{p zvVg-0ZX>F`ZpO@%)i?@%H?!AY;Joh!9JBl+Agd+BiOj*40Bk1^ZswuBHrCp5)TSkb zpHI7iYRWNbVMr3dZb;GTuoNA7I$0Ky=}Ff!eJddu4@u}(eHPJ*F#H7^<$|sQ5JN+S(UCTDQaWYh z=Ui7moU=0$1x`3;bw|MJ5)mhklM}CaWS;9S0m%VmA~pXIYPz8u1zlmuGU1V7U-*(O z{A~GsMx3Xa6!F8Z7Cq;)beaE4$cSptn;OEmqwp`Med%34qBA_=&LDP$ZZ9z}$ZU>j za0+Nli5*AJ#wCS;YdrnTBsnBx9N8Y4PU+G}L7{yfjx8ZzZb!6!74lMkE#t5)9zy#2 zGrovq1EKu#JfQAK+)|3oSng##nKJj(qGZuXOK~T8a=i1Uk@>nyugY}QBU<<bH>BGn=tBojVn#-xTPmgdVc65s4yP!Ms9Rlo_A$3=TofiO5&wrwKOmym5fqRaqiZ zdXa`EHb;02^FiPU)BGjDDDiSy6L3=y4p5)ZM#<@G(eFrk>-@P)n(HTHV!bsT9SOjk z$yYrJ$CkBeb5e$Y7sW(!oF=T2>K@|ceQ1m7)t^v9I_ZhWu98{`S2BpLrv;|mx>~UK z{KTLXxf=*e?%gc&Jf2(a3x1Y?^4<;N^mmi@a zaPD)uSQ^CkiNvbtt#3qmoSou|Elf%j)}FNAE(o=*dA(x`?`~f;4gbqJeg_c+X=gA6 zRPcMl#PR9V(C%JN`h2ndDbBzfA~L()$hb?xxrI8TN!A*CY-I{Kg@*`YB_z`0YqLH} zJ80*C+KAY5GXi=1qkMn_r~eXzZrKqKKV<~toy1+XEo$v$_;_*w*3hIg;_wGY^ifDc z({IrJ%K5`MHqh&FgOUTsGt*{c~EFXTDy|gfa^C=JS{~D}UTY4p|Fj4E;j$KJ$a5A=Ik(&ml2&nzAw0 zN}4J@g#0ZKLp`w&il(}bdBcAor@`A*z&4GI)53SwMoZ|NrJ?%#v>!R1Nj?JuZjt!S zn|cEw-(9KQiM=b|@HagvT=j9L_RQ3-&&n|BRo`-wRfg}u32X@_Xsj#n9$*3vib}OHaH)}0h1{qO z6YmD98YfGP`n`h@NsG*5&oVyi^`Ii)03nI~_*IK#QbRR&Bh(Z+X(~|*DTPtH3Lpwi zSwT|7f3_Etm0`J89KoeJB~ zQ@AKnE=!ZZVVoYtLx}m>#5_&|+7sCD&h6bxSMQ4jeeG#11s29l=$&AHyrC2r2f!|Bj_iWE`+U{Srf+9DKJT0>+ba zS=1DlZt)JzidbRFNQ@HXXA6ZfS!~$n%t?oCR*f$`avP=_4%Deoc3!D8vhjVOJUdb& z99TC@dY%EabChlM7#47H9Ix69?>9RwG{S+n9b705YAGVkgVr@%EuaEJrVu5&iAgoQ z$)ov<$D*$VX2iPyaaf_(>Yk!o&5S#H)&fFwGK#^>58)mojBXA+O1yALgKnIo5D$il zt7G90$K|GquPNw{QNSt@i{q>jqDzD!I+QdjARgDbFEfL>rN}Pv{}jh0i}o!x1`(l z+R?s7bKeUr5&d6&Y<|IGfUF!&TDZ?B1O7fOi6Uy%g|gN!u_qPlwz2;QAro1&v}|lf z8OdVVer*+*K@C3+&DiGnx1|FJul3#HxtBe7odujscCY9!0%N^TTs^pn_lO-KE@(mo2yhB@f~ zUu~@f4|F^f0-*HwYZ7~GBlnBh1Q{-T1){pDWzwm zjDm9;yf6LWrIddfzQS7I2CX}cWj+uyGATcJ zQnfqGN3_7OhUXm+Sg ziXnw!u{O|*Qd}Uwk%cBDYZL}%$Rx1icTfbr;EI``s`r|9Gs%^g&e1b@{hx=n^9m?; zAW_EeF$YNbg`OjDLJ#26*2nXn$ntO1jHL8y90!nDPr>mJ0I6FE141hwyk&%^S2`s` z;sy8WYO4xm^ZQq_{hHH@vd~G?%@v}6f5yp@Zcosl!ncEuea`mtAht#y{`H~E*^E;5 zMrn6Wpm=jn2Fh}adjxF&{6aT9LT#}Tm+!rfesYxoKs#Kw4&%u}lwN~*qKL;2o;T$9 zqIPT;(m&X!yN(harNuyWiCPLA%d7&pZI@}DN^&P>W?JI1W?tMmLsNrN;wEzA2#=MK zJ~N)Nrxti=+YH`S%>r~>MT7hZ&(mkHT5xRu=Ho}~s?YbwQu6WgRRwpq^FIAB`hmR( zT_>W+jVVjNqJ&yGp8ojH^wOq?@+&$`{O-~zv z`Lp14F<;)ar=dw0PUt|6@rV3?v|VZh(I6=@PB@7xe8>+0_-NuT^!(!%>0H3#vC^o0 zR%p_!f0($)nm^n1CjdzEAJ~dnYB5tW2{vq&b3oU3)I^w=ewqO6t#`KHOnxqY{U)xgP2uY5-(A<40?^Q~?|I)=TPX#l*e|OP zYfEICDBPDXI5p>saON_lzSOV326l(p5PXkAzj8wcCw>cBj(_(C48Q=3{U&ogdwRB> zJAot^^mAe4s7@XcK8Q~xy8wrGK04BAzcPjDMv}B-Yvxo{oat#cztIYpU1w@+s~8<@ zW_og=G(e@Ri)Dos;V2=hh1+Rq-_cN2G~xycuU&XiGg>uoBVXzf&AsYh{J;id5X&IN zv8>Rw<>R-?Hj=S4*0DXvUNLc~36gf%4=rk!!j@DG_0dN9vM^5~nmo5io;B z&7{meQP(N7-Z=B8-Mbky%u2%PO^G;IUOY8kiAjdy82j|~ZS_I_$$G{Qd$}B!`c7PY z9Xr`2hm!ZS#ABAVOyr>FC%bs`WAe$9|9*<|MHisVU$4MFW|w^L9R7Ph%C%6bcC+R> zH)TsxE_%K5?3rme#Sp=6xQ$bD+Ju8lXjs3RWx}rjTb7K?U2q>!I57G6)8mO}jnBMS z37S9P4Y)ACkZ|2c z53^#-U-~}!%NCuZI1a9{K_%&-e@Jv-0E(0337`@KKP5ZE!S?vh7@F`KYlvW3wfhA zFCLo;T3ybABBOjx*0ztyuI-(n63D(JC9lNdPjOs*y!$=h@tK$pF)-ufjc|44ks*IY zV8}pG#Z)5lr86S6vldgku~bCvOQdsv+NHqjl*K~Htv$WY;F0!xx$a5v6jTc1)E*89 znNpDXGm$>{_gO+(O_bR3F{Z2Qmai19`tj#K1ZNC-Q=P4wmK7lRIV}8?57jMx{U3+; z4aJlq3AFitkm=>fiqWT9p=-f+r$tX24U%Drw@Jzr29g5aHfQhRNo1Vy zC$`o1BO^VZDf!ZB^P!3Av`6%Ch^42wEJT$np-X%PM1?bV`$Pjh7?Z3Qo;bXD zqQ*;EJ+m_-{c~H$q9yeeU(bRYDb!8m-GriDT&F{n$SF^(X_c( z(GL>Ahd@(LHR5M#o|7qobj1qTrqSp+Uc55 zquNQF21+9IyAvCdT#h>T5JDGcs^v$uk9YyvJ~8_K9>QOUtH_!HsVL$6GL}Dk7>A5z zF(OU+c@&`hybsnoS>{UOO<}fl_85OG{s-;bqT_|-NeNjdxLw-r+>q|N{+g2%_$tIjubJUuJo}{TR2P5?8vnHNwE;JMm;53_?zS2dBlKLN( zyDdo;Tsq48BKbFW zP3;*%s%?ZAYdE51JYU9D@eM$CNvn+&r6+V{C>CHER+Q}zDgR3AZDdFcvHH)f_RvRw z$dt?BnH`2YUHY(Uyu20YBNsp9nBE#?U@|i1d%y?tDNcg-j1UM`rg6e?d_Q+pqJi1F z?E)Sb4W8LknGxT=3mD1J#NG$5&$x0uPWWBu<`QiFqDB*1F(@iF$5vKeUU z3Kj%JpBN$}$HDz|ekX)P{h^t$AS7Yg;oxYyRTXH>y2p|DaT7tZ%;k1H+)%Be;+-s` zSw8Nugsm(ek4%A#mszBfui`c5u-dW)f0Xee+~&cyg|ohjdEJR_v1LNBEOr|7+F}}i z>^EQ!dWaS2Zc2&G=AQY_RanF(MC$~owc{2T4Xdto-18|Ps2WMqrBHLp^{rs!QI&1y z)1$_hv7`#0)@nPhaH`Adf}ij1tD~~BDu|G5*?_TL6yyzc;9}9Lr5V!TsXJ=?^)lwA z(8D*!&z>HBlGp~Fd*JRrB|e7e54KsDh>k7B;;Q9f%;GoU2;?@>anYH3luv3R(G?&9 zAMY3p`c}GP+mM|{*1^!6$8bp%;5X+*CV}9S=7|suNVfH|wbc2OrCy>jenngo zI=@+hAG7jkH<~|KkGGv1mAOrrtNl!xmZnjX1a>p^3x4iaynLeMXyp?zZ5F3DM365$ z-Cvh?)-JKE|Kq&my1UYWr}qI?T5nfk6LbLzySULjnZ8~kA!1dcXvGu}o_|Zt5YSb@ zPI!AM@5@>(7*DnEy_OXanb zH1zC?I9@;A3X!In^MgoKEy?8KAyl0*lT@-v!WYIENPz;hY+@5aS0!~y{-AWPT>O&1 zk0!;`0`D2q4X;NFL~f~g`lfX+R>pA>N^%L-qJz3dEh4ce%+lGnY-LkC6a(gg**?(tHbQPd`b}3PrVS3x)TjChD`BG^O824`nLid;#2wUh*jE z-Bw&@`rqochPRg=_F1b-2XP^Q#Xv?hXJpa*m5OmGYAUfxviI)32hW_ROZfFD8O0Lk zGv~#FcD^xbm0BS-qSE;XsU|!tgQK3bRt+}_$EI4%Y;U3>PB;;+s@LmSm1tk*hTW$F zN+4N8=9Ksw!$8Vr+7IeNt2qi2@^iMqk7k$r*0$g|((JKKMN|(`Aj@_KyWltGTqEoc zOo53*t*;uB1YuP(Y0S-pXOAKPI!Xoi-GgL)BK~7zjC`_yrGl^rFmRsR4pbFPFRFBo zXCZ(4q8a4ENd9wgVG66rB6P#+4K-M?8pajR{2h#EuLte~6lnTG z_*oj|iIBQvQ7HBxjvL5Sa#2QhWnbyRd|rv3~+g{>iMu#yx~T>O$#_eEIl!`4+cX!aqNrr8sUTHMbMmR%dv{jt(w?;+u}*<}fX5&Dpq<&ilM3>p%{Y2nHq(_j9Lmey9!u~( zg`Dqj9qX}FFNt&u9C1Cp82A3UGDugZS&RB^LQa{jYkKR_M=p<@n-_NbrC}6f)x!tZ zy`;-jd|UN9qJT3S&1a%o@3oOVy>kxaAQ1;V|8H|HIyk6urvu|xCb5bVG805+4^ke@ zFxn4A+-Bd)wM7Gl&pw@d1~jraG$b5_pFIh?C&T=vE5@)k4@34!tOuup&w3q1oS9@W zDnAx6e)ND{_&`re;gaGm2W1snZ*Atnv$)&Fkj|ac+ZgArYt^7pNVkr$1XL;-IU2jP z+L}V2hqKiVB(XJ0FxL74l-Q>%$`ccN9T~>!r~cwRM=oUf4k-0~n=j#pEtFhW;pcg@ zPOelLmV9d{`7k_E0vgDnACDoOG>^cCa&e+}PDbg>2Q2Ej-Bd=JTDg}Omww^wkv6%-NSc`O1HAg0!M6VnZti+nDw-Fnm9 z{mhPZRNZIyNBFq9fOnivto){bCW6LGWR#aOhBb67uEot7E+f4OD5oiDzE@cyf&+6` zJ@U@0^^|oWlrBA-Esg=U_$HQv=E`(p4{hhXQ24DYY++9+zGGk-I%dVPraGKD5OKhc z=g$8d(c2+yuP=nuUtOAz`Q(&Na=SK3u?Bd8jR~K(jg|@6iGvtno22Yl2U z*5$M6G0K{@Llo5vX~uZ$xD7tfjfr!j)ANXBQ^jdtb0e@?BYR`d-?T-C!c8sE(h4{1 zcvl~66Cwq-K>9M<=-!!@_pC*r?yrct_TQ62UyHgKg-?won?=%i`?G;adEEb8xw%ZX z>o9{EyJ&P6A-GctG4O)TWMU39qIf3%EiU*(mS(KJLU0jb1`Ts3}~+Z5AF&C96$#SyI7!cTPqt&d=HRr^+t z$Jv?|B08RFY2|r~=M7#5y1&|7)DLE+0{S5Q5#ZxIpV9+V9L_d=wNOEsI_6chIi1ho z6YIV>%ffZ$ie-zASx2m(M+v*R^XNQcJSL^4Ibl(K2K#fdqQB~#;!Q0vE@ z+I=WmPhb(uW_STkQh_?beB)cGI#d8fK)S#0ckO#l0L04 zq{Csf;IwgQ14}ZoY3Ovwm22^-TQ!b?IOtaKMo-Kf*W%${xtSGyWI*d)iZ^gA85Le=Jw&kl1U@k)C zo1X{dqX%_R<8N7-b}tWo!%VlVx&r{d!B+bO3vB>5U2J|*1)q?uj=~; z1-u}ng)RPlVv6hiIQ&K_y(gfbm*2HGwlx{GD8iDAKD|puA7cNg53%0@E$e1Mjvi&@ zOpKnvR7W`v#wt@!Nv^Ft^&?rb`S!~|IWK9$xZlIf{(sw^ooWDMM-*>M7)<+8divp! zxg#~1W7a8hI)p(s6pyuasfCmdc>gW0sFRx-GLgs;du#pBR81^~L) z&31dGem&Bsm&bq&K>KOn$~(?^py4Lin@M2xjP_1hlc6U5qkM)-8T03`s8|SHC`hSr zCKlnKqWy-hSlKFfLJjF-kF5EX`Hm-xNY6%727Y`DbtISOT)X=*Od60<5!8r7QlPt% zSTfJ(NvIOCR=MW=F^QXxO0yR}G$`C7NOH)=b069%5Q^_4Ux}V7@15%}=3@yRJ{oJP zYg>ha+%gof>|-=UEoDe7MGIl+1&g$ee4%;!g_u=%wLA+{@4~Dng>5reF6RH ztb`9xnGl(x{K0cO%&FDKX7{oqhfXqb)R;vjQE7>_PCfti9K2)`h)`CyZBCr?jMAb6 z9Oalal?-Ig>8t0yjTv6#gm~TiQgRF@sQ+glF7hUN?I_IT~)naf~_bbMucZ z;bOW?j2aP~goJYJEyW=yhVdl9=PFOdJG$HF&FM~q)iuxhFoN34S((%%_ry0IRfKeO-{)@zL#d6?gd9(p+teds_DUBC8?YgQT z`t3HPI=zdn;)yvCC<9<@2zn3uBTnKeD?Jy2;b>8Cc6=LauXx#4IqM6~+F6u5>qCb$ zSm%M+SB@h*I^L4t`ZjY4Gi0vfC7V`ZwR*Oy5GahSWBH`JEyOF46U_tuLumTJ$h_Ig zQ6p1sd~3rUf{vl%iDtAN^ZA+%rkJcwLw=AFz3Um#;zC35AEo13$XVjejUM6}9AP#K zuSPsIuR43^tn1V`N(HOHk*rk%Z%(&sy%7a7(Mpz))PFb@L1i66fC#Qhq|ST)g%EMv zNM1H>66RdP>{RS+h4&7nJr_|vE7`2k6P%Mu!(@RQ^99bcm0Wk_NH46=5i@SIpd#i! zeFGEm$X#a6R%_#vC3M^d(;l0^5fMp=T@`u+;gHP6ozV7yI-+-~7OLgy-%K!A-3f&A1*fB2; zj#V0>$xW0&1ucey^u{`yOI9*Lli&o{yPOmI-u#ybA}t?4vd-%Yx)GSQug=LRzEWyy`5V za;|_3AJa0nRhzy_kv@?>@{XiWz0IcK!viQpoH_7px2@fxm{a(<_zQlQJ%pK;4@X?} z0g?CFl5Bo-%Xj5ct z&{}FzpnoJVn;rLTm8+da8XGx!;QY8R0T%+YnBkpl!Ueh`#O1zp$4JDGt>S_jh*GmNeua zq+&ESG?*P+O?BD~iP8+r(S<`U)K@9J$ z2Wgt~_X03Z!lTIuieod75v3_p#0V&yMbK8x7{LZ|b!t@X0@>nTd<}|!{Y0n@*z9_7 zb4Bvh%v*VNw~LbFVq~jjd}^1B?2Uj0bvzAa=%oYNd>p(*C=iNkolb|;VBry;@*L_G zZ9vzD`lGgcAwo#7)}xjZQIibx1D~3~&-J$q`DbqS?z2Qan6KN7 zAnwt)1wMxPDpdD5GP8a-9GNke?r(T63COSMmOoq&K4Med`TnPcLPu67;A#1h-s9M& zh0>_SpR3Aa3Ir03@$eGU=faz2zIrky>}OTRRH`}L3b@StOx;RRM`0FCWkoDcmo%VL zt!dGKyfv_^^4H#y#5|ayHo&@!-v+rnPf6XRp;fIpuqAJ*HGcC8VGgxV6Rcx~$;{t8CFE>49?(LNk`-`EzY1x+NUDQX;*4#wJae{@-d`onD*rvvwvW(^HZ@a z53Vf6Q!2+JMjtwGZeI9xTp}gD8|i-U2ByE z_^Prxkover%T}uB$a$c_SeuWSez(o@am_IMa(tTtf(FaSIN{oU-KB&;0t_*ad@V+s zt0=Snc1Z)lg{6H)X=#6X!=&~{275e9uoAR$h@UW!fbV?!xx85gq)6y8_TM;icg9&% z=6&jPL5otKxeLlS+Z}qhP981rHg%iFJ8BGmH4r0`Bv3-}9N7e-P&TfywSwk_? zLDQa<;fVu(+B6c!X3^QCB(PtJ7eZtfXz_z8a6<2UGwNFjVSvIKXZk@S*4Ew)dU6I; zqPR;bsg50c_{5EmwF!~83uYTLQJ742)@pb`>J5b#;I9%b^9g2KVb=Po*qNb*7={-= z+C!r3X9r6;I6aY>YEFfvO^0SChMO_=dCSH{XfnsXC2dC@iRtLj63$dOUwE~ob>z!0 z{WOd^0>2R>sU`IXp|PaLGTEg<5^#85C!LVZ-4+%w!)?xNhx?Yi zC%?M0!9lVtZz{&?f+)!*m$VhrvuLzJI)tAvYl5P7@|{=u&cvWihuqmH+VfP{O**DU zSxnj*t^cw?PTfL!exBw-@!@r(%v4odk6sK%&O8VU1Y=q2)^ztc#pN;AL77ekf*d~F zyIO5;Vag>y$jk|Tf&PInGM8e#pVeZg8|hrd3b;HNr>alYtVLaHZH3*@T2m_SqLETJ ziyMA-*Q$9hKK>ZJJ@8p8e%U*{;gI45(T_Nt`d3=+>HdRVNVukf;9 zHsc>&J^mYa>D`uC8_ylc0(xjfm!Je>=wpSzoIQL`tMZ9re#g(Y5TCmB#*&Y^Z>gDW zv-8xHHzABt3O$<*A<_rSR~MZOxl*>g;8diPrTJ@diMJtC)$W_MZri223v5J`t)D_h zYnimMr0ptS^Zq7eewQb-f>;3k23EV3W?IY=D?RDJpy84#N>{b^x!6{3^zu?C#YUx< z7Q<9T7QOk~`*oe~n-OIt5GAzM4TeSA!sd2S7#8PS3J++;ml~Kw!|Sn_Dn2xdRcF>I z_ET*QVtk2vP|}bWBE%F_T@ALoee_1OCz#gKe(aqx?^bU4(B-|LwTCa%9lC1zSFgk$ zSCmOUOo+Ovo*-{j>mOaWGpf46(uc1dzYDip{M!=ih$8Zz-sEnbckZ<2kg_`-gN)qm z16x#=tP+-oe1w@!cZJ0Qo`Nf&i@DutNKP(DAfC67Xn2x9CjvV>HH%5UPyf71MZn(@ zfq}|jf0w!@|7jyYq6!)Gu4Eo7Bq*4UKeKyvJMEg&ey|ewq*VIg8Y{y8D+Sp4=Q2MM1oxLhu`(;a z$A=afet#ivV_uq_Z_A!pXXfb2k>dWC?vx$mO@&`MmIu^=a#|)F5;31MkTD! zygThqD36ZoV8yj{o<_j6YWG*My7v(P{inYZ!mF=2N$j<-MO(ExRlu;0Ry?%0^hFU8 zgB(Faz#SnMdWi#esk3un9|IrdIPv0X^D6h%CR?*nto12Z94I4<>B_kuWx&@^GczZu zV7-u^z#p|*kXh8{@C)(twnu-G-fP}O7~ZbA()B3N(~vRSm1VWUt=ylBvHGXDoNta+ zD2(44F7GH6y?2mE$vp2X4k^&Xdk{}yk)$$W0ia-NAJ%B2)}z(%Q$;5s+Kfls(dAU? zu$LG<^wMW$LRbl3FJ%+o3}|yH2>V6JBQ}9Xi5KIeG!tNh>^oRBgsqXx&Ca~7_$NJc zBwGH`f)}*}?96%&<+GOfm>R8}#TcNACPdh{ELDIg7Iqhg0FF+kNvKU6!!B}#hh4?G zBg~~9d`yhVAnNQu70ObACo{fZ;*5*kXEHu>(S^YN36qU zpeVP6U2;PSW?k_+=N8y8M+DTu3?$FQj+9qW>07CcfS0^qOJDK@`q_-%|5S|^>(ZCa zmgM&f_^QYa&<5DtQ2+#W;W{OHL~Mxz4^x;*vdR6pt=>#Pu_XLoxN3oT^z<^*r}U(k zlmsve7^_6CaQ?Bl&5FUdD6WXB?-W@)(Ci>dHI~Q5&eG(J#|Z^by3UlejelCHVe#-# z$*}g$d=jZ$X-~XpAbip8c5|P2(IXCSZE$A)1vhjNt+1RuSV-}^vv1jZEobwJ$X>I3 zW10QX94##d`9Wj!x9VnECA{0JF2tU3gPgt@X4CJp)a;3%V6C|m@XPd<5;{%JFk=$q z_s1k$naEn|8YMeAn&>vIc>XcHhRojlTtvv?U56v(maBU45XGG}WwnImSA}X*TUqO0 zk_-kZ04<=62=ZZ7L3 z-^#@zuToYNNaW=YHF+a0!Yrx7`+Dhp(ZV2YI4}r|Qg~kY>lQx(5&8Mo z&)9;k!A>*r(YWxK$!bu`c057ifAfd-`9YWl9KiDG1WVz89r&+EDO|-62Q%O2;<{O% zh1L=SH$lRiv%*hny|)i1%em3u$KgjUbkczP!z4&crjcbcH` zflB(9I+Bb6{IEnHRX(ZW;X$9v)vT5;bSwmVLKGiJ(i9Fqn|N*uqzUV%@XEER(&KLn z{(;HtdY$iO#(LEb!XBV}>~rOFU*#B~{KAZG#0RLAPJseS)K`qcL%mjGWFO%d(5{6( zF86xxz{}iTv)4yef!TBzKqLg~kgMS${rY`Rz}&H-{q!#gK9RS`;gKvxk6fM&Au)Gv zqpe0x+;xY2K>LQG`d8Ewe1U$xGxZ>rFVK?o*cwpmphkE$xQk4&EP|yOMsL?Ma7^(jmhowOtZ!ab+6erpxjvXFXj){ULxtVm)@v zP?CzUl0{y*fB0Rqy`6dk1gQN^|1EYvH$H`a%Jegt4j| zU?aX*XE1FH87?C`rqjhg5AP=7rFhOVJ^Q5LnUZF<@<8rWS{bEiM6xXK*1_Ix#|vS% z_@dgK6N6gG5rsXE=-|CHA#v^ggJY8#qFp~x9^}cWKmE*_ z7wE1kM!je)dcorgPl^G)FJ*fQp8fD`VVLFjvD0H;7`b;%Lk#>nF|&*;3=&~mr!~TW zf=Zk%bfK&1l`L4%?XZ3IzL;KQuFSyFgE>i|ym^T}vn+si-pqG~KlSb36X$xhIcU&h zDNE+|LD?yB-~>9Tc;p)3=qODn8qREFeP}JoSXGImb7=DJn~sDbO+J}sslOGOkrIDu zK&+$eBLisId|l7^Hu zgoOsZqs#)Dr=G0g9JK~vJ)@D5Ws8x!niXMWJsl{+6TI|(FrG=##^^{GBS;+MeJZG~ zb-cEMY~Qv*WXt`}eqQqI=Ylu|tI|E`iWG)c8(|(q%@;uB`9*HY~=2_C44b z!2_nN=%65k(ym9?Qd~E>`FUIR z9G>ow?)|+FY4Sr4#$Ow(925-q3NPhYP^be#V1~T;JR84#_|4D3@3{G=Hv?k}J}JOv=1lYy=5$|+nXndkf-GI8aTa{o z*QP`GC_mtIE0t2I=CvM&YX3p5KKf{=VZ3U2A4ED&I^A*f$-+UB`02zv3nkKfu}CT>Tb zU5j*eRCpF!WyI!Qp%u&h7;+6o@AwJeBmL1oM_2XeA5>48IqNdw4FCUajD;5cbrhwk5V!J8!`Hr zH7{_rd^TSE@3dm-l#4ehxlS5W%Oy1MKra`BVj<MIA&`MxL!&T*weJsKL0YD#mUq# z0;3=d}efjie>vYhak`&aChB++K>x`MHS2N8Rc zXhHE?+bUR|W`jZSz64nnQ`eQyWD#t8KG_tsbOPyIFmH@shbw-0}B z8R>(+(Ovj^b)K2Tz%r1+HNFtMsSl2IQ+fI**l)SZSok!YYftAD&Tlx#d!;ul0#o@X z?pBH)DCU8z;i$bpcns1L0u~aQptb7o_IHLGZs7J{Tj_qnhSAiAFrOEkuTMgrS{MeA;s#N+ZGqnh;cP zBa@sS>Jk=X#gnBU6&~laCR>GD%EU0iZSGX77k!j<+wy4A{@o!SH7=!gDUEh)7-eR} z6mZdB>alS9jbe4puEkBGGN3tYZ(88$W7KR2ySr!!8L_dv3BVS-d|P-%=dAK1UVcI^ z(QuyIXLBH*r%G*Aij#<$>Q?-yNjK^0^VXVt!||l-5m|{(fv((%__UF|`o`?f23o#< z(XF0hfg+QJE^ z&kRkvm|e6RUyXaAu|C~19z5rj*0*y{CI%*bMhx08PxW`(!rF zkV^|~HkKn%b1!Ulss2~Gq)w~yX^YdA(xy44i9_EhCaWbB(#gFn9jrJ^cX=Wu%9PU@#4(gNfyy41l{V7hSN7?C)$Fg zmXjR2l(o0lMI(k}@f(ENHnX02c2@`+mH@=nzA}~!Ejq%{hu-M ze^>ex+H_-fcmW(9m0ma|PnQ6a4s2q4g6ltR&pqPqHqM?vwNnPE}5^lET~Q#AcSo!*;M!4$K-w zemy{nSfF-#HyCd6FHNlj$uq3(ZFhSLE7IY#S_k>I>ZbKoz@oq1ra`PPiJdhUr zyi~};w7)iM@S}g>MA~ydo7yLv$Iss>NdIksB7hhWH1zLc+xwqmo9(ye{v$eKRy?3>$X62amG{Wh zIEMUK;30*eY}95DCV-D+PB@v@!_5{AUb-)~2o4`bL*S3GMxgOK#pA^@ z3m%2yrCmZ(LY8c>vy!30eG0K9K+!0Cn}+;%z!qI;fef_>VF+s||j7oS}?Jb-EeG8dj1A}DZ}5DeYBb}5Y#Y14?#h$l-sgD zaf2VSWFX)WDG9~2VcUiJ(Few)c2s%CM5=A>oL_OPjBWH_)YdGg4JaAqxFsrH$YFnO1*Z4MErfzHBI;Q zzxD!12`E;5F1L6(VcihD;{fXT+U)ly`y;sGgvangNJVH9 zKDtHe@#Ks}lSpVemJN7Qedi4q)g#n_K+Tch%jfqODh6)Rb3_ySjHB^P)G!i0qnNK| zswS;u&!S-@?IdimSU`A!Ps_Xl6kK{W1MzcJ6rf5=-z9XIa`m6HVtC?yv)_Ir`Au3i zdmUx2;nOk;!c9H??bUXV%oGcpax%6ZF|>?{8CqYPU#UbjvSv~`ZkP<*N8Fl}ji0RV z7Em6H86E|wbjY%mS2Ee#*!QLA#YidT*8rIba@M@aFsKMbxCienim+;hCZ2Ae-MG)9 zvaaACc^Xdr*Q5}2!Riwb|D~nmh8WEjVN;*t#v@}QP+hOrW+>#CZUM+7Nv&RkC&1WL zI*Aoy-{zS#=9vme$UN4>f$Y=IA{cZ#Hmr$`xNxVNUW!Pm$%{pxEP#^Fh_;(lX7I%9 zL_?yK#ctwGx;VwiUKo0+9HW354r6vh`}l-yvnL9NA80Z=eG@g;v!|hg0M{NEA7s>+ zpTlJ9^Tzh{f+6kG?qxnA6Fz+c`MGwGab@v8(reb6 z^1CuN%hFO4dMbRheV>}a9*l-`?ctg&=fOw`MCzG*l_3hIeC4C?DbGre%q{z#>_ixv z7Lhs>xgKV-Ml)n}q+xkPb?;%u5tsxw9*B2OFl~8fN+vX~dnn7p_?$e~zAA_)KlL*7 zRA8ZkP6Yz{-YYt81eBq9a5m$GItM+~xSO9Kg86F+*h^q&jZU1o5Q08l$d1Jd;~Z7c z0E@=N@AMSm(NFm-pDG28F-RofL~Tw+?SVC%05%G7BK?M|E9cOehK zab?wHPaNjQ7c1K(KmNHH*q4rj&MHW=#Uwi;zYLAprPmyHMfGqSIXH`>}+W>IcFUhP1RN9$gDR zmAk}*HaA=sc1Fu3Xe;@`XF*e>&1^sR?Wx$94r?HaZZo57EO76o+hlC8Bjus5Fi|7qxNTo7^&Bk76i_`3e_R#JMa!5ujs4oXU z6NMPE7j#Xj%wjw@@Hd!lT2mCZMBrW`RZ$Shm(f8k2lQ?d3lV1XSUn?e*s2 zf4ugD%nHc$^xKNO3$F41Gn-$CvtU!rP$f!C+6tXC2UXPcI2m>X7rCCI;VZ6+Shos2 z!jCT%o0NwSw}SB7+B3#?>0u&5)oiJAuUetFkX!~a>JCYArYrhcw2-rk70WzVf9~Ce z6Tqp&{`h>mOl<=m6tX_@_ib54GvrhGMxK1IZkrdc-mG+t`QV|LDKtf8#D|e5$J}LQ z@^S#K&Fk5YYy~=*3HnkfS$?snLJb7s0a`#Qz{*M0-ZX~_riZ`fz1ogb3#c&bO5hRD zRoM>2Q+fsPrj9U~FC$*vvsEGAAAF{7uY$I4L|P7@BF z(1Al0%40wxQwL-)RD}_qRjpP+6y>8Tfa8Dx&!|-SKG|Dqmt5Y-t4*V%q6YGrD4WPc zP0ph;-{WW=$iZbYGf=uN$=Okj0U;gDW+lKodAtOPMTr(oI>BsT`!j_&m_$J}2G|Vw z$c3>Sb%cI%iL$f}P4OhVab0hOeZq^tS#EUbq-7JQ-#8X_bUuxQKA`@gN~zQ2)jc>0 zQ=1<)`@CZLT`H3kSk*&|W{QQ$@X`I`I#T)bZ>9VNF~8^bFd6O?5;{J5K!Yw3i=?;^ z;!Z*%W(f_~kg#QP+)<<0g=T7kk4CL))f%ymwD0nF3^(r%FJ-I3^9=y495O-asKeed zb1GP@#jlmhwrGP%KA>0w`DV((NSD@*pnmdC#0FaKg5cqy5W}@d1P}j1fRTb+QkGrY zP0b>Qk9SJBerGD;M#PX1zL8m5*}!K8UJv=nff@ks!H9_FfM}0$RU;50c3!+y;YpYQ z4A^C7UaoQzM|+Z)qX5}6^Xh5&OkmP04oKEg$03H&xMOSZa?0y}wNSIy*%o#JJ!iiu zK5=#p9Si$KUNl@CX#_*yIpV2x-mB#ZP{DGuj49cd+T~>++uAsDKac<C}6|F(UX{5Qa@)N&imr8Vp|em1=Kkq`rF+W7P}Z98)+6I zk_scf3{E{OBiV<%0!kz`UygDi!iM52*zk^cZ8#AWl8pv~N`q1ami% z6_vBPy?Bo%Q(hi(JqZho?C7q?QD9iAqWlLb7_So2u5)tTjb;7QUXEY#*@x&84}X7ojUIgGyGs=Gy|It{)p zxx6zfDhYOQ*GFQv%mybnE=RYaW#2E@+m#QICuBM*{!s%eJWGLMpcHmZWPLIQb%8ANTSCn4Z2|bFEh_QLfqOz zBN{-T`zXB4VpOncCaEnkq#4XRN!m*?Vzx=4(!c{G5Kv!!>L%Qjn2QV%K5S~%yY$|k!b-CjqUL%E5stvPYfLu+n2@T2f={<1 zgMoRtk$xuJS%$k+LAr#dRKTktE0%rS>t*6HH32d}#o2bkz8JY>$34^$f2FUvmtIS? zKd?GOIR~4v#}{A8;#9|s3J0}yPW-aDo4p;w0f#%a9226JP|gHt-cl{1(5STgQ!=xS zFxL>Qz5ZtyvW7IllE(ONDN+$_+?nNg>3e=km(uiV^H*!EnAbq$KD;2Sl!MQgqi4!dupQ7zS9aY)- zcfw!6{Iuma?S$rbO3sLf@1M&jZxrjSfq3}+=Dg5{)uy9Q0iQ$O)!e>EtQs=RaE;e1 z$tC?&5_wxWxQHEqY))xRyHQ}741)$$`IGCjrJYbdoAm_@mI4~uf>ICun2~Je^<2?3 z4$mpC7P(lsehN6`Y6#^sVB_Hr&M8qeTYrq9z-&vC54W^z>b}bbaZ*iyis@rrQA{fx zL5<;xm~v!(L@zPjOSF&DPvuozV|(1vdHaB)y*Ejhh4nike8*x zGu#H5n@FOw2S}&<)p-+*c;luDGV4}xlw-sfk1MLONA1Uc~%)#h4bUY+CJLD9Mz2o9!q%`{HLX_ zUYxoaZ|-V;gt;oyDY2Pwy(JN@GqvU#A2HabApYWbetQ-c3V@)+olCddZ-L72XYYl{ApW>q`S z9RLMz--6P>bpqSJ@6Rdw8rGsstS9ciK2D0$m8sdKeM3AbKT*H={&GPsK`6HRi`Ljd zVu@c=smO^bv}Fzm!8RD}=Fl7tS*t!Vzn8Rxq_awn(K8i!6S(sO-iq;g7zOa@f~e3^ z^MK;$(+)#6gb7Fjuohm1NDZ#YJ4B|mahpKKBC%{Hn;Me3QTdmA%d5&Z*FOpLiBtuU zuK-59D$l`2=;e7riH(fAzKQ%=sT+OB(D$)zP68%s&ozaQa9k&U>sJH)m;A%}BEuEw z4|$mFvz45(4@onsAlG{WC$>m;Ga-XsiWsph08#=@QzIsTd{OnSVNfV;v#AN!X$d>p zvI?lO)z*9zni+_PLJ|g8cN|{?auU>w$^r=jc>#F>+tYm2wakfM6p6XZnXIl$XzFoo zr`Z#dyzg|wgnV%7;ye5O7e#{z80ce3LwlhuE(BwRl*mStVh&6V4a$Gk!M(!&`REif z{D%-F;%KAIDTxc|J{;^?Wx!K+0o{(0@hLS$pQrq~dtth1Yrz%=3kSb(q1hr|CJPk^ ziK;meiXy7<9SD~vO*IU|jzTKX22K5ShY$_X5iNXa6dmsSAv^kgWUmY7LS9*%jjZYC zl#Bn9eig>ER|%n8IF5iV-N%PXIW{*BA-ez^7>z-hi`&s)qZMrpoLZ}mcu@Pv<=J5frjZBq0)|0dr$zLWY6EC zdJk_kl>tRObaCUqB)`9(u29lAXLEPaA@vy~->R>F)l zS5y&|H662NP%TRy$=K|B`1pAu0a7SZCwMfhIJ*Csuaq8ysNT?qdcWw_pDTkjX6I7k zqWd0M)GI;;Bu^-Tu}i%SR=>_{GD94Ed^3tg8LE>t@zIXF+AR{IX3;*WW#9AHG0`!f zepvNmGMitb;P}nv9%BYD&vcGXbKynnhByW??hxYPzy0l)&9MIHG~*&v4RMUo%UP9a zssu5AFaY@rUx`s>djn8jaw}+EqE*X z#zb|;6d--ts5@*f4uj6H1~xym8CY;Wytj_WM1q%}!Fr9Z@zH9A;&Mh`qPQ#@C@^2^Yoi%ljz=JFHo)D7BM7AY^=>BF5o zeoUnzOR3x+i(UdUp>P$7>iP*BZJNFt;4cKaie(3aYMK&k-@Z%5B8irU5GWMRItn4l z$cfuWZA9X~>Nu0;7?mH6=NzlCA^THW%f!fCfKOn>dj|&oq87*q<$n$wq8q|d_&O=Nq7jJTxpI^CT z2?w$`Me@_0`;)VAnzbq&;zI#~Gv2HHz!jmi0Rh=;fsjS} z=k)f`dyD!Tuj_aDENc7C<0Vr+{}^N02^k_}{F!-<;`n)oJZVi6{n@0YnKVhto4)#e zWIL4ZfAk-@{F`|_HbK;JERzA%YQN`gLX=-{k3t(@#lWZLm7I0@ZQvFqZh_(q!)(v` zh=mLkF7OTKQ(1MZ`S%ies(KC2u~yk zbtQ;FFd9OUkbp~shgE?5PaJp=*eMU4pDp$Iv<%m5AKa?xP*eYAh}%tg484RpnYbY% z6aGVw9Yp_GCGf#GZw;T%v#7TP4O5vde71)l4-a*}nly@4{Rxs%# zaA`NuqF5~`8Pogg1kP%bDwtb5o2wfK`|z%)^X$6NPO~g-C6{xNWDJW+H{1x8XIq>% zw(Q0>+B_kwBV3cs$^})Kd~`=?I)du^YgWmH$xMH8AGI>FW6I=dCN5d^aX}KJ)k@~? zY+Z6WCR_JyEkCkdxb1rErDY|@xE6JL^h!<3kX@4%@`;${b%i!I;XJ7vO)e%MlGyI6 z?q(@qgn9cBD}-MTt4gT-9#Hh zhEp=_tqyFOb!%`Cf^q{ss!*3xUPf8Cf3rMFqz3IaKI8CLAi1)^8nwJlJyXoJ`G8wP zZ}OHYSc7Mb!It0Vvl7c^LHK|Q60%R=ur>>p+#BiV{EFw+izhNQUWEh+ClbtH2 zch(lb=HwH*^WMY47kYVgLI2#-7ix09S1~kSnKBl7x{)pTyKpGhcGrA#&9^C!cPk}6 z0(Dm}@CLsnNk7kTQc+<68s8&99#$^uKfrmm@CJu?7u($R%#y41Ce<;`3+xm{3eYoPHenyNt(=g*xz4w?Ri0N92eD;ExS;S~Jq1wCqO12jlp-g}B`VYo>;f1E z=|;xms*$Pr#jwbZbNTyGsp1q~pLKG6QO`6mBVukh4nMLrcoHlcS>GHeObjV_rGBYO z9LEvklhhC3b;roar(U$*_*j4_{Qivwee=W$K;vQ8Ogn8FoNj2sXDn%X0>pN++I0$; z(K7F49bl0Im91%s3Ao2g;D@`}U}@pFk0$Y|cr3AIm>%rSiyMPo=7piFJmIEaCjJErw%UY%nN zNEDMSuY93x!!v08K`#vouPhVf9XsVp?(%6(gm^Lm;xuM8{YfG5lP1U|BSUrb)Wm>sSvNX;D_sDHZ+T2fm)TFP!o2W8HVFH28^&0nsTi{`S=P6VjL z21(Y722kM&9_M0+)1|87Ql+FIy4>NR&89`uzada12HY zAUF2gjvYHP4wJEr4Y&@(CfdSLnO-;^XNExP)1E~Rk z#ikp9VQC-cOxB|sSzRAK zRJkow0#M_3J;CE{M-JkK)2^rk72s+7eT6_o(|Q!rn5e%FrAQTz)E*|vV0Ls`Lx^xq zXa#A%5nkbqo+tX+(7Hg)J{fz}2jz>kQ^6wD$+i1X*s{mSY&81D_w^k4#C>Lr8qdGr zOdj0*T$vc1V%5bKDcntoN*NuJ~9H+(-_!@CvZ@7$l-F=av-sf-X=7Rh$_DT9R?xd+XVy zqJV|7#r$Kxf3g(3QANJ_#7QfeCm~^)3|~-;us{?WvENtmEN2Y1%(5v-+q9gi6nINQa?uLLY+&~-s&K#h10p~A?6@PeGp6^kaYwK^A zq<$+;qHWY=f_M{wvhN@_lZl+UOO;hKLz$*1zs}Lo01#zWM_Cl$%)CdZ%~kBQ;dEe1 zv$v7MUI-neY!SMg!E9W<{`ua9+i;yrmdRSoudAQ(_uySSMJW{0bN|Ner6DM@=M+P7 zC>!8PsnyQ&SZY2(U-ivpT0-`brup}M2vs1C51LD8{<(^S8-*>3Y}bApx+XKI6E zx!mtD3=&A%!($c0dto@^&Z`xsw{qa}{ul>b}!a1C{L@7O9 z8sqSTXDUsWbcRX|FO(Qm7uTtou`uT*Ck6d-ATd~4roZQ}SAe{l$@2a5rod_tj-7rx zrs1pl_+dUl3cx4q!s)8hLeRh!gM}7jaU&zcG+(^E5d$&C2NgqMrJh_1c=zH(eTGP> z0@R3OXHAs_BjB(7MBfIix&erFvKO@2+d4~Uc!rh{;&B#^Z`53P>9QPuqqh1cS-2-tws-oE4-$cfisWC3Glt@jICOs3oEs~uZ;4B(pcwq# zZl@9?@@9ytCP=J1PGv+H{1_6{bfb}4udxPw4B~c6JYoDq+3xy_=J%wz+S-oa#IEDD zO=9UqK{=+t*T{4B7y^!;mqS~N>B4_KTxLj9-b&IUwe6IBT5PT-5Vu%|p@#-gn2Cugao$(8CWt*L{R*c%|GlK~ZH9CB6?R7? ztJI>UD>bkF%0f}LcV!2IQohzs2%EcZUN78?dSU(9yqQ`E8EOW!*k|)^{GJahBVULN z=m6t)ux7Ek87rKkN1(wG6?o+T&9C`0l0Bndm;6dUC}t+jN+84gK$Y*j!}3tj2Ga0! zqjFsi-7%`yrqNo`xAHO!Q(DZXV8M<>rN$ zH|?^#sr?O@Y~7^oCE}#Au6TFjb%BMhAv#v834iPX&*Y7KO9A>}EuAgDmXvCJWMK3y zxuTjyF@tvug?yu&CEM(lpmbRi^=%u%^{!YL_W=OiR0-lYqEZ$Sj?gMJs3I2pQS|)fYq$&fSTOoZ7e&Wz6OWSc+*tBa1ajfh z4I@aGKJ4k?4zV|v6UvTz$u{7L0$QA|kNsvGzRnF3S?hawXn8gNs^y^!LO-0SvF$Vd zw?gLr*ELb{fA6AyK+hi2FgzRb^&QjSSbJI_;NkrdYG=_fqG&U_P$|owyhoX^ePcDO z4@KH=^YH0tQ}|nTGKJ-6^Y90%SU7T&g1vGfkQzV!Ydlp;vR4s@tjZqr{v}|m7A3G& zfVJ4wzt>^~S#y!f>S2wZe8^5YTR{{!;@RNS0;pVQ_HcdC+<0OZ>j+WTvyipN2V-cE zC;$i3zWqUwR+$CEMq6j;eAxEQE`oQ&Scy>l;yP4ZiYu2|zBP4~C=2pGl4G~*(rPG) zx#0@vge$b68>`tjqX^-f8UC*0cw71zJ-#2M)vU` zbgBS>T{g5|sV3$> zcj_wpk*6{HKWB@!3W)4{<627s@?psW&)YG@T0u6=w#i4aDTc9SQ|`psSNg7%04+e$ zziFtxWfP4*Gae&b@<`oth@brTf|Wb=ucsKuwLX7kdDZ!5Humf{bwg%&?L7E23`Aa3 z04XsJe9+1}^7LoZF9qFa{URp{AcjhbR?4EYY2Tcb1E{egFn*0J7YD;+!9Et>A^_F9MIJH7{TzLCQ#@=wJscOvDPbuH@zc|gOkNR{z_g(AwnEv5U*L{N` zvCv`sNEIyyDjwb&|L}Yc#tC6z*#KFP&F;{ScJogO^UTyK`}V8E{(-ocx(DJ(0Zri6 zYr%kt0pc5$4EN8(FVZ_%gEz#jMsqZI!5ph~rReu}Vxt7BoYLDewZtOMK>qwk;mb`W zZME$#Tm!y4;o)JlJ~wtD=0@&v;1Ay-fI{;%H0QCf>Qzbff(PW#mn|1)&&cJ=0yekR zVzQbv{UPtf;(INFI`X{g)lwh-l|_ho0;R*(G3ibvMb=9QWI&$+8nD+KmQ9M35bS-V zXL?3yL<54rK|(86gcac3SUj7#P$LGl9;w0=h%StqAy^|3G*Y+wUWx_GH06HgH&3fH zgM*~QR0O^tV?r~loTSgs$J4u1N&-aoMvTj_k zkIVw9S62SVcF3m&#TTqY`b&GF+AFXo0WGM;Hv8C84|BMtlf@m;9n$K%d~dD6x1XZZ z9{OXs&Sh!6$7c9lL0ho#EJQVmf{?`u5&pCr45N>s{V9I~L8iHL4vASB>Xug$Cwzk+ zns@My1(tZED3GFk@QR5vK5BXwT2iPjriCDAy~z-Lt5zf^*y+G43-F};C3eQzCaAw> z>M49H%Jv>S+QO})kD6hSJ`YU$V|~Z9pBN9K?s-G!ZfHQjU1}8ae&coyG*Ai&c1gJ= zxs~NP6%dLWDy_!YDCo$v8KIl9H|uaXN`%Uh{j+-sG!&Mb;@KG`P`piHI8*WW1BfE6 zE1U$BsQIDkM0DyU+u+cgHLwUy;IHXd*uaq)dZEz&&RP=1tey@)(@p?u?0^1#Eokl8=xffwSNQ&Hx6OnmerBidlH-0 zGbLF5B4RrW#9n(dAv$8zC~xxd(>rwN;7Z}{aRt*^=+8HwFE((k*{`qIQ_vfH5p!(u zIx5Fjsb459wQuv6kU~s;B3oN=66y5olwBSx0_Ap#{qWpR6HweM;i&Z{;+Xs{fq$u>!$teGpU zd^yjc*}d5D=i~ap9J4w7Lt6om0%keA2eWJzL;#C<_Qz^hitfV>FG#^OmvdSOF4)G( zE7eYLFZr1BV`~lG-IPze1ETpC5_))JRjSlN`q?U{<8MFoq$rFZxIq8Ua$>A`5&@oAJL%6gKd z(JsI$cOIVlLxnO8U)peN?n z6s)q!a(fK#zMzR&*7yL`lOon)Db|6fn}QPPS&{zJ>{u>UU3tQ_!(y6&iGPJb9^0ZI z1-ARFVukyW>F3y?b@usyk-ju#(0^Fn*B*eY*{W_zqsF4I;(7A|a{@6Qq4%S68>28!CE&TaIP-?Z+!*O;HkB12=|WRva*L`;bkPIrcX8Z` zM;mqITBPXwq_$^4-QpfveO5fmE+8Br<$&Z}-nhewe>j)8A6JvIYYLkCsJ?0W4C2`$ zf72r<#X2|i$0W~29aU#YayLhdKd>nQDSx_q34a!vMm`o}z~2e!LJH4la2rEwPRyuu zwN@`vHWUMi!q;|qZaa_l(Io_S^yntu^C@e(hNlwLD*OuiYrAc@cN#lR+KyY>D4(r2 z9FGKHQsg9PJ9`6ZY~1ik7{ik_a?Pg}4}qWM`8gee3}421A+&3N$+%q6`ih>#FUg7-a?fc zXzLf?)p=L7+?|mr$sk8)!$hHg0jMBhs6}rkYmRb^b!yC&rh=lo=N!P(lSQa?cA3$o zL&zbIMm{l?Z_qG1W|puCdMqBP^~?ObI^7nkQvz*tX$Rd=F-~g}NT-=la~v4=E>0`90o{36r&)}D*R%|m_QdbXgp*qx#D?+w-5_B#d493R1e2ks-VH>C?tzW855Ze zzowi+vxEmAzcq_k!E`|(H7b}jpQ+52&t?m)%sjb zYn8Ot-XP4-Ctzbz7k0Vtd}jS?M`!;+lw6U{vZ8GVBsB5lojCi}U^i~m|0w0dyO@Vp z)>k1tqPy3kVCx%pVMp4=J@;g=^usejDNnb%8}ZF;919u)B_dP(VW-6OFcuoX2?3+T z)(YS+lSomu*_E#|urJbhN#3&=af@^EDz$J)fC4J1zt7Ijo;--{94%rjlYz#z|)@d*)lh8PmCT$W*8{$kyD_Lh_LH3dVO9m1S z&^S`1K`WHWmLq$#7YcF~)1#3nLYLf0{6N5`b*Dk;tC~PTBzIM9QwQ%ygZ|_b{L=Hc zmKw+;%VJ#X`UwGpwYNZ3pQP6A{&ljql{@$t5nlD6h)yz#j-)?ojS0wSE)0~qXa>@x+}D`WDvWz3ms$!)@o(~~#?b_z;AhFqNQ zh~07J@;EJhsWnaSi%#NTa>}P7yKXnJ^Mt(;Uj}hBj@;%_KA|ko|JgMBopQRW1Y>C0 zQCyFk9FY}@9?LF-RU#K{5Q~MKCw9;_FGvXZyIj%@VE4$&g(D7Ud)~U zssEuj`0-)e>hW!}ZPD0cXyqx-mi#>t`BvJO3UJZTGlwCd=!!{DcHW`wQ3&2V_nnDb zvI0-G*g8n#d3!>zN71+(q}LLYseJzSx_dcEbZ|VPZyW2D(>Y%Xc1+6DT&JQZJUu4# zM^ZHXIjA^(U%)67MN(5qqEiLt6Vt$?=9CZV+d_dxcmt9FhwISdJomJ~W4s8Fb|IOg z&L^YNd4Zd#8H_TAK*(n_lU(VNIDaf!#;fVZ*;zP3xUqkrQf@a!Vx*f#B)@KW` z$k1M7H)tl|Cbo=5*Naa(G&HU0HJwb&NL=`=V>PWp@ES5VVwzY7Z1!_uz|U0ZqXEo9 zBEs6>LO}@oQxU^KEK_R=nOQN^eK7L!eVXV#oP^JQ)a79|GxQ~L{GaDwbg?WbEdknELd(+Ql;{kyaap5%E zfwsnZ2s_XdGl@lK*uzz9V1N)QNlAFo4AH z`1>@p6hM5^QVz5{fF%=q;YJ7We04V-AhdONoOK7UKsngcg+`#Bm$j(R^XQe z6Q3Sp46%l8Nzf`r)C9h@PrDOE%-rLcW;8KR-jIAwW3(rfxW=XhrxgN<$gT%lAx3sQ zgR%BJ#wybEI1y;e5sOXWCEeN=oH=s|(hH@&0u}Q3BF6-!eSz*fae(bj)h5p@071JayzqIgg;`kTa3$E-^seD7!^&2 z0H7&jixB*&7o$61a{6u#Rg}C{o_eJ!zY?$9aF)WzP!tar7Pag=VlK>}v+Y1YG0Y)W ztZ_$I06bkeEcq@CKj60g+A*pNs(Ou$(FGXM>cy{s%7VQ`b` zT{@SgQVe8j#G|vdLcV;8k{Ny(5;I7O!P!pY`HA0VP{3sAh#eYzBbyCn)LPqODOTt~ zCumIgTjZe{YEL+_^R1DC$71VfM0Ju7E!Bne!(;^ZFqwX7wg|bH=@i-X*eIu*V|Ms< z=|uUm7(nsunjdnVS^~iWtUk`-EM?`lrIsHInw1SE6aes+25#l=t%5Zu0WMgvD!gog zSOg-}N&3hh{KRqp4z9Ilvm3=i^!E3LJJRxS;F()}0J-KcA@LyGsCNZTY5!Iyl|5q) zZ-!V*Xe1?>nwb}JUJN_v7x}@14#U?^$j*xpUJil|drQ)6zJxJr=rLuAy>_#-31Dz-@F3Mj5 zI6JdX&{gpq(z7=MrlLhR6p+^JF07_MgMp0taAf5YLM;>#ov~iH+&yJ<#mwK z+9b}$*;3^=PGK+N4}_wW6OZ{bX4P^DWhv18>c+>CyYK*6gDWt3jS^BgK$@v#0UUwA zSe^L0_#%JdCg}OO{lqjCWZo*=nk<_ObK4ke6(zxc^CP33=vkLG?UGr$dX3D6FpbbY z$92+GgUW%N1&K55G*X^kzEdnwGEC`lB znR<4w9)>*Ae2L^0TUH@aXVj!B?`v zDC}!k3G5sDg5}|@kpoK<;C)Wi@(TV|4<87(^1|kcKS{j}<$DnJ1n{W2Ad3vlkkIqE zjUQOuIchE&_d=*mCl#RkHGx+<`jHTD+&hmQe;{Q6MEYkH#}Rn zrrZ*7yYaqrf0`KFY?Ut9&Lp_Sg^F;IgeCV)&f2^6lsU`DsrCM0gT#;VI2b~-jwNvm z*`G+F&^2{IlN_0!xjH6%1mz&)*m^WO<+GyQhG+KUA}}5GV)T0cxu_~q>S?}FjN0OT z8pF%C@yX3TDtnAovAb82ArNK9QyClx95GsbdND+;hp)(=3hbiU_+%$vA$np+fPjAX z*8vS4pX&JZE%KbzKPj!3wK=|pNcS2$iYn{xIg+;)_VkEi$q$5dg`5-?b9Ii+JfVb1 zn~Dz25}TkzSBIeNYf6`S6KABDhVfyzc4+p+8kQ}dtjs7#02P74xhofkYW8Ulu=BQ+ zBXBClh#L7&QiuT3b$SpckF3@_1AQaHn@NjMN#Sz)VCqt;{R3$mX&-KGv`+H}C*hZ# z1>hxE6oLXYvPF!Xt+hs?VgyQS59p9-ynoc4puC5vM$r&0ds6wT!q%48IOj2~vNwjp zfD@fxBNLNMF&Nu$d$6l>G>{A4(+(Q@kA^>BuA!tIO1^xHn7zTu{a#)+4Q|As2b)2J z7&uW^_L~{T#2VW@tC8xm%T#|iC$BaxU|~JZZt#WLD^5p2avK@4{mbEb2wG@hL=Z}g zS)H0dki3M^U_$vgdi37_%T_YpL8Om1Piht=?~{&;WWVlX&V-kOmaxxpxo2+1Ecn#` z$}Rf@w5DBf0q7qpq8|!60Xug1et3OMz$VpF>U#b0pXsILQje>!FG~xF$E6=e zvuyvUxk@2xSAMDY`a{b!CVXpar&2(}3&|t&q+ub{D=ozCAH8}^L{mfURBCaYLi?#8 zj~;W9>P5beaz-2$yz2n7X?T=U%{1A~9=c7D4K7R6E!H4NlV%8RgeEgTiZM_; zjJx1oQK`j^0QTJc`0Q$=^Q@~@FL^{XNiGK7Hif|)OCN(pwr`}N zZpqH6P6zEBauIME$`wTtK1oHQwBD@Y0Z40cM)&km;tcQ#PzAA(gm}XNMTzG~o$Ydf z7~FM7N3)PEz#AgBe0(>j^&9!^I;pigF~L=+k~EfJ^jha!9W< zhMLkHk-E3~!uam6e{L94nsIYEVSzv>Qe{SR#s!(l?yB&gK!I7XHy0dQDuNfvxE>ej zoF83mxx9hyd8!rHJ*D*KTIIX`(l=Yi96@AzCRvFU^yT(5gct|O1(jqo5+PdK($F$m zCiHmD&%>*2s&;bBGxCm3T>QZb{sF8)&GXQA`Imu@Ebk}glZOwOj{nF`drLJA`JYX5 zIw#%D|Fa$1Jl>|wCg*SdcekPmOg<|$T!pRMuFw|dX8YzNFI&!g=jI5NyUuVCHn{+v zLkk{AD$Ut99Zc_$fj<&rMw$YH0d!UQ!un*+QVafQo|4eF(qWIN)XAIBU5#t^7Eui~ zHt&h`6g+k7oA{50KrQHK&r-X#W%W+dC3(z3pQ2yAbV|oXUsAp^Om&`uKcs!ryy|3ULC{R8YQXZS z*H6r2Zb*#J?#~V>U)TwCS7^z8qG3>AF+F~x$j!L9f9B7N%87|IDe|aYgF}410*;O@ zRdkzBj=VNsQ@rcL0?^MzlqxJRIbkG>h@!|C=iQ0+#;p!kx4R*GR$SS0RrP5Jb|_Ai z8!2SG2=spdcX4Rt=%yz==t2PpNxXHl%5L9G?+0Mbi0K%ZTv~7Caa$BE1!Kb`pP*y&0bv7P-3_9E+CTEhBY@JK6TqgNr#MY}x>>zwDkyHOOHafZ~lW7AK zO8l{5E;X%HQ8wcS69HK0-;Fu5?+;`3wL%)mTnc-hH}&V}SWX16JQi|9KZ!{ccbcQT znQ12vdC1Na=}x}+xtC?(ea@NAxmr~6bm#xjbtM*Xk_*j{ceV^*q2T%u;%(t2+fj~ME<`tH{4DOVgtzkvx8Agdr^qG zGcqTlVh18VfK9s-0uhW=;GPl~SQGbHldN#FejmEpIUvGjIm z)u)GFEHpjg*gvtz`kpK1<$yXxV`_M%-?CY;vj`aOaAr{1D@*iJngdY}I`Gu#X&<7g z5HHo}Eh&q^p-v<_Qb_@3(|&6aLpNYigx_b+n|#l_bKp}J0VyuefQ?x6f*zfzB>tLbN>R?TY9)RXRdf04rwkbR0U3>Kc{We^_}&k zIfLjzb##91&pg<^VK(aDH6ut9tZYEYWr1=>JtGu|pnQ$5;&>epN%isbw^0W%20eX6 z7E~eZA)L;d5!LwZG=XXSKj>}wL{rjtKNqurz68z>2`yMIpM?=-OBxmH#7Z4=*|b?F z6T6BWz5~QW{V*HJA5Zz@YmW8P1Es0dRK{b4Zu~#(zUN@(r{OELbY;F2n#HX660`S` zA2udF(}LtFy^1BP?B+|G4}Pr>%Kl25CS%db!nRw@Pq+*x&M9ZKE#EpRarU&%Yh7@gor)9nQCnJgXCb&m_8g zR$8lB0kMonO`c?nePn{@Q!DW6FP=D%HSwG_y`0BmSRi)oLV2<$>wav`S+d)6oROOa zWvU@lq8@`D?>u|PQ0EFkwFEG`Lw?k#;&^j)H*0O=1t(rt(kCS5oj_u3PPQ^p})vtld%Wa%qr9`^9J+rKZWzd z$&Z562gq5|X$`kEHS_Ew(P||)mqX|Jkg;Xkz9aqr3$X@#>l(9bIV7v!JtOxc1Hn#h z7bhpr#?YK7kh z@bFVQZ8d>Oot@C={eMNPqkYjlYiW+Z#p0LhR8sUT9lvdo70(8?0oD-={5i9K|1VE8Q`eu=9$TCV96Au z4NDuCSZ|I=_{|1b%@+bJpQxPwYH?;Ny3+U>%leKNTl3%x$Gsa6c1RJ3b@bL%m23uB z7rgc&w`1?-nwRW6VJ+5dYcazI9Kd{t7Md4MWb{tCl1QKfni01|RZ4HZDa0U6v^Mq3 zYzl+VuVyQ56Adv}nIg4=E1~#6-;B{r(z>)+vtp0ms-12?yrn>wf*z4DUnY&FNmz(+ zP*Xh>gzNS?Y!`##AwWlck?d|WYd_iNHIp=-6rW^}omEVxsU_TBIE8lta*))bc$4`| zEk?#2Ti7|}$AtJ07xl6yfgtNbqC@V=AINQCOTrFhNr?O58fp}r(JYLM%tYqpK6?jq z8ew+LC@{SkE)Q2<`Q>yP?yG3rb2~(q)UnyUKz+>6Z3OrE;Lf6t?C zDF&&@YyCX=bA0xD!iN0IgT;|N>hO1TxS)iG;+({Tvgnot7T5OkM+r)?O+LW1>#E9_ z=)hAC_g2`nU=MP9LBio&ww}6y9Z)V+EMrcgyt}kKRHt}4s8+I{r9P`gv#uoN=pKS+ z?WI|mxJ}b#I*xAOAUsz+zfdfyD;7I{xP}09T0)?u^Xo3OXOw4AWRtXZkcsltEoCs8 z!+^+3ke3)5b?yYpHl~iUcQdQOfm>Q3!6~kmN{M(Fbt3=FUNlgM;tN0 z-la|i_#KcQ?{op-KQOsmh!xhctLEXy9{)YIX8n$0TH}|sBku{3M5Ebhq{(N;#eldq zMj<)&>cbOA1pkBI^%#+#@>mL4fKQ-%_ygszPr}5*Wt;h_y8#bYZW3zhaU^h}l_))0 zb`xP0V6eKv$w%=jbUo`KCmaHy3&cB~?D0o3{aA-f*8ok%D(^h0{F-rychRUBY)qqTK90e9K=_u@i8U@psVsuYPZgU zE^v-HY;Ir{O)L2?V2u(6k?-YcorAEs!56|N@RU60H*TY>a2V7 z-7^xs_6lKm)NV4Zxps0Kou(qm7X6VLLT|4H(-|~U*m1>PH5%cihn56Pi(fEHvmwalICcytQcEAwoNxq>xp6IxAm7;{?%Q9Qf4U#AZ|U$7 zTsO4@5Z2wkyZ!1{{iC7gLHeZ2<;a{zWNvU+$1mMA!xjx9+?RdSMS96IkMx3UvZ>h) zl^d(OuaFoJ@kp z@LZDy^vuv^Wj6h;--Pb%$|P&p_KUlAQhEX-2hmprLC}g_&l9YFvJ@y=Lr$7F&b+l& zll;5evLzVpvdO~2!r?Zz$e3ig?Wz26)&eX$;9c6sR)K~&QGQM@1BxbZHK_$RXRLCd z%!Nv{tY-kUy*0~z^3VjW9&)?|V^&T$!R6qTv$l$m#-reI{@n%kXcN)k_*oRTK=kSs z%tz#%gPN^-LR!TY<=3~1E6oH_UNtFKnat>B6tiK0n~;w*c2^pvOjw-yv~i@Rv!Rv6 zF5!5jXe4=$dWzhw#Fv%BPggGK<%{JErcrehJ=H`1( zp~XT!B=uGb{OCZ`eD=F4nirtQcz#juu;rqe_Ruanv2%>u`!MIwC^Mq|QF!rVw1V6* zl4KhKm+<}vECS)uRh$M|^JLx7234f}T;*rM5jvPnfm!x8upG=k7E~nxVrWeTY7*!7 z&4*){h9H89o})BP%iU4)wovND>9|n4-k~QC8U1bgDt^V#`R)*lG35HNWkjP`!_Vox zRjltQ{P_E9rMaV;@|9P;_Nodv?d)Kbi!gY(kzv~36^%)XtO_L|{={^Y=(twp;Mu6V zbYI?^lgar{wOgH3$z%udE9EZ!)a?500)!rq8k<8x@2x-rB%>whg2@q$>(PFmYTOfrZsv~Ibw&OAf4EWB4{Ws-gjJ#K}V>uvt7{3VBf zD}&dfg8bXD8JhlRMmnGwD+&|FdeeFQl)hXjpP4bjto!fNrz?ZuGoL*4>~=o)QkimU zlOJ%dbh76?wA`O1G$Q4&%3p5$8?d87M7xQ`D+5Y}D)T1YY<`EPS4?Mdr8IE=VtMX= zG_O@vlL3PdE%yRommhTagv*^MoM3IC^DaGAbx}pb`NF>QQ{-mf7=!8yZ`5e{!b_`? zI_H}v`0H!u*@VbY$p*pQcNQjqb8x{Eq9RokDU3xBPwF=<1Jpkg3)#S`S~cNgrC~Hd ze@3mhHrCKyKs+`Dp2*LK{`E8l4foZ!I1CwDhD8~u^u{q=kUerLi=0gqj~|e;q^7

    zW*4Tvfa?3NeclfJB&UOt#1Mo>8JWpGrVXf<@uI9iNS_o&iwO!tGroK0qYRy1DjShdEpnA0Fw8=HES5wzs`+9@{!y6+<#Bgr4mTPJX<{@H~@uFL1lOfu82 z4H_gf|ERd1WZZpCaS=6!D$AG^Ks#!uC66sA`w~Bdn!i}$v#Ny*g_CwY%YoXdGn||= zxsV})g-K4+u0e;gNl;{Pk?JQpRgAp&{3rqIclsQT$VI(V?pyY5T23XT^J!qn2Z= zDV=C5QTBky;*VJ6S8BM%YE*}?aXh`_)N~ZinziR~?nAiZ@q5l)<!nWWaX^RI<6$l13feYJ#{)DuFFb~*U1@0kS znLYrz>Cf%ktklb% zmx+>a6j3^9Pp9hz4obAXxm-@iav6ky2>Ty)X$9?iT(w(6oJp6UovJ$JWggX;)$ngK zA*0yLA5>YDFen$wqTh<~TIx)40;TM|YAq`b>zGa#ckQA`FmG7&Z9KcSqiR^`OCd* z>&6d%%}1H(qSZt}Ft)4Kb>VFk?91B`bei4#+JyxXKc~_q*1k}8_W$V>9kN13maqA1 zUp^m=2$U`xo9&mj6BL2)m)gi?)Okv?RBWI64RM4r8Nb*bXS2+9K9Bydy-)9eo?{~b zTl(we81K%W@Wc&JvR-IUQa0&*G-*tq)S)1!vE3%{@r0ABY`Q3`Jf>1rqs>fYt)%bF zhJ89Vmt&Uz*;LwX*OR133FeL#omCaJ^fnWR?UrSFtV9L}Uw^*nxo2x4DiR{fskq07 zM(p0?#!pdvZTY;CK4<;wVq=s=X(~thzC_!1a9EX|-Y6ITo0+XXTe*N>|H*lPz$|4C zLh~xKEH&JWqz-pnU}!k^=I&+Z5B=$sm{$OOADKT*`Rs(VfRdz=Ll0)u_wiZX$;C)y zh6GXErO`Hj-#x1(XyvZtk@5^ZEh5@tPK_{*7X`hFziX+g^&RqPWVDJWv^%R4Z)OQxdGBNb6 z9}q9nWe<9etmN{bdFZmE)dy7?q7ikrfn)VKFK`os^=p*?_CE;rl9{~f8lr8(&bs*y^uP6K57}D z)GQ!uM*9k*S0eRv(H2n1aLS9cy_}k~!?i=4B<9{>te=<&hOOOh5{|rSI+-Z-xmEQ`n#~{76jft(6hL3QK}P&!I3v1pc|eSyRJv!{54vg!V2;T~`kgoO z0yeRnEDhGo(DNTpf3i87Y}~>)&R?{ACZZwN4ZSbQSmRST8M7KZ-9m?N2$1#z87yW^ib|!G zeeKXq(46Hrf(+ax>B?F%`)^v`qCHFSZabV6`pfw5)``!)=Kn~yvE1|&s3J2%Spk9; zYlc{trcV*I8a(EwB0%ZfPB?mjJf$b0bDI})x~5q8nmj-M`ywRdRGTW3-qiX|)`li~ zNN4j!?zYwZH5 znL8q`x5$-vuhzLb;LbLYMrOE~0Y>(y1Cgeqr&Ex9=ol1}Y>ccGFq<`^t!WnQ)Sk!v zff;b#q};k2_VE~@o^&YXELkQ=L_TX4ud!BAwC@6?4;vM{>CPH5qK;w%^;qXi4g_nh z8k6%o0tXr=#;4}y0Wx`aBx6FKoZ@Zkh61VVPB0K6=b!Dgj{L;Sb=TnJZ;Tl6Go#^2 zDd;>M4e>bLP>F0vwaVU^5`l_d%H1k)F7h^@9b4nH->nH)}Zt=TYfXUFON zSTnnY*m1$6SKj6Tp4Hhbuy)z(T>PlAWUP{uMcU;L4_|1a^8cyxqM}o{D|d1+D_)s` zT1%%){`h~vEn6>NzvHFV#cEO(1zBh$etBx0#nnjkd0$^6tbuqLPPpkq-C7+a!(!tv!Q-J+xe&=}YOsGy4gWys3T?pC& zNWtztPmy}VLbZfHN3U3<3&Ww8M^XxoA58eWH}OXANC@6CI3%pe*YvA?xaj4u4>n6o ztkPy_8m>Dio`n#Zo!FkD4)lmeZN*A4u@1n&#+E`jB5ery@y$Qq#olo2;f35J&l~tW zs*h->#PR6y#jQGq3uu*9mSXu=)0vksRjly|jun&Ks?4RY7e_7$lWM!rD();0sW!tV5 z>oc0ZPqPlMsQ?Je;lrkbH}&lV^JDDv`pR&5rRzfzH#x%g=rBv)9y7dNG(kOPaohr6 zwzx4Dd6ZXB#UYx*9j(fV&2v736#+=3Vh(+-k~ylpgBSPJ9v<<(OxC5dB${uPl46w5lDYQWR7j;w4yNaQ^O z@IaxQ|Bh545g08~0ftXg%Yr>B`Fu;kE>7zqEAFg{Y^%R*sIU6U(L;k6_cRVA8&TVt z;P)?&J~>%7VDJGT}@}E zdh}$eJg0Ytx3uUBDJ|Ltd1J27mwEhRHjR%ycjK)5cqU|T%5>nF4|9QL6kE zgpaJI)Levz8I0RzTyj92j)N&84LS5RMan`c0cm3s@G4HuKW~{!HBOOnDa-VfcJsZ- z`cJObULe7HGj8Q|;pGI+&aN^%Sm-cN1(ECsLr9b^;mNhOB!NpEuBDZXvFONaIb6KB z``o;W;;`^d@6G z`I{ZL?}|36HPfh!zF+&6(3z1dEY20dKHO_+RuSX z+-a}nx1JbZB%`axB1zU)X?BLxuJ*BR$@8Qh(GWNL$wqE!dv&lLwhjI>It4=Rzj#A; z>7q3Dizvdyl}A43miRhX!&!}iFKoz%{`0`S(}fu@|9K>sh;CTxn@cBZmrTY*`q4EF zsHs=%MGC`xrS5valsfy*7gbV%_!JsqyN-Bj6D%xq=1JdDFeat2CQ@44Yp0 z$iGohqWlM6ccYI@U)vKH0H2`oUyl;ly-_^^CcT=}kHwnIS0rwZwL>jG-~kDVl%K7n z+f)2w@D^Mdo~!6LAW?A*t#ZKUB|csEstF}M{?KR~a(j!<*Ft0TZq|~p60x)t5%w=2 zMi2yed#r-v^NJ5&cq8}?0R$*ww%%;eAF=V9A_9xvr+YK&|8Y_vEgfye`z6BHpU9;`vaZx@1i&5@@EUtU%B#*?JQa*SE`p|E;@JpN1i-oBO z4=laFIcG!mCb`}+1uZ%`st#OQyGlg!M}m)}Gaw^W)bjMY!9hXmyeSmf1jk=5-}ha5 zb6%E)W?UR=yIbp{6LgT8#K78*_?mncH7Bk^@ZHKKg)la6#f5?XC+w}+rd$>3i8-)T zuYkU<=;q9GM^&#uv+XCl>q_w~;NV;6ohP1HE zWg+K4px1Z{aYB!$L%~X??h>$3R$?_HVe&=WB#rrrX|I9nKc%dbnp3u|mc1mJ5p9An zVhy_w+@TbQeDDzAYEjS*XZ6`mOd_HZ5 z=GdHhv`W}?1QSsj+9^3^f_La^g5OL@^Cy2i#rc8ez48W@yQN>j-yI}V8bPb*n*XFQ z_2hmzjMt4AFx+6Bjg=b;T3^W{!yiHgEJ#*y<70?!+$e!eN7rbh|47>}_D8{!BouIC z?r4gYo?A{|@#D!JVm@}?W=AkIpV&qkd7c=NKHE;||x9@w;=th!J_PU_qsmB|2 z=o&U*Ux=}}9po2PKEFp;;q${~$g6xK=bp7sLw<*q(6eq7{`50WdFN!%$&QHyjy%ae zO4{#1zMUTt8A?FCrl=fTelEjpJnbAFxM4J&4-oGNRvGVGhLp!}IF18;*D<|5oUeH) zPd)8kPbzSg#&b`fXUtQG#C=VZm@)i~T$*brNeBG`FNry)ho8t}W=u~wdI1@OO9@(7 zf-h@MAAO=JS3zNMswsaUi5RYAH3pdc>O9Ej(^Nz(+(auc>n4`Q=HY)xb+N$OvK0J3 z>?|+tsz&&BM?QfGXln{} z;a&XMc|s3KXp^~W;_a*5r=*2J4fxPSKM;S+L~Qjt_|%6LH#pr@^DS3>1NL$O1pg}nl*u7 zL9^u@fMhvCf>KH#m9q6t(N3hXD_=$(OQ3ra)uDEK38`_7xnbu@v@HxFp>(5K(|nMe z9+^CvI5lYPdBOB0krFyFV{!>Npr_(YMbE_i$2_@Nsf;N_uDvqCcMBrEsUoytpHm~d z`|&g}lXTMU6J&bPvQ{>B{hLlbPloWU7V};%rC9koZkI9o?(;3|S(yZ;PUe@IJ}tI( z)B%DnlZ~kNJyn6x_fOn6vrb1%7U16+Qnu-9>}W&kr@$(norMOcI4KrQUdi{&BWZJG zr^RFnu6!y8C?!m20}AaC#iuJ9UPu%R>#b3Uw{WyJNs=`Dyb&gao=i|)-?QvJO{b+7 zo`O6E0I5(@&Yy-~>7>-sZ65hTOt9738LrQvt?fxo-IGsL)+#nU-YeA!VK~K9>9Eug z%?7~^aB6E{>txNo@zt~EvHvmw{-$<`qvfS^!eIFmYE zcSQJ84#beMP14gQnnhBxB(FI1TE&6C9l$n(x5j%gxO=lueTQqKu))jCOT%E;;62p8 z2>a|Xm9%8Ro4bBU`=YmZ%>cWu;1-$L7X{@>q|c(+Q%{l6msWb=%Znplse+UXoueK% zPEY#my@Sk#qjlRfz0#(lJ#|W=E9pNs_7jA(Igd8NW8y~Blp9S6_H_%w@iVVG%`L1? zGWEy{jOO)$F5zVMhvx77AT4%^9AzYF-uQ!W(iwCt3sVi5me{mvpY! zcxmdb)OD|Ts*u3-C|$wzH(4yVTCAe6jD`9=5xBOBZyAv3#`Gl-~~qXKhx%^wq|?>m(<9!Lu>$?BGGQw zm@NJq9JIr=4X_w4S*$P8W~MW7N28#F#yzFaPsG)|D@qtR+P3*twKu>b^CB;eu!vNE z|2-38O?iaiIT*_Vd0ax`RvXqDaA;?vxlLZULnj`cwq83w)x7CZ%7Q~!mLQ#_rmhsO z{Ff2+uxgnjjwPERpY~ScQ7(yo)2{Ev>seLb@k)du5n<@xrl?B6RC*08%!#5ovY`^D zItpz2+w=o1x$WgZK1a6(%I8^9uVI%;ZRFMR@mnfSNAVme%5@8d=#ZF)g11~Fm=EZ$ z|DH~#i!GF3fj<0_r$)}HdyVG!1@xCUUDcn9C$IDr3H)=!+6JYh5UBObRXS4{x_47o zitdbmiEMSNovBW$Uv5Dp_;1iR#a{pDHaR@+20Y)6< zMo>;-Yc`A)!-0H*^IX3S0!BC!{ z+fdmKc(HkA!s#&zKsoQ(N2Rr+Cmf~shV&la3TU3k`6MY0IZ1b zkx@pB^bgH$mw#%R+LYmxAgq$2g88qh`+A$zs000OXZcOGbW}*^rjEAr)JD5`@B?bFXn6v87_nz3UtTKt3_fKR%;X$S^#4Z z2f6Q7^<+59rasKJ2d%uHIErk5%VL~^d~3-reol|&H%}UtzSoKp=VPF{7}7r|xkgdO z*>(zt$EWcj^eB^?6a*bm6h+oGPNK%1rP$hM&m(_XL@gOwcboO=f1aJnwAP>Zd9oYP zoKi5;eiJY9a&p>Ee9Mn5iijlIHUtP+ykIr5{dhOB9lWJ3op0X5%%lLZoKMFl*k*Yh zT2zBm08d;S63;9`z)py!0qit5%iqu|>tmhI`G#c- zY!%BclO3D#Z-ZknZl<`l?pl#QXKWC3lulDIk0f5&&|

    rJKf<7z!pFqq`V(JW2ZzY$$WJ0c`?8*AZ*H@QNXNH@tSU>$)+ct2fM_j zGUkHL707~``A%7RN4l(t>2$@4h=C?u30!;ou_dL$i{9t^x>+gS$k;y3yV(7En6XyehblwCk)yh0kY3P~5tc-g2iAp}cL<7_-uhSE@p0PIEb zZBO7o5SV*{;YdT6WTDe@3o7dI-9A91Ws)2{Q*i-F?~|@ET@e$Nk?M;B%5u>OS6|ia ztJy^wI64v}k!6@F8>Gk|-4o7Cd-$%y+;k8=;tP|IShS2->}aDJy(}p}c^$i0)?u;G z05)0)y3v%$z+*s5hkXlvl(G>mMt%M&w-$^}vK~k8?y|0_!V6-|(8wI+w9iid&^Nnd zX)@fu+3BSM6DZu;H$LiL4rN*2v$~BP(`8!V*VE8gdPWKFy)Bnqq7_;8$9>N`X0o~m zBsY1n&xT{Xc00-rX&qBTWj8&^$!pI|794K<8BK5`p=)h$Air7^rM!3wgg_g`V@wx^ zjJj0-17=+_EYYo({&YgCc=TRmomJxQ%Fl zHBM+ULaSg6G%(W?WJ6LE$9o)sK!s;=iraM93(GM3)FSm+m{GGc?~Xa5#h}2m^m4F} z^*kto*JYM%=QgGPdveoLkb5i7Cab^{4i5qvccTeV;wFC%ymZr9muFw>bD* zb>kc9prwdYNh+9h^ew;h2A!%1=9bGMN*F!Lw&}XEUc20a0=}Wk0tjdweM1+va;Ul5 ztGMe%N`Sr)@ESR7rsWJqRAq?aDrt9!%p2^(_JqipW+Ka9rRGBqD|*0)kEIpQN64#y ztCN;{er>KEXTv~t%$Y$-D(igq%?xCM69hC+ZEZ=ME?99&(JicO z*d|If<)zO`Q5M7xAL@aniLw3k|CEf`Vy`He$E$%lh`P z$LWh=h%|(GsOKP;hQS9jgR(MXn75>6jUV&Ie;q4xGFYgD7jja0H=z&*uo9^8MY$o! z@{$3y-O4B`t7l#Qf9X-$hjgAF@LBsTU@%TsDF=BNy`Zi$oX=%;b$410@86P#XTZK ztYxK$@Lnt|@G<%7nu@~`@ho6QTiAF|sO1v)O-hq5O~=7?Hb|M_4yEdE zy`>-1<*03iTeiFYcsUMDd$xD4q={xP$Ljv07d=^3)3ZF}A9xM4+nRsP;;^c{7*RK!l$8`g=Ge#%Z2$z;BSx26 zmNMP0X{UkOpjQ)pNQ9S&AuQ?5a#pCF)(bcq&PL#}U{96Mc`yBE zAX($&_ZLgJ7~1>v9Xhj~;)M;d79A`i!&{C~hq;VZdleGJwd%yhakFZ@Dz{n8FvlH= zc+|ZzHr^I=5kf>%?a!KqHNea`*9hsBau8`JJlQct&RE9;#+vrKmYltV{@f6?4 zk5yON+awuhJ8{t!jiV&qI)VBkI+eG(5y453@gmJ!FB1xA%GI-SpP>2}WMHwKSRPdH zNlQN{i|iFpjQjFE;1u0}~Q4uLC@aQvy+_1o}KKBF;?D)n-dMVu-2sx69w z(Qc-82$U@wdFgLIjK0t5G@`x9;y+cb2|juOr%liO{I z!?^`i!o1q6R3IklLEEpVe9Ri!_nwQ==n#(U;+x8&;f;^exBuJfHGb@$H-$0$m*!dj zl%*>jR9NUIZb*O$3OxyCB_23&^&ETG={2k7_}K^c<|xWj252CdlG{d)a54yveg87* zA(a(Hqb5UL!Y&zF2g(ZWJ9}e90y~TnM#q0LuH)n#7MO!YJIa#`_tKyvKOidxg%F94 zr`=3&`S!YdIg!%f5Hh79%GOyh8csO>0Bp`*{=d6fNy{)=w@bGyAYaj(XQ zNHr-iTKJH3Iw#8M|CsH}01!rWA^&51DvjJP{d4}7#-JVG+<*A5EXL z+y@vZlPH}izrw*^HWAulBn1iqt5}wPESHZ4F@g(d-`;SGU5>zM;-R5<2X~&0^C`c9 zRDwNsh3+V{re0-8(dMoctw-{wXewZE##O~qBh)7du~EsBS~Ea%R`ehj6+7a|S3qXF%sd=Wbs$Z@qt_%7E^4ieWHNkwBBp`OKg|v;${D06qE~Hp_qH%5UWf4{nVa@|IK*Oudc1L(ky? zBWX9u>e{vS#wesU$o+Fq?4hyXoRX$vF+)p<1wl6aa!gxMpMOYujYQ(=H0<(su^Ru9 zCn;+-ZDOV&0PlPox@tR_b5KT~w289HVw~cS3=qWc_T7b|D`x*AW2y)0zx;}z`RS`P zAA9CFUmqY13o~O+Xk($Qf`}}-E)?K8ad$5-_(b;0!*aW4;cuL%LAk)%UXgo z|N0Hn`>p81(#|fApuGVH=0;3-p{iBUbZWU$xEm8|;I>s5Qel)W1ufS3OjX*C_+qpX zpW)~!_W{+ry#?PzxE+}vaN)2E;dyTNJ0?E9vAlXb!z6zNwcf()&{}^q&SW7L4QIss zbihRAd59Os)448FSi((UR0`Y-&#|7+WTFeoYq`u|Iu>;yuLETuilUHBQAHvfa4KmP zY}<3zsqV|8kr!^zp@f53_c>Y8m>SoU3$3L?mSl<6P7nFrkkW-0$FVQ?qaO=_YfhE! zIf4xA!Ucjlty+Mt<4u?k&lJ$%(X0yI@<5@iePkfWHVq9+4m2m=gsH^ie68)d#@1Rv(@sPT6kiY3 z=5}pkLS#-=#99r{Z)=r*+@n^iWt0oRM=9 z7QAo+wr7G z(0A>pUwYrzsFQajdAV=R$X~80k`F+W$|MORsc|SDsv&%@_IfAWoBr!3dd$z9G_pLi zZeMoEy#p8jdyfpv(zc&MKWwq5(T=-Vwiwf%dp?2#bJLr@A$yo1>LtfzqSg+)w9v0t z?G?o>H#ba$3B|y{DnINclErhd?F0OT_n_|%+H>RNd3rqVLdM%X5_{D?T?}4p(KDFB zX)yL43qwV3f`8NXV6}>_fmk5w`x2-D&@-f3!eu>q*<&bGEXQK~M{P)}{q3E&iT+UV zlMVegUcjwpgIW4zj7Gsbhk{CMPR`UxJD!=nfG2Wk`e|qH! zV>v{M>QCs3X z2tX9qbT;#E#Q-OZKqd~wsY(h>BtmrQ^77n-XE!81iV+A-dNy!zXK5>Q>&ixK;w@Bb z(A~$R41^7Vu+iC+j=f3P(&_tK%j4l6tfsqadsO0vU?U_yR8XSNjjbi_X!5t!Y-0-& z_DSQSRU3#k{s2dNu-R}(8cQM{%?yZj-s?{iMh_JRim#+rI$lns|>wicpnx$GHX>G zhDD(BzlgFG|8Z*0TTM70n7I{y-(F9^SC^5cLo=P=LUnoc9a>Bz^<7mzjnuFX!~iDD5!+M ztjgI!qoQ|So8djCBwk25?zUFPW_r7@z^FNDNdBw1F)i+|X?L|CGYDZcF%#Rj7!LM> zSP}EH;jXnu{~>gM;_^tyh2ECun)(>MBYV8`UaD~96omEaiuJm}%Jvw!>TguyG)fIg zBubhLqj_v;;w9E(ZkpI5&xRwhd{%(_Q`oNqK@?CShoqVd@)P2c- zM91b@JZb7N7HLPW5T)iQgdg)Gt1ED`?JeBBvh-fQ%th@5BWyux3g%AG6HNtY=if^< zQ)#D>Bm!}d^?gog>N4~L-mH1!^E~owtw60xDn??SiXyw|PSYmeq8dS#YLQ8Z&nY*l z*e{oWnI=-i6(^mF@4L=1a6xbop6H~tBTt;7!|;$fJ;RX>Qx$K!*yOx$+Jz6GFW|#Y zO<&9BGPk=%mcO#gH<9jX)AUx$Kw|~bQ<5&P8Vb&v2U;Jo>o6r;gSRxrmSC$11n=A) z(C(wa9pds5*?vbwl1eRzEiu#n(O+9uHu(Xh#r0Bznb$DmMdgw-W%tG^YPf7jDSF+K zR&xzv8aDiivQQ(D-FZ3De8hO?ta=G4B;m3k!@RkW4-I}fF!O66VE8*(C6N~H6WVVvp^1tC=Kk?vpLQpt z-}QZLm8ZmwcNeyr%!ZZtAUUA%x9bFZS#u`1GgPW=V!p?Frq9*_E>!#L!|u9mCo0#- zf0u(8f8qt~1awRt0H!0(Zb&zpPZRP6)-zi|8X>9IGzxr5~JR zhi95e1>&&s@P7&mZO-Z1&GSXIdyi@P&g{lvGu4B?^y3uP5!|k`OJ<{>O6jgLnd=FQ zH;@oPqF%Y34G7_-L7juu!{5o%%zu9pV;MiZN+b-{4}b4t^&My*>pvIokX84GyAr>* z#S%4}DQ6NWmTHnt-1@S-?%3ksl#8V(C)<8I!M%E>elBhz9;_ag=1QNmaHV{p&(5MS z2Zc0$e*L4**-zzs`LW^#=FrnJ@k&*@QS&Eqh1A$Q7Bi3H;ljkZ$yhdMglT3o{X<4X z)-dLj1p(`jg=XA=sZefCh7MPevCt3MI7JD3by#`{V+fw%tAm{2*tX}#D~mluIQ~I) z5^I%%eW7nCt$FSckw?{}B*zw3H0GR`400M()7VI72Nzi#uSD)I52aY1=6dvf7(<*G z-uRE6FHS_Wfyu6yDHL?2b<>xi|*JH|;~H>_ZOIvC`mCoQuWb{SV_{A8mt*q@~x8p*IiyCv%;eo`SWBlWkF5z5TnlnGP&MVSW zNMy6HcX+5XNn$0oFy<&?*e&t^{@aV3wrIf4?U~ zPj4#G&9YWpTe8i)x5b3;_X_Nz66Qyt4362dG!}kdctNyG+(nz;UOoT*L(AjG7yb%V zFIJJ&l1p%HVZ^C+JN&s9Y7|zwvvH-m$Iv@W1qQmFYcgW8q zwVD4vkf622E-avLS?D7#OYhY+VU#;b|A%)fQjwE%&!*3Hf{QOWK(c z{%<^ls?+x0v^D?DYeg5@tm>UfI^~pS;w=m&x)7V#^EXeMb(6Xdu=o#Y@a}r8e$p&L zlcb&=>ER^@zy8rq*Zh6sNwMfMXXVR10710@^&d68RgYDX`eeVmm%_1&=bo%pFS#m$ zE>YI2?|SkD)5sxzM2O~3%TqLa7V!MkK947bqfN7EJA_Nhd0TNDpF~Xf9$8%##v65Z zd=*3AlK)Yf4znR0vPK{d$w|nH7ZzwEX^S$nd^!Xjx%4X(q(?{j0?L#;hEZ#9^2T|2 zKAFa8xT37TCZ^WUTa_UFW@Sr};3$15DuOp5ERN9~sA7^heQLru-{Cr4M#~ofC6!QW z(VZW#Vf!5C(M-Z&rD0q!Di>rfih>KV`Bo_O@5f8p@o6@+K$$F;n)fkr8c`PoK?qP z!seU(rov5tEvnQGu?&3BgHdUQm~ouiMJu6Ddbql>7Cdwp|y6G$2#K1Ux&w*G2(g@=>*3${#;I%s&06;1z^K+UVUsQqSot$3W0~Q zILyDrmDMl>9 zkJf%Z)JOY*^2XvLu^);@Ew0TY56y(El5%+#L50GjL`C&RzZ{ZnCs6Cw$wM+po%4?C zh`???Hm{yMXZ*t|bmLLY>uxX2Y|O-)$x*{x#u3ROmF6+An+0n3qP<~s{i0%B5*2ra zEbCdSDmwP<2|%c+j+ScrrBORKJsZ@9adCV@k!j}T-zOV=5jgp_eMq|O-ed+dPfJ|H zw4tx#-HH=pIX$4~Xnjct&s1qrgn24^W-U@N z8+4dXb}yq1Xm+n#Qxfxn3c{uk(@-xj@dHaEj{DnakJ%O{f*^ESm`x6wEW0)tSv@NF zW}<1joU7|YkY_fc0&*Y(*H6rHgA#ONxeo6x(HuNiL1;oI9G$+jGy(gpNE>j>p}2>L zE?8u|n2%N-xC4A`UVjcEVN#d*xQL{L@psOzkGM3_O5cUtpHOJb4XiH7e=<3&CFJ||IHX1Ohm+c!5i{iG0n*X8^xtqZ!zpiGf=x!@ zBs5IqODkhx3j1t}lwgeo`-y8}yqovi!UCwZea*^ssBx;to|r4$QFY6O^91A$LPQ|O zBR63e6gT1Lbdeh4o!Fn!IYe+&G=$P=vi33c46(zmzPg70)E|?7M!RgBxx~@3AE%*d zIh!F>Mq$|FWU>`TT$xjma@I;7=+Tc>e#AOeTZLRjLVubBRrE^2NzuxQNPUP(0ux&*P5VrJ6XhWPx4@ygL(O0Cvb zEd>Qi)bY%Zd6$lxT@-}aMdB90SNAH7%H8f7X~7AX`W2o(W!clnviw!r>sUeLGxy9h zln$6l779xb&3JVFrp1=BZdcSlm&f^nsLe23tkz(FYHtG~*|Md&Iqf$lbKeeWH=W|F z%D$WVqAWffN{kZpVXX_#7du#H2m&6g??7IkrhwMXgk%yLc*Jhwy_(yUoEpzhmIm&Z zXbu(9US(;%$AXy$PLx}$?G)s|>{34{&-66UR4-u({I4@G!IP&1;gnMeTz-pIzsB$4 zrdE^kw@+0SDtB#sC}N!&!Zit&=F=c=`(7qo&G3UY*J{#naetff(tSB?Czq) zw78FRnPXZ{i%Lb?*I0dY5BUBl6fL1BS9mOB>;Kq?s$njXw3GhT3!?+rGE4i{zK!L( z(lJh|XK&nZIiwuHOhQy#okq;)As9v(WSX68^P`%Jp^ZFbY+jY8rM_3L)Y{wTRjFj9 zXB}}sPeU)os35A)0c*uI&Q7x%<_{Ec8d@?*eor*bgHpJGdxA^6jWt!RlVSn9NrE$4 zAe1Bz4~y_V-QzqVQffbC(cm)hS*`gJ##Y)%u1X9BEKDpmS|d!M%I7^uI?>q)&_O=v zAL`_q$9hnVjbd$tAJ>38RckrV1D#PRY5jqU-&i-DgemhTUG3Ny(59=a4BSiYilpy_ z)3zvS#^w%vmv;K$K0MCu#)f2Pq$w|KGHmr^&8waDOMm8Tv~s!`c8&9v>21!Kfg*n< zf;Ace!r7&1aw>#278^6avHiQO!|6HYNNj~=g#DFyd04-lEh=^v8;oggXmj#sm(%4R z8QVQSRE#6k*^t;3_5#I&)%RI{>&EL@DK516RYO2EF#()Ek(g>~$hF{URaDmH9QbTWDNu!}HeBthek_wKfX70Ce1%1#J&z8;8dQ z!T_CgA$m0W0X{Q_^I~W$sqexTVhqU!=V$iYj@I0S)_>C-MDH7ye}bNGx?LK-`PNhO zYQIRiCtB zf%6-8%>#?`AAno2j~A_&_Rs15gitGk#=1$j1)sJ>(~5%wpIa9~%fKyqEXf`IFey?- z^(yIGYjn6xA)RppA+h+&Yz~({J&J;K6`u zvghDK1!33u>a6sBBqqX_lhPB)&$d`b@yvA6g3z@XOacgb(k3ihx7q}C1njcKeu{9- znaegxfbuylH#g9fudp8~xqS$~%!dq;UJaIquM?$yVfpBTP+=R;Evm8NkDAo9FC-zZ zl2Iaj6=F-6IJHBIFmdu22n60v9|}5c>$s_33wW4=`KyrA(Y6xN2Rn~wp6;HgiT0&l z6ayd@5twT$lc=e+l&!SAFk#u~Sgx9EV>x#Wu~XU;x2JS&$K%2p&AJsvR#FJ}Mq{xL z`}}xkcl9uV%!$X&s0|2(V5%??--`wm(`IS>r9f4KS6t%IoD1USrEBMs$FG3-Rb``t zlOvl*>4qs_Lif&lr|V>erxFJ~X1A(PDdM1XOt1w{{Y!!bY8x~LZh%h`OSrIN^S+pR zb2WkYCTz1XW7Wtcyvh5aJh1ZLsHZ|Z?{!H&MA{7;N73e}S|cNxQQNoRdwn<<>yENIGbY% zS#PzeIZp(6)Q!fqIpW<4OY&1>91WEBMXYO+cRZCy806U)md(wKC%=5g=cy{r$w%u` z`0tHt(vh_&0x(DXR%4^h zQJDc{^F5KF_@h4VUT}=FFROOXxf!zZOiDWZtwenV9basJMv7pU7>Tps$`xSh@I~w1 zC2s;7E^iK6Vd-ZyKS@QGa=opzL99?(^J8LcZbmJYw$xECKX6kx3bWwCYWJ3(iEVjy zIYky|D_jrYZO)oS!v;`BF?zF+XcJRl7ksP;HJ1Hli8tyQxw&h%Ylq$SylY3^iy%-G z!>ImE7`2%fwJn?0;?LSFgLX61x8H1@k7&vu=V1OjHYLk4F&@)vKn36@=TFfjfXHl72U!qVe<05GqJ;+qS;X}i=aMKjsD* zK82H4c(qUMfn1Mlsh+KcXKawS;^&p$y1bH~%8O`Ydi`tkE%(CN#>x-KVYc7=s*Y(~ zi~3n~YdKiJTd@EdFD`4#9=#lDHs(OHJS>G15$znq! zDZqK);NC{zd-&+kq}Iy8vD5>POtITrnser!#4$_LgU!2!j)=7#7M^IGx8{+#_dt-D z(@RRLfM}M=u;?*Pq|KcHI-b;Mf9?BI97FI`voN31SgM*x-@H5o1o5@lD@-vnn`02$ zKHo68Lq#dFeC!t}lX&@%5(-Vi^`~r=mB$2NG%dS5;x`Ikbtn6srxkjT^53u738*~F zoAtL^0p8#&TKdW2Xrkc5zyPQ@XrAakHb=cFIarL*zQRsbqO}OwnYq?9LDtUD$7*4i zFt`s+FduvD9HrZXuNSFWGDPk8MjsP!l@VN=tE|Ep`i^vLZ*V+3X7)Dpd}O80T})ov zu4qgUwreGS%Ogkh&0Ar-wgCN8rD6ZdA8`y;ktQc+#{eF4Zm2#n#0)J9I5*W1d%;`y z;f!pNPh%}MKl>(-A)G_o%dE>=nqxHAjQACZ1p+DzTL~waPdaOO@{j)I zR6f-GX&_wZc$+KV{JZu~nTZ(vkY?0Rq2tOz|2|QR@YN)RpIN5Vb16%n`{r1FQH@*T6p!Wr(f59`>%f?MH z0#Q&uT%Nj)^Qt>~M3|`^zjOOD7kv zLVcK17fb<4=LtdAUKsyFhvNe0Ks#|l^7TZ&Z6%}km#lmHugo~aF$jLwzWqwL&u!L7 zeQ4Bu=nIM4_zjYK=zP+Ph&>n9!nMd#FKeQYEvV^=CGcv6nvafxO(XqVfSh$o*&idR z6uS5W2X#Gh+H*%Al`2khEs`8k-4Knqm0x3ycPMp03Bt^e=dajk&&%>hBL|U0b9kp@ zx+AM%L=f1Eui5e5*6HY8MytS^kR^#&oVRCqAzgncW>rXJyRt`PF(dkfIeW<$J--*^ zVm`;wYx5XK9goyV%aXHFM-xH>xGg{#oLO{h3HwqDA-eDpa{(w6*NWKYF{@aZ)(mq> zzn7wHL>h&+I-3N=i<6Bi13PO&LQaB(qW~93@tauH9vU>5JEdBA_We$E)X`jx4;mSG zV69vIc*a@GN@k+Io+`O`ESASOQkI&DsjXllwIoIW@4lyA)3Qx>23OD`EYDI=D)siW z!o_RPb3k-5n0{hP`Kr|4as0|TA6ijVTDMpf@4cm?8|(K9F2J@E-o<}3kY}}-Nh>Ye zBVzb!3rRip7^K=?F4W=9T>^)?-~^p34t8Y&yrYizEw<8YxA&vef1V{CsEd4Pd#PVD zq%+mje{Hjjb~_d?^fDG3g*z>J8%Fx$Kd$4{qFe}weF_i5m`J}iJC3b!4C!Vr_M$my zT|A)&=%EkW=7gk?rnB+KbU8N1;xFVkh+4*vuKb%qOp3lQ4ol?AVC(b_4&;tzTD}PF zc}}5Nm*2F0<)fOQdTdpQCIkNA>WIfWrkBF7WJUCxhUQ4+Qv6T8YTV2eR`}rnw3hwB zo`}^sXQ%U6VJK~nYu+i?Nl#)s|5|=4Peb@_`tMh2qqkW$?ce!`Z8Myx2Jkn(_iQ4D zsF_48XDdCdv4Z-C%D!7zBDvmy5xaiS1GpWFanJz=zf@|k^q)wk_*x54C!ta70^~yf z^4hB%wJEORHpi0!M74=MpMDE|%o5(jf2mLnUQRO*hG^HGT3%*7Y}XvT(;NM+Wli&x zndDT*l_|;vf6*`dVdS-$#8Ii>8&+yM=~5*vDScUjRl>t5xG3%ay$Jk#H_a0^z%>JealAIaaVx-;T$^X82;dhcB* ztq3AB>+&&haI^0j!g2*Da1~c40Ktlbd!GJtii}m5p#Cd>ftDY;(vL)B8Mdv>kYu-* zpljX=(@Gg`51ib|a*+nA>-B9*!5{UF$Ck+z^GkTDZY~5Rb?d!_^|qVy)x8zxExx@O zM|P|m-a&MSN28vy#RRsLE(r?V_c8&fH4G_pDb10+l1Eg6ZW{Swa{a4R*rLX1-bS1H zP))DmD`xw3hSV#(etwL0_GS|Zf1+;SGhF4qX-_v_dYLI6@eGv77q5Q6!axfJQXH6i z1Vq4J`CA^Z`+f|R$p@rH=WwOVj|y(B8V;)hB+`>q^ikdy6|nC*otVwMxUkyk_0LP1 za(2;zmYrRbzx9*ix$CG`Zz zhiRuz@81*eGEZ!3!c-K2nYWbl=DSpLHC3jyXwsxNvqrTh_@*QU@0+&Qz-CeD zhE1`D7_s}NuBbMw<=ex(lZA5$v*H6-+g*rruI(UL;%3Sp)o5}$l!Z|T1n$(^=6ws_ zYi+WuQ(XC~d$-(~joFxj>pc8OLsY`k&kl|CBen6|HJx=4 zOKsm{HjlKq4P{eo>aJScdV)6MfNAHZ9i)(FmH$H=__3Y4PQ9I@xgMEu+@s3x~Lq5XNcp{~QF}DJV{) zG7*GfBiquldUTW_ce?b4r#fm>r2bKC;?I$zr#%2A5k?^|FJ9SewgB%+!`G&h1~H{& zfeb3oJw%ccVeK{z1hwTYVpoe!_e6WApw#;9^AELnTu5@y)f1f@F>b<#>ks9_^Q1`b z0qGeGXSfR3H$)zbsbn+SRROZTGPvUDaz6rpuU&Y;Bdan;Ky4YRKkh)EkQC)W^+rev zlzA0p?Wq}igdi9yk+}BWXq3dpN+5v=2t1oL?FJO;ulCEtqD7sKH3pR`PDvKL_QH*- z>8v{(Z60q|Qt+BiO+=#?pJfJ3S%I>Iu5a=LEnO`Xz|(^hGAKOyjAi29+>6Ys{SpB2 z+ZkLDA!Inaqh<>-sVZW(M5NPd+W@|{xU^U!jn;kc?mu5|V$yKp+)4~`|m7=-38 zUJNg-Kae@rR#Cbt%X7qTWXaj$;Bc{BPUzQC>?IY2;=04s1*_JNJ@>sM?U9QGlV#<- zHC}1uu2V(b`7nzq@Hjoetj^f1J-U1lh?N%YGdAGyei-X}(u?8smVh3(;bYKN8oQo? zV-bH>=8P}%^23lps=Wtm&Mv5A-f-T4gRWQ0>RN794r@fGE?B2JSx-xvaUd)eIet77 zq8$B`Ysd85NT3*`-bY4_qi#xFUsNKnLeV#u&ElH}Lujlw=ZXgikL@Y0m=JsNY!m$g z0yGf5`(nf2aDIEiu8e2sL;@ts3E zrp|U79SpDS87^e;M1S6vGYg3cXWv7~0R{m!-Wm7YjvpW9C(XifQkq{|Ne_w`el<2L z;jbgH<2zE2WkUs2_wwhf{c?LJf%+SIHYzQqcdFvcJ7O%-dY0#&HH#gt+2lxiuEKZM zsCsH1#kAQbIuCT#s>3;7w}~t4MrmqJ7GS%u2-0Ct}8TmduGAIE9=Pv;(MW1eXPES2hL+KvA)%x8#`$57x{?v z3xYh*Lp=&|XO7tY8;?F7jT#pY-F9aj~x=RW7k-&#av~xS$)HG1X&7938jsq?b$JnN?`ehe8|@RG4%Q4Ron3KKw{QWM8` zK%bDVu0(kNaNE9V>zsKJ(+f!i#^t5IvK z*Vf2#Gw+utt_Xk68r2HjuT%5eCJffyPg&iZ0JkKJbVnLL59R-)Jv9OQ%}>p_Ayxak zL^UiFs`{nf4q1T&Xj3dD@?rfU-#PBC&KB-J@k5{T|NgTX^3IytUEjqA3Xx_n`QD$J z>(F!|3N4SaFU#NLvxBEn{r_fVL@vZUO_f=@G}dfe=_<5iHN#YHH*v*4Ocxd4m3wM@Wwfymt!#)I2fsq1z}UK+s(T{3lNb9{wedaVRJ& z)D%Aug+P0Nb^SlHiUaS1sn*%gmkQ?6vAn>07ou-ZtUo@#9UMFG0dQm%i40xJ z7%j2)Fyo%VVx;cF_o{8sxgM$%&^ylVy1azBuc^XhUgczOF47dn&LKxr|Bs$*RAz9% z#S!|yP0wlaqHMcrR(`il;X_B7)=m>IVl&<)i71X>Xx{iCh$Z03_K5G8J8HuY`-g5V z=0ay7G0x1*yj#+^)HJU2b5I>&<%eu^(wkIzB_6e*Knq2A`^V^&`;yhT(j9@ z@#Ery%l>(*PycIkIZn-~U3-E4{+)yWOVg2md0czE{{Eeh|KsIIr|8<#<>!B8e*Raw zZqw?j@~_`H_&;6u&FaM9$9F%tQ0x|pX5Cd`uKTx&--;?SNf}F7hwM1nk6hG3S+?<= z19byA5G3i)>tuMVop8Ioo(S}*-C$Wxv~ zo^c?8LdAGL3N9q_6onw=okS|;YbzyF_$_)4b)6Nv3xW!UIHrAN0g&7v$_G!~$`xnZ zJ-l5DE@vH}ii?-1WrM-+ZYf(9lt*WKs|vc_U)EVx$}VvcjS1TrKBIc|>DRoZ-Lnu5 z<%1_5IcMSf-J|`yVQeQ`W z2ILYK-!5OkZ)}#`JyqEir;w5CuQd=T`bOm*>YnHp?o-$;k!rio?GhnYVUrJ zyC?VWh#rj z%*35fs}<1~;imP{Z6PWt>|LQJli;0y;|~Z&FU1DoBP;~lewVs`G-pSS&SZ}il&D-Icu(s5AU6C`K3Ll{@dQcx4ioepU1HwQ}XQx>y__*^#4iQ>*?CP z{bafD-4Fh;*}whJ9~;k_E(a&W^P8K&FUZfeOD zW3R3i{hU0eF{`LCs!rz_SmjA7Zi(G|qKP4Tw@3)wHCfCU3mU(m_bV+IuQ^|ckj1PF zH!S8SSs3ey17T7lr9p}KHwh=iKatK-Zj0%g3^=t`Z6r@}Hih>gc~rj8_kH}U$IRwp z`pzj{7*BkI>Vo(;-^TH;vIS{8(>EEYo5=UKIuLBe_y>vJ#Xp63+-C-ZM2Hzk_fs_I z>N#KmDuhh*Nm2BjV}DA=zHMIGGkS;lF$8SYU-Ja! zY2A0}pZ{zIyW)4v_I6SZr$4135B;Ql8?t=2=^33XBRMkd(PvWiYwv42N!cFZg<<|< zN9(WDQ|L6W6#uc`EBxY!j%Y&*<|HdzV5{es?6EhNeX+Y4Z@;%<6vb}DH@2>_Bx_aQ z9zGNwHC_k}irL8`amg<4KiM{DKrSRVrfZicR|EL8Q;q=gaB6#(1{5Lb%FiMmg=I*u z{b2;pVrell@5s}`Z+@>2K&GJQ_R?MNC0ko`dq(Eovzypbu&eGjQ#O~C0&sW@PSH#6%yNo7->+#5N^+Y3 z?uVv(K`nWgC$Sv`tOHkx)ClcCU2;{^-QIky@MSgKH3lF_7)uq6$ItW z@>A30|DwPAC6CJF=TqL-`LB-;zxm%Jt~$NOfyB=hQRN+s6OE%ZI0TjMj^9-1tly* z(W+uvDg(+1gQOP42QROn87tPD0$6t8;;K*11x_0FzpnYXN!4Dgr_=}lvFP)$$%<1O z5ZW@TpyYd@n|Q-&Xn-ug6EO?P!N(oWoE;zR;xD*?zH8zSDRO!tUm`?v}0KU1q7icx8%K?1J_wBOH!*BjZl(G3aw@?K0 znGQ{2Z>HxHTT;PoU_COKSdy4`PuzslG!e{ru(Z+?>u1r843p2Rn4~AjHfaF{a9*xn zLb-XkVWwCZh}hV%yuM-BfHh5!|JjT5~t;kp92a{o9h`Mv?`JzV}y9PddkpRv(c0P*zf_ zrRBG)ez%Pl>19$#A7eVq3h|-4G~XF+_4`{67|Mob z5!_o1P-Zd6>`%lP1?LV&e4n@*`ccuPa_HzHEq9dpCggoIB)NwVlQM$Uk6< zv|o&uuhuYVvG6ivKfd_L{7^0-k;|*<%DfDStydNgi=Ey$7Q;Gaz46t#@KU9tZL2(MpPM~L+)?R$e!AspPhI67#XA6`TREBXa$sR8 zxBh+=%{#OHhm8w)6vKH)Q#Ba@wfOGccuiW0VWGBuKFXgt{-v86S(8U}HXf*T^(Gf1 z#|u6KJ$F|qWyr0WJ5}C}y6%DHi4e=&RCcbDaVp+eXp?AS{W#Wc+#D|5b2ikCx76Zf z={g^que$z_FG^#c7f+kelFRElFN|t9@V_~Ocj_rhDNGbyw0!Wlulr8;!2MOA-9}%? z7KE5KKk)Kn3-BL&`2~<iv3|#&62+I?DXAq1K#(;a5+!p6(6_or)B!-M3s6?5@Ur?y zS6any)d4J?$xo)1rl$-z#c9K(0q_-J_DJq7@(xS_zd=-TGb_##6lTBir#_cMFQE}W za9+9K55hF0Xs>pTxo~ftmiNj>tEc5aQ4@$r2|f3RQQIaIW}pMdMB(J2=DYg~{5=vp zF!p%d5csoN!sWj$D1a`SoC3KYdVw zkm;ls_6lUJ6UHOb0$hTvsR(^|AzAVR%npyy-Wh9nC_ptAM3Xb|_}vxA~E!$yv*FtczR?P!sVK{a9IXelnU`ump z7&l2>i>pk@BT8co(tiCI^M1+WZaR`)dYTrhrM>^16js+l>+q7yuP0n^==H z-=6k`aSx)efwigK&nB$+xFnE+s{J^5j>Lx9U)L}RNt?frvAQN@F%b>$N&@jZPunCt zc;ORW);kb;&~}BzO>sirq`G{rc}6J6UgVEjU7_roq+zIc!X-pIAzk9zktY`&fG!KP zU=cY3c<%;lkfJ}Up=GpC-GICrhonaI^;rEV-zkpS4d_n$(@)eZn4a-jJrHn%y{oN&}B$em~;4vg{7x`58V8M((jl^h~NVzrMAsBsxv0S|3wfnh7`&$IM1r`gJ_= z4N%KjuXq{b_|!xOp^5ZZU+QIVZN*s_!T!oi)=BZf#^N$a@IdEsTIIZ}Gc_+QF}%=f zYy~=k#Y&dxMaXGo9WlIQcev2Ud_|rPQi#Owm{v=FEcZ29&s{BF&Q7}^}|=Gy5hWWMU(e9mIP<~NFg zXzSlJnF5XxvAG|#Xq)gDHihZgU@gmgpIhziX6)A=h>mJ*@@D;q%}osA8-siIehYEW z?9@CLs-PagtNv_Wr3<}UsHj71f#o84o#)aYs1rw=l5iVg#GE%0VV0c;}dJ+va9r6;`Zqpqpf|-G)upciy$_mV2gn!(t4LTG6H7 ze#cjxd%zwjQL2h73}1?nHbP4HxhO8q0tL!M1t-NZZ_KTIP6vzjW*OW$NMI?q3EOz3 zMcf~#y~Tpq)c~`A{dlzYk+_4W;liQ{wedDkWeahW22j{^^o>@G{?y%BKNRmC1z(G+ z@19=6RSG%t}gDJ zjh|e4tgKBk%5f;nu$-IaND~(aTte|KG)_4I&@ApTQ9$jv>Y(v8Baq+`4EItRnuQ6WyL%&ToE)I z43S6KbUW3Hru%5B?)g$-EV-0{t}wFKRIR-s0uX^%L}Z#jByQUr-yfUH1i{wfObLYj z%Z?Oe&P3jph^p{#Y4)|LC8Cx~baQZaE@MqNj9X$dEzlW;{lxaj)P3CmBiBPC^q{U< zKlV6x0$x{2KDf-kLumAEHA^ezj)mfa(nj5v869ZHTO0Mk_o9HvYQ3e)}cQ;ZYoZ&Stwn_J>M9sd;>EB}{?KL0@<*DX8Lx10+-&v6X&`@-Akp zA>!ypGWyy5MUMT8ZrP#7NC8ts!`A+R5wqG04cB`H(K702N0ra&Ab_TS?_TniYw6AX zWaTNy5$?7TV8MMPhGH~xbzMd zhGQ}GR#c+rQWMH`3`Jx5pbGI{{+p9jd4nNk^HGtX!`-d(3pXe4DvI-Q{x7&(w zElTw{@{+ArjMGmmXuR#_=bm60W0L+Cx_;C5l;FQsjlw!sZ^LHAO}(JaNfl8F>oR%a zf@+&us1sWKAN#|dfj58L#8lB5|JEy9eF0xFYsBjS!rGFq#8#HA2;Je9Qw*XtC#H?E zU=zqGara-m2%blD%8u(}y67^@6XkB+A?CNgoy{)hSXm?uP*jh8EYhb^H17{8hlkZm zK@^ohxzva4sA2{Bs@Ap>vQ{-BS2G@K#!3KeKD85(76k-kRqkngWiP$+i$beKc$K^S{M`Vm`6tEI+QLy^Zfa_yCZ>P8jK zq~@GQ+FO!tp_^4>2H*UVgrzCyaAz9-Y~gI_h(Uj1jD|;%pFN5!lPzVg#Y!oF%0oZ` zAk?a@$Fblc`r6@rtFc4(yf-^L9^&p~uyx)C%||70xO!$yziU$Ni6 znJ>MwH*7^pEX@|=L=kK>QW z`dK>Cva58kZZ!Hz?HJGbtA=^ZP=iK|pK{2=seUK?&8Pc@Cpdv6*`d;JRNt21|gYdw!gOLWq5cywt+&MIiRn>qmt~=q&cMLTo42460 zzPnoR3_vq$k=8b_w)C%X{wG_G9+8Ll zzEbfhdR1l?;tmdR+4M7=W}RpPXya|C#QZHVfuS4BU@ndektiUi2A>x4`}`{*^} zgLpY#cjHqb(p#x+!)P@aeTpzdZvr_NH0z4>&hHM}dc{sT;zi|?=LSCooDi8BMPv_x zHsUa+ZWkj!d+QCh;7=cCx8X!4v*A}G_Y_)+-na+HW6O-sHc=7WJP9XniuVqta?bMW zig3WM!K`X$w(ji3NwE%TDb%`N9yeHVGw>eCoUHnjFn@jvQWu*77!S5dimy0L{y+bau7e)Julev-^v-|W66*ubx)W&sGbO^GAl%K9xxnK-& zs-Q|mgbMqUG)<6$2m-<`%?0Q9CLa-s6g47G51pvr>ZiP+9TaCe4LaF@iZ~txXjygn zl1x7h#Zms8-;JD+r9BxPKyTzh$su&GNzVZE0znUNIm9Lhsuz7AKOk>%`4I9x`Jz0P zrb~O)jLML0ru9$A3`c?kXqbuwgAN6lQcxP&YsuUs&jagg`m}JK3r>7p)8x>dL%KR> zW@|^Xnkn-q>;l(`420ITjLq@`)6MxKiH6hS{q8gbF?gD0+4)NPcusWx>w1Z=CHR^cyZy5DVB zXo71><<66btWj>idyUWq_HE5E$*W$URg66eZ;|CQ1kE1al@qhtf9g(#w@Ntb61-yWaF1dxi`Ra9eJ2|wzZ7RFE?q-jlNac z{qw7BC>f0b%eem?b$D0-&qU9O0l2%{`1D3Itz6E^s6eI0eJYo z-9jJ5czaXK+IEWFkxvuz9%9w+ZmJ>yioEQu)+tT=gff|CS$&%0r0ga}Jt0-Z!>CYe zfmNYH0WA#qXh|tzWXU`%U6q?hPoBg&QF@gjyLU`i4zfJkFMYiXwQgq=BF7K?}yd*ma7NT%-KQlUvI^_MaB^|H)y z{Xg0IQZ{&ZLwiuy(4+^Z~oelnJlpWpL$qW<{<7Co8w-z~P zHH{Y(-;=X7>^g;B8UHEq$jJ9?_0McWZ|~_X*X$**?@F6o$?S;&h;pQ$(p7CW|b*9oqMQF{6AoC9^!ew#@2k-adN%l~q{vh4 zYZs;Fq+=v}2>T^}$t(j0Nm+Mb(!)>h#tf zdyb*J$n-rFP7UdNp)mr#Fy@WTj03d&7An10VIQRP0Lw7jS6vzL-i03|d)Ntm(UeWN zlSz{>Pjw4@`@f=Q(I?S9e{Z%K6CY@*d=~?FE*7uuu`qpT5G;erttRq2rDYBMyX$B3 zjgQKm`cVVUCn3x!j(_4l;E{Gml7QAsWl^56Heof%984Y#esv*eTugJ}x8So(eG{f2 zp87vzOSHE{E)u3+w)8I}H>VF5&4J=2(|RIUIe{^>lcpafY@^K&g|&g$*-r(=siIT@ z<ZPTJs_ zf4g0SCSpP`t_e0B4M|2ME_A`@?IGi|20@=H>J1LPQd4RP(CmywK!*zD`lD|Gp>Kr3 z04=uj^0E{nqc%F~w&>_H-q*n?%Db@BE=@B4r9PNGjiZq6-Ls`YoT@!MNB(tqj#&Hu zBekadER1R7<`tPI+CSD?M{BQbj+7Sf`*0|=wmHAP@0S4+ajaR2 z7H~zR6+<`gb)eqrg)UAuUWYv2rh5P4-X!(E#Cpq>#fzdNrFFqLj0%k?X!Kc)8?TP; zU|v=h*oCtH2JqY$(oNF$DU_Qm(~HLg4FAPtI=gMgF{qDQM_nrKxn>S3{GG z7YO1w`4w;j)HBLXkdA;$lhBjU1(iba)v~wE-E5^RI4_>+ey;cqdE(F`yEjrSdD7$$ zN}!HNqVXn?jXrn8bmGup)nUb(+X`3fok>}uR@BV%RMVvCJJT)R{Lh87Hg>kN>w3bE z!h)KeE^?<(UONYXlx-oi<|~fRU82YDa-e$3^|n|};_cJma&_lcJbmPMl*R$HUAC2I zQU@pC>k7e-nuj#6S7Ke+rI;5BNyfsZg&7k$2m`?hIZLl?kznI6x(R+=k27wm1qMYg z?XB^^^l_iBf5~`7%5vws!e~IuZ zpg8XgKXjAs9^{Bew#5u`Q~vA=%|s zswZ&1tVI*W(ww?AEqaNKh$=AgX?8H|QjuAMw?$&pf^d`3X_X^jtZR0k;*b0_T?fiV zNXi3IHg|{dxdSL=wm?<^3KDtUUlBfeWGU2R@ftPlpwvNc&{=Vg1}1AU_9d7`Fz9zS zqd$aIS+)Idu<=@QKteOi2iq#eE>vs-_&B%){&lhed6l54$SRIooxg6L{=C3w$OCnQwW! z*o(~z;Dl%ZNowvC**(|Lp~Xa+nvyL&N_q=NaFP(G{KIL&M@bXXWKQ!;sm)Vbpoh&H z$x@wVDr=)^zErTiS9jqaCZKjZfiGAxL5Mo@IWIg9rRj=BaVw6XEUkRol${b5qfvMY zaRN_4iXA8-Nafnlfv~sEyUB{anM7$ErmqOXP(4_a;F3n6e}-_JeN+#}MQ`AuX%-30 zAMJ$vAZr16H4#e#rW}I+~Xd8%pU1T~2zDM&^&i|g0lWcoQRgaP^nH{>_ z{PM%!e$fAZ{^37<_<6he&%gil;UAx#{`T&z^6hNbrV~6mmb%MRlUpoKvvlII?j+R+ z+#+4^(HY0C1f=lgw5yuRU0gbVouF$)Iw91)^YWf8+@uO)X+r_+R}E)Xr*H@6m)0P6hNL!i2tFbKN1Ld=L<%WM zTIRuxyIfUrnsAG#(r}?nRA}QaQ)JS-Dg9oDig`FSDOQj1g86=qX~47YdMTm%JxY#I zo?L;@E8(POaf3Cmtcmt9V6dd zypLH3fOD{tfu4wL>#@2t(_;y@6Yt?j>b%EG6FR6Wfy->DA14nZLD}RSMRuT2Wt+US z>sRd&C+mwlq7yBiEW9-9FuErGK^}WOjK+5M^i6fuy)g9f@g3Y5W^!I+CpRvkxlg^u z8;$Y_FRdk+hU(piYnDa%yBtLYy-(3Tr$>0R9rt(<`&%>3?%&Ev#M0H@Dv-ZT7i|Ys zlw?IKuq_!ruCw%LHNKF=OjE5p54|*WT@6TCv14Bk*=iiH%Z}A+^0?7xDdq$LmX14c zmY^YrpHqGQD}N9^a&ORU>IEu8NaLN8Zhgyo&pR$;nyhb0mdhYY1>p@TC|h3q&EqCg z8I$RI;(L|U^HYnrl6BcXS+FF zNCz&@O4#(v`P?XhRC&Rowl$;2;k4D91AfIqemlzRdEER|xPzcA-y=Qip&r$#QfUcK;*3R4 zLT}4GFnl0Fm(*=TZX8BcwV$AK&xqGvsiM2t&)-;CYssLs7X1I{qJ8ydgSVy!0;|tL zKrOF;-iT}}bK-voHGrw}7AhY1)%k+EUn=BEr%feW{+(?@3q)VcUsJSVd@LetI=oV( zE&_;oE#9^Znw81(zLC9LZdLPC^}%bt1vS7S1oFyc2x9zt+L+cMy?RV#6@@kOTYOjm zAp&kd6q7^8cIb!+)~}QvvKxu&KT)S!n&0U-j%$vLL3$`#>d6-yF0DE_Ofh7yFy`iT z`(o9`YmBKEH&ubh0(LjAr$z8+wCAi9X;gM~lw0SN(K-PkbvFuZ22N(Rn6F! zNvwj|>?xbb&Zs@+bpC9AlnRE*qfN!RXDc$l!2m=s7#qEF#KRmYv-7Esu_R+=7Kiag zI`Xi)j?0=D5r0wy!8ZqM*5?Mabx?oEcIRk$}63vYGco9NOQxS>_!%H29KZo7V(yc&H- zR@aPfI74y%kN6JO2rcSPoJ50emOX0V8sgH0RW70jB_tMVXE0xr=KiA9;G}6fkSz|l zQ8lf4J&EK389DC?MnxVH`+CRbg_iQa34pqvl2MS5o$RPmIqol}mUv>=Ajm=&oDVL> zBQOQ&vN6UJXE%}cWyTFibvK`E(IKkqiJf!aE6!1}$b^SAKCimvchE4#N8;r;7OYe} z4yim%<0&mdkkeKk2f9>fRW=a^3%Yh9>xun{CoX1o5D(HG{_$cHU-YD1Lv9XJv0mvs z>-hSFlIcvK%zLBpgaC#sE#P#r#xj~7ub~ZsQJL9Sd8bvpi-3F-GB0ftq0E%3EhT~T z?J^<^-;{7L9$_w$VKp_5X|4_o0SB51;tx-B@6;`4zB0bpOtIbl++6lAI$Yx14FYVM-i&n3a;t77kRLIlMcHITQuuAhxS$?WuR8J3``JBH=MzQ_^;{q@O>y#h+!c2oUpOfNtL*y@F(m zwrLe4{ouqkI`m>ggR+9YxNK{KAm^zHgSo5rFLH}I;hqIdNllGR?2obHB}5xP4x|+$ z`-_>Um4T198JGg1Hlr+lqk9n2FL^<<=nll_h=CGqB{i~;FA1*1V|*c^@B}#$8u17E zg2z%K^O^dAp0#R~I~cjaOXxwt+zW3`Cb2FhUPiJ2WbrwIS2mM+DsGcB<4@UT9z84}aa%)^~m; z1Q`=rP&2v6wQh9u=hP@mgXfEh=GL6wkg}7Q8fJR-gvlP~O*=7Ko~?+L5QIXzF8H+^ z)=LC4q+r15$g+_sfQG7ABdMmZa_A*|W1>3Dzd5hh-8QPZlZ=)l@TvG_}WG++wiTKa-^ zNHsW0L}vPkZvYUaia(~pF?38QyPI44mx{>5p=1%W5J@X{fM|P?2Qz2FM8Bj+6G(ez z33oX6giIRa7a?YXZCHpaYk2u2NG}(N96~y;bR)bYW_7Q|#yM^SL#_H9x}UJq7L^Qr z_HomrLTDc4V(Mya|Dd0t+KF1Q*KQwi6M{og8=TF1X6$L?F2qkzA1gWoA7E0MWS9%e)BR=oS9`$$xNsZUb+S1QTA^+i{&>4hG!o3S8XK>YLW4X33$~y`c|S} z76>>)lFMhI6PmmAML(kCeF_m+m<~}06em?0d(i)=a*2Y}IHfz!X};>VJdWiEWk$b< zx%*HY0glq>l|(e(tR+~vC@8n^wrSIOi^jfQ68b^Mom!t`h^J2#1SqwCRvOPMo?-69 zS*@BNTMAKP2Se?yO=o%5z(sb8#uuDran}_UB+;g0(1A0v%=1ez(W&a{rHf#mA9`?0 zIE7H{_*^vFfpSokDS{Pdw90mG_}SWgC6Or0Dew(z`#<|5u$?>d1mO_0H2+m( z&F1}Hl_s02^O@1u)*bI@#PA@2aYWpM+3z&(i>3HZ8xq9X)vH;Is~+lE)Jw5(gXB?2 zGNeuQQ?`>QZLdmJ=CUgF3c>CVjq2PkF29wJQ2>-U7KNGov;WMIh5HaITmxE}y{<;y z3%M&kakUwYBNhS0wP#c5H7Waux9<~n7JBI#UD78!_MLC!Az3@#2k<*`#)==8@ny}y+B`4iHPPm6LV@hTCVOsl=twAjtKjKLM zC~K?)>YU)gl(O>!++ul!nifHg$rvP&pMCBtnPj#{0MxD7!ocsc*aWj*0zxt+W_uo} z`r|JQ=jPOLJ_I#B_$HdoqX(b~6Er14JT_EDycVW|PU$Dnn?gpn;?}15q~g0ADknnJ zWdPY88Du2ji*?AjyB0y?7|O1L=C4mlyt7x6S*5WVB63fripf*kkJ+6?Vq`JLC5Ti8 zcuVP5(DzBwy7X%0?;G1A~gK?JlrsP&`dHaTKChmhA2{Fl0Yp6cg~QQoKAcMKmx#;7WDeH!G@ zBX}f(o7~3hjs3+}fPE?n@$y4}X{bEjROSTKI!tor6!z^?&mnVcjKDBR{w`m;4V(p{ zdav93MCj-*z5R*V2mXs{7}zDBJG2NW)~C2125ZI(${fB~w0+2N1f}2i)M+4{&z}AX zxX(xxmB(a1U8;c#o~dGM^G%&iO^lcImw0$2_>B?_|XodaPTFm5ug!sPm?v|%Y#;E^vTYim9&O9ZXV+%H?oF?x zO{y-=zFQ@00Ch6kDR>fS?Tqucu}-xEm{c65t>qKEy_rMWAFJKK9x!Kkz`=_J=zY84 z(YQFQTO~4NV9}$ki=LshxE!>LlNcEHUBwwa$$RmhS6-~%0%24}ORjPOUmRJ4T$^+PmFAfO`l4KNzLD<&-w%kqnt zmQ&|FLn18bYwwIU9c=g^jLCaVjuwP;YCaO$h<8{Fh?Iui(T$4arwSMdcXE|M_(f}q z=$gR;ay>r;l}J$Y7F-9IQ2x=6L)zC>@X377WiZ8#6Bxh1GbI~)iH-VroOu2gSF!ibkJGX*hsv1(E zR!q8VTS>#yS*W0s9j-^Cs%hGi<)Y83?5Eeo6QI!Ow%+u(=ZV{=b_~BCRAAA=!}g?1XJW zhg?-;U}Q&Ci}lLfnL0>;oT8)p())i>VvDZ9$2)fbdVi#8 zo*V#bcfPwx(G>;-4WfExv<-@_(pWa|)c^ePAAdGis_jF@?eac83P)0oQzyKOxx9%M3&EVea^F3}VX2pn4L(hF7!^{8;|Cbo>w)6m8# zfMxY3_Alro)uI@yCh#S>*(F?!9y$uLa6Jq?(#PmJD@V}JMvpFvMKt{sU4pZ` z(|vx&9R)r(bs(`-UdY}!5xPV96`e--luQ?q=ESc_*!SzMGk702h_x-X8y}QI_ZGec zl=fN^D5Hs=4{4X!6@xpuy9xO!YT%p+q0vv&o5yKn`&1LgEJ?vx9h~#Z%@r@H^O3$q zDKnc11Z3|2tG6zEik^X5NbBOgWgK$)gHg+>zam5m{)%%E7y)B7{35-kSccna^ zfI=pNfIlxt867WbCuyRBt%yws<-n`kqbUS(^-FqO{C&=eFhj4FWbFCYpT?mg2UvLA zW51Jqbc6{2DGKjc`pnq02VzaWvM}Jrn`VR_Vgg3D#9L&qVao0SYT+}4w|-t0#}W@E zF9M8^58-00wzIb=e-$rMKC^eJ_jfI(`TQ0>2V0@KyL=;9Wwn$%@{WVRnJQ|du{P+b zb?(P>)#Gw5Q%YaF*aDpb(;*kfvnekoS~sezP3{vDj7U^<8P)Y!T@jj-@EM`6LtQ!P z_ire%yjZmB!hWbq(2wu{j~wh-)@pyD+8A6PfZ7lcT^#wb=_@VR*2Zbzo+#Rhe(6V1 zF$kRy7?>}-PzR5YXI9BHs)tq-5VbYSMv~#M@@u_I{hM9~*`*}Y8UH@uFxnU?^GlXZ zp3ulh(`zkY?DhJR&@BK3LP|%VKOFf}{{&|(cDYe_uHP85t-&*J7bWlhWn|nCSFyS4 zf>#i>!;t8mO-2!T<{3aFwrsXppj!#n=idE>Nz*TYV|ZfoR7sWu*I*rofc>Pq){L&{ z1KQnd)*;>>J)@an1?J1Uei{8$gMpY&;mZ(>C$%IRoS#d#=Ghp){(0+!y8Wtz>Y-O7 z3B(_sZ|6ySgd}coCQzL2xt|ylM?l@wamjnOC0u8lwEkUe!(mq{3K~jfGULc6m_zo5 z_NubRjDu8dqVnIc(Km>sKBXO)yVy0RK>b3Um|+K^&kzr_w1MPBhQu%U3)1Yh-K z29AEpDo68er?%UX5wBSEnWlC{w9`Xh?N9!+vcECq#btCNNS7Y4H+xD#wjs-)KSl?` z0W~vEkv>*gPs^ojMvS1$=3B_bsl5{~Hf?<_=U0`Zj4>M`yh3#!^RCv^U6Pk#n>XF= zBR6l$H&z@Jv@$(eaF*Th;%&Je2a;x7h-5Fx7RRAyOk3U`09K*b7N0)0S=oy%<`;-C zE}AA(JdSLpVbkZQ(Z6Ja^$*1Hld}F}eyn2}nCwUB1E>Z;8GPh=b?{u929LZlca{%t z9e-ebek$^5mKV6$AL`IE0pVD8juxu5i$|`fo*174L#jVp=~thH5kY7nEGxM{{{B%m z_|^MnrZAe-S`a~PqVx19P*bfI|?T~GpSQ5h$nK5Y69QD(tYC#g{iq@8l$J88OC>Oa%I=LW+ zJ(;cgVw$rn>^kyZ<OO`W^9GcFBF%<>!2jH z1jg4K$Z`hmkn&FmBye!6qw~BApJDp!5ydezjB;bF)OC#Jqto=P8*Tk=pbnLN-PAmJ zk(?>w#7NW}(JyOKEj_xL%Y=_siLx8J_fu9KWq~E7M2MRn8{dC)(Y(*zxNP25iRXs- zLcKh`(JYC}52h7U3vtB20n0|r52eRLsk-q@X&x~>*<9w^mL~~W-@KVI9(1_$K}sZ~C+#%qP|wQ?+6STr6weB+6xMhNsQbLPY9$yTA1+nplnkJc+T@rxBMfW9&~(E>OJSF^FRKvV{YR@BAu7=P*RSoUvCnh6}tJZ`bXaHsIQRP7~K=>-H zx~v%<%RTrl+jkYWj8B(CnwK%EhT#y`FpD2T?Y-03d`Rjl;b_Hu#TfsJw;&< z4!EJGH!A9esa-Fx^BXSlg6w8^wOwgVrQXixX40+T(*cg!TE9>JCL+uWz=6BWtQIhH zNw;%HlxM0yVHu`Ali|}EBD^`xb;%JM==_pLMxhQ<#2ZZ#1safP<4J;wk=>`DErjxb zJ@wBo;LL3;m4@y8jETJ<#9k7*;mgeBT>;e&DZss1;Fz}pVp)EGMX*TGl zjf4QrCPT4$NT4Aojtj{668#m(hh>wcbfS-XzM|v35 z;jhQln(oTf841VE;~(cvsQ)e2oxU9b^d2tlZoU+YmdCMfb8tg8*~+j^gYB#JmVa>` zg_z+CcokF_!rnK9UG?74Gp|BtHwwbh+k#)lFH+3pd8A5bhJCIl^ z=_xo}yQ7%X_?f3JMj!SziYLkb>m$6!HG#+?U-L;c={O|- z4q`O~Zl%}~6x(FB2G3yV;8X~iqVhM4j|#D&*uSSzdPEiA)Zxqn?`$k%4yw-6g?k%O zM~L|9I>M`I{h||6T%cN+?z91r{2wuv#K)sWLr|_}zvs!^>3iS|!d)mva;v*kZ(*fo zG3+gcLR1*-_}ocrBj1&f$r8@RCem8S=sx*OhrJsadP3k{hT3w3oy ztP*||;?Hra2jGM#i59Rz1~B2*Z1i?g;1GJ{_#E4qDRbKLdBae(|5g?NjbbjA^_I6-AVSee#t&uJ|}S21_} zQgMb|?|uw&wFM#g$mA84a1zUe(o<)?tHHU{D3-?yp|wkCab1ZYVZF;^ZM2>1l1V=y&n^P(qcQK-zbL*hN8rKT}GY6-8BZEK8 z`Y4MxP+}&l!fv$jOX-ysBvn8s?yF(A3C}C2-twinj5+~eBEn*_Pt`O0NAYd-4e2UxBUY8Sl^q4VUXTuh~U-213l?TaGcUWM(INu%vnc`i|0KpV2XupyaCw1J6 z1V0SrTV2wNIE8M(dUeVZZ3EY@ozt>v^T}wDDrKA?2X+U00lHC`$BS}8nxDBfXE)s@h z70SGrUk_|q;JWm?ohY-0099<=ZzSYZY)~^E`It@%8ym&D>!u(MvoA8dSGsAg=W4+K zPe8E0Qz9DFwT=XbKT!RoPN7*BxyY5JL$9Cn2E0=#<_y&CdHg!3!YMX4iNv^T)_ua> zPo{Rr8W~#Rkbu29X6dnm-fMj+*eSw02mpH>I?d*22uh?T-39TL*YbJLZ2JRE!G_izn>bBBb z+@*Zz(ae$-z6^NNACI8_(hf$cdOdoxfSkyFF)O3haI{rx7pvdW7-#7%dPRBa@GS_P|1n2?s5wQS zF;ItDdZc6g6}y|H`dCpjt=q$JH?vzMnt_H&wZkZFiAu0{3sXwb5vu9FcF(~b;%FMl zkZcIWH|%%jK5J031MeIq_-eD>w!+kGNm19050GW*b98gExmn<*oy<1|w2o`dv|8Jp z6~pr$o2qAW>i~U}1?&d3J3M!s^= z7~tG18k4u>2O0c$NA$L&Ri5B&uQlXx!hS4cL|Kk81iF<{#(XGkKj$30^DuB*MDrH!^A#NF}6d`Ycn)2yAV zR{BN4RAPXXn2+V6OTV3)zE-9$Pn*tSwhyZi20Kx7wI!2L5+JOM)y^u}>Th!zezKBE z@bSk}%{A=rPxUmJcgEhhPr3*~E0P$@JQyD`q=iC*?){;Aph9x+iGsbs(qB32L=vIT z2@3DBWk_)(-{7czad8#3@58JER$QgC^+ks1dG4F$GAU$7fw`0w5C?GK66e#{h%7o9;KU0%$5&vy*S* z2RqD;v&R3+mfR$>Hh;&6Hy;QX(CQv(IwzqT}=9Mqq8yVMvJmbp#?PACW?(fvGYq@8#z1gyvr)=49{v< z-jmH~ z(&0G?fO1b923qN%_-Ga$TO5gi5mh1dFwBctC% z&Zal>Hbufi80_<69vUhdZ8c$84Rt@oMD!_dU&%D39d3$UdcG*jOuKbp#*t8;&Xcn} zQ_s#K4FpGas#ylb+(B>hwADnXqkQkRvz=wMQ|EBuoaUreV$FWARLL)WwF@mLK%+P` z#l+k`v@PmJKe^+dJp&RGz|dQE2ajT*lHIbuwzAdA-6p;C1)do9mSA08V*2#*epf%&yIa*cQ|1r($o>pXwCLh;DK?!A9|Sthu_QEB zKB~a%5vHfmIo|c^o_iV6;1;8&%Xu{=$3;6}bMqP+IX^T}<{?^kb_9Z--TO3j)xJq4 zTEY~G1!WJhkT0@%MLWwQ_PH|LmhCI)lnv=aV41wYt~&%Q03V^X{#bujl&d5Rqy$C> zkbkBn$Wm}nGsF>j5UQYNbNC%>&8_Re4b8k0jFeYSxJqbQf)hX?9Z*_zt9-Qai57Y4 zoonjw@*^)+p%8Y6h;EmyqQvDOqXrY89uEH$NJeE_xQr#Iwwn1Xz)YjOqS)Ed)JKIm zSxZbMB(Cu|sZFBZVvBE+D!VgzkCJE`t_K`dNRzoQx^m z)+oHc6Yh~hi;W0Lq%}o=pm;{AZt9qR!KVw}=fK*G*vE#RPvsk@y*=GM13j8Rf&^^3cZ7j{Z=m3GxCfxG(C3)EY{svssvpHxoiVFa|CrLH|&^6^r?h6To@rn*rCG;b1 zj(lpdGon~lO$$C7HYr;!ck!735N?i-j=w55v*qbjhiLm*W$(TMN8tlZFY=d>_BiBVZFBUxOJ20Lpk9s z%P^{;&emRjieG&3?VzFpVn+3Y4oRaj%zn~z^!hJ{<{n&WsQ%zPlC}oLBRyD*r!vFl zU&|O9Y+3AmwNA(A!?{8P`K7!AieR!4&NeX#NM%q>psU10xMZcLnous(*+BGeCkkPyPcP!ph4Ce? zdyNW0!^J#^rh-;gbLM4CdM@h*lE7Nx=4fWPGM=In{W6JNp*Tl~9*KBBri1x#& z`kn9Rr}TSz!FPtJ4oohy)BH6J{jRUtc)`E+)hNHFKe>lkZhzLby7D~P@ZcF3muerY ziI(U4k+ukO^#XK1Pt+sF!#Q|KV3@<(4Q`QrWf#<=sD(#o{ z&gn7gY33ZW;EXtmP+wQ-a=Hu)Ca`;Q^^@5Qe$A&!w{Yj0_d<{*qZ->!F}9Vx44zm> zEv^kKHR;o^=FQ-eS4Jhg8*+0ba>`U7uSsaEmgB}Z{3!blerFMiCDh0UmeAri&l#2= zP@&9PwXgVOKHEF;JFMdrG z<+W_Uw&klRUII_<&$jjAR?GSPb2cY!t@Rb=f9fYEB`7AeR2gnUi6L!M2s8`Ew`c@} zOvv{Uajh#ixTEkAfS|RLi?keTgZ*7jpPVFkImKmKD_9f3B~!DC%w3^Xo+sdctv>Cf zKk-cX`tQU9n@8v(s%7zMJ|37_G3_xQDXOj@tp_)ak+0(6aZ(reA;_bi`;($#Z&8V! zbItIJ7SYY9H{KxfA2y!4I1>s5vLFx)(rri+ zPn6{~c~bmY9WD)L?-fBH)T;@PM*;$mn?LFB1hAH*<^Rl$qe*Vk|jJ0w~%)m8+|P4u8Cq5wif3$3=PIIqaMAsEU$QVYxa_0 zt?!8U=e&ZHe(Y2Y4f*h+`g3mhVdYq2SJEzR3c!fbTz0wZ?6@)10!=Khm@_yf&?YS? z5YaKs19;=G3;HCaXsDkao9eeMw-)!ceR>$`?j?4fdfclZp+1^fnSS3D!$t~}%tvV|0?ZcvYsymi!RdJ%iWzbvj#UlcG~ z9_L#z%w4QL)0nYNCNC$O9o(nN8m|U+0IZB4>9x5IdnPn@f_;Wk89EuhuQTRDR?aA$t;7ZN)RVk zCc&&1II%jKjE5)ZMZK!FmNo}rm7vd#ND;HTB5pig^O85_Cnn9`?Py}lh9f7-kzr~( z{{2{*Aw!qs!(OQq+fH??pDAOW0`)8VFq9%xIkAy9)0PAT2;d;i#AY4X#POhO(sRZf z-a>WPBRu%BECyp4KuXU@af0K$wtNc0>rQS%W6VW!ux{lUYWBl++GoB`-Dnl!t*zlh zLXc0TIja0|i6@^oRl~p~P#L3trfX_O{~?Dq3*#ZhtYtKTtIQC^Si04ds|IXjSf%Ui}U4Ph++&$|86a@nL_W{%fSL4t>we z!u?rZZ7_gBWeb9ZaUKZMC2!t1Tkgi@E~|XWJ-)qp#H45PkJ4dV!~OsfD!-#Rpp<_axU{!~ec?SqCuna??5KNQC~v7KCo znN;sw>qrwYyHj)utt{E>GvQD%0{4UeFb9GsL_C4>86Li5&kwJ6T+BZ^0I%ZF6DY9) zWCyu#!1ksKmLdBHkE>@d(+%1WoFAE1MHr)S7hz(pdB&z@#niOY`Hq8%-;R~DWlJF- zEd#~RO50|iEuI4&Sv)QVhgBU?PS*YXV^2w?EfaKCgf*!;Z8}$zaLfS=#RH)%k=m7# z3USU0<=6vAYN+MJ!7fJ`iYJy@#{5g8QxCZ$I^Abq71fTaP|Q5w_^`*gaDXdq#Po7( zvz;$41N_rH@<2d$h3JIXio)mGDSHRlp%q(sH~qmkXLn^y53GgZRTdbUDtzv%pu&}20S_UG!A=e6j9__o+EWrF<> zE)cQ*^nKwK2l5Y~J}NKk#EDGPf8|bR%)f9pD-@Ck_)<6DrDpW#>K!<9^yK^j+=9>f zIT#H7r`KcqBQp$lb{@($QpPVofW6zsX+eo_(xn5YRQX?$8HXwcukD+F72#x&}%9$yyY6qcK?}gAiF9b zO_~TH>nB}T%W{=u7AfUJu1?h<8}phJc1JxANmr(mxCY+VN;=B5RhpdD$XSt)Pbr5= zoVz2VR@I;My-K*!<6YxeJ+JLYou{2!SNq^`5w2n^j>$&Q-`5oqy0c@oBcMu0!1ChJFNub#%>g^MMowkyVhmN zROEOKqWCRX1>q1&w>c|2-Gk@%#L}uTF?V&Ne+t11l1=;)Z;0yzdokzLjowSr?r;Y9 zbNF97C8^hfLhW4h9eTl6{-taKfiNLw z0wl?oI%};C8Q!*zbu+iLEaJq29p{Z|EC7$tACK@7I7yidk|FNouX0|?>F^DuEB{dj zFsknxX7u<15Z*}SbUNtc)bE|;ls~DCB$Bspsv*i@v~I}9b3N2kS!v`7qL2%Vi6(ZC@32%!zZ4Kv;? zNNX6XrB3d``McR(Gf91h`cze13$YY^rR9(x>*l#?yjJnt<14g;eV1o3hr~2k*#42| zVt<6OJ(>y-*RZJpEZmXBN2P%NMW1r2rasaZ7lS>1gXsRk%g_?ZG#d;kzP{4kZf!5E0gK8!gh%pktZ^R45Eybtqn;Hu6THmiz+gb(tM*#rT@{7nWP5K$#WB~uT2j3Mzc)s>?U<<2z6w?y`v?N zEyXYO?`JJ8S9uPRekm#*up^(VT|f9PMC`eJgLO$i&mIWFH5Ge1HnMI_W#*~4>X3Gq z7c0!R(thy7$pe`h;;8U!lzQB$7POI)ZHm@~{RA$8PKa@CiTq@TbgY|XX4Y)aaj06| z(|H^8zHZqA*g(I%5xBH^Bw%jm8-bQBx_QDxyum}518h|2p{RR9*-0SMIn6w4`2k%^ zmsxNggIn>SW#2oVV{6-xLa_rs&9R|fvv9KEz#v=Rm+{zwa>8p?goe@lJDuk+RM*bB zQ#5UBcf$Nm30`rYoJG9IlSr7!hN;h0x?S4%aMI1n zaI*ZPTe3t_LXH2&ItL=27p3^DJ@Z%pU>M1sRCSyF0+6&@Woz^Xf#P?m`)AdnLquO- zfKCe_E~J+8B;ghH5W3xRaIxdCCJHA#iQ=BjI_t!i)X-%}pc52pDBMI25P-w1Ri{D?^N27a&Z*&!s zh>-1Hee}kd?C@v)Euiszw>}gPuD!gK8Uk%{TttN@{(9qxcSU;sd!xG{cjPkaofKM$)?3G< zNtRHS8OiVDGP;Euu`$h#+L0l=jG`CI6ZhI{F0PS&z*pM-NkmEu$>6&`0FWUCV&eAT z6G`$z2bdf@?d%&x{`j-k>91@MwwwA${}!|Wzw#oV{~);=~xEG1a!>!6)bF=JWS?E^~ti5$AX6Z%oXa&H`S)VRTueWR{_rqbV& zrMA@|chQi*+?9$N&ppV5I5XsyMGWmJUDXv9c%G`-J*qnd27}@d#))&DpzQ-`Xi%68 znIz_eXFJSya};7XUgz8&deH2k2JhyT26l;g(y-;oh7$dG-XkzigCqvN)7>1#yM-j& zQkBo!z-3F2qv(fkZQ)3tW~+QL=%r6oF&4cpj~+nzl-Y%XUp)nD(?6sVd#! zoQ#PAw)F1SvoWaIR$g!=`UM?ojaQGwESplwl~vWqkjRquR2vr{^zQ{Nv7@r`s; z(0Y}+!GBGDHQNjT$=*GjhboKW)2ZrdVY+Ip^?#th)VI}wG4J!wO;vc-e$klvW&bQ3*3>FmL8QmKoQLm}1(Y?|2`&)?38+C4_U4{Vzp z=Df1%4c`DOc|L}D$h{e?gxfR}%1mAyq8wu+MyePAV(H8_q+#4zwDoV08WnPY8F$9x zCOGKXF4Kes#V{-gRb4r_QiPN}LF8;U=ZCf&EZ>Qnu~3nD9>}AqYDq2Nc`D0&E4kWM zJ8tzH`2=so&Kx$^(%|EfvxVcL80g-nCAB86BqDVN4W*h>>HH?@F$6B$x;`p=cFcYm zon6X2n6fnWB!2}+juOO5HI15xdJO>7@=h@dw+e@3XtEjU(&Qo4qvKd=egD9o7!Qw> z!Y0E~*iWDwQ46nu`ero&DZ6Llm4MfbnS#DLy1(8VaN2KXQ8#p0EMtRO(uFLvIw>3=r{nKKTSE$%dND zLOP7{HjTt~@&q#t2jHJtuLwCHC@z=x_PNZKA*sft*g%XbQOrO|dwS-BRIRAePDKzU zxSF3Dm*k4Milu44E41ipE9F!gFCgm%EY~vaFO3_ek!62Hm0e1ZsrtOA!Ewb`Y%kq))||$!Ezx3 z-a>quHoAzZpgfPkXMT~wq{Lb9zx)SMH5#S%byDw8X=AMNvdM4X_|eXnVYHa}v(I`M z0+kro*PLID)G*_QSTIjNF{G;&ycl3Vpaun7ku- zsp9Ny!{gJi(2m1 zm9jyHAQZ%Q##i)U9tdwjh;A8>{LYvNrMRXe%?J72b}B3!Ao1MWq`Te>FbCBdZ>d!4 zwxg)9izOI|89RR8--&T4?if=GSgRZWgPy%7Xuwwgva6&_^c5Y2(>{9?4t$KDCB|IW)!@m z^0<_;I%`ORAo8V$F;25ms0Z$sp;qLY5U1@R34i>%z{Da6AFDRW{4G7q&==|s)QuEO z%lZ+>T?x{qn>@2Kw!%!&o?h!AEkV(Cb1p@T8B^Ke~WnyPrgfqz62fSX%gZk~A=69)jg#-#4-YqhfriAOb*Nw$)*4wzkl} zV5z^EGPN1}{89%kVp9oif1(r4@^0KZcPrhN;?4vE0DfzVZub~W|;8;!-Tw#F6v?Ao`x*+*!v zJ&W%B=ip>lP@-V02*LqMHt5Mz$^qEkeyr`GK1zw~Dz;W& ztpwY7Z?yF-bjC)g1VKJJk28ju8m*<-8@c>tu31ne4D+(x0Y&zcO|8HoL;Z^$-$}ng^(c00}C~_*U`cUy#BR$`+h*InQ`-p!sEi zFfAt9k%gMVlvqQ` zM7&D7rof8a78p1+`kHV^nHaR5ikcE?o_ergd=RIApqviY6Z0-JX|H-o{>(;N6Tr}N zdWuc0Dwk%-S8?5?CQFgW3h-qfBY^UOchCamx4>rhuCO8yaNj&V8FzSCYer{wS*! ztGO-sCwRv2zDGkkTPcf+08w3A?hxjLe~SP}4% zGZ3f%w-{l;MLi&VN{&-Jc|JR`dBS(hNyu0LR}?ed9{f6yS=5aWc)MEv!V{bU=q*4SA16jB6*U0l_|rZY<*z;@J?M-qe_ryNDksv zt$kbw#woN?ZaPLe2aq%iDk5C*DDxn_^ZFMTY7g!R${_nKw=?WbAsbepQ zFwuS^#|l(9XDah3beDo?M}i`n_I zer}K+R2>(Hy~u|!YJhVq?3z%xPF+~aqhC@mA=3Q{5CZ2~>8V z-;zGL)k_(u43L$*FXXGaUV2gvVvUI9-o-MYj{-;=4h_02s#$6?IdUV&>#{?FH3SD+v>KTU{TOY7DGqePc z`9hKcOxQw~x40fJjo`z5rw7NTLmqe}*XhS6;72$v%F9&BsJupCO9h%wNJc!rhYQjL zGYdJGsEoJ>U>8Nqk<{Y0iXGx1Y}rJNmmPJXN*4rzf2t7g$n%fU4_dOYTUlHN`_Fy^ z^3bCR*F5p3m2)B6P?&uy)lH-THH96D?7fu)y)d&@C78Tmg{qWS#JPWsvo$lQy*f9X z)AGtLOCihG@Il79ts8jz<5tho5}Ewu%BXr)B=ux6#*t#s`g`r8nzTOlT6Ji*EB()G z9UE?z1+KspIz}>oaq_3nxRvixHS3~4g*+m;u*YS*}Cs) z4l0(Nk0(JMv$+Egi6cf)!y+XF)$o>85Hky^5;5*BNb^A(r3 z5QE#o98e}ki2kXwLQ{%E-dD#~fWyQlSl-*Dp0!VI?P7+Sys(wD`)Q z-)6nt2A5EOM7qf2BB$zjw5$t?8l7+3hM$VQ5?~HRU(eVgF7iXN=hk*fDPNxcfSFsc zHlZfm_0ULzm|u$38-_=X)oTqp;{?4DAU2RDY$D?!(y7>PYnJ9>1xOF9cC4mEf%Jp2h?6&aQ_&n2 z&bS%Xd(!P5{5Oc3-xvL4(pTHkxRn`b+}96+}n;yTVV z_l%TB<(U(>=>4?<$Ffg|-WvRt%lT276+8CYv@F6{JV}U5Lq*=D)KM+pAlY)Z`|zi@ z^mgf3#iSP&Y6NWv$3^=1#(>g-tX<3j5}#fb&Yy-v+LQKQcB7=4+%yy#Hb^EJ)}G<( z;rK&dy{CuGNB&3Pe8l_8_w#%qyxK=T&;0uWbi=_qxqLARqgZ@;Rp};{8CeX}O*vOe zffy$*IcQLRbzrI`xC$e^mHFS55*}sY!UNtJtt(zKiez@5_|0Ls`2k0%WrW&^l#fGX zZiPb=#KR;(^eY8_2BsqQJ%j7=HKn!w(MZA(zX7Hn@yE2(ew0ebUBOrdiPhZ^H9Wud zS!y<#ScrH^-jAl2|7xhuXFM^?NiF7NttVM`C*{&M&MdSD@jXUR5zPUMr6~0qM8f9X zFb{A6TrK0X(JfVQ;Ui2*D|45TrYrOY$z@Zj!fS&4)h2x_UXqDzYA6cki2HO=G@dC~ zP`GzZA#p_eC9ihKRXiAsPp?DwOh%GBTKmGyq03R&HJX9~Hv}I<9foazd4 zk>-$(8L0&Rcd!H{PwL0Gx}^JE9X{)`kB>@_3f_yyed<<#m2#W_K*N2N6=en%Fexkg zxF@K_+XTWofq(s8<4V?TvmirUl>=`-a2>^8$o^Sgf=4PR4%7nj7tST~et(lv)lrxl zem$X+jP@*Rnd4gwv{F-{M|K`^_}Wdg!_|Xq-#a)c#MBJcCIpZ31JdXJyqGhCoTzlF zJBZL*W%{A$1)3Q7ufW9nYE^k?|JtWULVFa1|DKoC1*Lf**{r$TM_nf-MT;~!c#tZF zwBBLOe7E_jVxfwWsfX%LdBq6T$PCNfJkb!rfCLChd<}eD)ULI-n|;voOHzavJK$(I zH}iI!lDIynE)A7I>x`8J4n);tE0YIN8VE$SPr{p~-_vVCxl~ z|L5VL)l=j^{7jL;z~>;V%?g`@GafG8OJ$qMHKntVLiE?hULAP7M8l99Km_EE)n ztLcM=fk_1B`}i4(qiT&_>)|<1{pWf(C}%>B;a{LTX_i8iIW&m6$uNIL#FKarjAk_K(1$nf!kG~xy1BcFa;FYvb0j*9*i|22pv&q?H6Y*}FBreo9^w5W5 zPA#B_$m=V?ER*7W2hub_5fRMuW~0*5s^Qs;kLkT0P}Zj#He*YT_Lfq^n*wk2V?nv7 z1Pw8z2ka_!MxG4j{kpho9e9H$hufEd&1)e0U>(s$@h+`~MexjEj?nW|XJEx}9L%GZ z_92^5+}{>c>##g8>p)D4CYCzDz+#na7Z#enW)un1?okgDHr31wVaR|V~U-^4$P zbJfu5W5IN!fMnPosJT0Z6XuG=#4xK>5H1Y5Sqe$FoB^j9gq^;r7^S)# zXL-E$k{C!V?#aAd1)nPLP9mXQp$CT>oXHq#%%|lUY8gT0pW8PHsk&lGWL``0p8Kzr zgF5@9Rrd|wY7*?mp5budO3WK(lYUEw85BZstdgIL{M1|{fGbPoD6{H%?+6J6VI?}4 z%26CBPq|l(MX>_0gkI35)jUGyTOeNsmSM2tb@t##z4a4cL(WWb^dx%Eir$)5{A^ufFpMZVBrh>IjS zUTr*{*o{tF>LpKN-Zw{~mIg~)mnTO#3XJEeE%uiVbmtdAKfhvmyENgfZXoE+T{c-wJ5-oljo|^xUbHJRQ=3*XMQ%M z-K0n87TR8GJV|yP&s*bd z^poiD<$oRxS?fcO&3d}*b`8rN>7+1lEia^9!j3t+@1nz|6ey zEgXv#pz1)>&XU8Uynz1nw_*jbfB{gY65$p1hiKZbC&mABrDSi8Nk%knD^_$`=eLuX z+tJx6$K5@YD3S58@ZVPjstD4)#4l{v2h210rn zujtB_d!rBXz93sEAc$3ariG1n2-|5)kc^$_mA^x9aFTW7#PAzV7TaNZ4xBpQ4*238 zWnNIfL?>v-dU_wLYuHyo;<(TUqa-fzMvDbjiW|h=$bWjl3ty0*S_r=&bT2bD}0u^R$Kz%p*MBv z^jbHPTQmBpV`w+U&H1l2!2^^@wnzEoWd3E#0C_QoNGqX3H4hxtpZUEwD68Jdr~$;= z(L;Usylv@NKLB-|(>ka^Y$84(ouFMCl&<6cE}jQE2*E3@;P-Sp?) z%VgX?S#fGb7wC@pxm-^!dop=5p^YwSgL23PECN2B*fc9mESKY4vg8)WQdH;}I&8*B z?tVIRTE~(3>ONa;ks}qZzz*5ddrpi|X5@%Bu!2J`w389S(SUHR6ln#Zf&(VSQOv2; zDCg{~k97!bI-$?iiIdIUKNE3Ck7kctq4!J`Rct59-1uJ_M>t1+)s0u-^a=Hx(eC8C zQmt{6#r?LL>Md;UW1eQ?u})G!CyOxy;<9@P92lLsa#W5FjEwC=A&@R?)5mHk5`f~@ z40OE?);bjWxzUy3gk(C{L(IV*=U`(L$Py1i5Eo_1xU{c+Akca>VoWq5hXXGYKuX1- zWKUnplBjo!m1E`Vb+~NX#9{Rt$Z6GyN$Zlzx7y?|9x< z#{uxd+iDOOnhNj6Ty$Ut6ki6+8lz#u~}?ov;xz@&}!vUHK;$|qq)BiXqHk;*rU zC#2c2P~7scP98q8jVyN>3@j_T>NJYU9@9{GwX{#~$$)F+Q=Q$znQ`rM182 zN!jfo`IV{*ISa~?v;NgknfVql^u*U|n&T5Bg%8mC@?|e5WJ>4@a*ff^6Ak|sdHqRkX ze=|wcph-IR;7)k1hX!A>KNzSVQG7xCqb?{%unMcVCBap2GzlO4^G#RTf7b_7p%B-1 z->WO~_x#q#p-S(K(Mpg+04zF36GI(ycy}urW1v-jt&dZ~C5G{w(xGk)X(~Y=%vKF$AWXiKx^!tJ+Pf|Y3n4gW?nef_fl1>Y9E-k!lExZt>sVCkWIABG9V0&;I5!8&I zq|!4A$4O}~o5&v*!|pGS_(N&VlV&jP?NweKvmE(skh}*gB(z3>Z&)wqrz(4{7%GY$ zWW7oVvZn1Sy(rj!{yy~khTuga{Ix=x`MCKzYiLk@GB0+DibOTiQ=HqP1mXG24YFHZ zqRghnxe`K9=tMgThK1Vb^YaZ!#${x!2^+TaaG{LOsqbI(2+CK75dTVv&ynT2^9Ij( zE(YpxHep`I<%+BTJtUlyD4We};2zLE(#^ot80t3!S1>-*N}2WGXJ=_F{BMooLlQxQ zb1?M05O=cQmT->VHQo`TuQWq7nt_3vwilt-2zN~{{W38uiJh5x|3Y!KVJf85Pvy$` zoV0TN%s8T1(UA!lDI&c{a+a`3qdEF^Pt^L0n7%4m8#^k4R2f=v#ol<(H_ze1e3>nDevs(*nbW81|5Uv=r(1}b zcLLSu&&Itp6La;($rWqEyLn_@xTSdru?P+rEGZy+e$Hiea&j>rxBCS7?i*(f3~XzG z1sL(dbQ%N(K3MVY%>Y9kFi=NY$T%-P=WMRl0Dv%L+d?8lWUO_#&87U?I6 zmgqyaeuOq+0W1RF4*e`M94VQXu&AG^-zAcAxx*D|=AP)&x>I$i4gqzKiZ2troPZKq z4uis(=m5uuggzihef3HV#>rpNWdW2->+Qs`IiwZ56QLK3d{CWGZRpj)dC!mTK`;FM z|EPx*qK7~H;(yFz`0sn^8U^7Oe8aqk^6GCw+`eT}5Mw=Q#dRY~dw&LminibS7hb;H zXBlO@^|nAH{P2SLt#4aXCbHcBp0|h^F|g(1 zfD$cUGQsYD{P-`uXkkwMcfDy#D``|Sd&BxRHEqwFzk$3_m@68W1{1{M$Ag>!;(%GVi-1i-BEenbb-h zE1AcXNI7QAjO1HUF(3a??RMsRd|4wvDw0$^o`Pgx!Tu)4RLCMFrLrrcx1b`TsX);W zySH(Gqd;eq^89Ly{_ogKe(9^-ziZv>T)}XM#6Vwaa=%n) zTK8JnreEpNwTeiA4{-Jh1BixQ%!{X{1pje=1LE9+`378QvZ-PTH4q|e0A(*vxnGz~_J%lPq!vm!(uSikgeWFn$jochCLAHoi-0miT%>XK5*9H(T+`jac zV)vpNW81B$B}}5Bbr50^r-69FfggyPYARrn+L4xjN&64Q#fIdS&-AkCP-!Xz18P`A zZPcw^fM%Xp8d$FE6yh)})vkXHjlA3`jBj?SOHANETC>FJFKj=QwTxZ|cX^44 z_?a@vliT=ljxJHY#f9nojx)Z>jNxbIi9sO@O_fJYK$E%B<^{XavASW0_`6isMb^^G zEmh0;U~rcnIDszE?7eU|o=tz5X;2h0G9u%-AmiB_SwcF75usI!>SRT7vp1K4Ni$TJ zP>33aeOl4c(WJdbamH;w15R@~Siuq$5H%V}?I_bN6I&k*cL(q~sS5aAEiTi|ufn9} zwZ&;b-_L*N863UfKF^@)8RpVcQ$@eiu}Q*+&{Z$#Q07S&lg3({xk=jcsEbOHhJ|?D zA6I7Nwn!mze08i^lr;ryql@*8g{t%KPydxyYTq?C?SZExHLj*F6z8tp(}KB=gj|X^ zVNyd``Hndjt_T(@{+*dHK8K0SrK5;=wWImK-Vi)_A|Ps%whkYSP>Y#5WkdqJTWB-tXBSpmZD zy{}JD-2xlUPgVZdXCoFDdKL(L{>fPGEZJbtLb+d-phE)Vm3WQ?TsfxwkR}J78rmnC zLrD!crZ`LQnkq-g4y)rsQV8$Mdk70>YoK|$4+Sl~rvlxsp^2$nKjNq5;f*<+*|%%j zq+5m2ZZ|7s(3N&8r0i5~fI)31^~MB{iLk*gx8meuFF&iLUpwwykXaP|u}KgYLh~?h z6QF@&NMN3oUYMYS43yMwUSE*$m~ADcODiwqaq}Thr~{R7DpiW`@(6a?P&u{XHB%ok zJt7-&^BS8j8Y?T^>|Hq)nHkjYdEFm`|4kC;SZ-yt2+RW3=ZDfrwpC4fW(J9hd3<0T zi6LotEIPPWY&FV=Sk?T*)VkZa44I&~{qCRLzfy^a zB^<&)i>A#W+$j+y($>O5ogFb0STGVEfM42HKMge&v^k;B@ajTZnq}3aM23xz73Tu2 zB6%W!X5#G)AK2v=i5tEnTxTcwrpgmXYB&nJ!F#%xfDl#yGeFG0@#p&-Y4T;eykp)h zTW9JZc@4Lf&?7Y5^$W57`&tPYo$ZWojX}=J1nI z(|04JJCz7tmoNeWW{BPrsu!DDS&|a5`HPYC|0h1*u?EirB3wniIk~)Rysx~= z51l}eRb|&H9*`%Y6{yW7E*nGOAo>qW&(nAT!Ed^vg7nem7s*61jtHm3;vkR8T`oUJ z_G~{;`GWRf6Q-Z>-b0c8fL>Z07yLf{nr;*)$S0NK=-h$F;8TxX%~DW2-YRS+1Lyat z;Vh#i9%n+s%+)41{eS6g$V(9MIHsk8@I}MDi43fyl)+UP__fjSkRlY28cA+d0dv!4 zlkk!AmyO01e{hl%;XjIm1MUCnsfxA-@y|y>0>b06bXrRbVlYqhVV=N{*la{)d96ki z;s+R6ZqN*U=gNyM)WXuIq^T0>a&?RyH31u5wd+Y5vl;(4!vLkH(?Cw$*tbfZf@}ty z5dJz{H=lY+-_!m6*k^+{Xu|a-ix24!O34xdQZ`}Dg-~$Oh1+Sf>c4QeGZ$CMU;~MT zD>pxgL(iGkCyRz)HN){|fqUYbFdd@V{LwLDrn0N~2Xqq2y#;kIm<;km^gGu_IHYXY z(Qw6&m)(-9WHL_ZGwfNRN_b;m{Ok;+nKdtvxn(IkP3s2TvzP~PunYgI^eN@)l#+Fy zHDp0FUttk)%za3=E%O5j?CRrOgTkniz;ETwC^E>zg82w`PXoiD7m-Ap9`HE_>?78V z=_YYhGC5^4C-oTme1c?`#awqHq0bVQiz`A_faWL|(dk``4nmWT<9zAt&khckG#7@I zjR0(;-`j!3@407d284+dY;ms?>0i3_pKLduh~wQZlBKs+Y5=eg0(o(0Wqq?$- zuCPO-T?*r37QPNpNy{ZH$c1nT$sgxjF+%|!x!Raa} zA9^E5ugBu82T%Q&<3wFuDD%_>SM)B~)cBuEw&y4Cyr_r;lKzOaQTs4CiF`C|F;{Ci z;`c{Yzfv8o>`>x|GvG;B!6ViQLu=`L9|uF3O-z*{Q7?Em0pMlnP!lf-erGr@g@9xg zc)J0`*26~l92wW=43n{8+qz-TPA$UC0%lB(fo)l`iE2fn$#{^M)HBcaFd~%}6QRoU zH;T}5JT6R__!CuTQ#K9K*7Y&Yo6O}!oKr-A)#RAgybyOMPWAE81fYLmlZp2TWstn? zsuA=jo-NH=7<>0*V})eQM#d{K}r`FEF{-9UES_6$D0mUqhRaYO8?J5%n=89=D9lS(5q{m}Z z@A=&jC8_X}OsvzBgQs150kfJS3Yg(l#cme7kwrN~C{(q-lPCs8@yue=StO&RXrEH2 z^toD`=RRsx(bu8R21?v2+%`r^wq*v!k{;Tq3J1x$X^`clZM2dk2VV*{MA~@5y<|&Q zaeQZ{Ck)O5JrCX7E^W$EM7$+K!W%({Z5RQi#)fh+y7!*azthc#Xr6x+a(5d8iS=mh zr)gdO)Clj{Jz*9fBD(vA*uCh#%~3`9?ef@i)oGsjwN>Ng@iLoy3JF`fHzDpi!urL=bh+s__kO2n;kG$gn^xjOgM0 z+VP;2EZjYsw~Z{34CGE}*^SRD9JMSr196EmYwUm#Dh=up@O*GcJ_d$S?| z5ua=%%vuMz7a_s^WX5Lg38cGZJn(gHpbO9NxJ(+Zj6;Qtf&B93SIMRZVpE6H?P3ZhA0P4MwSjQ!der43SJB zdtZ%3zeefYAdb@wfObL%m`SvT=kY)-rphYKd*N{xDk%|iuo5i1x7t1Lo0M_K8C;Ia zhVYlZ>4jnD`%`_9SI$B_BKABnIQ%9w@|t_SX_OuHb_?^Ul#Sd^$~t6AIQK5y`h0cA zp0AW~MrrZRYik~Ti4{@#70We~ov6lC(jo?3+&9zEbb<~N+k2w=(nVT_I=fKNu2B)POzERjhi;&=U)1Uf66x^PcJN7bOe3RwW1tuR9?FY zsObl3<)Q(dINws?IY$N!9{P5Czp=hLMtSlz^kBSU{^C8{dR8d9OlS->C>A^eRR1lv zryemjN~^SKo`pL(*~uj?TeyL{bns$?j7(l36FZUZtk_7Lr&JQsglSSnkE-2`GaaOT z;7wB%hEV$oTADPsn?`R9qIMcw79d7?A}v(CCOgO#wTnKE_Cno}LQeSS>a{-T6@o)a zB!m66(JrrSp%~ew=d|RPOu+m#F7V)q$x8B)&qJM5?TmgQ?q#i=J`1;{GhqUp=ef6D z1-mM9x8VKz*FLP-t;n&U4x@$!{Q6R%Hf}BgH(@K6>fIs?U@CJPu;N}lNnnBr`P-ga zu)6&%R0BC%I3-~slr;J@H%qI+)a{~x-$Bo*~h0_OarU}}I~6wnJ4 zK8vT|*lR=9YO0=mk#EWO+#5p*LIR44j9p0I#;LXcFvChH$jqjH4Ad>vImiPSEdf~8+$&| zna#?+lxTh*=|WrPy!oteTZk(UR&agTJVkipF5QFff9ah)l<%xKQYFlL2-(tr>~4Yh zsGf=fm`V+%boPEEXvd!c79S+D(qXtZPvwB^$=UpWaJW9B1Jw`(^_4iE@*XH}r5v8wiw8%n7a4Ma?| zq?c-_Qv?dx4}iRfR)^Lo*@lt{jd@zn$!o`USpwDh_o1!O`i;;StfCG#;!-TB5a~JQ zP2P%#rB}ntE3XdX7Aq6YtK#!>vtF{3V}L=y+H>9nJEj!D##o=hwJ~+Kexu%M6jkRW zIT43ciYGL(^JZK3c|;jj$)afG=(zb5<>~p6(*}8hX2;?=+h-($Fb1OZDEo`TBukE+ zA;C=STv$v|nf$Gkk0RE51L)pmI(9SofgMuvPjJa^3)abu!+~p_B zRp($dVH<29KgEZctS_xSxRB@kb}qC5(eEGbuE1q7ryxm|wfFV=xB`c(9AJ(w9A65U z5i@E|K=D<(%e_-Aw;G>rpW$mBg4z~KZ8=4Oiei7=-V!!Rq#t!AGD(`hAQA-fl;28| zg$$kqvi?KJP8^%9Cx!Na64j}?_~{`5(oFw%XkeO0wl~ ziX0FY*_M@U5i*=lWb)f0yvlR$B{l5v8RmAZk2n3*u4*c@nC@s;_erSf{gS)O&7XL! z(GWwl-f3vs8_LR-9lm4W@4-X75B^}8B)OaNknRPYlHkfDM_xj9+ZRbGbX*i4qTY)+ zo|0`dWd{_IHV*KXN;PFi-s|opTviewQsWNM5(3dgygHBNaXaB|1l*Mk?UmWIEFpoP z8EEY5Nu1UJ;m#c!QkSeOp;^|7fQmC#3#R$v!AB#g+UbVYOTJ#&;_UQ)M`oOcp)6}~ z2J&Q^z}?(lfGeCuPVnUMbUi!cxovubP33vZWNG-4-y0JF`Yn-XBd; zV}x8L^>gR|h?3U`S`Q+Q;z0#~(oFsKrNP&}xIxys;pi=u8ET1G=k5W`y4R= zhen?o^O*31K5avv#gWVgL%yt>rk$Qu&1fkZ4~2G!v8E|$*}SsA!5-ml3m@c}5qf_n zcaHL)mZc!m<6uogGTA}WP+EvZOAQicXNc(`03=7|z|*)hAIcM2l4v4$r@2Jz-JdLV z*HE8>3fkv2N*O?_=bur-Z+Ylm#FN-x4D-$&8awBWo}?{S#3)X$pl6P84mK6iGVNw!`{&G?T^(QF&!b6?#jWkM8!t)zq^W>g@!N5p6^4zWp@yCU7!} z+(Wo|N%qRugX@}D9(pHJr(JolwHjm)cuHMrYpH(TyQX{Dwh!qXY71Qma!pK}-llsi z+%yVo!Q|-&;96orMSW#Ms??*+stNTlnV~M1VfT~Y&gA<{=*g1(I`XPLyt#La>QUIe zJ)-!E-TG;Yw@oOngLO?#{ZOYFbGg-SPAnWPpo(ee^URBI!ACpk{}oh^-pyc8>uN08 zMCi9aWOLk~iG*U-2 zsHwyow{FZ&-K=!p6`jC&Cn1!O6q8Hmh?y5QO1PlpE{kD(BwdhNrKCde1g5~|W@jp* z>Scpps02|$T^%}JqQ$L^)KEW*(J`jnt^h-mRYY*v_UjVMR&h2S_#O`cA$kws7hbPnJb}48ZFkq zd>0rHl&3dFtN zojxo&?l6E8m1oE~jR8r^T3w{_>P?!fbhFu?OH<&Qbu^&!&62e*yF}kq4Oq!#1jwnF zcf2?@r{k20hBcuL44zc@AwaefS<7)h^Q%;cDUPAYXt=ZpA2k;72!dD}+FcHJs{5z%n3Tl?3lPYlzWlhYAL3hK%`4l$d;JcDhIkPqz^jtfBK+~?-r}~w%z{vd= z3>3))$7*pIUbm6~>0;p!GU`~~sGp$_PU{O}u9Yg*@FS7D*nh7=M@6&s`{0M?`$@l1 zYBaxRLZ)cUoT_K1$o6AL>3r(w)_tVVwQ$z~SF-nIR~beuy@K$oRg7B#)Pxxf;B=HAd~qX{aZV&KjYx6sLuTjXY z5N!CUY?+~^knO@P9jq;jvqjYu^=DTups;)sO}|kD#f1$k72ekP#BatT%JuY-#G@=3 zD3rQkaY}I`TuX^T)m;ii5c~rwi=(W{-}2Q67qwk5NAUVq>iJa$VB?W;0@_qR!xaRe zMRil?IOvK8816pwEHjJ zTHqgB8Mw(=^*8$=yV^)rQ7*(NIs4I+AlTjz3RpV?<+LrT3kSBZvUNRRt`7;~mXhsYtsZe4k#iPt9=WB^opcy3*^AY3U?Eu% zWX@-|D84U&km^nti)98m4wMdAIICKAV+UdGpC^5E%h**1Q?wT|z_;p>X92|$70wLF z5K58Eg)$?Mv2a{{eGk{_LH|SkK*<=6;eajjlE zA`l}n^^he9h%k>>cavEfkbT8vUB+fz9LdTIntTG+(qgd6%`k*ey-7{UGAS_r^GGY4 z5L%|}W1UYqCJ$nk{*{yg_(b2O?w?d-nVpPjqQmI5w_F+&h8&66rFssl^S=x^%>m_F-y^p2FmL%M?=^?;!BVxq68H6tCWO3n{t_$ zDAN5`rHL?~&pIi-9UY9aC<3t~jmQ3L-&IPPEVSVDlub!sEkp!Ipd`AZztvP+(b_k| z6$8l%ak+|zIw4EC}H^g>PwRIW7KXAY1C}~kj z=?f|u+bimGM#rmJWHuYu*xNOuDaWBjgEl>})7?-EqQ(4f z$*B|qrK)q9!DsD*g|o;Q2+TT&qIsC3H%9kSJ`oti$?FD{j`@&2Bj~mXnlaOdEV=~f zy!M(Kt$)!kkL;l4sdN@j9yy%Zx-6_bsl9UC7e4wGGyv3EDb5DPAUGX0DOJ2Y_95*m zv?Do)9eZp0C8W7vPHNr1FNU8^N)pT5v|cEtTk$AHhWwalOWtn2@7pr5V*YvwPC=_U z%GL0R6m(FJ!cIBr?Y!IY@Z92=)Yvp2hSbNznd7YbhCtb`tHz&i#`r8^6PFb37U$kdmY(C`KkUO`>p^fGT0x6?5@j z9+w+CnQ%AZSJD8!->Bb8=XxPXlLk&l$`*XVXcm&_gqRXF$+uoF-jLuJ(u}#sjZUg8 z$gUbOjhFGgIWTmpKrW+o=Zkd&7h>l@-(atUB`!uzI5#`1+b-zAOVp3lha9?*w>Hu=NO z-WDzI6mXbfJ!;K|Tcsll;L7M?<-hjV=zocqeo%(0gKh6eumbiG+bDLGX1#BI*6>5M za9O(iTky)EV#Lo(eJ#!=$O6yj1NIUavCzDl&-(4iSK6w#S|bMN@R;w(+hFH^Ob1cH*z?VN^Q~+ezGCT8;2nYC8q^ciOK`O z0l;LIqF8@rf&mHS8H_763qcSp?{ZrtmUCbsO%R84M!R4y2L}#d-mtETI-arbT!#u; zJ>NQ-H#lqh<9$6wU;R$V{VoRCYv;1jm$5oPq=r^Tg@q6(9zrs7+9G ztkHcx441Pu@nN2}THpe5m&|g0W(J@oR(&B+^#sNSleyAq$ku?;YP1Bz-3uZYNT}kU zs0__tgQOt)9nooLyA6EqD+Q1g+zF+bL^(8QZ5sBsccs3`!eE3pXI3TbOFmHpb)P-Q z>q?NXw6kPat!iGa>Qn|5hTcI?aR%p`E|Q~>?Ph!X+7rNyEdUz|gAN3DMNjv2fn*|~ z4iS-Gi6U|M+i~O>wSGQQIF0$E0NdErahKrBR}?yiIIX-?wa-)QAR*t{ z*Tgo0aF_fewY=YM{z(9)bd!F0t?S|0;Bu4xj~cKkM{Qyh>?nQ)u`N)VPEo#FcORLnA>b3EAz~DY zmjK9GV~Bm0XwD+smpwaPo|14eZI7Z2PMVK~a>~xBy2KUeIq6H@0Cy7jt@BVKEUv~= z3JzsK8@o%zXmrNe3q!&LLN<^$v9^1r^7W^}y`rABYf?}iA?a+*TT|>zS@s>nctMX& ze@g?>1e^oq08xN^aP@KKsLiy+5=E~J&4#0W@;0}i8mx1GkSZ}5@5nvI$#37&oRWsAQ|fp(t0YcfGIH9m5+6{ zurx%^X;=%NC9tFOjwX7IeuGGTAPw+=xlEsk-30o%^}s58%n8RNQj)+(2)NL4E;H%k06+otX+}j@&HOhPeWu>&S_?rhc?Ojc%fEMP!;t0IwfFdQ%f0i zC!UhGp{=3eoy?g<{D}z^HSdVtmhMK$Iw!<6i2hh#sK#9GI7#)I<{`0M=+C`Xq8Q-X zb1MItM$k7XqbJqGwQYH1qYSf`Pc<4|Wu`hZ2p)!vT@GaX!pd=VzxFI3s3^CLvN(yV z4&l8xCBic{NB@0WTx{+^&|-+OOWw5(A>3gTYFTV@jwMiID=^7*QEyRp%vVumW>oyt zN1Y&F>_jYcL{`d!p|B%2u7HM{lGQc4Ul2`SE!qDe`CU~9^>E;VC=dbJukXN!E9(iA7mq6MQ1SEy(pKAiaTaMs#Lj^61=o|CI zA>d#-@@J0%(GAy7w*&U-)O&H|e<4!Uyjx7M;?KH^+3Zx={rYgK ze&=!eDgB;a(A)i@Kr87{zo#j$sx0*DsN+c8pWG*NQ$O|*>3|o8{{$^3Yb%&!cxx$8 zivQwRN9OuuM!ZOUZ9`xtr;GsaGW`hF-&Jiu^2C}Jyd1T%7b@ofWqpz9JDA@&7?=8N z`)0zbIk2(x1pO-kk8j~nKo+;m5BOW?FbPF(0TW8D!Kmxb%t^wnh@0$^rZeAZfJhW< zvtC;VNeIduLF^i4t?o)rCok8&3km;wCw?!4o3tWi>S#4UZhB=*F$Lf2x161{x%NK# z4Y4uNW~Q(WR7R(RGPaEzFuyw6D}x&s-8VFnDzcQAE7(9U6R($P8JzcrcJa9*i@kXghMr`SB!D}(5MZoKVm02QnJnP-}Rwyq6jXaf|LeU zh7xuRi2z=3oCmM*;j$PyWgb|7CQvHJ(CWX=u^{|5E6VS z-g>1RvaSSr7^>%9LEzS;s=A-oMiE0Gu0Xa8AxWj&C8ftK_uEmZ9df~wuaf^Pi-VKb=A&Z$ng3Toaj=;Ku4NbV!V zeky2tX@&Syo%6e(^u2V_+ynwC!-}a<{Nlf>s>`npdyKT2c>{B7{{;!j@5F0xN;U}q zgr#qZii^9pDu73WIV!LBOGQ{hdZQG;?JH_;r-ZY%xG{+#Vm{mxR%iiqU%r<34nD38 z%g>aeqU_qPRt!m=r*-d?j?mUbbZ)8$NU<)MylR{DO0|Je%m%iR?t3Vz~R7Dap z+4~7+oV<8jhkQ$Z?yJ$zN%AGa3TR`Q1d}B;*E(i*j(C>*1>XPsFtQB~!n1xe(X>)o zT9_L}UZnVj6v*;U^<|zK?FI2_C%VKS!g}aIpNYX0zIaJi8b7c@>zMshly=gdG@DBW z+Z#A=PgrjG>_M7~a@iY6dabs~m)fH_6aWWR;b?Mw6d|8*ndPmMj8ZAP!m-*-3bfXf zDDnyqroc)}vCk@XoNcLiO15)6pr-+co{ag_nj?uGc+%aBvTJ%;rRQNyCz>9!H)L3L zk&Ew?oyB=wNIG#~hM&MC)aQtgdN~5IrW7lT_=xwdElcVlNf#H9axXnZ{{U5KqqN{3TOg2uFG&X#;iDuh?0Dk{0LYc*&svX`b8za3S4vVv#GrJA+{+m6Thoq;v= z1VKwHpPlV`Xpbm6$PSc2F=$~uD(M@R^#E14(jY32R6t~FlZtB}$%-6lj)xWiZh&IT zMK4MkW5R?$e3PT>Y@~YRew#W}y)*gCUZMw4@|_|9E@I}k^WolhDxm>BG{e90 zTi}WZd_iaAhcq5{1x}`aOCa}Ayi|;Uit=5Fi&NWu>$WV}a8*L13I*l4wvX@P zECeh~&Py6EsA#?Qd-89(RHoWTmAgd@Xek#RJYUo9@9CxV;C3->K;=1dgA6$xhj1F) z1CTwPu!}q_nsJIwLQ3q`=h4Ct?b_Th)h4YZ#>W6mK3u@SmgA%&dj%2=D7nMJm9es$ z8VdWMm7t?n)k8^PAkoK+C?N!uN9;8WMW7Dur9kKxZQU)ov0HP#7^Lu)*;8d_5b>9{ zessA?{K@53>s7{Q`X!B7thQ(4u_)MuRrlb@+ZKNqBLC2E7$-=kk}#q_}?M z1Vjr6my3p@utfPlo*i7A$)@ebu&JxHNt4Epr;uaa_Xdu9pC{|u4G+_LN66hq?{AM)}Se$5p=Jbdu|#*85YbcM?gCn%q06i z$TEfWB|J)al?!Y?L8s1|Y?(ix&jU@B#wQk_I#!k0ah_5Pv>LU5aW$0DHEBAvc~J_3 zb9NU;&p$QQ)s3got_G=_wNeu{!I(+YIw4NP3yP;L#wfZ6EEA~CvW{MLggDS~!2z-p zcZ0kM4#2Qe(KOBn3_)~(jyRK&ksL8xXbXiZ`h#32>>&;1@DS*a1q-h#50qI2U9>~T zd!W;?es?K?)Ulq5_L_qf$X_*1@Q+k&nI9QTJ2=zqVW5Y=-xYS|;zjgI=(Xq{M8he0 zw>*h`8aNI}R*Fhv6n#MRnG+B6t7pSP(^3OH{4zGg-=i6jgrded8;)P79!|?q{T9vo zb9`gRP;fqgNd=f#UJ3G@fGY*fOpG{@-UTCDUJDE<95u~OMJNUChT^HoZRKbdnA|aN8eHNb`)9zildT*O9Lu-!?D94QseE4qbAT;U z!bMT$IrMGb8_5fsPV-JgHOuoW#C1hdqotGqmAabroSBCo9YcYIwABZo z+KV42asC((EmVZ^!h|0wY+i_!wX_#mNn|_5W<~%=pu_2wj+Dvtt&PSy-IDfr^emPZ z?IVI%^=m|5dn|W8wYknLA|VyB1DGZcDt94UmZd)0kk6=?Wm2Ki`JOxl#V4im#M)#i zgeS!gGoYM^+=dyIuN*+I|NfZ61ky!$n7?IgU>3^8{jjJolXgPd4-(bqy$r1dE6~7c z#^T_IgiV?V7afN{@7zYpONGRPIL=3bKlKzfAiHCoo;?$O8H?PH3nTHqA1YX!kQ@2{ zm)Pe?^%AH>wx|_01f=4A^NF!h&V{ryIVC4TQlw2psN7OS<>?GLafhL-T9rsNK`!8! za;(LCC7Old)<$#|kMc_i>yza(HYjM|4>I25)>=nWlmY2h76W71NQX;&XoX*BZAPk&S#RW=ZA7I zm=cd2akkiU+cVbS(7`Lm{jF{XCt=AE^T15=5`)rQxr{!#CQvu}CZQv=wLuU}R5483 zVwjkTaI&g*6^h zMf6bcE&^nYNr3((hJB~V)Sb~7g>EXFcSnXZmC!3=F}o>(V=TG#{mdoL$E{#|9E3N= zx(b5>e&>XFY3xsVGY8e6-m8xDLM=s#u#6}j2@6hYGQ$W;)7uOa2H#DYr1^4sqc^8R zmxJ$()8ZEN>eOjT$t;Qi@Bsr&${p1@J5LgbSr;Wej!(A?#XB}=cGU{ZY`;){{iHWhdi&%>~m zvlNmdQ<*tc3J2ceDpw=lD*R9{38|%Qt|>|!Z&n(TX%#_&aqIIefb#`a@kG+TX;npU zKjD`}`x*{02jF8Y-CQ{&eocURN2)8MYXCS`4k2lPT6(XcAK00qqs}R$tl1s6m zNG&O-H9JJHjGT9&JPL!DhSzMTN2PQC+ls#_!aD=#@}V+!{g*m#a_#(qwae7!#CLhS zf3eoKU;1hXxBSvLJYNqJ{E`$nze|*CQN=9lefV_BerqiR1wYo!bJYmT<1J8nVDl-| zIZI>(C;_r>@p%72hhIQhL) zgH+`m6`0pHC~;eYe?C-gJ{fJq_(~`T{cGBZn8vwo{1)Lfw>QpHko35+In1gR*e;dP zH-EkB58lNfq7~9?Ok|=G)`)1-B=EJCeIO1Bo{gUV$`sGBI5$ugVo1K@fc=5WPC5J; zC++&89t_GTqmkG?LNe_{f`cY}d*c^l8a>%Ka5M3qy%~4mWB(AGU%(GW&)1BJqfv6i zAJ{0}giYGAdR#fqmg9Wxx$&B_*~*ZFC~6WnkMIQ}U&e`_W;j&r6!_Y2Zrqb2)$$1} z)~a@tYa&e5dnY7CgBr(1=gSZPFMLkZPf|F(e7mc#L|IXZ;8#~T+e8?5e8pM(Gz3EpJLMw?c z!YkLEa+yej#PrHZ&;@Qv+QlJ4Me|We=*GN_jK|t}!I=?=&J2giJIjkA`#}da4>l{Dz=jDYj1ENG7a8 zVl7GozlKyHh9#)sjTWmh>oe6k>!4Hjn%_kBObgcH&Az#S!fOJy6JH$8~B4 zKycawo7hR3+D3lQu@6 zlkb@CjUqKdx5?*($EXiU!-mcC1=*$-Lb>6d`hKuDj|*zXDungd)JdBf$eS4ZHrjxm znnLpe?OXz8bHs8`-3Q5~Qak}jXhD$Q_A7_h9jdU)sgKNxaD;Hjp2O)0JY-S~-IsXu;_$YMBDmAU<1a1%7mCmwW zPnrOvv3b<*M(%VbC=U0DduSG06u>gW>08H3nmwy?-22=ufXNQ5m%<>VQw}kArwQ3p zk{F5F=BtZRkZ3TfuoP*yb%>Y=00?QK&}ew?>61MNN_L>Xi0;0lj@(!BAq>7|I6E09%lKasWD0=?}Hgr_-W- zrStGTH@eX3(Of=#&DlQ%N)%j|6kw1wp6bBHmCi;;VchL1E{jEA(_(VT6 zFa1dT0r@mE7xMe~XCin6_9{hf}A&Y@_W*|@X z(17R}k7sLV<;A+8BU?8tg?e7h|U&yawhFT zt#3A!+4)5uDE~sGHuZi8(#Tc+pVKaf$;D4(7@fP|e=g{+o=Um`&Vai{q}d{HlyK{L z#fw;JYhAQ}Oqg}zH*4aOfoqB@)RwdyqtB-)YCaML=77B!_DS=qW=!7muv0Ao{Y79( zAWI8NSLz82D8}(?BZ4;egLXH^z}sw-Zj1t?HPhJ``U~31Wj0d|*t8_sXF`}`+C7An z;NX&hWy6J=j9DL3Z5B7bcO3jdJEu1Rfc=dATwQJLf4L0fCC!%5ublcsdwNS`t5G?J zpZER!i$6p@(a^ma^aBIKpwi6cxg-n^pG)UiYJ|Dqhqf{O7RVW zH=!&iREO6}MH4GikF$E|hEpb9RSo!du(II8l2&5ULaerL2rpWm1g>@GRY|v+FhMDa zp_dhon2{zCcaqizuD3{24g=Zqw*a(!Lp^|uaFQGbrN^4=K8YgyHuw`auoio%Q0Ahp zKpww1KQO+^W1)S@UcUb+Y1GAc)H`ZP%&yAGJdW%mCC21(;!V}g5YQ9F8VIc5WmQehB@*V6q7|~Qh8#w* zm!WV4w_Vr(NX3Q&r2!(U<;EggEcLCIdEz6LD{@dBPM6>FP*W9ZB&Bn0#++StprsLj zU}-8%D(YPc)>7?Yb|N%yMZek&Jr$j^BU&N`39c#%7I>24wAp++cME*`PJ+0f0+$Ht zM)1KX?NX=N6{ZVt<(6ZojC-LG%203yUkQL_bMQdnwbLJ76}>q+R2w4y58o1PwxQfEZIAk zRK+nPEZm z2pG@=>o>GiwVMEY-sRc!A}Pco)dv?k5YaSsU!0Jc7uhqELmY-S3eTPWGBgeF%V&e$ z6g6M^^jj2)nsWe~sfLF3vPh*Ha5o}XB%oT@RaiRIB08K+Zk#0pp@Sjc(n->2tR}}A zD!jt}mSoE3<*G!isMU7z`9P#c@1jaARk{k~`>ZWjTWt!E8AUi)VrDzlV7C2B2qC!1 zNSO;nKkODqy+nft%o{y{NA-a>uUO5fX3%J`eq_1h?~(zboYsa5TYzBQz7d`UtXrv( zHz+g^CI=F+HxAfwH_YE437SXAuA%QAFp=z=pZyl?3s^{EFbp#@zv0x3)~d=^s5&ZO z3fQms?w^3*`tV+Iuv9Z1q z6@BO~zz*dv^TxfeMo$o}8y?hohck|2BPm-{FuQU9Q9&2kxbkLr*z#*V1U`RS@W)Ex z8>L~xPem1xVH^fa)V>b#iRd*ND;;}(S*$$de5a7s<(NQlD#7$}PZNKy)G-mWi9DWRQ^<@=PN1JW{ldW!Is75kVWDR2 zOM0$;Cs$?av3Y&Chkez4XWa$4>}Z`clXov*4>eY*&{!JwWOQiAHbfZ^DU7u}h`NUN zDzv1)s_cD{hGa|5HWG%sT@oti!(d%Uh~!b%J@@AelIefzm8qKo=sX{(YT=k=c7BxC zR&W_YNf|^eP{!g0i=k@Cj<6<9%ywaF2|C_QU;dhSb+WhDpM`N$ssOa)(5xx#3fAY# zvMa}$lmZ_&f4?x-%g>h`VcwDiEwsrndL3M6^l;=12L)dCyTJQgGKM|ivTVhz$AYmw zbasSIhp@j1@Kkz}jb(tH>!8Z$lD$VECgh4UW+()dHDyNMN%>#ZsCLR3m#t*L2;sK0 zCrJ|~BD!uLpX!5f1%|^hW2_aVtQ2sS@}25xm+0sSDMwcASptzrMya@t`ZvZoK%oP>^jZiB*!Ks0 znzY>HNlk?a9|tC~w?*L~#q^xY;J1XBKld{2OcBq4Pc2rlMAY&#+4c4>FLl#dV#{l+ zxJ_3vO^*u~Wq&b(_G92&l3r{?{T8C_t2~hgDn>n6Tn_{)nj&Or`$60L2Q~fvGEO#O zQRTqacW!E_Yf@R{1jLhf?B}q85gn}I_AtAk(5|UfAd^bTR0Jjf*zYN)o0u@Dyc0*( z(~3tv$KXu~+|b#YpZfC|hR-Od&xVpDD2%y;d-9&q?m!Vw!82Y7S_E_50;h;KWM^G? zvM6WVxtER$oeG8qgv0ar46A#7W87V(8O`^&?Pjach z4@{I`!z7~})2ba6?JM{Ghyv&WBnVIf%DhT!@dQ_T@REQs^WX>b4!^uAINKyb*DBo3 zfgtWSZ#ONcjJN^y77?vNA{A7X-xuXFeyu-MyXxiT@**xO(|Az?GbZMu6DhR*nkSD@ zOFc37PFXqBzJzbso@v8$!7DZgsK(!pd6s`e5npV(hC9^G=_%P#w}QMtjuOL5B{uG| z#^O-f6BGWZ27`yUDf-5h0(!XzF53Yz-$#z9xp_i3*FHxjJK&p8jH?_fY>*lttUC}0 zXn*x0g^6RNXe*8cI~{_gk=|(TyMz_A(~~Alj(EAIC7Ie=^jxfm1kp}FkJH##QB~{dYse~1R|B>X@fNZPNqESZY;2S0PH<3FUq>eRfARI16BQ8zb_3-Ve zFa}Hm&LJi#M&|P$MZ>Ii7N20js8~lNt5T%km!fG6CduN{1Lc4|FZdvIEl}>NLy8B; zhN-enOXK4gSs6Qw78p(LD`nZn$p6M6qjhzTXWdcdR<`$#<$hxFN{`i0Bo&X1?PZ{x zRbO~SP|En$5($EH;ivsz%TEW96F+i;=*IK2i{8Z6ea3l5eBiZJ|GFv(WaZ3zkXuX^ zErsAod7Ait5#2<|I0q6|kgQ z`9Bo7`d62mIRXe;x95vM=_wC{((W}4h0yW45a^&;bk@T?Q>@!ER_-TK6!_R>vw1DK z$g=r2r5bZvvMWUp7O5c|1FscAUWQ4cK=5OKh(C->M)q4$(@T3ZtWtZ*ONV+U28Qve z^oN*vrqSg+pi>nM06jX}BOcg=fX$@uOcg}8cw;`UxG{R44~H3)mq8_3DfLM|gn2b; zwz=ji-hNvNijQ6`UDeDt-~K7RS7s&LOq&dDNP>4MB5JI%LwLcnymBw2X;UTl_8TF~ zdzAxJp5I1kHCqo#kZ0U#BqoKc){6<9)@dXCgme21RRO-oX$C>dsrF_oNa3(|y`z(&w5po`PC&80BV8F1 zAl(zd?=Y#JV2rDlJ085$GHEKx+V-5d3(~rSvRW`CD&&Ena0_-0+;qhui_#26x0MyIyH*vt|y-u z^;EFNG98e9$mGfca1a_jgZYmdG1#?0#$6l3B@nS*0Dx5ZWPSyknOE>I=e zVAc-1@m#Z{2iqg=dKj`{8T*YsQXy39eL>89_|k8gg)dryi72qY7#7hFbLhr>C< z6uwMk^W%~sCqF_$D{nZYo9sN2M_aQJ85It6KPP~)q~02iZT6}_-=7%Q#hi}xf%X84 zyCUTK(YTPsggzyjbvktSmPbU+sGu6JU_ZLpH!;UzP0rFYl%aXoKB<`VmBlZ=sKs z!Ib8JOO?}n6-os)NqCo#nNmp&y>oMvOp02`aT%9P<7mc zzBACQ5Cb*nh($zIvV)g%9HMW>D8%L{8O9d{*t>k)Lh>PR#aCw$-=&R|s&H?`e($zJ zfwr-GC)&XPX4nb3i%s~vkiPvtGg~6taAXo4OtFZabGlX#xlh(RP4G53aN-PegxA?V z$5DkAoVWAy39tz{764=ACb%Dlyg%Am4wrVvGFI=`CbU!^RWO=H=>zRZkJfWSzlVw1 zDier#T)4w;Ou~c<i@9YQ>0JIdaX~Fc@T}WXFv* zy1lVs0M;&F6>r%Y9mva7t@M0>)$AqQ*H3x-$lGQQzf9v;0gD1Nz{dPc%BzNWF=A5v z!I1ZqSFWaMHmrC+`h&OH9lG_i*;kJk4W^f(Y=%8=#`!`fd4zJpoU*e`i(D?uI& zpq?cQSA_=2Am|*y-CW#$FE5G7d_hQb_n4bZ;CRHyeGj?`9pxl30@Jv zPT@fp4I=W6d@sA{Aj_^x3yykDBL0Vb5-q{(ZqhbRHtiziCa@<{?nI&S8iMK?8r2C_ z8197|58IyhS(@mZaVpdom^MLX%;Zbf+ahx$++T%D>S|>4a_pE72}P_qlt@!_Jc7$q zJmw_#hgM|)hI#md{xNw3Q$SsWuOA-yCUB;&El9{Xz=!+jiuOS@@^ z-b_-@*bKlpJTmr7`miWx#HI%|EPnNzheJs~>~$Y1;e==%s52$U{LQVrNpN<=_egEP zVHg#y77tO;QLYxV|IJuUN=jjSg^>9!A%P}~<&m|`&(v}yL7dDytb>QmkX`Q7Vb`IF z069)1!fsJW<;hOSV(=8y8?zp3d4rZ^wLR%!&In262Yrz`uQU@^cdzE1+RI}iSpY+f z&D9|wA%mXX)^Y6HIpsRHI6FBFA?~GVP+~2ag`j@XA60eiZ-e%ke02jjpNiD^UGRCh z7kbWG7`wrpwb>CAyTq03A+-5Xhx-HLha};t3Ew(OJji$d?5%BZg>WHulu5f~dz(R< zPO(c0#Hsl317v*5&wi?ZXPN4SqV+ht z$cL89s{$a0J5GllQ6`^QulCY`7F#1{a*D8&Oz|HtI^jn@#*T$)V;B&^= zx~y<$ObDzfJ;RQPL1#2stJq<>oj>ptjQ7g3jPmVdPFo`e#Z-|VB>P*#kjO&R2Xgqw zwR@V%wA^=9)#!4o3*l{cGM!NP`)phCG-$)4i#W@pSCBfi;r0v8g}1 z2Rr*Qe@da_72WZ-u!@)8{ZcjAO=}mof-*TL5vZY|q_wq%^3m$VLRvW*W1qew;*llEq&B=z?N{qlTjK}c)$F1@ zUlK#N?xJ4j8;$2)!f7M%>-o~vqrGcqfuj#-K@*RrU@jdGsWhOdPBqkxq`4#X4$ery)W%M*b8?S?~Hrj|CUP}$RCCYt2ROIc3qY$)V?vXC{ZL$7; zsa__K>mNmb1i`+b_KYHV@b1;e84enhKAc9eSKu%(lUX23b$^u>P+k{|)Pk8k8{lq&r*&g+js8mWtO-Rqo_PIHTi`HN zR*XsqT|`cp@xQfTfZkX789@oGyC^iXEX;%ukV#Xele!O5trlREc+X(5RGsB zZ*g>aHA<62_!@wE){w?>4fK0@h_%rJ3efbF;VDm|gbx^HDx^8d)t^4ZCnSIBoF=hi z)ljrgY|w#NG8V+L!S&$NAVO99CO=G4#Zg5 zW-tRDtwzln8FMlwX`zt~|0pC^C+9r5%_K@jyh9K~8HzHoPd}=h6|--GCGpWxgm`#i(TdVHfe@I+h7G4cXigR3yu%R! zq~j!4T-Gi+2l2nzNy`D}Ul+e1oC$Rk+zRI62D;rXePKv`+%xa2n0!}hcEzhOEEQT4 z3Ocnde?}*xKOe~cNKrJQPhvfp{Y-`Kr}1t=KQ zQ2@o$1R{GRuS#`|BN|NT3l>WV=bfEXUD_EQz;!AqvMF(#Orw?`g68c*L3%tgkX~MV z=w&9}P!PkYA_rqnXD&6Hu^-Jyf(8>7LM$T)tFFc~W*|;PYDHP5WN{_Ba`%s^Ak#$V z)u;gTp=}v4oF8y^3kF`#mkx7L-?n=0m^W|oqIK8^Y83$~e41=to&W%PqnUQ{EQqRs zeqw|Y2-w}$kDE{Zp^tP*^GBj%e(c+RfR44f6}y4^o$0s^PMx+CSIu;;waudnrHR>U zrbJl{O0cM%K0}hUarAAb%?}TNYi^FH3tAVvhNEqFXDSY?ZN=eDea-99dsqWB#C((V z4m!3w;CyA*!y(iRYT;30l9gz=AEmTpqB#;)10vu8giO5Zkw0&vnphZ{6q66>|USR!_PZNgmAGarVV z$;uHbWsoj?x)ITKQFL5sG^{Mj1OYM>rF@JO-yXchdEXCt)>jU<7jIaHzv&N7<#C~Q zrF`LTN}P<6@;tGczyX(e?P7gEn)lc7F+8Uacs+}UMd8>SB!OP=ji&4YqeQH8UlP>+*GIvf^80A?lyfU>yLW z_H>e{JR2YQmJOGI>0|IzTlF@yUC7q@+*K|Bh&sh)QV|KV+5OcnDQe1eDlW|7)j9y3 zEcJ*baN=7`EP?u(Y$Vyi$XJYm43+xD+%xxpcS@Wryo}>s=QwAf{B!B21!90GTZC zq6xd~BXkE2nr4uWV~%o-UaL!CQtQb?7)ee>=8&c-D8$XvV~k5%b=$?KT`GOQC&5?C z9`BuPM`o7ZhL2=YOTj@Ww87q$cf^jR9SyW{ zE@!C1S~Abbb=|K4zWSClqF$!wT;tD}ke{6WPO4}_l321OMvZK)iGo1U`uU*H%4678 zE#FbPQpIzpld~L1Q`cn(Jse2n7u9ClXQ8j+k={d*wVNRiEjEtL+v*Z&aO|+RPE)Dy zXp71r8Cw$j0NW(6V4je`lTl&^TZiWB=h}i>?g$uOgM1CM_I0EOM0Y{1MK+hcx@4v@ zV*~=^Amq&uR9s{57o!|zi_$ikn`F#I!uU<2s$x0sN`h*JGFyWYyjNPihcLc3nOIv> zR&rc(fWjXFxw?fCod#flk|SEUC%m=s)Cys2+F!(T_r7>vWM02DDC5KgM^j)_2Jfr|H2o-C>} zn_tg#c~DW7*z#tZ(dfk-&`xk`ZQ$@%`w~MmTP&goIKs*k^>!vE}-YDqCjX-D(P}x>2`M1uL{U`45g&xc?{gmEejrsgOVa2$@JKVQ6K5=S3dC z<#Q&_l z{YdVv>5JWxgB6&v58q3Rh#aBR0kMGtN8nhl%}WvCG7lN9)zBJ4HeL_3k^sr3AhZ07 zTwV}MF$}JPJRAejhzM|GRtoi?v&TRG^?e!2<1U>Iw+O_m0iFNE~gq0n%*6zqB##$2}J{)VpAN+c{ zy}vgw?>2#kj?qB%xxOd%Z0HnBsdKXP*EkB{4YDLo2+HXO_&wf*8SEq*Gfg3=pb z!@x?uK2LG=_TO0SHrXFmq^mHUB!-}(2Q4sleds;t)@&^cy@_9sfbYvP&1+Kxkn9~k zu)$-IL(8(g<<-#8_vj51ZeQ^dSURI{LO`_`HsyIET(Bb%mVA(a?6E){@yQlE3HSlC zF^Cl^^|;6 zJ(KmInoEq1jvFr7RRiZ386Bo~PjtYF#Cj$jBW=p^ZXq0!P1n>D1{jQIkR3lWibN#H zJN^g!Hc+qJ&=I1^UG6!d8+gCbU0v_rsA3pAKxK@-_2zR>x=Qy8c^LJSFC8_~p(;)J z#_GT-<$R-b2R!a$D(^ym_Ek6X6f$S>JH0bITJO$(^{M9qti{XnrGG3H2_pksx7hpx zcK_ik^rlxw-s*}pbh>f!+yg(q>kr7HUdipE7G^fXdkxaOUjU$OILlXbQXM(x&(ITL zoShfh}rzokR*YF8iT}K;H%>PE-b>Ew$id6L}eo2 zQYpA4R?@`@_>E@Q^H_-jYfoHGSL_VO`k2r7N)9Vk={>uD9#kZ?xB~8g>uYU4ahggP zFr+Ql7E4QrglkwlRo$rcfQK9K*Rj*fDKOCncXw2Rx%{msCSuc{d>%6KxA%qhresce8~6CGTR- zL)9759xf?5)c8PSA=U6)=`M?5(u5@e9|Sh}L18NE*4lfm#Aw%Fvr)*X&fMF=wO#dM zJuX`JA4>P3^bhE4&{5+hnE!=BS}0)#Cm?_eoq<|z-k9A>j8Kt&DKDnI)K>MGyWQfEMqrRV@5MGu#-ay1@-BdZ}=Pon(iD`JPL@RV9?pXD2 zgO-)@)sOXaN18+I^)jr021TjwzvzY#JIw#y(~xvB-p;Ji@4IZ^_NV!$Pltn=&!txq zOJ1l9jI&O$y90~do*Y9^_;6BbRzwyEY*k;He+&W7=Q;W5A zDbks=6v6nrkU#B`a>|Xq9uc7 zEPP5ZmwtXCvbvY_)dYnZ+|*TDH=MO-Rv23miWUA^*dCdAL7_S&4|<}~3<{czwBU7nEjAPEnV{~n$>J0C z%{7v368AAjyN(|OqT#}_3Po6Gc5}52S@ep#LOCxyR#|w;I|0tmcfYTOGx%Y~`6l$M zEXbrrg8UG>QB=?3YyD%ls?cE310hd}#@#5J^1C&6QBJJQ+{c0Yo?1X60vb)PD`xjc za$_3z>6{d5Qo{bufL7MUhWm@OKpg&uU)!8efDrwX>+dU`hL{%4`Y;%tQ%eE%KO1&N z9zOTVwC-bG7{kxK=8_`%Es|i1d#17uQSXWRg z^zlF%Puo((#H5U_ec+PG(=s?#7zW=kJs)e2IR9JI?qBrSXkTqam)-T_4d?fYBN zi|}zQMBR!WOKCkS@308Z#Wn(j#_GDtkB@kv?hXrnnMJhqXX}<0PN2hjg#+z z5ncLSwYxRgv%Xw_`XG`WI!YR+z-MejwOtEAB4Hw=&W64_DME|VZj$vrqdCG*Wy}#0ww#!c~rdAoDoSU1VSqKWQ3PP^4=( zU_7!a!7xrOro&?YcmZ9#14@X@*#2z!>f%Y*Jr`^vzMJlncQ8JD?VU(+O@Y5(J3>yh zyWH5AHT6W^g4l5;#}st~MJ6=2fbx+v-OLwY)6R(%@TLMm4Lo-P;*uGr#O7eW9*~~c zDI$+2UDYWCz1`5a<52An6>Ann0IQQnEzToH*Z`(oUW}g^ zWSl2(l?-Wj328=r{aB*AHv;J;d^jfueJsPz9~4{(17ATL{jfPV7wCY50tOjX0WQk8 zkP}^S^+;!&)cH&Rn8J?vQ2B6|VHglVGNCNZTbu%WlQ}uoNWr&QTwnWC^SIKrj9Xat z?d`^ePdVvEpgABmI~U|KvYMHkr!r(Q9&$2ZIIG^!pygTDrfw?yP{zBH?E)WsMNMQw zUWeIfXS1fR*1OFoNtV5Rob*4*Td<}Y9fEhW>_m)zWmIor)-4ap&OT9ADxJ2WI=-%& z{B+!STNrW;wFrr5f~<~5fE2l9?o@6sN?o)_XjVbLqJ>OuGURJJN8VUxx!Y&vI!ktY(mUINMgP5@f) zryu6`lHJllnPs1$#r^C~?APSaht#V6qpdK^HwalU#)59<0GY?HE4*j`$<1dDW-W>E zodx-|7Oa_nq!q!%T3B{ajwt2S}?c# zSdHVVA(f|>De)kqdF?|jb%cCFzBs*R`;~}&*RGYl15=sRViCePCYXS7INC#`Y%rhJ zc9Sm${%XyBEi$`XP5HiGq!HI<#Uh>PFZMsE8+Z))!y@BbzGqTvmg{O9`o<>N76HHa z<=@NYM*tuH>TgujzH$2$*M0I|XI68fXkQo^0C0P&-t{Z`m?p7>A648~fZFfX_7vF5 zpdKTw{Sat+u$+JB+hqGE0n$MY9E%o+$V3kbIVx!k{A@oCKxIAC|9AwDI`}2uh-Dex zsL02`Wxe`J;cQ#65 zSjt9Y>rAQd=WUS|+_(=uavv&rgKg^&i<5-DVH70jB##aD;nDXv`UP?`g>%e?1A<%Y zseh*V(i+6)0uc4Ioh>r>gyrg8Se-XaO<_MnfVng??=1su3)1(2wfosEZ-+eWJyFJL zCBPq=4kF8KHrI6A^kayOm{Ts4G4Geow1B|bru;AM51#9^$bQnzDhc_9`Kt@(shGKU zUg?wIk|brf%;>$5%R4r-G&e)vglOWcc2pfD+ErLLLLv&vwx{bg4>5@nIf zL#2j~nGIh^69z-QC-yiVx98q^O*{!oTB0lC_h9Aoz-*G;^a_j=3u^3YCgQi2;}-|GZj9dgF(Q6VnxiqLd@W9I5_3E6fF6$*<;csg}h0!9TpvUck@9$Xi&q|I< z-71~;!%QI^|0%DHYM8C^Y`cO!uPz#GUgdlWrE;&-<84Age|&FReQ{jVGc}mYqIOEqZ4@TNaSajjBz!S;aoi9uGw&GY7Z-8UE|GCs@60jk+cPX(=JT{E$S4Wa`i{ig~1R{hI0+r7n|CE{o zb$#T2e(rbmKeY>yD&i@BQ=MY^#l`KV)ThK)01Kd(&?)nU;G<)ak zY^rVXymveg=(~>@yr6gCNebfyqX;uwAb>49y$BZx)csZ^8LRBd1d76#oG>I20cw&E6w)h+yw?o=C1txlQahoBBEbItM4zm5&Q7d-4HVW>Uuw?9gZvQdH)UZCHW5;CxbauigC{*2y5gvlG7A z%##oO{zRM4_9{9T$j(?=*)V6uH?7dSJ3iln;;SJSozU2y1 zg&;L<4(Lm5v15xEp}=qTy~`Zr6DWpU?Zqc1lF6|wm6VK%=ja15lEO;G;)#OE0ClqS zRq}>2_FT|c1n1|s32AJOE%%O3{?zJutTjp(&xL`BNp5atK31c0x@GSY5XIsdE&V@x z@gd7M-BcZv&|%m7()6n8tsf!v7f<@Ai6=`&6*@!r`*f|IbuX&;H49Ab+`j# zJOJI^I}_@qo{u9J`g`qh;m15*Ktp420%np@S>PNgpU+8+6i@po$YErrCl-_ZFBT02 zH>MG|;$EQNK*WlNs-;S<2pBhY%KKD8fsZoZ^9$eB;3bj>4j@CO3Dg*i^o094tk5pfnMoTfU-^|Wf&DD zo#G8!nW5JB;cj`GXDa1)>`PiY0gQJ-#2cb7Jf2pyWUOLQ7px!XRWi1r7>OrGFy}S3Zq$ zGsoxIhQzu-k3}LiJEsA2jG@MH)pJB4gtN+y60nY{alPwrqGpt*JmQmEV1d_DOd%Kf z1<(%j7_`;!N?4YQd780~R}c|7abK++EC*nG#FW*7&RLw*l4)3OHhxJ#_*S54)qz^2Z`Ih;yva{eVc;h_6}-IXFaUmS~{ zIrTCGB2}T)xk=swn@aJLt3k~+!304*I#Jmzwdm9zJB|?1Z13b3IvXf zZqh&?+_qE>NY|Ah-{9IKoMa~qA@-?gdzzqIr9Y>mINTL*ptKSSNWLG~=#n&rlj_mE zM)`6EdIP9O&DyC8wY2D&7#K;s5ehG#oRfYRu+;@&l&`KT1bx`aWtH7sI)72#S)xgB zeJ^%a^UL9C=c~660VHP>eSw6Bz1P`6b+;d=Ys!yPCV_Au&+bFImfE5!jdIVDfDxTl zS7h(T@xtoEq|h&C#zvU*pdw1*K+rIZtrtd4%Bl+G?N3F~rGxZkbFuz>b~ zrGpqWD72f+okjU|XnYDNL*uc?9^w`_W(tkX^kQFoMxz1U$l`fl0Okz|{YD37XC@pK z6ILLM^^4Dp26~`j0~r}w^zkjyiZr-$F09WboPY&<@$zh35B=kYW8PbevBoKhm6WK% zR@UPtX&iI%FvdIv_VcmcH9R17%~8UYH)D(w42F+sST8DQ9k1JhIo z5o1K5Dy0GJ*-&4xBF=@S!B@6TZxPm@!Z4h(rC2h-Hvw>gtTOaRtVkh zpPE*|%)PyHk6(ThnoEK`dCQhhk|q(A0jG7i7&WG9Qq0wsB>lAbrOaHXCzG8Y_=3Nt zz;O0!vawVK5?;;MRO-6rUyJU$9;fv{5VjkNXQAa+R2cJAokIhFTntapHQvb_SkQGoVGGn-U}hfc3JuNQjN6u-})uD%2fAPg)8LG8L!X zGZG$KetX+O=xyhNwO1L7A}*kNA#fjkC8J9K$I>+DX& z|FlG;GFK5GVxsvm3wl@_=(H9aVDBl&)mCK+YZwC$9P-jCB;vn^E*Q?Ub zSYjwp&dTrn#0|hhL+mOO9Z-<}5$LwnoC56uR-n3K+na(;dp+ee~4y=2|mB6ezK}%;{7Ak81o;HMa$-$XbH4u@IiX?{%MQ*#BC+{ z1g{lqM>YLbm7WTk80cv5=fWGa?;8z8E2KvFnDPb1iK^HY%%>mDCu-UX!3<$I_`CV0 zM|Pb%m3Mg{=NQQugq@33?00%$jDwH|o8SkU-^H?K_(`g)hGIOfmb-vJl6{YDpqr^W z!Y*kpGKu!01p2b*j(9=JeQRCIh7#F`^3a$)nW5x0BsioXTUbx*O601v^?{ZOJ-=#~ z-Ok@qJRSK2=k#EYL0N?ZwR0TOzF}XvGHv)Fp>5~DI^Z^uv<&&A;v${4oFXld*S;tk z;IaEFPx#`Md!Hyh?g;lmo2*@G(!cpJZ#0So&?LW6M;Vt~739y?J7m#wm7MyZ4MvZ) z0(Tn)2_#8sIxTC}R`H~+QVY=l@sdfXJmLq`pRqi-YZH^*joLfr{K+Pej)||gl%nTd zQLb~@Q3`{*Gg$HsxY*^7xZY~g0AidonSDz z&4kiS@d~DyvhOJUZEftWwsw6I0W}DRw>u7zT_v0HDM+f?OGA+Yy!GH^)}RL^h`l#;vVDfUUm}e`oPNlI*{bTL($v&+*1C&@ zGJKB=Ay1tvPmhWx$5y|mBL~>1L!kuhIZIW>rj{gbNqm;vWcJ<;P0*0TWGZ#EOL4?J zH!6zz3eD=WC{*LSp3J^RK%GvuN;_b_|Bn|7qXhJ--CDc^ zx9-=q5}tv`x%|*K)Z#0d59Z%YJ*je}u#(HAr{ zA90b*KXSiZc&`%7jn*6xg{7H*l-4+-Lfv2JJ7mqxj*YcCMx|7U_>G9y3Lw5^_s0nNmv%Ok1GU24##fo2Kz3!n zqKGp4Mj8P_NXV64Z1b(-ff<4McoQIB4Z$b{_4vk_g9M3px{!XbEDVV9Uyo z`i3Ie$6BlepPO+6R^}4oY`0jdP}yUnPP4c*aS|VebdH z#qusfVGo2%BoWK*AqN|zDL#U4i5k6+B!E1S8?Sp9i;mN7MaBQ_u`sq`2_Uk_#6!>u zZ&;db(^oGT_wOh%()l@Q1NYo?_6|s4zz{6-w2E%wOke@Yr1OiOX0ajL>GLB9vDD~y zH9LtaaC3#hrMUGDZ6^FRhYwX3PIV`t+Cmrear2iXu7LTdxXDK1a)d{eZIzl@PlVWw z@wTx?$*5%MD#g-yrV{Ke*c0x$N<6{a4RrdxYgqDjm%^;F9bm{i@Ix@yG^K3pD1PL7 z2m&cq6JF;w+zUbHyMh_p+jF{$aPzo z1czX{0Te}rSnUn>)Rv3&xOkKH7A2D<8idf|)%pw_`%W5Mr@nvDnh-jS#;H<8cRaRf zu6>7~Va~JQUsomr@s#iD5HFQyV_vV}Rl?{$S$aj7JWx~OQ&1~Kf5{sRv(*$n5;1kr zcQ{G4%YG8KG0(~P!U$rVkd7BaL$O+od><9^YWx*6EHuGC^@ZTL9P1xB-f+@nJB3-X zKQ@WX2{KGd6CcK&39RYtcwcdp_bho>jbI;fX>bNT43sEpLn=nuugTAXPhJSU@pkX! zaPXQA2!=xR%9B;Xv&E#@h`ejffhoFa7!$_>HYr?UQMy>C*leMra^nK$3*v??M|>?q zV6s7w*HRz3N}g@S4yZ|M1ftcb>WT6bI^U2j!MRR*j;gslaGx~`1hvxC=}7UOLe>W) zcoEU)$vMMRjYv#^k4K)hEtb`Ex_c+`g3S{yu6P%Z?hSG@HDq3hPB~U%F_UOPu1*>R zD-3--FR(Hw_W$Ch&g6Ra(k_TXB;2P~c~UvtHW>WwG;W;BUpS>bL0YGxBJw9Y`0t zx-PoT@rR<-Ckb50J8r5gJV2feM1|5@nVWJH{`AapL;xAtt+4*S;f)%L%e81hlvpUo2K)SJx8aoRO2?rw|Yu zm%((J>62xz!vfH=v4E`+E!N_eGhsPaX%=YU4ZQ;5ZW25L-4@nEHWqF@WX zT02YNH09f<(=*uRPnMhU^<|&Th<;-pQD8)io8)01$qQv}8^w!QK-b8`mKPNKH0%hm z*%@1mPDrcEt~ZQuLpQ~}h{phn=k$s_4^ukar{Z$xC43;>Cpk~Vn-x7#HWuDcz*F*| z$l{hk9|cimPho`<`+5W6N+#sa#kE4g&nv1*btg^#W(Bp*=Va}c308_&O$9Imecb8nmq>G+0VnGjuh3cv-9C2KPE!`=_9J2uGG@lNvo z&81Or=)k3gqw91zcOl%NW9C`1$=Y-;4VBtgHsk*mqWe%Y*NoSs`;pXns1ZIyok0sQ z%$q+~ul2!Ujq#YfFZR%5gKDeiu0G!4+AFNU>}3k*M4!ORCU-AXYG+gEn-Ued?B%u_ zb9AdRA&19`8c46QEYI=Q+?xc{yzfF)1=3x6DJMFE zwQsTR@jziqd}}K8`fc8^tDL2gZk(g zsDYB=C;<>x>g5aiut5zmW|Vk~*%6z`K=x^I7M&4mM~yu7cm2@an}Vb_=j}bXC20|e zg>4m&k#8A|RAqOfZJn<7l|n7s@eD>;WR3;b*x&#vsILzdK6y@pWIoV zXINX04X-s;+ZTWSw+gDBZ~q=v<<1FyPOtSJy8Ng7meQAg@~0273h=vHC2JU=vNlF{ z&NO?yQvfUC^&YJc=9~gm^qTSO-!^$Pv&OnpM@(fN!`}H7cHu7YI2w!y$Dkae<#xEh zH_IX~7M*5^cFD5bNf?79Au)rSo6?rrN|pH(JE2c9yYN?F&acc;7j-fCV&hH7Y_V23 z@vVMP%R!z5JkOwrZi5Hnj0c~c9*}k;N*`(7kiI~V=SZJ>MjG@6!Mr=zePe{i=b-ozZo7IH0;bEDX>@3wYf)F|!hhsRlbC`p8h7zFr^g%O!| zrhUp|C;fvK@gbFojAhrmf)zd>ck43l@?f=^O$H1dSvkSlgLqiP3xLjPp@3=w;}pB) zTb$Dgo=Z2y3u41a8|_sgB?1(DbZmc~1ExkC-E1w2QL{15j;RQva)LPw$w*w-#oh zlDeiOPC3#uVEvO|nU<_~R1TqhqG+IOm?X(dl*AP&zb+jaD`Ck`&dihQ)h6=GLnJe# zqd+oHe0N!F{vsAor{QMDsZ2H%^$riwM1mK&@00_ts0>>P)lYI`2+CZwq;XvNRyYL)d@uD;M4*^1N7&?iQE*lY~DF zMK!Dw3QUhpaPFknlgO?KZ6Pjt)g1N7N??DWb_%3Edak0a%Ob3zQmOOJ5Po!i%Yv67 zgtHLEqyY z8Bx@>_#Iq^l66al(b8>@juDT`-RF40PyxJifZbBH7x?Ul->XJ|4aJqV68NJ7j^=Wq zux&}7|G7|>!Dj*duK)b~cOrs$5%*vEw)Aa&f4fy2k3v25&7eWMG~*`P_UCaOqb_JIY^e{=pIGsBOu! zHtX_j2*Oe;xSDp7gp(wjm@6%H|I9&G_w9%_z)&P=0`D#pEIy+_&yHYR+y9Tbe_L`K zIg-cW`*{lLYIeycP~!NU%{@xKw|;X9 zCi`2>lx0%Ji-?#_m{Rhd<9WL|$O;6R$0F1z>D3EVLS9OTyl#3cumul&@z?rrTB^vI zRP`9fX$F!TX$mx1;nr7fqMDJ%$RBl9K3E50z*ruw&dL9k@{v$sVhpQ|6oJ~U8KK;A zR?JJ!)Ib*4w#Umc{~^#*1vVSv+tAYCF~9|2@+@V2XV9Forozbem#+N{A;yF~TcF$$ z5QOWNa%ovaWSFm7WWv+NklrnIyWymF{{Jar8x9RQdjA7miVHaw&GH~Fg&C`@Ew|d# zeOsk1b(rs3p33N~=^f}!d0^Jj>~TDkRTPpYz2M(^uXtd`oEFxuuRNY=_W8=UjD34y zcHS&L?iH|tY@GBwk5g-!r`P%`b}ue~oWkaHzh#&|&&}<{ieeLF!j1@&WKK^ zbz`74nad}gu1HCnzY?o*8?^8?NPqjrI!QYOf7B3b)i%X-&9;s!7*7${Sk+ibC!y2z zKG;3nOS;;IEWi1E+VQ0a;Ty;YzpdP!9*@;_>DT(PN?ysK0qj=AfW8m`HHT&Xy(Wu- zY*@c&O5;$O1_Xb28J zS-ewa_7)fDT3Z^0P~~2+4}DyEKgH+MZUyli=+{ev26fi$ryp+C49uvWWb<)lvr1k+=Aig@e| zt2wb;kRdF)?Tr!fHSaEWA<%LWd|&3 zp)K+8Eq-{cOdzI_mZj-Wcwj?@A~mu%2O~UM<`7gu6X|vF#(I$isn2YdJf~2%Yu}>x zk|%@0-ZP0L@GdvW8LN|@B&h}^uJWlXW!y#x#BggD&=@w_=Cl#a+vdh)ajGjR?b_}3 z^^1hZ_=>mnCheF=S~W%#1rLS(JbGH;B!Sy(IA}r`0C?@KmRRV?ixN9=-L$KVD2!tM zgx|cgHW8^o992xZX_t(AFjLNJMfix0e%10ts`98)5NRqkv=9#w)_!T#9kb2#rIWAG z3Sh@nCtp<|1fVzXKopbvcFh3YSfRf&JHvj)_Ojm-r}~C8o7*O9BvJPsL~hXM?n4Uv=!1^eoQ#9+kl_SjT0N`= zBT4oZ-(U;($@VmJO)%_W_>A*AD zX|~q>g`^5tmsSv($TSF^n&72!!Q6cuW2XQ>K)=5L9Zrp9MOrj$W=?75MS1xOZ_#MY z2F*2_S}xxp;W@#5X-%2()}O_CbOA*_C4x}+`HY^!}AbYR^rLL_pmu7t@-id7Yb zCM+zj*m~8dfn9Za;ITOmVDq?^!d8Mrtqpfo;{J`|@;I$B4K zLa%5aq!%s#eiXUDPkFor+P?*sZxESq@3ta)9LKFIwEMo+S9X`#WuPCVyXVnx=}Q*zuUL;yJ$N$11M4>Brs&a#V@??}IMQ%hT> z0Ypei5;qGomI)c;73&oVd`{=8^=9Si1#D<QPkFOpI65=bn%_l3ufeQ_V%d0wHn? zq_1v+@WanK=G*~cd!@xnI;><@f49mui#&Wa@rk!A$EB$wBjH@I1+mg$PbPEAfL69& zD5~c`Q`7BdJuAFd$y&)`y^Ij?kS&ON0|*;sZq{JVNREXF8ajQqg2~y&&Sx$Q+Y4)fT; zBGl+&6$|B!4#^ZsAJ{LJz6tNZ*jS%tJ|O(j(=Bhr6H2z^3!aT9_L`|CPjv}RSaZ$X z!1eNKWN*V|x2pwmZB-l6#htMExhFaE0c^44lj^gsBq?mXW`~$i{#vXl;>^+oUu{%U z*uXdHV5T|9bd4Msp*M!ysm^CAn?3~(M!P%+4wLrf3@5`^!V;hMzweWOT%8{#%m+}B z8uHKSab6^*;!v=3H{mnG0VX($jyX94h0x1%aP>F=6A_oA@63mihQeW z1O=;4*KgBG>5aE4d{yl($8q61vK3L|cK$nWo;2wdI3Q_89CbKlu3d#Gb*)wuiY%VK z2#PHH3`K6qqni%4tF+Kt_x3`CK`8b0>h0z5rzt;-*n;f=?oIl2r24((Nd|8OZa9mQ zsOHPA41G0EXxZB=sIk?5e}VVmu?E|0#r>56v{7pn9`J}(n7^@_;El5375Cw*xAJuF zRvJf`3~xl5UTZy1vW~yN?|>WEr{_)P^T{kcDP+3MMw{dPj0=We56fw1NfHiP4(Fm7 zxXVC6kny~#r{FK{g|n-=)ocdUq^7ehRt_uimV*qvrt>I>-fXSX%7JhB1fz~5nX5ts z0cN2!NBcNPD>cS_BiDg)g6B`rN_bNZL}9w?uH+mtD;rivURu5hMuUm%56Kw96W z*;BW~@OI`w=A44hjm_oNl3XB+?Om=a-T$jkxp;bOo$`EQp3S7;pTgbo6-fM@+?ILB zP;Mn`El{bgzmk%VMdwh}l0n*#@Wbav09ooLiB8rftaZG~-`#@=k!6}8mkc9XqMRw6 z9V_u)od!-KVTl5QG1X!Yb2?ak1UP?DE-Wcx!B2V6{kKBv#=cRZY|WbL8DJKW=rqF- zjHMl215n&_dRoja=JiVNpr;Tt- zL|=+}>qW)A#O0fWY$ak1=o+?YdQ1mTbI>JMp$=uxbC11J(iq<=D?8HP!R-GW0@6#eURA{f}zqG~gd8n$bS~mHoehe?0AJgI1k5h1!Q9K(lcD)z{hn5a{ z;9n2P67tCZuKG&u;B&gD;Ux5H9{a~3dExvV|J0A)_tiMXQ=y~xOzy4VqUisR^=J1J*hxkDD`0{W4 zoISQO`~vsp&&tp2wz^|?;v4ebOG&+L%=%k~>p%OZ8tnGk}~zN+Xg|6{jNq)UW@$OEFfpk*{$H|2x0b%g*2G z#U^61iPdXktB8NtFa9ZT9lr8^LI;hvvFo;W3+bQv9obp%&wP5hD&NpCUbe3OX^UF_ z@K-mE;9caK!ylz_;VX*P1*Ek!r);&wc2kL1pWx;s2M2aDVYi0uLt+jEnhIQXG==Xd zV5NI`Hkm3JVFob0CGCapRsBd7T{xWgj^FUWi-1{Z;nTdhS-zN|t-kSZIonsxfX$PS za`$ZM8Glk@_f6AVM@-%hFi@RE=#2#O$ondjv-Wz+*y6}K#Z4xNYuv$`z;&&%qXz3A z4s_jF{ACeF_B`9BK8n)j2k|4!-PLz17u9zw7r^sF_aPJdO7trmK1xCz-EcH|uFjXu zF}Xsk`qzq?+bfu!;c!3}jbSea^xQXJ8f4I!b(Q`S)HAq3JIDG%fva?d=Zq+tbS?dw zige*wCddYkz4*mW>?twv2ugl6yaOrCh^aUFZpY^kEri5^w=Xrr;yBB43i32a!>Cw5 zrWzSDfdHaR@67u1A3y!)C$EYJmYpESXOp)9FNDoOF0_Vnjyv$MnQ!h;h~#)FO@D?R zX{c`FEVyS?r0~vrmohV7uv&d-`j^<7ohM(xrE>4-OL(q7{U60~y}&QU{c&odMedw- z2^sq|Nj`Ks*)PLnUh{YrkDz47QJ@J@Ut8qak*CE*-$-RbX5Pw%;VwUgyr*Y~Ir=VC+L$~Hn=IUbHRS-s)o)zvg* z{;}@6?AjEI$yBAU0O7_AE!q@fr-*3VXMuW_jt;q(2KVzH!MFc&eI1?(8Lq*UfJCpz zZZbC;BgLr&AZtlsL=MwfK8035wU;tj3N?M1*xw@Gpbp=7tp7fgZsd&KBW2rDn!5Gg z;9{J?TUYABVlXL8Grq_pYMfaC z)v4!A2F1^^o=Ta?Dbo=3qnF>0Y}w9q<-OoQox@9uuZ~72yEW1hBmlWcQuLQ&)cbYh z`e%Qw>*6Ko=3uLVem3XldR4dob7}f8w!7xoq@8y>-k20L7m>5fwuZnbuDTHLFv7^- z-6HY$q2nLvhsoB2^Ba~7UBmdokWRP>GJD#CyRHJ~)wNu1(8dTnVr!bKX4IxPdHNBuL(*2v**ke~S!Kmt z%0=?k@sSept5vRl-HK*nI)TcXcel6#gIC|3>cH!4rHF8i;y&cu*rl`QWQ8syQjC>d z$N0=b`$K+lKFCJN#J1F)*zQ5EEwws|u)6(!Y4!qR%w3fGzG`0$H(jA(-uKqH%1!86 zp^ZAt-VdzatDj;S(+rN26{&@*bUj@s#e4TbQqFD_$hQ3NDwE7lM%Yg9mScq(Yt`)R zcrLnD?M;n|~Ip&SA(-(8s<5Km4EjU8?EA9!rH8s?h|I zcYDXkav5pIhB)b+WH57Im)Tl`9=>X?g6FdaJd+*MzmN4bZkd{1W3h$p5fe)r_9RgDA;g!UTA)Zz7F;@*V2a zJ?A%V(|@Z@Rg^4OK>--;H6Z&f^&r&}MT}g@_qRr(t3KK>E-G2(G_P(Upil!Pq=|~r z&8qMoWVsWCNT9C5@)u0y8>>H>sb~mv<+@||uh@Z?ZY~=_7a{0&5!YMUIvK1{qBuRk z$NM^O^}5pCasQUvQ8D?&8;T=&C}d^d_wv#z)lcnfQ4G_r*zAv^Agx#;C3n+>sq0X| z-_ksQ^#6z=UV4SH#{p-g{`*@S2`!tEbqFHFpN9Nd@{8)2BJMgT*L~X;atpF?ku^!p z$xo6xf|liPv08x$4A=?G14Y;ld(27hb z&2D5GXa%D#S) zVoBtypsI?#Kjv-a&HuAj(hMYb>>v^6A`YZXe8gocyB)Cpor&F|RH*fjPt|UB*~(uL ze27a9TuW2wgr6s6o-fTAwIj)O0#mFjQ!!`ob3ClR_7%M;x!RM#ah!%-0aXeqIZ1_Z z5A?H4Un$AEg1HDcXvS*Lb{RGy_T)WA`!#%mkWkHND{HoLro-GUqY~MnnQm~Q#Iehw z0lv?Z>MB>TS|TOT@BqK^SE3-}II{d}i1xm&3q&p(3glx;_{cCBN}Qtw^eZtmSfa`Q z&AvWBHg5M+`uSO!v<2p?6AEvLpmm{p;=Sdeepg5pYYm(=g_HJ3r$yMXF4T(h$rZLM zx$kMDl%^6u5J?|G(xmDif{O9jqr^0o>RDr} zq2B<#BodG$TeVi5m&y9pw>#mo3F**f60-wAR2(*R} zbW+B+$hv&GSZg&;K1i3`wamICH%mp5u_U+!u1NS(Kw&?d4Z3LWY)(6Qg@i{Sr>D=M z0lLVmtEtD95ZjqXh4 z1jErjIEHP{`KMj9pSWXy-(Ard9`|P>3+jhmO(uhP&VkDrTkk@q>NzF=*HVEE7)6K# zCMSi22-Oj(e_7lc>)1k8YE&ehgn2Th!!$b?ncIgh0?|0M@bDG1l-^Rv*GTHlTL_1G z9PKa&fTySi%}Z|l73Z)1T4Qa(Oo6^Je&kBIrR{DH%g-g+ltbbkC)#nL#v*af09o;u zn$?@$I@9XFC1Z+UgC%9?w^pjrE#N1hMF3#AS=uq(>#iE8#=Vy<&LEBz-)IXL1R?nD>@geB;%KlCg*KhcwTf_r(e`_0@i3-kG|{nenCTEz^Y! zBPcT%7G(MYl}peQ^hSVS`oi^^R3z8~kATiIXIG>}3z0G3U-DHxPy|VgAfTR~s3;+&hVNP&*^Mzn~L1t)USk1X+(Uqvj4`UrmmGkOOO!rp04_<*x zGoOaK>{@Dw2`Sxd^- zP*YE*`>8%3()}K>L45vgK_Ww^JL#3rAq>|GMm7X~k*~hCxGY3bpyJ%rw~inN-`8ZyXYnG zWT;@349*EbU{Dem!LL)t0b$MH_M0Qy=zLU1qMdI3vU7G|O>g1a1tiX=MzA~@y8pEv zEo+j_*-P;=w*AU{C4!)>!@fP_5|*W-_reY3{X5(|16xFTr+?~%#(dh=aWcjLJ%QZ( zsV+-u!GSXV?_}89+V~^(ICJ5dkxiw`#ZcH3^`4Z4zZjK2LlXfH#|`Sg>y2m#uN7u> zXv>jNfuCf1;7#k%dTU0-bW)Ej->_@X&1wym|YZMnoON07hT zrKl6C%M&5x$cK$eHp_d6f$RZ3iC}LaNVjsl7joEVw|y-*Qz;NH1mt{8J3-_Rb|Z@S z?$BoHtym!Yr-hSWtCFi#z_4B1JN9^yRu18M%3nL+klDNAaPv7J12~%lKqKXsYPsdh zacsuRj#b z+0=KlvnYZanfzeka>CQn^z)xerss9`o2CU39}WQgcH`Q;Ov~$mX)NR1P4juZ1-<_2a3!v-Sk44N~bqJ=v6Aluw>HXq;1~`DHo;)_W zGcNL{WasMbMT5R_JibSV*T3?@Z?(qmnHaj4`zK--5JZ9+Pu;}CsaxcLyDNKNbNZN_W zl=IO8MI{^uMf*N~s}z{Z2QpM&K$Dy)a00tIeo*h8)L-YO8xwutUqcMRAeJ#dd}1(5 zs6!-yV&4I@#lDNZ<{9Z1g!@0nj(J2j?^f?w^e{}5^lQO&OZN{Kk@g_=p1ZSon-&K))!g`@p=wflS@+p~k>Tj8$M56(Gz?No~?!V=VOSe0) z1)>Nv{#I?P^&4jtz|giiE>9@sRg}%B>yGzMbC48nE&=G`u$+z|cW%H+8Ds-ALLS8T zK^ATp%~;)hfKXE_r3)n2^C-4Yy9)a{Ut`K&?I;6)xP=FdY7tPQlk{J6G#S2I9yz_Y zHbeA`nw6J9)(!P3R&Kx)x|0AwkZAvNNd{eZZy?(Tj)kBst@vB{L^wSzr4i%Fv=^8O zE*(Lc3V{X^e=Z)F?mGDk{yS5K=A$l51-(9476TG;!&D4 zammnwV(9%f_^gQM{9?^4k}y^Qp06 zpq%JSwqFS8Zn;Gf;WBP|=QCMq*LsdzF}N)*#Bv0`X*s4R0Nu?>3o`WO_TxS;4-{tw!+p90N?l?ia;8@3G{6lZ6+JmG$4VBzsP_G2U*zYMH%Lb{;>m+}h6aPU8KWs)ON4@u(@G_p-HiDWu**BW zh~~D0_J6F&&~h2>C0HJ{>~wRkqG_h+?YP#P>rPfY9l*3Mm>>xkO&k%K8rc^4w>*)9 zqESHaq3+zOW2X9t`uC-kPc*ldrwh` zAQ>|qpWX`=sB7x!$$Ot|1Xj}U)@6nc?0^bZRDWM=e7zl&cd8n`GS+Yo_!9{mGXJ9S zE%NF(Np(C8Wj2GboZ3P1rw86PqWVmaYeVDI+8)Fja;+zxQ8Ovk(G>ySR?^6F?=3`70)3!!je;DA|tKVc63 z%P&VBXT9{CMeYL|&|orq`DjF1s%>$Dw#Ax{slQs7@O%M}-}ai`cs)L|z@;zVhZtsg zCE)845arlpUEA@HrMY%pcssnX-v^?p;AfQgS>jz$~FhantdC~YGv$*L~ z%Jh~eyxJ^&32fRmkYnCfF9dnRt5rsoNCNsfNZAL=2R)QD<9?o+7>)N0827Q#!rVr8 zTqAbBd1{LcrJvDk&S3x(v*>bE?6t9;&uF{gPFz$C5A-<~kzkd{Xb7_RM7X`^kvO2< zsEXdT_V4VA&WrI!i0P0^p3_l4NE)h$=VnO&K)}caXQR|X^WGR4s+?mg>XxpP2rUB z7wMjVO!LATC$U2Iz|U+{^KNFbK%Y@1yRl;jU)>Vv?W<(h4EyHg(r1Xkww~D|cIz1Z zCHxk-?*ENp0%($$Sk(uW)_H(&wZW*J?Tng&#N>B;o`mcak~zgAt(a4Eyhb}YB>{Y0VW#{mvF2_ z0GfduM7i%@J(vof)@B}VLWt77=_b@iDepyoZTIr0&N_N+=Bk9zV2l8Hs*^TLc3H+g zpQm**EB2 zqI?BZtG`d5Rk*lUwa^9oFS_H-v$ysm(@FE+3CE@tB%KhcMYWj#3Nbe3dHpqI7>_o55R1??!2kd2=s+U%$o26K$`^R1( z(IY@M@97p`tGIxKWE`#eLoOn;z_3dUIFJB>yW-<4)27-{^JaV5G>$+TNVC^l>?Ud( zl*6fum*nH|`{Y?QMk{zMR9=-i_CWff8@LT0)!XWtnO%ENns96rTWGnT6)!-sy!)D~CCE~fWELjCwL z7RXa(+LtU)b~41HqW>mFnI(v73Yg|Aa)2tD-5>rvuej-?H80m<>FD0h&aQSA z4fHqDQ4ju_qwOoj*cC^6S~$(fg^M$DZ?<_)T9)(gr$F*(4>uyUl<;Ud8@ zfAx^NxA3Er?y{0>sYQ@J2s+!Bra!?l=eukQ2q(|YOc<9jbMG`vk!tbs`+@0Majv#% zC+Ztk{5HtBNV49Z+9|$7FT%<%H2}*x8!-% zy@VhE=fBmaUzslJsjJ}%isK{Sq8Q^IG-=peGDmDfURX7AVE2{DKqpiwR%K=kiW7~~ zNWzhpsZgqGRBd3b-8aHYo~*-m;is`*j4{%mG!hHyBbugmDIUk7Dv5E8ujM-aWz2Hp z(yHJyx$ry8IxZBpH_{X3POADI4Lj!rVlAOLV%xPVhiZS%$r^1qk>xjxjVb)`x()ES~X}zmRxfY#r~wk zZfL5>6(5l$M7@AuR4fPlE&E94iTIlmjSt+tB5~|{* zOLV2hHNA`3W!uVJ1;4JQ+V#SCh4g(lT+6#2;920W!<+Qf{5 z2d}Qr$)aGZckE)*N`!j6(GbY_dYJ=Yb?784WGSamJQSUjn^_37hre6>f#I~ew8?uq zn;Y2ml6eye+2%z*YKG+I2$TIE^}Ta-bIJ*l#e1x${ncy<4r-kXM`KgP(PEv#Sq(H9 z`rPxe(Q>wnSD4+*J3N4OhvV)1pEQTNZ#o_3nf7GOo~?0e9z)ZLr141I!2fC_^%X$d zW6^MK%5WMMN}BohW)0&F(?352k>@{UZ9L57TTcxdXKc@h-Y_LR61u&a2Y zZlP>um>-3dC6?{AlU+?ny#X?P@-~Imx&Q?rBIUx+>GPbX#22`+X%%8CVvC!K!O+>t z3JXDrGF!%6o;?_S75i)9z_U%L@zC1F`Q%*Y61$MMpYU6y4XRq_pS*~31oV%m*Ay9j(>(DnT-S|89G2{D9p9)o5*aQYMw_I7ArCX6e;A*v8pj z>s`DV^&uw{^*0R)J!9XmF*N2WR6qu%ZS|{TgH0lx`2Vxfq5sW1juJDmw-j-Lp#(;6 z+a_{&(uG-*yEYCJLABN%2(l+}H>Ce;X9}jfZaa!7w0cpppmP_>3dFxyOz3~Qh_Uu= zX++Rk$|X`Z5SO6|w8Ij+2Byy3$2)8HnRj?CvVRI4%vpO!N|wAF^wMm^UR|xm5zPW5 z%Zbjmg)SUO$^IzsX~9=9cF94iXWR}~;Bk!yPEUuI6}sENQ&T7FF);Cf!-l%(Zk)9u zf2xgKj;lu|Elq9)hk`p1u(yfVVfJN%6t~-PU^0J^@BZ>1q0|+Y>Z| zav;rvAj*hpfYX?tITjR;9?YxA_^Glkk+2MPVGe1LG*SiD3oA9ibB=~t4HUXir`LGt zV&tPVjO*3!JF{g6!(aJ9QK;h&Ci|{Sqvb3-# zF_}{pUEALe#;bZ@TeE>wzjxxxBUUNilUj>pmKG*=mWm2C~G2sDunV zF!D{g5Y#*xp)A8wyLBZ#oi*-icxc_ai~Y8NT{~od*y`zm?gMN{?oDEft0lr0XEdNuIFiAHB$qe~W6)i`$JiEKcnstcn6^oO z-S@*{6bWd|9szdLx$6v^8Kqn|J_c(6RweZ5GIQBAy@uhz*~5fB2_*ZWL4VT29l5Eh zYX^?Qna2r{gG)N`wbtfu=Q!zK zgu}Y_O8-fb&Qis`!&P8{T|s}oJ8jYzx%F6th@}Szbbr;TuCBC$p{C*+b7L*kM%BXD!P zCN5XZ{eqS9L+6_BGcI*pML%oDJEwjq3pJRG09h1ArLetKxpm=JCR3AOOX~CLe5|%% z_6^1l!DXA>6|mPCz-)Bl1efv^%1px*DOrC5GV`EPn+e87!38K`<%S!@{)G$5$F$>v!wO%83a$<&dW%buwoq%m7QgXl`CN-@K@rUTLuN3tlww z7|x2f|7A{DY|55fX2j+TEl6N7n1n9oJQ6(KlmbWUbz02SZOl1607OF@f8qJg&h|*+ zzYVM~)|PZ}^pEh0g|8KE6w!38`n=_>nG+XL80wNitJ`R4_5=f+@ghpq}1Y=j@%Q1LVh!3XIu8#`I)c zNp57&taG*O*Z@YkGTa)Fb&5-rYp+-THfGkegKrk|H~#N-C!^}Bq9SU`Fc~F%zb^Zn zCu-P2osCTAq8%B4=#dJB!W3qkCZR0Z2xMRQ&^ZnS8Z8*fLEI8Pz2OSYmoxrIT_{XG zEpa(j4B#bgts-cWUrYfhd8;_$D0iMoq zf{SEwGs#gmS9)wc!# z=NPx(#(Orv-WM|8gpBM^ZDlGNCU-_);eybPj?w-{eIsK@E38zx*#+i4n1dcS z`$9MPKeaoC`919s)b2+cfLja!D#AXV?&c}d0RO&f7xN~YKKYW{Lj{RhkQ!lv9)*5O zLs}g20$TfKaQE!=Jx*(Pl(wqY98r2m$p?_)(DW~59nAuW;8C; z0FIL1Lq;n!=Ij5hmNWF1rp z0>*#ilPFT-A5^rA-p?QO{gnP1nak+0(dUEnfyu9V9$eUJd*oJtv<%h<%=X@iEtk`j zrSb-*Q%+NIKO=edNjN}yAy%~oq4l^E;kH$pRPbfK`?wG%iEo5RC4da&n1=XO)kP8O zE!hjWjvl8RWps2n#e>^Yz^!m^Vr|fCUzzmI_;!)?)2?&Oiz0uDJHl+vVG{WbiCZ*^ zMgnRIjwqo-oeysI%#5%@7%Y!6O(FYNtQIO&*q|Gq_e?c2ZR8SX3D+%&l=9=XxRbDraaMIn55d!CokC~+j?`}T0?;PRtM#d_fcYqQth}I zrso5kRbPyrATqB%#wP(RNl39lIla^8v8#PD7>lM&;O{qMPAc8j)1W@fcMWHfdvhW< z38t$nNrs9^AbPjmZ_m?&aLi&bY?2HqytZbjpZTF?;Haxq|2Te zx)TT@jAqhj8Ou+WZj0`!%*c;S@*9o0redrMW;~dj;Zp3s%zdi4hDJL?)8WVJ1qrnO zdoENaF$xY;e)IX99(CzKY@UsVyxXsq78_bw7#(i*{C$x+c76##F5@sBa;!F zXmA7pZL?}=E3|Xr5Q%D*@09I_PMS=uYpJ%9vA zvW2 z)@!XZ*$}9B6?h}TmyjYWyDRBhGh}D>tE^d?y`(8&4z*43#t9y2J7!jSBB{k29Pd`o z(&dKulL2KRAf5FUY-{RqASa@}TV0Qf1b0Ng#5Nr4B`H+tBW0Ou41DTjMaUl?I%!Kf zM4@dtz#4?)2xxn~%a_`Zs<|OpGO`c{wZQ(;-#;lbUFfvEd&bw(GMC}lp zr4txe!DxY`#VPnUdo)>jY2FZmL$#;9h%lTBQ=4y%bKzWk2~CK8Lp-(UO5Kqd$bH0pz$#?TNFP+ccbI2GI?^(9z?+4}Gj)3>HC@!Wo zJfZ-$%V!f9z;5=w3j!$ZC>zuyKzHxvrVvBKw|s|E*gG!d?U8*4{i|PW1Q`bSM(U)O zMOB7G@0#Ux2hk=)@;gTR;`SoyYdl7Myyl@s#}CGW|RE9=HZeFC!RQC2=v55 zI4c|DZwBdi;$i-{^c;Rj+wni^&1JLP*M##}2HbO7_^H@CDU_*b(#cv7X~x_X^)@^R zmK0c0D99_EmSKexzfB1r(Sug;F#wSMD0YPU{5S?WAz(H8>ULc4oP`>$Pky#NSF?3*+!)DvYzfloC>B z^~Nh7hOPJ}&7_^z4ZVR~M~4BwfVBE&FLT(m(|D-?n}}J;BhyvK*LBFBVCS>0reIa; z3JICs#2$4+MY3gwkjG16B57pwUY334OEbEAZ%5jwt_?8J>&0MwV_fEh@ww%z28+C` zBry@Ji+u7i?H`0U33m8}Ptob7&x!uont;eQ{Pya*3t9{;#nEo3|i$ zmUDGCtzeiPAufv(;g83r8X{gn=b8qgwkKow1)`v3hCa|-r@wM!-Pd@xoDt933XO|p z)%z!vtIdR)Ih~;3IZI`wh^x*Sgd^%)W)8r0B5u(z>0C7B`g3y*q$JK~;{9qn@l}(D zaMjO%QhM_jVr;dmQCiU@0I(1oc zmKA8vk!lzZUmHjaMKNxC6|><);v0-r#TcHFGvo%#$Y<{inYGRRwp{x+q}K@+H=U3y zn7bsFj^DlNQ~vB^qE#!>l`JWrlHn`h|YRn2YIdUJ_>o)q@gCKwC_^@`$VO!@KXA{WXnF(~iebO6)>LayXseIWB89pb6%N7B%Fgu}V7+B*JZ z?Tc(94v|EEPjA_6bpW-$i~J>pi=8j`F0yOOPJ(p2Q?3uc4R62BbY@w#fs;{VqY!02!Vk0rWNBp!Bk-0 z+fk*$t2gCA0nvM2N&p5ey!WS7AOWZT=yrQSipYZdg>`1_sJVhet~27x@Zv*V1*0yn z97SqHssIPDXL1VtY3F8XVrITQuACd`je#go8!ap<)I@vYQq_iI#hhqSEmQ4QypN~Q zsKo%dFg(}#xHw=lbed%*1oZuBOtIb8D9yCl35XXPNFGp=W~l3p$3q#IWV`6l6fJ<~ zHRB`gl~C4Z1)vs&H{&t6mm((65`4(Sp8}~b-?deL__8N`H6YMZmJpeA3gaT@Kg%j2b!;sUR()#3oxw*QK3Q_U2mB z{5#Ny;1KzZ6wM@kA`|9(1l0e*jFXhOPd5ebX&XWq4lVyne$Yu_$C9p>A$ZQCr5Bd` z7nXgu{p~Sej^q$?w+^iM4XZCA_dHRGk%gUH{tI$`mIuqLEVPjfrw)kt{jvJBJm`Ne z>D?c+gw4FGfg}AKG{LJJ7$C(Vn>$Y#A_x?btAr< zHV>0ohmoH%5-`PTKhjDl+F0oNdxgmoFp<0KRQ)qj=d3eFc~gK6;e$Ngb)7OLk2_D= zhROs7S^Lp4R>I)u5TqIIMRwcC-$p58agRm&>Xe#;rk-4hmdV;Rf~DKJQ6XdUsu?2q zJFWiih&<0@z#todjZV{@6UBL6Ion&>EIw5u5+nNX_!E{1u5EcPwNa0-c0S2C@tn*@ zPM?LYp35Pe5Au!ceK%ohEn&D%+zBn|8I%Qx928>bAv-wF)9Y9(M~3*ou^er_l$_dt z+j=m>B*4$61(2Rx7%{uHkU^G4`(Cza)~sS{%vq@2k);#%ytU~xAB>`8(-<7Y&+wER z><$Kz_S0DB?#G;VMXj=79Z+^=eO<<2VhLKZky;tleqa!CS?|D*zl)Tcv!wC_0?h!% z+PS0TgEdxL53o1Gg`)$gY4uUqn?D=~k`2X;*@@<*Km%u0q#KK8%^qh-?CobobIU~N z(9gGcd*MXWTZAFBeZ!n{DA-RvR$cp$4*sJ`ifES~NmR3h9V`}DQ{}U)O;H|SsE_2I z=*Jl+i-Vg%Lx9aKA1hNmSSJu9KjAuom!#TUT4aPBzEqPA z1c~7>!|UETL#=GJ;S9!FORDzbEKB6Lybo?uQe2hxm&qB)HE(BJ;><00`0wd1B>UU! zj>>Zvh@BmMxQ+%38IlwSNaGJpnvh;D=F&83QmsX5FIN!;Tqa_# zZ6)&h4i6nW?D}SN@TKu8D8S**U0V&><0%dP@~2Xg%KAX`&g;mZZ38L_TfXT4KS030 zssffwj_Wb`ibEHit`c+j4JYG7wuvbjPAy%W7#si8NW+yC1Az!lzjSZVgwErzMIJwW z{e+4yRs_^x%Y9KO!KLAtX#`AagEC^Md78?+xUV6g=jtwitc~e!{tI`%*KS84&E#^Opm$g^|eJSu9Er)i9^53UEV zlOFt!?9Yse01p+C6|g6NPQeU~Z0H{Lv>B6q3Yj4OK=x$hfEe2a_n~NhA1%HRR=7aaI7{6i zv{g4;#^!|_Mfv3$QCpGPj1*JpV!rul_r9NBr=R844qYEv$q;xeD`!a`A!C&+r+6 z7qv)Ei+6zb74P^KrU9Awk6P`SpSbTjCwbEpv#EjuTH#M=O~f6v&upCdcf5NbHV&OK zBA&G0kjn=r!mObX3IO5iAoecL1OgSPJPf4ukb+GLyLWrMa=ydn*YOtqPP3-x2gG%M z190WNf#V8x$wj78A<|(KU|LFx)AI>&@0%W_X^!Z&oI^*cObVFaKt!jTEd8L*HbWJu z`)Ie}u{KWnZfcnhsNbkbEt{1lc}a!D^dT~u#-IkuT4%-i2^|l(2$h|Z7UNKv<7h~G z>;=!Og@K%Sg328yj(obPr46cHd*pBQp%w+|z`~6UDQ6;iwISq=v){c%n8UoWqul7> z{BZM&dbO7mE4zb~E452L8mhDS&0r1B`6p)~!c4E?A%OL`vwuIR|1GXT^=pkqGvo$4 z;gF~l_qkIYOTEkDm|H#Avj0+37dqV@RHX!bCw8|vdHTxZCbLg=3LE+23lP}wf$+mT z0P!$9MsXZtzcinmHQ?F4I%uP?)h7&TTABPPnK!b4W365pzr-E+m%85nbiaiwQ;-)h zy8!l&J5kJfw}+-m|lOwjvoUPg*;D|x1!s>^z%Ha{P#w7h5`AgeJ@4U(bi55 zZm#ea5ERIhbrjw;1rO!EU!ax9uTOn&)wq+$931rO+`vzDrO|CA9+pJH*UIYYxgsNp z9IXLh9=v0g8-MC?*2sL^e!5i^dC3vt|b(`=YMmuB)CEw&%XtT#lNt>b?08`UVnrf+B|IPTvP_ zEscSuP8c5C^j?5ymz@ z8B58a);dovE%x#B%X{xHzdO&PX_wenU)pq;srvlM_ zzv-zb=<1f^!JM0GZ8YvL-Zw2l!czfQTCaW*B?oM|e2+JawyYr=Hw)|vjEwYEbCK)& z%T$SJbVW%MMN1LtEuuBblo~S)_eI0@G(`Csht6{${3S<-;xQ8HA9!oB&)B)M%&Xoh zW`fVNg60BGgpmU2FerHDjK`Tu%-K}AJ`?U+SAU6P&XIdL2uOL$_} zS;-sAX0qm}%PiTb+Vuz7MWFrZ2qn!su8o32-Uur?`Wq(h#Mi=tOwYNo(KB0Fq3n@k zY^=tqWI?7m>_mLFF;dLrqaSJ&zl1pLbTk=XA2M|M^!Sw#q_&|8%X7%c&zA>HV6C_b z$r_(bksiRwD)zZE6(DAXXQVvPajO4|n`AO$-ue^c15F)jBXKoSQ&JjaE$4gqH72qrn7ifdZA*moe zE0yU>?|*(s&lRew^lyjcLGc!u+>+e~E>kZOnu6mST$uYk}Gp zeeo_1GZjHTmZ(yArG5g#ukJe6SkzRU7EByDq1nT|FmJK=D$h4pQmUHcTS`iw3#Q)? zvkk!EIaSW6NwRtW7voc7BKXGS(F>IGCTCi*pCR$l8K%rGQNCjUMjS25k9Tg>I=y6(1czy0R3&`!STuYEs9Z`|d0)Fdh3kZ5!R*@UIovr24EpqtIYwZBLL!+=}12$J*T+15{! zve39_TONWPDfH4Zo!sD^6l&nG&E!x(D)Z|%mLx3A|8h~@;bJwQzQ%4cIMu`pa4Oc@ zTTHCjXremf<+T9LS-I%a)edj9@T*;j6V2=p8T& z$Gh$Knl>qV@b`_XgmRQ}DdaF~wqJmyXI3}Y_#m$V${%A3kQ^kdcEo@TYqHTfTnSaPs` zDr+S^6#_@A5vHHA)+WbUX?h~+x(rYDfH}bQr<~8?EK);y-_Qh)3hR=9gp4LaDkxKDKHP97yS{S=c3tfPyoQuG@lkdian%OJ zH+2^uccg*&1hTr0FeKVrKtciCdD;MBKT{MB-adIK_I8%08opN!y1-q!B6I2Y6i19* z!;^f%v%lrg9yAIyx*P!+ySksQ>)cIgH#Rzj+kp~h#>-`)!lbH*DP#QZL%|l{`5-)= zfQXtjl&CeQBVUnr!mBW@oAjn_B($?SFaX#95+Kh54Z^AP{BrG66Pfb6ie+2?(pWcl zLd?Mjpc+i4dN?@fPp=$|z4kF1(GX1)*xZ#Bo$p2G9qTRXsIfsI)(YYJFr(e~w+B*9 zr!T;tX!u3BePybfxy6HJaWL|fYQam(B(Tk>M0twZjLMmVcZhCkxzAc@a|@D`ciE`n zSt6i5K9~9{21w&emGzQ!H~nX%_x5oNmsl#Ff~4M920>h=!3?zGV;+)69~GbeZb7hC zFo@$Kwan&5SwRgXcA`r$YPVT+_SVcP!+xfgQBPVuyV$w1!V>SI)K+GyktYxRn9=ou zIM4)Gp-+34Oa;{-Vw7=31a;c7nSY6)w;QF~t58wBrcJ5{l$;_5J&Rs?0 zfZ&!U94hkbV^@7~G0>|>wrHRJM@!EBVAW(PXOBvY8Hu_ImgX=j5(}6Vu@l`@UZRzi zz}S6y8#3&EE85i7vG0bAs#fMlULN&N%f-88G3szVv*~9|z)V=#K0&65wK$gIBYCJi{vdR$4nKXl>PV2Lu`0U!DnueC zbip1Lk6rGrx=y&O8?|9+RK@2Dja?%Xm_2?ZZIF6OpopWP3=3V5r>YdFSC4~M&R11O zT=-2yI*nP2FO(mCn5hyn+yDG%EZ5auxKZ@6 zK#ZSrrKYJC744lOn`|DNMkapf^np`o3}^xaF8g$;79X@cc}b4}X`k|LrqaI(0^Sv8 zUh7Io|Fvy-NiPC(V$&gR0{@IoK|Z-b&R&|fHS}lGzS7z0oA{=JVy|1==5(r)OF0Uq zyX&DWqBxyq*i};(3W`5tSmoiPp05J+u-;j1#8JC6l}!9>Nf$LIlQ4)r7F#&Ep;nzip1Z_k;BO(3_>MlzO^4|UttgH*_;tL|jk z@yZ-2>%~t?>xHF57b+dJ|9B!KWa`1J;|KMqSyN}Jlx~7qnl7HpppLnN8B_?=v>Fe~ z;#dH742*WgE~<79E(Ko0bKURjtqAOx{MF~EucwnPG@p3aNkJ)uK(|jj)iK(K?^JuQ zB)&-Yr5%!cI55kR{@gXO>QhT*NjsGIQmf%${I-xR7nSGy&c)9J*TgsWv*=CT3_Vib zPE>l=Uc=3eh$_ER)i3?f1=)%qO^*!4iZpSf1E!Tj8*9}{LR-;VSm0mHnf6`dIuOVy z$^#0xN_Nd(CWvb-U}xG{w48dfC~pQY4I~e5F{H!_!@QEB&W6+%%}5lIv?uvPtsP$&(KGO4DLRYg8= zt(|A10UdX|1HSsF`)0vfypxqD^MO3o>83%)(OVXre-Q^_vD*X}kP6sC6^$~I-l1z* zFSvnW3|;m|NmcY-KE{W1RhsY#4$FV8wI`pnN=_L<=P)?}Hmv?==48!AbY9+;<-kOG z5zljAg!kM`q;%X^{x#1b4(%U06Ks&9^0%JHcMC%bA;JZ!*jhT7dg#~FJZ_dl3(wud z%W<{m8G1CCfNibLZJd{(NR*SQShm40=B(w9DnpRRt`aPByM%V38i~M3_^XgUXP;!F z_}sSH0@y%6=3Wj>?{a`WA*a4ITsIPLs9us4%!`N9`@@=gFz~y>qAsm$mdMP@Ch1F% zg1=!IhKOW5WG+t6E@iEFD67lUm(+akmc>88NDPsvEc@=%K_@$%daHd?#~JDyXd#Vw z0^c0H<8i8^2xgKfi~5qz`6c<`Y3+Wgf1R*oEI0_Vx;Z%HcwbKFsTZGpv8X`U@+=a` zKIZnvbRXUk>%qga4RT3j_>!AT4idjfkSd$i;qbOY%|SfW$8+AB?%a}R9)_^5uvS4&CmLoJ}*7fRhkG(-|;(<+kvY6A6n7)=@>@TN{&2`v2TdA@u zqpNxNKHbbF`>Jn}QNtnm*@S7Cltg(=wOAjbFeRc(^lMbwpM7nPqY)SdX8XJ1tQ2m@ z3au0U&_YL@0o$n#PKbu|@P?{mTDnRPaVI%aR^FNQxYyS<0X?bopkpcBj#?mcL>^oB zAR&hl+4SaQ={H0a@b{db*6QcDmr9c!-$O#mNiG5hQZjSTGd`M}QM^h|abFv6ohCUM zbAzkK8Y$OmtL5IbW}pmGjP@AFkR#mkfZS=>z7R5cFUPJY@2ec7{%E<*Ol|jgoF&xq z;Krq`v}+UD$Tw1Ewm6O(6Fyrw#+kmjji%y4;`k6+C%I0o9Q zQx>v|IB%nov0$06b)c@r%zA~IE46(>Zu!*pt;_q`8H9aJ&wyQ566Es}X;CHJa5}-e zv>D#Y2wG$hle;EUuI@FHU1cJebfQPaYqdtN8IP3_%Cf1+3Iq3Huq!Ob|4Y&k4D>hA zhB)yc=5}6swi_1}ie|^^$b)p*J?vZ7o!cH+O?h%`9$W~Vu#9l@8 z)dIbXqe^^6$Au{ntFDb*Pg-xBmH}+1F~iL+)ilI*l`c#VsmRepfD`s$ZD4tm&M>i5PaJSk_ zW4h!|rf51f?{>my>%QfLvTrqDA{|u|ZP!g&V^^wA#R5w+ngW%yK?jKsb+hd>-Z=WN z&YdTnp#gx%No^dt2VgR_)=UDe4Y9Z}#aRg0MHo4wy1cIXHQHjIcOgzM|pw9^R}BMWlw2AAc_1OAy;u zx@_6>y6vm^!kbG&@{;Z0(r*vBE#XC)kqC?e52?4Ir<<+)U;X zf~<=eGwn+JC1Hr^&;{LBHh;F?aE3JAL%nbce7-A&lz9TBn>|dDu&Z2LQu_k!M`gZu z|42BvFlX{ClN6c9zktv%!$wwhJz4>YOKSGf>PU~8)Ld~~C@5iKLO!OTM=1(M=55w{ z=kmtSSi3U37=$7-@wtNzU?&m`Ph=u?}#*601o_;|5w*tO^KeO})ZS;vxf3SfitYEc7_d8?At zLz_2E`1Ow3-{g3gg8QoE-=9qLxG_R)tBe7z;KeSVLE7h++sFM{5?Tsn@{{W*D{(PJ zY#Jh(>tChQ) zC3&A38 zQO3NEEmySR8zKrHSg_LcDAzKyAYT=6$b8jUZA6M^i>}bFb)6aw#O;$Gb7O*igr}HN zzQIY3^@a_)+=RyEvIE>BO|YGJEIz2AR+T7Hfkz;#{49{$>@q$ylbutf#R$)lr;k+U zk0da;Ma6&x?ACcwn5<)m~JZts3#@No5uva;W3c!D} zXVFq`fc&? zhECPJgN-fvAQqFB3z`rD`WO!$4P-)9Y;IgO@IKhyFDCWr8+>Fwn6A41a=iKWVQ1kh zQE`LNH~Q`k(psKR$sp5jB+9yl?o<$%%kBwM8pyzdmPgi)5qW8?~PTcNAPmEzW;oYY0|NbhZe69gz|5;iRZwqmL!L2-W7ddW642(xK?|-?&S@Er%W4)#4w?m*h80# zXrD70ZJHL>F*#>OEp5Py19`1knL<0mWt^587HR1^m0Y*hFV2EHtXa={Ph{auu#@Nx z>qe$O%fhYzPMO*KTP^JoD_0G^p$bM6qN}Bt%O;2NL#qXO_}837C!!JRg20gfvAb^S zMmr3p(O01tt@e1RoPr>&6+@zBBVV=(jkpP-k!%!Nr(Cb@HZ#l}Rta&XY3>$*21G0{ z#(0Qn8T{lGQX96si$JZwo5gr8s06{x2(5n&w-k*_Z`-0EBshBYBU5e(o7E^AUTZsH zj?wa5{DUc1U_P;Yy*kSd#NR*VRFL%d4gtA*n6|EtR{Xc2mPAEu4Qz3zqgqt!JdK*O zd6L03B?n7YOsSZ~f^XBDl?+CoZ`0c$(Q2tB9VWQ;K#Yq(t|S&2Er;Y5fyd#Xck@CY zj-iJ-WvL*3P{3+Y&{oTsS5Jl#38r&SK-&j0Niv&Tg%ULyn2ox{V$D&@cPr%wu+3X; z@EjjufLGUQ5PBqIYm?aM@SGmtUXJNNQ3|iatc4bDz%^LXOp?~5O+FaMGJAvXKsjYFV7nl#p10)tS z-T-FWtq$>HQ1w`RD1;7Oc7BX&lWdijlbA)L#jX?y7q3{-=6!Vx-H>$7N@8NeAdi;X zcrl_4d=TgWWu84pYmY@oQMw(K4GOVYy@zwRJPL=~a(pzm$LKrZIzQ!*etcwbs;2VbO4{ ztoj6gNMGWb?p)vVat~su{vkOOu~ePa0;|y;5!1ta-ijOdK9QC=XWq#>*Y6Fi&Ep{x z#=~WLlQO=#KaI>UNIqQJ*3-3qO!q7~BC`xeQJ|bjDLxl~R+JN}*G6cFN(e(B$?9F8 z*fL&~&9>HTo}<2FQrL7Y>UHoPUbHbZ-^M%F#j*h~zFYsXTufG!%Lee-X+=(;;Tc_T z)Bw*iH0f*1^1gPsaXo0J7xcH%!JMW-+Tp6r(L^P5miU9T^~+H@GRCacA)awfsvbA{ zEV2=o%%3m(P>;$*fLI4vsX8rSEiH4=^rlp~{eBMmmz?o%ci~^Gu-$SE;hXDgAZNN` zy7WC@R$JobrLV)8tvic^LR#Wl#i|^BG`yA~S-X%}J$<9aQ(pc!F$&H#r@G93iYK2X z3%ca`6`{J7%%*5aO~xeKDY+6p!zrxArVYYqYeh7wa+jRziIoDvGYo3998K^wL{j484I@Pi2$LmQrp{^EHtX5D^$?2%9h!)e#H9twa;o z;9CoF3V7+z2M7#Se9h(_I_&aa;jH7rm{8W?mOO>(b8P_xCroX1mENa0-^{;mAE+3-l-mDTGP#_^p?BkOV@Afle2K?o3-B7A8c<6q4WCC~!|GA_a-ss7BRy;$MM^%gF)(VlI zDd;1iaLfBK0ffVXJb*kP-1?S_k7>=&UA?w62^vk9oC~^6Imyy9$^bs7zO=~$Eh!`< zA(2M7==Z~z3V&c{iss*NA}o3R!s|)-x0~6oSRFP)p;TRV0_&%kx&2CR_s+>21YjkZ zWjgE_r)JDSZvM>tgR9D;_4S+;WR?K;BD-#snyq!mZ_G409V&yVS5( zw|AWm<})%Mc=-~qw$%~o&!Ct%*FS^XX0!w+QX8xCIagMzbjNDBEh!^X65Y=8A`YIK z=3dvUSLyZT)}PrQW{`2cp)`A+ikJ8~7om1U)u1E2_yYKX(!|WZPeUl$1(Q!jP-fb= z{@pU&NtVV`%9GC#vkZeFBzMirVI#jr73L-d8TCq%m8Q`8?5Ssp33!9g)D$4^2DS;* z?jAS|juimmw|a~TgWc4M+Dq9uFOKH`huXYS&*OzEo_cHNuIsII;!n#?8>2yF`7IOMrRxR9E`!ymp8zx!Wrh=f%TKMDnX z*~X8zZ>h@xK(?4DV~bK6rbf#X71sIDXt-3KS5D0O2RP2_9XoxiR)6DnRu(oU_w&IB zSf{OD6Rci|8%U2>ovMFEvLC6n(q+;;H$CU>DOy+IC_PT`dPTn=V`J@IkpDS&Lu+7i zzz3%Z$E24X(2n|c4iFOzwFa;>WCN87h1xyOLt5YZF@lu6Y0Ui3H?cHtO`lsGH(tLY zN|&7UeH|JFeJRXU92(De%k;>(6riBj&R>#`W{(IN8Tpem)kWvJF}X1Pj^M!9{w>d% zgG8lmB;`5B5a0}U{bUAip@T3^ZSf&1dWnJQNfNTK{OKPBvOF(Lh;6iZ+ zCG*jtNC)tebJbn78!RK?n`Ef`sjB|99+iD|!VEq_mgqy~CkxE7yp{E|SOJD`@g8G` z5)fYj|I`{!m3I2W1}{I3NWUGdd?)dPKbVAxmVYs0?mw%KNTBohcT*6KgFb6F!(QNLmLy0ZW%8Wgo0l#hObmb@?i+gY zhfjc;hDt9y_EWB3E6VP>Pg!Mc(80uAUNu;=>`Y!CxyGCGPWAs>Qe>g5TSdqz;Vw)4 z8}ZUx&D3QwoLv<{bKl(qk{}k>{?s>+AbM7K9~MBm)%N9KDk8|zqcMyGTll1~jnOcK zH9{8x58$$2CAM7XgcN%ZpV(5xv9XOA!U?}W)2G6Q$6ySn$m{ zb#WuAIZ0H*)>T;lg?nxswJ=)CLt!dkUt+qX*_0{72PAgqNjIsBQV4bMzt94(Q}`IY)?zh!(B78#}ol zRv?E6t(RKcC=BEoid&q4_VJA~Xsz=>VQJM=mr<$OI!7?*71ao@ZGze7zJwIKqMgRo zO8Q2-lkY*3rn<5+n6`uDEp+R-o<>pQn>C8czWHlh?>3EDyFevG(YgTwlNb5q8s4oOWZ zL|X;cS5VnkR4MgRCg)INl}pXb5sBS~cy()eFO{|a_M-+7w4c3Z~oM-O%*l{ zJn{LvpfJvW#aHco@`T9%algz-IH6%Enq+=R2IzdKWZ!pp<7DWjpT&C^6uXGfTKlup z>XrmRxn;o5G!!Z9@{Tu4X2AW)?ARb|L!=t%kZGkzC5^_EPkS8 zjf{gdDg$}1p;CkUh)rE=c#`+6Bq+LnF{+>5v4K6EtM8o&0!zg%08^^}wQlcsXu7>= z`$$l&`h;@k58h&aTm6R&gNxTYcSJ<-EGvU5$x%;oMl=@V;_Xq~s>1QDjY(fLk31huz|Yje4(DE}nA5qG zQGW<-{i7u_q>lj5DA%C&2wdc>hdfoYobHr$u`rN>1>`~pi(G4H$gs7$o(f2O?1rjs zD{p%W2JgXnd?1IlQi+0-vN6lyt?Jc{I)5Mie0qS;A=G68;V|t{i?%{@7hCZG996f_ zq|^kRz&QFvOMWJuF^aczt3Or$tUxH8=~4^JUdllWlI57`O)A5bA@{|bRrirvtZkNF zZNmxFS%l(QPo6;1(YgnPZZA@J^kJqBX?EE$@j|-}R&pT?Xf+YRUW}3y{_&>tzwUzu z7I-HHX8R^f2iBU3bc7WAgm8s7Ass_;VbWp9`QB66dR+{{5~yhE+}@?)aT-@ryh=VG zQqaIggwV)?y={L}B9Q6Z_=Xkm7~JcDN+*M|I4p=WDr>%1dapUc8wB7|)IFvlO!eqe z2T;aS$kNYKHge%lt4vm7xNKjKItZ3S(i&uql717>QWHi)&*55JCuf63~fz2zjABQ}Xnj#tO6t*R01(>q;JhN!w!`e4LAoO9;j)y_ ztul-k1MI*2cFK`wqq^1M5}X|ClA-H>uM|x>l7UC(2C8z%rQ^5wQhvOdJ%uHb{r(l2 zVW}3S_h1~k<@hw?Q)J7v4)K#T8hbIy`AVIAd`4HgXK%NA)ql$8e9UgM%~A>W48^E0 zQ3M^<7mL;r>#!=VmQlERQtVz$ut?$O)(8~!#<^=(icpll$cmX!%nVpbD&4x+@e7;)yKQaS zvl}OF{ZNIe40x@7H+)1)PdqE1FOr=0bz1_BHs8tk$Qqxv>`i0iJ^Fb9b!nA9Tm}ILj3#Q>=L& zh3J&VTNy8KX+*o1hJIgP{{t~r$@!s7B%A}4LpHWjvG0|~Rp*E#OTHKH+TB7{fHY7L z)_b9X(!B3e>?oCie=bQhr#pbV&*W;i7N!gYK?SN>X^99qOjVQ9%9xHRW32tVX@Mjt ziy3_~$0`?_p%?L>a0e8*3<@)bpObe4xPV?V30gHich}v#@-=tGa~o2YCJzc_Mo;L3 z^qwpr>s)R_js`%C0`rDlGBUSGo8(9~MwID_f&XayU7&Gd8~ zljG%vz$=y8!kqQ-q_MqM+d@2PUi_m7SxFk zrGl^zS?<)maWs$ARo%N^P~zqWwjYe9vDw3vYo{PTlme8@vNV-5iJ8R9Esz4sdvUr5 zIEFMiJe-@c7WI`|*asG7H)ey^?y$UFei%KN-XlDj2 zX9|@xP(YBAx5R*J{!pLu{GkX5_$$4H7n*f+&f(v{F33&F6^O~pJ6+~LOYi9FQbqkHH&(w=qVf(!LB!@7K|`)=1%Z7{Ch^i-ldOcJ8M zxcX5g1*$QIjN)d<45K`s39M{G`WyQmE1-c%zOU1}-$f?*v^i_eU3ucgV7oBW7IN8L3jJBLs@h$=_huL_ z2vYZB?zW%aQZoL|{yci74VmOzZv#j(;5kT(bI?g1Lv`4YP6v{wDG76|nJ2#}1Rgc- zPP4FwTD2woVnO7r6?h5&-w=c$+e$A(Ybw2y`72RfHnrXj_XB$KGx5{x!UhCK1<|XDN`tVPaMLLS z#502_;y|g}F(AN2%K22<^8Kc-4prdPb;--B+Ri@xbG7X@Pke?<*QQ_Qy9{Db)Ek=m zHMz#0rMe^fv6A?c-jQ<6n5fj$|6Ph1Bgx8Hlbl%x5D3sC^;h#1XD*&?_65{F4|FA) z6-rZXa?)r2BYqi-(LK_P^a^r!a+5oyi987nzcvZeW$rbs&iiQNj-%{@m*BB2|mn1P!WEH#|qF zH|te{veqv~Q9ad)pS@$FQRXY;EB0;UmsnDfDL}2GC$(P#i@k^XZn0njH~hHZ8<0aX z%`%Wa;7EDzDf;WdFT*^ef(J8>OWyO(fBQq24=EhxL7y7qa+)EW!L##BjSg+8XrVT~ zGHz7CL5j@Gv)(oDZE_n2irO7&%QR@kQhTkiI2374Gp%dmieH-r-Ca1Bh^foO@8S$F zN|ll(S+8zPEZgGB5e1SgE-y97-_RqU_B0#R@be+-KiC|~(?z>V;W2y<*v^B&@@!|D zYAZ;ri3;pSVj6?&>0er^Cm=^P0B^N(Lcu8#ah->}?F|;`@bKJec_X2rY#=x78X9y_ zQuo|I`;w?sPmG*cCu*sKwt{?6{qUGW@-Z`CIRE6SvpzFtgmoV9eWPUF+`~9J_9Q%I zx_3Mfnd9o(0)q#07@?TiImGUKxE-r)8Whh>)a_C9L-2mm6NNf`K;&tz%Z=dWoLuXZ z=%)8wcN%05+1FWge)Q}k0a=zWzO~g$C!zJ)NtNmm4sp@6>Cs+5U-7Y$H(qTvjsBkw z%*IqK?O1%NbQ$QbBKq961$c(EHIJUoy0Ld6!2RF1gelv)!#R|F!SMFvm7HJdPuhaK zNDpas=uI8&S!LJ1>8^9Xnzj2neYS(|b8H@w_jfYW^{*gNP_}OWWwoIIk0!W6E9QjX z+kb%*0k-sm@=E8#do%_iuFnx7S*wv@x{SscKhN4`_DT(uQJdh9qRd+eq1rd5x8Y5l z8i0YmK=UQ|^}9f_{TuIlC)Pot<6U=6R?T|VnQCEHyB=9dlXVF_5aMqhg58_KZ?gA- z49tmDi2ucHD)3#_rW&tSX^LLiLzTArG<>(wG?O$|LcL|jxc+1bqvSwHL%dZcjeWgS z$w$;G^ZS_H_02GmFPCOl1fz5QsI=9kh})CE%4t&x^k~beO zEhEXry!~|Y139xjTC*SBBEUk4SICEZk=nZ?1%C6RHPWl4kA}Ot^JM9`CzS*UlllIm z#k+Tez%7;*#sWYd8T%J!!krU3EB{VZ#q@Ujx(Q54!La$64O=xG2f+Xnv_A%+JO?y^5Ei@HVCAJir%4j$)hJ12bty2 z%A*#lLJJ?m0_Nau%ov&M6_3;ItW2TK49=xh?BU<S<8 z(C%o>3lA2+mCW$@{C*pLeQyvf@AqV#OMDs*bF( z?x41KNBS^!WqsCxH26*1{Lh7zv)?>YK`xzI##rjzX_GOl8va_n3biE-$899-Sbqvf zz26LONy=&FvXIWHsA`;j%K-(3Tm~kKf&4_87Sk;6bCtq71}F(Vxz%@({gPTRnVa5* z#~mfZk zFTEbN)!Evr)B!RVBXWG^?#yTQdwH6!e0@mHI=DvX2Xn+=JhVr4R}6q8SY5Wl7#(lbgu}ngaY*J6pa~9KI64K$@S4kXP2);s_G{E?BU$?Nrf0tP*$j*u$BgyVboxx{t+Dp5% zZJ0FkbYQ)!AIyTaOmA=2aQWY|i}E+OtIZp~GV-%%sm}8vEA{3YYb-@+@&dx;R+@qZ zj@GoAo@NNrOaM@g}cHnnv=RG}XP`&09xM*OBa&+F(5 z)%l;$llu1dh>7@+rb%`hwPrju3jd{wwkuwEGlEo2Vot``QFyY@#P4$$N+;>glLx`Q zy}z7fKeNvs6wFbX5E?w!zPoIWCOSGBMd)Q&<26~w4a8lyUXr=hmMzq+XiBpp<@&&3 zY&i`3PXCUCxtUR?5<(2$Dx_r|a$TCN6Wjhjmo)GL$V+y@9Wzk6a`47xc_7=yGh>x7 z$AY~wr*~}{-JFI-EDc$5B>%JDADgXr6NIf0G!j*O8zNf`bd@@OeEwm9Sua;I><)}3 zh;m!y<;cQ=R0KLw$xV{Ri>x9^Z^@Jw-E~K0|;JAA0CKG4d?o3%hRTjA1}SrpektEP%9QqIHsG_ z+f#gFeknnm-lqWOw?Yno(f)&37_Z={G!$u)x)-K96Q;YCN2TrZw)#VgR_kNjme)!) zXYI~iGRsQQ_iqgFrREBBwQ+~226Fns1tiC^+~ms(k*S(3{@jwsg(ZvT3w{${glzNN2ifwSR(T^NIF~J@>LAcz zs+GoSYh4>U3O2^Ervh~un#Z}?`E2ie#UZm>@>_sy2fP+1=F7Y!kQht z)dvsbibGS>lva}H-JdortSybG;z2Oc+rY9BIzVbCmKLC*dj=$9h%(THC?TVf6^WEJh`&A^E;w!X#+_Ae*4_j+XFt)@iWfTU8jsyNs&#iHd z0*?sou(e1UR)N5c$tykA!?!zw0FWU4F z1qi8C2|Tpy8Ye0_4wGvbt-%jwUKC=1uvcE7JR|krWdhnJrz|@TTC??~S5lEB;TQ-A zwvaFUsUzNT{V$oRYAEuLp*zK;a?;L8SqCR*uSP?7gE$aj8(?}4{00)OMCo0i`lnao zh2+*zb&bpy$y#Z$^tG+8rYWdD<*kt6mM#!kK~L-w08P+u9flYHT|4z5-DP{^vWgvA z=ncyTuv5Qp52cZ-baJ0c@2Da9vQA`Cg#L|Z{iPbWhwOxVvAg7;#3X}r2V|Ow+Zc{d zmsn#NUT)BfY`jQL%ki+nD%_^GAg8GJnzUlC=D~7UD!8AgwaO-*4~5mRGJr9kL&iY+Q2PA_H^c6C((!`jfX>G3TSXqq() zrL`^}?waVb0q@+wM6F6)3!mf{2y;?{FgtGAL!qd~A8)+XU45)>we~o1i9JR>TR8ei z>7AO9Va`-^QKJ~JO`0{yjTgKA(!L5?1WQV$% z($!y@{sqF++H|XiQB)JF)7h#WxiJkjwOaMt`es+Z2!2a_n|<>omopShZ`l%jhtC8( zK)L77&P^J>xN{TuH0^<1bIj$dhpggByu2JFM|gAc7g;~YT=BA#7AVyGB*~ukBy1ZE z**+XE5-wN{^O&MYOVbM5->z+x^Dc8D^fv{%WX8VYB3!13S#GtF7hqi3NRC&@7by<9 zzNECrbvB&IhZP5Z@ zuRlS;?Qvxll-_ftbqg^VIr)W=rL)KK$xXFBW+X(z$GRGC+_571he!^6aVDA2$+7cM zPWyeJr_U1&n}U%;nEINtXPfU4)VVr|%%rMyz<|7W!W3#;nZv-%*Oyc&fy@62ljsU$ zIPd11@s=aMPcX&ZSVCu|O(}Qyb-BYEU5z?V)=mKvb|r@8?ymQyXb5IG)I$17`ibbZ zO=&i;No5VyLD=FaJ+)bRv}BU7o#wrjV8P&DladLhMD>!_1TkxO@3r&I!9Ghre8gsv zr3kj$^eSAhgq=QpB?;Q**o?P11E@Cy|3jk0+FNTaM_Y&(&;`tQn8k6^8F!HldvJd= z>k#m!jK zuyZupn3s3lqJ!5+FHmTKRJQE0ZprmAMeug%K8q1`WjX8hn~&Ob^{L{n&NdU=IF+v| zr;WURi;>*afY#sz6*qEX_Ufa#VN)$S$}y82HeDZ@a}JdfC(`+>GoK)k8h!x%FLb$D z%&YWG+9XgH{^iM&&FNt%tflS2YTW+O-K>;%?3^Qt25ezc&4)Z{#NQp%@XLE+3SoQB zP`yML9)yTkeT|8O|XFytoh!ZLuF01v!8q|Xdr$AeWKy*(H#Vf~IdfV6|b7laiL zPfIyf(`09g=ep`+CmFIA_T3{P#>c*a2J(>=?Rlin&mzJH6^S3Wqd2@~SJ3)2@0T0AqE0()of zLH+BDH5SRHlNHCoIKHHoqk<{cVR}JMP?cPQ#;}bu)20@Eyik=i*Z6IMiTxCx!gmYV zxpCBz4qB;;V0>I&43$2ZO^R2l*4W(bF2Xr5h9%<-qz#dF3(&;_D!SH_3OWF#w!XCI zzS}ooc>RnIR{q7C zlrhVrZ5jGdM13d+e-HpnWuPwTNz2gZnzi^`!=XN>zhtZa((EjXPM_B2oc4X`DzUrM zbhFBa;aDZXioItBQ^2pI$jLmk1owDgA7$Ip^n}0fw1t18IgBodY^+`NE;^Y`DWh(G zk;+c9GIrad{`X*5CKxX$?7j4sM7pWJcmkAge9*(Bb$S?Yvty7(u!AaC30dZT9K1kG zBB|S^X`e5{I_wS_yZrsonC#9?;%vt6^wjhO)-o@W;(?nJn2FzPBs}33({pgaKbi^hz$oX_TA6@{N6Y|%*w@) z$itvNwvd){jtN>00r!ww1Qgv1o06qCX=T|6dcm`6_B1!mNv&eAIv=HivP|USZ>O*8 zK*limRK@zR^`y(=4*Mbx6D!K91JwTUUfs6TV?l zw4JSs@4mV)hzNLyGoe@;cGZvt^|_gwGmNi z;W?QS0RcACBEPSi>U^jS=)X;Vfrx$Y%=veAqA@p8Z$zPSGfF>}!2^+?KfMb~X$&fU z>P&7!hYupEWu~r@`bpmj(W}vH>Jw=}IJ{%syOglrm4IE<;8SyQOLvyH!>Y`bsapw2 zjHXI*ozf~QZ++AK3JkxvuF2qS8<~!hGe5Zy$+?3zBsIdc!mwKh(5!gl$+krziV{Fv zaK#_MN9h7y0Wzzqhv!Rwjs!thxzi_0ku~}D^i}1C3rI%*z?c?z_T#h&?wVKE` zz-kiNYVX}lh*}~sf>6z!#%Pj{Wl*Qvwmz}H6GO4gpcoG4Sr~1^%{W1Nyz&N71l51Pf0wNq%&%?Ir8;L`& zu~A@ZUKZ6v3I!`feU>HEOhpuY%!*d;EzP}6X0avlvS+sU>hO(eO*OUZ$PLZ$ivwOW zy#w-iGVe6Cc9t4d+AP>#qG(zg8)Z(?rnOylbF7G&>Prjk2a;%})3X5NS3>q7XiPEL zV~8i`=|>9RJRx(0YO4muogwRR&=f|wD(jYEJ(ww#!ku)65H`<~76dg+3{L~X0`cP> z?XA2@SQW3&y1i+^P%`zFpFLD-@^};yOVGw6Lei=3v}0gA%{k5U0NmR`c!Z$69w|?wyu$VCzgC~)Ha*?rD{XY!s>|f znih208qm=_=eN92Mfs?ik6m*qIzj*0tu%9UCNR@w> z3plAdV#eZDBS9lK*6i>5D!-HSd4C_wHYd2Aq1hW{5*tUPq}%n*c?i>Eg#EL|$NPFp z5%+3)xwgzO_wxM^^lDj8jog95g+AHgc50n}Ys$VlX-D!<4fILo%J_J{KZe$^(5K<8 z5+G;cgQA?Sh@DbcONWWL5Y1&F8x|aOX18<~I(FOF04d#~XIfSP2yG0=f?xyfLjs>x zdJwRXCHWBI0Wv}C2#?aHZIedfd4a898k~tKNp+A!IO=kb%ewoqc&;x$} zYUP$640{})0lL%W=V4VR#rETPlwN^;^o}TjSy@Q6M@x;3N`!m-TqJSP5wMB<0PL+1 zY}e&XfUBms9yp)V4`p9qG%@HhQ*Hp&RTGCV-|GCfO-UQan3Pn4@}-9s3mIh%R&6>Q0Mw2W#Gd2 z@;03b&4g^>64>0Rt$CD_1v#gQQBq(@-)50abHcD?8ewrpw@# zW>w|IJ=K`juU?x4NjEg~(&T$0-!m_pd?a4-DCcnf3TuJhxj4nUi3WHKbOPW0HG5IR z*w+9?yK+mOkWeW?0g%j%e1E-k^Hp9}ohKo`xA8f3BYX??j#Pcvl6KLF!+89alxk+7 zZoDXGKA@~eD1e8qXrqJH3b+11dn1y^GGzDG(>E{Ho0+`C;tQV;5~-QAN!f zlA1b+ibq%%X(hfix@ogyqD4M-1J>Swk|J`#NW-e?<7)tY; zT|YSa#pG2%ZMx$IXf58=N&)kW>(gitFt#!opBdO!;s(0YZ#sxeYOJ@KleO)*wk$K4 zi6Wbsy7gp8CTuYa%GXMYK6CNp_6J=Ox{Dt}QMR9{a)0;mG^*$XMwlP3Jb9@ltAX$+Ixv5#XGil{;2YpJw_ z0t9chgewTbaw~RCvvh3mw03QqLuw19SfgNc^B5obq$cp%riFW}D@Fn@huHp}34geq z$nN6~!41ECw|dXH0orj|#N-)Iejn1E+IB@)ztO!*0A{STS<9+kQiKNHS4fr`MI(uf z>yCM8OR9zU6LHfMBmbVEo7o%X$$MhnpQ#a7qgoi?k;T8Pb8x7SDu`5LJ}lnN8{uLa zh21|cy;d)kF^&Y?eEf_F8&$T!c=zz3GIm(+aPU}2ojw!jT1fOv{PbzE;$ymZ#>|

    hoF+%D50Y;GFgQ-fpL~4QV=ry$SNUc)Y9pY9&Hx1U6|(CgXIvJ@S?AFNk;2 z6V}#tgsJ7T^vSno0Ijw1k%g}Ogecn-ik_bfjnN_t>HLXGcb5A#RN?XlIU&O*<=|^0 zjZY(u5S`WP?tDc^6(tAj|$M%=8e%STSR4s;JVtIdD9`&TlbJmIt6Y zV=l-pi;W-VN!YbSTBWG>MNL2fGeQzqW?OJ`x+o5>XyskqHuWy#EYPkODPPJ)Hl{I> z+c{*K+S(O|t)U_N>gTR!RgO5+lPpE43tTn@cvYwiEOjZszQIk7|zn_v}fw;E41aWAC z2l}W1-A$!|(#T(X_G&SE!zs(;1|u&ZR?H@Y>2mS)_@e+r5IWOCBC5XXlX9l@^{Ljj zoAJQlPwrE_4=)$xV(h+Uv{(ez8U;!dl52ARyb-r;B z+FgADMSyF|DxtF_{Iv${S&chINb`;F4P-E5isMCiz4Hq6vlZRPoJ&YYbX$3~$lwQs zAQKxhka9J$Bj-ssGTB^NxQWS-LF;CCq0{?caIlTgYzn59n8U{@I4tF}dsRa(z$?bM z_Su+yO_NDO0+rJ^1$FfvM2fd8nAnU4syWL_0NV}+DR|&vw(9HT+svt08c|s($puE+ z^P0X{e{*ovpp4i7;Vk=&Q8YWtYeC+Uc4zB33Gz4r7&N zOG6RZlR=gn=(@N+`ohkwYxYT$UGib*J+3*7!5g9`_x9OpyS*sd_w6Me zYqsim7YPV8SW)w~DqM9$?4d=2Z{d!QKCKd~b!3T!(iaoHn{xbIIg1%IYz^WURoaR` z$V2fHC_HxTPfR9EQ@J9ERe}7U3a)IhR}-atRy66_rnvu(q(GwS?-y|w9*=Lh$uD4(Z%$2)WG5gtvv*d+`v+@ln$ zH^J5R*?^Jde9N$}?j-QkLuDQ2ZVqE8r3k`}CM)a;(Hy7MCy=6D4i(QAMWdp~v8{Tk z_w?PSez)YtlfHcissNZYbj$Xw>t|m;Xau}w0JtK;?rZwJWxGuffZiZ$InvZRBah$d zsoTyx0oF*SF2L{N6QA?_m=v8kPjW}Gq9B7{)QEMpD+1VT+#sA7?xl3dWayA^ExX{( zS_6?m>#N4_rT+aiHLPsgq1I#gB}C8&`bJj<)~5C}1i!zf`CP9R&%7c1=tB;+MFEB0 z0On>zW|#Cbhhg@@vGHbU>nU`hJH8~FQ5n0=pn$iv9|#t(;q@YumXsNAALU*I2lP_G z?Po<-l4(cFE4-vD%vakel`v(EH7ROm+re4lO!q|>-T`@(XX9J>)+dt~IA zR|FP)^}PN$JKv4|MX$?^PlkVKmYNUPad&& z%7~582jLtX!qDc1Lu6y7DN|kfPH2FywKK0Qm`KULhMq_-yPpT})ys>P(j_sNo+=0n zlN@x7(;Gb)3fIL!i?se{hOn4c)QJtnqK5x0c7uX)4(*cpPG7HpVgB^k*PM{TyaKD^zMp`~pK6O3Wns*PrDJJw5-q%gC z7vfJ53;%NHEJv^|@HJoR##yPgz$f#nH+}(xC0PleJ6ncu(~V=7f$++=D7GJ@<{7Bd zCu_kIK89GKNAVoXLYR0=n~wb9-LA=1?KE^CZ(*WYNS~~XsNL*ZvNykzXQq<_*K8(y zt#x6mA4|iz^dUGgSY5d%s$}8?lwVx@OYdjPMn5A))Yy+K`g>EwHIGy=NL?nh%qOyP zzxYJFBh7b&Do%ev>Rtf?Gi&tTMwE<(Vf*70K+l&J)Xh?PwVeh5uU-p6Ywx&>%^{K= zhewbdiXW@~mj3gy6DkCKN_@r`&*I)cr&q`Q;?La>oBHx03g#SQXybK&Nl(R?8{E>m zlzUMh@_pZ&QhZTKU0vKqUhjNh4&(x$@YR8ZElRs5Rvq7t>LFWqY&pPC-p zPF5SUXbNr{SVk-h;$XpRMRMLsUiy0VUV||6+zjQOl_%0;{+?5ZBbamc#c0S(LJ!iQ zx&+F!N&si>GMxyw$Q~onRA!7$>$*BlYFH7)?&*k+D5Ddi?>mD?hdr#+T?G83?)xrt zze`?sF!sJCoC#zv$ zF+^}uoc+^H-02RvO!NNYz+mF*>>kK2TMt_{KFnCb9dE4bpK1p-Na39)6Oh4GDpRQ} zLtvhh_Qx%64ytv|g zyP*Nt7}D6^1DCCejtz0sC>jNjQoX(Orj}*fkmphHESbNg=-wQw3tH{?vIR!Yjtkz) zNx0($*wJBKWo`&b4f~E?*5*0baFD~&@t%)zjwxZNDeR~by4U+Wd)bcE;sX^+gFUzd zvo)7Zb_zA!;LLl{MiEy{T%S~NkSu58?N*vB$$bCmFKm42{2#hP^2iKkwLL`Eue3(0 z^Vs}TJpdQrj8b_K*`C?wi@!fg`1nD>YoW0-ocE{=)7?g(#wM76r6VOVZ?%9K%@Kl$ zajn(LR;P?12jRndqHi*ld`o8RWc8o9Qco74;_xq{QzcBFc2pb0(1mM$DAhTIn@?by z0yN2`vYP@ZHFmeYfODnaULlQZbv5hnMg~LfMVyT-MG0KrNDPQ1yaYzc6B+eJ_OaNP zell&Qb9#w`CxIlJ(jAERzAymrU6c}v%2cA; z{Jkvs#)_b-gpwkOqnAG<@Z4An%rO}T6(Kv+##N9$-CiuPl0jV^#Q16Y$7n(#7}W|h z(q`S(tX4D(tE7{f$sCRG)2K<&012NfnQaeUJ#e)Ef-2oebe@y-Ju^+rjOejT$BRR6 zeSrk4*^u7Q@+h1wQV6E)I(WCVP8L6?T>Z9(l)Ox(n3smB!S=y9GH7bR-g*(bc;9$$ zB9Ml6ypY{g5Vw?0az;$6+K1XRP$u%j%v2l!K!7WSi$y}xEm;*9`{vf`fhEO|~W6t4}DlnbrC;?7L97@b*|3gw>6 zRbtWUvU;GEawY|&+Nn`OpOoEn`P0_X!fl0IHgXEz2q#UU zVt4X$lJH<@Esd;3vptF?v~|t4*poDY-igNqi2Jm+TTaq$^WZT3NyoOGb*^K^gy9Ow z%4?X@;%mtR?6RS6>v5^_aqvp_=-`i$&}DW8Hzw?DH&R?93u81g)l2qxdX}1UKgDn` z=h}Z?orS>ea+=e+&t(c%hXrLJm*2Lw;S2Zka501(V`toVn)d3pV~QkmJkDRzNvnoP z%$t655dersMcN#X%d@~a%WAs9h2NjT7p$k?(lSp=1LnJ>L!UxC^A}czsw3F)sS2dc zF$GGlHYW2~f5TJR9UGgt1X%4U4b+~xm9bi{^LF-gYfWCog6r$eWsX;o41nym+Gd6H zQI{(+ljR}TC`W@~VAlue$lV&`p)RUDvzewIQgy2SN%GDKhV-`jpNq)wc5Mu!PE@pr zz0lfC^kPJF+J+`6bel{0E16TgS(`cT&=c{dY|PUqAA-NPlSrYC=$N?qu@g!7kj|j) zB5TS@h>z1e?z~S_98u}0UG>D!z)Q=`JepEtr}lcago_DYLs)D$Xto+BRD;>bD<&XT zcMh%MFmden1c*Yv0QTmX7QB#s(#71@PA6Hlo!+D7c21f9O?P<@A=%k2;Rm;*4A?j} zd*Eawoqy)?oB9^GrfY_KLS(kNhX5GBMsaj}W{X}U@{+ZInXK<(;`w{-q#5Aw_)Lhc zO+w^B+9nJqC}z$0c)yFxHuO|Y;lxpOFArAo&5jvDYge+NR|#sptHF}N1EUxGhkcqeo4n!6k@7JwUv310N_tR-csuB+v-nWNG85QU7xv_;61jhez3y0qm= z=KP$yfN7{nZK2oBGj?1q-sb&%x~Y}$!TqEQXxh|jWJ8iTHj2FbHl{9&8vmETkB0)+ zm;xA!9iG7|k|WceC>{5hv`&Oo@yz9(FF0Xnj9b<)H77+N;*+45g+&Aan$eX#dD&t^m=+5vop(BDSU>hY3Nrw|QRy260Y0z#1P z9aX#dnXcHQC!hU^ym9)T0mZk?Y@nkB zk_TgO6=(jK_WHabN-1y;y?`mwKz_7V;Ytrc+VE? z!)nO8C2`{aT;z2-;>x;Bi(S+`Vzn|%lctzd_oz|Tx0}KGs}9lQARWg;+6nGdrb_S5i_Tq2>H!qrwobQ>VLUv;_5sEzQP|sTtW?eFD zw^(TtjzxGJ`;2AF_IT^qSzJJJ|9Ri_?ykNb4BMUDvF?lawWf#PYv!Ay900p+?^Wdz z2yAcs4DE;;;A*p;*X(h$q8E6EH7A4M3x3&jV^|uKPXsdDkkkXLx9gS9_~4D(`Q2{Q z%#{gfuAL$^4QerFuCg9-mpS7~lxh!-T;V$gA<}4oJMgYxulIKRriQmHYGbY4mENT| zqtXwNR^8Lw$qn4rX;r9Fi{e)_R0#bzo!+TheBP{k@(X*b+&q7x3FB<6(vJZx;Db_f z0WK72C(@4^?rcNl+A|C9j=hEuuLX4n{0X|O>W7eKp9bcd9{juyU8-A`s zkB5TZLD`~yn$u`b(17fP-kbH!YG0gWsiyg9C zB8f%2Y|eLX6cb)-Z0^~rsYwi(hU_{V>WmNfY|vklXSE8+p!dh~p{g%=mFl~PaXuqV zlO%gLV@M!p7W`KfiS^kq_V;pEGc^I?Yn=f(aJjpfti0wm_ZsZZ;-K+XNVvuy0YPpt zNoYO8_{5@Z@L>6JPvEupefD0)EgCdAI0Lz~E{Tc+)!>(IbHQ^j00bY?Fwzj%4xG*h z*5OtKZ(Tia@%&GPwI_Nx6UvwUdY?TSse0>vQo?rKq|CFiMmqnQtnCMI zb*U1_Na;t;9ut<}FYQ%f)3>r#K+fdau~i?cTyV@IjW%PkI@K^n*Qj${wb(|;edM>C zU(RcfdW+;$s-X7tW@1$m=4V$n zIW5_sMg3~Oe9Lb7PNEStXP=}vK`AgSq`M#I_K>m9x&S$mLF{-@IGzAfun&-X+!k%L zfD>94*6gpo02fp5fytrZwRpOlxIjzy&30MjD3LJzL-nNr+@)+?XzT3ll+s9tI#~R9 zS|;7WM{-+EA^e((L`kd7%S-mrO(WQE72J)SbTHsrvBnsr$X7|z7oOgf4K~atgt$0z zo9li@-(|`p$tSoohM&V*u+C&7GS8)m6TRyA&K@Gmz{ERDPe9@dxvdhqn!O&2N$K*; zt1Vvfbet)DfBfxCH5Hzbox)XHa3+KjUuKfn95U?2oAze{p1@DNFlJ$dtv=r-AxA`KM!-hnM-fQ8b@ zLJ{$p+~j&!B=dRklU0@=nYF3KJACP-Seq5eM4^<8VNOG)aopmF$*6$d)YRqa%Hu`m zjFj@9HptLCxXn7)QkGVyu15?ku8Y>qkmFypPubr#klsY<**Kaq-lFci6i~F8RRoht z8AENBSivjvKV;RJ37sR}DG#%QdCkN0M3SI(ya>CP9uwz87wZCjaQG5A9kr`|at#YtEt zr>k#bU}#be*5@xtZhgdAA#MT8xTn1FwVn0tqdvKSZwa z@qP$Q3Dr)H)�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 z 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 z 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

    U4X>#`Gyf`90A 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 z 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

    eJ+D_9IQHk-oI@uq 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

    7tnMn(+yp59o~QHnQw%D4^UIHwvnD+ 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

    5=Czib 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

    ETmAf_#n6FdtB^u=ep}vO%5Jv$kWWSUo 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

    #J)b4@RcQBIv}NT%vZ7fS|cLHcA|Q~ z{d#zKPt|*<0oK;@u!6Gu*{fuq0rzD@T27@Ul%Otc$2s#Ryr_^unL)>ksC>Hp$nI|5 zjU-|wg}c5EQL$E?M+NEi$G@2tP;bZ7R))KzE*k?Ufn4E8_9k)KXmsriI`ziT)A{&R z`n|s8tBF)@Bhm2-9hYsYJy|d6Z}gr5%XuhBLZ@q5sm?bW$9tFs6jB8W=hmgTR4{6l z#m>T0>?h{dO3<)1)4q`X3MT`(LhvZzcGC57JK*NG9y)iFl*y(KAPw;mRq!z7Ww${% zF+hAkw9IN<;jH9g_stVwh=smlIBAY@n9eZQI{U1`VXA85pu&Js=V=1(a%Yrp&Kk{r zDH&IU*kJ`PPM8~yk1imQcpJ&2wC5SP*mggv4mSuGL_(6&i6sdW!%%D+;YmxhP)HGi z3I)@b%h~mK4*llpdT_%W)%o6g(xS(Qa4=wl=5N#mO=RXK1&SN0AIEnKIJ8UUXs)#$ zN*8_(lx0y^h+{Aj7spNB0^^KYNsY0Y3tMmU2P;R*b=Po;XqT-J$AeRKzPj>t7AaJ56mNmh^?+sqr-f+mCFYuT9c1h#{a zblQ&F7!(%?6rZa%{;2KbYj*4WBkwY1=>{4iU zmSyyFPkJ3mEwSf@cbd@ZhFkp^gvX7*CWaE|j6PmdG!`; z`v-NBy`hZw<}|H)@(kN>c$kf0eNSgIozFo=q8{JX{6>bKYMHxFr4~+6IptFxYJhE z>~iOL&w0Ybu#~8AE;k>zbD`>++(G+sV zli49yu)Ja4{=ACMqU+DQ69}%;7{^!74T?R}RNq(-TanZyD zwo}So=e__%(5X^VH`<7~lMY>5P{6GhnNRQEaL^Tr-F-zOKeHL~s*QE-7-Z#9u@cSh zl^=W+r@?6^O}LjN-)#M>3};t?Qm%jU#halByt0CGH`_V7APn}s5;h{3N&+(38WdQ0 zhP@^@2xuM)Wxv_U4Aj!mCX=TzyP#J5c_N{fKl7-a(OFIus81{B?%&x)+@|P_<*UxO zO$0b+tWlqc+PMN^r&xw#@;$)emkNZ{THUEosKhBQ6kDh;L~pJJdi&Pb-_Rg04znXN zCiM7id&Pyk%`06ZKKp5@@>nW}z1ho?2BO0qc#H~Gbfn&|2@!up_KRfd9mRpUAK9Db#?()9HZEGhnO=v-Tj!W~*xw(w zqq3gd0IN{WFTQp{57vpoix1Lg0x6an(s3D>a~a$QInPr^0A^ z7;{Rd-!tK#75`tmG0c7#&m6X!T8$%`j!=L5`#vinr8?`aR4l3mLWOJRT|(Iho)Lqk zhphzIqq=b{wbrj)ly1RlJtzlFu<2#uPx()aeAbmOd9J=k@055Tw#-x-GTV$Jf#E(@ zNDmb@Am1f$_C;#g1m<<3Zq(QqJc@P#@w7*Ihl*_uff5G)W^J_6%s_P*JqH3qP94a* zd-1yyHKxrM>=RK%Q{(lhG|f5%QZ48xUc=NxsgJ**$=Z;@TiS7mNKR%>V5`b$irP3% zk;P%yuFjFHe~xf$O@L^Osvm?=2;cDi3D)8M7MtONEcKddcUjz!s>ZX@dsJ-MUsIYa z@A(p|-q>*3cm%jeZ;@NtpW8o{2^@5cNavGJSie2#6Q!Vt&{J%ZtAh-qE#eoPl^n!- z-`nBhW0}eVRI+e;@@^%tEN@C+<|f7<*Y+td9RPEq+prFHaH|Z*YF5q~4Ufz{<%PN& z{lS_$(D+FcxJ41vYmEi-t?Ebx;JaAvD`PDc$(a+%u3+0ZMO@waTAdJ(cs;h7$YZ%!bk_;AL_*w0yBg6Dgb!gnp$!*%kgmgb=$9F`(?X)ImbE= zwo~9&%DXN!|H3g+d0WwyU|bnLqb6!~E+i)>1fut;37c0|lXAx^^6sp>h@k6m=;XYb z6NEbPJj7eTEDf0h>+ADC)ulcVjBD(#yJD{RG?sZ`WHMUN-~@u37fpt>ynW^Uc}srS zCcSXYs=AdQ{52ZL{O&ZcofDFyjaE;Lhe3I|;(XNf#~3X+5&0B}Rj5qHOGcLlcIV$W z5{inZh{-ugWvF!kGeFG0J;K{`E?w$Vem77Z&Po^7^=Qy1E|%?CInx1lI^CU8^TGb< zGO5lfYMQ+K1Iz`tO8uFWofL=mrb*OY0;-`$mcFy`Yu;Fq8SE1k1zyFIQf|s*VIzI0 zHwxh&ie-c3=ee1LWO=no+;s>2jBsS4MykZRv!Um%W2=UtrlZ@W5>0~b6gTUwN`^S4 z(WtQpn;+GJZ9Gb6G8%C*_#Atcs^$Xd4U<0o&9YP7-55?EURBO|>`vW^rvuMJdy(6I z3dv=a_HAAMjetSraK}o%;g@AQ_phiX&=Nlc;>M;Z4KRVB(;Oo~N+<^`9*VR$$hThh zQ3Aj-b=m$D)0z9-_TLvgsdv9U6#T3Ws)9{W1@=yeXh%bEVRZPD-7me#UeFM3R`E`1 zLUif)#e$dzS~XW@WloZrGWM`{F-yQ{FIh(@91*0|Ozz1q{KJ@iEk|>lI&x{YCWu4d zF=nn?_qXN*5OWS-gnaNE3XUAX%V>XL0k1$MTjBv1pi_wqQ57l=?`D_8qa(D&42nh7 z!BT)nKJm%f>9dQ{!&0tJcf_pxowEV)q&Z!oFiv^`OJJfR=}aNPZTCJDBe<;+d?Mf8 z_5&c|**s0oCTx5-oqmXIQBz~jD(T1P!2CjQ>NFlHrlM6+v1DBB@t(oou2$~%iSi*j z*+DFsy4ZHgU&plJtzXaC=u0Eyvt+=Ya}O(}J=E6aAI*2eDtp6+JiT)7Ixi>V z>ipP3(>#RJZq$#ta?2&$)=3Q0WEAb)RlMZ;6yF8q?Xg>wGCz4xTS7S`dnGiEN|CqR z8UkR&gzmVEgq=3Y?iC}faA78*3njqs_)gu|8Otz8W!!57Y`5ZpXTP%JjVD=Oib&WU z*S}1A-8-iM^s#qNl&p473PQ3nJ_Risbs@=Y=#;_5IwmD9Vx=<%477Z# z*yIZ$hrY6oa*W4-eYqJhDuO27=|G;f9b!w#2oOe3nXZ>ppENj63+!Ab&Z2mi|A)A< z%bD!TjqO+oK5zn@KMe1+f+tHYq4i+EzJ1$LRehOx_754cf>~bQh^d4015VN_XPqZq zpmAVG=~_o8u9ApBLB-wK_!UCs zWUKLt+44xPYuw(Vs&i3A5vV(piDL~|5whiLQfheZgclsn&3ZP%F)?t;Cb-*R-#V=7 zFj(Nk`_u+1QMr5CAjsYI;n#pmNnBj)RZV7o?U z1}YkNy<%ERdg??5bcd4e9R`_4;x_pU>ctg0ze$Como8osR zQzv#00?uKkpyXgbj9FG)8c1offq;C5%mE0e$b5z>)0Jhx`6KV|J5O;z**YN z#9pbUS)S77rJ+(bH(ZR@PJFRqPy3#*eU&=ib4>&ToVr5f109O|_ND~%uJ%)JeMdOO zBNNfr3dSgQ&uOTiC++HG-$?W6d#=EYPN50n`dJjO==g9=Tb7=?HHzh8O z)q=Vaab7PU!_Wy)zqB`_Cz|r3mP_C=JSG>(@TD@`4CtfC?$EDk?5;QR@G&O@Tx=cgf5lH+$p2IE8V0z* z9CRP-D#7?$?j^60BU(Hr!Zqr*BQ?!#_NoD-F!MCL_|?EO)L2&*Lk zE5`LbvTC@$T0vjm1Pm?c@=Q8FF)W^sBb*iGL>=0ZH?b(SOXu3BpJ}O)%7uvJ$c6?Y z7^tKmpE&1SSV9{_Zyv2u-NzKPlV`WtAUdS2z>z^8fvUZif+|YM+IusyD^3QwStv7R zWcLVMl5DCQ2IuBER|V)baD)9*d{vco+vp6@aE_)QWt?QtAC>b2!tSzVK8B^Z5>-5o1(a=UQ z2Y^H49k;K*P7tFCTV<`y8@2Y>YGz0k>T_(W2{Vs5a2TD0l#NBKO~kapRf4gS&E|@; zMD8I5Y($C3Pi;D5&?s%E~xgl%x`uGnGjJ zt`zn2-SxRLfu$1W1yHerpZl3kMQQWpzBFv(K>Ra)7+})K7$9vep^ek(E43Dx9H-Bw z^dVnV@2c)6gFESva|QH7%0y=D*KX>oxMO8vS=vVGZ=qOoFAD3X&ad3R)ZDUu>b<5j zm!MKofU=1y{AGk^ubj0ytBt$8@81WY5jkvC(T_Hv{n*?@sX3bSX(3`Ls$2er9c-&Z zP8#hrQ;?S9)}>QrGv{zWXFJvhEw)^maT;~vGDfG0yygU&3Z7owt9*^k|1O{xhaVg7 zx@gyMwbQ(5?Q?E4MyySM(K^CK8qD`+=`_l`-{jWuk?)PR;qAjK9Y%>ffJWx9k z97R6zHP_63rSL7d^KcYb=}wtjqoBS^5V^9Tl=Z(=vbT=Fm z5x?dFKkS3Qo5>~Vd@DHhl?YhYR0+{s$^MdkMcmy8zdu4__Dq)}Pxepfm_=ccwE8%U zfS9giW$gu|<80kk8IhpL2E^YYe7?TT_Siyp)sQ3#cgAJOg(Wnx{o$dmL{~~YXxH-i z%&ZnS9{4>d4U2UlgngC4MWWwAtbMv=STWIuV|-(RMC_NcWpiiG3o*e-02xGOw}V|8 zrnbNFdzr3MYL8*5?N*A2NKy3e*%AA{Zu%V!?2`%bI#IiK@6Sz<>Y3hoh-kp1qOC8O zOCU)B+rYEGcozNTS*{ydI%^OdvmsX1aUm&)?~}?JY9lx{Lqwrt4+Cw|wMteh{mMt> zlTg`dNm^N6*-ecln(TxR>mjOC9=YBacy?rI8=FcZ)|z~VKUdB!DKo{Gu_iG<7=qR= zL2s1#)3NB3MzBag|EQ_Pn>=wN_%y++!Y_TLU2%c(<}o0lhIeNz zF6YY#EW!drxUX<>LWpc8MjXF@xLZa{pof!ZkZ16KsWhZ3q3IsPS2!r?*A?(Mxt1h4 z3Wv6TT#Q=~yAlp&Dkyn6%@*;_&ZGA?Qc(@>uN8Hu6BemG?EO6xRj0r>($hgVwk^Zy z_DRaXMHJ&ItM2OSJV~;H+1Z5mC+I+icPqi|$Ks2bLs-)#Tj`R3a@q~=3puj6Y zW_2`t0Iy95DkrPMGY&z55Y#cdpT)w@Y{m}k8tJ|qP2HMDGjfUgH;#q90?~=N*fnbJ z#H!OeD-|K(teVV5q3$1%R^5wzXrMp0R*D^Bq>5SvNHc6wO#0Q?$W9Vuhggt**lRf+ z%28LIP?0$d?|o?TlS6Q1n4R#!z3nPY=-dF<9GtSUhISa7TuW|}k<8nC zW*Al)hp(L=1`7$RWVMxjk9ikLGzlt@K8y3KQq} z3ZazB+pbO-MP*DbIQf(gU}YYz%ZVPqnFz0pE!Q)x`n%yD4(0Fd7*#N%fNabEJ_0*I z7x}R8SV!@=RE$j~)oM5OE?BuXkgQ$Zn#Aip>H5;$Mf&avqOoR?K&svTa zwAddv2gW${0uAaU?Nb+A#!h~;!~U#%aZG@@lCJxFlfe>{o4O&lg=Rk!^iVPf$39t> zT+O2(Qn`>CB`T>Tsj zMgbwy)(l7K@XBDtn&Ij(czi6a?LcWWDuKS)j3l)-6ie6uwHvy!ZKcX|GBizwt%)OW z?6{rm8(mGe^5y*`kK-#uO$(U1=RWpXeN|6OASTk5tI?@Kb~&Xw`UKOsnf8>qHnB#u zlotH1{+1-+LX{elV%7(7+y%B4rb$Mt2*Xm z>poX^I0Blk6syR99cR=!yGj+s^xv9{A;O8@7~2U{6I;fblV`-2XR~ucmRql-WdY~h zX!fum(B+TQ#?16)Vv>8pRkXxlFR#>kJORr=neM#&j*~C&34rnXJjLdJ z+q5PQ_l#^@{`~qC_|~;%)c8(bS7%f#VXiQ9(w$WP ztz$$So*2@oXuo|&S$%wQ9JP9Ueh6wwYU)xz0Fl9ZgZkT_=#L|IAH7l?gIuA3cSqZ7?e%(!dr*t?VxNE7R;&+TKvCG^Zia=h6j+gU^5aTpk4bzsr3}A{b zF?h@r=Sa!GJBQbB3sZ_ZZO$1js-Q?O$JDL#49upA*`Pc{${9&&U9>^=3bK`R-pNj% z7>A2m_oc@2>XuW{y4_o>`;!PQ?oI6U*f|cH)7|o;DRVZ%MhApQ%%=9S`K$W2;*d2) zspT{YXDjbjKQv;C0MnvLKNSBqAT=GVGP< zI+^SJQBK_AP%id9!>7>iL1Kq0<2w&Op@fPGLXF4RQol?)YQ9e6<4+!FgQK%!g4#|h ztW1@n-Ilr9(K@{JWasQp6kAH!AWS{qZ7P{%dGQuKl{$KAh}o;;5B;4ex6n0PZ#Ex* z#oH@gd4*NZQsUDI!7!UI^ju5M9ogtD?snqK_mTS}Wh47jMJWo}?cU!L-_~EFT_K8SMWmKu2p6QObN>IqU9s$V;ALWdT6152%)S??S!2sK!uGAJJZj zH7TmZ+^0Q;9K8xPncDAE&arbXECk6H09%6xL;o^8RMG{z##d{o68w-9VXR zME^e9eAiQhXz&J_oXkDE2StFbtuf{MZKPUwRcSY4FnT6aH(ciW>8^2XJXLFy1a~%* zT*;EdhV!o#mJ4Eju)#S~CT-PUir5EM+s)x>zIHtL+k^BD<8XAjr>^u3S@}Jzrz_p- zy?gX5a5!dZ-*J|m6cSl9o9dx>*BQ3+haI!c8(9&!8-9|dbA;0*)}0PDW(XGAx0Qy} zITB`MI)NW{PmcL+x8&vvIlYhS3d{Fm@RQ%X8v)&O&E3LRJ)^T}}0_3CBsh5LRoynnwzIR|P<4R89#) z*X_h)!U$~@efwy`9cdqJC42PwMrt$*3x8c1Esxx0{t`rL@+QcE;9P)j%}hf#B61ye z?*mKkgw5jBMbm~ZXV=69tWZbdU=y;2RIwvxlZ3<7W(!hb_wx>{kVihhWkNvp#2_G4 zw8Wd~ky8x*$>B&QbvC#!=N;g0Rd>i$k%>R?DU}lBdV2dQy?xjL#fiM>-#b21QC<)k zUy8BvW=Z{GWb3PR{P^i>ThvH@+HaOcpLAcI?dLH-{n&?=j08N13JcK`A#0HRT9Mff zU%kVhR9Q`Bo0^}`*|C#Rucjj^G~oo!iaO z=;rfOc!j;rxHdS9v&840$!~FSd2se_oY0wg>SbYv8xRe8xNc~0Qj|^*H)(HLBD>s` zg^GjpW#{M8eqx&(1`dXBi;Ao?M7O_1MWg&}M?6?DUIm!$M;f&3ZZ<`OvG7E$V1o6!u{d$FWy zzs#?F^-9}<6tHA)%!rc?a%d;mM`stgn!A}ynFsqLFUc{WEg}{b`BkY77=t$ zu1Xnn6&|1vehzCnaP?f!yCd&SF0Qys7`f-2baq9#uNN_^n1Uyi3Mpa9~IqO zgr1W~AX2oJ(RuD-IBVh<*%4a;77~HpgIc?m>mfWrn|+n_eKcU*sf0^pdc>^;68;s| zy>qdcx~{@Upt8t_`UYgcEZGrfp1qPtvC21m*m`sz`%%H1ma4Z-EKeyUsRh%Yff#rm zCW<5i+eNh(Lksulk~E!FE}~gzd05!F^Jb~J`Y$r}mFxOb28M`?Gbm`)1=UxiYKeYY zKXnv)-4`-@J{Tvq(!f8>#V_M~xd4d0ITSHKGwKba&CORRWDbbeyGFbw;qnjJ-AE2?DN zX_Pn_R_s}ciL33*bj zcxrz>WCHsJ)Im;y;igR-?*vdh`7qp;Dj#Yh_*94KMa&&wa3<>2^laLj4sd$05ytyq2v8 z*j%<;=vc$zx4>4M#h8uiuXw7##y~s{s)Yn7tkJj&7jjPP+$QpLBeI;BR`C}jqSB?r zmzfs|;>3T%>aLu^mzw;FAq$dOpR_l5@Xf`iY}0NHJ~tmGrc_G|lVEMv7nhlrbjHw( z(KNV;z|+RTnp1-|Ww6Y{tXZ`uqbGlXAR*5>Z=qA4D}PvIKYd*9q(^_XafO|9pZD?; z8MhXvvKQHz494hY2nQgFu-Ycgi)Q1amh?&ROru$vuu5KJFiH;k?HKrhEG(h;ukN6+ zA$~{QB0)$aclAe7-(VjIsbdV4%TJmE<4SUsibB!0##9c~h^Ym!@dW`+buSXcUU|~7 z$2HnI_X#hU*q7UO`CU@_r{Y?Fc;4z1LORP^RcOrD1o3EXj{r?VRg3bU zKR~DEQ~MbtjX6LZot_w+N#!WH+wR^qFntT}#ioZzGYeD|#922e&}FlKrNY}rg_}5M z{=G6&-XPYE##wo8!++upTdVk9ha4{~Ne_3p8~O#uhw(}J%OTR37bh=pP(%aoHL$;H zvVPuxMn2A)n`Y^I1)G>0B~r)8)cflUoTo!OM%bP*71o^GVS)tOe6XC1iSeGK>%BXP`= zyKIAQYtCrlB4p_Qn+i{Pq0_l;v4}R!lr>q%Lw2|dd+ci&LbCu7x(NV@z-&6XU0nrW zLEF3)ue>P)E~v?oy{A@LO@r8WO!J#gy+xs-^Xr0bv$p)f2Cs?~~*P9P5;f`ftQFNA*GSi~4D{W(em1^tUk3RNccu-)zW=)=8^hM3sqlX*41d?c@5#hbF-5cU#{hEYa1VAQ+MNv zHZz^2^If-EWwZ*Wjr*LO&Q$DHv3ycBE~_q>D-|C`w_M+-0{c%8roBd;J<0qPIwE3T z5LxD!w)kv6BZ@8DPPQy3(!7n|>UN4lIfik!ZWGk+OuZNy zse@oX{p)%0N0zf6TG8y&ae=a*(wvS}Q&8?EJTl7^Tp9TGNk5-ZNTZmSZ&S zm33~0U41npW0^J&mUU7pV1Mrz&ebBfBy%w?_o{pIZ>l*JROs?La(wUgxZo%e43UJ!dS&5Qv|AF|J$5#TT!$G%kdZiBbi`x9*{N;b5gjCrk!-v2+TPaDAv zEkHyywd*;ThTze~$DGz2|t9~-#R0x(50`hy_TLG>m= zU;pMp%m&Iv`%(m`ClmC2N$tuz@?KJpVH`cT_?=Qf9+FUL{wR zY&=Hjg;%Q6vnn*atfjmNhF!~nVsss7(Dsjr@tB9nS4PH5VK9j=P2RF5N@^uPv(#hM z02QT2g-~745;NKpm*QoK2G6X?lmi8m7~&V{+smqsR0cT5cSOgZhkBipw6qlTa)b9c z*PfS4WDwfPSm#(JsyU=<_M08!v)l~d_wbavsZ6BsNo`e~ym*MU+ZhLZJngtLyVQx@ zc*+=HMhc!&>I`19a~u5@2_5PGW%rmP?elEDCROqUbAs1=)Q9hDtaW;b<)JzOW~SAo zaj2vEZ)>Z47K1QteC0UECdhlG{3A`p0vFNG+vOE4Q~p}h1@*F*$dX@squhXXEKA{+ zxf*N3^FsLy7>`W?vFnYh&IO}%xw90n(UzpmEU%CHSfzG`y}k@70SST~jlN>C7h&U=d++mX5eR7UQS^uCw-0vrF|eq)_p!!X877R+Hh>q~JEc@`Q7h^qg#Flop(8)n5_kU)h>t(ZUy-9gzKS6E>wUDG^MHd+B@kT#yV7 z(+};aE5(I4E*GjKhaAH9f<{9*`4pz|i?|Ud%1Ql-dE2|$Q%*ciLY9cFDW zYAwS)TuZalJI%|ulj4J(gXL)i121U*y*Y)-E~u0A3>xQ39FeXDcY^zDw&oTKW;%Ee zTBV{gHu%aG)`M|fJ=pomqZ~g7T&mVgWn(zPulR;0ieI&P14=9|v_Dg*bqC~5a#D!s zOCya!zxjT^+xuuw1dG4vg}`X0bWpZpH;#%(M5P!fb)xTR%VHqM6JC@Q);Uw47$rnd z2|&km*Ceg%547ZUL}+|<0Dew2b60zUy&_-o$#z^??%{GJO%|3py>z6Ki_*Z_JcgU8 z8h_TdWUCAYjZ`tv@(XHK*`m}FWaPJe9Ztw()B{Plb+BX;ek0i`!vH!Gsi>NC@#>Q^ zVY;MX)hoIvZj%A^WTJ(|xe=eWWu@UDskz+p=!*S#v#X_YEysJh1nL;jPH`iXOBv%s zQQO8NJdT4Vb;a%04)6=VNgfY|@C@rPmN^a)e{T+&$}^ z83AjNy0~O$-rtw+{Y}*J6}kQfADU?aT?dv4PKqnxUXe1ILnDFDmpBzgqbh zzyH#FdU}O;MJcN{AGx&U6Cpx-vIJ>$0skFB4xaXuE@$`ssm>o-WP>NQ-0}2^S5oUP zCZz<_m%_N2t4$fuL{eFuRbD@WGr{i@;JHjoP&lI%ZYK*TMN(6Q;Hg|#^+m$XSKJG~ zJjVl%z2%%~mEWx!b9{$ExP)a(G%vF^M2fcxZgMzS^sd7Kv2mak7ErSDJ&){u+!Hs& zQsbGf=%$&7{7IoU_ln(Z`%Ic@XZl)+nH`Z>Ch(tl^v?~naBj3~7W6a{5X4$}-a1qq zEkehGw4NQ@mF~IZ#ILn#o@m$g)11bZqDC8^DpE!N9XGkY)H=2#6H>$Br|2DTEA@ah z4Eqb5#VpBX%8l))T*Ei*TEWpk-+7At6&+eEMf;|g7bNuevT2zj8+&FzLGS_>NFs3D_ z{#s~4=**gsu(+M~+Aq_(Kfx*i)x^R&%7f`>My%IXAS`(hBq&PCKi~0EBMR>GSFcK( zlN3(g^asbR1Y;%Nl;deWC%;o#U{~s9BdsE3Q_RK@oCt`OXvo}Vyfwmzv%C@7sb^Mh zlG!@zV@*=9Zk}3k%ZVN*DCD1`HqEP3h$rgR+D1RMu;z%DvDNNzPh1E@tmYIOULjj8 zn2X*YMUtAAYSA_*G=uuk6!lf;%(q4SiiC6YA zR^wLbKN-UW0hE;9X3s?Q9sN=xoByS8;SS0Rt8iz1UMO{Gj9DcYo|3L7aRWbd2qoXCn2+UQ-?M2Jx z;i4Ha&1Mu$5sk9Ie4dcn3x_?Q0j9iz@gis4pBf2|!)ZkM!@pLjF2>}%mUm5qjkDnr zz`!#mkNk4r#Zt!s)Jc~iGm?Yjz?b25e8M2;4F3yBJx^%l zkJTp^?j(cnGR{|9pa>Y)J&j02F6WorK6wr|21O(vFr}iQGH-H{P?-0&spDRB zR@CQ1rC@_fRRBWdEtC^6^0DSrR|fKYwYhUKBl*H0DUvE_t)-KaSDGyy6q0j-FuOty zVx4Iy53e}+kf_U}&+!ximC=&RwiGTR2=raKX!^fq`v5caRjKk9bI>`(#6 zEoWO!Zh5Rp_OKh^bhda^Bdrave%kMNP~AwJe-0#ZXDP>99kt;R;>eJ9h|3q{kVQml znE$1URETbb+sare4ha<4%CS!J(4DzE?st6@3477r;B}0!a@uDq@w&x_(|(0)zvkjw ziA?zyb4oHvR6R>MGSd}4=8`qgr(=nvzr%3lYFk?c$7P#fQap$sI{K3WT>*XFX_M;J zWhfB?LUSh~3beABblmWo4@R1AA`m`~F+`u}v@d@vR7x`Q1Y5tn1{%n=rVCILW;{_b z$By6N0^^@B2B~<$zRe_3#BE}+)NJKnV7H*or6Ld7y`R1Oht5Zx;$uv1Q~B&Z^(m8f zNW8L$ZJm*T`*fElD%;~bDJK56;p!es%2E_Zor{k=up0a9nvPfS+FH?hMpn|ScCWsv zQJK(al~74@T9mp@le7iEL<53+DFG+6ggsBw=5L5}BBuA82tLImH6LRlcVeQWSUQO@ z5G0<${ya&T*~EMuU~^!M61wl~S<)>OA_x2^@{*yPR_k@}*Rbne%b=v9lcgQL)i{#GVw~BR*w~83gJ-jNTh8@H7SN4z_tKJw$)`RoUq>}E zXs5O_JkRsP!@}#DzNFgPkb8n6N zfUrjlzn@W~w*cw@Vif;m0+Pa!_!*`r6{Ka`Dpj=Xu+EZ7h^XPM>NLs4saI3!C*I%1 z@zF0YoJQO7ne6YeM13o%7ZizT)aOI_ArxA_o=BtpncDp^KFA0PID|)Ux>Ryi?nI&q zJUaJYbiqSO3Sq0C35A*pD>#yrb`(|_5BMC`E8M*GC%ivU{?j~FS+I(!@t775^-d!h zSspyc|FB;!yZ(B-PD)3c+D@*Aa5Yn;dZ$Xt5Z9#s?F5*Thc<(u(2-)#2X#N%<6r^# zESD+`ny9AIG|@}i#MF~co#26KZ6Ffwnc`>YdvP}-0`%Okc3uMLaj}VDErDnw@vz>M zsPlp{%pBfdu|&;}=q*1ImqMqq`kP6CR+*;qOn-@}A7m%yPtCZpzuin{K-LhNUQ}~Q9d1Q;ZaDL z?F?NmM+kKh!ozYMh|3(&g|HP3@tVP&@}|_}f^{Tx87NWrLTC49w|1iKC+t=!sd|qB zuf%$zk89g`oA0$s9%vEvUH&z^AJ(261r0o1k{g;GXGQ8}qNoq^K?8O*krTW>E-d(k zHBRGOzTE z|5v#{ELRD1C^BWH#Bf1J0-uR{w=CjyZFLho@}nYOOK=Xa095uRZ@CO+wchQQ$Iv+M zBfGre(VL-HwY>QX&42Lx8SbMVU}n6ynQTC`yz7WXrbbvAlFhZ@08kU-4}4Ia`?pB0 z_cBLLaqA31R+6mBo*%70mwwa3eUnsi zs&hLby{A#Qc$qY6R1u=HLkvg`{K9G5jgbG7F(BKMXjdPVNdHy3qZnBP^@W8$^}}I7 zc-K1^6Jo|WnZQO}21nV#>xog(={IUlN8V}V4hL4wH#x&c%{MSqe{OQchkxTCdOej6 z`aM@MMpXtjlAYcdpFCHaI7)Vs<%!io^^6E>Id?VJG^D4mNea<<~}KYs%HkAxr`!8_MM8%D70yD+WRY`(XQ`BT=DE zJrV-XA>I+IXw;;y50N$zQQzX+q)v8uIufpvWhUEA5>#_&^0Vb43EGZkjO(`Ik2x}! zsUr?txjVbk|Jisv;jV#&6@~Lm!$FHyOv?s0m**s@m68y_*hc3^_|Ye^Z~~x?msXaZ z;np&oP!G$anCKGF(zWV*m3Jl{yqKm4k=m=a=YLcjKX^!uP%)+BV%qA>x5o>ZxG2~W zQBvypiM`Bq);lj*BIr_Bf%Th%VMq@i#EfDr=^i}we;MbmCuzS?XA05#Sn+n!o@r;p zqYvEX!P$LNwzOoX5!b1jx?Ww^*k^5{Yhs?sC?oWD4w=icfQn%|IJ>#MxF)S~9CT52 z%HMWB+r*38huWaeQGr&QTSoi*;Y*6W$ZbtenVy?zu?y7MR?|ffPsN3V+$d-`Glf7g ze6SJZ`knocf^vES2e}#Dd{=YKn&0VP3f=drdIGwDOv3M;jJd!Q!_O4}wEz}*kiPuU zBUrJat+dH94|lvTqkOznbOaso*L{BE8Y&nAnc>{qp)0pVwdV|yhK@UfHE=M)qY zebP)OFFDBs+C1wvvJ$Y+A2vx1^C+Q0a~kk+n^z|H#v3Aii&H4mfsmeu*8i6mdC$BQ zUe5Lf2K||_30)v_v>(={09VWyAGRh&pVqFQz4}p|E6hP*#$!%ZcnB-vQg;EmbW_XAW~4 zov7uo9Gi?Lk`+y-pc%13t0q0+t=zzUXB2blH<_ek#Fk9NW;kVb#6M=~MzzJ`+8rfg z@BlwX)xn02`CaL%$g@xIP+%yw6F&W=McgEthE~OkR-xYJk))gTrE)6LQ_TvD`JNS$ z!#|D`N%7b&L&fVLKiAP8KLan00OWJlkAh~euZKGzVd*r}@D{yV=`D;vtS|WYN!7M% z$8nL@`@vCoMTJM!p{XD@EcKoL(ObMdNe9(8Y2b6a?Z%V1Dxo=wUc!B6=1B$IUKD8c zf&1rA>PAuc6~TR3oeNfSHEg2`LmWn(Kbb8k^8>DL`MctLR|@mp$tqu0YBAI?SOt#- zJ2wKAeAS4t+7!2nh%8ZymK0Wio<<-!W95YoLs%*<Zi{{}6uUJ_p0l5Q^ z1)3qUEmQ?}S{jslmsIabENEe*IHvqB%qc3-@~Z|dAzX(mnkj3w3OI(E;}P;qAX$K!x2kwV$@A6cHjeVY_ND)fQO ztgkRk#Jt~n)Kd_^A;KO{NVDH;fqnsBEGNw@&@e`~lH-a6jl_Qc4B_FlmtHqR3F~IQ zCnRc6T*M%Z#yl0b1T<7d)bnMkx~Q7?ms$ z!PZdSnzO7m5m&F|O3zy6bGB4+Ft8nhM-$ZPr3%Fg`gc2fw94z0|Fn6~($QX7BCII=44r26+>@`)mBxd^&naTrwUYV;hETag z8wn#C9B`8z%8hbpSnk=tu6UQo6Ay0Z*@8CfxUqwCX~ecPP&XPhIdI)m+f0mk>IhTW zdLeJuFDlh;FI4Nm88f9}F4M|nS0gi9qmVTvlxgQYvaKuh;TsVF7tCA&)uTpV}>}%#U4V}j+QH^6rH6`R#;gL@-pP5QI9x0KYE{->fGUC@?#9pHHCT9ms>GkVF-RYPKO9f#$>tz-1;Iy>Pb2FZ&Fr{HSK0q$84d)yGmuKTIOh2 ztxAWTP#4v!ab!oi;9B4k?g+V{stkB+e5R|r7D*5g@MGEvr{i9au2HrtwF zXxj|>LJz0VAw_-HIhL+3;baUb=e~X_t1}+a*PW=yvxMg1WVZrY!e#Bu(vOyI5Se8) zJ;IQ_yD_<%m`R1(cvwF0COl$`8CYChaq)O%YBZmlCx)=uw8Eki8HJ{DT%9T>6`e?u0L*V;d=c8EuFx9Ap|o!+ z#<-`Mc%+%OtxjkiHmj?^7DC_?KW2js8kiL8)JofN-8Yj9uD?KPMMUM0x#1WF@5g2} z6CrgAOk&XPDw@y!Ugb{oBk7p-u;cVB6`#N z_w>8v*mD@-5&l4xU1guRY=dnvrJB_84P26B6XOweW{KiL-(z9KQf*MFrnK!juF(m~ zS;?J;d`A2!KA32Af;@HBk$_|P`YVb1m>(ha+ z4kdkX@gldh;lVH7n$59#p@rAjlamkC9;yx;?y%igc5=!C)l4zeWb1!Oa@S%W>lp|P zwBeZ(AsWp>6?uGxgV^=mI~rXk*|KTw3e|-Z%c8Q)Zm4`YHr=>xzbFj!zQmL(!&w8(SfKFR`qJhTnw6=PgwE4Lc>SAAT;3*Z_2=O=w4J?ttH!AY~o5Ld2?T^+4f-1%bea zd;2@%j5yxk?ctnAeN1J#Bz-x+9+^#X2O_$0m990D7r>}El+$S0J#bbYYugEj_c#PX zmt(VE>$LE1f+4x6*&#YYqIZF5{c#1htY~|c*@pDl!1cJ>_sbj4AJ$W}!7Jq|bh$pI z38UdxF;q97(8*U2D(jHNh#jAdsO`|}!WA<_Cjw5=rEAsSgR90Dc5cgl_+DzmGJYl)M-_n$V0Xgcg@hC<#y&aO=`&GB_Ioq|(tPdV|vSkbyE-3-U! zr$FT3GY@mb1xmG2GE7+BB2jh#H$ce0NNvC(aot4Oxj*CmvrvcUrUiRPynOScpOB@< zKxS6rRQ3~Rtdv^$Z(T?I;X3Bmb%ag+E7y^BBqESksd(D+5fSzm*P$-&qdhVlw!C5@ zyOQ=zj(c3k#3GMSZziroJ-Fv#<`W?LvtuhVkH2*tL;Z;5bXiJD#%7hBbqv~XJ$_sV z5^aB62kvP~#9a}f&XHNy>1wfIF}_nNJ8>O-#5bmK@8nNpjltM)IUtGrlidPeg9bt%Se`J&i|BvgyS)S{FV9C02uw??YgN!%eKjz1EbXdHf zijQZl{=;>Ycv1+$>O;VQNw$A-IoB#@*;ygkL|n%?i=7! zq1El?3X|q!H?G5!A0MAHIT>_N{6OcIA&Sp+aL_&*u@_x+-T>O==cBZYZZP$!ypQ}x z6{zqEES21^eRYisI#qKgTBKsY$^+093((OexDIjocBVo)2Sg}o39AX9!amye6W9M? zPbT&x;Mi|)>p;WJg;Ca)QqxB1vK^~*K)q_c_K$$!@Z86>YVPGKc?e4l-007hm8dNj~` zaib-qD+Oy=DvKDQHPY1Yihn1h#FMA#75Ne>R?TmZqgnKX@!W342B6!;gjuiug;+YC@J2<{P$dVYBzeID0Rkn{g zF7R&sC=SyZUs0OGFq7{k#M9M{3m^o5w{xH}7P8N@W;NKxGxDD@|M2=x;pdQu^q6P82R#B1~SmTRBLw+ zXP?sK^>7=}c38B{tc8+Tsb77DscFWhnUubeGcFLfzw^4|jciR8J6nElwxqpf7uJc6 z%wz)}HUOC=fniXk5D>{M5giO;846jJDa2_zTyKJ)cpM}GO));5cLMFMbRdeZ_$(`2 z{C-6M07s3%z5yVu>yJWzdP5lXjxo#G>RNVzf8r=P&N^yGR=D~i_Od=m8YtlDbl@Ax z^<1=)5wcZ}VCJA=n>h2+fIjcWN+)Qh#U*=a>;5E9Do#k5BbM#K8vlwG_r?ja-kV8~ zHuhEIO3sP6EWgvNPKrB;;*6i6CYi%m0PkJC^AD(GJNCr*oatIE+}MDxT2Bsno!;wY zMWy;SEQD3OLMth-9G20u?u zbV&YLg65<0Yep%f{K-oxk5)M5_R+oDG2*tWO@@0S-zPA&Eg|9|ZZ^-|v~cu&Gq(>5 z!(sB~=_-?V7Ari490&e>-E&fxbHnXQCrlgM%~m4x+Yvy|fEt850zV2e*ck3|4)8?S zNjGKKcx^7*ZlRI#$Q2#{p_zoHG9In_0gVbBJi`%{p{9;X!LC7C^xjMeAl z+Y*FLt$?BG@4iTf%9Fzr;rdKAlImn)R8oWP;!S%s9_Un*#%{je+_5hUVoL*!lTpnJ zd@sM>sL1&a1BUV)+#)&2`Qb5@@+E|E9rkVt+mt^LZZ&1NHSQfbLGrhbKqw`ZX&<102Q}amyo>IQqx88XL2Hmu@&;I|Hk?ZQnI7HqRtZd zkiYw09MgCCC$UPKiW}jL*>pX=t7|Plm5vhiQwT&cuIgOnayL{T zDyFm(Zj|RC!W(e|^;{6WhmY-!SZdG^&0sg4tRt_&6z!kxS?fd$@s$+lq1aUd)OVCA zL*^3NxQPGF?pj{oot6OI?%u596RHWfyeWcNt_Wpl6F~C;{DtjEnzzQoe9U+|z+T|Y z*qF(Sh!9HB(W2hXl;KrD9sC`v&iyc9m_6paMA{fHVo*IUnx@PstMWYv&#>R4*woY2 zY!(J_RNX|IrNWj>+B`00PG~D|RP2Fs_8UPhB?T zjKlYU)Q6CMl`N&R#yp49No3{(t){6QcfG5ND|-i6dyCbvq;y~AmcAb}MxSK(A^i9C ze(s5`&mCww8?E7xhv#eUW6}ryw_RF!#aYD*0!-1|qa9*oAf}KXhgDbB^_>pmqfxB_ z-KdAenH17CC7%HBUW4Uy$rfU;Z+FQgOgVg;YOK}?hrgM&#D0mIAM?RJT(po2O^>tb zd)SqY$dAqEK?vL6sQTVx6z=+PqKovPo71!GtAuaSY_bjnJ*DG_L|PlT)1{#3d!2K5 zh%?o+2Pg$YluVD;g63GgkO98t)L%b~0jZQ%>7m_db9;G({h7P8pJoM!P_lCvA&*Tv zD#1qk+iqzt2*sR*w!$G>x?HYKu`C^=f3l+5{AA(9lq(t|bsV5`;eY&!U$-?6q1lhk zKH@o^tR3B4TY(NdLi1Pjk~vLZ(i?V}8J2GglqU-)e0>V|;+RCq0MW6da~I#K+BW}J z;WX*i)8@=&NarafPmWbt@0)}-g|=@tz_gj)4}9RwxH%eqM5?nbdu)9n*%v#RG&QW( zbMW!roxaM^mINDJ*UwIUqzQG)dTA zVXI0PSj|XlQ&uj0!;2W0N6)^(pT)rlO|AvoROuidLAB!IyWfJP6pvsvV;+2tb!)yc z7-vcnqrS3zn(nNS(*05u@0SF8>d1#LE5aRzaf*O~#bg-A-=nMb1=f6EevtwyA6E>r zP~WpSe$eqdq+Hxv+s%l1DPL!4ONo?;g=iSsOO6$-!m}~Abw-31iXK5=&NdwhLZ5yH z%*>e1S|1{+1Wg9CWs?ZipG+AbldYuqXa2m(u+t7UD`Dp=37@lbjs6%FfVacxoz`R> z8RyL4G$U8C90(BnorPPmHe!eC@+tv^t0LB`x*5faocRo|SAcD@ax*|`&f!xz3n^Gv zkJutnnDiseaR2A-th;!VTIMxeTMj44aX5kbdd-_lK3uWd!{w`@{(yV3wp{JuamY0i zE zzmMQ-4o@dgp*1_7m-w}lebdY~J*ZL$@ezAbWPxvuKO%QinHcZt$l>#moiPqh;tdqh zirRr63&7mjIDR8qR7 zbJ5(@$Dx`RB(3G6lL(#8{;3t8p_qe$7CX@dC${KB`B^Cv0+k*C zvYLaq6wvYjsy3}%G21-9hzT{5=OzyF!^I)BW5V24?W=f518zb(7E^vMm88}zZ?X21 zUy((gb;aIBxiW$wSu&~%Z7%CWIC;>Amd!81~1UDQbX)@+AN`jlSlM()=~Z4? z9lMX&n-z|cN6%q~{MD*!xg8eLx|}R}Y&`E(-=yNEn|12#{!xuagRafpxSykq1O-P+ zShNsfPsDKqTWMwdi+a)Mly_77R{(M;sh{$RGnqUWrfL@U$g`;w7f$YMV&pyLuoxoiT!rO!cDC?E3O6rWLGNQj&IShkSJ61&lOuE|AQFh$@BFu8o(&-9l+=!>Qp4 zhG~aIuSh!=YuYkDWb}Eow7d1Ne}J+xHSauTr*Fr3gmRb>diQ?WpS>IB-^P|MlR6^s!N?XUGb0Ghh`?< zEymM1AdBx?>~(y%0++!=XOVqhz95LpY^sj+WZEY5w*n`_{UCgilZCGQ)zD&{az>NmqF3 zgIFe~=*Q|~IAaDEPPqesKG=Z8^AFmVhTqUM0R`!{~g=jNr zLg2*bat?!vOJN)uY4FjJNa?C6gS1!IKi{*joE8&}aWi-~^-2_VC=L^3M|SB zi?`<$(g@bZ!aXHpR#Rl52uktK6E z%xYn6P)N>79Bi-n&L^-fEMe92DW;ULIaoF6Z{LhbV)>n;>B7`@ODN{0K9qi;fi{1z zg&oM7d?`k|OIPn4n>9bp3;G!>elrOp5$2pUhZ!afZj9LxLapV1GSmAlNBaMRNWSKG zT-*tb$qQhQ(TQr@d=5t`4IAsyKSf~4*>C{cG1Zp4qXQ{;E)FM5S0x8K{$v3J^Z;(FRB+%kM069ea+_T5QbOL<$QMTjomA z>(fc#PV{9Is|CTk#oDB>!L8h;lE86(=%FhHnXhI}j`T@iKc!?0ogm=zVZKk34-i}S zjd;|dcT>hh6gUW9YC@~fBnuJU>;gspLX!rrdMn%Mp$#mE*Ay|286f(3?S6nU-GFwt zO-xCTxmZ8!zFi7ga0o22vJXMRbT?@0=}h3DcCMlldAbfQv&1G0%=|K_$=IUIyJG}G zUBCKG+szgsbo42Efs2};R8k%5Bl1Vg#|2vF^d~KAd@tGli8$8s_*v3q4D%rggRGHC z%HQB25d}GY9W2OMXJUMH!g1ss;Dng~YexI71MflLIN+g7<0vx-ZlS&vQI*p~Y*cmC zYasG{={q)8oidxT8Ykoy;;c&a*#s3iz4QREG-<`#^GOk7680%%`ht$Q^0O@CtZduU zTlv62%SBJ;8^o9ewFp9#TjnT}zGDx^`Nxqbr5qK@aBMq5+Ue4`LPHaDi&9p|IF9$e zdv#4HtrzEmQ>GbE=!bLFH%5!3p;jE#^6ZRa2(@Wd7omYZjW}(|4?N*+Gc8thNTSth|Q)Gg~)DSB^BHB++V3sU$@mo${AYV1(?CM zuaDmsu)EJ7R+tY;Py-&pqG4W*owiVIV7+TI{1hNvg=2!L>_jA)-GLPINYUh~%B0ju z1!2#v+BDA5D?$$DTiA?1ajUfOA!DZrHZ9<*B6l;N?Bf-N?@BD81Fz^!j}@hNC_+jG z?QW_tL_L$>MHUI7Ibjjk*D5j-Cd;L=SZ-kUqjIN;PbeXUUB<*iT(J-VB8No2N)N?8 zoq3dzYTMIug8lJO{5Z((;*Ki#oe!QO_m1GVjajPd>x3MTpT5Z9ot$Rx-(b2Sp&c{r zv=r@$tFz1-b?-^m(s6`8gsoV; zr)Eb1IHtVsqmAPNn}v45u*Xr?gpB+7qIdw`J#aVUXSv|n6mG$HJX*yUf50c7O>GJY z#2O!qOy#Dr=*?+|5{L*-H_JJocYs&SsL0}d@dIBVBaP(&!SD*@&P606q>QhgZ{Z1> zTtjO*8`O~OODt@9!0T@Aou?FG0rjV5JKQx1)vV@LpllqCH{xN!w_YWH9onA5E$zrR z<1o2r`@Lm6R$}dtZZxb} zirm6{9(Dq|P|Ky7LO5PT&0@5-g6!D7P>Lkxx@4+`Ku=yg=enE;i&F(WR>(XfZ9G+G zf8vH{BUvJHGSuI01gdS8Pd`^w#zvX;G49T-hS&!YV|yy8yBmS@l|x7V+60qm=+@QD zv@Mbm<(_+_c%h>uctUQwQE=MRI7@qfr=Rw^6E)GA2ijLk*50GDs|YFmcxcofHkl|g zRpRTzdb*;6bty+~szKp6u?)_ou_v8lL+RR`?0DZG!Xo>l=q8Ofx($=nT6@8-XD8`L z1qWp3$|#z^K|!xfkGBSRjPX{s_|?7yC6ycy&qx-qn}HXcX4aEKJ~1Jbt$YUu)m=V# z8a1TFf?|oRGUUJI3*1{v1kK6dZaZGvcGR*Y4YEENZ@48Q#5 z>c@UmXKt+Ac(nOlO!1!HQ($}7=~Ix#K9|qCQW+$^kYdvmU`KPJ$D(I||Bh9u zM4R`oH~~=*lk?C|PQ7Nv4fMjWK17hvFtWN8hPm%JHlR#S@>`hp8zYd%IHoq>@(9b9 zblGj5hyWPJ0UgT+R8B^>Kv=8VNBJXmdZR-sq->ocq)ASkFA3IuF(M6w6o4~qOaPrl z*8Eu8%G|=_9?GaZrMC6bdiUA&GH1qviX$IQ9U~@!iB~4tM;F;d*SJa**cWnZuR|@C z#Zm1ASpDob27JEQidi3@bS@mj5 zz0gw*WgN=YV4eoKX)~EHg36&7JB0rYlHEL`KPW(;Lq;%f9B==0<49ELrXG@$4mWEt z3&M`YTZx*2K;^rXVTGp!S1)L%Tz!dFoBjp6dSWiN!6G17s>75^8sPNoh4L&lO&kRV^1*YQ|lp1q;!0 z%g}1)9&jsWtYae8;tsYM0aafUgtM>l!Pm6A(wO}st6*vKNyad4U9lC`zHQ!5j>y`S z=^piZf71>CX3}=pJh-tnYAuGrjf(DA+opF%)Ako_^;`tZ2Zns0)RhBPZ2HgGfAn6> z0d#<6g7x`cfmT^X95I_*qETvS`Hr8!WOBbH8CXR5iH;Wqp6Z2}M)X4BfJO zC$mGwmg29XK>CvH0F6MC;JI@y75XUvr48D(Yh`VnV)YJr!atF6TIQanJ=c%H5B=m1 zNN39*+1Q5&*b-}nrG#VR*?GpXvQZ4uV#}}g1^?;VBm|u4Hg0Z5a3dPm^E>5=uv>U` zOi<+VaVUR1?>o2JP?y)WZ~HPh9Iurga^@5j z_DOy|g$#bgfm&tx!;zmHjEoOyj?1%w(4It5gd@+-t@O z5-tVfnHsiZId01jORV>jQztplaXfY1Aik0&u9D5}L8-BbOcEE3-%Ic$V(0N)%1#6G z8wq=vn7+)6MpYepxuFP7LEy?o7+F(eN_tLAe3sn`1La$WG=R;hcZo|vD@zun?KN(YG2&D^UB*IeGuzJ=Uql74^fNX`- zv|@=kkN2INsTM+g<)zc-mt$~$GXbG#1m@m3JgT07tKBeE+Pl|BO{JlTI{dhNO0K5b zR^s5>U6K$$w@v}UqjiRq5H%eXXmajjmO&ex$d1qx=&3y#NzBIUZ*V`V;x*JNhZma1 z&eP&xN6lsyY$n$BaOjX$%(_ma(#tt=r-wEnD5Q|7tO9JZemt{Y=qZRZZ;16W^(dRP z=W+b{%C{l3k$3o`T%o*ifxBOFXdbD6qLrmDa>yz!KTgqfize4jo1ZOdSxs}UQUR`Ev89P9NL0dUU{( z8I|?F$}lE^Icz9%yKO+?p+wC^4t@z)=W~i(IJ=d4C&~Bcu-Q=+2_|%!gSWSJb&RpN z<%K8N>rid2l-E3F%LLCqEPcY1mU~STN86OXMvr(;dLw`>B^n`e%ML35COE{~wmE^= z5*9BV{GXP$54^5%n+Tzw` z@j@LD8fb|?5>)h)aFkzzp{0%?qo>Pu_!d4!tTztU>fL5HZ-ty^!xcD@8cE=UVUUZu zu@z#=KLwJe5H(&Ca7>N7{;r~yizfqXE4#S)y;j@HM{5NPr|7N&og-GD0<93xYK=>1 zOC!@BKF-LI6w9PrtIo&EOeYfHj^P(5RQ7YU|0?{7IUHg;>RY6fw%Wbvuq)}Y%qVLj z$_(6zSeeaq$Z{hqE3k*GE6`4Xz~b`=YC#s8_q*vOR^23s4f_6}#Vg}%*b``0o2H5S zM_ZYydm9y2>@B_1LKOvFicGSp(mc1%=rhdCKn()>pjE{*l@2+EL+{^LWI|f?DVYuE zkk)c2YR_^?lUq^*I~X;5w5xjZ^t5)^W?`pLD2;JTMTFt8jD|!9crFJcSJc#AnlT7- zUM(d(>M(Avm0fw}4V}I3s7N`uX&y8p2JCFkMhvj{Y#oaZipqxox4){b z#_9__@nT_mRC9_HI#6d0h{`Qh0n2GTZA3!wvLZQ+O;j)NA5CW`d79HiV;#4ewWN0= z5*bvBE6}gE1JjPHQL#!a@%%?p3a1_3u~u|-6t`K*I-Y7|B(<5tdvcfu@&!{*)Rh3| zGvfTSH-#<;B-2_dM#p;X4bo(7^5y=yYvC`Ha6XMl{>DDr@WQ;VV!k(+g$0b=@r7tx zk~dcNEu8<`9`qVBx0p{@XjpD*SxfP0o2qpU{(Z2d6KmzBirFy=XE7TRG3#_W)$_eN zYPvHCEoim5vPyFU=J?lf%uJ-SmT~b74I-kYUGC94YS>8ge*u=$%&KlHQcU9+c z%fVgzdC36aKj&4e-W$fud8ASWf})H@10g~)!pnUk{7=8(cRD(uUC9!Y%I_yVvWU}? z`4B?U^8KV5G!%BndO7@|cm}eZQw7vZ!#6Qsgn?=&lLze`=89H^T{>a;dgK{B=`-?c z3QMWkBP_*Vkwmp>(`-dXDe7K&g@02I*0@KJOz*I-*=?DtC=R)pbc`XCh{BAAk$AeW z#ZR>nkVn_@filt>F96jAE*+?l&Wfg+ri_-U_4Z}rbJ2Qfz$rUx5W0qnPi7J4e(#d5 z(iseIc@LGkVpe1i^)oT@mS^Up!cFscUolZcSP7S~8sluW0ejAybBBmq9@c|CdhL;J z;BRCyl?jr3IY6(R`(`E1gwcu{Td|vNy}l}1IP6b2-;oiVh?0FthJJ{l>q1sBdoo}% z*l#kR9y&RkKC!_FVRW2-6PHbL5Ex;A;hH4vc8nr>qSLCJF>fN^#*+y4Z`bx;6*GD?TvcJmq4Nx81iIlnaPad8NhG4ll~;jdiIMR24A#?S~=C zAxMHLnS^*M8cr<_K_v$vV5`j`#R+(o{^S4|0Eyj>Z9d2?BLy81=iG}=RP%{HmWK28 zZX(u(Z7m&8w_kQ^O<9MVB zpRCS^lFbwR!b_^@mvFm+>1YFRoL9H5*>dzORU7jrp3!e}@BNTm<+au|Xj+hfqKFj< z-)Qs}2h_yG7S>*JO78ygf9#<{T}E_C-aHDV=GwY?BPF1|yazE||DKM^>sFSL@5@E; z(!WpVwp=6%!Ks8I{7+@n0)eo(0TZsG95?4w@;t;GvnEYnKNT$gH7EWr9P;M~wlmX{2G zgOYE~+@uYH0^#OHQ%pyX&?(6>j>^P^S4?0s9;87y^#vBnzzedq>~e!zr9MR!iYwA) z;^JOmIUq0>EkAvMtJay(c&m1RloC$w9FUiChzE#}d{@WEG`1Ub<6DYet%UZUU7O^| zt$PSmA*JaKCW2v6P22UHbE-`qg^@-~0S|pIk2ZgxJsPOCRld`%MKlw&ht z5&dn#Iyoi8_!$GG-gNfP@J$U)MCr{LW8cbp9?{3C{17QTrOuE))5rmqvR5?w?uh+y ztn5JUuxM0HU6@+I)Iyq^H$)~-l)nogjhc&T3_v!-3(y61vXs?#W<}Y{4=D>0ZHc3B zUSDBe={(yB>C#;{+7jP#K-FRXnO{gvr5Dl*>}3gtcYYUzH} z{u^TwVC!Yqub>j%ls;(CjsA$P+42D;7NzU;5izTuLMCDL`Ki>EOoFhL-h07Y$=&6` zgeoI&og9<*kt|Z7^_m2DeqCDNDbUe2qZo>9|J&w_VhL~IA}a- zIo>RGdLoFi%9UuJ)PYaz?su4$0f*>@8thnM#_oj2eeu)=3Mrt&MuPwK+%Z}x>Pg96 ztt~P(0#(Fm0L@*VkPbfHBr7-%ULQ|G3^I=BVx?xfNi*TbT<3SQTLzmYx?7Apizv~< zcIsVvWeZBb!KZ}*A}q>nj@NTAtlI9_Xa4+r(Mhp+d^WIETovDqm~0Xi0S+iMV1 z%!}h3qmk^6-_*~TG;k(Ed%24(gO2k2`FCeJr?MBp5D-o{iv-TfjE$*4KQ&8xJ1bwo zlSq=ZOwtPuIUhTVwv2o@=>%p==+(j>6z7Q&?A1NahULM#D$caUxoSu21s0(`RzrF`N zG$&DJ!`KaQ#&8w27t0-Kk;UeX1A^?bx@zYqD<|4%sQ`El=<66AScJipEUN9J#y?kJ zGv816OiaY&0P;E8E?p5#d-|`W7UDDG6N-}ai{V%lr*HhgG~=*q&*cyF4NHO$mkyoy zU5}CH_7ZU8hk#^CFB4(Z`JvsMZm+&qME})+_LF3D1N~Q`b@NjJyc3{1(WhwR^jVaG z`)`_Rc+8vYuB|3HogMTd)lq~XGwqI>$WDmkvjs)5{~jVO=HlQZ0oe1LME>P1mBucj zsTnjSc&XU?^s0msti#?3wIQ%Ee{ ztXY%VwgM?Eq{bsRw?#cc-RaP!#bh1S>!p~iSkrY8-u2{Jj7i@IUgwM17p}~^ERdba zEx*>tAIXQW>{oeIxUqUQ{E3n@tU+UNIZt0x!5<^;2Q#4z;_jCT`1}wX7r*ZF0!k;1 zA);Y3q~RdD8)P}jN_uQeeChrxG9E==)z6=1GicMMtQ3~meUGbkgB?t<^aI;*%~g<_ z1o`rk2D#tuaoJ44lRiF}zn@Ls52#15C&AlhpMPElnm`x9rTM-<(*#s1cF= z4D@)gUku~1&m3b;pa@f58nv1cEbN%otqrVEt0q%J)RCSe#~(#&Pey$HqUY=+{nflN z_03<+Td^%x+ngX1J1?XaWy?g6N~@}F7_D&TyBE@yPX~|m!_V23`^Adepu~ipfxe_( z`PFVvxn#fBvtcwb6}Eo+rdTD?se-e#91 z#0sbQ>Ib=QLN4`_1|k323DC9nV$D#I)Im#)+&5BFevbG>50tg+Dn*ll1K9HNu@d z?WHW&IdmM(ro^1?dN;^Fqufx-Zj61rKJ`Ts)3luQS0j!{qKv~nCN?s)a9#YbLb7y5 zwZKWvd|hZycF}r7m2!Ksu!yAWzMUNNJEkq?Jr_3LiAXY*nz46AW)zXvBKhwF<=+3k z-N}nv@L2QJl)NP>ha37ATXbx0r#%It#6?p7s_wA2(BKpZz~GBOTLwwy`d))vX0Fra zRBXIQTRTq^y$W2FBUm>9Od4t2qSxD7C%lPr;8iKa(OZGJ(a5{|Rrees86DhRzOBh% z)1^Ukm;x!yIi8=A1=pvmTOT=eDF{#DDtLRpLPsH@wkTrWSwOkJr&j_;5&MJG_Uwon zv^&jkZO!>IGxxaWKEL;`f#DSYSL5>H{nO6Egyx;q`|%3NRDT!r|E(&*3W#v+Y`Zx+qXO%@oBcc!RB91(%1*aXhAGaLP5!xe_*2!=(ZHJ6Jn76aL|>(I}m!K3o>vyY^Pq!P*8$xxCO+ zDFRWJWenxsCC(Go{SoUOH=ou)f=2|=ox8$0IDEf+ShM-=ygSy`DX%3?YV4$R4wuBb zjQ!)%f+Oc;>_JHR(4%*Q?eZdqzErdl(g`;dRKcSzI%@^QOMk4* z!r4&va)Hg%fg9{4_onPzYTyp6ReOk4n@&ar%a-;^U6 z7TPCM*0NRH)XnVoCRLuysC-E9t?HAHHE76-0g=0jYT;miGRd+mgWi6t|*>ULL*9v}YPgzz7%6X+qubuQC7P&>jJt;q{ z3Mz-fHp&6DbC*n`>%%uzKe47GmhEkKc?oS^t#Je8$D9JK9{;>)=Sj{>fI)9JV>$+& z*$-v`_A7}plTKTK_lEjz>O;lk+X31jY;|}7?G6ARtx^XZAk6O6%rhmdRGz>_a3D)y3ye@^aJjL zH6dCkQ%Gu-`NAp@=KDomSJdf{2E_d{NTH!y+wyBLUV2*GIQmqIQsw=o0Bi2P*YLV< zp`R4QI;d1fnWBjHZ}+=&hgT>Z%G?VoHEDC1=P|WzJ(nR(zQcS%EaAf{gz3%FHLkii zB{y4A%!dacn8k3BoL?D60OmSb#o-L|&{;9QPEpFYan)9rScf-aQbsf1GN1iPwW%1# z+zhtcv7vJc<7HN``tD_Lkm~zxi1yj_#I|47$ZJu=TJ$nUB!;eeDXU5F3SngmdJXXH%OrIvi7^OiYD@X z>Y+S>TiW^I>H3(h&auLuIxcXWv6x6JiyKp;mCA{cVF9Rc0aK{CbAf?5cc8r1>nO03 z4eWv&ZAXD8@Xa)i1qB%Jy0&^+o#@eL<8<{3c3<3UAD!PDJE`oMu^YdXP@O1T9HTGX zalRUdl(9`qeBS&N2j9-|dik%h&fICv(Tx6%H0lQjsc{NTxUL z?Y0w5y@TPUbuN;N#GPV&I+}4+cPPnC!q;ByD1&k@C-pS-gg^G~%~HQWqppSzNU$+K z62>Bsdb}=aUg~J%N^(K^%o_f z8}|l%D&IuVHT%EVM{c$kYE>(;-&AbWhhw_8OC&#&)$tmhJK7B6Yersr<4^OE@(Z(<7ZIoMrGqNj_d0T+Up5gO-&y%GK6~Yk>}NkY zLiuj9DM=d?x)~?z0k8xQ+96L9g@M%+;MQpFOQ+Que+gehcY+Hqs3|ge9)lMKFz^Do8-VFu2 zJzGcDAiHnz6h5b$yb9uU zWzQ)hTWybd%^)YeB8w7Vd5Sd-F8X5q7E}d(=<8kBgW{UnI0itiWWBZad2|IOZNj$B z!9W*HebMTsM7A{Ey51WLehG}+E|F&=iwtS6%*hZ_F;o8{nxY00Lt|`rG2x@E!5!;asR@LO$iZvwF@}d-; z_l&lIfGJXN2pdEQVg}oJ(2dL2mZJwPuo`R~rQz$Jl;&XHQ z`$87n;WXJBTUd1>{AR=r0x!jCVPd#iMQnwl}S8_3T*lDS0*|wKPI8z7j9P1m2W(%Bfz?-gCO&?)urF z?D;8idON)H{xc4sLZ-DJF(7pUeVpV$T=A(vgo@AE$mmM0qNJ&WeuS4)+5kv`g+&Tt zLEcq;#mxCEU-uq@?DH;?QMf7$7TwY|(zsW_K>t}tUuZjRlScYB(8gEcSof_SSJF@O z`{f>5%KzUR(&p|B8R1m_{Kp$=uPMm?CvUj_^oI4v8~)2D?3{&88oOHcw(?DKuyywZ zXLHt!Cepy`?=;^(k20e2rLne;iqC}Vp|Hvdrd}7Rptv`aUM`a3jL3!85x$@8%`zdDWfPzjQM;*1%16o zvr7O9;{-5rRP?bkAc+E-Q0MTkV?AxU+j^u`bftT~d-^M_=mTX|Jth!-Uuh+NN)tC? zF3W1k3+b;;5`43ZzG_CA^z07izCI2EVsJwBUN=Ozl7*J?JKX9$b%CFlVxbZea6I!B zt=(V1^tr)3R@jv%(^(xVym;Ab6Dln@QgF#BOQQC*D>Tn= z85NljG-`F6Cilq^N?K9QP{LL%dS%jqV&1k^%%_g_!iv`9b&L=~!Oop|Ro$KUxjV&O zTXWMQ0s>J@DN}HxR4A0~l8{p-e<^9?Nhl3BZST7p>s6VF@?gW$c)G5eAQKSL{4Qs% zLQi{w+fZ&qJD1O(!c7JG;uC$paBe$CH3oQt$8||_*Q)f^QnBP(K}}u@R7<4`hCv2J ztY)o~Qd{)`j1Q;S@**tkEL0vIrV@=IfHWylWK6>3k;Pq#agkOVw)v0-&~&qBhlWn4u@8P{K%i5^;sKB-6ge{ zx7ZHT9bSd^M#^yNO2&(3?IdgwyRebYoWrJwaq2f<_Go0Wi=O{PAD)+0iIx+E$gk7Q zU=z>J-DAp^n*EhXL!!AVfqR?Es7I&s@sTzpjw|5#t9XX?9CFSVh3Z4hsf|-wT<7Qe zY_=*3r|ibvaj0OUk1MCnl7Q`{oK$3lAjJen1mAC)afB6_`Sm&Rn2UdJsE2bJ1NgpJH> zbBndMMqa~Ot%uNOrZBNA`UMU863>%Op&i(=(&>r)bGZCVdQv<4X=16EpfQH<3KhO32P^$w0#5LTqa+ZwpAG|@Q3}^xZWc;G8qIiV^Hm^-do8XcI(*7-JrVk5?kDbr>XkUVG>_nyJl?A1*Qh=ZmApm55V^fM<+Iv$>(;#y zBx({%DY#Xu*3oz7M*)_jXlKSmoZgTXemizO4sd5tYWP7hK2v?Ity)gf=x z6NP6c(r`%4zWt_8X{8`Rd&%<3i=smr=T)#ht>)nZaZon)3Mf6?r1mHiw6T6p z20UTk;S0xWUU4c_|Kw!YMm9DUzc74nhIaZ`wc?26mxh>J7y?^H47=&AK<&l&RWczx z&3Lmu+PfC(j>VXc0c{b8N;Y&RGxd(Ko5&#!EO}42b2P9ZP{hjDkF#k}vlH;$6&ZV! zJu+`xeADc`3?Mg<+s>MMndyQ-qk_f^bEpH3qg134H#HyIE*|#A3P;~XJVVoRY}ux8 z8PPBydFPID(5a39uND zc{)*?rgb_HBydk<^fi&Ick13gR#CnSX-%Oh;^L6k zo$5-Ch3-5klG5ISVqHKAwjMPlo(_|u>3 z2OIQ6BXU~SL6FXg+I#R)g1A7PNTYlN*1_1(1R_sbsV;1Hp=t9hKH{GLzTqKTGBaF(+M;3GqT1C%_E6b zJLA0}5vYSIlP;W9%a^JD6dQm&QeeMdET|f<=M~@kL21$?ekl=W`&Zj_{RuWox=gac zdI&t2RM|-c8Op(F^s3^4>RmH`9*+~??h^AX#+T*8-D~1rp386==(*Eq?uwWEuB%gz zWn4mLkvgc-vegv+Nd%d9=yD0l*uRr`=-WJFZa-K6GCLdZW^ z8-M(WA3*$T8gq|u6?D#7R5A{9ZEWLy?NFrbt9WkvO!RVGo3+dk=Ip| z4ytX1psk&y;JS_lVceGxHEu=8Ri2Kyk5~co=2jh(4sl7Zg8a-UWtGyS9J_P~RH@$U zV8%*#%^l}Cx_<79h|P@{W*r`MF?pk>n?6)#2Pp5b(YL^fIB1U(Lzj*_c7a05o85R$ zP~r8pV*oN5s6RZZ4yD~!!LR&h#gASREXhX~jOXKRPYpP?<;S z@#pTEf2`!261vXd@dRLtm8wl1g;E)KNxJ<^zm27D{ZV_CGW0e{j=o#)go!82Lc+n& z*497Xn~97vctgjHb?kj0bdOcQ>-jvPHy}adC{N3Z@~a=}UJOaaHbF)tBs@QtD0{$Z zw;vidp~G!Cpp3CVd#~5sBhT4pK>XdpLTO{LK(`{Y0YrY%B9mnr3uD z-b>$*={AX6Q?SHZx5iq7SJWv3G}ydm-kKXhztxAPERTem&l6?KmBy2_0OR;CHh?;l z>$gYYVT9be*E09aN2O}E7Q)hCp>}mga~BwK5DFL%UdIvj7FNr;D`xlh4(GRTl$(Ag?<1%E4()KAzJALJcJ*tUSA1+BfI3 z*50U@mLp}UlDD$6dTJ<2I8o0}!Ti%MP4tnQo%`IV220!sZ*_Bds}*8YMx z4)qu`A#iw!{iZMYpygaGZ-I5P6^12 zrVo2mGHjhaYK~=QO;y;$yqmDJNyQB`!Kdb#nN_feX6%OB#Qj;0`rZPWI9RT=@SI86 z-5J@IUr=HxwfZll0H$GY3}TPGd>%cg;-XdUj%EDz^D;h%4oc!xA{m{5XAizy<~7le z4nI`Lc&eIX6WV;Xp7u5#HvK(Sg^d`SXb2DlT)}GHRjEBGg?tGPVkb`>1E}Y~1=>9I zaOdk{?ns{toJp*s$9);8GG;s?oQyP4wslozmb>QxkXptTox+>W9blmpT|paL+vbU* zrej~qa<5>O(-v@guVo`mx0L(>ff6R_@6ikWFRbB{L>t6;mc| z%*-Y}+stBU@9-E%4S;K_TeqfZ zziV%a%kYRp4DYRJx7tp2|7N+k->+<)CwZ;Vz7(%Y=X$rz;@@Spp*kmx0bX>1Y46!u z3<@}4+eR^}ru85&7ApY9S!bNzsi>X-h`C0nQdX8<^Pe@EDqP?RMSm5nv=aOo=!;$+ zyHhcC?9ZJXX$rkz4?H&0eFwX+U-rIM!abPae6ZTZGh;|V=-eIuUCG5ZnopIhvT2`a z>-yH5o3Ar>iFW8U{-452{+c12r6A@Oiw?g)KUKxF&@sp|7St`l`6*&4NC>bTUY^cH zfUgxBCmkwDlHi>KG{j`e#8)e#IZHrC57;nUw|>D{ipc0R>Aj=O zA;V6V9L`18>EdGyB3R4ly7jN&Ku$NnV2Pfse>q6+zLnLQP*>+kyg`| zC)r&m*9CC4g>=7}jr~0BF-%UGnNE515j>+$IhD^1GD4c%Igi=eZI}98BGygGJ>iT1b9`aBN96H;zz3LyUGaTJ#moa#(Cy2Vh|Zp zp|27cR3+?=2Pp@|DGMit22Ibd_~nUI=JVu~c`X&Ap{(QkPgVLPYRY-QX56XoAPBq}pYz4*eJf=s|+L%_wd8K@fk z^nJvVZV(bw5W?Gn67$w!O-s8iLO*NLM#|bD(u0(;dfzUV*wD^JEB&eJ0lXYWC+{IG97d zC*k5KHJ9TsZjyd(x#}r4tHoF{QX-9kE?V;@1`CZ_>1iFPw3|&9|Cwnc1T_etSQvwU zs5hn&ILYzIJ*L3+$<_D|M_=0FaZWi|H9~)(-_;C$rHx zVY9^wbT$boSH*cJ%44~J9{fgWe$sDU-jG90F|zlkrmj~$g2Fk3lzUbjai&@Rzl7^bbO}tbz4EUnqihBG@iF$#H1xp%R2feK z7B??MeL-zg_7SOl=(|use8i$(YAaQW7L`{>H46fV#2vfpF9L@O0G@=p0@Pft3Ixef zBb6%4Kq2C7K#;WG+(E(CfCxK>#Asn^UzE#LY=#!7Ulux;^h#^mo;i^jc33YxvM|kv zf=|Wj_~iC3Ua;Bx=RFnjwAjKME$hH}|(p*BiG z&F6EoTDCSR>LA8DK(3XoRDe2$iTb?=n$3atUz_UTDeDP9j@1mq8qDx9c!nhAn2wIo zO`=b{O^6?k^Q;xKj)*5u(MdSI`_yIPp>2y{;mUjv7)vjU(XEXERa+DR!x_X0rsy22 zCRXNngoTTk8F%cDL;^?O5>tk(Tdq{wVZd4}S*7lGY_n0=d!f7zMZ~x{P?TTKKik^I z8BF_OCo9!)8zQ)9%j$7(qbI>?Eg3j(1n)BWaRQdb5MHPKjD>R2h^z~H*UTucHrHE{ zDa_|BLPC0e6^cGMO##O@p$f@IT1xL(AYj0rDLW?_ClyQUR$?t?B|!HgX}7>7nOAos?x@#5$t+T%z=r%y)ra2MYJ(WYWMbS!6;;&fqx z8v@+FnAG2UOEnyNVCf2&TN){vJ)0SM|?km07sb;>2nvfo1vXn+QU-^$~!~U zoMr3Tm{5lQ^PMtG6ki9EG*7BSkNnPIoPE~=g5#GwCF_cc$dddY1@H_E7}b5~|>&bEwVow;$sO-p0l5RaHU zsC0>q4_Hi^km%1k`|cdN*0Q55rTJ#KXJiLi_#8HpWH40c@EBJRnkf0H(l%vLnL-2k zW)Gp(Ddk4SF1GLn@LZiWpX+<8cRTFKf}=lU*j5==Wa{epOptRE8zl<@2n*WD;`+(r zP&D7U@_gt~)?Sgvy|redZJqby`6+Jr`Y}^%3zhQ?AwPH>!q!pS^NE%JI-T+d8L-ZX0~jJv9yv#OR77gx#$*%bFuQbHs$ z`^Be7vwF1y-J{9wNa^=O2oNLC*OAA{z%@!Z8k($=kh_=SbVWC<)GU|aoiA423C5X} z5nlfsSi9qe)qR)U)M4duL?@O_A21s^nSIHeUag0mK|w-HH)A}()>IW0A7V~plX|{i z6mXBWCMsAe=0el8>Us!Q_Z^gyFY1_B39sJ61Y;F*+&NBD%R^vh@813H12-C=iF;&W zawcS=E_h}zG<##PyY_jZCoQ23AIHDL=0^61RE@%?L162ZV+}O!!)_x)h{6Dhq5$=3 zlH(k(WOW=DUgm8m`HSkn7;YlxsO+o^j@eogX_V+?qfUJyYI2BrPjt6+X>C8^O0y?c zHFowEc1+@|GM)7Yg+OU8msfY1t-F5E$`!`Y&JPuwkQ($vU*eC;PKm^ErX43t*2$zGBqesabO{eSOZf z_q{t4Rup*beu{ME9nAgOv7OcqYBb9~pRrMc!pWo?{)~p?p3n-v4ZUGPd(wDS)&UKm zvDh#QzfaguZ4AK}Akh^bS;>NL#xbP7>Z%c`eI2ArP>K0Yc!$qLi;KjB9#sO>zL;ub zbK=60ZkxNE;!Vi0l0HoCU;|?9(4!N2tlm*7cJEi09PMOC1Fuy?ApYgsM2h#m)`=nd8=q>Vp_Om8*q-^+8-mZ>Fa1wP zL-_ln`aUpvX=nrj3Zgyo(}%9SxfMh0C-o^JoOJ&ZfBKT2kRthUZ_40z|a(ws7%CP!iwrUVRN#{ zTarj&&t+H4;rVk5AJ0*vTSxX9=bjQ++m<)o&AKT^`i%4dg}oMwrlCWYFan6Nao)pg zW$uv6(ZPitBW&rO&Ng2_r*!ZKCr~ir-@)4IYT%SM+h4u?bDoC zr&;{A;vy+qCNvf|R6LE4W6A!lOtR9yBIVZOch2bOSjP%cpDje3Iv&B|$u-$A`!lpi z&G*2+gSY!lHYL5A&L3{Ii5$LGYhx7XSv5ECvM9GhN7`~8nOEGk4Ky(xh$PlMP{4>a zOLJ@)Eb8;Cx=9&P`{U%wxMh%%ax-BXAwwNxhmoiR^n96uI~i}8CP-b(1us2{cOZ)| znLjd|sS0FC#-DV0V!$P8DbECQY4mLwd-eb4B;w(Dt zrb%f>?@vR!dNIO?y81GLFK;&gD=y!0PvV+LL2eA+i*12W#Rt1zr_WP~*xA@C=y0c7 zwE{L}50z!77z%S1-3T6yQYsdUH;qpvu({Cv$!}@4PV#LxCQ9kJZu)zR+Wxv{E0YwQ zr$E@!Swk0(lX8>;RY5|E9V%!d0#CHk9XUNAH4Uk?9D3&un>5S-mJ;!T9kAD{F*4&# z3_f`CO2>L@$NO5A6Fw8;VuaSdwP}SPn3y_Eez!R1nR|aIt-QBMZvdw1FCcb({9`7* zAuARH;jEY5Z2JdXtvx;*H*-^WTF08ehbmE`{dtoYOF^lD6`!DxFu*P$w&|$ey)@^9 zF#ZJ@$mmg`;7OAFl(WX(yw(6wfX`OM>pc)SU`36tp&~e|BQvhpv%%<1pgYPoRqouskNM2DU?*D7h9o&G>)Q%E zG&Ak7W@lH{{NbFhm;@%`;Od%%+R6{Q>Cb@URX*8U2}=6t+YL8x%kpxpq+Ij$tT*iu zMW;wtf)#uWUud_^=MQb-o&Z`X^_{%KGCZNZn^^-|@Ptyh1wX|hPdk@n!rRBVhgTS# zAgiICC3ZAIclQoGO)z0ZpO;1IW(c=d7KQfv?^mcHf$wk?l%|5}HbuhVMQc!|hxTgZ zdtB;!ikXyxaCv~~h&Sz6#Wf*)YG&$y#x++ZCmK(RE+Za)gike{S(2U4yB>rBY0rmpB&RRIU#b-SI=FIB6>r%by%O_@Ta7c0>1jO|ClG)aRw7Sy#UI_~6Z zK{!Gj11Ud+<%*0B&(EU|Z(pe_&}^Xx00+}C90>rV`u;eVfG}aWye@WV7h0&I7d(C4 zb;r#KQww=;*2Q!lLhdFb-J%F3v{lYAqL|Ag({ZYl_JraJz_s_uZxL4Bs5XQtqmaxG zmE6TK*e%@#50$LO3KPecTyj!fHZ%Er+5je+!1b83gQ1eZtoYHG2!p+=52qWQBd;hCGs*u>lF?4{(4cf#=zwfiKx0VB_;U{BXZ7LB$) z|E0UHSjI+IBIf9iV=eRc2B;kC)*j<*yjro78^cKsGl5gGF8d;`An6y){A{mr^VvK! znm>XM*?^271$Bl`O%yLNqC?_O=)27&^nR?AJZORBSOv_V+aJ^77=t?(@Hc+hbxu78 zvxIFqaqMIO1;{PCkaFVB(%#<_O%7w*%Mb{TSB*ZiJmZ@SXEjGO30Uob>EAZXRXNiI zletd3v|1?0b*xw@U>CFEjxyu)+LGD`iRZM5r{2Tccb2w)O3CGiS~kmo(>AQL%#_a+ zrbj#A6%h$)gqR>5c*R1wGRP)MR3{&<=;@J?{UNxuh3sxmq_daLxV7)P<*P6zDtN6| z=PKDnb+(0^N9C|$u?zif@y#_299yNy|G$G6+1)w{BLG#TqB>Z?%7i^!sckt}YBN(s z=^-iDv=i!*fFqX$O>FDsoOrV4&KI*R%L_jD>JVW}`^0s~8MAyGY7-y!IHiCeDa)2h zOi?b10>F&JTnAq;7Bz8BqtSUQvy5-S3^Qn7`*i@4U?w|oBWe^NW6YSbgkeo=^D0w9 zQu;VAMKr!BA8zBUnv*DWUuh8wd3ug8Y-Bdmy#UQ$NL0Z z1BXRg=A50u!hnawI2k>1-^PS;#hlpa*`Jg(En~%*)W0d`&)!>EB{3XIRC1nAGL6OM zwVh$~7!U4dBax;RL6NP2PrCS??5D46o-m!$pW`L}9>?J-cc6F&l_zIwBg^^tR5l?^ zPDd-^7zZUuw>YL}de-8{xTe(l<`E+aGtjYK7E980sxdb7)vr9sv6YZlxr3a*$B$Nr z31?EF5Xo;{{Op0o{@J`p-@r||yAUcq|7#4X1*JK=()E_M$N$IT+gE~BE&90z6gYOB zD9VyHw(KoA^wc*bm*UPdDIIeG*GnEaSJ{;9uLW<#_H}ua`x_<7B!!g_e>qk{n@6kiTTf@B zp-i`wZ{nCagy&4#j~wjT;%A%vQd$EHAoS@@IeEv+3$!wo$}#P{O!<~H{k^R;jj!xi zPozN~OydJgB*N?!()HlI+j&k8fx=k|K%{4SmBy62CV#aR9t)4R98|VAmP9CT3R;SJC$1@JuoV`4L}?%ks*G zUMx60*qud8z$;SRUeO*+)12y%)mOkD!Um87ayzjoSrw?a^6p&kJAD&hmS@Q@+7njQ z#|aS?m7Mc^az3+Tq&F$kad!6#WAq~#eW%)Pu`$oaB|V9F_XZy=;6sqUGTHxsE$@NwHCT^nEtbRqOj$oEiE1kX6T4T~PmU|eupsE~x56oJxK{3FInGtT4f1i;eA zxT)Sgub&`$ME1ml(laTV1-q@OB{O4LMMCc_CXU}Ww{y~=tm~7bij3lBbIB*T9Qz#3 z#Ak9YHe*^!WF=9Trs1_?W%sBfaW8w$8bf=G?%Iw8x)aONW8X>ib|B5wzBHpm9BS?K zC^&=5;D=9^1RT>jB>wh0C=&x7fgAe_kWqk|E-?1i$-m&S5{bRS&GN1Y_6Cp2CRR|1 z`GlGIP0#PWkfEikEPkGE9f3^nfbZZB_tJr+%rlvI+7aG)>vfujLje{t^V(R^%G{+V zK6Jb*t~2_l69#zi33&1K%4;MOnN4dPZs?Ov6>*>d^9K{-KLBL!vz<}ClT4N|Qbcd1 zy~%th=+7RxatEtN$`(@WnQRNSKzxQzv!Wz|Zrt!9j$6 zRw??_Bo$)32M42e26ka$T~a| z{Xgn2ny3Q$=vaS$xHs_LF7{r0WZqiCa1v#)1-&bfuuY3N!1fAt(WF$W**T`}x@U$l z4OX~GjVin>0=Pp!IcXde9K?IZizq`hsfHo)LIs+2XbsS_^_R>7AO*(dLl1I{x%FP< zv@H$spn&xtWB(SqIR`wTev1|gRbi8{(3c7xPs^WlpOWVO?*@8?64I#R;)^kb7d^GN1J?O!mE(6iRAu7 zD=p&035dJVxZ_MitW7!Ba5flygRnL?2%+;z==_vTXJ`Vc6b7lftzS>CZ%u2(`>^2& z+1^LuvtHVO;xsKlW_`Cl#6)6S!G`j)SwCJRK)}Kz&+~%EXd|Ka{Kx%GKzWPqlt-6K zB^9-rT*Uq|ocz!je#IJUTY;k8-HM%M!2!Nj8@jfeXFLynNtnIzCi>A^BI1~gCCD2` zs^B+@kY!_=?b50JuIG&dtd}+6q+fF4n7*^;WKS-UcL#;-`PCKMR`Q#RQ~m_Na5IDw zb9ODI0G`XGqfYRe%`{TnX7JrHj5fZF&&{XJxF|i;;eo~>N+hUl+V-x@PAzm4l@_h- z`DE@-Pq>qBEtDU-@=25vZ$Z;m@>gKj9ZJN8$p(&xHQaS%_2*a0%}tG=FdtF%_3JA~ z9=!eD$o~cqlm(2bJlp)qjEorg=jm9sIQ5dxa+yAT!MEVHl^>of8j3>eI~7Oc%UN3g z0B5J<{NG!Y+NO0~8xHpSfvpvmR)B$opRz|_o8ki&dAse>OqM6 zx401+YMKLAx>T-dZ+;@evpSswS@2-^Mb29#t^*pEI=B)WNAFfr(80s%dwdvWViM4! z>fANqDZVnY0JTqRaze;#?X-+L4h{f}1!d?^7aI@35Rg&f%MarB@cFeBX(o2BICd`t z&gs2oY^{}GO@(4BOIZq*Sg|_qr?x}Z-aUh2m5*x6PbH!z3-`F^EbAPbGyLI&dBPx{ zMYpcGAM&|4;q=87prs9q4ICBZ?qL;aZSQdPDmR?zg)va#kn9}R=Ju)VTrl%I%~-2# z%D<0xnOR~dD^mkzAnyXRKVt&Jl6y65Zo)JJS{Pq~#fiWS5i2;ZK?&w=M|^0H*$MeX zK(MT7@N^vX_i8lO5Q<%V3*Fvnz$xde)L)yfrr2LW4s{c*xE5GtptJ+Csjale6gz3@ z3M0O<`-)_O*DyolR)%4$vLPb3sszXxV*_k*7@HsTaAE#7;SO|axW(Md6KgpI<9L*X zWXu%{wFsfr-M-iI%Sr?qyya^FQV4mmA^<$tc`Yo0nt1~-RVBv6q*H7u@JX1}ai<0R z2rR$^;GSB)sTA@zSK$nzno$Y;w!PfEtv1^24Klww3dN`-X}o3*LN?Jx zI~=_?R+vFHKJAI17Nn-Z!lm(w4lPErPwei@cOR?(R#ngsM;u$JmaUEmv4QqD?(r@$ z2YNDG)=HSpS>*H98WQ9g*ARj`&S|a~eX*tCrYfrWUN}Jq;HJk&~c{DA^tKL z+}6Z)F~D;BZpm=m&=t0|Qz;3O&OVp4%H%`OvgfQdOURR0(yMW@QSy$4*Eixdvs8Gi zw^n!1*W)8S2%g$FdOW5<*6=q*M!SyKP-%`9st9{~@_sDrju?quZ60%sG!ifA??;Pl z-S0JpWXf!_BWo_K;)-DwBO0@%lN_v6k{&H)5LMTl#F}nksyKE<#_9aTv$^#EBVeFNQ}zH>IG`=7kAlWcl2*x z?*XudVw9AOuJ)Na$qIR$G>%v6nCY^Ci+tu{p`{v`>1lKoB6!tRH)HtNjLIBOR)fuY zA3>bFQDd2BIe4Ptbk7h7HhqXn$^p%vKy(eRfbm{BvF3;lS{i;YTlUI-dfEDyugjqC z&*yZe#8<3jdh8Jrf{{;i!HPFs6kKxYK<}0c)Y^qWWsKGayZ*48T9T&H+U5$PM&jJ$)o-SGdNi?w0}%klBT z=hg|ts7;DCvg({}O8C2O8Ssg)^<^uTd=<=^?q!Z(D{TiY#PgzB#N2!0(PC&4QPypI z>nRl)6ppq-_$CK~211?dNZD%XqGLvR>gMKATU~iCygtZ@h+;6(NGl`O6HJHLncNWm z43$iGQ7pH^rP-UvaoI zr5CJBqWGp}C!KS>qH?(v&7lwEwPV6#7&N!@-=SY+B0|;%xTD(D3OQF`!o5*-H9DUa z97?yL7-elio4UYuzNW}!s)|~nrbwfddU2QXy~NJMl!zg!X%60mF4@P?1?hx{9mvM5 z&at;WcI}p=W$+z72`ED?b{!MVLLHN$NMgI8BVzDA~Qx z>m$e{>6X&?sE+C+Pzz;2v{XQd?tkbpJBhpR$_Y+c7h4Vn{>cu~P-ONiu3kDUK4!UM zSlA@D2lO&6&muG#sLB9BeZ+MM_8WeD2Y51V;%Ry3wmr5IWwwSA?Q%zyQIEK?W!;rV zK{DYxYmnq<6EzsQ$1ZRB73RxdIhKj|ZT>6?tXd$uEEwV4K|GnWtLd9EcWgn zl*OuCqGNuCmQupcC304iK`9g8yDF8npImNB{4(7`0V#kWVwsZ3S!f`w7YhwllQ4sO zAScOi6lZ;4q=crR3x(Q-|4g)?sMpyN?~TRX(Mb zljspws2JeZB}Y^l!rM*cB&w=weq3R(k$kr2;xs9vFYiN~`A}S^e9@Py?5)v0`HPTK@QS^kye#;tNmNBz>|qnmukH4&cCam=tbZDer)_1@;bJkblb~AJ**ary7dZJWte8K7;J< z%gZi)cW7-sQ=Brvw7_SO(nVG_)<0R$emW?ZbS#G}F+sT^X5FhOrVgdCCAlA0VOD;S zF0qrOBDH#Rs&EOmYGl)LX2z!I?Nbq@5A7upmqe`^!jBS!i@rOaIA%fkm<^l1yryA8 z`TShDjw$41X5`Kmn{LjP4nFD1Kw_$xVh_@Jb(NGC*(;lUw0q z9zV7GkK+pWb~AT8*W*?SSB_4kY+a{lTE(jwUHQiZ1y+Gh0>TBi6-vo)6ki48wcn~% z#u=c45YiE$Ej6*V>NofWW0wO>C>KQRS}>9j6(oF_gAOvFcY0_NCCI0H;U@x&Kia>0 zTSi(;rB2m|Mj2aOZu6jYLrk7G80S3fW3MbCGRrWza!|33k_*v^Dal-i>hQZv{X~#V z`Mj+lm%HekR@z@Ve9Zt60?VVV)}-g1fJ^LunN#_&$U{Cu$~x_L{5-{)+wjz{8;yxz zv^RQj*wp-aKUwZ_%R;n?Ik%eO7B06G7KL7WQmoaCO-y?xiqz+kj+Pp8z3`NL2c`oM zD|MDV(%qP@LWU~|1NL5F*@CXku1(ZcYJs=;&VPk~Hiydz*fh=!OA=4hJ{2fW#iXFl zVQhI*tXfxShw=v!ryY1D_zUD1CrQAb8;-n>R<$m4DF6YBYcN%g6d>xog>R)1H|;L1 zAeq&=Lcu}eoj(y9!Ml^}^z8tpWajE7pIcM9u6H?cx#ARTZfL+WRHHrrMM4e-^3ha@ zzKCu-u9J$1Z?pP$qQ2-L1>U7~Eg_t?!^mHYI@2;N%(C$>r&1jG`M2>JP0EI_Z)8U-N zAIplo^{$r~zKGEp3tiJ4;8?0}Ok!zX+mOgs(3#(FX5%c4slF@cLqgC0WO;;dF|jzpHDhrLK5{q3?cnMX23((ev~ zIXBfMOpAtZG+Bzw*r4Z(SyT_kH$S7v<#>0Tj#0$&`PXR%039Bi6p1K}rXPeL=_u~+ zH?L`Z=Uv7ae>UF(^?JNw0>bmu+uko`Nf}h)ySYk*PKTtDObAGO)|1b+47WT;iU8iu zzZ1m0@S3#Vj^r-*!Z2hsRJjgU5Rmwnd-oC=)rVUz124 zXkDqVpH-MvyeBPsGR55b7b>Tn!DYD_fTcMo2M6IM*ya>uGLjz{*t~!HGms6FFpoHuH5tPAk{bje)w_Q)rIRkfQdu$+Trc-7t71||;*cJNEg zMk^@{kl$Is^dn;&Jff6%tvQDRt$=(*ng%?2pd}{)I zX9%p76GxJE61%_}o+~T8dmA@Yiep}q&REhHI)=W|tHx(dzBH%STC$_VRD!RAZFq4C zGkqG+L-1IYI^?PD05iCVeQY{FOX~r4MCDD2n$N#cjyR?UF7CK>6Jalb||AaearU8@z}&XgK|x+DTrPVP|NJ0B2Rqa zqH?!JvA{v|ptkg{Zm-ss@WdvPPJm@$h`QNS2jwk}qoPpIuHFRE+Gc(M*~~chX6OKO zjwNUlL^#!05l7slCWxu#MiZ1*3?f9LRJe4H;m{fPi_HBCoO}(&_I=ESh zRaBN&afIqau+%BMD3_UJ%)VEv;9jMsWOX@x`V@YhqA?H#x}62HG9xpiRm*?iJg9g8 z8qG=Uq{{p*kTD5T%=kEkSdN!@2G+&SuRF^zbv1{~eJ80};E$RKtst>o#rRmcUtjsr zx2GTL^KSq3W6#gWu7CX4m<_Y8fB6XY^9WEWn(_cF>0IoZrv3*kjB!G{$Ha+-E}cIr zYkvoQJ(jhn{@2>aPgx~7fk~{#G$usOy?a+GSv|2**~rV~*SD?VQpYN4mpTP8@k#g3 zU{FU5AY}kJ-63hc=~(aCX&MC54#Y-A`70HnTk&$a)USS-dnY!P{a<8ZgoWq$)U4$N z&nA{o^$Q)V!l;pDNw(9CI~(%3h=r7jBzsQLHL-tb=dS zQ5RJw4JJLeItZt~Sq@3I+fg|9+=T`9piyb{(lj-vsCr{h=D>nps8ow>>?PukiECVS z&chp#xKR%I3=H%Y&)P^Zgaoa`$e^2+gB zZr{&)mEYT^;_!m&CE&wNI{^)D2!54pw2c(s69C*nz=Z-*Secy?$1NXnuBCCi03fUH-j42Pts_BP|&=ZoHQ_k#=7$ zlmW=5DDr8j%GxsBs*Q^aG7jQs>A7Gj&wYioRBn}ni}B~J8$m~r%AvYV%h^CpdX4m# zW8)TLIn}>$mjPo)7LeAhdA+5Q(v@Mmbd=B3`oEk!?mLsZef!zk7=(KxcL1)oTD11M zW)iLpa#6#V)+H;c5aQLA@zvB(NkB;AjcXKg1jUungf-3KPq)(&H2GI1R}4zuqQWW9 z5C^>(tzua^1Ypd>jJEbCi}NgIoUyF&s>N>cD90nfp0oYa$-==A?G=#bor?#i*v3+p zh-EdW*xJdl^Ry10Y@--3L?{3?aIV{KwqrCKXcg|*9@rQJ+&BJ0G@%}{_hT|S6J86Z zvjaHgh=#Ee`+FY#W*kOmpOOu#kPoBIJbsQrNiOBO3^3q}r zIDpbOcFmv;)_3xZU|FJv=x*yPcMI=?*!olj?mi#SuWFp&aBTyX?Zy^_No9ngU;W}j7N^SF4*2#7Qn5;?FJF&Dtpu$S*SO*qIqODC4(b&toTBYXxz z+03U@z^ast!H_MA2Y@}MG(WVKPG(c=jYFffR9lXX3XzJPi9e27hV05BYKykPUa-_( zT}&DDHkMyp*t91EqtrqwygC}q^O%W>oYiWy1EYgZ8qVQoX}GJ5D{Rkk;V@%Q?{rSP zw*)iav0ke)rGI`Zm;M7FZ}&5tc{>=zdRKPQS8K07M+EkF{TuA9@&P zcHe@UEZA@e&pEW>l+=1QL{8sMfmF%LnaA*)DBo9*U=}gQ!U- z9US0Jp>E9<&~xDz?_d2W>mZ17#b}AYoWjB|lfu@1Z^bxE$3v(P7NUs_G^|`j%G)=6`p*fWAUAh+S+FD|IT!?_mcj55xDRyvec#ov znahMkUsZ^m_@)V(6y)RzgtOEhGPL(e#@I(ctsESy|3R?=HVom6ix3#|G4eKLc#D?* zIM{^d;1eBH83z@tOnufF9$c}oD$LDlk56U~BepP*+;OvoJL)l&-F}T`?ZB0ru&ceH z{;v`v+N4}d!kQb8v;n&&oBsC7;H&Y0Wc*k${9T-ZLddN+C6}z^lcK}tn>wm1|9O%@ zIcUP0FsS#yT+zK9p4?n^28^-d5XyBh=+piJO;orae^JI?()Tt2DOV|F%B6R)0#^km zDF!rH;d5u|3Mru2w4=O@_26|z?$B3P-yZV}luH$<7;<^+o5@k6?_>l7pEUF(aAGgp>uyyaKfR@qyYEY!8NX~Xng#544dBKNd81* zq%TPtX;h>eL?NP6;bO}VM9^|Jt95R6nWk4PGk+K8^^35QcON4EK~e3&ndISo2HYGi zE=&lL19YdIL({0~7v8_ksfkA(IbMz$@r~;}aahTM$61(;>Jni9cdaax3Asba^XhW! z0r(7FAt+D39nXAsnjPFb>UMY<6xF7l;d=_`ogW&{!{e<_f&fuYJ8Xf>ODPcl;cfgH z(oz>eY78Olr}yc(=aTp}q`6h~im9(cedZEBAj3OY1C>M!A5kI8BUmBtzKtKjs$6nK zrXli%l$#Qcs82K-_7`rawIugWMmSd=cl>r*l44-VNbx_aB|!7QAjma@+%2Zi$}<@O!4wWF2_WKn<6rET1ylX5p69gCR;NCF=>K;|wUNc%JL2^>56v&WC8iP`&p^#&; zC|$u_wCbB9{B+1>3pPjBB0?RbOVWV+`U5i4X7a%~Mpi2Jd#zP`y~kqlZpvoZzT6$b z3^i5@3nUTHjelR!f$=PascSkqSm&Uc zHIq5U<~dkr-;x76S8693eRZ50yO-tigcsJ)41GOyBai@W)-~taL+?sys-oVAT43$s zv`_Lg77oXg;mqk2G014KHWilKSgWMX-mNI~_u8f}(n;yXyPM_2#fT|SIgPqK9RtiZ zzR6Btmw2K5sJa@-%KQ09uHnk7J`q?mK|@X~i|plzf^ zuTgVOqKPbc6n|2g_IL=wMg+DB5Engc@LLDoZUTf~`RCAgYFgCUz$+4KW%nC*TmOO%UY>6n&lEb)-*0^K&lY*$WEYRxH4k@$L zZ}hJdw^lf5OT5neU~>g!y*p}wS|yja`!(y7nhE}xl-Hi~POMzSPZ@A|U^H%mHAQLB9gU)h(&UgSbK+M1Cy0J?JRZ8G7 z;QmrY+H-b^bn`(>U9cC9rCJ3R=XKbFpel1Z>6b-4B3oyke0^Y!kOmN6iYhM%>fl8L z4{xE~4{v~QaFGDA(`J1k5$XVNKH6I=p_W0kW^8gG$MbsbOK&a?ki>scAk8TWKedtl zo$Kp+Y7c3vw<<`bxh62h_Zs{aUo=i{@yHJ1*c!$r!f)@xjCg5?eo6x{ z{$*k~&Z@&RTr?w1kAkegwtM^qn=S~xE73m!4>ym2l*CyaYHXO27jwvz1cB)u3MNoK54trV^`@ zniCn1Yczn+j8#td%U!(UZ;qpqqx9tiPY(85cXML-?R!57K_nEd-yjTiV+F9J=b&@# zMDuKGWJSeIm4EXCH3pS_LS1UKd0OS`d?lR(Ad))8>{<#40MJHS6zQuH^c=M>aBQdgw~(0JRE- z$V#|hbd|bF`9&|wrI(a~Prd3?=4>NoXws@ZGf)CP8nQ|&ThY( z;mAPT0XZ2Ud`(|kW;y9~uuw-JUpsN0DaSt*A_qe~&|w%l-u=pn3xZXPVJknc^{MZ7 z9C%jujF>{SP?BQpB%83*(Y(7|rrhdD^@}0p zl^}!4h{5d>uwCB(ypvlyHdKaWoJqN9ixx*%mJKS(qE6{3og(eQnKgfG^ao_H)GBUC zaOQ^UwbrjSfo517V|CFdMaB;Wp6EnQN---FVG2{v0(oTceY5=abW$up@-Xv9`|5}-~+W^{mQ zIbN{BE|cd^SGBn=&kMD?3T9)dM2#o)%x@6?4oZ2xc{-v%aUWf(CR&q@$=T&oPRbr< zZu145@oXUI)=l0`F)1c2%70kVjWLd-Mp<|%B@fem(~n5VPA~Sdmb1$iq_kX-NN{_E36|V= zM}Wh`;M)8qR*?I{>wD|$d%V{KTXQE%sMumYTe$VIt-ZWU`sY#zql*G+!p@`$)t*2d zf>I7K1l(ttShA{|)%Gg@k`ON={7zg=X1adraA*!6r-fQ3O%R-y(=mSYW2n{b!zi~j zdtT7nl=?t?`;%ibH;%UgY7Bxby16f7$?u;hd-Y1Wa4A&PQ8u#W$9N*W!;ih8Ax!I4&ipwNe%jS$AyWJh;6rLvJX0*f0efZGe@1l3-TGSZHi6c3- zLmU-(gboY<^QI0K#y((|@y{&z@E!Y!HnKVLf{0hD#(NGsvNdcx-0Ty4X4b1?$hUWQ z!y>Y4>KaEqL%pE0@ra3vaU-MU;;wP20c9=)H$Oe8toW`fTklv#Zp$VOkv~wf#O;K^z=Vm<&oLVGJTz^9L z3P^fZY=IYm7KpE~;|qfJ=i+A21WehQpT&?5>HMZG=Cx;kz!HFl!c*Q(I{JFFMBx-) z*MYWek+JB=3A$D8ld?(=wqYuVg%Y*yhq`4u?TvP3MZ!RA$&)ST;cMA$Z`K<@AO>JmFdF!}}y zbhlUOA(x5=nhZVcm<{Ct}&;HSPps`uarAJjwk zyw_3qL{Y#UuPh$uk$&6zny3%xGm}e`(v)Msc%-Eoi17rIJ7a)**11$m{M0&aHE1kT z6N#g?kpSs>KrlnVORT|4&c5rL8sJqA_CcIPhgWDQ?X~YQf1Ox`_YO8sY#fKBV|5b7 zGG@2f8w$EA(qUq43C@5Y6&jF`Dw6ofTadPFvdB{wP{f~ihbXHWv&~mm-k%=Mv|7>Q zd1&voetpr20Jzl>pGDlz^Y({KyOu8qz3Y+Q?4H+Q}K;q1q0S99JK@JCHgTFdG@ zJ!K>9x+7nqsh|E$nVQUTMoS>x;?ihAr_~%Po5&OTRB;d18?j#@FidTQVMR-|FS&@c zvE!*CP$0R!6f5EOZ>kmxY`EP}O3nH+2yHZBL|BWGdO5mh)iK4@YVzjWqdv7nUXr;^L#Uw{2WWdC^9z zo^+2Yq$Y($O%~>C!p=GGQ>@Mk@S_$$wMQB>_q((zKSkx{gp!-0H#J3*pY0UAa6+#p z+P0WOic*TmjCNeFMUJVeCyKSR2zO4-7UeSwl{tC03}xDuD6l>R!Z~>7f}o-+bxO+~ zksxZD94vjTr0!?VrW_rMn04zZM)KSw5K2-7YQiZ*0w*WmqEPPU3@QS2`ACKKN7_iF z1iWB_Rq=X$Htv8LFNQ0-$-Cf6@7KC2sLF4OCQlMbr1FvPjYApo?@psNxe{M%59v(O zsQN+5eIDwGZ>=ZIVfTkGriqD(oam~bq|b6G+46Tiv6X12&-0GJU@ag&lri3EBTUol zDy5t2T=3G7OH(sRAK)&Pw0;8>DNHi&L=RIz6;0GOCE=Z&$kG-+1mSpi>PPebR6rWR zBuHyfzw!xC4EzO|z{4irog&^e0^zJ=w!!#!D&LpekZV$u0)1OvQ;8#KYp*BunnaA} zr!*v``EOVIiAZWZBqjB|qGaI^eMFIk*`Ie7fr}(NK5;zoY>+dPTo{(y&8Q3xyZy~s z=jP%4DNudpTgHM&K#QnhzKYzIF^f}TOsdQysC$c}Y38V+@$>lx0RgHU9EW}5)WM;41@z?9oe;D>o zp}r=LfK{@h(+I|23nJ<-Vr_lRgf@;{8;`%9l~%rw?;n~%Ro12uoW2VE90=n#?DVVb zzyu}`GAL92&*pA-TEg6yO)9dy0~z0yyiCc+FUyGM+I7~b!pVqt_`Kfl`f8i^8a-3_ zdm*TLLc2^Fc)s_ipyh(2FAoT1D_Xy?*TO?E$1#Wd=H6~@%u`6p^g}*2T1Jf46Jn+1 zo59>O`fcECX)S!opKlRgL@9Rw{8i_!wpf}@jN)n7k1;!Y9dIZ20AD%zxmrDhZ(S9A zH)$M9YNvHk!p(W2IdRCJht?Wr|7g~AD}EWg^zgO+r|d_I%KubGA^1s38h63tt_}L}QN6K^cIi>Pk8Nl@ zeN?YBLAlLjj|;1(>r>;d16J$&>Ww4l5q3K}BdO%wl04~eL-ZJ<)S9xFyv>Enq}@%U zVsI*ue6uw@!p1d^bRqi=h;X%Rt|gsH$MEu^Fmp+4N~orv7h##&P;8wPhZ3yGeZxAc z(wZvDxbIm#Umc#(mO5UXE8$_NiVLq?tKedTHI8Fyw*S)WUksgOursjU+F7;E1Vso7 zzyi3de+`;ni|2JABxDo6ps9y9#zdPQCiPP8nwC`b9YEmO^da{|&@}P+5nac2u@Xd**ulgK0yRX$T27mp2ew0pPOVk6h}XTG*qJreus>Q@ z7TchSpct4Pzse!XcLrDEZ7(KqzEG&*}j`VLWU#;0xjGW-g-t-PrOOc$1+fpf94Wwn2OC&J0P<(dvp z$XpceUwc9gYMPz^rnn`ZaShQEiu?iW3P+ycuQF;h6amVJe~Sq>$rm1TlYDGtB&VR_ z4%(|DDxki;;*w%f6;S+YoU=XDm*S0A!ElW^UwL>Nz5Yk@f$)%Zq*Wf>Rn_<{w|LFLkM7OmxUuKV!pG(vduEaKj| z&3aNm=6IetIH#2Et6y$O-5p-6y*7EG*l2zfYZ0ZRTXWg~qf2S{P(FgU0yLQXt7LT& z!-@gqSR_#oR$?oBd`46z|Hmc1=q0HJ0TLk$Mj7|fj*i_gW07_w zo859asolZk&``9@(|&AdDrB~fW1ceFnG?O^9nQE%m?pe>X+@YDbGjU{4k_t)8BN4% zji|3=dV+fvdsKXqMJYL|JZjs?u<}%NmZf~B;&;+UYEr@CC8sP>Db*EKt5)GxsartG z`kBNA&18fX7Re#nEAAr_qc8Mn<`nE<3T6jL=|nm zJGbVDo2``x0gu~G!mXPorTZ?c7!r}AHFaai9x@Lf4Z6>F%4SU_bT}*He1yB5`oMPV zc#*i3>tu^x-e55kQ($D%A~yBP^5G1o;A0Hhr5<3YYF}` zSe{e`tHR9NDrQNp`FRKR5x-ABkKd%59#Ak4hk`Z+BJ133sz?>p`lD z($4)Y&$Q?;-M9O;D4nr)7CTN=DG}9stv;$;AFU| zLGsU7qR@Za59i7diGM}ruZWJvBdsm_Mlc*yo)h{y?BxDBxjFYuCBq#I&D8xwtL0)O z*A$^~q}Jpy1i#BzIkO5Vr^;jaM@ivKNpHj`&fN4*w3|wW-{28Pr_~8B%HNy_@UtZx zR=ES{dnVp_(pJV3w3)g*UTmq~5a;mU3yKjh8~eG)lC@=LxJlc1!t#BDr*8=FBvq&> zdb&;*Z8twFV?myZWDF>fr-s4Ip&>;1Z|_4vI#sc;%^d+pfmXag(VI)sCSvQ{i3uOt z=&d+E`<7%C6P#tVRo^Fks|pR}5ee*7mM~dTj77xty?>j)EYuTlj|-FV@<8gXcVoCe z6}lxK0EDg)@UGJ=Rbe)pKm|)uMlouPCe;gPA6v0YG zrDNf!YVi&p0AEiJfKsONQ;E2VvFUEUpp?U=TqwcD*uf)H+ZtApVAMoC3#W44Ila{5he+FGm4?cdBYZ^aE}6TLX_J=tn`2%FsFNs!JZ>_Apx5{QiEI8V5V ze^po*()Urt3N!8?^#DdhVO}RujB;%vfU;37a*`{p96RRurQw!BB+$7G+Z8A;{C?+C zLj?!RJYCyPQbxTk(d}Kb3Fo+17vypqXvS0pLvHfraiu)p+=)mG{Eo~^kjAyp^3qLk zAfxuBf36BB0bUl0#>qK`3n-QB3}E>{C0cP`B3P0NuJXmu!XQYg zs27NE(c5c$>Ae&JElp3j_eSK6cA6uFvJVKgvm$;y&FhE*=r%STF%-m%WiXv6YrT6- zj7idET8OJPn^kPnpDdD+1}|P&4%pqz*o3P3b=?#^CFJCLY*gm5!hWmfl2(sM5yl7kq^GekQTwWV!^Et0T51 zE{GeA{WtA+Yhh60E=vwpj@RN;7=b62x|DLkaq1LILb0KcPHqrYx8n2j(Xa`cNK0Xy z@_h;ufZEh`7`$)bP#5mj*3Lyw-MfL)T+%zn_ zp0uB0-WGFU9L1a+?vC{ku-7R8hIyg{spjJKS1Vo-nfb~~z*I;3c6%5&I|=j@(H>Yq zr?nhjF6gSDeGtOdi44pqeY7&+1R!cV-8iSx7IYQe9@Cz(_*m- z=3OZ(XC);*_21mZ22dJ$J#Hr=MuY>q)EK&tWp<#1#A^~yxebZeI21#DeWysv-k*@I8*QC_i|85-V{5wFO}P#@oG$@$>W+Z+16h_FV;bY^bxk_6alTw zWx}L?`u%EE-v=h>v~21XVKw$G{ov#%LQl?!w6?;vw^RR|}2K&Q!})s#8Sch$@5!yx93JugWc(#re@gcz(Vbs>Mc5 z&m_fd|3H&KuG1Q}7^kC!S7-V@(OS4CvmqFUFUD4oj49jf-KJ9bd7Z@BUi`foGk5&D zuZy46*V*E4D27raDB0U<@8hOUZ|qG5u|#6g9@qE0Jj4Ssj2IMl>S$yB|2uK&bPN- zW62r&LjilwA2%4j^Buz6|EyslEE8={=;$Omi6e2zk+WC2C$hVeV06eNpDOF0J{5~< zqa&+He(+Ypy=h`!&foNRO}m1^5RjX}%8q58lT5Evs5#$$Trn7$8)%hVZO68lc{O#! z+mcR8^{Lq-73eAmex;&M?6g&nbCR<^={&|>9Kq3b4zmD611L*vCEN&FUNS*dBqy~g zn;u*z0BlaQ;GdQ=x3p)9uL|(bg=LwJ(R7fc)t7se{d@x}&Cfi&I3XhwLok^{`SILt zU1x|5ujhJmv6i#EiV0q64aV6bGIWJo(A2Rs%ai-o?O{!^Ui^lNj5x?f)ji->^5yX9fZAoqf^ReXmF0ooF-rfZhW;{$#?# z1XLzA_~CcElLlcl^G#2}?as(I@{iB+Q>dMF>~)j#hl2>vaCW*3S~C;XDG7a(R(>8@ zL>RZr4Qq@_E#Msrw`6^$+R9x(kXqf7OG{ZjuV5 zs=#nYVOeW=NDyZ8I@!+u;?N_CKxmP_5h}u`?^xf->#&s162?>X zIeLYM?MQKZGc0Y7 z48pSa9H_T+R(i%)JdVi-hHeX}jLKQwuPi4!884+0)HUVQR)U{krTzG(x)cyGIC^Z_ zp^|qEe!PgFjP>M0z4v-Pwy&3P($KS;SzpmC&@yZjWkk>2;~_{0kre{<}) zOwsqtyd@RQZ~+-_W%xxi<`AUKI<~Ej*6X6%WuANf)}d4Eib$><|3wPK1H1Nu5b4N1MSPZ@uS{4n1Vbn&0eD{sVKa5 z>J2yYi#-Jm^0Unx*Y`;&usMr8;g+mWUy;>OZVotQxh=ev{%HY~ajYabNC=^CKzE6_ z+-kiV$q_x;_9>=-G)dc0wiBV7zmDaa_gbKH%)c2H(8mpBSax%xELuUi@5*KhzB^u5 zh4v}iKDJgDmb~9rY!okIgERzY0%tQbb9S^3)GFg zMrGn^Kx7@T*d&mta8!T_@^q%>+RoT^HA6QuUXcZ1cMgcKMk#}l6;7|e^5S3Mt-AJ- zC?#WIIcatzkI;dk`UEcftC)+EoFvlaY0m7%Zpd}JQ)gY;8z8ipT*f5AMPt)+*j!^MU@9`oLDSu`IFQ(itKx~ zsQ)ksbc#1(>daKsFB{`jv^&Y!)Oh9)#IIL;nL{lw(@Glo{oOJ5Bpy*;Q#iG`%k4$e zn4_DVVaY1J(GM2^{pOba=ut;U`dxVln&ZqhuCK?l~!<5NW&6HAWZiWzTCA)sjz&|;r0#e=nP2AnaA@Z z%VhC;><)7b6f^2E<&h%H1fMTbLED?zE^$iS0ezSb?s!OUK4xASp46ajW;0fHFW;*K zVo^|Vm{b$8v!CoVuRWXfcRvSL7hqW-Bu7C<1CUmEoW2Ry@C&PRXr1A^&Q%$)>Cn}8 zpi;|T>DV8X039u(;ddD6$eGlE5tNbcySWqIHXgShZn@WW#~Vy9;gvC!JGTa7%%z}o zScjk5h|j|1g&F313@8mMskdOj`~eSR9<3F{!LhtnErN~Q@Kq+lAvZ9pKgMLu^1Sq= zy5W|H!C+^7Hzy;?4D2CP@aj(F1-}$E6C#l2A9OVqDf<~J?;B-VD(wJQGz{Z$AxY4j zQu~l^mf$n8oMJ542bnGXMve28hLBRY2udlyqrIdV15k^*jG;LR$KV)yX@?56M5|xA z(fb zi(A9R>?GZtV{GyvSBv>xlv5i7y4U?Z*97ZF)R&MhKUw+SQ+tN3om_bZ)29zIwm|K6 zyLW$Fu$FH!a|CI}m@p2I6xvI=dcT1`RbJ;EjwBC*rlE#lI_vlM^)-D*B`4N}y;C~A z2%g40u_t-dV&x)tvk3Odn+-TZcx+~!RB-2NFPm@8^Wh)(YkT@XcTgFqQ7jpR z*R|B(Ja@QnUZle%=z68vc&)d*vo3Mx%D%;z5Wh&`3hQ8HCR9v&TQ|-yqV$~{1h;+D z-1%r6dxf`}kosfLbNf>EA%1IhG*CQi%EbK-LoU2j(P3vYsw9C7d82$sgNyYhs zlq<*gd{*P)hgVy%`Dld6;}Wmy!twxXdSe0u;&qx zHu;S)Dp^3{<^HKA8ee{XE&7oWSHE)1I?aq%h&6v*Ao|b-LTfu8Uy`JFYwLc(ofM@? zDe5p*ar5U9y-pD>R{3)YS`b|wQzW_)Pj{QJzwB)zBefKxmf+A&#o3^W1m6Q_GWbZ3 zom1b&Kzl7@?x0dm?54@|>1EEnZ2mPBBB!c17h&?zbB3`k%f<6G%P))RLYyUxc6&TesDeMH&Q zJD)^%8f2Z1@3}(~gsjC*gqJlrLSrWqRc4mjV4vs~G%=ZS;?tL5%3Y(Ep6F_xvpvxe zZ9yp(U?sT;#+bXms>{tJ$@}=RY`8f;KF7N{k=%O(wtF(d;(D0Ya5dV)H5;JqyQIQv zqy?qvo7#p~-+|(+R9zl+562zH4?c%_cR4avRm%OniMO%VS~JY6bhN5a3}lt9n>yiP zdndu~EL2_K;o;9ErM>frAAQeAU?wK|3&t!s<5H8)H}HF&KT{A;dbCmcMTZj{B^PEmcAWhr+^# zTnm38IoPDRPv}_*!KzBFS5kIvzDow66w|vQz-Lm@%E#w>vAjaQz>l3}v8nLRJb2WX z=E+~Yz!6<~LcB$Y1S23&k9R6{?|twuC$s}-(&ss)+>MEs`Ly?|IwNgduDs|}e*Joc zVLq1_qvUcl!h^@HOf{@s%ow@nKHi$#ahlbzB^$5y*I5BJY)>@uUpAW8`H4>=aLE|M z(#r8MPCcybn)1tiM_Bpl)wii!CFZBN1l0~KJi>AN$X{nMK-afE)Dp-A>jb*a_pNng z@5qeitmBMCMnz(}lkeYguaql{rd7lMUuiFlMl0vu0{PV=yFVVBUK@=U``CumFy;GO zgID8jBZkb%Fggv+si8r^bcP8$jq%)a#m;0vkJP<31Cog4z;Y^gVfrR4^&R_)#^8A> z@`g|MZsUZ?j9-be3*cZGW9vvI4bakboqJBJwEC+gJTD|hoU=MC(*^;wlyv7vQz)DM zbz^M^A*{)ns<8`vW)CSGCg>@GorI(?8`|H0Y{I)qF+PUERBxau2xO-{G?@^WbFB{?BnlX#_B# zVngvc)j6S6OEXU+f$!)G%cF*w4@`aH0fky=B{oE9Yx$)XBn!%h_1yNNTiuBXBQM4=n7-2=tb}uoRLs#(JqjE0ymIu7M3_jq}B_i{!rac{g z*>lmHB^}$mT|WHQNwd?6N|0n`js{YI3z4BWiVe`FT(3xnzT>jd?hWUZ-nD2BBe%qp zqhNJ~G5hpSuCWIghd5eaZT3Utnzh_)=|$?=?5%SgWW_Y*O9|~kpq`E{b&M@bd4nCy zY|>auiQ1@*S0Sw_2MYZH9-lFIa846fpLgx;|g7ZOeq$2`)6|DLJA6c z>8EwSoSJH18^pEzSEH_>>cN;mp%f;E%2J!lxdi`#R1-0C3u_L7ntfE@{z2yoYTO)u zQC&7P2T3X4PLn7As1cy`4Zg*vuf9#!n&{T%h3f(eA9d7vK!}i1#;mz>&u%s*$tiKaZuwH0=lj(N>DLGi*XLD!Oq^oq} zVhH2hr1=hNBLkel9D+JO`o_~`89}S*M`$|*@f}9N_He+Nr_dT8?x!;Twnr7UFNLEI z;n_eEnoUrl-j^-n;}Rut6v&r?KQq5m8M+X$>a8a{d~)Q6uhV49xI8fvdRh*!V%3UA z!ACH>e|nvW)JZZkVK~|m=&w8qN{#9s&DWs(2TN9YD0N^_+9xsA=(s9_v9LSKRy#Bk zAsdMn$Z#kV8RTAw4IlwVEQs=i1TYr4P-jf(+WuT`MOFDLmLAmo1g?i-zOwAHWb$b@ zGYKF6r(pB^!Yg%tdpJ-^9#EhkLNdLhjH_V$mH+Y{eS@IuuxY+{M{I&tZaW+e-^Byy zBLIaSvP3>Jx9F#JMEc;`So~}YM`gPbR*et56Wa`HjE`I#*F?l|i|FhdkSeA_RSDv>jspRcgH9q$T~zkTHK%Z}e# z`;I$@8~9*oqa#R>l>rCS9sfd#KFPc(hiw)p%8+_!!>p$pIdkpj?(ge*keJOSSEjVs zx}}=lug;e4U1W~rL}9ydd$;5QU6zpFSQFbd-3SWlaBDH(BmyRyNQUplV?)|RH=?8D zD08%kG!j+G+eCPxfM0>04UhBa!kLtORdG%%{V}OYNfB6+U=ae8Wrju-dZ(2 zwwiwl)((ETqOk|^B8r1eqt%)n`B9>nG325pHTSL6R?=kRMxNH8UiVTdYDLxj=!!_L zQ!-?`PyoEr#)ZCPJVI{gzB-~0-9H=fPeT|EK}vPUtlQ`5N$=VqWr70VF-(8G1S=Zc zgXw2qtEI7hU5OTDaVAY@xRG|nvZT)~W&)cyPOVDz*ZWcer#8bP<~Jz>o*yzSKM4~8 z#h3tR>Ky|<{!X-IRwxTbCA1=fio9<|X011JrIXcEP&+rH_H?Ub6*NNt62&+!M-if`gbn8bZEp$o2%o2$Yc}&AN7qb z7QK4@Qk>E=sTk<@S3CW5lhZ&M-AvR}_)KLeht3`%iv@79&4zS`8XkA zd%Kk47k{^89UK!ffVhq_CbB6yfwX!|o(6X5r&joIj_E&1PdUmJc0n4^MgW*2TKZLD z3(ITOlZ4Ao$i9wfWfHR^3tL}`m9PdEHr`z9$7xPLcVa;M(Z&?h)V5PwM!i+FpNwR6 zDKYVK7VLCy2xCmJGg#>|O=!lh1&EXrFuGppEbQ9`);M|!X8HfuHXWJN&6`8ElC*TW z2_YdMO-m^12U}11G)W+mu^12*In`8>@_S8)EuuuZX{nf0L-)+fLa{O7@&Zl)gVC~iG9GqbIkZ%tk%i5>q3L!Fw&%U&89y2GqDs7+d4F{|7Hy^H6bp?|aZR-nMN?gqH-&-sysl8S zM<~vD#FaK!>RheHsg*wVb+ONe8_CvGXSP87(WLUop7d0y{S8_doHflaJ}~9{IQ+^3e6BFh+f#YS^g*fuDL@%~o}uXVs-s zG8#Y5k@zer&w$UfyguZpy2-c#EPC9#-XC|%AKv*z1$$*Ht0cw{S8`Nd9fJR&VkSh@ zU!~`9GP%*k9a|EdHnaBBA_C?CuOs|4CBc@ z@16BDd!@S7C(nIS4R@=3 z2*x^$H8Y^|DU_#e(k4o4dylji2I?fO2Go{yP$LbgbM33IGj!fy>)KjXGDST)WMR8iV8w$-xMSX*6jsxvvmeD^eWPs+8PRXLN`#-rkxywqz_pVpR)75fK zdcIVEF6hI}gbY~;JB}J9_|8@)F+0M4vtRjp7ySN8-8OM5!gr<Kvxx-n6}I%&J(Y9KGTxu94>>>sdEw9q zCKevRHtHwqfbA(KWbUGWTZ6)1~BJH z?u(g2&^nog1c6pE$alXh^ZKYg_m>7C(d&=Z=!Sn!nmb5$NcQm) z_EE@98>rh|NNac_aEFp_sw%=_sA)u{Ea zzazKiGW#rxr-tRZ6J9}WsHBaWoiU_R8n?k{CXC!^0YW~7KRVVW8WwkRH};257y93V z5N8K08bK~USt_p|MfE=EhQ|jgveftj8azMuUG@@3x3zJ@ci-DuN=L7^LY#+`D~ter zkxJf46mj_Yy6T*h{c2d>(Q%ImOiUjFQA+w?UD4!v>@RKFP6id`tM5=6?3i8QY85H# zP{5R69X73`Em0S@ePgUQ99EJwusNhuW+nW@7)x!`D5*G18+=ik?^eUESvJzqFqe~t z5;)v+4T5Yc;4|703OR9c>$f-j#QZmP&8Wg;REQPP)B8Q-7MdNgxxRUu?vYnucKKQR zSnJv7`3S#Yj1cH2>9yvb_KFZ+Wh00 zv`546@Wj!W8;ivuTqpYIlRZJ?=o7}n{9(h;p4>>fllvtt7W`05Q72>oxEaTimv_qwSkf*M;qdfG7 zp~k&n3Lqath(oR_VvoldlPFb~*-$!;am~+{Lr=z1N6H%oq(ru9mD+oT$o&rDp8wro z7#jj<0kvj!jha)p$0MB>;Yhz9m=YE_zwNZshEtTPsj{%pBCAZe%nP(yy1MSMo-T%} zU{|*0awNBHev=^2K!?Q4pb4xSvEt3x1MnX5q>Zv^S5I5Lov(1Au@;PGDdSVJlxr() zlMX)?fg?R3e%UqKBm|wwEWh^Y;50I?FWnj@aAq?4{6w-}kfHonl&4h3(nXDL?>MVZ zRYuIRe-u=bo|(W$4LhCtj)r(u>1jZ}DekWi8BaOpticngZ?$%rHwP6t1d)^u2TjAS}rLBb2Kel8Ootc2rpCr+iYQOv{T8~r)EbL`e z;EZ4I;LDJo3YjfSN!;Heh3zF8Gwc98u(HrP6>!+2jyBm2+(7Un4d7D>KVI!q9Wu|D*}imy^2vmZ zlMb(pQuPJ26JJ#^x^*D(Qp-D2ih{{Rs&}K9U_!HTEnCei6NZ!pXK+i>r7AZKVU;$b z`3;)FSZVntCpn=?zK4LLKKm^Z!k&4wVkg;cx4EqkA05zQgqAr@(73U=YS9--;;LeR zQ-Z~jdX<%Ndxq($W1gNu382w8D}wSyP>%48KJ;q@Oo#B1SD}y{2C;=?}75O*?&S z4K-R{;PepLSu)ReN*Hf#E*R7y zN##-XNc#3M$r}y6veJ*Ncr!~TIRnVbC{CVqEOl&KN%o~j*~7)_Hl(#fRbpFVZRl@UmgqyrD50@*wR5Dnc~@QAgdC5Nvv;{p8LWuokW8Q38hw4# zrcjL0(fkB&AI3>MLASTZtd;!-^HC4tu`-YlnfCTehnnjZb%isCQ+DTEqAX^JKIK zn<@XKjPfir!0AkKgme{ds6S;Fct?Yb4tc>NSva=%X6+;q2KzHw1yT2oBIB z8NWNpW6M$hja@^%wmEQ>c!J*7$)M?VI3>9P2c4o>WI+}|D>$vgCWftQLn#0|K*YZ| zrOpM{C`96Vt+%R@kMXc5tO~sw|6@i_N{L>RDK45#`+gj9%cJjVe)x0Mxpt09yib+6 zoS}TZos()Pr0LM+9I3du>KJM>f0xOd9P_p=b9FGedztQ;IAFZ$ovXfouX=q05$Sk2 zwdLnPOiWmK8CN-^1>{#s(S>OiJY6YQD1`{m6W?hFO%htQNZtYay%Z?Af?gd$c@jB&8beqF^8YyzTt_mXs$Y=(#ed+2Aq0XL2+^zp@b?f z5%M}ag@~Q}vmbRtMU4Y^XV-a)cP`s(`JF&YSx8LDPAlUMh9O?Dim}RH)N6ja`&GDc z*=P_GAx+D|Nv`&j-!xXF104`ld_NNl1CD}h!#s9TOi*j+?F=fM&< z6@)h*gwt;%R4D^P^bkE(qMo4AXaXi@HWrAg4ILxLfIZ)|Z^PzpwJ1&Q%Pkhga z9cNSl)+~Kj=9Gest+i`(g}s~j+}OY~t+Z24y%u}E?^p-c@M$!K+4~9K%v@3A;h=ML zW8YbUIr}d9SVd?2Wv6}d6Dg_u0}sV&Fkx)BB**0JJgw5XAvGDZo{2&nj@xu8w?HrTI|mNYiPuiz3IZIi|VE;J_~2VZ@w!P#9)*s;(_-9>Rbb z%-HRmAwJpze_3KfoTR?o#m>Z+2L!qMZq%KJ5)W>;T#nfXbZi7EK>7|heNN(?t*c7Z zFrwdSzezDtu+8jmM566=2rA{Uk@)6{p+&~*;-M2BYHSBcR&)IOa0JpUh!dv|( zt~r2zZqNcxv0v4$NUa%_0Sz7pfTnAcpY~H#$7;1YQs@VChNi5o+?9RhI3UGl;=u zVEN+QTy*m1xDheh8Xt8pTm(L#1(3-P3W;0rT_)G$-0fE|UU6cT%V#Y;$*kfN8PU;P z0G{9llz&;n)j@3_wZS|&{r5eu(Se;UtBNIVh*RoiCG^^ik1GE|5ZA}@P`7ZP6itbw zK}(X$+>f_67-Gs%#qyus>}02{(tejP_@%5$5Izv-X!_>jUlbHjGPHf}vy!<%$mOU|VmSu&fP8zH-L z;ke>zvsQl2R&HM(yS6S(5Ehsm3#MOWj6wX`6sip(V5gx_uQr@+{jounyrx;YTO$z) zF#Wq_g5kYdDoIGOmNT@_tkb+Eu5rK9IVz;iEO`1(P1JT*ioQV(HOsw2>u76Vg;d0N z5AwNsQpxu)g+56o_E5*GGa(qQ>5_XY;5jcQ{+`h zpSOF9HeZrKbfj3HNvA}_uoJY{Q`IguJZCypurpdL)(UFVw^?Spb#ByeXl5jUr;|2~ z$q(()*yQUgE#0^<{fj_7L{aT3C+rWd>Y4nO39@e>JgQY~R7|J8>eOeWV5m9ceY0Nq zntUJ4I(r^NU7Ck8UZf(9fkvPgsXz(A%WuA158d&q$^bbLV{(oL*Oa@9<~-o zxfPWOKM=q6o+YNNPufI>nMCl#QE*V=8@aRZP*LbPN(h(!>BMk_8Ka;yNq5dI=V>d! zsHAb|UVBB|W#8zRj%9RpirOGZo`e?hBtS^%hhWnij1q$gAx*6<9XqC7x1KYuvX*HS zq(mQgs{)=_x|dLu5}0!fG4FV0Y0 zrMxN^bV>j?`yd~>^g{mD#21~zY{>|Z)3nd3|oWT3teza}sY!}aorU+bIedm)nY?RKHM0epEej4a#G|~m@mPoh;x=M6QrcY}_=jL9 zBPH{0R1V?Z62`vzr&UZpU&DUu-?ftp=4V0G*L&P%Ql9c+j=y4QDZiXz2Sd;`%SCVJBWlp*A=V0)-D!%&uS-8Tr66F18rK9tMmGAYriLkd5C*vWzDFo%Z-w@fW3`)xR$X$0db3W%ms|^M# zG z!Rbj<2d*&Y+Qyo3RfQ?&e$~(O!(aGh6DkhKYbmE|B8(;+a5Vb-&L(HC&YdZe7G&_V3EshZ8< z;(;qQ0;q!M?fiASIeu-D=xD~I)23vP`9}_aZ2o*HnD^-4jK^ev-Yd$a<^>c#u4uc(S@{;~}z)%VKH!I%}E?RkP z%^{@?d@9F{eBR7gZ|34R;0Dik-h$4!tM+@<8L(n;wc7+o&1G<v8G-!7|(z ztY=pCr%4t9-6@T7_-yWuF*w4s!RkhyUVQ)eR7HfsGP@QHqS)-8@VpTk=SEDPxEW`{ zW4$-4=zL#)qDE8&Qn#!S>~5c~lJzhqmTK<0T<>BMMhZJfLjDs>o?p$(y@v-+@WXT( zc>633EoNgy6|HU z!7or|+24j$q%MEXbBZ`(zF$Es26B`O@6ab`;%<9S#ZK?aS449_)#N<{QuN>(^=i)^ z{x|K6i(BK+O)K_F-N`bRSFbC1h3QsH*un?|%KqbauJ?NyERnIc4uJ|2Q6{EnZ-3^qQgQTIA`{{*)~GrtC|QKsiS5 zDp2>n4zcC1fnplv^!e%*68B*Ri~?q4Z=j*`P^{a^-J@V~@Ynxfijbi}gd>TX48XSY z-FkJYCXJj^wS#tyu_#r`UZq!)vPR>n?g$PEf5+jnv+eoOLM7rHi4paR4dwLmDja|~ z+;QVnn|0UJxQ9O<-Mu0{KP+^3n-%6XX9GUPUYlv8#6m0AllPS!bWcBXp_)0+w<(*A z14z@nnas#=W{So*Y4@GaS19#Av4QV0V|MPL+Q7<`-C&C~w2;)Xr+A#`Po!2Q5fUTS zI9DH{m7NXcl_y)iOCvyoDi4UPjbBV@Mig>}-7Ke51IGg5lkicYNBPVddSQTk@J>p1 z%&O(s5Om|lD%n*#9OI2v{HtPG{X<%n^Bdehi;nahk0463q8)Mo~1fl z&dQJt8d0)R>0LsH%q4S8=o%J%w2;s@ zo~$c%f-z*{>kn91jhh2Z_C8_Tg7bLWiWfva2wL08=yr<9hNXIhAcJ>MW;6rMjCT#i zJ;J};R7j)N_djU?Mk07yHg^R7K>kP(95L}31nNPm8{=H@wS6RMg4JoxPe;vDx}Hxp z_uKkz04I%$8Px23o4Ih(tc|z#-}Hy7rwX~sHx1ghqf~mHI7pJr2azkHnaD<-4Qe~O zt{arQOaBa_sImH58YtOTI7Nh^r?Oy1EUT_Zoi_V^FjXg<_vGfc3LaK1$*yK zrvu>;me%!Wx&ZFiPoZ@6n+~>dwVZ-1GPRws>*y-&_gnNiMZZDR-|6ZM`{3U0Q;(Tt z2Lq;%PlPsHa71$)aC)~svBJ?vd0w}+x-FqVxfjXX*rZCIP5rU_PYBCuHUOFh4}mp+ zTO2U3i~#;B4l4~uFVKO&iev*B^<$jE3H$o^x(%P^J6hCsQ0A$$o&7d{6*vhKR^#EN zE2FRM3<;J#QpfJ6_~MC{uz4VCH19VA1Y6o<3>W;Cx>B7vfC}+zhX!z^jrv^A36}(W zxn`=bjC=RQ+Z;sWwJ6teZqUrba`Fr$xeaGFzLX>F5~`v;4k`5rVix?_Ge*JjZV zu6ZjnN|$&hRt5M#BwNTKyXEcw%@xW5Sc`SQ7^a`2BqZ@^7)^lg-W?@ zPYZ)f(CI5do0vr6XPx0gbmp-}bU~(wqu7#juUR1{3=?6YNWO+XRWpFHEkJguiHkZ? zGb!*$p=cje=gg00_@Dk$ z6HZ~7e>O!^w;A$K%uFg}?pn8eKKEh?ebRXHq$p^N%Qi z4^CX)TsF7%EHs3FG&*PW@ZMEr>v&b2Z{2~{OnvYrNK4l087s*T1)8!z@+hbCu(>SC zvrT8_m|Buco2+SE`;%whv-NHq6?`fIj)J&agE=Ub6oC1m??J=ys5CZB#b}!V#C-a4 zHs&RvdQz_~2ir=uc+-lS&LpA0lo9|Joeyc>oZn>mgFOsQ48CL_;K{NEf-%+9%L|J7 z-xX=$hc0I6VG4K|YhmGZb+)73$^mO8M*xs&3nxB&;JQnMc%dwqF7fG{X&Xxm1PvUJ zdUZqKFf3sJ+%^8GeJgR3CqTTg2Md0AT$y^WTR1V-RXK;i1y>w@n5h+1sNH2fOBbk5 zph$h5YaIB((t^!05i3pF$T^giVlKe8Zv4PkQa9nGO&8EU@m?MBV70MDNUVcB_EYpV z3ET8`q(Ucf#3j5SFVg;^2vf}Bv9tBkNE|tnExpgf<;WlMvS&?_bxT?}V_faJd8pM< zA!xj50fp}PQhnesi5l!d?MRd5$p6Y_Etql-ZYkB+EVjJ;6(5s!o2bGy(po!fWs$}Z zRB|n&bzn+|n$i5~QfXReSwGv7bD9TklTBB;mA0?)b+Pa#nwYgyJ)x!$sm<@yMQI|_ zfZNjJPgj^5x+NYfdq6kSdjS`tm)`ZOFu%{!lwm_j6=<^ttcgJ(fNQUmvQw!$e~!-N zGuW;X;{Xh;;5ngpIv@Qoc7z6zulTg3DUdXegerlv>^Hool4aZ0r~?t> zD{(@NJ)bBDOlp_Ht|yW7{RzM!Udk6ij0b6wTIVBR_5Fk!Ka759HHd(zbYsmQ6q?6Y z7Gq4q;(mwSl|BmG7a`qWrES`uchG8`nVVNe^KhpNN~bmf0TqTt=z2QZ3ts+eTxkV= zPTwG`b%eA3K1(Q)@t*@*BN=#is!|Lm&Hv;-_h{}}y}FPTG~oBG6Y0r1N0})CnjZ`L z*=`68w7o0`CvqtU?qEk$rU$4IsH9y{a71p@U@5uRsD=4tT+e~e4Fv3U8n5jf(NzH4 zKCT}eL8~fzyGrM;RGO^KHGgra%`>_pI)^h7uG>l6jFKgD?|8jxlxy~-h^SXAgQh=s zGhp*WE{g0}6zWk-Rk^^t2Uo6h@%3yRqT#jL83rZIUOuv(fRbq27xiTM3-D}C(=lxb zR?N`Mjcn{{j9>11s$oOUSn40frK z`N>y8G7AB*;V0Pwxl=_!G3AAa$dGtlFPMmR)b!czHv3=(Zqi^U$=Uu4 ziUDZUAA~XHY0^DHkJDbV82=+fW^8bKUUqJdfIe{f?K`*alw~GC0!dS1tUq<oA^Mns|{A7+YiGoo6Q)Kn3^T`FRs&2rT)m6mUz-c+8j+dq` zSkrBX8P_v38(sT%yz+OzR{#KpNQ_{aO)b8nCW|wCWf?&NRtT<1Si#$$oNV4eUEBh~ z7R7?|x&%MoMY$>B+8d~pC&Y!9mO!rsh~m*cA63GO$`nG$YRdejFvV&hlPalZRQZ!*3Q?iD zGiGINVBqqcA>v3JEz4Z znKv6o#3Ari{Cgz66wMl3nnZU#I1$7|ir8Ue*CF(qK_dSX8H-Zo@)A4t1c=+_9uZWo zcS}dYNrA>Fyu6^x__1&YVNdC&01-B)k60YU)Dn*w5slkAHCO_9 z;44GIGHjXER|H-L6J3F!nX@Q_R>h92!&VwEFjGbrez1qgMhpnR>xk!=%n#Z>yMW`i z*GE~#J#@s#JuK0S&ViVRaY{31q{oBvD% zY|E-~F_+_t4i67$-lVSnbNNthWJ^s7 z(=RmgK}?VnFxj(FEpnO8Wm;rZUW;3}*WA$b^oT>HWhb8;;>aohB^{}=Cnxwzj^MSU zCsWQFq0OiqJPmn>R86@eb*w{C^fWDg;U z2ue`psV#Yq=x1d{R3p1`r@SZ4FoUGvY$`r<`A(h*n#s6iI<$p0DqTWYIg>@huDIt< zPVA--APJ|1iK#*K$T84kZFLX%8AH&ZKvQ_{4WCj8zDdVuP|LKEQe%Di%8&xJ36YsD zZ$A;2qNZuN@R{OG!_nh#$D~GW_7-geD|vVIkd2rFt`&;*zyl>R(P6B^mZ|irYrjB3 zcQI^Zaoit%q8u?PhxQn&TnYOYjH&ocCF{*jZ@%%8qZ)6zIHbuq#DUnz15u0jts{&Gyw|}L(O26k zt>GJ(atyTXG>VN{z37S=g_&i_=y=OoX>~j0xI!nRMXW+W@M+u$LE9&2TITB-rRJ{u zw^P*-zOK`ajY|WF(XeI)A#jv@dP|k{Tg?Lq7(J7GI3gzN$)<>OZ3-P@MWq=j@m|f* ztLtv3Qjq)`^eAW2I^W2@3bm6)Rx;xF-9l%oa`(2F7(C*i&W-Wv0RCirAmHxl#=rmK25m#Rb%UaU3jW&oiWAnK+kq*>~ z{&N$?Hp@h=9(iv5!V|vnF_IjGLtiWTLhp9?&Pj?sVu?KUBcd$Qf1#+TM_!XBm-Bz= zFj5dz|CwHDHvxGY&{GEYr-`-pWy8CHTa~)6+GlfTK39;2rE}KQW}q;PvDhnBj`Wm6 z0#hoT*nHh>Gx$L>`XhocC!89m^dkFNzEZ-!YihW9gHIW>wF0*>xK`ucv@o8Ie-i6w z3*=Hu5-u!5=(_~oWs58jbdGI6_80k9I>qWrjtkKR&+>UI1X(Sb;8x_pc_@0Jo+RT- zUQ*O^#2Ed6*AZ=Vwv)=DNx`9BhmMX*9#%Gg_OYZc)PXxQ!G$JxV-NtEMpt0! z;pCH>D!m>U**NMmce7R8C4HTUq&_&WqQ{8E3&yiSngIh-X&=DdhyVDrle@f_D}JZq zmA!QQ#?24kFcP|jm%$&|uPQ z%ui^|`jbI>Po6^Dv(CdeNkR~GP49Z_x@&eudrEf#vNcJOCCA98y=Km;&XYvxz49MK zT4_zNu#z&;{z@FQ#@XvR6enUqLiUkNgd-dqgceLQ3~0Q1CF#JIOZq<>wdauFqJ{)Y zuIkjz!uBAlW#Lr`tlqPh9)+J=p^6-e(i}fz#&Gsdom?P)Sl>znoEg-H89YZCg%GhO zTznCy#W_L{@k3jI%K8$v03Me{R#5i1khRk}5YSFzu>1~<`wSwJZ`Ne;CF0rGuycsy zX!Pq6?knCpC;)$)dU?moivnAIWm6=uLF6JlMcOT34nLYRLMVKeI>vwJQ>0ZH7wmP$ zl+ZTQ-A|EE=5y*^-_r|g2H<`MCA;s^@{`6ce#Q`CvoJZ^m zI4kgv+F2*g-%+Soh)vIWLJGlD{GU$hO*g&(oyG)4Q;PAjYu)b+@VAt$)z0GaR>A+q zb6fAlsS?m47+hPnksN*Phi_Kctoxe# zYB)bGi5l6sl~Q{j^Xd-S-8%kzBypv5_}T#5O{fg@`G!}S(mTS$9#UZWctQSVh(O@i z!PL!}LnxaPhO{$wF!xFD-D^bQ%EH3Be5TcZ4w@;Ov);G81H}{ z4M0M(&|!qHgxH+bGJ=zg)^}evi|s}zvE1pZPui!2vzI+NC)Epq-KagqBXwzWI9aS9Ypx60}GHME&L82*^Q1Wp^`)#6Od&s+)y%#zb(iydnDsB|uoP@AR<*1)a^_w+p+`xa|p z<39hJn`XO6Z^AO%3_xFPWAk6fv(UBPFLK(R=i!na4cNT`Y~T7=O_clwy!3&0mcv1> zA=T*$(bl)2MGhu7gQo8%`~6caq((ai2P)LEj=6Pc10z4idiK98@e;~0H6e&sBlKi) zf@&OPRjR<7R?-xSa!Ul^Z>jp{=8t1lR?cLnwXm%nv-J%|1qtQSEwRa^q2u)A410ej zVG%0dezP*741JOUiS7KYI6;O}EHjnRbqsGl*c%_CowE;!F2&$>FjZVYqWw^Yx(7(E zKziRg9kDIzojgLEfvjB#m2QVj%7mkH22JlFAKy#pk42^>wG7JZGeDNaW?^~M%lJZo%Cky`C4;ISAixxY4T>HJFT`M8X~bdy+?mdzwPDdz}_X8SXQe zO^9FksE}iu;@qSgtM`gWz${fcGihpVv02;5vSyj^>g0K0Ljd7H)>Eli0hK6^RH~@L zt$bT2Slq~d-$x;LhVN~FYyZV}9)dn>V=ZPIEJZ_f&_@LU35a)=*t_K@RE1U>ZgZx1 zfW(xPU%hX%iP(TJy*=`(hBlTfdtIDmz)a;O2-!dv&zOkc@NARTnfiTr&<4nDJ|0QG z-Wxid$??Z8wdO*kji44$%m$#u8l>-e4=mB=C_Wdx?In&u@|Hk=bRro&Xpgxn>cfmj z+1oE?NofR7-adM>!j6^X&}||_i<5;b|XJ zw6-}Z07D7;Vz&rP(}(x)D2L-l0X>o}?dtkjpUxQ2(Afh`A9(Arc%FF7(Iz8QOtU3k z8vk$pMOD*TBWJA~(l++tJaR8NqLw=mwiXH@xY~GK1cB(qeDop`ix;37nDf+c=Q2^! zb>v`;1-vxq^b1F>(%xnCp!VdTI+v8wsNkSrWR9IpQODs1LEr*Pq{xKQ3}v50!n(ae z3UL>mqxm2slc8sjo>V-c;=IhRdhcMXn+0H$xL8^EYIAEv41=&z|DpeJMdJ>Q=^0{KVZ&ZEGD;e?HyU}wNQT_&*FN*-Ed(0Hi9IxpyydDAWO>h;e zWiH$Ga*0@*=Pu_T1v^MGB_ElJpBlt;QG(I0(i5XefA-Nm!z25Q7-xJ3QPwF)Q$Imq zVSTu1RJ1W;i!gP%Y?C zjynH`as_wihB|G?($ePG+)JwDjb?QQTZ9U8IT@X*?lVyZ#Edkq@$Z_=3tVKUK4fv| zO15k{0JoO@K1N)?Ss{mm{OZdM6~i6{cFF)q;TY&*(wAmNpGWOKe_;O6aw%J2 z=VvYXCGK0NVYp93YXSw}kC2H=N^q3|R(Foc4q*9?3+Mb#sO& zd0gwdq`U8~(Osy+aHI<>9kUezQ{M=}u3pI55G? zz^8kBn~HLv^t{V`^{kDyv^TZ67Xv$X*W@`9)DwrDZi1Gc1067Kg3+sqc2Qh-Gsy6Pdzs}%RPi~QU-YK7dx4Ddth~92%hhQ9Ty(w1hQpx_Su5Ao96xfpA`>QlHk^}U z?rqLlLoU;+S2s}-NeTbuC>biFtCfruKt5k3=Vn=6EO_>+%_#`$k5(6(J|U+BA&j)0 zda!IkY=p^8il#*P^_rgeh0?24HYjI*r1{pIh^rG7m_lUXqa7S4xZhfS1JK6Z>he-p ztP*!tSWjQ@)0yq#G2W$8E;37_L_?f@jl7IBw4fA5iygcsEM3K5q{=6mWE6~iXGQ z(z3tr=^AbA-Mjz!{^-2YU;)=q1nHE()zoSyQ6|o?OZ-*if&;yZ!oxGmrYq?s%Gde2 z&=@6cHpSiG1W$|+zNQnHPzoZCDNPt)LCgo+GhXBe*~3Eil*$!)9}!nZ`9O0c#L{j` zjMECHc+YYmdQH;Vt-8RhWiXUe{Qm0{(1xRy7(2T2q~4B-$9%l zp?|QD9MhI2mvl2spt8_}Zg#htKr8&t1l;u{$6R(!sb?=qh(4I6doa-Y-SDfrxff@9 zqIx}p4m#Momx3C}VFFPer+}?A235BQY045>3EeyWkOfk`LJ2fn>y_di2r+}4CVE!j zZGM=g*a?M=Xd=Gb$W$nBk9~P}Z$4D=P)r!|vs^t6!&I^ZcV<=aGx1V^N=fzo6-v{G zOb|V`wI`R9rLfe=)C(*JI{FPmjx1=6-V1Ba=hU8A#9_0m=D)S8a?1!Q9=N6;E@m03O; zhSYNcwOA@x# zkZt4Z?=u1SS)4ikqC~6YBhgl5NNeQmqEpEP#iKnwkx+?Slc}=uwj+}(?S9NApc{dq9j&UI@(-X1Qs zahZ;>XdIhP#LK6x3@1R+7Ru-&IT5yD51@uMH1p%k)pCsIMGlnScij!PQPJu)k&QCq zX{w0H67=;3D5I{v#JJ+fIZGhhF+fZ+)NNqHI{oL}1pB#gk-;Zp?qP@4WQUKOqd^$J zjSUKoc!}@rO8=$0qc&}vxfEABX0??YEmm>(o=ODAlwC`}0HG~s&fXE@7Q$4Zb_qa) z+U-+{&)@$?j6{#dv49P7L5VT#9&NvU5)2KzVWdk-@IU$?_9%f6aDcrV0OSY|A^=fR zc4YV5v4I7cB_UtwAm=88F_3>b&?EgcFNpji8rbiodbd*PD6iCy$yYd0I%{l#XyREx zBpLicAMP#0VpA9P`0?gW<=EfxAyxdPJ}qbT8rxrS47_$lbIF1>*2PhYlVtcBlZkkh zs#7r@dVbO)-M|)lVbL=`=ToW!8it{4jYty_(_!G7w)^(}Z4RBuIU#^Pd^%VRAX=*? zM4rS{v<}>5pt!gGg5Dmt%EE5~Lpikyu=K)chnZ~M>Sreu$sAsMB`Rzqko3ep;BPdL z!WI{P>Z*Ss(v*j56_9FO8d*NkM)m{8aG8CTToadCjDXkZycYX4ciW^I9-U~E==N(- zHl{78*%^;rc_-b-l3I5B7G6;w+DnHjhMCr8H{Wijk%-%Be|(7*=e<`Q^(`Oaov~TJ zJ+m-s+KOcAeL_2kRZ~>;UKT3;4sQ7a`Wp8)54>Z=VV~RJ9mEpTJBJUaBCgo@iDoe< z=uKa zD$5MzH9$VH(j)7R+xSa&>Ctp@h1;EMZ(^md&~8En=)J%ynhj*RiAXnR+Q&q|c-sB= z@S%;NDJMs8F0H7S%IO41!U`gZBHuI6sefPc)u&`0lOmF z&^;E6AtF$SLN*d(eJWz>s?J0ba@LLG^;DJ{1VGdqH_vlJe%MaxR3L)2fS+pc342V5 zCer|OF7(W9@nyV%ngG)AUuD+tMvTqCnlnhnW&QzBWwgb}rM;wMWpdjp17Y#|2nQe^ zGDQ7@A%i3@(@`u)YaOOG-B~Y>ELG&t20NhxPV|PjpaYFE0+;Ir)*lMlix+yY7bhi@ zU%eH_A;mfif!cJ^VQ#2OhQ2;xN9H0`I3Pl*@tmetU@y>mBx$lwdw}|Oq9`MI;=yG= z)Wh`(#T*20@xa5qsZ1p@Bf*uYw|KaP6W)zBO?4>G4&)0hOToKJLIe@1UCkMwrdmJz z!0mXZc(FV@8pw(#()KvamaMKd?plAUjr?Yk`M;|Kyh+K~P$BxKhi2;gOyg`J)d?Tp zrFn}^g+NdfjqDwpc?Uc*^q1PfkX!A!c4yD=#Kx(5i!f^fe@n7!DnMglpx#LNA4hQC-DMkkD4rw=rUF$ysh5i_JLgha^C+Q}kQPG`NM3Q;KMCP<<4 z-phantA%pvE6*)oPEl$H;-TUVx$U_Ru%R=80j|+d6jXTOA)S|*z_?avdUzc$Qr+h> znfVI9Sa9?H)^j>%Yxz)McrtvFa*8Dwb7bI;0H_Z+RnGb>8Q}=4G@<~Ou{~s;fQKvT z4jx8ruwJY4e?R1jr@oq2!i`3zHkl00G|F1@SCV^bKXJ_(yuBant7!*pUdB=)C#Mwv zhrA^W)xe5(+DQ(KasZky_dQd5^o;na&bb6Ai%+^l?2}BM^iwtU_)n$s$n!|+lJw~rM8Yi?s=Gy#lrop=N@$v%( z0E+3|{Jj5_^6>kw_g@izeY1MG@p~){TvUnGRytnan+ixvNH!O_Av57|YE(*9v?NP0 zelI46bQp~LVLWWKwFU96tYxv2dQd7VZDRo{>o{pgYIvC9G~d5Dtb2|TL)v0jTlC-) zj1N>S9j2RMqe9N(sH7X}VJ3HUJ~|qHmn+$*?;WXhU%m2N@Km_49Ol(1(dCt3ff1U` z?Oz5|;-k^X6efj?r*Lojex!}dVk`w2b@MtAi^y*yhy7B5=0W55QfoBp*#K=kILvfxPMT|PwX?_C zPB=S!6k=@~@LMrz2K{Lbrkoz=jkwt4imwM_-iq95JcmuBmOdcV(sk*bbYDfZ*6TDz zgh8#T8cLOq1y@g{&{G*PWtzho0Kq#}r-R-Tb%Ih>o;+T-?5mYG@o(;*9kLHVU=$>% zw=S;%8H2ZgpD+rPmYmr{dHf(0r26^}r>;eAoZtSo4GCNI(OSkY*6o%6dub14u+VcQ zZe(J)n*hqq2m^OqOTM0A4c=TTZq`OupNQ1D&}MZQ3O_#nN2y+!FZlwW(1}V7ZO1gA zx^&60-#TP9JA8}Ex_r51ok|qM65gm5V=TtVACJ;GVV1C=3IrIGeZYCKPERa#HdS_$ z(U-&Non&O}(cvOm=WH`Qa7)|53Qb>-Ue<$m)HX-itly571B6yOZmF%_n4_)B%&9GO>ILA{)=n%~Zdc;9?4O0QLh*Yy4Ws^D>8?Jdn`A<@8L*Q_T z_ID|wW?huT^=I3}@qj!!ff~zsaq#8G`R+K$SQt-pozA5SMf?zJGQrh$H-G(y9^PDq z>B_d_tTN?2Q1G$#LXJZhQe=`3rS`e}cmXlou7uEWq;G!od_k*sDb*%;2oZ;mKAu!a zLa%jQB15+!O)u)rF&OVoT2h??kSb8^chZ{k zqt!nK)|nm=9HPSfGj@)vygAop8P_^5v_`SFcL5fSen1_QkNt(U8| z!DzdVGist=U-8%(fZ=hr<9^Gmh5DyA_R>S*kaMgw(g{JC5+a*^*p_m0cG8lQz*6b0 zaO9S2%*->yCa8>8OU&D7u?(x+)nR_i)L1Adcm(&X=u(sP{vX;~pVo;}uC#P(uXuUL zfjj_Qr(dzfzMsh06b0f!j!^1yG>6tEg!raWtX}9R`r6W6>5)*4Qd|cNAa&|Y2ny1< z2?4w=yRt!&G{q4oyAKMI1Tjf!!N*AIyL^p}V5#3s=4x~&)5vT}3n zx~;R~#@D4LRh4U#Xigy^yGh6?`u0eA4Y(1AAD*tr3|c!&41PzkZNKr9VZMTkj$DT6 zX5%v|3P0kuPQsSPW_Tu6Dp+i>TImTpO%Y^nUH4uOr5alkTjUerfXyX zv2-vFm$ze6;OuF@HMBq5UBUD*5^*w^}4S zGX@|M0wNx~Q7%>vX;%ef3l9^Fcu)L9}qeT;C0^9OtCT&rQV_!G`p1<6@Sx?2Y}s+kC+{ywpoL&q!?^~zuQc*pJq}IRfCcvebSr0_<{CCjfSA=ll zHSyAn39=FRQ+9#VmE*m(F14aG!M(HpBI&tAV$qqeCfV`&JXF}FkhQ`Iq-KK>knZ}E zXx=saklS3UZck!s1W8X0xk8n4o;c`ZUam2tz~K^cJo&7f)JoPSUss81(>SNO3i|4_ zPC8K0g(?Vz7}A~FwfXkVEn+&l4b!93U62qd01n8>oZ(20)kY zm}ed<<+xnj4h#P>ma$HqiJf^?*x^y0wOd2;+zq}&p#=|b!V0fVqmP`D0ry1`vYHkO z$~rwIk?Rf-w#a~O(Q3TAI37firjI9-2~4^o6WOgFv6U$?vF3P+oijt}Bb6m!)@p>H zoWWKaRb}RFuVRCcA*5?R(gcHrT=zt57<*$Ii%&U#^4q-i1Xi9si|8g#{De9#|4ae3 z(oO=n%D{vFf)rN}Z4B2aZWaYr=^*GW$?srJ=d=+9AP|z(9`86T;RjrcGSY)kUUF4V z=IHlWdHsM_%zHbAl{#w-ohBZb&n(9)OO=@^&WG0%?9Ns6&Fspx9$DQUF~2qSrk6U( z{=M%VT26wMr2%{_zB#+Ky`)FamYeNX=9|S}8FQj;$_IA=LKJc8LukgJgvt7)v(qqvxYuu8R^vS ztWBo!*C&uEB81em{vV4_l_!f&BA(H-(4vMF+FYb&@UiV#);D4Wt~MU=Ju*tY?L(8S zBDrlCrmZkypHZjE-70sYJj8As3D5%?;U1?L=(QyALnQx;OEPdH^V1o%BA$2OhM;}re-I^@UAHT+Na8u z&)|pGGm|fwhQkSe;*r~m3|UpshKgi14RN^YeXR!|^Ss2YFgna=HtJ@yn039m*O`eP zz9pI!D64tT$Muc4vJr}})llO|Icao-HlxR=YZzRa4Q=Jx(VpSz(K4OJ%rkd~pkX(S z-dfv0%t}Y#AXB5;i`WB*X)tSig0IEUms?vo8`c0fK*+xf_kG>M9^oVG8 zPB-87Gj!dE%W}dta6+O_t%Ku7TsW4%(kWYHajpX#&o`R;n&FxrZ3dTZ=a7Js#V`VO zBNAniLt|~GGZgHznSKZCuE>Xesv`3U&Up_@x1dTlqWGeeR^7N__{?2)4t)L-bxV3U zbW4;~35T{!k^P9c*OmKjx)*IGji4hx<5*!e(hDvNiOf0tIm@GC>WG3zNbv`JV9!Q} zo+p@hd8o7{>nNYY!7Mg(LVnlYpF5%(8Ll?^dDAj{@r^+=L`<1`+h3g#u9fob8N8sC z%-^(Z1S>IRg^KLAsnhTnT05csor(Tnz^ZA0t#~~m5m9ZM7mAzPi9rLL-~?_80vLOy zeZVTVyNN3E)7c-wW+}&X4h~qQW-2xLigULfO8q0HXHpLf;lWU>7#;9a8;k-`>s&L= z`Avdho?OFB3{}MlQB3S#m5kn{CWUkE&*aP!vtAP~hFO=Xofn86HDPy{7s-wVh>er7 zlz-|~*MsRLS+GurOsvP5N9{j{nE4s7+$5v|G@G2sX5r=aeO?Q$q_CVzfZ?pz8Ab&U z>(tP$JPqLHZLp;u4&kc11an=K^Ir^AeMUz`qU=*n)R8fi@TP!zzl;`)ux$(0g^5i0 z%>u5&jAdWI#&0?4SHkvD>SRB4K`UMM_t|_2z+Lb@zZv+Ng(`jSP#Yy>(dO#_MOEm< zCn!2_nbc4$xjMH=(D{;xcfbgweXx9V)M4MWL#aty+&5%vF6~9s0T(Nj_$HxfCl2bR zovT2o0i-DzGdAQYJq#Cof<(=8Iul2xWmIYYFa55-;1fe_F}oJu5QlM!fLE1~$JV37 zB(CYhVL0n|;v3}P^eWYe*~ z(Lq@&_@FD)gOY;AefOt479Cz$`kA0aA-gK-+Pax^_UgePpe#z zl=c!{VkG+G8!X@aC}11fI91kqhH~C`<_nfVlJ0RJt>#`q6H@EhOB!ww?Uv^R6_o3- z>B6lVrX1a$3P9(EXzlr=GKL%-YH7xl0TMONI(=n`x@r_kTAb=47r@jV$_U zwQZVF8#rf=;-qQco7!ER?8%nr2B%nbpMkD}Li@h+cX9I_CdOwQn~LZu!A_Pl4NH}XSljfw{>l*0t?d~&O}+um}PY47qT+LDpD zWp8cw9H41aV3>f#+ieXjY z#iW3azQICUR#O_U`SKft2(eC6zBr1W8F2rT7KN0q2~nJnA|}Y2q=TdfdPqwihl3Y+ z&qf&P-ELUy#A=l{Td+|f#A>t2i`-vc()LAQ1LEh4w*)a0C52a0>w1^T2c9vx&(5W` ztX6uy2^CBbq8&Sm0vKip_oR9Q6@pK|9_ERJYHa*X;Bc>)J|%HV45d+i{@NLX39H%K zbY6enuH0x)D)Vv~3L(o9jm<5x8hOq^mT!nFlOgBeAxYWHJ)`WO69RiMJ}%I4A971( zPBPE*ghvx%p8-o5KnI09L(0dN2wqE9|Cug%Lj0TDl^4i?>~iZm(9-l;@fUo;aRwl! zt1=Qij=wT8Bt)g8yklB!YYdj@er!K)MiNeEMQC3|g9jyjuuXG~?(no5hEl*iBxixU<`v!+5AzFPaMh-o*@ErbFd4_BPsM_U znJ2g~2t*PBw`lZpLoaKOFhXXlf4a@ODu@qxOU2v@g75{UC|<2mDh@Ij#haXt6>53) zsIg}=$w3RGbb=t9KiUnP=yji!l9Z#d@33@}{HrjivaT#jm zi?A5`NC>eBjp212n{6J0U#7U*2wDUza1?X3J!dSf@0e7% zwPrROSOnpLs`JU3i(D{&PyZvJ+@rK(!?8|GaY+{0aWcsDL(mNoS&2iIGxBQ8Tj<^( z3A2;3!K@h)U1>rlIr$hL2X;mq=vWKNlhfUb?f0@7dw2(gGNzFg@KGe+_+MsOO1@Vd z{yvyLW^v^~mp4>?UBW91LrJ{OylwYatFDKB+xo{6to|9!0rKI8sZN`jvi&@kqd_1+4BOYuks4?( zJF9P@R#Gr8N}XfhI?FJt3I-Xzu$I|6K0cpt#8AK|hdtuN1FV_mz&sKQY%VX%Y|{5@ zf>ZKfYs*PXRj~or8aLrTUl4P|r4qYguMk^CylahXe;O6&KYGdW&*k;T+gvG!Yly*| zRfr#;K5%jF9F)dNaGQ2Ozv+Y&jvG4fQ4$C6u`5vWP(^|t$|)rZFvS~#dgX@F>Vk8# zbJo$xq{uuu78{>NPx-0p<0mnIhxcq}alIM#wV4(+NHK<`~Ytz934d)ZGXHFsysO&i>Ka?sPp5>-Twla4|&Xx9Qhg{qlhc`I@e_*JoF;1?B3D0OWR~cnk-}Lp&MKt`vMs zH~u&bH^+#cJ9*mIsw&jk*0~wGt;C{Z$uq8{c2@u~Nt|*eRb>tOCk)_d@>=thlf-hj zsahrqY#aK#t5*>(cK=NfrGH%OgA5TwR9@H=|F^tB0;Wv_aALjhAuqFNIV?Id4 zNj==334;i|9I{OZRv@*Khy`2yKQRSsINk>9#OP?*h{>z(1R*-KXpIAj_@_6BvH^xv zG>@RzZ!%^k^H@Cq^|kD(N_0Pei9wYvV&ItiO>?+W$hA((k0XmfOZ5LIJX6>p)Zi;2 z+dK4F@2&%xlZLiytOR+9{X1RyMVcH#GrYX4ztXxml0WS8 zblfc}MW4fi|50NukVMQv!`2zYb+0?a($_wOG(H3e*-!*w|9+{Bh9Cfy(`gtAqc)JF z@==9)oHKG>3ys8Y8+Ut9Gb_VpHo1gN#U>lVXVWlu#QY}QUa)wTM6vkx0WZ00rl;f) zj0nYl2*8R;Ix_(%8mb|kCQ!7D<(Nj|=4jjC%jrk$9>>~;n5?>$UgJ^s!wrDg@L)5b zKwIlds&Q9o)0drjC8RrL!8k-uWMGKeMsjLU`uaXo8&oK&7fQ@~jI0b*ea%~hvYDu* zjHmQ2a}=5nMG~jhlw{Y*ZqIzky(@VmhpAnOjWA>I%t91T(^pKbW1S~H^Y(ni zyWEpjGorzrTz4qI=r*XN2)~X1FdrygZP<-&tS=fxZz*y$RGp78e#wHuDgUtO%=b3c zOkORR5>?Gv_gvt_I^R-Ny7%0!D}B!ym6N~F(J^bq&(4ZOU@X!rO5H(KuW1A%wh78e zUQ0LYAUUNJccP49N5$lQp(2!A$+j?^$g;&I?z!gF0rRb2u<~AHelFz%Xei?ppdH}X z5-nNeCu(CD4qLCD03txW^eXuGM1c21A(;O;f3lM-d0loDmH!?cPm@C2T2E{btJYGK zSfrhT^ig9-Qiy1PUJNW@Oi?>4@&~FzBXmw$*`CLX^wwbE=32B%cx;k+MAKy zZpK&b;$k9yh?tW1ekbr!|Bg*T$#)c^XMV}~DW;V;$`90`zuwtV+BYK>|}I#j?hF3w7%%wFHZ6LA22I2-qCqK ze^(g*KknP-KzN9@nxTxl{c`-I@|Tr|Q3WxSFG>;aKItY7cEIYp5s>-sgz}jVn4>ImPSdJUYxEEfL={pD5BjZd%GR+KwK&)T7A?{Wt~uhZ!1H%y%!i}#GIQ#+&`N%-{R z%GXOIcP;7Y+-Z~1^L?947R^rw!F|2YLuWBet#4AuPk?AGKQ>3RJy@!Pab%3n`!1y! z5d(~w59#~E+DxdlO_s^N3Z5gNTe}LOhvuCI0zu5;rmfGh!{uMCr?tmV3JftEdN!Ve zIk)}#OhhrS5^+b>9Z$pHm%&7vFOia;0H;zuS&)PuC?aCC*|_e_#n;xLJ8i=q12$ZC^53@j|ELe2>d7Eb%3Cg&n}VccuGpG zUnfA!dNnff$97g16G=Q2f)t)t(^~c-zd#F)6BZRCvXjw@5@;|Y?pPUBPz)cPVwX~i zZpgzT0cv8nmCNyXv@9@ObYzLxtmCgOHVH{Fp#u!nNV#X2(IMy&Q?E3Ge8lZ$j*$Oe zCU;^JqYE%W#wF+stCw^6vTl&ou2?i`P_5wt(u6}Hu7_IO@W9cf@TC&_sc9?HIdx81 z>@%#lMLg`%%{3LOx0=HMb57y3LPa=)hF9ZG*uf?+7(q?=NXqA^&Fp-PS=b?joFLuS z&~4kXBIt`0c@i9Kc?HX4+i@FDf$Ws>}7 z2TG9hgnK9+|F-HBf3+M*;A+#kp;LPa4n<3D!!`0yg5^WeR(YO^h4J8eqUKhS(Q!63 z4hfW^G6fo8)*7087E4xMjf&$tR>^=UgDcE0hZp5*G=9kJZh#;ny`(v!Z@n+7pv?tO z(Q{!Zt5MFi?kepZMGC<++nkLJ0~zocoo3R2!!Li#fsB0+625HA7y!s@F5#t}Mt-4v zc4tKohu<}xiW*29uh4P|(IJE2 zRa^c=G9E6onhrsX*q_OaL5w49rz?q5T7iRA%g{g|bEkdJ0ZsY<)l9%{|xne7g z@nEI|a6g~(i&`P;HeS{c|5dH*Rj*x)d%mo54C**c-vy@Z;2Zq_ltvzsih;{;AmJT? z0H&vyS7(X3`KiN{TQ}exJodv6XiSwe8RhR!zl{X!^CF&7Z3DS4p16ZtYbQ|*5DV>1 zy?mQV0me!5FS9a9KEBRa-?FLLV>(*KP$xgExvaUQBGTnF7WB~7Q|lv>w>!q0&!`R& z03N19CfQ}~BTz9pDb5XvOE+l-Z5zoK zG-Hj~G*v2|-MZ4>DsC-R_P7HTO1*DwGu1=&czylXcA2+*LGem5dHb6nSsk$sZH`Om z8p41ADCVH5w%b#|Q92I3X~ME{jHOqeqQBCYlI*v582c4e;EoW+hJ;ZoL~3ifrxLsx zU*AT7M5GC31-l_MSm74!TaH9F$3SH`m(}j+uYe=b7TXQ&vtu~0zdEII& zj}e(@Ow$d%1N)6ALdke{r&$OVCs34I;iFQDlm|>lRY=sDe2nDO1%8-r5mtKriF+Rd zuMw%+$+9lKMb7$Iy={3(%QL7*Qb|Sd%@yjML2TM}Yd?(@CUZm*!m2>b;rc05EOcr+ z1+rdAFy$uW%=1#ZeNOTb|`(5Q=t9D z)f)p`p&{OQ8vaM$XhRx}o_mZBz;RjaOgL~v0hV)Ob7-R;mNa2tK1yWxs~4OmnphVO zkvB6v0?G1>w%^GTMds$UQJ<6w$i=JqV#1VGn?G~nmP4`NwCw`uQc^+`1uk#&Mr0zn ze(FY9Lq?pB8;4N6WHb?n)kS-Rd?(xj{IHkHoYy}k_ zSXMG?K%HOR+|=GQUh2^64kHfES0?+?u?zLQy&X9w2yn-vwEO1L>L{MtKudky#l?5( zK`H9Y#k%Q&yj0CJb47OEbQDX|R=No5a%ls65CvTUC^oaNn_4J~l$F2A29RI*sCzBuOmsXf*ff;fC0b znn`YJopGbTUlkN-C0WrQtX5hrP7j zQ^!HF#mpVz6uSNd8m&E)gdV;dU1bln;A?XYzC}c|V8Cui=b}(nY@wra5yo^r@}W}= zI#yi$nQopUz#uL`bRtcEm%?K#sF)vWGe<+cw>g0oG1umvA?$Rg7CF~lhDnxi19D!F zb!5BlBJ*HkbUA2clw$7+s1ws(^*KCerBA1G>;zXmZXf-x229(9_pJA2slRWTJCtnn zMtB^4R*%6sA44^K4h>heK|Dt5zyjAuL>LOB#L+SW*<0D48i1`(RaVfF1AJ`|x)~!m za!Os!B`6P4J}0eVBmMUNSHhG&NOFX?=_Q(Xa^6XG$_lShfY*op?>W0FfT0WDwduT0 zsRk*Sk%8z)_<%R(drGg^=2TZ5pYDlfYYp7Dr#VdyiA3(ITJG&qpd$OP4OOx?Zl6j$ zJ#FNL3_k>zlov{!+huPmQtegrGox}vEV|z3s_z@H0#KiS9FAavzRW=C8XS+%Qs1f+ zEqL?sCNCN?wp7SXN|RGiF@p&%HJ zW7y1sA+RQUkW10gh?lgFC6W=T%$pGMa3J)vW*%r|u%J8#AUnpxu3UW2t^GLK`8dGQ z@yZB97_SFYzX65L=_R^tZHqYwpI=9;iBWLPWdVpRMkVVT!`Sp`QX78a*vcGskA&>H z&ocTdP6b$0`$`j4N6DSu<0LtC-}INfAM(5t=G?uEVv574j6Y2$3Qv2B)d2p2^OVEd zp4zKtlXf$Et+mr)>e|p)qQW*NU*pNvXJsZp_M2)zCaxA_a{9JY1}e>&l`J~2RX!KP z+Z8U-8F5*wV8+$Jh#T|l^_78+B93zU9`@Omx1J#cS!vkD>-L{}=w!%Es?LqHMPH*E z_j(;6(0xt|-nKuXU3I0%zvIEfH4_WSUClk^xP59jqRdj~w?W@xd9YakvAqL%Y&VE1 zJrVT?dC>vH&2MgaN#E@r2}QUz%7}2!Wvs5;{wYW9*qz;TQ%YXltSz>a<8z&T_hC5qYjO?r&VLq9IjBo$-@Cj6*r3F^!**EkAOdK3G;X79E zc8Jd9-!j$mK)=p^fZ|`SdU>zw@SR^h(%RLl%^DE~m6cTD{RkJT^{6|oYvXifPTf1D&vO^i zBBc@~O|-VmS13R(#gI8pj`8%#!?NQg9%D5$wk(u~HsF#T-JvS4RV@WyFIeOXO#Dwi zhtWbp8En$~v9fHSw+LAxQ3|~w+ju4-B7601loMcXFrP8xT;hmUbK9OUtl-I2o=Lj8 zf+62qk0P-hX4wn7qMbf#3BV|PLh8g}|A}%a2P#x`1tApEgbV1fltPHP$r4#f^UCbM zJ}S1*nnCA4FYpJObf2;|`9$v)%^D$0>Vraqy_iuPn zf5a$VJ=NDFeKX71fs9#Trz6Td;*~*~sWAf?TN`EvF1!EEi#t|RQOxmW;t{&+u7H2) zW-ic{AjF+Rs>0gU;$$!vy%`7WT=Z~3>PFU$Q5a5_`W42!s4jKQ`-P&8=iuz<=^0)z z2?3Uox);Tz*69U*lyGR6_YEcqNJ=g9KIoq0tr*cDaTYmqI>3fkZDj{MP&+s| z&e?@H+KYGu!YNdN*RNz>E#P;&z?I!YjugIi(N=_>gc(CtzIJuqlpaIN!k3}80+jUr zO^8YfG2ZOTb4hqvB>j=`s2Ybd@jfMwI_Vsp*b30U9Dx4lh$2oC|tR0Ua@yI43Fr#D5mC4#r@-G;@H`!}N1?6_R zhvnVnZ%lK$74gAJCewVDKC6SJU5B&X<_f72gLDnEk?pqZ!dvDKr`dx}7ulo%>bjtY#)bn}16T-vW{#Hb>b2H2hf0GAK zrWAZpsu1F=Fy=qXmd=HRX%pI#WZ{0fC8Y}lZ}NEh8!aFb5KICk^=ps9%1>#}441W+QY5XSL zR6k>rq^Eqrg&73;5c{(?2Its}QPxa2i(Dl+_`M0Ui1qX85Lre=#q=7eR(5r%<4W?$ z0qorL&Q{VG9HC;q#JeAnTA>AP4cP2oS}M4}f*<9r+C^o1Olew9+XNiGy<5S#A7}ff zQo-^<3?wQDPWZ*Api;cg8}AMP=`Y}s^7NIgTQ2#I&@D!oCp7eh`S_FPlqXSKT4vd8 zIfd9qMqGUS_s({e3##hChn}OnUHjRXurKSOXEOsVbo6(9y(EEcLNfHK=U8{w+_8`j zt?PQtjKz^zUbA#6RnV7r zL*Se#5E~cjv9lvwTrMKo_>f?5-$90m;c~M)hjvu7KnKydSQ&hA-s8kT+>6B_{f2w; zyjoKe+hK{6&e{|6odFa?p!T%+`#g|HHA&ra8RMxKoO!nwX`KRpP>O`5ot zRl~r4t7Fz+2tr*=;9QSr%j7EG!A&b{oet5V+iv1Nc}h=ye$kOwbn}Rv`x`9Ef6fPE zllHzL(vpy!Uk9)ZtU>9O^JVjya1)3wWW`3T{agP8ShRjeGx`Z-SqU4_TAbpYq^~yu zTdBKXU-M)Rf#d7|%SnWBHi%b0I)h5xWQWw&cQnfDj5rkLDSRjWR#lg1MO}p~PFhs< zX+_H;4DmBA(iu?a$D~-k3#{8XxXO8NjZp^SXeYuv_y;K!{U2F8hBjQ$!FVL>rufO< z-+|~#7$2XUXOMa${wu#;8EK6B`Waz;oBoz@5`HKfdO9wY%~I9HheteWfr(>I16YFu z$a_9}oP^P6zrBTG3QcNn);G7PS=z%*Y`qSTK<4DWr-FTq zrhJ`szi$<6?5iKtcNH`1=)Bu1o3~sPV0IQ{?UqgY6{6$5y+P3dC!S^XL$yXg3NU#> z*O__*>;EyRQ3Dx!vdRL6O0h1s16})HIbHhdb;Awhn1SH;_9xYJ9vUQKI}bGDC{f+k zPZXO$f3Rh4@>xzD$8o>VEW|%gkwd!TiZu_Y0J+u5W^pl5EG4B;b(=H+s-f@$hL*v z55E_2xW#&Nj2oY12dYfY%KvaJ^efc`R}kG%`KrZhl+GZH_>Xra`oQExyt6^iBP6_) zYN|~tg@JdZ{(Q6r@k#AvyO=;(*6mdXKx0B%yyxok6cio~;)Tc*ngX^3YS5=|1bhHm zisLdgvUlkyqOnU?F>^jTWQASM1P4-}-%#~mlIU7U}deZ42Hq%*P=&q~bz`ok8*Zk4wbi;a1X{*jd^U6BeDfVG z?L)1HjF42>JgNCLS`f-9n`($3W1h9_@b88Va0@ysOtG5CUkm~FbTXYy5kv?FaC%%P zvBBiIDmjn!AWy<45=$)MDjBWX7tN$ay7*S6cfLLHk}gF!FYG1XKsO%_98V1(uC~Nw0eZe zrMM|g4auaXdmNJi^VCF;xJp@S5MBs;Vv_ZpJG@!v70#u;Nw076mB}8KN)FarPO2y+ zHl9>M%W#0Do^E6jwpOBD)t>rWD|TNt5XR~KNmkR&8Nq>Vh|b%v-P36bxc?|VM7Os9 zfLF2YanWx*axkQQV$HItIWu!SdGs0(L6^1c!g7WK7T>gqDh@V~H_Ts)`vga_&T#=Q z5>XNM7x^Y2p{w%YAPbCC9wrQtQAvMmS?jyPp*$Ao)VF)%h)rVWmZD;EK`V=Ww{*>H}b)3fwsKT7kv2u1ygJEtNj3d|)N6t^~%* zL#5rWwFd-Qd8K7$h7pVEz@rBb^&w!aOqIfG`TvlPCbl+WE^=KUM>P*IugYh@zGU4TT4+oPD;6dSI<|?o4PT7i)D3 zWw%-7iQ?i|GipAhhJfW5SeBPWz0T|H16FR6PwAf zSjoU1P=zftJI9|{ zQrZ7GB+Z-iOQ06D`BVUu$0I{}WdV2>=Z=^~Wxo?7C|x;==efUm&^#XAbrii7IelJ) z@+wZbYu|~s3^x=tob)z$sbKOXkQ%<0F&N5LN1wmug4r2u>S)50U#5Fi5GEV} zJzhqR^swB%`yhCKtdw6P%EPY~-*4m02o;vZ3@N9D;CvND5}07JJc zlLQ(A>TvE-B@;pV0DFL*ia9tGeSaPKNZIJNiI}gxPu}Sq)=8xMh=2cw?~6K9SGY*M zaeSKs+Wr6W&UE9s*CXxncmiZ)!MIGU>wCT0%V@KLpG^fsskA!vHfVsBqx@@eJ;nXq zUEVuY!eaG=lky)KFbU-dVZKY!|Mj!Y&$9^}Nl>Dh;YQAKU?hUr@Hsm|#BwNd)$V%k zKovuUIm7@QpI{mh3ev2lh(3dhgW{M7W{pHD*+MFD;lx=L0iax3X(Kj9)WjKEqbO0H z)w+x_`lt+*l!wP%HEDoYwt z3K#KcMNO+Qw+#Su75l?oB{APD^gCOfudD=`6hj{|sblpy&+k#4?*oQ|LFLQ&EuWQz z*CIqXPzWu-EInnf*XxB*B*5bI&}01z|5U#e%I^`75%~+>`(7 zc98yUm{trzaTlh-P;F1DNi&5070TwS6x1?RE;y~(mW&ieRuIx2nOUWP(8JmY!a#&i z1>~~$x&3VMm?EyhX5wr0&ZxWI4@*L}KcDi9=meIr9^AI11|z;pXxEt0GUz?V8-diw zDGiKA%DO7`uA3c0HCF~_8(9skdmk`~CcV3*=Z zEnF(Gah%h%Yo&2CY6Sj1o9`I&LDd_M?_m$mp&HR4hW4-BEyZf@;V8;KLzqt@8M&c4 z>Vm@%pv@OBR%2G5B+B$i(@n-+Z0bob&`rD!%4hp!%;SyL1wm>AF4^)-BNjV4MV(ox(&j;hEu`^PBtKGh{3y-CSr5zEMJ5R~ zBV5c%>R_j>%^eV_kPz+R&!L&=FP4csWrT4jbsQy{EEUq~c-c4?>ueLb5XX=SM#R&O zX$d?C9B&OP3;)R26g{+!h#Bh>LYp;6M`||{@>`cPz9u!giu{cMVvw8&hpee&e) zU;Pb(9FtDqb8~sH_upfeq!1hRC0|g6_SA56zfqpO85vb*st-lBzGL3PLj4r3JrEj_ zrZAy3!cbAYHJ5qOr{22*T20?OlEHS#W@3In_CKBYk_S?}N@BW%3Uy*#UsxO8|IXGV z?P=$mUFr*XD;J=_3o6WZ5UH()rx?I0WNq6S5ZG3jLs~{|h5SlDw1=u=ZP(Q%fjZ>r zNcI>D;r?e_`_lp*>9)7&$-pAmT)U3H`A*$fD?K6f22MA~2~E?NH?{6Q0;26Y<>z#S zq?0M_k+npN@6~__dI|b1{&N-1T`|-n^Fca|HgvRmZx_dZ+n}vVRcbv5DTjjsgoYcx zj!E{BBm@-0+9YtsQkGS&P!6NLCsR&&&Nbnw^_ZNhR(h08n&`p%2Lq&0^ifPsSyzP%H|L&nVuazWy6js#cNC)^QJ&|8_;lX5s#W~E3l%kFEIUK zm}_<3{ON=-7X86etlo8Rxu2o&oy=gl`Z2qR;<78JdJP}*b>~1!VLp?F)WJL!jHL~o zgcxEdY8)1HCDRbvlIK}mcvpxow~+I-F$ifAl%B8O zZF*<&=2j4|U17fs$r^n5*mxv1JJe1|y%)*E!A5nZ`B8UsId}>shB}E)7D#{^aPho{ znpuCqbIdjmErA8UmL8{X5{!5@zMz^L3j_D85*X>tK2nW50v6*7_;{ zQ{lMtNlwUb~WNSB^gm>#C%<=_4v`La+-><|IfbJ$UjuJ==DA zu}gR2;yt-7Vb@a9-tWaHSp|*|sZq9?vH-)veUYytxdGn?8Zsco_a-g>wL5gIR%QL* z3!vJ2RCHvLq3L`S(IC~91Z2`&0@yloRk5%A>4fK|c3=8)|3GWR#q}NC2n$Z!? zwZa(3ptjS{#t5_$6LM})w{wDcI#&N96O~KV5qe6+%rc07xO16iLOrW?(eyZWYwVg~ z?FOXb)3S`MoVI}-vC!asE}Vmg8zdazWt0K6p*rX;3(h)aQI_ObB8Gd6?J8=oVhAF^ zm4w0#lFKrP)K`y6=hT)HAH^77|{*8-c9Go3KqOsv6i<&y8+ zs`ed~G|#8tV)$XSjjdv-5P9p*@L4wohPzf!76SxS-hp>dxw%TTVKysFy-kz%J}j$+ zA>ok2SC%aqrWp{n3i6DilrwwQeeOdUeNKqB^s%C_5kPtoj>u0w!4j{@m1m>0sjgfI zoZToam^VYrS#(Cdem0>ZZiG=ut=|dp=p!$z8faM}caFTpy$pwtM4nj1cx)!_!x1(# zhrG<2GR$!f?kVIC8p@BCA3AG&b0m zBdVK^O`3xZ`7Mfg-jVw(1RC#Bk6!^=-zb|K72f$>iu($WzLV1&1er7EGxi(b3!KsE zb){X|K0^FZDfG8!#EBrV85nw>z6Au3s!Z7Rp1$(9OXjJ&_9`m)k|S;Kt&2Ezp)X2t zno@s{(YMVT?>d)~6*;!H7zLYw)nlW8>3WuGhCReJ7js4=vJz-c{-oE+7|R;N?O`S-ie#G~HieNyxMGw95tq_R)q+1C-tkt(CBbQdk+V z6fw=Z@Ga2m1PJ7-%&wIUPgGst|i z%+U^bH%9hK(sovW`oE$oC`xkk-@@;SRg#?5nsYv2OzYKZJ^b(){PkLT!4xW#Rpa;g zR{pa&-vJ0cr&6|JnZmn`##pP0A4iG3Gc#^$x3d05P>4_uR3zgbPE8OjehsXjS{NHCaoB@T!I)aMqmSPDwX zNaBF+r=KYG;ZObm!yD_pv{XuOkupX<9#0K^wZFb+lS++17-gj_-@KILb8=`mm`xxN z0U*eFSKZvjvyrf_)V*rq{sz%2GZS-yAkBab)Ave>EIxVl1{F;c3>YHE=} z9qL;F6*Z|F20`9-@SK_J8q_f-qiU;mkVcBcIo5LI?r?bRF}=xDOlSHQ=DI1N6XKY& zLMhRW)Z9P=9ST)e&Ex|2#k*>-(J{zik&0e{gO8z09^2b3*=4g(LWc9(iN{6$+VxiL zr#?!D>T?v!=VXhXn4-eH3*u~$uU4DMv%0}k)w)Dp;)tm7l}(CdTF!O8`>2xk5H{1o zRbsWe(I0&w;oEU}Rgh(CbR{Wt zYD7*NhbW$$joL|`e^91R>y3&0>Yvh8lr66<$?-b;VNDhpG*enx9zkZ@cWbYrK*z*5 zL3vx1xo!FMRD0|>+i;vw0;c`qVc=dl^ZF_4kE5A=^BIG8U(bJOXzHiVaO>IB3eBS; zapud(oK)I4klozAk_HT6RA&f}-HFObi{s{mx(FU0Bi4%;$NX8dgPqe?*9zg*}S~d`|_6AYsy5d?0M5l4Im6{e8%b}EO4S~dD zoI@sLlPfD?TwnVn6x8}r#nhTHSrS%=r1JxH+Cw=yo#(tRp~~Th*HZ<*VxJJ9@p$1% z%9yvKgzyLT>aNw12ZnUJV0|5W`WaD4jQ$6bbKM zC4kj`T*Q#Emf`e%x{Kn@aGUV|uH-8T6n15k?d1Xa2&YM|;7)3^t4(MGeE6h}LRlsS z5RTf(78mAb^mjBu+xZjObo@M|gX;;e8P|UNlK+a=rTN}U*If=cN3e4S#wW$Un&IA$ z?YU=9OJ6R}E^uTMAC#91M}yR5EJ2$IH7$7#gFYr~3wVf$Mk+MgA+m0?ztx`_WHW5n z4A|HN2955iIi!G?UkS74x-#`Y^yHRsV%NBjEW?3`t0I&K{lYs5+)PE~SnqgGul;1s zuIm%T6K%FmKFA}Z)Z{5$%K(WYJz zIzD#AgOdVK2;7>we!riNz5+(UjR>u@2yIE(3->yS(9pCXvfb?RV=wp1yM8CLf;?J= z+j5@v7ExX4a_yi@IbYdgSU?k32$B{x7vlNK!NwOO4k$3Hv*PP{^kyK<*a1lN$|pUD z27G5C5x&C5#ZMfUE5da3g@pAjN(Snx>}MOcYHUE)A>gt<%DQ93quvxf`Px8BHW01! z5_+uiM2;Z(VOwc*L{$4>*i09dF-y*X8w%%70@jClm(Ju{qsaJx3?xC==h0dT|!zq6Ex6BeoI}p&Rs0lA_f;deUrxUm(ySyRN5Bmz;t#* z!6G(uKRW7Rh1p9rblR<=U#O-%)~k)ln65Z4m6V&Zl3ifnm|?EM#~GMtkJhs0s5t*8 z^Y@1GsQ4=27md4J%$-cdZMb_^l7Q~{Zm>%3)rFf)oBC86rcytOtS-w+9Ng0lZtQx` zBc6*=fDJ78ggC1iw4$BJ z`V;zm*0l0p_X}mzB9-zkA%lCZqY(f7w58NZoc- z^`z!Og$YiD!lvkmJ264c*E`qbx<^3K;tqbPAot5SN1;h;BgDu^)~Z4Kv~clAI=r^p zNJlVCxRJ0A8fK+Ol@`Tg;f*E@FB-CC^XbpNHHwycwV+Z9e2d-hW_EdDJ#NMby;?uI&)dwA*3@&Gap?xo0ss-jP(tNfqyOhE!%j$77_&qEu zm+}`4RAI0*Q!!SBxTFJwf_hvz=X;V>n&5kRrPN3aGWcfT_^bRKYmriKFLg; z+z=^5G_UfV_Z1i=?zo>s!;EpTD=3TJ_e(!15Mp;(G#D3q|Krau;f9jOJ zcsX7k3SJd_m|8z+xvw6LP4#&HRZq|I{vLfV96D>&;Q1Ne-^pKBD0ahNUm}cB4wTsu z&BqsPCKKb0U)vt3T=Ngw+Z-CtHGLN0?-7XOJq?A7=OaU=y=v_mOE=mO$m2@`z9mA; zmZ=@8R;lZu2?DLNw4KX;zQ3Zg7BI^POYA$=%OqVwkdD;*>i;Gt@*uWVFiW?VT@N>! z!WEdL4;8=^u~zB3*!rc&OEjrA5FhqjdxFT!e8vP3Jhu+gjh(jBhQ&!v#$ zltoA?-`1HaW7mZ7o2}f_92&(ruIY6`7V_s9x13>b3Q?Xtjh`g!)n%DWOr$`mvbmb> z;pLUcg5}_9<6iG}zpjs!g@;9Y>UFnsLN=72YQ1h%vmT}tRB8TpAj_e^S{-HR$)J@7 z?6EoOHWzgFn{ul18jGE3&Cq5p)&uoKE}HLVUd1iH@8m} z9yorQ%)irtrk0uF4Yp#3)3lwrsE|5B0A%H*UY~@*zBly{fyTi^AX*5BW@t88x)uh+ z?OSC=$Nvd;qcL{e8{!Q+#rYJ3o$npPIg*7DkLyZMd1UO50ve#+Qm!BbmW9gDh-|>I zH?*+@8{cVEPkotTNRX0srrIv&UUnW{Kq&E zbRnicV%S;v+vaQ?UWipfFh}Xr4$AL%jP0?5n-A zq<4w`hPCp%>OF#iZ76E zK+MhnK-)#wN@qzJUxTNI^if1Bsl&Kpdc7ZD+V<=hxEi z#)eXkbm`r(Wc=rmnLz*_!{!!DU|5@rIh6QK1$%TS7mDO~Iul?SU)+gjQ7QPoA*dCi zIo$eETl8<0f)Z4G>I7Wqn9^6j!&U@U zf~C9BxfEVCTDUDqi#lgit;z?ql-aJ5G;yU-NQ=>Og|^oGu4<|Q00HeNYXKT4egqyW zGlO9DSVCzr$(QCbtutv$OWwOoVqIKH~ zl`^Q@q;YUJQ=RsbOf<}9{ngC#UF{Gn5CoNel0fQJC4x>8AZqC82W3QnE4kRW9kT${V!2XD*qBUwc#U3O> zRN%8r$a0Yh%eTUO$0k-yFLf>zq*9)DAV@YBk=zQ;ma-baOoyQ*?Z@TRUxm zNF#q|2i-?mv9l#vQI`xDyF;fspO=@foMW+;Pv~24_&hk@+!mW=-OjAl$P>NNUZ%Ki z?rJ(EQl=AUr7GD`=gSa?MxC{3x3YM7O*s!<$xGd8np0Uv$D+tq;ePMe$o6v^fqiZL zdYvEzx8(a}K>^-{!8M}MgPG;=oRG#i3(<-D$Mm7{<(9E3O~(v)0;7qj3lt^;wEA2r z6o|K`q?HpM7Fk3=`EjgC^iCVKCT})?>0G**Yn%|ix17?U*`sOB`fwDUcW@em<2Q-2 zfUjLC++{PYcnqB#uZA}DVPfe==c2hT?}Qo@i26X}7roBq{~EecTsx;mhS9nA?*P`EG)Xu( z6%=}ku1*7DzPX4<8AQkZF#r0twFqK zOUI&AJ2KKOifx$X9R;g(qyeW&VfrAY;LW-fL2b{!m<>vCv>G6mLg&$_fwPW ziR{g!o+=Ku!$t8^Q-y@l_az~2Pmj~IBLsM5k2{Y-#^iBFDCwcUCoel@IFYy%T}PU* z7R~R*SWiqat#?#Z{bPr}l+O?uH1|nGPL`9Bq*zM3&$Lk3>VV`LqOAjNhyb9;8OCF1 zvJ2&2j^}&!aS*F2TjqmJXW{xj6o^Gx87-G?spn^?fosVKi?m?OXDu0*&>3umlgb$o zG%oDhMAwS5A!hH-*TqtNBt11%+e;xa{c`}Ua@+h-81F&G!%7IgLd}F`e!U+bW@QR_ zq@}Npt&`-n%n`v>rUxogI;xo6Ly1z*{YBVrYO&etCN;H+OMwE5%-%O9h*enG{OP`K@LOS=2J>B8#HqB*6bn`0MMnBjo z@@=60BS?&=}hfAwPUxVQl)A6hEB2aU}iCi}+6xeAdPUe3nQ2`ZK2?hEuZ z>9%eqc2S*oLceL0>8_R~$d^~$KY)G&FjdgpW*B28i$#;g`e-2Vy1A&{t|F*ZFW9x_ z%ePdu-#jZqjBCScNNq?_1I4HWc5KRRI47pBVz_Z?PB@gLD0g>qGKK(@wufld>3Esm zt>3#zh>y8DS*&$oZ_JGtMp_4Vo}yJLj8_mJUd`epoT zfeog9j`71T%H=67y6+W5Hgjf-{{2cBmvm$BD#Hhe<6%h*mfxU6eH8-}d*UiNPG5{pSa=QOfbF=BBPCbK>zFYYg9xg%#Kc8Vga63(sD zpG;Y!0WW8j3VPoWpug&+Zj{s~?9sXEl%;&4vQD1tv#H6| zKlm**4~LSfqOHsX!73nM+u2jvW734Gcw0xs?IT1S4*$sezvg3dIVLyjyj#>4uT7J5 z|3N0$Z@8sL=M_qDus(aWeuLSayeyUjShtz^;o$X&g!TXL3%O2x%H*9HPq}dduC%ox z<`E@LG8KBoFkmJHJx)1P>==x{HCFW_ZN;2Jb)e{kqH)uWs4POb2BDb!AZl4@8dVX< z*zUfTZo-10*%OswbZT;dns3s#a`3?(A`sa>7AYOdYdi6Ec0uL!AICS2RMpklO$@>9 zxBWEcQa`G%Svx_Xd96QXf^sFR_QS$)HsjRIB3w{@MJ-&Xc&vpRPgEcI<9;k6|LLmQ z6pnajN-bty(^JRAZ#%h0WJ46T#;@4CQC$mM0v%4ZUnmh8DJ;bWg-?{EV+<9NdpC~V zK{)Sc(^mRvCE1hS-a0VNdF^U74ZWi^xV5Rqtr~bQTwW%`B_7^zFdVu-=5q7_;463KDf?-n1l%P=l1nZgtvK~ z;ovq8a|KF=y%XykuEZ(W8VcD9KWPKu)wh(~)_&3DeH^CX<-Qsl^z z{iYnIaYe37Rh9HN6(Ft@Ar&KQsFEr)A4|&;7Y?^UHM-1Tg$g*i>Di+C4Tn{I|B?hMLE5e&EI=5!*5HuT;?ftb8%Hcm6B zdCVD1ub(+t!`yDVWDNO1~jTO9h zN?r# PEE;~M4hBP}*ZrF!6&yv{8axGwn32iJ+KXAuutq|5<9t(dhayGvY%I{ zE$C$UPkt}M%~q4%UHp+=@Xa8*PbhV_d>wxd%Z1zO;yC0KG=Zze2a7N^TNFEsolwc%= zpe#>GOyfl4Yt-v{dk3#qT+6f|S^*IZC-c2XL;=PF_jmqwEtcMi>iiI~j0J;RGq#cA zP3!Zly80b|)Xb~pm5+=hS+`vOgvgWQ1sqZFnVa#`y&Zve&Mn>h*L&GMH5ww#1`oB+ zar1Vvy(i48#1md#2ZN|N*I3pu>hUr^?`hkm*dj(eWQ-{Y2v_WD2^?v1hu9 zb(IHbrrYzAW3Mgga7$7D7;E$HRCQLuF0=c?YT0USVi>#p5UL~}A=B+C@EO(W{ zSgC1#mUcVu<807&ex`#)3dK?xnbh9etk;sY{Jo0}Q*Dh)J7-mJQCq8)4vDk*|EWVO zb}G)u3yUk48p>2+T?}D8?^1M@uc(hbLV<3di_2GDi*Ux*gd;IK8P4Pu#RJK}VHfWa z1?0*b5M?QX=NeFWe1=}Im;gv20(!xFQkKHHZ$d@4kTS1hdgk_gZzxmtGDnZZl*UUd z>2v`-2k2T!#jQzRb7P{UE42FrKh7_{_q3P26%_88QIFR$S`!L)jHbPhnzvqqAar{+ zhyky@am}#O{SwP7ksh$(Vphg)+Ak#~g^GZD>fG(>m_fdu@p`!(f09?+q(>oabA()A zlP*3B$pkehZK_%d$Ps;?g>QkBYEK}2RTcrxd8%0QI=9&smjF$6Vt_?pSOwD)?2&l{ z2(IBfUuU^k&O;?A;*-E&zE7n0VP83l6#USm=Ifpw-RfAXpe${P5{eoEdh$Aa57&uW zS_r=61&VUjv}DOdx%^vJD$2H_EWr;}Iix1u1EH5jJ!b}LJrZQUv_n4i<5(o>2~SEI zBRQyVCZQwEDoi_6!Zd)prq$NcWINxz;erGm7DG&O_D!8|&c>j$1kSH6qYyX+TvXC* zI*HUkJH&Hryy+9s^FfeDk#^1n;oZw>qAWZr!$cMd`B0~-Nzv{M?(ziy_2!z;Ad zkB^+_5W&c4zoE;`E*vbLfUHa#(D+;bE~@-aWpntvEaLj#yj#l9iHyY&Q?8%Xy%wdh zP>m^#Q#p`?eSeaIi0P&lFRp$L$a$Vf=A8?87J1Bz6MKxo&fensH7p*9tH;vIL589| zLGR>Vt5o`9lqb|C9%B4@RGGcLSNA#qMO>SZG@-~Gu!Lx2;Ot%f-0;-YMTh8d zLUrcmuZ;HEJeUKBWc0+u`roaeXh4eDo$^8atOhcDTE2M+JyrZ4|GfV1fBY;WZ?ffg zrSq>?P{&YKr3U(2g<<&x`B!#0rkpZfq(n)RukTD?GI51HLt9H{>fpW~Qn8^7@6z|l z;!X_Fg5LH@y)$lOZmGZBj4KNtsto?Tl`na~m?N<9n*zF8uZ>*5*bFv)qxrN|YUm{` z8v`1SP3MX|Kb7@;wOvum)8ZLA+4{MSL)v(cNl&9|75U9;T$yrU5xrvJG&onTh}|*1 z@_kTyFW(RV<(W=klsQjq)_BKKyf)HAAz9$hPJmrrgAH%5J3g>r8lK|J!f$A?ohhoZ z#!I)beqP?qI6Xp`ep37=f*@Xr83q~aC{l6nXx!a=%!!VV0OeHd+!jfAhV&Iy4YiW# z7od>}%+h&RrVrh{H_&>ob2D6ce=goX7iJaKi@Q7{7Yta(&iq7eT*%{4BdJasuf!E3 zFz1z6n;9i(xjXIq8q_h}l7NO|(b4wGG;b2;1YKeKMXXlEzj7zbv;3}@3K{!^s1j(C z@Ke?vKTzq%P0-c6Mtq#aBGmGH!BA&T-4~W1sS0Rnwt`9T zRNt6J<=dFRR7+pwBNu@uSp+7BrH+QM%qlU_RmcOxL{ou+)D2M*yhQ;}j2`Jh;?T?M z`qqaYVpJ(lz@aC207j+p7p86CBRke2Cql1rn2aj|j5o%cN+Kqqqk#Sy8N+&!sTcyH z3YI?IaT6|>UN-d(bItWp(&I^2rm=o&#U!Oo4cZAUs1wIskW+!}PaLE!bB9v7@3d!T zVD-8N`_%>r;6$xpbQbP_qUC7Ba^i5&!+)<0rBpG=MKlyBrE{r1m2A)P508$gq|LZ+ zmQq&?Lo~@AHtrN|Nb@}>OOHhnE+gFe9ej%o%C?$PIG5NB2*Ks5u>{^S*kyigy z#!@Cm88=Ner;gg?xNcdJWQ$zQ{v_^XwI(i0qH6cnQ(SrXHWChv*3_%XqXOOXSdU?bB`mq9KYfo`m(xwKh&L{3$0VaI5*id$@}`+M5XJ+T1T1vm2zTk2<(+iH$D z7AM@%%pK+n&s0vAOe<8OQke)6G2|$;{(+=)@|Xo??cC=>Qnluy^=PfNa1&q-n#8+x8^dkQK)TBtg%BQ@rMZ?ECcK~RkyKsCJ8cYqK_cAs(dQQo~d z?=Y!Hq84|3CtiyM;6q4gtk@E-Gqwu8@%L7?*GFZ)>~fsm71d|m&E%Y4$e}LsE4~Wb}YqJWKQ5eE^>I zXp={U0h&;dng$rI(iU+>2zFOrUuQ+)Xrtsl9zoGe0yTuFvp;97QWf|fczxq`zCpQJ z?P9FH-v_~<`Gc`$7`Dhx@v*(YPXhpA2dH%3eCA9SA^$mWzPi{H-Nu)yJ?lbWT` zwti^iFf!F4YUR)HaFir2fw=6!cC`>lW!>M{_(1-4_D!%p8C?+Rj5S)6;{)O;24=S+ zpPa?Yr#l?aZ@1N!j-GI6$QG2#}6dDnhkv-`9opn{Bjb< zi{%DQqB6>KImkYDaw%wG&2naAH2dgJLE#j zo@HOru^w!5s`iEQWBqf?X$4tMO}zxL!tgRSZUcO8TGmqke@PI@QDVXFba7+~Rg;b7 z^yFl*B=79ww}@C8m1k4X9m~Xs!3hFPn-4|k9VbPu5`MAU?w5$w_N6svlG4MwP^+)W zbHD7S{FXnjoCS~ZKiy*QfgK6rurUs~MUQ&~Zq!^GR(p6Fr|cuv?yrl2JMCwYb(E_| zngNtQKh}A*ib%^e4)V$V-o}_!>V{3g{Z`xI^6u|ph_7@Rh?Z>`GO z`cSEG9A7agrC`IUQ$AN_otYt9cXB@;dz~t2C;{nBpnON8xlH4fH+37273u+FQTIER zYpLK`3P|kIVuru(G5_6r>_5N9CYw-?k0dEYb?pWdrdXE1bs)0npyW%p<7yd7-n(U@ zfwHr=*IsWYyo;!e zYpk!2yi0`Q^!!IZMifma*Wgo`C~{kCZ2O&Sj$8MtdhE*kh&kB&wHI#OY@|&FP?Q!? z^mhNW&N?NMOIJ~C;g*{f5m2DBuScW`RR4(7nY3mcBDZX;pKB6*$CT@7E5B2B76i-u z$z#tV?T_uYV7fIB#|elVxufMP-^KCy(?I*OuAMYTUxClis_9|)f#)1OT{gvWR@S_| zM5T~cmULG#fR6n|wZ1cm5%KEyYOGv!wq~QoI=gI6p~M&TmHE8ncp&%{v)ulpW-?<2owYskSlX(JW#M8r!GIC1|uZ>tI5!HkUG15?6O z0k2AP;G-bdtI=OTmlO7C6a5Bm-!YWISDZ5=TPC+eqL5RCcZXE?1TDDznk(-ipb?oF zE>Iox459-s{Mtu5_OBI8oSePC$jqzG@~_MO7VGLPRBF8X_HWgpWQ(Szg8^EfWh}q& z@(vM?zESL49)2qhu21u`?wYLR`s{8vKn^*@bib=o{=1*Tf-n^$8G?~dhqVR5>`WL= z)!ZedfT*-k295zRD>9p4m<<^vqY;O40gytOlzme6%gPnalEPK}_2m#39gR9s;Oeh8 z7Br}&%=()8fK=ammDu%_8ul3pL@RemoUHQt7jy5H?ADee%eEDU4|D+iw=xa)z~oBH zvihxi^JMKUEs>Ms0YrF!%FWQ)Tpx0VEA!jpuT*r};=O#?&o5)#{4 zQj!@D6Ttptdqj=p?R|hv!}?3}6bj3EJ{si^Xp zo0|@^J?HVFv(~7z!vP~`l6Bhzd_I(twDFOK3101=53anl;A+3-s|eV&%|y^1-s?&; zqwP-8_ZJgHI9SKWllPSBT9w9Gm`=AIN66dlv33~5L3%X=6=)dvAtp%H5zdv~M^U*I zQjK;5UG6XG^}Dlfkn#SzJXC+~^%{xUlUy4-4c9($JW0j)WAD@JwRmQO_q%kH!-h!o zx?zUcLuAZXESPF^fK3Y>ms@ET9m`J(M*F_YHll#b%QMzcx$gE2?Qi?FlnQ`u{HuzY ztsyNPUB6;J3yYW>pOgn*Jw{R(E71E~F?@9PJF0WqGkm~tJ4=#f1QwB1R!uJ254`~YYrWjt_eI1DLzSW@&X ztpWM`ezm#O)QnCnf-56q!Pt8!zksz^S^@2zb1ssOeuUWu>qM$_SLJrTA&S;o}J z$+Rt>kA1h{R2az!8i>8u2h9}_(nnp>A{27k&XdYmXR(OM|L&0#^{sDjl+C2K?jz!+ zuRASG%?^(W8%Lk67!LP7Ex%NMt${CL+EE7d$dPo8+}jANNLVm;!LEMf`e*GbnnGo4 zpqcg}cN)E5_c<(1|5kp!LS{28a8Z}>cwe6GHDU&#>Q*#Xg6{Xd$k#T>>m?4M>L}T7 zjc`mpPnq$h!3Snl&_WuV?VRmznaz-t0g|k(j5B0Mk1?%nXB_6;?71x~l6U*C&d)hd2Od?Xn^-^8tkdGC7RucIARheX!jbm4 zvZX>*PPnXI1hL20RFNqg(SV$n)mP(NSQYnj7)W3pA_^;#&gVqgc+;FwQm*A-t&+u@ zbH1w#B%%D=u(1_43e5x<4@tPODZyn~`jn!yWzvzqL+8D$3=v8ZIkEHSULHsaIQllN zbc#>I9^;Dr?Kopm14Tm*+;Y3!@5FfnsHGTM_2Q_QJ1Vfhkj3OyGTU{c{(_nj(pnm3rg1)~7&3f+U&G2H_{fnlTSq5r zJ2RG}uh&Z8ao%qs9B3P1$ZK}t)^c*!dXo~rG`3Ai4N_OY=#fWTEx~Y+y(6@h`-8~a zNCSUww7%AO+7WU-PgLItn!Ss-0cuDJZv|D5BIpTG`6-m96!1Ion(zNt&uOVtd@Mj^ zGocFDy~CB5G7a8eXFB$U210QHVH>qV059 zzbl?d`+&;FiP@6$%|6N78{vscdN^GyIeE|~z(8kOyGq^k8QYyZ%#*u!%}wm;t~Npt zpE74dtZV|~HS6H1_4hbg15i6!dB^L={Z*+ie2J32*k0db(TMu9ewcUfm@SJ9+1mRW z8TxxR)OGEg(oj*sfe=Vy(r|s7pQ1W=TZ5_o7pPe&Fc@TLXGv@{uY{IWh#RZ4_jbKD zJ!vPdbI0d06thzSNn^3~2{=%*_HCNAm?U}gdlKt$<#_XLfeKHxZPuRWz5OUv?BSW_ zWR^MCYeuy{cVSiw!3O+%gi!3uV|RYP~G=u8gN_$CHQVs4@b%}B9t<* z0p0hQnFU7I(Aq#Mmu`_8=IZTX!~c8vBosFBod@)>PieJ?OeCLIU3fVj-=N? z>8&$r%cZWIw)B++8?(i68&MV2ll!YVV-%+%u0c3y>)Z;rcyq6h_+Hp@txh-FxBF`H z(p1C^W||<7P1FbHP4~86YV#cUY@>j+t}-b=mZ07dBX6X-jhm*>X{h@HyT*<+fnKq! zDoybhRd6##NAh%2e*!^rr_ppWXQP-v|5>dxi$CP20>J4DR%G|MwSa1~P8AB3ddT3+ zW#OffQC9iRuNlY=Wp}P=LZ+1M+;`CmU*vw$d~*$9AG-lcF+)xjvcp~o3;)Is<4Dxy z`0)LH04#~vB?8AYavr$jRw^8Yw#gQZ0sRhnXa!MBnsTc2`lWh*vE+^~^dPxFZmi>L z>v+rDUpPbeWbAC5>HZ0(ia&Tq-I9;a=2zb9j&1U7{W?F^{9~rHY*yB4y7MZ)E4%_s z&g)??#aW`VR*I2VCS9J+z=%-IyI6j;i{E^GyhWKCO!aEYtFpPRO1`xMY73jcnyht8 zhD~oz^IsdgJt@H+2ZY8tNLv=5R|0vXbwIQIHkGBsnumU`XsfEA#dKqc zSf!GoyZFFXFkO^!W^IZ$M>hg_t3NVG3#K-eUulTl$*@QApA6NPbh;}g#qL;>+40g& zXU=Fq5+w$pw-m;MwrTLZ8u9jO=UVP_bGSAALusYQRhiC~J zraTj~Lt2a#>FXSySgyotp7-Xm$on10u=3)tX%m|tJt}|pwV@U6-7q^N%*2d<dI*S-3FubMTm7KGUAY|ow1?BYz!&^<>r=t zB0VdWPD-6&g7@plyTi)_dx(89)k@%?;h!9%*vQRr1IaH!%Z$N0oa5|HknMZz>20gw zt@=DY&GGe@%$0jG-@EE06xO9aY9u0E{J?5kUU5~$y|T5Hw**U|!`l2`j*D||A}u=} zHAxABMNTAe5lY3Dm@m6;i(t!JA4 za97=P_!^DJCMl;llgm05l!^gZb-7FJgaBR@LekbS{s4P7PBCIg)p$=pte>*Dt9-7y z7exPQ4txiS4nm*TnLw_Wzfcai=xbY8)rn&PpDI;fVdoe#Fltmd^M82Cxx!R-bNMfy z%oR&w8o|5n6vT}Kv({}^`iX?_B~FI+GRGmFhgK3u4*x0CG^^xl@)6dlT*4%#vwpUv zvdMD0DZ4*B2n;?MUZ{k#aftq{Akfm_B`sGlVujW0ox%}rV50cH;$MwzI#9=eBk09ao2z8PaZs!pL35q%AJ z4U~A<&RS>Sz;7=BnfuUK$=8i8SJB%dHld2)lEB5XXdA0^Q`g(`8I>@;_(Y?Q_cfma zu|ku1w(LE_y=?pS-pPA?C;aVk-lO)!(x?33iGA2o^)MaZ(Upv@KEdo~Db>&SzhB_& z&XdSA02;Fze2=H-F?vp4mX1yS471V)g~-Fz=04L&pKD}mzWHiAhIQ=G<@8@9k-^4z zL0s9+7=myXxge01m1qdVE7#HkLeed9rG4M8_Gfj++xYu$>OC9hCg^S1?tlK00zsrO zpM&F0#_n7@%&}RC3aX0pxK6L;HhJzXNw#X0{*MVw?vbQjy)~Lx>Qk8@X1YOZxmpu; zIjB<*yL);ngL}F57JBMzi>MKokDQOYLA-^&lED;YN5_kOJAC!SMfHpTe;kC3Ehj$ z!5BHF(Qinkv7VL@@h7O<5Q1q{Cl}wVrDNp2*LTRd_(^s;uBK5im_GwTm-qJYhySYB}b+P1G;nn(OROnsru^Ojkpeuml!5$=*F` zhPq}~uQc`H<1xMH9O1YQgD^mUY>W|%;ZWa>mH3;}&YzOu9@~3w_6jOkeBxomTwDlA zO2#3yGj_&~%k|?JNiG#2vsCzV?{In!A*)R*$n}PW(+;|{E%Y)AJbbJ46F2zF5jH>X zoZ!xoDrz5^uw zOoH!MX}oCxxqVtPe`7?-lfTZ5EQ!LT!O$#-k)pn(t$l#GqchFks6pCJN+tKsS+y)K zUWI3ntK3{9EpIx}QHSbK0eA+1+pO|7*L)f2H}ERX*XWl1h!M|8}k3bT%@P|?JHF|+bQGnPYMSrfi ze{vx-z1Q@hLhP?ax1&N)7jy+)e7~xM0w$d(6zcl$6JsZwaPY_2!5KR%6!;)*M~cK{ z>qLDDy|}U~DBhM}IImY?xLmHp3y0{j9{2|}N0`ob-?(Xsx7fd$Oe#f@UW!rrf(#z* z7pjcqs_!8AWf2PZ#E!R1nqai~C>aqNk4Zv8tKnWj$Q0{lttZSZe`5DXJfq({&AM6A zW(@d*;O7Cbd>n5?)15+rfy$Z-wjCv15{!+r1+HH|Rx@@sV)o3*#msk~amyBWlo!E) z^LcFsHcGLSNWDdJIF+QYEQMC-9`df_Iho8Ddq9UFPBx}HhLG=Exu>Jukg~R?@w_dK z@aoHcSxl#MV#9LykOv7SfJHgp(TCiuj;!kX1o#f67RMJ3rqk~DYs?J^I>_^?Dv=c^D>DSg~56m$i zr1L`sAb@mD^`@g6(S@T1p%0Sjtmc&kZ8v>BzTONkKFjiG`hB0Egw*3He11+Hbg&UoOgwbraKEqKY;#+>;KH$ofn%6LL2LGwr}H~Ziu zl~dMkK1&-U@^sO(y$EABc1eR;gs*G(IvqG>M5>(mFI2;; zY=#jhs4ZQjH^bjQDRwgIIU>Wg!29K{5mK#5CZs~oUz%Dm@@hZTcI=R1liRzRf+!Du z(J5R{ltg^BV6ODF1($U-T7p#hUEyu{K~jRUNlQgt#XQZ{NZai_a1;E_7I z4q0u0tcXQr+mVmM@hoR_^h?(*CRUqFhfjqDE-)w~=OIqD_5#%-REJJ&lq>-fS<77m~h+;?f;mWWo8AQTRI5^qqeNiHLEp4;8Dz?2mm0rBq+z{q%L^6 z5nyk7Hcqmvt?!)7#aPTHd@gR37O9~jTN!QP%2Y}TuQPq$#cc$N>3=!0fP)>IDvfqq zc!kZ`z3^0rL1$#)WL^U!E`u$G9W|Y@W2Z6 zk^SW+nyC#${%I4XL3J+ShDg}<`f6*1S&J0cEhw3iM3ELNN;qR&F%#pNNaU*kn)u=O z0B@;C1dNi2#$IdS{;hVv_)jd(3ywQXE*kP3$SVWo#l_Sb!Og(D>ZorrtwW=CM4zRi z`bLG`J=myZ-deJS-PtdXibnZ#@Myt+sBXWej8Btf$^sfDlKKv8R>8alw6eZ!)wFuH zXD^g1_ujz5=CW6UhSLA($TEkh^+Qc+1$$?Dutq`Sh(J?W)4J$5j%2ajoiv_*iJ|w< zdP`Q+&Bo|R*=SIfEyQZWb0)BVFVMQI3akD7ymSX-zCD%6ygrsV%Kug;BvmrZn7I(j zTUHJ?eC7R*mgS$vi^g5uG8 zOr_?>ZjbmFSNya!!bYDCbV4d|HuTSuypX7ZO_31U*s%Tmxo*T~n%&RwHX!BPYrFdV z?AWv8JT-e2X41x*o#q(-=CBsNqB(;U2zmURD)1xMo_MW%z7BRjZc0FxKW=(i9)}J( z6@h&nY~sO}v9%cc@^()YLu+D%v*YE$AFoI^zp=66+^{gzJ_*{NE4k61O31*a777eS zMwX0oh|S{0+c`DMDd1}Ry z(mz)9UDA=HuqrmpaRKEyEB&mq*;sf(QNM&Bt9#24e4mVg9|nst)gz?xEWVpjI+3-X zta``A!B%yQt6hdW=1{dd$lkGIZ+kD5td*)m6g>)gTFy#gWn9b4&?eofSKEogtK|rF zoQ}SjF6a-ZVLK|Y=E8VT;KH|z&giV*pK>lB3ZK&iqrfVjcZK@VN1OsaQF4=xq@#M2 z6pV?1@^P=|ZM?)yJ~Z`xDOs@PN@f&MhW#*<3(gZgtqpVYH%JH0#HDnYW{L>ShSxmN z+;`Gf?>F|ZJt82QZu2fhvCr^ij6j~^$2D3_`9V;b_xqTEziHpA@d5!}pZZ>m4-dLf z@pNEJ+kl)+by!Y-!@afsuYpLZKd-J z59wl-N7wwLX)^p*L}%X#q_|tHF*=kSM+fl04bY+uanU%i)|m5bWvgrfpvl169<>wM z6NtkkUb??_A??|TK5oX^=i7kSqQkXHJ3%o_SVn%p{xNE~111UTqqdf>RJpW#GC3Lqx# z0`*2sSVbxMAt)!nmI5(}73FSc>Mv}Jm2D1rOD?|dZkfsk5sPW)I{`lrAdu6^>xwfF zfty$ji@U33DADGcInE_WWOrG>^f07qTNl<@*%k~#a#hSX&@!rj6a&{yY>W`e7oA)q zUuAfjy&0w(P1+>gl0HIK7li&ydq)DI6YGB-%)Y|xg7mNMIE0I*YOT< z=fVK}kamc5Z}SHkK1SL|u6zhii~kG1lER6Tc#q5pQPEgU&ZITU8zsy=#Vk0y<+%|o zW0vUUW2u!N*o)xtE#;!JQ>;c?co>+~YM(d+O z8J~J`8+Z^!v1BkVw^G5^>re+M#Nq9$buidZl@&0_0V;?xn7*&Gc;qTQ$u_{Lx7}<^ zj`4{T5v6QNZGyLDYJ~F~9xPk*piWSKJ8P0#Q{TV4^nH@-3`Rh#a05QT=WD zKyAlH5wK9_$5ARzPqG|c2QHE~;!wvQwZ74o57WJK=;0Ri`)XQvH`9+SdeAn|Xj^gH5hkO))@k9YEFvv(9*e_BmT*42GcTp*_R;j~#l)o_O>9l2 zKs2J!`5-(vom}5nnP2eeckoy+B(;wbpF;3p2i0+uT2L-Vd(6!^xA?!v<8^O30iiem z$<1n;fKVA_gS>m?yxemPh?IJzCd?bQLxN*!R`V6*RoI;7Y{d;&c2i($#mIY7tU|I7 zp$ZoFaqLW!sYcZiy(_z9QyGIZHBP5_rw#G*%osSe_E{XHy2dhAHAs8v4oM%rkG4on zL*ULKcHz}ITS-|7?Vlu@K${7unBzvJ3z>2G_z=VQwaQ)NdCJgn<)?P$!ki! zI$FSy5T;9UbX@^KsMo0Oiw&FcMR!PfgJ{=w0TonL4MWUp`U}@JIO0%#Az7ILVBeid1h*hX7*tEg)DsI&es8d2rpHZE!|Vr(DW4|bT}R^ zapPmbLZ0of+R;yvDQ>JrC1HPMtb&t={A!J?@pNrE#VxUst1~@pt+dmFICDOy>b0(W zf403?tjH)kKQGu(xTRXc4yjL+T*f=5y*45my&{5&7?~>PB}G`tUG#*KAvR;LFi%GR zfrSWAH#-e}6T#4Gx^qOTg4U|;Zfsq;`N8?hcVWbYuED0N?gXXzK0E|3$HLi`)9mQ= z%n@)&;HfmUSJbbn^CBclf!aZ_=>)VseY_rC^`elZ1&b@V`&i7Hfc5uQf|(i*vD(Z7 z!o}bk2pQ4ES(&}dOw=llQ$5*D9iE@_&+KmIa3}Ye^NWl~k>9;vKg8m9ecCou>RTRw zHQ0m}BXFk;dq@#coLN4HdvEXeMy5iy!L?AeOlxx0w2X{~aQ9YsE^i+7%|y2Ml(-7oh^@nw@Kr7utEa3 z*){qPqcr4IyU}0eaGWzEd>a8IDn;hBZ(0vBJ$>#t^PFzE-cl>fJSbR*kcD*{XsA2n z9DI#IMy9%|rfi>Y;2^sKA2ywWembdI)girZ<=yKxp_^XviT8N0p&SC8)eL*b4 zN3InGNiL;|GX5nJpACsf8b7E~hk=+Jc9K5MEz;oRuYlRzn6H{*)cVJUw0hmk!G(OK z#OQN*M64eOysukWF+^Vj0{W}2T+R@L7aEaM6;EQXL-H1axZNz z8S^fV$yydy0lVOUjjQ7(03}Ou%#2vPB7JaDNmd~pva=c|iq^%J4tRM9ofA{Xx%0vn z3Z*>*&DcPMlI#aa7x@1l*J#D=#-UvbqTqC_Tsgw$B-k_)QN3+ zi+S~`Y8OYV#7U_Xh!z~d<|=TL3+!y5!poD{4s7Lw$b(&Ie$1WROodS#54|28W6CKw z!J6Bd(d^inwsuo)h9X7#d(Rju>*AS1N3(W3U^!ML=jRo$1^c7@UZWhDJCdkw6lSB} z1PgN6awa1cs&We3aZV+`I2%LiLx&ec)t8lw4Uv*gI<5I#SHKg4oaxSBPSC5f9?tpX zg4AspWyGk1J1%y%ig#8G{jkQ6$k$hg;y2ZLGConFio^2H*?TwhIn!^{$Dy{)=b)pl zXM&~A=hwjHJdy(VsHMz6w;3jeYUoM^HRgXOb(O_XH;~{6pXi58Lb+N`b2M8BO->4j zP#f)D&rN@ug`|29MWZ&Mxe~(GgS|^H7Yy+bv`)2MWwmWR8(zg=iAN~5PjWe#wUp_a z(sLfp>)~jvU1rhtFXP)Ka-5L<1uz~6!kNgvc%|JR$k8>K`}r6oAsFtWY-F#kbk5f+ z;Zc?fn+_}~qI}Iq#Slvh#xt0a9LsOs>@C$gpQ7og5O%XN-fT7Yyw@3j>;az_+~cuw z!sBJ;p*v;_Wm&GxwbZ9{PQ8ur!#{jZn@A@#56DQhRQY<;+Nlt$1lO``Ik9)qp&rML zN}CRukmqdncWFJX-|7hUR;fcvI~jq`G8whv!mRpQcxUWE=Wk~Qz5hAxT=JmjS7oUq zl}RD~S>BnqX{cDI_zQM6LqyE#yc&sy^&*%9Q@c?dbRnjmTAlW8;e0|%y=DnaA;gnN zc__L_gLe;XdG8U-2E&Qgp8nx2Yh*KSSs53_U)u{R7isl~Y;Tpp(1_vs)LL>46i}$d z1iq}bx#`-h_~DBt9QNxBWf5>nqXJDHjVLwf=Dg+WB%B;A6P7A_ihAD6iX1!?2MA%Q zwz{PfK*dwq>r%lE1ERrmN2)HFM$cCz2=0@zqrR_2AJ#%FHU6hK9jtivm4Y&vbFM{#9$Z7NW-X@gJ|WtZ$+I=%Pl@`uyLj3 zmMp4^pI=uvjP;lVC0ItU&TU2TZ{H8^!reLaN{Q& zkBFf&XVZQN@rOjTLlU_zG8V!SGzz=QF+~bxR-rM(2TlKxmq!C*u zkm?lZAuw|LYV&wV2O!UM zVRim?{Lr_tRG8wBJ89-M)9vz%{hut9`|JU(+;3q2ElhiRi|mxYi>@uW{t z+yHGqi<}|M`;}6wy}*Odm?3mc@=sW;at3hQe}HrJE$VftwBD6(bY&m-!bCgST?%~= zO)~t=<6$JZ!kxhh_H<1~jqG4`a+JQpt8D zm_M-=jqTxLG$Dj$804eIZ*e0g$zo2=23Y!GNMkqxv3s2wJ3^PVp%JBz?b$1QQo5Z> z^k?5kbeNU`IVJDkL>+ArN*Cdmcl})4)C$bvT!N(wNUKZT+Ms}cs_ge_`(qOxNu6-y}I-2DE=)Dgn{ zvjRMtP-+f+PwsH;s~2{o@gs$J5y~F}9Bzg+P6`<<$0m*qi7J)(dpvpC?ftK)OmsuM zq3|PibF+OD|KbJbVa6lan96nS_!J)IR;Eyn>Y8Qc6C>oQhOCBAO}}u;s?W(s4Oddd z5vo7QKM5s2U-9bnP-K*&=>K=s73=S5sn51ng&DtrYrTrAxSJ~v{T)&YP(;OWF|*v} zhMT&)hA_Rx!5e*X8s{4E1XxUJPnjtvz$k9#gkQZ^+h`sS7J6cw#7gP;Y_Qv$z==P| zB`t@|Bk5DzF2(#p3MUbXUtB!y&8AgZ8k9V-RQ#4KMzp4$g3|h-qS^Nw5crjYUdz;; zQczVUoum!u7wH-H0}JPatwdD%8aK!upE(=Ns&wPDgP+Shhd_Xq&>ko)h96#~yiwq8 zCfOUK1;key;y3>9lv0mAYslJ>I(ASWMjy zRQN6hfnMhW?xv$?BWcL=$0U6i;P~SJ=T%Eu@SpZ%gLG^#to&k6nm>PF#zw_bAQlkg z9b(`)$euPDjiT>W9of#R7%eZiU8*5fOTPv;)#pZoo6on=WN(vwrR8y#a$-n4_* zR$e{GayQv?Sy3xSP&Mj(551JDC&imhWNX`Q9ieha9YjffRg|+mk{YEcGP(W8wVCnA ztv6xPj{MkeR8Hw8Et0~L)f-H@?nn_|Q{ISsG^L)Da-z%KIXJSuZT=^Gi`2)@z6#<} zetqK#_351Fd+RIsc0Uzlw4)%%PRF5+8R(lpX>Q@Ej_x2%nFxys%ahmJ{}F}t)iA7n z61KVPm`fpvAJ2uld()%I8iwnbVv`~hozQb<_6zr-Zh6^>26%0aW9i4Xig=02lA|C} zXKKq|Mel@ExzHORIvEKX8TT6PjuQih{d2X!;A+1?1%*ebpQSiXd!j%p$=)kNb=@24 zTBTyM<8)57WZWSm!u2o%xlF4vJ_*>Fj;o2?ZR5>?Ted#6i_nFq=!$Z^wjN(gXYWj1 zb*u)r$wOsf8wq#|&s{1zYrvgpE1NN2J6N%RejRY>ZplIc-Q)9PpgU{az6URY_f=w*5(+<$$s4;ns_u5knRzpy*2*&AtQO(4#oB4UN ztq9)iSc|ex&s79Mj8`{Q!1e_<(_;G;qD*@xV3nZEShr*bqJcS~!sX}UGdAgkj%#N< zt$JxM<=HI@!>jb>0oF2)v3;xJQ_$dZtK!L2Tl_9vcP3YCiayRwKcNl2E+4fVVtE?s z-K!WpC(8`-D{VTrJnbe)W$e%e55qhFRV)U(@n#WA<1L=nJrl=uKz^BhxG7w-{fcxz zlwXgCw7>&gR?87~E6VoJo>P`Ry=}sQQ>J;02^s{YRP>%=wZ!&i^4{#dWn?lFl}*gy z<4x-@l@&9K9QPrZ)Rm3J*As5mJpm$GGzrhD=2p98?NPx7on$EF)D(e|lulFDiP$2I z^p%vBc81YNZ6eiyMs9FHI735YVKIhWT{X7o!f&tU`lJP)3I$yaRcR8adZ`jE4wnJ5 zgJ(_t#^YIa5^Yu-Jw`~ZPw!ct4$ltBaawq z?_$n8y~SjFvJ6eq&g~hpqwZY^8A?EyQOlw^eiNE8{VwYgtV(k%Nk`ogm zrQ)5aqt#I*{d38k;xBk@eYN zD}>)Vx*Oi{1=wf1%_J zQ3L?Se$c5v7v;AOE%SoG<}Ld2JLaOQJbrrgH8(HQqRjX3>Npei!J3b~V?t+^41&+x zM+0?L(T?ZPXow-9u#<{Zm)>tI4d>&OE&FDHsk$eme5HNA#{Wsr5hY5)2cE(h4wbcY zL(j>J=3iS|yT!755{L-mkFSFVUgCNyuXX?;i^)lqyeEoM7qs(FJ?fVZu(y+lDJQ zsoLEe#6VIgh98QWpoX|oA3p_ELU~W+2FS^%G;tqNg$(0-Wgm4|seY{rYzMVo2}Apv z)ZvEoEGU0Aw6hXMO7DDy#w`o;1VD+FmM?|Ksp3_vOa+e4UFi&Ae8ccn(FlCu%W7%g zLc!k5fp`joaxRH=;8SIH{Lc~8$^1*DleD>oxVadojWA!w2ZC(<4CDKIV;x?eNJVR= zdy~q>ps^suziuWQ0wqNSS5rZkVP^OdoG@Oi=G?tE>!u}X ziYj_VjdNDM89Tyw@=3x^+Np2B2$l1BK;_*|5=oA_G@42-EiKX9$$ks9_zis0tsU`8 z?EZ6@JS}YksnmMZag!e2ZdS@iEkUi_+|u&_PwaFKB+h;hzSwr1HZ!O?M)8v~=xoMQ zckwcZt$5T(XSO%k@YtQaV3nU8iEq2XZvP}YZ>36(GEppaPAAk`lH^6U(DvC_o6Twp z^8<5$>ywH)?24M6J;EqY8oi|kSmV_pmr5V2(WQS9r9K>=ZXRiUjd67hP8VtkZ9uHM4Y}ny=jq+h zx+aelnu`mn-M9)ghQ)fJp*3!Q-{!Zv{{2A29Hzb{hWT7Ed2P8#voAwbjS+BylnzJi zAaA6a)b%O_c=se`-HA^or3Sny7Yn!!CCYHD}qXTrq+#7_+4eL3YmZPfqMLaaKDg~&zT$8)$ zXd9+xk5Q#pM!bWUGpHa82QILa69o~HWxaS?;%&cKijvn?W~1h;c|!C%0d}E$W7ppo zpN%FGV-#wQgvm6XaL|-;xJna?i-+oXF<#y}q>luMgtWOJM{E?A$s+vNN!3O#E$t1q zKN}muo!TQ$DztU14fSe`JCClgK6U>BW_wWVrdGSIvBoqs!=hQj6Zl%}jokFDZC=^p zcX9m2Wm$nkRTTHEn7>Us*6d=zsxD)w6RyzeV(r4tD>C!!)~(%4+AOnF7B zw8o!YQXFol*V_Z~FWgbZX6ZcMvGgmFm$7_Cxcn=Y5)IzO#pIeM!tWTY|a%>E-bp!gP*z4`cx-*APDlt?zdm^1%g zdOR@|)>rElf44K}^0e$bdSpi9hn!PT0i~t=N(O0g1(HWF)p&~d`3Pn18VrCRX+W`L zj#YYB-u1{8^N)WGlhVGbj0(ac&$)HCwH243=LJ} zvDT}Q>V!T{FVc;qU3Sna2ScfeqcBzX35E<{<;<3VI$FuNMMVnr&sU4uM_zomF$eQ_D7|Ls)ikmR^#otxF#_1_n?V@~N?!|iXl?B^y=76qGtbL>m%%dgc6 z)D>eRj#PrLy^kW{Q%hB~)}hM*%yripTm)kicf@4|?f zkluK$q6+k7la?}BcbDZgTpDR;?K)vo`%&RQsccKTN*=Fu46%-lQ3t{~%U)=8>m(Sk z5n_SU|Bp6LIat zibD@~{Gx@?AQK5S8dQTFJ7~KLo|d;Z$s%c%eP2^5wrsLG%jk9n4LD!|&Rq`<3X$5Z zI#l`6&Po=lv!ZKpnJexQ3!83ANgp!AVWbg-B+8>wo)OGp`qj`Jiv4cJNzEP;!RX2n zJC!7w$G8WRE{@%^CV4<>_4;7ghTo!ou~T_hkr-AyUUDLYxM9)1`a=na7-Y>Hgl(oC zV)qIM8f_T~hMCPOO8DxaLm&#UPI5CS{uRq_ig|^SJaAPUi2EX{>R#argFOL%S)_6y z@~o36aoMlIa6d-IQ^B*Spte;>Ff}?*>z1{{0Xqb$Ip+gq-?)`T2!;(M7qnnK9bSQV zO^1rt#($561(PKs$_y~)Kwzt_w1-PMHV5Lo5vSvuI#8xxAAUq8m`|yY7UN6Q627NCL|e9?bYykg66JtDZk+c$G?c0t%l#S3vYq9_ zD$XbN;pc{U-W0Zdb<5Tm*o}sdcxn;cB5q9-pzhI^?)B8SGBREV?lPsmfr4XGcGF z=B(L*sW0Bu8t2nR9{oJ$h6HheadBSDP_jw2?8{y4jD0fDgN*{AuRQx&mo$$tdD#>E zgUWzq#jIbRJP>A7w{e7}{=L`hAQ_KSTbAR;G#@N#<^A5uh>%A7(NS&@#${Wf^3(?z z){9k=o;3Xg*0pZFF3p=&jDAn-G*uA+g^!B&$HYIuB4t->;~Pb#z=nL!LgIWS#eiTY zGh2Ye)oDrH1Sy(`63%agUKw*CMDXID$+g-BJUeFKvX*0RQVXhCULE;yh@@g1qiR6Mp zS@B6@bf7->u4a%p{xjLP6i;b38ObJ2&Xu0cYz7bg z>f>S#+gdTXRF@&WceL_MAJ9X{Ak)IizEMMxzPkudNg}-U%^&TlNQntf++e8AkW?wQ zj8L*-pFfJ_;MkGcWq-8~Tp^-P-aX?Q6-wiV1S_>6C`X*);|4|7?&%7NjIYzV98KhE`gEr|+$n1Sq zbY$({?I*7URaM~62KbFZ3@5G=Q~E|R{+nxYt2Xryo=?H0R3{1nBL=k=k9vhw?_GBUS;%^;*3C9a!)Np9vOzowAy0SnM82*9Y zolfi*1QDdISaRx0xj*^Ku{6$;u`cW%r{_Gxp5?OkmW$1)ThT|R-Bwk{Kth^@L2+4L zZ2jU~F8%gh`aLGehtlm-tkQh()N7X;--%aiXhCilk36|^@&$ReH+%iduu^y5{V7>c z|w)6(sQ^)Qg+j42oLLbD-v!a33tMO(*FPNM>&n zYm6z@NOrD}CZ?-zr=O~=u(_|SPM#jAmmy`)FJc${djrs%MP|Si^(I= z3Xr81k|dz&I1{1GQmk83G23C|yeGSa=&bX$P}^Pu1J9>(V)5D(NV=0%BdxNu1Ld~R zVw!?bf#?}apvch0l(ki=bUY|iA_yOC``db|)=k>DHR~}c;S$SeU*&A58#jK@Stl>A zRvFc#i$;jWT1`hiO_nOv9+RP@DPWFAY|}LRPR)rwdRVUT#ZE(c{(&A?J?lS=XI%Wb z?hq$u5kNjvMCXKEDVhX)^a!^i9Zo5l7H}w!Nl!2M`_-GtP^Ty`jMS=ab?MveuMGt@ zji|@R<=l{AgkzmurnT|M(H_weTRAyA%tzvRF6S)PpAC(R7x9~)?KPVa9J7UYJi;}( zmEcT8`&E z{Dao0JE?U`N>pRk1=^J>B4lrqBUYzxf^;pjw0OLe_NVIR?7EeO=0LphdlUU{o0?b> z*l&;sc|rto=D?h+-`F=U%`-Ov4dZbPJk6IIY-V=lac)tSV?h`b)4J_phq=FvmE042 znmrEm2D~W5J*8@%+^pZ)ry!ZWeyn!%opXQ?AzMjjE1lye%*Qk-f>BBB(`BGFTF;j z2;*v9O*}Wsf@VAOwLqQM8~}uHK?juM_8m`8-^4c^deT$B(p)fg8D^_lWjzr2Xf!mC zWoMY92o!f1N!y>M(C3u6OWyULd`=~bCJXwbJkX~rqbVMwIV#XDe%Y}i zvMT99I>-u-;Ctt8>jbJS%ln(~_G$B>fuotM4ea)sSfK()zzL3QJkQ=b$YH-XF=PeR z1>jv{gty$8NKEwElqH`)2cF#COs4C;zglI$hn#*wPFp>J%J1OuDdOEoJIh{|Ust^4 zj_er>FiIyEikRWLQW4{K`gkqmLx`rX#MW~z`^HXV!zQDLir zN{AyEpR!lieH#m6+kTY~nbv!7jH3OnH3uM^ubiyOWJzu!4yPjJ-YrJfC^7eBI#faw zKtjlAL-16_hQzYLM1gO1V259^%0VsNdp6;ydK(WLeI7W zfNDRoeD1IO5Y_5dN$MuaUg1g4=3u|t!J2XwDS5j3%*uK=nS14&lY6u=(fP|e_@k*e zooY%&-msL3Q%2(}cgkYy$_o^ay`#KnMwE3w{`w>eYQ<*P8CVDrGYsNHKh%vtYecB^ z?bOnENKH62_eu{t;*vB9COeIvwLQ=gK!IkndEg_<-bTyK6=0Ad6K+bTii)*wW-3$l zHJPAbft28*BccM06$V@4rQjyFPSP4NsXO*m^wgZtxk)!}fznNmP8IS~JgHE&4@8Yc zKBEYn@+bvWB4DbrGUJ1>?l;6@1zUHja_?yLJ7N2!?g}m7QXDA_Jv>?yfM;dWxd7H(J3Ke`#DTpd)c}-~jNZOzD!1sGu z^UdYh^%OxsRS)F~Uo3)3zzdbUYAjpmnpwX=13(k6_KNbyJyDMJuLiol-f)S=;S%7Gnbo1*tyJ^Fs# z+SZN{S6@KAQT_d43Mr58HzrQ1$@8i0l8+V$rQ|E{lI~)QMOZ!gxk^(i)Ybz3xsqXY zV)EK(0+=$N_qV^A&9G-%v4d(JCES>#OqzvOpueEy&>|;4bc_$a0%g0VY`T^062_Y< zh)-422V|sA`{xD05EUx|tqa4qRrTjF-ys zk;(X2x~Hp0ziord(#6L{L64yRKbvcfH${!jU3&nlq*t_2>ARzesOwQtQp9m4%D~Ke zLlw{Sj5^8ER+v%O-?i{WHkFxGuZ~NPofk%BYGx;pCyB+YQscShOj7nPLT8cFkQ9B* zHK!Wtii6s?T_P}?rYRK$b*x~CUEhy)AmckBlk=>IPJPgqdlXxjjC-=zrBmWiYZhIa)Z_-C_EOlut66|ESAEt_W;c6#g5=?6r7u(L# zr7by9#Js%@i_7f%bxHG5I_jRl2F-mO1W!Yj=(JvkolGO4((zU!m)j4}Bbf~r_1%{| zQszlezWtw0i&ODXBi|YV_ufj81#!46Bv$QrOoD+|$ez|jv9G##gk`pndv$P>Epn!^ z($OMwu0UScuBN;yORd5Tc+CA3kK{T_Y4LMnWt^&@+GtYp6lVqvzS9@^b?z$*Wwk~- zku(?^|D+1!dQ;zt%dBMsQ`_$c_k}wt-=GWbGXG3v8s%Qjy{6HI;LQ%mr|@&PybSuu z&h3w`3p9$IG2t(~8b@m6d{A4`7Pb742uVEmP!V??v_@$<;;)>!QcAg`&xn;pmG5yp zTG)_keJ;Uz>*~?&NJhGf(~xZ;@cG7HrylQzguAYTdZ3+cQTV)#W*kWm%t55yZU!C3 zz?2)853q5D5w)9Xz97-g;|VOWFPhgeyYc*d4L&``f6aS&sXx(yty$X;r==Z-Ao7e) z;6*lwcEaQiMn(v1{uDn~`O67EH~Sud+ZB`mgYYW06I1S2rNuzvE|ygF_+_}#R z+&WtUY;EhM?70BrxiyxroYFdo?LExXi=S?a1drffK^M{{NLcq0G71i`Spo<cK-Vxm5FVbHJp$UIc%X*0K8Tjbi$t3xTd6bB=FvFZRCP1H^={K z5a+H!rv#m#!%!H^wvvzr;4I*1`wmeUYx zM+AuOy=a}@>t4h&A9CkGFX(!gp3=K1|6P$JMR?=A8}Dn>#wbRz&L!P~+aNmbUCuUW z_uCWd2agpKFZr}l#~cu;og}JO_i1OZoZ{hi_UA9ri%d;-QZtnOU84208>l5J0X${D zh*VTLtq}wVl7Fkx(6XBZJr)ZpY1zOs*)_MND_-PmaO-4xeZRt@@GqkTsEPtWJ^ZKy zl<&fTRpuLuUWR{s9Q8L+1NFC(K_JA~i$Q*FK3R5{JYxfJPO)NB7F&_s6a%Z9%I zq&XkL9}uA_qsdpR$4w+lTg5`G`tGrF9T@UU$3T|WCLr#U??U{pP@D<2BrT{z52GIA zR@PNWYcpqt6;>1)WOCvByXg!UJJWX}|2F%@5|aLGq6`YEjD@gbTB7Za;Ra}mp74k> z6?R;3CoJfrLy?alLtLT;+(gsmVj>`QteKc{s3GsgHu=30x=J|eUNJ(u6bRkdzatZ^ zd3R^mXkMST^u+^54oWG_Gz_V~L+DDS6<@Rm%Ap;)5534V7@4k;M!^PPzCFpc%zTS{ zgW^145W_XfsP=e-a`s}OD-zaDFl3Rl!Hi$%nYOYYz?0p z-<{qw>=K`vwK2D$ED%`j4r5{-`Crwqq^;Uf4G!CZ~-sNZ!*J!B|Jz$6mee|C79iovg%t+e2u4g*RQ!ue0s^z_Y84lAMg|yg5 zdt~XCgHIdRCOj=k4yA6?a5;6-Pz@bzprn)dfXP_Tc>0)@SB0(WG$&*ZkW_e;D?fF{ z(o!>(56-g9Pj-T8bh<5lxyjnsjGU%Y(U0+_$Q<(|Gt@!EF9%o0@WapEB?%lcAmI>{&>VJ%>TUV=4IbqfK|m);KPsG)Z~zkPpa;pa>C?Gl zAJ};=sttnkod9cukAgoj8r@v@cmL&}ory2@Y5GgRwj;c0-aPi9JoIXFQ7fg#Ui?ks z`3Xu_ZL_4WqQFXWAaMtOp<%S=)>A_o0X{emH;ewzcV(tKj+XP<^@lI0o$DqjJ@mOh zdF;}_Z#X@2p*i7PZ!V;BR_rtbGRv3?qb{npl5=2iG4^)kaHU9pj}dUvKa$3VryhUs zJ$_45`BZftRRv3w!)Hsi79n-!it2`>`tC73F71KE0}F^qCf@KJocbchoJGcxe4_QR z(2kj4L(hCp7!|XI8LJ+F8gO^Y!@e|y?h7?(_a#buWuPw-Md~2SBTWy#<3dE^OKVg6 zl)_P@-h7)u;%Zeh?6a*1OI(?R8>poC-S~?B(M4AC(cGDSf_NN!L{UVFr&dMDc{}_t zw}xCH0f|*Bj+hh`J?ycovlUhY>a<+BnL9f%kCdGpi%=l*vo@SfxL3Xcc9*_!&e3Yu z-%CSMAlcsI7$0cOb6cGu5BCN9Fv-Cy!vJN{H*kAHiIhbe3yq_B<|M7&YOQWUJ@%T9 z;8w^DID<+PZf(A(>*z%rNn+~W)owY}(hk2IOF%h08Oy=f;F)h6!IU3I6RI?x0Q4}y zQlUw3%JE>6!QZwv(IXB>X{6|5(su*77Ax(Svwt}yQ?M>~g7xRqU+a?{uK&D(#|eaE zFcQsKIz`OqM`g3xr^1O45l2@>0PNSnzzes<& z@78<;l>_!xedqg2_j;U*U%#aEqZhgpbeI=#Hp_vOjlbvO#?z0gU#wh#FA_r4c}0(W z#)tA>Yz04KaNJ>X)TKO;(s(?rmt!jP{Zn7PNeW*%X!G&zAGE+ok26%S6n6rqK6O}Jq=lLI%SvXpT{qR`0wmkCU7M4QT?j9@0T{Ok3pvx;AQrEIl+hs0t1-O68Y_b{+f#$z7adVEpZ^+P43lKXQ_~Jaz zD1!`psECsBccHYlJHFY9T82i3;2v6E8CkrR?ImlYsxOBUQi1`A4~w435ZuAQgzo4E^TQ^(gYlaaK*`+XZd zqz-%UAL4JD{q6XZ&!Rs@S+eIPyeQ5fvtC6^zIrPH{qlS$U#NkIV~b@m<^PpZ+gC!v zp=R`;$r}H3Z6!c>C*CN0;~q-_mBi~b->Vj|o~nE;%xx+u9*V|t5G(3^{q^BT zu?-&0Q9S=k9L4|$nBwgRB`#rLU1akr!O?+ol4}fkXhYWKf~EK6X_IB-OIhvz+5wd9 zopUQ9|2myBZ~*9ia?=4P_d4`FY{wA@ZHvhTJ;HiWGMR}xWA`ShWv*%otJFHPb}WmG zVtEY}{rVb)kRd+kv&LsBQ4Vs(-<_|eg4h?Rv9^YF)6EByAM&)96d+S2R%}K)@Cdsd z9rL=P~u>Cn9D`;JHYM4uR6I4)+SFb#NGrYTUa4tToE9@}`n@c9B4&vN$(RIwS`TWayiY&)J*BK= zz6_t;-H`MzhYIptwJ-l{=L%iHnr#3HGW65L+G+>Uxj~QJ3)jm#I{GVtJ#cCci@b*&;v=uXI) z)qmuWb#lB!FW0q>)LB$Kgob^VMsMe5TqZiEqeH`UD*aNRlArH-l-knnI{svaeX|wGBaPM_e zDad{?#y0U4D_%ra_r@W{D<%O}9?&`QT5DyPr9t}^a@o0(c@dvWc?Sjpn->`q8932juX6yg0oTG!9| zL_|4Kafjty{ZhX3BzQTck6nm^-APf~uc769?XiG}8@iUX#4Gly2V@@lgR^j$LS?3+ zUGKw+MrdT?=cEp|8Lh9-&S`vbr#&%a+_qfG=yYVs*2NTbU|F;_1)nu>IIS%C94zN9 zI+UO3=v_*{L_+*(vgAz%kRzD@9K3j_L+AW%KxHKYRsg*gIbNanreVRa5OaOnd{9>G zq>jl;rIV}t+tYi9JOGF4n?LDwvP^n|E@1JM4Y(>$sY#qBlhe)*)|Z-VHR$#oMCEhF z#JB$`olMW&Mwi-fltsR)T+wXG3BP9HQN6yC0%HK<``krBBfj0e3`;wO`sV_lP9s&s zKehEU$&QeK}WM3tmv`gUUd$!8@jNLnJikAy`DWDE2 z;GJjm0h^aE(jif_m|wB2fRngW{2Hn_016kY1Lwpa!qa1&j3Re%dbb?&{PE52$h_Cx z2tWXTiIi~zypa;v{?LSJSHw*|bTUWSE0Ezm5XQHFi7NC-`P# z7UPn}5s{J%o}RRM;A|@;n=@DDzv{egt(OZMJl7{cQHUonOeRcWYcyrh*M(Rd>9ffx z!e(O=C^#YrVujmImA^|}os{NMo(+g{!WoH+9ojTUg)$aRxoRsRToDuqMBj&_k zsoBC?2P`co!Xw?zB!NXnM7x~O^wWwOq@B|Ay2Bq!!Nv34*AWzVbC%LuiSi@=9Xh?b zdYF;2F;~_^GlR2|AgpZ}yn;~a5aMHi^f)AgEVI4bS(OcA%VO>g0qqH&wvJ0{(l2z# zpezo)oDc^j5fwCviO(cx=GL$$WK#o>$MY3(wymOGuB;OK$SFvR)Hhx*W}%QSreA0e zcwY{hEj+=xD=$J^(!q^KOB%%_1P8YS2@>ZMZf`a*bS$f zV1GJ~e4abC*IC+&G_Aqxm~@+Lc|tPvQV|~=Dt7dOz{kbd}(9I2g5A7K^|Aj#8WN2?sJMc|wst~EzSL#P*xo~`0Qj~Q$ z0Fo&hmh-Io?X0t$tJ?bF$x=na0#K)PCY^o4Yyg+nt1@D9X`Z!0bup2o??mS?0oTvE$csE1czVA!>1lJn+{bS@Dtye>F=YGfpa`ni7Hj z_$&#^9HPtCOyGdj19y*FMG&_+E!s?iVkcBS=7KBrQQ#g5Rrpv2og<0C4p(W8K(|v` zWbt1vD#K}c7{N)l*$p(b*FW3tV8zM2CUYwJ&TU;csd&5APzK$jGH(dLZmXJ_^$ZMu zU8J9;0*Jcu{m4%0*jgubwH8Cl;0WTzS5%HE8ft~rf_rNmj(jO~TUJmX z>v*3Qt@iVaW1~+_H91DSj*SCxHRizM0OAR%&G6w=Wylw^@38i}B`X3#<1d3K==T)R z`(9q%@z`wJdr}HV+J$DMQvYA)N6MEm5O6kPsSU<t(}Opnwg+NAQ#JUfn_kzbCS6 zuCppXc9d2f&}y*Qh;IYme94cDlLPoZHk$pXZ0<(waKz?8+w zKXH{8di^*Y&jS&!3LpWL=#=L#*U;#?r#D}6+Kev`6OsGk&P9>*!JG`>m1{P zQCQmJw0`fb{BSaX;x-6`c=I!-m|1r!Dy0M!GK#P^tr9;*vG(TIH6Rvy=gLA*dcgH^ zHO|G$2GD*LnfsG&d*#Y7*w)H{?A)r4DAu9wcj|1coc})Td3zFs7pqkj4Dsm0J7@?_ z^dyZq7Qf^6rWDL{MxnJUw37??X(v@pPayG;fgWL}?`CDi1-!1A862v^JEPWkmRYU9*kXuR_7|7=-xjt6f zW_leqf&6YAy>8wcSyl?&_&6wb&v>sGskK5kLQfQX>uaah| z0%$H2`ig#aZtAUa$6}UPPi#F2(wx?NQ(s<_oU2YMW}; zrL-M-Yd3qR33AXO0@Uc&F#Q@!ZXP3geD)RT^5U{ZEKg+EdvPf)fn_@&(2B!ORo;^k zCXFf!1CcTCSf|5P2x!R4FwwS9t}@l1{;kY}DH+emsL|1>aA8)RqBN#CraUQH&0~pz z-vOd%k?!xAF9t?Jj;pUf9c}Ytl8b28uUIOW1 zrqcdwU!nQw$vjl}Zbm%n)F_=jn5LZT)qZdQZrT-_s;_-^%iC;Y9)*#B%Z&saJO?UjH6drcYTnxoymaypz;?(b$UMsQ^cEz57#AMs`aiMj4l@x zhk|Qx>#dA&g06sv$zcZmO8-W3!6Oxxs zkTOW1)o!^0tkcCA0S$Uiwj(ZeHx>_Ng?dLl`3)j1KdfwTS~A9j`ak{|YdRhZR)Z98 ze}!zgtssbC&IWwJiY{T@y?vJgQjszU=Y&axkad_UeN{L&NnE%$!5#S>4H~zDvqqIvF(o;n}$tkVni>abQ2mn7-gh;ObsXU-ZdbcBJJ4?sGUg;b?77ERcB@*={YvHQAw**eJ7c-0CC~0 zJod$>`!4W%isVC9hHIa?cU1J}0flJVT}%psTT`@7-(uUO=+hxxLcFsC!FO~uoIU4x z;)o7Dv!dpxdCI4b-*vP0dh#U+;q%ogSsP#Rqm}~dFJQab(E~F0*-nf_p_XSNTjoA2 zRb?_9?XVNZIH(+db{7J#nm+^;!e33!!AbKhFU}F1M(>Y8I#9O|#+xGl9-;?eJwG{F%k4I6)OkTXk!Xxw1j*-jk3 zHp=R*Zs@&4^23RhL;W7$E0Hd`d0K*0E=|xC_3c%l6<^d%=qX=Sx1vV4e5wZ2?(mrOKxa~a#YFH*48{sf(Z%1=ER<@qo% zzyxZB=MvJbEQ zDFm}?4uPr=O47sLb9~d@b@6L>0lj)}LW==?1plld#dHSWV(x@R5|hw!j~T2|6l65{ ztFkEbkf6Qqa$FBx$Eq#AkrLNc?QW3|6I~!YXA3ywc`vk_KpdzcC7$g3ND;?E@I8O40@ieJn@q%2ng ztaRvv3(RLsbUD1UQ=>wLDgRC`A8(Zz`m%#en*^jmxOoP$CEEvHrSp-wWwv__!uu7y z9jP3EFRt62S~{Zkc~nz&A=8A-B+OW6bc_z`9?;%#CLWm{sUutG6kQ|I@dP$RX%ae)u)0*RrIPn~CXQ@Ac>P*uDcC0?m6MMAMY*pE z3HfSc#O>jJUe%wPLFNXtJx~}8UKRP|ol>cALb4m3gTis}F60^_O%0y3uw~za4(nx2 zuG-_~m#VDj6OvBJzHtecXPu!&II{Dn@Ixx-)izf@siQ&PCSo)DOy`gCADze~qNAx_ z3Nts;O3gm2`03y;5;9bB8Ek-@;j4Y0C)zLpCC>HaroInHNb6V9qRi(gx|CgIv8NsP zdBvjdtf53`CThJWRb(<1zfPF2Tb!Mhblb$CMx2_~Cgan%MX7(~=o$ADLUMVVlH`5>{X|wg{1;$@)VOBc6{KS;=XDbl(HihKyirqa{EXX#VdTHm%VwA{4iA ztn@@+?&KUP(r+?xP@;ehB-%)w8<1G+aXjF=*scnDv3Wk4xCM5@vUIZw<r#nQ zb2t2`GBGwLNf_OM138#u@stc=v*}l3kqva$9GabrUM*Bnd;VBO1bn%9oOxr`g~%B} zi3!rhlXbZix_bGD2L+VCS5~N&ERDw_Am*Y)GdtT?s{(iY`<#IC_T%!ZJ_T;-W2wTP zB{-a0lj8s>Zm+5@YGNXDB|RGAY$fST5gmz%QEZl zYNQIhvXDNtDIZ{zWf^(myN8pNv|phgZMI%LwLh$arS6;^G*_ie zw-kh%F{-UnTO;R$M(d zFD}S?x}w{e#4{KxjTYT*qfe{d)Ic#@XQQn;IjWB#QefD*3s*}`BsRYi5uS)Li*Svf!4yLWuc$$|GCv41Lo zi7I03Nwk^-)GeJ2X)ZrM`yU9`;~4qrOkRinG76hRRG&x91F^l65v5XAf@V511)L~t zeI-g;Qt`K9Pa7eyWM*0wYAj$pL&m20<{>jiVZ1UCQq}>Z;Gvtm^!~qk=?~9~ zuh_1ReNtIQWp-NcV4J8Y`>|`4Mms6&sYSr@%D@(*lgT_mD`j$ZgJH6Ro^`H7sVox( z#1IG%l>Ejg)4`Sw4ZgQ~m@x0sG1Gmh>_(;Z1!{}*kI;y9{&!*p=nc~ju_kMzEk*11 z9c_hd2VAxGB}&U%gyhJjl@)9(l+ZZ6VMNw^B-e1-FV-Pl6xDV3b1UgM`NTPao*T_Q+s5E2aYAHy0R`w3z~@w%ZtM2dVjJJ07GCQ|ML^_4;l@}*=~u{lj~yCr8WK(Z~)8oei;4U&M}uBYBnHO7d|o& zppXn%m4mfu318n8B>kdUS_-+D@*GS{hVMFfm5-^kDwW(jyB{RO4bjS#e3OBtzCdZT zCcT+u-P~*h4iXqitJL#cqG_T*f^s53JRdTo4-sSJL&=JqwaN%V^IaL` z4eg?wqUAfrCH!T6E#Hpa;V;*EmN-#zbIWM)aHYP1UN*jW>%Ndih-+6tTTwfC7m0mgw%{)KXiS?2W6T;Tl1WE)z8rJ?-XFmrAdd z1qckZd5D3{JMC4L7hNNuzmcAf4yyg&S|32u7fl+K8yFKqy%ttZ%GE^pko?a#Mhm`@ zE68((@1`ry@iNtpPHfpiDt3Rqrf$$z5fMU{6^MAENh4?NVee-ea)CWp+p)i$V*a#* zGQu|0R=E8E27zc5naLdKJMfP8;7_ceAf8wPPv5jyX0!1_P`{zgnK9>&9|SX##AM~v z5#zWf{LqzVYX4XQXYyB*7+g%$L6o>A$?ywvS~nrXkM7rsG9B53eOA*H3BCG!(G33z zJmGgW?7!N}x(QrD)vo{nm0%UG;8D+wnvWZAhALJaTY9Q<7Pn-SsQ_rB~}^F22oswjBVQ96;llE=_toF zF&NfL@?i0M|EhhnNP$@x=c1~Q-qw2=pc&Y?jhQd^e}p_1W9W)G|A?W5t#E2`pk+#$e;4*Y8PV>Thjt$!xHJ z!sXHR>X?8;@_M@1oJ3vnd?as|3{SGj@Q}WpjjQAuP5zHv#11;!x(4s37 zKflAXw$Akj1%zqXlucPP4z=>RvbJ6{!_V?E>3F2dHTioh_7L>Dl|htm@!mxYI!L}| zRXouP6w&?3TO$>+(LPq`I=NX!XfaNVk^p}3V0xqM9dB`awoZTgc`LCkCEZD^J`M5C zVI`Ve2Uk97Pa~RqD%>Qy)*1l8?D&FVB?=)P1yF`Bu%>-XwH!dBZV(^qh|Wn@>N9O7 zC??&pke^av&A_!V{wB8+m9Fz<6)r-lg1xBONLG}WRwH_QY8v!r%Yhd zD((GsCmBs~5Bhi@HBe?cc`mOxF6U3CC91(5uKY=@Yz?@1@CkluQ}s=3{5EkV3hP*Q zGGr|owuqt&%x=4COvE$oTM9by35NltVXH9f60mtPvuGan&xWlL8m4Lyv>d2bZrL|& z3zi2bKl!Ka0HZde$medqNI)s1U1>gy_1@`HcYGYe!joAXDg@@pE`y+NnY7_o#q6X9 zqhuizokH&f1$?0uT^o3&q<$5)trv4Mpu`w{&`6(B#D&$d0v7f_vMU#!aZjwVSE1(C zL8Xr8j&8s$4hMf?6&hW%lTZw5!TWZyD{3^hL3SXirMt3?)q1e^iS_jXReFXILM&P8 zDg^UMzgS={uhG)K|LgIPD5RFa{s|2>jRel9=x5XkS<3+{GoyEnw1RbYM_hXv+e<$6 zP38p(tLeRGFcG>h%M>Y!^Lg*ENWu3Lltck|R}@`EKTqkvfB!fJFz8*qdf?3urs{5c zC$-BmdP+BJS}uu2%#xo+Bki>Dx?#B=~3L5kuDJA%H%gc+gQl}nm{ zF5iSBiXn&GsMjjkS5ER$VLg6BN}{B_dMKhihI)M*v`#vP&QR zt*2^th1H?k8)7?>5z(rga)a@R-Ss3_(CA6~Ww(ml{+v;SO@-4rcEkp(C_Ucu-&q%R zgYy{LDzu=PYo&dK8S3fI_^-p?^0BKP5B-}vL|5>fkXSw$-z+`z#z#GTEGez(BF z{S9sTP*lr`c^qERLuuylTfC})W@t z-*=PNp6w$dz?VY9jfc?MlzAkRjExgdF5yjPX=!Du(As2VX|yZulH7jn&hI{$FczuA zlhkG4K}wiwcMm^$DVH|T9stSMh3le|;`!;_7pTA5O(pXvT)<&hA!tKnjF^GKIXcX- zRUCtQrN9DlafHR!iPXRnHLDRZQdpHw*q|S~TJPt^z0p)?X?&By&Nwn0%iyzN6?Pdz zekN3^h?L#Ruv_&3*=sy_hpAww%DKifedMah+hlvGuL^9AfIT7`n2~|0jjXbZ!2n`_ z-R7^M(aikZ9TmarBvJSR)X~{M#4w(*Qzd}65)&bZW>Z3Ah3vNLc3xx!L9?4{#F7sp zsI|aNJ}={{II~w%bU8r|9M3lD+F)NkctAU=t1Y!(S45P&(P^Tt(@?62&hZqGO*?zk z2X4B8r{2Ez9*D5QF(p9pMRer;k8X77ZjjT;E$sI9;8I%#CK2{)gvSxr1i~R`^qMsLVI&h+lTWz9LjQDDrWGb^7{v&3fwoB9 zI|o4)6}6_Y=tLA+@J8FVpiv-Ge)UP!&Bx!^?vWTGN!cy=Q|c*+^_Y;{j-#5;g6(etOpaB zFwU}!uubN^Aq>q?uf)=_tZ7R^Iq1h{b(@e2*rx4F9vJJ)2ev&3cZ6((L5$X!uXLoo zy%UH*v#;{so{qOGBZRi)RWeJz(~+3i1{5)&(6L_Pd);y{YNy|)dm-sMWb#!}@yIwC zU{936@)L?naO(Og-|fNGOQ>|VH>B#C1Mq-EP5#RG`c(iyABq`!k_{8 zM6^c9hO&0bX%0@PF$R&Z4Q#AyTm|Ame!@+}*=L8)tZ0^v*R~%(M7U*}VJ5CO7EW1= zbVGy$J?OO~GK3BOg&6T29k1i#xp6b$iU`G?lO=r#_eRXUB3U1m+>lc3#sma;w7@Cc zHh#+`NG(@Vu84kGU%6xTdIu)-;SEW*FF*s1%9t4YIBX9dvFXzkT<;~zu~5-xDh=6o z)Vf!&3vi7UF@_R~7x|DdRjfd;VbiAMa635cnwms3(|+4z_xlxE5e`I!Z4AN2nAJhQ z=#Pgqa1zct&|I<==I#2fuRogj}x_6fz!VJR^EbF@*Kqv?kFzf5V^49 z0Ncb8NFJnQ&SN2?2ylEwQ>8k3%3}){;g*J3=u9)?e=czb9xBW!(xsS>4f*D8O*HMDzINW;wU2CrG8G6DVA`c*b z#EnbqwSpm;b?C8c^_fd+6c@%4XR{N#mE&F=Mt(a;gKBJCI{|*`kbWwHRB^10ik7&H ze~~MGw5=zRf+e%{5#wJ5lS?)sYuPpSJ<|gKHf~_sx{ftn)!844d1G^MYm=D?(MwXW z=NC(g7HCP0`Bng9-waPuVkiC~GXkz>ySXS*{1wlv+^k$vfX5u(I<2Ts#R1ti7(d|~ z2v)TWy@1|HdbT5d`0z&%x;rJr15t9V@urmYqE^DXmiDYM(21Sq6;~cr4z+*M$+9R5 zWG^PL;mj}RaS9L{Ld!$~?EfrfJ!*xWhoB6LC8g%wk`(GP{FQay@5krj7xN zh*JYudh~hxM_sv!Q}*OL*>C~S=6S`N_ET$HCa|a+=>P^%<4R88KX*97B3t>LB+904 zyAAXR#f`nWTh`P11`zXG6JMtxc_){Hd2};ppga6Xr0c`CUu@)~#S~Wx__gg<=gNk_ z@^LwSK6VfrWcAVg~3#!vwGsQL^u-p+U2x85Z zYLT9z7A(U>r0HY?sLtVLD>)n+WF=R1Ei5UvoVLlZ)2O>O=M7u;M<4%StD{`7fX{5QBD|+S(tzX_y z?bPAki}Ir{T1P*n(4g@;iD5!G2>SXwFa;mDtl;MMdsrpT`)RpybVO;Z%5W=v5aG42 zT<))bg7GkQmet*+5RK3i=1SzZquWzGCnG_vgeSjxlL?<_E_p_Hd8rTC=K(1@v$s-_mnR9ppXX&>)Ro$_pOBFU!I z9(v06CMX8=PFrNWTr?B5WFu)^79@FB7^@A7MIYm&r{eU#j!OKVMk+WGFv%Ixl2^mD zRPwyjyz#Qo50;e0V+=A1v^U>wxu+%Eq#RL5_?+?vr-WJ^N3O*gqi%3Vbv~M8Zi;2v zwU>(671iK;&_%65!G(WOTN4kt*SLF5xr-T7RSl05<8?3#``{2Vlu5UndX*Cn{C2)W z^HR7%ycf98vo0zvPq3AIGWgXx9m?9CkI}OtRxz9*6ZLakUj^TAu{qcRq=@a}mk)QF|uhS5#)E-|vH6e#qED$nv)c8DBcd->iX<+ z#ywLhLRN1Pf@UJG*Xd~|R6VtFfvPNvzW?1<9<#K2>K-Se#-n(oWx=s6H{J;JeD;=3 z`L9O}rAK#FB97b75|Xr0V0)T-O6Y;oxByt(x8aztAiiTs_EQmG%eJ4(`FnmX)d)!0<4=s`RvA?O+}3&^EE%z-SWn21x*uHQhZa#;C; zW5hTL@ndHN1MfJwB&M75$=PHp!k#j9kmQQX3);rLUDoYc^leoLT;H__HX+_qH#wv-w~vPxVBfq*sh)lH+T zw^lLj%Nr}y60k{#?7m5gxtN1(IF5R2Ew}No&Q_En2XMS{jdxaA2$LX?xjl??^eNKx;^-|hxWoHvJY4GXGpH7Byo`8>y zAy0ng4SI(OJ!j`DZ^8)eM#~(4mUChkz(%M2sBp$D)h@;@)T#LBII`AzHZK=ib~>Yr z^L6}O=d}mbow}ftoLDhR#cp--A3m@>7ctEMpG>9Cuz@5+X4hoz{Ckz#{sgY!6rC3R zIGjYsVyi>$WQgJ_KGf6bK6supR1dSE17gMKX2-NAHSg($$=Kis$(8mwb*2F{wT;>d zD_%vAX@xYKfhEzH=ik2U-fg8!5|gD75d_J&JI2Jx>b`OzEaiS@!Jm2 zGur)<-c(mfcdA6N)az4CV|6%8l|y%E!_|q#rsGqE%4YKE@clqW(yEDfJAl}Me(!P~ zU6U&C{OhFD#u>D;6UZXLJNR#%*_z}s{V87Nx0C33>?aO+L}oIyXsytAL5u{@Wl9>A zUNG&5?`eScTx!HwgcMSr(QxyMs#{R`4Q@?bTPE)^jJgYX#K(sM8C83!9VsoOup5nf(9(Rb|69ec+jFS|oph`Zkt1*#A#Iq}XW+7HUw5faJRW4eE50IIPmKllcb9~)(EIVZgwgJY~) zN;sY>6>qtiolv%8)-%+A8?}ZjwL0@sXv01X7wXVCu!fbtjBx`l zc7!CHCVBUf4|W5QwfE5Z+|lDHGkY<5QqD9%dv@o2{j*@)$rhS|@Y$FVDbj)add|vq zpLF1b%1b(#^3m~(9)khs2!o%AwY_KPuX-t$r9b|| zH(7FHZ((xC!=-(47WV#BpI56Oc`K`+eiBtgp6ty?WlDVT(o!s*7H|?5PUsWckpt-` z8bDE>av5FY#?vK0I!+FTnU%9qPN#INUfDuQ{KJxZK9gAWPZbSTow$(CH`7tOg7($Z z6Q8a1W&t}D0DN-%zj7TaI=VBlSV#UMH~S6=Z31@vG%wxnJtr!a za?C+!{T#*}LNiGRTtq(lc$}opp4XfNNi2V=8Iny z5=oV#o9KTx8e%IFek6&jTJ|SR}EB%~<5D;d~WOQ?CE{@QCfXcL-q-TuJy4rDy;@h}D z&p{gNXL&qYR7C+Mim@8mlXm>uq^+^Wu~2#pBn0F2cPzqPmhGQEx=N19L<<~262sZC zfr~Q66<1|tT92aYZT<0?@*O@`jACK=);#4?*c#fZ&}BC_yOg24Ikgh(xMbm;DjCCqU=8{TlhqODw znOt+GPX4zQ0r5xwY4;e95B5}nR9nRJ#vQ-P@l!Lnbek345xI5Hg`d@`zX}0?$b12w zXL9ck68#Ltb_sr9SwTPa;);q9Q%-Jb@-t9P6s{(z6w`6X<)XY(k$Nb8zulQW)sn*y z$Dksh=dU8mI>#|Ut>6hamT1HQx+k1Clgg8Mk;&|{3Lt0IL!!;1LR-N%hr>sUFo}S2 zB!moLLmJ!f1HT$X4b|di>OpnwVX$kB=;sbzZr}$=(hvGzxMyg?G&Y0qcH>l>o&9RM z%Ie8qWw1s-7$wlng)Gm4Cu05%8w)vbGm|^g!u3QS$Y31;AC2mCsIz@jg34A_gxyXs zuvSMMF_X~1lcl66XA0x-R3A=57{%%oBLo9~IEX1Sxn9sac{S!TtI+{>{MB?E(bqC7 zOrWzwaWFW--Z1xeMlZM8kk+Kd1gBHc!ewoW3A(6ktU>GmJw0KAntF+MJ9EoAA4k4# z_1*EeuN;@+zpXlxkMsV3C4S=yO?gILtRWqNcm)MisuX3G8eDA9k^^}EO9zB%L8@2T zZ}a_=@y_)(70NAf$2M$X-SZ!_KuKx2&wDwAX@*EGB%#i(YYE+o=4usKm2H|k>YBzV zr0(?Df~~*S+GV)Qr5>lDK&n(J?JS^ZGH1X==G=MRZPFg{xr~K-`>04~d}}jHt)(_v zMr@B#JcByq^L@ncX_pgVgWgOa;q=w=18T2M6C{L;+RWU)8{A|Cwy@ATM*HE~=0jO7Gkl4(>snNJiNhEOkspsr#iWxOeq_UC2Feh<} zY4kX{0Oy<^l+vd!Z|s2Sg*E5ky~Y4l(c3>1Rz)5Ie8W@ilxz2iF?W!6_R;GeMCkuR zh4G8BoaWJmF%>CGnz?yjPuIJ5G3Gkd3?okdQ>Q(+lFKb%oshs>bH*I1K9E^lw()P_ z(}CwjH4VZG3sr7-`qXr)@fGi*^=$juG%7SfJw>Xckn7L%VQ_^xgDt6jj8_#W7kgxP z`Lmrg@WROs*6e6H)LFZg?a!Q;%EIV~}{G;VSc#gi#Jg3B;3 zUjR4cEv180$`rdUk7ZemX5f#;>kKRq?-h~1@rGH)d*Xr-VHjwWBjAr+DPEp7x3p?D zx$R^dqn5s{u7c{iLz3_L5w0q&*DpMgP}W_ zPG!VE8fJsMP8Q+Y0-dK7zpSzAj074>I?s$&gbTEar32QQub_9z#8ELkea|0b5w}`& z+*=*t6AR@iO9iRcec|?M)UXh*A@79Q@jwDe>V+54CAS3ASIH@r=pLs zcW+VhC8L}zDY?f%on23u_@h^B1&D|yIgF5t`jv;M_8FC-dk&{6D4bdDm~D`e&Q~p-`pA@ zMwuy8T`Hyf8*PHeaQe2R&5|_&vxoaIkCWjKPE2-d?h^%}<~323W3rqiqjE2NW^cbj zGaRu(M{GIfD;B9gNOEq_RdzBzw4yt61lHUVy^=5HBv=;5l9U(sN$#ag_nq|#{MgF0 z0uG49w)}BsXZm`WrlfOpR6iDQSNz$)~-EIXZMx@lFX2-}!p zrOQ|RH)mU&t7V|1b>gK6=P+sOm(mH_G39l(-z6r`^`CE5uRaW44s>bQh^@}}!UjVM z5>htZyJ-32((!iMj<)o6NG&Vhckd5WN>P{T{NaY_T zpzSibh$}F;ch+Q3zn{X}`{PK`BSz2DOwbiF*vVx}pUBUVA}X3kEnT>M=9>u7)w~Z| zb~XwoW!!YgwbHwbk2o^KfJ(1r-Lgmi(2B|GuFlmB;eDIn9&tnH`*HotwSoee8K92R zm*s@+T*}m|Gs0PDYc55wy~huh*L|8~_bOVTk++j|VNYqR5jie>d(mR~zTh5u8Fl>MS0MB4;pD;sIK2BoM@Y16~ELmj~9?1+3vk2@aKJ)u6)aj_}@ST=W+Y|FK zT=b*Zf*kQHugF1Bi>e+a62U_e)y@iaO5Gg0@obDea&4hTWU)8F280K%SR7M|B4Gl z?eJUu#bCL=)TabH?0Lfn;63utx0_!?xnDv|>zNzNdmmj0rdJ%lF!Ab*7`P|Zm!br` zhyAvy-Y3Gjh-7X@Ufq?fyhW~DchFe_E9U0ARAAu{`Et^y9XoE!t6Hbzkg)}6e+wOt zli3@1j6f+tl6#vq0=F|CC40V^2l#z;BnR!uUG2(921`H!nVbTs@q_?kz>}ye?vO(c zd&JI)eMJhQ{cxF@qrju?f_<7j)YnVD7_F#&770OR4L56@@cV4$OEiOFu^w zdpG0G6dW_kZ=;5!T69aa>MJ;6$dD#v=|(|vXI@KRMwqN8Ji3@@nbTE zO>mphrFFI0M1^I?U}YJiTicewr?4XyRZYm)j(#cfISj0my{{%SW8kGPk`5#Qa6l0T zh08b@%$QZBG%UZ)y`?EdA=WzcMSCsM&d> zP8#76(%zQ(_`Nft;{4L!EPIr;>ZIf$5;$tf^nKZ=WF6~*zo(*kR@xMWU-L^*t@SO& zL))=@`DcTb&aj(~c{NPrfdb61NkGM@@!iMZ)C4;$M%d&>KppUmlfw6MS`>Evk4DKw zEHAIe>K_xjn$WgDb{#{JYjZcHbW}Z5iViN;X)^x^_KlGTTwH}WJk*q;h^h|?cipjk zI`D~VzA}}em`Vr_@+%5hCkeDtbNp8EOaK4Qe5Vhr$Ji;CY5Ph3InSY(6wZMim!RlZ zq=+C9WEq>22*W-^!WMoR^yiQjuVlU4C|^t#vYorws;=XcJXhp-{IqWs&-ql%|r=G+nylg@Pw2+b(RA zzl?yA<8O&Y_3kY_>P@c|htR53ia1c?T_sD&sCh6j?QLCSDRWrsmXNm7;rAlm;^1>> z)kf?kpSL9uoiN6~PGTMV?ELVgv3ojwcXrx2p!a%yHdX+CP?GM#Q*%<#WuLCyNFhql zC=8GgR08Kpz5qZYgrw;Xu9aVi2N9+5cKMi*_5}J+gF4hBAu*SEFa81?^jp}p@&r~OXs#HgHwq^q?OC>*w@3Jjw_7qbk z_fnZ7%uyfEjtWW9D!&88CKB!~vsIYL9Hp{CygbnjXYC(QpQIkDQQi zIN?36R=X0Wu8PX99}p{UC(Pf#AH{mefz_CPDsUy1S!?kRASZvCW9_BTv>ZphzyD+e zBO^HK?-ljRtx)hK7a70E#s9OIjM;z0L+R4gt8*(zR_XRXr9&rGWht;xl2lGa_{n}j zP>1WlK_^XmGMN@7zdwnvWthOZZ9!g$B{Ke+9p#z+z;Vtbs*TR&EHo?vlo1Lp*D#@F z6R%2Qi8|&?Q%A-!v5B=xk!BjkyOv)Bd6ill{fMmsNK>ZbumxAEjMocdB3;*>YaFGa zJZ0K!QWLCrM;f?KT4*ba#bG(j9_8BTd_`<0`_>2fp;aD<#V}Ng%q?b)s!#!-JHZpTV7Lp{ zlue(p)Rf-@v$s@Q&8oaTi$jO!CtDb~N>~+s=qxSmI0*_VqHKMIY>#x7lEH^h=~XdJ zN|=KVoXQQhMMGd}?bM@C)*a`!*4N+`wA91dC|7hW&>>q1N8g9`6}CL`m2coQaxN+Z zebbg5MNqr3G;&u!-nXAgs92=UbGC$PJKtOxqdlG*vckK)^FHQv*01;h@(-kUA9TP$ z`HB1< zW};|#Pqk!g@NR4EJsl|}@v#wM(2?UjT~4iGsMJ9--wE%zNP3c_dK;2Ko$`E|>n}p; z(X7aubf&gLcKAUuU0qQ}UO|E~C4zJ8aDF&bb1jEzgtwpy+egynkZS93raX`Ha)7oA zBYfZYvWh7^HX57THy`3@6R!8qL`YgJ5oFXodWwELrffI`<55casQ|7y{V_q(#HdtQ zZDqCrjhbUyvLZ(7TbK6rS=;w$pdm^oXpR(Sa`P0h5;Lc*O!MI5D*masOnSSKoN!mp zxXC-lv1r^$g?H#+f|83l$Y4z?m?6OX$xN3r#cVJM;F~PhoEh>w%VEb)h5}aQ5wApq zIi^C@jYm-I>**Wh)M;TpF?N$u^{&{^=v1R=yup#1Ubg#1xEs+g*vQHOBzEJ%QH~4T z8lj*F*Smie#0PL+=|1%3Nk-?^PY!8+f$iLnfIICocrz+aE4CprHYer7h1LXtkwz-t zPDCHLAS>7#B7Z;f`tgU;dedxeI;9n-XKV+zan=-xsOjlsU5+FGM$0nLdy757mQDRh z0dV-QRi^zNB6_uDxs{hxXDY&b=TV+s)T3;njy-Y3299cboG=n0aAD{?14L_m+1sJ3 zw$sUODcKltXK9h=$lgUQVE_0kSs6#<5RO z4fEU0jM4^FaGjU+%J{iRvLHWKku@Xll)p9P|~c9;wZL-`ShM5mz6@jn=E`Mv;Y8Nw_%{TTRZ`}e1Lcs|+S+)GKT==JBvH*b zShz%7hyBV;BJxh|%{84!bI!7dz2l8HEK0ckwRTiR?Zi)sjOj&Krp|4g-`!%VbMX2n zh3?>lnA^_T@~UFzuW_wRpeopny>AOx2q&$k{-_Yl2S1Gu>(r@1Ir#p90^pQtV5btP zzh{-z-E8aTvbGVVlr2#6rr8cOyjo+?)!`qKMy)i%)Fe zYxk^tb{IZ{^9Bv%dWyP0X&TBaFWihg3{Rp6Qt%@&Bs}tj6%XxX=;Y){tgu{2_b^5^ z=3&_Z&+kN!yG@*C8%(Z&{n8R!6C$~nyKEq9pf#tR{|``r570LR*7;GW^b&ZdA5UFW4r+BqkXy4sF-34v34i5#vF^L8esd@DT;l zlK0+WZX%m9FGIfe_4+mD5J=E27W=gyZb{-L%cF9oS7tm{#7(#^{=1BXgpszsU}+Rd zr+#&WyyZ389kWU4B!S=9L6~8z^Ey*%TB3&%7qthqoLDOn8zs|c z?$Cd~dMG%L>!3%D3y1*#f7|57lcsVd@ZXH~?y6<*1Cm7)c}0qGjZ_*EvXC+<_&>>f(lWav;zCgkd?2XAi1aODXoJb|fJaUbQi+Y4fSW$R>+K zZN+02>>3TOb4P2HD;y^t2YWDUGBq=L)DP@EJ#=F?WDB}HG;t~_drT~&Z~k0=MXzX% zQ9qF>6QjOkz>y6h;&56COhtF@^U-$g-0M;Dpi`aQqW4sNANUU)@ov_LllWiB(QN85>hcV0c8E;R z2gKhK9vscDb*S(mPl1M5!%ylsVA-wT&bb1ji@Ev0*4wXn_U{k4D_^bCxz_L(PKelE z=R$@{stIZ+lSGLf;h`^2qu)*W#kTU|l*z}!4Cl>8p5y6ME?S(4D!5ZA@}ie@c4*8G z#W-X%HtNESmTt$ou%isxlwXfwS$^?%R`#)$7&W*l%g>ff9Xam`1fqL!k(?2NYK^ME z!quRA^665>)z}AB^`L?=g6|`6Hg$;joTpedp4gO?@Cw?S@_fUI@|*cdl|y}S0BUgE z1#%v4i3?%_CjDoqD@AEvWaiTdZA%M0y7*qqGkTNu53QFxK03E{?_OgcUhRCP4{bhR z`e4qtul>c^w~^nyGzU7;*WV{YPNZHdP+mjcQYvqlyrxHpmBWDmlx|N4U3DC8WoE?P zv*86!G7&)`>Do{^8>4@lopo$0_ke0_m>RVkgLg1))yk2Y#aKz2EJrRYQvxmf9=GP{ zFLzIcihfe^`WmZ1d+cRX_;^S3DZa>Y34;YbU}<_*iu1!v*1N#hdX$cDhyA#`=`s2k z&b8Xpbf$IKynRuy{Kk#byrX$6A4>T(fq4gtK9aq;4611}=Qg75Kzr7PaNt1RoRIa^WmlugrI&?!g-HF|u-A{7M zF>E&-a%gSg)!dUD^xdTR)((k;0OEN@TOg`c{*KmN(_UcPVy%@UFe@#XK`+#Yqrc8E ztoF#D4q9O=cy}ii7wof}JXCJhL6ppeT(WHPe!t==(Ls#7*6>2b5DA!98z6}qkMhx} z0jS|56yP#8)I%gMqKPw+sBkZ{eauB?_>etYkHFKroz1n+HOKC!@O<~lFw!&lIdR+@ zThj+7AVU5X%}3-bQS;Jk)^6d`Hll>W&JJxjif|ovB8Ky0f)q0!ApXsRk4ZR*a>g)T zllje;-XZcyR_Ml3fa8i!+@#!Atj&zYx<{@`>77P_&^Ypii)FX1&k8K^zg#9J8oy># zwPzi4y*3O^a`CC0uZ07Y!&M{Y+t7t{N$pXgCG6Eak_M6^Mq1)@FJuE&Fhp?Pp@-qUbLFvMk4&?<|U?rRvYdfvot8-6$B#x~7ehu4{hVLLuKr24c zmw#`c*mCn*OpI94X@FGV86ZHa$mKHWv6p9v&_B{xmW2Aa0~C&yQjw0fj6_k zR-gWADdp$ghpiJ}orXpGZy?Az@ixbd#UGrWT2CM*7U5y_=0noE>C^6}pG(*(jMBizL<@~CRHLf#_(DvqGK;GryKd@L6AE_{AYUN4;q^HonpU?D@ zdi2{-iMM9nRd7=-!Gu6~w?cB$FWo1q*f?7-c5y3Uk-3Q<42r*wa7{;R(EZADJ;(wn#_Iz} zLcK-+OF*>0$->7dz#DTX7@=UixxA=uGpY=Vz$e1*muHRF3owIt?JshKG?8Qq1Aw%Q zjp)k!*2Y%!6ga~?HX~KQZ#D*-%rP_`tIZi^DB;!Q^)mlJyUDKN9ZNQ|>OESYc|oO+ zjAbNXTeO6|g?;VMNStz$+CY`tcnV*QH}I_DiQA;K!}Qtoj`fz~qqfV9s7*pBxDDyU zeRd++yWk2SgRxGa$ux)NQw?k&f?%2?VP2rnG{D(<8SUQXaBbVxEs^i6wRiy|1A5U_ zz6D;t-_6pOFd>eAv$vBC!@R2O_4H!08-(=R^c*lsoTpxw!G%{6DBna4R47%WaGPD& z`vt{AC2Xa-3||l6?IhGiz}Pn&Y7gN}hf=dC!u698K}S|^4QS`Cdm^Q)yqXT{#M#

    ziG43a;bKCnlK#@FIEQTUfGjwd$1LBQROgORzg967N?bND+{3HUN&3pXgrAjJhW8B- z<$L~04JW)}s!G-)huTmeThZ52bJ^YN(7etxUnUIJ-Ljo>{EweWsrA_*5G_=ob+O}X z=}Ud_7wxBQ0s0cyIc=c$?Y0&1dRFT(+>QsBFHb>!?Q_sW*tDQMALDv{Qw|l^MwF~x%*x!%MQSc?ankLN9Y|yLo7{r?fHe@CTlH4DgbkH=%(+eLB=CkU{Xm?}CY%;?N#gRnhp*mu*bV zDjb*$uGCm=%5m_@(RtP`we@Z+KN$zt6_q-r7z!M=U2@BD&+28VL9aXu?dLpk+33Ze zEp=aGbIw*M#Hv?-6#V3T(tIpM`#GK0je3Q+mvqdJl%)_IO|f5qhkUuUA#D1UaWuP5 z%SUF~20k6r8A7N!UKv7}e*90f@hlWC{VUhBbnwXeIc08HL)$sq1n@VGWwk9$tb1vj ze@3>LTtHRZs=6-t1>~#i@vRtbTE@1ex~=W(?O6O@&i3}If-M2*P(`u;vcHh9Ztzgxql|-Hw_n-Z*QavEA)pDZZrnbn%Z`RCQ zWYSB%Qc-E9mn*9iPLJ{N6J=2{SuZ{~Kc+Tu_+t8~oK*NUrkw!Y`CJD+aSn=X>TFMf ztXj2Bn&1_%+NMROwb=%>m-=*s`h&HtAvaa1-n_SAZV$CJ$x#uMSP_t z1_Q2vq&t-hGV$6b3e>^5RI>Y`8fp4@oV^hN%qkt<6z`Sn@t~xrwlhziP)xST7VF{@ z46$E)3cRj7qN8mjNcc}ji(}pqRG4|G%+O zW%XF9mM_VTNcF;TzAAgiuqs7kJ*Vr>2-~3IMrRTeer7GeWkO@Tf~1>Bkh^i~!qxN* zm6g0Kez|epoX82G2OP3nyy}&-M6i`c)GgB>I3vl@en}{%4koo19RpF$8`((|4{PW2 zVv!$F>-I)NAgI=6OZJ+zQq^86N3rRsu-xiK!+0vpKZ%pZ>?$pB1@yW((j5t&*rq83 zo;l*%m5OeZgfYyfe{f8w|LY)<4u&751c8uRnILwib<(FDI+@B@cIRp$`yK0;FS;Aq zH7jE$f6wbz`G1B~&Oyj4_*iw)Tm7j!p168~7)_+NY4;jq#o<~}N*mx{cH8%saz+`? z^d{ADlD-9EM&pxnaANe=@PRWOT)j@S7uj^O(9#+r+B0y#@`eD=JaCghIvvRssBKp` zvTdb0-jF>{`=fxfP96R5@2t@h%ZU|gg5XEOKriU@;6z9qE1R_MVmyP-z7|;?tvJl^ zC}U|4CtU6w_9v@SqW6p#ZoBlFdoB&>#p>|dflQTQjFi5$F2z@^9RPP8W||L3yE!1M z`gC8kn$+e_DRh62!67JJEqW8SjAPBxwVp07=}w5`Zi=8FxhsT`i7}LFd*W^iRvKw$ ztX6~6@kMvNqS-VzN@lo+_ED~+EGh>6^x0PtaKle8_-Bf02GylpPy+;nNfX}Kc z8o@Kp*>HqfDJHG%NK~Rw-`c}ib3EJ;4-VIAA@@G3xoKb888E)4j6}iyJ8DLf%zLKkp^|>6U^POj&zRMZxli-!I|O z{qfLmZJ6E_jU1w+zFs}8?-Q1`#us}96=b%MC%af$r5SNs@16v)?- z2M?FatPsI-Ab9T_dtVQ$epHUw33Ryo@-nNF`-dG-iPKECg1KcasX(Gx7Q&IS z<0v{GuBX0(s5+;RAlKtPRN!20u|GfT-4MHJ=XToBsye6; zwv;X;5?i~4Kit|@6qlW0I5tS(VuzEHj@E?3F>$qfcbv4zR;7sZ|3JkyzX1a?K6U># zNkeP=3fJ_7`Kl0#7A-)PQ;8B0*Q;DZ|8u*grx(;|jTE-aJ(&>ob%v74&Vt;27z>Tz zO2wvs2eke9czM?#VP4{^bH=Eujs~3g8D&0djJl(U!{S^klnv39I+yB$%$qIh+@GBz zd(c5}hE`)NH$sR>z{bG>BFXZH!|CZ??|nl3xs1hp$?ITAq`@hD*eu=0oc&tqg(CcC zyJ;74s>vw@*+fl2EF$9{m6iaLjO|*UhU~2>Yn%qKlL$9X+=|mV z1LGgd$*WU)noQwHl!YRbKQP83V@HqfCd8ah^`wz2RK{)BcDh#bgRw}IZqm*%SY~|< zwSzq!I$XqIDTWCJ1a~^v*wo@KF0!j_y8f|1LXKHUOe)C-s$NoQqJ;Vlkh^P@07GPC z#~`a=(>6RP^Bt+HiQ&DHP&g;2auijS>hGL^c1B=(?vZ3nD?OrR*7bi{aQVRaunEPA zXBVoa$Ok072`pR?E2hryb8oDY4-H(){Y3zKq{mOk1x7kf#Dw{+7n>dDQ?*K%lz3Eb zHvM*?Tz?+@Yuh=0`T9j!9^<&DvI-N*qFKUmPdn+vLQ zoi>`n*tf0(iI(rmJJYQlm_YQ*-`&g5_MFW!&!D%KLd9FIyr1NID-XbzodPbUArY$_ zI?>d-aw9;hwBWtb4I3E^9k`y4={>5oW;>HqGNkfv-|673iMJcQaVhlK&Q?FzVYvlO zD;dQlcU3V!arSu-8k`ebZkZmQG+e%QZkMEmy7& zw9r;U8`~#z=JM)W12R|XU_Yw(9Ev;rwo7VgbjYG6`B*WS!+PA79OKpijY-DR+X)I= zV`2nJBed)3fRdK|%|+$@B`q6sZ!|1Bwgz$=5xZ_B0zIuvOC2>mid?e+Z>){SqQ8C+ zgRJ@0L^feNbpgq>vkhQd)N}dUlYl$W)|&SoBXUnT+{b&(*{tx2*@Yxju-4AGnm*SJ zijQ}!BYAXLNqZ-ad4Y+hY2Dpsht~O@SUDj8yTq@iNd>TTdat+C(X5LXxwtS$5-`fW z{UI!)GKFGETc=RbC7i#p4NX{L?UhmnePJNnxolB45g&rVImS$Iz-4%xSBz#8O6RCt#Mf(9zVp+!_Hwd z=CeWn49mT1YL;n;V|5Q9B=_)4a{UQ(7!MGtvU)IyrZ+%5l2pv)JGc)Al%Mxq>!!Et z3IOC4g5@2WyZB&!>lM+DWyCWnpKNUyCnQCHm6e2_`bf);UWs4HI2m_&a(X2LWoFV^ zrxH!@c2Ps;5I20#!J)V5#35OTNB3C|hj8PnA%VP8dsw;|dDW^CZNG4#$+YqpHtAN+V22)YO*K3+U zghfwkpLn`}Vi)3f-bLS#SwxyqPF%?n`Bnu^Zhrj4c_1Nfz|4-rAtUJ8DA=-h>t13a zjAupNDk&c9xgV(wni=sl36pfQs}6lNPaenY703gnlt3bfOecJE5`N{U!eS;V(SGD< zt*)w5?Zalq1yunKU+Vc{6V@Y%)GZ}=qL;ayBM1NE~i{iF%1-jym0OFN~Aol)EepGQ==}mSs zegRN8N{y^0Hc9+MbxBf}@feq$NX)R=8~xu|2z*aR`OLt7q&Dl5!n*X62#Q zIC|rBUD*of)Mk1&acSVA2QBH{E8k206~Lg<#0s%$7OY7wxZ*~ts^bE8L8zQ#NpFCb zssxti?69=<5ji-s;#L-FZ?1r^RFu*1wsdCb`%>+c+H7t!r^eOU^}2JU$yK|KBcNJX zb`(F)jC9U16V0<;cg}z{k#QtLh~VpCypB>B72 z6dp<#59q!T2E|G!AE?2VyR|k$*%GY?UlUv;hHZ)FvL>U7U1iXjHSP*KVv~UeQqx+Y zRX*LN?sbKS36oSnZuNRDI%6GNg!ix}R?h0A;(sYDl?mM9*w4xAbi>rF-`s6c;8a+i zL9~$I|C}zrZ~9F7xZU#^t@6sQjwZ-wEo}MtTw}Pul zJBT^HW@uDSNZjUb);>NHw<IP0mV@_HH?%EXf>Uf+F^**c5C3PlhG?*n>;GaJw6xVCOxs>k~k?`|Y2*#9J z*(4w3crs}b{=GJ|%B~~E3~PpYD?PiXW1^p(ljPTDgh;-BL<=Zdcs6O;*p|(xfuV`# zh!;%QFwq_B98_DNy=rO4jY#yWrCS7|wOr%C(fB;8{fFDI+EDXF{CT0h7U&p3T+F4@G(f60{N^9_Q``#do`;A<`lU_+Mq*Ca5kmn|7krTbM z&9%>jukafq$b{ZRr6Mctel6DnX&({=wK<+1A_rIHRWN_whkXT76ON>!iaxEUUaFA`j(W^49PS3W3dZUbVgm>I#*O^T#rv2#ePB+AvcQ_6=93|@Ng6GCVxSSUX z1_59MhP-NmcOhJgjgu)ltUdJ~+$P=mU{3X|>MJH-02tu-;gUEenL(^PL-YQr^L%9Q zJ&|73#!AUH0i^V4PKHVlTkB!bM4FtbM<@4#4SdIAK;(mZ9XsEY5=edh)qD|#)DO%y z6{NybLYg`Z4{kZCRZr98yxW`C%LjT&PBGE{3ckn$W3;A|Sg~y6M_Z8EF(`L{%3rG6J;C9RS@o!CFw1vg_xMU>;)Jf5 zjmGsSIuE;F}46=s@8Yv<$KoZXLfB<(83yGegP$WLMm@ z@qn<{UX2zc9G#@JWlQ4*NJ3WNy7loIrGyjz{<-6FqlWfR>pw)-rIDH&0{}#@>NY0r z@6weGhT>e2Gt;d%#c z+aPm%gd}vnwoU>RhrVkH^d$NdP0pWHyj(8bgw;f6Y0Q#wkCiT}-h6<)x~iVY}8pYY{wUf@60M%E{RdFZ?y^(ny3rX`QbWcDgwS1^w2V2@8J<0 zXP`!uZ#U%?^VygKGb@B7lc@5h&X@@E(zRSH-D+A8_Cq8_35o4vE^f(8T5Xd~pkc|M z>TfuR8)G(y3hICZ8vI19+@_2i3d=nFAwxt)U{mrwnBx4cNh_~6b8@%4&^@z*T{3X}UmI7#RSR8!&T zjXBupp|)JPVWxjv3PJToyt6L~dqE~VXL3(@^YZ+6F9}y2Q#nD zN4OKiW7x{D@YI^MWvHyRqVx007zkQlB7k_8gO!`*pw+CkExdjcClJA16tMyL85}gI zx7hXPadI(bAe*XfGkaEkM!W*tw3Fmd$G8p0( z>a%zeiP%9w^L33o#sY>7VzDc}!+!Gw?Q*r}&Y=2vtM$XSQsC-UPP>X74Yn$a4#(ZP zRq70bbUq98SUAVij_@cCPhC|3w2kH`0^l{EH%bma)f}@^BGQ?qDzwnHG$mR@H>_D> zK4t8gd)l__)L{rIuahP#`}AT)7<62kWvh~AC+_hbCsd&%^TKXD6BFZW62Z z1D7s$6+RQU1ZZI;ZU7rU7bVPK1JyI-^W%q2(d($jIb6%lbH%JE5d&o?gJf0H(zr-; zIl~mcm3kg&YtURvpjBBph2Vy-wm*bc(ck7#YR=8HT1CVZ;@?(oapQ2;cp1BLAe|fx z(L_Kt8iY>o%)@(i$BH;k3FbWI7-^PFmEahOtQSE_i!^w;_1>Fjkx(c$qEzW4tu=TH zTQ}P=2s55F5LuSDoXeI>S~lB=MQ^UaX4VLa-7~lMEZI9VfEVX1i&i?GpQW`3qN;<| zDB7(jcPD?9HQ}Dd7WGLrvVP_TT7Sg3M}%u8fr^b?of{D|&$1duX;Lma+M+K|E0giD z+@_Ao2|x$;L5Be$Orwuhccc6IB;tjlDBE;UkRL^-Ov3PB4n-?Egkx#0C6)t&wBD!&4odDpG?a}tJ#GxI#uiZ#EDXN^ zcvq)Bp8^{Hne|#q2UaphutH#V1H-)EtmrcfK{cw8O*U{cJF{OYq0~AS`T0aO%sOY-!lqItbs4}rSTeGiNrGrZ@Jef@HYy#@_O`LIw7D|k zeg8%&*os0BQDLh5)gQlH@$PpQ`-|hdw8~f~Qw5q?^^q}(@*NA?l-;&fqd5((ZyQiI z2A)PoK7mXChO2R5$Otg)M2X6$4O80H%sb-PunzVgE8e+n>Q!7ei?iH1*TnphDHO_0 zS&-cF5L0=%Its<_xEpMpPor6`8wUxA^b}yzID+p(&|w+OVasKH%o)f*I#Ys@;uV>_ zwgDCaW=&^M0>L2Xe}_%vxq=<^L_jqIOgy#brl<&|WyX5=1gpn?W&jp2O30~@h&pm~ zr>nBl;+JH;hD4k=d8-1Q*GdL_)qXd~b@ER}gyzr-x#lKY^=SvECG~qGn!q^#pt_Zl z2(luKDOasre}k1t$#2i$+ES5;bH@+nJidxK(F!Ti9a+;Buy^GOIKG8nRr8ebRT-km zNUp!Jm<0S@J+>YWQ#h$hR9qsYPzeWhysvUapu`Cw%$}2-=%2<(Jf#J#^&iMpnvPqF z!AM0J#R>*<#@uMG04EmZ`1--p^yP4+IKL0g8dnZ1bRg}^v>ZQ7YiD*)zL67MUMD?V zu`Ub~P_z-U0uD{tR*t9I)zbuTrbY&mQLyMk3elfhn4=vIN)qTs(SNBubm;dv$|*2< zX7jOe2MDuq1D18|pjZ#7hZ26G1&?_*(nI;}C1F3*(Yx-x#~@;PX*Squt%Hx zfn+vL6ct=Q(M)+JkCBk4X*@S+oP`rE=S5%y0nnwGMj0Y&O-yi2sYrFc1p#XfTTGFJ zE(<>H7+o7!QrZTVHi(+rbzcb@0>(!sQ#2y4MrAVXdA3sn*bt$6#|+DNomDU?@Qc?GK}BV+iwVuup9m+V+wK zidRnuR3^@ob7Isv&RD5*b@AvIe%S^m6mmUKuU7kBBD)h?GO^c*`;*wO2FgzuOtJ!r z>RSs{vCtl57R}VSW!hfj_9;TQ=V)7W9NhBYf~CmROBAaymZr_APIj>g11D|%W~t=S zi3-a~8s^2h&}I3fz{NpV9!a}Tc5xZXYvuyhTbRMl_<4Od`$B?tI|l`u|e8&fw| zrF;OYpJoiaTa$c{pMpvyE%(>{JayfO`sba zh)Dz8&rO#3n!X0ZITUi9T}P7+?k7+63IT$(yWh}g4(8hF@>^uus$Zg}%Ib?@5IRrf zL8+SX+j>nrHx$W=X+^|}aRXKB#70BXQVVFuNgrE-u3{YYR!5c;X56>KqqMvABepO~ ze3shus(GcL%g z{N96a@{AS{U}nY^uo=G;h02)yXyQp*JiS6pbvZh_zp!)f-~K4Z#v_Rfc$i7#RtZP# z0@~Fv?n3OfEVo3P76rVz(^f&%8!|M0n5V2Oh5BY$-hZv+v=c;!0xp|x=Oo`TA#TY&iHKSHH-;j~(raRFfSWA)>6|@zt zKjLM+JJ)e8u_6$ry}@mppl9Qm4SX~oMsvieDnD6*#nCUV!5(63V03NsFIHCd6vI>w zP*t+smP{-pRi^T8!F(duAFhsW+Bi&)9O9?)%N&Y(M!Z+>rVt9Oo}4p#RvYhFBz`J5 zey{dhNmLsPJShxe5JxI**D$%Q9wco`3;Bv(-S{3uL26%a0~H!U&c(U)-rzgb1U8MP zh;mBqWYX^p%NI}xfAPXEMQuZ3@X-8ZjZ6J#R6l-#Yf?dPjUUk;t$y<^qH)HhP!(%i zX`s=n=Exgtg_}ty@@Gnq%ZTc#mgP|UnaS1sl|_V$N9(e3sj)V4=UooftaZXmYwLW6 zN5zrzGn6K3?JA1MO*-NErnl&FO1PQi%&JeK<9_ooB!lY(8~evhI%cr%*xfk1d_1)K z@nY=fJDC+1Y?QD!2tuBQXwb+Bj|Izq;dOjQdVM#kk0gJ(e=5>692V6eqE4pC`P;-bUx^cfzk|OHyqtJh^9Q}j(724{ zUZHGfQj2dcY~bF;iP?VRw3U(*$iU@Oo~wd2EOvhEd>XOLVdP3BD+sKYf4+MM5~IBs zRnq!B)@uf#p>JvP1|&_?+J-60R-+)`1O>M0}}vyHo{!)jS`^ivC^4q zpuiR;BNmv6d)nA|J5l1zO&FtC^A_#Z&1qKr60%0Aq~Kx~ng}}*RWB_YMR@i#a=KWF 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

    <*yPx_C^J_{ND!qP0i=&B!{DeS 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

    (7l6!(pKL|9&67G!M5iEP%?*Xv#$hjfea{g? zm4=~kG@7W`I*kOH&Z6^_6V^UxipbJEY0f{JUA8^~R$zoLQA?6oK3f!Ad)b))Pe8E0 zD3!QPMsAU;%riyERTQjd51(A(&ht3d3dQBv!C)s!gEfTcV+t+{{<5XTo9IK1nhii2 zcC1lAINxm|21lUG#+*pg1ilUSP0*ug2ABF+<>*N-vPL%RICf|tdwnP&B?1=KXNt)*3gPK$z)_Kop(r!YXOjGcMduJIrJ*jV; zChtWTAtOd+Ayj?l#~P(X+0hCWtEa|h(AAZ2D?+)GVa6_k+4v~^3G7_sQGy~#03XTS zZK4w-c&8p)N#4`?22cv?&pIqE|!i&oQpJ%S_0*WNwMb4=~)>$i94HJlXtC8y;Au)^a;JYvBOj_eUnU zroKI3MH^D@!6EVvl<1Q0@Y$6b#l!XKtg3KsFhdEId}ckno#68m7-eeNsV+Q1Sran* z6ida!-_$S7Yt0g>buOIWOoF_P^YiA$Ar2S9|0EUevHHq6C#92JEGN_a({|!dr4D-} zc~};t7Bq7u)yGL8Q!CFsPN-VnPCrfPcT(niYBjmz4zF%*6gFfNe5zTtr~tql!d)f< z1zC11B}Y9EOOfZdehr4z^IX!}i*bl^WZLa#zkZv463o#y`viRMkT-AlFx@#VlD!&M zd1oiS)c*1D2I`5)e$e3B6pqEYhtL7mhf;CkKa74f;2rz(xeu3q)6<1tQ#sw)m%}li z+4Lzvb04Y__v|4cUOX2HM)@B9u4ESc0gg>!aJ$;$PmZ9`!MJo#x=DX2v#~P*VkE+o z2Cv{G!(y~9@tf6P(O0oq&K}d;z0OUivl!qbRueuV{LNvkxWov(MxhV zG)V^y@@F}71k%Q5b=fB_D&0>kG64#2%g2lmEBU}90@FDW(<9C5xG)|^~ zTfW_zE{$VEJgdAtosc-vGi+SH{>Q@4-oG>ALzPUVDH6a6b=Crr3skSVk#W*DsrK$I zEp63&WJ2qzo{Sqi?dZA_d{7$X(JLHNVaMI+Ush?AnofqY9>Nw9j_{a)v(*5+r$0fq zm4;h!IEE5|9r1nAvAH@-9`$+x@>i7Kn_xX2AYKxHvd$W0bVDvo4V9251KBilYaj4z z(oIn+oBZG;i2NnjuN4AYU57zs(jM>xbtEWQ_MHpV7i1>T#Ql(O$GUTVQIxunTkc6q z-cz)3YzCWDt`wNY10@HqBy2+OiQ}IMVNrq2M_7cCPUNj466cq>wXE@`&GPI7gT|X| zhLJjrOfCa}-)!9KuPPiTkK|Y&xGlmQe_d-D@wtkX{VieFyL)hnN@&l1IOMV%c5We` zgB1$D<4h~3He7e7(qGVSyA?4g2LK#!w&<^ZMvH*i9B7-$gN{RtN*hrQ=akOW8nRXb z?RFT{)gm?wTM_pt_|TkhsKR?bB@SK(PL03CK}6`9&-6^1Y``9Sh~_*gqY=dIe$lmUIyp21!JK2r-2@E)U3DKl>bV?=j=MdBYwboty+=+1%1{Dh!zaUu($)gwrhY|VFj6T2dU%vCz`N2$ z0~^#9<5)!4TYtgs$VE5@#|~6Qpw+X0&Q2V@#a*d#jM4mQm|bTcDN^cJt)>O3F!YGM zw;y;?t&f?k0eXLx?PI;+gr_Ff9f_C&7p?PXU=ie_#dLwRfUib$f0?z0J?^2)VjpKO zGIpN5M0nY0JmElBB!%1Rp@1 z-z=&aI(6>YTnQKbc^er#^1pxl8UY-v4?zr4{z5p3ZM0sob=VoP&n}_Xcw^uNXyHzR zh>d-3R>nrDzlapGb{url2~wCINoU(512(Enlk?s!F**6g!7Hp5Kam}viBvU~N2L?g z=W5Xw5pqwjPiY#qR?zqt zvQN6B3Gq!n;d~SMxtKcdM)%e9IV z%y?g;P^-S3L*qmD^K`7vDw&Bvc6oR2`Yn1I_1a_XbBbWHhQ}8wBakBqQddWeqxX96 zg^-*Dvre6o%Qa1BQ}=x4IAaEr-h)bDDb^Nn%JIFlZu6rFP*COtEoxANlrc@17N6@@ zF&dhVy7v*Q8{Yld#i7Sgn0`uVM1`d(3?(krAQw5ADp6vnT*DcXK}c%KZ$nXI>+87g z0*ECvZQTa%NOJXbNDTt#+J@*Yh@xf`a7y_49?)B#amDJ}t06sQTS$SeV{1qerN;&c zw_>urGh||B)JfSYac5Wx;J9Ko3qj93hGgX+J2pERBuBFPng!4yuvCx2hO^hAlh_#O zSF3cTthSV@wTQVA5z`J-nWpIzF}SJ<71f*NxLWCYbLHJ42QECE#Jz5!{RA= z5|7Q8g>;h72F{vsX&{5S`>%59pDdRb?#Si@fEeWNJns7fF!9uN70Ph!inT60P9 zWCf_#tbmVrm^d;3)Dh3;NU-E10J10@MldIe%^JJlhHByr{C|-cCoj3qKJ{`Ux?Qd` zSevJ&hak?t8OUc0DDf8^nB0jSIQ1UqNv^bYreRc0Hce9j5lBbjA=jv{l;-x&=?p~U z#K?GY-v9W1SjxA-%mzC#Ab(pa;39j<1 zIOh8?nwsX0W#R7#v0$mMCZseC|L4(==!&qn3P|_2?dQiV+Jie;1V!EoaPB#np*o}s zX=7P#&-I!Dq@Ei^s7JB+Y*7giGQ{4C=sXf-^P-i{)yc_L@rgXen>Ko{T zPO*|eRDe~cS2gr+=V}Oaly4zbg{qDUuq&#T%PS0vflY_3FT=&JYP5c&j(%D^E@v*c z%~bSq`6|vj09zRl97RJB>FdYs0At%t4u5ac*#qyoqF?L61!Lkl5a^AT=e6wpjVs`v zYd7Ytby1(7;Of->3BcZgu80KXFek>SNZUc8!4t2v)Ras2@;oi^;##^?l_V-fA3 z6zaF8ZhPIIAUnv_xV^AD^`to^01K$J*-N}bdT8)wIj60L@!5_6qJob%&f)e{wRs(a z?49=VpqcjF_+v0>3aSWRj72f&16O+Xsu*Jq&il*v7aeZib?I)tsUc(JAs9>0!OB(t z;a`dBHIEm=j%3_%6U zB~B16aAqpFl7fKWi-5G5HV~#OsDc`6VcLl1J`O~SQp&rVg``&J5jR#PV)zx^_1S^= zLG@3rZ9FH(!0bRQc-xYr!I0Tdrngns1agR`+ya)k{4Y9I?@8phA5o-FCs--sha)(r zJ)%bH{h>-edo+m&bA!$9m6h*D?tFn4)!S-GQuL}YTid*eg6D4OY};#IYS`g)(iY>C z6c%{d{wYHjsE-~70=g3Q(Tii}X&p1T-~x`}0JbKhVIV|JU3LZ>SW z?*&O|HEWa9O1`A1WFFg@Vv?#&AV$hr<{A_dD-p+~(?1<*4p7#hm8Wkf;#j-*QO}Oh zDq$u~O5WVxWZ}EwJcVSDee(>c=#0>w=$h~O^Cya*cO{iB^LAG9<2uxdS6daR)XaoL z%`unNz>1rOOFg$pOz?!qWi>4Co%_dk3nTN@iV12uup=R+Ft;NOMR#|osuXQL|ID$V zdGC;tVc8m)vB;vbV1Yp8oao*d$bfQ7u+Wa7BZ=W9rT_o13P$w(?9D8paIyngsF#`P zACzvKDD1XGCNM%+=mXCl;43Yk)v2U@lS%BnU{uQdB-@{+jER1mc64e6e+IACL0v~= zll`u~E?5cuQAs^6o~Mm^lBJDe?8Rnr6;fwG$5yN z%Vu5}Zyou&X<~>OerkB&Fu2&8gsQj*N{BNO9T!&){$$z5Ar%KwB+C(JG|AHZZ6XCp zu*iVgXll`4yQ1Mu07inj%qP`P48eesDBv}0NdcQ8QGO`_H{CR&3(iqRU&+O&ZBVl; zwf;+-H-wUFN*>#Q#C}f@yUMR$vKL|LOM_%16H(zm^XW*XQS4C!C?ZBJbQ?O#`J zAnyMR*J8r56JI;|+lm`>*YPLZPgUOyv(Coj6@8j0;vJl%3kkN8Ua29`c57%bsK)Gk zh@nftYR};X_9{kTB!Hh7hVQM7i65H^9r|P-pP5J(L(mzQ825{X1i9gU;ku3o`nG2D z<@WJV!tu=AHpZO(88Xi`wIFBnHYVY7?vpK(9@3l_eTR99(;@Dc?CT_fxT8d6@1_vd zu3Un&)Binz2~_?&HX$jc%H(?@?hULMwU2)qfk5@y|FTTlo9x`=pKK$Z%C%)MGDf&z<`th-<$m#pKSl37TYJvPREd;$F|}bVPvMFCC^sSprDz9;o7O~ z?rTt(5P+;!)vU&f7@^(pz&6^#zXwyk^T8XGPG^-Fb&xbBK3h|KL-gZid7 z=M=DzS}~9@eizlWE7DNNYi+k#(ltBP6$dzL4^$}In{12H1sJP=tP7b>?$5%W0`3mI znd^DI5X<8c!LAJs;K}-p$0@zm%3tsIPfl4IJexNnPs5D)0gT1hp^XN@E~Xi zPnOW;i4Q2goH|I#zVdFZE@JGpu%W6^Ar56lvQ`ll&riK1HMnOK^x=T`TDk{iuRdc^2U=Sq@)u(k<-!` zIFJQuCSSa&`HEVdA?lklqM2B%RwTWOe+m4}>y+S0Wy)tODr6bCY%~`}aFrMx0h^jy z2864n%!H!&SlK9JU_@m-Jxu^Y72nq9#u|r>HA}MD&M7OBdW#@!0-elrc$dfam|%PH zn~k{WA{{-}f;n6m=Z*JJuXKXj)XCPGu_5oAjv<1=>Jr_NIaT~eCi*(x;6r{HZev>U zksLV*0i_KIhYL9_t*{!TL z*%@`4#{q_N8CwE}g`Q;ImY_-OU0&L<=5_9aR)_`8Zp}&CgZ#M>AHPDXFaPr>gL2l5 zhL6wu(Oyf^MwKRA`I?@}Nsgi3BhMA;%?m~+6Jd|&$AoD~W^I2#2o>+cRFUwB^MC@$ zl|(Zs!t8t)@Xi@pq|1647na1XTG-{9U6*dN$O6u+X!FgO6EoaHv$NiRNF1$xg@!;Ac%#jeSb2>{F1Jg5deHN3orwYe~ zxp#%9`YbuP0#-^OKp^Oqcmz%Drl>~EQ&cp11c8*I#QHMe$d^JR9 ziKc4PP{aG==tM?MV(8?(3QRsJdX{QDD5cq31N=sv0+U;Tc#3Z9XWEY=z}A9x260_b zrn|N?C5%+5@NufVJ~8?+N~MAqG+UnSx{F#Li?P*N6*UV9f+F~JZv6Hc0=4Z&Wh4`M zmHz-b8v{QxxXOSWC2%`<3}}G zK)OYLT#uYHXF$I&aBRGDcGftcZ(ARSdE>Cy~38EaMoVyg70eTMaqw}|;_qu!u# z1S_8M{E_{tr73Avzz|J0xfF^tb5^B6)cTNHx#go`d`#?^B(o<5S<>ZwCxU{<^iF3- zeIeg|_+rm}`ignkerk{AXlJy?B{hwepQ&~Y>#G9qRLJ;o5h?p8H`0?8I#kem4Wh{m zV%?OV^k&~VIp=*&@>N&8_lJS5P^xsm-4?BP*%G%j^ms&44ml;7`Q&y5{b(>Jy|eUK zrOUVLj*q)1fvF=60|2*Mh9b5EEf#I@O9hg;^V=`?7~2- z9P068(DmNAuW#%Wi&kG;YY{d2U`x8%BPI;dvC3{`e^Of_4X^LcVfHiuQkF;lN%HqzFQLgh4+1#U)lp~ad z?7paFY4Gg_2u9 z^Tu8RGo#bk`Xt=$Ve^VM-?f3P`N@Ha5;gr8SA6mC<$>X=EDl4$Y^Q4sj+ZZ2bcYpJ${x-4siT zQ=KR^4&%B!S9NPBVAckdOrz~B6LL<8cS{U!t}(OLyPYWNQ17*X#i&6dv~}`9;KuSb z8LPpjp$k6IzeLeABp>73e3b!7F70(woq8#)Hm>D?IrTJ5EZDS-TIU2Ye5{EITHiDe zA=S+zpW38<6H15K-Y` z1F6(jaG;iO^@mKfsl3^2*gwJ6YHD~KVh9HwHN~sF8I#pY)$KIe_@7GP(7ADc0Y2PB z8N5pS@3@uW_#A=5R*#s19om(<2|aLy1#WHdvL<1%WNgVqF9A}cH zHVU63S#>(3nSdRMny6wU9H_K=v?+35{2c+~ssr)`S8c{K0p8BAAURnmj@zC3OcX9Q zhsP1lNWXVyUGU*9DljmG!+*XLanI#kFzRZ=ntd9Nfk>0Ojp<&7{>ns)$Bp2d9L)6j z5@wHtIRgudCbfg=4$IStL#-U`v*wH^Gu~uVxhzMZKsK>h7PwQ|?wo0S`>j~Ovq$Fg z^~8x0R<~|*PrYF&7cbk^&yW1$0fp&yM&rU4sCSA2Lg#kcZ39m9Je)V$lkt+jdI^V9 zrI`S2Qs|Tj!Kv^378b}ktojijv?I(EkBn4&8FTAQ2l{?O7hqWCSYFp~fBo!Q#0gRc z0dwKF2nHqGStO5rw)VTwg7t81Vi4MOA@aT8I1S}xvW>9IioJm5uG^2DTGzj|N7LB~ zB?%vd3KHUT6{rSx%47t~d0IeG-5u2)1M0{EdhX-o$3r?zQ}E0SRI03J@tG*9cQ)Rl zmZBXu>5pSBkk9Q(CmHij;mC7<_HAFCwla+K-1jO#!?=6Bj2=xRKswb0D24-syLM%U zibbii#CGaLrBQG_o#GBk!CsZ6vn;IT!(^lmy{%<$%|Aj@{t0|PyHG~#qC+uuGn@md zV$z|->&m|hYLYXy4v8=c$Ih8k9dVjV9m{9=oq_SGbqG!QWr(*y5`2&Kq7-RB3S;6z z@U4dAXo}u*g`^9YgZO$zlO8YyliZb4o}jE8Zss{iM9Jnjoh)R`LyKHH%Cqbcd>X>k zh=LpGGU)4)1@Bz7^d=HEHgJ0TYe|L!X1)Z~-@?5oxZ!b{2BndM0CW!?@g|Kd+s-7Z zOQto!c%GxS5&cNT(T8=Mz3!-mE~7r_nosW>ZN8SUD(3Z*koS`Qt_Z0TNd-UI4LCLQ z$2H*?gR|M6((5kfL3;}VMofTyYUH3w8s4El^sP!EbWQ7bC54cY;xiHdYa=fSS;d7S zpa-Sb8>c$x7FP4;D6bL}Y;gJ+dVmw%3w<2gO`9VcEe9%SkPL1rXAaTuOy9?Srl%7KJv_D}@VXnKO8SchnR`TkGwuLBL}N0YE~x2lS$* z0a4VSpf3p!!m?5a7JkN-q=U(t?P(QMo30G_Xd^28ozwp21T3(BvqNvDq_^0 zMT;q?(baReDmB{fI~KJ1d@2%t^BYP%iX&mE3M*i`))?<3fLNuxS6ssm;>!zh%2BFT8PNn5ubd3~;# zBE`XJi{3DOb3CjDvkkqf=p4bB`dMrr`M}ZAGVHes`jO?RS%TZNfJF1@0E1{m3^E?9 zoEXC?URl4mqD?{V6)I15X-7~_m+8 zIZg{18y|-Fp%-sJt*24%yJlG5V28LhTB4i^WS^`TwAub=&r2ux+Ub7Ly`~p2X9Si- zi8Fe@EN?Y80%gatXT1$sahGh51bOx){XuTfhHDYW`_fGvuTsDvX}dp84g>O^lqRBC zOq3mqZGbdFxaA8$D%|$jTljN&5oDG35)$IY+D=yyv#x-q%d&4&^t$QAwz59 zQWs9%NG;fay}--bs2REZTVkc#k(k4%7p7H!5UG*wE8$JZmJp$nS8qh}D-SEPp~Fk~ zIFy~m46nLWLy%s9VXSEk_=YBHzZ7>majm#D)8TB*o|lpDA*`khy}EpR!h9p5h+_#E z1&6U+c0I7pF)7_sB_M~SHXeRyT%l_L<_% zY}|GpN-N&4S>^YReU1um!Qw;TU$p`)jds618k+&fHa&9WCgoaFwcvxjwBK_#Y8}ok zRniuszvfY|>PLqvZYq$5zt)SoGwU2qn*aboI-GsM~zVvx5 zwAvf{%l=OjFLy#^Us;!TvnAA_6(6Z=+e4w1=$AdXRGyQuGNsYUTN0ohY7h#@sMZWH zk=u|blqJ!VubJ)u@b%rf_p$kxcnsBBBl zF?#^Nje95n)oDoY(Z{<#;Ut-3VCTI}or~9a0gpBeHNH@6jrVOr*`&Jfh$CDQb%b>^ z9o{lcnW62CG>n&9TU~|D@Z4Dx3{~o1zzC9ekn1)ksk+A=Euy%E_5B27Ypg~+wQQOD zb@H=ReM+^=-8Rq>DI5^As>-n~yn^3Afz80=NyW}sg+)?_9Bk-`Rd2UFfpzN?$mm_x ze=R)VHJp1I_2l2^bc30#lXiNgViQ_lR_)ksHJfo|)XvYO;E!Yt$VSz=)m|N^aMXU` zjA^y`T9sp5;LWgcDpesOO&<$(g^JVjvt>@SMu z0Ua9I3j6Q&&Mmz!{} zm(~_lkYP@&3=OlK4sEFl&{x@~mo+p-Z}jdcCXo;2^47>B(iijXh^=!!I+xYMN*@tp z<5d)qyHatD;LY^xshoGx{B|%ESTO0KX} z>&^p2$Hm*MN#3p>p%*A!+o^!D=d?B}1gYoyj|Kf3GUtEzluOq<;Y23xRl}N{G;S!i zaX}^HA(P1$-~<-2x%zem>nlYh0_8v{Lk29tSYSV;J3s9U(Z^U4=~U5;hU78q$Z-d; z>QvYr{+{bj6X#5}0~TVqL_`yG-YJ*!O{{?ysj?k0opp0|0QR&BT)C#k=E2liC<`T!*j8Ok}o0s>Pdx$h0M!Pd_QN2uZ? zN&tIwdr_2CGKD7>->&=}XLPmCS-G_U6P!R&)&dBg=F@kE^?hYL=0y{Qk+!I zfkBd3;WhTq?wBdYiyRNp{UZ6~2>#+qEKD!6b7;J$DMtJvyH(1LNyxY~Tu;RwpWA{& z1bXDB)`xhvhvs?@v1KotMl?EPOVob+oFDHPPj^jjSQi>+9m4h00*2zJ%$W!;Nna`o zZv(c^onGb9J8n{;;J8iwP?V%q8;{q9OCVCDvK`teUwh)jo5@~bB_s2>pbnB3iPIPk zyjqAUf3{voXK4jF*O3mYu~HLGXHsXv0$S@o9<+}CTxAM4M5X8;)Doh;1_O)GhDtsI z?nk*J5Jp?m$N^w#q!NiD^uh7vT!8$wgw<8&tv!I(tq);D;RW_r*fx}iYU>9&X#g2$ zl8V5TCM7b6@hgX;fYXg;`pmN#sA5)@0}&vd!edkC9C<)(b>34c``c44^z1y+-bfhG zE9Xc!Ts|w2%0UHIdYA|K1vCc*YVI-&J1R(HWlhg#-C1A+o#)x1j8MJ)!<;~Zv?MPX z-%=L+Hr*Kbkh`nt@ai$$l7Mq6`N?LJn@-Z^LZ0HWIvRw8AszKD(tHFF){}Ru{XP37 zK~t9EDDzDYz|}jlErE-JvFA8#k|SIQmXZ+AX(O6etgf;m{RVJEQaHMm6rwpwIp(4)J|hqBq0Vfj8senOM+&N>%{gsmW0wmSXX z+SF=myr8?%mt2?alPr~%UmhG~unX^0#jbiN56!@igo?dU$syX39DE-oz zXn?aJ;NmbaM$C>n0iLjZ)Y(aEor~c&HWWc4;1W#kM8#-!Cxb7metFfK=&I5IFpsM< zrZ4RwG)kL*od@08Bh2`IF);@AJzVPg)7@&Onr}~MQZ#`83>nYD2$lzjXiurUZt@Nr zOS>a~*i$3P2-;yG>69ZUV~w2twhDE!FY4V(8**V4V$b32Oe2#8X*THNE*iI}d?_iE z#{5ux0+LQaA45@&V1?XM2Ra5;v7n;Og$^d5xs+-OhuBT5v7U-8@#IASultaZohWQfwpP8c$#P3y|Hm6nm;)X>Kv+bGDK0{O*}Zkqs z$o$4H>tsgg;acVHt7`Z;>b(!b42Fu)IzXffZ+KQ}WmXpI6PqPSD!vW1&Jt{pA$$HR z;~1k-EQ9dWo%;9X%rGEjSmT1wc$AkhO4DD8n#sC$r@#&NM(E6T1|YIde6c%Zq~qCI zofeg*kDZL`)X6w0=I|2LY^4RL9h3%wt3DjEcNoSmmf3r_Drl(%e>%EJV_|zABeG#X ziU3s^WvDnuKS1l1J`w4Q*^FI^c;0{Mz#? zErc9gOp#)>(-iU)(d%wfvo_k}A*jjM#Ws#dm9F}muD4pt(eQSXq80&h_Uxg#(_A({ z#E~akQ}z)G&mn!!v<(oh!0IXoHxj?Kkb&3C9ggtB%(sLmTvGJZ*U);ef_(F2V6=C_ zuZ^?ADK6oaJa`uLV=Ec3Q`^@%th)wGw=IKBwbRPq1YvbnC#%Xzq*3(Mop_8%g{lyy zNiniVrqEor*(ng87Eku0JtC^WrSY~J?e)2^55j3W{-KEa?eQM;Nq(1_&fvb?iS3O6 z+AndsGc=LB@!$}~S1N28HI5vR@*FZm$Y;n(3KXt5Y~5%x0}qyze`Lfhi?X|{ zS3(s;%Du~$vpfP2{%gz88M;YENkng3Me|7eo)}LQ>^d8kbR4xPQTV>108i&m=mt!x zBwr4nzwYYGHq%*~JDK=(rw#8$Eb_Hkcn>~**y4s38q)8#^S4UIbuFf2KccAP7+5+Q zCr?VxtUEnSnC+xEt}B3s?fZElQfo9zjtY95t97=O!yUd6*n}=gAz2t&hQM9%>WI#J zp!Ak}(EA*k{oKXsekm6m1EiiIIQbSijV9No1A<-sb1dQ=AHDj98?JJg-*wOe1N- z@=yo_lnnab)(s5SSZg_3?9455nA}=^%r}zrYgF!j+)W_+U`3N;wE?;njwAqv3P`KkgbfWU1muQ-ukhW{) zke+f9;uyqc=Tr(p;GT?3gfewgJYl1oB1famARCQb;Hs9q*f?A$3TjOLo|JRnOCTqQ zZ%zm?jiBhOAY)32+4M$5B*1_@d`Y#TLSOzTzM#^M`?)55%ACoTSNWn(FqhS64Cm+< zxxJ;|0FM8{eYmd?#F8baTF3d&n<0&D*byDMzdgHy>gwK}+8+ zTWv=>fJsqMC=jq*%=5vB&LF7OUA6R<2lAMn)y;)?g&)$ZgRQFT&S z+T@0WIdN;&GXZhqWE;w2oC5IgteMDZG6SbvaPD~o^0O=7OVhNYQhRheje2;AoM5DqM6m(Y7^}xB0@XQnzxy0Tr~+u zi1aM&Mc+~&`#LQhg{jo!DRG?H#-Nf=fX*Q6qFX%mOdojJ0g=X2y`hox5}b4w_l+CfOJj16aV{fA%WIPnIW0TrFL#JH6EkozyCCtLmMJ<8RkfY;~> zT0@5~6_ms$=&{k9Z0JhhlBbCh=@_jF1_UGoj_QYv6H|NLKb@(T)dx-4C?F2m0;7lh455Em3b^b!$U@)@8|H09 zNKn~1Jz~L0ZZm8^@CKZ#P5h{Vxp<|#cAY9FO=VA&*utm6$Q5NlPeV_LuuN%RelGw$ zkPma|L=`>`cX8F{9}9jFSB4qBMk9MLG#c~Q7oW;RO+XN8?hEC4Ww-8Qt6e2_qGjrHH!5mkmH&;LK>O^AzE@FcEMqKZ-tS_ zA}vsNA)w|`UzayI4W1~!95Y~F07`XUpR?-h(`(0>x~P;8ms#iTZybUZVw;v(>q`|< zAv;#L0#b2?J~>ci2NO)}4G&j&^(w80RRwUOg}%0G@V%zy^&9PIY$`C?Zb`p=D7{KSAst0c;6Ak#s?Q7h|!PgL&UOZ7^|8v~Zo{Cu6aGVsp_u@Tzu zMlqY}n5e{$lT*sNxpomUssHo6}mVv4yNdzJwOH~dGkc=D2YNtV}I_;zLNhD|%8UmK&kLCL!EzR{wT4P&O( zXePs}%omi$T!o{cQ#&i2qVif1)^3IZT(2c4&f+~Rw{XQu8G(uijKi#o^SYbYa6QK_ zO6V%tPM%K3bKY&VV|t_1q{ilSJrT|}E7Pv}sz-Ny`#A!nDqk$3>_&lJ&qVUgH@E=L zpkk<<%mA!_87*2L*}HuO-Oo^fBEwVQI2(Qk)obSy<8q=vzB**EQl=-U7o%ZX(T@Nr zxQQk+x!FaCY^X=-4z@?Byy-EbN|u3DTE2=?UQJJaK~SDzowTLx`MGnv&>#1EqlS+> z`83tm?yjrIz28HzBQtC@m;&2C>VZn1E^qRR$YE9AYOTBOwYp`mr?hBBcvBfB2A8^5 z&U4hA56^K5n^~O*ia3z+U6*i}1VsTv8nEB6{~Ml5t24!1(@Nw4r5Xmf`qPf|uS`?5 zYMRnIt{Cc?`Ys`#4c3@QbDkm!4@Y2>3l?D^RU577YG=Y7jwqCY6Jz5}!dW*eQdz++ z*K^8E=BG$>D2j78;1qqyS|bo)bd5Zc#rq0(-iLUq(?ZhujD_v1YZrPqu^vE0Q;kGv zH6$it=#u*-JV7CB18=;EfIh|o^_1t^qQ}Fb!NMkE{$cMPsivE*6e9&If=zsyIMPXj z*|?~x`*O@W{Wxc(p}^_xEXjKgQzx*?@%C2h*{!mMDnpr+7AbMeeYZC6>iO`@^5DOx zxEfWTPPw$&54MmFcGAO32X#qrDp&;?Te`c?NmJJVWUrOPzHeoyxWl~Ynml-67utCD z0M{`CAGT@`yv+18;5 z`6n+nzITrUrfruC01x0#IS2gK9IE^4JZv4CpF1{Yqf-K35qbY|gH2W6DI(D6I*+pa z+gPPNzOt6e8xBfCJi!|ZG1qWRs*0p_G5J+;Sz&RG2(}Tde!sT~B@c&L2P!+aESZdafOEwR6?J zB<v&1V_41yiPzIGMYR#*?549+Id=C!BoK{ z8IcE2?>#rlr;0;@RBC0^2?oES z%DV#u)@ViVH};#%W{5t%Su?wuK26d#KJ&aD&gbDB^oZQ5oDdn!HMuETs^{ZM4y}1n zi1#3E1QjKrHb13QChAzJzZx#HNR|ATy?Krwr~&2i2MqwB5lv9_^GzH8t%4#1h7VOw zsS#ydBZe0@I{#+5G+a=a%=`_u#sF-BA0a_?J@zACtC!>_1u_<5MjPjpnDS^JvKo#-D;;c(`#-E3I^Mc3s&>)=LMc;-29@T^^9N*^D8{8 zz3nD?D?xe0LKJmO8^L{dP-4z5J)bN`icvfzgpK9&Qoj|iV$i$%y*zQhSuS?tYj~@X zXNL$@0A-$BM==1l#{1sV8u(VWS><9_R%#3X$}QntA9^(*d0VEFZ|uz-WD08>PH01n z{-%#dMXptQE-5^ml4tLksFj|<1OQNGR6DN&AeV3WUs4%M|)!At6Ul0j@(KxK|B8;6L5LNY>W&XBjw zc`CP_i!+WQwe`oIZ$Y2{#L49dnEO+rqcy1S0)lO@$ zDjpU6-?33$1nf=Tm8dLcmBvN9j~4ebD%&=6glqHdc{z>D><|I}qMQ$+t!2gRBJSQ- z8`$3HbtCgd6=(5v)a5a5O9?p6cj5L88YZEnsZb>oiL?hwPXxtSlZC%X9E+!Z^Y)Xs z<`e+4lty?<@XNo~he*mpn(x%-Sd}qaieH%*nbFYaGv1Y8e-GSpV$;0(B!)ta$g3`| zLq`q|^fDd5tJHo~co~$-z|BQ34xEjSR%jc}-E1`OKH_b|FBg7lAipAN?d95#Q4M8& z<|Ez&YwXvd>3+A7S@0YwPW#9O2EifM4tB+xBBj56 z$;Z0XlMYHI;ABx{C$rMp+|unAw*RwLzQd@CgIE|!$qPCo1~&b3!`1n<#?_(?wv~pf zu-h8iJeNE)G4A+TZYxpnHxjyLcg2P&59#=QpI7;;&vZ>h%BJr6&W-XKTOKpDvoR3s z>jgJ~deOku*YSK{s^G8BHfnPw%Gds^P15OQ_bYjLJ4`dJo*gn|tX#l`U9*O%o0VZR zSC#F$0Z2__-mGD{?b`vzXwO^$gb9sELj{bL^EJ<@@O%}(Z&wLQC=-EEW`asUAjjr& z-kZ{#v<9%XrR=KGD?OKb;;pHk@w-9}QqdM>Qn%xNI)Y-PRPRFcg`J#6tnP6uHy9zQ zR&zUx!FIQ-tz8h1Pusg#%mY)por+6EmQo|7@CdyL1+w}W##W;KaVpo+f<-j?2Px{G zPmA~laPkG;7X4EnQNqnp-|uEO2AhWk86#0XAcWc=;ut`hyV5p5Gi*xkYVnk=>KA(> z0k|%z(qT1PY9!348yI!mZ5x1C`gL6@xT9(w+zRAf_X_^G{GGun;+!9EDJOqUJhPOx!z)`t=68UTVK-w605N}Qtc$U~#UbP{7W+1o)hMeOW5j8j%7)>fz#lRM z7JV^E^bx}u_DblzqL>V&Q; zweIAy1d=DrL7xUR9Xvwz!0D`nB?p}1fl#!Yq5b!v6INU3S)I?gY)JO4GvVV|p0Io! zDwd^%c!~W93A_dmDzd2)NAx7{6TjN(JF+($Y>bEM6ap9MNZUp*>1-#*p}n9&V5v7 zj4O)X-q`Yo=mR>-k2P1g+`;XuxmdE6bo+#L<=~K5F@ig)ToZ{M&g1g7szK*_D4p7j(@~Ea(d=eowe{sE^1tH6 zlWkv)_o8Kdr|kJKWpnu$2Gqswg>mq-#)9r9Vrmjrn)ol(>gFfZi<@bQgnSbX+G#WE zvvjm9cLz@}KMB~D=eOl6e+cxQtR&rivhjqX?y`Xg=2>+o{*KHVP4cAyjEmHlkA8)X zipb_IV~4X**tMR()Nz}!b=gNc=38q+)_x>TMmdg=H#%QtC3~Ru7EM=W%Wty zEJ|0!g|${+gZ=(QrOurPYe;Q}LQVM@AAehtSL^PX)w2ghc*I3aQdX}b?Fm0u$rE@I z)-OJ(HuFSoQOnhr;#%V0c8pqp=exslzVB}>sBjvzZxon3%bLwUr+V62*bC_wE_&^$ zwh5J)4Jc@OJ*;yahU@vDIeoPPZnGnZVM0bL);rl|GdHAPWcMpz z;6p7LEXDVvH>Uk$<{KL`sre#;u|%^0gJUI#$BWQo*&j}_R>a1N+K!2$Lf?Cy4UmJF zB-&j2=k6UhKpg!#&`sr8v)Qmf75+rxdrY|6_pA_qR|zfEn0|*~P}${|XL~()@6Ow3 zoDthC!Hzl9r;&977(6yHt3zoo*K;+Rd_ig>0TCxWF?7Txp=7-#X%APJOYFM{$~xNl zk8n>OC?TyR(D|!oB7-8}`w(*5KFvqw1peF~j-_6V`WAI8bYNAx*C^d9(55SH&F;`V z)-@aL(kxC)kls8u= z%}w7bDPC_xJJ$)erQLOEIP%15fLh`Lb@&l39){k`2PxPk&}uIYCN=p=I6J=*%yb%A z=}X&vLl0MyLhB43%abOM8F~H{b*?a&BF4J}BO0U-gG!?{meTf9w|u;#w_-W8C*G;9 zfco39A0GcM+nWlQludN6u%)T(7wuJ6tL~lfx-w%ALa z?!7AgHeP2uL7G^t)7s<+JQwl1WZIJT+A`(B$st6W-7DSTtq1KSP7ii?(%p1H-C_z! z4V`5193Q0XQ#&O%S7A*>YQdjS-p%MSuJ~uDaVEc8bLTMAsazdf_9~sf5Mm{1yF%I# z(X)9?>dfnGn72g~TVym{Q$I6+>nwYM%$A~;b43}Jqrq;f541k5a&Rvrg5(KLdrUgL zoozMYdufSQ0KVuOo2h7j2S`S2!a=DH<}|i0a~h7Saw(_~d%~oYqa-f1`em4$Gq%4Ys zM+jz@AKQZ1d_tr3834!AB-9^Dm~6B+rFWfZMl96i3rFaIAGp+~T_cwmHyGKm1bobH zdcUZb$nSStwZ@i4HQ zxs28voLJ5oh}FV_lsN+u(o<|%9aHmE#fbwxmAHN$Dd2~_=Ue)T#;^4H%6--)`(@WC z1ByQ5ZzdqXwkQ$ej49DO-=tzaVW1J*D@4i*f%^$D>^#Ch=boHCXp&L&r!IoFrgp0k zwn;>^Qls7P9`8)j|635`Q|Yec^TH5+%HI{CT66jOH~@;=Ue1{z25#@QV_AgJNIC~2 zTDatQ+REdE$Hr+oMZnIWUz91?WLuMD%Cypw&N3ec|>Xj4;+8P`%C=xC1^&hI{o+2+r8qiBy@m~K1BL|V&UvAL@ZKT|$7fY>af z>xf(D!xSl&htnMu|IMzPk~GN`P=@cVY*B3N#4fS{+0i9C9os{lN+W#l-}yRH5({>J zb5v<~7Lm8Urb{&MQt>e2kpb3^=}vUCR$aGC;HRX_J(n?0X;bQkZ%}d09J<-wc?ff1 z>Yno$30i4K=x=(h?aF6d=g|WhGr6$QRGow47pb?dqjogP#Ba|=Bd-Yq=GKtG*|Yk} zRaK%0Ubzn8usE^U)J<)&S3l;%=b7Eld>LueIU!bt6K`n%4=ns<2m+-??YB9XUyc5f zIBNFMh+-^)=FZ!SCGBo}4tH!I%gT5K{yi{ytgCvDO&FISQZC$&Y(#XHfy&%jD^nEH znrBrUyD=zTd?O!?L43P!%iOQ2L4yKY-Mq!(c66OjkFcdF9Shbr?#9kzn2?e6Eg8dl zoFbfAK8x)@M^Jw^E}oE{cOb+rRipayiN1V8p)EL5V#T^NnF|1|W23XJC1)0oBSul+ z{=){5`URK`VFww_F;=3mSxYb+qT;aQ)he93_b{s{i_12F#E)&JwyDDvufE-ZZ}Mr55bwrzV^+z)r7T6H74G-K#~+$O>}|GtwfcAQj*!$_#fq>R5Jgi9l-T7)~xd-JS5BU1uA(vi<{3Q59lT(t6L9 zpYm1`#v;(E#Cd&c9;Om+mx7y6LWEeWjjLX92iPYkY|Fug8q3PGi8R%D(y0Hsed?>2zH-4Wy?0PPQ`EV zn&Kwp2eoKBH*>eS#U*f@kdG86{Fh~Q+O%CK*gn467<|iNYv3&tEW{*WsjJ}B9T#i> zo;BYR;WX(A;1=!-Fr+D?NyVioh}*9zb3HCC%ySVLVi5`-8!X(@psSDgDXL#BbQ594 z*2#F}8ao+}hC#PScM??ZI;!}c(eC2}K*l=qT0xpDGNBZEGUe}`iOz0hJXcE;j+r&* z`F7%mOHW&FO*~K`)}S)#f2=wUOBvNjF-!7Dn;GQcaaCLi1+7fPji=TP=D**O%iC_C ztPHDz6hbaXHA??caVCc&6vwo=C(RW02oYz3^2$h{dioU!BzL09N{1soq?`iqM%IVc zk**1J6*<-|?MEzfb>@6f49nJWiQU?sTA(X?STT?}9tIiYji(~Qns^W&G}guf63vn) zjjJ!qU`eUywT4d0uFcEb6~LpMXr~-Nkg8GLusR)2b=fc5An}#B!iAzDM*W4>QyW68 zDp5iMc|1LyPJ==X6HLrTOs_~~E3c*~zlODvTiGS$dPU=Jzewn8y_PVg(DqFlloH=Q z)-gH^dtA$FMGDMnbV*Imnz#s-fvCu(=2(qF@Yi2jE-kyQ9+VOrJn8V;u^c26p3mn1{$+ar{N)vsU6w`0_3lqfU4Qhc_tm3EDt+m!Q0P7QM2$BorW41vWYM7!#{F^tfU2fX*O&M zPGNuPBOZEf#wxhGF-zm(hZhEv`4GG*fhD_TzH*UY2`}&M$(KbV9rICGPa-}~#Kl6~@;NKZ^ZJZh z$tq1x#JOY*Z`eU#twp+-*K{&Hwl4S-qkk0M|w~KHr0n)3_))w zgCW}cP}E%djUQS{O6oNA)Bd@AcK*BfAx7wp3Pm=ufvY2uV;Lv3w9NBctW7Cc8m%3& z=Q@JQR^g6}9$-e=G4}|T>0DCh1Dh92`gc35CX83tIQ6+Fna5+kaZ)Qs>)r3Uj5}r1 z)b{G-MKV7C0Z@)9r%U>tfB|AO)SU+~<{@CSO7G!AT){>Hxyg7ijzxl2ABu*M%HMNDuMn4|7w*Kwsa34H=F z8h=yj^a5M)Y&z+9+<@xUK5?Oots^kq{Z)N`P*H$RQ^bFFWoH}nhb_eTN1&^Rlvnsa~ADLXJPRWDNFdF>fmjoI|&BzjD^PsE?Da)$w;^>pjjKc z9IA4U$z!|>JqO5`zB@LGqRbIWWe-r%`SXgJKhWPTFC4uIdTV6KVh*hgRvB@PXyxTB zXIdqV65lA1<}&E*SZzOEJNYg$jFQq3Qm@4U8(Ud!wwqKKEq_nUK-tFE*#hVJA`W!1I(d!8&)YZ!!Y>XMVe&O!pt0I*i$M=DX`9tT96jhU;VZX zfh;fWEpe2sljWNIKaz(lOL0ze3e;G#NhX)(U^yHRs59G9ShXyw-5(hxk380 z+j09l*0|Pq7R{gHX+XLYIhsQ8xJ6hL3#~J(zQ!b{CF`Y~ZX*Xu7+US9 z1JBuePEyPUc&CQrkl^V{LXlel1f~D=xPaM}DYADI2UIE~K?DqXBrP62ttFt{aWJ>2 ziX0bw@Y8DpC2)@wSHSU1-{(GMU{kZ;G(Hyh)P`M~DiQp-&->Clbkb{Ez7C#LA&3@v zuIjz5hX^arROLKUY-={jKN|kDO-ZEC5=xPGr8t4S)PIc&Ko)<^&OvzX?N3735^;TK zln86PichCxxD{1;`ywq6PAVj*c*Av*qvUNzt|o(bch*zj0M!=fSZ8;jTaL;_yKkTE zXSQt9B^1||E6Vzc>Cop}p|^>J%)`e`zjm9lY_)T7-Y9AC(^v^j|YTjxe+zK`HAMA3hnSgTx}U7?!o zBQPB*+#CFaSiK3&jp$Zk$eUz76yQ#PnS#nbr*26P0}7FJ>WNM^?`;u#`gNAS?G`LqhRx*1bPuD1t7!;(ikl`ii^c-&@8Mmke4-`n=s zP%r-AW!&sNN0G8CE3j}o7r>O};1ln(yfNyg&mfQWvs?otuWjXhr?Br@-d_nod3>@y z3Wa_zG7lM?8seLOkkef_?Qf4-0F%bz#ClI!@8|+0MW>R>@42J8E2UjNi#YyH5Uqe7 z;q!f~!jrQ%X15DPve`OeYyBALnAVYHVdpxIg6&xjrnvKAbO@=bYa^=z+OEXwPBE9F zOWc=Sb9sdZ5};3wZ>3!jiRH>c&yzjOBLis@p{lBs9i#XGm!gcyLngNwbrS>`Ph)zj z2jUrAw7`*1x=}u=7IrJ5=z7B88`wby5_g92@O~@aJU-WY=Z;-am2k)H>;Ol>k2813 zB-Jy2b%m=k9A-q{AMf?8lH4-UF7issNxv(CiE;z8$r-Rk!vT`1ld`u0Wgx7$`1c$H zcN@;A9L55%svFeI3dB)(3DbS$%b+80w{Lo>6AZi?UhPMH$P^#b@J{v|T8_~ylB3P{ z^Hxl*B7H}nT!3O%K?LtPmyT`8mJM)_xZao4^znrHa^n?;#7052;mE?bCqPjtq-$nW z`omz=fL6D=(sZ&z>}|Ejl1}Yc3Keb)YhzzsdF>$auj&I5hv$E;#9Y&8W~9DJfURu$|nl^f%n6diDE}0x=@meQZ25)chsgdFN7L|erooBUuS$HvlQfk@{VtKRL z0T&WMNk52#!R4h#*Zg89nFF7HZIwf7^q5^?xRu}hVQQuERhm(xby)At(6($;3o6Cq zZRK<^p9}Yv<9iRQ5HjtZB|+Rg)+QWiW#tzIg;(;H9qZjO9sI8)&tOEJN8=ZKn;6(O zxmId|G~JYPqC6L!QCu(hE$Y|^E0IiuICXbro}5pc<7&n1Q(sS}oZuWzgO|>VGH0gf z;l*?+Gach9naN2)NZwdu)eNg3nV6Rj-kAuSgb6%qza&>j{r8+@+g#9}VMgrG@ert6 z-sR;O*_&MXNiI-LhxjVD=!T(04=ltx=o{@s45)yu%8p6ygPX{M8Aymi1f# z31SpsXQ6m7e#F=)0i+a9-VJGa(QtTLTAy%p2-g1#!H?#QH7w~Q^Wr^JDkab*<$`@_S0Xp^(BhofUKfnnqDNQfG~ zN*;6y`PRcUHX`#gL7{XGPYf@!CT^^=&WNX(h3rd-C2Wov(LXGEPO_I&|9V|8g(9PV z_d+sZ_5x7pgBQ64@(S;c=UkcP;L@fJLqK&p4DxLOYeMSkt)ru?oXz&MmIS{^;@PJT z-c{0;)dZg*mIB1zv(O#-nb%5oPfQu0vo3-2`>}M&%#ImJ~34mU&Sy@ZkjBvgCZ>Y<0#U8 zMP{ThJpHu=t{(?c?26QjGa|;_%z3=5KMA4-%w(*<+YyUCnx2M>7dwNGJrsBDdT(3CTDD&l_FBmjUcxGyqsX;Ccd0%2H!+5cnJf zZ^+$hq~G?DeL8*je}IaIg0iQz91Y$CoPssQ^jjNASt7qNJ%ZL9Z8bik(0~rLx2!Es zzH0(cVe=}>=6twgSctE-yiK>CeXPtwZZg=-R<5kIcK5@Cx69l~=ayHjTy;Cilt6;9 zclj$)v=S>GzQuv#tsb5yhRpAf7GiC?aPd|sI=(#66G7E39qNVx$$N_cxj)Dmf1Pqp z6k2u83`Ij%GMiE7Q5uIy0E@L>RZg3L{-`I+2eF17Yx|y;6@|@@ zmPh)l{>9*N3kI;s55X=)JLqc1A*r0^e3S!Mw+3vYRyz%L=9`QT4m=iFut zz?ekd)<^kf&ySeRY<;py=Nhd_$!P?5TPZd(ZqwqDqGnS$@`E-@cU`3pSxoaGcgOYt zG+lLJH72Z-zx+itfscxe!fwa@9EgsI+%+aLki3}oF~@D ze$OuLcBb0NlyPi28-hDnWt!LR{gm*c-CZivxo(6t7*z=6zAK7>+M*UsiF<5mMvT1`CGS@Uue8#lzo+PmIW*ss}0$f&Ig zJ6ESZlYt%{9ll2t=S9c^q~}~GJg_r%GC4&l)SJr9>dEq`xIF72NnzerOqDM`LX)G#`<2DXOCVRyCpY%``=uzc@DV)+cP$$y$%Pp$o915~_x7YK>e? z0J>r0Byg}?B)`bcLee^i?#{(Hu?zZVmRqWVwJz26lV)_GwQ>6v8fe&K&d|uE*za|0 zTL#UG(f&vPdmGt5_rR0m4<9trC+Hc}ylpvE?`_0M%=CHjw^ZxdTcZGOSG=UuKfFS6 z^_FKh-)TxBSB~RdR0s=x4`|i3tRf40M9KeNg)bo}b}3!)Qmtr7*dc`kv`+IY&x#FdEo%BB??=aI#CRR$r3Kk;hGMK-fPY8Y1g zT8CH=gRferi0ecW7gMRraiaBCisWc>C^*9cEgxJ9etz-|#ENx6&oTnl|D)|}b1cbn zW4Ts>Kez$zAHp0nXWZ-t1dIUbNi*G5S$QMQ2kCCktY`RnFNtC-@CqdbTbAnY%!S|M zeZi>Eklj!@Wc;wmG8ukql50Wn63|Tcx9zs;>8iV58~Rf^q`VAihsVFPFx0dMMaXQ- z>YRq^yUuhG3PfJ(kS_oGB*LwFr*_&`WS}Jvw-DZ+{iuAKj-YCdxf;8$e$cTkJ+~OnmKFe*01}Du0wZ zJ#E5AImX;;6`%5TB6zw&9IxifV#S8Ga-mIqo3g*VgQjM+@3cC~Cv4$s#;?y;et(4i z$n{-|b|qnsO1{94&fwCmI36@jn?~31m2V~OFYR;oeQnQ)HL9H7VSdDXA1Zc&#Kq>m z$X=1&>lftUA?PQXt>9kgQT5Mz5bZY&^b zpPN;2_=Js4xi+d@sy)jyCXj)us%f3EzMxo=IJ(U@X)k8FHBZk;n!9mAJVwGplQ>uJ zJ0#;APTBPokc)Y7&KSd_QFJ*;$7hz#epF-c3REScc{^ea$>diK(waGW-1=G> zMY0J2rpTLFRUNMV(O=IwmndQu%5q>uK}U3%+^_6gqZzn`2l5>i8hZneTF5+=0ymN< zIxbMr;weQ;s&_(tgPQr1p4Ns&c$#ajud{`Cqhz!CRfv?w109z_TB$F1<rV+84Y969_xw&V9?Eq8a+!)9FxneGg1B)6 zdm71gtU!~2%(0#E4ja_TGe~?a8|-Q2C>jgFd^Zk5l*g~HZ%?+lrb=bQx+G#dG>O2e z<1{ts9dwvp5hC`!8#Dqh^4^oN(AS*NPa<8M0Jhl9!GjUw>%yMFrX#zF%qo=HO`;X} zgP_zksqZPybmx(0yGri76f!-1*QP|rVvntskU`Lk6G`zBY zEFJ^_n|OE)@C3EPvTU1B3Xz8&MsYG?^G>p}CZ#NBvfzT@A9oUHocH{uUr>f^%|~Ig zcCVML!JpeOB}Nrz1;(&r|zUVA>gf=Vllygdxgn zCO=(}Ye$Sc2FXem86q2GmBoOu_I4vqLh$k0cF;22!AoL1D)FlMXS2du{~bNssyz3U zlowK?!ZW0R+a7(4A2i47m5rXV6Rq8IAEB_=(4c7TkrHCim|gPEA_q!S!6IbjGir;I zVn!^h;BNYz;~_R>&ydef%cY2!4R58ms@l-@BRjJW!*ZwguLR_o39Xl+G)UOZKYh#$@)~3rBT&qkYh7TP$wj- zPX{Tf^t`d%rUJoRe!+M!3jNq>jka5i|26i+HI1`Ar6r%k?$B!mSA{h5q2n3u60}aD zzP^Hg7JoKrRZj0_X2FILzuxk~z7j`GXHM`sSMD9=$}aPL$33N^X4_cW5G?&@#!@&) zUHPeiBvU~+N$OP9j*QMtj>UUmch*3$RaKp^c-8Ea4v+D*mU6O20@vqkqUJh0dI)TM z{rvvV36CIT2;!Bd%5!4RR=fq=T?ZZ8=1p3Yr+we>9vF3y_ z(9xmwJlf=`2wV2bBMSYyhO~tYbl>Hb_40R9&ZMDqj$UyqdyP&!Lq950aOcD) z+6=T3ulL@I3x|MmNJR*o)`NpFrfZNe9%D>)S-?1Wl0IE+3jXB8{xYYWs2Jyz%}l6F z>p~rTl|+wLyCqxYlYHl2pW~U~N}rFYXbEV-$j<(39O{>1sZQ-4Zl^)pJfw~?7p zE++`>PStsj_q{p?PS{!B0p1MDx&^J-V`n;dKkx#IDATY^_c(?fEl5WkrHq|2I*dWt zmiSpLpek}3rkT0A9n`VGU)CcLDZ0lTukv;|wi_sKv`2jsW}(|=Ql=9}OTXJbnZfil z29I}a`3L=+N-+7t;t+b+7bbKCTwb2G@~)cBmWoraU_H%1#Pn2#kV3gtb-9WJ zBJQ`zo2cJ!sCi%y{cdRn0FXR0&HR1-S)-Zy&>y9l3@fU6VPmEFOY64EOAsdfaN^PQ zlJTEdd;VWUpUYNO>=750{O#pk>+o_s8CMYBXoXML)>cEnm&5+vl6*65tZG19+o*hh+OJ z+@hc6$b?Wj;~w0!v*A2adXft}D5BA-$s)8l^7$JRUP|PIwKX(8x}YQ)^?{n8EPJqE zZe#NKuqbnGX2&w!;EV3@Eq_`70w$8t?b|rmD~a>x#|YM=<}BHS&|#H0O#1nx$Wr%C zZ$`Sc6j9RQK1mQ)ikh}P@zuek{SNx~Y<)615`2wz>{(&~gt2?NKPq3}-dU^3e9n6x z`tR6DjWvnIs=uJ^i07|Wu2elewuZd8MD%x$?(v%4Y;!PB@;e`RA|#ZsO?Pk-X3E}L zZ5zr@Klqg`em#fT^>e}xRoWdpo|Xo_VUPI^X_#}eOh3g(XFk<_KpBepv3>MUp%^@8 zDXw&Ae01x@H6*9#Hca6jX>6gYYGjhh9VH93upugScgLTypM)GbsSd|^8~SNY>eOo- zJ(v@|PpI4P09t~KkuhqyeOlAfPPg+)>Y8G#@uc`1<%$Ajy_L8ma*_nrOn_oFx^uB#%6z32&cu@gI1f?{=Aqa8KUWhwYJTXObNYRNuozeNrvh}Zl2vO4eYuTOF$->Y|{N+S3%GXAT-ZJAoLeyp)CdPR;I z*fB=;0Iqj`Vj?u#3jM_LG|mXu=b!kpJ0Plz$|2(Z1`0IMD49T4RJCSPyz5P|8hVxd@uyDEduBGePvN6Z z?nJ|Vf3E~q=>4%mm%Tqy9aA)S1S_jY1@y*gA#>f<9op#aImYc>;Fw`ty?p}BQDeUI zcIO9vIX8`i4I^xBtw?-=!*{v4(HNy$s=3ezn_2pLA`0=ARi?+gpo)0Any4NFc28dC zU*5Jmy|DUyg%Mo_Z7`o-kZY&oe@Ng~Cgj?ISoH&okw%VNwKeGBuM_QNYeV`m%zTHg zA2l0)E61=IMJi`0N(stYQi;8PqWQ`+^4%0Xn@McF{VKQG?CPpY1(Zb~TS!1KPTmH%v+GNrKZ>Q& zpgz76lCeV1+xS<+vHY8~Ja5t73(OBkHbt*I`LVUb(7)VAF zrKFpS@b>C#pWhi%MY>#w>a5kPj9oxUbsuxl)Hs~;x}g!0c;3Rf8osg(Sgng)ui z@W(2n?5PZrIE~4W8K|v}Zb6qK@)?73HrTw?&rUSZ<57WZQ%1Y@j4id9i&?q6U9rF% zva(`hO|kN}HFC6-^_Zu;_nF+RG4vb(+HQsh+%WsXtH7eypV8u8mt)cguzo>;6;EXV z$E-~O*SP-awunM>D78fb1q*uHy_8CdFq0#jR3Dwj9!|!f!rN8WaRQ-x2@0ax0ly`u zz8{BR;|RtpUjNDT>}6S%%iu~f&3Bj_6Z)ofb=bK&je~oxq@!cz`JQ3Jz2Cz+JK$_r zogaQ>YGb49hdDohZrrzv&z;GIpK(ZeA*U10{5C8??u3i;Ej*kBxnWNV4bylW`A9F7 zBZcqdX{`Hd!*Tk=kR+?-HQlt9ppm%UZUdP>e-r~g)9Td@=Ap;!2`!u_MT2#4a&~M_ zBB|!*=4%pdzPHevDQJ$vWGH92*SLcBkiwza64>E*^v$QUlQh4bhwg2;v6 z#C_77Ly=yYwq__dQ_skbz(A&&JfVFZZXcsO9b!MoF$LDMbAE`GUcFp_sz_cwVA~0> z(RY=Uv1kpOriJwlJ?}~;+ljg~^zK-9x*Xw{BAf!T4la9iH4GS=RRCR&+z6FYYh*fV zeSsl_eLLbpzb|{*sHH<~Puu#k)Fq^v32bVlM}$3J-x(^i2Pi6hE7jO{+Gqt-US@;L zAuvUZAm`2uK#i?X|8i4IJ!pLu zTQWD8$zdByM@M2s=mZrB*EB(Akl2^x=1{y)3vMStHfK3`m1VEv0Q?PPjg5&Z3c4l` zIOh`*6d-N!8KwQ|Bfv(e*TSU~M18(K!AW}e*EmWL@eY$?O3v0)MR3J&PHdYlh$2{# zeK{J;?mM+6Ze$`(eHs71xl|~RI&)ilKS$7UB2J+AZ`MKhSZ#h2Hkkp>_4$I3gcYCh zV7jhd2Oa>?q^T^56pb&q1U-%x4utA5Xe}^trhB+X-0#UssVQ!hWWNxL?lS4P&i9;# zXeyeW+=)zLBIglbrO6C@&V| zF3(Lzg+NiQSD!r&JODn1+ZdxxkW6*_okUS+d&cC=}Lt(8*zN?>zU#j9(gwW@Nic*iY_~Q%D480 zanx#mM$g|9A%sTi70@ZvL4HPRy%&u(+k-Y%yv0mImNDrZsm&8}uDYJ!mRmkZNx z<%)Pox<_7rFSb79ObUqZjn8Bsfpie9dW=yiJNgE4j5Tt!hl{y$% zyk26DCOrxx3U<@Ua_E;_L|viKD*?!M?+HQ9zaVmu`eXDFG1p;{`q3&MarQ%Y21dmZ)aJFXJRY^t4ShxC!7x0ZftnyvOUb} z3ah4D6n=9>p2Nf!3TdQ6Mz@^?(Up)_ZAaDkw0!Sr1Q{QCP9~ z&WiLEpWx2*dzy+&w=gzx(c>ttRL-+mbAL8oz9Z}gD8;;Cir;~*7-&wHA0cIa?yuvtJS|FAgiF=;7lgL4_Lj?FB7VW7u=|)b&KUv_xL? zD#^msbDGDc14(qo!XV!qJf(CPj#0`t{tQ51fWYWKE)ayxy0}#n8%V4K@hMvXE}N=4 z>aJ->su(P(6_)n_0t_GIQr9Y!ro= zU3iPsqKHWew8@c@rBgR6%%*mD{!RjCU*>F)vr9lpqrcNi_7%`#o# zPcRN$1RId)Ip-U@@;#}l&pVws6=Kock>4iB9NwN!U4&7eO2J3HqP-m_-eKSMO{KKO z`lyFK9$UwOe3eZei@x@HS#oo?bO`i%)lfc1kTM?g7=iKc<7-=aQ0UP3(GPgb@+3)N410 z%uEd`|J*BemRt#VG4fgVGgrg&fVrv_3*#SXj$$T_R*6v+g2f)KaN{l2 z*R}ivP2y&MZ4*w0StNlSqtSzO@JF1#;N2&;Nw*84i0YQuReXvUy!W`cy~^Tg6Z^4h z8!TLV*MVxt1rfpu4=nYRCi|FOQ^S=*n2eUqtNy5|TmMbgRKSL1-y=uzxfdUKZ{$YG zz3pUI?JP@#6_UfuOvm`@04-5RZG@5UBUs@$am>wqEf<^%E4(pqJI0hb`=i(_Qw8~z zYJxZsQ1OMhYw9&tl2fUQ_ebv>t=9)q7hCqaI(;ZJHkQaB)Q5t=SjqWndteUng%(aM z5FF9$ngT&WgN>y%$5v9kH7X`$tk~}H81o2=ZM5ayVYUQ$n^>QYi#9uN&z{}I{E8}f z*Y@-8QnbC(I}@Il6P$h`0FULf(#jp&`tn{VT09(`?i^8Zx6)%HI_mxUURR{$OE1(O zhUz254>z#rZ9E?YG=);lBADwsg-3_vx@+ba?22eV<#=tD*e0^guc6D z7PPCfD1kub^%|;}rxSFCYE!VvBt<}l>RY!eXG6$mykD_Ps70ahB=VVziA;V_y@3(v zHdTFsCHygR871Wu(T4{eoN`d7NoGW7Y`53}_m0IIPd_jndpmsh}TunSh*QV!pk;BUaVW$VG~*OatnWO)W~T6{pff#VDW_f2oY$ zkQP`Q?y(||U%6BIRyzP^O}%KkUjqAfm_i`onp=#)a@tHO!P zfM`tW9liM!KcJxW}RrT(!~o}81l+=Fx1xInR0X@Ybunw5uEz+el)D!!eY8M2AEwQ&F3T2SF~>xJ}(^ z#3RuD&=>EsoX1n-G=tHmOm-9J(nY`-CkTW_q{SS(RNj%pzru%D4^?fqwf&KS_T*q7 z`2HQ4!0}ewa|T~mu7pKNSbk#TX#GwGiube$isbs%e651?AW8LV7>7TdeeQgIz(M|@ zxf)BflOD^zYSE#tobhQzt~jXj2U{3znQb@dc&Ohhmrt7PyFWid0y>4(X>vUpf$HDZ~EKG1uPC!+^F1|i3@!NGA<8`(2%)+UoXR}#LN z#Ifx1p?dc^dD8 zg>g{GEu{t)vkuHyCwmO(f}(0KXxwCT}&$)#>vvlp0%%HnrKHu}<@( z$^@Q!HYIQ@VO=3y`MWMS1Gc!H-~wTRrj_#;1(Zl&Gp$kW&slbeJed2US>Husc$8J$ zXfyQ7ye>9J50X?NRCM|z;XB(IVhWdKdF>3LB5_HSBmSA)6aejY8Lj7gy|O9g&z4lQ zqqI54sUanPQbR}N;HfrHbxf(o^MgeidNKnZp1AHGE8lHvnD|Bym;E}Zn6 zn71}Iw;F>5oC=uW+hM$KqVV7Y!8WBXveaCt=8LzHLmh?4>GXjKao=`)hbi8w?Wmd% zMhME%SGLHqljr1gf<~We6aTQDlj(n|9__PXIj#aK-GKq>RBcj)^7Lzl*z!!M{hDiF zJc?~Y8=gj^KqH3zH|?OOg34rrXg`T2Z*g(l0cer%%Gu1x=M%0?M~fp$EuMJ<#bW`F-|`zyI^F zvowy@K&2tGsDxzGncPT_v_Fs!ZB~&q$~V*q6_k%Q<<&EJFIm48VecV*XKi1TsY_<# ze`p*h5R3C!dfsnK#nldrjsF{;KnJSmJH7DhlNDu=4G{@klREFmxu2Bb z@eWzDHfztZ&I9?@$SKyN!XWa4u^OD$pm#Ge4X#z$QE!7V37*<4OGMg2BQ>Vu?Co`; zDwT5kAm+va(&the5$@wxtQm41~DNmQdkSvB!3g4ssJA)Jn`LmoZe{6uH9BFhjQ z-Asnnsy~DQp5S2H{Jqj;50_oDRG+H#cgHN*UtJ?zX@-%E1{=GLloDUtt2W1?83E!? zi3~csNx9eLwHrqcr&OUPqFFn00t(7m4gNr+iNM!&L;&*k4dhi8{|gU?Tc5lSF$2oUE+>`j1y5(3*oTJBar0ut+`VzK6y9eJm@2R< zAmSO8=MblIGER3#_O`s~0L5#3>KQXI{jcD%gJMLulEP!YMAs5C^asX}wz-y=S5`s` zvRl$j)*wtzV-&>uR4|aHHIUslLr$Id^tsPi^aaP?Tzq&L4es3a^UxLZ?KiW4jLwF)aRcr8>Hx4ND1;>Tsfoei?7eSr+u z&Jm*yi5l``F3~g5&U8(MiRf^!$epg1j~JI?9Zj2@UUKwYkhuv|%eS)}oG%1>nclVB z!yCEU%IO21{f%1{dT3Pb-YIn8Sn13R^=ZV;hGuQm0r<=sum5b5ZdT1l1->cnNv5m| z^IN1vUK`Ce3FxIBW5QCIbg_>pH(1n3+;2Q2&CEh6uj{sT*Ahng7{7(}Ej%a*hdaZ$uF{po0@AH{bO59R-@)KS3ENp^F_{eE8 zz_O|GduxSbeM|io%1%WEm(w)`@p@?@>CMz)SG*Zh20g{w%K+Z6@SVVCW{2_c|S-;>W|JZ_D66!@)_oIF7F?t_SVh z?G%r-E>>XnB!z{)a%JuJ5;y=WK-9mheusGk%!9v(WR{!ts_)?UR?5&nIT8YX4YeCI zna7{|anP2bjni}!kjq^`I=#A*!mnJ%fz#Np_<5UOc~M@9McXKMH^~23vIW&&&jSnE z@26PUEzW%z3j%~v5_;6cLncIlitzbf%gMafR^hWbWjO+smx@Fx`Mp(7ETwRQvGc$R zwCW2??9@1PsSWa|HWKnDSy>S;2*fDXSBOkkZdbrXu(bx)SwT;ujGye#a69Qr<{8|k zNwb;k41XgPI$KAK=@3aunl2?UNW5iN=r^&j54QHMCj3Nz(B1^udSJ_84Cl&0WCrW4 zNFhQj0DX5Z76%jPBY}?Ct&!Q3Hd(dSbBaxw`kv(EQYDm=!dJ3BWLz#|JEs>IA!2{4 z6eKIORTC3JyNC6i%}TBn!RU9RKb6-;&oe9F!j3-zT0qBk2I5yE~oqHV(98Al=anK~FTgvTn zuA}37@vIkdu=!U&{X!jL=ezgOtSKE35}^~>a^tG{%f(AGSl@4rHQh<&qqdHPP>GF8 zUdxCj%mJ!Yl>_CYK!9`KxsX%LdBeG@rGF{>#GTyf%B)#iAgDyI8zbf?`W7iZITD$o z5ha!Z)}UTu8HkqVV)yrt?>5d?cse~Vm@yVVe&pLvn)$G}6%98;0;$6Adc`@ua_l|ui;Uu)Y$z5}@&q4v{rgqb73{$h9l#s9 z99SiG#UzJ)K`h^0x6hE9q2+qjzKJOsiFz)?a50+m6wo*seBHEpj2hOv0BC1KB zE@A4V=Tpxbf&0Imoqh{hj*{$RD`ezo!?=Qn? zgBTK#dxANGQ(0M zr<2>m;|~`g4R#Ei+C^FkB=0+WIkCYiyn(l+sdT)YntS9|W%Kdp#=8<(fzlG}b?*K9 z6)JKC_|%WY_Xi_3*Dxd@O(#Egvs%Z#`Ko7y57XlBGv;vSn9ATznhIo$9!{5={{%6l zneM*XnuV|>AoH_U;i9>4h~pH(3Tb#04dd|BhJk_HT4yVt^gwQ0o$(%6ud^=Q?r6PXzkc0#{+rLRk9Y3e5go6X$PTt^TV zSn9{qP^S-dp&GWy=@^ephm-9oAn=*enHoPf2hT2efxz$N4ekGTk+ zuCq|&CN>^T%%&LSwjf{1Yho`$%)Hjr6=I53RE52RN~ein=-YIw_L>~HGCLm435Co| zXObt?J3j|r*94*h#t>jimz%&!N{d+B%gce*j`!wNP(r$VS<&PF7fRIAlm~n;}$%e zC1-!6Qo%PWr*1(SySj0*>(G|@D7nx(93vH7M_Z`<*pBxdg)v2SIqdByiMVGR0}>*F+vb_c=JO6 zVdqXZh%pC(Io|7}v6i}7-uJznP8vA`QM5B85Cn4=`O z5@p)jUeUE*;|q#DD)5m{-lajKt<`yL`Pk~dvM6oQpz8{NN(LG$X=r7ZoWlT%Q_290 zMmH+J7H+NQzQrrpvw~)0mUJx@%9A#;))WccuMA>Q;Cp;Z^(REkOHE>e1`9l6Ly92k z`%3?w5QXh^+~qsIPvdnE-k4U1sa2X=XMYvebO1NCmP1?ip`h8kK6;O~jULR!)(Kz~ ze5q?LNu0YwTqff$>k_l7YW9fPE{WuBnv9o!@`3YJadA?D60%7EBd*kA{j>=6tbOzs zs|5bw$W?oz^5Y&-HXmN}L6z-OY5QR!N*o>M2J9%@aBrh*oCwMi*DP?6{(@atCKd`W zZdJ`9U1OwTM3Z$SIG+1(i{GJ8_M7W7gg$&LwPu=L51=2dMR75vVES&pJR+Dr#hMM$>Lba-v&gYn>vr-B#jftELkUw!@gZ3TbL6 zed&r(V3>D~zVf~@&s^BC@}K=(dO9-*m^IE9BLwTieZSHis<%yM4*shRElQE@EDXgV zYt%ouU^v}k$T0Ku`)piW1>D-IG}RJKE8zbnB&$)+8y%xGf;BrGTn;u40$Ymk+%TZ%=goGS*=lfTS^yb-+>ojz{Qm6d* z1v&TZ8JxN`)cNMzdkAMqbUBP(!kgkxk=w9Ak9s99!vE=CG$)qul0}7@-B|aW_=x&? zjV7?Gsk!=usG4zFr(%l7#h^>TQYzZZ887dqyh1|h=>rdT>_SS~=;dwiDyB584c{X{ zcp{)OL^uQsHpAtS(5ggkhQupiM9Ek55pKgFlVpW-tquaSkr0Exi%KQmz85d;#^nbB z#Ks|b3b7U5Od=G$5*LeyW0WyNSrYZ93+TBD7?WU8d`I^yL`?@a!llCzX_Bg}KA%s; zgwlI>oR(-AEhnf1Z8TDz33~%K_9CtMY2+&=SjkA?dH^MAXHJ)IVy21U92^XX@$mEHmdXmvqLg=x2wBNFG5&GM1YEVbC zn-ae2E)R@ML5i>nl zBMyR^ETq;f1k0s&stsGG9Ps78H=mL#T%|KUPsk z7Nh^)A!802Wz&{xV;=e^MuG($ZOR3DAql8wstcT-h2;jPrBf{STm--EwLZh0(gYED z#*&7NQB!p|go32vt*PdrEtyBrw0Ob*P*ik^!S&T6lzQa^kUHJn(O*e1%W@`dvz8dR zoENVwV4&1RXB3%|z>!2<{guulR$fX;uFs(o7#hORvjIJLN zprTFiimpLwNsSx3L z5#Y9_K8dy;bkPlRN?npw-n{i_FRy*6)6AA{Q|Tw_f0OU+JLw`|@|B&``lWs=S~#vc zL`rZ_Z*X7GPec{BfYlUtPKc7Oie@dX!&UFy3o)mayG}&V6|_Y%=s98K*LNWBTs7RJ zsP`lYj)JWU7@&n_%3;O)$xcs%#2RSdK#=Nyi&~YJuJs2!Rg8$;iqv0QDBO^pN6T?H zKm8wU$e$9|ygpRnd*V9|`zJq?1F?qdry#-xM!61CdYSEWXetz7{1aA|ahjWa9i7|= z3Ahq@R(uAbT-&hq>JEml}bMK1?`H z2Z1ci&B7Yu1X_o(DI=|z*iFN9-ONZT>LBK>C6#M#9rGA8jZQOeq5xp8azZwTsBViC zOVpPG46O^uvu}2R^ZXvKg}e{}ePWS^P59c#;Q0E>yx!g2jLwaz80L{^w>zc2v;VLD zFNwV`1x5J6hf2JLywL*Cwrpn9Puqj1GX=W6(`$Q95#`4`5Bl#+VAXro?8yVo|qbbvS)z^+RxJQ`{6YU|D8>gS%WKhfhl%)d?MZf7U zIvlYA&xm`oKyH+6wDV-wOKbLu5QS?9BHuT_!aIK8#nv_TH3fc$yJ&Dd>#VM?EAz5uu+36+ywxk^bk~8n@T&pE>y;} zx}9b0>}hM>%r@nRoaR{p2NYE#6$`ci|Mb!;K0aiiyY}_hNi}`cNly=rBs?gxx2>l`b$? z8AP>CBgQgt{$n!N@9MD14;IAPs7O8iz8ObO5yoLywb6z@?xsBtCx5;PkTWTA=~s1U zkEMl2Q_k2~)W&*w%a$|~4eugaHY!)6Mcl&o+^vm}@=F;a2+T2NFi%P3u^W#{Q@_Gx z7{emb&%FF^7fZ{`c57boJyw1U0>YgaTq~7_h*!ZET64k}ExB54zwEV^FeIn75Yv=j z-8>;o(HF)1!If?kgvg3f%@iaL$M`?a4;}l}C$-2L}MkNry`;>ni$%vxh z&@6K}Y`eD6;tLXLp1lS355djkZ)q9eVj2`gc2uX{Aw&%<*&45b^Xg$rRISwDS0MqB z7T2Mdg-Ogk>5>PF9Be?d=`KjdcGTd#W4|I*-!Vz=+BDNvkay|??-PD)E0gLA!i58Z z{xFNw1~lkIIBZ4LL2-89akKaMV1k?TLxk+k`#T~O(w{EuM#g2 zFNasF+*c04Ih)|)9WwKavpW$u@hbG;N@_R&JDi4P+cjMM+6RK>IFNFQ@BE4cH3kWz zVA=2?E;o|I{~r$g(bA#HNNMyO@pX1Qu?LHDp|&-iqv8BvtvZY^!g=Gw+a?DBx1?LH}k{43w)I9FQ zsy@EY;r~hoBMhqVx4{G3>4z5j**KgSpUo5;8qo7f#s{lDYL3K2`ZID<-pB6@=Qhu= z*Ky-3Zp_;Eem@SG0^zFTNZ081i^KIP8alqmd1*>9=jG$E?2qMlI{9x{R-D>$YDh3p zer-G>kRvCZREl_Ns|`qV^Me-pTc|T)o*Q2kyZ>p^1;| zRpw)mZmz1jE#9s2`*rGUDQ_xzrteP1T5i-~mg3jFs3^*y3|XY0;j$#mM|*^~$cLTK zOgtM>e<|C1G|>VSqLQr5kL{_;y!a2Xt0JT|VmDhUZ@BNTvL-gK01I*`9df`&D$o0Z zjAEE7e{{x9<0#X{_&l*!denC&Hfi_g&HCyRMa%HVke|!LIYJ`^d?-~=1T2(fqa!V% zFtA@LS~!Z7^fUObP$-M?#33L!Tb$KZhu5qA(@i+hPQJ_`Cc)r-?9NBUMFe$ZYDH(at!=I>GD5u^6Ru&y9KAR!LUKI~5O^HKB&m)SUdzq`SI;GQ!QLu;i|_&pB?-|9Lj!rkwcVC5AD13M zi7v+qZrM*ny?hf-RfX#tTijss;?P8YvT0mA9KDo1+Xg zW_(99#&d9!90gi{Lb>UTo2H7y?@Dcv;IVjoiUUPGogb!%Xx{+kvF*uRl|f(XH}_|q z`csoZGU@&*FCiI?gR)f4V2CtvX==QghE%DXYTt3_TJNj3p3@GI&B&t4hvze8 zwG8m3eqbZd{89uKWxqu}q9tmt%QR?8d^{z6~7aPv35Lf7fa) zM3j*V>>3<>>8e6hG};#8)x-rR6#9hY9CcJN*WwXN%llTdn%xu3IYs7U8>4MOW<$AJ z>nh<(;U>`J53Mr~wb8Q!r3)@_9_7@;~@_gbel-@ufj z{_+QC8h721kw`nt9LoR+FA!C^68QnYuVJ`j z^@FY~&}$&jl~Ig%HgknVmsD-I`mPbDId4yZ>h+G5~!dR7?FE#A{$<3XEL-xpDFXywL(??NOdAd zd{2_f<(rdOab91?A@3n}a^dVkD5-bH*Mx^IB>!TuJdOo6;{P=yq0AACk}BVy1ts4y zDrx^)N~IrIF9ijgKWNHfLK9)~Rv!hZYPJaAy&WXgU+gggU>`TWP^YG(4AD&dn<9Xp zaH4f>8$7FQI5uBD8}l-SdDVd-c09>hc?@j56~jLMBx)Zw?)v#Di+9`{E*SG)`T^&f zvtzN;sg_fXOc79NO@S?`ovK736F3tVoAqowkmz?)TkXy%SAHx+Dr@1cWoS6ew+So-uSmgy)04;^Aw$d=9(nw zn_jsWO60T}rC5@vJOsm>B4zVtHPC=mS4#1k?9^8R_IsItR_js&Wtugwjn2$1;`j(P zD%pbl=e_S-@nosE(e_dYBB}M2`2jC%0@bJRBHrG0t1FH-X9F4&^YeX=i_ z=*gt9-2n5EDYH+YLhK?pkzo}MV&aX2Nlx!`2C$a%MmQHS-|nx_r{`7%9CFW;6raGM za9onU(Fbk5jvIfUUbfom>_7g|>6}y!fPwwP*)IBbDQ@`Y+MLay_S=znQex0LtZ~bV zyN&4tKa=XpqjHUTI&8G;Icf;UU=ym!ot7=9jQPh91Dy{iS?3b3ZdY095u=}`FE~N* zpZAn{QI0FPrE<433+mbP^R1DadsSQj_Qu`we?B9YpDf((4n>x)c1W<3X^oI`@PegX zl2nzCK+|2?Wm9?Y(};5g$95L7nCt7LY-_hs4@aT|HN9m(!L5LM1vOgAao?{p3qYzv%J@g? zmIw9uY;vK=rb}uzhc`2dLuqEDbWb=e)$QkRzBfJ=p%4d;|(~HWJ$eK016!s-I5Mn zi9b4;G4xMTT7jJ6^tAiy6snMv7YwHtP-CVUQoU0Jf|$5NLuME!tq(1CpxxQs>`_S= z5@rxtW>Du>ldz1scR!7fQ|^>(Zbdr2ltIQgdGL<9bx2=H!KxI}*Te>8rN<%%0mV!d zc-P8WKcbeXR8dUwR#vRzTrTc3_?lwDoEmi8k#jU&o?(6ksn_8xrY-m_uNFH72jJpwZXzsBa1H{7H=FztXch_f zZ>UfyXWkpSKPX674|&6c=UnJCz%<|}=ejmUo6`e6@|5sApU}te%XE}!+^Y61ktMGZ zJPM|v#|(XC6!@9aODn6CWFrh8(pEp$~I7A@o+|V*CoL)0w&a88ln?FIv!qeZ7&-harUrzd7 zphYfEOZl4bo+4Hu6}*r$ZX<>AjtP1kSgnm2JPRa#7+Og~o8iv~YMme;J{zJp5It85 z$Y><7mV_#AwI2ahT`2O z5BP=@lCf5fnZd*+*`u$Tp~2BV8S08FErp25kwtN&2UH@ht9wU=w&Gaxsm>%oIilbl z>MUJFWm}4g5Y}l8Iwkd!r38=n1eYmcR|5@~CKxb~rS7e0g^-M|Du3uX1ZX2zE8IN~ z)VtxwKuq*66AwJCo|D#7S&{A7$;oqY#6}SQU4*d%_2tJ z3-v);)TxxQw!~4|w-xE-J*1jf8l5@`1WnP4tg1+kG6#t^8=>Z~DmfZKi)+m~Yv~8c zN3$KN4jf=Lr07>!=6u71L!t2LbZAF#)Z#k!peUqVy&tL_i1v0FY(I#^vrtmA1x%Nt zYb-tO7hK&TFx#@>GI^lLD+At1F{3)EJxW3b*fIgxp`MGaT(G$4xAMJd`)IVC$lvmY zY{|XMs3w~Dtz0W{p$i`^s68u2<$Bvak@^+4J)($00hxx(IQISeWOuw`zOfb0>X9)~ zH7avyrX%ftBO(*Z`|dVWC(WP|n+}wadrYxG<%(`@_ft{u#`V0gql+U3x=6O)3U ze3%G;p9+>SThrs7QL2jgvLt>F)=EicW6;K ziS%LM{3CCH3U$218oiT?CO3{EN>CB;yx{AJ8k#z-iIW*TCM_&gKH4>HSWAGIdi!a2 zS1Why3?>m7=Vhk*ibpvG@ln#)gnTgHxlvtELmOtX?>>^Zm*k^9#5^h|V=k(u^v+jn z6@*mIkrBx;2A5sHC9Q`rUtE;|6pswpWio3rNl}34^!4gHiT+f8mZ=oe)TFn`ln}v| zb3}}82rHjyw9qZOm7_K;gGgl)^J{Q6p0rFo@S+8T?#XlaCRPbhjt6!a0$}e#v9lrCF90p0O>LcPUUl zQT`&A$(h$6;PXjHgUlUO(w#LTHPf~1^Brs1LMIywafg|d?n%kesV`?L$HW|`V%$R0 z9Vz+R?y0YBlU#=prZT$jw^YK*^CUrkiutu!A;eZ?P}(UUXuffsKYqZId}bI+#OuOh zfeo*@V}TSmJi`c=5+^qIBB@Y{52}QrP38R0FB-K_$GdWik+7U8?;k+pNkm$MoaytR z5dU%B3KiDINbI4{E@zn>Ivs%kmvo>d<da>E-F@!-r#UPC3syLEcjFkKv7j9WnJ1wI{2baeqhy>jJTV$MfRRr%jfQA zI9n7(HQeB6`>#Z*oY@L-CSaxCIBLSe`cDQNjCqZy^@@a7V=#E{)!!E=V^#qW|J-Tg zsz~bfXu_BS8Yf=d91f;frM}bZHX<4G5$KV1!#Mc^t{FC#wl~4r-yJ1jciB2Jx#i=U zj+5bYZWoi&=Tm2_wVZln2rQ*E7_%P^gXxB2pt)mr@)+7$wQlEUsemlbiVCWCa+Q0& zveeQ=0S8^zTPeaw|K1J+=zP4zpZA5MYXROKxq~rKrF-t=p?Xq}^Ut$c5x-hb)%94C z2rR4+2-@ptG1gXfp)w8y(-+E?xS!jps8)IoMb^Wa6{O6~XbVT@khqrph6l*TLNX`S zvJy79XV4g4rTgD6vny^3kEF4!7C<*%?z$_2G7NnJ#W za(x7ILmWIB2Ra2pqwR|oy_JK&<+5@V^PB z4YcxXIS_n6_Z z_gnD;>s32ww~+;Sfi;~!44A~O<0m+KPBnzgTE$vx1Ru~quXS}w-ZFZ)R2BajZ|yNI zZ7MAa7P}uJstn4YCq}fukyRUMw|E@wJQv$+Dq6wG&Rpiy)({lu&J02V7JM*UOv`fi zJA4`}4fMMJh(+Nf){A?M!0~>IA<7G;pqvXe;5m9mWZoQx^s&7ap{c|t6~&DB;oLfR z;#T@Y3H)i^ad~VXOc@IPi-(x@AkjzjJ-xS4Gh^Xg&02pTERxHow)_4Zm6FFlankZ~ zl)=gu_5_xXgg?`V!m*b|+NWiLLo--lo;4K&fJ5b#Y-G+@%>>YIdTQza zW=|$&Bw-V|uQ^Fv0hhoBD}Lc)isq6?u=o7s^#xMCU;zzmm5@S`FNOnT*?`?-2kcl+Qf22;IeXZhoH9N)b9||@L=s_Qt#RbSdpR|* zCMg@7D>w;xm;GAl0!#ZTDsjCCah=|Hm$UC!9 zRYPgUu&KwRF(>vqBgRiZ-=Hb-j50_il_cQ65XnjQE4i1s2N$0Y?(0V+i_yMM6C>U! z7QVpx_=*PE1{qVb)SDXvIj5z~9nkkbxq_jKJY41p`66=Tk9J`{*B|E^Uql*R-VDzS zvDC0@YX&kZ6cJO=6mh!fbh~zZ7h@wM145bM_GsHb;iEPiSfLXjWGfRiOaE?U9sXh> zywhvb9MBK21^7w|ElRAyb>BJ29mv6d1#{GqR$D#HE+&|OZTWfsEeqUVJ z$*$0-wzq6P;NYyJx_{A)j)5r#7w>i@5E?geX?&t zTlht!r;1ipAbBedf-jQHpPPZ_So69g1eIT()otq@w=3Oe4lmP%PzQ`8pX>{o`E{t8 zkWu)REKQZIXv!6USAFOw3zU2OAZ)tX=!V z$H~nl8WU(Rx}S3vc%n5!FU%S$wsjzz>!+MV_S({1OwUvfh&R9=iDG@wr@fjl&&c3?={xVM zNANT@t|%ry_jRgW!%|HLeN3gG@!r1MlWZ?@CZfqjF+WGpnXJ2>+vWgq|9Gj2O+&Y- z($eLW`8#RocN47LHSz@fot_W}*QdRCnX*RE$2nPhIULrqqA1QhZJhR=tJs~>I`i4C zjr^||=V}HXPU~0wrwKz@;+R+ zNx4RNKHV`TZ#5z_Qb4#TdldOC-0Cb;@RZP0uSuj;hnk}wTAN6H3PR(!BdH=J$fg3% zG^cz}i+fWeHL8Ko?XZ}%9^{oy7Kb^qr&2U>5&c618y1(C(()XS2*BFD7K@C`Opdje zpT=_iJx_r~N8s47R50ydD${7(!Xw{_?@H!1x};Zha@NiL@5JcbG^$F1 zlyIgTlE$za3FBmNdVPK<)n`zC=<8H`*yOoH99x5LiY4VhPkd>s>!L*-tk3m6^ zQ2nflsA><{f8cvUAA$xE!xNjLv~68=no!j_X>*+%K(sD51=(IUtH>;BiD&ei zER%FxcHd*bORmpljl|u{gHnwqN#EPYF)JD-LQRY`+HFe$8{EabmHArd+ zK6Zr~2E`H3bdnakDJN_&%rmCz77!}&DhBJ4h^$e7@CH^QY(w2S%IV@24?`vOQmxvB zl0PYVn_p~yE@*H>agHPMnw(p2;hINO9};Z%vY-AMtStw{AoVYKL9Ij0)y^&D}7Q@D*KSoVhW zQA}*Pen&%DUmK!f_YkF&{SSRC$N->~X;mFI_84_mcnuEser9Agm#Q>2umEj3hraDW zxuBF0yDRaAFWGY1W_)gPe$2A0PvOj1Yi`RJ`)a=&-!FBlIm`Ztkqw=!Xse1<05e6= zD5gf{d=SYvX)@LUpG5!Hsd^Yjj!hn^oM3@agbBWWqC}@DK+h&WmY}tua;QtpPkgs` zMuuO&$N(AkK+twa%ZwYNoQmd0W@cC%0p9x1vbS{r=3WD-bwsjb?2NWp!))ajP^Z=7 zt|IK`Lq6OwICWKKCC~&`t&SR4RKMVuy9T@P9Y^~tlJ&4r1-+MPS(XeEf$AKUIQ7v; z@xG6@#{SG`2VkfTjrY5*l*-dsHBp}bBQc9Y%pSBW<=-Wr|AbNJ0mY^6X zBuqh8Fj1l7a@Ey*N%eJ}qyeZuQ}f}5D*THYrvN23u+xzi+?zgxR*A?v#YDA>a@nw~ zRqNBlFlS=7&bZy=bh-h+1J-l>fsOZB-rBH`{K^oF?S0!h za~wRECQ#l;9TPM?UdMC`i z%y9JN<2ycyJDXXoh?g=xkYM;E9bt#(WXp5-hB(T2BwiHciv3XQP-y7alb_)wic??I z!Ikd1o+RFpH>kmx6|?x$-4HE#6ms0gc!nYyoKyTZ=e?7|P#TbTm#CSvY+v6smLnZA zL~IefMI5a$6@2RrtGa%@>I$HSS%)Ags%Wl;Zt^clV&7Y*8`$b%tsMbrZ+tm@)_pnL z&KE#6nHsZ~Lc#+N>uD<=gV)6LB7nkPDd_gs=XlxGWgjC`d=gN{is@HMRlYdK1qtAP z(=A}8m-LkYh?5LTxqL8QD5OE&iorVovCe348PiO_C>J#)N9+_~x}L2{MlZcJWSgHO z>UE%zuZvs?(Szc=CV3`O(mhRx#NwDp;@HOq1@tKgAl&4`ILSs>bIxa-xa}aQoQNmm zgnktknL4CDQg&GS-bC}gETDG zQLpyaVeWRSxpKcUl|u}HhARQyldYBV(AWgtjs;M{y^i*7&={^H>2_WD2W1Boh3AHQ zq#oBvoP#sQ*K$4<)%{r~ae*3Quxj@GCZ>7Y0+ypcq^ z5+d5S@NyDUG=|}}()1a#Frz6ADoB8PxHNiGK|dR?u0~>m%cvzpF4w-CAu@Qu0XE_D z21K?ec>2yLX%$;x>p21$zDn+(^$@JeLGyh&zu)G`5msg65a`{^Wf1n~^VbjO}qGVkID-pjCc4{g0wVmovAg=+oLO3XdSp!E(dI}%>-nfM4KHRdY2NLgmFOkqY>zImOP z&G$=aIv(&)l(kZQ`%~xhvLby821%T%8v$LBXUA>sPIUPAgQxHZD=vPcpnw zbWL(i1Rz>IX4|l|CyB`k>k^q)bb}c=5j+OBt@`Q%;g_{iqde^oUAcK30GYptpsikh zT+q{aX2aIqFBghWO|xvg3sab|JoQvM4YQL7r>Vejjg(#5dmllIoWiCeMZvq-sSZgu zG~kU`J}w8=0dA39Nt_NFZ`^VAwxf6g2aS~9&k2(leITZ7aV*<$P92O@^Zl|ZNObqnvx?f~n{M(c zk6-DtDPXxx3qpO0VWe1&oFZMQ8$D9fk~6q4sJ~G|3yL~QsY=6OIL-U7S-5+xwee4} z*XV&t00cl07rZO#>|h;6*#OAZqw8)g7J)hNpZ4sA)mO!Z`CWJ`h~glBoVwmtC~k@-%8%Cru$tu!(F=zvt+&pDi+Yk#StZ)iHOkgp zKPQ&718w0kHLQ>}yd2l$2D9!6?cI?|rXYY9Y+OUp!M-*0S8S$O16bKBcLK=GR)d{l zKO~d=)5<`g=-*P$A=2e81|pn>P#Bp_R4Scjs9SvFPH?{+3Z0J8E}-PDGSGr!icKZDIppEk)=+QQMx)E^TNbWrftve#qBSXtxm0p|F zvIV;W+V{Ch&un@beJOQqV&--$=j4eDvHK1-f+H)_fo5saZPGPr@|$L%$k~qbBnlG> z{&NMvcDX>RKlQ&pV{ip@AM3fPfVIJ=FD*p5zByXpi1d4co3iOmj7FIR0KshC3`!K~ z1z`i_fw2}i+=U;$i})2|GDn!yv78ny9W^YYz3A^CVpwh0%{(e*T3LiGlrf$N?8N-J z;;ZlGHd0U&4V-c+dC79qIHF3K9~8u_HGl?`nT^(nZ*Ju(Qd`nlL^NY*1Px8jkW^=` zF(NZXaUAtEi4+#B-g`g?#i|=Gh@&6P;5bM zp_P{?u74F4>G{wBpI(%~{o?@Sw!Ztw`ba>wZt72`*BNQ+#6yUo@mE5-lc!<`>I6Dv zaHDqxMuPgvp{kU9H*3R_KYU;ZrLmiz>u0Ga&FIwWgt2-EIA)SYL~6A5^p_Oo&wI73 zy}+{(!mAbrSBRWty~`lCZT6762}ti)+Nrp)w}_}VrG-|r(RVo~mR~L%*GQ#R;eU9X zc0izHN&=2mru^8@b)DgI@sNBoougs?PQ+OWBBVkLoS+-_8#-(DqZ0U#Xqii_w^z-G zv^4JY6P0cg0{YHM=V^2C)9(zB*1qZVUsp6eMF9UJ04ydLXynwFSB}zoNR+YQgz(`@ z-O?7MrZkL%k>ITabq0xh6KX0oDp$`Z?5x*iGfJMplWBTcLZz(YT|#xL4or6O;dS<` zq_2i=t|)^RVk=yTEUNAS?5>x3vAKRI1-MpbGI`xOE!hbnT0o&XPyK1#^?X#)p7VBy zy{Hrx0Z%(aFCNl}tEn~;=w?(lR3|@!$Tt+$Wo4`dD}oa9r8<$#z)@>?7-N%u^@nE|Wmso4DiB&FjHl>a z@acqc7d-2F*s|5jv$&Lcan>uGR49J(#NYZXVi*`hI6i8*08zzl>(WDszHZl$Spc3< z=qkeNXIg^Ax64+?T62T~5fpG^?NM$qPr^>w9=({OlBX1=cvff{2&N?M;Hxh6BL1J2 z-0(757-i66oxQeekl&~*Dhib9woklJ_2T;kfzq$!5z)^YLf4=oD6~C0VQb5nh7QML zSz$grZuz}8+FX88TxHt&zrOi@_q|W||3820KTGnjZ;cd=k;H8PkYCY#QWFHnwi&wc za}x;i*uqUywDf7C%jPANcJk>80b*ox_=MX`EpZGat|NzQ@7RXhR6uQ-`fE4|1zI)) zKV~TiS*)87gm*RTtII#A(5i7knIVRK$Vk^(CO2(+Ep=^E*J!g9aeEUGSi)EHeRqQ* zF7E|=j)^I4bRi?-xMP4Xv-l$MVnEjxQ<(rQ_XRPxA2vhO^O%5uTZAmy$mdC1pc+bF z354}Xo8W{{>8|;&{`sgr3(sk});o7Yy&}hYs%~#PigLN&iOUg;)D)M{7S^GU1#Cl* zG3ceSCq^KeYC~`oTK^;uQN#HBns5EsR=0WY(3INIrWk&xj@Nu?@v$VlKhjRKz+{(_-e_B=kzF#{v;n4 zzuSv!Co4%xjb!>=(}{T42|EBcK*+!ShR)12@~Lk-1y)vBV6Hf0kAD5!43VN>QYjg8 z{kzN#${2dvL|x0y<%pCr@FuIII+el%mbl&HDmLzI-t2e{Lzk) zKcoCA(;j#%eXBW*-3e|;e?$7&GB*__A%ErdBh-vRF?KRx4qWLsbhq4}E0owi;Uh&5 z>xbP>ur|3p_gK+Dllzxr%ADh>3mRxxF?Tb2jqbewJ%v(q>5(y~5}IWRk?%lQyQOt9 z8n(Q?0fRsn9nV_;;?q%ft(AZ!U6smD;#8dwMD;x0i-K%AvN^G}PvJYVL7xdbz*CH_ zU>pGJ^GIhoDESsqbtR!U#}L1}DSN62c>#Uk6p{Co?!||qUnZkN7GRuqZq_*?K8dS> zlc3%ZLo5DD=hY#mAk180o<-^LtJmDl%`HfDqwC_<=MJq~;Np2bF`gs-wyHdF6`I?*nr1EN7T3E|Wig*?| z2FwrTqQYlXfH%JM>Zw7fCl!8g)>vVSPjf?X9L+{Bir@NcAY7f0C>~0>nYK5`ASy{4 zShJ=T|V&Yg3a94GQ*zmFDW`F-0~_N`A`m{s2(kv-w9)+o3%%Ld>5tBD%L zZ$yd216R-w;*=Kms3l-c13SLdnHnt}{Y>~|Re|VyPt!9}0%1&95!{&B43JHKD3QXV zw@05>MCDZ_{E{{7mGMohis0UC3QdV{pp6vDiCW&pv$S4PDTZB?Mx}h7I5n2N^%9DV z`RY-$wv(3$ti=Pmx0Q9kR`3(oMOY#!h@TsIN~xEvWME_>leyE1OuHr6YvOrz|8>(1 zJdz$}p{m|X6EB6FlEJ&#PFdqcI4vcjh)ta@&G&im+sJ79@2B~kmzcCll(2OL6 zO>f#X-5vALTe-Op!~*9oLQM^(PZrwYOZE6{IV?)WY|J;MkS#wad~=OloOv; zF@5UaI(?T8bSO&3t`_E)1f&a2#|g-tWMHb4@=aOzFEO)wANhiX7J1uylMNH}+;0|# zm$2uG7Q$@{d`Aw)5ic@7PGQpVHKsY{gce#hZ(YE>L@DeNOYm3G~i%di4Ffv5cZ zj4mur!hM|0u<7gYZ~{C?OE7WJK@;T!2H#&odG4G^jp6m|AZZ?xsAsu0bcBMO^Yk(>rY)2te{N@(J_hH&LblxVEVWv9eRDVXmjfw zq+@t0)G`2qJPd7dv#pWhs_xJ?mp zD%jrAG=fB_I&H6>6Q&N{B)#G1X)`skShLy;V{m=AfyIdIH{ypRS_C2@^)rz zQE1*$1^Rx{Bp#-Nuw*sJ0nXAg23Oas-YKHCV#&E-CXc~c+yNZQnhEW#DS3}cm>%lg z#vbVv`pZ+qxLT#WDr^EaG^#7XRY6)cyxyBE^OBgZ(Bt+W_r?%JX0cpG0_A+x&ha)d zChR9xr<~m2hOb&1@gJfdwh2%{=F-gYRqx&L7#m-LxT3496Hr8JjIME)07d?g&n1qP zmMGpD)cfLa(ZsJoCoG7a&GvR~xaSyBHIX*~{s;bRC2Y-Bo=bZG@9z1rrYTV zdcLX*n{r^mX=nvLJ$RC@I?~;kv+-D1-;{s;BF~>De1Uk1CS9LJrXWsl9x&dFGk5*e z>a(5svQpaPbS;F|$EbGkX~ZvOR2%9<8y~Qyc&Uk4+i-r20Zop0MitEp^p$l3Q5~Vp zEO%g`w$xv>yM>3sijadun~zxH#FJ^+W+VMa!Mti5`%Z;kL%0F}(%^+-eNe4Bv}K$c z^0=l(J6ICa;c*qjVCZlScAe;I$x>y^X6~oRHr%%~hwGfo9hojs@M!zWgu zT@MAneBfcW;$-ly9l`Lq;U01#s7?l8M&X2>W^d3FA~|aY=?2x2TTT?pzCF1o)}^%! z6;vcJm7U;FjB|t|LTX9$4OodPd=DClH@>tre(k>K^RTL-7%ht z5^R2h1I6P-w6M^qzYB-qSTTf5L2Xdb7yG8D&83}a<@V1q93%{ZL* zOxybf?B7OvQHuTklkchcEb<+0IdZytrl&qcQI zt4d0yIo8y@N$rURG$J@NLh?c)r&N5CG#3y_t~z|t&JsK_PCrxZZU zz@UJk($;1+@)ecT@ONK_1~OK@N#~ai4u$&}7fKslXl*#74JyOVIB%1_OCi7^?~ju& z2fh8S6@|w*=bS=qewHJY`K%?(Mh2%V{5gBWpWMviY(m!7SZrD#L)w)os?|cwjT~vV zEnMM}>ZLYBt*4XwxZ3Y` z8hs`{uqY+O5&^aAn)es#yut^5RK=i6Z+EXfNY&N3d=mr}3qVc6OJtba;#3Z!P*dAV zBSYK`fu@63*s2|!^P$rUY4Ak1@y?xpl&+!+XZ@5C0A<~Cis;5uXkaV0{2c)+$%s-A zOLoPvjgP{iojh5aID(Xw=dx0>+tGy}H4iv;6(eBa`+jL!0xH^*vMP9D^~=2s=Hn8L z_>s(Z=**gicXNuN1SzIWM=*yEh0Rq;QQxOh@9LQF+#i|Bin-WX&7R0~zV`Ack9))s=(c>77wOAr zQ~mI1i2?3^d6dktU5Qm^)^s7bvjM`c-h4HPyUt^&ALNGEf}Ltr4USF zwiOe2S=Nb63jz08vX@uBUmA-uH$R6~o;Ply(quyD&Pv$CO{5B5mbO~b%Te5q5N;BR zv3~I>(N=A9gz|h?C(>Mt!>+vkHYt}wiY|ECfWuW<#|IQYC@E9bkJLv6e~5T%En?90 zA89u>W5P+6Ysm*a|6q8wlCShau9kulS=AvUniof$)P{J6N$2ZJ^t6dqy*oX00~){1 zaU_FR@-&cd6Q9FDI*0@5+ZiXLu{!&d=%NV;)Dc}4x7(^SEE=nmbPiR0xxMCKMYdK9*cyEnjV3IKUwds(cn60mybv7pd zyMd!}+mK6b13MIUgknh|l z-S)nDwDQe!D51N8ez${eOP4#Ld1-*?O0<7UI@P%|C(W0&WOG|7VH#r{J0C4y@bzH7 z4os{EDX*Nzh(J9R;lqP``1SFi5Jq@uPiQQ_zqvUxS&5W(Hbr!pmSEb6HhkY%Uz<&A z4<((if=qPqBsWS(WpishOc-qFtckYV)L_%9R|)h=DJQYSac+86`wFO!8U?(^(W&q_ z^+2mIL8@sX_bq?nJD?e@I*(M}9l09b4$S(4kMRhRzQM`Rgx2tHCUV~eLYcX|q0i}3 z;p-eM^K-EYRoE)H8C9uFAy(|6Lt~H#hI2bA5Y4kKbm!m~0+%=i?*UiNAr;`PKb;}G z_Nm_?8KEueeEPRZZxjobn0?#+Xq{WL>Vr8A?w!Yz?FhHWxJ`nq4!K*l!BRQ%%b{II zbaTfyyhtu#yh)9Hmwv^p1lqaU?Q=Rk;s$bxvbwl3>hB)45aZi+G`mUSfkgmrM}VqN zmiy+)4*{~L{Y)A}aj;#l2Ci)voYmnp*6=y3(I%FaYD629^3~?TZS-4=Lt81SE5|bR zT9rCrkBh|^GsV_MA*UWsCx!t)tmEm4M%471oyrOX1h>6Y_0kQyuwIO4A?j0(kl)-7 z*QKH3VhzFMW$%;afRjLS-n$V%h`D1e0cBBIlrIX6+1qB4AfD$=fC5^ttZAv!*_PHU zcjjl*2T`jMtMORSgxH%`76{qLLQ&q_2=OA_iDuxYB^@~#mC=U&x<|tAK#K7s@=rGO z4!q7H@SgZv>#Xq1=z|Aw{1cK#J$}&X0QD1X0xIuLU&4X1r zc1jP2I_`?HeWNv)ar_dGWL0H*tu*dIRCIrGbRA;|e>U*3L5v24c zM#mnk1(!G@lV#L=jt73SCnwUeK{N`Zcq7SFL~C0UC06u}_A~v{;fe8KS zQzgw+Bl4Bi@FCVHWP7$|j`3O()En(j08kFgxnYnLoBckQDqi_KT0>OOCbx1Rj+lWe z$U1$8^>T1vF&nA+epN9{X)?1BK=+9#ENlsiZ2p~9vVu!6Xx%(^FxBf!)KMdqib6$u zj=7e+Er5f3yPZfNu7)AD7Ct2sinz~oBFa9vAf<-pCv@Fh{{~V zSd;cH@u!Uf9F`SUIB`15AEN`6l5wf{?qnc`?DlN@&lQY?PRKwBl&Q^67o6a=xi*dj zCRlxoD%SF4>R>k>@^!gAv@~)5?oa7$BEB01mwTw&vbKhX%Sjv%1hT0tOCRpx+n)}( zx}!d|HLa>Lh_TfCYFB(GRVv#}gVh$4-Kxx1*GFV7U{WpIzR8^|_lGiTVE0}S6`PX% z+RoIEVQ0NbOuTHH)2)Nb3+#9et zrZFvkRoRMV!<16)cpJ21Yp=b$J`9GwUM7yws$Gz%J>oOi3juZb3^JL@R9Wasx8wVjXk%33t-fMFpeUaN`bee8rSPIcrZ~N{)lO%S z`$P9QW6>J~^n8Jyx<#Ki^8$X4xS zn$AR0-Wij6i$kCDfU3a4~sM+OzLTD-)p6vZFDC+bl*~ zmCe^z@q-E6)2`4h-_X7H_v;w`%EMyiZAKU zsBsrlenu$P%9nRrke+qJ2wXr@55z8K7gA*Z%C@LR#2tPATOnnyYXvFIX5@liGD2*W zZa@xk3MS0Qv;eLx$PrliZKPU%(CNLH9`~k7>u&jb-I7{}W?e&EvaV;t=5RiUV#cSC z&}OIY#vIU)?L(!V$3x!sY0CmI2TVJ0Whr>~sB+zud^&4%UIm7D4o9uF6Bo$o&UeeC zu;*Xp{kftzAo@$LA=^2095gF(iUQ&9l9&h^pFpNMbAa8(`rsztl^vg+v|mq_@p;`S zNsix&n6=?l+#RO{#uykE1DANcMr&m;lv1GBsR>%-^gF~O=S(U!;mS{(6|YbGS1I;<#pctcr+ZxAe^q#F(Xeq7@a?x zV86e?UUCjomvQ3mxf&|C^@v1J_}+{h|!5*CFkj(Ng#g)CG}G9@GB_2R5a9jXEO z>*aOAN(hlV2 zW}>D-gTR|~RKtzU*i~7MRBWYClJ@a@wt95<;JGxkW7w5$VpN1ul-O|(#s^!MAh9ah z*_erxG1M1~as4?uZ>>G77#+d@pxTCJ_u1bz#GvvF`);f%?O0csFJ0`i3b(&*WB$>) zz0J#VIgB{RN>kp7RSK2sVx~|D8mQR*I*>_$$+tE6;<%X4t6Ma~6dbE>`~0uesP|RK z$&)+N62V~jsT*=jBp^Pq7$)Nt04tbK6=KRffzmFux{VIGM&b^RbAh$8qif&Wl8@3D zzl6wIbQ2?!#G@T!u~oN?-HKO&oHNc`u6Aq{73Jrjg0`jQk+3D4Wk?-1N~~G3y)~yF zO>O-5lb=ZJOyNQNi%oC)d$!EN;+Jp0Zh6TtT5+NDmRb&MiR*Z>I zZ`7B@p@LTM*Q9**RD>2RW9KA5w0p-^%$17OnxQia65Z*NXj4?B8o-0IACswz+M?h~ zw;cfAna(Xo8c6S%<{eK^#{gb9#pH-}ySzMJY3X%`-8<)1M!5tE+JCTsV%P|Xq7!#a zJEb-%{sIf=#jX=tdKt_rc{2__L-xPexd8$m*}m6*tGR)ki1z$@xw zL{@$}Yqz10JhL{h5l@Kwk zjb`Plb9hJ9A?08>w4ff#EHVXCLJ{@C#i-E}+u?Xk-v@ZJ2#&`oLnR_YQj8PHCD5r% z7g%LFp%M-($1oIh<3=JZ?N-!h-Us~~AhF}j1E$w8NFSy*;aT|6elpJ8a;_tlF}uk^ zSvfvjGfp+Al1B2OI2XM!Azd1S!P?xjF4@cdU>>K1ATWX^bXQ@rlV8te)#6X8uX8(? znblxy;;^-Q5pqx#Bf>Tc^2;?e)#3(FL`in2&j@3$0mXCI0BuLCl0!|^hXv6hXff00 zw$yaP&u#0HaOSFOixzi<3-HRHNp7DX7RuqTTTv5)W+(Gb@U(eguWnWcbnR4PsY5Mu z$5c3xZ4$098p|q53|lWD0uW86?ac1qzD{De0UP0k>uxPsE16^tD^R%Fh*3=u$1A8% z<5nOdohO416jyqPS6^bSkIF}HT0a;vKsX?Aqq^2{m5mb&%RGY=0qmrxciu}#ycy!UgH!@(G$tZ>$PB{^Q<@IxpDE8U9 zK%wFnlWvjy$T*ZwH1|`k_33or1FQGd62@&N>o8(-8IMBvYtyUmkOhfdQUWLcS{Vc6 zv~Vm9j5!JkoGUDbu-xxTe6+d#k1geP`~0Qv2SZT z`gn-4;?9}V$`EX;*z^?C0Pbsf8s=7XR4K10JOQi-^|{Rp_!2IoD845Vgk|n^>DTv8 z=OB|Srzlws3Iv18lZ+x1vwq)m#=ob{WcPJ4M6*q*qBn8ra zCmn5aUplfoNyFR;Fv6G{kkDpqo_u2law@znsFgs{vak*x%W!KP3%!-nu1-e`jTWB+ zr8dLzPP!A#D9Er8jnXP@?&x9oU+y`qm+akd3!=ui&?)Jy(2<6g`esURRs7?u^iN=#a|y-2~ZUoHWj3uS#kTlq&H7 zs7dQXTiQApjWk|VjaGkFuekd2RIa1QYSwoH?El8-9-x&rv+#AerYQ~WQ7rYM66C#q z4xheT*HJrHC&$sqNfarPFakpf^PRBeiUllEgOrQA`gYt_RM!UO^W{-{8iPhnUk;;P zUrFQ2zdcD+0RwftfYE_Y;6l~l-PZEo*;{HCMBwe@Qwt+{@gulDlIV88ZsOu3Nt09d zb?c%4bEQWRf_5EX`rA>}T!(q*^b^LH6IFam-GyQ0W(Axrl{}FLiGI!r(x)>B29tT> zb0zP{e8V_LC?fyVhx;>9LZ9hTc(et4<^VQ(&xL!3BH??2CT|S|fcLW+5LX&LhyA&6 zv8#v~PQ?3!Vv-;(e$#==F%3Bo=`ATXQW71E?$_%W8+AG1GSVP!OscW2pZ<2U1*QsM zjJm?}zGhMZxz_8!u-vajSNk@|E1G=z+(s5qnIt2L*89XjnsI6FMHFwnxJ2!R<%PG? zpZoMLzfYo_Js~#jHY{I>nqr-E z#5Xi211r!+W*Oo|@Bb2s3?{Ovg6V|lh3RidHpUibpA6~JU`;A<`lU}lMdUA`V8c?V3 zd6&+R(`#Rb7+g$L$N*_4DivLE_iL3+D}c8vOzu3sX6^yY$f#}h`G0TsxugLcysGXn zK(wVw3O*}f3Taz&Rell<@#DBoof>8?<~|e52ESDvD?3lQF+5h#I_0r;6`jC*V>eh} z#J|17XISuJioTT6Ty4{|N{tckW0wTPU?(3i0CIFX3>;mWr`EQa1R@k;(> z_!;;ogGh`|oBsMVLEHJXG;NGPsenC>?@=_F zqiifub!uKfda>=9^~Fgn4uZ7UN zzsJ%5E5P1I!3h)rWkZJ8jq%cGZb$mNMwO0QA^vkqhJS=SHVbKZvF@uvNSXarelDT5 zy1^rp`J~|~MX(SG!aw>PMMj97mTRB!3Zf|2iHC^|Q1*SUw=c`5xnepeSF2&hH~{>O z=9!B(>Ay*yfm3afXkq!_Hvye%pcmXxH{iP6hWkBA`L z@D>G|;j7!-nm;bE#F)Gz+1uzRsLORCq%BJ*ivUjRf2nczWQSj7?W3{aYCa~E7b9Evv7ETU~(ZvmOhy6k8-q-7j8cy^GQJCb8QP^Do5 zbL98T^1vl;e zc5}G69&quk9Hj1#m?Ql20ZFXhXN@L#BZ?ta9O*I)LvrWt`8{7-W4Pfy;~BBH7l>f} zhG2#rC}9pWk!chM`Tswd6BiO_`|G%maA)ZB6`k3f7+fZ+14fe9CE>+SINz{>Mvwny)+BlWcciMR?CuB^r_~K9E3zT9tO-Q8A>k15w(j z!A64-8S6M_iZwFnBM!4CQvwE(y<)r#=;%1&5l-Z3cFi3p#%CpGkL&=WAaBa1PwI?- zre?QO3ZlA^`j;VxpGTsv(hE$YmDxAo>zM*7*OVZBlvoIG_ers-mZxTYq1%P2R~k?H&WbC4tzA5b&I|7;#zChT6_R}a>P(0 zxk$1j?<1utZS=-o^-HC*=gksbQ2prRWsXXH)1xJQsyX)lgL|4n>P>n==yKxhr#i+(UX@aYc^l z&O-Lil2FSt$kG`(;1_93t<32(*^NYb|8VZTY@2rbnoP!wDLQ#O2cHk6`k#&&W{rxD zC)i@&79ZLtl|VZ72W>g`!_pS9&w9_@7e>77ey#s2*+OF^0!{orm^>;mgIO)A4RN%V zu^{-1Bq7XASo_dFPluttCqdtI^5m45c(qQE8!ctA;P01VuapurylJi{bev_NL?(if zeWCiC{xdxom2{UvN{72z9bUF#k-gLtbL8zvc%mC(2$b zS~0(Se#P2?uhW1agi_O5LeA?=0H?!F^!OZ{iS?EOZX&^pFO#In^}i?a_)kR#b2J7U-@{s$klAyic1F@sm53d;9^_asAcMXqsWg9lfRlhWeS7q^oCH*f=DaHe0J2FVoSPi%KJ?P_ zthwB?j&PVcs3CG<*Z_r4K5Fh9u!W^_9wkqN1D9qK(eh{UH0s^MA?A*jPNNhRmzLsf zw4)fKk8k2GaJRm6T2xQ#=eI!r930X>Zr{RjXAb=#TnCXOOyPBFOTbfcGn}?3; zn?NbY2{wPb=}IBBZnf#`3Vm!Yatyz-seN9<&T|5+I?t9e8iLGyfXz24T0uIQ@?QV! zdearZyTL*VgGgnH3IvGyqs`b2&4$9$&2E_0+E`%B$gOeBcx1Y(Pm8A7gLXCu7E+5w z%?V*v1EeGAgk&U&`9^_;GHR@jwd;LQXv+VI{?p~N0_%85PU>Qg&cL+(DON1x&fro) z46vI}z6t{(WYo{LaBQ8+Sf=jqgJ@8Gn8?VNlKng~;6X@!1SP`>ECWQ&Un z=MKJvQH$dJqFSIX#=nR_ZosTn+=ka7a;QZ*LWx`rN|9HL ztGZ&ZK!&bFX%1CTRB?yg)}K6cIM`wIP`~rShv!m0U_Y^WU`zj?!r?6;_w#@$mQSJh z?ktwwPbJyl2+1Gb`)|DMX#C22JCuAUuCbHw8HIV;#R}Yo)g)eX7dvp!V~2vqgcVt5 z+F~brI?Cj(Rf3m1LwilkFcqQOah+<>F@9eIPT7DIUu6aX)l)a&AuHdfQmXI1(L1^# zl_O$#Ih(M2Yc3unQ3;NYMGf=)^4Fgzs*bRaNs*1a8B(yzaM6(%s8GCDryTK7^uCA@ zRR*$uxu;^g2~w6F8ZUZ$!Vi9OoG-;)@;Yn1P90Xc0O3pn8BHC~n$O9`NYJQ4>nWl606g3X9U_cb+Ix6e{HQ#OLO> zDuU5;H_%li)`s`vjKcGzydpWWhuUY7k6D{nR$tdB0eqa(_xf&FbU*m_sg)GXYbKinh+YzjgVzO-M~v7n`VXnP0i%j=Hhe!=;C#Bp`L?h9gJy}(^T zwcdAaqxoP_Y^~#)#t&xr8l9~LUpf&S23sTN@nBaT?5%*IPfJaxKSo&C`Lu_b`<`nr zIiaB2q8V+VMN*8*qdcslMM2O8kuXXiQSNWwsUv__ZY0!95xQSp2CDLn9h`tFC=t3M z_4}^p9e$L}K>0KoTx^@!tWT!PXlhASmbd-gl}df9v57pOCpSf~#ER4=8o*Nkv&osx z9ZW#W)U{!RBRxL=6dDl{`0Yu%Ihw?1A@qG{KBZXCijFs zE!wEt@)E>)h&{RFE(jq^hrrU?%TJ11Ys+>&0sFz42A0hL(B>P@tN z8n1Fkjn^jl7vX^=Hcs?AF%C#XR#L8{EZYtgnh%ZAZ^a5qA;~6Xagm`L#hALb_5gE6 zP$v#MSiizB-_`}RaO*h!v^iz!aJozdxKp}-q0`}g9YwIZP5{xXBrD?`-?{wOBgf+c zR+yB*TlGCzc#nX>7NbU5)#n8bT#Kyft`!g5!U3@Un6CabO0k1z+d@$jCygSAq^rw; zUs<#J)e#}&i4xX=uP}YBEm`+;AO+v+jOy38p}0C6>j@JbVARghUP*PL6guJZ>aFaO zZ)6UVMJI4^VCwxjd&FLNxavlQdJBju>~DK+>gQwrhKuNqx2eOuNEeQ<&kn@eAt~!{ zcxW@pMWFlpBb=ajHazV&$0P5e&W{zWEE@O?b0c&irbqtOj41QFfJHVR_kx7i!J;Ru ze_c7=cB!ebnErlcRTJnFf)R5F+l?BDuq6m4mXV3NItF&$BZ2%t=n}+Zbq<2;{A>u()%odHOzD$=6P2Z>gt=i!~vWpX1b- zWNmqcG79D9PgPuEC8Ojl_kMln_}V1?=#;lO4d2S-d?6Z;Xz(6)PoGrN&#q4sLeRlM-63iuPU zK;th{1!+bFT~?y#7WPxm6)U_{c4+HFNU~IAQV={KBRhR(GwG*?IldK5 zcGH)*;heZWFQjp;fW7m8Er|GG2W4dX7_ zp;u251=&@(e<_)lE0-Ip?rC-u{9FN29P3WsmmSC3h)}fXXE>3PW*zEOyoF^`!Pro;g^YfLP&C21Re2Doi>gH8yrMH%Wui>5T z#n2&E1img#lWog2Yr@-4@$(9MM*+L=_Af3eBn3z@gbQ%URbx`s#{mPPm`M@mpl?*( za}tru6(lGQr#HQ7^_CJ1%_D$x@gkm$P76t|ZewCse9|lvM6#P6KmWOSNUgA(Qz0Yf zjcoC7P)f>rY9!!|OloC+-w7Jys1t8uT^gXdw&~T^ z-A9k!c6Hvr3@UdcD)2uQN=4zvT}dc~mngEUv^i(1mBmrjK|nOEJt%k)J3Cn1@{GSb zNd9zf(vBdSQKhjWb*D)^UnywX#frVE46>O6Rrpc2&^?X)dUt1>288fV;Eqaz+raZ! zsn|Sb$x082cFH0`J8+cy0J`9=;xJMVHkGn4;C7GGBrri;y|bigXg?7@R#f*| z$G+at7E@0<#PTeFv^@5xM3k6lPA^AxbMc4mr%qGJ6<=X}bK<^oEtRPd7NMHkvkrUb z_J(yXy-(|g?agb$pyTW#+LXeoLY&ly zXt%7Vv-wrInS4zLc8JT6?n48ko5@lL0Mg$ja>mrJ>b#RHy}%+AIE?zm(70PsNs~{k)YYyx!!QlrMQGR zbQW0v|JmgpLEJ5*h@J@8sC&TG~Rx~##JY;R~8)N!5bTc{Q2Gzvr zy$u~*i#MG91MD>_?a>cAf7Drv9Bd5X1_7-#^ljyJbFFRpJp_?D!J+OK3!DS+J@fH{ z{V|MmXbQxdSb~jMGx)fp2J~mvg}RFOK_@*P(3ORrckkr7@V{@b-$N6 zeX*MMIg!|wFk3i_cCa|lrtC$5mTu}b0UYTwfj2|3iSS$BomZZa=Hm_k5v#?Nkhp|S zZYa%Uj-J>(LYi0ZaFclN>!H!;a)|*hliQbz4B1uuN^`a{BZ_(eJ4A0^!>o!gOz`nK zz@61if{zW~U@S#ESVfTG!~CG56kTuVTvoX$pN>+>0;qbf$I?E;Eq;up~m)>!Xhn8$p{W=6k#(ONf$LSw`xcQ{EkNwqtjRi_5bb*~Nsl!`s z)?EY$@ZeEtfgZ`4phXc4HIn)fT*-P*K2DjysUNNRMx9Hu#kj6BCFo+s_Oy20nyj_c z!$V}%Fy7o7(Lh%3y)0#E@)5|L-RtnlWmPS#$lEPuHNk~&pv_l&!%)E@JVg{fhefOP z77X-ttgM4a(3RmAYoQTJ*U#1Uf};`eBT8BpCY0E(CeILAwyBm%wE!7)Pg1teau~sy zsBf_4q&iao=pOqa)cqt#)<})^{za9ikkTC&JzRSbDn@Yg4zuue&PFUGg+yylEdP}xKM z*;gLR4J#g9M-YGqKAZRio5{#^b{$or;=Sbu?4k@j+he;3#hpw(lmG<4I7Wvce}xg} z$RHt&zUZDd77AUdTo_U-wM_-;Fv;dd(9iAY<^iuihzxc!Q6W1K*wAO4UBdK5y?Mjh zL*ZFX5Fw6P-MqV0XPx34g`-4J9|oyhC966;hD|n0oI{F{baEAQ;Z=Gc%J?81@5=YU z0Ft%w2Rvn{;Whr;4d80OB-MJwGs_+GUMXm!YzeC;5XpMu%tOAOQgj{mC!AlK78qNv zSn9hg3VXg#38&S&&kXHi-}xBJlU{8UfK9W0cc!e?Jy)>upLzRy^u@~a6IJceWt(Z^ zX#76I`KXGSK<&nO+0=ruVqQ^XVxT0weM*f2H~9wuB4w@dePuG1ScIN6UTw?GZjP^$ zxuu%otWbOR=R4Hx1RYDPN#ob9hYrS+CShEMJ&i`91rW^n?pQXa5=yFw zCL+773=x{&{rT+HX5@XE ze|7{qo^her+Oz2lP8#fwTjJdYd%#T0+0SeU%QJdEw2-ynGS8b9qJy(J@_x}2NKu<}KklQ#Q`H1(E4^gPiL@IMw zSlP&xJ=z!<_eF0BTs$2el~TO1u|A-j)4y0BYHH$u6AyIomVQoPc<=QS1|Yos8o7vE z{M36h^Kf;0=YQ8ZIF2ZATXisZJg}dis=Y$9CODUd1avD1Rs>;*%$!7K(rkdO$KT6e zsHtl(W;u6bp4=uV4;SOy+AzyTq2kN&u-qmOfdl|6K-9mfJT`wN&zd)(A~8W0+J>m* z8GgQlImM6~q}L)V5;tG3PR=zMh@#16W~e_msg#8q)G;90Il{t;!HRYdAnQr~75a?- z3Ykb2xbfh-&fRw8u24qXq3DPuiW2F}8Hwg?Yq;299^`$lKL`;#XVBZ%=hyq;9oFo7 z*B7$*y&c9}p!HZ|^DmYD4RM}tpz6rWnSYK+e~-P_F(;DW#xx1>t-Jj_^>-X9(EH~< zZ}yv(Q3v!AF2^*uQLNn>by>df@p;SxMwO!4a)VbIHtMJAOs1y;eH=vLf&HRBMXBdl z&FNNaMeGd95wFK88@$gBI!KOh*S$gkVx!xzqI)dB${Aj2gO{|f5@X(q<}F6QpfIwI zSaw5rED)$@fAnSY+~Hu7;Gn`z8I%;c&u`UnNi)P$6i3!wJl|fY(`7io*23p@r&^*m zGZrTl2lbptn`pzz*3vewPq*_9L8?4}w(ZAfQo6qSvgw6^FNO@7t??XZSqLUN+mXqX zJ&Cnig^&&&!W%S)2s#i*-)h&c3tZ~+m6FG~NJ6eQdbia6NY!;#pf7m0aaO}rSnE= z)oMkS#{wygjzZ9WzA@M1nos%fD%8`3WoZ~BCmMrwGPp4a;4d<;l~?r zxtm>Ex)#+e?9&+e3p?1;U+a>;2N_UBQ?F(wA9rz-@_k&)LYR3e(94+1%Nosr&rElx z&aB9^52Ol=_#-u`;4*$(53>>T)0I=U>340u0UH;PJeief>*jj;5}1(17?tQL8rkY< z5sy5>R|eldmYpQ0CXa1%O1x-Z_bPQqx2~A(DcSj2a>a1*46l(LtgE&k71(m`M?p~y zt4ik>l~Z33lQ1S&H`?V;BzLnDRx$keie*LExIF^I-taNUpD@=0qefcofiz*sC5Jd+ z+F)$KO;h;eF`J4L(>)G7j(=aonw*JPC?aWmj!(HC6RYA+2t;(59GK0IT);q>0ZQZ_ zZ+K-xlE6Jm<}}76@+3?PS1wt%?1bz_$Nc_L+{j{W)2_0bKZ|GeHjAC0604iKif5%f{Z>;8K*8z zh1PFEaBSCW7NZz=SJBq`DqBxRphXAik(AN<36{c4cqZU&2}zS0-W%~lGGiRyZK|E2 zPFn2>;RXYtyJn)bL(1D?ZLb-FophpinD`#r#JW#=ZW8WeY>;ej?)qUo*=9UH^0 z;niGD&~av*K#Ne$7~JhO1>8d>QaTv2?5>Ye z*pC?;8s~z`x;dUklB)ObgsZ{e8#bISBbnl$Q0|jn(T-*1AEH!Rq|zdwoObJUij%)d zF;*i~DU&*%R^jss6W!Cs_S8u%&~ExOc0ah!Y>YIr*!U7FzoR);P8-BlFj@L3vlrYzx6ZrLwt2<%Ydrp1s1%3RU;*hjtivV5b%(br zF!4J0t6*<0TWg>NSWo>}$F2|Mpy1VvQ%~IBFD)uNV?cLdK}VJy+|2&MrOXA zydEX3)Nk|y$u=vQc6oK@APxvuLzF<5YrMP;;yFb#8K5Z0o7t)v#z%I(s$FW5x5T5k zw#aZXn!i54E6aqL8&ZxcWjzy3R5xe2B{0Be+x4p zH3}}K7E0^JOy!zKC0R%u6a)gx8kno?(vUFYyJE94m+=_Bazec=eNGZ}HuroghbmJ7 zgJqa|7zr+hzH)F9*d`3fbIMoQYgZ|$2zpEIwi0+AK_Z5_m$|x(dC;MITfQcYTRkN; zoSPPY8zNh)3x}M^QxwP}TEnMSeXqQq@i-norg_zpi)bRR59>$G`u2TN0dzfK^tdzC zJmUqTYuq&9o+w2JpQeu$m>gYQL;(Uz-3Jh5NJORjRc6uh(>xQfoAnRW=A~e29$Jgd z69g(4_jiz-Ok<$s*RY}S7aoKuvIz$+z?ZJ^QiPt(Xc#Rn9GkKiJE zh!Q@zSr*m`t+XKX2Z2Ac=2j-AST_fZCNG4;)(Ta^r4hC$Huj2Jg|;48P#<&M)IZ0( zbxXkX(z-x6NGv)DgznKP0fw19O#P_>fJ$EuLSZmG>A=8!;^TQ%xXw{ddAv-{eE$VE~vgK+5u|%f6kqGp^=Tty8sP>GprlUA>O9k#PK2Dy$kP|m6x%=<*Y}l zInrSTP;6k`%b?jj_mmff)mJ9f2HIvanJjpg-8!19oR0>AjL=||oDqPOWP)F45}^|W zR>u8veOCZz@gYAa;Y&ASQ+2GIZG+02TQKsq<8bpgTOVH2`7x|ztl za%+r(J{+|w20LyL!Z;VJ( z(z^q3F&fy5O3AvevAmL!pr~a2+!aGcYXb7X2ueHvQKa*&E|kbZJ#?%2j^?w`^+}x^ z7Xq%@^k-BPm(#E0n3vVLC7+Ow8Cm`-K`ve~PU&(@B}VB9%;@V4cY@f; zAi1s8SI<;5&mG3`yiCO@<*>M-?}DpXTg_}QY&oY?uybZt>bJ*v6$PC$_iCCpxpW%+ zy%6afRea>;+`YC@#pz#BnTq;KEwC}J-~-9a((+pxC7yBBvikK1CxaC;|FN&Lma#=- zrL^Atezpr$T=uDzF6-92&BQ289ns@8h(gzJ$N-Xuf+j0n{uO_=J2CM3^92=UqS3VG zw;~7s!J@{JZj^K`wI0G=8Ly_Ems%l8B&lrd7;aP$4&x~kH+A*F9Jc&Z6yA}qA@rZX zsfanQ1mdm*A0YKz z-Q=d5ud#qc@X?SiaCLfY3P%{Ys6B%rx%f{|KTX|pLs9l4iqfjsD4I#axWBLdw0d3r z;Qnu1HeBx-M&CIL$q;q_($gXXqqv}JmxClA;9_X}fT>5BRa|kM(UXP}UtB|nH z@f>>GTOyJY9p-a)vsvDF{v*Zg0z}yfqMQs48Ak_#w8|;!b>;GQR1e2Rqm(e_u5YS> zR~BW654@6^oawuK0Ca^9HI0U;PRx6ngoat*VO4h5(mN)GMprprJcr|NMAt2JHBF!^ zcjb3=59{aW*sRW@+U-rlNgACy#G+T21F)~}EILF~roo;VA=ze~xyjV!0IBHs#Xe9Hb9EAayw7Z%*6JhyXJ`b`h9&SV$SNWw8l?~};f`Gx z)Y2o;7-AuOzS_v@eNR_FFCH`>OOVP0 zJp-F=W1X`-i%_o=H2E1YGS!|s%U+j>=mC}R{pS7m2Z*5v*@wP@?*=LJ$oteInxc+xr=U@x$=^S@KSYYHy>w`Uou zt!WqMcMVbKyrs-|HCJ`D1|Y%J?XydGfEnv~ooJ8Dm)NR$9M`6mAsQ)|#=aojGAIkO z)AX2=FciBa9glT;&rV+tMIytb2kc>p$~;FQ5_I_JkV^S6 zG><&m5uaf@OpYfSiPB5kSrrf8xe1M~nVA68w>{u)q^xJ=om+Nfydo2lxxOufy?UIf1k{z#SJ-7_oZGPG)b#5X3Oes#i$p28_A6fFwBq5~73hS63|3|n*<Vw{sf0eyL)N9Yc zzr(~PlCriXb941l&5J>^78;V@*%8rdAdOW_<;S2rM5EO4ZDQMCS=pfXSPN9$yTtZ} z_b5PhG0j>7L9qIio`aaSAGo+kL=8v(Y-2nn%YFqqvS;lsUpcpvQx&`*85>*9hOY8@ zeC7lu5{NnkWh|^1f5`T}b;PkOHmY-mY_(z)EMd`1%CrtHdj0q8ii;Ng!l7_vMS4*7 z1B!^E!)~~;oJ1hj381#@>Q-oFQk(KmN1~u^h4*Pa0JiJqOE;Eu7Lg|*jXrkcj6Gv3VR#*J3*cxeE@DCosw`W(37vV;PGlV;*e-Q~!<5zrC25SSvldz*O2Kj= zGamy5@PZA8R(|DOPbsps$dp2e=f;hS(G2G<%O^ivVdvIZuM;&*YM>7}@i4lLw(>M0l=HmW3+$4?HP>g;7kth}TcLCl zsc&9!j#@N2gG8RuC_1TLS16+(tc|Y}G5!e9)ppIK`?SVfMCe}a_mDOMs~k6cJT{%N zE&*w&%-_0>lB|H56`0L%4~LWE`Ux!SS;q_!5w~{4>;2K}f>Jo#>3R7gd-c2s)jG!WFrjvpua@mYkhH^EtjujfLX3 zoVp+8?q@QZ?XR010LQ2kEvl$|hh7;d1qbUhz6D}Hp^l|dhTAX^k3VbJDc36t>52bx z4eyULMe=726$#(|2DbA!8F{-k^ccVI@Mt8pu;gpXGAStD#1}#{^1@ggQn?^QPnEwS z$z2@}>QSoa-N*4*MOL*wG(2Ove=k=mNh?RPTopC2K8?U8tICq`4ztHfQA zAHb%#Dt|RYYkXsuXMg3mCu-DFL=S!KXQfnIF@r_i+WgnKky}0esZREmPW7Tu-`p%<<*f(`P0Uyh#2# zV*U%=H^-d)>UOh`Qisql+lUu#sCqK1D6LX8l=d^>xxswX+h7TB@ zsiW{XuOv?5jF?~Uf#O_c%09LMT--bM-FR9`p#)WZmCqJTVR(~cgA0h*J|*NIiS6Lj z`2as!QNgARmy52iS((lAl%dAs5{_ zO;(rLDZKG7gLkH=4XVubDGv`~`_nbwu{1c|^LHven3*Qffj3$RRC8$l%HXg9@Y&b9V`fCtR1ej~D=vxVdZBVSMkf zNaihgCew)EzO8(M9m>W!S)lgQfCt|Bb;yR{v+qjdBxmwn+Y-6^-S(eibn**6k-Iv_ zI~Ao0xKE&Vww6TDk25J)Z#7)l`Z?F*TR+zBK{*mrKUVoiFpL*=DmxuHj-UZ(mXNFB ze36px=*B~p-e^+=;6XS)){eF?S=GGd6n%T-S} z5XMX~dSCaEK<6s@t~5(a{cTOyavR$-9v1MH(XKO{&cUUhiwM0kZT+n~d!z2hG$9ej z)|hl*(+n&e7I0UfKZCSshyB}wH`8#`*@9K_-LQU5{i%sjA5U{qwxEDD$B5Ard?>;v z?5UMuDaed(t!ItPGFK;_&)h)faiD;Mf88!+v;(~OSC+B;L5En-44sS_M1@jcq(9P3 zs|uz%5{_5Qn=oszf)zGG9yEu&Qk^*L6s~RAvzM{!n*-?jcn$R=7ac|ET#2j``+!%! zQj?y&DFWpQSG`>{bthJI`~Ex!NpIjIPW1D&;o#P_`IThb>MHfVH5;o_rI%4y`u?-Ti?k3IyX`4<#`aEKdY`T*o2wKY zaFz8WmC1Kmb6_tNHY`^*1r6zh3G7Hj=BwyMh@lC)ewm7Hs*>W8ND+}(wK&_)hZO-- z(ZH(CZK?3oo;MZ&s#SVbWO|86w=zU>7o3ZE@rK9nx@InN8~IuEAfl)LHr^2y#RLXE4QaJ=vCnx1P_V0~3Mdd9x^mtEHifJ7J)-!tzYo z)pVWJAysNbG^qBablwWNw+9u2idZeH;=xlz+9nNk0wm!=SreX@(v$l)uI(D>o1xKs z%AO|E4o{riaAEgMG(#%IUi{cOiep=;w$D(ri4a;|jRBrirs^c``LlM?mIdf-?4h~Q z_X7?6wF6|E?kzh@Q=@m}6XE~u@|Jbci@jbwZuc|!YMU}9_1WGRauvVxL+j+XKPDK= zkB^{gn1p~fyM0Kk0Zo~umQluVz!4^0iAAHAz@9i51m z5Sn+FJew_$FQpwS)zPFF;|fBFkA7@{wytQ5A|(Att<#+dy!`J~pMy82Jx!w4%>A-` z~~%;U(*qLvHbHV3OjT*& z?~e|;2u{+KM1{8>4vKF`GNWug1KwWURk5-Q&@p*ymk;mI-{iJbxu)W!N zVUG}e3ipkaBHbp7r_61qhuh(1g0%`XQ{az-i1av)j}-?ENsXV_OGVDW(-=Xq+LXgv z8zaTb$`A9aH1w>#{say~SW_!{D{*zNS|E8y!rI>J*|x$+c5epY!-BdSEvXz#`P}4; zlResGYI1)Bs%R)4eN=+<;j+siG~un>I)j4tO~#(zk*iKlmGpb1jD{c%5S+;^$9348 z7;DEf;%@0IHuerlW#BMza5-Nlw77{5?TCOLHB&d~VTwKW-p+Rzoxx`#K7(n-Ba6Cm zCc_;ie0yELr}`0Smf_dF`{NTEOfnXl2x_qT84)VpS`>s&fFITaM(wAV&g}>0JZoE58?QpnBWWpF=G2pcggwr=Hs0! z{U~N3nq>s(K;pV7WMH8FjPkGMdJo=CMLfj6iVCpoev?3+vU0qolv5%f|KR%5T4+)> zch;m88arih5AG=kt$WQ`Y%X!G%+FAuDvHnpIKM$dNVKe1Z5*q!$3I4_0f6$hT&Bg^TURACj#tijUvW#&+@8$ON&oQT*|#to&k z)=c1@0qPHd|4<~wh@KoieF>sgpF6gj*S=#9pAwJQNkZ`2-EX?Rm5xU>>;Uh7q3m8p z`L1c_%J!FrrWwTGz4xR4DRzKa{`xKZxntzD2J@Y^Rw{i<+L;Gyy;3{2I8G>MeH_~` zYk*j%ppU|PFIlk`i+y-9i-s}S2;MzqX#CZ3zCKC$YGzYuI0*5QwU|ICZic8##i5K> z_OGH>7N-z4L1Yo>C(+0FYF2|5Y91Snw2qi$U*z(RfanR{f|Nm*oLGAJ@r0E{Z4C8G zf!4on7s|AKm!2Yjr$XPO@Ea{dLX?g-j`#G(YShh~pTs1>l~?q}nWUEcQaQ`!W(}?2 z;rCXG0Nz?_&36P#i6%Hf@s9|lVxb*dP)mJN_Eb=e<>@*KX$ylIG9xd<#VcsjwKGrE zVpuSt=hfR|TGPpO)~Sus1$iY#|H{qf?3UH_9WCzvWS{x~QUnxsP~u{Rq7T#WMMTlI zK0|wmYI9oXaIL}V$~=obK<`a;E6annie7{BLIrLL&{bFh4w{cPjO?W=O6OZW3%kID zU*R}r2a^r{`2DzyxZOR0U@q4aW&vxf(lchdnpfs>M!Qsq3%*(YJRecj^{I5U6~(Pk zirUZ$~UY$dtKenX#0afIkXO4B+crxdZ4G}o^>SXt3wKxwEq?IW@Lah zfd^T{=Wei%HYPN-?;LEuDjzq=ERnD@da?K`?!pl(HtU?x3ooRO=&{aJECvL)F|>$x z3>(TexzjY>wk@02e|Tln7trlY8wtR8ZEn#z=jLaxNC{)N!L#2|;-wvl(+LYZ~JgMz(z-A{2UT zj~oj;-qe1TgtCd|ZeAMQBGejDYEcW5?_s0!4R5uPXjO-*84k)p2yQ`Z7-I5{`MN>Pt=;@Pf8@8{BGer5j(05fvjc%}OGn;1jty)gjpwllKg-*qRQ)`R5e- z5njYfc&H!L;w9%)sh>?rFw|aIU4ch76uK_;Y8xxzDuu1g>tV^B6pVaDC7Svll3AV6UX_asymC5iS54j^sq!Ty0I#O%}*;4JR{K}5jvJ~ZE zoMG~NjwF8aXL}|?6$X@Cw6)_OLowtiz*-{=mczwrDJ{abfMPnY5!_lg_IgpG?zejC zEDOc35xuszEb`CXOv+T1?t9YWgWvzRtAvqh^|eet;DVFBEO$<|&TwRpf_+bBmlTyl z^gcw#hY{xwnuwVJ`BYFu4-B}_hG7n~rrVE~uneWOZO098i zoROl;UrF@E>GvCwpMx=x;N>INN0IC$)e)ct2|SZybBy#CBut8jXY$GzU!XGQe#_~) z?w0EPm=g0Wuj|qMoe_~;NkRCMSd`bW9Ox>&T5E-%qsK;W4W0~fl^HCW`1#K9vsZKa z@@UA5Kv&U1Pr%JtKIuK*_WzNqnW)k*BKfHwu6RepG^e?*m198VH_KV^ zj?!6t_p&Xb9_3CvICVu32k`h#77#{V=Y`00MrJ`0@RNefRs9deSKQjk6O>0?wCBrN zj!sc;RP$=PXiKh73Qrw|@UU9)E$VG+H7+4pzNFdD&0~WWvX_w+A_Xr}DJ57d!-$f} zclKF1iqNJ`M22}2ql(!HREL*q{Y9m8)N*AGLhMbb}44oMpxd+JGWFtD#VVv zUug~p&zQDo^)=N!hvN2}yW<3%#>8uKWK#m>bv zH=g8(&cyKmwRmJ}d+gEW+B8~v&z8orI85ubiH0v{+Qe+j;Ag~RD|cuQi)FdF<7Ymg zHqZyVo>oA%ej>K=6Kxu7T78QHR61-*QA51G*y!MztB3DZwLup!J+&m*9a&+W7|#ws z8_*u30s6UtmKVE^b3MN_l$6pw;`t%;xM`90t16)Ftr_0B|9UDo1XW8FT zTNkZvXhKE#)Q~>ZqpR(z&iu(cG*t&iyr)@|cCj26=DS*rF)=POyP}vY^Cuk16FbIw zY*z$aD+fF2XY0u)IYt@XIB64|i1x_Q>ujP9*P^wl+^wTw*-^}pRFEnASNQ@YTBWGf z$I~_tdZm5b21iK<_+G6ExkKM`R6ZFxM|NqMK{dqFUe<@uF$2*&OSL({>`NxTdzP3! zAQ-G9O#PL<=4hqkzPp&wAO0zB7vVAf;DBRi{DQO%T$ZZx>VrAf*mh{2otaMgG(WWf zvA^+M{ZNrJLU8LtsJ|qlwF5=cLBn&1h&RrjjKY}zVs?@tjn}&-(PZ0u+B(W42DPMN z9m!S9h7{BZ7wF!b!bA)5SV4`;oqB_qaYwe~zw z4_lu0F_{*CL#~IMDzfX3_K^L5a|LK!e=6qvUYgxop%o(4)qkciHOzy5aI7iEpWXrH zALj>T9z68L)o&?R2{!a``o?PUa7$5>@*(Tr$E;yyIv4g6Il=A7#?l=Vm!}$I%BlCh zX89sZ-IF44>^VC2%!Q}DO4Ok;eQWN#f{BzJPXj`;4JjE1F+A|mDeL1*(0OI7)Iu_{ zm7d^CJ6vlP=&oz2XmQ%4!zE6pcGuzIL{*g#ufN)(9_Xl!u(`hCq>In}C=ZTbgUF<%8hZlvo#eu`|4 zmhu4j6_R!Iy}TGxri_+YYb}xE6^#zn@iCm>tkuKMS+n;TQWIzZs_Y_D>$Dk}obWVnl|yJ7Ufk9(u--s2bT)*Vh;H0~HGqa}i2`UnST9oOL74Gcp4(YPeBZ3koS+QG z=WQ$zP@=7|h_@{oB(6`k$({tC%z=8Sj_d;voVR8|E>lV;I$E+Ok!fpPc{YQ=3lmwq z-DEzhglnuOKy(%GykB>So5HVkKlOEw6h_q7rotfCCRbHj3N0_+x5If3$&bU}ab-=X zgGWf-z|6VKeK3Bs*)kt0!Z;7h2-4l0H4;#^&TFkg2m%WB3sozObS&DLcNxE}z`On+7!H;?&D#me>0H;i5CKrfIvc$44J7-! zkPA=G<)QhhN@USy-!$7ySLU>8Nmg&pFsO=k4H|C~Dt_1c8%v9h9V;Lt>rmQ=6}3Kn z)2oy6^4c1xhN=&w9=QUNPmI6*z+?=^%3c?q_?gNgVj-=rw=cn|d~_UHX?{F1Je=0s zbewKxaMJlLeH>cxZ==@Pv2yLuCt;-G$KV_jeW46aU(hk07R2D6xe$iURs! zy=8=CoiEs&uSUKmB0V3w7uHjFXXe+m|^Bvf^avYf2L9fQa)Ih>o^oCYgREK;nxPI z9aiSpK8zwnXymBwiuj>-ws3#$I8`umB6*;e(r^R;dde z{}rVO{!Y5a*H{QTsO#?uOqQa&)A4*CjZ7&iYA=L{=3El5sI(;vo1Yei8tC*R;kA=; zlm7G;hGU2qk!VvRmvpzo9u;_aYR2u{XY&m}c$0g( zT7Y9V;4n~U1h(Z|x!2!2O7DthE?ZoVe%<;BOg*k)3YpVoA;1~+v3ZGi#|H;_>D_R> zO=L=ur0e!iCEjQ}Ofm)O*5r!uv1o0~6jPDgj5blJ=* zl?ZSSgDTT7f9@XUv4{wra8akfpg*gxb5JqZM;XvCKIyjn+mnuHH2`YwHyu9fb^5C7t;gqr4=qNfB2 zQL-t|oPyTk^Dfk-=Go$DQq{NfC6#sP0RyfTUd=okwsNL?6aW+C?OHC`3w7xti`;e! zRq1&7Xs6mge|iK^p$dkNIBrG`CT4)%v^i3%Qjd}NDb+!+lbtTv>Ac3@nsaWW4Sv>( zN9PoYDS!ygxwPmPYaQ%MRjcGuaAQV+ZtEA{0I_X)G*qLvSp!$36_bp4^PQ?WPM!+H zL`I~`8j5sB*stHp2$j^YL7;hH{Lb*f+kQ^hEh@pZ*z(?xI~(KH}Skvf97|HObq`1da>kBrHsUr=JW~C&`=@OXT8nB+Ir zk}}1tm6EXApm;Q%p^9aUS30+GgS%7OjmP4A-6!PyxMplZML*GvHcD^4gV1|;?;HM> zsrmgUbs83o2fF&gO(YV3RHg%x&bbi7><+JUti{*xbXI7W?2wbFQksyFK?JJKFAkqR zh@dLZumVh{vKy^z%JV1wH^P2R1<91iuw0_VvZ&vKvZ)YbgT=BH3(zV&01{RrW}gIc z!b2@nnWf-SDpkGU4k$?8Ks}=?^k)GQQw9I;k^Pk@b&Nx_p5oE17_4V)m3D00z0FSA z70*ZAk9SO6%3Im>+EPW|clv>Lk@6}Pi7oI3<&eep39>P{w zQ0{u-+9+SB4#HE61Ua0eH^+IFDWzo5^@ye@ z0k+Jje4e$oX~H0Sz9eri!@occ5)L!o0u**vo(%=^ltrRwjZMXJXc)rrG6Pcm^ zX}2ydSgo54bA~xvl+u^j<q_e22n(pGgr{263t}yXmo9n1BdlRyhdchIFw`EgTrM zsZ+#VJ=>J^QyN8CiD%tlq?SDBi4^b#aG6+=B^bQZd{?E@0?D{16EqKOB^|;+`)=+c zQ<$_-YoLwPXI7ypJGvMedlYN%_!u!pU2W2^Bx4pvLLx^A6`3ENakN=4-BGBQtw_X* zt-K~9@0y0Qc0PtHrE&v^ZQVPXq~JPCnb$ujF5ZG&*NR_6qLj`7u`t;jQMEh=Y&2(O zlv<0dvlylTf8IvL;Y>g$h&Wy28QH*a7WJJ-^M~;qQ@)Yls`Dk72LtX`gofo;=73ns zL3e;9RpP*8=FO1IO&kLHqZ0#N9DZD(%TcWJ4g2#i75KP1e5_Ha8d3kWhDnfXr^A7u z3xh?^tiUW-&Qb&=eo%#FwRipIrGR6_;+ljF{U>To{pmg7!5db7i-NM>Fy361iB`6m zm90DMV~v}*aMKhFxy=75TJLq_32th%bDVWlNV=LLa?Aq2pJ&AmUhY^*1^V?b7u z8Xz@51C}RS#kzqJdaXrS;DS__}zvQ*NvKWL*ptngbfjDW0YDk+`GxqS-5d;#}~jkCxEsR z5>k}MO5Y_g;*3Hfsxp%LuAxd=3>J_4if~yiQfHDu!<(Qoq9x{V?fN5}#ii4J95DJG zghiLTfSYX4s|~yJUmID@E=uRu;OCTo)hSM%ayZ86K2kZyUT&)`x@_lri1U^3w$h_r zs6sm`sTOGl_8Y>l+Vsa9g*CKqsSPI2a6lqI7-u!dPllE z-(F@=ZhhFC+s4!G_qkNDD@qi>u%c8RfUdF(k19BugA45#^GQ~c?I$4EAb2%WBvOf9nvLclNQy!9h zTAO1o$<63hZYdd1llKknsbnx$EWYuA*>WWdUma(WA1F7F8q^h6$ zPySS(C-jWF_U*2!bceEN%7z_ye)4r8n5e&wF`zE+Zn_nfXGO#E8F}-K&Uz2bTQhLe z-ZCtPEoKdEzE7y3xK<1LKERG%Ru|NvbOhED7YDG2H*aLAbI2+PCB%OCxlFBTRXDN! znIB|js0N|GHhhyQP(F!{>h7z); zVI109r#YywB_Ajwh3|27MLKm7UnTRVVip|-$%02(G(_C&38&0N3R7C!M2BQahH}O2 zn~ZW?Li`$_K+S=l?5BquI?;mmH#7hC2e}0*`C1p1ye_s{85~;_QCXHa4Nm&UYAYyY0BHA6UstR^qKX08pGAl^ z9-!0kgJXFm-a+p@@8~X}W$3&uY_B-vS&GG*`jy@>*0+wXXf=ilcGq)mD+w2hLk!Ak zv2I4#bm#Tf2hu+FAzq~QUv3~)a^s|+i({N@yuBfb>*R+0Ycy1?Y#3$U95tJRp_@W8 z#>vF3cu@2qO_dFW7Cjr=F;E!1gjveq+2H;)y>Jc9(36#N&FnjBU?w6rrGQV=k%3du z=weGyR^TSG7I&Uf z>eJPvgFlmobnC|QH#u|5;q4%=%Oo+C+V5*Pi=Nl+ZDo zCt@ZgNEernPfHV07Bd|3OE*&KzdT3d3Ia^vu{owk%d91H`-&tpJ2i42otwqIBAQ}JlU{X%a$}Cg6X3A@#vRig+R}Bq zgzH@`+4AX)m`3QZ2?2#3#Li4a17*$FFhZ|Hv|`(EvQ_HZ!S{O`dLAK8Cm7vrO&Iqv z`kl3*mgcd4z2vDRa_Y5s zp@p%u*15x83XHuz``~S{Py)x7N=(ev^S~_xY6pj7Sd{%UpWP)mD?us)3rTgc&^Xan ze1uwYtaqGW<_5>6R}p#e#MO4@0u|m5?N#nl#}vnl2q@jxNXjPLD@Fqk?yh46&o+&_MKwHmHB?2)oLnw`q%E+>^0G|IS>plmf1MG1$E5Xy1uEJ+`Q*!Hkn6w4T3 zv%G6FS>xtc7tNZb&6x7xY7RmZGQ+~yRVyT>jbcKjpgh<7=|{E zne-#ZL(c1e-nSi3Oke1L^14be0!ozYGpPFE5Xd%liyJ27X;*kFmpx&&9T4<3Zw1{= zbOl=jf-?cZZep}vU>bV4f}hzU*#)WwphAzk)M`Nspa8z#ErsrOmWYF|U5ZlO%$DSA zU{PRJZ*xfPI8H^i=ya{y_jPQ(?t;1#pCrZY(MnS?>Hi~utUPDOMJ5%!y@;pADzh74 z7x!4H6P>i;ktK$rYA(XGellE+Zw>+;y8R|x|M%_O<4$>sP}8Lot(}ZG`~Y3xt%V)V z4&3H7p9pufl8@!kq(23~ODgk`jm7U(X+d>8 zkCDh10BgBzSrFklc9K0j6ZJEaEpH9z_gd2XfcH)}oP$;&mq{y((P9~RIG#ukRN356 z%MefO=Xn~DAbaO+8WD<(DZb5kqHgS2yif^#rZe?G2;>WG7sM102 zX#nLD-8S8l${F*}m9H^%1o4SNO3=v9MnOJXA&-8&3$te(om5p<^N>v2{HZ3bLt$&= z8n9&5mRq1H?XlT2v@UffI}8pD?s(_)ZNn0nc|6r)Dt7+uy=S-mBn+t9&1lLUpTU8( zNS?-p%-7ep(>0)oS}D$hq&90UIbQ{_+d0R>=D5j$;43^|8L9IhyGebg^X;ASq*X;K zMdMoK91IbtVh%im`8t0zoN7V5voeSwZVg;9;jsWiK)k>8QEs^MchWfJ>$-M)wJnc& z(Z->i>0$bScC^Vngv^QHpY}jBc_t8w*cDm_zoa+rRW|2scI`S0NKox_2rrqXEk)as z>Xfq~C5W>wZ=@uan^#kg=^Gols5G6-={VlF4iB)i#O1n{ds`Q7uPnD%GRhOaCQL$raN2EEn9y&O}eXyrt$U!+J58s2GkG zN857!?XlFFpcrn$o87UB4y9RDOK=Bpk ziC}Jf1MG19!%hs0WSdbi&e^`K@f18qH6pj;_&^ASJHv;Voh+RIssR2&cIh6QbAg@v-B6=+Z)N?Lm z4V6rMc?(nStUU{^TvH8?{Hr63bdizzVV;#@`d&BzniZrHyQlT0&r#VT_2{h~CFUZ( z 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 z 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%0Ahzvv5Q$d@qff#hLp4ZOtNRaOe#+FY$4^Q5DzuBm z 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

    oX2EW#Ig{pyp1zAPBY&bq(%;;CC7 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�xk{(@*p9 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%0D^!v~|- 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⩔Fx+rs$VksJdEz@6K! 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

    c4XF)StkK&2!5Ok6~NWS2* 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�w%BHpN@X3^u1Q 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

    3KAc1@{()*JhRP`S-P((Iy1aO$I9EuPWBF= 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 + + + root://localhost:10943//data/a048e67f-4397-4bb8-85eb-8d7e40d90763.dat + + + diff --git a/tests/XRootD/cluster/mvdata/mlFileTest2.meta4 b/tests/XRootD/cluster/mvdata/mlFileTest2.meta4 new file mode 100644 index 00000000000..a78aaddc3b6 --- /dev/null +++ b/tests/XRootD/cluster/mvdata/mlFileTest2.meta4 @@ -0,0 +1,8 @@ + + + + root://localhost:10943//data/a048e67f-4397-4bb8-85eb-8d7e40d90763.dat + root://localhost:10944//data/a048e67f-4397-4bb8-85eb-8d7e40d90763.dat + + + diff --git a/tests/XRootD/cluster/mvdata/mlFileTest3.meta4 b/tests/XRootD/cluster/mvdata/mlFileTest3.meta4 new file mode 100644 index 00000000000..fd810e15faa --- /dev/null +++ b/tests/XRootD/cluster/mvdata/mlFileTest3.meta4 @@ -0,0 +1,8 @@ + + + + root://localhost:10945//data/a048e67f-4397-4bb8-85eb-8d7e40d90763.dat + root://localhost:10944//data/a048e67f-4397-4bb8-85eb-8d7e40d90763.dat + + + diff --git a/tests/XRootD/cluster/mvdata/mlFileTest4.meta4 b/tests/XRootD/cluster/mvdata/mlFileTest4.meta4 new file mode 100644 index 00000000000..194665b5936 --- /dev/null +++ b/tests/XRootD/cluster/mvdata/mlFileTest4.meta4 @@ -0,0 +1,8 @@ + + + + root://localhost:10945//data/3c9a9dd8-bc75-422c-b12c-f00604486cc1.dat + root://localhost:10944//data/3c9a9dd8-bc75-422c-b12c-f00604486cc1.dat + + + diff --git a/tests/XRootD/cluster/mvdata/mlTpcTest.meta4 b/tests/XRootD/cluster/mvdata/mlTpcTest.meta4 new file mode 100644 index 00000000000..31b5287d8a4 --- /dev/null +++ b/tests/XRootD/cluster/mvdata/mlTpcTest.meta4 @@ -0,0 +1,9 @@ + + + + b43f105c + 16777216 + root://localhost:10943//data/cb4aacf1-6f28-42f2-b68a-90a73460f424.dat + + + diff --git a/tests/XRootD/cluster/mvdata/mlZipTest.meta4 b/tests/XRootD/cluster/mvdata/mlZipTest.meta4 new file mode 100644 index 00000000000..661627c54bd --- /dev/null +++ b/tests/XRootD/cluster/mvdata/mlZipTest.meta4 @@ -0,0 +1,9 @@ + + + + 75a16a5b + 4047392 + root://localhost:10946//data/large.zip + + + diff --git a/tests/XRootD/cluster/setup.sh b/tests/XRootD/cluster/setup.sh new file mode 100755 index 00000000000..d94a5a82cd8 --- /dev/null +++ b/tests/XRootD/cluster/setup.sh @@ -0,0 +1,198 @@ +#!/usr/bin/env bash + +###### +# Starts a cluster configuration locally, instead of using +# containers. +##### + +set -e + +: ${XROOTD:=$(command -v xrootd)} +: ${CMSD:=$(command -v cmsd)} +: ${OPENSSL:=$(command -v openssl)} +: ${CRC32C:=$(command -v xrdcrc32c)} +: ${STAT:=$(command -v stat)} + +servernames=("metaman" "man1" "man2" "srv1" "srv2" "srv3" "srv4") +datanodes=("srv1" "srv2" "srv3" "srv4") + +DATAFOLDER="./xrd-data" +TMPDATAFOLDER="./rout" +PREDEF="./mvdata" + +FILESFOLDER="/xrootd/docker/data" + +filenames=("1db882c8-8cd6-4df1-941f-ce669bad3458.dat" + "3c9a9dd8-bc75-422c-b12c-f00604486cc1.dat" + "7235b5d1-cede-4700-a8f9-596506b4cc38.dat" + "7e480547-fe1a-4eaf-a210-0f3927751a43.dat" + "89120cec-5244-444c-9313-703e4bee72de.dat" + "a048e67f-4397-4bb8-85eb-8d7e40d90763.dat" + "b3d40b3f-1d15-4ad3-8cb5-a7516acb2bab.dat" + "b74d025e-06d6-43e8-91e1-a862feb03c84.dat" + "cb4aacf1-6f28-42f2-b68a-90a73460f424.dat" + "cef4d954-936f-4945-ae49-60ec715b986e.dat") + +filesize() { + case $(uname) in + Darwin) ${STAT} -f"%z" $1 ;; + Linux) ${STAT} -c"%s" $1 ;; + *) ${STAT} -c"%s" $1 ;; + esac +} + +formatfiles() { + case $(uname) in + Darwin) + sed -i '' "s|.*|$new_size|" $1 + sed -i '' "s|.*|$new_hash|" $1 + sed -i '' "s|.*|$new_hash|" $1 + ;; + Linux) + sed -i "s|.*|$new_size|" $1 + sed -i "s|.*|$new_hash|" $1 + sed -i "s|.*|$new_hash|" $1 + ;; + *) + sed -i "s|.*|$new_size|" $1 + sed -i "s|.*|$new_hash|" $1 + sed -i "s|.*|$new_hash|" $1 + ;; + esac +} + +generate(){ + + # check if files are in the data directory already... + if [[ -e ${DATAFOLDER}/${i} ]]; then + return + fi + + mkdir -p ${TMPDATAFOLDER} + + for i in ${filenames[@]}; do + ${OPENSSL} rand -out "${TMPDATAFOLDER}/${i}" $(( 2**24 )) + done + + # correct the info inside of metalink files + insertFileInfo + + # create local srv directories + echo "Creating directories for each instance..." + + for i in ${servernames[@]}; do + mkdir -p ${DATAFOLDER}/${i}/data + done + + for i in ${datanodes[@]}; do + mkdir -p ${DATAFOLDER}/${i}/data/bigdir + cd ${DATAFOLDER}/${i}/data/bigdir + for i in `seq 1000`; + do touch `uuidgen`.dat; + done + cd - >/dev/null + done + + for i in ${servernames[@]}; do + if [[ ${i} == 'metaman' ]] ; then + # download the a test file for upload tests + mkdir -p ${DATAFOLDER}/${i}/data + cp ${TMPDATAFOLDER}/a048e67f-4397-4bb8-85eb-8d7e40d90763.dat ${DATAFOLDER}/${i}/data/testFile.dat + fi + + # download the test files for 'srv1' + if [[ ${i} == 'srv1' ]] ; then + cp ${TMPDATAFOLDER}/a048e67f-4397-4bb8-85eb-8d7e40d90763.dat ${DATAFOLDER}/${i}/data + cp ${TMPDATAFOLDER}/b3d40b3f-1d15-4ad3-8cb5-a7516acb2bab.dat ${DATAFOLDER}/${i}/data + cp ${TMPDATAFOLDER}/b74d025e-06d6-43e8-91e1-a862feb03c84.dat ${DATAFOLDER}/${i}/data + cp ${TMPDATAFOLDER}/cb4aacf1-6f28-42f2-b68a-90a73460f424.dat ${DATAFOLDER}/${i}/data + cp ${TMPDATAFOLDER}/cef4d954-936f-4945-ae49-60ec715b986e.dat ${DATAFOLDER}/${i}/data + mkdir -p ${DATAFOLDER}/${i}/data/metalink + cp ${PREDEF}/input*.meta* ${DATAFOLDER}/${i}/data/metalink/ + cp ${PREDEF}/ml*.meta* ${DATAFOLDER}/${i}/data/metalink/ + fi + + # download the test files for 'srv2' and add another instance on 1099 + if [[ ${i} == 'srv2' ]] ; then + cp ${TMPDATAFOLDER}/1db882c8-8cd6-4df1-941f-ce669bad3458.dat ${DATAFOLDER}/${i}/data + cp ${TMPDATAFOLDER}/3c9a9dd8-bc75-422c-b12c-f00604486cc1.dat ${DATAFOLDER}/${i}/data + cp ${TMPDATAFOLDER}/7235b5d1-cede-4700-a8f9-596506b4cc38.dat ${DATAFOLDER}/${i}/data + cp ${TMPDATAFOLDER}/7e480547-fe1a-4eaf-a210-0f3927751a43.dat ${DATAFOLDER}/${i}/data + cp ${TMPDATAFOLDER}/89120cec-5244-444c-9313-703e4bee72de.dat ${DATAFOLDER}/${i}/data + fi + + # download the test files for 'srv3' + if [[ ${i} == 'srv3' ]] ; then + cp ${TMPDATAFOLDER}/1db882c8-8cd6-4df1-941f-ce669bad3458.dat ${DATAFOLDER}/${i}/data + cp ${TMPDATAFOLDER}/3c9a9dd8-bc75-422c-b12c-f00604486cc1.dat ${DATAFOLDER}/${i}/data + cp ${TMPDATAFOLDER}/89120cec-5244-444c-9313-703e4bee72de.dat ${DATAFOLDER}/${i}/data + cp ${TMPDATAFOLDER}/b74d025e-06d6-43e8-91e1-a862feb03c84.dat ${DATAFOLDER}/${i}/data + cp ${TMPDATAFOLDER}/cef4d954-936f-4945-ae49-60ec715b986e.dat ${DATAFOLDER}/${i}/data + fi + + # download the test files for 'srv4' + if [[ ${i} == 'srv4' ]] ; then + cp ${TMPDATAFOLDER}/1db882c8-8cd6-4df1-941f-ce669bad3458.dat ${DATAFOLDER}/${i}/data + cp ${TMPDATAFOLDER}/7e480547-fe1a-4eaf-a210-0f3927751a43.dat ${DATAFOLDER}/${i}/data + cp ${TMPDATAFOLDER}/89120cec-5244-444c-9313-703e4bee72de.dat ${DATAFOLDER}/${i}/data + cp ${TMPDATAFOLDER}/b74d025e-06d6-43e8-91e1-a862feb03c84.dat ${DATAFOLDER}/${i}/data + cp ${TMPDATAFOLDER}/cef4d954-936f-4945-ae49-60ec715b986e.dat ${DATAFOLDER}/${i}/data + cp ${PREDEF}/data.zip ${DATAFOLDER}/${i}/data + cp ${PREDEF}/large.zip ${DATAFOLDER}/${i}/data + fi + done + + rm -rf ${TMPDATAFOLDER} +} + +start(){ + generate + set -x + # start for each component + for i in "${servernames[@]}"; do + ${XROOTD} -b -k fifo -l xrootd_${i}.log -s xrootd_${i}.pid -c configs/xrootd_${i}.cfg + done + + # start cmsd in the redirectors + for i in "${servernames[@]}"; do + ${CMSD} -b -k fifo -l cmsd_${i}.log -s cmsd_${i}.pid -c configs/xrootd_${i}.cfg + done +} + +stop() { + sleep 1 + for i in "${servernames[@]}"; do + kill -s TERM $(cat xrootd_${i}.pid) || true + kill -s TERM $(cat cmsd_${i}.pid) || true + done + rm -rf ${DATAFOLDER} +} + +insertFileInfo() { + # modifies metalink data + for file in ${filenames[@]}; do + for i in ${PREDEF}/*.meta*; do + # update size and hash + if grep -q $file $i; then + echo "Pattern ${file} found in ${i}!!" + new_size=$(filesize ${TMPDATAFOLDER}/${file}) + new_hash=$(${CRC32C} < ${TMPDATAFOLDER}/${file} | cut -d' ' -f1) + $(formatfiles $i) + else + echo "URL not found in the XML." + fi + done + done +} + +usage() { + echo $0 start or stop +} + +[[ $# == 0 ]] && usage && exit 0 + +CMD=$1 +shift +[[ $(type -t ${CMD}) == "function" ]] || die "unknown command: ${CMD}" +$CMD $@ + diff --git a/tests/XRootD/cluster/smoketest-clustered.sh b/tests/XRootD/cluster/smoketest-clustered.sh new file mode 100755 index 00000000000..aa4229d31db --- /dev/null +++ b/tests/XRootD/cluster/smoketest-clustered.sh @@ -0,0 +1,140 @@ +#!/usr/bin/env bash + +# we probably need all of these still +: ${ADLER32:=$(command -v xrdadler32)} +: ${CRC32C:=$(command -v xrdcrc32c)} +: ${XRDCP:=$(command -v xrdcp)} +: ${XRDFS:=$(command -v xrdfs)} +: ${OPENSSL:=$(command -v openssl)} +: ${HOST_METAMAN:=root://localhost:10940} +: ${HOST_MAN1:=root://localhost:10941} +: ${HOST_MAN2:=root://localhost:10942} +: ${HOST_SRV1:=root://localhost:10943} +: ${HOST_SRV2:=root://localhost:10944} +: ${HOST_SRV3:=root://localhost:10945} +: ${HOST_SRV4:=root://localhost:10946} + +# checking for command presence +for PROG in ${ADLER32} ${CRC32C} ${XRDCP} ${XRDFS} ${OPENSSL}; do + if [[ ! -x "${PROG}" ]]; then + echo 1>&2 "$(basename $0): error: '${PROG}': command not found" + exit 1 + fi +done + +# This script assumes that ${host} exports an empty / as read/write. +# It also assumes that any authentication required is already setup. + +set -xe + +echo "xrdcp/fs-test1" +${XRDCP} --version + +for host in "${!hosts[@]}"; do + ${XRDFS} ${hosts[$host]} query config version +done + +# query some common server configurations + +CONFIG_PARAMS=( version role sitename ) + +for PARAM in ${CONFIG_PARAMS[@]}; do + for host in "${!hosts[@]}"; do + ${XRDFS} ${hosts[$host]} query config ${PARAM} + done +done + +# some extra query commands that don't make any changes +${XRDFS} ${HOST_METAMAN} stat / +${XRDFS} ${HOST_METAMAN} statvfs / +${XRDFS} ${HOST_METAMAN} spaceinfo / + +RMTDATADIR="/srvdata" +LCLDATADIR="/tmp/localdata" # client folder + +mkdir -p /tmp/localdata + +# hostname-address pair, so that we can keep track of files more easily +declare -A hosts +hosts["metaman"]="${HOST_METAMAN}" +hosts["man1"]="${HOST_MAN1}" +hosts["man2"]="${HOST_MAN2}" +hosts["srv1"]="${HOST_SRV1}" +hosts["srv2"]="${HOST_SRV2}" +hosts["srv3"]="${HOST_SRV3}" +hosts["srv4"]="${HOST_SRV4}" + +cleanup() { + echo "Error occured. Cleaning up..." + for host in "${!hosts[@]}"; do + rm -rf ${LCLDATADIR}/${host}.dat + rm -rf ${LCLDATADIR}/${host}.ref + done +} +trap "cleanup; exit 1" ABRT + +# create local files with random contents using OpenSSL +echo "Creating files for each instance..." + +for host in "${!hosts[@]}"; do + ${OPENSSL} rand -out "${LCLDATADIR}/${host}.ref" $((1024 * $RANDOM)) +done + +# upload local files to the servers in parallel +echo "Uploading files..." + + +for host in "${!hosts[@]}"; do + ${XRDCP} ${LCLDATADIR}/${host}.ref ${hosts[$host]}/${RMTDATADIR}/${host}.ref +done + +# list uploaded files, then download them to check for corruption +echo "Downloading them back..." + +for host in "${!hosts[@]}"; do + ${XRDFS} ${hosts[$host]} ls -l ${RMTDATADIR} +done + +echo "Downloading them back... pt2" + +for host in "${!hosts[@]}"; do + ${XRDCP} ${hosts[$host]}/${RMTDATADIR}/${host}.ref ${LCLDATADIR}/${host}.dat +done + +# check that all checksums for downloaded files match +echo "Comparing checksum..." + +for host in "${!hosts[@]}"; do + REF32C=$(${CRC32C} < ${LCLDATADIR}/${host}.ref | cut -d' ' -f1) + NEW32C=$(${CRC32C} < ${LCLDATADIR}/${host}.dat | cut -d' ' -f1) + SRV32C=$(${XRDFS} ${hosts[$host]} query checksum ${RMTDATADIR}/${host}.ref?cks.type=crc32c | cut -d' ' -f2) + + REFA32=$(${ADLER32} < ${LCLDATADIR}/${host}.ref | cut -d' ' -f1) + NEWA32=$(${ADLER32} < ${LCLDATADIR}/${host}.dat | cut -d' ' -f1) + SRVA32=$(${XRDFS} ${hosts[$host]} query checksum ${RMTDATADIR}/${host}.ref?cks.type=adler32 | cut -d' ' -f2) + echo "${host}: crc32c: reference: ${REF32C}, server: ${SRV32C}, downloaded: ${REF32C}" + echo "${host}: adler32: reference: ${NEWA32}, server: ${SRVA32}, downloaded: ${NEWA32}" + + if [[ "${NEWA32}" != "${REFA32}" || "${SRVA32}" != "${REFA32}" ]]; then + echo 1>&2 "$(basename $0): error: adler32 checksum check failed for file: ${host}.dat" + exit 1r + fi + if [[ "${NEW32C}" != "${REF32C}" || "${SRV32C}" != "${REF32C}" ]]; then + echo 1>&2 "$(basename $0): error: crc32 checksum check failed for file: ${host}.dat" + exit 1 + fi +done + +echo "All good! Now removing stuff..." + +for host in "${!hosts[@]}"; do + ${XRDFS} ${HOST_METAMAN} rm ${RMTDATADIR}/${host}.ref & + rm ${LCLDATADIR}/${host}.dat & +done + +wait + +${XRDFS} ${HOST_METAMAN} rmdir ${RMTDATADIR} + +echo "ALL TESTS PASSED" +exit 0 diff --git a/tests/XrdCl/CMakeLists.txt b/tests/XrdCl/CMakeLists.txt index 93ce4f630cf..ca8c59e560c 100644 --- a/tests/XrdCl/CMakeLists.txt +++ b/tests/XrdCl/CMakeLists.txt @@ -3,8 +3,38 @@ add_executable(xrdcl-unit-tests XrdClURL.cc XrdClPoller.cc XrdClSocket.cc - XrdClZip.cc XrdClUtilsTest.cc + ../common/Server.cc + ../common/Utils.cc + ../common/TestEnv.cc + ) + +target_link_libraries(xrdcl-unit-tests + XrdCl + XrdXml + XrdUtils + ZLIB::ZLIB + GTest::GTest + GTest::Main +) + +target_include_directories(xrdcl-unit-tests + PRIVATE ${CMAKE_SOURCE_DIR}/src ../common +) + +gtest_discover_tests(xrdcl-unit-tests TEST_PREFIX XrdCl::) + +if(XRDCL_ONLY) + return() +endif() + +execute_process(COMMAND id -u OUTPUT_VARIABLE UID OUTPUT_STRIP_TRAILING_WHITESPACE) + +if (UID EQUAL 0) + return() +endif() + +add_executable(xrdcl-cluster-tests IdentityPlugIn.cc XrdClFileTest.cc XrdClFileCopyTest.cc @@ -13,13 +43,13 @@ add_executable(xrdcl-unit-tests XrdClLocalFileHandlerTest.cc XrdClPostMasterTest.cc XrdClThreadingTest.cc + XrdClZip.cc ../common/Server.cc ../common/Utils.cc ../common/TestEnv.cc -) - + ) -target_link_libraries(xrdcl-unit-tests +target_link_libraries(xrdcl-cluster-tests XrdCl XrdXml XrdUtils @@ -28,8 +58,9 @@ target_link_libraries(xrdcl-unit-tests GTest::Main ) -target_include_directories(xrdcl-unit-tests +target_include_directories(xrdcl-cluster-tests PRIVATE ${CMAKE_SOURCE_DIR}/src ../common ) -gtest_discover_tests(xrdcl-unit-tests TEST_PREFIX XrdCl::) +gtest_discover_tests(xrdcl-cluster-tests TEST_PREFIX XrdCl:: + PROPERTIES DEPENDS XRootD_Cluster FIXTURES_REQUIRED XRootD_Cluster) diff --git a/tests/XrdCl/XrdClFile.cc b/tests/XrdCl/XrdClFile.cc new file mode 100644 index 00000000000..d98817ffd2a --- /dev/null +++ b/tests/XrdCl/XrdClFile.cc @@ -0,0 +1,38 @@ +#undef NDEBUG + +#include +#include + +#include + +using namespace testing; + +class FileTest : public ::testing::Test {}; + +TEST(FileTest, StreamTimeout) +{ + XrdCl::Env *env = XrdCl::DefaultEnv::GetEnv(); + + env->PutInt("StreamTimeout", 1); //60 is default + env->PutInt("TimeoutResolution", 0); //15 is default + + char buf[16]; + uint32_t BytesRead = 0; + XrdCl::File f; + + f.SetProperty("ReadRecovery", "false"); + + auto st = f.Open("root://localhost//test.txt", XrdCl::OpenFlags::Read); + + EXPECT_TRUE(st.IsOK()) << "Open not OK:" << st.ToString() << std::endl; + + sleep(3); // wait for timeout + + st = f.Read(0, 5, buf, BytesRead, 0); + + EXPECT_TRUE(st.IsOK()) << "Read not OK:" << st.ToString() << std::endl; + + st = f.Close(); + + EXPECT_TRUE(st.IsOK()) << "Close not OK:" << st.ToString() << std::endl; +} diff --git a/tests/XrdCl/XrdClFileCopyTest.cc b/tests/XrdCl/XrdClFileCopyTest.cc index 175145f2c3b..44a842ce3b3 100644 --- a/tests/XrdCl/XrdClFileCopyTest.cc +++ b/tests/XrdCl/XrdClFileCopyTest.cc @@ -142,10 +142,12 @@ void FileCopyTest::UploadTestFunc() std::string address; std::string dataPath; std::string localFile; + std::string localDataPath; EXPECT_TRUE( testEnv->GetString( "MainServerURL", address ) ); EXPECT_TRUE( testEnv->GetString( "DataPath", dataPath ) ); - EXPECT_TRUE( testEnv->GetString( "LocalFile", localFile ) ); + EXPECT_TRUE( testEnv->GetString( "LocalDataPath", localDataPath ) ); + localFile = localDataPath + "/metaman/data/testFile.dat"; URL url( address ); EXPECT_TRUE( url.IsValid() ); @@ -161,6 +163,7 @@ void FileCopyTest::UploadTestFunc() // Open //---------------------------------------------------------------------------- int fd = -1; + GTEST_ASSERT_ERRNO( (fd=open( localFile.c_str(), O_RDONLY )) > 0 ) GTEST_ASSERT_XRDST( f.Open( fileUrl, OpenFlags::Delete|OpenFlags::Update ) ); @@ -269,7 +272,18 @@ namespace //------------------------------------------------------------------------ // Constructor/destructor //------------------------------------------------------------------------ - CancelProgressHandler(): pCancel( false ) {} + + // file size limit in MB + uint64_t sizeLimit; + + CancelProgressHandler(): pCancel( false ) { + sizeLimit = 128*1024*1024; + } + + CancelProgressHandler(uint64_t sl): pCancel( false ) { + sizeLimit = sl*1024*1024; + } + virtual ~CancelProgressHandler() {}; //------------------------------------------------------------------------ @@ -279,7 +293,7 @@ namespace uint64_t bytesProcessed, uint64_t bytesTotal ) { - if( bytesProcessed > 128*1024*1024 ) + if( bytesProcessed > sizeLimit ) pCancel = true; } @@ -310,12 +324,19 @@ void FileCopyTest::CopyTestFunc( bool thirdParty ) std::string manager2; std::string sourceFile; std::string dataPath; + std::string relativeDataPath; + std::string localDataPath; + + + EXPECT_TRUE( testEnv->GetString( "MainServerURL", metamanager ) ); + EXPECT_TRUE( testEnv->GetString( "Manager1URL", manager1 ) ); + EXPECT_TRUE( testEnv->GetString( "Manager2URL", manager2 ) ); + EXPECT_TRUE( testEnv->GetString( "RemoteFile", sourceFile ) ); + EXPECT_TRUE( testEnv->GetString( "DataPath", dataPath ) ); + EXPECT_TRUE( testEnv->GetString( "LocalDataPath", relativeDataPath ) ); - EXPECT_TRUE( testEnv->GetString( "MainServerURL", metamanager ) ); - EXPECT_TRUE( testEnv->GetString( "Manager1URL", manager1 ) ); - EXPECT_TRUE( testEnv->GetString( "Manager2URL", manager2 ) ); - EXPECT_TRUE( testEnv->GetString( "RemoteFile", sourceFile ) ); - EXPECT_TRUE( testEnv->GetString( "DataPath", dataPath ) ); + // getting the abs path to that it can work with the "file" protocol + localDataPath = realpath(relativeDataPath.c_str(), NULL); std::string sourceURL = manager1 + "/" + sourceFile; std::string targetPath = dataPath + "/tpcFile"; @@ -327,7 +348,7 @@ void FileCopyTest::CopyTestFunc( bool thirdParty ) std::string fileInZip = "paper.txt"; std::string fileInZip2 = "bible.txt"; std::string xcpSourceURL = metamanager + "/" + dataPath + "/1db882c8-8cd6-4df1-941f-ce669bad3458.dat"; - std::string localFile = "/data/localfile.dat"; + std::string localFile = localDataPath + "/metaman/localfile.dat"; CopyProcess process1, process2, process3, process4, process5, process6, process7, process8, process9, process10, process11, process12, process13, process14, process15, process16, process17; @@ -424,7 +445,7 @@ void FileCopyTest::CopyTestFunc( bool thirdParty ) properties.Set( "source", sourceURL ); properties.Set( "target", targetURL ); properties.Set( "xrate", 1024 * 1024 ); //< limit the transfer rate to 1MB/s (the file is 1GB big so the transfer will take 1024 seconds) - properties.Set( "cpTimeout", 10 ); //< timeout the job after 10 seconds + properties.Set( "cpTimeout", 5 ); //< timeout the job after 10 seconds (now the file are smaller so we have to decrease it to 5 sec) GTEST_ASSERT_XRDST( process15.AddJob( properties, &results ) ); GTEST_ASSERT_XRDST( process15.Prepare() ); GTEST_ASSERT_XRDST_NOTOK( process15.Run(0), XrdCl::errOperationExpired ); @@ -435,17 +456,17 @@ void FileCopyTest::CopyTestFunc( bool thirdParty ) //-------------------------------------------------------------------------- results.Clear(); properties.Clear(); - std::string localtrg = "file://localhost/data/tpcFile.dat"; + std::string localtrg = "file://localhost" + localDataPath + "/metaman/tpcFile.dat"; properties.Set( "source", sourceURL ); properties.Set( "target", localtrg ); properties.Set( "posc", true ); - CancelProgressHandler progress16; //> abort the copy after 100MB + CancelProgressHandler progress16(5); //> abort the copy after 5MB GTEST_ASSERT_XRDST( process16.AddJob( properties, &results ) ); GTEST_ASSERT_XRDST( process16.Prepare() ); GTEST_ASSERT_XRDST_NOTOK( process16.Run( &progress16 ), errOperationInterrupted ); XrdCl::FileSystem localfs( "file://localhost" ); XrdCl::StatInfo *ptr = nullptr; - GTEST_ASSERT_XRDST_NOTOK( localfs.Stat( "/data/tpcFile.dat", ptr ), XrdCl::errLocalError ); + GTEST_ASSERT_XRDST_NOTOK( localfs.Stat( dataPath + "/tpcFile.dat", ptr ), XrdCl::errLocalError ); //-------------------------------------------------------------------------- // Test --retry and --retry-policy @@ -474,7 +495,7 @@ void FileCopyTest::CopyTestFunc( bool thirdParty ) properties.Set( "source", metalinkURL ); properties.Set( "target", targetURL ); properties.Set( "checkSumMode", "end2end" ); - properties.Set( "checkSumType", "zcrc32" ); + properties.Set( "checkSumType", "crc32c" ); GTEST_ASSERT_XRDST( process5.AddJob( properties, &results ) ); GTEST_ASSERT_XRDST( process5.Prepare() ); GTEST_ASSERT_XRDST( process5.Run(0) ); @@ -486,7 +507,7 @@ void FileCopyTest::CopyTestFunc( bool thirdParty ) properties.Set( "source", xcpSourceURL ); properties.Set( "target", targetURL ); properties.Set( "checkSumMode", "end2end" ); - properties.Set( "checkSumType", "zcrc32" ); + properties.Set( "checkSumType", "crc32c" ); properties.Set( "xcp", true ); properties.Set( "nbXcpSources", 3 ); GTEST_ASSERT_XRDST( process7.AddJob( properties, &results ) ); @@ -502,7 +523,7 @@ void FileCopyTest::CopyTestFunc( bool thirdParty ) properties.Set( "source", sourceURL ); properties.Set( "target", "file://localhost" + localFile ); properties.Set( "checkSumMode", "end2end" ); - properties.Set( "checkSumType", "zcrc32" ); + properties.Set( "checkSumType", "crc32c" ); GTEST_ASSERT_XRDST( process8.AddJob( properties, &results ) ); GTEST_ASSERT_XRDST( process8.Prepare() ); GTEST_ASSERT_XRDST( process8.Run(0) ); @@ -526,7 +547,7 @@ void FileCopyTest::CopyTestFunc( bool thirdParty ) properties.Set( "source", "file://localhost" + localFile ); properties.Set( "target", targetURL ); properties.Set( "checkSumMode", "end2end" ); - properties.Set( "checkSumType", "zcrc32" ); + properties.Set( "checkSumType", "crc32c" ); properties.Set( "preserveXAttr", true ); GTEST_ASSERT_XRDST( process9.AddJob( properties, &results ) ); GTEST_ASSERT_XRDST( process9.Prepare() ); @@ -553,7 +574,7 @@ void FileCopyTest::CopyTestFunc( bool thirdParty ) properties.Set( "source", sourceURL ); properties.Set( "target", targetURL ); properties.Set( "checkSumMode", "end2end" ); - properties.Set( "checkSumType", "zcrc32" ); + properties.Set( "checkSumType", "crc32c" ); if( thirdParty ) properties.Set( "thirdParty", "only" ); GTEST_ASSERT_XRDST( process1.AddJob( properties, &results ) ); @@ -595,8 +616,8 @@ void FileCopyTest::CopyTestFunc( bool thirdParty ) // Copy from a non-existent source //---------------------------------------------------------------------------- results.Clear(); - properties.Set( "source", "root://localhost:9997//test" ); // was 9999, this change allows for - properties.Set( "target", targetURL ); // parallel testing + properties.Set( "source", "root://localhost:9999//test" ); + properties.Set( "target", targetURL ); properties.Set( "initTimeout", 10 ); properties.Set( "thirdParty", "only" ); GTEST_ASSERT_XRDST( process3.AddJob( properties, &results ) ); @@ -627,9 +648,9 @@ TEST_F(FileCopyTest, ThirdPartyCopyTest) } //------------------------------------------------------------------------------ -// Cormal copy test +// Normal copy test //------------------------------------------------------------------------------ -TEST_F (FileCopyTest, NormalCopyTest) +TEST_F(FileCopyTest, NormalCopyTest) { CopyTestFunc( false ); } diff --git a/tests/XrdCl/XrdClFileSystemTest.cc b/tests/XrdCl/XrdClFileSystemTest.cc index 17ca8cd7547..d1ce103f096 100644 --- a/tests/XrdCl/XrdClFileSystemTest.cc +++ b/tests/XrdCl/XrdClFileSystemTest.cc @@ -23,7 +23,7 @@ #include "GTestXrdHelpers.hh" #include - +#include #include "TestEnv.hh" #include "IdentityPlugIn.hh" @@ -394,9 +394,18 @@ void FileSystemTest::StatTest() std::string address; std::string remoteFile; + std::string localDataPath; EXPECT_TRUE( testEnv->GetString( "MainServerURL", address ) ); EXPECT_TRUE( testEnv->GetString( "RemoteFile", remoteFile ) ); + EXPECT_TRUE( testEnv->GetString( "LocalDataPath", localDataPath ) ); + + std::string localFilePath = localDataPath + "/srv1" + remoteFile; + + struct stat localStatBuf; + int rc = stat(localFilePath.c_str(), &localStatBuf); + EXPECT_TRUE( rc == 0 ); + uint64_t fileSize = localStatBuf.st_size; URL url( address ); EXPECT_TRUE( url.IsValid() ); @@ -405,7 +414,7 @@ void FileSystemTest::StatTest() StatInfo *response = 0; GTEST_ASSERT_XRDST( fs.Stat( remoteFile, response ) ); EXPECT_TRUE( response ); - EXPECT_TRUE( response->GetSize() == 1048576000 ); + EXPECT_TRUE( response->GetSize() == fileSize ); EXPECT_TRUE( response->TestFlags( StatInfo::IsReadable ) ); EXPECT_TRUE( response->TestFlags( StatInfo::IsWritable ) ); EXPECT_TRUE( !response->TestFlags( StatInfo::IsDir ) ); @@ -525,7 +534,7 @@ void FileSystemTest::DirListTest() DirectoryList *list = 0; GTEST_ASSERT_XRDST( fs.DirList( lsPath, DirListFlags::Stat | DirListFlags::Locate, list ) ); EXPECT_TRUE( list ); - EXPECT_TRUE( list->GetSize() == 40000 ); + EXPECT_TRUE( list->GetSize() == 4000 ); std::set dirls1; for( auto itr = list->Begin(); itr != list->End(); ++itr ) @@ -571,14 +580,15 @@ void FileSystemTest::DirListTest() //---------------------------------------------------------------------------- // Now list an empty directory //---------------------------------------------------------------------------- - GTEST_ASSERT_XRDST( fs.MkDir( "/data/empty", MkDirFlags::None, Access::None ) ); - GTEST_ASSERT_XRDST( fs.DeepLocate( "/data/empty", OpenFlags::PrefName, info ) ); + std::string emptyDirPath = dataPath + "/empty" ; + GTEST_ASSERT_XRDST( fs.MkDir( emptyDirPath, MkDirFlags::None, Access::None ) ); + GTEST_ASSERT_XRDST( fs.DeepLocate( emptyDirPath, OpenFlags::PrefName, info ) ); EXPECT_TRUE( info->GetSize() ); FileSystem fs3( info->Begin()->GetAddress() ); - GTEST_ASSERT_XRDST( fs3.DirList( "/data/empty", DirListFlags::Stat, list ) ); + GTEST_ASSERT_XRDST( fs3.DirList( emptyDirPath, DirListFlags::Stat, list ) ); EXPECT_TRUE( list ); EXPECT_TRUE( list->GetSize() == 0 ); - GTEST_ASSERT_XRDST( fs.RmDir( "/data/empty" ) ); + GTEST_ASSERT_XRDST( fs.RmDir( emptyDirPath ) ); delete list; list = 0; @@ -632,6 +642,7 @@ void FileSystemTest::PrepareTest() std::string dataPath; EXPECT_TRUE( testEnv->GetString( "MainServerURL", address ) ); + EXPECT_TRUE( testEnv->GetString( "DataPath", dataPath ) ); URL url( address ); EXPECT_TRUE( url.IsValid() ); @@ -639,8 +650,10 @@ void FileSystemTest::PrepareTest() Buffer *id = 0; std::vector list; - list.push_back( "/data/1db882c8-8cd6-4df1-941f-ce669bad3458.dat" ); - list.push_back( "/data/1db882c8-8cd6-4df1-941f-ce669bad3458.dat" ); + + std::string fileLocation = dataPath + "/1db882c8-8cd6-4df1-941f-ce669bad3458.dat"; + list.push_back( fileLocation ); + list.push_back( fileLocation ); GTEST_ASSERT_XRDST( fs.Prepare( list, PrepareFlags::Stage, 1, id ) ); EXPECT_TRUE( id ); diff --git a/tests/XrdCl/XrdClFileTest.cc b/tests/XrdCl/XrdClFileTest.cc index b0303621cd2..8cc8914a135 100644 --- a/tests/XrdCl/XrdClFileTest.cc +++ b/tests/XrdCl/XrdClFileTest.cc @@ -34,6 +34,9 @@ #include "XrdCl/XrdClZipArchive.hh" #include "XrdCl/XrdClConstants.hh" #include "XrdCl/XrdClZipOperations.hh" +#include +#include +#include using namespace XrdClTests; @@ -86,10 +89,12 @@ TEST_F(FileTest, VectorWriteTest) VectorWriteTest(); } -TEST_F(FileTest, VirtualRedirectorTest) -{ - VirtualRedirectorTest(); -} +// This test is commented out as of now since we think that there's a bug in the code + +// TEST_F(FileTest, VirtualRedirectorTest) +// { +// VirtualRedirectorTest(); +// } TEST_F(FileTest, XAttrTest) { @@ -169,9 +174,12 @@ void FileTest::ReadTest() std::string address; std::string dataPath; + std::string localDataPath; EXPECT_TRUE( testEnv->GetString( "MainServerURL", address ) ); EXPECT_TRUE( testEnv->GetString( "DataPath", dataPath ) ); + EXPECT_TRUE( testEnv->GetString( "LocalDataPath", localDataPath ) ); + URL url( address ); EXPECT_TRUE( url.IsValid() ); @@ -179,60 +187,92 @@ void FileTest::ReadTest() std::string filePath = dataPath + "/cb4aacf1-6f28-42f2-b68a-90a73460f424.dat"; std::string fileUrl = address + "/"; fileUrl += filePath; + localDataPath = realpath(localDataPath.c_str(), NULL); + // using the file protocol to access local files, so that we can use XRootD's own functions + std::string localFileUrl = "file://localhost" + localDataPath + "/srv1" + filePath; //---------------------------------------------------------------------------- // Fetch some data and checksum //---------------------------------------------------------------------------- const uint32_t MB = 1024*1024; - char *buffer1 = new char[40*MB]; - char *buffer2 = new char[40*MB]; - uint32_t bytesRead1 = 0; - uint32_t bytesRead2 = 0; - File f; - StatInfo *stat; + // buffers containing remote file data + char *buffer1 = new char[10*MB]; + char *buffer2 = new char[10*MB]; + // comparison buffers containing local file data + char *buffer1Comp = new char[10*MB]; + char *buffer2Comp = new char[10*MB]; + uint32_t bytesRead1 = 0, bytesRead2 = 0; + File f, fLocal; + StatInfo *statInfo; //---------------------------------------------------------------------------- // Open the file //---------------------------------------------------------------------------- GTEST_ASSERT_XRDST( f.Open( fileUrl, OpenFlags::Read ) ); + GTEST_ASSERT_XRDST( fLocal.Open( localFileUrl, OpenFlags::Read ) ); //---------------------------------------------------------------------------- // Stat1 //---------------------------------------------------------------------------- - GTEST_ASSERT_XRDST( f.Stat( false, stat ) ); - EXPECT_TRUE( stat ); - EXPECT_TRUE( stat->GetSize() == 1048576000 ); - EXPECT_TRUE( stat->TestFlags( StatInfo::IsReadable ) ); - delete stat; - stat = 0; + GTEST_ASSERT_XRDST( f.Stat( false, statInfo ) ); + EXPECT_TRUE( statInfo ); + + struct stat localStatBuf; + + std::string localFilePath = localDataPath + "/srv1" + filePath; + + int rc = stat(localFilePath.c_str(), &localStatBuf); + EXPECT_TRUE( rc == 0 ); + uint64_t fileSize = localStatBuf.st_size; + EXPECT_TRUE( statInfo->GetSize() == fileSize ); + EXPECT_TRUE( statInfo->TestFlags( StatInfo::IsReadable ) ); + + delete statInfo; + statInfo = 0; //---------------------------------------------------------------------------- // Stat2 //---------------------------------------------------------------------------- - GTEST_ASSERT_XRDST( f.Stat( true, stat ) ); - EXPECT_TRUE( stat ); - EXPECT_TRUE( stat->GetSize() == 1048576000 ); - EXPECT_TRUE( stat->TestFlags( StatInfo::IsReadable ) ); - delete stat; + GTEST_ASSERT_XRDST( f.Stat( true, statInfo ) ); + EXPECT_TRUE( statInfo ); + EXPECT_TRUE( statInfo->GetSize() == fileSize ); + EXPECT_TRUE( statInfo->TestFlags( StatInfo::IsReadable ) ); + delete statInfo; //---------------------------------------------------------------------------- // Read test //---------------------------------------------------------------------------- - GTEST_ASSERT_XRDST( f.Read( 10*MB, 40*MB, buffer1, bytesRead1 ) ); - GTEST_ASSERT_XRDST( f.Read( 1008576000, 40*MB, buffer2, bytesRead2 ) ); - EXPECT_TRUE( bytesRead1 == 40*MB ); - EXPECT_TRUE( bytesRead2 == 40000000 ); + GTEST_ASSERT_XRDST( f.Read( 5*MB, 5*MB, buffer1, bytesRead1 ) ); + GTEST_ASSERT_XRDST( f.Read( fileSize - 3*MB, 4*MB, buffer2, bytesRead2 ) ); + EXPECT_TRUE( bytesRead1 == 5*MB ); + EXPECT_TRUE( bytesRead2 == 3*MB ); + + uint32_t crc = XrdClTests::Utils::ComputeCRC32( buffer1, 5*MB ); + + // reinitializing the file cursor + bytesRead1 = bytesRead2 = 0; + GTEST_ASSERT_XRDST( fLocal.Read( 5*MB, 5*MB, buffer1Comp, bytesRead1 ) ); + GTEST_ASSERT_XRDST( fLocal.Read( fileSize - 3*MB, 4*MB, buffer2Comp, bytesRead2 ) ); - uint32_t crc = XrdClTests::Utils::ComputeCRC32( buffer1, 40*MB ); - EXPECT_TRUE( crc == 3303853367UL ); + // compute and compare local file buffer crc + uint32_t crcComp = XrdClTests::Utils::ComputeCRC32( buffer1Comp, 5*MB ); - crc = XrdClTests::Utils::ComputeCRC32( buffer2, 40000000 ); - EXPECT_TRUE( crc == 898701504UL ); + EXPECT_TRUE( crc == crcComp ); + // do the same for the second buffer + crc = XrdClTests::Utils::ComputeCRC32( buffer2, 3*MB ); + crcComp = XrdClTests::Utils::ComputeCRC32( buffer2Comp, 3*MB ); + + EXPECT_TRUE( crc == crcComp ); + + // cleanup after ourselves delete [] buffer1; delete [] buffer2; + delete [] buffer1Comp; + delete [] buffer2Comp; GTEST_ASSERT_XRDST( f.Close() ); + GTEST_ASSERT_XRDST( fLocal.Close() ); //---------------------------------------------------------------------------- // Read ZIP archive test (uncompressed) @@ -326,8 +366,9 @@ void FileTest::WriteTest() //---------------------------------------------------------------------------- // Write the data //---------------------------------------------------------------------------- - EXPECT_TRUE( f1.Open( fileUrl, OpenFlags::Delete | OpenFlags::Update, - Access::UR | Access::UW ).IsOK() ); + GTEST_ASSERT_XRDST( f1.Open( fileUrl, OpenFlags::Delete | OpenFlags::Update, + Access::UR | Access::UW )); + EXPECT_TRUE( f1.Write( 0, 4*MB, buffer1 ).IsOK() ); EXPECT_TRUE( f1.Write( 4*MB, 4*MB, buffer2 ).IsOK() ); EXPECT_TRUE( f1.Sync().IsOK() ); @@ -336,11 +377,11 @@ void FileTest::WriteTest() //---------------------------------------------------------------------------- // Read the data and verify the checksums //---------------------------------------------------------------------------- - StatInfo *stat = 0; + StatInfo *statInfo = 0; EXPECT_TRUE( f2.Open( fileUrl, OpenFlags::Read ).IsOK() ); - EXPECT_TRUE( f2.Stat( false, stat ).IsOK() ); - EXPECT_TRUE( stat ); - EXPECT_TRUE( stat->GetSize() == 8*MB ); + EXPECT_TRUE( f2.Stat( false, statInfo ).IsOK() ); + EXPECT_TRUE( statInfo ); + EXPECT_TRUE( statInfo->GetSize() == 8*MB ); EXPECT_TRUE( f2.Read( 0, 4*MB, buffer3, bytesRead1 ).IsOK() ); EXPECT_TRUE( f2.Read( 4*MB, 4*MB, buffer4, bytesRead2 ).IsOK() ); EXPECT_TRUE( bytesRead1 == 4*MB ); @@ -368,7 +409,7 @@ void FileTest::WriteTest() delete [] buffer3; delete [] buffer4; delete response; - delete stat; + delete statInfo; } //------------------------------------------------------------------------------ @@ -433,11 +474,11 @@ void FileTest::WriteVTest() //---------------------------------------------------------------------------- // Read the data and verify the checksums //---------------------------------------------------------------------------- - StatInfo *stat = 0; + StatInfo *statInfo = 0; EXPECT_TRUE( f2.Open( fileUrl, OpenFlags::Read ).IsOK() ); - EXPECT_TRUE( f2.Stat( false, stat ).IsOK() ); - EXPECT_TRUE( stat ); - EXPECT_TRUE( stat->GetSize() == 8*MB ); + EXPECT_TRUE( f2.Stat( false, statInfo ).IsOK() ); + EXPECT_TRUE( statInfo ); + EXPECT_TRUE( statInfo->GetSize() == 8*MB ); EXPECT_TRUE( f2.Read( 0, 8*MB, buffer3, bytesRead1 ).IsOK() ); EXPECT_TRUE( bytesRead1 == 8*MB ); @@ -450,7 +491,7 @@ void FileTest::WriteVTest() delete [] buffer1; delete [] buffer2; delete [] buffer3; - delete stat; + delete statInfo; } //------------------------------------------------------------------------------ @@ -467,9 +508,12 @@ void FileTest::VectorReadTest() std::string address; std::string dataPath; + std::string localDataPath; EXPECT_TRUE( testEnv->GetString( "MainServerURL", address ) ); EXPECT_TRUE( testEnv->GetString( "DataPath", dataPath ) ); + EXPECT_TRUE( testEnv->GetString( "LocalDataPath", localDataPath ) ); + URL url( address ); EXPECT_TRUE( url.IsValid() ); @@ -477,49 +521,78 @@ void FileTest::VectorReadTest() std::string filePath = dataPath + "/a048e67f-4397-4bb8-85eb-8d7e40d90763.dat"; std::string fileUrl = address + "/"; fileUrl += filePath; + localDataPath += "/srv1" + filePath; + localDataPath = realpath(localDataPath.c_str(), NULL); //---------------------------------------------------------------------------- // Fetch some data and checksum //---------------------------------------------------------------------------- const uint32_t MB = 1024*1024; - char *buffer1 = new char[40*MB]; - char *buffer2 = new char[40*256000]; - File f; + char *buffer1 = new char[10*MB]; + char *buffer2 = new char[10*256000]; + char *buffer1Comp = new char[10*MB]; + char *buffer2Comp = new char[10*256000]; + File f, fLocal; //---------------------------------------------------------------------------- // Build the chunk list //---------------------------------------------------------------------------- ChunkList chunkList1; ChunkList chunkList2; - for( int i = 0; i < 40; ++i ) + for( int i = 0; i < 10; ++i ) { - chunkList1.push_back( ChunkInfo( (i+1)*10*MB, 1*MB ) ); - chunkList2.push_back( ChunkInfo( (i+1)*10*MB, 256000 ) ); + chunkList1.push_back( ChunkInfo( (i+1)*1*MB, 1*MB ) ); + chunkList2.push_back( ChunkInfo( (i+1)*1*MB, 256000 ) ); } //---------------------------------------------------------------------------- // Open the file //---------------------------------------------------------------------------- GTEST_ASSERT_XRDST( f.Open( fileUrl, OpenFlags::Read ) ); + GTEST_ASSERT_XRDST( fLocal.Open( localDataPath, OpenFlags::Read ) ); + + // remote file vread1 VectorReadInfo *info = 0; GTEST_ASSERT_XRDST( f.VectorRead( chunkList1, buffer1, info ) ); - EXPECT_TRUE( info->GetSize() == 40*MB ); + EXPECT_TRUE( info->GetSize() == 10*MB ); delete info; - uint32_t crc = 0; - crc = XrdClTests::Utils::ComputeCRC32( buffer1, 40*MB ); - EXPECT_TRUE( crc == 3695956670UL ); + // local file vread1 + info = 0; + GTEST_ASSERT_XRDST( fLocal.VectorRead( chunkList1, buffer1Comp, info ) ); + EXPECT_TRUE( info->GetSize() == 10*MB ); + delete info; + + // checksum comparison + uint32_t crc = 0, crcComp = 0; + crc = XrdClTests::Utils::ComputeCRC32( buffer1, 10*MB ); + crcComp = XrdClTests::Utils::ComputeCRC32( buffer1Comp, 10*MB ); + EXPECT_TRUE( crc == crcComp ); + + // remote vread2 info = 0; GTEST_ASSERT_XRDST( f.VectorRead( chunkList2, buffer2, info ) ); - EXPECT_TRUE( info->GetSize() == 40*256000 ); + EXPECT_TRUE( info->GetSize() == 10*256000 ); delete info; - crc = XrdClTests::Utils::ComputeCRC32( buffer2, 40*256000 ); - EXPECT_TRUE( crc == 3492603530UL ); - GTEST_ASSERT_XRDST( f.Close() ); + // local vread2 + info = 0; + GTEST_ASSERT_XRDST( f.VectorRead( chunkList2, buffer2Comp, info ) ); + EXPECT_TRUE( info->GetSize() == 10*256000 ); + delete info; + + // checksum comparison again + crc = XrdClTests::Utils::ComputeCRC32( buffer2, 10*256000 ); + crcComp = XrdClTests::Utils::ComputeCRC32( buffer2Comp, 10*256000 ); + EXPECT_TRUE( crc == crcComp ); + // cleanup + GTEST_ASSERT_XRDST( f.Close() ); + GTEST_ASSERT_XRDST( fLocal.Close() ); delete [] buffer1; delete [] buffer2; + delete [] buffer1Comp; + delete [] buffer2Comp; } void gen_random_str(char *s, const int len) @@ -567,8 +640,7 @@ void FileTest::VectorWriteTest() //---------------------------------------------------------------------------- const uint32_t MB = 1024*1024; - const uint32_t GB = 1000*MB; // maybe that's not 100% precise but that's - // what we have in our testbed + const uint32_t limit = 10*MB; time_t seed = time( 0 ); srand( seed ); @@ -586,12 +658,12 @@ void FileTest::VectorWriteTest() for( size_t i = 0; i < nbChunks; ++i ) { // figure out the offset - size_t offset = min_offset + rand() % ( GB - min_offset + 1 ); + size_t offset = min_offset + rand() % ( limit - min_offset + 1 ); // figure out the size size_t size = MB + rand() % ( MB + 1 ); - if( offset + size >= GB ) - size = GB - offset; + if( offset + size >= limit ) + size = limit - offset; // generate random string of given size char *buffer = new char[size]; @@ -603,7 +675,7 @@ void FileTest::VectorWriteTest() chunks.push_back( XrdCl::ChunkInfo( offset, size, buffer ) ); min_offset = offset + size; - if( min_offset >= GB ) + if( min_offset >= limit ) break; } @@ -675,7 +747,10 @@ void FileTest::VirtualRedirectorTest() File f1, f2, f3, f4; - const std::string fileUrl = "root://srv1:1094//data/a048e67f-4397-4bb8-85eb-8d7e40d90763.dat"; + std::string srv1Hostname; + EXPECT_TRUE( testEnv->GetString( "Server1URL", srv1Hostname ) ); + const std::string fileUrl = "root://" + srv1Hostname + "/" + dataPath + "/a048e67f-4397-4bb8-85eb-8d7e40d90763.dat"; + const std::string key = "LastURL"; std::string value; @@ -709,8 +784,14 @@ void FileTest::VirtualRedirectorTest() // Open the 4th metalink file // (the metalink contains 2 files, both exist) //---------------------------------------------------------------------------- - const std::string replica1 = "root://srv3:1094//data/3c9a9dd8-bc75-422c-b12c-f00604486cc1.dat"; - const std::string replica2 = "root://srv2:1094//data/3c9a9dd8-bc75-422c-b12c-f00604486cc1.dat"; + + std::string srv3Hostname; + EXPECT_TRUE( testEnv->GetString( "Server3URL", srv3Hostname ) ); + std::string replica1 = "root://" + srv3Hostname + "/" + dataPath + "/3c9a9dd8-bc75-422c-b12c-f00604486cc1.dat"; + + std::string srv2Hostname; + EXPECT_TRUE( testEnv->GetString( "Server2URL", srv2Hostname ) ); + const std::string replica2 = "root://" + srv2Hostname + "/" + dataPath + "/3c9a9dd8-bc75-422c-b12c-f00604486cc1.dat"; GTEST_ASSERT_XRDST( f4.Open( mlUrl4, OpenFlags::Read ) ); EXPECT_TRUE( f4.GetProperty( key, value ) ); @@ -721,7 +802,7 @@ void FileTest::VirtualRedirectorTest() // Delete the replica that has been selected by the virtual redirector //---------------------------------------------------------------------------- FileSystem fs( replica1 ); - GTEST_ASSERT_XRDST( fs.Rm( "/data/3c9a9dd8-bc75-422c-b12c-f00604486cc1.dat" ) ); + GTEST_ASSERT_XRDST( fs.Rm( dataPath + "/3c9a9dd8-bc75-422c-b12c-f00604486cc1.dat" ) ); //---------------------------------------------------------------------------- // Now reopen the file //---------------------------------------------------------------------------- diff --git a/tests/XrdCl/XrdClLocalFileHandlerTest.cc b/tests/XrdCl/XrdClLocalFileHandlerTest.cc index 80979d6ab5a..9232ae5011d 100644 --- a/tests/XrdCl/XrdClLocalFileHandlerTest.cc +++ b/tests/XrdCl/XrdClLocalFileHandlerTest.cc @@ -32,6 +32,7 @@ class LocalFileHandlerTest: public ::testing::Test { public: void CreateTestFileFunc( std::string url, std::string content = "GenericTestFile" ); + void readTestFunc( bool offsetRead, uint32_t offset ); void OpenCloseTest(); void ReadTest(); void ReadWithOffsetTest(); @@ -58,6 +59,46 @@ void LocalFileHandlerTest::CreateTestFileFunc( std::string url, std::string cont EXPECT_EQ( rc, 0 ); } +//---------------------------------------------------------------------------- +// Performs a ReadTest +//---------------------------------------------------------------------------- +void LocalFileHandlerTest::readTestFunc(bool offsetRead, uint32_t offset){ + using namespace XrdCl; + std::string targetURL = "/tmp/lfilehandlertestfileread"; + std::string toBeWritten = "tenBytes10"; + std::string expectedRead = "Byte"; + uint32_t size = + ( offsetRead ? expectedRead.size() : toBeWritten.size() ); + char *buffer = new char[size]; + uint32_t bytesRead = 0; + + //---------------------------------------------------------------------------- + // Write file with POSIX calls to ensure correct write + //---------------------------------------------------------------------------- + CreateTestFileFunc( targetURL, toBeWritten ); + + //---------------------------------------------------------------------------- + // Open and Read File + //---------------------------------------------------------------------------- + OpenFlags::Flags flags = OpenFlags::Update; + Access::Mode mode = Access::UR|Access::UW|Access::GR|Access::OR; + File *file = new File(); + GTEST_ASSERT_XRDST( file->Open( targetURL, flags, mode ) ); + EXPECT_TRUE( file->IsOpen() ); + GTEST_ASSERT_XRDST( file->Read( offset, size, buffer, bytesRead ) ); + GTEST_ASSERT_XRDST( file->Close() ); + + std::string read( buffer, size ); + if (offsetRead) EXPECT_TRUE( expectedRead == read ); + else EXPECT_TRUE( toBeWritten == read ); + + EXPECT_TRUE( remove( targetURL.c_str() ) == 0 ); + + delete[] buffer; + delete file; + +} + TEST_F(LocalFileHandlerTest, SyncTest){ using namespace XrdCl; std::string targetURL = "/tmp/lfilehandlertestfilesync"; @@ -201,70 +242,11 @@ TEST_F(LocalFileHandlerTest, WriteMkdirTest){ delete file; } -TEST_F(LocalFileHandlerTest, ReadTest){ - using namespace XrdCl; - std::string targetURL = "/tmp/lfilehandlertestfileread"; - std::string toBeWritten = "tenBytes10"; - uint32_t offset = 0; - uint32_t writeSize = toBeWritten.size(); - char *buffer = new char[writeSize]; - uint32_t bytesRead = 0; - - //---------------------------------------------------------------------------- - // Write file with POSIX calls to ensure correct write - //---------------------------------------------------------------------------- - CreateTestFileFunc( targetURL, toBeWritten ); - - //---------------------------------------------------------------------------- - // Open and Read File - //---------------------------------------------------------------------------- - OpenFlags::Flags flags = OpenFlags::Update; - Access::Mode mode = Access::UR|Access::UW|Access::GR|Access::OR; - File *file = new File(); - GTEST_ASSERT_XRDST( file->Open( targetURL, flags, mode ) ); - EXPECT_TRUE( file->IsOpen() ); - GTEST_ASSERT_XRDST( file->Read( offset, writeSize, buffer, bytesRead ) ); - GTEST_ASSERT_XRDST( file->Close() ); - - std::string read( (char*)buffer, writeSize ); - EXPECT_TRUE( toBeWritten == read ); - EXPECT_TRUE( remove( targetURL.c_str() ) == 0 ); - - delete[] buffer; - delete file; -} - -TEST_F(LocalFileHandlerTest, ReadWithOffsetTest){ - using namespace XrdCl; - std::string targetURL = "/tmp/lfilehandlertestfileread"; - std::string toBeWritten = "tenBytes10"; - uint32_t offset = 3; - std::string expectedRead = "Byte"; - uint32_t readsize = expectedRead.size(); - char *buffer = new char[readsize]; - uint32_t bytesRead = 0; - - //---------------------------------------------------------------------------- - // Write file with POSIX calls to ensure correct write - //---------------------------------------------------------------------------- - CreateTestFileFunc( targetURL, toBeWritten ); - - //---------------------------------------------------------------------------- - // Open and Read File - //---------------------------------------------------------------------------- - OpenFlags::Flags flags = OpenFlags::Update; - Access::Mode mode = Access::UR|Access::UW|Access::GR|Access::OR; - File *file = new File(); - GTEST_ASSERT_XRDST( file->Open( targetURL, flags, mode ) ); - EXPECT_TRUE( file->IsOpen() ); - GTEST_ASSERT_XRDST( file->Read( offset, readsize, buffer, bytesRead ) ); - GTEST_ASSERT_XRDST( file->Close() ); - - std::string read( buffer, readsize ); - EXPECT_TRUE( expectedRead == read ); - EXPECT_TRUE( remove( targetURL.c_str() ) == 0 ); - delete[] buffer; - delete file; +TEST_F(LocalFileHandlerTest, ReadTests){ + // Normal read test + readTestFunc(false, 0); + // Read with offset test + readTestFunc(true, 3); } TEST_F(LocalFileHandlerTest, TruncateTest){ @@ -460,8 +442,14 @@ TEST_F(LocalFileHandlerTest, XAttrTest) // Initialize // (we do the test in /data as /tmp might be on tpmfs, // which does not support xattrs) + // In this case, /data is /xrd-data inside of the build directory //---------------------------------------------------------------------------- - std::string targetURL = "/data/lfilehandlertestfilexattr"; + Env *testEnv = TestEnv::GetEnv(); + std::string localDataPath; + EXPECT_TRUE( testEnv->GetString( "LocalDataPath", localDataPath ) ); + + localDataPath = realpath(localDataPath.c_str(), NULL); + std::string targetURL = localDataPath + "/metaman/lfilehandlertestfilexattr"; CreateTestFileFunc( targetURL ); File f; diff --git a/tests/XrdCl/XrdClOperationsWorkflowTest.cc b/tests/XrdCl/XrdClOperationsWorkflowTest.cc index b0ddd9f6ea5..48cf4a56f4a 100644 --- a/tests/XrdCl/XrdClOperationsWorkflowTest.cc +++ b/tests/XrdCl/XrdClOperationsWorkflowTest.cc @@ -34,6 +34,7 @@ #include "XrdCl/XrdClFwd.hh" #include +#include using namespace XrdClTests; @@ -179,11 +180,24 @@ TEST(WorkflowTest, ReadingWorkflowTest){ const OpenFlags::Flags flags = OpenFlags::Read; uint64_t offset = 0; + Env *testEnv = TestEnv::GetEnv(); + std::string localDataPath; + std::string dataPath; + EXPECT_TRUE( testEnv->GetString( "DataPath", dataPath ) ); + EXPECT_TRUE( testEnv->GetString( "LocalDataPath", localDataPath ) ); + std::string filePath = dataPath + "/cb4aacf1-6f28-42f2-b68a-90a73460f424.dat"; + + struct stat localStatBuf; + std::string localFilePath = localDataPath + "/srv1" + filePath; + int rc = stat(localFilePath.c_str(), &localStatBuf); + EXPECT_TRUE( rc == 0 ); + uint64_t fileSize = localStatBuf.st_size; + auto &&pipe = Open( f, fileUrl, flags ) >> openHandler // by reference - | Stat( f, true) >> [size, buffer]( XRootDStatus &status, StatInfo &stat ) mutable + | Stat( f, true) >> [fileSize, size, buffer]( XRootDStatus &status, StatInfo &stat) mutable { GTEST_ASSERT_XRDST( status ); - EXPECT_TRUE( stat.GetSize() == 1048576000 ); + EXPECT_TRUE( stat.GetSize() == fileSize ); size = stat.GetSize(); buffer = new char[stat.GetSize()]; } @@ -561,11 +575,11 @@ TEST(WorkflowTest, ParallelTest){ //---------------------------------------------------------------------------- // Test the policies //---------------------------------------------------------------------------- - std::string url_exists = "/data/1db882c8-8cd6-4df1-941f-ce669bad3458.dat"; - std::string not_exists = "/data/blablabla.txt"; + std::string url_exists = GetPath("1db882c8-8cd6-4df1-941f-ce669bad3458.dat"); + std::string not_exists = GetPath("blablabla.txt"); GTEST_ASSERT_XRDST( WaitFor( Parallel( Stat( fs, url_exists ), Stat( fs, url_exists ) ).Any() ) ); - std::string also_exists = "/data/3c9a9dd8-bc75-422c-b12c-f00604486cc1.dat"; + std::string also_exists = GetPath("3c9a9dd8-bc75-422c-b12c-f00604486cc1.dat"); GTEST_ASSERT_XRDST( WaitFor( Parallel( Stat( fs, url_exists ), Stat( fs, also_exists ), Stat( fs, not_exists ) ).Some( 2 ) ) ); @@ -729,8 +743,8 @@ TEST(WorkflowTest, WorkflowWithFutureTest) // Fetch some data and checksum //---------------------------------------------------------------------------- const uint32_t MB = 1024*1024; - char *expected = new char[40*MB]; - char *buffer = new char[40*MB]; + char *expected = new char[10*MB]; + char *buffer = new char[10*MB]; uint32_t bytesRead = 0; File f; @@ -738,8 +752,8 @@ TEST(WorkflowTest, WorkflowWithFutureTest) // Open and Read and Close in standard way //---------------------------------------------------------------------------- GTEST_ASSERT_XRDST( f.Open( fileUrl, OpenFlags::Read ) ); - GTEST_ASSERT_XRDST( f.Read( 10*MB, 40*MB, expected, bytesRead ) ); - EXPECT_TRUE( bytesRead == 40*MB ); + GTEST_ASSERT_XRDST( f.Read( 1*MB, 10*MB, expected, bytesRead ) ); + EXPECT_TRUE( bytesRead == 10*MB ); GTEST_ASSERT_XRDST( f.Close() ); //---------------------------------------------------------------------------- @@ -747,7 +761,7 @@ TEST(WorkflowTest, WorkflowWithFutureTest) //---------------------------------------------------------------------------- File file; std::future ftr; - Pipeline pipeline = Open( file, fileUrl, OpenFlags::Read ) | Read( file, 10*MB, 40*MB, buffer ) >> ftr | Close( file ); + Pipeline pipeline = Open( file, fileUrl, OpenFlags::Read ) | Read( file, 1*MB, 10*MB, buffer ) >> ftr | Close( file ); std::future status = Async( std::move( pipeline ) ); try @@ -888,6 +902,7 @@ TEST(WorkflowTest, XAttrWorkflowTest) TEST(WorkflowTest, MkDirAsyncTest) { using namespace XrdCl; + std::string asyncTestFile = GetPath("MkDirAsyncTest"); FileSystem fs( GetAddress() ); @@ -901,8 +916,8 @@ TEST(WorkflowTest, MkDirAsyncTest) { XrdCl::Access::Mode::UX | XrdCl::Access::Mode::GR | XrdCl::Access::Mode::GW | XrdCl::Access::Mode::GX; - auto &&t = Async( MkDir( fs, "/data/MkDirAsyncTest", XrdCl::MkDirFlags::None, access ) >> mkdirTask | - RmDir( fs, "/data/MkDirAsyncTest" ) + auto &&t = Async( MkDir( fs, asyncTestFile, XrdCl::MkDirFlags::None, access ) >> mkdirTask | + RmDir( fs, asyncTestFile ) ); EXPECT_TRUE(t.get().status == stOK); @@ -916,7 +931,10 @@ TEST(WorkflowTest, CheckpointTest) { "czarna ma skore ten nasz kolezka\n" "Uczy sie pilnie przez cale ranki\n" "Ze swej murzynskiej pierwszej czytanki."; - std::string url = "root://localhost//data/chkpttest.txt"; + + std::string chkpttestFile = GetPath("chkpttest.txt"); + std::string serverName = (GetAddress()).GetURL(); + std::string url = serverName + chkpttestFile; GTEST_ASSERT_XRDST( WaitFor( Open( f1, url, OpenFlags::New | OpenFlags::Write ) | Write( f1, 0, sizeof( data ), data ) | @@ -965,6 +983,6 @@ TEST(WorkflowTest, CheckpointTest) { // Now clean up //--------------------------------------------------------------------------- FileSystem fs( url ); - GTEST_ASSERT_XRDST( WaitFor( Rm( fs, "/data/chkpttest.txt" ) ) ); + GTEST_ASSERT_XRDST( WaitFor( Rm( fs, chkpttestFile ) ) ); } diff --git a/tests/XrdCl/XrdClPostMasterTest.cc b/tests/XrdCl/XrdClPostMasterTest.cc index 5677379b23c..f5427aa8299 100644 --- a/tests/XrdCl/XrdClPostMasterTest.cc +++ b/tests/XrdCl/XrdClPostMasterTest.cc @@ -490,26 +490,28 @@ namespace //---------------------------------------------------------------------------- // Create a ping message //---------------------------------------------------------------------------- - XrdCl::Message *CreatePing( char streamID1, char streamID2 ) - { - using namespace XrdCl; - Message *m = new Message(); - m->Allocate( sizeof( ClientPingRequest ) ); - m->Zero(); - - ClientPingRequest *request = (ClientPingRequest *)m->GetBuffer(); - request->streamid[0] = streamID1; - request->streamid[1] = streamID2; - request->requestid = kXR_ping; - XRootDTransport::MarshallRequest( m ); - return m; - } + // XrdCl::Message *CreatePing( char streamID1, char streamID2 ) + // { + // using namespace XrdCl; + // Message *m = new Message(); + // m->Allocate( sizeof( ClientPingRequest ) ); + // m->Zero(); + + // ClientPingRequest *request = (ClientPingRequest *)m->GetBuffer(); + // request->streamid[0] = streamID1; + // request->streamid[1] = streamID2; + // request->requestid = kXR_ping; + // XRootDTransport::MarshallRequest( m ); + // return m; + // } } //------------------------------------------------------------------------------ // Connection test //------------------------------------------------------------------------------ + +#if 0 TEST(PostMasterTest, MultiIPConnectionTest) { using namespace XrdCl; @@ -570,3 +572,4 @@ TEST(PostMasterTest, MultiIPConnectionTest) EXPECT_TRUE( resp->hdr.status == kXR_ok ); EXPECT_TRUE( m2.GetSize() == 8 ); } +#endif \ No newline at end of file diff --git a/tests/common/TestEnv.cc b/tests/common/TestEnv.cc index ed526347265..3e64307de2a 100644 --- a/tests/common/TestEnv.cc +++ b/tests/common/TestEnv.cc @@ -30,19 +30,27 @@ XrdCl::Log *TestEnv::sLog = 0; //------------------------------------------------------------------------------ TestEnv::TestEnv() { - PutString( "MainServerURL", "localhost:1094" ); - PutString( "Manager1URL", "man1:1094" ); - PutString( "Manager2URL", "man2:1094" ); - PutString( "DiskServerURL", "localhost:1094" ); + PutString( "MainServerURL", "localhost:10940" ); + PutString( "Manager1URL", "localhost:10941" ); + PutString( "Manager2URL", "localhost:10942" ); + PutString( "Server1URL", "localhost:10943" ); + PutString( "Server2URL", "localhost:10944" ); + PutString( "Server3URL", "localhost:10945" ); + PutString( "Server4URL", "localhost:10946" ); + PutString( "DiskServerURL", "localhost:10940" ); PutString( "DataPath", "/data" ); PutString( "RemoteFile", "/data/cb4aacf1-6f28-42f2-b68a-90a73460f424.dat" ); PutString( "LocalFile", "/data/testFile.dat" ); PutString( "MultiIPServerURL", "multiip:1099" ); - + PutString( "LocalDataPath", "../XRootD/cluster/xrd-data" ); ImportString( "MainServerURL", "XRDTEST_MAINSERVERURL" ); ImportString( "DiskServerURL", "XRDTEST_DISKSERVERURL" ); ImportString( "Manager1URL", "XRDTEST_MANAGER1URL" ); ImportString( "Manager2URL", "XRDTEST_MANAGER2URL" ); + ImportString( "Server1URL", "XRDTEST_SERVER1URL" ); + ImportString( "Server2URL", "XRDTEST_SERVER2URL" ); + ImportString( "Server3URL", "XRDTEST_SERVER3URL" ); + ImportString( "Server4URL", "XRDTEST_SERVER4URL" ); ImportString( "DataPath", "XRDTEST_DATAPATH" ); ImportString( "LocalFile", "XRDTEST_LOCALFILE" ); ImportString( "RemoteFile", "XRDTEST_REMOTEFILE" ); From 92c66a8f473bf834bb7ab5809c9423f0b9089cce Mon Sep 17 00:00:00 2001 From: Angelo Galavotti Date: Mon, 28 Aug 2023 13:30:29 +0000 Subject: [PATCH 288/442] [Tests] Enable tests to run in parallel --- tests/XRootD/CMakeLists.txt | 2 +- tests/XRootD/cluster/smoketest-clustered.sh | 6 +++ tests/XrdCl/CMakeLists.txt | 45 ++++++++++++++++++--- 3 files changed, 47 insertions(+), 6 deletions(-) diff --git a/tests/XRootD/CMakeLists.txt b/tests/XRootD/CMakeLists.txt index 4439cee9f55..9af40697dac 100644 --- a/tests/XRootD/CMakeLists.txt +++ b/tests/XRootD/CMakeLists.txt @@ -8,7 +8,7 @@ if (UID EQUAL 0) return() endif() -set(XRD_TEST_PORT "10940" CACHE STRING "Port for XRootD Test Server") +set(XRD_TEST_PORT "11940" CACHE STRING "Port for XRootD Test Server") list(APPEND XRDENV "XRDCP=$") list(APPEND XRDENV "XRDFS=$") diff --git a/tests/XRootD/cluster/smoketest-clustered.sh b/tests/XRootD/cluster/smoketest-clustered.sh index aa4229d31db..839db62f2ff 100755 --- a/tests/XRootD/cluster/smoketest-clustered.sh +++ b/tests/XRootD/cluster/smoketest-clustered.sh @@ -1,5 +1,11 @@ #!/usr/bin/env bash +# macOS, as of now, cannot run this test because of the 'declare -A' +# command that we use later, so we just skip this test (sorry apple users) +if [[ $(uname) == "Darwin" ]]; then + exit 0 +fi + # we probably need all of these still : ${ADLER32:=$(command -v xrdadler32)} : ${CRC32C:=$(command -v xrdcrc32c)} diff --git a/tests/XrdCl/CMakeLists.txt b/tests/XrdCl/CMakeLists.txt index ca8c59e560c..9208fb3856b 100644 --- a/tests/XrdCl/CMakeLists.txt +++ b/tests/XrdCl/CMakeLists.txt @@ -1,4 +1,3 @@ - add_executable(xrdcl-unit-tests XrdClURL.cc XrdClPoller.cc @@ -36,10 +35,10 @@ endif() add_executable(xrdcl-cluster-tests IdentityPlugIn.cc - XrdClFileTest.cc - XrdClFileCopyTest.cc - XrdClFileSystemTest.cc - XrdClOperationsWorkflowTest.cc + # XrdClFileTest.cc + # XrdClFileCopyTest.cc + # XrdClFileSystemTest.cc + # XrdClOperationsWorkflowTest.cc XrdClLocalFileHandlerTest.cc XrdClPostMasterTest.cc XrdClThreadingTest.cc @@ -64,3 +63,39 @@ target_include_directories(xrdcl-cluster-tests gtest_discover_tests(xrdcl-cluster-tests TEST_PREFIX XrdCl:: PROPERTIES DEPENDS XRootD_Cluster FIXTURES_REQUIRED XRootD_Cluster) + +# tests to be run in serial, otherwise they are problematic +set(SERIAL_TESTS_FILES + XrdClFileTest.cc + XrdClFileCopyTest.cc + XrdClFileSystemTest.cc + XrdClOperationsWorkflowTest.cc +) + +# create a separate executable target for each "problematic" test suite +foreach(i ${SERIAL_TESTS_FILES}) + add_executable(${i}-tests + ${i} + IdentityPlugIn.cc + ../common/Server.cc + ../common/Utils.cc + ../common/TestEnv.cc + ) + + target_link_libraries(${i}-tests + XrdCl + XrdXml + XrdUtils + ZLIB::ZLIB + GTest::GTest + GTest::Main + ) + + target_include_directories(${i}-tests + PRIVATE ${CMAKE_SOURCE_DIR}/src ../common + ) + + gtest_discover_tests(${i}-tests TEST_PREFIX XrdCl:: + PROPERTIES DEPENDS XRootD_Cluster FIXTURES_REQUIRED XRootD_Cluster RUN_SERIAL 1) + +endforeach() From f14b653153cc0d062209744d105ad20304b01e5c Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 19 Oct 2023 11:35:00 +0200 Subject: [PATCH 289/442] [Tests] Reduce time to run test suite by reducing wait times Before: $ ctest -j16 ... 100% tests passed, 0 tests failed out of 120 Total Test time (real) = 308.54 sec After: $ ctest -j16 ... 100% tests passed, 0 tests failed out of 120 Total Test time (real) = 278.48 sec --- tests/XrdCl/XrdClPoller.cc | 2 +- tests/XrdCl/XrdClPostMasterTest.cc | 8 ++++---- tests/XrdCl/XrdClThreadingTest.cc | 2 +- tests/XrdCl/XrdClUtilsTest.cc | 7 +++---- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/tests/XrdCl/XrdClPoller.cc b/tests/XrdCl/XrdClPoller.cc index cb6b669c6a9..ceeabfed28f 100644 --- a/tests/XrdCl/XrdClPoller.cc +++ b/tests/XrdCl/XrdClPoller.cc @@ -238,7 +238,7 @@ TEST(PollerTest, FunctionTest) // All the business happens elsewhere so we have nothing better to do // here that wait, otherwise server->stop will hang. //---------------------------------------------------------------------------- - ::sleep(5); + ::sleep(1); //---------------------------------------------------------------------------- // Cleanup diff --git a/tests/XrdCl/XrdClPostMasterTest.cc b/tests/XrdCl/XrdClPostMasterTest.cc index f5427aa8299..9de000b494f 100644 --- a/tests/XrdCl/XrdClPostMasterTest.cc +++ b/tests/XrdCl/XrdClPostMasterTest.cc @@ -305,7 +305,7 @@ TEST(PostMasterTest, FunctionalTest) Env *env = DefaultEnv::GetEnv(); Env *testEnv = TestEnv::GetEnv(); env->PutInt( "TimeoutResolution", 1 ); - env->PutInt( "ConnectionWindow", 15 ); + env->PutInt( "ConnectionWindow", 5 ); PostMasterFetch pmfetch; PostMaster *postMaster = pmfetch.Get(); @@ -316,7 +316,7 @@ TEST(PostMasterTest, FunctionalTest) //---------------------------------------------------------------------------- // Send a message and wait for the answer //---------------------------------------------------------------------------- - time_t expires = ::time(0)+1200; + time_t expires = ::time(0)+60; Message m1, m2; URL host( address ); @@ -351,7 +351,7 @@ TEST(PostMasterTest, FunctionalTest) SyncMsgHandler msgHandler2; msgHandler2.SetFilter( 1, 2 ); - time_t shortexp = ::time(0) + 3; + time_t shortexp = ::time(0) + 1; msgHandler2.SetExpiration( shortexp ); GTEST_ASSERT_XRDST( postMaster->Send( localhost1, &m1, &msgHandler2, false, shortexp ) ); @@ -572,4 +572,4 @@ TEST(PostMasterTest, MultiIPConnectionTest) EXPECT_TRUE( resp->hdr.status == kXR_ok ); EXPECT_TRUE( m2.GetSize() == 8 ); } -#endif \ No newline at end of file +#endif diff --git a/tests/XrdCl/XrdClThreadingTest.cc b/tests/XrdCl/XrdClThreadingTest.cc index 16d11ddb47a..d592be8597f 100644 --- a/tests/XrdCl/XrdClThreadingTest.cc +++ b/tests/XrdCl/XrdClThreadingTest.cc @@ -286,7 +286,7 @@ void forkAndRead( ThreadData *data ) XrdCl::Log *log = XrdClTests::TestEnv::GetLog(); for( int chld = 0; chld < 5; ++chld ) { - sleep(10); + sleep(1); pid_t pid; log->Debug( 1, "About to fork" ); GTEST_ASSERT_ERRNO( (pid=fork()) != -1 ); diff --git a/tests/XrdCl/XrdClUtilsTest.cc b/tests/XrdCl/XrdClUtilsTest.cc index fcbfeb8b4d1..b78d0877327 100644 --- a/tests/XrdCl/XrdClUtilsTest.cc +++ b/tests/XrdCl/XrdClUtilsTest.cc @@ -156,11 +156,10 @@ TEST(UtilsTest, TaskManagerTest) ::sleep( 6 ); taskMan.UnregisterTask( tsk2 ); + ::sleep( 1 ); - ::sleep( 2 ); - - EXPECT_TRUE( runs1.size() == 1 ); - EXPECT_TRUE( runs2.size() == 3 ); + EXPECT_EQ( runs1.size(), 1 ); + EXPECT_EQ( runs2.size(), 3 ); EXPECT_TRUE( taskMan.Stop() ); } From 2db2cd5d8db3954964d723d8d46a643ef28404bd Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 19 Oct 2023 15:37:25 +0200 Subject: [PATCH 290/442] [Tests] Use GTest's SetUp() and TearDown() in ZipTest In ExtractTest, zip_file.CloseArchive(NULL, timeout) was called without waiting for the result, so the test instance (hence the zip_file object) would get destroyed before the completion handler could run. This led to the completion handler trying to access invalid memory, since zip_file is gone by the time it runs. Found by running the tests with -DASAN=TRUE. --- tests/XrdCl/XrdClZip.cc | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/tests/XrdCl/XrdClZip.cc b/tests/XrdCl/XrdClZip.cc index 3715da269e8..19b1ed1d425 100644 --- a/tests/XrdCl/XrdClZip.cc +++ b/tests/XrdCl/XrdClZip.cc @@ -34,14 +34,14 @@ using namespace XrdCl; //------------------------------------------------------------------------------ class ZipTest: public ::testing::Test { public: - void Init(); + void SetUp() override; + void TearDown() override; std::string archiveUrl; std::string testFileUrl; ZipArchive zip_file; }; -void ZipTest::Init(){ - using namespace XrdCl; +void ZipTest::SetUp(){ Env *testEnv = TestEnv::GetEnv(); std::string address; @@ -54,41 +54,36 @@ void ZipTest::Init(){ archiveUrl = address + "/" + path; std::string testFilePath = dataPath + "/san_martino.txt"; testFileUrl = address + "/" + testFilePath; + + GTEST_ASSERT_XRDST(WaitFor(OpenArchive(zip_file, archiveUrl, OpenFlags::Read))); +} + +void ZipTest::TearDown() +{ + GTEST_ASSERT_XRDST(WaitFor(CloseArchive(zip_file))); } TEST_F(ZipTest, ExtractTest) { - Init(); - uint16_t timeout = 2; - GTEST_ASSERT_XRDST(zip_file.OpenArchive(archiveUrl, OpenFlags::Read, NULL, timeout)); - GTEST_ASSERT_XRDST(zip_file.CloseArchive(NULL, timeout)); + /* intentionally empty, just let SetUp() and TearDown() do the work */ } TEST_F(ZipTest, OpenFileTest){ - Init(); - GTEST_ASSERT_XRDST(WaitFor(OpenArchive(zip_file, archiveUrl, OpenFlags::Read))) GTEST_ASSERT_XRDST(zip_file.OpenFile("paper.txt", OpenFlags::Read)); // get stat info for the given file StatInfo* info_out; GTEST_ASSERT_XRDST(zip_file.Stat("paper.txt", info_out)); GTEST_ASSERT_XRDST(zip_file.CloseFile()); GTEST_ASSERT_XRDST_NOTOK(zip_file.OpenFile("gibberish.txt", OpenFlags::Read), errNotFound); - GTEST_ASSERT_XRDST(WaitFor(CloseArchive(zip_file))); } TEST_F(ZipTest, ListFileTest) { - Init(); - GTEST_ASSERT_XRDST(WaitFor(OpenArchive(zip_file, archiveUrl, OpenFlags::Read))); DirectoryList* dummy_list; GTEST_ASSERT_XRDST(zip_file.List(dummy_list)); EXPECT_TRUE(dummy_list != NULL); - GTEST_ASSERT_XRDST(WaitFor(CloseArchive(zip_file))); } TEST_F(ZipTest, GetterTests) { - Init(); - // Get file - GTEST_ASSERT_XRDST(WaitFor(OpenArchive(zip_file, archiveUrl, OpenFlags::Read))); File* file = NULL; file = &(zip_file.GetFile()); EXPECT_TRUE(file != NULL); @@ -100,4 +95,4 @@ TEST_F(ZipTest, GetterTests) { // Get offset (i.e. byte position in the archive) uint64_t offset; GTEST_ASSERT_XRDST(zip_file.GetOffset("paper.txt", offset)); -} \ No newline at end of file +} From 70c08a4937cc34fe42c15244c28fda869f643426 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 23 Oct 2023 15:44:14 +0200 Subject: [PATCH 291/442] [Tests] Fix memory leak in LocalFileHandlerTest --- tests/XrdCl/XrdClLocalFileHandlerTest.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/XrdCl/XrdClLocalFileHandlerTest.cc b/tests/XrdCl/XrdClLocalFileHandlerTest.cc index 9232ae5011d..80019fc5d1f 100644 --- a/tests/XrdCl/XrdClLocalFileHandlerTest.cc +++ b/tests/XrdCl/XrdClLocalFileHandlerTest.cc @@ -19,6 +19,7 @@ #include "GTestXrdHelpers.hh" #include "XrdCl/XrdClFile.hh" +#include #include #include #include @@ -448,7 +449,9 @@ TEST_F(LocalFileHandlerTest, XAttrTest) std::string localDataPath; EXPECT_TRUE( testEnv->GetString( "LocalDataPath", localDataPath ) ); - localDataPath = realpath(localDataPath.c_str(), NULL); + char resolved_path[PATH_MAX]; + localDataPath = realpath(localDataPath.c_str(), resolved_path); + std::string targetURL = localDataPath + "/metaman/lfilehandlertestfilexattr"; CreateTestFileFunc( targetURL ); From 0295178227f341b67c48a503bd7c2f6359917812 Mon Sep 17 00:00:00 2001 From: Mattias Ellert Date: Tue, 31 Oct 2023 09:45:58 +0100 Subject: [PATCH 292/442] The installed cmake files are now generated and contain architecture dependent information. Installing them in datadir (/usr/share) now results in conflicts if packages for more than one architecture is installed in parallel. Move the installed cmake files to libdir (/usr/lib(64)?, /usr/lib/). Found by Debian's MultiArch hinter. --- CMakeLists.txt | 4 ++-- packaging/debian/libxrootd-dev.install | 2 +- packaging/rhel/xrootd.spec.in | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 36a7f62b570..438ff5666e8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,7 +68,7 @@ configure_package_config_file(cmake/${PROJECT_NAME}Config.cmake.in cmake/${PROJE INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX} INSTALL_DESTINATION - ${CMAKE_INSTALL_DATADIR}/xrootd/cmake + ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} PATH_VARS CMAKE_INSTALL_INCLUDEDIR CMAKE_INSTALL_LIBDIR @@ -76,7 +76,7 @@ configure_package_config_file(cmake/${PROJECT_NAME}Config.cmake.in cmake/${PROJE ) install(DIRECTORY ${PROJECT_BINARY_DIR}/cmake/ - DESTINATION ${CMAKE_INSTALL_DATADIR}/xrootd/cmake) + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}) #------------------------------------------------------------------------------- # Configure an 'uninstall' target diff --git a/packaging/debian/libxrootd-dev.install b/packaging/debian/libxrootd-dev.install index c9241223538..e6ffeaa6bea 100644 --- a/packaging/debian/libxrootd-dev.install +++ b/packaging/debian/libxrootd-dev.install @@ -13,4 +13,4 @@ usr/lib/*/libXrdCrypto.so usr/lib/*/libXrdCryptoLite.so usr/lib/*/libXrdUtils.so usr/lib/*/libXrdXml.so -usr/share/xrootd/cmake/XRootDConfig.cmake +usr/lib/*/cmake/XRootD diff --git a/packaging/rhel/xrootd.spec.in b/packaging/rhel/xrootd.spec.in index 449de06a38c..4a6af8f1808 100644 --- a/packaging/rhel/xrootd.spec.in +++ b/packaging/rhel/xrootd.spec.in @@ -919,7 +919,7 @@ fi %{_libdir}/libXrdUtils.so %{_libdir}/libXrdXml.so %{_includedir}/xrootd/XrdXml/XrdXmlReader.hh -%{_datadir}/xrootd/cmake +%{_libdir}/cmake/XRootD %files client-libs %defattr(-,root,root,-) From 80f4d4c0e18f3c72b238089ad3cf7ce390a14a15 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 28 Aug 2023 16:18:33 +0200 Subject: [PATCH 293/442] [DEB] Modernize Debian packaging for XRootD The supported OSs are Debian 11 and Ubuntu 22.04 or later, as stated in the top-level README.md. Based on downstream Debian packaging, but with some important differences, such as added support for erasure coding, running tests as part of the build, and using a separate step to install Python bindings, as the standard installation goes into the wrong destination on Debian 11 (/usr/lib/python3.X/site-packages instead of /usr/lib/python3/dist-packages). --- packaging/debian/compat | 2 +- packaging/debian/control | 669 +++++++++--------- packaging/debian/copyright | 346 ++++++++- packaging/debian/libxrdapputils2.install | 1 + packaging/debian/libxrdcephposix0.install | 1 + packaging/debian/libxrdcl3.install | 1 + packaging/debian/libxrdcrypto2.install | 1 + packaging/debian/libxrdcryptolite2.install | 1 + packaging/debian/libxrdec1.install | 1 + packaging/debian/libxrdffs3.install | 1 + packaging/debian/libxrdhttputils2.install | 1 + packaging/debian/libxrdposix3.install | 4 + .../debian/libxrdposix3.lintian-overrides | 3 + packaging/debian/libxrdserver3.install | 1 + packaging/debian/libxrdssilib2.install | 1 + packaging/debian/libxrdssishmap2.install | 1 + packaging/debian/libxrdutils3.install | 1 + packaging/debian/libxrdxml3.install | 1 + packaging/debian/libxrootd-client-dev.install | 13 +- packaging/debian/libxrootd-dev.install | 32 +- .../debian/libxrootd-private-dev.install | 6 +- packaging/debian/libxrootd-server-dev.install | 19 +- packaging/debian/python3-xrootd.install | 4 +- packaging/debian/python3-xrootd.install.new | 1 - packaging/debian/rules | 110 ++- packaging/debian/xrootd-ceph-plugins.install | 2 + .../xrootd-ceph-plugins.lintian-overrides | 2 + .../debian/xrootd-client-http-plugins.install | 2 + ...ootd-client-http-plugins.lintian-overrides | 2 + .../debian/xrootd-client-plugins.install | 9 +- .../xrootd-client-plugins.lintian-overrides | 2 + packaging/debian/xrootd-client.install | 18 + packaging/debian/xrootd-doc.install | 2 + packaging/debian/xrootd-fuse.install | 5 +- packaging/debian/xrootd-plugins.install | 15 +- .../debian/xrootd-plugins.lintian-overrides | 5 + .../debian/xrootd-scitokens-plugins.docs | 1 + .../debian/xrootd-scitokens-plugins.install | 1 + ...xrootd-scitokens-plugins.lintian-overrides | 2 + .../debian/xrootd-server-plugins.install | 30 +- .../xrootd-server-plugins.lintian-overrides | 2 + packaging/debian/xrootd-server.install | 54 +- packaging/debian/xrootd-server.postinst | 21 + packaging/debian/xrootd-voms-plugins.install | 5 + .../xrootd-voms-plugins.lintian-overrides | 2 + 45 files changed, 958 insertions(+), 446 deletions(-) create mode 100644 packaging/debian/libxrdapputils2.install create mode 100644 packaging/debian/libxrdcephposix0.install create mode 100644 packaging/debian/libxrdcl3.install create mode 100644 packaging/debian/libxrdcrypto2.install create mode 100644 packaging/debian/libxrdcryptolite2.install create mode 100644 packaging/debian/libxrdec1.install create mode 100644 packaging/debian/libxrdffs3.install create mode 100644 packaging/debian/libxrdhttputils2.install create mode 100644 packaging/debian/libxrdposix3.install create mode 100644 packaging/debian/libxrdposix3.lintian-overrides create mode 100644 packaging/debian/libxrdserver3.install create mode 100644 packaging/debian/libxrdssilib2.install create mode 100644 packaging/debian/libxrdssishmap2.install create mode 100644 packaging/debian/libxrdutils3.install create mode 100644 packaging/debian/libxrdxml3.install delete mode 100644 packaging/debian/python3-xrootd.install.new create mode 100644 packaging/debian/xrootd-ceph-plugins.install create mode 100644 packaging/debian/xrootd-ceph-plugins.lintian-overrides create mode 100644 packaging/debian/xrootd-client-http-plugins.install create mode 100644 packaging/debian/xrootd-client-http-plugins.lintian-overrides create mode 100644 packaging/debian/xrootd-client-plugins.lintian-overrides create mode 100644 packaging/debian/xrootd-doc.install create mode 100644 packaging/debian/xrootd-plugins.lintian-overrides create mode 100644 packaging/debian/xrootd-scitokens-plugins.docs create mode 100644 packaging/debian/xrootd-scitokens-plugins.install create mode 100644 packaging/debian/xrootd-scitokens-plugins.lintian-overrides create mode 100644 packaging/debian/xrootd-server-plugins.lintian-overrides create mode 100644 packaging/debian/xrootd-server.postinst create mode 100644 packaging/debian/xrootd-voms-plugins.install create mode 100644 packaging/debian/xrootd-voms-plugins.lintian-overrides diff --git a/packaging/debian/compat b/packaging/debian/compat index f599e28b8ab..b1bd38b62a0 100644 --- a/packaging/debian/compat +++ b/packaging/debian/compat @@ -1 +1 @@ -10 +13 diff --git a/packaging/debian/control b/packaging/debian/control index 42f1ac24d64..f5db17708bd 100644 --- a/packaging/debian/control +++ b/packaging/debian/control @@ -1,395 +1,406 @@ Source: xrootd -Maintainer: Jozsef Makai Section: net Priority: optional -Standards-Version: 3.9.3 -Build-Depends: debhelper (>= 9), cmake (>=3.3.0), zlib1g-dev, libfuse-dev, python3-dev, libssl-dev, libxml2-dev, ncurses-dev, libkrb5-dev, libreadline-dev, libsystemd-dev, selinux-policy-dev, libcurl4-openssl-dev, systemd, uuid-dev, dh-python, voms-dev, davix-dev, python3-pip, python3-setuptools, pkgconf -Homepage: https://github.com/xrootd/xrootd -Vcs-Git: https://github.com/xrootd/xrootd.git +Standards-Version: 4.6.2 +Build-Depends: + debhelper (>= 13), + dh-python, + cmake, + libgtest-dev, + libcppunit-dev, + libisal-dev, + pkg-config, + libfuse-dev [linux-any kfreebsd-any], + libkrb5-dev, + libcurl4-openssl-dev, + libtinyxml-dev, + libxml2-dev, + ncurses-dev, + libssl-dev, + libreadline-dev, + zlib1g-dev, + libsystemd-dev [linux-any], + python3-dev, + python3-pip, + python3-wheel, + libjson-c-dev, + libmacaroons-dev, + uuid-dev, + voms-dev, + libscitokens-dev, + davix-dev, + librados-dev [i386 amd64 armel armhf arm64 mips mipsel mips64el powerpc ppc64el riscv64 s390x], + libradospp-dev [i386 amd64 armel armhf arm64 mips mipsel mips64el powerpc ppc64el riscv64 s390x], + libradosstriper-dev [i386 amd64 armel armhf arm64 mips mipsel mips64el powerpc ppc64el riscv64 s390x] +Build-Depends-Indep: + dh-sequence-sphinxdoc, + doxygen, + graphviz, + python3-sphinx +Homepage: http://xrootd.org/ +Maintainer: XRootD Developers Vcs-Browser: https://github.com/xrootd/xrootd +Vcs-Git: https://github.com/xrootd/xrootd.git -#------------------------------------------------------------------------------ -# XRootD libraries (compatibility) -#------------------------------------------------------------------------------ -Package: xrootd-libs +Package: xrootd-server Architecture: any -Section: libs -Depends: libxrdapputils1 (=${binary:Version}), - libxrdcrypto1 (=${binary:Version}), - libxrdcryptolite1 (=${binary:Version}), - libxrdutils2 (=${binary:Version}), - libxrdxml2 (=${binary:Version}), - xrootd-plugins (=${binary:Version}) -Conflicts: xrootd-libs (<<${binary:Version}) -Description: This package contains libraries used by the xrootd servers and - clients. - . - This is a transitional dummy package which can be removed - afterinstallation of dependencies. +Multi-Arch: foreign +Section: net +Depends: + xrootd-plugins (= ${binary:Version}), + xrootd-server-plugins (= ${binary:Version}), + expect, + logrotate, + adduser, + ${perl:Depends}, + ${shlibs:Depends}, + ${misc:Depends} +Description: Extended ROOT file server + The Extended root file server consists of a file server called xrootd + and a cluster management server called cmsd. + . + The xrootd server was developed for the root analysis framework to + serve root files. However, the server is agnostic to file types and + provides POSIX-like access to any type of file. + . + The cmsd server is the next generation version of the olbd server, + originally developed to cluster and load balance Objectivity/DB AMS + database servers. It provides enhanced capability along with lower + latency and increased throughput. -#------------------------------------------------------------------------------ -# XRootD plug-ins -#------------------------------------------------------------------------------ -Package: xrootd-plugins +Package: libxrdapputils2 Architecture: any -Section: net -Depends: ${shlibs:Depends} -Conflicts: xrootd-plugins (<<${binary:Version}) -Description: This package contains additional plugins used by both client and - server. +Multi-Arch: same +Section: libs +Depends: + ${shlibs:Depends}, + ${misc:Depends} +Description: Utilities library for xrootd applications + This package contains the xrootd utilities library for applications. -#------------------------------------------------------------------------------ -# XRootD development files (compatibility) -#------------------------------------------------------------------------------ -Package: xrootd-devel +Package: libxrdcrypto2 Architecture: any -Section: libdevel -Depends: libxrootd-dev (=${binary:Version}) -Conflicts: xrootd-devel (<<${binary:Version}) -Description: This package contains header files and development libraries for - xrootd development. - . - This is a transitional dummy package which can be removed after - installation of dependencies +Multi-Arch: same +Section: libs +Depends: + ${shlibs:Depends}, + ${misc:Depends} +Description: Cryptograpic library for xrootd + This package contains the xrootd cryptograpic library. -#------------------------------------------------------------------------------ -# XRootD development files -#------------------------------------------------------------------------------ -Package: libxrootd-dev +Package: libxrdcryptolite2 Architecture: any -Section: libdevel -Depends: libxrdapputils1 (=${binary:Version}), - libxrdcrypto1 (=${binary:Version}), - libxrdcryptolite1 (=${binary:Version}), - libxrdutils2 (=${binary:Version}), - libxrdxml2 (=${binary:Version}), -Conflicts: xrootd-devel (<<${binary:Version}) -Description: This package contains header files and development libraries for - xrootd development. +Multi-Arch: same +Section: libs +Depends: + ${shlibs:Depends}, + ${misc:Depends} +Description: Light version of cryptograpic library for xrootd + This package contains the light version of the xrootd cryptograpic library. -#------------------------------------------------------------------------------ -# XRootD client libraries (compatibility) -#------------------------------------------------------------------------------ -Package: xrootd-client-libs +Package: libxrdutils3 Architecture: any -Depends: libxrdcl2 (=${binary:Version}), - libxrdffs2 (=${binary:Version}), - libxrdposix2 (=${binary:Version}), - xrootd-client-plugins (=${binary:Version}), - xrootd-libs (=${binary:Version}) -Conflicts: xrootd-client-libs (<<${binary:Version}) -Description: This package contains libraries used by xrootd clients. - . - This is a transitional dummy package which can be removed after - installation of dependencies +Multi-Arch: same +Section: libs +Depends: + ${shlibs:Depends}, + ${misc:Depends} +Description: Utilities library for xrootd + This package contains the xrootd utilities library. -#------------------------------------------------------------------------------ -# XRootD client plug-ins -#------------------------------------------------------------------------------ -Package: xrootd-client-plugins +Package: libxrdxml3 Architecture: any -Section: net -Depends: ${shlibs:Depends} -Conflicts: xrootd-client-plugins (<<${binary:Version}) -Description: This package contains additional plugins used by the client. +Multi-Arch: same +Section: libs +Depends: + ${shlibs:Depends}, + ${misc:Depends} +Description: XML library for xrootd + This package contains the xrootd XML library. -#------------------------------------------------------------------------------ -# XRootD client development files (compatibility) -#------------------------------------------------------------------------------ -Package: xrootd-client-devel +Package: xrootd-plugins Architecture: any -Section: libdevel -Depends: libxrootd-client-dev (=${binary:Version}) -Conflicts: xrootd-client-devel (<<${binary:Version}) -Description: This package contains header files and development libraries for - xrootd client development. - . - This is a transitional dummy package which can be removed after - installation of dependencies +Multi-Arch: same +Section: libs +Depends: + ${shlibs:Depends}, + ${misc:Depends} +Description: Plugins used by xrootd servers and clients + This package contains plugins used by the xrootd servers and clients. -#------------------------------------------------------------------------------ -# XRootD client development files -#------------------------------------------------------------------------------ -Package: libxrootd-client-dev +Package: libxrootd-dev Architecture: any +Multi-Arch: same Section: libdevel -Depends: ${shlibs:Depends}, - libxrootd-dev (=${binary:Version}), - libxrdcl2 (=${binary:Version}), - libxrdffs2 (=${binary:Version}), - libxrdposix2 (=${binary:Version}) -Conflicts: xrootd-client-devel (<<${binary:Version}) -Description: This package contains header files and development libraries for - xrootd client development. +Depends: + libxrdapputils2 (= ${binary:Version}), + libxrdcrypto2 (= ${binary:Version}), + libxrdcryptolite2 (= ${binary:Version}), + libxrdutils3 (= ${binary:Version}), + libxrdxml3 (= ${binary:Version}), + ${misc:Depends} +Description: Development files for xrootd + This package contains header files and development libraries for xrootd + development. -#------------------------------------------------------------------------------ -# XRootD client (compatibility) -#------------------------------------------------------------------------------ -Package: xrootd-client +Package: libxrdcl3 Architecture: any -Section: net -Depends: xrootd-clients (=${binary:Version}) -Conflicts: xrootd-client (<<${binary:Version}) -Description: This package contains the command line tools used to communicate - with xrootd servers. - . - This is a transitional dummy package which can be removed after - installation of dependencies - -#------------------------------------------------------------------------------ -# XRootD client -#------------------------------------------------------------------------------ -Package: xrootd-clients -Architecture: any -Section: net -Depends: ${shlibs:Depends}, - xrootd-client-libs (=${binary:Version}) -Conflicts: xrootd-clients (<<${binary:Version}) -Description: This package contains the command line tools used to communicate - with xrootd servers. +Multi-Arch: same +Section: libs +Depends: + ${shlibs:Depends}, + ${misc:Depends} +Recommends: + xrootd-plugins (= ${binary:Version}), + xrootd-client-plugins(= ${binary:Version}) +Description: Client library for xrootd + This package contains the xrootd client library. -#------------------------------------------------------------------------------ -# XRootD server -#------------------------------------------------------------------------------ -Package: xrootd-server +Package: libxrdec1 Architecture: any -Section: net -Depends: ${shlibs:Depends}, - xrootd-server-libs (=${binary:Version}) -Conflicts: xrootd-server (<<${binary:Version}) -Description: This package contains the server applications and associated - utilities. +Multi-Arch: same +Section: libs +Depends: + ${shlibs:Depends}, + ${misc:Depends} +Recommends: + xrootd-plugins (= ${binary:Version}), + xrootd-client-plugins(= ${binary:Version}) +Description: Client library for xrootd + This package contains the xrootd client library for erasure coding. -#------------------------------------------------------------------------------ -# XRootD private development files (compatibility) -#------------------------------------------------------------------------------ -Package: xrootd-private-devel +Package: libxrdffs3 Architecture: any -Section: libdevel -Depends: libxrootd-private-dev (=${binary:Version}) -Conflicts: xrootd-private-devel (<<${binary:Version}) -Description: This package contains some private xrootd headers. The use of these - headers is strongly discouraged. Backward compatibility between - versions is not guaranteed for these headers. - . - This is a transitional dummy package which can be removed after - installation of dependencies +Multi-Arch: same +Section: libs +Depends: + ${shlibs:Depends}, + ${misc:Depends} +Description: File protocol library for xrootd + This package contains the xrootd file protocol library. -#------------------------------------------------------------------------------ -# XRootD private development files -#------------------------------------------------------------------------------ -Package: libxrootd-private-dev +Package: libxrdposix3 Architecture: any -Section: libdevel -Depends: libxrootd-server-dev (=${binary:Version}), -Conflicts: libxrootd-private-dev (<<${binary:Version}) -Description: This package contains some private XRootd headers. The use of - these headers is strongly discouraged. Backward compatibility - between versions is not guaranteed for these headers. +Multi-Arch: same +Section: libs +Depends: + ${shlibs:Depends}, + ${misc:Depends} +Description: Posix interface library for xrootd + This package contains the xrootd Posix interface library. -#------------------------------------------------------------------------------ -# XRootD server libs (compatibility) -#------------------------------------------------------------------------------ -Package: xrootd-server-libs +Package: libxrdssilib2 Architecture: any +Multi-Arch: same Section: libs -Depends: libxrdhttputils1 (=${binary:Version}), - libxrdserver2 (=${binary:Version}), - libxrdssilib1 (=${binary:Version}), - libxrdssishmap1 (=${binary:Version}), - xrootd-client-libs (=${binary:Version}), - xrootd-server-plugins (=${binary:Version}) -Conflicts: xrootd-server-libs (<<${binary:Version}) -Description: This package contains libraries used by xrootd servers. - . - This is a transitional dummy package which can be removed after - installation of dependencies +Depends: + ${shlibs:Depends}, + ${misc:Depends} +Description: Server internals library for xrootd + This package contains an xrootd server internals library. -#------------------------------------------------------------------------------ -# XRootD server development files (compatibility) -#------------------------------------------------------------------------------ -Package: xrootd-server-devel +Package: libxrdssishmap2 Architecture: any -Section: libdevel -Depends: libxrootd-server-dev (=${binary:Version}) -Conflicts: xrootd-server-devel (<<${binary:Version}) -Description: This package contains header files and development libraries for - xrootd server development. - . - This is a transitional dummy package which can be removed after - installation of dependencies +Multi-Arch: same +Section: libs +Depends: + ${shlibs:Depends}, + ${misc:Depends} +Description: Server internals library for xrootd + This package contains an xrootd server internals library. -#------------------------------------------------------------------------------ -# XRootD server development files -#------------------------------------------------------------------------------ -Package: libxrootd-server-dev +Package: xrootd-client-plugins Architecture: any -Section: libdevel -Depends: libxrdhttputils1 (=${binary:Version}), - libxrdserver2 (=${binary:Version}), - libxrdssilib1 (=${binary:Version}), - libxrdssishmap1 (=${binary:Version}), - libxrootd-client-dev (=${binary:Version}) -Conflicts: libxrootd-server-dev (<<${binary:Version}) -Description: High performance, scalable fault tolerant data repositories. - This package contains header files and development libraries for - xrootd server development. +Multi-Arch: same +Section: libs +Depends: + ${shlibs:Depends}, + ${misc:Depends} +Description: Plugins used by xrootd clients + This package contains plugins used by xrootd clients. -#------------------------------------------------------------------------------ -# XRootD server development files -#------------------------------------------------------------------------------ -Package: xrootd-fuse +Package: xrootd-client-http-plugins Architecture: any -Section: net -Depends: ${shlibs:Depends}, - libfuse-dev, - xrootd-client-libs (=${binary:Version}), -Conflicts: xrootd-fuse (<<${binary:Version}) -Description: This package contains the FUSE (file system in user space) - xrootd mount tool. +Multi-Arch: same +Section: libs +Depends: + ${shlibs:Depends}, + ${misc:Depends} +Description: HTTP client plugin for XRootD client + This package contains an XRootD client plugin which allows XRootD to + interact with HTTP repositories. -#------------------------------------------------------------------------------ -# XRootD server plug-ins -#------------------------------------------------------------------------------ -Package: xrootd-server-plugins +Package: libxrootd-client-dev Architecture: any -Section: net -Depends: ${shlibs:Depends} -Description: This package contains additional plug-in libraries used by an - XRootd server. +Multi-Arch: same +Section: libdevel +Depends: + libxrdcl3 (= ${binary:Version}), + libxrdffs3 (= ${binary:Version}), + libxrdposix3 (= ${binary:Version}), + libxrootd-dev (= ${binary:Version}), + ${misc:Depends} +Description: Development files for xrootd clients + This package contains header files and development libraries for xrootd + client development. -#------------------------------------------------------------------------------ -# XRootD libxrdapputils1 (xroottd-libs) -#------------------------------------------------------------------------------ -Package: libxrdapputils1 +Package: libxrdhttputils2 Architecture: any +Multi-Arch: same Section: libs -Depends: ${shlibs:Depends} -Conflicts: libxrdapputils1 (<<${binary:Version}) -Description: Library of utilities for applications. +Depends: + ${shlibs:Depends}, + ${misc:Depends} +Description: HTTP protocol utilities library for xrootd + This package contains the xrootd HTTP protocol utilities library. -#------------------------------------------------------------------------------ -# XRootD libxrdcl2 (xrootd-client-libs) -#------------------------------------------------------------------------------ -Package: libxrdcl2 +Package: libxrdserver3 Architecture: any +Multi-Arch: same Section: libs -Depends: ${shlibs:Depends} -Conflicts: libxrdcl2 (<<${binary:Version}) -Description: Client library. +Depends: + ${shlibs:Depends}, + ${misc:Depends} +Recommends: + xrootd-plugins (= ${binary:Version}), + xrootd-server-plugins(= ${binary:Version}) +Description: Server library for xrootd + This package contains the xrootd server library. -#------------------------------------------------------------------------------ -# XRootD libxrdcrypto1 (xrootd-libs) -#------------------------------------------------------------------------------ -Package: libxrdcrypto1 -Architecture: any -Section: libs -Depends: ${shlibs:Depends} -Conflicts: libxrdcrypto1 (<<${binary:Version}) -Description: Cryptograpic library. - -#------------------------------------------------------------------------------ -# XRootD libxrdcryptolite1 (xrootd-libs) -#------------------------------------------------------------------------------ -Package: libxrdcryptolite1 +Package: xrootd-server-plugins Architecture: any +Multi-Arch: same Section: libs -Depends: ${shlibs:Depends} -Conflicts: libxrdcryptolite1 (<<${binary:Version}) -Description: Light version of cryptograpic library. +Depends: + ${shlibs:Depends}, + ${misc:Depends} +Description: Plugins used by xrootd servers + This package contains plugins used by xrootd servers. -#------------------------------------------------------------------------------ -# XRootD libxrdffs2 (xrootd-client-libs) -#------------------------------------------------------------------------------ -Package: libxrdffs2 -Architecture: any -Section: libs -Depends: ${shlibs:Depends} -Conflicts: libxrdffs2 (<<${binary:Version}) -Description: File protocol library - -#------------------------------------------------------------------------------ -# XRootD libxrdhttputils1 (xrootd-server-libs) -#------------------------------------------------------------------------------ -Package: libxrdhttputils1 +Package: libxrootd-server-dev Architecture: any -Section: libs -Depends: ${shlibs:Depends} -Conflicts: libxrdhttputils1 (<<${binary:Version}) -Description: Library of utilities for HTTP protocol - -#------------------------------------------------------------------------------ -# XRootD libxrdposix2 (xrootd-client-libs) -#------------------------------------------------------------------------------ -Package: libxrdposix2 +Multi-Arch: same +Section: libdevel +Depends: + libxrdhttputils2 (= ${binary:Version}), + libxrdserver3 (= ${binary:Version}), + libxrootd-dev (= ${binary:Version}), + libxrootd-client-dev (= ${binary:Version}), + ${misc:Depends} +Description: Development files for xrootd servers + This package contains header files and development libraries for xrootd + server development. + +Package: libxrootd-private-dev Architecture: any -Section: libs -Depends: ${shlibs:Depends} -Conflicts: libxrdposix2 (<<${binary:Version}) -Description: Posix interface library. - -#------------------------------------------------------------------------------ -# XRootD libxrdserver2 (xrootd-server-libs) -#------------------------------------------------------------------------------ -Package: libxrdserver2 +Multi-Arch: same +Section: libdevel +Depends: + libxrdssilib2 (= ${binary:Version}), + libxrdssishmap2 (= ${binary:Version}), + libxrootd-dev (= ${binary:Version}), + libxrootd-client-dev (= ${binary:Version}), + libxrootd-server-dev (= ${binary:Version}), + ${misc:Depends} +Description: Private xrootd headers + This package contains some private xrootd headers. Backward and forward + compatibility between versions is not guaranteed for these headers. + +Package: xrootd-client Architecture: any -Section: libs -Depends: ${shlibs:Depends} -Conflicts: libxrdserver2 (<<${binary:Version}) -Description: Server library. +Multi-Arch: foreign +Section: net +Depends: + xrootd-plugins (= ${binary:Version}), + xrootd-client-plugins(= ${binary:Version}), + ${shlibs:Depends}, + ${misc:Depends} +Description: Xrootd command line client tools + This package contains the command line tools used to communicate with + xrootd servers. + +Package: xrootd-fuse +Architecture: linux-any kfreebsd-any +Multi-Arch: foreign +Section: net +Depends: + xrootd-plugins (= ${binary:Version}), + xrootd-client-plugins (= ${binary:Version}), + fuse, + ${shlibs:Depends}, + ${misc:Depends} +Description: Xrootd FUSE tool + This package contains the FUSE (file system in user space) xrootd mount + tool. -#------------------------------------------------------------------------------ -# XRootD libxrdssilib1 (xrootd-server-libs) -#------------------------------------------------------------------------------ -Package: libxrdssilib1 +Package: xrootd-voms-plugins Architecture: any +Multi-Arch: same Section: libs -Depends: ${shlibs:Depends} -Conflicts: libxrdssilib1 (<<${binary:Version}) -Description: Server internals library. +Depends: + ${shlibs:Depends}, + ${misc:Depends} +Description: VOMS attribute extractor plugin for XRootD + This package contains the xrootd VOMS attribute extractor plugin. -#------------------------------------------------------------------------------ -# XRootD libxrdssishmap1 (xrootd-server-libs) -#------------------------------------------------------------------------------ -Package: libxrdssishmap1 +Package: xrootd-scitokens-plugins Architecture: any +Multi-Arch: same Section: libs -Depends: ${shlibs:Depends} -Conflicts: libxrdssishmap1 (<<${binary:Version}) -Description: Server internals library +Depends: + ${shlibs:Depends}, + ${misc:Depends} +Description: SciTokens authorization support for XRootD + This ACC (authorization) plugin for the XRootD framework utilizes the + SciTokens library to validate and extract authorization claims from a + SciToken passed during a transfer. Configured appropriately, this + allows the XRootD server admin to delegate authorization decisions for + a subset of the namespace to an external issuer. -#------------------------------------------------------------------------------ -# XRootD libxrdutils2 (xrootd-libs) -#------------------------------------------------------------------------------ -Package: libxrdutils2 -Architecture: any +Package: libxrdcephposix0 +Architecture: i386 amd64 armel armhf arm64 mips mipsel mips64el powerpc ppc64el riscv64 s390x +Multi-Arch: same Section: libs -Depends: ${shlibs:Depends} -Conflicts: libxrdutils2 (<<${binary:Version}) -Description: Library of utilities - -#------------------------------------------------------------------------------ -# XRootD libxrdxml2 (xrootd-libs) -#------------------------------------------------------------------------------ -Package: libxrdxml2 -Architecture: any +Depends: + ${shlibs:Depends}, + ${misc:Depends} +Description: Ceph posix library for xrootd + This package contains an xrootd library used by the ceph plugins. + +Package: xrootd-ceph-plugins +Architecture: i386 amd64 armel armhf arm64 mips mipsel mips64el powerpc ppc64el riscv64 s390x +Multi-Arch: same Section: libs -Depends: ${shlibs:Depends}, - libxml2 -Conflicts: libxrdxml2 (<<${binary:Version}) -Description: XML library - -#------------------------------------------------------------------------------ -# XRootD python3-xrootd -#------------------------------------------------------------------------------ +Depends: + ${shlibs:Depends}, + ${misc:Depends} +Description: XRootD plugin for interfacing with the Ceph storage platform + The xrootd-ceph is an OSS layer plugin for the XRootD server for + interfacing with the Ceph storage platform. + Package: python3-xrootd Architecture: any +Multi-Arch: foreign Section: python -Depends: ${python3:Depends}, - ${shlibs:Depends}, - xrootd-client-libs (=${binary:Version}) -Conflicts: python3-xrootd (<<${binary:Version}) -Provides: ${python3:Provides} -Description: Python interface +Provides: + ${python3:Provides} +Depends: + xrootd-plugins (= ${binary:Version}), + xrootd-client-plugins (= ${binary:Version}), + ${python3:Depends}, + ${shlibs:Depends}, + ${misc:Depends} +Description: Python 3 bindings for xrootd + This package contains Python 3 bindings for xrootd. - +Package: xrootd-doc +Architecture: all +Multi-Arch: foreign +Section: doc +Depends: + ${sphinxdoc:Depends}, + ${misc:Depends} +Built-Using: + ${sphinxdoc:Built-Using} +Description: Developer documentation for the xrootd libraries + This package contains the API documentation of the xrootd libraries. diff --git a/packaging/debian/copyright b/packaging/debian/copyright index 341c4ec5bbd..c0c64a30374 100644 --- a/packaging/debian/copyright +++ b/packaging/debian/copyright @@ -1,26 +1,320 @@ -Copyright (c) 2005-2012, Board of Trustees of the Leland Stanford, -Jr. University. Produced under contract DE-AC02-76-SF00515 with the -US Department of Energy. All rights reserved. The copyright holder's -institutional names may not be used to endorse or promote products -derived from this software without specific prior "written permission. - -This file is part of the XRootD software suite. - -XRootD is free software: you can redistribute it and/or modify it -under the terms of the GNU Lesser General Public License as published -by the Free Software "Foundation, either version 3 of the License, or -(at your option) any later version. - -XRootD is distributed in the hope that it will be useful, but WITHOUT -ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public -License for more details (/usr/share/common-licenses/LGPL). - -You should have received a copy of the GNU Lesser General Public -License along with XRootD in a file called COPYING.LESSER (LGPL -license) and file COPYING (GPL license). If not, see -. - -Prior to September 2nd, 2012 the XRootD software suite was licensed -under a modified BSD license (see file COPYING.BSD). This applies to -all code that was in the XRootD git repository prior to that date. +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: xrootd +Upstream-Contact: https://xrootd.slac.stanford.edu/ + +Files: * +Copyright: + 2000-2023 Board of Trustees of the Leland Stanford, Jr. University. + Produced under contract DE-AC02-76-SF00515 with the US Department of + Energy. All rights reserved. +License: LGPL-3 and BSD-3-clause-modified + The copyright holder's institutional names may not be used to endorse + or promote products derived from this software without specific prior + written permission. + . + This file is part of the XRootD software suite. + . + XRootD is free software: you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + . + XRootD is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + License for more details. + . + You should have received a copy of the GNU Lesser General Public + License along with XRootD in a file called COPYING.LESSER (LGPL + license) and file COPYING (GPL license). If not, see + . + . + On Debian systems the full text of the GNU lesser general public + license version 3 can be found in /usr/share/common-licenses/LGPL-3. + . + Prior to September 2nd, 2012 the XRootD software suite was licensed + under a modified BSD license shown below. This applies to all code + that was in the XRootD git repository prior to that date. All code is + now licensed under LGPL. + . + Conditions of Use + . + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + . + a. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + b. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + c. Neither the name of the Leland Stanford, Jr. University nor the + names of its contributors may be used to endorse or promote + products derived from this software without specific prior written + permission. + d. Products derived from this software that do not adhere to the + xrootd or cmsd protocol specifications may not use the acronyms + 'cmsd', 'Scalla', 'xroot', and 'xrootd', regardless of + capitalization, to describe such derivative works. + . + DISCLAIMER + . + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + 'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Files: + bindings/python/* + src/XrdApps/XrdClProxyPlugin/* + src/XrdApps/XrdClRecordPlugin/* + src/XrdCeph/* + src/XrdCks/XrdCksCalczcrc32.cc + src/XrdCl/* + src/XrdEc/* + src/XrdHttp/* + src/XrdOssCsi/* + src/XrdOuc/XrdOucCompiler.hh + src/XrdOuc/XrdOucEnum.hh + src/XrdSys/XrdSysKernelBuffer.hh + src/XrdTls/XrdTlsContext.* + src/XrdTls/XrdTlsSocket.* + src/XrdZip/* + tests/common/* + tests/XrdCephTests/* + tests/XrdClTests/* + tests/XrdEcTests/* + utils/xrootd-config +Copyright: + 2011-2023 European Organization for Nuclear Research (CERN) +Comment: + In applying this licence, CERN does not waive the privileges and immunities + granted to it by virtue of its status as an Intergovernmental Organization + or submit itself to any jurisdiction. +License: LGPL-3 + +Files: + src/XrdCks/XrdCksCalcadler32.hh + src/XrdOuc/XrdOucCRC32C.* +Copyright: + 1995-1998, 2013, 2015 Mark Adler +License: zlib/libpng + +Files: + src/XrdCl/XrdClLocalFileHandler.* + src/XrdCl/XrdClLocalFileTask.* + src/XrdCms/XrdCmsRedirLocal.* + tests/XrdClTests/LocalFileHandlerTest.cc +Copyright: + 2017, 2019 GSI Helmholtzzentrum für Schwerionenforschung GmbH +License: LGPL-3 + +Files: src/XrdCl/XrdClMonitor.hh +Copyright: + 2012 Board of Trustees of the Leland Stanford, Jr., University + 2012 European Organization for Nuclear Research (CERN) +License: LGPL-3 + +Files: src/XrdOuc/XrdOucJson.hh +Copyright: + 2013-2019 Niels Lohmann +License: Expat + +Files: src/XrdOuc/XrdOucSHA3.* +Copyright: + 2015 Markku-Juhani O. Saarinen +License: Expat + +Files: src/XrdOuc/XrdOucUri.* +Copyright: + 2016 by David Farrell +License: BSD-2-clause + +Files: src/XrdSciTokens/* +Copyright: + Brian Bockelman, Andrew Hanushevsky, Derek Weitzel +License: Apache-2.0 + On Debian systems the full text of the Apache license version 2 can + be found in /usr/share/common-licenses/Apache-2.0. + +Files: src/XrdSciTokens/vendor/inih/* +Copyright: + 2009, Ben Hoyt. All rights reserved. +License: BSD-3-clause + +Files: src/XrdSciTokens/vendor/picojson/* +Copyright: + 2009-2010 Cybozu Labs, Inc. + 2011-2014 Kazuho Oku + All rights reserved. +License: BSD-2-clause + +Files: src/XrdSciTokens/vendor/picojson/picotest/* +Copyright: + 2014 DeNA Co., Ltd. +License: Expat + +Files: src/XrdSecztn/XrdSecztn.cc +Copyright: + 2015 Erwin Jansen +License: Expat + +Files: src/XrdSys/XrdSysFallocate.* +Copyright: + 2010 Mozilla Foundation. All Rights Reserved. + 2015, 2016 R.J.V. Bertin for KDE project. +Comment: + Original license allows modification / redistribution under LGPL 2.1 + or higher. Redistributed under LGPL-3 or higher. +License: LGPL-3 + +Files: src/XrdTls/XrdTlsHostcheck.* +Copyright: + 1998-2012 Daniel Stenberg, , et al. +License: curl + This software is licensed as described in the file COPYING, which + you should have received as part of this distribution. The terms + are also available at http://curl.haxx.se/docs/copyright.html. + . + You may opt to use, copy, modify, merge, publish, distribute and/or sell + copies of the Software, and permit persons to whom the Software is + furnished to do so, under the terms of the COPYING file. + . + This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + KIND, either express or implied. + +Files: src/XrdTls/XrdTlsNotaryUtils.* +Copyright: + 2012 iSEC Partners. +License: Expat + +Files: src/XrdXml/tinyxml/* +Copyright: + 2000-2006 Lee Thomason (www.grinninglizard.com) +Comment: Not used in debian build, libtinyxml-dev used instead. +License: zlib/libpng + +Files: debian/* +Copyright: + 2020-2023 Mattias Ellert +License: LGPL-3 + +License: LGPL-3 + This file is part of the XRootD software suite. + . + XRootD is free software: you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + . + XRootD is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + License for more details. + . + You should have received a copy of the GNU Lesser General Public + License along with XRootD in a file called COPYING.LESSER (LGPL + license) and file COPYING (GPL license). If not, see + . + . + On Debian systems the full text of the GNU lesser general public + license version 3 can be found in /usr/share/common-licenses/LGPL-3. + +License: zlib/libpng + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any + damages arising from the use of this software. + . + Permission is granted to anyone to use this software for any + purpose, including commercial applications, and to alter it and + redistribute it freely, subject to the following restrictions: + . + 1. The origin of this software must not be misrepresented; you must + not claim that you wrote the original software. If you use this + software in a product, an acknowledgment in the product + documentation would be appreciated but is not required. + . + 2. Altered source versions must be plainly marked as such, and + must not be misrepresented as being the original software. + . + 3. This notice may not be removed or altered from any source + distribution. + +License: BSD-2-clause + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + . + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + . + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +License: BSD-3-clause + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + . + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name of [insert "CERN" or "Ben Hoyt" as appropriate] + nor the names of its contributors may be used to endorse or + promote products derived from this software without specific prior + written permission. + . + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +License: Expat + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + . + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + . + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. diff --git a/packaging/debian/libxrdapputils2.install b/packaging/debian/libxrdapputils2.install new file mode 100644 index 00000000000..93362c3e45c --- /dev/null +++ b/packaging/debian/libxrdapputils2.install @@ -0,0 +1 @@ +/usr/lib/*/libXrdAppUtils.so.* diff --git a/packaging/debian/libxrdcephposix0.install b/packaging/debian/libxrdcephposix0.install new file mode 100644 index 00000000000..36bd6c377bd --- /dev/null +++ b/packaging/debian/libxrdcephposix0.install @@ -0,0 +1 @@ +/usr/lib/*/libXrdCephPosix.so.* diff --git a/packaging/debian/libxrdcl3.install b/packaging/debian/libxrdcl3.install new file mode 100644 index 00000000000..655caa87025 --- /dev/null +++ b/packaging/debian/libxrdcl3.install @@ -0,0 +1 @@ +/usr/lib/*/libXrdCl.so.* diff --git a/packaging/debian/libxrdcrypto2.install b/packaging/debian/libxrdcrypto2.install new file mode 100644 index 00000000000..46dc28cdc0a --- /dev/null +++ b/packaging/debian/libxrdcrypto2.install @@ -0,0 +1 @@ +/usr/lib/*/libXrdCrypto.so.* diff --git a/packaging/debian/libxrdcryptolite2.install b/packaging/debian/libxrdcryptolite2.install new file mode 100644 index 00000000000..cdb6bf81dbe --- /dev/null +++ b/packaging/debian/libxrdcryptolite2.install @@ -0,0 +1 @@ +/usr/lib/*/libXrdCryptoLite.so.* diff --git a/packaging/debian/libxrdec1.install b/packaging/debian/libxrdec1.install new file mode 100644 index 00000000000..fe56c742a69 --- /dev/null +++ b/packaging/debian/libxrdec1.install @@ -0,0 +1 @@ +/usr/lib/*/libXrdEc.so.* diff --git a/packaging/debian/libxrdffs3.install b/packaging/debian/libxrdffs3.install new file mode 100644 index 00000000000..11aa0046d49 --- /dev/null +++ b/packaging/debian/libxrdffs3.install @@ -0,0 +1 @@ +/usr/lib/*/libXrdFfs.so.* diff --git a/packaging/debian/libxrdhttputils2.install b/packaging/debian/libxrdhttputils2.install new file mode 100644 index 00000000000..b5becae8f13 --- /dev/null +++ b/packaging/debian/libxrdhttputils2.install @@ -0,0 +1 @@ +/usr/lib/*/libXrdHttpUtils.so.* diff --git a/packaging/debian/libxrdposix3.install b/packaging/debian/libxrdposix3.install new file mode 100644 index 00000000000..d4b46f09cc7 --- /dev/null +++ b/packaging/debian/libxrdposix3.install @@ -0,0 +1,4 @@ +/usr/lib/*/libXrdPosix.so.* +/usr/lib/*/libXrdPosixPreload.so.* +# This lib may be used for LD_PRELOAD so the .so link needs to be included +/usr/lib/*/libXrdPosixPreload.so diff --git a/packaging/debian/libxrdposix3.lintian-overrides b/packaging/debian/libxrdposix3.lintian-overrides new file mode 100644 index 00000000000..7f1d3a729a1 --- /dev/null +++ b/packaging/debian/libxrdposix3.lintian-overrides @@ -0,0 +1,3 @@ +# This lib may be used for LD_PRELOAD so the .so link needs to be included +link-to-shared-library-in-wrong-package * [usr/lib/*/libXrdPosixPreload.so] +lacks-unversioned-link-to-shared-library * [usr/lib/*/libXrdPosixPreload.so.*] diff --git a/packaging/debian/libxrdserver3.install b/packaging/debian/libxrdserver3.install new file mode 100644 index 00000000000..fb9075c5f83 --- /dev/null +++ b/packaging/debian/libxrdserver3.install @@ -0,0 +1 @@ +/usr/lib/*/libXrdServer.so.* diff --git a/packaging/debian/libxrdssilib2.install b/packaging/debian/libxrdssilib2.install new file mode 100644 index 00000000000..369c6f12382 --- /dev/null +++ b/packaging/debian/libxrdssilib2.install @@ -0,0 +1 @@ +/usr/lib/*/libXrdSsiLib.so.* diff --git a/packaging/debian/libxrdssishmap2.install b/packaging/debian/libxrdssishmap2.install new file mode 100644 index 00000000000..44577ea477a --- /dev/null +++ b/packaging/debian/libxrdssishmap2.install @@ -0,0 +1 @@ +/usr/lib/*/libXrdSsiShMap.so.* diff --git a/packaging/debian/libxrdutils3.install b/packaging/debian/libxrdutils3.install new file mode 100644 index 00000000000..8cbb616f877 --- /dev/null +++ b/packaging/debian/libxrdutils3.install @@ -0,0 +1 @@ +/usr/lib/*/libXrdUtils.so.* diff --git a/packaging/debian/libxrdxml3.install b/packaging/debian/libxrdxml3.install new file mode 100644 index 00000000000..25ee16e9adf --- /dev/null +++ b/packaging/debian/libxrdxml3.install @@ -0,0 +1 @@ +/usr/lib/*/libXrdXml.so.* diff --git a/packaging/debian/libxrootd-client-dev.install b/packaging/debian/libxrootd-client-dev.install index 73c00fd23ab..dbaedb281be 100644 --- a/packaging/debian/libxrootd-client-dev.install +++ b/packaging/debian/libxrootd-client-dev.install @@ -1,7 +1,6 @@ -usr/bin/xrdgsitest -usr/lib/*/libXrdCl.so -usr/lib/*/libXrdFfs.so -usr/lib/*/libXrdPosix.so -usr/share/man/man1/xrdgsitest.1* -usr/include/xrootd/XrdCl -usr/include/xrootd/XrdPosix +/usr/include/xrootd/XrdCl +/usr/include/xrootd/XrdPosix +/usr/lib/*/libXrdCl.so +/usr/lib/*/libXrdEc.so +/usr/lib/*/libXrdFfs.so +/usr/lib/*/libXrdPosix.so diff --git a/packaging/debian/libxrootd-dev.install b/packaging/debian/libxrootd-dev.install index e6ffeaa6bea..e8e910d6210 100644 --- a/packaging/debian/libxrootd-dev.install +++ b/packaging/debian/libxrootd-dev.install @@ -1,16 +1,16 @@ -usr/bin/xrootd-config -usr/include/xrootd/XProtocol -usr/include/xrootd/Xrd -usr/include/xrootd/XrdCks -usr/include/xrootd/XrdNet -usr/include/xrootd/XrdOuc -usr/include/xrootd/XrdSec -usr/include/xrootd/XrdSys -usr/include/xrootd/XrdVersion.hh -usr/include/xrootd/XrdXml/XrdXmlReader.hh -usr/lib/*/libXrdAppUtils.so -usr/lib/*/libXrdCrypto.so -usr/lib/*/libXrdCryptoLite.so -usr/lib/*/libXrdUtils.so -usr/lib/*/libXrdXml.so -usr/lib/*/cmake/XRootD +/usr/bin/xrootd-config +/usr/include/xrootd/XProtocol +/usr/include/xrootd/Xrd +/usr/include/xrootd/XrdCks +/usr/include/xrootd/XrdNet +/usr/include/xrootd/XrdOuc +/usr/include/xrootd/XrdSec +/usr/include/xrootd/XrdSys +/usr/include/xrootd/XrdXml +/usr/include/xrootd/XrdVersion.hh +/usr/lib/*/libXrdAppUtils.so +/usr/lib/*/libXrdCrypto.so +/usr/lib/*/libXrdCryptoLite.so +/usr/lib/*/libXrdUtils.so +/usr/lib/*/libXrdXml.so +/usr/lib/*/cmake/XRootD diff --git a/packaging/debian/libxrootd-private-dev.install b/packaging/debian/libxrootd-private-dev.install index 38c034f2bb2..5b5910df5db 100644 --- a/packaging/debian/libxrootd-private-dev.install +++ b/packaging/debian/libxrootd-private-dev.install @@ -1,3 +1,3 @@ -usr/include/xrootd/private -usr/lib/*/libXrdSsiLib.so -usr/lib/*/libXrdSsiShMap.so +/usr/include/xrootd/private +/usr/lib/*/libXrdSsiLib.so +/usr/lib/*/libXrdSsiShMap.so diff --git a/packaging/debian/libxrootd-server-dev.install b/packaging/debian/libxrootd-server-dev.install index 100447f1f6c..0a267b049ce 100644 --- a/packaging/debian/libxrootd-server-dev.install +++ b/packaging/debian/libxrootd-server-dev.install @@ -1,9 +1,10 @@ -usr/include/xrootd/XrdAcc -usr/include/xrootd/XrdCms -usr/include/xrootd/XrdPfc -usr/include/xrootd/XrdOss -usr/include/xrootd/XrdSfs -usr/include/xrootd/XrdXrootd -usr/include/xrootd/XrdHttp -usr/lib/*/libXrdServer.so -usr/lib/*/libXrdHttpUtils.so +/usr/include/xrootd/XrdAcc +/usr/include/xrootd/XrdCms +/usr/include/xrootd/XrdHttp +/usr/include/xrootd/XrdOfs +/usr/include/xrootd/XrdOss +/usr/include/xrootd/XrdPfc +/usr/include/xrootd/XrdSfs +/usr/include/xrootd/XrdXrootd +/usr/lib/*/libXrdHttpUtils.so +/usr/lib/*/libXrdServer.so diff --git a/packaging/debian/python3-xrootd.install b/packaging/debian/python3-xrootd.install index b882c0b561d..23b15747d77 100644 --- a/packaging/debian/python3-xrootd.install +++ b/packaging/debian/python3-xrootd.install @@ -1 +1,3 @@ -usr/lib/python3*/site-packages/* +/usr/lib/python3/dist-packages/xrootd-*.*-info +/usr/lib/python3/dist-packages/pyxrootd +/usr/lib/python3/dist-packages/XRootD diff --git a/packaging/debian/python3-xrootd.install.new b/packaging/debian/python3-xrootd.install.new deleted file mode 100644 index 6c1d7e1948e..00000000000 --- a/packaging/debian/python3-xrootd.install.new +++ /dev/null @@ -1 +0,0 @@ -usr/local/lib/python*/dist-packages/* \ No newline at end of file diff --git a/packaging/debian/rules b/packaging/debian/rules index df662e1c07c..479f83d1fdc 100755 --- a/packaging/debian/rules +++ b/packaging/debian/rules @@ -1,15 +1,111 @@ #!/usr/bin/make -f + +export LC_ALL=C export DH_VERBOSE=1 export PYBUILD_NAME=xrootd +export DEB_BUILD_MAINT_OPTIONS = hardening=+all optimize=-lto %: - dh $@ --builddirectory=build --destdir=deb_packages --with python3 --buildsystem=cmake + dh $@ --with python3 --buildsystem cmake override_dh_auto_configure: - dh_auto_configure -- -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_LIBDIR=lib/$(shell dpkg-architecture -qDEB_HOST_MULTIARCH) -DPython_EXECUTABLE=/usr/bin/python3 -DCMAKE_SKIP_INSTALL_RPATH=ON -DENABLE_XRDCLHTTP=TRUE -DINSTALL_PYTHON_BINDINGS=TRUE -DPIP_OPTIONS='--no-deps --disable-pip-version-check --verbose' + dh_auto_configure -- \ + -DENABLE_FUSE:BOOL=1 \ + -DENABLE_HTTP:BOOL=1 \ + -DENABLE_KRB5:BOOL=1 \ + -DENABLE_MACAROONS:BOOL=1 \ + -DENABLE_PYTHON:BOOL=1 \ + -DENABLE_READLINE:BOOL=1 \ + -DENABLE_SCITOKENS:BOOL=1 \ + -DENABLE_VOMS:BOOL=1 \ + -DENABLE_XRDCLHTTP:BOOL=1 \ + -DENABLE_XRDEC:BOOL=1 \ + -DENABLE_TESTS:BOOL=1 \ + -DFORCE_ENABLED:BOOL=1 \ + -DINSTALL_PYTHON_BINDINGS:BOOL=0 \ + -DUSE_SYSTEM_ISAL:BOOL=1 \ + -DXRDCEPH_SUBMODULE:BOOL=1 + +override_dh_auto_build: + dh_auto_build + doxygen Doxyfile + +override_dh_auto_clean: + dh_auto_clean + rm -rf doxydoc + rm -rf bindings/python/docs/build + +override_dh_auto_install: + dh_auto_install + python3 -m pip install --target debian/tmp/usr/lib/python3/dist-packages \ + --no-deps --no-build-isolation --disable-pip-version-check --verbose \ + --ignore-installed --use-pep517 obj-$(DEB_HOST_MULTIARCH)/bindings/python + + rm -f debian/tmp/usr/bin/xrdshmap + rm -f debian/tmp/usr/bin/test-runner + rm -f debian/tmp/usr/lib/*/libXrd*Test* + rm -f debian/tmp/usr/lib/*/libXrdCephPosix.so + + rm -f debian/tmp/usr/lib/python3/dist-packages/xrootd-*.*-info/direct_url.json + rm -f debian/tmp/usr/lib/python3/dist-packages/xrootd-*.*-info/RECORD + [ -r debian/tmp/usr/lib/python3/dist-packages/xrootd-*.*-info/INSTALLER ] && \ + sed -i -e s/pip/dpkg/ \ + debian/tmp/usr/lib/python3/dist-packages/xrootd-*.*-info/INSTALLER + + LD_LIBRARY_PATH=$${LD_LIBRARY_PATH}:$(CURDIR)/debian/tmp/usr/lib/$(DEB_HOST_MULTIARCH) \ + PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=$(CURDIR)/debian/tmp/usr/lib/python3/dist-packages \ + make -C bindings/python/docs html && \ + mv bindings/python/docs/build/html bindings/python/docs/build/python + + # Service unit files + mkdir -p debian/tmp/lib/systemd/system + install -m 644 packaging/common/xrootd@.service debian/tmp/lib/systemd/system + install -m 644 packaging/common/xrootd@.socket debian/tmp/lib/systemd/system + install -m 644 packaging/common/xrdhttp@.socket debian/tmp/lib/systemd/system + install -m 644 packaging/common/cmsd@.service debian/tmp/lib/systemd/system + install -m 644 packaging/common/frm_xfrd@.service debian/tmp/lib/systemd/system + install -m 644 packaging/common/frm_purged@.service debian/tmp/lib/systemd/system + mkdir -p debian/tmp/usr/lib/tmpfiles.d + install -m 644 packaging/rhel/xrootd.tmpfiles debian/tmp/usr/lib/tmpfiles.d/xrootd.conf + + # Server config + mkdir -p debian/tmp/etc/xrootd + install -m 644 -p packaging/common/xrootd-clustered.cfg \ + debian/tmp/etc/xrootd/xrootd-clustered.cfg + install -m 644 -p packaging/common/xrootd-standalone.cfg \ + debian/tmp/etc/xrootd/xrootd-standalone.cfg + install -m 644 -p packaging/common/xrootd-filecache-clustered.cfg \ + debian/tmp/etc/xrootd/xrootd-filecache-clustered.cfg + install -m 644 -p packaging/common/xrootd-filecache-standalone.cfg \ + debian/tmp/etc/xrootd/xrootd-filecache-standalone.cfg + sed 's!/usr/lib64/!!' packaging/common/xrootd-http.cfg > \ + debian/tmp/etc/xrootd/xrootd-http.cfg + + # Client config + mkdir -p debian/tmp/etc/xrootd/client.plugins.d + install -m 644 -p packaging/common/client.conf \ + debian/tmp/etc/xrootd/client.conf + sed 's!/usr/lib/!!' packaging/common/client-plugin.conf.example > \ + debian/tmp/etc/xrootd/client.plugins.d/client-plugin.conf.example + sed -e 's!/usr/lib64/!!' -e 's!-5!!' packaging/common/recorder.conf > \ + debian/tmp/etc/xrootd/client.plugins.d/recorder.conf + sed 's!/usr/lib64/!!' packaging/common/http.client.conf.example > \ + debian/tmp/etc/xrootd/client.plugins.d/xrdcl-http-plugin.conf + + chmod 644 debian/tmp/usr/share/xrootd/utils/XrdCmsNotify.pm + sed 's!/usr/bin/env perl!/usr/bin/perl!' -i \ + debian/tmp/usr/share/xrootd/utils/netchk \ + debian/tmp/usr/share/xrootd/utils/XrdCmsNotify.pm \ + debian/tmp/usr/share/xrootd/utils/XrdOlbMonPerf + + sed 's!/usr/bin/env bash!/bin/bash!' -i \ + debian/tmp/usr/bin/xrootd-config + + mkdir -p debian/tmp/etc/xrootd/config.d + + mkdir -p debian/tmp/var/log/xrootd + mkdir -p debian/tmp/var/spool/xrootd -override_dh_install: - install -D -m 644 packaging/common/client.conf deb_packages/etc/xrootd/client.conf - install -D -m 644 packaging/common/client-plugin.conf.example deb_packages/etc/xrootd/client.plugins.d/client-plugin.conf.example - install -D -m 644 packaging/common/http.client.conf.example deb_packages/etc/xrootd/client.plugins.d/xrdcl-http-plugin.conf - dh_install --sourcedir=deb_packages + mkdir -p debian/tmp/etc/logrotate.d + install -m 644 -p packaging/common/xrootd.logrotate \ + debian/tmp/etc/logrotate.d/xrootd diff --git a/packaging/debian/xrootd-ceph-plugins.install b/packaging/debian/xrootd-ceph-plugins.install new file mode 100644 index 00000000000..b457dde4c48 --- /dev/null +++ b/packaging/debian/xrootd-ceph-plugins.install @@ -0,0 +1,2 @@ +/usr/lib/*/libXrdCeph-5.so +/usr/lib/*/libXrdCephXattr-5.so diff --git a/packaging/debian/xrootd-ceph-plugins.lintian-overrides b/packaging/debian/xrootd-ceph-plugins.lintian-overrides new file mode 100644 index 00000000000..754468af41c --- /dev/null +++ b/packaging/debian/xrootd-ceph-plugins.lintian-overrides @@ -0,0 +1,2 @@ +# These are plugins - no soname on purpose +sharedobject-in-library-directory-missing-soname [usr/lib/*/libXrd*-5.so] diff --git a/packaging/debian/xrootd-client-http-plugins.install b/packaging/debian/xrootd-client-http-plugins.install new file mode 100644 index 00000000000..98aa6cf5769 --- /dev/null +++ b/packaging/debian/xrootd-client-http-plugins.install @@ -0,0 +1,2 @@ +/usr/lib/*/libXrdClHttp-5.so +/etc/xrootd/client.plugins.d/xrdcl-http-plugin.conf diff --git a/packaging/debian/xrootd-client-http-plugins.lintian-overrides b/packaging/debian/xrootd-client-http-plugins.lintian-overrides new file mode 100644 index 00000000000..754468af41c --- /dev/null +++ b/packaging/debian/xrootd-client-http-plugins.lintian-overrides @@ -0,0 +1,2 @@ +# These are plugins - no soname on purpose +sharedobject-in-library-directory-missing-soname [usr/lib/*/libXrd*-5.so] diff --git a/packaging/debian/xrootd-client-plugins.install b/packaging/debian/xrootd-client-plugins.install index c7b320992a6..d0732040476 100644 --- a/packaging/debian/xrootd-client-plugins.install +++ b/packaging/debian/xrootd-client-plugins.install @@ -1,4 +1,5 @@ -usr/lib/*/libXrdClProxyPlugin-5.so -usr/lib/*/libXrdPosixPreload.so* -usr/lib/*/libXrdClHttp-5.so -etc/xrootd/client.plugins.d/xrdcl-http-plugin.conf \ No newline at end of file +/usr/lib/*/libXrdClProxyPlugin-5.so +/usr/lib/*/libXrdClRecorder-5.so +/etc/xrootd/client.conf +/etc/xrootd/client.plugins.d/client-plugin.conf.example +/etc/xrootd/client.plugins.d/recorder.conf diff --git a/packaging/debian/xrootd-client-plugins.lintian-overrides b/packaging/debian/xrootd-client-plugins.lintian-overrides new file mode 100644 index 00000000000..754468af41c --- /dev/null +++ b/packaging/debian/xrootd-client-plugins.lintian-overrides @@ -0,0 +1,2 @@ +# These are plugins - no soname on purpose +sharedobject-in-library-directory-missing-soname [usr/lib/*/libXrd*-5.so] diff --git a/packaging/debian/xrootd-client.install b/packaging/debian/xrootd-client.install index e69de29bb2d..330a27cf985 100644 --- a/packaging/debian/xrootd-client.install +++ b/packaging/debian/xrootd-client.install @@ -0,0 +1,18 @@ +/usr/bin/xrdadler32 +/usr/bin/xrdcks +/usr/bin/xrdcopy +/usr/bin/xrdcp +/usr/bin/xrdcrc32c +/usr/bin/xrdfs +/usr/bin/xrdgsiproxy +/usr/bin/xrdgsitest +/usr/bin/xrdmapc +/usr/bin/xrdpinls +/usr/bin/xrdreplay +/usr/share/man/man1/xrdadler32.1 +/usr/share/man/man1/xrdcopy.1 +/usr/share/man/man1/xrdcp.1 +/usr/share/man/man1/xrdfs.1 +/usr/share/man/man1/xrdgsiproxy.1 +/usr/share/man/man1/xrdgsitest.1 +/usr/share/man/man1/xrdmapc.1 diff --git a/packaging/debian/xrootd-doc.install b/packaging/debian/xrootd-doc.install new file mode 100644 index 00000000000..5c1b34c5aa5 --- /dev/null +++ b/packaging/debian/xrootd-doc.install @@ -0,0 +1,2 @@ +doxydoc/html /usr/share/doc/xrootd +bindings/python/docs/build/python /usr/share/doc/xrootd diff --git a/packaging/debian/xrootd-fuse.install b/packaging/debian/xrootd-fuse.install index 4353d0fed3a..639e78d6f5f 100644 --- a/packaging/debian/xrootd-fuse.install +++ b/packaging/debian/xrootd-fuse.install @@ -1,3 +1,2 @@ -usr/bin/xrootdfs -usr/share/man/man1/xrootdfs.1* -etc/xrootd \ No newline at end of file +/usr/bin/xrootdfs +/usr/share/man/man1/xrootdfs.1 diff --git a/packaging/debian/xrootd-plugins.install b/packaging/debian/xrootd-plugins.install index b4fa550bc4e..6effd2f0662 100644 --- a/packaging/debian/xrootd-plugins.install +++ b/packaging/debian/xrootd-plugins.install @@ -1,3 +1,12 @@ -usr/lib/*/libXrdCks*-5.so -usr/lib/*/libXrdCryptossl-5.so -usr/lib/*/libXrdSec*-5.so +/usr/lib/*/libXrdCksCalczcrc32-5.so +/usr/lib/*/libXrdCryptossl-5.so +/usr/lib/*/libXrdSec-5.so +/usr/lib/*/libXrdSecProt-5.so +/usr/lib/*/libXrdSecgsi-5.so +/usr/lib/*/libXrdSecgsiAUTHZVO-5.so +/usr/lib/*/libXrdSecgsiGMAPDN-5.so +/usr/lib/*/libXrdSeckrb5-5.so +/usr/lib/*/libXrdSecpwd-5.so +/usr/lib/*/libXrdSecsss-5.so +/usr/lib/*/libXrdSecunix-5.so +/usr/lib/*/libXrdSecztn-5.so diff --git a/packaging/debian/xrootd-plugins.lintian-overrides b/packaging/debian/xrootd-plugins.lintian-overrides new file mode 100644 index 00000000000..a18591c49dc --- /dev/null +++ b/packaging/debian/xrootd-plugins.lintian-overrides @@ -0,0 +1,5 @@ +# These are plugins - no soname on purpose +sharedobject-in-library-directory-missing-soname [usr/lib/*/libXrd*-5.so] + +# False positive +library-not-linked-against-libc [usr/lib/*/libXrdCksCalczcrc32-5.so] diff --git a/packaging/debian/xrootd-scitokens-plugins.docs b/packaging/debian/xrootd-scitokens-plugins.docs new file mode 100644 index 00000000000..ccd73502fdd --- /dev/null +++ b/packaging/debian/xrootd-scitokens-plugins.docs @@ -0,0 +1 @@ +src/XrdSciTokens/README.md diff --git a/packaging/debian/xrootd-scitokens-plugins.install b/packaging/debian/xrootd-scitokens-plugins.install new file mode 100644 index 00000000000..f285abbad81 --- /dev/null +++ b/packaging/debian/xrootd-scitokens-plugins.install @@ -0,0 +1 @@ +/usr/lib/*/libXrdAccSciTokens-5.so diff --git a/packaging/debian/xrootd-scitokens-plugins.lintian-overrides b/packaging/debian/xrootd-scitokens-plugins.lintian-overrides new file mode 100644 index 00000000000..754468af41c --- /dev/null +++ b/packaging/debian/xrootd-scitokens-plugins.lintian-overrides @@ -0,0 +1,2 @@ +# These are plugins - no soname on purpose +sharedobject-in-library-directory-missing-soname [usr/lib/*/libXrd*-5.so] diff --git a/packaging/debian/xrootd-server-plugins.install b/packaging/debian/xrootd-server-plugins.install index d66668eff39..6e8777e4157 100644 --- a/packaging/debian/xrootd-server-plugins.install +++ b/packaging/debian/xrootd-server-plugins.install @@ -1,13 +1,17 @@ -usr/lib/*/libXrdBwm-5.so -usr/lib/*/libXrdPss-5.so -usr/lib/*/libXrdXrootd-5.so -usr/lib/*/libXrdPfc-5.so -usr/lib/*/libXrdBlacklistDecision-5.so -usr/lib/*/libXrdHttp-5.so -usr/lib/*/libXrdHttpTPC-5.so -usr/lib/*/libXrdN2No2p-5.so -usr/lib/*/libXrdOssSIgpfsT-5.so -usr/lib/*/libXrdSsi-5.so -usr/lib/*/libXrdSsiLog-5.so -usr/lib/*/libXrdThrottle-5.so -usr/lib/*/libXrdCmsRedirectLocal-5.so +/usr/lib/*/libXrdBlacklistDecision-5.so +/usr/lib/*/libXrdBwm-5.so +/usr/lib/*/libXrdCmsRedirectLocal-5.so +/usr/lib/*/libXrdFileCache-5.so +/usr/lib/*/libXrdHttp-5.so +/usr/lib/*/libXrdHttpTPC-5.so +/usr/lib/*/libXrdMacaroons-5.so +/usr/lib/*/libXrdN2No2p-5.so +/usr/lib/*/libXrdOfsPrepGPI-5.so +/usr/lib/*/libXrdOssCsi-5.so +/usr/lib/*/libXrdOssSIgpfsT-5.so +/usr/lib/*/libXrdPfc-5.so +/usr/lib/*/libXrdPss-5.so +/usr/lib/*/libXrdSsi-5.so +/usr/lib/*/libXrdSsiLog-5.so +/usr/lib/*/libXrdThrottle-5.so +/usr/lib/*/libXrdXrootd-5.so diff --git a/packaging/debian/xrootd-server-plugins.lintian-overrides b/packaging/debian/xrootd-server-plugins.lintian-overrides new file mode 100644 index 00000000000..754468af41c --- /dev/null +++ b/packaging/debian/xrootd-server-plugins.lintian-overrides @@ -0,0 +1,2 @@ +# These are plugins - no soname on purpose +sharedobject-in-library-directory-missing-soname [usr/lib/*/libXrd*-5.so] diff --git a/packaging/debian/xrootd-server.install b/packaging/debian/xrootd-server.install index 3442b30314e..a75d81b9ec3 100644 --- a/packaging/debian/xrootd-server.install +++ b/packaging/debian/xrootd-server.install @@ -1,23 +1,31 @@ -usr/bin/cconfig -usr/bin/cmsd -usr/bin/frm_admin -usr/bin/frm_purged -usr/bin/frm_xfragent -usr/bin/frm_xfrd -usr/bin/mpxstats -usr/bin/wait41 -usr/bin/xrdacctest -usr/bin/xrdpfc_print -usr/bin/xrdpwdadmin -usr/bin/xrdsssadmin -usr/bin/xrootd -usr/share/man/man8/cmsd.8 -usr/share/man/man8/frm_admin.8 -usr/share/man/man8/frm_purged.8 -usr/share/man/man8/frm_xfragent.8 -usr/share/man/man8/frm_xfrd.8 -usr/share/man/man8/mpxstats.8 -usr/share/man/man8/xrdpfc_print.8 -usr/share/man/man8/xrdpwdadmin.8 -usr/share/man/man8/xrdsssadmin.8 -usr/share/man/man8/xrootd.8 +/usr/bin/cconfig +/usr/bin/cmsd +/usr/bin/frm_admin +/usr/bin/frm_purged +/usr/bin/frm_xfragent +/usr/bin/frm_xfrd +/usr/bin/mpxstats +/usr/bin/wait41 +/usr/bin/xrdacctest +/usr/bin/xrdpfc_print +/usr/bin/xrdpwdadmin +/usr/bin/xrdsssadmin +/usr/bin/xrootd +/usr/share/man/man8/cmsd.8 +/usr/share/man/man8/frm_admin.8 +/usr/share/man/man8/frm_purged.8 +/usr/share/man/man8/frm_xfragent.8 +/usr/share/man/man8/frm_xfrd.8 +/usr/share/man/man8/mpxstats.8 +/usr/share/man/man8/xrdpfc_print.8 +/usr/share/man/man8/xrdpwdadmin.8 +/usr/share/man/man8/xrdsssadmin.8 +/usr/share/man/man8/xrootd.8 +/usr/share/xrootd/utils +/lib/systemd/system/* +/usr/lib/tmpfiles.d/xrootd.conf +/etc/logrotate.d/xrootd +/etc/xrootd/config.d +/etc/xrootd/*.cfg +/var/log/xrootd +/var/spool/xrootd diff --git a/packaging/debian/xrootd-server.postinst b/packaging/debian/xrootd-server.postinst new file mode 100644 index 00000000000..e08626eb245 --- /dev/null +++ b/packaging/debian/xrootd-server.postinst @@ -0,0 +1,21 @@ +#!/bin/sh + +set -e + +if test "$1" = "configure" -o "$1" = "reconfigure" ; then + + getent group xrootd > /dev/null || \ + addgroup --quiet --system xrootd + + getent passwd xrootd > /dev/null || \ + adduser --quiet --system --home /var/spool/xrootd --shell /bin/false \ + --ingroup xrootd --disabled-password --disabled-login \ + --gecos "XRootD runtime user" xrootd + + chown xrootd:xrootd /etc/xrootd/*.cfg + chown xrootd:xrootd /var/log/xrootd + chown xrootd:xrootd /var/spool/xrootd + +fi + +#DEBHELPER# diff --git a/packaging/debian/xrootd-voms-plugins.install b/packaging/debian/xrootd-voms-plugins.install new file mode 100644 index 00000000000..331ebced9ad --- /dev/null +++ b/packaging/debian/xrootd-voms-plugins.install @@ -0,0 +1,5 @@ +/usr/lib/*/libXrdVoms-5.so +/usr/lib/*/libXrdHttpVOMS-5.so +/usr/lib/*/libXrdSecgsiVOMS-5.so +/usr/share/man/man1/libXrdVoms.1 +/usr/share/man/man1/libXrdSecgsiVOMS.1 diff --git a/packaging/debian/xrootd-voms-plugins.lintian-overrides b/packaging/debian/xrootd-voms-plugins.lintian-overrides new file mode 100644 index 00000000000..754468af41c --- /dev/null +++ b/packaging/debian/xrootd-voms-plugins.lintian-overrides @@ -0,0 +1,2 @@ +# These are plugins - no soname on purpose +sharedobject-in-library-directory-missing-soname [usr/lib/*/libXrd*-5.so] From 4158dd85c2df56dc04153ca5ec54a59bc5263c57 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 28 Aug 2023 16:20:09 +0200 Subject: [PATCH 294/442] [DEB] Move packaging/debian to top directory --- {packaging/debian => debian}/compat | 0 {packaging/debian => debian}/control | 0 {packaging/debian => debian}/copyright | 0 {packaging/debian => debian}/libxrdapputils1.install | 0 {packaging/debian => debian}/libxrdapputils2.install | 0 {packaging/debian => debian}/libxrdcephposix0.install | 0 {packaging/debian => debian}/libxrdcl2.install | 0 {packaging/debian => debian}/libxrdcl3.install | 0 {packaging/debian => debian}/libxrdcrypto1.install | 0 {packaging/debian => debian}/libxrdcrypto2.install | 0 {packaging/debian => debian}/libxrdcryptolite1.install | 0 {packaging/debian => debian}/libxrdcryptolite2.install | 0 {packaging/debian => debian}/libxrdec1.install | 0 {packaging/debian => debian}/libxrdffs2.install | 0 {packaging/debian => debian}/libxrdffs3.install | 0 {packaging/debian => debian}/libxrdhttputils1.install | 0 {packaging/debian => debian}/libxrdhttputils2.install | 0 {packaging/debian => debian}/libxrdposix2.install | 0 {packaging/debian => debian}/libxrdposix3.install | 0 {packaging/debian => debian}/libxrdposix3.lintian-overrides | 0 {packaging/debian => debian}/libxrdserver2.install | 0 {packaging/debian => debian}/libxrdserver3.install | 0 {packaging/debian => debian}/libxrdssilib1.install | 0 {packaging/debian => debian}/libxrdssilib2.install | 0 {packaging/debian => debian}/libxrdssishmap1.install | 0 {packaging/debian => debian}/libxrdssishmap2.install | 0 {packaging/debian => debian}/libxrdutils2.install | 0 {packaging/debian => debian}/libxrdutils3.install | 0 {packaging/debian => debian}/libxrdxml2.install | 0 {packaging/debian => debian}/libxrdxml3.install | 0 {packaging/debian => debian}/libxrootd-client-dev.install | 0 {packaging/debian => debian}/libxrootd-dev.install | 0 {packaging/debian => debian}/libxrootd-private-dev.install | 0 {packaging/debian => debian}/libxrootd-server-dev.install | 0 {packaging/debian => debian}/python3-xrootd.install | 0 {packaging/debian => debian}/rules | 0 {packaging/debian => debian}/source/format | 0 {packaging/debian => debian}/xrootd-ceph-plugins.install | 0 .../debian => debian}/xrootd-ceph-plugins.lintian-overrides | 0 {packaging/debian => debian}/xrootd-client-devel.install | 0 {packaging/debian => debian}/xrootd-client-http-plugins.install | 0 .../xrootd-client-http-plugins.lintian-overrides | 0 {packaging/debian => debian}/xrootd-client-libs.install | 0 {packaging/debian => debian}/xrootd-client-plugins.install | 0 .../debian => debian}/xrootd-client-plugins.lintian-overrides | 0 {packaging/debian => debian}/xrootd-client.install | 0 {packaging/debian => debian}/xrootd-clients.install | 0 {packaging/debian => debian}/xrootd-devel.install | 0 {packaging/debian => debian}/xrootd-doc.install | 0 {packaging/debian => debian}/xrootd-fuse.install | 0 {packaging/debian => debian}/xrootd-libs.install | 0 {packaging/debian => debian}/xrootd-plugins.install | 0 {packaging/debian => debian}/xrootd-plugins.lintian-overrides | 0 {packaging/debian => debian}/xrootd-private-devel.install | 0 {packaging/debian => debian}/xrootd-scitokens-plugins.docs | 0 {packaging/debian => debian}/xrootd-scitokens-plugins.install | 0 .../debian => debian}/xrootd-scitokens-plugins.lintian-overrides | 0 {packaging/debian => debian}/xrootd-server-devel.install | 0 {packaging/debian => debian}/xrootd-server-libs.install | 0 {packaging/debian => debian}/xrootd-server-plugins.install | 0 .../debian => debian}/xrootd-server-plugins.lintian-overrides | 0 {packaging/debian => debian}/xrootd-server.install | 0 {packaging/debian => debian}/xrootd-server.postinst | 0 {packaging/debian => debian}/xrootd-voms-plugins.install | 0 .../debian => debian}/xrootd-voms-plugins.lintian-overrides | 0 65 files changed, 0 insertions(+), 0 deletions(-) rename {packaging/debian => debian}/compat (100%) rename {packaging/debian => debian}/control (100%) rename {packaging/debian => debian}/copyright (100%) rename {packaging/debian => debian}/libxrdapputils1.install (100%) rename {packaging/debian => debian}/libxrdapputils2.install (100%) rename {packaging/debian => debian}/libxrdcephposix0.install (100%) rename {packaging/debian => debian}/libxrdcl2.install (100%) rename {packaging/debian => debian}/libxrdcl3.install (100%) rename {packaging/debian => debian}/libxrdcrypto1.install (100%) rename {packaging/debian => debian}/libxrdcrypto2.install (100%) rename {packaging/debian => debian}/libxrdcryptolite1.install (100%) rename {packaging/debian => debian}/libxrdcryptolite2.install (100%) rename {packaging/debian => debian}/libxrdec1.install (100%) rename {packaging/debian => debian}/libxrdffs2.install (100%) rename {packaging/debian => debian}/libxrdffs3.install (100%) rename {packaging/debian => debian}/libxrdhttputils1.install (100%) rename {packaging/debian => debian}/libxrdhttputils2.install (100%) rename {packaging/debian => debian}/libxrdposix2.install (100%) rename {packaging/debian => debian}/libxrdposix3.install (100%) rename {packaging/debian => debian}/libxrdposix3.lintian-overrides (100%) rename {packaging/debian => debian}/libxrdserver2.install (100%) rename {packaging/debian => debian}/libxrdserver3.install (100%) rename {packaging/debian => debian}/libxrdssilib1.install (100%) rename {packaging/debian => debian}/libxrdssilib2.install (100%) rename {packaging/debian => debian}/libxrdssishmap1.install (100%) rename {packaging/debian => debian}/libxrdssishmap2.install (100%) rename {packaging/debian => debian}/libxrdutils2.install (100%) rename {packaging/debian => debian}/libxrdutils3.install (100%) rename {packaging/debian => debian}/libxrdxml2.install (100%) rename {packaging/debian => debian}/libxrdxml3.install (100%) rename {packaging/debian => debian}/libxrootd-client-dev.install (100%) rename {packaging/debian => debian}/libxrootd-dev.install (100%) rename {packaging/debian => debian}/libxrootd-private-dev.install (100%) rename {packaging/debian => debian}/libxrootd-server-dev.install (100%) rename {packaging/debian => debian}/python3-xrootd.install (100%) rename {packaging/debian => debian}/rules (100%) rename {packaging/debian => debian}/source/format (100%) rename {packaging/debian => debian}/xrootd-ceph-plugins.install (100%) rename {packaging/debian => debian}/xrootd-ceph-plugins.lintian-overrides (100%) rename {packaging/debian => debian}/xrootd-client-devel.install (100%) rename {packaging/debian => debian}/xrootd-client-http-plugins.install (100%) rename {packaging/debian => debian}/xrootd-client-http-plugins.lintian-overrides (100%) rename {packaging/debian => debian}/xrootd-client-libs.install (100%) rename {packaging/debian => debian}/xrootd-client-plugins.install (100%) rename {packaging/debian => debian}/xrootd-client-plugins.lintian-overrides (100%) rename {packaging/debian => debian}/xrootd-client.install (100%) rename {packaging/debian => debian}/xrootd-clients.install (100%) rename {packaging/debian => debian}/xrootd-devel.install (100%) rename {packaging/debian => debian}/xrootd-doc.install (100%) rename {packaging/debian => debian}/xrootd-fuse.install (100%) rename {packaging/debian => debian}/xrootd-libs.install (100%) rename {packaging/debian => debian}/xrootd-plugins.install (100%) rename {packaging/debian => debian}/xrootd-plugins.lintian-overrides (100%) rename {packaging/debian => debian}/xrootd-private-devel.install (100%) rename {packaging/debian => debian}/xrootd-scitokens-plugins.docs (100%) rename {packaging/debian => debian}/xrootd-scitokens-plugins.install (100%) rename {packaging/debian => debian}/xrootd-scitokens-plugins.lintian-overrides (100%) rename {packaging/debian => debian}/xrootd-server-devel.install (100%) rename {packaging/debian => debian}/xrootd-server-libs.install (100%) rename {packaging/debian => debian}/xrootd-server-plugins.install (100%) rename {packaging/debian => debian}/xrootd-server-plugins.lintian-overrides (100%) rename {packaging/debian => debian}/xrootd-server.install (100%) rename {packaging/debian => debian}/xrootd-server.postinst (100%) rename {packaging/debian => debian}/xrootd-voms-plugins.install (100%) rename {packaging/debian => debian}/xrootd-voms-plugins.lintian-overrides (100%) diff --git a/packaging/debian/compat b/debian/compat similarity index 100% rename from packaging/debian/compat rename to debian/compat diff --git a/packaging/debian/control b/debian/control similarity index 100% rename from packaging/debian/control rename to debian/control diff --git a/packaging/debian/copyright b/debian/copyright similarity index 100% rename from packaging/debian/copyright rename to debian/copyright diff --git a/packaging/debian/libxrdapputils1.install b/debian/libxrdapputils1.install similarity index 100% rename from packaging/debian/libxrdapputils1.install rename to debian/libxrdapputils1.install diff --git a/packaging/debian/libxrdapputils2.install b/debian/libxrdapputils2.install similarity index 100% rename from packaging/debian/libxrdapputils2.install rename to debian/libxrdapputils2.install diff --git a/packaging/debian/libxrdcephposix0.install b/debian/libxrdcephposix0.install similarity index 100% rename from packaging/debian/libxrdcephposix0.install rename to debian/libxrdcephposix0.install diff --git a/packaging/debian/libxrdcl2.install b/debian/libxrdcl2.install similarity index 100% rename from packaging/debian/libxrdcl2.install rename to debian/libxrdcl2.install diff --git a/packaging/debian/libxrdcl3.install b/debian/libxrdcl3.install similarity index 100% rename from packaging/debian/libxrdcl3.install rename to debian/libxrdcl3.install diff --git a/packaging/debian/libxrdcrypto1.install b/debian/libxrdcrypto1.install similarity index 100% rename from packaging/debian/libxrdcrypto1.install rename to debian/libxrdcrypto1.install diff --git a/packaging/debian/libxrdcrypto2.install b/debian/libxrdcrypto2.install similarity index 100% rename from packaging/debian/libxrdcrypto2.install rename to debian/libxrdcrypto2.install diff --git a/packaging/debian/libxrdcryptolite1.install b/debian/libxrdcryptolite1.install similarity index 100% rename from packaging/debian/libxrdcryptolite1.install rename to debian/libxrdcryptolite1.install diff --git a/packaging/debian/libxrdcryptolite2.install b/debian/libxrdcryptolite2.install similarity index 100% rename from packaging/debian/libxrdcryptolite2.install rename to debian/libxrdcryptolite2.install diff --git a/packaging/debian/libxrdec1.install b/debian/libxrdec1.install similarity index 100% rename from packaging/debian/libxrdec1.install rename to debian/libxrdec1.install diff --git a/packaging/debian/libxrdffs2.install b/debian/libxrdffs2.install similarity index 100% rename from packaging/debian/libxrdffs2.install rename to debian/libxrdffs2.install diff --git a/packaging/debian/libxrdffs3.install b/debian/libxrdffs3.install similarity index 100% rename from packaging/debian/libxrdffs3.install rename to debian/libxrdffs3.install diff --git a/packaging/debian/libxrdhttputils1.install b/debian/libxrdhttputils1.install similarity index 100% rename from packaging/debian/libxrdhttputils1.install rename to debian/libxrdhttputils1.install diff --git a/packaging/debian/libxrdhttputils2.install b/debian/libxrdhttputils2.install similarity index 100% rename from packaging/debian/libxrdhttputils2.install rename to debian/libxrdhttputils2.install diff --git a/packaging/debian/libxrdposix2.install b/debian/libxrdposix2.install similarity index 100% rename from packaging/debian/libxrdposix2.install rename to debian/libxrdposix2.install diff --git a/packaging/debian/libxrdposix3.install b/debian/libxrdposix3.install similarity index 100% rename from packaging/debian/libxrdposix3.install rename to debian/libxrdposix3.install diff --git a/packaging/debian/libxrdposix3.lintian-overrides b/debian/libxrdposix3.lintian-overrides similarity index 100% rename from packaging/debian/libxrdposix3.lintian-overrides rename to debian/libxrdposix3.lintian-overrides diff --git a/packaging/debian/libxrdserver2.install b/debian/libxrdserver2.install similarity index 100% rename from packaging/debian/libxrdserver2.install rename to debian/libxrdserver2.install diff --git a/packaging/debian/libxrdserver3.install b/debian/libxrdserver3.install similarity index 100% rename from packaging/debian/libxrdserver3.install rename to debian/libxrdserver3.install diff --git a/packaging/debian/libxrdssilib1.install b/debian/libxrdssilib1.install similarity index 100% rename from packaging/debian/libxrdssilib1.install rename to debian/libxrdssilib1.install diff --git a/packaging/debian/libxrdssilib2.install b/debian/libxrdssilib2.install similarity index 100% rename from packaging/debian/libxrdssilib2.install rename to debian/libxrdssilib2.install diff --git a/packaging/debian/libxrdssishmap1.install b/debian/libxrdssishmap1.install similarity index 100% rename from packaging/debian/libxrdssishmap1.install rename to debian/libxrdssishmap1.install diff --git a/packaging/debian/libxrdssishmap2.install b/debian/libxrdssishmap2.install similarity index 100% rename from packaging/debian/libxrdssishmap2.install rename to debian/libxrdssishmap2.install diff --git a/packaging/debian/libxrdutils2.install b/debian/libxrdutils2.install similarity index 100% rename from packaging/debian/libxrdutils2.install rename to debian/libxrdutils2.install diff --git a/packaging/debian/libxrdutils3.install b/debian/libxrdutils3.install similarity index 100% rename from packaging/debian/libxrdutils3.install rename to debian/libxrdutils3.install diff --git a/packaging/debian/libxrdxml2.install b/debian/libxrdxml2.install similarity index 100% rename from packaging/debian/libxrdxml2.install rename to debian/libxrdxml2.install diff --git a/packaging/debian/libxrdxml3.install b/debian/libxrdxml3.install similarity index 100% rename from packaging/debian/libxrdxml3.install rename to debian/libxrdxml3.install diff --git a/packaging/debian/libxrootd-client-dev.install b/debian/libxrootd-client-dev.install similarity index 100% rename from packaging/debian/libxrootd-client-dev.install rename to debian/libxrootd-client-dev.install diff --git a/packaging/debian/libxrootd-dev.install b/debian/libxrootd-dev.install similarity index 100% rename from packaging/debian/libxrootd-dev.install rename to debian/libxrootd-dev.install diff --git a/packaging/debian/libxrootd-private-dev.install b/debian/libxrootd-private-dev.install similarity index 100% rename from packaging/debian/libxrootd-private-dev.install rename to debian/libxrootd-private-dev.install diff --git a/packaging/debian/libxrootd-server-dev.install b/debian/libxrootd-server-dev.install similarity index 100% rename from packaging/debian/libxrootd-server-dev.install rename to debian/libxrootd-server-dev.install diff --git a/packaging/debian/python3-xrootd.install b/debian/python3-xrootd.install similarity index 100% rename from packaging/debian/python3-xrootd.install rename to debian/python3-xrootd.install diff --git a/packaging/debian/rules b/debian/rules similarity index 100% rename from packaging/debian/rules rename to debian/rules diff --git a/packaging/debian/source/format b/debian/source/format similarity index 100% rename from packaging/debian/source/format rename to debian/source/format diff --git a/packaging/debian/xrootd-ceph-plugins.install b/debian/xrootd-ceph-plugins.install similarity index 100% rename from packaging/debian/xrootd-ceph-plugins.install rename to debian/xrootd-ceph-plugins.install diff --git a/packaging/debian/xrootd-ceph-plugins.lintian-overrides b/debian/xrootd-ceph-plugins.lintian-overrides similarity index 100% rename from packaging/debian/xrootd-ceph-plugins.lintian-overrides rename to debian/xrootd-ceph-plugins.lintian-overrides diff --git a/packaging/debian/xrootd-client-devel.install b/debian/xrootd-client-devel.install similarity index 100% rename from packaging/debian/xrootd-client-devel.install rename to debian/xrootd-client-devel.install diff --git a/packaging/debian/xrootd-client-http-plugins.install b/debian/xrootd-client-http-plugins.install similarity index 100% rename from packaging/debian/xrootd-client-http-plugins.install rename to debian/xrootd-client-http-plugins.install diff --git a/packaging/debian/xrootd-client-http-plugins.lintian-overrides b/debian/xrootd-client-http-plugins.lintian-overrides similarity index 100% rename from packaging/debian/xrootd-client-http-plugins.lintian-overrides rename to debian/xrootd-client-http-plugins.lintian-overrides diff --git a/packaging/debian/xrootd-client-libs.install b/debian/xrootd-client-libs.install similarity index 100% rename from packaging/debian/xrootd-client-libs.install rename to debian/xrootd-client-libs.install diff --git a/packaging/debian/xrootd-client-plugins.install b/debian/xrootd-client-plugins.install similarity index 100% rename from packaging/debian/xrootd-client-plugins.install rename to debian/xrootd-client-plugins.install diff --git a/packaging/debian/xrootd-client-plugins.lintian-overrides b/debian/xrootd-client-plugins.lintian-overrides similarity index 100% rename from packaging/debian/xrootd-client-plugins.lintian-overrides rename to debian/xrootd-client-plugins.lintian-overrides diff --git a/packaging/debian/xrootd-client.install b/debian/xrootd-client.install similarity index 100% rename from packaging/debian/xrootd-client.install rename to debian/xrootd-client.install diff --git a/packaging/debian/xrootd-clients.install b/debian/xrootd-clients.install similarity index 100% rename from packaging/debian/xrootd-clients.install rename to debian/xrootd-clients.install diff --git a/packaging/debian/xrootd-devel.install b/debian/xrootd-devel.install similarity index 100% rename from packaging/debian/xrootd-devel.install rename to debian/xrootd-devel.install diff --git a/packaging/debian/xrootd-doc.install b/debian/xrootd-doc.install similarity index 100% rename from packaging/debian/xrootd-doc.install rename to debian/xrootd-doc.install diff --git a/packaging/debian/xrootd-fuse.install b/debian/xrootd-fuse.install similarity index 100% rename from packaging/debian/xrootd-fuse.install rename to debian/xrootd-fuse.install diff --git a/packaging/debian/xrootd-libs.install b/debian/xrootd-libs.install similarity index 100% rename from packaging/debian/xrootd-libs.install rename to debian/xrootd-libs.install diff --git a/packaging/debian/xrootd-plugins.install b/debian/xrootd-plugins.install similarity index 100% rename from packaging/debian/xrootd-plugins.install rename to debian/xrootd-plugins.install diff --git a/packaging/debian/xrootd-plugins.lintian-overrides b/debian/xrootd-plugins.lintian-overrides similarity index 100% rename from packaging/debian/xrootd-plugins.lintian-overrides rename to debian/xrootd-plugins.lintian-overrides diff --git a/packaging/debian/xrootd-private-devel.install b/debian/xrootd-private-devel.install similarity index 100% rename from packaging/debian/xrootd-private-devel.install rename to debian/xrootd-private-devel.install diff --git a/packaging/debian/xrootd-scitokens-plugins.docs b/debian/xrootd-scitokens-plugins.docs similarity index 100% rename from packaging/debian/xrootd-scitokens-plugins.docs rename to debian/xrootd-scitokens-plugins.docs diff --git a/packaging/debian/xrootd-scitokens-plugins.install b/debian/xrootd-scitokens-plugins.install similarity index 100% rename from packaging/debian/xrootd-scitokens-plugins.install rename to debian/xrootd-scitokens-plugins.install diff --git a/packaging/debian/xrootd-scitokens-plugins.lintian-overrides b/debian/xrootd-scitokens-plugins.lintian-overrides similarity index 100% rename from packaging/debian/xrootd-scitokens-plugins.lintian-overrides rename to debian/xrootd-scitokens-plugins.lintian-overrides diff --git a/packaging/debian/xrootd-server-devel.install b/debian/xrootd-server-devel.install similarity index 100% rename from packaging/debian/xrootd-server-devel.install rename to debian/xrootd-server-devel.install diff --git a/packaging/debian/xrootd-server-libs.install b/debian/xrootd-server-libs.install similarity index 100% rename from packaging/debian/xrootd-server-libs.install rename to debian/xrootd-server-libs.install diff --git a/packaging/debian/xrootd-server-plugins.install b/debian/xrootd-server-plugins.install similarity index 100% rename from packaging/debian/xrootd-server-plugins.install rename to debian/xrootd-server-plugins.install diff --git a/packaging/debian/xrootd-server-plugins.lintian-overrides b/debian/xrootd-server-plugins.lintian-overrides similarity index 100% rename from packaging/debian/xrootd-server-plugins.lintian-overrides rename to debian/xrootd-server-plugins.lintian-overrides diff --git a/packaging/debian/xrootd-server.install b/debian/xrootd-server.install similarity index 100% rename from packaging/debian/xrootd-server.install rename to debian/xrootd-server.install diff --git a/packaging/debian/xrootd-server.postinst b/debian/xrootd-server.postinst similarity index 100% rename from packaging/debian/xrootd-server.postinst rename to debian/xrootd-server.postinst diff --git a/packaging/debian/xrootd-voms-plugins.install b/debian/xrootd-voms-plugins.install similarity index 100% rename from packaging/debian/xrootd-voms-plugins.install rename to debian/xrootd-voms-plugins.install diff --git a/packaging/debian/xrootd-voms-plugins.lintian-overrides b/debian/xrootd-voms-plugins.lintian-overrides similarity index 100% rename from packaging/debian/xrootd-voms-plugins.lintian-overrides rename to debian/xrootd-voms-plugins.lintian-overrides From 6339dc70fec53f54c67e61bfc9f2182e893d8753 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 15 Aug 2023 16:01:45 +0200 Subject: [PATCH 295/442] [RPM] Modernize spec for XRootD --- packaging/rhel/xrootd.spec.in | 1341 ++++++++++++++------------------- 1 file changed, 577 insertions(+), 764 deletions(-) diff --git a/packaging/rhel/xrootd.spec.in b/packaging/rhel/xrootd.spec.in index 4a6af8f1808..2a10c3556d2 100644 --- a/packaging/rhel/xrootd.spec.in +++ b/packaging/rhel/xrootd.spec.in @@ -1,196 +1,143 @@ -#------------------------------------------------------------------------------- -# Helper macros -#------------------------------------------------------------------------------- -%if %{?rhel:1}%{!?rhel:0} - # starting with rhel 7 we have systemd and macaroons, - %define use_systemd 1 - %define have_macaroons 1 - - %if %{rhel} == 7 - # we build both python2 and python3 bindings for EPEL7 - %define _with_python2 1 - %define _with_python3 1 - %else - # we only build both python3 bindings for EPEL>7 - %define _with_python2 0 - %define _with_python3 1 - %endif -%else - # do we have macaroons ? - %if %{?fedora}%{!?fedora:0} >= 28 - %define have_macaroons 1 - %else - %define have_macaroons 0 - %endif - # do we have systemd ? - %if %{?fedora}%{!?fedora:0} >= 19 - %define use_systemd 1 - %else - %define use_systemd 0 - %endif - # we only build python3 bindings for fedora - %define _with_python2 0 - %define _with_python3 1 +%bcond_with asan +%bcond_with ceph +%bcond_with clang +%bcond_with compat +%bcond_with docs +%bcond_with git + +%bcond_without tests +%bcond_without xrdec + +%if %{?rhel}%{!?rhel:0} == 7 +%bcond_with openssl11 %endif +%global compat_version 4.12.9 +%global devtoolset devtoolset-7 +%global llvmtoolset llvm-toolset-7 +Name: xrootd +Epoch: 1 +Release: 1%{?dist}%{?with_clang:.clang}%{?with_asan:.asan}%{?with_openssl11:.ssl11} +Summary: Extended ROOT File Server +Group: System Environment/Daemons +License: LGPL-3.0-or-later AND BSD-2-Clause AND BSD-3-Clause AND curl AND MIT AND Zlib +URL: https://xrootd.slac.stanford.edu -%if %{?_with_ceph11:1}%{!?_with_ceph11:0} - %define _with_ceph 1 +%if %{with git} +Version: %(git describe --match 'v*' | sed -e 's/v//; s/-rc/~rc/; s/-g/+git/; s/-/.post/; s/-/./') +Source0: %{name}.tar.gz +%else +Version: 5.6.1 +Source0: %{url}/download/v%{version}/%{name}-%{version}.tar.gz %endif -%if %{?rhel:1}%{!?rhel:0} - %if %{rhel} > 7 - %define use_cmake3 0 - %else - %define use_cmake3 1 - %endif -%else - %define use_cmake3 0 +%if %{with compat} +Source1: %{url}/download/v%{compat_version}/%{name}-%{compat_version}.tar.gz %endif +%undefine __cmake_in_source_build +%undefine __cmake3_in_source_build -# Remove default rpm python bytecompiling scripts -%global __os_install_post \ - %(echo '%{__os_install_post}' | \ - sed -e 's!/usr/lib[^[:space:]]*/brp-python-bytecompile[[:space:]].*$!!g \ - s!/usr/lib[^[:space:]]*/brp-python-hardlink[[:space:]].*$!!g') - -#------------------------------------------------------------------------------- -# Package definitions -#------------------------------------------------------------------------------- -Name: xrootd -Epoch: 1 -Version: __VERSION__ -Release: __RELEASE__%{?dist}%{?_with_clang:.clang}%{?_with_asan:.asan} -Summary: Extended ROOT file server -Group: System Environment/Daemons -License: LGPLv3+ -URL: http://xrootd.org/ - -%define compat_version 4.12.9 - -# git clone http://xrootd.org/repo/xrootd.git xrootd -# cd xrootd -# git-archive master | gzip -9 > ~/rpmbuild/SOURCES/xrootd.tgz -Source0: xrootd.tar.gz - -# Need to keep in sync with the compat_version above -# Cannot use variable, as makesrpm.sh cannot expand it -%if 0%{?_with_compat} -Source1: xrootd-4.12.9.tar.gz +%if %{with tests} +# CppUnit crashes with LTO enabled +%global _lto_cflags %nil %endif -BuildRoot: %{_tmppath}/%{name}-root - -%if %{use_cmake3} -BuildRequires: cmake3 +%if %{?rhel}%{!?rhel:0} == 7 +BuildRequires: cmake3 >= 3.16 +BuildRequires: %{devtoolset}-toolchain %else -BuildRequires: cmake -%endif -BuildRequires: krb5-devel -BuildRequires: readline-devel -BuildRequires: fuse-devel -BuildRequires: libxml2-devel -BuildRequires: krb5-devel -BuildRequires: zlib-devel -BuildRequires: ncurses-devel -BuildRequires: libcurl-devel -BuildRequires: libuuid-devel -BuildRequires: voms-devel >= 2.0.6 -BuildRequires: git -BuildRequires: pkgconfig -%if %{have_macaroons} -BuildRequires: libmacaroons-devel +BuildRequires: cmake >= 3.16 +BuildRequires: gcc-c++ +%endif +BuildRequires: make +BuildRequires: pkgconfig +BuildRequires: fuse-devel +BuildRequires: krb5-devel +BuildRequires: libcurl-devel +BuildRequires: tinyxml-devel +BuildRequires: libxml2-devel +BuildRequires: ncurses-devel +BuildRequires: perl-generators +BuildRequires: readline-devel +BuildRequires: zlib-devel +BuildRequires: selinux-policy-devel +BuildRequires: systemd-rpm-macros +BuildRequires: systemd-devel +%if %{?fedora}%{!?fedora:0} || %{?rhel}%{!?rhel:0} >= 8 +BuildRequires: python3-devel +BuildRequires: python3-pip +BuildRequires: python3-setuptools +BuildRequires: python3-wheel %endif -BuildRequires: json-c-devel - -%if %{_with_python2} -BuildRequires: python2-pip -BuildRequires: python2-devel -BuildRequires: python2-setuptools +%if %{?rhel}%{!?rhel:0} == 7 +BuildRequires: python2-devel +BuildRequires: python2-pip +BuildRequires: python2-setuptools +BuildRequires: python%{python3_pkgversion}-devel +BuildRequires: python%{python3_pkgversion}-pip +BuildRequires: python%{python3_pkgversion}-setuptools +BuildRequires: python%{python3_other_pkgversion}-devel +BuildRequires: python%{python3_other_pkgversion}-pip +BuildRequires: python%{python3_other_pkgversion}-setuptools +%endif +BuildRequires: json-c-devel +BuildRequires: libmacaroons-devel +BuildRequires: libuuid-devel +BuildRequires: voms-devel >= 2.0.6 +BuildRequires: scitokens-cpp-devel +BuildRequires: davix-devel + +%if %{with asan} +BuildRequires: libasan +%if %{?rhel}%{!?rhel:0} == 7 +BuildRequires: %{devtoolset}-libasan-devel %endif -%if %{_with_python3} -BuildRequires: python%{python3_pkgversion}-devel -BuildRequires: python%{python3_pkgversion}-setuptools +Requires: libasan %endif -BuildRequires: openssl-devel - -BuildRequires: selinux-policy-devel - -%if %{?_with_tests:1}%{!?_with_tests:0} -BuildRequires: cppunit-devel -BuildRequires: gtest-devel +%if %{with ceph} +BuildRequires: librados-devel +BuildRequires: libradosstriper-devel %endif -%if %{?_with_ceph:1}%{!?_with_ceph:0} - %if %{?_with_ceph11:1}%{!?_with_ceph11:0} -BuildRequires: librados-devel >= 11.0 -BuildRequires: libradosstriper-devel >= 11.0 - %else -BuildRequires: ceph-devel >= 0.87 - %endif +%if %{with clang} +%if %{?rhel}%{!?rhel:0} == 7 +BuildRequires: %{llvmtoolset}-clang +%else +BuildRequires: clang %endif - -%if %{?_with_xrdclhttp:1}%{!?_with_xrdclhttp:0} -BuildRequires: davix-devel %endif +%if %{with docs} BuildRequires: doxygen BuildRequires: graphviz -%if %{?rhel}%{!?rhel:0} == 5 -BuildRequires: graphviz-gd -%endif - -%if %{?_with_clang:1}%{!?_with_clang:0} -BuildRequires: clang +%{?el7:BuildRequires: python2-sphinx} +%{!?el7:BuildRequires: python3-sphinx} %endif -%if %{?_with_asan:1}%{!?_with_asan:0} -BuildRequires: libasan -%if %{?rhel}%{!?rhel:0} == 7 -BuildRequires: devtoolset-7-libasan-devel -%endif -Requires: libasan +%if %{with openssl11} +BuildRequires: openssl11-devel +%else +BuildRequires: openssl-devel %endif -%if %{?_with_scitokens:1}%{!?_with_scitokens:0} -BuildRequires: scitokens-cpp-devel +%if %{with tests} +BuildRequires: cppunit-devel +BuildRequires: gtest-devel %endif -%if %{?_with_isal:1}%{!?_with_isal:0} -BuildRequires: autoconf -BuildRequires: automake -BuildRequires: libtool -BuildRequires: yasm +%if %{with xrdec} +BuildRequires: autoconf +BuildRequires: automake +BuildRequires: libtool +BuildRequires: yasm %endif Requires: %{name}-server%{?_isa} = %{epoch}:%{version}-%{release} Requires: %{name}-selinux = %{epoch}:%{version}-%{release} -%if %{use_systemd} -BuildRequires: systemd -BuildRequires: systemd-devel -Requires(pre): systemd -Requires(post): systemd -Requires(preun): systemd -Requires(postun): systemd -%else -Requires(pre): shadow-utils -Requires(pre): chkconfig -Requires(post): chkconfig -Requires(preun): chkconfig -Requires(preun): initscripts -Requires(postun): initscripts -%endif - -%if %{?rhel}%{!?rhel:0} == 7 -BuildRequires: devtoolset-7 -%else -BuildRequires: gcc-c++ -%endif - %description The Extended root file server consists of a file server called xrootd and a cluster management server called cmsd. @@ -204,637 +151,537 @@ originally developed to cluster and load balance Objectivity/DB AMS database servers. It provides enhanced capability along with lower latency and increased throughput. -#------------------------------------------------------------------------------- -# libs -#------------------------------------------------------------------------------- +%package server +Summary: XRootD server daemons +Group: System Environment/Daemons +Requires: %{name}-libs%{?_isa} = %{epoch}:%{version}-%{release} +Requires: %{name}-client-libs%{?_isa} = %{epoch}:%{version}-%{release} +Requires: %{name}-server-libs%{?_isa} = %{epoch}:%{version}-%{release} +Requires: expect +Requires: logrotate +Requires(pre): shadow-utils +%{?systemd_requires} + +%description server +This package contains the XRootD servers without the SELinux support. +Unless you are installing on a system without SELinux also install the +xrootd-selinux package. + +%package selinux +Summary: SELinux policy modules for the XRootD servers +Group: System Environment/Base +BuildArch: noarch +Requires: selinux-policy +Requires(post): policycoreutils +Requires(postun): policycoreutils + +%description selinux +This package contains SELinux policy modules for the xrootd-server package. + %package libs -Summary: Libraries used by xrootd servers and clients +Summary: Libraries used by XRootD servers and clients Group: System Environment/Libraries %description libs -This package contains libraries used by the xrootd servers and clients. +This package contains libraries used by the XRootD servers and clients. -#------------------------------------------------------------------------------- -# devel -#------------------------------------------------------------------------------ %package devel -Summary: Development files for xrootd +Summary: Development files for XRootD Group: Development/Libraries +Provides: %{name}-libs-devel = %{epoch}:%{version}-%{release} +Provides: %{name}-libs-devel%{?_isa} = %{epoch}:%{version}-%{release} Requires: %{name}-libs%{?_isa} = %{epoch}:%{version}-%{release} +Obsoletes: %{name}-libs-devel < %{epoch}:%{version}-%{release} %description devel -This package contains header files and development libraries for xrootd +This package contains header files and development libraries for XRootD development. -#------------------------------------------------------------------------------- -# client-libs -#------------------------------------------------------------------------------- %package client-libs -Summary: Libraries used by xrootd clients +Summary: Libraries used by XRootD clients Group: System Environment/Libraries Requires: %{name}-libs%{?_isa} = %{epoch}:%{version}-%{release} %description client-libs -This package contains libraries used by xrootd clients. +This package contains libraries used by XRootD clients. -#------------------------------------------------------------------------------- -# client-devel -#------------------------------------------------------------------------------- %package client-devel -Summary: Development files for xrootd clients +Summary: Development files for XRootD clients Group: Development/Libraries +Provides: %{name}-cl-devel = %{epoch}:%{version}-%{release} +Provides: %{name}-cl-devel%{?_isa} = %{epoch}:%{version}-%{release} Requires: %{name}-devel%{?_isa} = %{epoch}:%{version}-%{release} Requires: %{name}-client-libs%{?_isa} = %{epoch}:%{version}-%{release} +Obsoletes: %{name}-cl-devel < %{epoch}:%{version}-%{release} %description client-devel -This package contains header files and development libraries for xrootd +This package contains header files and development libraries for XRootD client development. -#------------------------------------------------------------------------------- -# server-libs -#------------------------------------------------------------------------------- %package server-libs -Summary: Libraries used by xrootd servers +Summary: Libraries used by XRootD servers Group: System Environment/Libraries Requires: %{name}-libs%{?_isa} = %{epoch}:%{version}-%{release} Requires: %{name}-client-libs%{?_isa} = %{epoch}:%{version}-%{release} -Obsoletes: xrootd-macaroons -Obsoletes: xrootd-tpc %description server-libs -This package contains libraries used by xrootd servers. +This package contains libraries used by XRootD servers. -#------------------------------------------------------------------------------- -# server-devel -#------------------------------------------------------------------------------- %package server-devel -Summary: Development files for xrootd servers +Summary: Development files for XRootD servers Group: Development/Libraries Requires: %{name}-devel%{?_isa} = %{epoch}:%{version}-%{release} Requires: %{name}-client-devel%{?_isa} = %{epoch}:%{version}-%{release} Requires: %{name}-server-libs%{?_isa} = %{epoch}:%{version}-%{release} %description server-devel -This package contains header files and development libraries for xrootd +This package contains header files and development libraries for XRootD server development. -#------------------------------------------------------------------------------- -# private devel -#------------------------------------------------------------------------------- %package private-devel -Summary: Private xrootd headers +Summary: Private XRootD headers Group: Development/Libraries Requires: %{name}-devel%{?_isa} = %{epoch}:%{version}-%{release} Requires: %{name}-client-devel%{?_isa} = %{epoch}:%{version}-%{release} Requires: %{name}-server-devel%{?_isa} = %{epoch}:%{version}-%{release} +Requires: %{name}-client-libs%{?_isa} = %{epoch}:%{version}-%{release} %description private-devel -This package contains some private xrootd headers. Backward and forward +This package contains some private XRootD headers. Backward and forward compatibility between versions is not guaranteed for these headers. -#------------------------------------------------------------------------------- -# client -#------------------------------------------------------------------------------- %package client -Summary: Xrootd command line client tools +Summary: XRootD command line client tools Group: Applications/Internet +Provides: %{name}-cl = %{epoch}:%{version}-%{release} +Provides: %{name}-cl%{?_isa} = %{epoch}:%{version}-%{release} Requires: %{name}-libs%{?_isa} = %{epoch}:%{version}-%{release} Requires: %{name}-client-libs%{?_isa} = %{epoch}:%{version}-%{release} +Obsoletes: %{name}-cl < %{epoch}:%{version}-%{release} %description client This package contains the command line tools used to communicate with -xrootd servers. - -#------------------------------------------------------------------------------- -# server -#------------------------------------------------------------------------------- -%package server -Summary: Extended ROOT file server -Group: System Environment/Daemons -Requires: %{name}-libs = %{epoch}:%{version}-%{release} -Requires: %{name}-client-libs = %{epoch}:%{version}-%{release} -Requires: %{name}-server-libs = %{epoch}:%{version}-%{release} -Requires: expect - -%description server -XRootD server binaries +XRootD servers. -#------------------------------------------------------------------------------- -# fuse -#------------------------------------------------------------------------------- %package fuse -Summary: Xrootd FUSE tool +Summary: XRootD FUSE tool Group: Applications/Internet Requires: %{name}-libs%{?_isa} = %{epoch}:%{version}-%{release} Requires: %{name}-client-libs%{?_isa} = %{epoch}:%{version}-%{release} Requires: fuse %description fuse -This package contains the FUSE (file system in user space) xrootd mount +This package contains the FUSE (file system in user space) XRootD mount tool. -#------------------------------------------------------------------------------- -# Python bindings -#------------------------------------------------------------------------------- - -%if %{_with_python2} -#------------------------------------------------------------------------------- -# python2 -#------------------------------------------------------------------------------- -%package -n python2-%{name} -Summary: Python 2 bindings for XRootD -Group: Development/Libraries -Provides: python-%{name} -Provides: %{name}-python = %{epoch}:%{version}-%{release} -Obsoletes: %{name}-python < 1:4.8.0-1 -Requires: %{name}-client-libs%{?_isa} = %{epoch}:%{version}-%{release} - -%description -n python2-xrootd -Python 2 bindings for XRootD -%endif - -%if %{_with_python3} -#------------------------------------------------------------------------------- -# python3 -#------------------------------------------------------------------------------- -%package -n python%{python3_pkgversion}-%{name} -Summary: Python 3 bindings for XRootD -Group: Development/Libraries -%{?python_provide:%python_provide python%{python3_pkgversion}-%{name}} -Requires: %{name}-client-libs%{?_isa} = %{epoch}:%{version}-%{release} +%package voms +Summary: VOMS attribute extractor plugin for XRootD +Group: System Environment/Libraries +Provides: vomsxrd = %{epoch}:%{version}-%{release} +Provides: %{name}-voms-plugin = %{epoch}:%{version}-%{release} +Provides: xrdhttpvoms = %{epoch}:%{version}-%{release} +Requires: %{name}-libs%{?_isa} = %{epoch}:%{version}-%{release} +Obsoletes: %{name}-voms-plugin < 1:0.6.0-3 +Obsoletes: xrdhttpvoms < 0.2.5-9 +Obsoletes: vomsxrd < 1:0.6.0-4 -%description -n python%{python3_pkgversion}-%{name} -Python 3 bindings for XRootD -%endif +%description voms +The VOMS attribute extractor plugin for XRootD. -#------------------------------------------------------------------------------- -# doc -#------------------------------------------------------------------------------- -%package doc -Summary: Developer documentation for the xrootd libraries -Group: Documentation -%if %{?fedora}%{!?fedora:0} >= 10 || %{?rhel}%{!?rhel:0} >= 6 -BuildArch: noarch -%endif +%package scitokens +Summary: SciTokens authorization support for XRootD +Group: System Environment/Libraries +License: Apache-2.0 AND BSD-2-Clause AND BSD-3-Clause +Requires: %{name}-server%{?_isa} = %{epoch}:%{version}-%{release} +Requires: %{name}-libs%{?_isa} = %{epoch}:%{version}-%{release} -%description doc -This package contains the API documentation of the xrootd libraries. +%description scitokens +This ACC (authorization) plugin for the XRootD framework utilizes the +SciTokens library to validate and extract authorization claims from a +SciToken passed during a transfer. Configured appropriately, this +allows the XRootD server admin to delegate authorization decisions for +a subset of the namespace to an external issuer. -#------------------------------------------------------------------------------- -# selinux -#------------------------------------------------------------------------------- -%package selinux -Summary: SELinux policy extensions for xrootd. -Group: System Environment/Base -%if %{?fedora}%{!?fedora:0} >= 10 || %{?rhel}%{!?rhel:0} >= 6 -BuildArch: noarch -%endif -Requires(post): policycoreutils -Requires(postun): policycoreutils -Requires: selinux-policy +%package -n xrdcl-http +Summary: HTTP client plugin for XRootD +Group: System Environment/Libraries +Requires: %{name}-libs%{?_isa} = %{epoch}:%{version}-%{release} +Requires: %{name}-client-libs%{?_isa} = %{epoch}:%{version}-%{release} -%description selinux -SELinux policy extensions for running xrootd while in enforcing mode. +%description -n xrdcl-http +xrdcl-http is an XRootD client plugin which allows XRootD to interact +with HTTP repositories. -#------------------------------------------------------------------------------- -# ceph -#------------------------------------------------------------------------------- -%if %{?_with_ceph:1}%{!?_with_ceph:0} +%if %{with ceph} %package ceph -Summary: Ceph back-end plug-in for XRootD -Group: Development/Tools -Requires: %{name}-server = %{epoch}:%{version}-%{release} +Summary: XRootD plugin for interfacing with the Ceph storage platform +Group: System Environment/Libraries +Requires: %{name}-libs%{?_isa} = %{epoch}:%{version}-%{release} + %description ceph -Ceph back-end plug-in for XRootD. +The xrootd-ceph is an OSS layer plugin for the XRootD server for +interfacing with the Ceph storage platform. %endif -#------------------------------------------------------------------------------- -# xrdcl-http -#------------------------------------------------------------------------------- -%if %{?_with_xrdclhttp:1}%{!?_with_xrdclhttp:0} -%package -n xrdcl-http -Summary: HTTP client plug-in for XRootD client -Group: System Environment/Libraries -Requires: %{name}-client = %{epoch}:%{version}-%{release} -%description -n xrdcl-http -xrdcl-http is an XRootD client plugin which allows XRootD to interact -with HTTP repositories. +%if %{?rhel}%{!?rhel:0} == 7 +%package -n python2-%{name} +Summary: Python 2 bindings for XRootD +Group: System Environment/Libraries +%py_provides python2-%{name} +Provides: %{name}-python = %{epoch}:%{version}-%{release} +Requires: %{name}-libs%{?_isa} = %{epoch}:%{version}-%{release} +Requires: %{name}-client-libs%{?_isa} = %{epoch}:%{version}-%{release} +Obsoletes: %{name}-python < %{epoch}:%{version}-%{release} + +%description -n python2-%{name} +This package contains Python 2 bindings for XRootD. %endif -#------------------------------------------------------------------------------- -# xrootd-voms -#------------------------------------------------------------------------------- -%package voms -Summary: VOMS attribute extractor plug-in for XRootD -Group: System Environment/Libraries -Provides: vomsxrd = %{epoch}:%{version}-%{release} -Obsoletes: vomsxrd < 1:4.12.4-1 -Requires: %{name}-libs = %{epoch}:%{version}-%{release} -Obsoletes: xrootd-voms-plugin -%description voms -The VOMS attribute extractor plug-in for XRootD. +%package -n python%{python3_pkgversion}-%{name} +Summary: Python 3 bindings for XRootD +Group: System Environment/Libraries +%py_provides python%{python3_pkgversion}-%{name} +Requires: %{name}-libs%{?_isa} = %{epoch}:%{version}-%{release} +Requires: %{name}-client-libs%{?_isa} = %{epoch}:%{version}-%{release} -#------------------------------------------------------------------------------- -# xrootd-scitokens -#------------------------------------------------------------------------------- -%if %{?_with_scitokens:1}%{!?_with_scitokens:0} -%package scitokens -Summary: SciTokens authentication plugin for XRootD -Group: Development/Tools -Requires: %{name}-server = %{epoch}:%{version}-%{release} -%description scitokens -SciToken athorization plug-in for XRootD. -%endif +%description -n python%{python3_pkgversion}-%{name} +This package contains Python 3 bindings for XRootD. + +%if %{?rhel}%{!?rhel:0} == 7 +%package -n python%{?python3_other_pkgversion}-%{name} +Summary: Python 3 bindings for XRootD +Group: System Environment/Libraries +%py_provides python%{python3_other_pkgversion}-%{name} +Requires: %{name}-libs%{?_isa} = %{epoch}:%{version}-%{release} +Requires: %{name}-client-libs%{?_isa} = %{epoch}:%{version}-%{release} -#------------------------------------------------------------------------------- -# tests -#------------------------------------------------------------------------------- -%if %{?_with_tests:1}%{!?_with_tests:0} -%package tests -Summary: CPPUnit tests -Group: Development/Tools -Requires: %{name}-client = %{epoch}:%{version}-%{release} -%description tests -This package contains a set of CPPUnit tests for xrootd. +%description -n python%{?python3_other_pkgversion}-%{name} +This package contains Python 3 bindings for XRootD. %endif -%if 0%{?_with_compat} -#------------------------------------------------------------------------------- -# client-compat -#------------------------------------------------------------------------------- +%package doc +Summary: Developer documentation for the XRootD libraries +Group: Documentation +BuildArch: noarch + +%description doc +This package contains the API documentation of the XRootD libraries. + +%if %{with compat} %package client-compat Summary: XRootD 4 compatibility client libraries Group: System Environment/Libraries %description client-compat -This package contains compatibility libraries for xrootd 4 clients. +This package contains compatibility libraries for XRootD 4 clients. -#------------------------------------------------------------------------------- -# server-compat -#------------------------------------------------------------------------------- %package server-compat Summary: XRootD 4 compatibility server binaries Group: System Environment/Daemons Requires: %{name}-libs%{?_isa} = %{epoch}:%{version}-%{release} %description server-compat -This package contains compatibility binaries for xrootd 4 servers. - +This package contains compatibility binaries for XRootD 4 servers. %endif -#------------------------------------------------------------------------------- -# Build instructions -#------------------------------------------------------------------------------- %prep -%if 0%{?_with_compat} -%setup -c -n xrootd-compat -a 1 -T +%if %{with compat} +%autosetup -T -b 1 -n %{name}-%{compat_version} %endif -%setup -c -n xrootd +%if %{with git} +%autosetup -n %{name} +%else +%autosetup +%endif %build - %if %{?rhel}%{!?rhel:0} == 7 -. /opt/rh/devtoolset-7/enable +. /opt/rh/%{devtoolset}/enable +%if %{with clang} +. /opt/rh/%{llvmtoolset}/enable +%endif %endif -cd xrootd - -%if %{?_with_clang:1}%{!?_with_clang:0} +%if %{with clang} export CC=clang export CXX=clang++ %endif -mkdir build -pushd build - -%if %{use_cmake3} -cmake3 \ -%else -cmake \ -%endif - -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=RelWithDebInfo \ - -DFORCE_WERROR=TRUE \ -%if %{?_with_tests:1}%{!?_with_tests:0} - -DENABLE_TESTS=TRUE \ +%if %{?fedora}%{!?fedora:0} >= 36 +# Mark some warnings from gcc 12 as not errors +# These are likely bogus - hopefully they can be fixed in gcc updates +%ifarch %{arm} +%set_build_flags +CXXFLAGS="${CXXFLAGS} -Wno-error=stringop-overflow" +%endif +%endif + +%if %{with compat} +%__cmake3 \ + -S %{_builddir}/%{name}-%{compat_version} \ + -B %{_builddir}/%{name}-%{compat_version}/build \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_C_FLAGS_RELEASE:STRING='%{optflags}' \ + -DCMAKE_CXX_FLAGS_RELEASE:STRING='%{optflags}' \ + -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON \ + -DCMAKE_INSTALL_PREFIX:PATH=%{_prefix} \ + -DINCLUDE_INSTALL_DIR:PATH=%{_includedir} \ + -DCMAKE_INSTALL_LIBDIR:PATH=%{_libdir} \ + -DCMAKE_INSTALL_SYSCONFDIR:PATH=%{_sysconfdir} \ + -DFORCE_ENABLED:BOOL=TRUE \ + -DUSE_SYSTEM_ISAL:BOOL=FALSE \ + -DENABLE_ASAN:BOOL=%{with asan} \ + -DENABLE_FUSE:BOOL=TRUE \ + -DENABLE_KRB5:BOOL=TRUE \ + -DENABLE_MACAROONS:BOOL=TRUE \ + -DENABLE_READLINE:BOOL=TRUE \ + -DENABLE_SCITOKENS:BOOL=TRUE \ + -DENABLE_TESTS:BOOL=FALSE \ + -DENABLE_VOMS:BOOL=TRUE \ + -DENABLE_XRDCL:BOOL=TRUE \ + -DENABLE_XRDCLHTTP:BOOL=TRUE \ + -DENABLE_XRDEC:BOOL=%{with xrdec} \ + -DXRDCEPH_SUBMODULE:BOOL=%{with ceph} \ + -DENABLE_XRDCLHTTP:BOOL=TRUE \ + -DXRDCL_ONLY:BOOL=FALSE \ + -DXRDCL_LIB_ONLY:BOOL=FALSE \ + -DENABLE_PYTHON:BOOL=FALSE +make -C %{_builddir}/%{name}-%{compat_version}/build %{?_smp_mflags} +%endif + +%cmake3 \ + -DFORCE_ENABLED:BOOL=TRUE \ + -DUSE_SYSTEM_ISAL:BOOL=FALSE \ + -DENABLE_ASAN:BOOL=%{with asan} \ + -DENABLE_FUSE:BOOL=TRUE \ + -DENABLE_KRB5:BOOL=TRUE \ + -DENABLE_MACAROONS:BOOL=TRUE \ + -DENABLE_READLINE:BOOL=TRUE \ + -DENABLE_SCITOKENS:BOOL=TRUE \ + -DENABLE_TESTS:BOOL=%{with tests} \ + -DENABLE_VOMS:BOOL=TRUE \ + -DENABLE_XRDCL:BOOL=TRUE \ + -DENABLE_XRDCLHTTP:BOOL=TRUE \ + -DENABLE_XRDEC:BOOL=%{with xrdec} \ + -DXRDCEPH_SUBMODULE:BOOL=%{with ceph} \ + -DENABLE_XRDCLHTTP:BOOL=TRUE \ + -DXRDCL_ONLY:BOOL=FALSE \ + -DXRDCL_LIB_ONLY:BOOL=FALSE \ +%if %{with openssl11} + -DOPENSSL_INCLUDE_DIR=/usr/include/openssl11 \ + -DOPENSSL_CRYPTO_LIBRARY=/usr/lib64/libcrypto.so.1.1 \ + -DOPENSSL_SSL_LIBRARY=/usr/lib64/libssl.so.1.1 \ +%endif + -DENABLE_PYTHON:BOOL=TRUE \ + -DINSTALL_PYTHON_BINDINGS:BOOL=FALSE \ +%if %{?rhel}%{!?rhel:0} == 7 + -DXRD_PYTHON_REQ_VERSION=%{python2_version} %else - -DENABLE_TESTS=FALSE \ -%endif -%if %{?_with_asan:1}%{!?_with_asan:0} - -DENABLE_ASAN=TRUE \ + -DXRD_PYTHON_REQ_VERSION=%{python3_version} %endif -%if %{?_with_ceph:1}%{!?_with_ceph:0} - -DXRDCEPH_SUBMODULE=TRUE \ -%endif -%if %{?_with_xrdclhttp:1}%{!?_with_xrdclhttp:0} - -DENABLE_XRDCLHTTP=TRUE \ -%endif -%if %{?_with_isal:1}%{!?_with_isal:0} - -DENABLE_XRDEC=TRUE \ -%endif - -DXRootD_VERSION_STRING=v%{version} \ - -DINSTALL_PYTHON_BINDINGS=FALSE \ - ../ -make -i VERBOSE=1 %{?_smp_mflags} -popd +%cmake3_build -pushd packaging/common -make -f /usr/share/selinux/devel/Makefile -popd +make -C packaging/common -f /usr/share/selinux/devel/Makefile +%if %{with docs} doxygen Doxyfile - -%if 0%{?_with_compat} -pushd $RPM_BUILD_DIR/xrootd-compat/xrootd -mkdir build -pushd build -%if %{use_cmake3} -cmake3 \ -%else -cmake \ %endif - -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=RelWithDebInfo \ - -DFORCE_WERROR=TRUE \ -%if %{?_with_tests:1}%{!?_with_tests:0} - -DENABLE_TESTS=TRUE \ -%else - -DENABLE_TESTS=FALSE \ -%endif -%if %{?_with_ceph:1}%{!?_with_ceph:0} - -DXRDCEPH_SUBMODULE=TRUE \ -%endif -%if %{?_with_xrdclhttp:1}%{!?_with_xrdclhttp:0} - -DENABLE_XRDEC=TRUE \ -%endif - ../ -make -i VERBOSE=1 %{?_smp_mflags} -popd -popd +%if %{with tests} +%check +%ctest3 %endif -%undefine _hardened_build - -pushd build/bindings/python -# build python3 bindings -%if %{_with_python2} -%py2_build -%endif -# build python2 bindings -%if %{_with_python3} -%py3_build +%install +%if %{?rhel}%{!?rhel:0} == 7 +. /opt/rh/%{devtoolset}/enable %endif -popd -%check -cd xrootd/build -%if %{use_cmake3} -ctest3 --output-on-failure -%else -ctest --output-on-failure -%endif +%if %{with compat} +pushd %{_builddir}/%{name}-%{compat_version}/build -#------------------------------------------------------------------------------- -# Installation -#------------------------------------------------------------------------------- -%install -rm -rf $RPM_BUILD_ROOT - -#------------------------------------------------------------------------------- -# Install compat -#------------------------------------------------------------------------------- -%if 0%{?_with_compat} -pushd $RPM_BUILD_DIR/xrootd-compat/xrootd/build -make install DESTDIR=$RPM_BUILD_ROOT -rm -rf $RPM_BUILD_ROOT%{_includedir} -rm -rf $RPM_BUILD_ROOT%{_datadir} -rm -f $RPM_BUILD_ROOT%{_bindir}/{cconfig,cns_ssi,frm_admin,frm_xfragent,mpxstats} -rm -f $RPM_BUILD_ROOT%{_bindir}/{wait41,xprep,xrd,xrdadler32,xrdcrc32c,XrdCnsd,xrdcopy} -rm -f $RPM_BUILD_ROOT%{_bindir}/{xrdcks,xrdcp,xrdcp-old,xrdfs,xrdgsiproxy,xrdpwdadmin} -rm -f $RPM_BUILD_ROOT%{_bindir}/{xrdqstats,xrdsssadmin,xrdstagetool,xrootdfs} -rm -f $RPM_BUILD_ROOT%{_libdir}/libXrdAppUtils.so -rm -f $RPM_BUILD_ROOT%{_libdir}/{libXrdClient.so,libXrdCl.so,libXrdCryptoLite.so} -rm -f $RPM_BUILD_ROOT%{_libdir}/{libXrdCrypto.so,libXrdFfs.so,libXrdMain.so} -rm -f $RPM_BUILD_ROOT%{_libdir}/{libXrdOfs.so,libXrdPosixPreload.so,libXrdPosix.so} -rm -f $RPM_BUILD_ROOT%{_libdir}/{libXrdServer.so,libXrdUtils.so} +make install DESTDIR=%{buildroot} + +rm -rf %{buildroot}%{_datadir} %{buildroot}%{_includedir} +rm -f %{buildroot}%{_libdir}/libXrd{AppUtils,Cl,Client,Crypto,CryptoLite}.so +rm -f %{buildroot}%{_libdir}/libXrd{Ffs,Main,Ofs,Posix*,Server,Utils}.so for i in cmsd frm_purged frm_xfrd xrootd; do - mv $RPM_BUILD_ROOT%{_bindir}/$i $RPM_BUILD_ROOT%{_bindir}/${i}-4 + mv %{buildroot}%{_bindir}/$i %{buildroot}%{_bindir}/${i}-4 done -rm -f $RPM_BUILD_ROOT%{python2_sitearch}/xrootd-v%{compat_version}*.egg-info +pushd %{buildroot}%{_bindir} +find . ! -name '*-4' -delete popd -%endif -#------------------------------------------------------------------------------- -# Install 5.x.y -#------------------------------------------------------------------------------- -pushd xrootd -pushd build -make install DESTDIR=$RPM_BUILD_ROOT popd +%endif -# configuration stuff -rm -rf $RPM_BUILD_ROOT%{_sysconfdir}/xrootd/* - -# ceph posix unversioned so -rm -f $RPM_BUILD_ROOT%{_libdir}/libXrdCephPosix.so - -# config paths -mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/%{name}/config.d/ - -# var paths -mkdir -p $RPM_BUILD_ROOT%{_var}/log/xrootd -mkdir -p $RPM_BUILD_ROOT%{_var}/run/xrootd -mkdir -p $RPM_BUILD_ROOT%{_var}/spool/xrootd - -# init stuff -mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/xrootd - -%if %{use_systemd} - -mkdir -p $RPM_BUILD_ROOT%{_unitdir} -install -m 644 packaging/common/xrootd@.service $RPM_BUILD_ROOT%{_unitdir} -install -m 644 packaging/common/xrdhttp@.socket $RPM_BUILD_ROOT%{_unitdir} -install -m 644 packaging/common/xrootd@.socket $RPM_BUILD_ROOT%{_unitdir} -install -m 644 packaging/common/cmsd@.service $RPM_BUILD_ROOT%{_unitdir} -install -m 644 packaging/common/frm_xfrd@.service $RPM_BUILD_ROOT%{_unitdir} -install -m 644 packaging/common/frm_purged@.service $RPM_BUILD_ROOT%{_unitdir} - -# tmpfiles.d -mkdir -p $RPM_BUILD_ROOT%{_tmpfilesdir} -install -m 0644 packaging/rhel/xrootd.tmpfiles $RPM_BUILD_ROOT%{_tmpfilesdir}/%{name}.conf - -%else - -mkdir -p $RPM_BUILD_ROOT%{_initrddir} -mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/sysconfig -install -m 644 packaging/rhel/xrootd.sysconfig $RPM_BUILD_ROOT%{_sysconfdir}/sysconfig/xrootd +%cmake3_install -install -m 755 packaging/rhel/cmsd.init $RPM_BUILD_ROOT%{_initrddir}/cmsd -install -m 755 packaging/rhel/frm_purged.init $RPM_BUILD_ROOT%{_initrddir}/frm_purged -install -m 755 packaging/rhel/frm_xfrd.init $RPM_BUILD_ROOT%{_initrddir}/frm_xfrd -install -m 755 packaging/rhel/xrootd.init $RPM_BUILD_ROOT%{_initrddir}/xrootd -install -m 755 packaging/rhel/xrootd.functions $RPM_BUILD_ROOT%{_initrddir}/xrootd.functions +# Remove test binaries and libraries +%if %{with tests} + rm -f %{buildroot}%{_bindir}/test-runner + rm -f %{buildroot}%{_bindir}/xrdshmap + rm -f %{buildroot}%{_libdir}/libXrd*Tests* + rm -f %{buildroot}%{_libdir}/libXrdClTestMonitor*.so +%endif +%if %{with ceph} + rm -f %{buildroot}%{_libdir}/libXrdCephPosix.so %endif -# logrotate -mkdir $RPM_BUILD_ROOT%{_sysconfdir}/logrotate.d -install -p -m 644 packaging/common/xrootd.logrotate $RPM_BUILD_ROOT%{_sysconfdir}/logrotate.d/xrootd +rm -f %{buildroot}%{python3_sitearch}/xrootd-*.*-info/direct_url.json +rm -f %{buildroot}%{python3_sitearch}/xrootd-*.*-info/RECORD +[ -r %{buildroot}%{python3_sitearch}/xrootd-*.*-info/INSTALLER ] && \ + sed s/pip/rpm/ -i %{buildroot}%{python3_sitearch}/xrootd-*.*-info/INSTALLER -install -m 644 packaging/common/xrootd-clustered.cfg $RPM_BUILD_ROOT%{_sysconfdir}/xrootd/xrootd-clustered.cfg -install -m 644 packaging/common/xrootd-standalone.cfg $RPM_BUILD_ROOT%{_sysconfdir}/xrootd/xrootd-standalone.cfg -install -m 644 packaging/common/xrootd-filecache-clustered.cfg $RPM_BUILD_ROOT%{_sysconfdir}/xrootd/xrootd-filecache-clustered.cfg -install -m 644 packaging/common/xrootd-filecache-standalone.cfg $RPM_BUILD_ROOT%{_sysconfdir}/xrootd/xrootd-filecache-standalone.cfg -%if %{use_systemd} -install -m 644 packaging/common/xrootd-http.cfg $RPM_BUILD_ROOT%{_sysconfdir}/xrootd/xrootd-http.cfg -%endif +%{__python3} -m pip install \ + --no-deps --ignore-installed --disable-pip-version-check --verbose \ + --prefix %{buildroot}%{_prefix} %{_vpath_builddir}/bindings/python -# client plug-in config -mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/xrootd/client.plugins.d -install -m 644 packaging/common/client-plugin.conf.example $RPM_BUILD_ROOT%{_sysconfdir}/xrootd/client.plugins.d/client-plugin.conf.example -install -m 644 packaging/common/recorder.conf $RPM_BUILD_ROOT%{_sysconfdir}/xrootd/client.plugins.d/recorder.conf +%if %{?rhel}%{!?rhel:0} == 7 +%{__python2} -m pip install \ + --no-deps --ignore-installed --disable-pip-version-check --verbose \ + --prefix %{buildroot}%{_prefix} %{_vpath_builddir}/bindings/python -%if %{?_with_xrdclhttp:1}%{!?_with_xrdclhttp:0} -install -m 644 packaging/common/http.client.conf.example $RPM_BUILD_ROOT%{_sysconfdir}/xrootd/client.plugins.d/xrdcl-http-plugin.conf +%{__python3_other} -m pip install \ + --no-deps --ignore-installed --disable-pip-version-check --verbose \ + --prefix %{buildroot}%{_prefix} %{_vpath_builddir}/bindings/python %endif -# client config -install -m 644 packaging/common/client.conf $RPM_BUILD_ROOT%{_sysconfdir}/xrootd/client.conf - -# documentation -mkdir -p %{buildroot}%{_docdir}/%{name}-%{version} -cp -pr doxydoc/html %{buildroot}%{_docdir}/%{name}-%{version} +%if %{with docs} +%if %{?rhel}%{!?rhel:0} == 7 +LD_LIBRARY_PATH=%{buildroot}%{_libdir} \ +PYTHONPATH=%{buildroot}%{python2_sitearch} \ +PYTHONDONTWRITEBYTECODE=1 \ +make -C bindings/python/docs html +%endif +%if %{?fedora}%{!?fedora:0} || %{?rhel}%{!?rhel:0} >= 8 +LD_LIBRARY_PATH=%{buildroot}%{_libdir} \ +PYTHONPATH=%{buildroot}%{python3_sitearch} \ +PYTHONDONTWRITEBYTECODE=1 \ +make -C bindings/python/docs html SPHINXBUILD=sphinx-build-3 +%endif +%endif + +# Service unit files +mkdir -p %{buildroot}%{_unitdir} +install -m 644 packaging/common/xrootd@.service %{buildroot}%{_unitdir} +install -m 644 packaging/common/xrootd@.socket %{buildroot}%{_unitdir} +install -m 644 packaging/common/xrdhttp@.socket %{buildroot}%{_unitdir} +install -m 644 packaging/common/cmsd@.service %{buildroot}%{_unitdir} +install -m 644 packaging/common/frm_xfrd@.service %{buildroot}%{_unitdir} +install -m 644 packaging/common/frm_purged@.service %{buildroot}%{_unitdir} +mkdir -p %{buildroot}%{_tmpfilesdir} +install -m 644 packaging/rhel/xrootd.tmpfiles %{buildroot}%{_tmpfilesdir}/%{name}.conf + +# Server config +mkdir -p %{buildroot}%{_sysconfdir}/%{name} +install -m 644 -p packaging/common/%{name}-clustered.cfg \ + %{buildroot}%{_sysconfdir}/%{name}/%{name}-clustered.cfg +install -m 644 -p packaging/common/%{name}-standalone.cfg \ + %{buildroot}%{_sysconfdir}/%{name}/%{name}-standalone.cfg +install -m 644 -p packaging/common/%{name}-filecache-clustered.cfg \ + %{buildroot}%{_sysconfdir}/%{name}/%{name}-filecache-clustered.cfg +install -m 644 -p packaging/common/%{name}-filecache-standalone.cfg \ + %{buildroot}%{_sysconfdir}/%{name}/%{name}-filecache-standalone.cfg +sed 's!/usr/lib64/!!' packaging/common/%{name}-http.cfg > \ + %{buildroot}%{_sysconfdir}/%{name}/%{name}-http.cfg + +# Client config +mkdir -p %{buildroot}%{_sysconfdir}/%{name}/client.plugins.d +install -m 644 -p packaging/common/client.conf \ + %{buildroot}%{_sysconfdir}/%{name}/client.conf +sed 's!/usr/lib/!!' packaging/common/client-plugin.conf.example > \ + %{buildroot}%{_sysconfdir}/%{name}/client.plugins.d/client-plugin.conf.example +sed -e 's!/usr/lib64/!!' -e 's!-5!!' packaging/common/recorder.conf > \ + %{buildroot}%{_sysconfdir}/%{name}/client.plugins.d/recorder.conf +sed 's!/usr/lib64/!!' packaging/common/http.client.conf.example > \ + %{buildroot}%{_sysconfdir}/%{name}/client.plugins.d/xrdcl-http-plugin.conf + +chmod 644 %{buildroot}%{_datadir}/%{name}/utils/XrdCmsNotify.pm + +sed 's!/usr/bin/env perl!/usr/bin/perl!' -i \ + %{buildroot}%{_datadir}/%{name}/utils/netchk \ + %{buildroot}%{_datadir}/%{name}/utils/XrdCmsNotify.pm \ + %{buildroot}%{_datadir}/%{name}/utils/XrdOlbMonPerf + +sed 's!/usr/bin/env bash!/bin/bash!' -i %{buildroot}%{_bindir}/xrootd-config + +mkdir -p %{buildroot}%{_sysconfdir}/%{name}/config.d + +mkdir -p %{buildroot}%{_localstatedir}/log/%{name} +mkdir -p %{buildroot}%{_localstatedir}/spool/%{name} + +mkdir -p %{buildroot}%{_sysconfdir}/logrotate.d +install -m 644 -p packaging/common/%{name}.logrotate \ + %{buildroot}%{_sysconfdir}/logrotate.d/%{name} -# selinux mkdir -p %{buildroot}%{_datadir}/selinux/packages/%{name} -install -m 644 -p packaging/common/xrootd.pp \ - %{buildroot}%{_datadir}/selinux/packages/%{name}/%{name}.pp +install -m 644 -p packaging/common/%{name}.pp \ + %{buildroot}%{_datadir}/selinux/packages/%{name} -pushd build/bindings/python -# install python2 bindings -%if %{_with_python2} -%py2_install -%endif -# install python3 bindings -%if %{_with_python3} -%py3_install -%endif -popd +%if %{with docs} + mkdir -p %{buildroot}%{_pkgdocdir} + cp -pr doxydoc/html %{buildroot}%{_pkgdocdir} -%clean -rm -rf $RPM_BUILD_ROOT + cp -pr bindings/python/docs/build/html %{buildroot}%{_pkgdocdir}/python + rm %{buildroot}%{_pkgdocdir}/python/.buildinfo +%endif -#------------------------------------------------------------------------------- -# RPM scripts -#------------------------------------------------------------------------------- -%post libs -p /sbin/ldconfig -%postun libs -p /sbin/ldconfig +%ldconfig_scriptlets libs -%post client-libs -p /sbin/ldconfig -%postun client-libs -p /sbin/ldconfig +%ldconfig_scriptlets client-libs -%post server-libs -p /sbin/ldconfig -%postun server-libs -p /sbin/ldconfig +%ldconfig_scriptlets server-libs %pre server - -getent group xrootd >/dev/null || groupadd -r xrootd -getent passwd xrootd >/dev/null || \ - useradd -r -g xrootd -c "XRootD runtime user" \ - -s /sbin/nologin -d %{_localstatedir}/spool/xrootd xrootd -exit 0 - -%if %{use_systemd} +getent group %{name} >/dev/null || groupadd -r %{name} +getent passwd %{name} >/dev/null || useradd -r -g %{name} -s /sbin/nologin \ + -d %{_localstatedir}/spool/%{name} -c "System user for XRootD" %{name} %post server if [ $1 -eq 1 ] ; then - /usr/bin/systemctl daemon-reload >/dev/null 2>&1 || : + systemctl daemon-reload >/dev/null 2>&1 || : fi %preun server if [ $1 -eq 0 ] ; then - for DAEMON in xrootd cmsd frm_purged frm_xfrd; do - for INSTANCE in `/usr/bin/systemctl | grep $DAEMON@ | awk '{print $1;}'`; do - /usr/bin/systemctl --no-reload disable $INSTANCE > /dev/null 2>&1 || : - /usr/bin/systemctl stop $INSTANCE > /dev/null 2>&1 || : - done - done + for DAEMON in xrootd cmsd frm_purged frm_xfrd; do + for INSTANCE in `systemctl | grep $DAEMON@ | awk '{print $1;}'`; do + systemctl --no-reload disable $INSTANCE > /dev/null 2>&1 || : + systemctl stop $INSTANCE > /dev/null 2>&1 || : + done + done fi %postun server -if [ $1 -ge 1 ] ; then - /usr/bin/systemctl daemon-reload >/dev/null 2>&1 || : - for DAEMON in xrootd cmsd frm_purged frm_xfrd; do - for INSTANCE in `/usr/bin/systemctl | grep $DAEMON@ | awk '{print $1;}'`; do - /usr/bin/systemctl try-restart $INSTANCE >/dev/null 2>&1 || : - done - done -fi - -%else - -%post server -if [ $1 -eq 1 ]; then - /sbin/chkconfig --add xrootd - /sbin/chkconfig --add cmsd - /sbin/chkconfig --add frm_purged - /sbin/chkconfig --add frm_xfrd -fi - -%preun server -if [ $1 -eq 0 ]; then - /sbin/service xrootd stop >/dev/null 2>&1 || : - /sbin/service cmsd stop >/dev/null 2>&1 || : - /sbin/service frm_purged stop >/dev/null 2>&1 || : - /sbin/service frm_xfrd stop >/dev/null 2>&1 || : - /sbin/chkconfig --del xrootd - /sbin/chkconfig --del cmsd - /sbin/chkconfig --del frm_purged - /sbin/chkconfig --del frm_xfrd -fi +%tmpfiles_create %{_tmpfilesdir}/%{name}.conf -%postun server -if [ $1 -ge 1 ]; then - /sbin/service xrootd condrestart >/dev/null 2>&1 || : - /sbin/service cmsd condrestart >/dev/null 2>&1 || : - /sbin/service frm_purged condrestart >/dev/null 2>&1 || : - /sbin/service frm_xfrd condrestart >/dev/null 2>&1 || : +if [ $1 -ge 1 ] ; then + systemctl daemon-reload >/dev/null 2>&1 || : + for DAEMON in xrootd cmsd frm_purged frm_xfrd; do + for INSTANCE in `systemctl | grep $DAEMON@ | awk '{print $1;}'`; do + systemctl try-restart $INSTANCE >/dev/null 2>&1 || : + done + done fi -%endif - -#------------------------------------------------------------------------------- -# Add a new user and group if necessary -#------------------------------------------------------------------------------- -%pre fuse -getent group xrootd >/dev/null || groupadd -r xrootd -getent passwd xrootd >/dev/null || \ - useradd -r -g xrootd -c "XRootD runtime user" \ - -s /sbin/nologin -d %{_localstatedir}/spool/xrootd xrootd -exit 0 - -#------------------------------------------------------------------------------- -# Selinux -#------------------------------------------------------------------------------- %post selinux /usr/sbin/semodule -i %{_datadir}/selinux/packages/%{name}/%{name}.pp >/dev/null 2>&1 || : %postun selinux if [ $1 -eq 0 ] ; then - /usr/sbin/semodule -r %{name} >/dev/null 2>&1 || : + /usr/sbin/semodule -r %{name} >/dev/null 2>&1 || : fi -#------------------------------------------------------------------------------- -# Files -#------------------------------------------------------------------------------- %files -# empty +# Empty %files server -%defattr(-,root,root,-) %{_bindir}/cconfig %{_bindir}/cmsd %{_bindir}/frm_admin @@ -843,50 +690,43 @@ fi %{_bindir}/frm_xfrd %{_bindir}/mpxstats %{_bindir}/wait41 +%{_bindir}/xrdacctest +%{_bindir}/xrdpfc_print %{_bindir}/xrdpwdadmin %{_bindir}/xrdsssadmin %{_bindir}/xrootd -%{_bindir}/xrdpfc_print -%{_bindir}/xrdacctest %{_mandir}/man8/cmsd.8* %{_mandir}/man8/frm_admin.8* %{_mandir}/man8/frm_purged.8* %{_mandir}/man8/frm_xfragent.8* %{_mandir}/man8/frm_xfrd.8* %{_mandir}/man8/mpxstats.8* +%{_mandir}/man8/xrdpfc_print.8* %{_mandir}/man8/xrdpwdadmin.8* %{_mandir}/man8/xrdsssadmin.8* %{_mandir}/man8/xrootd.8* -%{_mandir}/man8/xrdpfc_print.8* -%{_datadir}/xrootd/utils -%attr(-,xrootd,xrootd) %config(noreplace) %{_sysconfdir}/xrootd/xrootd-clustered.cfg -%attr(-,xrootd,xrootd) %config(noreplace) %{_sysconfdir}/xrootd/xrootd-standalone.cfg -%attr(-,xrootd,xrootd) %config(noreplace) %{_sysconfdir}/xrootd/xrootd-filecache-clustered.cfg -%attr(-,xrootd,xrootd) %config(noreplace) %{_sysconfdir}/xrootd/xrootd-filecache-standalone.cfg -%if %{use_systemd} -%attr(-,xrootd,xrootd) %config(noreplace) %{_sysconfdir}/xrootd/xrootd-http.cfg -%endif -%attr(-,xrootd,xrootd) %dir %{_var}/log/xrootd -%attr(-,xrootd,xrootd) %dir %{_var}/run/xrootd -%attr(-,xrootd,xrootd) %dir %{_var}/spool/xrootd -%attr(-,xrootd,xrootd) %dir %{_sysconfdir}/%{name}/config.d -%config(noreplace) %{_sysconfdir}/logrotate.d/xrootd - -%if %{use_systemd} +%dir %{_datadir}/%{name} +%{_datadir}/%{name}/utils %{_unitdir}/* %{_tmpfilesdir}/%{name}.conf -%else -%config(noreplace) %{_sysconfdir}/sysconfig/xrootd -%{_initrddir}/* -%endif +%config(noreplace) %{_sysconfdir}/logrotate.d/%{name} +%dir %{_sysconfdir}/%{name}/config.d +%attr(-,xrootd,xrootd) %config(noreplace) %{_sysconfdir}/%{name}/*.cfg +%attr(-,xrootd,xrootd) %{_localstatedir}/log/%{name} +%attr(-,xrootd,xrootd) %{_localstatedir}/spool/%{name} +%ghost %attr(-,xrootd,xrootd) %{_rundir}/%{name} + +%files selinux +%{_datadir}/selinux/packages/%{name}/%{name}.pp %files libs -%defattr(-,root,root,-) -%{_libdir}/libXrdAppUtils.so.2* -%{_libdir}/libXrdClProxyPlugin-5.so -%{_libdir}/libXrdCks*-5.so -%{_libdir}/libXrdCrypto.so.2* -%{_libdir}/libXrdCryptoLite.so.2* +%{_libdir}/libXrdAppUtils.so.* +%{_libdir}/libXrdCrypto.so.* +%{_libdir}/libXrdCryptoLite.so.* +%{_libdir}/libXrdUtils.so.* +%{_libdir}/libXrdXml.so.* +# Plugins +%{_libdir}/libXrdCksCalczcrc32-5.so %{_libdir}/libXrdCryptossl-5.so %{_libdir}/libXrdSec-5.so %{_libdir}/libXrdSecProt-5.so @@ -898,105 +738,99 @@ fi %{_libdir}/libXrdSecsss-5.so %{_libdir}/libXrdSecunix-5.so %{_libdir}/libXrdSecztn-5.so -%{_libdir}/libXrdUtils.so.3* -%{_libdir}/libXrdXml.so.3* +%license COPYING* LICENSE %files devel -%defattr(-,root,root,-) -%dir %{_includedir}/xrootd %{_bindir}/xrootd-config -%{_includedir}/xrootd/XProtocol -%{_includedir}/xrootd/Xrd -%{_includedir}/xrootd/XrdCks -%{_includedir}/xrootd/XrdNet -%{_includedir}/xrootd/XrdOuc -%{_includedir}/xrootd/XrdSec -%{_includedir}/xrootd/XrdSys -%{_includedir}/xrootd/XrdVersion.hh +%dir %{_includedir}/%{name} +%{_includedir}/%{name}/XProtocol +%{_includedir}/%{name}/Xrd +%{_includedir}/%{name}/XrdCks +%{_includedir}/%{name}/XrdNet +%{_includedir}/%{name}/XrdOuc +%{_includedir}/%{name}/XrdSec +%{_includedir}/%{name}/XrdSys +%{_includedir}/%{name}/XrdXml +%{_includedir}/%{name}/XrdVersion.hh %{_libdir}/libXrdAppUtils.so %{_libdir}/libXrdCrypto.so %{_libdir}/libXrdCryptoLite.so %{_libdir}/libXrdUtils.so %{_libdir}/libXrdXml.so -%{_includedir}/xrootd/XrdXml/XrdXmlReader.hh %{_libdir}/cmake/XRootD +%dir %{_datadir}/%{name} %files client-libs -%defattr(-,root,root,-) -%{_libdir}/libXrdCl.so.3* -%{_libdir}/libXrdFfs.so.3* -%{_libdir}/libXrdPosix.so.3* -%{_libdir}/libXrdPosixPreload.so.2* -%{_libdir}/libXrdSsiLib.so.2* -%{_libdir}/libXrdSsiShMap.so.2* -%{_libdir}/libXrdClRecorder-5.so -%if %{?_with_isal:1}%{!?_with_isal:0} -%{_libdir}/libXrdEc.so.1* +%{_libdir}/libXrdCl.so.* +%if %{with xrdec} +%{_libdir}/libXrdEc.so.* %endif -%{_sysconfdir}/xrootd/client.plugins.d/client-plugin.conf.example -%{_sysconfdir}/xrootd/client.plugins.d/recorder.conf -%config(noreplace) %{_sysconfdir}/xrootd/client.conf +%{_libdir}/libXrdFfs.so.* +%{_libdir}/libXrdPosix.so.* +%{_libdir}/libXrdPosixPreload.so.* # This lib may be used for LD_PRELOAD so the .so link needs to be included %{_libdir}/libXrdPosixPreload.so +%{_libdir}/libXrdSsiLib.so.* +%{_libdir}/libXrdSsiShMap.so.* +# Plugins +%{_libdir}/libXrdClProxyPlugin-5.so +%{_libdir}/libXrdClRecorder-5.so +%dir %{_sysconfdir}/%{name} +%config(noreplace) %{_sysconfdir}/%{name}/client.conf +%dir %{_sysconfdir}/%{name}/client.plugins.d +%config(noreplace) %{_sysconfdir}/%{name}/client.plugins.d/client-plugin.conf.example +%config(noreplace) %{_sysconfdir}/%{name}/client.plugins.d/recorder.conf %files client-devel -%defattr(-,root,root,-) -%{_bindir}/xrdgsitest -%{_includedir}/xrootd/XrdCl -%{_includedir}/xrootd/XrdPosix +%{_includedir}/%{name}/XrdCl +%{_includedir}/%{name}/XrdPosix %{_libdir}/libXrdCl.so +%if %{with xrdec} +%{_libdir}/libXrdEc.so +%endif %{_libdir}/libXrdFfs.so %{_libdir}/libXrdPosix.so -%{_mandir}/man1/xrdgsitest.1* %files server-libs -%defattr(-,root,root,-) +%{_libdir}/libXrdHttpUtils.so.* +%{_libdir}/libXrdServer.so.* +# Plugins +%{_libdir}/libXrdBlacklistDecision-5.so %{_libdir}/libXrdBwm-5.so -%{_libdir}/libXrdPss-5.so -%{_libdir}/libXrdXrootd-5.so -%{_libdir}/libXrdPfc-5.so +%{_libdir}/libXrdCmsRedirectLocal-5.so %{_libdir}/libXrdFileCache-5.so -%{_libdir}/libXrdBlacklistDecision-5.so %{_libdir}/libXrdHttp-5.so %{_libdir}/libXrdHttpTPC-5.so -%{_libdir}/libXrdHttpUtils.so.2* -%if %{have_macaroons} %{_libdir}/libXrdMacaroons-5.so -%endif %{_libdir}/libXrdN2No2p-5.so +%{_libdir}/libXrdOfsPrepGPI-5.so %{_libdir}/libXrdOssCsi-5.so %{_libdir}/libXrdOssSIgpfsT-5.so -%{_libdir}/libXrdServer.so.3* +%{_libdir}/libXrdPfc-5.so +%{_libdir}/libXrdPss-5.so %{_libdir}/libXrdSsi-5.so %{_libdir}/libXrdSsiLog-5.so %{_libdir}/libXrdThrottle-5.so -%{_libdir}/libXrdCmsRedirectLocal-5.so -%{_libdir}/libXrdOfsPrepGPI-5.so +%{_libdir}/libXrdXrootd-5.so %files server-devel -%defattr(-,root,root,-) -%{_includedir}/xrootd/XrdAcc -%{_includedir}/xrootd/XrdCms -%{_includedir}/xrootd/XrdPfc -%{_includedir}/xrootd/XrdOss -%{_includedir}/xrootd/XrdOfs -%{_includedir}/xrootd/XrdSfs -%{_includedir}/xrootd/XrdXrootd -%{_includedir}/xrootd/XrdHttp -%{_libdir}/libXrdServer.so +%{_includedir}/%{name}/XrdAcc +%{_includedir}/%{name}/XrdCms +%{_includedir}/%{name}/XrdHttp +%{_includedir}/%{name}/XrdOfs +%{_includedir}/%{name}/XrdOss +%{_includedir}/%{name}/XrdPfc +%{_includedir}/%{name}/XrdSfs +%{_includedir}/%{name}/XrdXrootd %{_libdir}/libXrdHttpUtils.so +%{_libdir}/libXrdServer.so %files private-devel -%defattr(-,root,root,-) -%{_includedir}/xrootd/private +%{_includedir}/%{name}/private %{_libdir}/libXrdSsiLib.so %{_libdir}/libXrdSsiShMap.so -%if %{?_with_isal:1}%{!?_with_isal:0} -%{_libdir}/libXrdEc.so -%endif %files client -%defattr(-,root,root,-) %{_bindir}/xrdadler32 %{_bindir}/xrdcks %{_bindir}/xrdcopy @@ -1004,6 +838,7 @@ fi %{_bindir}/xrdcrc32c %{_bindir}/xrdfs %{_bindir}/xrdgsiproxy +%{_bindir}/xrdgsitest %{_bindir}/xrdmapc %{_bindir}/xrdpinls %{_bindir}/xrdreplay @@ -1012,80 +847,58 @@ fi %{_mandir}/man1/xrdcp.1* %{_mandir}/man1/xrdfs.1* %{_mandir}/man1/xrdgsiproxy.1* +%{_mandir}/man1/xrdgsitest.1* %{_mandir}/man1/xrdmapc.1* %files fuse -%defattr(-,root,root,-) %{_bindir}/xrootdfs %{_mandir}/man1/xrootdfs.1* -%dir %{_sysconfdir}/xrootd - -%if %{_with_python2} -%files -n python2-%{name} -%defattr(-,root,root,-) -%{python2_sitearch}/* -%endif - -%if %{_with_python3} -%files -n python%{python3_pkgversion}-%{name} -%defattr(-,root,root,-) -%{python3_sitearch}/* -%endif %files voms -%defattr(-,root,root,-) %{_libdir}/libXrdVoms-5.so -%{_libdir}/libXrdSecgsiVOMS-5.so %{_libdir}/libXrdHttpVOMS-5.so -%doc %{_mandir}/man1/libXrdVoms.1.gz -%doc %{_mandir}/man1/libXrdSecgsiVOMS.1.gz +%{_libdir}/libXrdSecgsiVOMS-5.so +%doc %{_mandir}/man1/libXrdVoms.1* +%doc %{_mandir}/man1/libXrdSecgsiVOMS.1* -%files doc -%defattr(-,root,root,-) -%doc %{_docdir}/%{name}-%{version} +%files scitokens +%{_libdir}/libXrdAccSciTokens-5.so +%doc src/XrdSciTokens/README.md + +%files -n xrdcl-http +%{_libdir}/libXrdClHttp-5.so +%config(noreplace) %{_sysconfdir}/%{name}/client.plugins.d/xrdcl-http-plugin.conf -%if %{?_with_ceph:1}%{!?_with_ceph:0} +%if %{with ceph} %files ceph -%defattr(-,root,root,-) %{_libdir}/libXrdCeph-5.so %{_libdir}/libXrdCephXattr-5.so -%{_libdir}/libXrdCephPosix.so* +%{_libdir}/libXrdCephPosix.so.* %endif -%if %{?_with_xrdclhttp:1}%{!?_with_xrdclhttp:0} -%files -n xrdcl-http -%defattr(-,root,root,-) -%{_libdir}/libXrdClHttp-5.so -%{_sysconfdir}/xrootd/client.plugins.d/xrdcl-http-plugin.conf -%endif +%files -n python%{python3_pkgversion}-%{name} +%{python3_sitearch}/xrootd-*.*-info +%{python3_sitearch}/pyxrootd +%{python3_sitearch}/XRootD -%if %{?_with_scitokens:1}%{!?_with_scitokens:0} -%files scitokens -%defattr(-,root,root,-) -%{_libdir}/libXrdAccSciTokens-5.so -%endif +%if %{?rhel}%{!?rhel:0} == 7 +%files -n python2-%{name} +%{python2_sitearch}/xrootd-*.*-info +%{python2_sitearch}/pyxrootd +%{python2_sitearch}/XRootD -%if %{?_with_tests:1}%{!?_with_tests:0} -%files tests -%defattr(-,root,root,-) -%{_bindir}/test-runner -%{_bindir}/xrdshmap -%{_libdir}/libXrdClTests.so -%{_libdir}/libXrdClTestsHelper.so -%{_libdir}/libXrdClTestMonitor*.so -%if %{?_with_isal:1}%{!?_with_isal:0} -%{_libdir}/libXrdEcTests.so -%endif -%if %{?_with_ceph:1}%{!?_with_ceph:0} -%{_libdir}/libXrdCephTests*.so -%endif +%files -n python%{?python3_other_pkgversion}-%{name} +%{python3_other_sitearch}/xrootd-*.*-info +%{python3_other_sitearch}/pyxrootd +%{python3_other_sitearch}/XRootD %endif -%files selinux -%defattr(-,root,root) -%{_datadir}/selinux/packages/%{name}/%{name}.pp +%if %{with docs} +%files doc +%doc %{_pkgdocdir} +%endif -%if 0%{?_with_compat} +%if %{with compat} %files client-compat # from xrootd-libs: %{_libdir}/libXrdAppUtils.so.1* @@ -1122,9 +935,7 @@ fi %{_libdir}/libXrdHttp-4.so %{_libdir}/libXrdHttpTPC-4.so %{_libdir}/libXrdHttpUtils.so.1* -%if %{have_macaroons} %{_libdir}/libXrdMacaroons-4.so -%endif %{_libdir}/libXrdN2No2p-4.so %{_libdir}/libXrdOssSIgpfsT-4.so %{_libdir}/libXrdServer.so.2* @@ -1133,14 +944,16 @@ fi %{_libdir}/libXrdThrottle-4.so %{_libdir}/libXrdCmsRedirectLocal-4.so %{_libdir}/libXrdVoms-4.so - %endif -# end _with_compat -#------------------------------------------------------------------------------- -# Changelog -#------------------------------------------------------------------------------- %changelog + +* Fri Aug 11 2023 Guilherme Amadio - 1:5.6.1-1 +- Modernize spec file to add more optional features and select + default build options automatically for each supported OS. +- Use latest official release tarball by default. +- Enable snapshot builds from git. + * Thu Oct 15 2020 Michal Simon - 5.0.2-1 - Introduce xrootd-scitokens package From a84c71d7239ebaed9c823d5bbd63487211533f06 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 15 Aug 2023 16:05:38 +0200 Subject: [PATCH 296/442] [RPM] Move spec file to top directory The new spec file is no longer a template, but meant to be used as is to build either a snapshot version of XRootD from git or the latest release directly. --- packaging/rhel/xrootd.spec.in => xrootd.spec | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packaging/rhel/xrootd.spec.in => xrootd.spec (100%) diff --git a/packaging/rhel/xrootd.spec.in b/xrootd.spec similarity index 100% rename from packaging/rhel/xrootd.spec.in rename to xrootd.spec From 9ee7d88ae4fbea80a0693104ac87d82d97d9c9ce Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 15 Sep 2023 14:24:11 +0200 Subject: [PATCH 297/442] [RPM] Update spec file for XRootD 5.6.2 release --- xrootd.spec | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/xrootd.spec b/xrootd.spec index 2a10c3556d2..66b6f61a614 100644 --- a/xrootd.spec +++ b/xrootd.spec @@ -18,7 +18,7 @@ Name: xrootd Epoch: 1 -Release: 1%{?dist}%{?with_clang:.clang}%{?with_asan:.asan}%{?with_openssl11:.ssl11} +Release: 2%{?dist}%{?with_clang:.clang}%{?with_asan:.asan}%{?with_openssl11:.ssl11} Summary: Extended ROOT File Server Group: System Environment/Daemons License: LGPL-3.0-or-later AND BSD-2-Clause AND BSD-3-Clause AND curl AND MIT AND Zlib @@ -28,7 +28,7 @@ URL: https://xrootd.slac.stanford.edu Version: %(git describe --match 'v*' | sed -e 's/v//; s/-rc/~rc/; s/-g/+git/; s/-/.post/; s/-/./') Source0: %{name}.tar.gz %else -Version: 5.6.1 +Version: 5.6.2 Source0: %{url}/download/v%{version}/%{name}-%{version}.tar.gz %endif @@ -36,8 +36,15 @@ Source0: %{url}/download/v%{version}/%{name}-%{version}.tar.gz Source1: %{url}/download/v%{compat_version}/%{name}-%{compat_version}.tar.gz %endif +Patch0: %{url}/download/v%{version}/%{name}-%{version}-authfile.patch + %undefine __cmake_in_source_build + +%if %{?rhel}%{!?rhel:0} == 7 +%define cmake %cmake3 +%define __cmake %__cmake3 %undefine __cmake3_in_source_build +%endif %if %{with tests} # CppUnit crashes with LTO enabled @@ -155,7 +162,7 @@ latency and increased throughput. Summary: XRootD server daemons Group: System Environment/Daemons Requires: %{name}-libs%{?_isa} = %{epoch}:%{version}-%{release} -Requires: %{name}-client-libs%{?_isa} = %{epoch}:%{version}-%{release} +Requires: %{name}-client%{?_isa} = %{epoch}:%{version}-%{release} Requires: %{name}-server-libs%{?_isa} = %{epoch}:%{version}-%{release} Requires: expect Requires: logrotate @@ -386,13 +393,13 @@ This package contains compatibility binaries for XRootD 4 servers. %prep %if %{with compat} -%autosetup -T -b 1 -n %{name}-%{compat_version} +%setup -T -b 1 -n %{name}-%{compat_version} %endif %if %{with git} %autosetup -n %{name} %else -%autosetup +%autosetup -p1 %endif %build @@ -418,9 +425,14 @@ CXXFLAGS="${CXXFLAGS} -Wno-error=stringop-overflow" %endif %if %{with compat} -%__cmake3 \ +%__cmake \ -S %{_builddir}/%{name}-%{compat_version} \ -B %{_builddir}/%{name}-%{compat_version}/build \ +%if %{with openssl11} + -DOPENSSL_INCLUDE_DIR=/usr/include/openssl11 \ + -DOPENSSL_CRYPTO_LIBRARY=/usr/lib64/libcrypto.so.1.1 \ + -DOPENSSL_SSL_LIBRARY=/usr/lib64/libssl.so.1.1 \ +%endif -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_C_FLAGS_RELEASE:STRING='%{optflags}' \ -DCMAKE_CXX_FLAGS_RELEASE:STRING='%{optflags}' \ @@ -450,7 +462,7 @@ CXXFLAGS="${CXXFLAGS} -Wno-error=stringop-overflow" make -C %{_builddir}/%{name}-%{compat_version}/build %{?_smp_mflags} %endif -%cmake3 \ +%cmake \ -DFORCE_ENABLED:BOOL=TRUE \ -DUSE_SYSTEM_ISAL:BOOL=FALSE \ -DENABLE_ASAN:BOOL=%{with asan} \ @@ -948,6 +960,13 @@ fi %changelog +* Mon Sep 18 2023 Guilherme Amadio - 1:5.6.2-2 +- Add patch with fix for id parsing in XrdAccAuthFile (#2088) + +* Fri Sep 15 2023 Guilherme Amadio - 1:5.6.2-1 +- Link XRootD 4 with openssl1.1 when using --with openssl11 +- XRootD 5.6.2 + * Fri Aug 11 2023 Guilherme Amadio - 1:5.6.1-1 - Modernize spec file to add more optional features and select default build options automatically for each supported OS. @@ -975,7 +994,7 @@ fi * Tue Jan 08 2019 Edgar Fajardo - Create config dir /etc/xrootd/config.d -* Tue May 08 2018 Michal Simon +* Tue May 08 2018 Michal Simon - Make python3 sub-package optional * Fri Nov 10 2017 Michal Simon - 1:4.8.0-1 From 2f3adc23ae491d805f2de9c991127cd5fc18288d Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 6 Oct 2023 11:54:57 +0200 Subject: [PATCH 298/442] [RPM] Update spec file for XRootD 5.6.3 --- xrootd.spec | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/xrootd.spec b/xrootd.spec index 66b6f61a614..9fee2b5d2f7 100644 --- a/xrootd.spec +++ b/xrootd.spec @@ -28,16 +28,15 @@ URL: https://xrootd.slac.stanford.edu Version: %(git describe --match 'v*' | sed -e 's/v//; s/-rc/~rc/; s/-g/+git/; s/-/.post/; s/-/./') Source0: %{name}.tar.gz %else -Version: 5.6.2 +Version: 5.6.3 Source0: %{url}/download/v%{version}/%{name}-%{version}.tar.gz +Patch0: %{url}/download/v%{version}/%{name}-%{version}-install-xrdnet-pmark-header.patch %endif %if %{with compat} Source1: %{url}/download/v%{compat_version}/%{name}-%{compat_version}.tar.gz %endif -Patch0: %{url}/download/v%{version}/%{name}-%{version}-authfile.patch - %undefine __cmake_in_source_build %if %{?rhel}%{!?rhel:0} == 7 @@ -58,6 +57,7 @@ BuildRequires: %{devtoolset}-toolchain BuildRequires: cmake >= 3.16 BuildRequires: gcc-c++ %endif +BuildRequires: gdb BuildRequires: make BuildRequires: pkgconfig BuildRequires: fuse-devel @@ -415,15 +415,6 @@ export CC=clang export CXX=clang++ %endif -%if %{?fedora}%{!?fedora:0} >= 36 -# Mark some warnings from gcc 12 as not errors -# These are likely bogus - hopefully they can be fixed in gcc updates -%ifarch %{arm} -%set_build_flags -CXXFLAGS="${CXXFLAGS} -Wno-error=stringop-overflow" -%endif -%endif - %if %{with compat} %__cmake \ -S %{_builddir}/%{name}-%{compat_version} \ @@ -960,6 +951,9 @@ fi %changelog +* Fri Oct 27 2023 Guilherme Amadio - 1:5.6.3-2 +- XRootD 5.6.3 + * Mon Sep 18 2023 Guilherme Amadio - 1:5.6.2-2 - Add patch with fix for id parsing in XrdAccAuthFile (#2088) From 0b270dcb1be153c2573914e6ad0dbc164cd3b8ae Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 16 Aug 2023 08:31:19 +0200 Subject: [PATCH 299/442] [CI] Simplify GitHub Actions CI workflow using CTest --- .github/workflows/CI.yml | 210 ++++++++ .github/workflows/build.yml | 969 ------------------------------------ 2 files changed, 210 insertions(+), 969 deletions(-) create mode 100644 .github/workflows/CI.yml delete mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 00000000000..15a0e0f68a5 --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,210 @@ +name: CI + +on: + push: + paths-ignore: + - .gitignore + - .gitlab-ci.yml + - '**.md' + - 'docs/**' + - 'docker/**' + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +defaults: + run: + shell: bash + +env: + CMAKE_VERBOSE_MAKEFILE: true + CTEST_OUTPUT_ON_FAILURE: true + +jobs: + alpine: + name: Alpine + runs-on: ubuntu-latest + container: alpine + + env: + CMAKE_ARGS: -DCMAKE_INSTALL_PREFIX=/usr + + steps: + - name: Install dependencies + shell: sh + run: | + apk add \ + bash \ + cmake \ + cppunit-dev \ + curl-dev \ + fuse-dev \ + fuse3-dev \ + g++ \ + git \ + gtest-dev \ + json-c-dev \ + krb5-dev \ + libxml2-dev \ + linux-headers \ + make \ + openssl-dev \ + py3-pip \ + py3-setuptools \ + py3-wheel \ + python3-dev \ + readline-dev \ + tinyxml-dev \ + util-linux-dev \ + zlib-dev + + - name: Clone repository + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: CTest Build + run: ctest -VV -S test.cmake + + - name: Install with CMake + run: cmake --install build + + - name: Run post-install tests + run: tests/post-install.sh + + fedora: + name: Fedora + runs-on: ubuntu-latest + container: fedora + + env: + CMAKE_GENERATOR: Ninja + CMAKE_ARGS: -DCMAKE_INSTALL_RPATH='$ORIGIN/../$LIB' + + steps: + - name: Install dependencies + run: dnf install -y dnf-plugins-core git ninja-build rpmdevtools + + - name: Clone repository + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Install XRootD build dependencies + run: dnf builddep -y xrootd.spec + + - name: Build and Test with CTest + run: ctest -VV -S test.cmake + + - name: Install with CMake + run: cmake --install build + + - name: Run post-install tests + run: tests/post-install.sh + + ubuntu: + name: Ubuntu + runs-on: ubuntu-latest + + strategy: + matrix: + compiler: [ gcc, clang ] + + env: + CC: ${{ matrix.compiler }} + DEBIAN_FRONTEND: noninteractive + CMAKE_ARGS: '-DINSTALL_PYTHON_BINDINGS=0;-DUSE_SYSTEM_ISAL=1;-DCMAKE_INSTALL_PREFIX=/usr' + + steps: + - name: Install dependencies + run: | + sudo apt update -q + sudo apt install -y \ + cmake \ + clang \ + davix-dev \ + g++ \ + libcppunit-dev \ + libcurl4-openssl-dev \ + libfuse-dev \ + libgtest-dev \ + libisal-dev \ + libjson-c-dev \ + libkrb5-dev \ + libmacaroons-dev \ + libreadline-dev \ + libscitokens-dev \ + libssl-dev \ + libsystemd-dev \ + libtinyxml-dev \ + libxml2-dev \ + make \ + pkg-config \ + python3-dev \ + python3-pip \ + python3-setuptools \ + python3-wheel \ + uuid-dev \ + voms-dev \ + zlib1g-dev + + - name: Clone repository + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Build and Test with CTest + run: env CC=${CC} CXX=${CC/g*/g++} ctest -VV -S test.cmake + + - name: Install with CMake + run: sudo cmake --install build + + - name: Install Python bindings + run: | + sudo python3 -m pip install \ + --target /usr/lib/python3/dist-packages \ + --use-pep517 --verbose build/bindings/python + + - name: Run post-install tests + run: tests/post-install.sh + + macos: + strategy: + matrix: + version: [ 12, 13 ] + + name: macOS + runs-on: macos-${{ matrix.version }} + + env: + CC: clang + CXX: clang++ + CMAKE_ARGS: "-DPython_FIND_UNVERSIONED_NAMES=FIRST;-DUSE_SYSTEM_ISAL=TRUE" + CMAKE_PREFIX_PATH: /usr/local/opt/openssl@3 + PYTHONPATH: /usr/local/lib/python3.11/site-packages + + steps: + - name: Workaround for issue 1772 + run: sudo sed -i -e "s/localhost/localhost $(hostname)/g" /etc/hosts + + - name: Install dependencies with Homebrew + run: brew install cmake cppunit davix googletest isa-l openssl@3 python@3.11 + + - name: Install Python dependencies with pip + run: python3 -m pip install --upgrade pip setuptools wheel + + - name: Clone repository + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Build and Test with CTest + run: ctest -VV -S test.cmake + + - name: Install with CMake + run: cmake --install build + + - name: Run post-install tests + run: tests/post-install.sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 892c44284c2..00000000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,969 +0,0 @@ -name: build - -on: - push: - pull_request: - schedule: - - cron: '23 1 * * 0' - release: - types: [published] - workflow_dispatch: - -defaults: - run: - shell: bash - -concurrency: - group: build-${{ github.ref }} - cancel-in-progress: true - -jobs: - - cmake-almalinux8: - - runs-on: ubuntu-latest - container: almalinux:8 - - steps: - - name: Install external dependencies with yum - run: | - dnf update -y - dnf clean all - dnf install -y epel-release - dnf install -y --enablerepo=powertools \ - cmake \ - cppunit-devel \ - curl-devel \ - davix-devel \ - diffutils \ - file \ - fuse-devel \ - gcc-c++ \ - git \ - gtest-devel \ - json-c-devel \ - krb5-devel \ - libmacaroons-devel \ - libtool \ - libuuid-devel \ - libxml2-devel \ - make \ - openssl-devel \ - python3-devel \ - python3-setuptools \ - readline-devel \ - scitokens-cpp-devel \ - systemd-devel \ - tinyxml-devel \ - voms-devel \ - yasm \ - zlib-devel - dnf clean all - - - name: Clone repository - uses: actions/checkout@v3 - - - name: Build with cmake - run: | - cd .. - cmake \ - --log-level=DEBUG \ - -DCMAKE_CXX_STANDARD=17 \ - -DCMAKE_BUILD_TYPE=RelWithDebInfo \ - -DCMAKE_INSTALL_PREFIX=/usr \ - -DPython_EXECUTABLE=$(command -v python3) \ - -DFORCE_ENABLED=ON \ - -DENABLE_TESTS=ON \ - -DENABLE_XRDEC=ON \ - -DENABLE_MACAROONS=ON \ - -DENABLE_SCITOKENS=ON \ - -DPIP_OPTIONS="--verbose" \ - -S xrootd \ - -B build - cmake build -LH - cmake \ - --build build \ - --clean-first \ - --parallel $(($(nproc) - 1)) - cmake --build build --target install - python3 -m pip list - - - name: Verify install - run: | - command -v xrootd - command -v xrdcp - - - name: Verify Python bindings - run: | - python3 --version --version - python3 -m pip list - python3 -m pip show xrootd - python3 -c 'import XRootD; print(XRootD)' - python3 -c 'import pyxrootd; print(pyxrootd)' - python3 -c 'from XRootD import client; print(client.FileSystem("root://someserver:1094"))' - - - name: Run tests with CTest - run: | - ctest --output-on-failure --test-dir ../build - - cmake-almalinux9: - - runs-on: ubuntu-latest - container: almalinux:9 - - steps: - - name: Install external dependencies with yum - run: | - dnf update -y - dnf clean all - dnf install -y epel-release - dnf install -y --enablerepo=crb \ - cmake \ - cppunit-devel \ - curl-devel \ - davix-devel \ - diffutils \ - file \ - fuse-devel \ - gcc-c++ \ - git \ - gtest-devel \ - json-c-devel \ - krb5-devel \ - libmacaroons-devel \ - libtool \ - libuuid-devel \ - libxml2-devel \ - make \ - openssl-devel \ - python3-devel \ - python3-setuptools \ - readline-devel \ - scitokens-cpp-devel \ - systemd-devel \ - tinyxml-devel \ - voms-devel \ - yasm \ - zlib-devel - dnf clean all - - - name: Clone repository - uses: actions/checkout@v3 - - - name: Build with cmake - run: | - cd .. - cmake \ - --log-level=DEBUG \ - -DCMAKE_CXX_STANDARD=17 \ - -DCMAKE_BUILD_TYPE=RelWithDebInfo \ - -DCMAKE_INSTALL_PREFIX=/usr \ - -DPython_EXECUTABLE=$(command -v python3) \ - -DFORCE_ENABLED=ON \ - -DENABLE_TESTS=ON \ - -DENABLE_XRDEC=ON \ - -DENABLE_MACAROONS=ON \ - -DENABLE_SCITOKENS=ON \ - -DPIP_OPTIONS="--verbose" \ - -S xrootd \ - -B build - cmake build -LH - cmake \ - --build build \ - --clean-first \ - --parallel $(($(nproc) - 1)) - cmake --build build --target install - python3 -m pip list - - - name: Verify install - run: | - command -v xrootd - command -v xrdcp - - - name: Verify Python bindings - run: | - python3 --version --version - python3 -m pip list - python3 -m pip show xrootd - python3 -c 'import XRootD; print(XRootD)' - python3 -c 'import pyxrootd; print(pyxrootd)' - python3 -c 'from XRootD import client; print(client.FileSystem("root://someserver:1094"))' - - - name: Run tests with CTest - run: | - ctest --output-on-failure --test-dir ../build - - cmake-alpine-musl: - - runs-on: ubuntu-latest - container: alpine - - steps: - - name: Install external dependencies - shell: sh - run: | - apk add \ - bash \ - cmake \ - cppunit-dev \ - curl-dev \ - fuse-dev \ - fuse3-dev \ - g++ \ - git \ - gtest-dev \ - json-c-dev \ - krb5-dev \ - libxml2-dev \ - linux-headers \ - make \ - openssl-dev \ - py3-pip \ - python3-dev \ - readline-dev \ - tinyxml-dev \ - util-linux-dev \ - zlib-dev - - - name: Clone repository - uses: actions/checkout@v3 - - - name: Build with cmake - run: | - cd .. - # need to fix ownership not to confuse git - chown -R -v "$( id -u; ):$( id -g; )" xrootd - cmake \ - --log-level=DEBUG \ - -DCMAKE_CXX_STANDARD=17 \ - -DCMAKE_BUILD_TYPE=RelWithDebInfo \ - -DCMAKE_INSTALL_PREFIX=/usr \ - -DCMAKE_INSTALL_LIBDIR=lib \ - -DPython_EXECUTABLE=$(command -v python3) \ - -DFORCE_ENABLED=ON \ - -DENABLE_HTTP=OFF \ - -DENABLE_TESTS=ON \ - -DENABLE_VOMS=OFF \ - -DENABLE_XRDEC=OFF \ - -DENABLE_XRDCLHTTP=OFF \ - -DENABLE_MACAROONS=OFF \ - -DENABLE_SCITOKENS=OFF \ - -DPIP_OPTIONS="--verbose" \ - -S xrootd \ - -B build - cmake build -LH - cmake \ - --build build \ - --clean-first \ - --parallel $(($(nproc) - 1)) - cmake --build build --target install - python3 -m pip list - - - name: Verify install - run: | - command -v xrootd - command -v xrdcp - - - name: Verify Python bindings - run: | - python3 --version --version - python3 -m pip list - python3 -m pip show xrootd - python3 -c 'import XRootD; print(XRootD)' - python3 -c 'import pyxrootd; print(pyxrootd)' - python3 -c 'from XRootD import client; print(client.FileSystem("root://someserver:1094"))' - - - name: Run tests with CTest - run: | - ctest --output-on-failure --test-dir ../build - - cmake-centos7: - - runs-on: ubuntu-latest - container: centos:7 - - steps: - - name: Install external dependencies with yum - run: | - yum update -y - yum install -y epel-release centos-release-scl - yum clean all - yum install --nogpg -y \ - cmake3 \ - make \ - krb5-devel \ - libuuid-devel \ - libxml2-devel \ - openssl-devel \ - systemd-devel \ - zlib-devel \ - devtoolset-7-gcc-c++ \ - python3-devel \ - python3-setuptools \ - git \ - cppunit-devel \ - gtest-devel - yum clean all - - # Need to use v1 of action as image Git is too old - - name: Clone repository now that Git is available - uses: actions/checkout@v1 - - - name: Build with cmake - run: | - . /opt/rh/devtoolset-7/enable - cd .. - cmake3 \ - --log-level=DEBUG \ - -DCMAKE_INSTALL_PREFIX=/usr/local/ \ - -DPython_EXECUTABLE=$(command -v python3) \ - -DENABLE_TESTS=ON \ - -DPIP_OPTIONS="--verbose" \ - -S xrootd \ - -B build - cmake3 build -LH - cmake3 \ - --build build \ - --clean-first \ - --parallel $(($(nproc) - 1)) - cmake3 --build build --target install - python3 -m pip list - - - name: Verify install - run: | - command -v xrootd - command -v xrdcp - - - name: Verify Python bindings - run: | - python3 --version --version - python3 -m pip list - python3 -m pip show xrootd - python3 -c 'import XRootD; print(XRootD)' - python3 -c 'import pyxrootd; print(pyxrootd)' - python3 -c 'from XRootD import client; print(client.FileSystem("root://someserver:1094"))' - - - name: Run tests with CTest - run: | - cd ../build - ctest3 --output-on-failure - - cmake-centos7-updated-python: - - runs-on: ubuntu-latest - container: centos:7 - - steps: - - name: Install external dependencies with yum - run: | - yum update -y - yum install -y epel-release centos-release-scl - yum clean all - yum install --nogpg -y \ - cmake3 \ - make \ - krb5-devel \ - libuuid-devel \ - libxml2-devel \ - openssl-devel \ - systemd-devel \ - zlib-devel \ - devtoolset-7-gcc-c++ \ - python3-devel \ - python3-setuptools \ - git \ - cppunit-devel \ - gtest-devel - yum clean all - python3 -m pip --no-cache-dir install --upgrade pip setuptools wheel - - # Need to use v1 of action as image Git is too old - - name: Clone repository now that Git is available - uses: actions/checkout@v1 - - # Use extra PIP_OPTIONS strings as example that this is possible. - # N.B.: None of the PIP_OPTIONS are required for this step to work. - - name: Build with cmake - run: | - . /opt/rh/devtoolset-7/enable - cd .. - cmake3 \ - --log-level=DEBUG \ - -DCMAKE_INSTALL_PREFIX=/usr/local/ \ - -DPython_EXECUTABLE=$(command -v python3) \ - -DENABLE_TESTS=ON \ - -DPIP_OPTIONS="--verbose --force-reinstall --prefix /usr/local/" \ - -S xrootd \ - -B build - cmake3 build -LH - cmake3 \ - --build build \ - --clean-first \ - --parallel $(($(nproc) - 1)) - cmake3 --build build --target install - python3 -m pip list - - - name: Verify install - run: | - command -v xrootd - command -v xrdcp - - - name: Verify Python bindings - run: | - python3 --version --version - python3 -m pip list - python3 -m pip show xrootd - python3 -c 'import XRootD; print(XRootD)' - python3 -c 'import pyxrootd; print(pyxrootd)' - python3 -c 'from XRootD import client; print(client.FileSystem("root://someserver:1094"))' - - # TODO: Drop once Python 2 support is dropped - cmake-centos7-python2: - - runs-on: ubuntu-latest - container: centos:7 - - steps: - # python2-pip is broken on CentOS so can't upgrade pip, setuptools, or wheel - - name: Install external dependencies with yum - run: | - yum update -y - yum install -y epel-release centos-release-scl - yum clean all - yum install --nogpg -y \ - cmake3 \ - make \ - krb5-devel \ - libuuid-devel \ - libxml2-devel \ - openssl-devel \ - systemd-devel \ - zlib-devel \ - devtoolset-7-gcc-c++ \ - python2-pip \ - python2-setuptools \ - python2-devel \ - git \ - cppunit-devel \ - gtest-devel - yum clean all - - # Need to use v1 of action as image Git is too old - - name: Clone repository now that Git is available - uses: actions/checkout@v1 - - # Deprecated setup.py install will try to install under ${CMAKE_INSTALL_PREFIX}/lib64 - # so set CMAKE_INSTALL_PREFIX=/usr/ to make testing easy - - name: Build with cmake - run: | - . /opt/rh/devtoolset-7/enable - cd .. - cmake3 \ - --log-level=DEBUG \ - -DCMAKE_INSTALL_PREFIX=/usr/ \ - -DPython_EXECUTABLE=$(command -v python2) \ - -DENABLE_TESTS=ON \ - -DPIP_OPTIONS="--verbose" \ - -DXRD_PYTHON_REQ_VERSION="2.7" \ - -S xrootd \ - -B build - cmake3 build -LH - cmake3 \ - --build build \ - --clean-first \ - --parallel $(($(nproc) - 1)) - cmake3 --build build --target install - python2 -m pip list - - - name: Verify install - run: | - command -v xrootd - command -v xrdcp - - - name: Verify Python bindings - run: | - python2 --version - python2 -m pip list - python2 -m pip show xrootd - python2 -c 'import XRootD; print(XRootD)' - python2 -c 'import pyxrootd; print(pyxrootd)' - python2 -c 'from XRootD import client; print(client.FileSystem("root://someserver:1094"))' - - cmake-ubuntu-updated-python: - - # Use of sudo as https://github.com/actions/virtual-environments requires it - runs-on: ubuntu-latest - - steps: - - name: Install external dependencies with apt-get - run: | - sudo apt-get update -y - DEBIAN_FRONTEND=noninteractive sudo apt-get install -y \ - g++ \ - git \ - cmake \ - uuid-dev \ - dpkg-dev \ - libcppunit-dev \ - libgtest-dev \ - libssl-dev \ - libx11-dev \ - python3 \ - python3-pip \ - python3-venv \ - python3-dev - sudo apt-get autoclean -y - python3 -m pip --no-cache-dir install --upgrade pip setuptools wheel - - - name: Clone repository - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - name: Build with cmake - run: | - cd .. - cmake \ - --log-level=DEBUG \ - -DCMAKE_INSTALL_PREFIX=/usr \ - -DPython_EXECUTABLE=$(command -v python3) \ - -DENABLE_TESTS=ON \ - -DPIP_OPTIONS="--verbose" \ - -S xrootd \ - -B build - cmake build -LH - cmake \ - --build build \ - --clean-first \ - --parallel $(($(nproc) - 1)) - sudo cmake --build build --target install - python3 -m pip list - - - name: Verify install - run: | - command -v xrootd - command -v xrdcp - - - name: Verify Python bindings - run: | - python3 --version --version - python3 -m pip list - python3 -m pip show xrootd - python3 -c 'import XRootD; print(XRootD)' - python3 -c 'import pyxrootd; print(pyxrootd)' - python3 -c 'from XRootD import client; print(client.FileSystem("root://someserver:1094"))' - - cmake-macos: - - runs-on: macos-latest - - env: - CC: clang - CXX: clang++ - CMAKE_ARGS: "-DUSE_SYSTEM_ISAL=TRUE;-DINSTALL_PYTHON_BINDINGS=FALSE" - CMAKE_PREFIX_PATH: /usr/local/opt/openssl@3 - - steps: - - name: Install dependencies with Homebrew - run: | - brew install cmake cppunit davix googletest isa-l openssl@3 python@3.11 - - - name: Install Python dependencies with pip - run: | - python3 --version --version - python3 -m pip install --upgrade pip setuptools wheel - python3 -m pip list - python3 -m site - - - name: Clone repository - uses: actions/checkout@v3 - - - name: Build and Run Tests with CTest - run: | - # workaround for issue #1772, should be removed when that's fixed - sudo sed -i -e "s/localhost/localhost $(hostname)/g" /etc/hosts - ctest -VV -S test.cmake - - - name: Install with CMake - run: cmake --install build - - - name: Install Python Bindings - run: python3 -m pip install --verbose --use-pep517 --user build/bindings/python - - - name: Run Post-install Tests - run: | - export DYLD_LIBRARY_PATH=/usr/local/lib - xrdcp --version - python3 -c 'import XRootD; print(XRootD);' - python3 -c 'from pyxrootd import client; print(client);' - python3 -c 'from pyxrootd import client; print(client.XrdVersion_cpp())' - python3 -c 'from XRootD import client; print(client.FileSystem("root://localhost:1094"))' - python3 -m pip show xrootd - - rpm-centos7: - - runs-on: ubuntu-latest - container: centos:7 - - steps: - - name: Overwrite /etc/yum.repos.d/epel.repo to remove epel-source - run: | - yum install -y epel-release centos-release-scl - head -n -8 /etc/yum.repos.d/epel.repo > /tmp/epel.repo - mv -f /tmp/epel.repo /etc/yum.repos.d/epel.repo - yum clean all - - - name: Install external dependencies with yum - run: | - yum update -y - yum install --nogpg -y \ - gcc-c++ \ - rpm-build \ - git \ - python-srpm-macros \ - centos-release-scl - yum clean all - - # Need to use v1 of action as image Git is too old - - name: Clone repository now that Git is available - uses: actions/checkout@v1 - - - name: Build - run: | - cd packaging/ - ./makesrpm.sh \ - --define "_with_python3 1" \ - --define "_with_tests 1" \ - --define "_with_xrdclhttp 1" \ - --define "_with_scitokens 1" \ - --define "_with_isal 1" - yum-builddep --nogpgcheck -y *.src.rpm - mkdir RPMS - rpmbuild --rebuild \ - --define "_rpmdir RPMS/" \ - --define "_with_python3 1" \ - --define "_with_tests 1" \ - --define "_with_xrdclhttp 1" \ - --define "_with_scitokens 1" \ - --define "_with_isal 1" \ - --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" \ - *.src.rpm - - - name: Install - run: | - ls -lh packaging/RPMS/ - yum install -y \ - ./packaging/RPMS/xrootd-*.rpm \ - ./packaging/RPMS/python3-xrootd-*.rpm - - - name: Verify install - run: | - command -v xrootd - command -v xrdcp - - - name: Verify Python bindings - run: | - python3 --version --version - python3 -m pip list - python3 -m pip show xrootd - python3 -c 'import XRootD; print(XRootD)' - python3 -c 'import pyxrootd; print(pyxrootd)' - python3 -c 'from XRootD import client; print(client.FileSystem("root://someserver:1094"))' - - - name: Build sdist using publishing workflow - run: | - cp packaging/wheel/* . - ./publish.sh - ls -lhtra dist/ - - rpm-fedora: - - runs-on: ubuntu-latest - container: fedora:37 - - steps: - - name: Install external dependencies with dnf - run: | - dnf update -y - dnf install --nogpg -y \ - gcc-c++ \ - rpm-build \ - tar \ - dnf-plugins-core \ - git - dnf clean all - - - name: Clone repository - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - name: Build - run: | - # c.f. https://github.com/actions/checkout/issues/760 - git config --global --add safe.directory "$GITHUB_WORKSPACE" - cd packaging/ - ./makesrpm.sh \ - --define "_with_python3 1" \ - --define "_with_ceph11 1" - dnf builddep --nogpgcheck -y *.src.rpm - mkdir RPMS - rpmbuild --rebuild \ - --define "_rpmdir RPMS/" \ - --define "_with_python3 1" \ - --define "_with_ceph11 1" \ - --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" \ - *.src.rpm - - - name: Install - run: | - ls -lh packaging/RPMS/ - dnf install -y \ - ./packaging/RPMS/xrootd-*.rpm \ - ./packaging/RPMS/python3-xrootd-*.rpm - - - name: Verify install - run: | - command -v xrootd - command -v xrdcp - - - name: Verify Python bindings - run: | - python3 --version --version - python3 -m pip list - python3 -m pip show xrootd - python3 -c 'import XRootD; print(XRootD)' - python3 -c 'import pyxrootd; print(pyxrootd)' - python3 -c 'from XRootD import client; print(client.FileSystem("root://someserver:1094"))' - - dpkg-ubuntu: - - # Use of sudo as https://github.com/actions/virtual-environments requires it - runs-on: ubuntu-latest - - steps: - - name: Install external dependencies with apt-get - run: | - sudo apt-get update -y - DEBIAN_FRONTEND=noninteractive sudo apt-get install -y \ - g++ \ - git \ - cmake \ - debhelper \ - devscripts \ - equivs \ - gdebi-core - sudo apt-get autoclean -y - - - name: Clone repository - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - name: Build .deb - run: | - mv packaging/debian/python3-xrootd.install.new packaging/debian/python3-xrootd.install - cp -R packaging/debian/ . - mk-build-deps --build-dep debian/control - sudo gdebi -n xrootd-build-deps-depends*.deb - version=`./genversion.sh --print-only` - dch --create -v `echo $version | sed 's/^v\(.*\)/\1/'` --package xrootd --urgency low --distribution $(lsb_release -cs) -M "This package is built and released automatically. For important notices and releases subscribe to our maling lists or visit our website." - dpkg-buildpackage -b -us -uc -tc --buildinfo-option="-udeb_packages" --buildinfo-file="deb_packages/xrootd_$(dpkg-parsechangelog -S version)_$(dpkg-architecture -qDEB_BUILD_ARCH).buildinfo" --changes-option="-udeb_packages" --buildinfo-file="deb_packages/xrootd_$(dpkg-parsechangelog -S version)_$(dpkg-architecture -qDEB_BUILD_ARCH).changes" - - - name: Install - run: | - ls -lh deb_packages/*.deb - sudo apt-get install -y \ - ./deb_packages/libxr*_*.deb \ - ./deb_packages/xrootd-libs_*.deb \ - ./deb_packages/xrootd-client*_*.deb \ - ./deb_packages/xrootd-devel_*.deb \ - ./deb_packages/xrootd-plugins_*.deb \ - ./deb_packages/xrootd-server*_*.deb \ - ./deb_packages/python3-xrootd_*.deb - - - name: Verify install - run: | - command -v xrootd - command -v xrdcp - - - name: Verify Python bindings - run: | - python3 --version --version - python3 -m pip list - python3 -m pip show xrootd - python3 -c 'import XRootD; print(XRootD)' - python3 -c 'import pyxrootd; print(pyxrootd)' - python3 -c 'from XRootD import client; print(client.FileSystem("root://someserver:1094"))' - - sdist-centos7: - - runs-on: ubuntu-latest - container: centos:7 - - steps: - - name: Install external dependencies with yum - run: | - yum update -y - yum install -y epel-release centos-release-scl - yum clean all - yum install --nogpg -y \ - cmake3 \ - gcc-c++ \ - make \ - krb5-devel \ - libuuid-devel \ - libxml2-devel \ - openssl-devel \ - systemd-devel \ - zlib-devel \ - devtoolset-7-gcc-c++ \ - python3-devel \ - python3-setuptools \ - git \ - tree \ - cppunit-devel \ - gtest-devel - yum clean all - python3 -m pip --no-cache-dir install wheel - - # Need to use v1 of action as image Git is too old - - name: Clone repository now that Git is available - uses: actions/checkout@v1 - - - name: Build sdist using publishing workflow - run: | - . /opt/rh/devtoolset-7/enable - cp packaging/wheel/* . - ./publish.sh - python3 -m pip --verbose install --upgrade ./dist/xrootd-*.tar.gz - python3 -m pip list - - - name: Show site-packages layout for XRootD modules - run: | - find $(python3 -c 'import XRootD; import pathlib; print(str(pathlib.Path(XRootD.__path__[0]).parent))') \ - -type d \ - -iname "*xrootd*" | xargs tree - - - name: Verify Python bindings - run: | - python3 --version --version - python3 -m pip list - python3 -m pip show xrootd - python3 -c 'import XRootD; print(XRootD)' - python3 -c 'import pyxrootd; print(pyxrootd)' - python3 -c 'from XRootD import client; print(client.FileSystem("root://someserver:1094"))' - - sdist-centos7-updated-python: - - runs-on: ubuntu-latest - container: centos:7 - - steps: - - name: Install external dependencies with yum - run: | - yum update -y - yum install -y epel-release centos-release-scl - yum clean all - yum install --nogpg -y \ - cmake3 \ - gcc-c++ \ - make \ - krb5-devel \ - libuuid-devel \ - libxml2-devel \ - openssl-devel \ - systemd-devel \ - zlib-devel \ - devtoolset-7-gcc-c++ \ - python3-devel \ - python3-setuptools \ - git \ - tree \ - cppunit-devel \ - gtest-devel - yum clean all - python3 -m pip --no-cache-dir install --upgrade pip setuptools wheel - - # Need to use v1 of action as image Git is too old - - name: Clone repository now that Git is available - uses: actions/checkout@v1 - - - name: Build sdist using publishing workflow - run: | - . /opt/rh/devtoolset-7/enable - cp packaging/wheel/* . - ./publish.sh - python3 -m pip --verbose install --upgrade ./dist/xrootd-*.tar.gz - python3 -m pip list - - - name: Show site-packages layout for XRootD modules - run: | - find $(python3 -c 'import XRootD; import pathlib; print(str(pathlib.Path(XRootD.__path__[0]).parent))') \ - -type d \ - -iname "*xrootd*" | xargs tree - - - name: Verify Python bindings - run: | - python3 --version --version - python3 -m pip list - python3 -m pip show xrootd - python3 -c 'import XRootD; print(XRootD)' - python3 -c 'import pyxrootd; print(pyxrootd)' - python3 -c 'from XRootD import client; print(client.FileSystem("root://someserver:1094"))' - - sdist-ubuntu: - - # Use of sudo as https://github.com/actions/virtual-environments requires it - runs-on: ubuntu-latest - - steps: - - name: Install external dependencies with apt-get - run: | - sudo apt-get update -y - DEBIAN_FRONTEND=noninteractive sudo apt-get install -y \ - g++ \ - git \ - cmake \ - uuid-dev \ - dpkg-dev \ - libssl-dev \ - libx11-dev \ - python3 \ - python3-pip \ - python3-venv \ - python3-dev \ - pkg-config \ - tree - sudo apt-get autoclean -y - # Remove packages with invalid versions which cause sdist build to fail - sudo apt-get remove python3-debian python3-distro-info - python3 -m pip --no-cache-dir install --upgrade pip setuptools wheel - python3 -m pip list - - - name: Clone repository - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - name: Build sdist using publishing workflow - run: | - cp packaging/wheel/* . - ./publish.sh - python3 -m pip --verbose install --upgrade ./dist/xrootd-*.tar.gz - python3 -m pip list - - - name: Show site-packages layout for XRootD modules - run: | - find $(python3 -c 'import XRootD; import pathlib; print(str(pathlib.Path(XRootD.__path__[0]).parent))') \ - -type d \ - -iname "*xrootd*" | xargs tree - - - name: Verify Python bindings - run: | - python3 --version --version - python3 -m pip list - python3 -m pip show xrootd - python3 -c 'import XRootD; print(XRootD)' - python3 -c 'import pyxrootd; print(pyxrootd)' - python3 -c 'from XRootD import client; print(client.FileSystem("root://someserver:1094"))' From 4ea1ee0263031aea5d54dbf1f3ee7284d244ce1e Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 15 Aug 2023 16:35:33 +0200 Subject: [PATCH 300/442] [CI] Add RPM workflow to GitHub Actions --- .github/workflows/RPM.yml | 197 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 .github/workflows/RPM.yml diff --git a/.github/workflows/RPM.yml b/.github/workflows/RPM.yml new file mode 100644 index 00000000000..8f0f6895adb --- /dev/null +++ b/.github/workflows/RPM.yml @@ -0,0 +1,197 @@ +name: RPM + +on: + push: + branches: + - devel + - master + paths-ignore: + - .gitignore + - .gitlab-ci.yml + - '**.md' + - 'docs/**' + - 'docker/**' + pull_request: + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + centos7: + name: CentOS 7 + runs-on: ubuntu-latest + container: centos:7 + + steps: + - name: Install git + run: yum install -y git + + - name: Clone repository + uses: actions/checkout@v1 + + - name: Install RPM development tools + run: | + yum install -y centos-release-scl epel-release + yum install -y epel-rpm-macros rpmdevtools yum-utils + + - name: Install XRootD build dependencies + run: yum-builddep -y xrootd.spec + + - name: Build RPMs + run: | + rpmdev-setuptree + git config --global --add safe.directory "$GITHUB_WORKSPACE" + git archive --prefix xrootd/ -o $(rpm -E '%{_sourcedir}')/xrootd.tar.gz HEAD + rpmbuild -bb --with git xrootd.spec + + - name: Install RPMs + run: yum install -y $(rpm -E '%{_rpmdir}')/*/*.rpm + + - name: Run post-install tests + run: tests/post-install.sh + + - name: Move RPMs to Artifact Directory + run: mkdir RPMS && mv $(rpm -E '%{_rpmdir}')/ RPMS$(rpm -E '%{dist}' | tr . /) + + - name: Upload Artifacts + uses: actions/upload-artifact@v3 + with: + name: RPM + path: RPMS + retention-days: 1 + + alma8: + name: Alma Linux 8 + runs-on: ubuntu-latest + container: almalinux:8 + + steps: + - name: Install git + run: yum install -y git + + - name: Clone repository + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Install RPM development tools + run: | + dnf install -y epel-release rpmdevtools dnf-plugins-core + dnf config-manager --set-enabled powertools + + - name: Install XRootD build dependencies + run: dnf builddep -y xrootd.spec + + - name: Build RPMs + run: | + rpmdev-setuptree + git config --global --add safe.directory "$GITHUB_WORKSPACE" + git archive --prefix xrootd/ -o $(rpm -E '%{_sourcedir}')/xrootd.tar.gz HEAD + rpmbuild -bb --with git xrootd.spec + + - name: Install RPMs + run: dnf install -y $(rpm -E '%{_rpmdir}')/*/*.rpm + + - name: Run post-install tests + run: tests/post-install.sh + + - name: Move RPMs to Artifact Directory + run: mkdir RPMS && mv $(rpm -E '%{_rpmdir}')/ RPMS$(rpm -E '%{dist}' | tr . /) + + - name: Upload Artifacts + uses: actions/upload-artifact@v3 + with: + name: RPM + path: RPMS + retention-days: 1 + + alma9: + name: Alma Linux 9 + runs-on: ubuntu-latest + container: almalinux:9 + + steps: + - name: Install git + run: yum install -y git + + - name: Clone repository + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Install RPM development tools + run: | + dnf install -y epel-release rpmdevtools dnf-plugins-core + dnf config-manager --set-enabled crb + + - name: Install XRootD build dependencies + run: dnf builddep -y xrootd.spec + + - name: Build RPMs + run: | + rpmdev-setuptree + git config --global --add safe.directory "$GITHUB_WORKSPACE" + git archive --prefix xrootd/ -o $(rpm -E '%{_sourcedir}')/xrootd.tar.gz HEAD + rpmbuild -bb --with git xrootd.spec + + - name: Install RPMs + run: dnf install -y $(rpm -E '%{_rpmdir}')/*/*.rpm + + - name: Run post-install tests + run: tests/post-install.sh + + - name: Move RPMs to Artifact Directory + run: mkdir RPMS && mv $(rpm -E '%{_rpmdir}')/ RPMS$(rpm -E '%{dist}' | tr . /) + + - name: Upload Artifacts + uses: actions/upload-artifact@v3 + with: + name: RPM + path: RPMS + retention-days: 1 + + fedora: + name: Fedora 39 + runs-on: ubuntu-latest + container: fedora:39 + + steps: + - name: Install git + run: yum install -y git + + - name: Clone repository + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Install RPM development tools + run: | + dnf install -y rpmdevtools dnf-plugins-core + + - name: Install XRootD build dependencies + run: dnf builddep -y --define 'with_ceph 1' xrootd.spec + + - name: Build RPMs + run: | + rpmdev-setuptree + git config --global --add safe.directory "$GITHUB_WORKSPACE" + git archive --prefix xrootd/ -o $(rpm -E '%{_sourcedir}')/xrootd.tar.gz HEAD + rpmbuild -bb --with git --with ceph xrootd.spec + + - name: Install RPMs + run: dnf install -y $(rpm -E '%{_rpmdir}')/*/*.rpm + + - name: Run post-install tests + run: tests/post-install.sh + + - name: Move RPMs to Artifact Directory + run: mkdir RPMS && mv $(rpm -E '%{_rpmdir}')/ RPMS$(rpm -E '%{dist}' | tr . /) + + - name: Upload Artifacts + uses: actions/upload-artifact@v3 + with: + name: RPM + path: RPMS + retention-days: 1 From fdfbadfb7267474fefc6248ae0be6598dbd32130 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 17 Aug 2023 12:03:00 +0200 Subject: [PATCH 301/442] [CI] Add DEB workflow to GitHub Actions --- .github/workflows/DEB.yml | 121 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 .github/workflows/DEB.yml diff --git a/.github/workflows/DEB.yml b/.github/workflows/DEB.yml new file mode 100644 index 00000000000..574c5c589b5 --- /dev/null +++ b/.github/workflows/DEB.yml @@ -0,0 +1,121 @@ +name: DEB + +on: + push: + branches: + - devel + - master + paths-ignore: + - .gitignore + - .gitlab-ci.yml + - '**.md' + - 'docs/**' + - 'docker/**' + pull_request: + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +defaults: + run: + shell: bash + +env: + DEBIAN_FRONTEND: noninteractive + +jobs: + debian: + name: Debian + + strategy: + matrix: + version: [ 11, 12 ] + + runs-on: ubuntu-latest + container: debian:${{ matrix.version }} + + steps: + - name: Install development tools + run: | + apt update -qq + apt install -y build-essential devscripts git + + - name: Clone repository + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Install XRootD build dependencies + run: mk-build-deps --install --remove debian/control <<< yes + + - name: Build DEBs + run: | + git config --global --add safe.directory "$GITHUB_WORKSPACE" + VERSION=$(git describe --match 'v*' | sed -e 's/v//; s/-rc/~rc/; s/-g/+git/; s/-/.post/; s/-/./') + dch --create --package xrootd -v ${VERSION} -M 'XRootD automated build.' + debuild --no-tgz-check --no-sign -b + + - name: Install DEBs + run: apt install -y ../*.deb + + - name: Run post-install tests + run: tests/post-install.sh + + - name: Move DEBs to Artifact Directory + run: | + source /etc/os-release + mkdir -p DEB/${ID}/${VERSION_CODENAME} + mv ../*.* DEB/${ID}/${VERSION_CODENAME} + + - name: Upload Artifacts + uses: actions/upload-artifact@v3 + with: + name: DEB + path: DEB + retention-days: 1 + + ubuntu: + name: Ubuntu (22.04) + runs-on: ubuntu-22.04 + + steps: + - name: Install development tools + run: | + sudo apt update -qq + sudo apt install -y build-essential devscripts equivs git + + - name: Clone repository + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Install XRootD build dependencies + run: mk-build-deps --install --remove -s sudo debian/control <<< yes + + - name: Build DEBs + run: | + git config --global --add safe.directory "$GITHUB_WORKSPACE" + VERSION=$(git describe --match 'v*' | sed -e 's/v//; s/-rc/~rc/; s/-g/+git/; s/-/.post/; s/-/./') + dch --create --package xrootd -v ${VERSION} -M 'XRootD automated build.' + debuild --no-tgz-check --no-sign -b + + - name: Install DEBs + run: sudo apt install -y ../*.deb + + - name: Run post-install tests + run: tests/post-install.sh + + - name: Move DEBs to Artifact Directory + run: | + source /etc/os-release + mkdir -p DEB/${ID}/${VERSION_CODENAME} + mv ../*.* DEB/${ID}/${VERSION_CODENAME} + + - name: Upload Artifacts + uses: actions/upload-artifact@v3 + with: + name: DEB + path: DEB + retention-days: 1 From 9a9b1df4d24bb239ee4fffd4e211230521abc71f Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 22 Aug 2023 09:58:22 +0200 Subject: [PATCH 302/442] [CI] Add Python workflow to GitHub Actions This workflow tests building binary wheels for XRootD with pip using the main setup.py, for various versions of Python. --- .github/workflows/python.yml | 74 ++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 .github/workflows/python.yml diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml new file mode 100644 index 00000000000..49b0048c192 --- /dev/null +++ b/.github/workflows/python.yml @@ -0,0 +1,74 @@ +name: Python + +on: + push: + branches: + - devel + - master + paths: + - setup.py + - pyproject.toml + - bindings/python + pull_request: + paths: + - setup.py + - pyproject.toml + - bindings/python + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + manylinux: + name: Python + runs-on: ubuntu-latest + container: quay.io/pypa/manylinux_2_28_x86_64 + + strategy: + matrix: + version: [ "3.6", "3.8", "3.10", "3.11", "3.12" ] + + env: + PYTHON: python${{ matrix.version }} + + steps: + - name: Install dependencies + run: dnf install -y git krb5-devel libuuid-devel openssl-devel + + - name: Clone repository + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Build source distribution tarball + run: | + git config --global --add safe.directory "$GITHUB_WORKSPACE" + ./genversion.sh >| VERSION + ${PYTHON} -m build --sdist + + - name: Build binary wheel + run: ${PYTHON} -m pip wheel --use-pep517 --verbose dist/*.tar.gz + + - name: Install binary wheel + run: ${PYTHON} -m pip install xrootd*.whl + + - name: Run post-installation tests + run: | + command -v ${PYTHON} && ${PYTHON} --version + ${PYTHON} -m pip show xrootd + ${PYTHON} -c 'import XRootD; print(XRootD)' + ${PYTHON} -c 'import pyxrootd; print(pyxrootd)' + ${PYTHON} -c 'from XRootD import client; help(client)' + ${PYTHON} -c 'from XRootD import client; print(client.FileSystem("root://localhost"))' + + - name: Move binary wheels to artifact directory + run: mv *.whl dist/ + + - name: Upload Artifacts + uses: actions/upload-artifact@v3 + with: + name: Python + path: dist + retention-days: 1 From 8d17671452853a87505b9a246f49ba7bd815b2ca Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 31 Oct 2023 14:50:05 +0100 Subject: [PATCH 303/442] [Python] Fix content type of README --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 62019633d30..c676e0854fb 100644 --- a/setup.py +++ b/setup.py @@ -101,7 +101,7 @@ def build_extensions(self): keywords=['XRootD', 'network filesystem'], license='LGPLv3+', long_description=open('README.md').read(), - long_description_content_type='text/plain', + long_description_content_type='text/markdown', packages = ['XRootD', 'XRootD.client', 'pyxrootd'], package_dir = { 'pyxrootd' : 'bindings/python/src', From 2859153a986efaed21046c67fa5b6a183483eca8 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 31 Oct 2023 16:47:45 +0100 Subject: [PATCH 304/442] [CI] Adapt macOS workflow for variable Python3 versions Some build runners have Python 3.11 and some were updated and now have Python 3.12 as default interpreter, so the workflow needs to export PYTHONPATH according to which version is the default. --- .github/workflows/CI.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 15a0e0f68a5..b5f75d73c1e 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -183,14 +183,13 @@ jobs: CXX: clang++ CMAKE_ARGS: "-DPython_FIND_UNVERSIONED_NAMES=FIRST;-DUSE_SYSTEM_ISAL=TRUE" CMAKE_PREFIX_PATH: /usr/local/opt/openssl@3 - PYTHONPATH: /usr/local/lib/python3.11/site-packages steps: - name: Workaround for issue 1772 run: sudo sed -i -e "s/localhost/localhost $(hostname)/g" /etc/hosts - name: Install dependencies with Homebrew - run: brew install cmake cppunit davix googletest isa-l openssl@3 python@3.11 + run: brew install cmake cppunit davix googletest isa-l openssl@3 python3 - name: Install Python dependencies with pip run: python3 -m pip install --upgrade pip setuptools wheel @@ -207,4 +206,7 @@ jobs: run: cmake --install build - name: Run post-install tests - run: tests/post-install.sh + run: | + export PYVERSION=$(python3 --version | grep -o 3...) + export PYTHONPATH=/usr/local/lib/python${PYVERSION}/site-packages + tests/post-install.sh From 6d42bf3feb84a4986443d8575073fd693101e22b Mon Sep 17 00:00:00 2001 From: Mattias Ellert Date: Sat, 28 Oct 2023 15:54:24 +0200 Subject: [PATCH 305/442] Posix open() called with wrong flags in test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OpenFlags::Flags should not be used for the flags argument in a posix open() call, since they use different values. The OpenFlags::Flags values are defined in src/XrdCl/XrdClFileSystem.hh: MakePath = kXR_mkpath, //!< Create directory path if it does not //!< already exist New = kXR_new, //!< Open the file only if it does not already Update = kXR_open_updt, //!< Open for reading and writing The kXR_* values are defined in src/XProtocol/XProtocol.hh: kXR_new = 0x0008, // 8 kXR_mkpath = 0x0100, // 256 kXR_open_updt= 0x0020, // 32 As can be seen these do not correspond to the O_* values used in the posix open() call. What they actually mean depends on the system and varies between architectures. On hppa Linux O_CREAT is defind in /usr/include/hppa-linux-gnu/asm/fcntl.h: #define O_CREAT 000000400 /* not fcntl */ Since 0o400 = 0x100 = 256 this by chance matches kXR_mkpath and triggers the error below. On other architectures the wrong flags are just silently accepted. In file included from /usr/include/fcntl.h:342, from /<>/tests/XrdClTests/LocalFileHandlerTest.cc:25: In function ‘int open(const char*, int, ...)’, inlined from ‘void LocalFileHandlerTest::WriteMkdirTest()’ at /<>/tests/XrdClTests/LocalFileHandlerTest.cc:210:17: /usr/include/hppa-linux-gnu/bits/fcntl2.h:50:31: error: call to ‘__open_missing_mode’ declared with attribute error: open with O_CREAT or O_TMPFILE in second argument needs 3 arguments 50 | __open_missing_mode (); | ~~~~~~~~~~~~~~~~~~~~^~ --- tests/XrdClTests/LocalFileHandlerTest.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/XrdClTests/LocalFileHandlerTest.cc b/tests/XrdClTests/LocalFileHandlerTest.cc index 0a58ed6effd..f8618c14f7b 100644 --- a/tests/XrdClTests/LocalFileHandlerTest.cc +++ b/tests/XrdClTests/LocalFileHandlerTest.cc @@ -142,7 +142,7 @@ void LocalFileHandlerTest::WriteTest(){ //---------------------------------------------------------------------------- // Read file with POSIX calls to confirm correct write //---------------------------------------------------------------------------- - int fd = open( targetURL.c_str(), flags ); + int fd = open( targetURL.c_str(), O_RDWR ); int rc = read( fd, buffer, int( writeSize ) ); CPPUNIT_ASSERT_EQUAL( rc, int( writeSize ) ); std::string read( (char *)buffer, writeSize ); @@ -176,7 +176,7 @@ void LocalFileHandlerTest::WriteWithOffsetTest(){ //---------------------------------------------------------------------------- // Read file with POSIX calls to confirm correct write //---------------------------------------------------------------------------- - int fd = open( targetURL.c_str(), flags ); + int fd = open( targetURL.c_str(), O_RDWR ); int rc = read( fd, buffer, offset ); CPPUNIT_ASSERT_EQUAL( rc, int( offset ) ); std::string read( (char *)buffer, offset ); @@ -207,7 +207,7 @@ void LocalFileHandlerTest::WriteMkdirTest(){ //---------------------------------------------------------------------------- // Read file with POSIX calls to confirm correct write //---------------------------------------------------------------------------- - int fd = open( targetURL.c_str(), flags ); + int fd = open( targetURL.c_str(), O_RDWR ); int rc = read( fd, buffer, writeSize ); CPPUNIT_ASSERT_EQUAL( rc, int( writeSize ) ); std::string read( buffer, writeSize ); From 65fda231c992aa5a4dfac841b405606be43233b0 Mon Sep 17 00:00:00 2001 From: Mattias Ellert Date: Sat, 28 Oct 2023 17:55:58 +0200 Subject: [PATCH 306/442] PATH_MAX / MAXPATHLEN might be undefined XrdSys/XrdSysPlatform.hh defines MAXPATHLEN if needed. /<>/tests/XrdCl/XrdClURL.cc: In member function 'virtual void URLTest_LocalURLs_Test::TestBody()': /<>/tests/XrdCl/XrdClURL.cc:17:12: error: 'PATH_MAX' was not declared in this scope 17 | char url[PATH_MAX]; | ^~~~~~~~ /<>/tests/XrdCl/XrdClURL.cc:21:18: error: 'url' was not declared in this scope 21 | snprintf(url, sizeof(url), "%s%s%s", protocol, path, params); | ^~~ /<>/tests/XrdCl/XrdClURL.cc: In member function 'virtual void URLTest_RemoteURLs_Test::TestBody()': /<>/tests/XrdCl/XrdClURL.cc:47:12: error: 'PATH_MAX' was not declared in this scope 47 | char url[PATH_MAX]; | ^~~~~~~~ /<>/tests/XrdCl/XrdClURL.cc:57:26: error: 'url' was not declared in this scope 57 | snprintf(url, sizeof(url), "%s%s%s%s%s%s%s%s%s%s%s%s", | ^~~ /<>/tests/XrdCl/XrdClURL.cc:63:26: error: 'path_params' was not declared in this scope 63 | snprintf(path_params, sizeof(path_params), "%s%s", *path == '/' ? path+1 : path, params); | ^~~~~~~~~~~ --- tests/XrdCl/XrdClURL.cc | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/XrdCl/XrdClURL.cc b/tests/XrdCl/XrdClURL.cc index 24e7fdc2aad..046f41a8c8f 100644 --- a/tests/XrdCl/XrdClURL.cc +++ b/tests/XrdCl/XrdClURL.cc @@ -1,6 +1,8 @@ #undef NDEBUG #include +#include "XrdSys/XrdSysPlatform.hh" + #include #include @@ -14,7 +16,7 @@ class URLTest : public ::testing::Test {}; TEST(URLTest, LocalURLs) { - char url[PATH_MAX]; + char url[MAXPATHLEN]; for (const auto& protocol : { "", "file://" }) { for (const auto& path : { "/dev", "/dev/", "/dev/null" }) { for (const auto& params : { "", "?param=value", "?param1=value1¶m2=value2" }) { @@ -44,8 +46,8 @@ TEST(URLTest, LocalURLs) TEST(URLTest, RemoteURLs) { - char url[PATH_MAX]; - char path_params[PATH_MAX]; + char url[MAXPATHLEN]; + char path_params[MAXPATHLEN]; for (const char *protocol : { "", "http", "root", "https", "roots" }) { int default_port = *protocol == 'h' ? (strlen(protocol) == 4 ? 80 : 443) : 1094; for (const char *user : { "", "alice", "bob", "user_123", "xrootd" }) { From b5f5496d9fa705d82c31d69f54df89a0c91d2e22 Mon Sep 17 00:00:00 2001 From: Mattias Ellert Date: Tue, 31 Oct 2023 18:17:23 +0100 Subject: [PATCH 307/442] Don't try to enable TCP_CORK in GNU/Hurd The "man tcp" says about TCP_CORK: "This option should not be used in code intended to be portable". So, it should not be expected to be working everywhere. The failure below is from Debian GNU/Hurd: test 22 Start 22: XRootD::smoke-test 22: Test command: /usr/bin/sh "-c" "/<>/tests/XRootD/smoke.sh" 22: Working Directory: /<>/obj-i686-gnu/tests/XRootD 22: Environment variables: 22: XRDCP=/<>/obj-i686-gnu/src/XrdCl/xrdcp 22: XRDFS=/<>/obj-i686-gnu/src/XrdCl/xrdfs 22: CRC32C=/<>/obj-i686-gnu/src/xrdcrc32c 22: ADLER32=/<>/obj-i686-gnu/src/xrdadler32 22: HOST=root://localhost:10940 22: Test timeout computed to be: 10000000 22: Using /usr/bin/openssl: OpenSSL 3.0.12 24 Oct 2023 (Library: OpenSSL 3.0.12 24 Oct 2023) 22: v5.6.3 22: Using /<>/obj-i686-gnu/src/XrdCl/xrdcp: 22: [FATAL] Socket opt error: no message of desired type 21/22 Test #22: XRootD::smoke-test ........................................................***Failed 0.13 sec Using /usr/bin/openssl: OpenSSL 3.0.12 24 Oct 2023 (Library: OpenSSL 3.0.12 24 Oct 2023) v5.6.3 Using /<>/obj-i686-gnu/src/XrdCl/xrdcp: [FATAL] Socket opt error: no message of desired type --- src/XrdCl/XrdClSocket.cc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/XrdCl/XrdClSocket.cc b/src/XrdCl/XrdClSocket.cc index a94eb4b486a..a2cb47c127e 100644 --- a/src/XrdCl/XrdClSocket.cc +++ b/src/XrdCl/XrdClSocket.cc @@ -781,7 +781,8 @@ namespace XrdCl //------------------------------------------------------------------------ XRootDStatus Socket::Cork() { -#if defined(TCP_CORK) // it's not defined on mac, we might want explore the possibility of using TCP_NOPUSH +#if defined(TCP_CORK) && !defined(__GNU__) + // it's not defined on mac, we might want explore the possibility of using TCP_NOPUSH if( pCorked ) return XRootDStatus(); int state = 1; @@ -798,7 +799,8 @@ namespace XrdCl //------------------------------------------------------------------------ XRootDStatus Socket::Uncork() { -#if defined(TCP_CORK) // it's not defined on mac, we might want explore the possibility of using TCP_NOPUSH +#if defined(TCP_CORK) && !defined(__GNU__) + // it's not defined on mac, we might want explore the possibility of using TCP_NOPUSH if( !pCorked ) return XRootDStatus(); int state = 0; From 4fb456fd62d6f01ea6632c3e957c03e1395b4311 Mon Sep 17 00:00:00 2001 From: David Smith Date: Thu, 2 Nov 2023 09:46:54 +0100 Subject: [PATCH 308/442] [HTTP] Initialize SecEntity.addrInfo in fresh protocol object; Fixes #2114 --- src/XrdHttp/XrdHttpProtocol.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdHttp/XrdHttpProtocol.cc b/src/XrdHttp/XrdHttpProtocol.cc index fcb481add84..3b82de72e75 100644 --- a/src/XrdHttp/XrdHttpProtocol.cc +++ b/src/XrdHttp/XrdHttpProtocol.cc @@ -282,7 +282,7 @@ XrdProtocol *XrdHttpProtocol::Match(XrdLink *lp) { // that is is https without invoking TLS on the actual link. Eventually, // we should just use the link's TLS native implementation. // - SecEntity.addrInfo = lp->AddrInfo(); + hp->SecEntity.addrInfo = lp->AddrInfo(); XrdNetAddr *netP = const_cast(lp->NetAddr()); netP->SetDialect("https"); netP->SetTLS(true); From 55958ab7e27de66451ce7b07f30b8539a8360817 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 3 Nov 2023 08:59:35 +0100 Subject: [PATCH 309/442] [Tests] Add script to sanity-check installed headers This script is meant to be run after installation to make sure each installed header can be included without errors. For fixing issues, it's better run it against a staged installation, as show below: xrootd $ ctest -V -S test.cmake xrootd $ env DESTDIR=${PWD}/install cmake --install build/ xrootd $ tests/check-headers.sh install/usr/include/xrootd The XrdPosix headers are special, as they require specific compile options and have include dependencies between them. Therefore, they are skipped by this script. --- tests/check-headers.sh | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100755 tests/check-headers.sh diff --git a/tests/check-headers.sh b/tests/check-headers.sh new file mode 100755 index 00000000000..eba2ddd65e6 --- /dev/null +++ b/tests/check-headers.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +# This script checks that each installed public and private XRootD header can +# be included individually without errors. The intention is to identify which +# headers may have missing includes, missing forward declarations, or missing +# header dependencies, that is, headers from XRootD which it includes, but were +# not installed by the install target. + +# We need to split CXXFLAGS +# shellcheck disable=SC2086 + +: "${CXX:=c++}" +: "${CXXFLAGS:=-Wall}" +: "${INCLUDE_DIR:=${1:-/usr/include/xrootd}}" + +if ! command -v "${CXX}" >/dev/null; then + exit 1 +fi + +STATUS=0 + +HEADERS=$(find "${INCLUDE_DIR}" -type f -name '*.hh') +PUBLIC_HEADERS=$(grep -E -v '(XrdPosix|private)' <<< "${HEADERS}") +PRIVATE_HEADERS=$(grep private <<< "${HEADERS}") + +# Check public headers without adding private include directory. +# This ensures public headers do not depend on any private headers. + +while IFS=$'\n' read -r HEADER; do + "${CXX}" -fsyntax-only ${CXXFLAGS} -I"${INCLUDE_DIR}" "${HEADER}" || STATUS=1 +done <<< "${PUBLIC_HEADERS}" + +# Check private headers + +while IFS=$'\n' read -r HEADER; do + "${CXX}" -fsyntax-only ${CXXFLAGS} -I"${INCLUDE_DIR}"{,/private} "${HEADER}" || STATUS=1 +done <<< "${PRIVATE_HEADERS}" + +exit $STATUS From 3149472f45c593c8737819563bba5075edc2d2b3 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 3 Nov 2023 09:07:47 +0100 Subject: [PATCH 310/442] [CI] Enable header sanity check in GitHub Actions --- .github/workflows/CI.yml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index b5f75d73c1e..e8f635d7989 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -72,7 +72,9 @@ jobs: run: cmake --install build - name: Run post-install tests - run: tests/post-install.sh + run: | + tests/post-install.sh + tests/check-headers.sh fedora: name: Fedora @@ -81,7 +83,7 @@ jobs: env: CMAKE_GENERATOR: Ninja - CMAKE_ARGS: -DCMAKE_INSTALL_RPATH='$ORIGIN/../$LIB' + CMAKE_ARGS: "-DCMAKE_INSTALL_PREFIX=/usr;-DCMAKE_INSTALL_RPATH='$ORIGIN/../$LIB'" steps: - name: Install dependencies @@ -102,7 +104,9 @@ jobs: run: cmake --install build - name: Run post-install tests - run: tests/post-install.sh + run: | + tests/post-install.sh + tests/check-headers.sh ubuntu: name: Ubuntu @@ -168,7 +172,9 @@ jobs: --use-pep517 --verbose build/bindings/python - name: Run post-install tests - run: tests/post-install.sh + run: | + tests/post-install.sh + tests/check-headers.sh macos: strategy: From 4d970063603a075d4a041ba64f06925ddf7e613e Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 2 Nov 2023 16:59:59 +0100 Subject: [PATCH 311/442] [Xrd] Include for strlen(), not MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit XrdLinkMatch.hh:34:1: note: ‘strlen’ is defined in header ‘’; did you forget to ‘#include ’? --- src/Xrd/XrdLinkMatch.hh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Xrd/XrdLinkMatch.hh b/src/Xrd/XrdLinkMatch.hh index 1bd36e5add9..76ce4b22d24 100644 --- a/src/Xrd/XrdLinkMatch.hh +++ b/src/Xrd/XrdLinkMatch.hh @@ -29,8 +29,7 @@ /* specific prior written permission of the institution or contributor. */ /******************************************************************************/ -#include -#include +#include class XrdLinkMatch { From 00407bca2eef3fe5f16fa2140f22173b93b30660 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 2 Nov 2023 17:02:01 +0100 Subject: [PATCH 312/442] [Xrd] Add XrdNetAddrInfo forward declaration in XrdTcpMonPin.hh --- src/Xrd/XrdTcpMonPin.hh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Xrd/XrdTcpMonPin.hh b/src/Xrd/XrdTcpMonPin.hh index bdaa83b1cb7..6d4e9b7a2e4 100644 --- a/src/Xrd/XrdTcpMonPin.hh +++ b/src/Xrd/XrdTcpMonPin.hh @@ -42,6 +42,8 @@ @endcode */ +class XrdNetAddrInfo; + class XrdTcpMonPin { public: From 74cc358ca06c493e12b8080aff051aaa8775d8ca Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 2 Nov 2023 17:05:16 +0100 Subject: [PATCH 313/442] [XrdCl] Add missing includes and Needed for std::future and std::logic_error, respectively. --- src/XrdCl/XrdClArg.hh | 1 + src/XrdCl/XrdClCtx.hh | 1 + 2 files changed, 2 insertions(+) diff --git a/src/XrdCl/XrdClArg.hh b/src/XrdCl/XrdClArg.hh index 3ff9fce9f5b..147d73b51cb 100644 --- a/src/XrdCl/XrdClArg.hh +++ b/src/XrdCl/XrdClArg.hh @@ -29,6 +29,7 @@ #include "XrdCl/XrdClFwd.hh" #include "XrdCl/XrdClOptional.hh" +#include #include #include #include diff --git a/src/XrdCl/XrdClCtx.hh b/src/XrdCl/XrdClCtx.hh index 41a167348bc..3bd4eebf7ba 100644 --- a/src/XrdCl/XrdClCtx.hh +++ b/src/XrdCl/XrdClCtx.hh @@ -27,6 +27,7 @@ #define SRC_XRDCL_XRDCLCTX_HH_ #include +#include namespace XrdCl { From d2a8ffc6cfe909276a235f9d042769627f124e57 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 2 Nov 2023 17:05:51 +0100 Subject: [PATCH 314/442] [XrdCl] Add XRootDStatus forward declaration --- src/XrdCl/XrdClFinalOperation.hh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/XrdCl/XrdClFinalOperation.hh b/src/XrdCl/XrdClFinalOperation.hh index 2204c04869b..19d6102ee19 100644 --- a/src/XrdCl/XrdClFinalOperation.hh +++ b/src/XrdCl/XrdClFinalOperation.hh @@ -30,6 +30,8 @@ namespace XrdCl { + class XRootDStatus; + //--------------------------------------------------------------------------- //! Final operation in the pipeline, always executed, no matter if the //! pipeline failed or not. From 52611c7a9c58ee59de1272645f11d30d315c4333 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 2 Nov 2023 17:06:42 +0100 Subject: [PATCH 315/442] [XrdCl] Install missing private header dependencies - XrdClJobManager.hh is needed by XrdClResponseJob.hh - XrdClSyncQueue.hh is needed by XrdClJobManager.hh - XrdClUtils.hh is needed by XrdEcUtilities.hh - XrdClXRootDTransport.hh is needed by XrdClUtils.hh --- src/XrdCl/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/XrdCl/CMakeLists.txt b/src/XrdCl/CMakeLists.txt index 22857187c72..adb25fa4f3f 100644 --- a/src/XrdCl/CMakeLists.txt +++ b/src/XrdCl/CMakeLists.txt @@ -206,11 +206,13 @@ install( install( FILES # Additional client headers + XrdClJobManager.hh XrdClMessage.hh XrdClPostMaster.hh XrdClPostMasterInterfaces.hh XrdClTransportManager.hh XrdClResponseJob.hh + XrdClSyncQueue.hh XrdClZipArchive.hh XrdClZipCache.hh # Declarative operations @@ -224,6 +226,8 @@ install( XrdClFileOperations.hh XrdClFileSystemOperations.hh XrdClFinalOperation.hh + XrdClUtils.hh + XrdClXRootDTransport.hh XrdClZipOperations.hh DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/xrootd/private/XrdCl ) From 6ed73d4e52ba3518e37e5b6d241621ec99745239 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 6 Nov 2023 11:12:31 +0100 Subject: [PATCH 316/442] [XrdCl] Make XrdClPlugInManager.hh header private Includes XrdOuc/XrdOucPinLoader.hh, which we do not wish to expose as a public header. --- src/XrdCl/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdCl/CMakeLists.txt b/src/XrdCl/CMakeLists.txt index adb25fa4f3f..a8fc5424271 100644 --- a/src/XrdCl/CMakeLists.txt +++ b/src/XrdCl/CMakeLists.txt @@ -198,7 +198,6 @@ install( XrdClXRootDResponses.hh XrdClOptional.hh XrdClPlugInInterface.hh - XrdClPlugInManager.hh XrdClPropertyList.hh XrdClLog.hh DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/xrootd/XrdCl ) @@ -208,6 +207,7 @@ install( # Additional client headers XrdClJobManager.hh XrdClMessage.hh + XrdClPlugInManager.hh XrdClPostMaster.hh XrdClPostMasterInterfaces.hh XrdClTransportManager.hh From 2a6279ef1fc6ed96a96db759deeda3a6d0c3a53f Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 2 Nov 2023 17:08:05 +0100 Subject: [PATCH 317/442] [XrdOuc] Include for strlen()/strncmp() --- src/XrdOuc/XrdOucPList.hh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdOuc/XrdOucPList.hh b/src/XrdOuc/XrdOucPList.hh index 24ff1104f0a..69082ced35c 100644 --- a/src/XrdOuc/XrdOucPList.hh +++ b/src/XrdOuc/XrdOucPList.hh @@ -31,7 +31,7 @@ /******************************************************************************/ #include -#include +#include #include class XrdOucPList From 3930a71ba626a480a58e479c52fbd7c71cf6894c Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 2 Nov 2023 17:08:41 +0100 Subject: [PATCH 318/442] [XrdOuc] Fix forward declaration of XrdSysLogger --- src/XrdOuc/XrdOucPinObject.hh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdOuc/XrdOucPinObject.hh b/src/XrdOuc/XrdOucPinObject.hh index e92eafc6728..1bc893718f5 100644 --- a/src/XrdOuc/XrdOucPinObject.hh +++ b/src/XrdOuc/XrdOucPinObject.hh @@ -36,7 +36,7 @@ */ class XrdOucEnv; -class XrdOucLogger; +class XrdSysLogger; template class XrdOucPinObject From d414b2951855d2dbcba952ec9f271d918865e391 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 2 Nov 2023 17:09:28 +0100 Subject: [PATCH 319/442] [XrdZip] Include for standard int types --- src/XrdZip/XrdZipDataDescriptor.hh | 2 ++ src/XrdZip/XrdZipExtra.hh | 3 +++ src/XrdZip/XrdZipUtils.hh | 6 +++--- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/XrdZip/XrdZipDataDescriptor.hh b/src/XrdZip/XrdZipDataDescriptor.hh index 22e76b5e1e0..9e48b2446fa 100644 --- a/src/XrdZip/XrdZipDataDescriptor.hh +++ b/src/XrdZip/XrdZipDataDescriptor.hh @@ -25,6 +25,8 @@ #ifndef SRC_XRDZIP_XRDZIPDATADESCRIPTOR_HH_ #define SRC_XRDZIP_XRDZIPDATADESCRIPTOR_HH_ +#include + namespace XrdZip { //--------------------------------------------------------------------------- diff --git a/src/XrdZip/XrdZipExtra.hh b/src/XrdZip/XrdZipExtra.hh index c415faa7ee7..041b50ec20d 100644 --- a/src/XrdZip/XrdZipExtra.hh +++ b/src/XrdZip/XrdZipExtra.hh @@ -27,6 +27,9 @@ #include "XrdZip/XrdZipUtils.hh" +#include +#include + namespace XrdZip { //--------------------------------------------------------------------------- diff --git a/src/XrdZip/XrdZipUtils.hh b/src/XrdZip/XrdZipUtils.hh index 1d48e543d08..619f16daf95 100644 --- a/src/XrdZip/XrdZipUtils.hh +++ b/src/XrdZip/XrdZipUtils.hh @@ -25,12 +25,12 @@ #ifndef SRC_XRDZIP_XRDZIPUTILS_HH_ #define SRC_XRDZIP_XRDZIPUTILS_HH_ +#include +#include #include - #include -#include -#include #include +#include namespace XrdZip { From b76fdb2fcbabebc8c7cb2c2c3c4d0a26ebdda801 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 2 Nov 2023 17:09:46 +0100 Subject: [PATCH 320/442] [XrdSfs] Include for standard int types --- src/XrdSfs/XrdSfsGPFile.hh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/XrdSfs/XrdSfsGPFile.hh b/src/XrdSfs/XrdSfsGPFile.hh index 6241d21a481..e38d267c9ec 100644 --- a/src/XrdSfs/XrdSfsGPFile.hh +++ b/src/XrdSfs/XrdSfsGPFile.hh @@ -29,6 +29,8 @@ /* specific prior written permission of the institution or contributor. */ /******************************************************************************/ +#include + class XrdSfsGPInfo; class XrdSfsGPFile From 42993eb5f3414c85deed04a302bc20d87299f252 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 2 Nov 2023 17:11:21 +0100 Subject: [PATCH 321/442] [XrdSec] Add XrdOucErrInfo and XrdSecEntity forward declarations --- src/XrdSec/XrdSecEntityPin.hh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/XrdSec/XrdSecEntityPin.hh b/src/XrdSec/XrdSecEntityPin.hh index d4585bb719a..7602b022038 100644 --- a/src/XrdSec/XrdSecEntityPin.hh +++ b/src/XrdSec/XrdSecEntityPin.hh @@ -39,6 +39,9 @@ the entity object, if a stacked plugin exists; unless you return false. */ +class XrdOucErrInfo; +class XrdSecEntity; + class XrdSecEntityPin { public: From 4c3e4240741c5dba9902904edf9f1574aea9235e Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 2 Nov 2023 17:11:48 +0100 Subject: [PATCH 322/442] [XrdPosix] Include for strlen() --- src/XrdPosix/XrdPosixXrootdPath.hh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/XrdPosix/XrdPosixXrootdPath.hh b/src/XrdPosix/XrdPosixXrootdPath.hh index 8a9c2fc0c20..7bab33b6e40 100644 --- a/src/XrdPosix/XrdPosixXrootdPath.hh +++ b/src/XrdPosix/XrdPosixXrootdPath.hh @@ -29,6 +29,8 @@ /* be used to endorse or promote products derived from this software without */ /* specific prior written permission of the institution or contributor. */ /******************************************************************************/ + +#include class XrdPosixXrootPath { From 648ca15e33d49add1549aa56fc7d1bb8496da088 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 2 Nov 2023 17:13:51 +0100 Subject: [PATCH 323/442] [XrdHeaders] Install missing header dependencies - XrdOucPinLoader.hh is needed by XrdClPlugInManager.hh - XrdZipDataDescriptor.hh is needed by XrdZipCDFH.hh - XrdCryptoFactory.hh is needed by XrdCryptosslAux.hh - XrdOucCRC32C.hh is needed by XrdEcUtilities.hh - XrdOucTUtils.hh is needed by XrdClUtils.hh --- src/XrdHeaders.cmake | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/XrdHeaders.cmake b/src/XrdHeaders.cmake index 5117772a2c2..8f7df3319eb 100644 --- a/src/XrdHeaders.cmake +++ b/src/XrdHeaders.cmake @@ -128,12 +128,16 @@ set( XROOTD_PRIVATE_HEADERS XrdNet/XrdNetIF.hh XrdSecsss/XrdSecsssID.hh XrdSys/XrdSysPriv.hh + XrdOuc/XrdOucCRC32C.hh XrdOuc/XrdOucExport.hh XrdOuc/XrdOucGatherConf.hh XrdOuc/XrdOucPList.hh XrdOuc/XrdOucN2NLoader.hh + XrdOuc/XrdOucPinLoader.hh + XrdOuc/XrdOucTUtils.hh XrdPosix/XrdPosixMap.hh XrdZip/XrdZipCDFH.hh + XrdZip/XrdZipDataDescriptor.hh XrdZip/XrdZipEOCD.hh XrdZip/XrdZipExtra.hh XrdZip/XrdZipLFH.hh @@ -172,6 +176,7 @@ if( NOT XRDCL_ONLY ) XrdCrypto/XrdCryptoX509.hh XrdCrypto/XrdCryptoX509Chain.hh XrdCrypto/XrdCryptoAux.hh + XrdCrypto/XrdCryptoFactory.hh XrdCrypto/XrdCryptosslAux.hh XrdCrypto/XrdCryptoX509Crl.hh XrdCrypto/XrdCryptoX509Req.hh From 097539b4dff918f82d35852bb82bcc2cc8663e37 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 3 Nov 2023 09:23:07 +0100 Subject: [PATCH 324/442] [XrdHeaders] Conditionally install XrdVoms.hh --- src/XrdHeaders.cmake | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/XrdHeaders.cmake b/src/XrdHeaders.cmake index 8f7df3319eb..6c99c2ef9cb 100644 --- a/src/XrdHeaders.cmake +++ b/src/XrdHeaders.cmake @@ -185,10 +185,15 @@ if( NOT XRDCL_ONLY ) XrdSut/XrdSutAux.hh XrdSut/XrdSutBucket.hh - XrdVoms/XrdVoms.hh - XrdOuc/XrdOucPgrwUtils.hh ) + + if ( BUILD_VOMS ) + set( XROOTD_PRIVATE_HEADERS + ${XROOTD_PRIVATE_HEADERS} + XrdVoms/XrdVoms.hh + ) + endif() endif() install_headers( From 162f9c2bfe072e078d8c28b850c6aa407b0349db Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 3 Nov 2023 10:40:58 +0100 Subject: [PATCH 325/442] [XrdCks] Include for time_t --- src/XrdCks/XrdCksAssist.hh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/XrdCks/XrdCksAssist.hh b/src/XrdCks/XrdCksAssist.hh index ccd9d21a9b8..e5baabc2f6c 100644 --- a/src/XrdCks/XrdCksAssist.hh +++ b/src/XrdCks/XrdCksAssist.hh @@ -33,6 +33,8 @@ #include #include +#include + //------------------------------------------------------------------------------ //! This header file defines linkages to various XRootD checksum assistants. //! The functions described here are located in libXrdUtils.so. From 07a76d4459580083c30c4c09734fe0e1c36e6135 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 3 Nov 2023 10:43:36 +0100 Subject: [PATCH 326/442] [XrdZip] Include for dev_t/mode_t with MUSL --- src/XrdZip/XrdZipCDFH.hh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/XrdZip/XrdZipCDFH.hh b/src/XrdZip/XrdZipCDFH.hh index c7f4606cfc4..96851ea3523 100644 --- a/src/XrdZip/XrdZipCDFH.hh +++ b/src/XrdZip/XrdZipCDFH.hh @@ -36,6 +36,8 @@ #include #include +#include + namespace XrdZip { //--------------------------------------------------------------------------- From 886eaf3c07d4629038555d62ab8486f018814500 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 3 Nov 2023 10:51:55 +0100 Subject: [PATCH 327/442] [XrdPosix] Include sys/types.h for dev_t/mode_t with MUSL --- src/XrdPosix/XrdPosixMap.hh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/XrdPosix/XrdPosixMap.hh b/src/XrdPosix/XrdPosixMap.hh index ac2da483a57..10028b39a1c 100644 --- a/src/XrdPosix/XrdPosixMap.hh +++ b/src/XrdPosix/XrdPosixMap.hh @@ -31,11 +31,12 @@ /* Modified by Frank Winklmeier to add the full Posix file system definition. */ /******************************************************************************/ -#include - #include "XrdCl/XrdClFileSystem.hh" #include "XrdCl/XrdClXRootDResponses.hh" +#include +#include + class XrdPosixMap { public: From aedcbb77a7664cae26953802434fde09520ab809 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 3 Nov 2023 13:26:27 +0100 Subject: [PATCH 328/442] [XrdEc] Do not install headers when using bundled isa-l XrdEc headers include , which is only available when isa-l is taken from the system as an external dependency. --- src/XrdEc/CMakeLists.txt | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/XrdEc/CMakeLists.txt b/src/XrdEc/CMakeLists.txt index 71c95e5adc7..ac56c33f7f6 100644 --- a/src/XrdEc/CMakeLists.txt +++ b/src/XrdEc/CMakeLists.txt @@ -40,15 +40,17 @@ install( #------------------------------------------------------------------------------ # Install private header files #------------------------------------------------------------------------------ -install( - FILES - XrdEcReader.hh - XrdEcObjCfg.hh - XrdEcStrmWriter.hh - XrdEcWrtBuff.hh - XrdEcThreadPool.hh - XrdEcUtilities.hh - XrdEcObjCfg.hh - XrdEcConfig.hh - XrdEcRedundancyProvider.hh - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/xrootd/private/XrdEc ) +if (USE_SYSTEM_ISAL) + install( + FILES + XrdEcReader.hh + XrdEcObjCfg.hh + XrdEcStrmWriter.hh + XrdEcWrtBuff.hh + XrdEcThreadPool.hh + XrdEcUtilities.hh + XrdEcObjCfg.hh + XrdEcConfig.hh + XrdEcRedundancyProvider.hh + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/xrootd/private/XrdEc ) +endif() From 9566f43f385fe505d0d5a980386729d710faa719 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 3 Nov 2023 13:59:38 +0100 Subject: [PATCH 329/442] [CI] Install only missing dependencies with brew --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index e8f635d7989..ab2f4b85ae5 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -195,7 +195,7 @@ jobs: run: sudo sed -i -e "s/localhost/localhost $(hostname)/g" /etc/hosts - name: Install dependencies with Homebrew - run: brew install cmake cppunit davix googletest isa-l openssl@3 python3 + run: brew install cppunit davix googletest isa-l - name: Install Python dependencies with pip run: python3 -m pip install --upgrade pip setuptools wheel From 9a77983c4cfd04454983b9acfd36d473a1bd17c7 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 10 Nov 2023 16:47:30 +0100 Subject: [PATCH 330/442] [CI] Allow a few retries of failed tests on macOS Some tests sporadically fail on macOS in GitHub Actions, but so far I cannot reproduce these test failures locally on a macOS machine. Allow a few retries in the CI to avoid having to re-run workflows manually. --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index ab2f4b85ae5..e75311a0327 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -206,7 +206,7 @@ jobs: fetch-depth: 0 - name: Build and Test with CTest - run: ctest -VV -S test.cmake + run: ctest -VV --repeat until-pass:3 -S test.cmake - name: Install with CMake run: cmake --install build From 89fb45b493aeb98ffa65ed0f9d8739dc7bd25f24 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 13 Nov 2023 09:22:42 +0100 Subject: [PATCH 331/442] [CMake] Do not run tests if build fails in test.cmake --- test.cmake | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test.cmake b/test.cmake index 8e5dfe63692..c2e6295a873 100644 --- a/test.cmake +++ b/test.cmake @@ -164,7 +164,11 @@ list(APPEND CTEST_NOTES_FILES ${CTEST_BINARY_DIRECTORY}/CMakeCache.txt) endsection() section("Build") -ctest_build() +ctest_build(RETURN_VALUE BUILD_RESULT) + +if(NOT ${BUILD_RESULT} EQUAL 0) + message(FATAL_ERROR "Build failed") +endif() if(INSTALL) set(ENV{DESTDIR} "${CTEST_BINARY_DIRECTORY}/install") From 06470b01e24601186db374a7e79f9c1115831d64 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 16 Nov 2023 10:58:19 +0100 Subject: [PATCH 332/442] [Xrd] Initialize pidFN to pidpath base directory if an error occurs When all.pidpath is set to a non-existent directory and it cannot be created for some reason (permission denied as shown below), before this change the variable pidFN would be uninitialized and the error message would contain garbage instead of the name of the directory that could not be created. ... ++++++ xrootd anon@gentoo.cern.ch initialization started. Config using configuration file /tmp/xrootd.cfg =====> all.adminpath /var/spool/xrootd =====> all.pidpath /run/xrootd =====> xrd.port any 231116 10:57:41 8927 XrdSetIF: Skipping duplicate private interface [::172.17.0.1] 231116 10:57:41 8927 XrdConfig: Unable to create /run/xrootd; permission denied ... --- src/Xrd/XrdConfig.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Xrd/XrdConfig.cc b/src/Xrd/XrdConfig.cc index 7f15a38b668..73c91d45b41 100644 --- a/src/Xrd/XrdConfig.cc +++ b/src/Xrd/XrdConfig.cc @@ -1102,7 +1102,7 @@ bool XrdConfig::PidFile(const char *clpFN, bool optbg) // Create the path if it does not exist and write out the pid // if ((rc = XrdOucUtils::makePath(ppath,XrdOucUtils::pathMode))) - {xop = "create"; errno = rc;} + {xop = "create"; snprintf(pidFN, sizeof(pidFN), "%s", ppath); errno = rc;} else {snprintf(pidFN, sizeof(pidFN), "%s/%s.pid", ppath, myProg); if ((xfd = open(pidFN, O_WRONLY|O_CREAT|O_TRUNC,0644)) < 0) From 548ea40918ebba592948e0d54a9db6a16c3ea79e Mon Sep 17 00:00:00 2001 From: Mattias Ellert Date: Sun, 19 Nov 2023 09:55:54 +0100 Subject: [PATCH 333/442] Fix maybe-uninitialized warning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit .../src/XrdEc/XrdEcReader.cc:934:72: error: ‘info’ may be used uninitialized [-Werror=maybe-uninitialized] 934 | blockMap[blkid]->stripes[strpid].resize( info ->GetSize() ); | ^ .../src/XrdEc/XrdEcReader.cc:932:42: note: ‘info’ was declared here 932 | XrdCl::StatInfo* info; | ^ --- src/XrdEc/XrdEcReader.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdEc/XrdEcReader.cc b/src/XrdEc/XrdEcReader.cc index 62dcb046194..9ac55b5387e 100644 --- a/src/XrdEc/XrdEcReader.cc +++ b/src/XrdEc/XrdEcReader.cc @@ -929,7 +929,7 @@ namespace XrdEc } blockMap[blkid]->state[strpid] = block_t::Loading; - XrdCl::StatInfo* info; + XrdCl::StatInfo* info = nullptr; if(dataarchs[url]->Stat(objcfg.GetFileName(blkid, strpid), info).IsOK()) blockMap[blkid]->stripes[strpid].resize( info ->GetSize() ); From fdac0a73644fe07e79bf8ff73d6bb274cdebd182 Mon Sep 17 00:00:00 2001 From: Mattias Ellert Date: Wed, 22 Nov 2023 06:20:19 +0100 Subject: [PATCH 334/442] Avoid /tmp when running some tests Many features in xrootd require file system support for extended file attributes. Tests that run tests on these features fail if the file system does not support them. The /tmp directory in many Linux installations is using a tmpfs partition. The tmpfs file system does not support extended file attributes, so some tests that use /tmp to store files fail. This commit changes some affected tests so that they create the temporary directory containing the test files in the current working directory instead of /tmp. Example of failure: 17/34 Test #20: XrdEc::AlignedWriteTest ...................................................***Failed 0.07 sec You have selected: Selected tests/ Selected tests/MicroTest::AlignedWriteTest Running: .F MicroTest.cc:506:Assertion Test name: MicroTest::AlignedWriteTest assertion failed - Expression: _st.IsOK() - [*status]: [ERROR] Internal error: std::bad_alloc Failures !!! Run: 1 Failure total: 1 Failures: 1 Errors: 0 The following tests FAILED: 20 - XrdEc::AlignedWriteTest (Failed) 21 - XrdEc::SmallWriteTest (Failed) 22 - XrdEc::BigWriteTest (Failed) 23 - XrdEc::VectorReadTest (Failed) 24 - XrdEc::IllegalVectorReadTest (Failed) 25 - XrdEc::AlignedWrite1MissingTest (Failed) 26 - XrdEc::AlignedWrite2MissingTest (Failed) 27 - XrdEc::AlignedWriteTestIsalCrcNoMt (Failed) 28 - XrdEc::SmallWriteTestIsalCrcNoMt (Failed) 29 - XrdEc::BigWriteTestIsalCrcNoMt (Failed) 30 - XrdEc::AlignedWrite1MissingTestIsalCrcNoMt (Failed) 31 - XrdEc::AlignedWrite2MissingTestIsalCrcNoMt (Failed) In addition to addressing the above failures, the commit also addresses the following warnings during the XRootD::smoke-test test: 34: setfattr: /tmp/xrdfs-test-ChGSEb/01.ref: Operation not supported 34: Extended attributes not supported, using downloaded checksums for server checks 34: setfattr: /tmp/xrdfs-test-ChGSEb/02.ref: Operation not supported 34: Extended attributes not supported, using downloaded checksums for server checks 34: setfattr: /tmp/xrdfs-test-ChGSEb/03.ref: Operation not supported 34: Extended attributes not supported, using downloaded checksums for server checks --- tests/XRootD/smoke.sh | 2 +- tests/XrdEc/MicroTest.cc | 6 +++++- tests/XrdEcTests/MicroTest.cc | 6 +++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/tests/XRootD/smoke.sh b/tests/XRootD/smoke.sh index 58e0f93f0f7..76ae67fd9ab 100755 --- a/tests/XRootD/smoke.sh +++ b/tests/XRootD/smoke.sh @@ -39,7 +39,7 @@ ${XRDFS} ${HOST} statvfs / ${XRDFS} ${HOST} spaceinfo / # create local temporary directory -TMPDIR=$(mktemp -d /tmp/xrdfs-test-XXXXXX) +TMPDIR=$(mktemp -d ${PWD}/xrdfs-test-XXXXXX) # cleanup after ourselves if something fails trap "rm -rf ${TMPDIR}" EXIT diff --git a/tests/XrdEc/MicroTest.cc b/tests/XrdEc/MicroTest.cc index 61727e8c0fc..61e26916327 100644 --- a/tests/XrdEc/MicroTest.cc +++ b/tests/XrdEc/MicroTest.cc @@ -32,6 +32,8 @@ #include "XrdCl/XrdClMessageUtils.hh" +#include "XrdSys/XrdSysPlatform.hh" + #include "XrdZip/XrdZipCDFH.hh" #include @@ -323,7 +325,9 @@ void XrdEcTests::Init( bool usecrc32c ) objcfg.reset( new ObjCfg( "test.txt", nbdata, nbparity, chsize, usecrc32c, true ) ); rawdata.clear(); - char tmpdir[32] = "/tmp/xrootd-xrdec-XXXXXX"; + char tmpdir[MAXPATHLEN]; + EXPECT_TRUE( getcwd(tmpdir, MAXPATHLEN - 21) ); + strcat(tmpdir, "/xrootd-xrdec-XXXXXX"); // create the data directory EXPECT_TRUE( mkdtemp(tmpdir) ); datadir = tmpdir; diff --git a/tests/XrdEcTests/MicroTest.cc b/tests/XrdEcTests/MicroTest.cc index d02743351b9..5b375c6fe4f 100644 --- a/tests/XrdEcTests/MicroTest.cc +++ b/tests/XrdEcTests/MicroTest.cc @@ -34,6 +34,8 @@ #include "XrdZip/XrdZipCDFH.hh" +#include "XrdSys/XrdSysPlatform.hh" + #include #include #include @@ -280,7 +282,9 @@ void MicroTest::Init( bool usecrc32c ) objcfg.reset( new ObjCfg( "test.txt", nbdata, nbparity, chsize, usecrc32c, true ) ); rawdata.clear(); - char tmpdir[32] = "/tmp/xrootd-xrdec-XXXXXX"; + char tmpdir[MAXPATHLEN]; + CPPUNIT_ASSERT( getcwd(tmpdir, MAXPATHLEN - 21) ); + strcat(tmpdir, "/xrootd-xrdec-XXXXXX"); // create the data directory CPPUNIT_ASSERT( mkdtemp(tmpdir) ); datadir = tmpdir; From 61f280ca91860f8f936057ab7e47dce96c228f81 Mon Sep 17 00:00:00 2001 From: David Smith Date: Mon, 20 Nov 2023 15:27:57 +0100 Subject: [PATCH 335/442] [SciTokens] Initialize SecEntity.addrInfo to avoid SEGV --- src/XrdSciTokens/XrdSciTokensAccess.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/XrdSciTokens/XrdSciTokensAccess.cc b/src/XrdSciTokens/XrdSciTokensAccess.cc index 6ab82c39bf1..8d0f238c8bb 100644 --- a/src/XrdSciTokens/XrdSciTokensAccess.cc +++ b/src/XrdSciTokens/XrdSciTokensAccess.cc @@ -532,6 +532,7 @@ class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper, new_secentity.grps = nullptr; new_secentity.role = nullptr; new_secentity.secMon = Entity->secMon; + new_secentity.addrInfo = Entity->addrInfo; const auto &issuer = access_rules->get_issuer(); if (!issuer.empty()) { new_secentity.vorg = strdup(issuer.c_str()); From fde7669ff9006495034a493386c72d080158f027 Mon Sep 17 00:00:00 2001 From: Mattias Ellert Date: Wed, 22 Nov 2023 19:29:12 +0100 Subject: [PATCH 336/442] If extended attributes are not supported xattr->Get() returns -ENOTSUP. When this small negative value is cast to a size_t in the call to new on the following line it becomes a very large integer and the request to allocate this enormous memory block fails with a std::bad_alloc exception. This commit adds a check on the returned size and returns an error if it is negative avoiding triggering the std::bad_alloc exception. --- src/XrdCl/XrdClLocalFileHandler.cc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/XrdCl/XrdClLocalFileHandler.cc b/src/XrdCl/XrdClLocalFileHandler.cc index e25d8f2a2ba..42cce9a30c2 100644 --- a/src/XrdCl/XrdClLocalFileHandler.cc +++ b/src/XrdCl/XrdClLocalFileHandler.cc @@ -664,6 +664,12 @@ namespace XrdCl std::unique_ptr buffer; int size = xattr->Get( name.c_str(), 0, 0, 0, fd ); + if( size < 0 ) + { + XRootDStatus status( stError, errLocalError, -size ); + response.push_back( XAttr( *itr, "", status ) ); + continue; + } buffer.reset( new char[size] ); int ret = xattr->Get( name.c_str(), buffer.get(), size, 0, fd ); From b021f3c3405c1e2b95f1685b5708a86d089445db Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 22 Nov 2023 13:56:10 +0100 Subject: [PATCH 337/442] [XrdSecgsi] Fix crashes of xrdgsitest when CA is not properly setup --- src/XrdSecgsi/XrdSecgsitest.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/XrdSecgsi/XrdSecgsitest.cc b/src/XrdSecgsi/XrdSecgsitest.cc index 4e10dd33635..b0be6da0471 100644 --- a/src/XrdSecgsi/XrdSecgsitest.cc +++ b/src/XrdSecgsi/XrdSecgsitest.cc @@ -280,6 +280,8 @@ int main( int argc, char **argv ) pdots("Loading CA certificate", 1); } else { pdots("Loading CA certificate", 0); + rCAfound = 0; + break; } // Check if self-signed if (!strcmp(xCA[nCA]->IssuerHash(), xCA[nCA]->SubjectHash())) { @@ -329,7 +331,7 @@ int main( int argc, char **argv ) pline("Testing ExportChain"); XrdCryptoX509ExportChain_t ExportChain = gCryptoFactory->X509ExportChain(); XrdSutBucket *chainbck = 0; - if (ExportChain) { + if (ExportChain && chain->End()) { chainbck = (*ExportChain)(chain, 0); pdots("Attach to X509ExportChain", 1); } else { From d9ef244293634e294797135d826434e39b3d97ba Mon Sep 17 00:00:00 2001 From: Brian Bockelman Date: Sun, 19 Nov 2023 21:50:29 -0600 Subject: [PATCH 338/442] Switch from using a cert file to a cert chain file Outside most grid contexts, we need to present the certificate chain in the TLS handshake for the verification path through the intermediate CA to the root. This commit switches over to the corresponding OpenSSL function call. Fixes the ability to use most CAB CAs with XRootD. --- src/XrdTls/XrdTlsContext.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdTls/XrdTlsContext.cc b/src/XrdTls/XrdTlsContext.cc index 6f7227a92ca..d48a2b598c3 100644 --- a/src/XrdTls/XrdTlsContext.cc +++ b/src/XrdTls/XrdTlsContext.cc @@ -745,7 +745,7 @@ XrdTlsContext::XrdTlsContext(const char *cert, const char *key, // Load certificate // - if (SSL_CTX_use_certificate_file(pImpl->ctx, cert, SSL_FILETYPE_PEM) != 1) + if (SSL_CTX_use_certificate_chain_file(pImpl->ctx, cert) != 1) FATAL_SSL("Unable to create TLS context; invalid certificate."); // Load the private key From 6ac1e7c09a634cb19ebf7dac46d59d65704608e5 Mon Sep 17 00:00:00 2001 From: Mattias Ellert Date: Mon, 27 Nov 2023 10:35:42 +0100 Subject: [PATCH 339/442] Reapply the fix to tests/XrdClTests/LocalFileHandlerTest.cc also to tests/XrdCl/XrdClLocalFileHandlerTest.cc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Original commit message: OpenFlags::Flags should not be used for the flags argument in a posix open() call, since they use different values. The OpenFlags::Flags values are defined in src/XrdCl/XrdClFileSystem.hh: MakePath = kXR_mkpath, //!< Create directory path if it does not //!< already exist New = kXR_new, //!< Open the file only if it does not already Update = kXR_open_updt, //!< Open for reading and writing The kXR_* values are defined in src/XProtocol/XProtocol.hh: kXR_new = 0x0008, // 8 kXR_mkpath = 0x0100, // 256 kXR_open_updt= 0x0020, // 32 As can be seen these do not correspond to the O_* values used in the posix open() call. What they actually mean depends on the system and varies between architectures. On hppa Linux O_CREAT is defind in /usr/include/hppa-linux-gnu/asm/fcntl.h: #define O_CREAT 000000400 /* not fcntl */ Since 0o400 = 0x100 = 256 this by chance matches kXR_mkpath and triggers the error below. On other architectures the wrong flags are just silently accepted. In file included from /usr/include/fcntl.h:342, from /<>/tests/XrdClTests/LocalFileHandlerTest.cc:25: In function ‘int open(const char*, int, ...)’, inlined from ‘void LocalFileHandlerTest::WriteMkdirTest()’ at /<>/tests/XrdClTests/LocalFileHandlerTest.cc:210:17: /usr/include/hppa-linux-gnu/bits/fcntl2.h:50:31: error: call to ‘__open_missing_mode’ declared with attribute error: open with O_CREAT or O_TMPFILE in second argument needs 3 arguments 50 | __open_missing_mode (); | ~~~~~~~~~~~~~~~~~~~~^~ --- tests/XrdCl/XrdClLocalFileHandlerTest.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/XrdCl/XrdClLocalFileHandlerTest.cc b/tests/XrdCl/XrdClLocalFileHandlerTest.cc index 80019fc5d1f..cedb3e28fd4 100644 --- a/tests/XrdCl/XrdClLocalFileHandlerTest.cc +++ b/tests/XrdCl/XrdClLocalFileHandlerTest.cc @@ -168,7 +168,7 @@ TEST_F(LocalFileHandlerTest, WriteTest){ //---------------------------------------------------------------------------- // Read file with POSIX calls to confirm correct write //---------------------------------------------------------------------------- - int fd = open( targetURL.c_str(), flags ); + int fd = open( targetURL.c_str(), O_RDWR ); int rc = read( fd, buffer, int( writeSize ) ); EXPECT_EQ( rc, int( writeSize ) ); std::string read( (char *)buffer, writeSize ); @@ -202,7 +202,7 @@ TEST_F(LocalFileHandlerTest, WriteWithOffsetTest){ //---------------------------------------------------------------------------- // Read file with POSIX calls to confirm correct write //---------------------------------------------------------------------------- - int fd = open( targetURL.c_str(), flags ); + int fd = open( targetURL.c_str(), O_RDWR ); int rc = read( fd, buffer, offset ); EXPECT_EQ( rc, int( offset ) ); std::string read( (char *)buffer, offset ); @@ -233,7 +233,7 @@ TEST_F(LocalFileHandlerTest, WriteMkdirTest){ //---------------------------------------------------------------------------- // Read file with POSIX calls to confirm correct write //---------------------------------------------------------------------------- - int fd = open( targetURL.c_str(), flags ); + int fd = open( targetURL.c_str(), O_RDWR ); int rc = read( fd, buffer, writeSize ); EXPECT_EQ( rc, int( writeSize ) ); std::string read( buffer, writeSize ); From 6cf0871f1f7f1883e6a56d41e6902e707383de7d Mon Sep 17 00:00:00 2001 From: Mattias Ellert Date: Mon, 27 Nov 2023 10:54:27 +0100 Subject: [PATCH 340/442] PATH_MAX / MAXPATHLEN might be undefined XrdSys/XrdSysPlatform.hh defines MAXPATHLEN if needed. --- tests/XrdCl/XrdClLocalFileHandlerTest.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/XrdCl/XrdClLocalFileHandlerTest.cc b/tests/XrdCl/XrdClLocalFileHandlerTest.cc index cedb3e28fd4..84de7d8bcd7 100644 --- a/tests/XrdCl/XrdClLocalFileHandlerTest.cc +++ b/tests/XrdCl/XrdClLocalFileHandlerTest.cc @@ -18,6 +18,7 @@ #include "TestEnv.hh" #include "GTestXrdHelpers.hh" #include "XrdCl/XrdClFile.hh" +#include "XrdSys/XrdSysPlatform.hh" #include #include @@ -449,7 +450,7 @@ TEST_F(LocalFileHandlerTest, XAttrTest) std::string localDataPath; EXPECT_TRUE( testEnv->GetString( "LocalDataPath", localDataPath ) ); - char resolved_path[PATH_MAX]; + char resolved_path[MAXPATHLEN]; localDataPath = realpath(localDataPath.c_str(), resolved_path); std::string targetURL = localDataPath + "/metaman/lfilehandlertestfilexattr"; From 279909253e9ed52c2b070d7c1a3dae2b41ea72b4 Mon Sep 17 00:00:00 2001 From: Mattias Ellert Date: Tue, 28 Nov 2023 11:16:33 +0100 Subject: [PATCH 341/442] Get more useful diagnostics in case of test failures EXPECT_EQ( A, B ) provides more useful diagnostics than EXPECT_TRUE ( A == B ). EXPECT_NE( A, B ) provides more useful diagnostics than EXPECT_TRUE ( A != B ). --- tests/XrdCephTests/CephParsingTest.cc | 12 ++-- tests/XrdCl/XrdClFileCopyTest.cc | 30 ++++----- tests/XrdCl/XrdClFileSystemTest.cc | 26 ++++---- tests/XrdCl/XrdClFileTest.cc | 72 +++++++++++----------- tests/XrdCl/XrdClLocalFileHandlerTest.cc | 68 ++++++++++---------- tests/XrdCl/XrdClOperationsWorkflowTest.cc | 39 ++++++------ tests/XrdCl/XrdClPoller.cc | 4 +- tests/XrdCl/XrdClPostMasterTest.cc | 46 +++++++------- tests/XrdCl/XrdClSocket.cc | 29 +++++---- tests/XrdCl/XrdClThreadingTest.cc | 11 ++-- tests/XrdCl/XrdClUtilsTest.cc | 32 +++++----- tests/XrdCl/XrdClZip.cc | 4 +- tests/XrdEc/MicroTest.cc | 56 +++++++---------- 13 files changed, 211 insertions(+), 218 deletions(-) diff --git a/tests/XrdCephTests/CephParsingTest.cc b/tests/XrdCephTests/CephParsingTest.cc index 194bd5e40c8..06cb3366097 100644 --- a/tests/XrdCephTests/CephParsingTest.cc +++ b/tests/XrdCephTests/CephParsingTest.cc @@ -69,12 +69,12 @@ void checkResult(CephFile a, CephFile b) { << " / " << b.name << " " << b.pool << " " << b.userId << " " << b.nbStripes << " " << b.stripeUnit << " " << b.objectSize << std::endl; - CPPUNIT_ASSERT(a.name == b.name); - CPPUNIT_ASSERT(a.pool == b.pool); - CPPUNIT_ASSERT(a.userId == b.userId); - CPPUNIT_ASSERT(a.nbStripes == b.nbStripes); - CPPUNIT_ASSERT(a.stripeUnit == b.stripeUnit); - CPPUNIT_ASSERT(a.objectSize == b.objectSize); + CPPUNIT_ASSERT_EQUAL(a.name, b.name); + CPPUNIT_ASSERT_EQUAL(a.pool, b.pool); + CPPUNIT_ASSERT_EQUAL(a.userId, b.userId); + CPPUNIT_ASSERT_EQUAL(a.nbStripes, b.nbStripes); + CPPUNIT_ASSERT_EQUAL(a.stripeUnit, b.stripeUnit); + CPPUNIT_ASSERT_EQUAL(a.objectSize, b.objectSize); } //------------------------------------------------------------------------------ diff --git a/tests/XrdCl/XrdClFileCopyTest.cc b/tests/XrdCl/XrdClFileCopyTest.cc index 44a842ce3b3..d991b358e15 100644 --- a/tests/XrdCl/XrdClFileCopyTest.cc +++ b/tests/XrdCl/XrdClFileCopyTest.cc @@ -76,7 +76,7 @@ void FileCopyTest::DownloadTestFunc() const uint32_t MB = 1024*1024; char *buffer = new char[4*MB]; - StatInfo *stat = 0; + StatInfo *stat = nullptr; File f; //---------------------------------------------------------------------------- @@ -120,7 +120,7 @@ void FileCopyTest::DownloadTestFunc() EXPECT_TRUE( f.GetProperty( "LastURL", lastUrl ) ); GTEST_ASSERT_XRDST( Utils::GetRemoteCheckSum( remoteSum, "zcrc32", URL( lastUrl ) ) ); - EXPECT_TRUE( remoteSum == transferSum ); + EXPECT_EQ( remoteSum, transferSum ); delete stat; delete crc32Sum; @@ -185,7 +185,7 @@ void FileCopyTest::UploadTestFunc() offset += bytesRead; } - EXPECT_TRUE( bytesRead >= 0 ); + EXPECT_GE( bytesRead, 0 ); close( fd ); GTEST_ASSERT_XRDST( f.Close() ); delete [] buffer; @@ -194,20 +194,20 @@ void FileCopyTest::UploadTestFunc() // Find out which server has the file //---------------------------------------------------------------------------- FileSystem fs( url ); - LocationInfo *locations = 0; + LocationInfo *locations = nullptr; GTEST_ASSERT_XRDST( fs.DeepLocate( remoteFile, OpenFlags::Refresh, locations ) ); EXPECT_TRUE( locations ); - EXPECT_TRUE( locations->GetSize() != 0 ); + EXPECT_NE( locations->GetSize(), 0 ); FileSystem fs1( locations->Begin()->GetAddress() ); delete locations; //---------------------------------------------------------------------------- // Verify the size //---------------------------------------------------------------------------- - StatInfo *stat = 0; + StatInfo *stat = nullptr; GTEST_ASSERT_XRDST( fs1.Stat( remoteFile, stat ) ); EXPECT_TRUE( stat ); - EXPECT_TRUE( stat->GetSize() == offset ); + EXPECT_EQ( stat->GetSize(), offset ); //---------------------------------------------------------------------------- // Compare the checksums @@ -220,7 +220,7 @@ void FileCopyTest::UploadTestFunc() f.GetProperty( "LastURL", lastUrl ); GTEST_ASSERT_XRDST( Utils::GetRemoteCheckSum( remoteSum, "zcrc32", lastUrl ) ); - EXPECT_TRUE( remoteSum == transferSum ); + EXPECT_EQ( remoteSum, transferSum ); //---------------------------------------------------------------------------- // Delete the file @@ -400,9 +400,8 @@ void FileCopyTest::CopyTestFunc( bool thirdParty ) GTEST_ASSERT_XRDST( process12.AddJob( properties, &results ) ); GTEST_ASSERT_XRDST( process12.Prepare() ); GTEST_ASSERT_XRDST_NOTOK( process12.Run(0), XrdCl::errCheckSumError ); - XrdCl::StatInfo *info = 0; - XrdCl::XRootDStatus status = fs.Stat( targetPath, info ); - GTEST_ASSERT_XRDST( status.status == XrdCl::stError && status.code == XrdCl::errNotFound ); + XrdCl::StatInfo *info = nullptr; + GTEST_ASSERT_XRDST_NOTOK( fs.Stat( targetPath, info ), XrdCl::errErrorResponse ); properties.Clear(); //-------------------------------------------------------------------------- @@ -539,7 +538,7 @@ void FileCopyTest::CopyTestFunc( bool thirdParty ) std::vector attrs; attrs.push_back( xattr_t( "foo", "bar" ) ); std::vector result; GTEST_ASSERT_XRDST( lf.SetXAttr( attrs, result ) ); - EXPECT_TRUE( result.size() == 1 ); + EXPECT_EQ( result.size(), 1 ); GTEST_ASSERT_XRDST( result.front().status ); GTEST_ASSERT_XRDST( lf.Close() ); @@ -557,16 +556,17 @@ void FileCopyTest::CopyTestFunc( bool thirdParty ) // now test if the xattrs were preserved std::vector xattrs; GTEST_ASSERT_XRDST( fs.ListXAttr( targetPath, xattrs ) ); - EXPECT_TRUE( xattrs.size() == 1 ); + EXPECT_EQ( xattrs.size(), 1 ); XAttr &xattr = xattrs.front(); GTEST_ASSERT_XRDST( xattr.status ); - EXPECT_TRUE( xattr.name == "foo" && xattr.value == "bar" ); + EXPECT_EQ( xattr.name, "foo" ); + EXPECT_EQ( xattr.value, "bar" ); //---------------------------------------------------------------------------- // Cleanup //---------------------------------------------------------------------------- GTEST_ASSERT_XRDST( fs.Rm( targetPath ) ); - EXPECT_TRUE( remove( localFile.c_str() ) == 0 ); + EXPECT_EQ( remove( localFile.c_str() ), 0 ); //---------------------------------------------------------------------------- // Initialize and run the copy diff --git a/tests/XrdCl/XrdClFileSystemTest.cc b/tests/XrdCl/XrdClFileSystemTest.cc index d1ce103f096..51a3082d4f7 100644 --- a/tests/XrdCl/XrdClFileSystemTest.cc +++ b/tests/XrdCl/XrdClFileSystemTest.cc @@ -182,7 +182,7 @@ void FileSystemTest::LocateTest() LocationInfo *locations = 0; GTEST_ASSERT_XRDST( fs.Locate( remoteFile, OpenFlags::Refresh, locations ) ); EXPECT_TRUE( locations ); - EXPECT_TRUE( locations->GetSize() != 0 ); + EXPECT_NE( locations->GetSize(), 0 ); delete locations; } @@ -260,7 +260,7 @@ void FileSystemTest::ServerQueryTest() arg.FromString( remoteFile ); GTEST_ASSERT_XRDST( fs.Query( QueryCode::Checksum, arg, response ) ); EXPECT_TRUE( response ); - EXPECT_TRUE( response->GetSize() != 0 ); + EXPECT_NE( response->GetSize(), 0 ); delete response; } @@ -404,7 +404,7 @@ void FileSystemTest::StatTest() struct stat localStatBuf; int rc = stat(localFilePath.c_str(), &localStatBuf); - EXPECT_TRUE( rc == 0 ); + EXPECT_EQ( rc, 0 ); uint64_t fileSize = localStatBuf.st_size; URL url( address ); @@ -414,7 +414,7 @@ void FileSystemTest::StatTest() StatInfo *response = 0; GTEST_ASSERT_XRDST( fs.Stat( remoteFile, response ) ); EXPECT_TRUE( response ); - EXPECT_TRUE( response->GetSize() == fileSize ); + EXPECT_EQ( response->GetSize(), fileSize ); EXPECT_TRUE( response->TestFlags( StatInfo::IsReadable ) ); EXPECT_TRUE( response->TestFlags( StatInfo::IsWritable ) ); EXPECT_TRUE( !response->TestFlags( StatInfo::IsDir ) ); @@ -496,7 +496,7 @@ void FileSystemTest::DeepLocateTest() LocationInfo *locations = 0; GTEST_ASSERT_XRDST( fs.DeepLocate( remoteFile, OpenFlags::Refresh, locations ) ); EXPECT_TRUE( locations ); - EXPECT_TRUE( locations->GetSize() != 0 ); + EXPECT_NE( locations->GetSize(), 0 ); LocationInfo::Iterator it = locations->Begin(); for( ; it != locations->End(); ++it ) EXPECT_TRUE( it->IsServer() ); @@ -534,7 +534,7 @@ void FileSystemTest::DirListTest() DirectoryList *list = 0; GTEST_ASSERT_XRDST( fs.DirList( lsPath, DirListFlags::Stat | DirListFlags::Locate, list ) ); EXPECT_TRUE( list ); - EXPECT_TRUE( list->GetSize() == 4000 ); + EXPECT_EQ( list->GetSize(), 4000 ); std::set dirls1; for( auto itr = list->Begin(); itr != list->End(); ++itr ) @@ -575,7 +575,7 @@ void FileSystemTest::DirListTest() delete info; info = 0; - EXPECT_TRUE( dirls1 == dirls2 ); + EXPECT_EQ( dirls1, dirls2 ); //---------------------------------------------------------------------------- // Now list an empty directory @@ -587,7 +587,7 @@ void FileSystemTest::DirListTest() FileSystem fs3( info->Begin()->GetAddress() ); GTEST_ASSERT_XRDST( fs3.DirList( emptyDirPath, DirListFlags::Stat, list ) ); EXPECT_TRUE( list ); - EXPECT_TRUE( list->GetSize() == 0 ); + EXPECT_EQ( list->GetSize(), 0 ); GTEST_ASSERT_XRDST( fs.RmDir( emptyDirPath ) ); delete list; @@ -621,7 +621,7 @@ void FileSystemTest::SendInfoTest() Buffer *id = 0; GTEST_ASSERT_XRDST( fs.SendInfo( "test stuff", id ) ); EXPECT_TRUE( id ); - EXPECT_TRUE( id->GetSize() == 4 ); + EXPECT_EQ( id->GetSize(), 4 ); delete id; } @@ -723,8 +723,8 @@ void FileSystemTest::XAttrTest() { GTEST_ASSERT_XRDST( itr3->status ); auto match = attributes.find( itr3->name ); - EXPECT_TRUE( match != attributes.end() ); - EXPECT_TRUE( match->second == itr3->value ); + EXPECT_NE( match, attributes.end() ); + EXPECT_EQ( match->second, itr3->value ); } result2.clear(); @@ -738,8 +738,8 @@ void FileSystemTest::XAttrTest() { GTEST_ASSERT_XRDST( itr3->status ); auto match = attributes.find( itr3->name ); - EXPECT_TRUE( match != attributes.end() ); - EXPECT_TRUE( match->second == itr3->value ); + EXPECT_NE( match, attributes.end() ); + EXPECT_EQ( match->second, itr3->value ); } result2.clear(); diff --git a/tests/XrdCl/XrdClFileTest.cc b/tests/XrdCl/XrdClFileTest.cc index 8cc8914a135..8c7f9bcbfed 100644 --- a/tests/XrdCl/XrdClFileTest.cc +++ b/tests/XrdCl/XrdClFileTest.cc @@ -222,9 +222,9 @@ void FileTest::ReadTest() std::string localFilePath = localDataPath + "/srv1" + filePath; int rc = stat(localFilePath.c_str(), &localStatBuf); - EXPECT_TRUE( rc == 0 ); + EXPECT_EQ( rc, 0 ); uint64_t fileSize = localStatBuf.st_size; - EXPECT_TRUE( statInfo->GetSize() == fileSize ); + EXPECT_EQ( statInfo->GetSize(), fileSize ); EXPECT_TRUE( statInfo->TestFlags( StatInfo::IsReadable ) ); delete statInfo; @@ -235,7 +235,7 @@ void FileTest::ReadTest() //---------------------------------------------------------------------------- GTEST_ASSERT_XRDST( f.Stat( true, statInfo ) ); EXPECT_TRUE( statInfo ); - EXPECT_TRUE( statInfo->GetSize() == fileSize ); + EXPECT_EQ( statInfo->GetSize(), fileSize ); EXPECT_TRUE( statInfo->TestFlags( StatInfo::IsReadable ) ); delete statInfo; @@ -244,8 +244,8 @@ void FileTest::ReadTest() //---------------------------------------------------------------------------- GTEST_ASSERT_XRDST( f.Read( 5*MB, 5*MB, buffer1, bytesRead1 ) ); GTEST_ASSERT_XRDST( f.Read( fileSize - 3*MB, 4*MB, buffer2, bytesRead2 ) ); - EXPECT_TRUE( bytesRead1 == 5*MB ); - EXPECT_TRUE( bytesRead2 == 3*MB ); + EXPECT_EQ( bytesRead1, 5*MB ); + EXPECT_EQ( bytesRead2, 3*MB ); uint32_t crc = XrdClTests::Utils::ComputeCRC32( buffer1, 5*MB ); @@ -257,13 +257,13 @@ void FileTest::ReadTest() // compute and compare local file buffer crc uint32_t crcComp = XrdClTests::Utils::ComputeCRC32( buffer1Comp, 5*MB ); - EXPECT_TRUE( crc == crcComp ); + EXPECT_EQ( crc, crcComp ); // do the same for the second buffer crc = XrdClTests::Utils::ComputeCRC32( buffer2, 3*MB ); crcComp = XrdClTests::Utils::ComputeCRC32( buffer2Comp, 3*MB ); - EXPECT_TRUE( crc == crcComp ); + EXPECT_EQ( crc, crcComp ); // cleanup after ourselves delete [] buffer1; @@ -314,7 +314,7 @@ void FileTest::ReadTest() result.assign( static_cast(c.buffer), c.length ); } ) ); - EXPECT_TRUE( testset[i].expected == result ); + EXPECT_EQ( testset[i].expected, result ); } GTEST_ASSERT_XRDST( WaitFor( CloseArchive( zip ) ) ); @@ -358,8 +358,8 @@ void FileTest::WriteTest() uint32_t bytesRead2 = 0; File f1, f2; - EXPECT_TRUE( XrdClTests::Utils::GetRandomBytes( buffer1, 4*MB ) == 4*MB ); - EXPECT_TRUE( XrdClTests::Utils::GetRandomBytes( buffer2, 4*MB ) == 4*MB ); + EXPECT_EQ( XrdClTests::Utils::GetRandomBytes( buffer1, 4*MB ), 4*MB ); + EXPECT_EQ( XrdClTests::Utils::GetRandomBytes( buffer2, 4*MB ), 4*MB ); uint32_t crc1 = XrdClTests::Utils::ComputeCRC32( buffer1, 4*MB ); crc1 = XrdClTests::Utils::UpdateCRC32( crc1, buffer2, 4*MB ); @@ -381,15 +381,15 @@ void FileTest::WriteTest() EXPECT_TRUE( f2.Open( fileUrl, OpenFlags::Read ).IsOK() ); EXPECT_TRUE( f2.Stat( false, statInfo ).IsOK() ); EXPECT_TRUE( statInfo ); - EXPECT_TRUE( statInfo->GetSize() == 8*MB ); + EXPECT_EQ( statInfo->GetSize(), 8*MB ); EXPECT_TRUE( f2.Read( 0, 4*MB, buffer3, bytesRead1 ).IsOK() ); EXPECT_TRUE( f2.Read( 4*MB, 4*MB, buffer4, bytesRead2 ).IsOK() ); - EXPECT_TRUE( bytesRead1 == 4*MB ); - EXPECT_TRUE( bytesRead2 == 4*MB ); + EXPECT_EQ( bytesRead1, 4*MB ); + EXPECT_EQ( bytesRead2, 4*MB ); uint32_t crc2 = XrdClTests::Utils::ComputeCRC32( buffer3, 4*MB ); crc2 = XrdClTests::Utils::UpdateCRC32( crc2, buffer4, 4*MB ); EXPECT_TRUE( f2.Close().IsOK() ); - EXPECT_TRUE( crc1 == crc2 ); + EXPECT_EQ( crc1, crc2 ); //---------------------------------------------------------------------------- // Truncate test @@ -402,7 +402,7 @@ void FileTest::WriteTest() StatInfo *response = 0; EXPECT_TRUE( fs.Stat( filePath, response ).IsOK() ); EXPECT_TRUE( response ); - EXPECT_TRUE( response->GetSize() == 20*MB ); + EXPECT_EQ( response->GetSize(), 20*MB ); EXPECT_TRUE( fs.Rm( filePath ).IsOK() ); delete [] buffer1; delete [] buffer2; @@ -447,8 +447,8 @@ void FileTest::WriteVTest() uint32_t bytesRead1 = 0; File f1, f2; - EXPECT_TRUE( XrdClTests::Utils::GetRandomBytes( buffer1, 4*MB ) == 4*MB ); - EXPECT_TRUE( XrdClTests::Utils::GetRandomBytes( buffer2, 4*MB ) == 4*MB ); + EXPECT_EQ( XrdClTests::Utils::GetRandomBytes( buffer1, 4*MB ), 4*MB ); + EXPECT_EQ( XrdClTests::Utils::GetRandomBytes( buffer2, 4*MB ), 4*MB ); uint32_t crc1 = XrdClTests::Utils::ComputeCRC32( buffer1, 4*MB ); crc1 = XrdClTests::Utils::UpdateCRC32( crc1, buffer2, 4*MB ); @@ -478,13 +478,13 @@ void FileTest::WriteVTest() EXPECT_TRUE( f2.Open( fileUrl, OpenFlags::Read ).IsOK() ); EXPECT_TRUE( f2.Stat( false, statInfo ).IsOK() ); EXPECT_TRUE( statInfo ); - EXPECT_TRUE( statInfo->GetSize() == 8*MB ); + EXPECT_EQ( statInfo->GetSize(), 8*MB ); EXPECT_TRUE( f2.Read( 0, 8*MB, buffer3, bytesRead1 ).IsOK() ); - EXPECT_TRUE( bytesRead1 == 8*MB ); + EXPECT_EQ( bytesRead1, 8*MB ); uint32_t crc2 = XrdClTests::Utils::ComputeCRC32( buffer3, 8*MB ); EXPECT_TRUE( f2.Close().IsOK() ); - EXPECT_TRUE( crc1 == crc2 ); + EXPECT_EQ( crc1, crc2 ); FileSystem fs( url ); EXPECT_TRUE( fs.Rm( filePath ).IsOK() ); @@ -554,37 +554,37 @@ void FileTest::VectorReadTest() // remote file vread1 VectorReadInfo *info = 0; GTEST_ASSERT_XRDST( f.VectorRead( chunkList1, buffer1, info ) ); - EXPECT_TRUE( info->GetSize() == 10*MB ); + EXPECT_EQ( info->GetSize(), 10*MB ); delete info; // local file vread1 info = 0; GTEST_ASSERT_XRDST( fLocal.VectorRead( chunkList1, buffer1Comp, info ) ); - EXPECT_TRUE( info->GetSize() == 10*MB ); + EXPECT_EQ( info->GetSize(), 10*MB ); delete info; // checksum comparison uint32_t crc = 0, crcComp = 0; crc = XrdClTests::Utils::ComputeCRC32( buffer1, 10*MB ); crcComp = XrdClTests::Utils::ComputeCRC32( buffer1Comp, 10*MB ); - EXPECT_TRUE( crc == crcComp ); + EXPECT_EQ( crc, crcComp ); // remote vread2 info = 0; GTEST_ASSERT_XRDST( f.VectorRead( chunkList2, buffer2, info ) ); - EXPECT_TRUE( info->GetSize() == 10*256000 ); + EXPECT_EQ( info->GetSize(), 10*256000 ); delete info; // local vread2 info = 0; GTEST_ASSERT_XRDST( f.VectorRead( chunkList2, buffer2Comp, info ) ); - EXPECT_TRUE( info->GetSize() == 10*256000 ); + EXPECT_EQ( info->GetSize(), 10*256000 ); delete info; // checksum comparison again crc = XrdClTests::Utils::ComputeCRC32( buffer2, 10*256000 ); crcComp = XrdClTests::Utils::ComputeCRC32( buffer2Comp, 10*256000 ); - EXPECT_TRUE( crc == crcComp ); + EXPECT_EQ( crc, crcComp ); // cleanup GTEST_ASSERT_XRDST( f.Close() ); @@ -704,9 +704,9 @@ void FileTest::VectorWriteTest() VectorReadInfo *info2 = 0; GTEST_ASSERT_XRDST( f.VectorRead( chunks, buffer2, info2 ) ); - EXPECT_TRUE( info2->GetSize() == totalSize ); + EXPECT_EQ( info2->GetSize(), totalSize ); uint32_t crc32 = XrdClTests::Utils::ComputeCRC32( buffer2, totalSize ); - EXPECT_TRUE( crc32 == expectedCrc32 ); + EXPECT_EQ( crc32, expectedCrc32 ); //---------------------------------------------------------------------------- // And finally revert to the original state @@ -761,7 +761,7 @@ void FileTest::VirtualRedirectorTest() GTEST_ASSERT_XRDST( f1.Open( mlUrl1, OpenFlags::Read ) ); EXPECT_TRUE( f1.GetProperty( key, value ) ); URL lastUrl( value ); - EXPECT_TRUE( lastUrl.GetLocation() == fileUrl ); + EXPECT_EQ( lastUrl.GetLocation(), fileUrl ); GTEST_ASSERT_XRDST( f1.Close() ); //---------------------------------------------------------------------------- @@ -771,7 +771,7 @@ void FileTest::VirtualRedirectorTest() GTEST_ASSERT_XRDST( f2.Open( mlUrl2, OpenFlags::Read ) ); EXPECT_TRUE( f2.GetProperty( key, value ) ); URL lastUrl2( value ); - EXPECT_TRUE( lastUrl2.GetLocation() == fileUrl ); + EXPECT_EQ( lastUrl2.GetLocation(), fileUrl ); GTEST_ASSERT_XRDST( f2.Close() ); //---------------------------------------------------------------------------- @@ -796,7 +796,7 @@ void FileTest::VirtualRedirectorTest() GTEST_ASSERT_XRDST( f4.Open( mlUrl4, OpenFlags::Read ) ); EXPECT_TRUE( f4.GetProperty( key, value ) ); URL lastUrl3( value ); - EXPECT_TRUE( lastUrl3.GetLocation() == replica1 ); + EXPECT_EQ( lastUrl3.GetLocation(), replica1 ); GTEST_ASSERT_XRDST( f4.Close() ); //---------------------------------------------------------------------------- // Delete the replica that has been selected by the virtual redirector @@ -809,7 +809,7 @@ void FileTest::VirtualRedirectorTest() GTEST_ASSERT_XRDST( f4.Open( mlUrl4, OpenFlags::Read ) ); EXPECT_TRUE( f4.GetProperty( key, value ) ); URL lastUrl4( value ); - EXPECT_TRUE( lastUrl4.GetLocation() == replica2 ); + EXPECT_EQ( lastUrl4.GetLocation(), replica2 ); GTEST_ASSERT_XRDST( f4.Close() ); //---------------------------------------------------------------------------- // Recreate the deleted file @@ -886,8 +886,8 @@ void FileTest::XAttrTest() { GTEST_ASSERT_XRDST( itr3->status ); auto match = attributes.find( itr3->name ); - EXPECT_TRUE( match != attributes.end() ); - EXPECT_TRUE( match->second == itr3->value ); + EXPECT_NE( match, attributes.end() ); + EXPECT_EQ( match->second, itr3->value ); } //---------------------------------------------------------------------------- @@ -900,8 +900,8 @@ void FileTest::XAttrTest() { GTEST_ASSERT_XRDST( itr3->status ); auto match = attributes.find( itr3->name ); - EXPECT_TRUE( match != attributes.end() ); - EXPECT_TRUE( match->second == itr3->value ); + EXPECT_NE( match, attributes.end() ); + EXPECT_EQ( match->second, itr3->value ); } //---------------------------------------------------------------------------- diff --git a/tests/XrdCl/XrdClLocalFileHandlerTest.cc b/tests/XrdCl/XrdClLocalFileHandlerTest.cc index 84de7d8bcd7..d298a0c6fc5 100644 --- a/tests/XrdCl/XrdClLocalFileHandlerTest.cc +++ b/tests/XrdCl/XrdClLocalFileHandlerTest.cc @@ -91,10 +91,10 @@ void LocalFileHandlerTest::readTestFunc(bool offsetRead, uint32_t offset){ GTEST_ASSERT_XRDST( file->Close() ); std::string read( buffer, size ); - if (offsetRead) EXPECT_TRUE( expectedRead == read ); - else EXPECT_TRUE( toBeWritten == read ); + if (offsetRead) EXPECT_EQ( expectedRead, read ); + else EXPECT_EQ( toBeWritten, read ); - EXPECT_TRUE( remove( targetURL.c_str() ) == 0 ); + EXPECT_EQ( remove( targetURL.c_str() ), 0 ); delete[] buffer; delete file; @@ -115,7 +115,7 @@ TEST_F(LocalFileHandlerTest, SyncTest){ GTEST_ASSERT_XRDST( file->Open( targetURL, flags, mode ) ); GTEST_ASSERT_XRDST( file->Sync() ); GTEST_ASSERT_XRDST( file->Close() ); - EXPECT_TRUE( remove( targetURL.c_str() ) == 0 ); + EXPECT_EQ( remove( targetURL.c_str() ), 0 ); delete file; } @@ -133,7 +133,7 @@ TEST_F(LocalFileHandlerTest, OpenCloseTest){ GTEST_ASSERT_XRDST( file->Open( targetURL, flags, mode ) ); EXPECT_TRUE( file->IsOpen() ); GTEST_ASSERT_XRDST( file->Close() ); - EXPECT_TRUE( remove( targetURL.c_str() ) == 0 ); + EXPECT_EQ( remove( targetURL.c_str() ), 0 ); //---------------------------------------------------------------------------- // Try open non-existing file @@ -144,7 +144,7 @@ TEST_F(LocalFileHandlerTest, OpenCloseTest){ //---------------------------------------------------------------------------- // Try close non-opened file, return has to be error //---------------------------------------------------------------------------- - EXPECT_TRUE( file->Close().status == stError ); + EXPECT_EQ( file->Close().status, stError ); delete file; } @@ -173,8 +173,8 @@ TEST_F(LocalFileHandlerTest, WriteTest){ int rc = read( fd, buffer, int( writeSize ) ); EXPECT_EQ( rc, int( writeSize ) ); std::string read( (char *)buffer, writeSize ); - EXPECT_TRUE( toBeWritten == read ); - EXPECT_TRUE( remove( targetURL.c_str() ) == 0 ); + EXPECT_EQ( toBeWritten, read ); + EXPECT_EQ( remove( targetURL.c_str() ), 0 ); delete[] buffer; delete file; } @@ -207,8 +207,8 @@ TEST_F(LocalFileHandlerTest, WriteWithOffsetTest){ int rc = read( fd, buffer, offset ); EXPECT_EQ( rc, int( offset ) ); std::string read( (char *)buffer, offset ); - EXPECT_TRUE( notToBeOverwritten == read ); - EXPECT_TRUE( remove( targetURL.c_str() ) == 0 ); + EXPECT_EQ( notToBeOverwritten, read ); + EXPECT_EQ( remove( targetURL.c_str() ), 0 ); delete[] (char*)buffer; delete file; } @@ -238,8 +238,8 @@ TEST_F(LocalFileHandlerTest, WriteMkdirTest){ int rc = read( fd, buffer, writeSize ); EXPECT_EQ( rc, int( writeSize ) ); std::string read( buffer, writeSize ); - EXPECT_TRUE( toBeWritten == read ); - EXPECT_TRUE( remove( targetURL.c_str() ) == 0 ); + EXPECT_EQ( toBeWritten, read ); + EXPECT_EQ( remove( targetURL.c_str() ), 0 ); delete[] buffer; delete file; } @@ -278,7 +278,7 @@ TEST_F(LocalFileHandlerTest, TruncateTest){ GTEST_ASSERT_XRDST( file->Read( 0, truncateSize + 3, buffer, bytesRead ) ); EXPECT_EQ( truncateSize, bytesRead ); GTEST_ASSERT_XRDST( file->Close() ); - EXPECT_TRUE( remove( targetURL.c_str() ) == 0 ); + EXPECT_EQ( remove( targetURL.c_str() ), 0 ); delete file; delete[] buffer; } @@ -311,7 +311,7 @@ TEST_F(LocalFileHandlerTest, VectorReadTest) chunks.push_back( ChunkInfo( 10, 5, new char[5] ) ); GTEST_ASSERT_XRDST( file.VectorRead( chunks, NULL, info ) ); GTEST_ASSERT_XRDST( file.Close() ); - EXPECT_TRUE( info->GetSize() == 10 ); + EXPECT_EQ( info->GetSize(), 10 ); EXPECT_EQ( 0, memcmp( "Gener", info->GetChunks()[0].buffer, info->GetChunks()[0].length ) ); @@ -334,11 +334,11 @@ TEST_F(LocalFileHandlerTest, VectorReadTest) GTEST_ASSERT_XRDST( file.Open( targetURL, flags, mode ) ); GTEST_ASSERT_XRDST( file.VectorRead( chunks, buffer, info ) ); GTEST_ASSERT_XRDST( file.Close() ); - EXPECT_TRUE( info->GetSize() == 10 ); + EXPECT_EQ( info->GetSize(), 10 ); EXPECT_EQ( 0, memcmp( "GenertFile", info->GetChunks()[0].buffer, info->GetChunks()[0].length ) ); - EXPECT_TRUE( remove( targetURL.c_str() ) == 0 ); + EXPECT_EQ( remove( targetURL.c_str() ), 0 ); delete[] buffer; delete info; @@ -387,14 +387,14 @@ TEST_F(LocalFileHandlerTest, VectorWriteTest) EXPECT_EQ( 0, memcmp( buffer, "AAAAABBBBB", 10 ) ); GTEST_ASSERT_XRDST( file.Close() ); - EXPECT_TRUE( info->GetSize() == 10 ); + EXPECT_EQ( info->GetSize(), 10 ); delete[] (char*)chunks[0].buffer; delete[] (char*)chunks[1].buffer; delete[] buffer; delete info; - EXPECT_TRUE( remove( targetURL.c_str() ) == 0 ); + EXPECT_EQ( remove( targetURL.c_str() ), 0 ); } TEST_F(LocalFileHandlerTest, WriteVTest) @@ -429,11 +429,11 @@ TEST_F(LocalFileHandlerTest, WriteVTest) uint32_t bytesRead = 0; buffer.resize( 17 ); GTEST_ASSERT_XRDST( file.Read( 0, 17, buffer.data(), bytesRead ) ); - EXPECT_TRUE( buffer.size() == 17 ); + EXPECT_EQ( buffer.size(), 17 ); std::string expected = "GenericWriteVTest"; - EXPECT_TRUE( std::string( buffer.data(), buffer.size() ) == expected ); + EXPECT_EQ( std::string( buffer.data(), buffer.size() ), expected ); GTEST_ASSERT_XRDST( file.Close() ); - EXPECT_TRUE( remove( targetURL.c_str() ) == 0 ); + EXPECT_EQ( remove( targetURL.c_str() ), 0 ); } TEST_F(LocalFileHandlerTest, XAttrTest) @@ -450,7 +450,7 @@ TEST_F(LocalFileHandlerTest, XAttrTest) std::string localDataPath; EXPECT_TRUE( testEnv->GetString( "LocalDataPath", localDataPath ) ); - char resolved_path[MAXPATHLEN]; + char resolved_path[PATH_MAX]; localDataPath = realpath(localDataPath.c_str(), resolved_path); std::string targetURL = localDataPath + "/metaman/lfilehandlertestfilexattr"; @@ -487,13 +487,13 @@ TEST_F(LocalFileHandlerTest, XAttrTest) GTEST_ASSERT_XRDST( resp[0].status ); GTEST_ASSERT_XRDST( resp[1].status ); - EXPECT_TRUE( resp.size() == 2 ); + EXPECT_EQ( resp.size(), 2 ); int vid = resp[0].name == "version" ? 0 : 1; int did = vid == 0 ? 1 : 0; - EXPECT_TRUE( resp[vid].name == "version" && - resp[vid].value == "v3.3.3" ); - EXPECT_TRUE( resp[did].name == "description" && - resp[did].value == "a very important file" ); + EXPECT_EQ( resp[vid].name, std::string("version") ); + EXPECT_EQ( resp[vid].value, std::string("v3.3.3") ); + EXPECT_EQ( resp[did].name, std::string("description") ); + EXPECT_EQ( resp[did].value, std::string("a very important file") ); //---------------------------------------------------------------------------- // Test XAttr Del @@ -502,7 +502,7 @@ TEST_F(LocalFileHandlerTest, XAttrTest) names.push_back( "description" ); st_resp.clear(); GTEST_ASSERT_XRDST( f.DelXAttr( names, st_resp ) ); - EXPECT_TRUE( st_resp.size() == 1 ); + EXPECT_EQ( st_resp.size(), 1 ); GTEST_ASSERT_XRDST( st_resp[0].status ); //---------------------------------------------------------------------------- @@ -510,17 +510,17 @@ TEST_F(LocalFileHandlerTest, XAttrTest) //---------------------------------------------------------------------------- resp.clear(); GTEST_ASSERT_XRDST( f.ListXAttr( resp ) ); - EXPECT_TRUE( resp.size() == 2 ); + EXPECT_EQ( resp.size(), 2 ); vid = resp[0].name == "version" ? 0 : 1; int cid = vid == 0 ? 1 : 0; - EXPECT_TRUE( resp[vid].name == "version" && - resp[vid].value == "v3.3.3" ); - EXPECT_TRUE( resp[cid].name == "checksum" && - resp[cid].value == "0x22334455" ); + EXPECT_EQ( resp[vid].name, std::string("version") ); + EXPECT_EQ( resp[vid].value, std::string("v3.3.3") ); + EXPECT_EQ( resp[cid].name, std::string("checksum") ); + EXPECT_EQ( resp[cid].value, std::string("0x22334455") ); //---------------------------------------------------------------------------- // Cleanup //---------------------------------------------------------------------------- GTEST_ASSERT_XRDST( f.Close() ); - EXPECT_TRUE( remove( targetURL.c_str() ) == 0 ); + EXPECT_EQ( remove( targetURL.c_str() ), 0 ); } diff --git a/tests/XrdCl/XrdClOperationsWorkflowTest.cc b/tests/XrdCl/XrdClOperationsWorkflowTest.cc index 48cf4a56f4a..02041c9fbdc 100644 --- a/tests/XrdCl/XrdClOperationsWorkflowTest.cc +++ b/tests/XrdCl/XrdClOperationsWorkflowTest.cc @@ -190,14 +190,14 @@ TEST(WorkflowTest, ReadingWorkflowTest){ struct stat localStatBuf; std::string localFilePath = localDataPath + "/srv1" + filePath; int rc = stat(localFilePath.c_str(), &localStatBuf); - EXPECT_TRUE( rc == 0 ); + EXPECT_EQ( rc, 0 ); uint64_t fileSize = localStatBuf.st_size; auto &&pipe = Open( f, fileUrl, flags ) >> openHandler // by reference | Stat( f, true) >> [fileSize, size, buffer]( XRootDStatus &status, StatInfo &stat) mutable { GTEST_ASSERT_XRDST( status ); - EXPECT_TRUE( stat.GetSize() == fileSize ); + EXPECT_EQ( stat.GetSize(), fileSize ); size = stat.GetSize(); buffer = new char[stat.GetSize()]; } @@ -274,7 +274,7 @@ TEST(WorkflowTest, WritingWorkflowTest){ | Stat( f, true ) >> [size, buffer, createdFileSize]( XRootDStatus &status, StatInfo &info ) mutable { GTEST_ASSERT_XRDST( status ); - EXPECT_TRUE( createdFileSize == info.GetSize() ); + EXPECT_EQ( createdFileSize, info.GetSize() ); size = info.GetSize(); buffer = new char[info.GetSize()]; } @@ -284,7 +284,7 @@ TEST(WorkflowTest, WritingWorkflowTest){ XRootDStatus status = WaitFor( std::move( pipe ) ); GTEST_ASSERT_XRDST( status ); - EXPECT_TRUE( rdresp.get() == texts[0] + texts[1] + texts[2] ); + EXPECT_EQ( rdresp.get(), texts[0] + texts[1] + texts[2] ); free( (*iov)[0].iov_base ); free( (*iov)[1].iov_base ); @@ -595,7 +595,8 @@ TEST(WorkflowTest, ParallelTest){ GTEST_ASSERT_XRDST( WaitFor( Parallel( Stat( fs, url_exists ) >> hndl, Stat( fs, also_exists ) >> hndl, Stat( fs, not_exists ) >> hndl ).AtLeast( 1 ) ) ); - EXPECT_TRUE( okcnt == 2 && errcnt == 1 ); + EXPECT_EQ( okcnt, 2 ); + EXPECT_EQ( errcnt, 1 ); } @@ -710,7 +711,7 @@ TEST(WorkflowTest, MixedWorkflowTest){ char *buffer = reinterpret_cast( chunk.buffer ); std::string result( buffer, chunk.length ); delete[] buffer; - EXPECT_TRUE( result == content[i] ); + EXPECT_EQ( result, content[i] ); } EXPECT_TRUE(cleaningHandlerExecuted); @@ -753,7 +754,7 @@ TEST(WorkflowTest, WorkflowWithFutureTest) //---------------------------------------------------------------------------- GTEST_ASSERT_XRDST( f.Open( fileUrl, OpenFlags::Read ) ); GTEST_ASSERT_XRDST( f.Read( 1*MB, 10*MB, expected, bytesRead ) ); - EXPECT_TRUE( bytesRead == 10*MB ); + EXPECT_EQ( bytesRead, 10*MB ); GTEST_ASSERT_XRDST( f.Close() ); //---------------------------------------------------------------------------- @@ -768,7 +769,7 @@ TEST(WorkflowTest, WorkflowWithFutureTest) { ChunkInfo result = ftr.get(); EXPECT_TRUE( result.length = bytesRead ); - EXPECT_TRUE( strncmp( expected, (char*)result.buffer, bytesRead ) == 0 ); + EXPECT_EQ( strncmp( expected, (char*)result.buffer, bytesRead ), 0 ); } catch( PipelineException &ex ) { @@ -828,7 +829,7 @@ TEST(WorkflowTest, XAttrWorkflowTest) try { - EXPECT_TRUE( xattr_value == rsp1.get() ); + EXPECT_EQ( xattr_value, rsp1.get() ); } catch( PipelineException &ex ) { @@ -856,13 +857,13 @@ TEST(WorkflowTest, XAttrWorkflowTest) [&]( XRootDStatus &status, std::vector &rsp ) { GTEST_ASSERT_XRDST( status ); - EXPECT_TRUE( rsp.size() == attrs.size() ); + EXPECT_EQ( rsp.size(), attrs.size() ); for( size_t i = 0; i < rsp.size(); ++i ) { auto itr = std::find_if( attrs.begin(), attrs.end(), [&]( xattr_t &a ){ return std::get<0>( a ) == rsp[i].name; } ); - EXPECT_TRUE( itr != attrs.end() ); - EXPECT_TRUE( std::get<1>( *itr ) == rsp[i].value ); + EXPECT_NE( itr, attrs.end() ); + EXPECT_EQ( std::get<1>( *itr ), rsp[i].value ); } } | DelXAttr( file4, names ) @@ -882,9 +883,9 @@ TEST(WorkflowTest, XAttrWorkflowTest) [&]( XRootDStatus &status, std::vector &rsp ) { GTEST_ASSERT_XRDST( status ); - EXPECT_TRUE( rsp.size() == 1 ); - EXPECT_TRUE( rsp[0].name == xattr_name ); - EXPECT_TRUE( rsp[0].value == xattr_value ); + EXPECT_EQ( rsp.size(), 1 ); + EXPECT_EQ( rsp[0].name, xattr_name ); + EXPECT_EQ( rsp[0].value, xattr_value ); } | DelXAttr( fs, filePath, xattr_name ); @@ -892,7 +893,7 @@ TEST(WorkflowTest, XAttrWorkflowTest) try { - EXPECT_TRUE( xattr_value == rsp2.get() ); + EXPECT_EQ( xattr_value, rsp2.get() ); } catch( PipelineException &ex ) { @@ -920,7 +921,7 @@ TEST(WorkflowTest, MkDirAsyncTest) { RmDir( fs, asyncTestFile ) ); - EXPECT_TRUE(t.get().status == stOK); + EXPECT_EQ(t.get().status, stOK); } TEST(WorkflowTest, CheckpointTest) { @@ -958,7 +959,7 @@ TEST(WorkflowTest, CheckpointTest) { Read( f3, 0, sizeof( readout ), readout ) | Close( f3 ) ) ); // we expect the data to be unchanged - EXPECT_TRUE( strncmp( readout, data, sizeof( data ) ) == 0 ); + EXPECT_EQ( strncmp( readout, data, sizeof( data ) ), 0 ); //--------------------------------------------------------------------------- // Update the file and commit the changes @@ -975,7 +976,7 @@ TEST(WorkflowTest, CheckpointTest) { Read( f5, 0, sizeof( readout ), readout ) | Close( f5 ) ) ); // we expect the data to be unchanged - EXPECT_TRUE( strncmp( readout, update, sizeof( update ) ) == 0 ); + EXPECT_EQ( strncmp( readout, update, sizeof( update ) ), 0 ); EXPECT_TRUE( strncmp( readout + sizeof( update ), data + sizeof( update ), sizeof( data ) - sizeof( update ) ) == 0 ); diff --git a/tests/XrdCl/XrdClPoller.cc b/tests/XrdCl/XrdClPoller.cc index ceeabfed28f..5569c64799b 100644 --- a/tests/XrdCl/XrdClPoller.cc +++ b/tests/XrdCl/XrdClPoller.cc @@ -254,8 +254,8 @@ TEST(PollerTest, FunctionTest) EXPECT_TRUE( !poller->IsRegistered( &s[i] ) ); stats[i] = handler->GetReceivedStats( s[i].GetSockName() ); statsServ[i] = server.GetSentStats( s[i].GetSockName() ); - EXPECT_TRUE( stats[i].first == statsServ[i].first ); - EXPECT_TRUE( stats[i].second == statsServ[i].second ); + EXPECT_EQ( stats[i].first, statsServ[i].first ); + EXPECT_EQ( stats[i].second, statsServ[i].second ); } for( int i = 0; i < 3; ++i ) diff --git a/tests/XrdCl/XrdClPostMasterTest.cc b/tests/XrdCl/XrdClPostMasterTest.cc index 9de000b494f..0e91c6db3cc 100644 --- a/tests/XrdCl/XrdClPostMasterTest.cc +++ b/tests/XrdCl/XrdClPostMasterTest.cc @@ -59,8 +59,8 @@ namespace XrdCl::PostMaster *pm = Get(); pm->Stop(); pm->Finalize(); - EXPECT_TRUE( pm->Initialize() != 0 ); - EXPECT_TRUE( pm->Start() != 0 ); + EXPECT_NE( pm->Initialize(), 0 ); + EXPECT_NE( pm->Start(), 0 ); return pm; } }; @@ -262,9 +262,9 @@ void *TestThreadFunc( void *arg ) XrdCl::Message msg; GTEST_ASSERT_XRDST( msgHandlers[i].WaitFor( msg ) ); ServerResponse *resp = (ServerResponse *)msg.GetBuffer(); - EXPECT_TRUE( resp != 0 ); - EXPECT_TRUE( resp->hdr.status == kXR_ok ); - EXPECT_TRUE( msg.GetSize() == 8 ); + EXPECT_TRUE( resp ); + EXPECT_EQ( resp->hdr.status, kXR_ok ); + EXPECT_EQ( msg.GetSize(), 8 ); } return 0; } @@ -338,9 +338,9 @@ TEST(PostMasterTest, FunctionalTest) GTEST_ASSERT_XRDST( msgHandler1.WaitFor( m2 ) ); ServerResponse *resp = (ServerResponse *)m2.GetBuffer(); - EXPECT_TRUE( resp != 0 ); - EXPECT_TRUE( resp->hdr.status == kXR_ok ); - EXPECT_TRUE( m2.GetSize() == 8 ); + EXPECT_TRUE( resp ); + EXPECT_EQ( resp->hdr.status, kXR_ok ); + EXPECT_EQ( m2.GetSize(), 8 ); //---------------------------------------------------------------------------- // Send out some stuff to a location where nothing listens @@ -402,9 +402,9 @@ TEST(PostMasterTest, FunctionalTest) GTEST_ASSERT_XRDST( msgHandler4.WaitFor( m2 ) ); resp = (ServerResponse *)m2.GetBuffer(); - EXPECT_TRUE( resp != 0 ); - EXPECT_TRUE( resp->hdr.status == kXR_ok ); - EXPECT_TRUE( m2.GetSize() == 8 ); + EXPECT_TRUE( resp ); + EXPECT_EQ( resp->hdr.status, kXR_ok ); + EXPECT_EQ( m2.GetSize(), 8 ); //---------------------------------------------------------------------------- // Sleep 10 secs waiting for iddle connection to be closed and see @@ -418,9 +418,9 @@ TEST(PostMasterTest, FunctionalTest) GTEST_ASSERT_XRDST( msgHandler5.WaitFor( m2 ) ); resp = (ServerResponse *)m2.GetBuffer(); - EXPECT_TRUE( resp != 0 ); - EXPECT_TRUE( resp->hdr.status == kXR_ok ); - EXPECT_TRUE( m2.GetSize() == 8 ); + EXPECT_TRUE( resp ); + EXPECT_EQ( resp->hdr.status, kXR_ok ); + EXPECT_EQ( m2.GetSize(), 8 ); } @@ -466,9 +466,9 @@ TEST(PostMasterTest, PingIPv6) sc = postMaster->Receive( localhost1, m2, &f1, false, 1200 ); EXPECT_TRUE( sc.IsOK() ); ServerResponse *resp = (ServerResponse *)m2->GetBuffer(); - EXPECT_TRUE( resp != 0 ); - EXPECT_TRUE( resp->hdr.status == kXR_ok ); - EXPECT_TRUE( m2->GetSize() == 8 ); + EXPECT_TRUE( resp ); + EXPECT_EQ( resp->hdr.status, kXR_ok ); + EXPECT_EQ( m2->GetSize(), 8 ); //---------------------------------------------------------------------------- // Send the message - localhost2 @@ -479,9 +479,9 @@ TEST(PostMasterTest, PingIPv6) sc = postMaster->Receive( localhost2, m2, &f1, 1200 ); EXPECT_TRUE( sc.IsOK() ); resp = (ServerResponse *)m2->GetBuffer(); - EXPECT_TRUE( resp != 0 ); - EXPECT_TRUE( resp->hdr.status == kXR_ok ); - EXPECT_TRUE( m2->GetSize() == 8 ); + EXPECT_TRUE( resp ); + EXPECT_EQ( resp->hdr.status, kXR_ok ); + EXPECT_EQ( m2->GetSize(), 8 ); #endif } @@ -568,8 +568,8 @@ TEST(PostMasterTest, MultiIPConnectionTest) GTEST_ASSERT_XRDST( postMaster->Send( url3, m, &msgHandler3, false, expires ) ); GTEST_ASSERT_XRDST( msgHandler3.WaitFor( m2 ) ); ServerResponse *resp = (ServerResponse *)m2.GetBuffer(); - EXPECT_TRUE( resp != 0 ); - EXPECT_TRUE( resp->hdr.status == kXR_ok ); - EXPECT_TRUE( m2.GetSize() == 8 ); + EXPECT_TRUE( resp ); + EXPECT_EQ( resp->hdr.status, kXR_ok ); + EXPECT_EQ( m2.GetSize(), 8 ); } #endif diff --git a/tests/XrdCl/XrdClSocket.cc b/tests/XrdCl/XrdClSocket.cc index 676d8c01aa3..df8f433bff5 100644 --- a/tests/XrdCl/XrdClSocket.cc +++ b/tests/XrdCl/XrdClSocket.cc @@ -228,10 +228,10 @@ TEST(SocketTest, TransferTest) EXPECT_TRUE( serv.Setup( port, 1, new RandomHandlerFactory() ) ); EXPECT_TRUE( serv.Start() ); - EXPECT_TRUE( sock.GetStatus() == Socket::Disconnected ); + EXPECT_EQ( sock.GetStatus(), Socket::Disconnected ); EXPECT_TRUE( sock.Initialize( AF_INET6 ).IsOK() ); EXPECT_TRUE( sock.Connect( "localhost", port ).IsOK() ); - EXPECT_TRUE( sock.GetStatus() == Socket::Connected ); + EXPECT_EQ( sock.GetStatus(), Socket::Connected ); //---------------------------------------------------------------------------- // Get the number of packets @@ -246,7 +246,7 @@ TEST(SocketTest, TransferTest) uint64_t receivedCounter = 0; uint32_t receivedChecksum = ::Utils::ComputeCRC32( 0, 0 ); sc = sock.ReadRaw( &packets, 1, 60, bytesTransmitted ); - EXPECT_TRUE( sc.status == stOK ); + EXPECT_EQ( sc.status, stOK ); //---------------------------------------------------------------------------- // Read each packet @@ -254,9 +254,9 @@ TEST(SocketTest, TransferTest) for( int i = 0; i < packets; ++i ) { sc = sock.ReadRaw( &packetSize, 2, 60, bytesTransmitted ); - EXPECT_TRUE( sc.status == stOK ); + EXPECT_EQ( sc.status, stOK ); sc = sock.ReadRaw( buffer, packetSize, 60, bytesTransmitted ); - EXPECT_TRUE( sc.status == stOK ); + EXPECT_EQ( sc.status, stOK ); receivedCounter += bytesTransmitted; receivedChecksum = ::Utils::UpdateCRC32( receivedChecksum, buffer, bytesTransmitted ); @@ -268,17 +268,20 @@ TEST(SocketTest, TransferTest) packets = random() % 100; sc = sock.WriteRaw( &packets, 1, 60, bytesTransmitted ); - EXPECT_TRUE( (sc.status == stOK) && (bytesTransmitted == 1) ); + EXPECT_EQ( sc.status, stOK ); + EXPECT_EQ( bytesTransmitted, 1 ); for( int i = 0; i < packets; ++i ) { packetSize = random() % 50000; - EXPECT_TRUE( ::Utils::GetRandomBytes( buffer, packetSize ) == packetSize ); + EXPECT_EQ( ::Utils::GetRandomBytes( buffer, packetSize ), packetSize ); sc = sock.WriteRaw( (char *)&packetSize, 2, 60, bytesTransmitted ); - EXPECT_TRUE( (sc.status == stOK) && (bytesTransmitted == 2) ); + EXPECT_EQ( sc.status, stOK ); + EXPECT_EQ( bytesTransmitted, 2 ); sc = sock.WriteRaw( buffer, packetSize, 60, bytesTransmitted ); - EXPECT_TRUE( (sc.status == stOK) && (bytesTransmitted == packetSize) ); + EXPECT_EQ( sc.status, stOK ); + EXPECT_EQ( bytesTransmitted, packetSize ); sentCounter += bytesTransmitted; sentChecksum = ::Utils::UpdateCRC32( sentChecksum, buffer, bytesTransmitted ); @@ -294,8 +297,8 @@ TEST(SocketTest, TransferTest) std::pair sent = serv.GetSentStats( socketName ); std::pair received = serv.GetReceivedStats( socketName ); - EXPECT_TRUE( sentCounter == received.first ); - EXPECT_TRUE( receivedCounter == sent.first ); - EXPECT_TRUE( sentChecksum == received.second ); - EXPECT_TRUE( receivedChecksum == sent.second ); + EXPECT_EQ( sentCounter, received.first ); + EXPECT_EQ( receivedCounter, sent.first ); + EXPECT_EQ( sentChecksum, received.second ); + EXPECT_EQ( receivedChecksum, sent.second ); } diff --git a/tests/XrdCl/XrdClThreadingTest.cc b/tests/XrdCl/XrdClThreadingTest.cc index d592be8597f..baee052331d 100644 --- a/tests/XrdCl/XrdClThreadingTest.cc +++ b/tests/XrdCl/XrdClThreadingTest.cc @@ -163,7 +163,7 @@ void ThreadingTest::ReadTestFunc( TransferCallback transferCallback ) uint32_t bytesRead = 0; GTEST_ASSERT_XRDST( f->Read( offset, 4*MB, buffer, bytesRead ) ); - EXPECT_TRUE( bytesRead == 4*MB ); + EXPECT_EQ( bytesRead, 4*MB ); threadData[j*5+i].firstBlockChecksum = XrdClTests::Utils::ComputeCRC32( buffer, 4*MB ); delete [] buffer; @@ -215,8 +215,7 @@ void ThreadingTest::ReadTestFunc( TransferCallback transferCallback ) threadData[i].file->GetProperty( "LastURL", lastUrl ); GTEST_ASSERT_XRDST( Utils::GetRemoteCheckSum( remoteSum, "zcrc32", lastUrl ) ); - EXPECT_TRUE( remoteSum == transferSum ); // TODO; same test repeated twice?? (check w amadio) - EXPECT_TRUE( remoteSum == transferSum ) << path[i]; + EXPECT_EQ( remoteSum, transferSum ) << path[i]; } //---------------------------------------------------------------------------- @@ -263,8 +262,8 @@ int runChild( ThreadData *td ) uint32_t bytesRead = 0; GTEST_ASSERT_XRDST( td[i].file->Read( offset, 4*MB, buffer, bytesRead ) ); - EXPECT_TRUE( bytesRead == 4*MB ); - EXPECT_TRUE( td[i].firstBlockChecksum == + EXPECT_EQ( bytesRead, 4*MB ); + EXPECT_EQ( td[i].firstBlockChecksum, XrdClTests::Utils::ComputeCRC32( buffer, 4*MB ) ); delete [] buffer; } @@ -299,7 +298,7 @@ void forkAndRead( ThreadData *data ) GTEST_ASSERT_ERRNO( waitpid( pid, &status, 0 ) != -1 ); log->Debug( 1, "Wait done, status: %d", status ); EXPECT_TRUE( WIFEXITED( status ) ); - EXPECT_TRUE( WEXITSTATUS( status ) == 0 ); + EXPECT_EQ( WEXITSTATUS( status ), 0 ); } } diff --git a/tests/XrdCl/XrdClUtilsTest.cc b/tests/XrdCl/XrdClUtilsTest.cc index b78d0877327..7ba30ec8676 100644 --- a/tests/XrdCl/XrdClUtilsTest.cc +++ b/tests/XrdCl/XrdClUtilsTest.cc @@ -185,18 +185,18 @@ TEST(UtilsTest, SIDManagerTest) GTEST_ASSERT_XRDST( manager->AllocateSID( sid5 ) ); EXPECT_TRUE( (sid1[0] != sid2[0]) || (sid1[1] != sid2[1]) ); - EXPECT_TRUE( manager->NumberOfTimedOutSIDs() == 0 ); + EXPECT_EQ( manager->NumberOfTimedOutSIDs(), 0 ); manager->TimeOutSID( sid4 ); manager->TimeOutSID( sid5 ); - EXPECT_TRUE( manager->NumberOfTimedOutSIDs() == 2 ); - EXPECT_TRUE( manager->IsTimedOut( sid3 ) == false ); - EXPECT_TRUE( manager->IsTimedOut( sid1 ) == false ); - EXPECT_TRUE( manager->IsTimedOut( sid4 ) == true ); - EXPECT_TRUE( manager->IsTimedOut( sid5 ) == true ); + EXPECT_EQ( manager->NumberOfTimedOutSIDs(), 2 ); + EXPECT_FALSE( manager->IsTimedOut( sid3 ) ); + EXPECT_FALSE( manager->IsTimedOut( sid1 ) ); + EXPECT_TRUE( manager->IsTimedOut( sid4 ) ); + EXPECT_TRUE( manager->IsTimedOut( sid5 ) ); manager->ReleaseTimedOut( sid5 ); - EXPECT_TRUE( manager->IsTimedOut( sid5 ) == false ); + EXPECT_FALSE( manager->IsTimedOut( sid5 ) ); manager->ReleaseAllTimedOut(); - EXPECT_TRUE( manager->NumberOfTimedOutSIDs() == 0 ); + EXPECT_EQ( manager->NumberOfTimedOutSIDs(), 0 ); } //------------------------------------------------------------------------------ @@ -213,9 +213,9 @@ TEST(UtilsTest, PropertyListTest) std::string s1; EXPECT_TRUE( l.Get( "s1", s1 ) ); - EXPECT_TRUE( s1 == "test string 1" ); + EXPECT_EQ( s1, "test string 1" ); EXPECT_TRUE( l.Get( "i1", i1 ) ); - EXPECT_TRUE( i1 == 123456789123ULL ); + EXPECT_EQ( i1, 123456789123ULL ); EXPECT_TRUE( l.HasProperty( "s1" ) ); EXPECT_TRUE( !l.HasProperty( "s2" ) ); EXPECT_TRUE( l.HasProperty( "i1" ) ); @@ -230,16 +230,16 @@ TEST(UtilsTest, PropertyListTest) EXPECT_TRUE( l.Get( "vect_int", i, num ) ); EXPECT_TRUE( num = i+1000 ); } - EXPECT_TRUE( i == 1000 ); + EXPECT_EQ( i, 1000 ); XRootDStatus st1, st2; st1.SetErrorMessage( "test error message" ); l.Set( "status", st1 ); EXPECT_TRUE( l.Get( "status", st2 ) ); - EXPECT_TRUE( st2.status == st1.status ); - EXPECT_TRUE( st2.code == st1.code ); - EXPECT_TRUE( st2.errNo == st1.errNo ); - EXPECT_TRUE( st2.GetErrorMessage() == st1.GetErrorMessage() ); + EXPECT_EQ( st2.status, st1.status ); + EXPECT_EQ( st2.code , st1.code ); + EXPECT_EQ( st2.errNo , st1.errNo ); + EXPECT_EQ( st2.GetErrorMessage(), st1.GetErrorMessage() ); std::vector v1, v2; v1.push_back( "test string 1" ); @@ -248,5 +248,5 @@ TEST(UtilsTest, PropertyListTest) l.Set( "vector", v1 ); EXPECT_TRUE( l.Get( "vector", v2 ) ); for( size_t i = 0; i < v1.size(); ++i ) - EXPECT_TRUE( v1[i] == v2[i] ); + EXPECT_EQ( v1[i], v2[i] ); } diff --git a/tests/XrdCl/XrdClZip.cc b/tests/XrdCl/XrdClZip.cc index 19b1ed1d425..086d73742d7 100644 --- a/tests/XrdCl/XrdClZip.cc +++ b/tests/XrdCl/XrdClZip.cc @@ -79,14 +79,14 @@ TEST_F(ZipTest, OpenFileTest){ TEST_F(ZipTest, ListFileTest) { DirectoryList* dummy_list; GTEST_ASSERT_XRDST(zip_file.List(dummy_list)); - EXPECT_TRUE(dummy_list != NULL); + EXPECT_TRUE(dummy_list); } TEST_F(ZipTest, GetterTests) { // Get file File* file = NULL; file = &(zip_file.GetFile()); - EXPECT_TRUE(file != NULL); + EXPECT_TRUE(file); // Get checksum uint32_t cksum; diff --git a/tests/XrdEc/MicroTest.cc b/tests/XrdEc/MicroTest.cc index 61e26916327..b39b6d24558 100644 --- a/tests/XrdEc/MicroTest.cc +++ b/tests/XrdEc/MicroTest.cc @@ -79,17 +79,17 @@ class XrdEcTests : public ::testing::Test } inline void VectorReadTest(){ - Init(true); + Init(true); - AlignedWriteRaw(); + AlignedWriteRaw(); - Verify(); + Verify(); - uint32_t seed = std::chrono::system_clock::now().time_since_epoch().count(); + uint32_t seed = std::chrono::system_clock::now().time_since_epoch().count(); - VerifyVectorRead(seed); + VerifyVectorRead(seed); - CleanUp(); + CleanUp(); } inline void IllegalVectorReadTest(){ @@ -339,7 +339,7 @@ void XrdEcTests::Init( bool usecrc32c ) ss << std::setfill('0') << std::setw( 2 ) << i; std::string strp = datadir + '/' + ss.str() + '/'; objcfg->plgr.emplace_back( strp ); - EXPECT_TRUE( mkdir( strp.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH ) == 0 ); + EXPECT_EQ( mkdir( strp.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH ), 0 ); } } @@ -378,19 +378,16 @@ void XrdEcTests::CorruptChunk( size_t blknb, size_t strpnb ) XrdZip::CDFH &cdfh = *cdvec[cdmap[fn]]; uint64_t offset = cdfh.offset + lfhsize + fn.size(); // offset of the data XrdCl::File f; - XrdCl::XRootDStatus status2 = f.Open( url, XrdCl::OpenFlags::Write ); - GTEST_ASSERT_XRDST( status2 ); + GTEST_ASSERT_XRDST( f.Open( url, XrdCl::OpenFlags::Write ) ); std::string str = "XXXXXXXX"; - status2 = f.Write( offset, str.size(), str.c_str() ); - GTEST_ASSERT_XRDST( status2 ); - status2 = f.Close(); - GTEST_ASSERT_XRDST( status2 ); + GTEST_ASSERT_XRDST( f.Write( offset, str.size(), str.c_str() ) ); + GTEST_ASSERT_XRDST( f.Close() ); } void XrdEcTests::UrlNotReachable( size_t index ) { XrdCl::URL url( objcfg->plgr[index] ); - EXPECT_TRUE( chmod( url.GetPath().c_str(), 0 ) == 0 ); + EXPECT_EQ( chmod( url.GetPath().c_str(), 0 ), 0 ); } void XrdEcTests::UrlReachable( size_t index ) @@ -398,7 +395,7 @@ void XrdEcTests::UrlReachable( size_t index ) XrdCl::URL url( objcfg->plgr[index] ); mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH | S_IXUSR | S_IXGRP | S_IXOTH; - EXPECT_TRUE( chmod( url.GetPath().c_str(), mode ) == 0 ); + EXPECT_EQ( chmod( url.GetPath().c_str(), mode ), 0 ); } void XrdEcTests::CorruptedReadVerify() @@ -453,9 +450,9 @@ void XrdEcTests::VerifyVectorRead(uint32_t seed){ GTEST_ASSERT_XRDST( *status ); delete status; for(int i = 0; i < 5; i++){ - std::string result(buffers[i].data(), expected[i].size()); - EXPECT_TRUE( result == expected[i] ); - } + std::string result(buffers[i].data(), expected[i].size()); + EXPECT_EQ( result, expected[i] ); + } XrdCl::SyncResponseHandler handler2; reader.Close( &handler2 ); @@ -472,7 +469,7 @@ void XrdEcTests::IllegalVectorRead(uint32_t seed){ reader.Open(&handler1); handler1.WaitForResponse(); XrdCl::XRootDStatus *status = handler1.GetStatus(); - GTEST_ASSERT_XRDST(*status); + GTEST_ASSERT_XRDST( *status ); delete status; std::default_random_engine random_engine(seed); @@ -500,10 +497,7 @@ void XrdEcTests::IllegalVectorRead(uint32_t seed){ h.WaitForResponse(); status = h.GetStatus(); // the response should be negative since one of the reads was over the file end - if (status->IsOK()) - { - EXPECT_TRUE(false); - } + EXPECT_FALSE(status->IsOK()); delete status; buffers.clear(); @@ -528,17 +522,14 @@ void XrdEcTests::IllegalVectorRead(uint32_t seed){ h2.WaitForResponse(); status = h2.GetStatus(); // the response should be negative since we requested too many reads - if (status->IsOK()) - { - EXPECT_TRUE(false); - } + EXPECT_FALSE(status->IsOK()); delete status; XrdCl::SyncResponseHandler handler2; reader.Close(&handler2); handler2.WaitForResponse(); status = handler2.GetStatus(); - GTEST_ASSERT_XRDST(*status); + GTEST_ASSERT_XRDST( *status ); delete status; } @@ -576,7 +567,7 @@ void XrdEcTests::ReadVerify( uint32_t rdsize, uint64_t maxrd ) if( rawoff + rawsz > rawdata.size() ) rawsz = rawdata.size() - rawoff; std::string expected( rawdata.data() + rawoff, rawsz ); // make sure the expected and actual results are the same - EXPECT_TRUE( result == expected ); + EXPECT_EQ( result, expected ); delete status; delete rsp; rdoff += bytesrd; @@ -632,7 +623,7 @@ void XrdEcTests::RandomReadVerify() else if( rawoff + rawlen > rawdata.size() ) rawlen = rawdata.size() - rawoff; std::string expected( rawdata.data() + rawoff, rawlen ); // make sure the expected and actual results are the same - EXPECT_TRUE( result == expected ); + EXPECT_EQ( result, expected ); delete status; delete rsp; delete[] rdbuff; @@ -666,8 +657,7 @@ void XrdEcTests::Corrupted1stBlkReadVerify() reader.Read( rdoff, rdlen, rdbuff, &h, 0 ); h.WaitForResponse(); status = h.GetStatus(); - EXPECT_TRUE( status->status == XrdCl::stError && - status->code == XrdCl::errDataError ); + GTEST_ASSERT_XRDST_NOTOK( *status, XrdCl::errDataError ); delete status; delete[] rdbuff; @@ -683,7 +673,7 @@ void XrdEcTests::Corrupted1stBlkReadVerify() int unlink_cb(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf) { int rc = remove( fpath ); - EXPECT_TRUE( rc == 0 ); + EXPECT_EQ( rc, 0 ); return rc; } From 33031b5b16e7c47b862a0961501f4c2e16f72381 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 18 May 2018 11:30:58 +0200 Subject: [PATCH 342/442] [XrdSsi] Remove declarations of crc32 and adler32 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These break compilation with the error below: xrootd-4.8.3/tests/XrdSsiTests/XrdShMap.cc: In function ‘int DoA32(const char*)’: xrootd-4.8.3/tests/XrdSsiTests/XrdShMap.cc:418:34: error: expected initializer before ‘OF’ ZEXTERN uLong ZEXPORT adler32 OF((uLong adler, const Bytef *buf, uInt len)); Original patch: https://gitweb.gentoo.org/repo/gentoo.git/commit/?id=518dbf0b81b27225e09bc939ba6af14a15830922 --- src/XrdSsi/XrdSsiShMam.cc | 2 +- tests/XrdSsiTests/XrdShMap.cc | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/XrdSsi/XrdSsiShMam.cc b/src/XrdSsi/XrdSsiShMam.cc index 3b707447824..f664d6bcbde 100644 --- a/src/XrdSsi/XrdSsiShMam.cc +++ b/src/XrdSsi/XrdSsiShMam.cc @@ -890,7 +890,7 @@ bool XrdSsiShMam::GetItem(void *data, const char *key, int hash) /******************************************************************************/ int XrdSsiShMam::HashVal(const char *key) -{ ZEXTERN uLong ZEXPORT crc32 OF((uLong crc, const Bytef *buf, uInt len)); +{ uLong crc; int hval, klen = strlen(key); diff --git a/tests/XrdSsiTests/XrdShMap.cc b/tests/XrdSsiTests/XrdShMap.cc index 14fe81e4faa..709ed8467f8 100644 --- a/tests/XrdSsiTests/XrdShMap.cc +++ b/tests/XrdSsiTests/XrdShMap.cc @@ -423,7 +423,6 @@ void Explain(const char *what) int DoA32(const char *buff) { - ZEXTERN uLong ZEXPORT adler32 OF((uLong adler, const Bytef *buf, uInt len)); uLong adler = adler32(0L, Z_NULL, 0); // Check for ID request now @@ -446,8 +445,6 @@ int DoA32(const char *buff) int DoC32(const char *buff) { - ZEXTERN uLong ZEXPORT crc32 OF((uLong crc, const Bytef *buf, uInt len)); - // Check for ID request now // if (!buff) {int myID; memcpy(&myID, "c32 ", sizeof(int)); return myID;} From d2abe4eefbf17e7d9ad60cc9d8cac266c34f6aa1 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 3 Nov 2023 15:28:46 +0100 Subject: [PATCH 343/442] [CI] Remove old docker-based jobs from GitLab CI The tests can now be run locally, without the need to build docker images. --- .gitlab-ci.yml | 109 ------------------------------------------------- 1 file changed, 109 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6440ea7f6cc..0ea9feb0b25 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,8 +1,5 @@ stages: - build:rpm - - build:dockerimage:prepare - - build:dockerimage - - test - publish - post:publish - clean @@ -513,112 +510,6 @@ weekly:cc7: except: - tags -xrootd_docker_get: - stage: build:dockerimage:prepare - image: gitlab-registry.cern.ch/linuxsupport/cc7-base - script: - - yum install -y git - - git clone https://gitlab.cern.ch/eos/xrootd-docker.git - - if [ ! -d "epel-7" ]; then mkdir epel-7; cp cc-7/* epel-7; fi - artifacts: - expire_in: 1 day - paths: - - xrootd-docker/ - - epel-7/ - tags: - - docker_node - only: - - web - - schedules - except: - - tags - -xrootd_dockerimage: - stage: build:dockerimage - tags: - - docker-image-build - script: - - "" - variables: - TO: gitlab-registry.cern.ch/dss/xrootd - DOCKER_FILE: xrootd-docker/Dockerfile.ci - dependencies: - - xrootd_docker_get - only: - - schedules - - web - except: - - tags - -xrootd_docker_test: - stage: test - script: - - docker pull gitlab-registry.cern.ch/dss/xrootd - - cd xrootd-docker - - yum -y install wget - - sudo ./start.sh -i gitlab-registry.cern.ch/dss/xrootd - - docker exec metaman text-runner /usr/lib64/libXrdClTests.so "All Tests/UtilsTest/" - - docker exec metaman text-runner /usr/lib64/libXrdClTests.so "All Tests/SocketTest/" - - docker exec metaman text-runner /usr/lib64/libXrdClTests.so "All Tests/PollerTest/" - - docker exec metaman text-runner /usr/lib64/libXrdClTests.so "All Tests/PostMasterTest/" - - docker exec metaman text-runner /usr/lib64/libXrdClTests.so "All Tests/FileSystemTest/" -## - docker exec metaman text-runner /usr/lib64/libXrdClTests.so "All Tests/FileTest/" - - docker exec metaman text-runner /usr/lib64/libXrdClTests.so "All Tests/LocalFileHandlerTest/" - - after_script: - - sudo ./xrootd-docker/clean.sh - tags: - - shell-with-docker - dependencies: - - xrootd_docker_get - only: - - schedules - - web - except: - - tags - allow_failure: true - -pyxrootd_dockerimage: - stage: build:dockerimage - tags: - - docker-image-build - script: - - "" - variables: - TO: gitlab-registry.cern.ch/dss/xrootd:pylatest - DOCKER_FILE: xrootd-docker/Dockerfile-python.ci - dependencies: - - xrootd_docker_get - only: - - schedules - - web - except: - - tags - allow_failure: true - -pyxrootd_docker_test: - stage: test - script: - - docker pull gitlab-registry.cern.ch/dss/xrootd:pylatest - - sudo docker run -dit --privileged -e "container=docker" --name pyxrootd-container -h pyxrootd-container gitlab-registry.cern.ch/dss/xrootd:pylatest /sbin/init - - docker exec pyxrootd-container systemctl start xrootd@standalone - - docker exec pyxrootd-container sh -c "cd xrootd/bindings/python/tests && pytest test_file.py test_filesystem.py test_copy.py test_threads.py test_url.py" - - after_script: - - sudo docker rm -f pyxrootd-container - - sudo docker rmi -f gitlab-registry.cern.ch/dss/xrootd:pylatest - - tags: - - shell-with-docker - dependencies: - - xrootd_docker_get - only: - - schedules - - web - except: - - tags - allow_failure: true - publish:rhel: stage: publish image: gitlab-registry.cern.ch/linuxsupport/cc7-base From 896f95c382b3bde7d7c6d184649a7e145d9f3419 Mon Sep 17 00:00:00 2001 From: Jonas Hahnfeld Date: Mon, 4 Dec 2023 11:51:44 +0100 Subject: [PATCH 344/442] [CMake] Trace actual variables --- cmake/XRootDConfig.cmake.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/XRootDConfig.cmake.in b/cmake/XRootDConfig.cmake.in index 971b415a704..0f95a8f54dc 100644 --- a/cmake/XRootDConfig.cmake.in +++ b/cmake/XRootDConfig.cmake.in @@ -174,8 +174,8 @@ message(TRACE "XRootD_VERSION_NUMBER = '${XRootD_VERSION_NUMBER}'") message(TRACE "XRootD_FOUND = '${XRootD_FOUND}'") message(TRACE "XRootD_INSTALL_PREFIX = '@CMAKE_INSTALL_PREFIX@'") -message(TRACE "XRootD_INCLUDE_DIR = '@PACKAGE_CMAKE_INSTALL_INCLUDEDIR@'") -message(TRACE "XRootD_LIBRARY_DIR = '@PACKAGE_CMAKE_INSTALL_LIBDIR@'") +message(TRACE "XRootD_INCLUDE_DIR = '${XRootD_INCLUDE_DIR}'") +message(TRACE "XRootD_LIBRARY_DIR = '${XRootD_LIBRARY_DIR}'") message(TRACE "XRootD_LIBRARIES = '${XRootD_LIBRARIES}'") foreach(COMPONENT UTILS CLIENT SERVER HTTP POSIX SSI) From ccedaabc377e668280eec8bb22f0dbb76550beec Mon Sep 17 00:00:00 2001 From: Jonas Hahnfeld Date: Mon, 4 Dec 2023 11:52:44 +0100 Subject: [PATCH 345/442] [CMake] Fix include path in XRootDConfig.cmake Headers are installed into the subdirectory xrootd/. This was broken with commit c6e0e598ce ("[CMake] Find only XRootD matching XRootDConfig.cmake installation path") released with version 5.6.3. --- cmake/XRootDConfig.cmake.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/XRootDConfig.cmake.in b/cmake/XRootDConfig.cmake.in index 0f95a8f54dc..0a4ce3fe557 100644 --- a/cmake/XRootDConfig.cmake.in +++ b/cmake/XRootDConfig.cmake.in @@ -50,7 +50,7 @@ SET( XROOTD_SSI_FOUND FALSE ) set_and_check(XRootD_CMAKE_DIR "${CMAKE_CURRENT_LIST_DIR}") set_and_check(XRootD_DATA_DIR "@PACKAGE_CMAKE_INSTALL_DATADIR@") -set_and_check(XRootD_INCLUDE_DIR "@PACKAGE_CMAKE_INSTALL_INCLUDEDIR@") +set_and_check(XRootD_INCLUDE_DIR "@PACKAGE_CMAKE_INSTALL_INCLUDEDIR@/xrootd") set_and_check(XRootD_LIB_DIR "@PACKAGE_CMAKE_INSTALL_LIBDIR@") set(XRootD_INCLUDE_DIRS "${XRootD_INCLUDE_DIR};${XRootD_INCLUDE_DIR}/private") From 760c8b52dd069e8987cde7e49382baef14d86b51 Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Thu, 30 Nov 2023 16:28:35 +0100 Subject: [PATCH 346/442] [XrdHttp] Modified SciTags min and max value according to the specification (64 < scitag.flow < 65536) --- src/XrdHttp/XrdHttpReq.cc | 2 +- src/XrdNet/XrdNetPMark.hh | 21 ++++++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/XrdHttp/XrdHttpReq.cc b/src/XrdHttp/XrdHttpReq.cc index cd645ad7d40..0f77be0196c 100644 --- a/src/XrdHttp/XrdHttpReq.cc +++ b/src/XrdHttp/XrdHttpReq.cc @@ -234,7 +234,7 @@ void XrdHttpReq::parseScitag(const std::string & val) { if(scitagS[0] != '-') { try { mScitag = std::stoi(scitagS.c_str(), nullptr, 10); - if (mScitag > XrdNetPMark::maxTotID || mScitag < 0) { + if (mScitag > XrdNetPMark::maxTotID || mScitag < XrdNetPMark::minTotID) { mScitag = 0; } } catch (...) { diff --git a/src/XrdNet/XrdNetPMark.hh b/src/XrdNet/XrdNetPMark.hh index e62ece457e5..8a99ce22664 100644 --- a/src/XrdNet/XrdNetPMark.hh +++ b/src/XrdNet/XrdNetPMark.hh @@ -71,19 +71,22 @@ virtual Handle *Begin(XrdNetAddrInfo &addr, Handle &handle, static bool getEA(const char *cgi, int &ecode, int &acode); XrdNetPMark() {} - -static const int maxTotID = 0x7fff; - -protected: +virtual ~XrdNetPMark() {} // This object cannot be deleted! // ID limits and specifications // -static const int btsActID = 6; -static const int mskActID = 63; -static const int maxActID = 63; +/** + * From the specifications: Valid value for scitag is a single positive integer > 64 and <65536 (16bit). Any other value is considered invalid. + */ +static const int minTotID = 65; +static const int maxTotID = 65535; -static const int maxExpID = 511; +protected: + +static const int btsActID = 6; +static const int mskActID = 63; +static const int maxExpID = maxTotID >> btsActID; +static const int maxActID = maxTotID & mskActID; -virtual ~XrdNetPMark() {} // This object cannot be deleted! }; #endif From f4851db98f3ba68a778d870de9bf84f56962c669 Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Fri, 1 Dec 2023 09:46:35 +0100 Subject: [PATCH 347/442] [XrdNet] PMarks - Modified the configuration to check for min values for expID and actID Also modified the validation of the scitag provided by the user. Checks whether the expID and actID are within the min and max values. In the case the provided value is not correct, the packets will be marked with a scitag = 0 (expId = 0 and actId = 0) --- src/XrdNet/XrdNetPMark.cc | 33 ++++++++++++++++++--------------- src/XrdNet/XrdNetPMark.hh | 10 +++++++--- src/XrdNet/XrdNetPMarkCfg.cc | 4 ++-- 3 files changed, 27 insertions(+), 20 deletions(-) diff --git a/src/XrdNet/XrdNetPMark.cc b/src/XrdNet/XrdNetPMark.cc index d9e00d99fc1..d50f3cf6fb6 100644 --- a/src/XrdNet/XrdNetPMark.cc +++ b/src/XrdNet/XrdNetPMark.cc @@ -40,24 +40,27 @@ bool XrdNetPMark::getEA(const char *cgi, int &ecode, int &acode) { + ecode = acode = 0; // If we have cgi, see if we can extract rge codes from there // - if (cgi) - {const char *stP = strstr(cgi, "scitag.flow="); - if (stP) - {char *eol; - int eacode = strtol(stP+12, &eol, 10); - if (eacode >= 0 && eacode <= XrdNetPMark::maxTotID - && (*eol == '&' || *eol ==0)) - {ecode = eacode >> XrdNetPMark::btsActID; - acode = eacode & XrdNetPMark::mskActID; - return true; - } - } + if (cgi) { + const char *stP = strstr(cgi, "scitag.flow="); + if (stP) { + char *eol; + int eacode = strtol(stP + 12, &eol, 10); + if (*eol == '&' || *eol == 0) { + if (eacode >= XrdNetPMark::minTotID && eacode <= XrdNetPMark::maxTotID) { + ecode = eacode >> XrdNetPMark::btsActID; + acode = eacode & XrdNetPMark::mskActID; + } + // According to the specification, if the provided scitag.flow has an incorrect value + // the packets will be marked with a scitag = 0 + return true; } + } + } -// No go -// - ecode = acode = 0; + // No go + // return false; } diff --git a/src/XrdNet/XrdNetPMark.hh b/src/XrdNet/XrdNetPMark.hh index 8a99ce22664..5787d2fe74d 100644 --- a/src/XrdNet/XrdNetPMark.hh +++ b/src/XrdNet/XrdNetPMark.hh @@ -30,6 +30,8 @@ /* specific prior written permission of the institution or contributor. */ /******************************************************************************/ +#include + class XrdNetAddrInfo; class XrdSecEntity; @@ -41,11 +43,11 @@ class Handle {public: bool getEA(int &ec, int &ac) - {if (eCode >= 0) {ec = eCode; ac = aCode; return true;} + {if (Valid()) {ec = eCode; ac = aCode; return true;} ec = ac = 0; return false; } - - bool Valid() {return eCode >= 0;} + // According to the specifications, ExpID and actID can be equal to 0 for HTTP-TPC. + bool Valid() {return (eCode == 0 && aCode == 0) || (eCode >= minExpID && eCode <= maxExpID && aCode >= minActID && aCode <= maxActID);} Handle(const char *app=0, int ecode=0, int acode=0) : appName(app), eCode(ecode), aCode(acode) {} @@ -85,6 +87,8 @@ protected: static const int btsActID = 6; static const int mskActID = 63; +static const int minExpID = minTotID >> btsActID; +static const int minActID = minTotID & mskActID; static const int maxExpID = maxTotID >> btsActID; static const int maxActID = maxTotID & mskActID; diff --git a/src/XrdNet/XrdNetPMarkCfg.cc b/src/XrdNet/XrdNetPMarkCfg.cc index 343fc8bd556..e49f300b987 100644 --- a/src/XrdNet/XrdNetPMarkCfg.cc +++ b/src/XrdNet/XrdNetPMarkCfg.cc @@ -897,7 +897,7 @@ bool XrdNetPMarkCfg::LoadJson(char *buff) for (auto it : j_exp) {std::string expName = it["expName"].get(); if (expName.empty()) continue; - if (!it["expId"].is_number() || it["expId"] < 0 || it["expId"] > maxExpID) + if (!it["expId"].is_number() || it["expId"] < minExpID || it["expId"] > maxExpID) {eDest->Say("Config warning: ignoring experiment '", expName.c_str(), "'; associated ID is invalid."); continue; @@ -921,7 +921,7 @@ bool XrdNetPMarkCfg::LoadJson(char *buff) {std::string actName = j_acts[i]["activityName"].get(); if (actName.empty()) continue; if (!j_acts[i]["activityId"].is_number() - || j_acts[i]["activityId"] < 0 + || j_acts[i]["activityId"] < minActID || j_acts[i]["activityId"] > maxActID) {eDest->Say("Config warning:", "ignoring ", expName.c_str(), " actitivity '", actName.c_str(), From b80015ec820a7a852b080b9e07a277f916c6b114 Mon Sep 17 00:00:00 2001 From: Mattias Ellert Date: Sun, 3 Dec 2023 20:24:37 +0100 Subject: [PATCH 348/442] Redo accidentally reverted change --- tests/XrdCl/XrdClLocalFileHandlerTest.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/XrdCl/XrdClLocalFileHandlerTest.cc b/tests/XrdCl/XrdClLocalFileHandlerTest.cc index d298a0c6fc5..a80b29180bf 100644 --- a/tests/XrdCl/XrdClLocalFileHandlerTest.cc +++ b/tests/XrdCl/XrdClLocalFileHandlerTest.cc @@ -450,7 +450,7 @@ TEST_F(LocalFileHandlerTest, XAttrTest) std::string localDataPath; EXPECT_TRUE( testEnv->GetString( "LocalDataPath", localDataPath ) ); - char resolved_path[PATH_MAX]; + char resolved_path[MAXPATHLEN]; localDataPath = realpath(localDataPath.c_str(), resolved_path); std::string targetURL = localDataPath + "/metaman/lfilehandlertestfilexattr"; From 9ae7ef137b8b790a1072107179e339396fe0e8a6 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 1 Dec 2023 16:55:16 +0100 Subject: [PATCH 349/442] [Tests] Move cluster test configuration one level up The motivation for this is to make paths to sockets created by XRootD at initialization shorter, because in certain cases (i.e. in CI and when running with package managers) the paths may become too long and cause the tests to fail. Too long in this case means exceeding the size in struct sockaddr_un(3type), which can be quite small: $ root.exe -l root [0] #include root [1] sizeof(struct sockaddr_un) (unsigned long) 110 root [2] struct sockaddr_un addr; root [3] sizeof(addr.sun_path) (unsigned long) 108 --- tests/CMakeLists.txt | 1 + tests/XRootD/CMakeLists.txt | 2 -- tests/{XRootD => }/cluster/CMakeLists.txt | 0 tests/{XRootD => }/cluster/configs/xrootd_man1.cfg | 0 tests/{XRootD => }/cluster/configs/xrootd_man2.cfg | 0 .../{XRootD => }/cluster/configs/xrootd_metaman.cfg | 0 tests/{XRootD => }/cluster/configs/xrootd_srv1.cfg | 0 tests/{XRootD => }/cluster/configs/xrootd_srv2.cfg | 0 tests/{XRootD => }/cluster/configs/xrootd_srv3.cfg | 0 tests/{XRootD => }/cluster/configs/xrootd_srv4.cfg | 0 tests/{XRootD => }/cluster/mvdata/data.zip | Bin tests/{XRootD => }/cluster/mvdata/input1.meta4 | 0 tests/{XRootD => }/cluster/mvdata/input1.metalink | 0 tests/{XRootD => }/cluster/mvdata/input2.meta4 | 0 tests/{XRootD => }/cluster/mvdata/input2.metalink | 0 tests/{XRootD => }/cluster/mvdata/input3.meta4 | 0 tests/{XRootD => }/cluster/mvdata/input3.metalink | 0 tests/{XRootD => }/cluster/mvdata/input4.meta4 | 0 tests/{XRootD => }/cluster/mvdata/input4.metalink | 0 tests/{XRootD => }/cluster/mvdata/input5.meta4 | 0 tests/{XRootD => }/cluster/mvdata/input5.metalink | 0 tests/{XRootD => }/cluster/mvdata/large.zip | Bin tests/{XRootD => }/cluster/mvdata/mlFileTest1.meta4 | 0 tests/{XRootD => }/cluster/mvdata/mlFileTest2.meta4 | 0 tests/{XRootD => }/cluster/mvdata/mlFileTest3.meta4 | 0 tests/{XRootD => }/cluster/mvdata/mlFileTest4.meta4 | 0 tests/{XRootD => }/cluster/mvdata/mlTpcTest.meta4 | 0 tests/{XRootD => }/cluster/mvdata/mlZipTest.meta4 | 0 tests/{XRootD => }/cluster/setup.sh | 0 tests/{XRootD => }/cluster/smoketest-clustered.sh | 0 tests/common/TestEnv.cc | 2 +- 31 files changed, 2 insertions(+), 3 deletions(-) rename tests/{XRootD => }/cluster/CMakeLists.txt (100%) rename tests/{XRootD => }/cluster/configs/xrootd_man1.cfg (100%) rename tests/{XRootD => }/cluster/configs/xrootd_man2.cfg (100%) rename tests/{XRootD => }/cluster/configs/xrootd_metaman.cfg (100%) rename tests/{XRootD => }/cluster/configs/xrootd_srv1.cfg (100%) rename tests/{XRootD => }/cluster/configs/xrootd_srv2.cfg (100%) rename tests/{XRootD => }/cluster/configs/xrootd_srv3.cfg (100%) rename tests/{XRootD => }/cluster/configs/xrootd_srv4.cfg (100%) rename tests/{XRootD => }/cluster/mvdata/data.zip (100%) rename tests/{XRootD => }/cluster/mvdata/input1.meta4 (100%) rename tests/{XRootD => }/cluster/mvdata/input1.metalink (100%) rename tests/{XRootD => }/cluster/mvdata/input2.meta4 (100%) rename tests/{XRootD => }/cluster/mvdata/input2.metalink (100%) rename tests/{XRootD => }/cluster/mvdata/input3.meta4 (100%) rename tests/{XRootD => }/cluster/mvdata/input3.metalink (100%) rename tests/{XRootD => }/cluster/mvdata/input4.meta4 (100%) rename tests/{XRootD => }/cluster/mvdata/input4.metalink (100%) rename tests/{XRootD => }/cluster/mvdata/input5.meta4 (100%) rename tests/{XRootD => }/cluster/mvdata/input5.metalink (100%) rename tests/{XRootD => }/cluster/mvdata/large.zip (100%) rename tests/{XRootD => }/cluster/mvdata/mlFileTest1.meta4 (100%) rename tests/{XRootD => }/cluster/mvdata/mlFileTest2.meta4 (100%) rename tests/{XRootD => }/cluster/mvdata/mlFileTest3.meta4 (100%) rename tests/{XRootD => }/cluster/mvdata/mlFileTest4.meta4 (100%) rename tests/{XRootD => }/cluster/mvdata/mlTpcTest.meta4 (100%) rename tests/{XRootD => }/cluster/mvdata/mlZipTest.meta4 (100%) rename tests/{XRootD => }/cluster/setup.sh (100%) rename tests/{XRootD => }/cluster/smoketest-clustered.sh (100%) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c2712245cff..d5218f91ddc 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -16,3 +16,4 @@ if( BUILD_CEPH ) endif() add_subdirectory( XRootD ) +add_subdirectory( cluster ) diff --git a/tests/XRootD/CMakeLists.txt b/tests/XRootD/CMakeLists.txt index 9af40697dac..8bae4faba96 100644 --- a/tests/XRootD/CMakeLists.txt +++ b/tests/XRootD/CMakeLists.txt @@ -32,5 +32,3 @@ add_test(NAME XRootD::smoke-test set_tests_properties(XRootD::smoke-test PROPERTIES ENVIRONMENT "${XRDENV}" FIXTURES_REQUIRED XRootD) - -add_subdirectory(cluster) diff --git a/tests/XRootD/cluster/CMakeLists.txt b/tests/cluster/CMakeLists.txt similarity index 100% rename from tests/XRootD/cluster/CMakeLists.txt rename to tests/cluster/CMakeLists.txt diff --git a/tests/XRootD/cluster/configs/xrootd_man1.cfg b/tests/cluster/configs/xrootd_man1.cfg similarity index 100% rename from tests/XRootD/cluster/configs/xrootd_man1.cfg rename to tests/cluster/configs/xrootd_man1.cfg diff --git a/tests/XRootD/cluster/configs/xrootd_man2.cfg b/tests/cluster/configs/xrootd_man2.cfg similarity index 100% rename from tests/XRootD/cluster/configs/xrootd_man2.cfg rename to tests/cluster/configs/xrootd_man2.cfg diff --git a/tests/XRootD/cluster/configs/xrootd_metaman.cfg b/tests/cluster/configs/xrootd_metaman.cfg similarity index 100% rename from tests/XRootD/cluster/configs/xrootd_metaman.cfg rename to tests/cluster/configs/xrootd_metaman.cfg diff --git a/tests/XRootD/cluster/configs/xrootd_srv1.cfg b/tests/cluster/configs/xrootd_srv1.cfg similarity index 100% rename from tests/XRootD/cluster/configs/xrootd_srv1.cfg rename to tests/cluster/configs/xrootd_srv1.cfg diff --git a/tests/XRootD/cluster/configs/xrootd_srv2.cfg b/tests/cluster/configs/xrootd_srv2.cfg similarity index 100% rename from tests/XRootD/cluster/configs/xrootd_srv2.cfg rename to tests/cluster/configs/xrootd_srv2.cfg diff --git a/tests/XRootD/cluster/configs/xrootd_srv3.cfg b/tests/cluster/configs/xrootd_srv3.cfg similarity index 100% rename from tests/XRootD/cluster/configs/xrootd_srv3.cfg rename to tests/cluster/configs/xrootd_srv3.cfg diff --git a/tests/XRootD/cluster/configs/xrootd_srv4.cfg b/tests/cluster/configs/xrootd_srv4.cfg similarity index 100% rename from tests/XRootD/cluster/configs/xrootd_srv4.cfg rename to tests/cluster/configs/xrootd_srv4.cfg diff --git a/tests/XRootD/cluster/mvdata/data.zip b/tests/cluster/mvdata/data.zip similarity index 100% rename from tests/XRootD/cluster/mvdata/data.zip rename to tests/cluster/mvdata/data.zip diff --git a/tests/XRootD/cluster/mvdata/input1.meta4 b/tests/cluster/mvdata/input1.meta4 similarity index 100% rename from tests/XRootD/cluster/mvdata/input1.meta4 rename to tests/cluster/mvdata/input1.meta4 diff --git a/tests/XRootD/cluster/mvdata/input1.metalink b/tests/cluster/mvdata/input1.metalink similarity index 100% rename from tests/XRootD/cluster/mvdata/input1.metalink rename to tests/cluster/mvdata/input1.metalink diff --git a/tests/XRootD/cluster/mvdata/input2.meta4 b/tests/cluster/mvdata/input2.meta4 similarity index 100% rename from tests/XRootD/cluster/mvdata/input2.meta4 rename to tests/cluster/mvdata/input2.meta4 diff --git a/tests/XRootD/cluster/mvdata/input2.metalink b/tests/cluster/mvdata/input2.metalink similarity index 100% rename from tests/XRootD/cluster/mvdata/input2.metalink rename to tests/cluster/mvdata/input2.metalink diff --git a/tests/XRootD/cluster/mvdata/input3.meta4 b/tests/cluster/mvdata/input3.meta4 similarity index 100% rename from tests/XRootD/cluster/mvdata/input3.meta4 rename to tests/cluster/mvdata/input3.meta4 diff --git a/tests/XRootD/cluster/mvdata/input3.metalink b/tests/cluster/mvdata/input3.metalink similarity index 100% rename from tests/XRootD/cluster/mvdata/input3.metalink rename to tests/cluster/mvdata/input3.metalink diff --git a/tests/XRootD/cluster/mvdata/input4.meta4 b/tests/cluster/mvdata/input4.meta4 similarity index 100% rename from tests/XRootD/cluster/mvdata/input4.meta4 rename to tests/cluster/mvdata/input4.meta4 diff --git a/tests/XRootD/cluster/mvdata/input4.metalink b/tests/cluster/mvdata/input4.metalink similarity index 100% rename from tests/XRootD/cluster/mvdata/input4.metalink rename to tests/cluster/mvdata/input4.metalink diff --git a/tests/XRootD/cluster/mvdata/input5.meta4 b/tests/cluster/mvdata/input5.meta4 similarity index 100% rename from tests/XRootD/cluster/mvdata/input5.meta4 rename to tests/cluster/mvdata/input5.meta4 diff --git a/tests/XRootD/cluster/mvdata/input5.metalink b/tests/cluster/mvdata/input5.metalink similarity index 100% rename from tests/XRootD/cluster/mvdata/input5.metalink rename to tests/cluster/mvdata/input5.metalink diff --git a/tests/XRootD/cluster/mvdata/large.zip b/tests/cluster/mvdata/large.zip similarity index 100% rename from tests/XRootD/cluster/mvdata/large.zip rename to tests/cluster/mvdata/large.zip diff --git a/tests/XRootD/cluster/mvdata/mlFileTest1.meta4 b/tests/cluster/mvdata/mlFileTest1.meta4 similarity index 100% rename from tests/XRootD/cluster/mvdata/mlFileTest1.meta4 rename to tests/cluster/mvdata/mlFileTest1.meta4 diff --git a/tests/XRootD/cluster/mvdata/mlFileTest2.meta4 b/tests/cluster/mvdata/mlFileTest2.meta4 similarity index 100% rename from tests/XRootD/cluster/mvdata/mlFileTest2.meta4 rename to tests/cluster/mvdata/mlFileTest2.meta4 diff --git a/tests/XRootD/cluster/mvdata/mlFileTest3.meta4 b/tests/cluster/mvdata/mlFileTest3.meta4 similarity index 100% rename from tests/XRootD/cluster/mvdata/mlFileTest3.meta4 rename to tests/cluster/mvdata/mlFileTest3.meta4 diff --git a/tests/XRootD/cluster/mvdata/mlFileTest4.meta4 b/tests/cluster/mvdata/mlFileTest4.meta4 similarity index 100% rename from tests/XRootD/cluster/mvdata/mlFileTest4.meta4 rename to tests/cluster/mvdata/mlFileTest4.meta4 diff --git a/tests/XRootD/cluster/mvdata/mlTpcTest.meta4 b/tests/cluster/mvdata/mlTpcTest.meta4 similarity index 100% rename from tests/XRootD/cluster/mvdata/mlTpcTest.meta4 rename to tests/cluster/mvdata/mlTpcTest.meta4 diff --git a/tests/XRootD/cluster/mvdata/mlZipTest.meta4 b/tests/cluster/mvdata/mlZipTest.meta4 similarity index 100% rename from tests/XRootD/cluster/mvdata/mlZipTest.meta4 rename to tests/cluster/mvdata/mlZipTest.meta4 diff --git a/tests/XRootD/cluster/setup.sh b/tests/cluster/setup.sh similarity index 100% rename from tests/XRootD/cluster/setup.sh rename to tests/cluster/setup.sh diff --git a/tests/XRootD/cluster/smoketest-clustered.sh b/tests/cluster/smoketest-clustered.sh similarity index 100% rename from tests/XRootD/cluster/smoketest-clustered.sh rename to tests/cluster/smoketest-clustered.sh diff --git a/tests/common/TestEnv.cc b/tests/common/TestEnv.cc index 3e64307de2a..e389ab82de2 100644 --- a/tests/common/TestEnv.cc +++ b/tests/common/TestEnv.cc @@ -42,7 +42,7 @@ TestEnv::TestEnv() PutString( "RemoteFile", "/data/cb4aacf1-6f28-42f2-b68a-90a73460f424.dat" ); PutString( "LocalFile", "/data/testFile.dat" ); PutString( "MultiIPServerURL", "multiip:1099" ); - PutString( "LocalDataPath", "../XRootD/cluster/xrd-data" ); + PutString( "LocalDataPath", "../cluster/xrd-data" ); ImportString( "MainServerURL", "XRDTEST_MAINSERVERURL" ); ImportString( "DiskServerURL", "XRDTEST_DISKSERVERURL" ); ImportString( "Manager1URL", "XRDTEST_MANAGER1URL" ); From 17fdaffce1d6d456069b29301aac3dce0015b070 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 1 Dec 2023 17:01:34 +0100 Subject: [PATCH 350/442] [Tests] Use XRootD namespacing to simplify paths in cluster setup --- tests/XrdCl/XrdClLocalFileHandlerTest.cc | 2 +- tests/cluster/configs/xrootd_man1.cfg | 5 +++-- tests/cluster/configs/xrootd_man2.cfg | 5 +++-- tests/cluster/configs/xrootd_metaman.cfg | 5 +++-- tests/cluster/configs/xrootd_srv1.cfg | 5 +++-- tests/cluster/configs/xrootd_srv2.cfg | 5 +++-- tests/cluster/configs/xrootd_srv3.cfg | 5 +++-- tests/cluster/configs/xrootd_srv4.cfg | 5 +++-- tests/cluster/setup.sh | 13 +++++-------- tests/common/TestEnv.cc | 2 +- 10 files changed, 28 insertions(+), 24 deletions(-) diff --git a/tests/XrdCl/XrdClLocalFileHandlerTest.cc b/tests/XrdCl/XrdClLocalFileHandlerTest.cc index a80b29180bf..23ee197303d 100644 --- a/tests/XrdCl/XrdClLocalFileHandlerTest.cc +++ b/tests/XrdCl/XrdClLocalFileHandlerTest.cc @@ -444,7 +444,7 @@ TEST_F(LocalFileHandlerTest, XAttrTest) // Initialize // (we do the test in /data as /tmp might be on tpmfs, // which does not support xattrs) - // In this case, /data is /xrd-data inside of the build directory + // In this case, /data is /data inside of the build directory //---------------------------------------------------------------------------- Env *testEnv = TestEnv::GetEnv(); std::string localDataPath; diff --git a/tests/cluster/configs/xrootd_man1.cfg b/tests/cluster/configs/xrootd_man1.cfg index bb3c6464e6f..a4531d26005 100644 --- a/tests/cluster/configs/xrootd_man1.cfg +++ b/tests/cluster/configs/xrootd_man1.cfg @@ -12,8 +12,9 @@ all.role manager all.manager meta localhost:20940 all.manager localhost:20941 -oss.localroot @CMAKE_CURRENT_BINARY_DIR@/xrd-data/man1 -all.adminpath @CMAKE_CURRENT_BINARY_DIR@/xrd-adm/man1 +oss.localroot @CMAKE_CURRENT_BINARY_DIR@/data/man1 +all.adminpath @CMAKE_CURRENT_BINARY_DIR@ +all.pidpath @CMAKE_CURRENT_BINARY_DIR@ all.sitename XRootDman1 ofs.ckslib zcrc32 @CMAKE_BINARY_DIR@/src/libXrdCksCalczcrc32.so diff --git a/tests/cluster/configs/xrootd_man2.cfg b/tests/cluster/configs/xrootd_man2.cfg index 35852a54fbe..042190d99b9 100644 --- a/tests/cluster/configs/xrootd_man2.cfg +++ b/tests/cluster/configs/xrootd_man2.cfg @@ -12,8 +12,9 @@ all.role manager all.manager meta localhost:20940 all.manager localhost:20942 -oss.localroot @CMAKE_CURRENT_BINARY_DIR@/xrd-data/man2 -all.adminpath @CMAKE_CURRENT_BINARY_DIR@/xrd-adm/man2 +oss.localroot @CMAKE_CURRENT_BINARY_DIR@/data/man2 +all.adminpath @CMAKE_CURRENT_BINARY_DIR@ +all.pidpath @CMAKE_CURRENT_BINARY_DIR@ all.sitename XRootDman2 ofs.ckslib zcrc32 @CMAKE_BINARY_DIR@/src/libXrdCksCalczcrc32.so diff --git a/tests/cluster/configs/xrootd_metaman.cfg b/tests/cluster/configs/xrootd_metaman.cfg index 00c2824e440..24f5248d152 100644 --- a/tests/cluster/configs/xrootd_metaman.cfg +++ b/tests/cluster/configs/xrootd_metaman.cfg @@ -11,8 +11,9 @@ all.export / all.role meta manager all.manager meta localhost:20940 -oss.localroot @CMAKE_CURRENT_BINARY_DIR@/xrd-data/metaman -all.adminpath @CMAKE_CURRENT_BINARY_DIR@/xrd-adm/metaman +oss.localroot @CMAKE_CURRENT_BINARY_DIR@/data/metaman +all.adminpath @CMAKE_CURRENT_BINARY_DIR@ +all.pidpath @CMAKE_CURRENT_BINARY_DIR@ all.sitename XRootDmetaman ofs.ckslib zcrc32 @CMAKE_BINARY_DIR@/src/libXrdCksCalczcrc32.so diff --git a/tests/cluster/configs/xrootd_srv1.cfg b/tests/cluster/configs/xrootd_srv1.cfg index 918c7850ba0..f2ad39b45ac 100644 --- a/tests/cluster/configs/xrootd_srv1.cfg +++ b/tests/cluster/configs/xrootd_srv1.cfg @@ -10,8 +10,9 @@ all.export / all.role server all.manager localhost:20941 -oss.localroot @CMAKE_CURRENT_BINARY_DIR@/xrd-data/srv1 -all.adminpath @CMAKE_CURRENT_BINARY_DIR@/xrd-adm/srv1 +oss.localroot @CMAKE_CURRENT_BINARY_DIR@/data/srv1 +all.adminpath @CMAKE_CURRENT_BINARY_DIR@ +all.pidpath @CMAKE_CURRENT_BINARY_DIR@ all.sitename XRootDsrv1 ofs.ckslib zcrc32 @CMAKE_BINARY_DIR@/src/libXrdCksCalczcrc32.so diff --git a/tests/cluster/configs/xrootd_srv2.cfg b/tests/cluster/configs/xrootd_srv2.cfg index 870b8800819..0e78cf36f37 100644 --- a/tests/cluster/configs/xrootd_srv2.cfg +++ b/tests/cluster/configs/xrootd_srv2.cfg @@ -10,8 +10,9 @@ all.export / all.role server all.manager localhost:20941 -oss.localroot @CMAKE_CURRENT_BINARY_DIR@/xrd-data/srv2 -all.adminpath @CMAKE_CURRENT_BINARY_DIR@/xrd-adm/srv2 +oss.localroot @CMAKE_CURRENT_BINARY_DIR@/data/srv2 +all.adminpath @CMAKE_CURRENT_BINARY_DIR@ +all.pidpath @CMAKE_CURRENT_BINARY_DIR@ all.sitename XRootDsrv2 ofs.ckslib zcrc32 @CMAKE_BINARY_DIR@/src/libXrdCksCalczcrc32.so diff --git a/tests/cluster/configs/xrootd_srv3.cfg b/tests/cluster/configs/xrootd_srv3.cfg index 51ebddcd944..7adfa2c9db3 100644 --- a/tests/cluster/configs/xrootd_srv3.cfg +++ b/tests/cluster/configs/xrootd_srv3.cfg @@ -10,8 +10,9 @@ all.export / all.role server all.manager localhost:20942 -oss.localroot @CMAKE_CURRENT_BINARY_DIR@/xrd-data/srv3 -all.adminpath @CMAKE_CURRENT_BINARY_DIR@/xrd-adm/srv3 +oss.localroot @CMAKE_CURRENT_BINARY_DIR@/data/srv3 +all.adminpath @CMAKE_CURRENT_BINARY_DIR@ +all.pidpath @CMAKE_CURRENT_BINARY_DIR@ all.sitename XRootDsrv3 ofs.ckslib zcrc32 @CMAKE_BINARY_DIR@/src/libXrdCksCalczcrc32.so diff --git a/tests/cluster/configs/xrootd_srv4.cfg b/tests/cluster/configs/xrootd_srv4.cfg index 51142c889bf..6bb692f8729 100644 --- a/tests/cluster/configs/xrootd_srv4.cfg +++ b/tests/cluster/configs/xrootd_srv4.cfg @@ -10,8 +10,9 @@ all.export / all.role server all.manager localhost:20942 -oss.localroot @CMAKE_CURRENT_BINARY_DIR@/xrd-data/srv4 -all.adminpath @CMAKE_CURRENT_BINARY_DIR@/xrd-adm/srv4 +oss.localroot @CMAKE_CURRENT_BINARY_DIR@/data/srv4 +all.adminpath @CMAKE_CURRENT_BINARY_DIR@ +all.pidpath @CMAKE_CURRENT_BINARY_DIR@ all.sitename XRootDsrv4 ofs.ckslib zcrc32 @CMAKE_BINARY_DIR@/src/libXrdCksCalczcrc32.so diff --git a/tests/cluster/setup.sh b/tests/cluster/setup.sh index d94a5a82cd8..d738b7f35c3 100755 --- a/tests/cluster/setup.sh +++ b/tests/cluster/setup.sh @@ -16,12 +16,10 @@ set -e servernames=("metaman" "man1" "man2" "srv1" "srv2" "srv3" "srv4") datanodes=("srv1" "srv2" "srv3" "srv4") -DATAFOLDER="./xrd-data" +DATAFOLDER="./data" TMPDATAFOLDER="./rout" PREDEF="./mvdata" -FILESFOLDER="/xrootd/docker/data" - filenames=("1db882c8-8cd6-4df1-941f-ce669bad3458.dat" "3c9a9dd8-bc75-422c-b12c-f00604486cc1.dat" "7235b5d1-cede-4700-a8f9-596506b4cc38.dat" @@ -150,20 +148,19 @@ start(){ set -x # start for each component for i in "${servernames[@]}"; do - ${XROOTD} -b -k fifo -l xrootd_${i}.log -s xrootd_${i}.pid -c configs/xrootd_${i}.cfg + ${XROOTD} -b -k fifo -n ${i} -l xrootd.log -s xrootd.pid -c configs/xrootd_${i}.cfg done # start cmsd in the redirectors for i in "${servernames[@]}"; do - ${CMSD} -b -k fifo -l cmsd_${i}.log -s cmsd_${i}.pid -c configs/xrootd_${i}.cfg + ${CMSD} -b -k fifo -n ${i} -l cmsd.log -s cmsd.pid -c configs/xrootd_${i}.cfg done } stop() { - sleep 1 for i in "${servernames[@]}"; do - kill -s TERM $(cat xrootd_${i}.pid) || true - kill -s TERM $(cat cmsd_${i}.pid) || true + kill -s TERM $(cat ${i}/xrootd.pid) || true + kill -s TERM $(cat ${i}/cmsd.pid) || true done rm -rf ${DATAFOLDER} } diff --git a/tests/common/TestEnv.cc b/tests/common/TestEnv.cc index e389ab82de2..ec6fa4f5726 100644 --- a/tests/common/TestEnv.cc +++ b/tests/common/TestEnv.cc @@ -42,7 +42,7 @@ TestEnv::TestEnv() PutString( "RemoteFile", "/data/cb4aacf1-6f28-42f2-b68a-90a73460f424.dat" ); PutString( "LocalFile", "/data/testFile.dat" ); PutString( "MultiIPServerURL", "multiip:1099" ); - PutString( "LocalDataPath", "../cluster/xrd-data" ); + PutString( "LocalDataPath", "../cluster/data" ); ImportString( "MainServerURL", "XRDTEST_MAINSERVERURL" ); ImportString( "DiskServerURL", "XRDTEST_DISKSERVERURL" ); ImportString( "Manager1URL", "XRDTEST_MANAGER1URL" ); From 351a252a83cf14f9502d4ac7ee24988dbd127033 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 5 Dec 2023 12:02:40 +0100 Subject: [PATCH 351/442] [Tests] Use XRootD namespacing and set pidpath in smoke test --- tests/XRootD/CMakeLists.txt | 4 ++-- tests/XRootD/xrootd.cfg | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/XRootD/CMakeLists.txt b/tests/XRootD/CMakeLists.txt index 8bae4faba96..84ab85b28af 100644 --- a/tests/XRootD/CMakeLists.txt +++ b/tests/XRootD/CMakeLists.txt @@ -20,11 +20,11 @@ configure_file(xrootd.cfg xrootd.cfg @ONLY) add_test(NAME XRootD::start COMMAND sh -c "mkdir -p data && \ - $ -b -k fifo -l xrootd.log -s xrootd.pid -c xrootd.cfg") + $ -b -k fifo -n standalone -l xrootd.log -s xrootd.pid -c xrootd.cfg") set_tests_properties(XRootD::start PROPERTIES FIXTURES_SETUP XRootD) add_test(NAME XRootD::stop - COMMAND sh -c "sleep 1 && rm -rf data && kill -s TERM $(cat xrootd.pid)") + COMMAND sh -c "rm -rf data && kill -s TERM $(cat standalone/xrootd.pid)") set_tests_properties(XRootD::stop PROPERTIES FIXTURES_CLEANUP XRootD) add_test(NAME XRootD::smoke-test diff --git a/tests/XRootD/xrootd.cfg b/tests/XRootD/xrootd.cfg index a763a184f8b..7351b1e3436 100644 --- a/tests/XRootD/xrootd.cfg +++ b/tests/XRootD/xrootd.cfg @@ -3,7 +3,8 @@ all.export / all.sitename XRootD -all.adminpath @CMAKE_CURRENT_BINARY_DIR@/adm +all.adminpath @CMAKE_CURRENT_BINARY_DIR@ +all.pidpath @CMAKE_CURRENT_BINARY_DIR@ oss.localroot @CMAKE_CURRENT_BINARY_DIR@/data xrd.port @XRD_TEST_PORT@ xrootd.chksum chkcgi adler32 crc32c From bd1e9d71e0302240b47aa5673fc67a49b9dea332 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 5 Dec 2023 10:32:02 +0100 Subject: [PATCH 352/442] [Tests] Remove last uses of /tmp from tests --- tests/XrdCl/CMakeLists.txt | 2 ++ tests/XrdCl/XrdClLocalFileHandlerTest.cc | 38 +++++++++++++++++------- tests/cluster/setup.sh | 5 ++-- tests/cluster/smoketest-clustered.sh | 4 +-- 4 files changed, 34 insertions(+), 15 deletions(-) diff --git a/tests/XrdCl/CMakeLists.txt b/tests/XrdCl/CMakeLists.txt index 9208fb3856b..015f4c92501 100644 --- a/tests/XrdCl/CMakeLists.txt +++ b/tests/XrdCl/CMakeLists.txt @@ -33,6 +33,8 @@ if (UID EQUAL 0) return() endif() +file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/tmp") + add_executable(xrdcl-cluster-tests IdentityPlugIn.cc # XrdClFileTest.cc diff --git a/tests/XrdCl/XrdClLocalFileHandlerTest.cc b/tests/XrdCl/XrdClLocalFileHandlerTest.cc index 23ee197303d..8515835b6be 100644 --- a/tests/XrdCl/XrdClLocalFileHandlerTest.cc +++ b/tests/XrdCl/XrdClLocalFileHandlerTest.cc @@ -33,6 +33,9 @@ using namespace XrdClTests; class LocalFileHandlerTest: public ::testing::Test { public: + void SetUp() override; + void TearDown() override; + void CreateTestFileFunc( std::string url, std::string content = "GenericTestFile" ); void readTestFunc( bool offsetRead, uint32_t offset ); void OpenCloseTest(); @@ -47,8 +50,23 @@ class LocalFileHandlerTest: public ::testing::Test void SyncTest(); void WriteVTest(); void XAttrTest(); + + std::string m_tmpdir; }; +void LocalFileHandlerTest::SetUp() +{ + char cpath[MAXPATHLEN]; + ASSERT_TRUE(getcwd(cpath, sizeof(cpath))) << + "Could not get current working directory"; + m_tmpdir = std::string(cpath) + "/tmp"; +} + +void LocalFileHandlerTest::TearDown() +{ + /* empty */ +} + //---------------------------------------------------------------------------- // Create the file to be tested //---------------------------------------------------------------------------- @@ -66,7 +84,7 @@ void LocalFileHandlerTest::CreateTestFileFunc( std::string url, std::string cont //---------------------------------------------------------------------------- void LocalFileHandlerTest::readTestFunc(bool offsetRead, uint32_t offset){ using namespace XrdCl; - std::string targetURL = "/tmp/lfilehandlertestfileread"; + std::string targetURL = m_tmpdir + "/lfilehandlertestfileread"; std::string toBeWritten = "tenBytes10"; std::string expectedRead = "Byte"; uint32_t size = @@ -103,7 +121,7 @@ void LocalFileHandlerTest::readTestFunc(bool offsetRead, uint32_t offset){ TEST_F(LocalFileHandlerTest, SyncTest){ using namespace XrdCl; - std::string targetURL = "/tmp/lfilehandlertestfilesync"; + std::string targetURL = m_tmpdir + "/lfilehandlertestfilesync"; CreateTestFileFunc( targetURL ); //---------------------------------------------------------------------------- @@ -121,7 +139,7 @@ TEST_F(LocalFileHandlerTest, SyncTest){ TEST_F(LocalFileHandlerTest, OpenCloseTest){ using namespace XrdCl; - std::string targetURL = "/tmp/lfilehandlertestfileopenclose"; + std::string targetURL = m_tmpdir + "/lfilehandlertestfileopenclose"; CreateTestFileFunc( targetURL ); //---------------------------------------------------------------------------- @@ -150,7 +168,7 @@ TEST_F(LocalFileHandlerTest, OpenCloseTest){ TEST_F(LocalFileHandlerTest, WriteTest){ using namespace XrdCl; - std::string targetURL = "/tmp/lfilehandlertestfilewrite"; + std::string targetURL = m_tmpdir + "/lfilehandlertestfilewrite"; std::string toBeWritten = "tenBytes1\0"; uint32_t writeSize = toBeWritten.size(); CreateTestFileFunc( targetURL, "" ); @@ -181,7 +199,7 @@ TEST_F(LocalFileHandlerTest, WriteTest){ TEST_F(LocalFileHandlerTest, WriteWithOffsetTest){ using namespace XrdCl; - std::string targetURL = "/tmp/lfilehandlertestfilewriteoffset"; + std::string targetURL = m_tmpdir + "/lfilehandlertestfilewriteoffset"; std::string toBeWritten = "tenBytes10"; std::string notToBeOverwritten = "front"; uint32_t writeSize = toBeWritten.size(); @@ -215,7 +233,7 @@ TEST_F(LocalFileHandlerTest, WriteWithOffsetTest){ TEST_F(LocalFileHandlerTest, WriteMkdirTest){ using namespace XrdCl; - std::string targetURL = "/tmp/testdir/further/muchfurther/evenfurther/lfilehandlertestfilewrite"; + std::string targetURL = m_tmpdir + "/testdir/further/muchfurther/evenfurther/lfilehandlertestfilewrite"; std::string toBeWritten = "tenBytes10"; uint32_t writeSize = toBeWritten.size(); char *buffer = new char[writeSize]; @@ -256,7 +274,7 @@ TEST_F(LocalFileHandlerTest, TruncateTest){ //---------------------------------------------------------------------------- // Initialize //---------------------------------------------------------------------------- - std::string targetURL = "/tmp/lfilehandlertestfiletruncate"; + std::string targetURL = m_tmpdir + "/lfilehandlertestfiletruncate"; CreateTestFileFunc(targetURL); //---------------------------------------------------------------------------- @@ -290,7 +308,7 @@ TEST_F(LocalFileHandlerTest, VectorReadTest) //---------------------------------------------------------------------------- // Initialize //---------------------------------------------------------------------------- - std::string targetURL = "/tmp/lfilehandlertestfilevectorread"; + std::string targetURL = m_tmpdir + "/lfilehandlertestfilevectorread"; CreateTestFileFunc( targetURL ); VectorReadInfo *info = 0; ChunkList chunks; @@ -351,7 +369,7 @@ TEST_F(LocalFileHandlerTest, VectorWriteTest) //---------------------------------------------------------------------------- // Initialize //---------------------------------------------------------------------------- - std::string targetURL = "/tmp/lfilehandlertestfilevectorwrite"; + std::string targetURL = m_tmpdir + "/lfilehandlertestfilevectorwrite"; CreateTestFileFunc( targetURL ); ChunkList chunks; @@ -404,7 +422,7 @@ TEST_F(LocalFileHandlerTest, WriteVTest) //---------------------------------------------------------------------------- // Initialize //---------------------------------------------------------------------------- - std::string targetURL = "/tmp/lfilehandlertestfilewritev"; + std::string targetURL = m_tmpdir + "/lfilehandlertestfilewritev"; CreateTestFileFunc( targetURL ); //---------------------------------------------------------------------------- diff --git a/tests/cluster/setup.sh b/tests/cluster/setup.sh index d738b7f35c3..37c4d57fc73 100755 --- a/tests/cluster/setup.sh +++ b/tests/cluster/setup.sh @@ -159,10 +159,9 @@ start(){ stop() { for i in "${servernames[@]}"; do - kill -s TERM $(cat ${i}/xrootd.pid) || true - kill -s TERM $(cat ${i}/cmsd.pid) || true + kill -s TERM $(cat ${i}/cmsd.pid) + kill -s TERM $(cat ${i}/xrootd.pid) done - rm -rf ${DATAFOLDER} } insertFileInfo() { diff --git a/tests/cluster/smoketest-clustered.sh b/tests/cluster/smoketest-clustered.sh index 839db62f2ff..47e2389de7a 100755 --- a/tests/cluster/smoketest-clustered.sh +++ b/tests/cluster/smoketest-clustered.sh @@ -56,9 +56,9 @@ ${XRDFS} ${HOST_METAMAN} statvfs / ${XRDFS} ${HOST_METAMAN} spaceinfo / RMTDATADIR="/srvdata" -LCLDATADIR="/tmp/localdata" # client folder +LCLDATADIR="${PWD}/localdata" # client folder -mkdir -p /tmp/localdata +mkdir -p ${LCLDATADIR} # hostname-address pair, so that we can keep track of files more easily declare -A hosts From a310a926e9f22672a730d43eb33960877370001a Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 5 Dec 2023 13:27:59 +0100 Subject: [PATCH 353/442] [Tests] Check errno when something fails in CreateTestFileFunc --- tests/XrdCl/XrdClLocalFileHandlerTest.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/XrdCl/XrdClLocalFileHandlerTest.cc b/tests/XrdCl/XrdClLocalFileHandlerTest.cc index 8515835b6be..cbf02eae078 100644 --- a/tests/XrdCl/XrdClLocalFileHandlerTest.cc +++ b/tests/XrdCl/XrdClLocalFileHandlerTest.cc @@ -71,10 +71,14 @@ void LocalFileHandlerTest::TearDown() // Create the file to be tested //---------------------------------------------------------------------------- void LocalFileHandlerTest::CreateTestFileFunc( std::string url, std::string content ){ + errno = 0; mode_t openmode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; int fd = open( url.c_str(), O_RDWR | O_CREAT | O_TRUNC, openmode ); + EXPECT_NE( fd, -1 ); + EXPECT_EQ( errno, 0 ); int rc = write( fd, content.c_str(), content.size() ); EXPECT_EQ( rc, int( content.size() ) ); + EXPECT_EQ( errno, 0 ); rc = close( fd ); EXPECT_EQ( rc, 0 ); } From ddf1c4d024adad5ebdd51f6d2281b9ff3ea1f413 Mon Sep 17 00:00:00 2001 From: Mattias Ellert Date: Mon, 4 Dec 2023 13:00:07 +0100 Subject: [PATCH 354/442] Avoid bus errors Do not dereference unaligned pointers. --- src/XProtocol/XProtocol.cc | 4 ++-- src/XrdCl/XrdClXRootDMsgHandler.cc | 4 ++-- src/XrdZip/XrdZipCDFH.hh | 32 +++++++++++++++--------------- src/XrdZip/XrdZipEOCD.hh | 14 ++++++------- src/XrdZip/XrdZipZIP64EOCD.hh | 18 ++++++++--------- src/XrdZip/XrdZipZIP64EOCDL.hh | 6 +++--- 6 files changed, 39 insertions(+), 39 deletions(-) diff --git a/src/XProtocol/XProtocol.cc b/src/XProtocol/XProtocol.cc index dd15990c060..1e1772d3fb0 100644 --- a/src/XProtocol/XProtocol.cc +++ b/src/XProtocol/XProtocol.cc @@ -203,7 +203,7 @@ char* ClientFattrRequest::VVecInsert( const char *value, char *buffer ) // char* ClientFattrRequest::NVecRead( char* buffer, kXR_unt16 &rc ) { - rc = *reinterpret_cast( buffer ); + memcpy(&rc, buffer, sizeof(kXR_unt16)); rc = htons( rc ); buffer += sizeof( kXR_unt16 ); return buffer; @@ -222,7 +222,7 @@ char* ClientFattrRequest::NVecRead( char* buffer, char *&name ) // char* ClientFattrRequest::VVecRead( char* buffer, kXR_int32 &len ) { - len = *reinterpret_cast( buffer ); + memcpy(&len, buffer, sizeof(kXR_int32)); len = htonl( len ); buffer += sizeof( kXR_int32 ); return buffer; diff --git a/src/XrdCl/XrdClXRootDMsgHandler.cc b/src/XrdCl/XrdClXRootDMsgHandler.cc index 81e41d21bb1..da651387206 100644 --- a/src/XrdCl/XrdClXRootDMsgHandler.cc +++ b/src/XrdCl/XrdClXRootDMsgHandler.cc @@ -2445,10 +2445,10 @@ namespace XrdCl { if( sizeof( T ) > buflen ) return Status( stError, errDataError ); - result = *reinterpret_cast( buffer ); + memcpy(&result, buffer, sizeof(T)); buffer += sizeof( T ); - buflen -= sizeof( T ); + buflen -= sizeof( T ); return Status(); } diff --git a/src/XrdZip/XrdZipCDFH.hh b/src/XrdZip/XrdZipCDFH.hh index 96851ea3523..e6dbb36f23b 100644 --- a/src/XrdZip/XrdZipCDFH.hh +++ b/src/XrdZip/XrdZipCDFH.hh @@ -194,22 +194,22 @@ namespace XrdZip //------------------------------------------------------------------------- CDFH( const char *buffer, const uint32_t maxSize = 0 ) { - zipVersion = *reinterpret_cast( buffer + 4 ); - minZipVersion = *reinterpret_cast( buffer + 6 ); - generalBitFlag = *reinterpret_cast( buffer + 8 ); - compressionMethod = *reinterpret_cast( buffer + 10 ); - timestmp.time = *reinterpret_cast( buffer + 12 ); - timestmp.date = *reinterpret_cast( buffer + 14 ); - ZCRC32 = *reinterpret_cast( buffer + 16 ); - compressedSize = *reinterpret_cast( buffer + 20 ); - uncompressedSize = *reinterpret_cast( buffer + 24 ); - filenameLength = *reinterpret_cast( buffer + 28 ); - extraLength = *reinterpret_cast( buffer + 30 ); - commentLength = *reinterpret_cast( buffer + 32 ); - nbDisk = *reinterpret_cast( buffer + 34 ); - internAttr = *reinterpret_cast( buffer + 36 ); - externAttr = *reinterpret_cast( buffer + 38 ); - offset = *reinterpret_cast( buffer + 42 ); + zipVersion = to(buffer + 4); + minZipVersion = to(buffer + 6); + generalBitFlag = to(buffer + 8); + compressionMethod = to(buffer + 10); + timestmp.time = to(buffer + 12); + timestmp.date = to(buffer + 14); + ZCRC32 = to(buffer + 16); + compressedSize = to(buffer + 20); + uncompressedSize = to(buffer + 24); + filenameLength = to(buffer + 28); + extraLength = to(buffer + 30); + commentLength = to(buffer + 32); + nbDisk = to(buffer + 34); + internAttr = to(buffer + 36); + externAttr = to(buffer + 38); + offset = to(buffer + 42); if(maxSize > 0 && (uint32_t)(cdfhBaseSize+filenameLength + extraLength + commentLength) > maxSize){ throw bad_data(); } diff --git a/src/XrdZip/XrdZipEOCD.hh b/src/XrdZip/XrdZipEOCD.hh index d38a6a1c865..575a300bc0d 100644 --- a/src/XrdZip/XrdZipEOCD.hh +++ b/src/XrdZip/XrdZipEOCD.hh @@ -53,13 +53,13 @@ namespace XrdZip //------------------------------------------------------------------------- EOCD( const char *buffer, uint32_t maxSize = 0 ) { - nbDisk = *reinterpret_cast( buffer + 4 ); - nbDiskCd = *reinterpret_cast( buffer + 6 ); - nbCdRecD = *reinterpret_cast( buffer + 8 ); - nbCdRec = *reinterpret_cast( buffer + 10 ); - cdSize = *reinterpret_cast( buffer + 12 ); - cdOffset = *reinterpret_cast( buffer + 16 ); - commentLength = *reinterpret_cast( buffer + 20 ); + nbDisk = to(buffer + 4); + nbDiskCd = to(buffer + 6); + nbCdRecD = to(buffer + 8); + nbCdRec = to(buffer + 10); + cdSize = to(buffer + 12); + cdOffset = to(buffer + 16); + commentLength = to(buffer + 20); if(maxSize > 0 && (uint32_t)(eocdBaseSize + commentLength) > maxSize) throw bad_data(); comment = std::string( buffer + 22, commentLength ); diff --git a/src/XrdZip/XrdZipZIP64EOCD.hh b/src/XrdZip/XrdZipZIP64EOCD.hh index c7ab0a3bbba..09e5ecc4f39 100644 --- a/src/XrdZip/XrdZipZIP64EOCD.hh +++ b/src/XrdZip/XrdZipZIP64EOCD.hh @@ -28,15 +28,15 @@ namespace XrdZip ZIP64_EOCD( const char* buffer ): extensibleDataLength( 0 ) { - zip64EocdSize = *reinterpret_cast( buffer + 4 ); - zipVersion = *reinterpret_cast( buffer + 12 ); - minZipVersion = *reinterpret_cast( buffer + 14 ); - nbDisk = *reinterpret_cast( buffer + 16 ); - nbDiskCd = *reinterpret_cast( buffer + 20 ); - nbCdRecD = *reinterpret_cast( buffer + 24 ); - nbCdRec = *reinterpret_cast( buffer + 32 ); - cdSize = *reinterpret_cast( buffer + 40 ); - cdOffset = *reinterpret_cast( buffer + 48 ); + zip64EocdSize = to(buffer + 4); + zipVersion = to(buffer + 12); + minZipVersion = to(buffer + 14); + nbDisk = to(buffer + 16); + nbDiskCd = to(buffer + 20); + nbCdRecD = to(buffer + 24); + nbCdRec = to(buffer + 32); + cdSize = to(buffer + 40); + cdOffset = to(buffer + 48); zip64EocdTotalSize = zip64EocdBaseSize + extensibleDataLength; } diff --git a/src/XrdZip/XrdZipZIP64EOCDL.hh b/src/XrdZip/XrdZipZIP64EOCDL.hh index d2cea2edcfc..cceca756a52 100644 --- a/src/XrdZip/XrdZipZIP64EOCDL.hh +++ b/src/XrdZip/XrdZipZIP64EOCDL.hh @@ -26,9 +26,9 @@ namespace XrdZip //------------------------------------------------------------------------- ZIP64_EOCDL( const char *buffer ) { - nbDiskZip64Eocd = *reinterpret_cast( buffer + 4 ); - zip64EocdOffset = *reinterpret_cast( buffer + 8 ); - totalNbDisks = *reinterpret_cast( buffer + 16 ); + nbDiskZip64Eocd = to(buffer + 4); + zip64EocdOffset = to(buffer + 8); + totalNbDisks = to(buffer + 16); } //------------------------------------------------------------------------- From 4e00965cb35c5a4de1e64fa1acc1c7e715eb6ed9 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 5 Dec 2023 17:04:23 +0100 Subject: [PATCH 355/442] [XrdPss] Avoid null pointer dereference in XrdPssConfig Fixes: #2140 --- src/XrdPss/XrdPssConfig.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdPss/XrdPssConfig.cc b/src/XrdPss/XrdPssConfig.cc index 8a3ff5e8610..11dc4bbd90e 100644 --- a/src/XrdPss/XrdPssConfig.cc +++ b/src/XrdPss/XrdPssConfig.cc @@ -782,7 +782,7 @@ int XrdPssSys::xorig(XrdSysError *errp, XrdOucStream &Config) // Check if there is a port number. This could be as ':port' or ' port'. // if (!(val = index(mval,':')) && !isURL) val = Config.GetWord(); - else {*val = '\0'; val++;} + else if (val) {*val = '\0'; val++;} // At this point, make sure we actually have a host name // From a93ccf4279bc124d5713c79263aa5d1d22d90c29 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 5 Dec 2023 17:18:40 +0100 Subject: [PATCH 356/442] [XrdPss] Use default port for pss.origin when not specified --- src/XrdPss/XrdPssConfig.cc | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/XrdPss/XrdPssConfig.cc b/src/XrdPss/XrdPssConfig.cc index 11dc4bbd90e..7295706aa73 100644 --- a/src/XrdPss/XrdPssConfig.cc +++ b/src/XrdPss/XrdPssConfig.cc @@ -800,7 +800,17 @@ int XrdPssSys::xorig(XrdSysError *errp, XrdOucStream &Config) {errp->Emsg("Config", "unable to find tcp service", val); port = 0; } - } else errp->Emsg("Config","origin port not specified for",mval); + } else { + if (protName) { + // use default port for protocol + port = *protName == 'h' ? (strncmp(protName, "https", 5) == 0 ? 443 : 80) : 1094; + } else { + // assume protocol is root(s):// + port = 1094; + } + errp->Say("Config warning: origin port not specified, using port ", + std::to_string(port).c_str(), " as default for ", protName); + } // If port is invalid or missing, fail this // From 00e8da57146b1290754f2639be87f68dd509ec18 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 8 Dec 2023 17:33:36 +0100 Subject: [PATCH 357/442] [XrdPosix] Keep large file offset definitions on MUSL Required to have off64_t and statvfs64 available during compilation. --- src/XrdPosix/XrdPosixPreload32.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/XrdPosix/XrdPosixPreload32.cc b/src/XrdPosix/XrdPosixPreload32.cc index 436e23dc2bb..3d99ba9d2ee 100644 --- a/src/XrdPosix/XrdPosixPreload32.cc +++ b/src/XrdPosix/XrdPosixPreload32.cc @@ -32,6 +32,7 @@ #undef _FORTIFY_SOURCE #endif +#if !defined(MUSL) #ifdef _LARGEFILE_SOURCE #undef _LARGEFILE_SOURCE #endif @@ -43,6 +44,7 @@ #ifdef _FILE_OFFSET_BITS #undef _FILE_OFFSET_BITS #endif +#endif #define XRDPOSIXPRELOAD32 From 85d43107e47f536372dafae7bcb3c423c8b4b8d7 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Sun, 10 Dec 2023 17:06:44 +0100 Subject: [PATCH 358/442] [DEB] Add uuid-runtime build dependency Needed to get uuidgen command line utility, which is used in tests. --- debian/control | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/control b/debian/control index f5db17708bd..ff45b390d0e 100644 --- a/debian/control +++ b/debian/control @@ -26,6 +26,7 @@ Build-Depends: libjson-c-dev, libmacaroons-dev, uuid-dev, + uuid-runtime, voms-dev, libscitokens-dev, davix-dev, From e6c3b16721fead4e8b94b2f44883848367a97053 Mon Sep 17 00:00:00 2001 From: Mattias Ellert Date: Mon, 4 Dec 2023 19:07:19 +0100 Subject: [PATCH 359/442] [XrdZip] Support Big Endian The ZIP file format specifies that "all values MUST be stored in little-endian byte order unless otherwise specified in this document for a specific data element". For more information, see section 4.3.3 at https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT Co-authored-by: Guilherme Amadio --- src/XrdSys/XrdSysPlatform.hh | 12 ++++++++++++ src/XrdZip/XrdZipLFH.hh | 3 ++- src/XrdZip/XrdZipUtils.hh | 14 +++++++++++++- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/XrdSys/XrdSysPlatform.hh b/src/XrdSys/XrdSysPlatform.hh index eaeaeabac58..0024369bb03 100644 --- a/src/XrdSys/XrdSysPlatform.hh +++ b/src/XrdSys/XrdSysPlatform.hh @@ -31,6 +31,7 @@ // Include stdlib so that ENDIAN macros are defined properly // +#include #include #ifdef __linux__ @@ -46,6 +47,7 @@ #include #include #include +#include #define fdatasync(x) fsync(x) #define MAXNAMELEN NAME_MAX #ifndef dirent64 @@ -153,6 +155,16 @@ typedef off_t offset_t; typedef off_t off64_t; #endif +#if defined(__APPLE__) +#define bswap_16 OSSwapInt16 +#define bswap_32 OSSwapInt32 +#define bswap_64 OSSwapInt64 +#endif + +static inline uint16_t bswap(uint16_t x) { return bswap_16(x); } +static inline uint32_t bswap(uint32_t x) { return bswap_32(x); } +static inline uint64_t bswap(uint64_t x) { return bswap_64(x); } + // Only sparc platforms have structure alignment problems w/ optimization // so the h2xxx() variants are used when converting network streams. diff --git a/src/XrdZip/XrdZipLFH.hh b/src/XrdZip/XrdZipLFH.hh index 972f373a497..aac99848eb0 100644 --- a/src/XrdZip/XrdZipLFH.hh +++ b/src/XrdZip/XrdZipLFH.hh @@ -80,7 +80,8 @@ namespace XrdZip from_buffer( minZipVersion, buffer ); from_buffer( generalBitFlag, buffer ); from_buffer( compressionMethod, buffer ); - from_buffer( timestmp, buffer ); + from_buffer( timestmp.time, buffer ); + from_buffer( timestmp.date, buffer ); from_buffer( ZCRC32, buffer ); from_buffer( compressedSize, buffer ); from_buffer( uncompressedSize, buffer ); diff --git a/src/XrdZip/XrdZipUtils.hh b/src/XrdZip/XrdZipUtils.hh index 619f16daf95..9b92a0a1e50 100644 --- a/src/XrdZip/XrdZipUtils.hh +++ b/src/XrdZip/XrdZipUtils.hh @@ -25,6 +25,8 @@ #ifndef SRC_XRDZIP_XRDZIPUTILS_HH_ #define SRC_XRDZIP_XRDZIPUTILS_HH_ +#include "XrdSys/XrdSysPlatform.hh" + #include #include #include @@ -61,7 +63,11 @@ namespace XrdZip { const char *begin = reinterpret_cast( &value ); const char *end = begin + sizeof( INT ); +#ifdef Xrd_Big_Endian + std::reverse_copy( begin, end, std::back_inserter( buffer ) ); +#else std::copy( begin, end, std::back_inserter( buffer ) ); +#endif } //--------------------------------------------------------------------------- @@ -72,6 +78,9 @@ namespace XrdZip inline static void from_buffer( INT &var, const char *&buffer ) { memcpy( &var, buffer, sizeof( INT ) ); +#ifdef Xrd_Big_Endian + var = bswap(var); +#endif buffer += sizeof( INT ); } @@ -82,7 +91,10 @@ namespace XrdZip inline static INT to( const char *buffer ) { INT value; - memcpy( &value, buffer, sizeof( INT) ); + memcpy( &value, buffer, sizeof( INT ) ); +#ifdef Xrd_Big_Endian + value = bswap(value); +#endif return value; } From 6b14775649b1210edf57cb4777dd9c4dc4e7507c Mon Sep 17 00:00:00 2001 From: Mattias Ellert Date: Sun, 10 Dec 2023 18:23:47 +0100 Subject: [PATCH 360/442] Avoid duplicate definitions --- src/XrdOssCsi/XrdOssCsiTagstoreFile.hh | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/XrdOssCsi/XrdOssCsiTagstoreFile.hh b/src/XrdOssCsi/XrdOssCsiTagstoreFile.hh index 0202579ccb4..2ae948048ac 100644 --- a/src/XrdOssCsi/XrdOssCsiTagstoreFile.hh +++ b/src/XrdOssCsi/XrdOssCsiTagstoreFile.hh @@ -34,23 +34,11 @@ #include "XrdOss/XrdOss.hh" #include "XrdOssCsiTagstore.hh" #include "XrdOuc/XrdOucCRC.hh" +#include "XrdSys/XrdSysPlatform.hh" #include #include -#if defined(__APPLE__) -// Make sure that byte swap functions are not already defined. -#if !defined(bswap_16) -// Mac OS X / Darwin features -#include -#define bswap_16(x) OSSwapInt16(x) -#define bswap_32(x) OSSwapInt32(x) -#define bswap_64(x) OSSwapInt64(x) -#endif -#else -#include -#endif - class XrdOssCsiTagstoreFile : public XrdOssCsiTagstore { public: From 34f5584090b243681f9bf1f7f8dade8ab9a20a73 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 8 Dec 2023 16:15:50 +0100 Subject: [PATCH 361/442] Cleanup gitignore --- .gitignore | 64 +++++------------------------------------------------- 1 file changed, 5 insertions(+), 59 deletions(-) diff --git a/.gitignore b/.gitignore index adfe0558ac7..11165537c4d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,61 +1,7 @@ -*.o -*.lo -.libs -.deps -Makefile -Makefile.in -*.la -GNUmakefile.classic -aclocal.m4 -autom4te.cache/ -compile -config/GNUmake.rules.sunCC -config/GNUmake.rules.sunCCamd -config/GNUmake.rules.sunCCamd510 -config/GNUmake.rules.sunCCamd64 -config/GNUmake.rules.sunCCi86pc -config.guess -config.log -config.status -config.sub -configure -depcomp -docker/data +# CMake build directory +build/ +# docker builds docker/xrootd.tar.gz -install-sh -lib/ -libtool -ltmain.sh -missing -src/GNUmake.env -src/GNUmake.options -src/Makefile_include -src/XrdAcc/XrdAccTest -src/XrdApps/mpxstats -src/XrdApps/wait41 -src/XrdApps/xrdadler32 -src/XrdClient/TestXrdClient -src/XrdClient/TestXrdClient_read -src/XrdClient/XrdClientAdmin_c_wrap.cc -src/XrdClient/xprep -src/XrdClient/xrd -src/XrdClient/xrdcp -src/XrdClient/xrdstagetool -src/XrdCms/cmsd -src/XrdCns/XrdCnsd -src/XrdCns/cns_ssi -src/XrdFrm/frm_admin -src/XrdFrm/frm_purged -src/XrdFrm/frm_xfragent -src/XrdFrm/frm_xfrd -src/XrdSec/testclient -src/XrdSec/testserver -src/XrdSecgsi/xrdgsiproxy -src/XrdSecpwd/xrdpwdadmin -src/XrdSecssl/xrdsecssltest -src/XrdSecsss/xrdsssadmin -src/XrdXrootd/xrootd -test/testconfig.sh -xrootd.spec -dist +# Python build artifacts +dist/ *.egg-info From 7547a9258c160450bb5c2eaacf9779cdc8d316c3 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 3 Nov 2023 16:13:49 +0100 Subject: [PATCH 362/442] [CI] Rewrite GitLab CI starting over from a copy of GitHub Actions builds --- .gitlab-ci.yml | 737 +++++-------------------------------------------- 1 file changed, 75 insertions(+), 662 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0ea9feb0b25..5073d3c9cfe 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,684 +1,97 @@ stages: - - build:rpm - - publish - - post:publish - - clean + - build -.template:deb_ubuntu_build: &deb_ubuntu_build_def - stage: build:rpm - script: - - export DEBIAN_FRONTEND=noninteractive - - apt-get update - - apt-get install -y git cmake g++ debhelper devscripts equivs gdebi-core - - cp -R packaging/debian/ . - - mk-build-deps --build-dep debian/control - - gdebi -n xrootd-build-deps-depends*.deb - - version=`./genversion.sh --print-only` - - dch --create -v `echo $version | sed 's/^v\(.*\)/\1/'` --package xrootd --urgency low --distribution ${DIST} -M "This package is built and released automatically. For important notices and releases subscribe to our maling lists or visit our website." - - dpkg_version=`dpkg-query --showformat='${Version}' --show dpkg` - - rc=0 ; dpkg --compare-versions $dpkg_version "ge" "1.18.11" || rc=$? - - if [ $rc -eq "0" ]; then - dpkg-buildpackage -b -us -uc -tc --buildinfo-option="-udeb_packages" --changes-option="-udeb_packages" ; - else - dpkg-buildpackage -b -us -uc -tc --changes-option="-udeb_packages" ; - fi - - mkdir ${DIST}/ - - cp deb_packages/*.deb ${DIST}/ - - if [[ $DEBUG = "true" ]] ; then cp deb_packages/*.ddeb ${DIST}/; fi - artifacts: - expire_in: 1 day - paths: - - ${DIST}/ - tags: - - docker_node - -.template:deb_ubuntu_build: &deb_ubuntu_build_new_def - stage: build:rpm - script: - - export DEBIAN_FRONTEND=noninteractive - - apt-get update - - apt-get install -y git cmake g++ debhelper devscripts equivs gdebi-core - - mv packaging/debian/python3-xrootd.install.new packaging/debian/python3-xrootd.install - - cp -R packaging/debian/ . - - mk-build-deps --build-dep debian/control - - gdebi -n xrootd-build-deps-depends*.deb - - version=`./genversion.sh --print-only` - - dch --create -v `echo $version | sed 's/^v\(.*\)/\1/'` --package xrootd --urgency low --distribution ${DIST} -M "This package is built and released automatically. For important notices and releases subscribe to our maling lists or visit our website." - - dpkg-buildpackage -b -us -uc -tc --buildinfo-option="-udeb_packages" --buildinfo-file="deb_packages/xrootd_$(dpkg-parsechangelog -S version)_$(dpkg-architecture -qDEB_BUILD_ARCH).buildinfo" --changes-option="-udeb_packages" --buildinfo-file="deb_packages/xrootd_$(dpkg-parsechangelog -S version)_$(dpkg-architecture -qDEB_BUILD_ARCH).changes" - - mkdir ${DIST}/ - - cp deb_packages/*.deb ${DIST}/ - - if [[ $DEBUG = "true" ]] ; then cp deb_packages/*.ddeb ${DIST}/; fi - artifacts: - expire_in: 1 day - paths: - - ${DIST}/ +default: tags: - docker_node -build:cs9: - stage: build:rpm - image: gitlab-registry.cern.ch/linuxsupport/cs9-base - script: - - dnf install -y epel-release - - dnf install -y gcc-c++ rpm-build tar dnf-plugins-core git - - dnf install -y cppunit-devel gtest-devel - - cd packaging/ - - ./makesrpm.sh --define "_with_python3 1" --define "_with_tests 1" --define "_without_xrootd_user 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" -D "dist .el9" - - dnf builddep -y *.src.rpm - - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_with_python3 1" --define "_with_tests 1" --define "_without_xrootd_user 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" -D "dist .el9" *.src.rpm - - cd .. - - mkdir cs-9 - - cp packaging/RPMS/*.rpm cs-9 - - cp packaging/*src.rpm cs-9 - artifacts: - expire_in: 1 day - paths: - - cs-9/ - tags: - - docker_node - only: - - master - - /^stable-.*$/ - except: - - tags - - schedules - - web - allow_failure: true - -build:cs8: - stage: build:rpm - image: gitlab-registry.cern.ch/linuxsupport/cs8-base - script: - - dnf install -y epel-release - - dnf install -y gcc-c++ rpm-build tar dnf-plugins-core git - - dnf config-manager --set-enabled powertools - - cd packaging - - ./makesrpm.sh --define "_with_python3 1" --define "_with_tests 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" - - dnf builddep -y *.src.rpm - - dnf -y update libarchive - - mkdir RPMS - - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_with_python3 1" --define "_with_tests 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" *.src.rpm - - cd .. - - mkdir cs-8 - - cp packaging/*.src.rpm cs-8 - - cp packaging/RPMS/* cs-8 +.deb_build: &deb_build + stage: build + variables: + DEBIAN_FRONTEND: noninteractive + script: + - source /etc/os-release + - apt update -qq + - apt install -y build-essential devscripts equivs git + - mk-build-deps --install --remove debian/control <<< y + - VERSION=$(git describe --match 'v*' | sed -e 's/v//; s/-rc/~rc/; s/-g/+git/; s/-/.post/; s/-/./') + - dch --create --package xrootd -v ${VERSION} -M 'XRootD automated build.' + - debuild --no-tgz-check --no-sign -b + - apt install -y ../*.d*eb + - mkdir -p DEB/${ID}/${VERSION_CODENAME} + - mv ../*.* DEB/${ID}/${VERSION_CODENAME} + - tests/post-install.sh artifacts: - expire_in: 1 day - paths: - - cs-8/ - tags: - - docker_node - only: - - master - - /^stable-.*$/ - except: - - tags - - schedules - - web - -build:cc7: - stage: build:rpm - image: gitlab-registry.cern.ch/linuxsupport/cc7-base - script: - - head -n -6 /etc/yum.repos.d/epel.repo > /tmp/epel.repo ; mv -f /tmp/epel.repo /etc/yum.repos.d/epel.repo - - yum install -y gcc-c++ rpm-build git python-srpm-macros centos-release-scl - - cd packaging/ - - ./makesrpm.sh --define "_with_python3 1" --define "_with_tests 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" --define "_with_isal 1" - - yum-builddep -y *.src.rpm - - mkdir RPMS - - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_with_python3 1" --define "_with_tests 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" --define "_with_isal 1" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" *.src.rpm - - cd .. - - mkdir cc-7/ - - cp packaging/*.src.rpm cc-7 - - cp packaging/RPMS/* cc-7 + paths: [ DEB ] + expire_in: 1d + +.rpm_build_yum: &rpm_build_yum + stage: build + script: + - yum install -y centos-release-scl epel-release git + - yum install -y epel-rpm-macros rpmdevtools yum-utils + - yum-builddep -y xrootd.spec + - rpmdev-setuptree + - git archive --prefix xrootd/ -o $(rpm -E '%{_sourcedir}')/xrootd.tar.gz HEAD + - rpmbuild -bb --with git xrootd.spec + - yum install -y $(rpm -E '%{_rpmdir}')/*/*.rpm + - tests/post-install.sh + - mkdir -p RPMS + - mv $(rpm -E '%{_rpmdir}')/ RPMS$(rpm -E '%{dist}' | tr . /) artifacts: - expire_in: 1 day - paths: - - cc-7/ - tags: - - docker_node - only: - - master - - /^stable-.*$/ - except: - - tags - - schedules - - web - -build:fedora-37: - stage: build:rpm - image: fedora:37 - script: - - dnf install -y gcc-c++ rpm-build tar dnf-plugins-core git - - cd packaging/ - - ./makesrpm.sh --define "_with_python3 1" --define "_with_xrdclhttp 1" --define "_with_ceph11 1" - - dnf builddep -y *.src.rpm - - mkdir RPMS - - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_with_python3 1" --define "_with_xrdclhttp 1" --define "_with_ceph11 1" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" *.src.rpm - - cd .. - - mkdir fc-37/ - - cp packaging/*.src.rpm fc-37 - - cp packaging/RPMS/* fc-37 + paths: [ RPMS ] + expire_in: 1d + +.rpm_build_dnf: &rpm_build_dnf + stage: build + script: + - dnf install -y dnf-plugins-core git rpmdevtools + - rpmdev-setuptree + - dnf builddep -y xrootd.spec + - git archive --prefix xrootd/ -o $(rpm -E '%{_sourcedir}')/xrootd.tar.gz HEAD + - rpmbuild -bb --with git xrootd.spec + - dnf install -y $(rpm -E '%{_rpmdir}')/*/*.rpm + - tests/post-install.sh + - mkdir -p RPMS + - mv $(rpm -E '%{_rpmdir}')/ RPMS$(rpm -E '%{dist}' | tr . /) artifacts: - expire_in: 1 day - paths: - - fc-37/ - tags: - - docker_node - only: - - master - - /^stable-.*$/ - except: - - tags - - schedules - - web - allow_failure: true + paths: [ RPMS ] + expire_in: 1d -build:fedora-38: - stage: build:rpm - image: fedora:38 - script: - - dnf install -y gcc-c++ rpm-build tar dnf-plugins-core git - - cd packaging/ - - ./makesrpm.sh --define "_with_python3 1" --define "_with_xrdclhttp 1" --define "_with_ceph11 1" - - dnf builddep -y *.src.rpm - - mkdir RPMS - - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_with_python3 1" --define "_with_xrdclhttp 1" --define "_with_ceph11 1" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" *.src.rpm - - cd .. - - mkdir fc-38/ - - cp packaging/*.src.rpm fc-38 - - cp packaging/RPMS/* fc-38 - artifacts: - expire_in: 1 day - paths: - - fc-38/ - tags: - - docker_node - only: - - master - - /^stable-.*$/ - except: - - tags - - schedules - - web - allow_failure: true +Debian 11: + image: debian:11 + <<: *deb_build -build:fedora-39: - stage: build:rpm - image: fedora:39 - script: - - dnf install -y gcc-c++ rpm-build tar dnf-plugins-core git - - cd packaging/ - - ./makesrpm.sh --define "_with_python3 1" --define "_with_xrdclhttp 1" --define "_with_ceph11 1" - - dnf builddep -y *.src.rpm - - mkdir RPMS - - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_with_python3 1" --define "_with_xrdclhttp 1" --define "_with_ceph11 1" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" *.src.rpm - - cd .. - - mkdir fc-39/ - - cp packaging/*.src.rpm fc-39 - - cp packaging/RPMS/* fc-39 - artifacts: - expire_in: 1 day - paths: - - fc-39/ - tags: - - docker_node - only: - - master - - /^stable-.*$/ - except: - - tags - - schedules - - web - allow_failure: true - -build:deb_ubuntu_focal: - image: ubuntu:focal - <<: *deb_ubuntu_build_def - variables: - DIST: focal - only: - - master - - /^stable-.*$/ - except: - - tags - - schedules - - web +Debian 12: + image: debian:12 + <<: *deb_build -build:deb_ubuntu_jammy: - image: ubuntu:jammy - <<: *deb_ubuntu_build_new_def - variables: - DIST: jammy - only: - - master - - /^stable-.*$/ - except: - - tags - - schedules - - web - allow_failure: true +Ubuntu 22.04: + image: ubuntu:22.04 + <<: *deb_build -release:cs8-x86_64: - stage: build:rpm - image: gitlab-registry.cern.ch/linuxsupport/cs8-base - script: - - dnf install -y epel-release - - dnf install -y gcc-c++ rpm-build tar dnf-plugins-core git python-macros - - dnf config-manager --set-enabled powertools - - dnf install -y cppunit-devel gtest-devel - - dnf -y update libarchive - - mkdir cs-8-x86_64 - - ./gen-tarball.sh $CI_COMMIT_TAG - - mv xrootd-${CI_COMMIT_TAG#"v"}.tar.gz cs-8-x86_64 - - cd packaging/ - - git checkout tags/${CI_COMMIT_TAG} - - ./makesrpm.sh --define "_with_python3 1" --define "_with_tests 1" --define "_without_xrootd_user 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" --define "_with_isal 1" -D "dist .el8" - - dnf builddep -y *.src.rpm - - mkdir RPMS - - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_with_python3 1" --define "_with_tests 1" --define "_without_xrootd_user 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" --define "_with_isal 1" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" -D "dist .el8" *.src.rpm - - cd .. - - cp packaging/RPMS/*.rpm cs-8-x86_64 - - cp packaging/*src.rpm cs-8-x86_64 - artifacts: - expire_in: 1 day - paths: - - cs-8-x86_64/ - tags: - - docker_node - only: - - web - except: - - branches +CentOS 7: + image: centos:7 + <<: *rpm_build_yum -release:alma8-x86_64: - stage: build:rpm +AlmaLinux 8: image: almalinux:8 - script: + before_script: - dnf install -y epel-release - - dnf install -y gcc-c++ rpm-build tar dnf-plugins-core git python-macros - dnf config-manager --set-enabled powertools - - dnf install -y cppunit-devel gtest-devel - - dnf -y update libarchive - - mkdir alma-8-x86_64 - - ./gen-tarball.sh $CI_COMMIT_TAG - - mv xrootd-${CI_COMMIT_TAG#"v"}.tar.gz alma-8-x86_64 - - cd packaging/ - - git checkout tags/${CI_COMMIT_TAG} - - ./makesrpm.sh --define "_with_python3 1" --define "_with_tests 1" --define "_without_xrootd_user 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" --define "_with_isal 1" -D "dist .el8" - - dnf builddep -y *.src.rpm - - mkdir RPMS - - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_with_python3 1" --define "_with_tests 1" --define "_without_xrootd_user 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" --define "_with_isal 1" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" -D "dist .el8" *.src.rpm - - cd .. - - cp packaging/RPMS/*.rpm alma-8-x86_64 - - cp packaging/*src.rpm alma-8-x86_64 - artifacts: - expire_in: 1 day - paths: - - alma-8-x86_64/ - tags: - - docker_node - only: - - web - except: - - branches + <<: *rpm_build_dnf -release:alma9-x86_64: - stage: build:rpm +AlmaLinux 9: image: almalinux:9 - script: + before_script: - dnf install -y epel-release - - dnf install -y dnf-plugins-core rpmdevtools git - dnf config-manager --set-enabled crb - - mkdir alma-9-x86_64 - - ./gen-tarball.sh $CI_COMMIT_TAG - - mv xrootd-${CI_COMMIT_TAG#"v"}.tar.gz alma-9-x86_64 - - cd packaging/ - - git checkout tags/${CI_COMMIT_TAG} - - ./makesrpm.sh --define "_with_python3 1" --define "_with_tests 1" --define "_without_xrootd_user 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" --define "_with_isal 1" -D "dist .el9" - - dnf builddep -y *.src.rpm - - mkdir RPMS - - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_with_python3 1" --define "_with_tests 1" --define "_without_xrootd_user 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" --define "_with_isal 1" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" -D "dist .el9" *.src.rpm - - cd .. - - cp packaging/RPMS/*.rpm alma-9-x86_64 - - cp packaging/*src.rpm alma-9-x86_64 - artifacts: - expire_in: 1 day - paths: - - alma-9-x86_64/ - tags: - - docker_node - only: - - web - except: - - branches + <<: *rpm_build_dnf -release:cc7-x86_64: - stage: build:rpm - image: gitlab-registry.cern.ch/linuxsupport/cc7-base - script: - - head -n -6 /etc/yum.repos.d/epel.repo > /tmp/epel.repo ; mv -f /tmp/epel.repo /etc/yum.repos.d/epel.repo - - yum install -y gcc-c++ rpm-build git python-srpm-macros centos-release-scl - - mkdir cc-7-x86_64 - - ./gen-tarball.sh $CI_COMMIT_TAG - - mv xrootd-${CI_COMMIT_TAG#"v"}.tar.gz cc-7-x86_64 - - cd packaging/ - - git checkout tags/${CI_COMMIT_TAG} - - ./makesrpm.sh --define "_with_python3 1" --define "_with_tests 1" --define "_without_xrootd_user 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" --define "_with_isal 1" -D "dist .el7" - - yum-builddep -y *.src.rpm - - mkdir RPMS - - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_with_python3 1" --define "_with_tests 1" --define "_without_xrootd_user 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" --define "_with_isal 1" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" -D "dist .el7" *.src.rpm - - cd .. - - cp packaging/RPMS/*.rpm cc-7-x86_64 - - cp packaging/*src.rpm cc-7-x86_64 - artifacts: - expire_in: 1 day - paths: - - cc-7-x86_64/ - tags: - - docker_node - only: - - web - except: - - branches - -release:deb_ubuntu_focal: - image: ubuntu:focal - <<: *deb_ubuntu_build_def - variables: - DIST: focal - only: - - web - except: - - branches - -release:deb_ubuntu_jammy: - image: ubuntu:jammy - <<: *deb_ubuntu_build_new_def - variables: - DIST: jammy - only: - - web - except: - - branches - allow_failure: true - - -release:pypi: - stage: build:rpm - image: gitlab-registry.cern.ch/linuxsupport/cc7-base - script: - - yum install -y git python3-pip - - cp packaging/wheel/* . - - ./publish.sh - artifacts: - expire_in: 1 day - paths: - - dist/ - tags: - - docker_node - only: - - web - except: - - branches - allow_failure: true - -publish:pypi: - stage: publish - image: gitlab-registry.cern.ch/linuxsupport/cc7-base - script: - - yum install -y sssd-client sudo - - sudo -u stci -H mkdir -p /eos/project/s/storage-ci/www/xrootd/release/pypi-dist - - sudo -u stci -H cp dist/*.tar.gz /eos/project/s/storage-ci/www/xrootd/release/pypi-dist/. - tags: - - docker_node - only: - - web - except: - - branches - allow_failure: true - - -weekly:cs8: - stage: build:rpm - image: gitlab-registry.cern.ch/linuxsupport/cs8-base - script: - - dnf install -y epel-release - - dnf install -y gcc-c++ rpm-build tar dnf-plugins-core git python-macros - - dnf config-manager --set-enabled powertools - - dnf install -y cppunit-devel gtest-devel - - dnf -y update libarchive - - xrootd_version=$(git for-each-ref --sort=taggerdate --format '%(refname)' refs/tags | grep '^refs/tags/v' | grep -v 'rc.*$' | grep -v 'osghotfix' | grep -v 'CERN$' | sed -e '$!d') - - xrootd_version=${xrootd_version:11} - - short_hash=$(git rev-parse --verify HEAD | awk '{print substr($0, 0, 8)}') - - a=( ${xrootd_version//./ } ) - - ((a[2]++)) || true - - experimental_version="${a[0]}.${a[1]}.${a[2]}-0.experimental."${CI_PIPELINE_ID}.$short_hash - - cd packaging/ - - ./makesrpm.sh --version $experimental_version --define "_with_python3 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" --define "_with_isal 1" - - dnf builddep -y *.src.rpm - - mkdir RPMS - - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_with_tests 1" --define "_with_python3 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" --define "_with_isal 1" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" *.src.rpm - - cd .. - - mkdir epel-8/ - - cp packaging/*.src.rpm epel-8 - - cp packaging/RPMS/* epel-8 - artifacts: - expire_in: 1 day - paths: - - epel-8/ - tags: - - docker_node - only: - - schedules - except: - - tags - -weekly:cc7: - stage: build:rpm - image: gitlab-registry.cern.ch/linuxsupport/cc7-base - script: - - head -n -6 /etc/yum.repos.d/epel.repo > /tmp/epel.repo ; mv -f /tmp/epel.repo /etc/yum.repos.d/epel.repo - - yum install -y gcc-c++ rpm-build git cppunit-devel gtest-devel python-srpm-macros centos-release-scl - - xrootd_version=$(git for-each-ref --sort=taggerdate --format '%(refname)' refs/tags | grep '^refs/tags/v5' | grep -v 'rc.*$' | grep -v 'osghotfix' | grep -v 'CERN$' | sed -e '$!d') - - xrootd_version=${xrootd_version:11} - - echo $xrootd_version - - short_hash=$(git rev-parse --verify HEAD | awk '{print substr($0, 0, 8)}') - - a=( ${xrootd_version//./ } ) - - ((a[2]++)) || true - - echo $CI_PIPELINE_ID - - experimental_version="${a[0]}.${a[1]}.${a[2]}-0.experimental."${CI_PIPELINE_ID}.$short_hash - - echo $experimental_version - - cd packaging/ - - ./makesrpm.sh --version $experimental_version --define "_with_python3 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" --define "_with_isal 1" - - yum-builddep -y *.src.rpm - - mkdir RPMS - - rpmbuild --rebuild --define "_rpmdir RPMS/" --define "_with_tests 1" --define "_with_python3 1" --define "_with_xrdclhttp 1" --define "_with_scitokens 1" --define "_with_isal 1" --define "_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm" *.src.rpm - - cd .. - - mkdir epel-7/ - - cp packaging/*.src.rpm epel-7 - - cp packaging/RPMS/* epel-7 - artifacts: - expire_in: 1 day - paths: - - epel-7/ - tags: - - docker_node - only: - - schedules - - web - except: - - tags - -publish:rhel: - stage: publish - image: gitlab-registry.cern.ch/linuxsupport/cc7-base - script: - - yum install -y sssd-client sudo createrepo - - prefix=/eos/project/s/storage-ci/www/xrootd - - "for platform in cs-8 cc-7 fc-{37..39}; do - repo=$prefix/${CI_COMMIT_REF_NAME}/$platform/x86_64 - path=$repo/$(date +'%Y%m%d'); - sudo -u stci -H mkdir -p $path; - sudo -u stci -H find ${path} -type f -name '*.rpm' -delete; - sudo -u stci -H cp $platform/* $path; - sudo -u stci -H createrepo --update -q $path; - sudo -u stci -H createrepo --update -q $repo; - done" - tags: - - docker_node - only: - - master - - /^stable-.*$/ - except: - - tags - - schedules - - web - -publish:debian: - stage: publish - image: ubuntu:jammy - script: - - apt-get update - - apt-get install -y sudo apt-utils sssd gpg - - mkdir /home/stci - - chown -R stci:def-cg /home/stci - - chmod -R 700 /home/stci - - sudo -u stci -H gpg --homedir /home/stci/ --allow-secret-key-import --import /keys/stci-debian-repo.sec - - sudo -u stci -H ./packaging/debian_scripts/publish_debian_cern.sh ${CI_COMMIT_REF_NAME} - tags: - - docker_node - dependencies: - - build:deb_ubuntu_focal - - build:deb_ubuntu_jammy - only: - - master - - /^stable-.*$/ - except: - - tags - - schedules - - web - -publish:rhel:release: - stage: publish - image: gitlab-registry.cern.ch/linuxsupport/cc7-base - script: - - yum install -y sssd-client sudo createrepo - - prefix=/eos/project/s/storage-ci/www/xrootd - - tarball=cc-7-x86_64/xrootd-*.tar.gz - - "for platform in alma-8-x86_64 alma-9-x86_64 cs-8-x86_64 cc-7-x86_64; do - path=$prefix/release/$platform/$CI_COMMIT_TAG/; - sudo -u stci -H mkdir -p $path/{source,tarball}; - sudo -u stci -H cp $platform/*.rpm $path; - sudo -u stci -H find ${path} -type f -name '*.src.rpm' -delete; - sudo -u stci -H cp $platform/*.src.rpm $path/source; - sudo -u stci -H cp $tarball $path/tarball; - sudo -u stci -H createrepo --update -q $path; - sudo -u stci -H createrepo --update -q $prefix/release/$platform; - done" - tags: - - docker_node - only: - - web - except: - - branches - -publish:debian:release: - stage: publish - image: ubuntu:jammy - script: - - apt-get update - - apt-get install -y sudo apt-utils sssd gpg - - mkdir /home/stci - - chown -R stci:def-cg /home/stci - - chmod -R 700 /home/stci - - sudo -u stci -H gpg --homedir /home/stci/ --allow-secret-key-import --import /keys/stci-debian-repo.sec - - repo=release - - if [[ $CI_COMMIT_TAG == *rc* ]] ; then repo=testing ; fi - - sudo -u stci -H ./packaging/debian_scripts/publish_debian_cern.sh $repo - tags: - - docker_node - dependencies: - - release:deb_ubuntu_focal - - release:deb_ubuntu_jammy - only: - - web - except: - - branches - -publish:weekly: - stage: publish - image: gitlab-registry.cern.ch/linuxsupport/cc7-base - script: - - yum install -y sssd-client sudo createrepo - - prefix=/eos/project/s/storage-ci/www/xrootd - - "for platform in epel-8 epel-7 epel-6; do - if [ -d $platform ] ; then - path=$prefix/experimental/$platform/x86_64/; - sudo -u stci -H mkdir -p $path; - ls -latr $platform/; - echo $path; - sudo -u stci -H cp $platform/* $path; - sudo -u stci -H createrepo --update -q $path; - fi; - done" - tags: - - docker_node - dependencies: - - weekly:cc7 - - weekly:cs8 - only: - - schedules - - web - except: - - tags - -publish:koji:cs8: - stage: post:publish - image: gitlab-registry.cern.ch/linuxsupport/rpmci/kojicli - script: - - yum install -y sssd-client - - kinit stci@CERN.CH -k -t /stci.krb5/stci.keytab - - path=/eos/project/s/storage-ci/www/xrootd/release/cs-8-x86_64/$CI_COMMIT_TAG/source/ - - if [[ $CI_COMMIT_TAG != *rc* ]] ; then koji build eos8 $path/*.src.rpm ; else stat $path/*.src.rpm ; fi - tags: - - docker_node - only: - - web - except: - - branches - when: manual - -publish:koji:cc7: - stage: post:publish - image: gitlab-registry.cern.ch/linuxsupport/rpmci/kojicli - script: - - yum install -y sssd-client - - kinit stci@CERN.CH -k -t /stci.krb5/stci.keytab - - path=/eos/project/s/storage-ci/www/xrootd/release/cc-7-x86_64/$CI_COMMIT_TAG/source/ - - if [[ $CI_COMMIT_TAG != *rc* ]] ; then koji build eos7 $path/*.src.rpm ; else stat $path/*.src.rpm ; fi - tags: - - docker_node - only: - - web - except: - - branches - when: manual - -clean:artifacts: - stage: clean - image: gitlab-registry.cern.ch/linuxsupport/cc7-base - script: - - yum install -y sssd-client sudo createrepo - - sudo -u stci -H bash -c 'for commit_dir in /eos/project/s/storage-ci/www/xrootd/master/*/*/; do find ${commit_dir} -mindepth 1 -maxdepth 1 -type d -ctime +10 | xargs rm -rf; createrepo --update -q ${commit_dir}; done' - - sudo -u stci -H bash -c 'for commit_dir in /eos/project/s/storage-ci/www/xrootd/stable-*/*/*/; do find ${commit_dir} -type f -name '"'"'*.rpm'"'"' -mtime +30 -delete; createrepo --update -q ${commit_dir}; done' - - sudo -u stci -H bash -c 'for commit_dir in /eos/project/s/storage-ci/www/xrootd/experimental/*/x86_64/; do find ${commit_dir} -type f -name '"'"'*.rpm'"'"' -mtime +30 -delete; createrepo --update -q ${commit_dir}; done' - tags: - - docker_node - allow_failure: true - only: - - schedules - - web - except: - - tags +Fedora 38: + image: fedora:38 + <<: *rpm_build_dnf +Fedora 39: + image: fedora:39 + <<: *rpm_build_dnf From 932b1d2f59b7bdf361331c00704c7c3b10632cd7 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 8 Dec 2023 11:49:30 +0100 Subject: [PATCH 363/442] [RPM] Update spec file for XRootD 5.6.4 --- xrootd.spec | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/xrootd.spec b/xrootd.spec index 9fee2b5d2f7..a8f2d658161 100644 --- a/xrootd.spec +++ b/xrootd.spec @@ -18,19 +18,21 @@ Name: xrootd Epoch: 1 -Release: 2%{?dist}%{?with_clang:.clang}%{?with_asan:.asan}%{?with_openssl11:.ssl11} +Release: 1%{?dist}%{?with_clang:.clang}%{?with_asan:.asan}%{?with_openssl11:.ssl11} Summary: Extended ROOT File Server Group: System Environment/Daemons License: LGPL-3.0-or-later AND BSD-2-Clause AND BSD-3-Clause AND curl AND MIT AND Zlib URL: https://xrootd.slac.stanford.edu -%if %{with git} -Version: %(git describe --match 'v*' | sed -e 's/v//; s/-rc/~rc/; s/-g/+git/; s/-/.post/; s/-/./') -Source0: %{name}.tar.gz -%else -Version: 5.6.3 +%if !%{with git} +Version: 5.6.4 Source0: %{url}/download/v%{version}/%{name}-%{version}.tar.gz -Patch0: %{url}/download/v%{version}/%{name}-%{version}-install-xrdnet-pmark-header.patch +%else +%define git_version %(tar xzf %{_sourcedir}/%{name}.tar.gz -O xrootd/VERSION) +%define src_version %(sed -e "s/%%(describe)/v5.7-rc%(date +%%Y%%m%%d)/" <<< "%git_version") +%define rpm_version %(sed -e 's/v//; s/-rc/~rc/; s/-g/+git/; s/-/.post/; s/-/./' <<< "%src_version") +Version: %rpm_version +Source0: %{name}.tar.gz %endif %if %{with compat} @@ -51,10 +53,10 @@ Source1: %{url}/download/v%{compat_version}/%{name}-%{compat_version}.tar.gz %endif %if %{?rhel}%{!?rhel:0} == 7 -BuildRequires: cmake3 >= 3.16 +BuildRequires: cmake3 BuildRequires: %{devtoolset}-toolchain %else -BuildRequires: cmake >= 3.16 +BuildRequires: cmake BuildRequires: gcc-c++ %endif BuildRequires: gdb @@ -92,7 +94,7 @@ BuildRequires: python%{python3_other_pkgversion}-setuptools BuildRequires: json-c-devel BuildRequires: libmacaroons-devel BuildRequires: libuuid-devel -BuildRequires: voms-devel >= 2.0.6 +BuildRequires: voms-devel BuildRequires: scitokens-cpp-devel BuildRequires: davix-devel @@ -131,15 +133,14 @@ BuildRequires: openssl-devel %endif %if %{with tests} +BuildRequires: attr BuildRequires: cppunit-devel BuildRequires: gtest-devel +BuildRequires: openssl %endif %if %{with xrdec} -BuildRequires: autoconf -BuildRequires: automake -BuildRequires: libtool -BuildRequires: yasm +BuildRequires: isa-l-devel %endif Requires: %{name}-server%{?_isa} = %{epoch}:%{version}-%{release} @@ -433,7 +434,6 @@ export CXX=clang++ -DCMAKE_INSTALL_LIBDIR:PATH=%{_libdir} \ -DCMAKE_INSTALL_SYSCONFDIR:PATH=%{_sysconfdir} \ -DFORCE_ENABLED:BOOL=TRUE \ - -DUSE_SYSTEM_ISAL:BOOL=FALSE \ -DENABLE_ASAN:BOOL=%{with asan} \ -DENABLE_FUSE:BOOL=TRUE \ -DENABLE_KRB5:BOOL=TRUE \ @@ -444,7 +444,6 @@ export CXX=clang++ -DENABLE_VOMS:BOOL=TRUE \ -DENABLE_XRDCL:BOOL=TRUE \ -DENABLE_XRDCLHTTP:BOOL=TRUE \ - -DENABLE_XRDEC:BOOL=%{with xrdec} \ -DXRDCEPH_SUBMODULE:BOOL=%{with ceph} \ -DENABLE_XRDCLHTTP:BOOL=TRUE \ -DXRDCL_ONLY:BOOL=FALSE \ @@ -455,7 +454,7 @@ make -C %{_builddir}/%{name}-%{compat_version}/build %{?_smp_mflags} %cmake \ -DFORCE_ENABLED:BOOL=TRUE \ - -DUSE_SYSTEM_ISAL:BOOL=FALSE \ + -DUSE_SYSTEM_ISAL:BOOL=TRUE \ -DENABLE_ASAN:BOOL=%{with asan} \ -DENABLE_FUSE:BOOL=TRUE \ -DENABLE_KRB5:BOOL=TRUE \ @@ -951,6 +950,11 @@ fi %changelog +* Fri Dec 08 2023 Guilherme Amadio - 1:5.6.4-1 +- Use isa-l library from the system +- Extract version from tarball when building git snapshots +- XRootD 5.6.4 + * Fri Oct 27 2023 Guilherme Amadio - 1:5.6.3-2 - XRootD 5.6.3 From c968dddaf17fadca4029714144cfd17854f34012 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 11 Dec 2023 00:48:45 +0100 Subject: [PATCH 364/442] [DEB] Add attr build dependency (for setfattr command) --- debian/control | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/control b/debian/control index ff45b390d0e..f447fe51b9c 100644 --- a/debian/control +++ b/debian/control @@ -5,6 +5,7 @@ Standards-Version: 4.6.2 Build-Depends: debhelper (>= 13), dh-python, + attr, cmake, libgtest-dev, libcppunit-dev, From f6ac31a8fb82f094822171d7b54489f46d3fda83 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 16 Nov 2023 14:00:29 +0100 Subject: [PATCH 365/442] [docker] Update xrd-docker and Dockerfiles for new packaging The tests now run as part of the RPM builds, so no need to setup and start containers. Just running the builds will already run the tests. --- docker/build/Dockerfile.alma8 | 40 +++----- docker/build/Dockerfile.alma9 | 40 +++----- docker/build/Dockerfile.centos7 | 38 +++----- docker/build/Dockerfile.debian | 31 ++++++ docker/build/Dockerfile.fedora | 32 +++++++ docker/build/Dockerfile.ubuntu | 33 +++++++ docker/config/xrootd-clustered.cfg | 59 ------------ docker/config/xrootd-srv2.cfg | 4 - docker/scripts/setup.sh | 68 ------------- docker/xrd-docker | 148 +++++------------------------ 10 files changed, 159 insertions(+), 334 deletions(-) create mode 100644 docker/build/Dockerfile.debian create mode 100644 docker/build/Dockerfile.fedora create mode 100644 docker/build/Dockerfile.ubuntu delete mode 100644 docker/config/xrootd-clustered.cfg delete mode 100644 docker/config/xrootd-srv2.cfg delete mode 100755 docker/scripts/setup.sh diff --git a/docker/build/Dockerfile.alma8 b/docker/build/Dockerfile.alma8 index 83ece6b5b8b..06aad45a888 100644 --- a/docker/build/Dockerfile.alma8 +++ b/docker/build/Dockerfile.alma8 @@ -1,14 +1,18 @@ FROM almalinux:8 # Install tools necessary for RPM development -RUN dnf install -y rpm-build rpmdevtools dnf-plugins-core epel-release \ +RUN dnf install -y dnf-plugins-core epel-release rpmdevtools sudo \ && dnf config-manager --set-enabled powertools +# Create xrootd user to avoid running tests as root +RUN groupadd xrootd && useradd -g xrootd -m xrootd + +USER xrootd +WORKDIR /home/xrootd + # Create directory tree for building RPMs RUN rpmdev-setuptree -WORKDIR /root - # XRootD source tarball must be created in the # current directory in order to build this image COPY xrootd.tar.gz rpmbuild/SOURCES @@ -16,31 +20,13 @@ COPY xrootd.tar.gz rpmbuild/SOURCES # Extract spec file to build RPMs RUN tar xzf rpmbuild/SOURCES/xrootd.tar.gz --strip-components=1 xrootd/xrootd.spec +USER root + # Install build dependencies with dnf -RUN dnf builddep -y -D 'dist .el8' --define '_with_isal 1' --define '_with_python3 1' \ - --define '_with_scitokens 1' --define '_with_tests 1' --define '_with_xrdclhttp 1' \ - --define '_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm' xrootd.spec +RUN dnf builddep -y xrootd.spec # Build RPMS for XRootD -RUN rpmbuild -bb -D 'dist .el8' --define '_with_isal 1' --define '_with_python3 1' \ - --define '_with_scitokens 1' --define '_with_tests 1' --define '_with_xrdclhttp 1' \ - --define '_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm' xrootd.spec - -# Second stage, build test image -FROM almalinux:8 - -COPY --from=0 /root/rpmbuild/RPMS/* /tmp/ - -# Install RPMS for XRootD and cleanup unneeded files -RUN rm /tmp/*-{devel,doc}* \ - && dnf install -y dnf-plugins-core epel-release \ - && dnf config-manager --set-enabled powertools \ - && dnf install -y /tmp/*.rpm \ - && dnf autoremove -y \ - && rm -rf /tmp/* - -# Copy configuration files -COPY config/*.cfg /etc/xrootd/ -COPY scripts/setup.sh /bin/setup.sh +RUN sudo -u xrootd rpmbuild -bb --with git xrootd.spec -CMD [ "/sbin/init" ] +# Install RPMS +RUN yum install -y rpmbuild/RPMS/*/*.rpm diff --git a/docker/build/Dockerfile.alma9 b/docker/build/Dockerfile.alma9 index ec7ea70494c..d44a781858c 100644 --- a/docker/build/Dockerfile.alma9 +++ b/docker/build/Dockerfile.alma9 @@ -1,14 +1,18 @@ FROM almalinux:9 # Install tools necessary for RPM development -RUN dnf install -y rpm-build rpmdevtools dnf-plugins-core epel-release \ +RUN dnf install -y dnf-plugins-core epel-release rpmdevtools sudo \ && dnf config-manager --set-enabled crb +# Create xrootd user to avoid running tests as root +RUN groupadd xrootd && useradd -g xrootd -m xrootd + +USER xrootd +WORKDIR /home/xrootd + # Create directory tree for building RPMs RUN rpmdev-setuptree -WORKDIR /root - # XRootD source tarball must be created in the # current directory in order to build this image COPY xrootd.tar.gz rpmbuild/SOURCES @@ -16,31 +20,13 @@ COPY xrootd.tar.gz rpmbuild/SOURCES # Extract spec file to build RPMs RUN tar xzf rpmbuild/SOURCES/xrootd.tar.gz --strip-components=1 xrootd/xrootd.spec +USER root + # Install build dependencies with dnf -RUN dnf builddep -y -D 'dist .el9' --define '_with_isal 1' --define '_with_python3 1' \ - --define '_with_scitokens 1' --define '_with_tests 1' --define '_with_xrdclhttp 1' \ - --define '_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm' xrootd.spec +RUN dnf builddep -y xrootd.spec # Build RPMS for XRootD -RUN rpmbuild -bb -D 'dist .el9' --define '_with_isal 1' --define '_with_python3 1' \ - --define '_with_scitokens 1' --define '_with_tests 1' --define '_with_xrdclhttp 1' \ - --define '_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm' xrootd.spec - -# Second stage, build test image -FROM almalinux:9 - -COPY --from=0 /root/rpmbuild/RPMS/* /tmp/ - -# Install RPMS for XRootD and cleanup unneeded files -RUN rm /tmp/*-{devel,doc}* \ - && dnf install -y dnf-plugins-core epel-release \ - && dnf config-manager --set-enabled crb \ - && dnf install -y /tmp/*.rpm \ - && dnf autoremove -y \ - && rm -rf /tmp/* - -# Copy configuration files -COPY config/*.cfg /etc/xrootd/ -COPY scripts/setup.sh /bin/setup.sh +RUN sudo -u xrootd rpmbuild -bb --with git xrootd.spec -CMD [ "/sbin/init" ] +# Install RPMS +RUN yum install -y rpmbuild/RPMS/*/*.rpm diff --git a/docker/build/Dockerfile.centos7 b/docker/build/Dockerfile.centos7 index ac3df9a45b9..984fad32478 100644 --- a/docker/build/Dockerfile.centos7 +++ b/docker/build/Dockerfile.centos7 @@ -1,9 +1,14 @@ FROM centos:7 # Install tools necessary for RPM development -RUN yum install -y centos-release-scl epel-release rpm-build rpmdevtools yum-utils +RUN yum install -y centos-release-scl epel-release git \ + && yum install -y epel-rpm-macros rpmdevtools sudo yum-utils -WORKDIR /root +# Create xrootd user to avoid running tests as root +RUN groupadd xrootd && useradd -g xrootd -m xrootd + +USER xrootd +WORKDIR /home/xrootd # Create directory tree for building RPMs RUN rpmdev-setuptree @@ -15,30 +20,13 @@ COPY xrootd.tar.gz rpmbuild/SOURCES # Extract spec file to build RPMs RUN tar xzf rpmbuild/SOURCES/xrootd.tar.gz --strip-components=1 xrootd/xrootd.spec +USER root + # Install build dependencies with yum -RUN yum-builddep -y --define '_with_isal 1' --define '_with_python3 1' \ - --define '_with_scitokens 1' --define '_with_tests 1' --define '_with_xrdclhttp 1' \ - --define '_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm' xrootd.spec +RUN yum-builddep -y xrootd.spec # Build RPMS for XRootD -RUN rpmbuild -bb --define '_with_isal 1' --define '_with_python3 1' \ - --define '_with_scitokens 1' --define '_with_tests 1' --define '_with_xrdclhttp 1' \ - --define '_build_name_fmt %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm' xrootd.spec - -# Second stage, build test image -FROM centos:7 - -COPY --from=0 /root/rpmbuild/RPMS/* /tmp/ - -# Install RPMS for XRootD and cleanup unneeded files -RUN rm /tmp/*-{devel,doc}* \ - && yum install -y epel-release \ - && yum install -y wget /tmp/*.rpm \ - && yum autoremove -y \ - && rm -rf /tmp/* - -# Copy configuration files -COPY config/*.cfg /etc/xrootd/ -COPY scripts/setup.sh /bin/setup.sh +RUN sudo -u xrootd rpmbuild -bb --with git xrootd.spec -CMD [ "/sbin/init" ] +# Install RPMS +RUN yum install -y rpmbuild/RPMS/*/*.rpm diff --git a/docker/build/Dockerfile.debian b/docker/build/Dockerfile.debian new file mode 100644 index 00000000000..b375f81c593 --- /dev/null +++ b/docker/build/Dockerfile.debian @@ -0,0 +1,31 @@ +ARG version=12 +FROM debian:$version + +RUN apt update +RUN apt install -y build-essential devscripts equivs sudo + +# Create xrootd user to avoid running tests as root +RUN groupadd xrootd && useradd -g xrootd -m xrootd + +USER xrootd +WORKDIR /home/xrootd + +# XRootD source tarball must be created in the +# current directory in order to build this image +COPY xrootd.tar.gz . + +# Extract tarball +RUN tar xzf xrootd.tar.gz + +USER root +WORKDIR /home/xrootd/xrootd + +# Install build dependencies with dnf +RUN echo yes | mk-build-deps --install --remove debian/control + +# Build DEB packages for XRootD +RUN export VERSION=$(sed -e 's/v//; s/-rc/~rc/; s/-g/+git/; s/-/.post/; s/-/./' < VERSION) \ + && sudo -u xrootd dch --create --package xrootd -v ${VERSION} -M 'XRootD automated build.' \ + && sudo -u xrootd debuild --no-tgz-check --no-sign -b + +RUN apt install -y ../*.d*eb diff --git a/docker/build/Dockerfile.fedora b/docker/build/Dockerfile.fedora new file mode 100644 index 00000000000..6c11a2b91c7 --- /dev/null +++ b/docker/build/Dockerfile.fedora @@ -0,0 +1,32 @@ +ARG version=rawhide +FROM fedora:$version + +# Install tools necessary for RPM development +RUN dnf install -y dnf-plugins-core rpmdevtools sudo + +# Create xrootd user to avoid running tests as root +RUN groupadd xrootd && useradd -g xrootd -m xrootd + +USER xrootd +WORKDIR /home/xrootd + +# Create directory tree for building RPMs +RUN rpmdev-setuptree + +# XRootD source tarball must be created in the +# current directory in order to build this image +COPY xrootd.tar.gz rpmbuild/SOURCES + +# Extract spec file to build RPMs +RUN tar xzf rpmbuild/SOURCES/xrootd.tar.gz --strip-components=1 xrootd/xrootd.spec + +USER root + +# Install build dependencies with dnf +RUN dnf builddep -y xrootd.spec + +# Build RPMS for XRootD +RUN sudo -u xrootd rpmbuild -bb --with git xrootd.spec + +# Install RPMS +RUN yum install -y rpmbuild/RPMS/*/*.rpm diff --git a/docker/build/Dockerfile.ubuntu b/docker/build/Dockerfile.ubuntu new file mode 100644 index 00000000000..612a748c76a --- /dev/null +++ b/docker/build/Dockerfile.ubuntu @@ -0,0 +1,33 @@ +ARG version=22.04 +FROM ubuntu:$version + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt update +RUN apt install -y build-essential devscripts equivs sudo + +# Create xrootd user to avoid running tests as root +RUN groupadd xrootd && useradd -g xrootd -m xrootd + +USER xrootd +WORKDIR /home/xrootd + +# XRootD source tarball must be created in the +# current directory in order to build this image +COPY xrootd.tar.gz . + +# Extract tarball +RUN tar xzf xrootd.tar.gz + +USER root +WORKDIR /home/xrootd/xrootd + +# Install build dependencies with dnf +RUN echo yes | mk-build-deps --install --remove debian/control + +# Build DEB packages for XRootD +RUN export VERSION=$(sed -e 's/v//; s/-rc/~rc/; s/-g/+git/; s/-/.post/; s/-/./' < VERSION) \ + && sudo -u xrootd dch --create --package xrootd -v ${VERSION} -M 'XRootD automated build.' \ + && sudo -u xrootd debuild --no-tgz-check --no-sign -b + +RUN apt install -y ../*.d*eb diff --git a/docker/config/xrootd-clustered.cfg b/docker/config/xrootd-clustered.cfg deleted file mode 100644 index f6228b28a99..00000000000 --- a/docker/config/xrootd-clustered.cfg +++ /dev/null @@ -1,59 +0,0 @@ -all.export /data -cms.delay startup 10 -cms.space linger 0 recalc 15 min 2% 1g 5% 2g - -xrootd.diglib * /etc/xrootd/dbgauth - -ofs.ckslib zcrc32 /usr/lib64/libXrdCksCalczcrc32.so -xrootd.chksum zcrc32 - -all.adminpath /var/spool/xrootd -all.pidpath /run/xrootd - -xrd.port 1094 if exec xrootd -xrd.port 2094 if exec cmsd - -if metaman+ -all.role meta manager -all.manager meta metaman 2094 - -else if man1+ -all.role manager -all.manager meta metaman 2094 -all.manager man1 2094 - -else if man2+ -all.role manager -all.manager meta metaman 2094 -all.manager man2 2094 - -else if srv1+ -all.role server -all.manager man1 2094 -ofs.tpc ttl 60 60 xfr 9 pgm /usr/bin/xrdcp --server -ofs.chkpnt enable - -else if srv2+ -all.role server -all.manager man1 2094 -ofs.tpc ttl 60 60 xfr 9 pgm /usr/bin/xrdcp --server -ofs.chkpnt enable - -else if srv3+ -all.role server -all.manager man2 2094 -ofs.tpc ttl 60 60 xfr 9 pgm /usr/bin/xrdcp --server -ofs.chkpnt enable - -else if srv4+ -all.role server -all.manager man2 2094 -ofs.tpc ttl 60 60 xfr 9 pgm /usr/bin/xrdcp --server -ofs.chkpnt enable -fi - -if defined ?~TEST_SIGNING -xrootd.seclib /usr/lib64/libXrdSec-4.so -sec.protocol unix -sec.level compatible force -fi diff --git a/docker/config/xrootd-srv2.cfg b/docker/config/xrootd-srv2.cfg deleted file mode 100644 index 57c022faa20..00000000000 --- a/docker/config/xrootd-srv2.cfg +++ /dev/null @@ -1,4 +0,0 @@ -all.export /data -all.adminpath /var/spool/xrootd -all.pidpath /var/run/xrootd -xrd.port 1099 diff --git a/docker/scripts/setup.sh b/docker/scripts/setup.sh deleted file mode 100755 index 0da39070d3d..00000000000 --- a/docker/scripts/setup.sh +++ /dev/null @@ -1,68 +0,0 @@ -#!/bin/bash - -# set the TEST_SIGNING -export TEST_SIGNING=1 - -if [[ ${HOSTNAME} = 'metaman' ]] ; then - # download the a test file for upload tests - mkdir -p /data - cp /downloads/a048e67f-4397-4bb8-85eb-8d7e40d90763.dat /data/testFile.dat - chown -R xrootd:xrootd /data -fi - -# the data nodes -datanodes=('srv1' 'srv2' 'srv3' 'srv4') - -# create 'bigdir' in each of the data nodes -if [[ ${datanodes[*]} =~ ${HOSTNAME} ]] ; then - mkdir -p /data/bigdir - cd /data/bigdir - for i in `seq 10000`; do touch `uuidgen`.dat; done - cd - >/dev/null -fi - -# download the test files for 'srv1' -if [[ ${HOSTNAME} = 'srv1' ]] ; then - cp /downloads/a048e67f-4397-4bb8-85eb-8d7e40d90763.dat /data - cp /downloads/b3d40b3f-1d15-4ad3-8cb5-a7516acb2bab.dat /data - cp /downloads/b74d025e-06d6-43e8-91e1-a862feb03c84.dat /data - cp /downloads/cb4aacf1-6f28-42f2-b68a-90a73460f424.dat /data - cp /downloads/cef4d954-936f-4945-ae49-60ec715b986e.dat /data - mkdir /data/metalink - cp /downloads/input*.meta* /data/metalink/ - cp /downloads/ml*.meta* /data/metalink/ -fi - -# download the test files for 'srv2' and add another instance on 1099 -if [[ ${HOSTNAME} = 'srv2' ]] ; then - cp /downloads/1db882c8-8cd6-4df1-941f-ce669bad3458.dat /data - cp /downloads/3c9a9dd8-bc75-422c-b12c-f00604486cc1.dat /data - cp /downloads/7235b5d1-cede-4700-a8f9-596506b4cc38.dat /data - cp /downloads/7e480547-fe1a-4eaf-a210-0f3927751a43.dat /data - cp /downloads/89120cec-5244-444c-9313-703e4bee72de.dat /data -fi - -# download the test files for 'srv3' -if [[ ${HOSTNAME} = 'srv3' ]] ; then - cp /downloads/1db882c8-8cd6-4df1-941f-ce669bad3458.dat /data - cp /downloads/3c9a9dd8-bc75-422c-b12c-f00604486cc1.dat /data - cp /downloads/89120cec-5244-444c-9313-703e4bee72de.dat /data - cp /downloads/b74d025e-06d6-43e8-91e1-a862feb03c84.dat /data - cp /downloads/cef4d954-936f-4945-ae49-60ec715b986e.dat /data -fi - -# download the test files for 'srv4' -if [[ ${HOSTNAME} = 'srv4' ]] ; then - cp /downloads/1db882c8-8cd6-4df1-941f-ce669bad3458.dat /data - cp /downloads/7e480547-fe1a-4eaf-a210-0f3927751a43.dat /data - cp /downloads/89120cec-5244-444c-9313-703e4bee72de.dat /data - cp /downloads/b74d025e-06d6-43e8-91e1-a862feb03c84.dat /data - cp /downloads/cef4d954-936f-4945-ae49-60ec715b986e.dat /data - cp /downloads/data.zip /data - cp /downloads/large.zip /data -fi - -# make sure the test files and directories are owned by 'xrootd' user -if [[ ${datanodes[*]} =~ ${HOSTNAME} ]] ; then - chown -R xrootd:xrootd /data -fi diff --git a/docker/xrd-docker b/docker/xrd-docker index 41563620b24..4ec7367318f 100755 --- a/docker/xrd-docker +++ b/docker/xrd-docker @@ -11,10 +11,22 @@ build() { ${DOCKER} build -f build/Dockerfile.${OS} -t xrootd -t xrootd:${OS} . } + +buildx() { + OS=${1:-fedora} + ARCH=${2:-amd64} + ARCH=${ARCH/linux\/} + [[ -f xrootd.tar.gz ]] || package + [[ -f build/Dockerfile.${OS} ]] || die "unknwon OS: $OS" + ${DOCKER} buildx build --platform linux/${ARCH} -f build/Dockerfile.${OS} -t xrootd:${OS}-${ARCH/\/} . +} + +qemu() { + ${DOCKER} run --rm --privileged multiarch/qemu-user-static --reset -p yes +} + clean() { rm -f xrootd.tar.gz - ${DOCKER} rm -f metaman man1 man2 srv1 srv2 srv3 srv4 - ${DOCKER} system prune -f } die() { @@ -22,142 +34,30 @@ die() { exit 1 } -fetch() { - [[ -d data/tmp ]] || mkdir -p data/tmp - - pushd data >/dev/null - - if ! command -v curl >/dev/null; then - die "curl command not availble, cannot download data" - fi - - TEST_FILES=( - {data,large}.zip - input{1..5}.meta{4,link} - mlFileTest{1..4}.meta4 - mlTpcTest.meta4 - mlZipTest.meta4 - tmp/{E.coli,bible.txt,world192.txt} - 1db882c8-8cd6-4df1-941f-ce669bad3458.dat - 3c9a9dd8-bc75-422c-b12c-f00604486cc1.dat - 7e480547-fe1a-4eaf-a210-0f3927751a43.dat - 7235b5d1-cede-4700-a8f9-596506b4cc38.dat - 89120cec-5244-444c-9313-703e4bee72de.dat - a048e67f-4397-4bb8-85eb-8d7e40d90763.dat - b3d40b3f-1d15-4ad3-8cb5-a7516acb2bab.dat - b74d025e-06d6-43e8-91e1-a862feb03c84.dat - cb4aacf1-6f28-42f2-b68a-90a73460f424.dat - cef4d954-936f-4945-ae49-60ec715b986e.dat - ) - - for FILE in ${TEST_FILES[@]}; do - if [[ ! -f ${FILE} ]]; then - echo ">>> Downloading ${FILE}" - curl -L http://xrootd.cern.ch/tests/test-files/${FILE} -o ${FILE} - fi - done -} - package() { REPODIR=$(git rev-parse --show-toplevel) VERSION=$(git describe ${1:-HEAD}) - - # sanitize version name to work with RPMs - VERSION=${VERSION#v} # remove "v" prefix - VERSION=${VERSION/-rc/~rc} # release candidates use ~ in RPMs - VERSION=${VERSION/-g*/} # snapshots not supported well, filter out - VERSION=${VERSION/-/.post} # handle git describe for post releases - VERSION=${VERSION//-/.} # replace remaining dashes with dots - - pushd ${REPODIR} >/dev/null echo Creating tarball for XRootD ${VERSION} - sed -e "s/__VERSION__/${VERSION}/" -e 's/__RELEASE__/1/' \ - packaging/rhel/xrootd.spec.in >| xrootd.spec - git archive --prefix=xrootd/ --add-file xrootd.spec -o xrootd.tar.gz ${1:-HEAD} - rm xrootd.spec + pushd ${REPODIR} >/dev/null + git archive --prefix=xrootd/ -o xrootd.tar.gz ${1:-HEAD} popd >/dev/null mv ${REPODIR}/xrootd.tar.gz . } -run() { - TEST_SUITE=( - Utils - Socket - Poller - PostMaster - FileSystem - File - FileCopy - Threading - LocalFileHandler - Workflow - ) - - for T in ${@:-${TEST_SUITE[@]}}; do - ${DOCKER} exec -it metaman test-runner /usr/lib64/libXrdClTests.so "All Tests/${T}Test" - done - - # XrdEc tests are not working - # ${DOCKER} exec -it metaman test-runner /usr/lib64/libXrdEcTests.so 'All Tests' -} - -search() { - for container in metaman srv{1..4}; do - echo ${container}: - ${DOCKER} exec ${container} find /data $@ - echo - done -} - -setup() { - ${DOCKER} network create xrootd - - if [[ ${DOCKER} =~ podman ]]; then - EXTRA_OPTS="--group-add keep-groups" - else - EXTRA_OPTS="--privileged" - fi - for container in metaman man{1,2} srv{1..4}; do - echo ">>> Start ${container} container" - [[ ${container} =~ ^man* ]] && ALIAS=--network-alias=manalias || ALIAS='' - ${DOCKER} run -d ${EXTRA_OPTS} --name ${container} -h ${container} \ - -v ${PWD}/data:/downloads:z --net=xrootd --network-alias=multiip ${ALIAS} \ - --network-alias=${container} xrootd:${1:-latest} /sbin/init - echo ">>> Setup ${container}" - ${DOCKER} exec ${container} setup.sh - echo ">>> Start xrootd and cmsd in ${container}" - ${DOCKER} exec ${container} systemctl start xrootd@clustered - ${DOCKER} exec ${container} systemctl start cmsd@clustered - echo - done - - echo ">>> Start xrootd@srv2 in srv2" - ${DOCKER} exec srv2 systemctl start xrootd@srv2 - ${DOCKER} ps -} - -shell() { - ${DOCKER} exec -it ${1:-metaman} /bin/bash -} - usage() { echo $(basename $BASH_ARGV0) [COMMAND] [ARGS] echo echo COMMANDS: echo - echo " fetch -- create data/ directory and download all data for running tests" - echo " package [VERSION] -- create xrootd.tar.gz tarball (VERSION=HEAD by default)" - echo " build [OS] -- build docker image based on OS: centos7 (default), alma8, alma9" - echo " setup [OS] -- setup and launch all containers required to run the test suite" - echo " run [TEST] -- run [TEST] test within metaman (runs all tests if called without arguments)" - echo " clean -- clean up tarball, remove and stop all docker containers and networks" - echo " shell [CONTAINER] -- start a bash shell inside container CONTAINER (default: metaman)" + echo " clean -- remove tarball created by package command" + echo " package [VERSION] -- create xrootd.tar.gz tarball (VERSION=HEAD by default)" + echo " build [OS] -- build docker image based on OS: centos7 (default), alma8, alma9" + echo " buildx [OS] [ARCH] -- cross-build docker image based on OS/ARCH pair. Supported architectures" + echo " are amd64, aarch64, ppc64le, s390x (big-endian). Default OS is fedora." + echo " You can see supported platforms with docker buildx inspect --bootstrap." + echo " qemu -- setup QEMU to be able to run cross-builds with buildx command." echo - echo " A complete run of the test suite requires running the following commands:" - echo "" - echo " fetch, package, build, setup, run, clean" - echo "" - echo " The fetch command needs to be run only once to download testing data." + echo " Note: The test suite runs automatically during the container builds" echo } From c1285e5e7f1623ff6cbaa0a2d5bcafcbe1c84238 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 8 Dec 2023 16:15:02 +0100 Subject: [PATCH 366/442] [docs] Update documentation on how to run the tests --- docs/TESTING.md | 102 +++++++++++++----------------------------------- 1 file changed, 27 insertions(+), 75 deletions(-) diff --git a/docs/TESTING.md b/docs/TESTING.md index 6a63b9c9fd3..8bbdf98b08d 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -280,42 +280,42 @@ Dependencies to run containerized tests can be installed with apt install podman ``` -## Running XRootD Containerized Tests with Docker and/or Podman +## Running XRootD Tests on other platforms with Docker and/or Podman -Some XRootD tests implemented using [CppUnit](http://cppunit.sourceforge.net/) -require a containerized environment to run. This file explains how to use the -recently added `xrd-docker` script to setup and run these containerized tests. -The steps needed are described below. +If you would like to run XRootD tests on other platforms, you can use +the `xrd-docker` script and associated `Dockerfile`s in the `docker/` +subdirectory. The steps needed are described below. -1. Creating an XRootD tarball to build a container image +### Create an XRootD tarball to build in the container -The first thing that needs to be done is packaging a special tarball with the -right version of XRootD to be used to build the container image for testing. The -command `xrd-docker package` by default creates a tarball named `xrootd.tar.gz` -in the current directory. We recommend changing directory to the `docker/` +The first thing that needs to be done is packaging a tarball with the version +of XRootD to be used to build in the container image. The command `xrd-docker package` +by default creates a tarball named `xrootd.tar.gz` in the current directory using the +`HEAD` of the currently checked branch. We recommend changing directory to the `docker/` directory in the XRootD git repository in order to run these commands. Suppose -we would like to run the tests for release v5.6.0. Then, we would run +we would like to run the tests for release v5.6.4. Then, we would run ```sh -$ xrd-docker package v5.6.0 +$ xrd-docker package v5.6.4 ``` to create the tarball that will be used to build the container image. The -tarball created by this command is *not* a standard tarball. It prepares and -adds a spec file with the version already replaced in that the `Dockerfile`s use -during the build, so it is important to use `xrd-docker package` instead of -renaming a standard tarball and placing it in the current directory. - -1. Building a container image for testing - -The next step is to build the container image that will be used for testing. -The container image can be built with either `docker` or `podman`, and the -currently supported OSs are CentOS 7, AlmaLinux 8, and AlmaLinux 9. The command -to build the image is +tarball created by this command is a standard tarball created with `git archive`. +Inside it, the `VERSION` file contains the expanded version which is used by the +new spec file to detect the version of XRootD being built. You can also create a +source RPM with such tarballs, but they must be built with `rpmbuild --with git` +as done in the CI builds and the `Dockerfile`s in the `docker/build/` subdirectory. + +### Build the container image + +The next step is to build the container image for the desired OS. It can be built +with either `docker` or `podman`. The `xrd-docker` script has the `build` command +to facilitate this. Currently, supported OSs for building are CentOS 7, AlmaLinux 8, +AlmaLinux 9, Fedora. The command to build the image is simply ```sh $ xrd-docker build ``` -where `` is one of `centos7` (default), `alma8`, or `alma9`. The name -simply chooses which `Dockerfile` is used from the `build/` directory, as they -are named `Dockerfile.` for each suported OS. It is possible to add new +where `` is one of `centos7` (default), `alma8`, `alma9`, or `fedora`. The +name simply chooses which `Dockerfile` is used from the `build/` directory, as +they are named `Dockerfile.` for each suported OS. It is possible to add new `Dockerfile`s following this same naming scheme to support custom setups and still use `xrd-docker build` command to build an image. The images built with `xrd-docker build` are named simply `xrootd` (latest being a default tag added @@ -326,7 +326,7 @@ declared in the spec file, in the first stage, building the RPMs in a second stage, then, in a third stage starting from a fresh image, the RPMs built in stage 2 are copied over and installed with `yum` or `dnf`. -**Switching between `docker` and `podman` if both are installed +#### Switching between `docker` and `podman` if both are installed The `xrd-docker` script takes either `docker` or `podman` if available, in this order. If you have only one of the two installed, everything should work without @@ -338,54 +338,6 @@ $ export DOCKER=$(command -v podman) $ xrd-docker build # uses podman from now on... ``` -1. Downloading required test data - -Before setting up the containers and running the tests, we must ensure that all -data required to run the tests is present in the `data/` directory. This can be -done with a call to `xrd-docker fetch`. The `data/` directory is mounted into -the container images during setup, and each container in the small cluster -copies part of the data into the root directory to be served via XRootD. - -1. Setting up the cluster of containers - -Now that we have a container image and test data available, it's time to start -the cluster of containers that will be used for testing. The setup will create -a docker or podman network and add each container to it. The cluster structure -is as follows: - -```mermaid -graph TD; - metaman --> man1; - metaman --> man2; - man1 --> srv1; - man1 --> srv2; - man2 --> srv3; - man2 --> srv4; -``` - -The `metaman` container runs `cmsd` and acts as redirector for `man1` and -`man2`, which themselves are also redirectors for `srv{1..4}`, which in turn -serve data files. The container cluster can be setup with the command -```sh -$ xrd-docker setup -``` -where `` is optional and if not given, the `xrootd:latest` image will be -used. - -1. Running the tests - -Now that everything is setup, we can run the tests with - -```sh -$ xrd-docker run -``` - -If something goes wrong, it is possible to enter each container with the command -```sh -$ xrd-docker shell -``` -where `` is one of `metaman` (default), `man{1,2}` or `srv{1..4}`. - ### Appendix #### Setting up `podman` From ab4335702def1f4e126f41fabf323fb8c2579705 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Sun, 10 Dec 2023 22:36:46 +0100 Subject: [PATCH 367/442] [CI] Add workflow for builds on alternative architectures These builds are very slow since they run via emulation with QEMU. Therefore, they are only run on demand. --- .github/workflows/QEMU.yml | 57 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 .github/workflows/QEMU.yml diff --git a/.github/workflows/QEMU.yml b/.github/workflows/QEMU.yml new file mode 100644 index 00000000000..ae9534570da --- /dev/null +++ b/.github/workflows/QEMU.yml @@ -0,0 +1,57 @@ +name: QEMU + +on: + workflow_dispatch: + inputs: + os: + description: 'OS' + required: true + default: 'fedora' + type: choice + options: + - alma8 + - alma9 + - centos7 + - debian + - fedora + - ubuntu + arch: + description: 'Architecture' + required: true + default: 's390x' + type: choice + options: + - 386 + - amd64 + - arm + - arm64 + - ppc64le + - s390x + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-${{ inputs.os }}-${{ inputs.arch }} + cancel-in-progress: true + +defaults: + run: + shell: bash + +env: + DOCKER: podman + +jobs: + buildx: + name: QEMU (${{ inputs.os }}-${{ inputs.arch }}) + runs-on: ubuntu-latest + + steps: + - name: Clone repository + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Setup QEMU for cross-building images + run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes + + - name: Cross-build container with docker/podman buildx + run: cd docker && ./xrd-docker buildx ${{ inputs.os }} ${{ inputs.arch }} From 730df73734e3d3f6fd09714c0b4812f0efb67bd6 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 8 Dec 2023 17:06:14 +0100 Subject: [PATCH 368/442] XRootD 5.6.4 --- docs/ReleaseNotes.txt | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/docs/ReleaseNotes.txt b/docs/ReleaseNotes.txt index 5cebf989090..5843c1ff4bb 100644 --- a/docs/ReleaseNotes.txt +++ b/docs/ReleaseNotes.txt @@ -5,6 +5,37 @@ XRootD Release Notes ============= +------------- +Version 5.6.4 +------------- + ++ **Major bug fixes** + **[XrdHttp]** Fix segfault with macaroons (issue #2114) + **[XrdPss]** Fix segfault if pss.origin uses https protocol with no port (issue #2140) + ++ **Minor bug fixes** + **[CMake]** Fix include path in XRootDConfig.cmake (#2142) + **[Headers]** Fix header dependencies and missing includes/declarations (#2119) + **[Server]** Initialize pidFN to pidpath base directory if an error occurs + **[XrdCl]** Don't try to enable TCP_CORK in GNU/Hurd (#2115) + **[XrdCl]** Reapply fix for null-characters in error output (#2138, issue #1501) + **[XrdEc]** Fix alignment issues on SPARC (issue #2131) + **[XrdHttp,XrdNet]** Adapt Scitag min and max value to change in spec (#2139) + **[XrdPosix]** Fix compilation failure with latest MUSL libc + **[XrdSciTokens]** Initialize SecEntity.addrInfo to avoid SEGV (#2128) + **[XrdTls]** Switch from using a cert file to a cert chain file (issue #2126) + **[XrdZip]** Support big endian architectures in XrdZip (#2145) + ++ **Miscellaneous** + **[CMake]** Install CMake config file into lib/lib64 rather than share (#2116) + **[DEB/RPM]** Rewrite packaging for Debian and RHEL based distributions + **[Tests]** Convert tests to GoogleTest and run without containers (#2055, GSoC 2023) + **[Tests]** Other fixes and improvements to tests (#2115, #2129, #2130, #2137, #2141) + ++ **Known Issues** + **[XrdSciTokens]** In this release, as in previous ones, using scitokens with the ZTN + protocol may grant more access than should be allowed by the token scopes (issue #2121). + ------------- Version 5.6.3 ------------- From 333a5dd8187a897bc2c3c5e8097be0aef11b8e7e Mon Sep 17 00:00:00 2001 From: Mattias Ellert Date: Tue, 12 Dec 2023 13:39:53 +0100 Subject: [PATCH 369/442] Add #include for BSD and GNU/Hurd --- src/XrdSys/XrdSysPlatform.hh | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/XrdSys/XrdSysPlatform.hh b/src/XrdSys/XrdSysPlatform.hh index 0024369bb03..6f67b3b72ef 100644 --- a/src/XrdSys/XrdSysPlatform.hh +++ b/src/XrdSys/XrdSysPlatform.hh @@ -67,12 +67,18 @@ #if defined(__FreeBSD__) || (defined(__FreeBSD_kernel__) && defined(__GLIBC__)) #include #include +#if defined(__FreeBSD__) +#include +#else +#include +#endif #define MAXNAMELEN NAME_MAX #endif #ifdef __GNU__ #include #include +#include // These are undefined on purpose in GNU/Hurd. // The values below are the ones used in Linux. // The proper fix is to rewrite the code to not use hardcoded values, @@ -161,6 +167,12 @@ typedef off_t off64_t; #define bswap_64 OSSwapInt64 #endif +#if defined(__FreeBSD__) +#define bswap_16 bswap16 +#define bswap_32 bswap32 +#define bswap_64 bswap64 +#endif + static inline uint16_t bswap(uint16_t x) { return bswap_16(x); } static inline uint32_t bswap(uint32_t x) { return bswap_32(x); } static inline uint64_t bswap(uint64_t x) { return bswap_64(x); } From cdf2bc119b60bea28f18b78062855e61528008a0 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Wed, 13 Dec 2023 21:56:04 -0800 Subject: [PATCH 370/442] [HttpTpc] Align code and files with established naming conventions. --- src/XrdTpc.cmake | 2 +- src/XrdTpc/{PMarkManager.cc => XrdTpcPMarkManager.cc} | 7 +++++-- src/XrdTpc/{PMarkManager.hh => XrdTpcPMarkManager.hh} | 4 +++- src/XrdTpc/XrdTpcTPC.hh | 4 ++-- 4 files changed, 11 insertions(+), 6 deletions(-) rename src/XrdTpc/{PMarkManager.cc => XrdTpcPMarkManager.cc} (97%) rename src/XrdTpc/{PMarkManager.hh => XrdTpcPMarkManager.hh} (99%) diff --git a/src/XrdTpc.cmake b/src/XrdTpc.cmake index cbe22019b83..b3e5f46ce48 100644 --- a/src/XrdTpc.cmake +++ b/src/XrdTpc.cmake @@ -39,7 +39,7 @@ if( BUILD_TPC ) XrdTpc/XrdTpcState.cc XrdTpc/XrdTpcState.hh XrdTpc/XrdTpcStream.cc XrdTpc/XrdTpcStream.hh XrdTpc/XrdTpcTPC.cc XrdTpc/XrdTpcTPC.hh - XrdTpc/PMarkManager.cc XrdTpc/PMarkManager.hh) + XrdTpc/XrdTpcPMarkManager.cc XrdTpc/XrdTpcPMarkManager.hh) target_link_libraries( ${LIB_XRD_TPC} diff --git a/src/XrdTpc/PMarkManager.cc b/src/XrdTpc/XrdTpcPMarkManager.cc similarity index 97% rename from src/XrdTpc/PMarkManager.cc rename to src/XrdTpc/XrdTpcPMarkManager.cc index 796033385bd..201fce0c425 100644 --- a/src/XrdTpc/PMarkManager.cc +++ b/src/XrdTpc/XrdTpcPMarkManager.cc @@ -21,8 +21,10 @@ #include -#include "PMarkManager.hh" +#include "XrdTpcPMarkManager.hh" +namespace XrdTpc +{ PMarkManager::SocketInfo::SocketInfo(int fd, const struct sockaddr * sockP) { netAddr.Set(sockP,fd); client.addrInfo = static_cast(&netAddr); @@ -69,4 +71,5 @@ void PMarkManager::endPmark(int fd) { // We need to delete the PMark handle associated to the fd passed in parameter // we just look for it and reset the unique_ptr to nullptr to trigger the PMark handle deletion mPmarkHandles.erase(fd); -} \ No newline at end of file +} +} // namespace XrdTpc diff --git a/src/XrdTpc/PMarkManager.hh b/src/XrdTpc/XrdTpcPMarkManager.hh similarity index 99% rename from src/XrdTpc/PMarkManager.hh rename to src/XrdTpc/XrdTpcPMarkManager.hh index 3c166f413d4..3da5ddb0aa8 100644 --- a/src/XrdTpc/PMarkManager.hh +++ b/src/XrdTpc/XrdTpcPMarkManager.hh @@ -43,6 +43,8 @@ * In the case of multi-stream HTTP TPC transfers, a packet marking handle will be created for each stream. * The first one will be created as a basic one. The other will be created using the first packet marking handle as a basis. */ +namespace XrdTpc +{ class PMarkManager { public: @@ -108,6 +110,6 @@ private: // The file descriptor used to create the first packet marking handle int mInitialFD = -1; }; - +} // namespace XrdTpc #endif //XROOTD_PMARKMANAGER_HH diff --git a/src/XrdTpc/XrdTpcTPC.hh b/src/XrdTpc/XrdTpcTPC.hh index 4356299dba7..76f600c58c9 100644 --- a/src/XrdTpc/XrdTpcTPC.hh +++ b/src/XrdTpc/XrdTpcTPC.hh @@ -10,7 +10,7 @@ #include "XrdHttp/XrdHttpUtils.hh" #include "XrdTls/XrdTlsTempCA.hh" -#include "PMarkManager.hh" +#include "XrdTpcPMarkManager.hh" #include @@ -79,7 +79,7 @@ private: int tpc_status; unsigned int streams; bool isIPv6; - PMarkManager pmarkManager; + XrdTpc::PMarkManager pmarkManager; }; int ProcessOptionsReq(XrdHttpExtReq &req); From e19df1ae3694aecf68f4c3d75b1f608f7a9d4869 Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Fri, 15 Dec 2023 23:10:12 -0800 Subject: [PATCH 371/442] [Server] Align monitoring ID with http - replaces PR #2134 --- src/XrdXrootd/XrdXrootdMonitor.cc | 52 ++++++++++++++++++++----------- src/XrdXrootd/XrdXrootdMonitor.hh | 2 +- src/XrdXrootd/XrdXrootdXeq.cc | 2 +- 3 files changed, 36 insertions(+), 20 deletions(-) diff --git a/src/XrdXrootd/XrdXrootdMonitor.cc b/src/XrdXrootd/XrdXrootdMonitor.cc index e06a8ecfd3a..53a36ef7be4 100644 --- a/src/XrdXrootd/XrdXrootdMonitor.cc +++ b/src/XrdXrootd/XrdXrootdMonitor.cc @@ -286,29 +286,45 @@ void XrdXrootdMonitor::User::Enable() /******************************************************************************/ /* X r d X r o o t d M o n i t o r : : U s e r : : R e g i s t e r */ /******************************************************************************/ - + +// The gcc specific pragmas are to prevent gcc from complaining about an +// impossible situtation. Even if it were possible we don't care in practice. +// The snprintf below cannot be truncated as we made sure of that by setting +// null bytes to delimit the source buffer. However, gcc is not clever enough +// to figure that out and complains. See gcc Bug 1431678 for the history. +// BTW checking the return value works in C but not in C++, another oversight. +// The only other solution is to wait for C++20 and use std::format_to. + void XrdXrootdMonitor::User::Register(const char *Uname, const char *Hname, - const char *Pname) + const char *Pname, unsigned int xSID) { - const char *colonP, *atP; - char uBuff[1024], *uBP; - int n; +#ifndef NODEBUG + const char *TraceID = "Monitor"; +#endif + char *dotP, *colonP, *atP; + char uBuff[1024], tBuff[1024], sBuff[64]; + +// Decode the user name as a.b:c@d and remap it for monitoring as +// /a.{b|xSID}:@host +// + snprintf(tBuff, sizeof(tBuff), Uname); + if ((dotP = index(tBuff, '.')) && (colonP = index(dotP+1, ':')) && + (atP = index(colonP+1, '@'))) + {*dotP = 0; *colonP = 0; *atP = 0; + if (xSID) + {snprintf(sBuff, sizeof(sBuff), " %u", xSID); + dotP = sBuff; + } -// The identification always starts with the protocol being used -// - n = sprintf(uBuff, "%s/", Pname); - uBP = uBuff + n; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-truncation" + snprintf(uBuff, sizeof(uBuff), "%s/%s.%s:%s@%s", Pname, tBuff, + dotP+1, kySID, atP+1); +#pragma GCC diagnostic pop -// Decode the user name as a.b:c@d -// - if ((colonP = index(Uname, ':')) && (atP = index(colonP+1, '@'))) - {n = colonP - Uname + 1; - strncpy(uBP, Uname, n); - strcpy(uBP+n, kySID); - n += kySIDSZ; *(uBP+n) = '@'; n++; - strcpy(uBP+n, Hname); - } else strcpy(uBP, Uname); + if (xSID) {TRACE(LOGIN,"Register remap "< "<ID, Link->Host(), "xroot"); + {Monitor.Register(Link->ID, Link->Host(), "xroot", mySID); if (Monitor.Logins() && (!Monitor.Auths() || !(Status & XRD_NEED_AUTH))) {Monitor.Report(Entity.moninfo); if (Entity.moninfo) {free(Entity.moninfo); Entity.moninfo = 0;} From 4a98f484ab85aebdc0227505c14620eb8d65a36a Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Sat, 16 Dec 2023 23:30:45 -0800 Subject: [PATCH 372/442] [Server] Correct snprintf() that gcc never complained aout. --- src/XrdXrootd/XrdXrootdMonitor.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdXrootd/XrdXrootdMonitor.cc b/src/XrdXrootd/XrdXrootdMonitor.cc index 53a36ef7be4..d3c0e381b7f 100644 --- a/src/XrdXrootd/XrdXrootdMonitor.cc +++ b/src/XrdXrootd/XrdXrootdMonitor.cc @@ -308,7 +308,7 @@ void XrdXrootdMonitor::User::Register(const char *Uname, // Decode the user name as a.b:c@d and remap it for monitoring as // /a.{b|xSID}:@host // - snprintf(tBuff, sizeof(tBuff), Uname); + snprintf(tBuff, sizeof(tBuff), "%s", Uname); if ((dotP = index(tBuff, '.')) && (colonP = index(dotP+1, ':')) && (atP = index(colonP+1, '@'))) {*dotP = 0; *colonP = 0; *atP = 0; From d416b2a458841ef17d892189db88c0e6842df02a Mon Sep 17 00:00:00 2001 From: Brian Bockelman Date: Fri, 15 Dec 2023 18:03:20 -0600 Subject: [PATCH 373/442] Send User-Agent as the monitor info --- src/XrdHttp/XrdHttpProtocol.cc | 25 +++++++++++++++++++++++-- src/XrdHttp/XrdHttpProtocol.hh | 3 +++ src/XrdHttp/XrdHttpReq.cc | 4 ++++ src/XrdHttp/XrdHttpReq.hh | 6 ++++++ 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/XrdHttp/XrdHttpProtocol.cc b/src/XrdHttp/XrdHttpProtocol.cc index 3b82de72e75..0001b251fe3 100644 --- a/src/XrdHttp/XrdHttpProtocol.cc +++ b/src/XrdHttp/XrdHttpProtocol.cc @@ -582,9 +582,29 @@ int XrdHttpProtocol::Process(XrdLink *lp) // We ignore the argument here } else CurrentReq.reqstate++; + } else if (!DoneSetInfo && !CurrentReq.userAgent().empty()) { // DoingLogin is true, meaning the login finished. + std::string mon_info = "monitor info " + CurrentReq.userAgent(); + DoneSetInfo = true; + if (mon_info.size() >= 1024) { + TRACEI(ALL, "User agent string too long"); + } else if (!Bridge) { + TRACEI(ALL, "Internal logic error: Bridge is null after login"); + } else { + TRACEI(DEBUG, "Setting " << mon_info); + memset(&CurrentReq.xrdreq, 0, sizeof (ClientRequest)); + CurrentReq.xrdreq.set.requestid = htons(kXR_set); + CurrentReq.xrdreq.set.modifier = '\0'; + memset(CurrentReq.xrdreq.set.reserved, '\0', sizeof(CurrentReq.xrdreq.set.reserved)); + CurrentReq.xrdreq.set.dlen = htonl(mon_info.size()); + if (!Bridge->Run((char *) &CurrentReq.xrdreq, (char *) mon_info.c_str(), mon_info.size())) { + SendSimpleResp(500, nullptr, nullptr, "Could not set user agent.", 0, false); + return -1; + } + return 0; + } + } else { + DoingLogin = false; } - DoingLogin = false; - // Read the next request header, that is, read until a double CRLF is found @@ -1851,6 +1871,7 @@ void XrdHttpProtocol::Reset() { myBuffStart = myBuffEnd = 0; DoingLogin = false; + DoneSetInfo = false; ResumeBytes = 0; Resume = 0; diff --git a/src/XrdHttp/XrdHttpProtocol.hh b/src/XrdHttp/XrdHttpProtocol.hh index 6083d547b24..9d476480037 100644 --- a/src/XrdHttp/XrdHttpProtocol.hh +++ b/src/XrdHttp/XrdHttpProtocol.hh @@ -298,6 +298,9 @@ private: /// Tells that we are just logging in bool DoingLogin; + + /// Indicates whether we've attempted to send app info. + bool DoneSetInfo; /// Tells that we are just waiting to have N bytes in the buffer long ResumeBytes; diff --git a/src/XrdHttp/XrdHttpReq.cc b/src/XrdHttp/XrdHttpReq.cc index 0f77be0196c..db700cdeb2a 100644 --- a/src/XrdHttp/XrdHttpReq.cc +++ b/src/XrdHttp/XrdHttpReq.cc @@ -198,6 +198,9 @@ int XrdHttpReq::parseLine(char *line, int len) { if(prot->pmarkHandle != nullptr) { parseScitag(val); } + } else if (!strcasecmp(key, "User-Agent")) { + m_user_agent = val; + trim(m_user_agent); } else { // Some headers need to be translated into "local" cgi info. auto it = std::find_if(prot->hdr2cgimap.begin(), prot->hdr2cgimap.end(),[key](const auto & item) { @@ -2752,6 +2755,7 @@ void XrdHttpReq::reset() { m_req_cksum = nullptr; m_resource_with_digest = ""; + m_user_agent = ""; headerok = false; keepalive = true; diff --git a/src/XrdHttp/XrdHttpReq.hh b/src/XrdHttp/XrdHttpReq.hh index b88d3ab4588..ed56ede2066 100644 --- a/src/XrdHttp/XrdHttpReq.hh +++ b/src/XrdHttp/XrdHttpReq.hh @@ -74,6 +74,9 @@ private: int httpStatusCode; std::string httpStatusText; + // The value of the user agent, if specified + std::string m_user_agent; + // Whether transfer encoding was requested. bool m_transfer_encoding_chunked; long long m_current_chunk_offset; @@ -191,6 +194,9 @@ public: void addCgi(const std::string & key, const std::string & value); + // Return the current user agent; if none has been specified, returns an empty string + const std::string &userAgent() const {return m_user_agent;} + // ---------------- // Description of the request. The header/body parsing // is supposed to populate these fields, for fast access while From cf8466b965c5167fe160da32b788cd9431c94c03 Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Thu, 11 Jan 2024 15:39:12 +0100 Subject: [PATCH 374/442] [XrdTpcTPC] PMarkManager - corrected a segfault that may happen in the creation of the first packet marking handle --- src/XrdTpc/XrdTpcPMarkManager.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/XrdTpc/XrdTpcPMarkManager.cc b/src/XrdTpc/XrdTpcPMarkManager.cc index 201fce0c425..81437e6ee73 100644 --- a/src/XrdTpc/XrdTpcPMarkManager.cc +++ b/src/XrdTpc/XrdTpcPMarkManager.cc @@ -51,14 +51,14 @@ void PMarkManager::beginPMarks() { // This base pmark handle will be placed at the beginning of the vector of pmark handles std::stringstream ss; ss << "scitag.flow=" << mReq->mSciTag; - auto sockInfo = mSocketInfos.front(); + SocketInfo & sockInfo = mSocketInfos.front(); mInitialFD = sockInfo.client.addrInfo->SockFD(); mPmarkHandles.emplace(mInitialFD,mPmark->Begin(sockInfo.client, mReq->resource.c_str(), ss.str().c_str(), "http-tpc")); mSocketInfos.pop(); } else { // The first pmark handle was created, or not. Create the other pmark handles from the other connected sockets while(!mSocketInfos.empty()) { - auto & sockInfo = mSocketInfos.front(); + SocketInfo & sockInfo = mSocketInfos.front(); if(mPmarkHandles[mInitialFD]){ mPmarkHandles.emplace(sockInfo.client.addrInfo->SockFD(),mPmark->Begin(*sockInfo.client.addrInfo, *mPmarkHandles[mInitialFD], nullptr)); } From f6ef82f21b41af9eebf96d6d61204929b8fafc86 Mon Sep 17 00:00:00 2001 From: Mattias Ellert Date: Wed, 17 Jan 2024 09:59:02 +0100 Subject: [PATCH 375/442] Fix compilation error reported with gcc 14 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /builddir/build/BUILD/xrootd-5.6.4/src/XrdSecgsi/XrdSecgsitest.cc: In function ‘pdots(char const*, bool)’: /builddir/build/BUILD/xrootd-5.6.4/src/XrdSecgsi/XrdSecgsitest.cc:90:15: error: ‘%s’ directive argument is null [-Werror=format-overflow=] 90 | printf("|| %s ", t); | ^~ --- src/XrdSecgsi/XrdSecgsitest.cc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/XrdSecgsi/XrdSecgsitest.cc b/src/XrdSecgsi/XrdSecgsitest.cc index b0be6da0471..98a3fdda78c 100644 --- a/src/XrdSecgsi/XrdSecgsitest.cc +++ b/src/XrdSecgsi/XrdSecgsitest.cc @@ -87,7 +87,11 @@ static void pdots(const char *t, bool ok = 1) unsigned int i = 0; unsigned int l = (t) ? strlen (t) : 0; unsigned int np = PRTWIDTH - l - 8; - printf("|| %s ", t); + if (l > 0) { + printf("|| %s ", t); + } else { + printf("|| "); + } for (; i < np ; i++) { printf("."); } printf(" %s\n", (ok ? "PASSED" : "FAILED")); } From 0c043c1e101ec359cebc8fa717a9ddb4fd123d8c Mon Sep 17 00:00:00 2001 From: David Smith Date: Wed, 17 Jan 2024 16:11:52 +0100 Subject: [PATCH 376/442] [GSI] Skip check of our standard DH parameters --- src/XrdCrypto/XrdCryptosslCipher.cc | 39 +++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/src/XrdCrypto/XrdCryptosslCipher.cc b/src/XrdCrypto/XrdCryptosslCipher.cc index cb282c7eaeb..ea9c3b78ad3 100644 --- a/src/XrdCrypto/XrdCryptosslCipher.cc +++ b/src/XrdCrypto/XrdCryptosslCipher.cc @@ -166,7 +166,33 @@ static int DSA_set0_key(DSA *d, BIGNUM *pub_key, BIGNUM *priv_key) } #endif +static EVP_PKEY *getFixedDHParams() { + static EVP_PKEY *dhparms = [] { + EVP_PKEY *dhParam = 0; + + BIO *biop = BIO_new(BIO_s_mem()); + BIO_write(biop, dh_param_enc, strlen(dh_param_enc)); + PEM_read_bio_Parameters(biop, &dhParam); + BIO_free(biop); + return dhParam; + }(); + + assert(dhparms); + return dhparms; +} + static int XrdCheckDH (EVP_PKEY *pkey) { + // If the DH parameters we received are our fixed set we know they + // are acceptable. The parameter check requires computation and more + // with openssl 3 than previously. So skip if DH params are known. + const EVP_PKEY *dhparms = getFixedDHParams(); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + const bool skipcheck = EVP_PKEY_parameters_eq(pkey, dhparms); +#else + const bool skipcheck = EVP_PKEY_cmp_parameters(pkey, dhparms); +#endif + if (skipcheck) return 1; + int rc; #if OPENSSL_VERSION_NUMBER < 0x10101000L DH *dh = EVP_PKEY_get0_DH(pkey); @@ -524,10 +550,9 @@ XrdCryptosslCipher::XrdCryptosslCipher(bool padded, int bits, char *pub, deflength = 1; if (!pub) { - static EVP_PKEY *dhparms = [] { - DEBUG("generate DH parameters"); - EVP_PKEY *dhParam = 0; + DEBUG("generate DH parameters"); + EVP_PKEY *dhparms = getFixedDHParams(); // // Important historical context: // - We used to generate DH params on every server startup (commented @@ -558,18 +583,10 @@ XrdCryptosslCipher::XrdCryptosslCipher(bool padded, int bits, char *pub, EVP_PKEY_paramgen(pkctx, &dhParam); EVP_PKEY_CTX_free(pkctx); */ - BIO *biop = BIO_new(BIO_s_mem()); - BIO_write(biop, dh_param_enc, strlen(dh_param_enc)); - PEM_read_bio_Parameters(biop, &dhParam); - BIO_free(biop); - DEBUG("generate DH parameters done"); - return dhParam; - }(); DEBUG("configure DH parameters"); // // Set params for DH object - assert(dhparms); EVP_PKEY_CTX *pkctx = EVP_PKEY_CTX_new(dhparms, 0); EVP_PKEY_keygen_init(pkctx); EVP_PKEY_keygen(pkctx, &fDH); From 484f59f9cc3ba5dfcde3bd33379d8bd677cb8c3f Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 22 Jan 2024 15:13:36 +0100 Subject: [PATCH 377/442] [RPM] Update spec file for XRootD 5.6.5 --- xrootd.spec | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/xrootd.spec b/xrootd.spec index a8f2d658161..70223531da9 100644 --- a/xrootd.spec +++ b/xrootd.spec @@ -25,7 +25,7 @@ License: LGPL-3.0-or-later AND BSD-2-Clause AND BSD-3-Clause AND curl AND MIT AN URL: https://xrootd.slac.stanford.edu %if !%{with git} -Version: 5.6.4 +Version: 5.6.5 Source0: %{url}/download/v%{version}/%{name}-%{version}.tar.gz %else %define git_version %(tar xzf %{_sourcedir}/%{name}.tar.gz -O xrootd/VERSION) @@ -950,6 +950,9 @@ fi %changelog +* Mon Jan 22 2024 Guilherme Amadio - 1:5.6.5-1 +- XRootD 5.6.5 + * Fri Dec 08 2023 Guilherme Amadio - 1:5.6.4-1 - Use isa-l library from the system - Extract version from tarball when building git snapshots From 8f8498d66aa583c54c0875bb1cfe432f4be040f4 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 22 Jan 2024 15:18:10 +0100 Subject: [PATCH 378/442] XRootD 5.6.5 --- docs/ReleaseNotes.txt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/ReleaseNotes.txt b/docs/ReleaseNotes.txt index 5843c1ff4bb..6efc74dc54f 100644 --- a/docs/ReleaseNotes.txt +++ b/docs/ReleaseNotes.txt @@ -5,6 +5,22 @@ XRootD Release Notes ============= +------------- +Version 5.6.5 +------------- + ++ **Major bug fixes** + **[XrdTpc]** Fix potential segmentation fault when creating packet marking handle (issue #2161) + ++ **Minor bug fixes** + **[XrdSecgsi]** Fix compilation with GCC 14 (#2165) + **[XrdSys]** Include for BSD and GNU/Hurd (#2149) + ++ **Miscellaneous** + **[Server]** Align monitoring ID with HTTP (issue #2133) + **[XrdCrypto]** Skip check of our standard DH parameters (issue #2162) + **[XrdHttp]** Send User-Agent as part of monitoring info (#2154) + ------------- Version 5.6.4 ------------- From 3c02a5f9cb12626e72bf594886bff3a214616b90 Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Wed, 24 Jan 2024 09:30:24 +0100 Subject: [PATCH 379/442] [XrdHttp] The PostProcessHttpReq callback now takes into account the setting of the user agent. --- src/XrdHttp/XrdHttpReq.cc | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/XrdHttp/XrdHttpReq.cc b/src/XrdHttp/XrdHttpReq.cc index db700cdeb2a..7d4beed9b75 100644 --- a/src/XrdHttp/XrdHttpReq.cc +++ b/src/XrdHttp/XrdHttpReq.cc @@ -1773,9 +1773,18 @@ XrdHttpReq::PostProcessChecksum(std::string &digest_header) { int XrdHttpReq::PostProcessHTTPReq(bool final_) { - TRACEI(REQ, "PostProcessHTTPReq req: " << request << " reqstate: " << reqstate); + TRACEI(REQ, "PostProcessHTTPReq req: " << request << " reqstate: " << reqstate << " final_:" << final_); mapXrdErrorToHttpStatus(); + if(xrdreq.set.requestid == htons(kXR_set)) { + // We have set the user agent, if it fails we return a 500 error, otherwise the callback is successful --> we continue + if(xrdresp != kXR_ok) { + prot->SendSimpleResp(500, nullptr, nullptr, "Could not set user agent.", 0, false); + return -1; + } + return 0; + } + switch (request) { case XrdHttpReq::rtUnknown: { From 2fa810c26683617a1dec33b7186f6a59d9673463 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 23 Jan 2024 16:39:12 +0100 Subject: [PATCH 380/442] [Xrd] Set TZ environment variable to avoid race conditions XrdSysLoggerRT runs on a separate thread, and XRootD's initialization calls XrdOucEnv::Export a few times, so there is a potential race condition between localtime_r() and mktime() due to that. Setting TZ before we enter the multi-threaded phase of the initialization will ensure that later calls to tzset() will not attempt to modify it later. Issue: #2107 --- src/Xrd/XrdMain.cc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Xrd/XrdMain.cc b/src/Xrd/XrdMain.cc index 1254c76a147..04177d45c48 100644 --- a/src/Xrd/XrdMain.cc +++ b/src/Xrd/XrdMain.cc @@ -165,6 +165,13 @@ int main(int argc, char *argv[]) char buff[128]; int i, retc; +// Set TZ environment variable to read the system timezone from /etc/localtime +// if it is not already set, to avoid race conditions between localtime_r() and +// mktime() during the multi-threaded phase of the initialization. + + if (access("/etc/localtime", R_OK) == 0) + setenv("TZ", ":/etc/localtime", /* overwrite */ false); + // Call tzset() early to ensure thread-safety of localtime_r() and mktime(). tzset(); From e15bf1cc2f77e5c461855b8aee5b54756cd1c93b Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 24 Jan 2024 13:17:57 +0100 Subject: [PATCH 381/442] [XrdCl] Treat errOperationInterrupted as a recoverable error Fixes: #2169 --- src/XrdCl/XrdClFileStateHandler.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/XrdCl/XrdClFileStateHandler.cc b/src/XrdCl/XrdClFileStateHandler.cc index 90100056ed1..d9491c85c3c 100644 --- a/src/XrdCl/XrdClFileStateHandler.cc +++ b/src/XrdCl/XrdClFileStateHandler.cc @@ -2880,7 +2880,8 @@ namespace XrdCl bool FileStateHandler::IsRecoverable( const XRootDStatus &status ) const { if( status.code == errSocketError || status.code == errInvalidSession || - status.code == errTlsError || status.code == errSocketTimeout ) + status.code == errTlsError || status.code == errSocketTimeout || + status.code == errOperationInterrupted) { if( IsReadOnly() && !pDoRecoverRead ) return false; From 930819af5e959b82c0a05f3003801aba61be618e Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 25 Jan 2024 10:31:13 +0100 Subject: [PATCH 382/442] [RPM] Update spec file for XRootD 5.6.6 --- xrootd.spec | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/xrootd.spec b/xrootd.spec index 70223531da9..fa694061fe1 100644 --- a/xrootd.spec +++ b/xrootd.spec @@ -25,7 +25,7 @@ License: LGPL-3.0-or-later AND BSD-2-Clause AND BSD-3-Clause AND curl AND MIT AN URL: https://xrootd.slac.stanford.edu %if !%{with git} -Version: 5.6.5 +Version: 5.6.6 Source0: %{url}/download/v%{version}/%{name}-%{version}.tar.gz %else %define git_version %(tar xzf %{_sourcedir}/%{name}.tar.gz -O xrootd/VERSION) @@ -950,6 +950,9 @@ fi %changelog +* Thu Jan 25 2024 Guilherme Amadio - 1:5.6.6-1 +- XRootD 5.6.6 + * Mon Jan 22 2024 Guilherme Amadio - 1:5.6.5-1 - XRootD 5.6.5 From 60c9cc44487858b0d4d8aacf52c32734e83d4824 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 25 Jan 2024 10:39:15 +0100 Subject: [PATCH 383/442] [Misc] Fix typos (teh -> the) --- src/Xrd/XrdLink.hh | 2 +- src/Xrd/XrdLinkCtl.hh | 2 +- src/XrdCms/XrdCmsPrepArgs.cc | 2 +- src/XrdOfs/XrdOfsConfig.cc | 2 +- src/XrdOuc/XrdOucArgs.cc | 2 +- src/XrdOuc/XrdOucFileInfo.hh | 2 +- src/XrdSfs/XrdSfsInterface.hh | 2 +- src/XrdSsi/XrdSsiCms.cc | 2 +- src/XrdSsi/XrdSsiShMam.cc | 2 +- src/XrdSys/XrdSysFAttr.cc | 2 +- src/XrdSys/XrdSysUtils.hh | 4 ++-- src/XrdXrootd/XrdXrootdXeq.cc | 2 +- 12 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Xrd/XrdLink.hh b/src/Xrd/XrdLink.hh index 3d11e9d6832..5f6a997bdfe 100644 --- a/src/Xrd/XrdLink.hh +++ b/src/Xrd/XrdLink.hh @@ -162,7 +162,7 @@ static XrdLink *Find(int &curr, XrdLinkMatch *who=0); //! criterea (typically, client name and host name). If the //! pointer is nil, a match always occurs. //! -//! @return !0 The length of teh name placed in the buffer. +//! @return !0 The length of the name placed in the buffer. //! =0 No more links exist with the specified criterea. //----------------------------------------------------------------------------- diff --git a/src/Xrd/XrdLinkCtl.hh b/src/Xrd/XrdLinkCtl.hh index 1fee8ac5d92..66df9abb4ab 100644 --- a/src/Xrd/XrdLinkCtl.hh +++ b/src/Xrd/XrdLinkCtl.hh @@ -113,7 +113,7 @@ static XrdPollInfo *fd2PollInfo(int fd) //! @param curr Is an internal tracking value that allows repeated calls. //! It must be set to a value of 0 or less on the initial call //! and not touched therafter unless a null pointer is returned. -//! @param who If the object use to check if teh link matches the wanted +//! @param who If the object use to check if the link matches the wanted //! criterea (typically, client name and host name). If the //! ppointer is nil, the next link is always returned. //! diff --git a/src/XrdCms/XrdCmsPrepArgs.cc b/src/XrdCms/XrdCmsPrepArgs.cc index 9a6d191cde1..ca6e9162c69 100644 --- a/src/XrdCms/XrdCmsPrepArgs.cc +++ b/src/XrdCms/XrdCmsPrepArgs.cc @@ -56,7 +56,7 @@ int XrdCmsPrepArgs::isIdle= 1; XrdCmsPrepArgs::XrdCmsPrepArgs(XrdCmsRRData &Arg) : XrdJob("prepare") { -// Copy variable pointers and steal teh data buffer behind them +// Copy variable pointers and steal the data buffer behind them // Request = Arg.Request; Request.streamid = 0; Ident = Arg.Ident; diff --git a/src/XrdOfs/XrdOfsConfig.cc b/src/XrdOfs/XrdOfsConfig.cc index 6c06ce93916..0b9873a6d64 100644 --- a/src/XrdOfs/XrdOfsConfig.cc +++ b/src/XrdOfs/XrdOfsConfig.cc @@ -760,7 +760,7 @@ char *XrdOfs::ConfigTPCDir(XrdSysError &Eroute, const char *sfx, return 0; } -// list the contents of teh directory +// list the contents of the directory // XrdOucNSWalk nsWalk(&Eroute, aPath, 0, nswOpt); XrdOucNSWalk::NSEnt *nsX, *nsP = nsWalk.Index(rc); diff --git a/src/XrdOuc/XrdOucArgs.cc b/src/XrdOuc/XrdOucArgs.cc index 1ace0e07b20..054ae0813a4 100644 --- a/src/XrdOuc/XrdOucArgs.cc +++ b/src/XrdOuc/XrdOucArgs.cc @@ -101,7 +101,7 @@ XrdOucArgs::XrdOucArgs(XrdSysError *erp, optp = 0; eDest = erp; epfx = strdup(etxt ? etxt : ""); -// Process teh valid opts +// Process the valid opts // if (StdOpts && *StdOpts == ':') {missarg = ':'; StdOpts++;} else missarg = '?'; diff --git a/src/XrdOuc/XrdOucFileInfo.hh b/src/XrdOuc/XrdOucFileInfo.hh index 5897c3be4b5..3dee49d1851 100644 --- a/src/XrdOuc/XrdOucFileInfo.hh +++ b/src/XrdOuc/XrdOucFileInfo.hh @@ -145,7 +145,7 @@ long long GetSize() {return fSize;} //! //! @return Pointer to the url. The url is valid until this object is deleted. //! If no more urls exist, a nil pointer is returned. A subsequent call -//! will start at the front of teh list. +//! will start at the front of the list. //----------------------------------------------------------------------------- const char *GetUrl(char *cntry=0, int *prty=0); diff --git a/src/XrdSfs/XrdSfsInterface.hh b/src/XrdSfs/XrdSfsInterface.hh index 7295443460f..c0ec1cb9a5d 100644 --- a/src/XrdSfs/XrdSfsInterface.hh +++ b/src/XrdSfs/XrdSfsInterface.hh @@ -1257,7 +1257,7 @@ virtual int stat(const char *Name, //! //! @return One of SFS_OK, SFS_ERROR, SFS_REDIRECT, SFS_STALL, or SFS_STARTED //! When SFS_OK is returned, mode must contain mode information. If -//! teh mode is -1 then it is taken as an offline file. +//! the mode is -1 then it is taken as an offline file. //----------------------------------------------------------------------------- virtual int stat(const char *path, diff --git a/src/XrdSsi/XrdSsiCms.cc b/src/XrdSsi/XrdSsiCms.cc index 694708033dd..b2966280c94 100644 --- a/src/XrdSsi/XrdSsiCms.cc +++ b/src/XrdSsi/XrdSsiCms.cc @@ -61,7 +61,7 @@ XrdSsiCms::XrdSsiCms(XrdCmsClient *cmsP) : theCms(cmsP) tP = stP; while(tP) {manNum++; tP = tP->next;} -// Allocate an array of teh right size +// Allocate an array of the right size // manList = new char*[manNum]; diff --git a/src/XrdSsi/XrdSsiShMam.cc b/src/XrdSsi/XrdSsiShMam.cc index f664d6bcbde..fdd70e14309 100644 --- a/src/XrdSsi/XrdSsiShMam.cc +++ b/src/XrdSsi/XrdSsiShMam.cc @@ -1141,7 +1141,7 @@ bool XrdSsiShMam::Resize(XrdSsiShMat::CRZParms &parms) parms.mode = accMode; if (!newMap.Create(parms)) return false; -// Compute the offset of the first item and get the offset of teh last item. +// Compute the offset of the first item and get the offset of the last item. // fence = SHMINFO(lowFree); // Atomic?? iOff = shmInfoSz; diff --git a/src/XrdSys/XrdSysFAttr.cc b/src/XrdSys/XrdSysFAttr.cc index 616d0202b39..de4739e5804 100644 --- a/src/XrdSys/XrdSysFAttr.cc +++ b/src/XrdSys/XrdSysFAttr.cc @@ -120,7 +120,7 @@ void XrdSysFAttr::Free(XrdSysFAttr::AList *aLP) { AList *aNP; -// Free all teh structs using free as they were allocated using malloc() +// Free all the structs using free as they were allocated using malloc() // while(aLP) {aNP = aLP->Next; free(aLP); aLP = aNP;} } diff --git a/src/XrdSys/XrdSysUtils.hh b/src/XrdSys/XrdSysUtils.hh index cd71b782ea0..8a3adb45da2 100644 --- a/src/XrdSys/XrdSysUtils.hh +++ b/src/XrdSys/XrdSysUtils.hh @@ -72,7 +72,7 @@ static int GetSigNum(const char *sname); //! Block common signals. This must be called at program start. //! //! @return true - common signals are blocked. -//! @return false - common signals not blocked, errno has teh reason. +//! @return false - common signals not blocked, errno has the reason. //----------------------------------------------------------------------------- static bool SigBlock(); @@ -84,7 +84,7 @@ static bool SigBlock(); //! @aparam numsig - The signal value to be blocked. //! //! @return true - signal is blocked. -//! @return false - signal not blocked, errno has teh reason. +//! @return false - signal not blocked, errno has the reason. //----------------------------------------------------------------------------- static bool SigBlock(int numsig); diff --git a/src/XrdXrootd/XrdXrootdXeq.cc b/src/XrdXrootd/XrdXrootdXeq.cc index 56fb86583c0..5efe8df9162 100644 --- a/src/XrdXrootd/XrdXrootdXeq.cc +++ b/src/XrdXrootd/XrdXrootdXeq.cc @@ -3387,7 +3387,7 @@ int XrdXrootdProtocol::do_WriteSpan() int XrdXrootdProtocol::do_WriteV() { // This will write multiple buffers at the same time in an attempt to avoid -// the disk latency. The information with the offsets and lengths of teh data +// the disk latency. The information with the offsets and lengths of the data // to write is passed as a data buffer. We attempt to optimize as best as // possible, though certain combinations may result in multiple writes. Since // socket flushing is nearly impossible when an error occurs, most errors From de37caac7dc3072fb751f755eccd46fec348b4e7 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 25 Jan 2024 10:41:16 +0100 Subject: [PATCH 384/442] XRootD 5.6.6 --- docs/ReleaseNotes.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/ReleaseNotes.txt b/docs/ReleaseNotes.txt index 6efc74dc54f..293bcc3953f 100644 --- a/docs/ReleaseNotes.txt +++ b/docs/ReleaseNotes.txt @@ -5,6 +5,17 @@ XRootD Release Notes ============= +------------- +Version 5.6.6 +------------- + ++ **Major bug fixes** + **[XrdHttp]** Fix PostProcessHttpReq to take into account User-Agent setting (#2173) + ++ **Minor bug fixes** + **[Server]** Set TZ environment variable to avoid race conditions (issue #2107) + **[XrdCl]** Treat errOperationInterrupted as a recoverable error (issue #2169) + ------------- Version 5.6.5 ------------- From 6e38f10f19d16e731e681828aa8175c28214bf9f Mon Sep 17 00:00:00 2001 From: Brian Bockelman Date: Sat, 3 Feb 2024 14:06:24 -0600 Subject: [PATCH 385/442] Fix ordering of the debug levels With this, `pss.setopt DebugLevel N` has the following meanings: - 0: None - 1: Error - 2: Warning - 3: Info - 4: Debug - 5: Dump Previously, 1 and 3 were swapped. --- src/XrdPosix/XrdPosixConfig.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdPosix/XrdPosixConfig.cc b/src/XrdPosix/XrdPosixConfig.cc index 9a20c99ac36..e08c651971c 100644 --- a/src/XrdPosix/XrdPosixConfig.cc +++ b/src/XrdPosix/XrdPosixConfig.cc @@ -488,7 +488,7 @@ bool XrdPosixConfig::SetConfig(XrdOucPsx &parms) void XrdPosixConfig::SetDebug(int val) { - const std::string dbgType[] = {"Info", "Warning", "Error", "Debug", "Dump"}; + const std::string dbgType[] = {"Error", "Warning", "Info", "Debug", "Dump"}; // The default is none but once set it cannot be unset in the client // From 42c4035a2fe35ee47d980c6015c7e10df97d4d69 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 31 Jan 2024 16:40:08 +0100 Subject: [PATCH 386/442] [XrdCl] Fix typo in error message --- src/XrdCl/XrdClXRootDMsgHandler.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdCl/XrdClXRootDMsgHandler.cc b/src/XrdCl/XrdClXRootDMsgHandler.cc index da651387206..895784bbfba 100644 --- a/src/XrdCl/XrdClXRootDMsgHandler.cc +++ b/src/XrdCl/XrdClXRootDMsgHandler.cc @@ -904,7 +904,7 @@ namespace XrdCl log->Dump( XRootDMsg, "[%s] Message %s has been successfully sent.", pUrl.GetHostId().c_str(), message->GetDescription().c_str() ); - log->Debug( ExDbgMsg, "[%s] Moving MsgHandler: 0x%x (message: %s ) from out-queu to in-queue.", + log->Debug( ExDbgMsg, "[%s] Moving MsgHandler: 0x%x (message: %s ) from out-queue to in-queue.", pUrl.GetHostId().c_str(), this, pRequest->GetDescription().c_str() ); From 5c6d959f901563640556175f03c62d4e1c3b589f Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Mon, 29 Jan 2024 17:07:24 +0100 Subject: [PATCH 387/442] [XrdTpcTPC] PMark - handle the cases where the PMark handle cannot be created because the socket is not yet connected --- src/XrdTpc/XrdTpcPMarkManager.cc | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/XrdTpc/XrdTpcPMarkManager.cc b/src/XrdTpc/XrdTpcPMarkManager.cc index 81437e6ee73..ee822642df9 100644 --- a/src/XrdTpc/XrdTpcPMarkManager.cc +++ b/src/XrdTpc/XrdTpcPMarkManager.cc @@ -53,16 +53,28 @@ void PMarkManager::beginPMarks() { ss << "scitag.flow=" << mReq->mSciTag; SocketInfo & sockInfo = mSocketInfos.front(); mInitialFD = sockInfo.client.addrInfo->SockFD(); - mPmarkHandles.emplace(mInitialFD,mPmark->Begin(sockInfo.client, mReq->resource.c_str(), ss.str().c_str(), "http-tpc")); - mSocketInfos.pop(); + std::unique_ptr initialPmark(mPmark->Begin(sockInfo.client, mReq->resource.c_str(), ss.str().c_str(), "http-tpc")); + if(initialPmark) { + // It may happen that the socket attached to the file descriptor is not connected yet. If this is the case the initial + // Pmark will be nullptr... + mPmarkHandles.emplace(mInitialFD,std::move(initialPmark)); + mSocketInfos.pop(); + } } else { // The first pmark handle was created, or not. Create the other pmark handles from the other connected sockets while(!mSocketInfos.empty()) { SocketInfo & sockInfo = mSocketInfos.front(); if(mPmarkHandles[mInitialFD]){ - mPmarkHandles.emplace(sockInfo.client.addrInfo->SockFD(),mPmark->Begin(*sockInfo.client.addrInfo, *mPmarkHandles[mInitialFD], nullptr)); + std::unique_ptr pmark(mPmark->Begin(*sockInfo.client.addrInfo, *mPmarkHandles[mInitialFD], nullptr)); + if(pmark) { + mPmarkHandles.emplace(sockInfo.client.addrInfo->SockFD(),std::move(pmark)); + mSocketInfos.pop(); + } else { + // We could not create the pmark handle from the socket, we break the loop, we will retry later on when + // this function will be called again. + break; + } } - mSocketInfos.pop(); } } } From 1030a0a92f4e02deba8de4a803f9ff67592fb545 Mon Sep 17 00:00:00 2001 From: David Smith Date: Wed, 31 Jan 2024 10:45:22 +0100 Subject: [PATCH 388/442] [XrdSecsss] Fix possible buffer overrun when serialising creds. Closes #2143 --- src/XrdSecsss/XrdSecsssEnt.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdSecsss/XrdSecsssEnt.cc b/src/XrdSecsss/XrdSecsssEnt.cc index fdfa8657bc4..cfad10aceba 100644 --- a/src/XrdSecsss/XrdSecsssEnt.cc +++ b/src/XrdSecsss/XrdSecsssEnt.cc @@ -235,7 +235,7 @@ bool XrdSecsssEnt::Serialize() // must be at the end because it can optionally be pruned when returned. // if (eP->credslen && eP->credslen <= XrdSecsssRR_Data::MaxCSz) - {tLen += eP->credslen; + {tLen += eP->credslen + 3; incCreds = true; } From 3a0e2ac4da8a77a98c3255ebbe755f7eb8fe6058 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 5 Feb 2024 15:08:25 +0100 Subject: [PATCH 389/442] [XrdCl] Account for control stream when configuring TPC jobs If we do not account for the control stream here, it will be passed down as nbStrm = 1 to XrdOucTPC::cgiC2Dst and be converted in XrdOfsTPCProg::Xeq to a -S 1 command-line option passed by the server to the client, which actually starts the client with an extra data stream in addition to the control stream. Issue: #2164 --- src/XrdCl/XrdClThirdPartyCopyJob.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/XrdCl/XrdClThirdPartyCopyJob.cc b/src/XrdCl/XrdClThirdPartyCopyJob.cc index d3fae4ac1e9..f853470080f 100644 --- a/src/XrdCl/XrdClThirdPartyCopyJob.cc +++ b/src/XrdCl/XrdClThirdPartyCopyJob.cc @@ -348,6 +348,9 @@ namespace XrdCl XrdCl::Env *env = XrdCl::DefaultEnv::GetEnv(); env->GetInt( "SubStreamsPerChannel", nbStrm ); + // account for the control stream + if (nbStrm > 0) --nbStrm; + bool tpcLiteOnly = false; if( !delegate ) From c7d014cf891a8b78bbddccab72f8fa45c453391b Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 5 Feb 2024 16:28:42 +0100 Subject: [PATCH 390/442] [XrdHeaders] Install XrdSfsFAttr.hh as private header --- src/XrdHeaders.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/src/XrdHeaders.cmake b/src/XrdHeaders.cmake index 6c99c2ef9cb..e07de6f1542 100644 --- a/src/XrdHeaders.cmake +++ b/src/XrdHeaders.cmake @@ -156,6 +156,7 @@ if( NOT XRDCL_ONLY ) XrdOfs/XrdOfsHandle.hh XrdOfs/XrdOfsTrace.hh XrdOfs/XrdOfsTPCInfo.hh + XrdSfs/XrdSfsFAttr.hh XrdSsi/XrdSsiAtomics.hh XrdSsi/XrdSsiCluster.hh XrdSsi/XrdSsiEntity.hh From 0feedae1b91c69e55148d211423e867d6602be80 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 5 Feb 2024 19:51:05 +0100 Subject: [PATCH 391/442] [XrdCl] Ensure proper shutdown by waiting for other threads to stop When small files are copied over a TLS connection and multiple streams are used, the copy might happen entirely on the first (control) stream before the other streams are fully initialized. If we don't wait for the other threads to finish, what may happen is that the initialization of the data streams might try to start a TLS handshake to connect while OpenSSL's teardown is already ongoing, causing a crash. Fixes: #2164 --- src/XrdCl/XrdClCopy.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/XrdCl/XrdClCopy.cc b/src/XrdCl/XrdClCopy.cc index 2506da5e963..d0acd37e1ef 100644 --- a/src/XrdCl/XrdClCopy.cc +++ b/src/XrdCl/XrdClCopy.cc @@ -952,6 +952,7 @@ int main( int argc, char **argv ) return st.GetShellCode(); } CleanUpResults( resultVect ); + XrdCl::DefaultEnv::GetPostMaster()->Stop(); return 0; } From 0364b285a5910449a9cfc16bf005e4c630006b3e Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 6 Feb 2024 09:22:43 +0100 Subject: [PATCH 392/442] [XrdCl] Do nothing on PostMaster::Stop() if it is already stopped --- src/XrdCl/XrdClPostMaster.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdCl/XrdClPostMaster.cc b/src/XrdCl/XrdClPostMaster.cc index 1389571393d..0389ca27062 100644 --- a/src/XrdCl/XrdClPostMaster.cc +++ b/src/XrdCl/XrdClPostMaster.cc @@ -187,7 +187,7 @@ namespace XrdCl //---------------------------------------------------------------------------- bool PostMaster::Stop() { - if( !pImpl->pInitialized ) + if( !pImpl->pInitialized || !pImpl->pRunning ) return true; if( !pImpl->pJobManager->Stop() ) From ada3120597be7b4822e2a244bf914eaf271c1555 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 6 Feb 2024 09:08:41 +0100 Subject: [PATCH 393/442] [RPM] Update spec file for XRootD 5.6.7 --- xrootd.spec | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/xrootd.spec b/xrootd.spec index fa694061fe1..7e5ff46a5be 100644 --- a/xrootd.spec +++ b/xrootd.spec @@ -25,7 +25,7 @@ License: LGPL-3.0-or-later AND BSD-2-Clause AND BSD-3-Clause AND curl AND MIT AN URL: https://xrootd.slac.stanford.edu %if !%{with git} -Version: 5.6.6 +Version: 5.6.7 Source0: %{url}/download/v%{version}/%{name}-%{version}.tar.gz %else %define git_version %(tar xzf %{_sourcedir}/%{name}.tar.gz -O xrootd/VERSION) @@ -950,6 +950,9 @@ fi %changelog +* Tue Feb 06 2024 Guilherme Amadio - 1:5.6.7-1 +- XRootD 5.6.7 + * Thu Jan 25 2024 Guilherme Amadio - 1:5.6.6-1 - XRootD 5.6.6 From 5b5a1f6957def2816b77ec773c7e1bfb3f1cfc5b Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 5 Feb 2024 21:54:56 +0100 Subject: [PATCH 394/442] XRootD 5.6.7 --- docs/ReleaseNotes.txt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/ReleaseNotes.txt b/docs/ReleaseNotes.txt index 293bcc3953f..274c1578da9 100644 --- a/docs/ReleaseNotes.txt +++ b/docs/ReleaseNotes.txt @@ -5,6 +5,22 @@ XRootD Release Notes ============= +------------- +Version 5.6.7 +------------- + ++ **Major bug fixes** + **[XrdCl]** Fix crash at teardown when using copies with multiple streams (issue #2164) + **[XrdSecsss]** Fix buffer overrun when serializing credentials (issue #2143) + ++ **Minor bug fixes** + **[XrdCl]** Fix TPC initialization to take into account control stream (issue #2164) + **[XrdPosix]** Fix ordering of debug levels in pss.setop DebugLevel (#2183) + **[XrdTpc]** Properly handle creation of packet marking handles when socket is not yet connected (#2179) + ++ **Miscellaneous** + **[XrdHeaders]** Install XrdSfsFAttr.hh as private header + ------------- Version 5.6.6 ------------- From 9cec6f04f5264e64acd34fdf8e4399c0b0b1820f Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 20 Feb 2024 14:33:39 +0100 Subject: [PATCH 395/442] Update .mailmap file --- .mailmap | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.mailmap b/.mailmap index 116e6e98944..aa20ef11ac5 100644 --- a/.mailmap +++ b/.mailmap @@ -38,6 +38,8 @@ James Walder snafus Jan Iven Jozsef Makai Jozsef Makai Jozsef Makai +Jyothish Thomas Jo-stfc <71326101+Jo-stfc@users.noreply.github.com> +Jyothish Thomas root Kian-Tat Lim ktlim Lukasz Janyst Lukasz Janyst Lukasz Janyst Lukasz Janyst From 971ffc52dc279a868fd492e2b11be801cc3e7b94 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Tue, 20 Feb 2024 14:32:54 +0100 Subject: [PATCH 396/442] [CI] Ignore changes to the git mailmap file --- .github/workflows/CI.yml | 1 + .github/workflows/DEB.yml | 1 + .github/workflows/RPM.yml | 1 + 3 files changed, 3 insertions(+) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index e75311a0327..06181224928 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -5,6 +5,7 @@ on: paths-ignore: - .gitignore - .gitlab-ci.yml + - .mailmap - '**.md' - 'docs/**' - 'docker/**' diff --git a/.github/workflows/DEB.yml b/.github/workflows/DEB.yml index 574c5c589b5..b8a1f6e962d 100644 --- a/.github/workflows/DEB.yml +++ b/.github/workflows/DEB.yml @@ -8,6 +8,7 @@ on: paths-ignore: - .gitignore - .gitlab-ci.yml + - .mailmap - '**.md' - 'docs/**' - 'docker/**' diff --git a/.github/workflows/RPM.yml b/.github/workflows/RPM.yml index 8f0f6895adb..cfdde9e2a5e 100644 --- a/.github/workflows/RPM.yml +++ b/.github/workflows/RPM.yml @@ -8,6 +8,7 @@ on: paths-ignore: - .gitignore - .gitlab-ci.yml + - .mailmap - '**.md' - 'docs/**' - 'docker/**' From fdeab66a51a6704ae98fbea1f6191879f9e31d28 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 12 Feb 2024 11:18:17 +0100 Subject: [PATCH 397/442] [CMake] Put OS on build name, not site name in test.cmake --- test.cmake | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/test.cmake b/test.cmake index c2e6295a873..8ef3c471ef1 100644 --- a/test.cmake +++ b/test.cmake @@ -15,7 +15,9 @@ macro(endsection) endif() endmacro() -site_name(CTEST_SITE) +if(NOT DEFINED CTEST_SITE) + site_name(CTEST_SITE) +endif() if(EXISTS "/etc/os-release") file(STRINGS "/etc/os-release" OS_NAME REGEX "^ID=.*$") @@ -24,6 +26,7 @@ if(EXISTS "/etc/os-release") string(REGEX REPLACE "VERSION_ID=[\"']?([^\"'.]*).*$" "\\1" OS_VERSION "${OS_VERSION}") file(STRINGS "/etc/os-release" OS_FULL_NAME REGEX "^PRETTY_NAME=.*$") string(REGEX REPLACE "PRETTY_NAME=[\"']?([^\"']*)[\"']?$" "\\1" OS_FULL_NAME "${OS_FULL_NAME}") + string(REGEX REPLACE "[ ]*\\(.*\\)" "" OS_FULL_NAME "${OS_FULL_NAME}") elseif(APPLE) set(OS_NAME "macOS") execute_process(COMMAND sw_vers -productVersion @@ -35,8 +38,6 @@ else() set(OS_FULL_NAME "${OS_NAME} ${OS_VERSION}") endif() -string(APPEND CTEST_SITE " (${OS_FULL_NAME} - ${CMAKE_SYSTEM_PROCESSOR})") - cmake_host_system_information(RESULT NCORES QUERY NUMBER_OF_PHYSICAL_CORES) cmake_host_system_information(RESULT @@ -58,8 +59,6 @@ if(NOT DEFINED CTEST_CONFIGURATION_TYPE) endif() endif() -set(CTEST_BUILD_NAME "${CMAKE_SYSTEM_NAME}") - execute_process(COMMAND ${CMAKE_COMMAND} --system-information OUTPUT_VARIABLE CMAKE_SYSTEM_INFORMATION ERROR_VARIABLE ERROR) @@ -74,6 +73,7 @@ string(REPLACE "GNU" "GCC" COMPILER_ID "${COMPILER_ID}") string(REGEX REPLACE ".+CMAKE_CXX_COMPILER_VERSION \"([^\"]+)\".*$" "\\1" COMPILER_VERSION "${CMAKE_SYSTEM_INFORMATION}") +set(CTEST_BUILD_NAME "${OS_FULL_NAME}") string(APPEND CTEST_BUILD_NAME " ${COMPILER_ID} ${COMPILER_VERSION}") string(APPEND CTEST_BUILD_NAME " ${CTEST_CONFIGURATION_TYPE}") @@ -88,6 +88,10 @@ if(NOT CTEST_CMAKE_GENERATOR MATCHES "Makefile") string(APPEND CTEST_BUILD_NAME " ${CTEST_CMAKE_GENERATOR}") endif() +if(NOT CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64") + string(APPEND CTEST_BUILD_NAME " ${CMAKE_SYSTEM_PROCESSOR}") +endif() + if(NOT DEFINED CTEST_SOURCE_DIRECTORY) set(CTEST_SOURCE_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}") endif() From 12d8bee83f880e9f4571233c12708667f1b01030 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 9 Feb 2024 15:29:08 +0100 Subject: [PATCH 398/442] [CMake] Use CTest module and optionally submit to CDash --- .github/workflows/CI.yml | 1 + CMakeLists.txt | 10 ++++------ CTestConfig.cmake | 2 ++ test.cmake | 12 ++++++++++-- tests/CMakeLists.txt | 4 ++++ 5 files changed, 21 insertions(+), 8 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 06181224928..e1989743740 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -20,6 +20,7 @@ defaults: shell: bash env: + CDASH: ${{ vars.CDASH }} CMAKE_VERBOSE_MAKEFILE: true CTEST_OUTPUT_ON_FAILURE: true diff --git a/CMakeLists.txt b/CMakeLists.txt index 438ff5666e8..c771264ff36 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,13 +41,11 @@ configure_file(src/XrdVersion.hh.in src/XrdVersion.hh) include_directories(BEFORE ${CMAKE_SOURCE_DIR}/src ${CMAKE_BINARY_DIR}/src) -add_subdirectory( src ) -add_subdirectory( bindings ) +include(CTest) -if( BUILD_TESTS ) - ENABLE_TESTING() - add_subdirectory( tests ) -endif() +add_subdirectory(src) +add_subdirectory(bindings) +add_subdirectory(tests) add_subdirectory(docs) add_subdirectory(utils) diff --git a/CTestConfig.cmake b/CTestConfig.cmake index a2b1e4739ec..f2ce8e263fe 100644 --- a/CTestConfig.cmake +++ b/CTestConfig.cmake @@ -1,2 +1,4 @@ set(CTEST_PROJECT_NAME "XRootD") set(CTEST_NIGHTLY_START_TIME "00:00:00 UTC") +set(CTEST_DROP_SITE_CDASH TRUE) +set(CTEST_SUBMIT_URL https://my.cdash.org/submit.php?project=XRootD) diff --git a/test.cmake b/test.cmake index 8ef3c471ef1..bc67d5fa9ea 100644 --- a/test.cmake +++ b/test.cmake @@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 3.16) set(ENV{LANG} "C") set(ENV{LC_ALL} "C") +set(CTEST_USE_LAUNCHERS TRUE) macro(section title) if (DEFINED ENV{CI}) @@ -171,6 +172,9 @@ section("Build") ctest_build(RETURN_VALUE BUILD_RESULT) if(NOT ${BUILD_RESULT} EQUAL 0) + if(CDASH OR (DEFINED ENV{CDASH} AND "$ENV{CDASH}")) + ctest_submit() + endif() message(FATAL_ERROR "Build failed") endif() @@ -184,7 +188,7 @@ section("Test") ctest_test(PARALLEL_LEVEL $ENV{CTEST_PARALLEL_LEVEL} RETURN_VALUE TEST_RESULT) if(NOT ${TEST_RESULT} EQUAL 0) - message(FATAL_ERROR "Tests failed") + message(SEND_ERROR "Tests failed") endif() endsection() @@ -196,7 +200,7 @@ if(DEFINED CTEST_COVERAGE_COMMAND) ${GCOVR} -r ${CTEST_SOURCE_DIRECTORY}/src ${CTEST_BINARY_DIRECTORY} --html-details ${CTEST_BINARY_DIRECTORY}/html/ ERROR_VARIABLE ERROR) if(ERROR) - message(FATAL_ERROR "Failed to generate coverage report") + message(SEND_ERROR "Failed to generate coverage report") endif() endif() ctest_coverage() @@ -208,3 +212,7 @@ if(DEFINED CTEST_MEMORYCHECK_COMMAND) ctest_memcheck() endsection() endif() + +if(CDASH OR (DEFINED ENV{CDASH} AND "$ENV{CDASH}")) + ctest_submit() +endif() diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d5218f91ddc..070d7bf7dfd 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,3 +1,7 @@ +if(NOT BUILD_TESTS) + return() +endif() + include(GoogleTest) add_subdirectory( XrdCl ) add_subdirectory(XrdHttpTests) From 9608e0d06b36f0932a17cf40ff889dd990013ea6 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 12 Feb 2024 12:32:04 +0100 Subject: [PATCH 399/442] [CMake] Use GitHub Actions environment variables in CDash --- test.cmake | 115 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 75 insertions(+), 40 deletions(-) diff --git a/test.cmake b/test.cmake index bc67d5fa9ea..023d665e8c6 100644 --- a/test.cmake +++ b/test.cmake @@ -4,22 +4,6 @@ set(ENV{LANG} "C") set(ENV{LC_ALL} "C") set(CTEST_USE_LAUNCHERS TRUE) -macro(section title) - if (DEFINED ENV{CI}) - message("::group::${title}") - endif() -endmacro() - -macro(endsection) - if (DEFINED ENV{CI}) - message("::endgroup::") - endif() -endmacro() - -if(NOT DEFINED CTEST_SITE) - site_name(CTEST_SITE) -endif() - if(EXISTS "/etc/os-release") file(STRINGS "/etc/os-release" OS_NAME REGEX "^ID=.*$") string(REGEX REPLACE "ID=[\"']?([^\"']*)[\"']?$" "\\1" OS_NAME "${OS_NAME}") @@ -93,32 +77,67 @@ if(NOT CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64") string(APPEND CTEST_BUILD_NAME " ${CMAKE_SYSTEM_PROCESSOR}") endif() +if(DEFINED ENV{GITHUB_ACTIONS}) + set(CTEST_SITE "GitHub Actions ($ENV{GITHUB_REPOSITORY_OWNER})") + + if("$ENV{GITHUB_REPOSITORY_OWNER}" STREQUAL "xrootd") + set(CDASH TRUE) + set(MODEL "Continuous") + endif() + + if("$ENV{GITHUB_EVENT_NAME}" MATCHES "pull_request") + set(GROUP "Pull Requests") + set(ENV{BASE_REF} $ENV{GITHUB_SHA}^1) + set(ENV{HEAD_REF} $ENV{GITHUB_SHA}^2) + string(REGEX REPLACE "/merge" "" PR_NUMBER "$ENV{GITHUB_REF_NAME}") + string(PREPEND CTEST_BUILD_NAME "#${PR_NUMBER} ($ENV{GITHUB_ACTOR})") + else() + set(ENV{HEAD_REF} $ENV{GITHUB_SHA}) + string(APPEND CTEST_BUILD_NAME " ($ENV{GITHUB_REF_NAME})") + endif() + + if("$ENV{GITHUB_RUN_ATTEMPT}" GREATER 1) + string(APPEND CTEST_BUILD_NAME " #$ENV{GITHUB_RUN_ATTEMPT}") + endif() + + macro(section title) + message("::group::${title}") + endmacro() + + macro(endsection) + message("::endgroup::") + endmacro() +else() + macro(section title) + endmacro() + macro(endsection) + endmacro() +endif() + +if(NOT DEFINED CTEST_SITE) + site_name(CTEST_SITE) +endif() + if(NOT DEFINED CTEST_SOURCE_DIRECTORY) set(CTEST_SOURCE_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}") endif() if(NOT DEFINED CTEST_BINARY_DIRECTORY) - get_filename_component(CTEST_BINARY_DIRECTORY "$ENV{PWD}/build" REALPATH) + get_filename_component(CTEST_BINARY_DIRECTORY "build" REALPATH) endif() -find_program(CTEST_GIT_COMMAND NAMES git) - -if(EXISTS ${CTEST_GIT_COMMAND}) - if(DEFINED ENV{GIT_PREVIOUS_COMMIT} AND DEFINED ENV{GIT_COMMIT}) - set(CTEST_CHECKOUT_COMMAND - "${CTEST_GIT_COMMAND} -C ${CTEST_SOURCE_DIRECTORY} checkout -f $ENV{GIT_PREVIOUS_COMMIT}") - set(CTEST_GIT_UPDATE_CUSTOM - ${CTEST_GIT_COMMAND} -C ${CTEST_SOURCE_DIRECTORY} checkout -f $ENV{GIT_COMMIT}) - elseif(DEFINED ENV{GIT_COMMIT}) - set(CTEST_CHECKOUT_COMMAND - "${CTEST_GIT_COMMAND} -C ${CTEST_SOURCE_DIRECTORY} checkout -f $ENV{GIT_COMMIT}") - set(CTEST_GIT_UPDATE_CUSTOM - ${CTEST_GIT_COMMAND} -C ${CTEST_SOURCE_DIRECTORY} checkout -f $ENV{GIT_COMMIT}) +if(NOT DEFINED MODEL) + if(DEFINED CTEST_SCRIPT_ARG) + set(MODEL ${CTEST_SCRIPT_ARG}) else() - set(CTEST_GIT_UPDATE_CUSTOM ${CTEST_GIT_COMMAND} -C ${CTEST_SOURCE_DIRECTORY} diff HEAD) + set(MODEL Experimental) endif() endif() +if(NOT DEFINED GROUP) + set(GROUP ${MODEL}) +endif() + set(CMAKE_ARGS $ENV{CMAKE_ARGS} ${CMAKE_ARGS}) if(COVERAGE) @@ -144,21 +163,37 @@ foreach(FILENAME ${OS_NAME}${OS_VERSION}.cmake ${OS_NAME}.cmake config.cmake) endif() endforeach() -if(NOT DEFINED MODEL) - if(DEFINED CTEST_SCRIPT_ARG) - set(MODEL ${CTEST_SCRIPT_ARG}) - else() - set(MODEL Experimental) - endif() -endif() - if(IS_DIRECTORY "${CTEST_BINARY_DIRECTORY}") ctest_empty_binary_directory("${CTEST_BINARY_DIRECTORY}") endif() ctest_read_custom_files("${CTEST_SOURCE_DIRECTORY}") -ctest_start(${MODEL}) +find_program(CTEST_GIT_COMMAND NAMES git) + +if(EXISTS ${CTEST_GIT_COMMAND} AND DEFINED ENV{HEAD_REF} AND NOT DEFINED ENV{BASE_REF}) + set(CTEST_CHECKOUT_COMMAND + "${CTEST_GIT_COMMAND} -C ${CTEST_SOURCE_DIRECTORY} checkout -f $ENV{HEAD_REF}") +endif() + +ctest_start(${MODEL} GROUP "${GROUP}") + +if(EXISTS ${CTEST_GIT_COMMAND}) + if(DEFINED ENV{BASE_REF}) + execute_process(COMMAND ${CTEST_GIT_COMMAND} checkout -f $ENV{BASE_REF} + WORKING_DIRECTORY ${CTEST_SOURCE_DIRECTORY} ERROR_QUIET RESULT_VARIABLE GIT_STATUS) + if(NOT ${GIT_STATUS} EQUAL 0) + message(FATAL_ERROR "Could not checkout base ref: $ENV{BASE_REF}") + endif() + endif() + if(DEFINED ENV{HEAD_REF}) + set(CTEST_GIT_UPDATE_CUSTOM + ${CTEST_GIT_COMMAND} -C ${CTEST_SOURCE_DIRECTORY} checkout -f $ENV{HEAD_REF}) + else() + set(CTEST_GIT_UPDATE_CUSTOM ${CTEST_GIT_COMMAND} -C ${CTEST_SOURCE_DIRECTORY} diff) + endif() +endif() + ctest_update() section("Configure") From ada1e19d6a0f8df26b61a15b9e8de0519c0385fc Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Sun, 18 Feb 2024 18:26:22 +0100 Subject: [PATCH 400/442] [CMake] Adapt test.cmake to work with old git and tarballs On CentOS 7 the version of git available is too old for the -C option, so we need to use --git-dir instead. The checkout action on GitHub may also adapt to old git versions by downloading a tarball, or one can try to use test.cmake with a release tarball. In this case, we need to adapt test.cmake to not use git at all, and not run the ctest_update() command as well. --- test.cmake | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/test.cmake b/test.cmake index 023d665e8c6..936b2cd6f6c 100644 --- a/test.cmake +++ b/test.cmake @@ -169,16 +169,18 @@ endif() ctest_read_custom_files("${CTEST_SOURCE_DIRECTORY}") -find_program(CTEST_GIT_COMMAND NAMES git) +if(IS_DIRECTORY ${CTEST_SOURCE_DIRECTORY}/.git) + find_program(CTEST_GIT_COMMAND NAMES git) +endif() -if(EXISTS ${CTEST_GIT_COMMAND} AND DEFINED ENV{HEAD_REF} AND NOT DEFINED ENV{BASE_REF}) +if(EXISTS "${CTEST_GIT_COMMAND}" AND DEFINED ENV{HEAD_REF} AND NOT DEFINED ENV{BASE_REF}) set(CTEST_CHECKOUT_COMMAND - "${CTEST_GIT_COMMAND} -C ${CTEST_SOURCE_DIRECTORY} checkout -f $ENV{HEAD_REF}") + "${CTEST_GIT_COMMAND} --git-dir ${CTEST_SOURCE_DIRECTORY}/.git checkout -f $ENV{HEAD_REF}") endif() ctest_start(${MODEL} GROUP "${GROUP}") -if(EXISTS ${CTEST_GIT_COMMAND}) +if(EXISTS "${CTEST_GIT_COMMAND}") if(DEFINED ENV{BASE_REF}) execute_process(COMMAND ${CTEST_GIT_COMMAND} checkout -f $ENV{BASE_REF} WORKING_DIRECTORY ${CTEST_SOURCE_DIRECTORY} ERROR_QUIET RESULT_VARIABLE GIT_STATUS) @@ -188,13 +190,13 @@ if(EXISTS ${CTEST_GIT_COMMAND}) endif() if(DEFINED ENV{HEAD_REF}) set(CTEST_GIT_UPDATE_CUSTOM - ${CTEST_GIT_COMMAND} -C ${CTEST_SOURCE_DIRECTORY} checkout -f $ENV{HEAD_REF}) + ${CTEST_GIT_COMMAND} --git-dir ${CTEST_SOURCE_DIRECTORY}/.git checkout -f $ENV{HEAD_REF}) else() - set(CTEST_GIT_UPDATE_CUSTOM ${CTEST_GIT_COMMAND} -C ${CTEST_SOURCE_DIRECTORY} diff) + set(CTEST_GIT_UPDATE_CUSTOM ${CTEST_GIT_COMMAND} --git-dir ${CTEST_SOURCE_DIRECTORY}/.git diff) endif() -endif() -ctest_update() + ctest_update() +endif() section("Configure") ctest_configure(OPTIONS "${CMAKE_ARGS}") From 5fea034c75bcb62c0135e3ddec9e72f2b20996aa Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 16 Feb 2024 14:35:42 +0100 Subject: [PATCH 401/442] [CI] Run only on latest macOS --- .github/workflows/CI.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index e1989743740..75b17c727fd 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -179,17 +179,13 @@ jobs: tests/check-headers.sh macos: - strategy: - matrix: - version: [ 12, 13 ] - name: macOS - runs-on: macos-${{ matrix.version }} + runs-on: macos-latest env: CC: clang CXX: clang++ - CMAKE_ARGS: "-DPython_FIND_UNVERSIONED_NAMES=FIRST;-DUSE_SYSTEM_ISAL=TRUE" + CMAKE_ARGS: "-DPython_FIND_UNVERSIONED_NAMES=FIRST" CMAKE_PREFIX_PATH: /usr/local/opt/openssl@3 steps: From 13c75578a4640e77a89c46155287183a75dd9d28 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 16 Feb 2024 14:44:26 +0100 Subject: [PATCH 402/442] [CI] Run DEB/RPM workflows only on master branch and on demand --- .github/workflows/DEB.yml | 2 -- .github/workflows/RPM.yml | 2 -- 2 files changed, 4 deletions(-) diff --git a/.github/workflows/DEB.yml b/.github/workflows/DEB.yml index b8a1f6e962d..0410cb22b08 100644 --- a/.github/workflows/DEB.yml +++ b/.github/workflows/DEB.yml @@ -3,7 +3,6 @@ name: DEB on: push: branches: - - devel - master paths-ignore: - .gitignore @@ -12,7 +11,6 @@ on: - '**.md' - 'docs/**' - 'docker/**' - pull_request: workflow_dispatch: concurrency: diff --git a/.github/workflows/RPM.yml b/.github/workflows/RPM.yml index cfdde9e2a5e..0b977154aee 100644 --- a/.github/workflows/RPM.yml +++ b/.github/workflows/RPM.yml @@ -3,7 +3,6 @@ name: RPM on: push: branches: - - devel - master paths-ignore: - .gitignore @@ -12,7 +11,6 @@ on: - '**.md' - 'docs/**' - 'docker/**' - pull_request: workflow_dispatch: concurrency: From bba21a7cbfe25f281793f56a1360c11ce83afc41 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 16 Feb 2024 14:53:35 +0100 Subject: [PATCH 403/442] [CI] Add CentOS 7, Alma 8, and Alma 9 builds to main CI workflow --- .github/workflows/CI.yml | 105 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 75b17c727fd..aa8e2147591 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -2,6 +2,8 @@ name: CI on: push: + branches-ignore: + - master paths-ignore: - .gitignore - .gitlab-ci.yml @@ -10,6 +12,7 @@ on: - 'docs/**' - 'docker/**' pull_request: + workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -78,6 +81,108 @@ jobs: tests/post-install.sh tests/check-headers.sh + centos7: + name: CentOS 7 + runs-on: ubuntu-latest + container: centos:7 + + env: + CMAKE_ARGS: "-DCMAKE_INSTALL_PREFIX=/usr;-DCMAKE_INSTALL_RPATH='$ORIGIN/../$LIB'" + + steps: + - name: Install dependencies + run: | + yum install -y centos-release-scl epel-release git + yum install -y epel-rpm-macros rpmdevtools sudo yum-utils + + - name: Clone repository + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Setup GitHub runner user within container + run: adduser --uid 1001 runner && chown -R runner:runner . + + - name: Install XRootD build dependencies + run: yum-builddep -y xrootd.spec + + - name: Build and Test with CTest + run: | + source /opt/rh/devtoolset-7/enable + su -p runner -c 'ctest3 -VV -S test.cmake' + + alma8: + name: Alma 8 + runs-on: ubuntu-latest + container: almalinux:8 + + env: + CMAKE_ARGS: "-DCMAKE_INSTALL_PREFIX=/usr;-DCMAKE_INSTALL_RPATH='$ORIGIN/../$LIB'" + + steps: + - name: Install dependencies + run: | + dnf install -y dnf-plugins-core epel-release git rpmdevtools sudo + dnf config-manager --set-enabled powertools + + - name: Clone repository + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Setup GitHub runner user within container + run: adduser --uid 1001 runner && chown -R runner:runner ${GITHUB_WORKSPACE} + + - name: Install XRootD build dependencies + run: dnf builddep -y xrootd.spec + + - name: Build and Test with CTest + run: sudo -E -u runner ctest -VV -S test.cmake + + - name: Install with CMake + run: cmake --install build + + - name: Run post-install tests + run: | + tests/post-install.sh + tests/check-headers.sh + + alma9: + name: Alma 9 + runs-on: ubuntu-latest + container: almalinux:9 + + env: + CMAKE_ARGS: "-DCMAKE_INSTALL_PREFIX=/usr;-DCMAKE_INSTALL_RPATH='$ORIGIN/../$LIB'" + + steps: + - name: Install dependencies + run: | + dnf install -y dnf-plugins-core epel-release git rpmdevtools sudo + dnf config-manager --set-enabled crb + + - name: Clone repository + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Setup GitHub runner user within container + run: adduser --uid 1001 runner && chown -R runner:runner ${GITHUB_WORKSPACE} + + - name: Install XRootD build dependencies + run: dnf builddep -y xrootd.spec + + - name: Build and Test with CTest + run: sudo -E -u runner ctest -VV -S test.cmake + + - name: Install with CMake + run: cmake --install build + + - name: Run post-install tests + run: | + tests/post-install.sh + tests/check-headers.sh + fedora: name: Fedora runs-on: ubuntu-latest From b5e22157de56fa51bfd2156fb431a565bf62462e Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 15 Feb 2024 13:48:13 +0100 Subject: [PATCH 404/442] [XrdCl] Remove duplicates from URL list to avoid undefined behavior See https://en.cppreference.com/w/cpp/algorithm/unique --- src/XrdCl/XrdClPlugInManager.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/XrdCl/XrdClPlugInManager.cc b/src/XrdCl/XrdClPlugInManager.cc index 5d8cc00f9d6..3fdaf149630 100644 --- a/src/XrdCl/XrdClPlugInManager.cc +++ b/src/XrdCl/XrdClPlugInManager.cc @@ -459,7 +459,9 @@ namespace XrdCl } std::sort( normalizedURLs.begin(), normalizedURLs.end() ); - std::unique( normalizedURLs.begin(), normalizedURLs.end() ); + + auto last = std::unique( normalizedURLs.begin(), normalizedURLs.end() ); + normalizedURLs.erase( last, normalizedURLs.end() ); if( normalizedURLs.empty() ) return false; From 7c7a0e02ed24feebd2f36e03be758c26097a24b8 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Sun, 18 Feb 2024 11:33:16 +0100 Subject: [PATCH 405/442] [XrdPosix] Fix build on FreeBSD Fixes: #2090 --- src/XrdPosix/XrdPosix.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdPosix/XrdPosix.cc b/src/XrdPosix/XrdPosix.cc index e4f020c09bf..6e61297af2e 100644 --- a/src/XrdPosix/XrdPosix.cc +++ b/src/XrdPosix/XrdPosix.cc @@ -69,7 +69,7 @@ static inline void fseterr(FILE *fp) #if defined _IO_ERR_SEEN || defined _IO_ftrylockfile || __GNU_LIBRARY__ == 1 /* GNU libc, BeOS, Haiku, Linux libc5 */ fp->_flags |= _IO_ERR_SEEN; -#elif defined __sferror || defined __APPLE__ || defined __DragonFly__ || defined __ANDROID__ +#elif defined __sferror || defined __APPLE__ || defined __DragonFly__ || defined __FreeBSD__ || defined __ANDROID__ /* FreeBSD, NetBSD, OpenBSD, DragonFly, Mac OS X, Cygwin, Minix 3, Android */ fp->_flags |= __SERR; #elif defined _IOERR From 81c3ef37c9b952d35715b97b3ba50fd1f2948ca0 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Sun, 18 Feb 2024 11:33:01 +0100 Subject: [PATCH 406/442] [XrdSecztn] Fix build on FreeBSD --- src/XrdSecztn/XrdSecProtocolztn.cc | 11 ++++++++--- src/XrdSecztn/XrdSecztn.cc | 6 +++++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/XrdSecztn/XrdSecProtocolztn.cc b/src/XrdSecztn/XrdSecProtocolztn.cc index 6312910dbc9..ec74502f43d 100644 --- a/src/XrdSecztn/XrdSecProtocolztn.cc +++ b/src/XrdSecztn/XrdSecProtocolztn.cc @@ -29,8 +29,7 @@ /******************************************************************************/ #define __STDC_FORMAT_MACROS 1 -#include -#include + #include #include #include @@ -40,13 +39,19 @@ #include #include #include -#include #include #include + +#ifndef __FreeBSD__ +#include +#endif + #include #include #include #include +#include +#include #include "XrdVersion.hh" diff --git a/src/XrdSecztn/XrdSecztn.cc b/src/XrdSecztn/XrdSecztn.cc index dcb955e1f58..036baed646e 100644 --- a/src/XrdSecztn/XrdSecztn.cc +++ b/src/XrdSecztn/XrdSecztn.cc @@ -25,10 +25,14 @@ // heavily edited to solve this particular problem. For more info see: // https://github.com/pokowaka/jwt-cpp -#include #include +#include #include +#ifndef __FreeBSD__ +#include +#endif + #define WHITESPACE 64 #define EQUALS 65 #define INVALID 66 From b95ce5f697672ea930838c534e94b443bad312ff Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Sun, 18 Feb 2024 11:37:21 +0100 Subject: [PATCH 407/442] [Tests] Include for IPv6 --- tests/common/Server.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/common/Server.cc b/tests/common/Server.cc index 6097fd09770..6f9e7cd7304 100644 --- a/tests/common/Server.cc +++ b/tests/common/Server.cc @@ -26,6 +26,7 @@ #include #include #include +#include #include From 1c600c4e856264a8fe5f1adf50d150cde3bc0a2b Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 5 Feb 2024 23:02:42 +0100 Subject: [PATCH 408/442] [Server] Add warning when monitor ID is truncated Clang doesn't know about GCC pragmas and warns about it. Replace GCC pragmas with a simple warning in the log that the ID was truncated. This silences both GCC and Clang warnings. --- src/XrdXrootd/XrdXrootdMonitor.cc | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/XrdXrootd/XrdXrootdMonitor.cc b/src/XrdXrootd/XrdXrootdMonitor.cc index d3c0e381b7f..08dc60493d7 100644 --- a/src/XrdXrootd/XrdXrootdMonitor.cc +++ b/src/XrdXrootd/XrdXrootdMonitor.cc @@ -287,14 +287,6 @@ void XrdXrootdMonitor::User::Enable() /* X r d X r o o t d M o n i t o r : : U s e r : : R e g i s t e r */ /******************************************************************************/ -// The gcc specific pragmas are to prevent gcc from complaining about an -// impossible situtation. Even if it were possible we don't care in practice. -// The snprintf below cannot be truncated as we made sure of that by setting -// null bytes to delimit the source buffer. However, gcc is not clever enough -// to figure that out and complains. See gcc Bug 1431678 for the history. -// BTW checking the return value works in C but not in C++, another oversight. -// The only other solution is to wait for C++20 and use std::format_to. - void XrdXrootdMonitor::User::Register(const char *Uname, const char *Hname, const char *Pname, unsigned int xSID) @@ -317,11 +309,11 @@ void XrdXrootdMonitor::User::Register(const char *Uname, dotP = sBuff; } -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wformat-truncation" - snprintf(uBuff, sizeof(uBuff), "%s/%s.%s:%s@%s", Pname, tBuff, - dotP+1, kySID, atP+1); -#pragma GCC diagnostic pop + int n = snprintf(uBuff, sizeof(uBuff), "%s/%s.%s:%s@%s", Pname, tBuff, + dotP+1, kySID, atP+1); + + if (n < 0 || n >= (int) sizeof(uBuff)) + TRACE(LOGIN, "Login ID was truncated: " << uBuff); if (xSID) {TRACE(LOGIN,"Register remap "< "< Date: Tue, 20 Feb 2024 14:15:12 +0100 Subject: [PATCH 409/442] [XrdTls] Removed openssl code to replace the server certificate The functions SSL_CTX_get_cert_store() and SSL_CTX_get_cert_store() simply do not work... Using the same methodology to replace the server certificate regardless of the openssl version will ease our life in the future. Solves #1678 --- src/XrdTls/XrdTlsContext.cc | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/XrdTls/XrdTlsContext.cc b/src/XrdTls/XrdTlsContext.cc index d48a2b598c3..1d82795d2eb 100644 --- a/src/XrdTls/XrdTlsContext.cc +++ b/src/XrdTls/XrdTlsContext.cc @@ -922,7 +922,6 @@ void *XrdTlsContext::Session() //We have a new context generated by Refresh, so we must use it. XrdTlsContext * ctxnew = pImpl->ctxnew; -#if OPENSSL_VERSION_NUMBER < 0x10101000L /*X509_STORE *newX509 = SSL_CTX_get_cert_store(ctxnew->pImpl->ctx); SSL_CTX_set1_verify_cert_store(pImpl->ctx, newX509); SSL_CTX_set1_chain_cert_store(pImpl->ctx, newX509);*/ @@ -938,10 +937,6 @@ void *XrdTlsContext::Session() //we just created, we don't want that to happen. We therefore set it to 0. //The SSL_free called on the session will cleanup the context for us. ctxnew->pImpl->ctx = 0; -#else - X509_STORE *newX509 = SSL_CTX_get_cert_store(ctxnew->pImpl->ctx); - SSL_CTX_set1_cert_store(pImpl->ctx, newX509); -#endif // Save the generated context and clear it's presence // From a4439513925e7e5e0c40ac454b75b32f2b9c24fb Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Wed, 14 Feb 2024 15:57:49 +0100 Subject: [PATCH 410/442] [XrdTpcTPC] Packet marking - avoid infinite loop in the case the first packet marking handle created got removed after the corresponding socket got closed --- src/XrdTpc/XrdTpcPMarkManager.cc | 50 +++++++++++++++----------------- src/XrdTpc/XrdTpcPMarkManager.hh | 2 -- 2 files changed, 24 insertions(+), 28 deletions(-) diff --git a/src/XrdTpc/XrdTpcPMarkManager.cc b/src/XrdTpc/XrdTpcPMarkManager.cc index ee822642df9..4d99bd6621a 100644 --- a/src/XrdTpc/XrdTpcPMarkManager.cc +++ b/src/XrdTpc/XrdTpcPMarkManager.cc @@ -45,37 +45,35 @@ void PMarkManager::startTransfer(XrdHttpExtReq * req) { } void PMarkManager::beginPMarks() { - if(!mSocketInfos.empty() && mPmarkHandles.empty()) { - // Create the first pmark handle that will be used as a basis for the other handles - // if that handle cannot be created (mPmark->Begin() would return nullptr), then the packet marking will not work - // This base pmark handle will be placed at the beginning of the vector of pmark handles + if(mSocketInfos.empty()) { + return; + } + + if(mPmarkHandles.empty()) { + // Create the first pmark handle std::stringstream ss; ss << "scitag.flow=" << mReq->mSciTag; SocketInfo & sockInfo = mSocketInfos.front(); - mInitialFD = sockInfo.client.addrInfo->SockFD(); - std::unique_ptr initialPmark(mPmark->Begin(sockInfo.client, mReq->resource.c_str(), ss.str().c_str(), "http-tpc")); - if(initialPmark) { - // It may happen that the socket attached to the file descriptor is not connected yet. If this is the case the initial - // Pmark will be nullptr... - mPmarkHandles.emplace(mInitialFD,std::move(initialPmark)); - mSocketInfos.pop(); + auto pmark = mPmark->Begin(sockInfo.client, mReq->resource.c_str(), ss.str().c_str(), "http-tpc"); + if(!pmark) { + return; } - } else { - // The first pmark handle was created, or not. Create the other pmark handles from the other connected sockets - while(!mSocketInfos.empty()) { - SocketInfo & sockInfo = mSocketInfos.front(); - if(mPmarkHandles[mInitialFD]){ - std::unique_ptr pmark(mPmark->Begin(*sockInfo.client.addrInfo, *mPmarkHandles[mInitialFD], nullptr)); - if(pmark) { - mPmarkHandles.emplace(sockInfo.client.addrInfo->SockFD(),std::move(pmark)); - mSocketInfos.pop(); - } else { - // We could not create the pmark handle from the socket, we break the loop, we will retry later on when - // this function will be called again. - break; - } - } + mPmarkHandles.emplace(sockInfo.client.addrInfo->SockFD(),std::unique_ptr(pmark)); + mSocketInfos.pop(); + } + + auto pmarkHandleItor = mPmarkHandles.begin(); + while(!mSocketInfos.empty()) { + SocketInfo & sockInfo = mSocketInfos.front(); + auto pmark = mPmark->Begin(*sockInfo.client.addrInfo, *(pmarkHandleItor->second), nullptr); + if (!pmark) { + // The packet marking handle could not be created from the first handle, let's retry next time + break; } + + int fd = sockInfo.client.addrInfo->SockFD(); + mPmarkHandles.emplace(fd, std::move(std::unique_ptr(pmark))); + mSocketInfos.pop(); } } diff --git a/src/XrdTpc/XrdTpcPMarkManager.hh b/src/XrdTpc/XrdTpcPMarkManager.hh index 3da5ddb0aa8..938de5c62d8 100644 --- a/src/XrdTpc/XrdTpcPMarkManager.hh +++ b/src/XrdTpc/XrdTpcPMarkManager.hh @@ -107,8 +107,6 @@ private: bool mTransferWillStart; // The XrdHttpTPC request information XrdHttpExtReq * mReq; - // The file descriptor used to create the first packet marking handle - int mInitialFD = -1; }; } // namespace XrdTpc From 25532121357f627e8de007639bd823835a1ff560 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 21 Feb 2024 15:53:13 +0100 Subject: [PATCH 411/442] [XrdClTls] Split TLS context and TLS socket initializations --- src/XrdCl/XrdClTls.cc | 70 +++++++++++++++++++++++++++++-------------- src/XrdCl/XrdClTls.hh | 3 ++ 2 files changed, 51 insertions(+), 22 deletions(-) diff --git a/src/XrdCl/XrdClTls.cc b/src/XrdCl/XrdClTls.cc index 3b66ff9f334..a788b7c2229 100644 --- a/src/XrdCl/XrdClTls.cc +++ b/src/XrdCl/XrdClTls.cc @@ -25,10 +25,13 @@ #include "XrdTls/XrdTls.hh" #include "XrdTls/XrdTlsContext.hh" +#include "XrdOuc/XrdOucUtils.hh" #include #include +static std::unique_ptr tlsContext = nullptr; + namespace { //------------------------------------------------------------------------ @@ -85,21 +88,52 @@ namespace return XrdTls::dbgOFF; } }; - - //------------------------------------------------------------------------ - // Helper function for setting the CA directory in TLS context - //------------------------------------------------------------------------ - static const char* GetCaDir() - { - static const char *envval = getenv("X509_CERT_DIR"); - static const std::string cadir = envval ? envval : - "/etc/grid-security/certificates"; - return cadir.c_str(); - } } namespace XrdCl { + bool InitTLS() + { + if (tlsContext) + return true; + + XrdCl::Env *env = XrdCl::DefaultEnv::GetEnv(); + XrdCl::Log *log = XrdCl::DefaultEnv::GetLog(); + + int notls = false; + env->GetInt("NoTlsOK", notls); + + if (notls) + return false; + + const char *cadir = getenv("X509_CERT_DIR"); + const char *cafile = getenv("X509_CERT_FILE"); + + if (!cadir && !cafile) + cadir = "/etc/grid-security/certificates"; + + const char *msg; + const mode_t camode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; + + if (cadir && (msg = XrdOucUtils::ValPath(cadir, camode, true))) { + log->Error(XrdCl::TlsMsg, "Failed to initialize TLS context: CA directory %s", msg); + env->PutInt("NoTlsOK", 1); + return false; + } + + std::string emsg = "unknown error"; + tlsContext = std::make_unique(nullptr, nullptr, cadir, cafile, 0ul, &emsg); + + if (!tlsContext || !tlsContext->isOK()) { + tlsContext.reset(nullptr); + log->Error(XrdCl::TlsMsg, "Failed to initialize TLS context: %s", emsg.c_str()); + env->PutInt("NoTlsOK", 1); + return false; + } + + return true; + } + //------------------------------------------------------------------------ // Constructor //------------------------------------------------------------------------ @@ -109,20 +143,12 @@ namespace XrdCl // Set the message callback for TLS layer //---------------------------------------------------------------------- SetTlsMsgCB::Once(); - //---------------------------------------------------------------------- - // we only need one instance of TLS - //---------------------------------------------------------------------- - std::string emsg = "Failed to initialize TLS context"; - static XrdTlsContext tlsContext( 0, 0, GetCaDir(), 0, 0, &emsg ); - //---------------------------------------------------------------------- - // If the context is not valid throw an exception! We throw generic - // exception as this will be translated to TlsError anyway. - //---------------------------------------------------------------------- - if( !tlsContext.isOK() ) throw std::runtime_error( emsg ); + if( !InitTLS() ) + throw std::runtime_error( "Failed to initialize TLS" ); pTls.reset( - new XrdTlsSocket( tlsContext, pSocket->GetFD(), XrdTlsSocket::TLS_RNB_WNB, + new XrdTlsSocket( *tlsContext, pSocket->GetFD(), XrdTlsSocket::TLS_RNB_WNB, XrdTlsSocket::TLS_HS_NOBLK, true ) ); } diff --git a/src/XrdCl/XrdClTls.hh b/src/XrdCl/XrdClTls.hh index 1b8cc4563d9..f74ce3ecacb 100644 --- a/src/XrdCl/XrdClTls.hh +++ b/src/XrdCl/XrdClTls.hh @@ -30,6 +30,9 @@ namespace XrdCl { class Socket; + /** Initialize TLS context, returns false on failure */ + bool InitTLS(); + //---------------------------------------------------------------------------- //! TLS layer for socket connection //---------------------------------------------------------------------------- From be89ee0cb3aed0b8735fb65b3aacb365db9e9a7b Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 21 Feb 2024 15:54:22 +0100 Subject: [PATCH 412/442] [XrdCl] Only claim to be TLS capable if TLS initialization succeeds --- src/XrdCl/XrdClXRootDTransport.cc | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/XrdCl/XrdClXRootDTransport.cc b/src/XrdCl/XrdClXRootDTransport.cc index 8604f107e5b..239ac6dc853 100644 --- a/src/XrdCl/XrdClXRootDTransport.cc +++ b/src/XrdCl/XrdClXRootDTransport.cc @@ -1905,25 +1905,24 @@ namespace XrdCl request->flags = ClientProtocolRequest::kXR_secreqs | ClientProtocolRequest::kXR_bifreqs; + int notlsok = DefaultNoTlsOK; + int tlsnodata = DefaultTlsNoData; + XrdCl::Env *env = XrdCl::DefaultEnv::GetEnv(); - int notlsok = DefaultNoTlsOK; env->GetInt( "NoTlsOK", notlsok ); - if (info->encrypted || !notlsok) - request->flags |= ClientProtocolRequest::kXR_ableTLS; + if (expect & ClientProtocolRequest::kXR_ExpBind) + env->GetInt( "TlsNoData", tlsnodata ); - bool nodata = false; - if( expect & ClientProtocolRequest::kXR_ExpBind ) - { - int value = DefaultTlsNoData; - env->GetInt( "TlsNoData", value ); - nodata = bool( value ); - } + if (info->encrypted || InitTLS()) + request->flags |= ClientProtocolRequest::kXR_ableTLS; - if( info->encrypted && !nodata ) + if (info->encrypted || !(notlsok || tlsnodata)) request->flags |= ClientProtocolRequest::kXR_wantTLS; + request->expect = expect; + //-------------------------------------------------------------------------- // If we are in the curse of establishing a connection in the context of // TPC update the expect! (this will be never followed be a bind) From f2844fc40a4dabfd219c2277bbe9795d00ffad2f Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 22 Feb 2024 13:16:23 +0100 Subject: [PATCH 413/442] [XrdCl] Only consider an endpoint TLS-enabled if the connection is encrypted --- src/XrdCl/XrdClXRootDTransport.cc | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/XrdCl/XrdClXRootDTransport.cc b/src/XrdCl/XrdClXRootDTransport.cc index 239ac6dc853..60fb3e461ed 100644 --- a/src/XrdCl/XrdClXRootDTransport.cc +++ b/src/XrdCl/XrdClXRootDTransport.cc @@ -2635,9 +2635,7 @@ namespace XrdCl // credentials //-------------------------------------------------------------------------- XrdNetAddr &srvAddrInfo = *const_cast(hsData->serverAddr); - if( info->encrypted || ( info->serverFlags & kXR_gotoTLS ) || - ( info->serverFlags & kXR_tlsLogin ) ) - srvAddrInfo.SetTLS( true ); + srvAddrInfo.SetTLS( info->encrypted ); while(1) { //------------------------------------------------------------------------ From a4c21299d30f6b254a2136669572a92914ab23a5 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 22 Feb 2024 11:40:12 +0100 Subject: [PATCH 414/442] [CI] Run containerized builds as non-root user --- .github/workflows/CI.yml | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index aa8e2147591..4ffcf83fb84 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -44,25 +44,30 @@ jobs: bash \ cmake \ cppunit-dev \ + ceph-dev \ curl-dev \ fuse-dev \ fuse3-dev \ g++ \ git \ gtest-dev \ + isa-l-dev \ json-c-dev \ krb5-dev \ libxml2-dev \ linux-headers \ make \ + openssl \ openssl-dev \ py3-pip \ py3-setuptools \ py3-wheel \ python3-dev \ readline-dev \ + sudo \ tinyxml-dev \ util-linux-dev \ + uuidgen \ zlib-dev - name: Clone repository @@ -70,8 +75,11 @@ jobs: with: fetch-depth: 0 - - name: CTest Build - run: ctest -VV -S test.cmake + - name: Setup GitHub runner user within container + run: adduser -D --uid 1001 runner && chown -R runner:runner ${GITHUB_WORKSPACE} + + - name: Build and Test with CTest + run: sudo -E -u runner ctest -VV -S test.cmake - name: Install with CMake run: cmake --install build @@ -201,11 +209,14 @@ jobs: with: fetch-depth: 0 + - name: Setup GitHub runner user within container + run: adduser --uid 1001 runner && chown -R runner:runner ${GITHUB_WORKSPACE} + - name: Install XRootD build dependencies - run: dnf builddep -y xrootd.spec + run: dnf builddep -y --define 'with_ceph 1' xrootd.spec - name: Build and Test with CTest - run: ctest -VV -S test.cmake + run: sudo -E -u runner ctest -VV -S test.cmake - name: Install with CMake run: cmake --install build From cf605fe1e6de1d19a2ba2292227251f70cd65ff0 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 22 Feb 2024 14:56:07 +0100 Subject: [PATCH 415/442] [CI] Do not run CI jobs when pushing tags --- .github/workflows/CI.yml | 1 + .github/workflows/DEB.yml | 1 + .github/workflows/RPM.yml | 1 + .github/workflows/python.yml | 1 + 4 files changed, 4 insertions(+) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 4ffcf83fb84..00a9de9fab8 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -11,6 +11,7 @@ on: - '**.md' - 'docs/**' - 'docker/**' + tags-ignore: pull_request: workflow_dispatch: diff --git a/.github/workflows/DEB.yml b/.github/workflows/DEB.yml index 0410cb22b08..4f6c7a34f41 100644 --- a/.github/workflows/DEB.yml +++ b/.github/workflows/DEB.yml @@ -11,6 +11,7 @@ on: - '**.md' - 'docs/**' - 'docker/**' + tags-ignore: workflow_dispatch: concurrency: diff --git a/.github/workflows/RPM.yml b/.github/workflows/RPM.yml index 0b977154aee..423a1a4b939 100644 --- a/.github/workflows/RPM.yml +++ b/.github/workflows/RPM.yml @@ -11,6 +11,7 @@ on: - '**.md' - 'docs/**' - 'docker/**' + tags-ignore: workflow_dispatch: concurrency: diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 49b0048c192..b27d40c20ba 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -9,6 +9,7 @@ on: - setup.py - pyproject.toml - bindings/python + tags-ignore: pull_request: paths: - setup.py From e08fef083e186cafd8fa55d3d9407aca1f027243 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 23 Feb 2024 13:35:20 +0100 Subject: [PATCH 416/442] [RPM] Install the client as dependency of main RPM In some cases, like when a server is configured for third-party transfers, the server calls the client directly. Therefore, it is a good idea to install both server and client when running dnf install xrootd. --- xrootd.spec | 1 + 1 file changed, 1 insertion(+) diff --git a/xrootd.spec b/xrootd.spec index 7e5ff46a5be..0d07dd6fea8 100644 --- a/xrootd.spec +++ b/xrootd.spec @@ -143,6 +143,7 @@ BuildRequires: openssl BuildRequires: isa-l-devel %endif +Requires: %{name}-client%{?_isa} = %{epoch}:%{version}-%{release} Requires: %{name}-server%{?_isa} = %{epoch}:%{version}-%{release} Requires: %{name}-selinux = %{epoch}:%{version}-%{release} From 0199839117a5f4b7a90817ce2c1818b60a06a421 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 23 Feb 2024 13:48:02 +0100 Subject: [PATCH 417/442] [RPM] Create systemd tmpfiles at post-install step --- xrootd.spec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xrootd.spec b/xrootd.spec index 0d07dd6fea8..33d215f1ef6 100644 --- a/xrootd.spec +++ b/xrootd.spec @@ -647,6 +647,8 @@ getent passwd %{name} >/dev/null || useradd -r -g %{name} -s /sbin/nologin \ -d %{_localstatedir}/spool/%{name} -c "System user for XRootD" %{name} %post server +%tmpfiles_create %{_tmpfilesdir}/%{name}.conf + if [ $1 -eq 1 ] ; then systemctl daemon-reload >/dev/null 2>&1 || : fi @@ -662,8 +664,6 @@ if [ $1 -eq 0 ] ; then fi %postun server -%tmpfiles_create %{_tmpfilesdir}/%{name}.conf - if [ $1 -ge 1 ] ; then systemctl daemon-reload >/dev/null 2>&1 || : for DAEMON in xrootd cmsd frm_purged frm_xfrd; do From 16509a17b32f814744b53e30e48037fddb4926b2 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 23 Feb 2024 13:50:08 +0100 Subject: [PATCH 418/442] [RPM] Remove hard-coded /usr/sbin from commands --- xrootd.spec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xrootd.spec b/xrootd.spec index 33d215f1ef6..a1aae74471c 100644 --- a/xrootd.spec +++ b/xrootd.spec @@ -674,11 +674,11 @@ if [ $1 -ge 1 ] ; then fi %post selinux -/usr/sbin/semodule -i %{_datadir}/selinux/packages/%{name}/%{name}.pp >/dev/null 2>&1 || : +semodule -i %{_datadir}/selinux/packages/%{name}/%{name}.pp >/dev/null 2>&1 || : %postun selinux if [ $1 -eq 0 ] ; then - /usr/sbin/semodule -r %{name} >/dev/null 2>&1 || : + semodule -r %{name} >/dev/null 2>&1 || : fi %files From c1e671e513fc53532460474f481a59cb23f1426f Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 23 Feb 2024 15:56:09 +0100 Subject: [PATCH 419/442] Update README.md - Update DEB/RPM install instructions - Mention that XRootD is also available in conda-forge - Replace relative with full links, so that links work in PyPI Fixes: #2203 --- README.md | 52 +++++++++++++++++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index c17a2c513bd..a21832d4c22 100644 --- a/README.md +++ b/README.md @@ -38,34 +38,44 @@ XRootD is officially supported on the following platforms: * Debian 11 and Ubuntu 22.04 or later * macOS 11 (Big Sur) or later -Support for other operating systems is provided by the community. +Support for other operating systems is provided on a best-effort basis +and by contributions from the community. ## Installation Instructions XRootD is available via official channels in most operating systems. -Installation via the system package manager should be preferred if possible. +Installation via your system's package manager should be preferred. In RPM-based distributions, like CentOS, Alma, Rocky, Fedora, etc, one can search and install XRootD packages with ```sh -$ yum search xrootd -$ sudo yum install xrootd* python3-xrootd +$ sudo yum install xrootd ``` or ```sh -$ dnf search xrootd -$ sudo dnf install xrootd* python3-xrootd +$ sudo dnf install xrootd ``` -In some distributions, it may be necessary to first install the EPEL release -repository with `yum install epel-release` or `dnf install epel-release`. +In RHEL-based distributions, it will be necessary to first install the EPEL +release repository with `yum install epel-release` or `dnf install epel-release`. + +If you would like to use our official repository for XRootD RPMs, you can enable +it on RHEL-based distributions with + +```sh +$ sudo curl -L https://cern.ch/xrootd/xrootd.repo -o /etc/yum.repos.d/xrootd.repo +``` + +and on Fedora with +```sh +$ sudo curl -L https://cern.ch/xrootd/xrootd-fedora.repo -o /etc/yum.repos.d/xrootd.repo +``` On Debian 11 or later, and Ubuntu 22.04 or later, XRootD can be installed via apt ```sh -$ apt search xrootd -$ sudo apt install xrootd* python3-xrootd +$ sudo apt install xrootd-client xrootd-server python3-xrootd ``` On macOS, XRootD is available via Homebrew @@ -73,27 +83,35 @@ On macOS, XRootD is available via Homebrew $ brew install xrootd ``` -Finally, it is also possible to install the XRootD python bindings from PyPI -using pip: +XRootD can also be installed with conda, as it is also available in conda-forge: +```sh +$ conda config --add channels conda-forge +$ conda config --set channel_priority strict +$ conda install xrootd ``` + +Finally, it is possible to install the XRootD python bindings from PyPI using pip: +```sh $ pip install xrootd ``` For detailed instructions on how to build and install XRootD from source code, -please see [docs/INSTALL.md](docs/INSTALL.md) in this repository. +please see [docs/INSTALL.md](https://github.com/xrootd/xrootd/blob/master/docs/INSTALL.md) +in the main repository on GitHub. ## User Support and Bug Reports Bugs should be reported using [GitHub issues](https://github.com/xrootd/xrootd/issues). You can open a new ticket by clicking [here](https://github.com/xrootd/xrootd/issues/new). -For general questions about XRootD, you can send a message to our user mailing -list at xrootd-l@slac.stanford.edu. Please check XRootD's contact page at -http://xrootd.org/contact.html for further information. +For general questions about XRootD, please send a message to our user mailing +list at xrootd-l@slac.stanford.edu or open a new [discussion](https://github.com/xrootd/xrootd/discussions) +on GitHub. Please check XRootD's contact page at http://xrootd.org/contact.html +for further information. ## Contributing User contributions can be submitted via pull request on GitHub. We recommend that you create your own fork of XRootD on GitHub and use it to submit your patches. For more detailed instructions on how to contribute, please refer to -the file [docs/CONTRIBUTING.md](docs/CONTRIBUTING.md). +the file [docs/CONTRIBUTING.md](https://github.com/xrootd/xrootd/blob/master/docs/CONTRIBUTING.md). From 72ed79c6b57d0350737c91f88def214e494f6dd8 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 23 Feb 2024 16:15:21 +0100 Subject: [PATCH 420/442] [RPM] Update spec file for XRootD 5.6.8 --- xrootd.spec | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/xrootd.spec b/xrootd.spec index a1aae74471c..4645f924568 100644 --- a/xrootd.spec +++ b/xrootd.spec @@ -25,7 +25,7 @@ License: LGPL-3.0-or-later AND BSD-2-Clause AND BSD-3-Clause AND curl AND MIT AN URL: https://xrootd.slac.stanford.edu %if !%{with git} -Version: 5.6.7 +Version: 5.6.8 Source0: %{url}/download/v%{version}/%{name}-%{version}.tar.gz %else %define git_version %(tar xzf %{_sourcedir}/%{name}.tar.gz -O xrootd/VERSION) @@ -951,6 +951,9 @@ fi %changelog +* Fri Feb 23 2024 Guilherme Amadio - 1:5.6.8-1 +- XRootD 5.6.8 + * Tue Feb 06 2024 Guilherme Amadio - 1:5.6.7-1 - XRootD 5.6.7 From d39a45683807ced96c7766f96a12aaffa6092c2d Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 23 Feb 2024 16:34:55 +0100 Subject: [PATCH 421/442] XRootD 5.6.8 --- docs/ReleaseNotes.txt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/ReleaseNotes.txt b/docs/ReleaseNotes.txt index 274c1578da9..41d46cf1926 100644 --- a/docs/ReleaseNotes.txt +++ b/docs/ReleaseNotes.txt @@ -5,6 +5,24 @@ XRootD Release Notes ============= +------------- +Version 5.6.8 +------------- + ++ **Minor bug fixes** + **[RPM]** Create systemd tmpfiles at post-install step + **[XrdCl]** Only claim to be TLS capable if TLS initialization succeeds (issue #2020) + **[XrdCl]** Only consider an endpoint TLS-enabled if the connection is encrypted** + **[XrdCl]** Remove duplicates from URL list to avoid undefined behavior + **[XrdHttpTPC]** Fix infinite loop when scitags packet marking is enabled (issue #2192) + **[XrdPosix,XrdSecztn]** Fix build on FreeBSD (issue #2090) + **[XrdTls]** Fix automatic renewal of server certificate with OpenSSL>=1.1 (issue #1678) + ++ **Miscellaneous** + **[CMake]** Use CTest module in test.cmake and optionally submit to CDash + **[RPM]** Install the client as dependency of main RPM + **[Server]** Fix clang compile warnings + ------------- Version 5.6.7 ------------- From 609f065a91c029f982061d019c7820dc594f1a0e Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Sat, 24 Feb 2024 12:51:49 +0100 Subject: [PATCH 422/442] [XrdCl] Fix logic error when upgrading connections to TLS Fixes: be89ee0cb3aed0b8735fb65b3aacb365db9e9a7b --- src/XrdCl/XrdClXRootDTransport.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdCl/XrdClXRootDTransport.cc b/src/XrdCl/XrdClXRootDTransport.cc index 60fb3e461ed..0be81f902ba 100644 --- a/src/XrdCl/XrdClXRootDTransport.cc +++ b/src/XrdCl/XrdClXRootDTransport.cc @@ -1918,7 +1918,7 @@ namespace XrdCl if (info->encrypted || InitTLS()) request->flags |= ClientProtocolRequest::kXR_ableTLS; - if (info->encrypted || !(notlsok || tlsnodata)) + if (info->encrypted && !(notlsok || tlsnodata)) request->flags |= ClientProtocolRequest::kXR_wantTLS; request->expect = expect; From fd0f77274726e58b8d8b725df200eaa6715686a0 Mon Sep 17 00:00:00 2001 From: Brian Bockelman Date: Fri, 15 Dec 2023 08:00:18 -0600 Subject: [PATCH 423/442] Attempt to use restricted paths even when scope is more generic If a token has a broad scope and all the listed restricted paths are more specific, then try to generate ACLs that include the restricted paths. Example: suppose we have a token with scope `storage.read:/` but restricted paths `/foo` and `/bar`. Currently, we will reject the scope and give the token no permissions. With this commit, we will generate acls of `read:/foo` and `read:/bar`. So, as long as the access being attempted is within the restricted path, we can permit it. --- src/XrdSciTokens/XrdSciTokensAccess.cc | 34 ++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/src/XrdSciTokens/XrdSciTokensAccess.cc b/src/XrdSciTokens/XrdSciTokensAccess.cc index 8d0f238c8bb..9eab1327811 100644 --- a/src/XrdSciTokens/XrdSciTokensAccess.cc +++ b/src/XrdSciTokens/XrdSciTokensAccess.cc @@ -862,20 +862,43 @@ class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper, int idx = 0; std::set paths_write_seen; std::set paths_create_or_modify_seen; + std::vector acl_paths; + acl_paths.reserve(config.m_restricted_paths.size() + 1); while (acls[idx].resource && acls[idx++].authz) { + acl_paths.clear(); const auto &acl_path = acls[idx-1].resource; const auto &acl_authz = acls[idx-1].authz; - if (!config.m_restricted_paths.empty()) { - bool found_path = false; + if (config.m_restricted_paths.empty()) { + acl_paths.push_back(acl_path); + } else { + auto acl_path_size = strlen(acl_path); for (const auto &restricted_path : config.m_restricted_paths) { + // See if the acl_path is more specific than the restricted path; if so, accept it + // and move on to applying paths. if (!strncmp(acl_path, restricted_path.c_str(), restricted_path.size())) { - found_path = true; + // Only do prefix checking on full path components. If acl_path=/foobar and + // restricted_path=/foo, then we shouldn't authorize access to /foobar. + if (acl_path_size > restricted_path.size() && acl_path[restricted_path.size()] != '/') { + continue; + } + acl_paths.push_back(acl_path); break; } + // See if the restricted_path is more specific than the acl_path; if so, accept the + // restricted path as the ACL. Keep looping to see if other restricted paths add + // more possible authorizations. + if (!strncmp(acl_path, restricted_path.c_str(), acl_path_size)) { + // Only do prefix checking on full path components. If acl_path=/foo and + // restricted_path=/foobar, then we shouldn't authorize access to /foobar. + if (restricted_path.size() > acl_path_size && restricted_path[acl_path_size] != '/') { + continue; + } + acl_paths.push_back(restricted_path); + } } - if (!found_path) {continue;} } - for (const auto &base_path : config.m_base_paths) { + for (const auto &acl_path : acl_paths) { + for (const auto &base_path : config.m_base_paths) { if (!acl_path[0] || acl_path[0] != '/') {continue;} std::string path; MakeCanonical(base_path + acl_path, path); @@ -901,6 +924,7 @@ class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper, } else if (!strcmp(acl_authz, "write")) { paths_write_seen.insert(path); } + } } } for (const auto &write_path : paths_write_seen) { From aa87750f0aa05c4222945b99a97a109dcfb649fc Mon Sep 17 00:00:00 2001 From: Brian Bockelman Date: Tue, 23 Jan 2024 08:58:53 -0600 Subject: [PATCH 424/442] Only match ACLs on a full path match. If there's an ACL for `/foo`, do not permit `/foobar` but do permit `/foo/bar`. --- src/XrdSciTokens/XrdSciTokensAccess.cc | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/XrdSciTokens/XrdSciTokensAccess.cc b/src/XrdSciTokens/XrdSciTokensAccess.cc index 9eab1327811..31e719443f7 100644 --- a/src/XrdSciTokens/XrdSciTokensAccess.cc +++ b/src/XrdSciTokens/XrdSciTokensAccess.cc @@ -350,7 +350,16 @@ class XrdAccRules bool apply(Access_Operation oper, std::string path) { for (const auto & rule : m_rules) { - if ((oper == rule.first) && !path.compare(0, rule.second.size(), rule.second, 0, rule.second.size())) { + // The rule permits if both conditions are met: + // - The operation type matches the requested operation, + // - The requested path is a substring of the ACL's permitted path, AND + // - Either the requested path and ACL path is the same OR the requested path is a subdir of the ACL path. + // + // The third rule implies if the rule permits read:/foo, we should NOT authorize read:/foobar. + if ((oper == rule.first) && + !path.compare(0, rule.second.size(), rule.second, 0, rule.second.size()) && + (rule.second.size() == path.length() || path[rule.second.size()]=='/')) + { return true; } } From 50c540afecb414b8cfea03badc9d376b8494c5d1 Mon Sep 17 00:00:00 2001 From: Brian Bockelman Date: Mon, 12 Feb 2024 21:21:25 -0600 Subject: [PATCH 425/442] If the token path is `/`, then use the restricted path. This handles the case where the token path ends in `/`; because the scitokens-cpp library normalizes the paths to not end with the '/' character, the only case to handle is the root directory. --- src/XrdSciTokens/XrdSciTokensAccess.cc | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/XrdSciTokens/XrdSciTokensAccess.cc b/src/XrdSciTokens/XrdSciTokensAccess.cc index 31e719443f7..71d35147e93 100644 --- a/src/XrdSciTokens/XrdSciTokensAccess.cc +++ b/src/XrdSciTokens/XrdSciTokensAccess.cc @@ -898,8 +898,12 @@ class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper, // more possible authorizations. if (!strncmp(acl_path, restricted_path.c_str(), acl_path_size)) { // Only do prefix checking on full path components. If acl_path=/foo and - // restricted_path=/foobar, then we shouldn't authorize access to /foobar. - if (restricted_path.size() > acl_path_size && restricted_path[acl_path_size] != '/') { + // restricted_path=/foobar, then we shouldn't authorize access to /foobar. Note: + // - The scitokens-cpp library guaranteees that acl_path is normalized and not + // of the form `/foo/`. + // - Hence, the only time that the acl_path can end in a '/' is when it is + // set to `/`. + if ((restricted_path.size() > acl_path_size && restricted_path[acl_path_size] != '/') && (acl_path_size != 1)) { continue; } acl_paths.push_back(restricted_path); From 81ba2dfd57d8ee59d86b5405b7954a653b7571e2 Mon Sep 17 00:00:00 2001 From: Jo-stfc <71326101+Jo-stfc@users.noreply.github.com> Date: Thu, 8 Feb 2024 13:27:44 +0000 Subject: [PATCH 426/442] add stat permissions to create, modify and write stat permissions are implicitly required for storage systems for create, modify and delete operations. Currently tokens with purely create and modify permissions would fail with permission denied on stat on attempts to perform those operations --- src/XrdSciTokens/XrdSciTokensAccess.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/XrdSciTokens/XrdSciTokensAccess.cc b/src/XrdSciTokens/XrdSciTokensAccess.cc index 71d35147e93..501731fcf65 100644 --- a/src/XrdSciTokens/XrdSciTokensAccess.cc +++ b/src/XrdSciTokens/XrdSciTokensAccess.cc @@ -925,6 +925,7 @@ class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper, xrd_rules.emplace_back(AOP_Mkdir, path); xrd_rules.emplace_back(AOP_Rename, path); xrd_rules.emplace_back(AOP_Excl_Insert, path); + xrd_rules.emplace_back(AOP_Stat, path); } else if (!strcmp(acl_authz, "modify")) { paths_create_or_modify_seen.insert(path); xrd_rules.emplace_back(AOP_Create, path); @@ -933,6 +934,7 @@ class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper, xrd_rules.emplace_back(AOP_Insert, path); xrd_rules.emplace_back(AOP_Update, path); xrd_rules.emplace_back(AOP_Chmod, path); + xrd_rules.emplace_back(AOP_Stat, path); xrd_rules.emplace_back(AOP_Delete, path); } else if (!strcmp(acl_authz, "write")) { paths_write_seen.insert(path); @@ -948,6 +950,7 @@ class XrdAccSciTokens : public XrdAccAuthorize, public XrdSciTokensHelper, xrd_rules.emplace_back(AOP_Rename, write_path); xrd_rules.emplace_back(AOP_Insert, write_path); xrd_rules.emplace_back(AOP_Update, write_path); + xrd_rules.emplace_back(AOP_Stat, write_path); xrd_rules.emplace_back(AOP_Chmod, write_path); xrd_rules.emplace_back(AOP_Delete, write_path); } From df818436229f54cb491ec2fe5caf12b8eb778091 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 9 Feb 2024 13:55:31 +0000 Subject: [PATCH 427/442] allow creation of superfolders for the given filescope as required by WLCG token spec (https://github.com/WLCG-AuthZ-WG/common-jwt-profile/blob/master/profile.md) --- src/XrdSciTokens/XrdSciTokensAccess.cc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/XrdSciTokens/XrdSciTokensAccess.cc b/src/XrdSciTokens/XrdSciTokensAccess.cc index 501731fcf65..601cdb94ad0 100644 --- a/src/XrdSciTokens/XrdSciTokensAccess.cc +++ b/src/XrdSciTokens/XrdSciTokensAccess.cc @@ -362,6 +362,12 @@ class XrdAccRules { return true; } + // according to WLCG token specs, allow creation of required superfolders for a new file if requested + if ((oper == AOP_Stat || oper == AOP_Mkdir) && + !rule.second.compare(0, path.size(), path, 0, path.size()) && + rule.second.size() >= path.length() ) { + return true; + } } return false; } From e47303c8118cf6233b120b9c56d735ca46ea9c95 Mon Sep 17 00:00:00 2001 From: Cedric Caffy Date: Thu, 8 Feb 2024 15:42:26 +0100 Subject: [PATCH 428/442] [XrdTpcTPC] Send a 400 - invalid request - response when a user requests more than 100 streams for the HTTP-TPC PULL transfer --- src/XrdTpc/XrdTpcTPC.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdTpc/XrdTpcTPC.cc b/src/XrdTpc/XrdTpcTPC.cc index 88434b0b38e..bd35d8ce79e 100644 --- a/src/XrdTpc/XrdTpcTPC.cc +++ b/src/XrdTpc/XrdTpcTPC.cc @@ -986,7 +986,7 @@ int TPCHandler::ProcessPullReq(const std::string &resource, XrdHttpExtReq &req) } if (stream_req < 0 || stream_req > 100) { char msg[] = "Invalid request for number of streams"; - rec.status = 500; + rec.status = 400; logTransferEvent(LogMask::Info, rec, "INVALID_REQUEST", msg); return req.SendSimpleResp(rec.status, NULL, NULL, msg, 0); } From cca20aef2a4739e90e2227b786a52f4752b182a0 Mon Sep 17 00:00:00 2001 From: Jyothish Thomas Date: Fri, 9 Feb 2024 13:55:31 +0000 Subject: [PATCH 429/442] [XrdScitokens] Allow creation of superfolders for requested filescope As required by WLCG token spec, allow the creation of superfolders if a filepath is requested with a create or modify scope. https://github.com/WLCG-AuthZ-WG/common-jwt-profile/blob/master/profile.md Fixes: #2185 --- src/XrdSciTokens/XrdSciTokensAccess.cc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/XrdSciTokens/XrdSciTokensAccess.cc b/src/XrdSciTokens/XrdSciTokensAccess.cc index 601cdb94ad0..30a1527b3a3 100644 --- a/src/XrdSciTokens/XrdSciTokensAccess.cc +++ b/src/XrdSciTokens/XrdSciTokensAccess.cc @@ -363,9 +363,10 @@ class XrdAccRules return true; } // according to WLCG token specs, allow creation of required superfolders for a new file if requested - if ((oper == AOP_Stat || oper == AOP_Mkdir) && - !rule.second.compare(0, path.size(), path, 0, path.size()) && - rule.second.size() >= path.length() ) { + if ((oper == rule.first) && (oper == AOP_Stat || oper == AOP_Mkdir) + && rule.second.size() >= path.length() + && !rule.second.compare(0, path.size(), path, 0, path.size()) + && (rule.second.size() == path.length() || rule.second[path.length()] == '/')) { return true; } } From 3a2fd5350b1e648df687cd5120f9a6987322df5a Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Mon, 26 Feb 2024 16:48:06 +0100 Subject: [PATCH 430/442] [XrdCl] Stop Poller before TaskManager In 37b75132821e5ac9e9e5644590b7ef1b958850f3, stopping the poller was (possibly inadvertently) moved to happen after stopping the task manager. However, if the task manager is stopped first, an event may become ready for read/write after the task manager has already been destroyed, which may cause a socket callback to try to reinitialize the poller by calling Poller->Init() while Poller->Stop() is running on another thread, which may cause a crash. --- src/XrdCl/XrdClPostMaster.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/XrdCl/XrdClPostMaster.cc b/src/XrdCl/XrdClPostMaster.cc index 0389ca27062..f75dc911e2d 100644 --- a/src/XrdCl/XrdClPostMaster.cc +++ b/src/XrdCl/XrdClPostMaster.cc @@ -192,10 +192,10 @@ namespace XrdCl if( !pImpl->pJobManager->Stop() ) return false; - if( !pImpl->pTaskManager->Stop() ) - return false; if( !pImpl->pPoller->Stop() ) return false; + if( !pImpl->pTaskManager->Stop() ) + return false; pImpl->pRunning = false; return true; } From 2fe6b8669de561e90e8fefb8518fabc2911160bd Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 28 Feb 2024 15:27:14 +0100 Subject: [PATCH 431/442] [CMake] Set coverage flags also for C language --- test.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/test.cmake b/test.cmake index 936b2cd6f6c..ef074d774c4 100644 --- a/test.cmake +++ b/test.cmake @@ -142,6 +142,7 @@ set(CMAKE_ARGS $ENV{CMAKE_ARGS} ${CMAKE_ARGS}) if(COVERAGE) find_program(CTEST_COVERAGE_COMMAND NAMES gcov) + list(PREPEND CMAKE_ARGS "-DCMAKE_C_FLAGS=--coverage -fprofile-update=atomic") list(PREPEND CMAKE_ARGS "-DCMAKE_CXX_FLAGS=--coverage -fprofile-update=atomic") endif() From 0ecf63e006f965a94a607362978e56f7d0a71ee7 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 1 Mar 2024 15:55:47 +0100 Subject: [PATCH 432/442] [CMake] Set --gcov-executable to ${CTEST_COVERAGE_COMMAND} when calling gcovr Important to ensure that, say, a build with gcc-14/g++-14 uses the matching gcov-14 for collecting coverage information, otherwise it fails. --- test.cmake | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test.cmake b/test.cmake index ef074d774c4..f86d52b10dc 100644 --- a/test.cmake +++ b/test.cmake @@ -235,8 +235,9 @@ if(DEFINED CTEST_COVERAGE_COMMAND) find_program(GCOVR NAMES gcovr) if(EXISTS ${GCOVR}) execute_process(COMMAND - ${GCOVR} -r ${CTEST_SOURCE_DIRECTORY}/src ${CTEST_BINARY_DIRECTORY} - --html-details ${CTEST_BINARY_DIRECTORY}/html/ ERROR_VARIABLE ERROR) + ${GCOVR} --gcov-executable ${CTEST_COVERAGE_COMMAND} + -r ${CTEST_SOURCE_DIRECTORY} ${CTEST_BINARY_DIRECTORY} + --html-details ${CTEST_BINARY_DIRECTORY}/coverage/ ERROR_VARIABLE ERROR) if(ERROR) message(SEND_ERROR "Failed to generate coverage report") endif() From 448b85a2fd9d1c551b264239338debd0c1bb0635 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 28 Feb 2024 16:24:15 +0100 Subject: [PATCH 433/442] [Tests] Cleanup left over server directories after running tests --- tests/XRootD/CMakeLists.txt | 2 +- tests/cluster/setup.sh | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/XRootD/CMakeLists.txt b/tests/XRootD/CMakeLists.txt index 84ab85b28af..7ac633ba807 100644 --- a/tests/XRootD/CMakeLists.txt +++ b/tests/XRootD/CMakeLists.txt @@ -24,7 +24,7 @@ add_test(NAME XRootD::start set_tests_properties(XRootD::start PROPERTIES FIXTURES_SETUP XRootD) add_test(NAME XRootD::stop - COMMAND sh -c "rm -rf data && kill -s TERM $(cat standalone/xrootd.pid)") + COMMAND sh -c "kill -s TERM $(cat standalone/xrootd.pid); rm -rf data standalone") set_tests_properties(XRootD::stop PROPERTIES FIXTURES_CLEANUP XRootD) add_test(NAME XRootD::smoke-test diff --git a/tests/cluster/setup.sh b/tests/cluster/setup.sh index 37c4d57fc73..7e5abdf7094 100755 --- a/tests/cluster/setup.sh +++ b/tests/cluster/setup.sh @@ -161,6 +161,7 @@ stop() { for i in "${servernames[@]}"; do kill -s TERM $(cat ${i}/cmsd.pid) kill -s TERM $(cat ${i}/xrootd.pid) + [[ -d "${i}" ]] && rm -rf "${i}" done } From cf3046776d6785177e3b517a1335deeb1cf5ead9 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 6 Mar 2024 16:32:34 +0100 Subject: [PATCH 434/442] [Python] Check list of files in prepare to ensure they are strings Issue: #2011, https://its.cern.ch/jira/browse/EOS-6078 --- bindings/python/src/PyXRootDFileSystem.cc | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/bindings/python/src/PyXRootDFileSystem.cc b/bindings/python/src/PyXRootDFileSystem.cc index ef85995d5bb..4eb1c8a831f 100644 --- a/bindings/python/src/PyXRootDFileSystem.cc +++ b/bindings/python/src/PyXRootDFileSystem.cc @@ -625,15 +625,16 @@ namespace PyXRootD } std::vector files; - const char *file; - PyObject *pyfile; - - // Convert python list to stl vector - for ( int i = 0; i < PyList_Size( pyfiles ); ++i ) { - pyfile = PyList_GetItem( pyfiles, i ); - if ( !pyfile ) return NULL; - file = PyUnicode_AsUTF8( pyfile ); - files.push_back( std::string( file ) ); + for (int i = 0; i < PyList_Size(pyfiles); ++i) { + PyObject *item = PyList_GetItem(pyfiles, i); + + if (!PyUnicode_Check(item)) { + PyErr_SetString(PyExc_TypeError, + "files parameter must be a list of strings"); + return NULL; + } + + files.emplace_back(PyUnicode_AsUTF8(item)); } XrdCl::PrepareFlags::Flags flags; From 0e694422515935d558b27ed2b92c6f585f5db06c Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 6 Mar 2024 17:18:26 +0100 Subject: [PATCH 435/442] [Python] Use int for 'force' in File::Stat Closes: #2208 --- bindings/python/src/PyXRootDFile.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/python/src/PyXRootDFile.cc b/bindings/python/src/PyXRootDFile.cc index c366e7b1c30..09ebbea0d39 100644 --- a/bindings/python/src/PyXRootDFile.cc +++ b/bindings/python/src/PyXRootDFile.cc @@ -106,7 +106,7 @@ namespace PyXRootD PyObject* File::Stat( File *self, PyObject *args, PyObject *kwds ) { static const char *kwlist[] = { "force", "timeout", "callback", NULL }; - bool force = false; + int force = 0; uint16_t timeout = 0; PyObject *callback = NULL, *pyresponse = NULL, *pystatus = NULL; XrdCl::XRootDStatus status; From 9a5c3c8d46d14d47213676bb70e2c0eca2f4f693 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 6 Mar 2024 18:20:08 +0100 Subject: [PATCH 436/442] [Python] Fix iteration over a file with Python3 --- bindings/python/libs/client/file.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bindings/python/libs/client/file.py b/bindings/python/libs/client/file.py index bff9b429e7f..fe44df570a0 100644 --- a/bindings/python/libs/client/file.py +++ b/bindings/python/libs/client/file.py @@ -44,10 +44,11 @@ def __iter__(self): return self def __next__(self): - return self.__file.next() + return self.__file.__next__() # Python 2 compatibility - next = __next__ + def next(self): + return self.__file.next() def open(self, url, flags=0, mode=0, timeout=0, callback=None): """Open the file pointed to by the given URL. From f63602f3b7eaf138208d50939f86b04ad617c106 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Wed, 6 Mar 2024 11:48:39 +0100 Subject: [PATCH 437/442] [Tests] Optimize cluster configuration to speedup tests On my machine, before this commit: Total Test time (real) = 299.07 sec and after this commit: Total Test time (real) = 144.01 sec --- tests/cluster/CMakeLists.txt | 33 +++++++++---------- tests/cluster/common.cfg | 25 ++++++++++++++ tests/cluster/configs/xrootd_man1.cfg | 25 -------------- tests/cluster/configs/xrootd_man2.cfg | 25 -------------- tests/cluster/configs/xrootd_metaman.cfg | 24 -------------- tests/cluster/configs/xrootd_srv1.cfg | 26 --------------- tests/cluster/configs/xrootd_srv2.cfg | 26 --------------- tests/cluster/configs/xrootd_srv3.cfg | 26 --------------- tests/cluster/configs/xrootd_srv4.cfg | 26 --------------- tests/cluster/man1.cfg | 10 ++++++ tests/cluster/man2.cfg | 10 ++++++ tests/cluster/metaman.cfg | 9 +++++ tests/cluster/setup.sh | 16 +++++---- tests/cluster/srv1.cfg | 7 ++++ tests/cluster/srv2.cfg | 7 ++++ tests/cluster/srv3.cfg | 7 ++++ tests/cluster/srv4.cfg | 7 ++++ .../{smoketest-clustered.sh => test.sh} | 0 18 files changed, 107 insertions(+), 202 deletions(-) create mode 100644 tests/cluster/common.cfg delete mode 100644 tests/cluster/configs/xrootd_man1.cfg delete mode 100644 tests/cluster/configs/xrootd_man2.cfg delete mode 100644 tests/cluster/configs/xrootd_metaman.cfg delete mode 100644 tests/cluster/configs/xrootd_srv1.cfg delete mode 100644 tests/cluster/configs/xrootd_srv2.cfg delete mode 100644 tests/cluster/configs/xrootd_srv3.cfg delete mode 100644 tests/cluster/configs/xrootd_srv4.cfg create mode 100644 tests/cluster/man1.cfg create mode 100644 tests/cluster/man2.cfg create mode 100644 tests/cluster/metaman.cfg create mode 100644 tests/cluster/srv1.cfg create mode 100644 tests/cluster/srv2.cfg create mode 100644 tests/cluster/srv3.cfg create mode 100644 tests/cluster/srv4.cfg rename tests/cluster/{smoketest-clustered.sh => test.sh} (100%) diff --git a/tests/cluster/CMakeLists.txt b/tests/cluster/CMakeLists.txt index aaa5fe3a4ed..2f59c9fb55e 100644 --- a/tests/cluster/CMakeLists.txt +++ b/tests/cluster/CMakeLists.txt @@ -9,7 +9,6 @@ if (UID EQUAL 0) return() endif() -# find executables list(APPEND XRDENV "XRDCP=$") list(APPEND XRDENV "XRDFS=$") list(APPEND XRDENV "CRC32C=$") @@ -17,24 +16,24 @@ list(APPEND XRDENV "ADLER32=$") list(APPEND XRDENV "XROOTD=$") list(APPEND XRDENV "CMSD=$") -set(SRVNAMES "metaman" "man1" "man2" "srv1" "srv2" "srv3" "srv4") - -foreach(i ${SRVNAMES}) - configure_file("configs/xrootd_${i}.cfg" "configs/xrootd_${i}.cfg" @ONLY) +foreach(config common metaman man1 man2 srv1 srv2 srv3 srv4) + configure_file(${config}.cfg ${config}.cfg @ONLY) endforeach() +file(COPY mvdata DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) +add_test(NAME XRootD::cluster::start + COMMAND sh -c "cp -r ${CMAKE_CURRENT_SOURCE_DIR}/mvdata ${CMAKE_CURRENT_BINARY_DIR} \ + && ${CMAKE_CURRENT_SOURCE_DIR}/setup.sh start") +set_tests_properties(XRootD::cluster::start + PROPERTIES ENVIRONMENT "${XRDENV}" FIXTURES_SETUP XRootD_Cluster) -# Start the smoke test for the cluster -add_test(NAME XRootD::start::cluster - COMMAND sh -c "cp -r ${CMAKE_CURRENT_SOURCE_DIR}/mvdata ${CMAKE_CURRENT_BINARY_DIR} && \ - ${CMAKE_CURRENT_SOURCE_DIR}/setup.sh start" ) -set_tests_properties(XRootD::start::cluster PROPERTIES ENVIRONMENT "${XRDENV}" FIXTURES_SETUP XRootD_Cluster) - -add_test(NAME XRootD::smoke-test-cluster - COMMAND sh -c "${CMAKE_CURRENT_SOURCE_DIR}/smoketest-clustered.sh" ) -set_tests_properties(XRootD::smoke-test-cluster PROPERTIES ENVIRONMENT "${XRDENV}" FIXTURES_REQUIRED XRootD_Cluster) +add_test(NAME XRootD::cluster::test + COMMAND sh -c "${CMAKE_CURRENT_SOURCE_DIR}/test.sh") +set_tests_properties(XRootD::cluster::test + PROPERTIES ENVIRONMENT "${XRDENV}" FIXTURES_REQUIRED XRootD_Cluster) -add_test(NAME XRootD::stop::cluster - COMMAND sh -c "${CMAKE_CURRENT_SOURCE_DIR}/setup.sh stop" ) -set_tests_properties(XRootD::stop::cluster PROPERTIES ENVIRONMENT "${XRDENV}" FIXTURES_CLEANUP XRootD_Cluster) +add_test(NAME XRootD::cluster::stop + COMMAND sh -c "${CMAKE_CURRENT_SOURCE_DIR}/setup.sh stop") +set_tests_properties(XRootD::cluster::stop + PROPERTIES ENVIRONMENT "${XRDENV}" FIXTURES_CLEANUP XRootD_Cluster) diff --git a/tests/cluster/common.cfg b/tests/cluster/common.cfg new file mode 100644 index 00000000000..47d7ad281de --- /dev/null +++ b/tests/cluster/common.cfg @@ -0,0 +1,25 @@ +all.sitename $name + +set pwd = ${PWD} +set lib = @CMAKE_BINARY_DIR@/src + +all.export / +oss.localroot $pwd/data/$name +all.adminpath $pwd +all.pidpath $pwd + +xrd.maxfd strict 8k +xrd.network cache 5m nodnr norpipa + +cms.delay startup 2 lookup 1 qdl 2 suspend 1 +cms.space linger 0 recalc 15 min 2% 1g 5% 2g + +ofs.chkpnt enable +ofs.tpc streams 8 pgm @CMAKE_BINARY_DIR@/src/XrdCl/xrdcp --server +ofs.ckslib zcrc32 $lib/libXrdCksCalczcrc32.so +xrootd.chksum zcrc32 chkcgi adler32 crc32c + +all.trace all +tpc.trace all +xrd.trace all +xrootd.trace all diff --git a/tests/cluster/configs/xrootd_man1.cfg b/tests/cluster/configs/xrootd_man1.cfg deleted file mode 100644 index a4531d26005..00000000000 --- a/tests/cluster/configs/xrootd_man1.cfg +++ /dev/null @@ -1,25 +0,0 @@ -# This minimal configuration file starts a standalone server -# that exports the data directory as / without authentication. -cms.delay startup 10 -cms.space linger 0 recalc 15 min 2% 1g 5% 2g - -xrd.port 10941 if exec xrootd -xrd.port 20941 if exec cmsd - -all.export / - -all.role manager -all.manager meta localhost:20940 -all.manager localhost:20941 - -oss.localroot @CMAKE_CURRENT_BINARY_DIR@/data/man1 -all.adminpath @CMAKE_CURRENT_BINARY_DIR@ -all.pidpath @CMAKE_CURRENT_BINARY_DIR@ - -all.sitename XRootDman1 -ofs.ckslib zcrc32 @CMAKE_BINARY_DIR@/src/libXrdCksCalczcrc32.so -xrootd.chksum zcrc32 chkcgi adler32 crc32c - -xrd.trace all - -xrd.maxfd strict 64k \ No newline at end of file diff --git a/tests/cluster/configs/xrootd_man2.cfg b/tests/cluster/configs/xrootd_man2.cfg deleted file mode 100644 index 042190d99b9..00000000000 --- a/tests/cluster/configs/xrootd_man2.cfg +++ /dev/null @@ -1,25 +0,0 @@ -# This minimal configuration file starts a standalone server -# that exports the data directory as / without authentication. -cms.delay startup 10 -cms.space linger 0 recalc 15 min 2% 1g 5% 2g - -xrd.port 10942 if exec xrootd -xrd.port 20942 if exec cmsd - -all.export / - -all.role manager -all.manager meta localhost:20940 -all.manager localhost:20942 - -oss.localroot @CMAKE_CURRENT_BINARY_DIR@/data/man2 -all.adminpath @CMAKE_CURRENT_BINARY_DIR@ -all.pidpath @CMAKE_CURRENT_BINARY_DIR@ - -all.sitename XRootDman2 -ofs.ckslib zcrc32 @CMAKE_BINARY_DIR@/src/libXrdCksCalczcrc32.so -xrootd.chksum zcrc32 chkcgi adler32 crc32c - -xrd.trace all - -xrd.maxfd strict 64k \ No newline at end of file diff --git a/tests/cluster/configs/xrootd_metaman.cfg b/tests/cluster/configs/xrootd_metaman.cfg deleted file mode 100644 index 24f5248d152..00000000000 --- a/tests/cluster/configs/xrootd_metaman.cfg +++ /dev/null @@ -1,24 +0,0 @@ -# This minimal configuration file starts a standalone server -# that exports the data directory as / without authentication. -cms.delay startup 10 -cms.space linger 0 recalc 15 min 2% 1g 5% 2g - -xrd.port 10940 if exec xrootd -xrd.port 20940 if exec cmsd - -all.export / - -all.role meta manager -all.manager meta localhost:20940 - -oss.localroot @CMAKE_CURRENT_BINARY_DIR@/data/metaman -all.adminpath @CMAKE_CURRENT_BINARY_DIR@ -all.pidpath @CMAKE_CURRENT_BINARY_DIR@ - -all.sitename XRootDmetaman -ofs.ckslib zcrc32 @CMAKE_BINARY_DIR@/src/libXrdCksCalczcrc32.so -xrootd.chksum zcrc32 chkcgi adler32 crc32c - -xrd.trace all - -xrd.maxfd strict 64k \ No newline at end of file diff --git a/tests/cluster/configs/xrootd_srv1.cfg b/tests/cluster/configs/xrootd_srv1.cfg deleted file mode 100644 index f2ad39b45ac..00000000000 --- a/tests/cluster/configs/xrootd_srv1.cfg +++ /dev/null @@ -1,26 +0,0 @@ -# This minimal configuration file starts a standalone server -# that exports the data directory as / without authentication. -cms.delay startup 10 -cms.space linger 0 recalc 15 min 2% 1g 5% 2g - -xrd.port 10943 - -all.export / - -all.role server -all.manager localhost:20941 - -oss.localroot @CMAKE_CURRENT_BINARY_DIR@/data/srv1 -all.adminpath @CMAKE_CURRENT_BINARY_DIR@ -all.pidpath @CMAKE_CURRENT_BINARY_DIR@ - -all.sitename XRootDsrv1 -ofs.ckslib zcrc32 @CMAKE_BINARY_DIR@/src/libXrdCksCalczcrc32.so -xrootd.chksum zcrc32 chkcgi adler32 crc32c - -ofs.tpc ttl 60 60 xfr 9 pgm @CMAKE_BINARY_DIR@/src/XrdCl/xrdcp --server -ofs.chkpnt enable - -xrd.trace all - -xrd.maxfd strict 64k \ No newline at end of file diff --git a/tests/cluster/configs/xrootd_srv2.cfg b/tests/cluster/configs/xrootd_srv2.cfg deleted file mode 100644 index 0e78cf36f37..00000000000 --- a/tests/cluster/configs/xrootd_srv2.cfg +++ /dev/null @@ -1,26 +0,0 @@ -# This minimal configuration file starts a standalone server -# that exports the data directory as / without authentication. -cms.delay startup 10 -cms.space linger 0 recalc 15 min 2% 1g 5% 2g - -xrd.port 10944 - -all.export / - -all.role server -all.manager localhost:20941 - -oss.localroot @CMAKE_CURRENT_BINARY_DIR@/data/srv2 -all.adminpath @CMAKE_CURRENT_BINARY_DIR@ -all.pidpath @CMAKE_CURRENT_BINARY_DIR@ - -all.sitename XRootDsrv2 -ofs.ckslib zcrc32 @CMAKE_BINARY_DIR@/src/libXrdCksCalczcrc32.so -xrootd.chksum zcrc32 chkcgi adler32 crc32c - -ofs.tpc ttl 60 60 xfr 9 pgm @CMAKE_BINARY_DIR@/src/XrdCl/xrdcp --server -ofs.chkpnt enable - -xrd.trace all - -xrd.maxfd strict 64k \ No newline at end of file diff --git a/tests/cluster/configs/xrootd_srv3.cfg b/tests/cluster/configs/xrootd_srv3.cfg deleted file mode 100644 index 7adfa2c9db3..00000000000 --- a/tests/cluster/configs/xrootd_srv3.cfg +++ /dev/null @@ -1,26 +0,0 @@ -# This minimal configuration file starts a standalone server -# that exports the data directory as / without authentication. -cms.delay startup 10 -cms.space linger 0 recalc 15 min 2% 1g 5% 2g - -xrd.port 10945 - -all.export / - -all.role server -all.manager localhost:20942 - -oss.localroot @CMAKE_CURRENT_BINARY_DIR@/data/srv3 -all.adminpath @CMAKE_CURRENT_BINARY_DIR@ -all.pidpath @CMAKE_CURRENT_BINARY_DIR@ - -all.sitename XRootDsrv3 -ofs.ckslib zcrc32 @CMAKE_BINARY_DIR@/src/libXrdCksCalczcrc32.so -xrootd.chksum zcrc32 chkcgi adler32 crc32c - -ofs.tpc ttl 60 60 xfr 9 pgm @CMAKE_BINARY_DIR@/src/XrdCl/xrdcp --server -ofs.chkpnt enable - -xrd.trace all - -xrd.maxfd strict 64k \ No newline at end of file diff --git a/tests/cluster/configs/xrootd_srv4.cfg b/tests/cluster/configs/xrootd_srv4.cfg deleted file mode 100644 index 6bb692f8729..00000000000 --- a/tests/cluster/configs/xrootd_srv4.cfg +++ /dev/null @@ -1,26 +0,0 @@ -# This minimal configuration file starts a standalone server -# that exports the data directory as / without authentication. -cms.delay startup 10 -cms.space linger 0 recalc 15 min 2% 1g 5% 2g - -xrd.port 10946 - -all.export / - -all.role server -all.manager localhost:20942 - -oss.localroot @CMAKE_CURRENT_BINARY_DIR@/data/srv4 -all.adminpath @CMAKE_CURRENT_BINARY_DIR@ -all.pidpath @CMAKE_CURRENT_BINARY_DIR@ - -all.sitename XRootDsrv4 -ofs.ckslib zcrc32 @CMAKE_BINARY_DIR@/src/libXrdCksCalczcrc32.so -xrootd.chksum zcrc32 chkcgi adler32 crc32c - -ofs.tpc ttl 60 60 xfr 9 pgm @CMAKE_BINARY_DIR@/src/XrdCl/xrdcp --server -ofs.chkpnt enable - -xrd.trace all - -xrd.maxfd strict 64k \ No newline at end of file diff --git a/tests/cluster/man1.cfg b/tests/cluster/man1.cfg new file mode 100644 index 00000000000..0ec7042f2ce --- /dev/null +++ b/tests/cluster/man1.cfg @@ -0,0 +1,10 @@ +set name = man1 + +all.role manager +all.manager meta localhost:20940 +all.manager localhost:20941 + +xrd.port 10941 if exec xrootd +xrd.port 20941 if exec cmsd + +continue @CMAKE_CURRENT_BINARY_DIR@/common.cfg diff --git a/tests/cluster/man2.cfg b/tests/cluster/man2.cfg new file mode 100644 index 00000000000..077129bdfde --- /dev/null +++ b/tests/cluster/man2.cfg @@ -0,0 +1,10 @@ +set name = man2 + +all.role manager +all.manager meta localhost:20940 +all.manager localhost:20942 + +xrd.port 10942 if exec xrootd +xrd.port 20942 if exec cmsd + +continue @CMAKE_CURRENT_BINARY_DIR@/common.cfg diff --git a/tests/cluster/metaman.cfg b/tests/cluster/metaman.cfg new file mode 100644 index 00000000000..842d20bc8f2 --- /dev/null +++ b/tests/cluster/metaman.cfg @@ -0,0 +1,9 @@ +set name = metaman + +all.role meta manager +all.manager meta localhost:20940 + +xrd.port 10940 if exec xrootd +xrd.port 20940 if exec cmsd + +continue @CMAKE_CURRENT_BINARY_DIR@/common.cfg diff --git a/tests/cluster/setup.sh b/tests/cluster/setup.sh index 7e5abdf7094..3c5624359f9 100755 --- a/tests/cluster/setup.sh +++ b/tests/cluster/setup.sh @@ -148,21 +148,23 @@ start(){ set -x # start for each component for i in "${servernames[@]}"; do - ${XROOTD} -b -k fifo -n ${i} -l xrootd.log -s xrootd.pid -c configs/xrootd_${i}.cfg + ${XROOTD} -b -k fifo -n ${i} -l xrootd.log -s xrootd.pid -c ${i}.cfg done # start cmsd in the redirectors for i in "${servernames[@]}"; do - ${CMSD} -b -k fifo -n ${i} -l cmsd.log -s cmsd.pid -c configs/xrootd_${i}.cfg + ${CMSD} -b -k fifo -n ${i} -l cmsd.log -s cmsd.pid -c ${i}.cfg done } stop() { - for i in "${servernames[@]}"; do - kill -s TERM $(cat ${i}/cmsd.pid) - kill -s TERM $(cat ${i}/xrootd.pid) - [[ -d "${i}" ]] && rm -rf "${i}" - done + for i in "${servernames[@]}"; do + if [[ -d "${i}" ]]; then + kill -s TERM $(cat ${i}/cmsd.pid) + kill -s TERM $(cat ${i}/xrootd.pid) + rm -rf "${i}" + fi + done } insertFileInfo() { diff --git a/tests/cluster/srv1.cfg b/tests/cluster/srv1.cfg new file mode 100644 index 00000000000..b99d8634336 --- /dev/null +++ b/tests/cluster/srv1.cfg @@ -0,0 +1,7 @@ +set name = srv1 + +all.role server +all.manager localhost:20941 +xrd.port 10943 + +continue @CMAKE_CURRENT_BINARY_DIR@/common.cfg diff --git a/tests/cluster/srv2.cfg b/tests/cluster/srv2.cfg new file mode 100644 index 00000000000..1f07efdeaf6 --- /dev/null +++ b/tests/cluster/srv2.cfg @@ -0,0 +1,7 @@ +set name = srv2 + +all.role server +all.manager localhost:20941 +xrd.port 10944 + +continue @CMAKE_CURRENT_BINARY_DIR@/common.cfg diff --git a/tests/cluster/srv3.cfg b/tests/cluster/srv3.cfg new file mode 100644 index 00000000000..b5eae6bfc0f --- /dev/null +++ b/tests/cluster/srv3.cfg @@ -0,0 +1,7 @@ +set name = srv3 + +all.role server +all.manager localhost:20942 +xrd.port 10945 + +continue @CMAKE_CURRENT_BINARY_DIR@/common.cfg diff --git a/tests/cluster/srv4.cfg b/tests/cluster/srv4.cfg new file mode 100644 index 00000000000..ae7f6bc38f6 --- /dev/null +++ b/tests/cluster/srv4.cfg @@ -0,0 +1,7 @@ +set name = srv4 + +all.role server +all.manager localhost:20942 +xrd.port 10946 + +continue @CMAKE_CURRENT_BINARY_DIR@/common.cfg diff --git a/tests/cluster/smoketest-clustered.sh b/tests/cluster/test.sh similarity index 100% rename from tests/cluster/smoketest-clustered.sh rename to tests/cluster/test.sh From 32a93381113a2167b7b790fa5b37afde399f944c Mon Sep 17 00:00:00 2001 From: Andrew Hanushevsky Date: Thu, 7 Mar 2024 21:40:59 -0800 Subject: [PATCH 438/442] [Utils] Correct comparison that wrongly missed reaping certain directives. --- src/XrdOuc/XrdOucGatherConf.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XrdOuc/XrdOucGatherConf.cc b/src/XrdOuc/XrdOucGatherConf.cc index 29179744a4c..cef0c6c2c53 100644 --- a/src/XrdOuc/XrdOucGatherConf.cc +++ b/src/XrdOuc/XrdOucGatherConf.cc @@ -143,7 +143,7 @@ int XrdOucGatherConf::Gather(const char *cfname, Level lvl, const char *parms) while((var = Config.GetMyFirstWord())) {tP = Match; while(tP && ((tP->val && strncmp(var, tP->text, tP->val)) || - strcmp( var, tP->text))) tP = tP->next; + (!tP->val && strcmp( var, tP->text)))) tP = tP->next; if (tP) {if (addKey) From 57ea2a43fae2e23fd1a1f65f6ab0a67348dcd6be Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 8 Mar 2024 17:29:32 +0100 Subject: [PATCH 439/442] [RPM] Update spec file for XRootD 5.6.9 --- xrootd.spec | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/xrootd.spec b/xrootd.spec index 4645f924568..415e53a1956 100644 --- a/xrootd.spec +++ b/xrootd.spec @@ -25,7 +25,7 @@ License: LGPL-3.0-or-later AND BSD-2-Clause AND BSD-3-Clause AND curl AND MIT AN URL: https://xrootd.slac.stanford.edu %if !%{with git} -Version: 5.6.8 +Version: 5.6.9 Source0: %{url}/download/v%{version}/%{name}-%{version}.tar.gz %else %define git_version %(tar xzf %{_sourcedir}/%{name}.tar.gz -O xrootd/VERSION) @@ -951,6 +951,9 @@ fi %changelog +* Fri Mar 08 2024 Guilherme Amadio - 1:5.6.9-1 +- XRootD 5.6.9 + * Fri Feb 23 2024 Guilherme Amadio - 1:5.6.8-1 - XRootD 5.6.8 From fcc2e2480fdb9e31602b5604345d5d77beefcea2 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Thu, 22 Feb 2024 14:38:24 +0100 Subject: [PATCH 440/442] Simplify script to generate release tarballs --- gen-tarball.sh | 42 ++++++++---------------------------------- 1 file changed, 8 insertions(+), 34 deletions(-) diff --git a/gen-tarball.sh b/gen-tarball.sh index 588c372f7f2..cb05ec7c2ac 100755 --- a/gen-tarball.sh +++ b/gen-tarball.sh @@ -1,37 +1,11 @@ #!/bin/bash -# Generate a source tarball including submodules -if [ -z "${1}" ] ; then - echo No tag or branch given - exit 1 -fi -ver=${1} -# Remove initial v from tag name for use in filenames -if [ ${ver:0:1} = 'v' ] ; then - fver=${ver:1} -else - fver=${ver} -fi -if [ -r xrootd-${fver}.tar.gz ] ; then - echo xrootd-${fver}.tar.gz already exists - exit 1 -fi -curdir=$(pwd) -tdir=$(mktemp -d) -cd ${tdir} -git clone https://github.com/xrootd/xrootd.git -cd xrootd -git checkout ${ver} -if [ $? -ne 0 ] ; then - echo No such tag or branch: ${ver} - cd ${curdir} - rm -rf ${tdir} - exit 1 -fi -git archive --prefix xrootd-${fver}/ ${ver} -o ${tdir}/xrootd-${fver}.tar -cd ${tdir} -gzip xrootd-${fver}.tar -mv xrootd-${fver}.tar.gz ${curdir} -cd ${curdir} -rm -rf ${tdir} +set -e + +TAG=$(printf "%s" "$(git describe "${1:-HEAD}")") +NAME="xrootd-${TAG#v}" + +set -x + +git archive -9 --prefix="${NAME}/" -o "${NAME}.tar.gz" ${TAG} From 619e93fe896f23ac04fd424ac5223c2443fec296 Mon Sep 17 00:00:00 2001 From: Guilherme Amadio Date: Fri, 8 Mar 2024 17:31:28 +0100 Subject: [PATCH 441/442] XRootD 5.6.9 --- docs/ReleaseNotes.txt | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/ReleaseNotes.txt b/docs/ReleaseNotes.txt index 41d46cf1926..920407f317c 100644 --- a/docs/ReleaseNotes.txt +++ b/docs/ReleaseNotes.txt @@ -5,6 +5,25 @@ XRootD Release Notes ============= +------------- +Version 5.6.9 +------------- + ++ **Minor bug fixes** + **[Python]** Check list of files in prepare to ensure they are strings + **[Python]** Fix iteration over a file with Python3 + **[Python]** Use int for 'force' in File::Stat (#2208) + **[Utils]** Correct comparison that wrongly missed reaping certain directives + **[XrdCl]** Fix logic error when upgrading connections to TLS + **[XrdCl]** Stop Poller before TaskManager (fixes rare crashes at shutdown) + **[XrdHttpTPC]** Fix 500 server response code if X-Number-Of-Streams > 100 (issue #2186) + **[XrdSciTokens]** Add stat permissions to create, modify and write operations (issue #2185) + **[XrdSciTokens]** Allow creation of parent directories if necessary (#2184) + **[XrdSciTokens]** Fix bug when scope includes basepath or `/` (issue #2132) + ++ **Miscellaneous** + **[Tests]** Optimize cluster configuration to speedup tests + ------------- Version 5.6.8 ------------- From 03840a838592031a6b25d70cbc8028f8381a3800 Mon Sep 17 00:00:00 2001 From: Jyothish Thomas Date: Wed, 10 Apr 2024 10:19:51 +0000 Subject: [PATCH 442/442] [XrdCms] add a tunable weighed random load balancing algorithm for SelbyLoad based selection. This can be enabled by adding the configuration option: cms.sched randlb 1. --- src/XrdCms/XrdCmsCluster.cc | 46 ++++++++++++++++++++++++++++++++----- src/XrdCms/XrdCmsConfig.cc | 4 +++- src/XrdCms/XrdCmsConfig.hh | 1 + 3 files changed, 44 insertions(+), 7 deletions(-) diff --git a/src/XrdCms/XrdCmsCluster.cc b/src/XrdCms/XrdCmsCluster.cc index 011bcde2813..5744183f01c 100644 --- a/src/XrdCms/XrdCmsCluster.cc +++ b/src/XrdCms/XrdCmsCluster.cc @@ -35,6 +35,8 @@ #include #include #include +#include +#include #include "XProtocol/YProtocol.hh" @@ -1786,10 +1788,15 @@ XrdCmsNode *XrdCmsCluster::SelbyLoad(SMask_t mask, XrdCmsSelector &selR) XrdCmsNode *np, *sp = 0; bool Multi = false, reqSS = (selR.needSpace & XrdCmsNode::allowsSS) != 0; -// Scan for a node (preset possible, suspended, overloaded, full, and dead) -// - selR.Reset(); SelTcnt++; - for (int i = 0; i <= STHi; i++) + // Scan for a node (preset possible, suspended, overloaded, full, and dead) + // + selR.Reset(); SelTcnt++; + int selCap = 1; + int randomSel=1; + //default 0 to skip the node in random selection if the below checks fail + int *weighed = new int[STHi] {0}; + + for (int i = 0; i <= STHi; i++) if ((np = NodeTab[i]) && (np->NodeMask & mask)) {if (!(selR.needNet & np->hasNet)) {selR.xNoNet= true; continue;} selR.nPick++; @@ -1800,7 +1807,17 @@ XrdCmsNode *XrdCmsCluster::SelbyLoad(SMask_t mask, XrdCmsSelector &selR) || (reqSS && np->isNoStage))) {selR.xFull = true; continue;} if (!sp) sp = np; - else{if (selR.needSpace) + else{ + if (Config.P_randlb==1){ + //add 1 to the inverse load, this is to allow some selection in case reported loads hit 100 + weighed[i] = selCap + static_cast(101 - np->myLoad + + std::pow(101 - np->myLoad, + std::log(100)-std::log(std::min(std::max(Config.P_fuzz,1),100)))/2); + selCap += static_cast(101 - np->myLoad + + std::pow(101 - np->myLoad, + std::log(100)-std::log(std::min(std::max(Config.P_fuzz,1),100)))/2); + } + else{if (selR.needSpace) {if (abs(sp->myMass - np->myMass) <= Config.P_fuzz) {if (sp->RefW > (np->RefW+Config.DiskLinger)) sp=np;} else if (sp->myMass > np->myMass) sp=np; @@ -1816,12 +1833,29 @@ XrdCmsNode *XrdCmsCluster::SelbyLoad(SMask_t mask, XrdCmsSelector &selR) } Multi = true; } + } } - + if (Config.P_randlb==1){ + // pick a random weighed node + // + std::random_device rand_dev; + std::mt19937 generator(rand_dev()); + std::uniform_int_distribution distr(randomSel,selCap); + randomSel = distr(generator); + for(int i=0;i<=STHi;i++){ + if(randomSel<=weighed[i]){ + sp=NodeTab[i]; + break; + } + } + } + delete [] weighed; // Check for overloaded node and return result // if (!sp) return calcDelay(selR); + if (Config.P_randlb!=1){ RefCount(sp, Multi, selR.needSpace); + } return sp; } diff --git a/src/XrdCms/XrdCmsConfig.cc b/src/XrdCms/XrdCmsConfig.cc index bcf3e465d30..ca47b3ab6a5 100644 --- a/src/XrdCms/XrdCmsConfig.cc +++ b/src/XrdCms/XrdCmsConfig.cc @@ -735,6 +735,7 @@ void XrdCmsConfig::ConfigDefaults(void) P_load = 0; P_mem = 0; P_pag = 0; + P_randlb = 0; // SelbyLoad algoruthm choice AskPerf = 10; // Every 10 pings AskPing = 60; // Every 1 minute PingTick = 0; @@ -2651,7 +2652,8 @@ int XrdCmsConfig::xsched(XrdSysError *eDest, XrdOucStream &CFile) {"refreset", -1, &RefReset}, {"affinity", -2, 0}, {"affpath", -3, 0}, - {"tryhname", 1, &V_hntry} + {"tryhname", 1, &V_hntry}, + {"randlb", 1, &P_randlb} }; int numopts = sizeof(scopts)/sizeof(struct schedopts); diff --git a/src/XrdCms/XrdCmsConfig.hh b/src/XrdCms/XrdCmsConfig.hh index 8f2977029e7..8857aeff0b8 100644 --- a/src/XrdCms/XrdCmsConfig.hh +++ b/src/XrdCms/XrdCmsConfig.hh @@ -107,6 +107,7 @@ int P_io; // % I/O Capacity in load factor int P_load; // % MSC Capacity in load factor int P_mem; // % MEM Capacity in load factor int P_pag; // % PAG Capacity in load factor +int P_randlb; // enable weighed random load balancing char DoMWChk; // When true (default) perform multiple write check char DoHnTry; // When true (default) use hostnames for try redirs

    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�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�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

HLe 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

!1C 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